PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.2.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.2.0
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 2 years ago ExcludedTables.php 4 years ago OptionPreservationHandler.php 2 years ago SearchReplace.php 2 years ago SelectedTables.php 2 years ago TableDto.php 5 years ago TableService.php 2 years ago TablesRenamer.php 2 years ago WpDbInfo.php 2 years ago iDbInfo.php 2 years ago
SearchReplace.php
250 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
210 return $data;
211 }
212
213 private function replaceObject($data)
214 {
215 // This is not reliable as JsonSerializable and Serializable interfaces can record data into database
216 // but get_object_vars won't be able to fetch them to replace
217 // TODO use reflection to make sure even protected and private properties are searched and replaced
218 $props = get_object_vars($data);
219 if (!empty($props['__PHP_Incomplete_Class_Name'])) {
220 return $data;
221 }
222
223 foreach ($props as $key => $value) {
224 if ($key === '' || (isset($key[0]) && ord($key[0]) === 0)) {
225 continue;
226 }
227
228 $data->{$key} = $this->walker($value);
229 }
230
231 return $data;
232 }
233
234 private function strReplace($data = '')
235 {
236 $regexExclude = '';
237 foreach ($this->exclude as $excludeString) {
238 //TODO: I changed (FAIL) to (*FAIL) because that's what tutorials say is the right syntax. This may need testing
239 $regexExclude .= $excludeString . '(*SKIP)(*FAIL)|';
240 }
241
242 $pattern = '#' . $regexExclude . preg_quote($this->currentSearch, null) . '#';
243 if (!$this->caseSensitive) {
244 $pattern .= 'i';
245 }
246
247 return preg_replace($pattern, $this->currentReplace, $data);
248 }
249 }
250