← All changes
|
jetpack_vendor/automattic/jetpack-sync/src/sync-queue/class-queue-storage-options.php
+293
-0
16.2-beta
→
16.3
View file →
| @@ -1,0 +1,293 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * The class responsible for storing Queue events in the `wp_options` table. | |
| 4 | + * | |
| 5 | + * Used by class Queue. | |
| 6 | + * | |
| 7 | + * @see \Automattic\Jetpack\Sync\Queue | |
| 8 | + * | |
| 9 | + * @package automattic/jetpack-sync | |
| 10 | + */ | |
| 11 | + | |
| 12 | +namespace Automattic\Jetpack\Sync\Queue; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * `wp_options` storage backend for the Queue. | |
| 16 | + */ | |
| 17 | +class Queue_Storage_Options { | |
| 18 | + /** | |
| 19 | + * What queue is this instance responsible for. | |
| 20 | + * | |
| 21 | + * @var string | |
| 22 | + */ | |
| 23 | + public $queue_id = ''; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * Class constructor. | |
| 27 | + * | |
| 28 | + * @param string $queue_id The queue name this instance will be responsible for. | |
| 29 | + * | |
| 30 | + * @throws \Exception If queue name was not provided. | |
| 31 | + */ | |
| 32 | + public function __construct( $queue_id ) { | |
| 33 | + if ( empty( $queue_id ) ) { | |
| 34 | + // TODO what should we return here or throw an exception? | |
| 35 | + throw new \Exception( 'Invalid queue_id provided' ); | |
| 36 | + } | |
| 37 | + | |
| 38 | + // TODO validate the value maybe? | |
| 39 | + $this->queue_id = $queue_id; | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Insert an item in the queue. | |
| 44 | + * | |
| 45 | + * @param string $item_id The item ID. | |
| 46 | + * @param string $item Serialized item data. | |
| 47 | + * | |
| 48 | + * @return bool If the item was added. | |
| 49 | + */ | |
| 50 | + public function insert_item( $item_id, $item ) { | |
| 51 | + global $wpdb; | |
| 52 | + | |
| 53 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 54 | + $rows_added = $wpdb->query( | |
| 55 | + $wpdb->prepare( | |
| 56 | + "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES (%s, %s,%s)", | |
| 57 | + $item_id, | |
| 58 | + $item, | |
| 59 | + 'no' | |
| 60 | + ) | |
| 61 | + ); | |
| 62 | + | |
| 63 | + return ( 0 !== $rows_added ); | |
| 64 | + } | |
| 65 | + | |
| 66 | + /** | |
| 67 | + * Fetch items from the queue. | |
| 68 | + * | |
| 69 | + * @param int|null $item_count How many items to fetch from the queue. | |
| 70 | + * Null for no limit. | |
| 71 | + * @param string $order Sort direction for the items. Accepts 'ASC' or 'DESC'. | |
| 72 | + * Any other value will be treated as 'ASC'. | |
| 73 | + * | |
| 74 | + * @return array|object|null Array of result objects on success, or null on failure. | |
| 75 | + */ | |
| 76 | + public function fetch_items( $item_count, $order = 'ASC' ) { | |
| 77 | + global $wpdb; | |
| 78 | + | |
| 79 | + $order = 'DESC' === $order ? 'DESC' : 'ASC'; | |
| 80 | + | |
| 81 | + $sql_order = "ORDER BY option_name {$order}"; | |
| 82 | + | |
| 83 | + $sql = "SELECT option_name AS id, option_value AS value | |
| 84 | + FROM $wpdb->options | |
| 85 | + WHERE option_name LIKE %s | |
| 86 | + {$sql_order}"; | |
| 87 | + | |
| 88 | + $params = array( "jpsq_{$this->queue_id}-%" ); | |
| 89 | + | |
| 90 | + if ( $item_count ) { | |
| 91 | + $sql .= ' LIMIT %d'; | |
| 92 | + $params[] = $item_count; | |
| 93 | + } | |
| 94 | + | |
| 95 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 96 | + $items = $wpdb->get_results( | |
| 97 | + $wpdb->prepare( $sql, $params ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared | |
| 98 | + OBJECT | |
| 99 | + ); | |
| 100 | + | |
| 101 | + return $items; | |
| 102 | + } | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Fetches items with specific IDs from the Queue. | |
| 106 | + * | |
| 107 | + * @param array $items_ids Items IDs to fetch from the queue. | |
| 108 | + * | |
| 109 | + * @return \stdClass[]|null | |
| 110 | + */ | |
| 111 | + public function fetch_items_by_ids( $items_ids ) { | |
| 112 | + global $wpdb; | |
| 113 | + | |
| 114 | + // return early if $items_ids is empty or not an array. | |
| 115 | + if ( empty( $items_ids ) || ! is_array( $items_ids ) ) { | |
| 116 | + return array(); | |
| 117 | + } | |
| 118 | + | |
| 119 | + $ids_placeholders = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) ); | |
| 120 | + | |
| 121 | + $query_with_placeholders = "SELECT option_name AS id, option_value AS value | |
| 122 | + FROM $wpdb->options | |
| 123 | + WHERE option_name IN ( $ids_placeholders )"; | |
| 124 | + | |
| 125 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 126 | + $items = $wpdb->get_results( | |
| 127 | + $wpdb->prepare( | |
| 128 | + $query_with_placeholders, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared | |
| 129 | + $items_ids | |
| 130 | + ), | |
| 131 | + OBJECT | |
| 132 | + ); | |
| 133 | + | |
| 134 | + return $items; | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * Clear out the queue. | |
| 139 | + * | |
| 140 | + * @return bool|int|\mysqli_result|resource|null | |
| 141 | + */ | |
| 142 | + public function clear_queue() { | |
| 143 | + global $wpdb; | |
| 144 | + | |
| 145 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 146 | + return $wpdb->query( | |
| 147 | + $wpdb->prepare( | |
| 148 | + "DELETE FROM $wpdb->options WHERE option_name LIKE %s", | |
| 149 | + "jpsq_{$this->queue_id}-%" | |
| 150 | + ) | |
| 151 | + ); | |
| 152 | + } | |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Check how many items are in the queue. | |
| 156 | + * | |
| 157 | + * @return int | |
| 158 | + */ | |
| 159 | + public function get_item_count() { | |
| 160 | + global $wpdb; | |
| 161 | + | |
| 162 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 163 | + return (int) $wpdb->get_var( | |
| 164 | + $wpdb->prepare( | |
| 165 | + "SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s", | |
| 166 | + "jpsq_{$this->queue_id}-%" | |
| 167 | + ) | |
| 168 | + ); | |
| 169 | + } | |
| 170 | + | |
| 171 | + /** | |
| 172 | + * Return the lag amount for the queue. | |
| 173 | + * | |
| 174 | + * @param float|int|null $now A timestamp to use as starting point when calculating the lag. | |
| 175 | + * | |
| 176 | + * @return float|int The lag amount. | |
| 177 | + */ | |
| 178 | + public function get_lag( $now = null ) { | |
| 179 | + global $wpdb; | |
| 180 | + | |
| 181 | + // TODO replace with peek and a flag to fetch only the name. | |
| 182 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 183 | + $first_item_name = $wpdb->get_var( | |
| 184 | + $wpdb->prepare( | |
| 185 | + "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1", | |
| 186 | + "jpsq_{$this->queue_id}-%" | |
| 187 | + ) | |
| 188 | + ); | |
| 189 | + | |
| 190 | + if ( ! $first_item_name ) { | |
| 191 | + return 0; | |
| 192 | + } | |
| 193 | + | |
| 194 | + if ( null === $now ) { | |
| 195 | + $now = microtime( true ); | |
| 196 | + } | |
| 197 | + | |
| 198 | + // Break apart the item name to get the timestamp. | |
| 199 | + $matches = null; | |
| 200 | + if ( preg_match( '/^jpsq_' . $this->queue_id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) { | |
| 201 | + return $now - (float) $matches[1]; | |
| 202 | + } else { | |
| 203 | + return 0; | |
| 204 | + } | |
| 205 | + } | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * Add multiple items to the queue at once. | |
| 209 | + * | |
| 210 | + * @param array $items Array of items to add. | |
| 211 | + * @param string $id_prefix Prefix to use for all the items. | |
| 212 | + * | |
| 213 | + * @return bool|int|\mysqli_result|resource|null | |
| 214 | + */ | |
| 215 | + public function add_all( $items, $id_prefix ) { | |
| 216 | + global $wpdb; | |
| 217 | + | |
| 218 | + $query = "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES "; | |
| 219 | + | |
| 220 | + $rows = array(); | |
| 221 | + $count_items = count( $items ); | |
| 222 | + for ( $i = 0; $i < $count_items; ++$i ) { | |
| 223 | + // skip empty items. | |
| 224 | + if ( empty( $items[ $i ] ) ) { | |
| 225 | + continue; | |
| 226 | + } | |
| 227 | + try { | |
| 228 | + $option_name = esc_sql( $id_prefix . '-' . $i ); | |
| 229 | + $option_value = esc_sql( serialize( $items[ $i ] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize | |
| 230 | + $rows[] = "('$option_name', '$option_value', 'no')"; | |
| 231 | + } catch ( \Exception $e ) { | |
| 232 | + // Item cannot be serialized so skip. | |
| 233 | + continue; | |
| 234 | + } | |
| 235 | + } | |
| 236 | + | |
| 237 | + $rows_added = $wpdb->query( $query . implode( ',', $rows ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 238 | + | |
| 239 | + return $rows_added; | |
| 240 | + } | |
| 241 | + | |
| 242 | + /** | |
| 243 | + * Return $max_count items from the queue, including their value string length. | |
| 244 | + * | |
| 245 | + * @param int $max_count How many items to fetch from the queue. | |
| 246 | + * | |
| 247 | + * @return object[]|null | |
| 248 | + */ | |
| 249 | + public function get_items_ids_with_size( $max_count ) { | |
| 250 | + global $wpdb; | |
| 251 | + | |
| 252 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 253 | + return $wpdb->get_results( | |
| 254 | + $wpdb->prepare( | |
| 255 | + "SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d", | |
| 256 | + "jpsq_{$this->queue_id}-%", | |
| 257 | + $max_count | |
| 258 | + ), | |
| 259 | + OBJECT | |
| 260 | + ); | |
| 261 | + } | |
| 262 | + | |
| 263 | + /** | |
| 264 | + * Delete items with specific IDs from the queue. | |
| 265 | + * | |
| 266 | + * @param array $ids IDs of the items to remove from the queue. | |
| 267 | + * | |
| 268 | + * @return bool|int|\mysqli_result|resource|null | |
| 269 | + */ | |
| 270 | + public function delete_items_by_ids( $ids ) { | |
| 271 | + global $wpdb; | |
| 272 | + | |
| 273 | + if ( ! is_array( $ids ) || empty( $ids ) ) { | |
| 274 | + return false; | |
| 275 | + } | |
| 276 | + | |
| 277 | + // TODO check if it's working properly - no need to delete all options in the table if the params are not right | |
| 278 | + $ids_placeholders = implode( ', ', array_fill( 0, count( $ids ), '%s' ) ); | |
| 279 | + | |
| 280 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 281 | + return $wpdb->query( | |
| 282 | + $wpdb->prepare( | |
| 283 | + /** | |
| 284 | + * Ignoring the linting warning, as there's still no placeholder replacement for DB field name, | |
| 285 | + * in this case this is `$ids_placeholders`, as we're preparing them above and are a dynamic count. | |
| 286 | + */ | |
| 287 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 288 | + "DELETE FROM {$wpdb->options} WHERE option_name IN ( $ids_placeholders )", | |
| 289 | + $ids | |
| 290 | + ) | |
| 291 | + ); | |
| 292 | + } | |
| 293 | +} | |