PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.12.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.12.0
2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk All 53 releases
thinkrank / includes / admin / importers / class-snapshot-store.php

class-snapshot-store.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.12.0, at includes/admin/importers/class-snapshot-store.php

187 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Snapshot Store
5 *
6 * Static CRUD for chunked wp_options snapshot data.
7 * All options use autoload=false to prevent loading on every page view.
8 *
9 * @package ThinkRank\Admin\Importers
10 * @since 2.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\Admin\Importers;
16
17 if (!defined('ABSPATH')) {
18 exit;
19 }
20
21 /**
22 * Snapshot Store Class
23 *
24 * Manages chunked read/write of snapshot data in wp_options.
25 *
26 * @since 2.0.0
27 */
28 class Snapshot_Store {
29
30 /**
31 * Option key prefix for all snapshot data
32 */
33 private const OPTION_PREFIX = 'thinkrank_snapshot_';
34
35 /**
36 * Width of `wp_options.option_name`, VARCHAR(191) since WordPress 4.4 cut it
37 * to fit a utf8mb4 index.
38 *
39 * @var int
40 */
41 private const OPTION_NAME_MAX = 191;
42
43 /**
44 * Digits held back for the chunk page number in a key. Ten is far past what
45 * any real snapshot pages to; it costs ten characters of type slug.
46 *
47 * @var int
48 */
49 private const PAGE_DIGITS = 10;
50
51 /**
52 * Longest type slug whose chunk key still fits `option_name`.
53 *
54 * MySQL's default (non-strict) mode truncates an over-long option name on
55 * write instead of refusing it, and the truncated row is then invisible to
56 * the read, which looks the full key up. In-request option caching hides
57 * that until the next request, when read_chunk() starts returning null for
58 * records the manifest still counts — a snapshot that advertises data it
59 * cannot produce. Callers taking a type slug from an uploaded file must
60 * check it against this before storing anything under it.
61 *
62 * @since 2.12.0
63 *
64 * @param string $plugin Plugin slug the chunks are filed under.
65 * @return int Maximum type slug length, in characters.
66 */
67 public static function max_type_length(string $plugin): int {
68 // The key is OPTION_PREFIX . "{$plugin}_{$type}_chunk_{$page}".
69 $overhead = strlen(self::OPTION_PREFIX)
70 + strlen($plugin)
71 + strlen('__chunk_')
72 + self::PAGE_DIGITS;
73
74 return max(1, self::OPTION_NAME_MAX - $overhead);
75 }
76
77 /**
78 * Write a chunk of snapshot data
79 *
80 * @param string $plugin Plugin slug (e.g., 'yoast')
81 * @param string $type Data type (e.g., 'postmeta')
82 * @param int $page Chunk/page number
83 * @param array $records Array of normalized records
84 * @return void
85 */
86 public static function write_chunk(string $plugin, string $type, int $page, array $records): void {
87 $option_key = self::OPTION_PREFIX . "{$plugin}_{$type}_chunk_{$page}";
88 update_option($option_key, $records, false);
89 }
90
91 /**
92 * Write or update the manifest for a plugin snapshot
93 *
94 * @param string $plugin Plugin slug
95 * @param array $manifest Manifest data
96 * @return void
97 */
98 public static function write_manifest(string $plugin, array $manifest): void {
99 $option_key = self::OPTION_PREFIX . "{$plugin}_manifest";
100 update_option($option_key, $manifest, false);
101 }
102
103 /**
104 * Read a chunk of snapshot data
105 *
106 * @param string $plugin Plugin slug
107 * @param string $type Data type
108 * @param int $page Chunk/page number
109 * @return array|null Chunk data or null if not found
110 */
111 public static function read_chunk(string $plugin, string $type, int $page): ?array {
112 $option_key = self::OPTION_PREFIX . "{$plugin}_{$type}_chunk_{$page}";
113 $data = get_option($option_key, null);
114 return is_array($data) ? $data : null;
115 }
116
117 /**
118 * Get the manifest for a plugin snapshot
119 *
120 * @param string $plugin Plugin slug
121 * @return array|null Manifest data or null if not found
122 */
123 public static function get_manifest(string $plugin): ?array {
124 $option_key = self::OPTION_PREFIX . "{$plugin}_manifest";
125 $data = get_option($option_key, null);
126 return is_array($data) ? $data : null;
127 }
128
129 /**
130 * Get all available snapshots with their manifests
131 *
132 * @return array Array of manifests keyed by plugin slug
133 */
134 public static function get_available_snapshots(): array {
135 global $wpdb;
136
137 $results = $wpdb->get_results(
138 $wpdb->prepare(
139 "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s",
140 $wpdb->esc_like(self::OPTION_PREFIX) . '%_manifest'
141 ),
142 ARRAY_A
143 );
144
145 $snapshots = [];
146 foreach ($results as $row) {
147 // Extract plugin slug from option name: thinkrank_snapshot_{plugin}_manifest
148 $option_name = $row['option_name'];
149 $plugin = str_replace([self::OPTION_PREFIX, '_manifest'], '', $option_name);
150
151 $manifest = Safe_Unserializer::unserialize($row['option_value']);
152 if (is_array($manifest)) {
153 $snapshots[$plugin] = $manifest;
154 }
155 }
156
157 return $snapshots;
158 }
159
160 /**
161 * Delete all snapshot data for a plugin
162 *
163 * @param string $plugin Plugin slug
164 * @return int Number of options deleted
165 */
166 public static function delete_snapshot(string $plugin): int {
167 global $wpdb;
168
169 // Find all options for this plugin's snapshot
170 $option_names = $wpdb->get_col(
171 $wpdb->prepare(
172 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
173 $wpdb->esc_like(self::OPTION_PREFIX . $plugin . '_') . '%'
174 )
175 );
176
177 $deleted = 0;
178 foreach ($option_names as $option_name) {
179 if (delete_option($option_name)) {
180 $deleted++;
181 }
182 }
183
184 return $deleted;
185 }
186 }
187