vendor/pimcore/pimcore/models/DataObject/Concrete/Dao.php line 258

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore\Model\DataObject\Concrete;
  15. use Pimcore\Db;
  16. use Pimcore\Logger;
  17. use Pimcore\Model;
  18. use Pimcore\Model\DataObject;
  19. use Pimcore\Model\DataObject\ClassDefinition\Data\CustomResourcePersistingInterface;
  20. use Pimcore\Model\DataObject\ClassDefinition\Data\LazyLoadingSupportInterface;
  21. use Pimcore\Model\DataObject\ClassDefinition\Data\QueryResourcePersistenceAwareInterface;
  22. use Pimcore\Model\DataObject\ClassDefinition\Data\ResourcePersistenceAwareInterface;
  23. /**
  24. * @internal
  25. *
  26. * @property \Pimcore\Model\DataObject\Concrete $model
  27. */
  28. class Dao extends Model\DataObject\AbstractObject\Dao
  29. {
  30. use Model\Element\Traits\ScheduledTasksDaoTrait;
  31. use Model\Element\Traits\VersionDaoTrait;
  32. /**
  33. * @var DataObject\Concrete\Dao\InheritanceHelper
  34. */
  35. protected $inheritanceHelper = null;
  36. public function init()
  37. {
  38. $this->inheritanceHelper = new DataObject\Concrete\Dao\InheritanceHelper($this->model->getClassId());
  39. }
  40. /**
  41. * Get the data for the object from database for the given id
  42. *
  43. * @param int $id
  44. *
  45. * @throws Model\Exception\NotFoundException
  46. */
  47. public function getById($id)
  48. {
  49. $data = $this->db->fetchRow("SELECT objects.*, tree_locks.locked as o_locked FROM objects
  50. LEFT JOIN tree_locks ON objects.o_id = tree_locks.id AND tree_locks.type = 'object'
  51. WHERE o_id = ?", $id);
  52. if (!empty($data['o_id'])) {
  53. $this->assignVariablesToModel($data);
  54. $this->getData();
  55. } else {
  56. throw new Model\Exception\NotFoundException('Object with the ID ' . $id . " doesn't exists");
  57. }
  58. }
  59. /**
  60. * @param string $fieldName
  61. *
  62. * @return array
  63. */
  64. public function getRelationIds($fieldName)
  65. {
  66. $relations = [];
  67. $allRelations = $this->db->fetchAll('SELECT * FROM object_relations_' . $this->model->getClassId() . " WHERE fieldname = ? AND src_id = ? AND ownertype = 'object' ORDER BY `index` ASC", [$fieldName, $this->model->getId()]);
  68. foreach ($allRelations as $relation) {
  69. $relations[] = $relation['dest_id'];
  70. }
  71. return $relations;
  72. }
  73. /**
  74. * @param string $field
  75. * @param bool $forOwner
  76. * @param string $remoteClassId
  77. *
  78. * @return array
  79. */
  80. public function getRelationData($field, $forOwner, $remoteClassId)
  81. {
  82. $id = $this->model->getId();
  83. if ($remoteClassId) {
  84. $classId = $remoteClassId;
  85. } else {
  86. $classId = $this->model->getClassId();
  87. }
  88. $params = [$field, $id, $field, $id, $field, $id];
  89. $dest = 'dest_id';
  90. $src = 'src_id';
  91. if (!$forOwner) {
  92. $dest = 'src_id';
  93. $src = 'dest_id';
  94. }
  95. $relations = $this->db->fetchAll('SELECT r.' . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, o.o_className as subtype, o.o_published as published, concat(o.o_path ,o.o_key) as path , r.index
  96. FROM objects o, object_relations_' . $classId . " r
  97. WHERE r.fieldname= ?
  98. AND r.ownertype = 'object'
  99. AND r." . $src . ' = ?
  100. AND o.o_id = r.' . $dest . "
  101. AND r.type='object'
  102. UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, a.type as subtype, "null" as published, concat(a.path,a.filename) as path, r.index
  103. FROM assets a, object_relations_' . $classId . " r
  104. WHERE r.fieldname= ?
  105. AND r.ownertype = 'object'
  106. AND r." . $src . ' = ?
  107. AND a.id = r.' . $dest . "
  108. AND r.type='asset'
  109. UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, d.type as subtype, d.published as published, concat(d.path,d.key) as path, r.index
  110. FROM documents d, object_relations_' . $classId . " r
  111. WHERE r.fieldname= ?
  112. AND r.ownertype = 'object'
  113. AND r." . $src . ' = ?
  114. AND d.id = r.' . $dest . "
  115. AND r.type='document'
  116. ORDER BY `index` ASC", $params);
  117. if (is_array($relations) && count($relations) > 0) {
  118. return $relations;
  119. } else {
  120. return [];
  121. }
  122. }
  123. /**
  124. * Get all data-elements for all fields that are not lazy-loaded.
  125. */
  126. public function getData()
  127. {
  128. if (empty($this->model->getClass())) {
  129. return;
  130. }
  131. if (!$data = $this->db->fetchRow('SELECT * FROM object_store_' . $this->model->getClassId() . ' WHERE oo_id = ?', $this->model->getId())) {
  132. return;
  133. }
  134. $fieldDefinitions = $this->model->getClass()->getFieldDefinitions(['object' => $this->model]);
  135. foreach ($fieldDefinitions as $key => $value) {
  136. if ($value instanceof CustomResourcePersistingInterface) {
  137. if (!$value instanceof LazyLoadingSupportInterface || !$value->getLazyLoading()) {
  138. // datafield has it's own loader
  139. $params = [
  140. 'context' => [
  141. 'object' => $this->model,
  142. ],
  143. 'owner' => $this->model,
  144. 'fieldname' => $key,
  145. ];
  146. $value = $value->load($this->model, $params);
  147. if ($value === 0 || !empty($value)) {
  148. $this->model->setValue($key, $value);
  149. }
  150. }
  151. }
  152. if ($value instanceof ResourcePersistenceAwareInterface) {
  153. // if a datafield requires more than one field
  154. if (is_array($value->getColumnType())) {
  155. $multidata = [];
  156. foreach ($value->getColumnType() as $fkey => $fvalue) {
  157. $multidata[$key . '__' . $fkey] = $data[$key . '__' . $fkey];
  158. }
  159. $this->model->setValue($key, $value->getDataFromResource($multidata));
  160. } else {
  161. $this->model->setValue($key, $value->getDataFromResource($data[$key], $this->model, [
  162. 'owner' => $this->model,
  163. 'fieldname' => $key,
  164. ]));
  165. }
  166. }
  167. }
  168. }
  169. /**
  170. * Save changes to database, it's an good idea to use save() instead
  171. *
  172. * @param bool|null $isUpdate
  173. */
  174. public function update($isUpdate = null)
  175. {
  176. parent::update($isUpdate);
  177. // get fields which shouldn't be updated
  178. $fieldDefinitions = $this->model->getClass()->getFieldDefinitions();
  179. $untouchable = [];
  180. $db = Db::get();
  181. foreach ($fieldDefinitions as $key => $fd) {
  182. if ($fd instanceof LazyLoadingSupportInterface && $fd->getLazyLoading()) {
  183. if (!$this->model->isLazyKeyLoaded($key) || $fd instanceof DataObject\ClassDefinition\Data\ReverseObjectRelation) {
  184. //this is a relation subject to lazy loading - it has not been loaded
  185. $untouchable[] = $key;
  186. }
  187. }
  188. if (!DataObject::isDirtyDetectionDisabled() && $fd->supportsDirtyDetection()) {
  189. if ($this->model instanceof Model\Element\DirtyIndicatorInterface && !$this->model->isFieldDirty($key)) {
  190. if (!in_array($key, $untouchable)) {
  191. $untouchable[] = $key;
  192. }
  193. }
  194. }
  195. }
  196. // empty relation table except the untouchable fields (eg. lazy loading fields)
  197. if (count($untouchable) > 0) {
  198. $untouchables = "'" . implode("','", $untouchable) . "'";
  199. $condition = $this->db->quoteInto('src_id = ? AND fieldname not in (' . $untouchables . ") AND ownertype = 'object'", $this->model->getId());
  200. } else {
  201. $condition = 'src_id = ' . $db->quote($this->model->getId()) . ' AND ownertype = "object"';
  202. }
  203. if (!DataObject::isDirtyDetectionDisabled()) {
  204. $condition = '(' . $condition . ' AND ownerType != "localizedfield" AND ownerType != "fieldcollection")';
  205. }
  206. $this->db->deleteWhere('object_relations_' . $this->model->getClassId(), $condition);
  207. $inheritedValues = DataObject::doGetInheritedValues();
  208. DataObject::setGetInheritedValues(false);
  209. $data = [];
  210. $data['oo_id'] = $this->model->getId();
  211. foreach ($fieldDefinitions as $key => $fd) {
  212. $getter = 'get' . ucfirst($key);
  213. if ($fd instanceof CustomResourcePersistingInterface) {
  214. // for fieldtypes which have their own save algorithm eg. fieldcollections, relational data-types, ...
  215. $saveParams = ['isUntouchable' => in_array($fd->getName(), $untouchable),
  216. 'isUpdate' => $isUpdate,
  217. 'context' => [
  218. 'containerType' => 'object',
  219. ],
  220. 'owner' => $this->model,
  221. 'fieldname' => $key,
  222. ]
  223. ;
  224. if ($this->model instanceof Model\Element\DirtyIndicatorInterface) {
  225. $saveParams['newParent'] = $this->model->isFieldDirty('o_parentId');
  226. }
  227. $fd->save($this->model, $saveParams);
  228. }
  229. if ($fd instanceof ResourcePersistenceAwareInterface) {
  230. // pimcore saves the values with getDataForResource
  231. if (is_array($fd->getColumnType())) {
  232. $insertDataArray = $fd->getDataForResource($this->model->$getter(), $this->model,
  233. [
  234. 'isUpdate' => $isUpdate,
  235. 'owner' => $this->model,
  236. 'fieldname' => $key,
  237. ]);
  238. if (is_array($insertDataArray)) {
  239. $data = array_merge($data, $insertDataArray);
  240. }
  241. } else {
  242. $insertData = $fd->getDataForResource($this->model->$getter(), $this->model,
  243. [
  244. 'isUpdate' => $isUpdate,
  245. 'owner' => $this->model,
  246. 'fieldname' => $key,
  247. ]);
  248. $data[$key] = $insertData;
  249. }
  250. }
  251. }
  252. $method = $isUpdate ? 'insertOrUpdate' : 'insert';
  253. $this->db->$method('object_store_' . $this->model->getClassId(), $data);
  254. // get data for query table
  255. $data = [];
  256. $this->inheritanceHelper->resetFieldsToCheck();
  257. $oldData = $this->db->fetchRow('SELECT * FROM object_query_' . $this->model->getClassId() . ' WHERE oo_id = ?', $this->model->getId());
  258. $inheritanceEnabled = $this->model->getClass()->getAllowInherit();
  259. $parentData = null;
  260. if ($inheritanceEnabled) {
  261. // get the next suitable parent for inheritance
  262. $parentForInheritance = $this->model->getNextParentForInheritance();
  263. if ($parentForInheritance) {
  264. // we don't use the getter (built in functionality to get inherited values) because we need to avoid race conditions
  265. // we cannot DataObject::setGetInheritedValues(true); and then $this->model->$method();
  266. // so we select the data from the parent object using FOR UPDATE, which causes a lock on this row
  267. // so the data of the parent cannot be changed while this transaction is on progress
  268. $parentData = $this->db->fetchRow('SELECT * FROM object_query_' . $this->model->getClassId() . ' WHERE oo_id = ? FOR UPDATE', $parentForInheritance->getId());
  269. }
  270. }
  271. foreach ($fieldDefinitions as $key => $fd) {
  272. if ($fd instanceof QueryResourcePersistenceAwareInterface) {
  273. //exclude untouchables if value is not an array - this means data has not been loaded
  274. if (!in_array($key, $untouchable)) {
  275. $method = 'get' . $key;
  276. $fieldValue = $this->model->$method();
  277. $insertData = $fd->getDataForQueryResource($fieldValue, $this->model,
  278. [
  279. 'isUpdate' => $isUpdate,
  280. 'owner' => $this->model,
  281. 'fieldname' => $key,
  282. ]);
  283. $isEmpty = $fd->isEmpty($fieldValue);
  284. if (is_array($insertData)) {
  285. $columnNames = array_keys($insertData);
  286. $data = array_merge($data, $insertData);
  287. } else {
  288. $columnNames = [$key];
  289. $data[$key] = $insertData;
  290. }
  291. // if the current value is empty and we have data from the parent, we just use it
  292. if ($isEmpty && $parentData) {
  293. foreach ($columnNames as $columnName) {
  294. if (array_key_exists($columnName, $parentData)) {
  295. $data[$columnName] = $parentData[$columnName];
  296. if (is_array($insertData)) {
  297. $insertData[$columnName] = $parentData[$columnName];
  298. } else {
  299. $insertData = $parentData[$columnName];
  300. }
  301. }
  302. }
  303. }
  304. if ($inheritanceEnabled && $fd->supportsInheritance()) {
  305. //get changed fields for inheritance
  306. if ($fd->isRelationType()) {
  307. if (is_array($insertData)) {
  308. $doInsert = false;
  309. foreach ($insertData as $insertDataKey => $insertDataValue) {
  310. $oldDataValue = $oldData[$insertDataKey] ?? null;
  311. $parentDataValue = $parentData[$insertDataKey] ?? null;
  312. if ($isEmpty && $oldDataValue == $parentDataValue) {
  313. // do nothing, ... value is still empty and parent data is equal to current data in query table
  314. } elseif ($oldDataValue != $insertDataValue) {
  315. $doInsert = true;
  316. break;
  317. }
  318. }
  319. if ($doInsert) {
  320. $this->inheritanceHelper->addRelationToCheck($key, $fd, array_keys($insertData));
  321. }
  322. } else {
  323. $oldDataValue = $oldData[$key] ?? null;
  324. $parentDataValue = $parentData[$key] ?? null;
  325. if ($isEmpty && $oldDataValue == $parentDataValue) {
  326. // do nothing, ... value is still empty and parent data is equal to current data in query table
  327. } elseif ($oldDataValue != $insertData) {
  328. $this->inheritanceHelper->addRelationToCheck($key, $fd);
  329. }
  330. }
  331. } else {
  332. if (is_array($insertData)) {
  333. foreach ($insertData as $insertDataKey => $insertDataValue) {
  334. $oldDataValue = $oldData[$insertDataKey] ?? null;
  335. $parentDataValue = $parentData[$insertDataKey] ?? null;
  336. if ($isEmpty && $oldDataValue == $parentDataValue) {
  337. // do nothing, ... value is still empty and parent data is equal to current data in query table
  338. } elseif ($oldDataValue != $insertDataValue) {
  339. $this->inheritanceHelper->addFieldToCheck($insertDataKey, $fd);
  340. }
  341. }
  342. } else {
  343. $oldDataValue = $oldData[$key] ?? null;
  344. $parentDataValue = $parentData[$key] ?? null;
  345. if ($isEmpty && $oldDataValue == $parentDataValue) {
  346. // do nothing, ... value is still empty and parent data is equal to current data in query table
  347. } elseif ($oldDataValue != $insertData) {
  348. // data changed, do check and update
  349. $this->inheritanceHelper->addFieldToCheck($key, $fd);
  350. }
  351. }
  352. }
  353. }
  354. } else {
  355. Logger::debug('Excluding untouchable query value for object [ ' . $this->model->getId() . " ] key [ $key ] because it has not been loaded");
  356. }
  357. }
  358. }
  359. $data['oo_id'] = $this->model->getId();
  360. $this->db->insertOrUpdate('object_query_' . $this->model->getClassId(), $data);
  361. DataObject::setGetInheritedValues($inheritedValues);
  362. }
  363. public function saveChildData()
  364. {
  365. $this->inheritanceHelper->doUpdate($this->model->getId(), false, [
  366. 'inheritanceRelationContext' => [
  367. 'ownerType' => 'object',
  368. ],
  369. ]);
  370. $this->inheritanceHelper->resetFieldsToCheck();
  371. }
  372. /**
  373. * Save object to database
  374. */
  375. public function delete()
  376. {
  377. $this->db->delete('object_query_' . $this->model->getClassId(), ['oo_id' => $this->model->getId()]);
  378. $this->db->delete('object_store_' . $this->model->getClassId(), ['oo_id' => $this->model->getId()]);
  379. $this->db->delete('object_relations_' . $this->model->getClassId(), ['src_id' => $this->model->getId()]);
  380. // delete fields which have their own delete algorithm
  381. if ($this->model->getClass()) {
  382. foreach ($this->model->getClass()->getFieldDefinitions() as $fd) {
  383. if ($fd instanceof CustomResourcePersistingInterface) {
  384. $fd->delete($this->model);
  385. }
  386. }
  387. }
  388. parent::delete();
  389. }
  390. }