PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 3.0.4
WP STAGING – WordPress Backups, Restore, Migration & Clone v3.0.4
4.11.2 4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Database / SearchReplace.php
wp-staging / Framework / Database Last commit date
QueryBuilder 2 years ago DbInfo.php 3 years ago ExcludedTables.php 4 years ago OptionPreservationHandler.php 2 years ago SearchReplace.php 3 years ago SelectedTables.php 3 years ago TableDto.php 5 years ago TableService.php 3 years ago TablesRenamer.php 2 years ago WpDbInfo.php 3 years ago iDbInfo.php 3 years ago
SearchReplace.php
248 lines
1 <?php
2
3 namespace WPStaging\Framework\Database;
4
5 use RuntimeException;
6
7 use function WPStaging\functions\debug_log;
8
9 class SearchReplace
10 {
11 /** @var array */
12 private $search;
13
14 /** @var array */
15 private $replace;
16
17 /** @var array */
18 private $exclude;
19
20 /** @var bool */
21 private $caseSensitive;
22
23 /** @var string */
24 private $currentSearch;
25
26 /** @var string */
27 private $currentReplace;
28
29 /** @var bool */
30 private $isWpBakeryActive;
31
32 protected $smallerReplacement = PHP_INT_MAX;
33
34 public function __construct(array $search = [], array $replace = [], $caseSensitive = true, array $exclude = [])
35 {
36 $this->search = $search;
37 $this->replace = $replace;
38 $this->caseSensitive = $caseSensitive;
39 $this->exclude = $exclude;
40 $this->isWpBakeryActive = false;
41 }
42
43 /**
44 * @return int
45 */
46 public function getSmallerSearchLength()
47 {
48 if ($this->smallerReplacement < PHP_INT_MAX) {
49 return $this->smallerReplacement;
50 }
51
52 foreach ($this->search as $search) {
53 if (strlen($search) < $this->smallerReplacement) {
54 $this->smallerReplacement = strlen($search);
55 }
56 }
57
58 return $this->smallerReplacement;
59 }
60
61 /**
62 * @param array|object|string $data
63 * @return array|object|string
64 */
65 public function replace($data)
66 {
67 if (defined('DISABLE_WPSTG_SEARCH_REPLACE') && DISABLE_WPSTG_SEARCH_REPLACE) {
68 return $data;
69 }
70
71 if (!$this->search || !$this->replace) {
72 return $data;
73 }
74
75 $totalSearch = count($this->search);
76 $totalReplace = count($this->replace);
77 if ($totalSearch !== $totalReplace) {
78 throw new RuntimeException(
79 sprintf(
80 'Can not search and replace. There are %d items to search and %d items to replace',
81 $totalSearch,
82 $totalReplace
83 )
84 );
85 }
86
87 for ($i = 0; $i < $totalSearch; $i++) {
88 $this->currentSearch = (string)$this->search[$i];
89 $this->currentReplace = (string)$this->replace[$i];
90 $data = $this->walker($data);
91 }
92
93 return $data;
94 }
95
96 // This is extended replace job which support search replace for WP Bakery
97 public function replaceExtended($data)
98 {
99 if ($this->isWpBakeryActive) {
100 $data = preg_replace_callback('/\[vc_raw_html\](.+?)\[\/vc_raw_html\]/S', [$this, 'replaceWpBakeryValues'], $data);
101 }
102
103 return $this->replace($data);
104 }
105
106 public function replaceWpBakeryValues($matched)
107 {
108 $data = base64_decode($matched[1]);
109 $data = $this->replace($data);
110 return '[vc_raw_html]' . base64_encode($data) . '[/vc_raw_html]';
111 }
112
113 public function setSearch(array $search)
114 {
115 $this->search = $search;
116 return $this;
117 }
118
119 public function setReplace(array $replace)
120 {
121 $this->replace = $replace;
122 return $this;
123 }
124
125 public function setCaseSensitive($caseSensitive)
126 {
127 $this->caseSensitive = $caseSensitive;
128 return $this;
129 }
130
131 public function setExclude(array $exclude)
132 {
133 $this->exclude = $exclude;
134 return $this;
135 }
136
137 /**
138 * Set whether WP Bakery active
139 *
140 * @return self
141 */
142 public function setWpBakeryActive($isActive = true)
143 {
144 $this->isWpBakeryActive = $isActive;
145 return $this;
146 }
147
148 /**
149 * @param string|array|object $data
150 * @return string|array|object|bool|int|float|null
151 */
152 private function walker($data)
153 {
154 switch (gettype($data)) {
155 case "string":
156 return $this->replaceString($data);
157 case "array":
158 return $this->replaceArray($data);
159 case "object":
160 return $this->replaceObject($data);
161 }
162
163 return $data;
164 }
165
166 /**
167 * @param string $data
168 * @return string|array|object|bool|int|float|null
169 */
170 private function replaceString($data)
171 {
172 if (!is_serialized($data)) {
173 return $this->strReplace($data);
174 }
175
176 // PDO instances can not be serialized or unserialized
177 if (strpos($data, 'O:3:"PDO":0:') !== false) {
178 return $data;
179 }
180
181 // DateTime object can not be unserialized.
182 // Would throw PHP Fatal error: Uncaught Error: Invalid serialization data for DateTime object in
183 // Bug PHP https://bugs.php.net/bug.php?id=68889&thanks=6 and https://github.com/WP-Staging/wp-staging-pro/issues/74
184 if (strpos($data, 'O:8:"DateTime":0:') !== false) {
185 return $data;
186 }
187
188 // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
189 try {
190 $unserialized = @unserialize($data);
191 } catch (\Exception $e) {
192 debug_log('replaceString. Can not unserialize data. Error: ' . $e->getMessage() . ' Data: ' . $data);
193 } catch (\TypeError $err) {
194 debug_log('replaceString. Can not unserialize data. Error: ' . $err->getMessage() . ' Data: ' . $data);
195 }
196
197 if ($unserialized !== false) {
198 return serialize($this->walker($unserialized));
199 }
200
201 return $data;
202 }
203
204 private function replaceArray(array $data)
205 {
206 foreach ($data as $key => $value) {
207 $data[$key] = $this->walker($value);
208 }
209 return $data;
210 }
211
212 private function replaceObject($data)
213 {
214 // This is not reliable as JsonSerializable and Serializable interfaces can record data into database
215 // but get_object_vars won't be able to fetch them to replace
216 // TODO use reflection to make sure even protected and private properties are searched and replaced
217 $props = get_object_vars($data);
218 if (!empty($props['__PHP_Incomplete_Class_Name'])) {
219 return $data;
220 }
221
222 foreach ($props as $key => $value) {
223 if ($key === '' || (isset($key[0]) && ord($key[0]) === 0)) {
224 continue;
225 }
226 $data->{$key} = $this->walker($value);
227 }
228
229 return $data;
230 }
231
232 private function strReplace($data = '')
233 {
234 $regexExclude = '';
235 foreach ($this->exclude as $excludeString) {
236 //TODO: I changed (FAIL) to (*FAIL) because that's what tutorials say is the right syntax. This may need testing
237 $regexExclude .= $excludeString . '(*SKIP)(*FAIL)|';
238 }
239
240 $pattern = '#' . $regexExclude . preg_quote($this->currentSearch, null) . '#';
241 if (!$this->caseSensitive) {
242 $pattern .= 'i';
243 }
244
245 return preg_replace($pattern, $this->currentReplace, $data);
246 }
247 }
248