PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/versionpress/src/Database/WpdbMirrorBridge.php

https://gitlab.com/vanafroo/landingpage
PHP | 447 lines | 299 code | 81 blank | 67 comment | 36 complexity | 5ef1f582add0420ea799e6c7c146265e MD5 | raw file
  1. <?php
  2. namespace VersionPress\Database;
  3. use VersionPress\Storages\Mirror;
  4. /**
  5. * Bridge between hooks in {@see wpdb} and {@see Mirror}. Transforms WordPress data to the form suitable for Mirror.
  6. * Especially, it transforms WP ids to VPIDs.
  7. */
  8. class WpdbMirrorBridge
  9. {
  10. /** @var Mirror */
  11. private $mirror;
  12. /** @var DbSchemaInfo */
  13. private $dbSchemaInfo;
  14. /** @var Database */
  15. private $database;
  16. /** @var VpidRepository */
  17. private $vpidRepository;
  18. /** @var ShortcodesReplacer */
  19. private $shortcodesReplacer;
  20. /** @var bool */
  21. private $disabled;
  22. public function __construct(
  23. $database,
  24. Mirror $mirror,
  25. DbSchemaInfo $dbSchemaInfo,
  26. VpidRepository $vpidRepository,
  27. ShortcodesReplacer $shortcodesReplacer
  28. ) {
  29. $this->database = $database;
  30. $this->mirror = $mirror;
  31. $this->dbSchemaInfo = $dbSchemaInfo;
  32. $this->vpidRepository = $vpidRepository;
  33. $this->shortcodesReplacer = $shortcodesReplacer;
  34. }
  35. public function insert($table, $data)
  36. {
  37. if ($this->disabled) {
  38. return;
  39. }
  40. $id = $this->database->insert_id;
  41. $entityInfo = $this->dbSchemaInfo->getEntityInfoByPrefixedTableName($table);
  42. if (!$entityInfo) {
  43. return;
  44. }
  45. $data = $this->database->get_row("SELECT * FROM `$table` WHERE `$entityInfo->idColumnName` = '$id'", ARRAY_A);
  46. $entityName = $entityInfo->entityName;
  47. $data = $this->vpidRepository->replaceForeignKeysWithReferences($entityName, $data);
  48. array_walk($data, function (&$value, $key) {
  49. if ($value === false) {
  50. $value = '';
  51. }
  52. });
  53. $shouldBeSaved = $this->mirror->shouldBeSaved($entityName, $data);
  54. if (!$shouldBeSaved) {
  55. return;
  56. }
  57. $data = $this->vpidRepository->identifyEntity($entityName, $data, $id);
  58. $data = $this->shortcodesReplacer->replaceShortcodesInEntity($entityName, $data);
  59. $this->mirror->save($entityName, $data);
  60. }
  61. public function update($table, $data, $where)
  62. {
  63. if ($this->disabled) {
  64. return;
  65. }
  66. $entityInfo = $this->dbSchemaInfo->getEntityInfoByPrefixedTableName($table);
  67. if (!$entityInfo) {
  68. return;
  69. }
  70. $entityName = $entityInfo->entityName;
  71. $data = array_merge($where, $data);
  72. array_walk($data, function (&$value, $key) {
  73. if ($value === false) {
  74. $value = '';
  75. }
  76. });
  77. if (!$entityInfo->usesGeneratedVpids) { // options etc.
  78. $data = $this->vpidRepository->replaceForeignKeysWithReferences($entityName, $data);
  79. $this->mirror->save($entityName, $data);
  80. return;
  81. }
  82. $ids = $this->detectAllAffectedIds($entityName, $data, $where);
  83. foreach ($ids as $id) {
  84. $this->updateEntity($data, $entityName, $id);
  85. }
  86. }
  87. public function delete($table, $where, $parentIds)
  88. {
  89. if ($this->disabled) {
  90. return;
  91. }
  92. $entityInfo = $this->dbSchemaInfo->getEntityInfoByPrefixedTableName($table);
  93. if (!$entityInfo) {
  94. return;
  95. }
  96. $entityName = $entityInfo->entityName;
  97. if (!$entityInfo->usesGeneratedVpids) {
  98. $this->mirror->delete($entityName, $where);
  99. return;
  100. }
  101. $ids = $this->detectAllAffectedIds($entityName, $where, $where);
  102. foreach ($ids as $id) {
  103. $where['vp_id'] = $this->vpidRepository->getVpidForEntity($entityName, $id);
  104. if (!$where['vp_id']) {
  105. continue; // already deleted - deleting postmeta is sometimes called twice
  106. }
  107. if ($this->dbSchemaInfo->isChildEntity($entityName)
  108. && !isset($where["vp_{$entityInfo->parentReference}"])) {
  109. $where["vp_{$entityInfo->parentReference}"] = $parentIds[$id];
  110. }
  111. $this->vpidRepository->deleteId($entityName, $id);
  112. $this->mirror->delete($entityName, $where);
  113. }
  114. }
  115. /**
  116. * Fill parentIds of child entities and returns them as associative array in `$id => $parentId` format.
  117. * Returns FALSE when table contains entity which is not an childEntity.
  118. *
  119. * @param $table
  120. * @param $where
  121. * @return array
  122. */
  123. public function getParentIdsBeforeDelete($table, $where)
  124. {
  125. $entityInfo = $this->dbSchemaInfo->getEntityInfoByPrefixedTableName($table);
  126. if (!$entityInfo) {
  127. return [];
  128. }
  129. if (!$this->dbSchemaInfo->isChildEntity($entityInfo->entityName)) {
  130. return [];
  131. }
  132. $ids = $this->detectAllAffectedIds($entityInfo->entityName, $where, $where);
  133. $parentIds = [];
  134. foreach ($ids as $id) {
  135. $parentIds[$id] = $this->fillParentId($entityInfo->entityName, $entityInfo, $id);
  136. }
  137. return $parentIds;
  138. }
  139. /**
  140. * @param ParsedQueryData $parsedQueryData
  141. */
  142. public function query($parsedQueryData)
  143. {
  144. if ($this->disabled) {
  145. return;
  146. }
  147. $entityInfo = $this->dbSchemaInfo->getEntityInfoByPrefixedTableName($parsedQueryData->table);
  148. if (!$entityInfo) {
  149. return;
  150. }
  151. switch ($parsedQueryData->queryType) {
  152. case ParsedQueryData::UPDATE_QUERY:
  153. $this->processUpdateQuery($parsedQueryData);
  154. break;
  155. case ParsedQueryData::DELETE_QUERY:
  156. $this->processDeleteQuery($parsedQueryData, $entityInfo);
  157. break;
  158. case ParsedQueryData::INSERT_QUERY:
  159. $this->processInsertQuery($parsedQueryData);
  160. break;
  161. case ParsedQueryData::INSERT_UPDATE_QUERY:
  162. $this->processInsertUpdateQuery($parsedQueryData);
  163. break;
  164. }
  165. }
  166. /**
  167. * Returns all ids from DB suitable for given restriction.
  168. * E.g. all comment_id values where comment_post_id = 1
  169. * @param string $entityName
  170. * @param array $where
  171. * @return array
  172. */
  173. private function getIdsForRestriction($entityName, $where)
  174. {
  175. $idColumnName = $this->dbSchemaInfo->getEntityInfo($entityName)->idColumnName;
  176. $table = $this->dbSchemaInfo->getPrefixedTableName($entityName);
  177. $sql = "SELECT {$idColumnName} FROM {$table} WHERE ";
  178. $sql .= join(
  179. " AND ",
  180. array_map(
  181. function ($column) {
  182. return "`$column` = %s";
  183. },
  184. array_keys($where)
  185. )
  186. );
  187. $ids = $this->database->get_col($this->database->prepare($sql, $where));
  188. return $ids;
  189. }
  190. private function updateEntity($data, $entityName, $id)
  191. {
  192. $vpId = $this->vpidRepository->getVpidForEntity($entityName, $id);
  193. $table = $this->dbSchemaInfo->getPrefixedTableName($entityName);
  194. $idColumnName = $this->dbSchemaInfo->getEntityInfo($entityName)->idColumnName;
  195. $data = array_merge(
  196. $this->database->get_row("SELECT * FROM `$table` WHERE `$idColumnName` = '$id'", ARRAY_A),
  197. $data
  198. );
  199. $data['vp_id'] = $vpId;
  200. $data = $this->vpidRepository->replaceForeignKeysWithReferences($entityName, $data);
  201. if ($this->dbSchemaInfo->isChildEntity($entityName)) {
  202. $entityInfo = $this->dbSchemaInfo->getEntityInfo($entityName);
  203. $parentVpReference = "vp_" . $entityInfo->parentReference;
  204. if (!isset($data[$parentVpReference])) {
  205. $data[$parentVpReference] = $this->fillParentId($entityName, $entityInfo, $id);
  206. }
  207. }
  208. $shouldBeSaved = $this->mirror->shouldBeSaved($entityName, $data);
  209. if (!$shouldBeSaved) {
  210. return;
  211. }
  212. // the post exists in DB for a while but until now it wasn't tracked, so we have to save its postmeta
  213. $savePostmeta = !$vpId && $entityName === 'post';
  214. if (!$vpId) {
  215. $data = $this->vpidRepository->identifyEntity($entityName, $data, $id);
  216. }
  217. $data = $this->shortcodesReplacer->replaceShortcodesInEntity($entityName, $data);
  218. $this->mirror->save($entityName, $data);
  219. if (!$savePostmeta) {
  220. return;
  221. }
  222. $postmeta = $this->database->get_results(
  223. "SELECT meta_id, meta_key, meta_value FROM {$this->database->postmeta} WHERE post_id = {$id}",
  224. ARRAY_A
  225. );
  226. foreach ($postmeta as $meta) {
  227. $meta['vp_post_id'] = $data['vp_id'];
  228. $meta = $this->vpidRepository->replaceForeignKeysWithReferences('postmeta', $meta);
  229. if (!$this->mirror->shouldBeSaved('postmeta', $meta)) {
  230. continue;
  231. }
  232. $meta = $this->vpidRepository->identifyEntity('postmeta', $meta, $meta['meta_id']);
  233. $meta = $this->shortcodesReplacer->replaceShortcodesInEntity('postmeta', $meta);
  234. $this->mirror->save('postmeta', $meta);
  235. }
  236. }
  237. /**
  238. * Returns all database IDs matching the restriction.
  239. * In most cases it returns ID from $where array.
  240. * For meta-entities it can find the ID by key and parent entity ID, if
  241. * the ID is missing in the $where array.
  242. * For all other cases see {@link WpdbMirrorBridge::getIdsForRestriction}.
  243. *
  244. * @param $entityName
  245. * @param $data
  246. * @param $where
  247. * @return array List of ids
  248. */
  249. private function detectAllAffectedIds($entityName, $data, $where)
  250. {
  251. $idColumnName = $this->dbSchemaInfo->getEntityInfo($entityName)->idColumnName;
  252. if (isset($where[$idColumnName])) {
  253. return [$where[$idColumnName]];
  254. }
  255. return $this->getIdsForRestriction($entityName, $where);
  256. }
  257. private function fillParentId($metaEntityName, $entityInfo, $id)
  258. {
  259. $parentReference = $entityInfo->parentReference;
  260. $parent = $entityInfo->references[$parentReference];
  261. $vpIdTable = $this->database->vp_id;
  262. $entityTable = $this->dbSchemaInfo->getPrefixedTableName($metaEntityName);
  263. $parentTable = $this->dbSchemaInfo->getTableName($parent);
  264. $idColumnName = $this->dbSchemaInfo->getEntityInfo($metaEntityName)->idColumnName;
  265. return $this->database->get_var(
  266. "SELECT HEX(vp_id) FROM $vpIdTable
  267. WHERE `table` = '{$parentTable}'
  268. AND ID = (SELECT {$parentReference} FROM $entityTable WHERE {$idColumnName} = '$id')"
  269. );
  270. }
  271. /**
  272. * Disables all actions. Useful for deactivating VersionPress.
  273. */
  274. public function disable()
  275. {
  276. $this->disabled = true;
  277. }
  278. /**
  279. * Processes ParsedQueryData from UPDATE query and stores updated entity/entities data into Storage.
  280. *
  281. * @param ParsedQueryData $parsedQueryData
  282. */
  283. private function processUpdateQuery($parsedQueryData)
  284. {
  285. foreach ($parsedQueryData->ids as $id) {
  286. $this->updateEntity([], $parsedQueryData->entityName, $id);
  287. }
  288. }
  289. /**
  290. * Process ParsedQueryData from DELETE query and deletes entity/entities data from Storage.
  291. * Source parsed query does not contain any special Sql functions (e.g. NOW)
  292. *
  293. * @param ParsedQueryData $parsedQueryData
  294. * @param $entityInfo
  295. */
  296. private function processDeleteQuery($parsedQueryData, $entityInfo)
  297. {
  298. if (!$entityInfo->usesGeneratedVpids) {
  299. foreach ($parsedQueryData->ids as $id) {
  300. $where[$parsedQueryData->idColumnName] = $id;
  301. $this->vpidRepository->deleteId($parsedQueryData->entityName, $id);
  302. $this->mirror->delete($parsedQueryData->entityName, $where);
  303. }
  304. return;
  305. }
  306. foreach ($parsedQueryData->ids as $id) {
  307. $where['vp_id'] = $this->vpidRepository->getVpidForEntity($parsedQueryData->entityName, $id);
  308. if (!$where['vp_id']) {
  309. continue; // already deleted - deleting postmeta is sometimes called twice
  310. }
  311. if ($this->dbSchemaInfo->isChildEntity($parsedQueryData->entityName)) {
  312. $parentVpReference = "vp_" . $entityInfo->parentReference;
  313. $where[$parentVpReference] = $this->fillParentId($parsedQueryData->entityName, $entityInfo, $id);
  314. }
  315. $this->vpidRepository->deleteId($parsedQueryData->entityName, $id);
  316. $this->mirror->delete($parsedQueryData->entityName, $where);
  317. }
  318. }
  319. /**
  320. * Process ParsedQueryData from INSERT query and stores affected entity into Storage.
  321. * Source parsed query does not contain any special Sql functions (e.g. NOW)
  322. *
  323. * @param ParsedQueryData $parsedQueryData
  324. */
  325. private function processInsertQuery($parsedQueryData)
  326. {
  327. $id = $this->database->insert_id;
  328. $entitiesCount = count($parsedQueryData->data);
  329. for ($i = 0; $i < $entitiesCount; $i++) {
  330. $data = $this->vpidRepository->replaceForeignKeysWithReferences(
  331. $parsedQueryData->entityName,
  332. $parsedQueryData->data[$i]
  333. );
  334. $shouldBeSaved = $this->mirror->shouldBeSaved($parsedQueryData->entityName, $data);
  335. if (!$shouldBeSaved) {
  336. continue;
  337. }
  338. $data = $this->vpidRepository->identifyEntity($parsedQueryData->entityName, $data, ($id - $i));
  339. $this->mirror->save($parsedQueryData->entityName, $data);
  340. }
  341. }
  342. /**
  343. * Processes ParsedQueryData from INSERT ... ON DUPLICATE UPDATE query and stores changes into Storage
  344. *
  345. * @param ParsedQueryData $parsedQueryData
  346. */
  347. private function processInsertUpdateQuery($parsedQueryData)
  348. {
  349. if ($parsedQueryData->ids != 0) {
  350. $id = $parsedQueryData->ids;
  351. $data = $this->vpidRepository->replaceForeignKeysWithReferences(
  352. $parsedQueryData->entityName,
  353. $parsedQueryData->data[0]
  354. );
  355. $shouldBeSaved = $this->mirror->shouldBeSaved($parsedQueryData->entityName, $data);
  356. if (!$shouldBeSaved) {
  357. return;
  358. }
  359. $data = $this->vpidRepository->identifyEntity($parsedQueryData->entityName, $data, $id);
  360. $this->mirror->save($parsedQueryData->entityName, $data);
  361. } else {
  362. $data = $this->database->get_results($parsedQueryData->sqlQuery, ARRAY_A)[0];
  363. $this->updateEntity($data, $parsedQueryData->entityName, $data[$parsedQueryData->idColumnName]);
  364. }
  365. }
  366. }