PageRenderTime 28ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/tine20/Tinebase/Controller/Record/Abstract.php

https://gitlab.com/rsilveira1987/Expresso
PHP | 1343 lines | 720 code | 184 blank | 439 comment | 162 complexity | aa8736ec64e64a13b0ebbc9e7541cbef MD5 | raw file
  1. <?php
  2. /**
  3. * Abstract record controller for Tine 2.0 applications
  4. *
  5. * @package Tinebase
  6. * @subpackage Controller
  7. * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
  8. * @author Philipp Schüle <p.schuele@metaways.de>
  9. * @copyright Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
  10. *
  11. * @todo this should be splitted into smaller parts!
  12. */
  13. /**
  14. * abstract record controller class for Tine 2.0 applications
  15. *
  16. * @package Tinebase
  17. * @subpackage Controller
  18. */
  19. abstract class Tinebase_Controller_Record_Abstract
  20. extends Tinebase_Controller_Abstract
  21. implements Tinebase_Controller_Record_Interface, Tinebase_Controller_SearchInterface
  22. {
  23. /**
  24. * application backend class
  25. *
  26. * @var Tinebase_Backend_Sql_Interface
  27. */
  28. protected $_backend;
  29. /**
  30. * Model name
  31. *
  32. * @var string
  33. *
  34. * @todo perhaps we can remove that and build model name from name of the class (replace 'Controller' with 'Model')
  35. */
  36. protected $_modelName;
  37. /**
  38. * check for container ACLs
  39. *
  40. * @var boolean
  41. *
  42. * @todo rename to containerACLChecks
  43. */
  44. protected $_doContainerACLChecks = TRUE;
  45. /**
  46. * do right checks - can be enabled/disabled by _setRightChecks
  47. *
  48. * @var boolean
  49. */
  50. protected $_doRightChecks = TRUE;
  51. /**
  52. * delete or just set is_delete=1 if record is going to be deleted
  53. * - legacy code -> remove that when all backends/applications are using the history logging
  54. *
  55. * @var boolean
  56. */
  57. protected $_purgeRecords = TRUE;
  58. /**
  59. * omit mod log for this records
  60. *
  61. * @var boolean
  62. */
  63. protected $_omitModLog = FALSE;
  64. /**
  65. * resolve customfields in search()
  66. *
  67. * @var boolean
  68. */
  69. protected $_resolveCustomFields = FALSE;
  70. /**
  71. * clear customfields cache on create / update
  72. *
  73. * @var boolean
  74. */
  75. protected $_clearCustomFieldsCache = FALSE;
  76. /**
  77. * Do we update relation to this record
  78. *
  79. * @var boolean
  80. */
  81. protected $_doRelationUpdate = TRUE;
  82. /**
  83. * Do we force sent modlog for this record
  84. *
  85. * @var boolean
  86. */
  87. protected $_doForceModlogInfo = FALSE;
  88. /**
  89. * send notifications?
  90. *
  91. * @var boolean
  92. */
  93. protected $_sendNotifications = FALSE;
  94. /**
  95. * if some of the relations should be deleted
  96. *
  97. * @var array
  98. */
  99. protected $_relatedObjectsToDelete = array();
  100. /**
  101. * record alarm field
  102. *
  103. * @var string
  104. */
  105. protected $_recordAlarmField = 'dtstart';
  106. /**
  107. * duplicate check fields / if this is NULL -> no duplicate check
  108. *
  109. * @var array
  110. */
  111. protected $_duplicateCheckFields = NULL;
  112. /**
  113. * holds new relation on update multiple
  114. * @var array
  115. */
  116. protected $_newRelations = NULL;
  117. /**
  118. * holds relations to remove on update multiple
  119. * @var array
  120. */
  121. protected $_removeRelations = NULL;
  122. /**
  123. * result of updateMultiple function
  124. *
  125. * @var array
  126. */
  127. protected $_updateMultipleResult = array();
  128. /**
  129. * should each record be validated in updateMultiple
  130. * - FALSE: only the first record is validated with the incoming data
  131. *
  132. * @var boolean
  133. */
  134. protected $_updateMultipleValidateEachRecord = FALSE;
  135. /**
  136. * returns controller for records of given model
  137. *
  138. * @param string $_model
  139. */
  140. public static function getController($_model)
  141. {
  142. list($appName, $i, $modelName) = explode('_', $_model);
  143. return Tinebase_Core::getApplicationInstance($appName, $modelName);
  144. }
  145. /**
  146. * returns backend for this controller
  147. *
  148. */
  149. public function getBackend()
  150. {
  151. return $this->_backend;
  152. }
  153. /*********** get / search / count **************/
  154. /**
  155. * get list of records
  156. *
  157. * @param Tinebase_Model_Filter_FilterGroup|optional $_filter
  158. * @param Tinebase_Model_Pagination|optional $_pagination
  159. * @param boolean $_getRelations
  160. * @param boolean $_onlyIds
  161. * @param string $_action for right/acl check
  162. * @return Tinebase_Record_RecordSet|array
  163. */
  164. public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
  165. {
  166. $this->_checkRight($_action);
  167. $this->checkFilterACL($_filter, $_action);
  168. $this->_addDefaultFilter($_filter);
  169. $cols = $_onlyIds;
  170. if(! $_onlyIds && $this->_modelName != null) {
  171. $model = new $this->_modelName();
  172. $model->addRequiredFieldsToFilter($_filter);
  173. if($_filter->getFields() != null) {
  174. $cols = $_filter->getFields();
  175. }
  176. }
  177. $result = $this->_backend->search($_filter, $_pagination, $cols);
  178. if (! $_onlyIds) {
  179. if ($_getRelations) {
  180. $result->setByIndices('relations', Tinebase_Relations::getInstance()->getMultipleRelations($this->_modelName, $this->_getBackendType(), $result->getId()));
  181. }
  182. if ($this->resolveCustomfields()) {
  183. Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($result);
  184. }
  185. }
  186. return $result;
  187. }
  188. /**
  189. * you can define default filters here
  190. *
  191. * @param Tinebase_Model_Filter_FilterGroup $_filter
  192. */
  193. protected function _addDefaultFilter(Tinebase_Model_Filter_FilterGroup $_filter = NULL)
  194. {
  195. }
  196. /**
  197. * Gets total count of search with $_filter
  198. *
  199. * @param Tinebase_Model_Filter_FilterGroup $_filter
  200. * @param string $_action for right/acl check
  201. * @return int
  202. */
  203. public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
  204. {
  205. $this->checkFilterACL($_filter, $_action);
  206. $count = $this->_backend->searchCount($_filter);
  207. return $count;
  208. }
  209. /**
  210. * set/get the sendNotifications state
  211. *
  212. * @param boolean optional
  213. * @return boolean
  214. */
  215. public function sendNotifications()
  216. {
  217. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  218. return $this->_setBooleanMemberVar('_sendNotifications', $value);
  219. }
  220. /**
  221. * set/get a boolean member var
  222. *
  223. * @param string $name
  224. * @param boolean $value
  225. * @return boolean
  226. */
  227. protected function _setBooleanMemberVar($name, $value = NULL)
  228. {
  229. $currValue = $this->{$name};
  230. if ($value !== NULL) {
  231. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Resetting ' . $name . ' to ' . (int) $value);
  232. $this->{$name} = $value;
  233. }
  234. return $currValue;
  235. }
  236. /**
  237. * set/get purging of record when deleting
  238. *
  239. * @param boolean optional
  240. * @return boolean
  241. */
  242. public function purgeRecords()
  243. {
  244. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  245. return $this->_setBooleanMemberVar('_purgeRecords', $value);
  246. }
  247. /**
  248. * set/get checking ACL rights
  249. *
  250. * @param boolean optional
  251. * @return boolean
  252. */
  253. public function doContainerACLChecks()
  254. {
  255. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  256. return $this->_setBooleanMemberVar('_doContainerACLChecks', $value);
  257. }
  258. /**
  259. * set/get resolving of customfields
  260. *
  261. * @param boolean optional
  262. * @return boolean
  263. */
  264. public function resolveCustomfields()
  265. {
  266. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  267. $currentValue = ($this->_setBooleanMemberVar('_resolveCustomFields', $value)
  268. && Tinebase_CustomField::getInstance()->appHasCustomFields($this->_applicationName, $this->_modelName));
  269. return $currentValue;
  270. }
  271. /**
  272. * set/get modlog active
  273. *
  274. * @param boolean optional
  275. * @return boolean
  276. */
  277. public function modlogActive()
  278. {
  279. if (! $this->_backend) {
  280. throw new Tinebase_Exception_NotFound('Backend not defined');
  281. }
  282. $currValue = $this->_backend->getModlogActive();
  283. if (func_num_args() === 1) {
  284. $paramValue = (bool) func_get_arg(0);
  285. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Resetting modlog active to ' . (int) $paramValue);
  286. $this->_backend->setModlogActive($paramValue);
  287. $this->_omitModLog = ! $paramValue;
  288. }
  289. return $currValue;
  290. }
  291. /**
  292. * set/get relation update
  293. *
  294. * @param boolean optional
  295. * @return boolean
  296. */
  297. public function doRelationUpdate()
  298. {
  299. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  300. return $this->_setBooleanMemberVar('_doRelationUpdate', $value);
  301. }
  302. /**
  303. * set/get force modlog info
  304. *
  305. * @param boolean optional
  306. * @return boolean
  307. */
  308. public function doForceModlogInfo()
  309. {
  310. $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
  311. return $this->_setBooleanMemberVar('_doForceModlogInfo', $value);
  312. }
  313. /**
  314. * get by id
  315. *
  316. * @param string $_id
  317. * @param int $_containerId
  318. * @param bool $_getRelatedData
  319. * @param bool $_getDeleted
  320. * @return Tinebase_Record_Interface
  321. * @throws Tinebase_Exception_AccessDenied
  322. */
  323. public function get($_id, $_containerId = NULL, $_getRelatedData = TRUE, $_getDeleted = FALSE)
  324. {
  325. $this->_checkRight('get');
  326. if (! $_id) { // yes, we mean 0, null, false, ''
  327. $record = new $this->_modelName(array(), true);
  328. if ($this->_doContainerACLChecks) {
  329. if ($_containerId === NULL) {
  330. $containers = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), $this->_applicationName, Tinebase_Core::getUser(), Tinebase_Model_Grants::GRANT_ADD);
  331. $record->container_id = $containers[0]->getId();
  332. } else {
  333. $record->container_id = $_containerId;
  334. }
  335. }
  336. } else {
  337. $record = $this->_backend->get($_id, $_getDeleted);
  338. $this->_checkGrant($record, 'get');
  339. // get related data only on request (defaults to TRUE)
  340. if ($_getRelatedData) {
  341. $this->_getRelatedData($record);
  342. if ($record->has('notes')) {
  343. $record->notes = Tinebase_Notes::getInstance()->getNotesOfRecord($this->_modelName, $record->getId());
  344. }
  345. }
  346. }
  347. return $record;
  348. }
  349. /**
  350. * check if record with given $id exists
  351. *
  352. * @param string $id
  353. * @return boolean
  354. */
  355. public function exists($id)
  356. {
  357. $this->_checkRight('get');
  358. try {
  359. $record = $this->_backend->get($id);
  360. $result = $this->_checkGrant($record, 'get', FALSE);
  361. } catch (Tinebase_Exception_NotFound $tenf) {
  362. $result = FALSE;
  363. }
  364. return $result;
  365. }
  366. /**
  367. * add related data to record
  368. *
  369. * @param Tinebase_Record_Interface $record
  370. */
  371. protected function _getRelatedData($record)
  372. {
  373. if ($record->has('tags')) {
  374. Tinebase_Tags::getInstance()->getTagsOfRecord($record);
  375. }
  376. if ($record->has('relations')) {
  377. $record->relations = Tinebase_Relations::getInstance()->getRelations($this->_modelName, $this->_getBackendType(), $record->getId());
  378. }
  379. if ($record->has('alarms')) {
  380. $this->getAlarms($record);
  381. }
  382. if ($this->resolveCustomfields()) {
  383. Tinebase_CustomField::getInstance()->resolveRecordCustomFields($record);
  384. }
  385. if ($record->has('attachments') && Setup_Controller::getInstance()->isFilesystemAvailable()) {
  386. Tinebase_FileSystem_RecordAttachments::getInstance()->getRecordAttachments($record);
  387. }
  388. if ($record->has('notes')) {
  389. $record->notes = Tinebase_Notes::getInstance()->getNotesOfRecord($this->_modelName, $record->getId());
  390. }
  391. }
  392. /**
  393. * Returns a set of records identified by their id's
  394. *
  395. * @param array $_ids array of record identifiers
  396. * @param bool $_ignoreACL don't check acl grants
  397. * @return Tinebase_Record_RecordSet of $this->_modelName
  398. */
  399. public function getMultiple($_ids, $_ignoreACL = FALSE)
  400. {
  401. $this->_checkRight('get');
  402. // get all allowed containers and add them to getMultiple query
  403. $containerIds = ($this->_doContainerACLChecks && $_ignoreACL !== TRUE)
  404. ? Tinebase_Container::getInstance()->getContainerByACL(
  405. Tinebase_Core::getUser(),
  406. $this->_applicationName,
  407. Tinebase_Model_Grants::GRANT_READ,
  408. TRUE)
  409. : NULL;
  410. $records = $this->_backend->getMultiple($_ids, $containerIds);
  411. if ($this->resolveCustomfields()) {
  412. Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($records);
  413. }
  414. return $records;
  415. }
  416. /**
  417. * Gets all entries
  418. *
  419. * @param string $_orderBy Order result by
  420. * @param string $_orderDirection Order direction - allowed are ASC and DESC
  421. * @throws Tinebase_Exception_InvalidArgument
  422. * @return Tinebase_Record_RecordSet
  423. */
  424. public function getAll($_orderBy = 'id', $_orderDirection = 'ASC')
  425. {
  426. $this->_checkRight('get');
  427. $records = $this->_backend->getAll($_orderBy, $_orderDirection);
  428. if ($this->resolveCustomfields()) {
  429. Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($records);
  430. }
  431. return $records;
  432. }
  433. /*************** add / update / delete / move *****************/
  434. /**
  435. * add one record
  436. *
  437. * @param Tinebase_Record_Interface $_record
  438. * @param boolean $_duplicateCheck
  439. * @return Tinebase_Record_Interface
  440. * @throws Tinebase_Exception_AccessDenied
  441. */
  442. public function create(Tinebase_Record_Interface $_record, $_duplicateCheck = TRUE, $_getOnReturn = TRUE)
  443. {
  444. $this->_checkRight('create');
  445. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
  446. . print_r($_record->toArray(),true));
  447. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  448. . ' Create new ' . $this->_modelName);
  449. $db = (method_exists($this->_backend, 'getAdapter')) ? $this->_backend->getAdapter() : Tinebase_Core::getDb();
  450. try {
  451. $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
  452. // add personal container id if container id is missing in record
  453. if ($_record->has('container_id') && empty($_record->container_id)) {
  454. $containers = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), $this->_applicationName, Tinebase_Core::getUser(), Tinebase_Model_Grants::GRANT_ADD);
  455. $_record->container_id = $containers[0]->getId();
  456. }
  457. $_record->isValid(TRUE);
  458. $this->_checkGrant($_record, 'create');
  459. // added _doForceModlogInfo behavior
  460. if ($_record->has('created_by')) {
  461. $origRecord = clone ($_record);
  462. Tinebase_Timemachine_ModificationLog::setRecordMetaData($_record, 'create');
  463. $this->_forceModlogInfo($_record, $origRecord, 'create');
  464. }
  465. $this->_inspectBeforeCreate($_record);
  466. if ($_duplicateCheck) {
  467. $this->_duplicateCheck($_record);
  468. }
  469. $createdRecord = $this->_backend->create($_record);
  470. $this->_inspectAfterCreate($createdRecord, $_record);
  471. $this->_setRelatedData($createdRecord, $_record, TRUE);
  472. $this->_setNotes($createdRecord, $_record);
  473. if ($this->sendNotifications()) {
  474. $this->doSendNotifications($createdRecord, Tinebase_Core::getUser(), 'created');
  475. }
  476. $this->_increaseContainerContentSequence($createdRecord, Tinebase_Model_ContainerContent::ACTION_CREATE);
  477. Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
  478. } catch (Exception $e) {
  479. $this->_handleRecordCreateOrUpdateException($e);
  480. }
  481. if ($this->_clearCustomFieldsCache) {
  482. Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('customfields'));
  483. }
  484. if ($_getOnReturn){
  485. $return = $this->get($createdRecord);
  486. }else{
  487. $return = $_record;
  488. }
  489. return $return;
  490. }
  491. /**
  492. * handle record exception
  493. *
  494. * @param Exception $e
  495. * @throws Exception
  496. *
  497. * @todo invent hooking mechanism for database/backend independant exception handling (like lock timeouts)
  498. */
  499. protected function _handleRecordCreateOrUpdateException(Exception $e)
  500. {
  501. Tinebase_TransactionManager::getInstance()->rollBack();
  502. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
  503. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
  504. if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
  505. throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
  506. }
  507. throw $e;
  508. }
  509. /**
  510. * inspect creation of one record (before create)
  511. *
  512. * @param Tinebase_Record_Interface $_record
  513. * @return void
  514. */
  515. protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
  516. {
  517. }
  518. /**
  519. * do duplicate check (before create)
  520. *
  521. * @param Tinebase_Record_Interface $_record
  522. * @return void
  523. * @throws Tinebase_Exception_Duplicate
  524. */
  525. protected function _duplicateCheck(Tinebase_Record_Interface $_record)
  526. {
  527. $duplicateFilter = $this->_getDuplicateFilter($_record);
  528. if ($duplicateFilter === NULL) {
  529. return;
  530. }
  531. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  532. ' Doing duplicate check.');
  533. $duplicates = $this->search($duplicateFilter, new Tinebase_Model_Pagination(array('limit' => 5)));
  534. if (count($duplicates) > 0) {
  535. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  536. ' Found ' . count($duplicates) . ' duplicate(s).');
  537. $ted = new Tinebase_Exception_Duplicate('Duplicate record(s) found');
  538. $ted->setModelName($this->_modelName);
  539. $ted->setData($duplicates);
  540. $ted->setClientRecord($_record);
  541. throw $ted;
  542. } else {
  543. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  544. ' No duplicates found.');
  545. }
  546. }
  547. /**
  548. * get duplicate filter
  549. *
  550. * @param Tinebase_Record_Interface $_record
  551. * @return Tinebase_Model_Filter_FilterGroup|NULL
  552. */
  553. protected function _getDuplicateFilter(Tinebase_Record_Interface $_record)
  554. {
  555. if (empty($this->_duplicateCheckFields)) {
  556. return NULL;
  557. }
  558. $filters = array();
  559. foreach ($this->_duplicateCheckFields as $group) {
  560. $addFilter = array();
  561. foreach ($group as $field) {
  562. if (! empty($_record->{$field})) {
  563. $addFilter[] = array('field' => $field, 'operator' => 'equals', 'value' => $_record->{$field});
  564. }
  565. }
  566. if (! empty($addFilter)) {
  567. $filters[] = array('condition' => 'AND', 'filters' => $addFilter);
  568. }
  569. }
  570. if (empty($filters)) {
  571. return NULL;
  572. }
  573. $filterClass = $this->_modelName . 'Filter';
  574. $filterData = (count($filters) > 1) ? array(array('condition' => 'OR', 'filters' => $filters)) : $filters;
  575. // exclude own record if it has an id
  576. $recordId = $_record->getId();
  577. if (! empty($recordId)) {
  578. $filterData[] = array('field' => 'id', 'operator' => 'notin', 'value' => array($recordId));
  579. }
  580. $filter = new $filterClass($filterData);
  581. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($filter->toArray(), TRUE));
  582. return $filter;
  583. }
  584. /**
  585. * inspect creation of one record (after create)
  586. *
  587. * @param Tinebase_Record_Interface $_createdRecord
  588. * @param Tinebase_Record_Interface $_record
  589. * @return void
  590. */
  591. protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
  592. {
  593. }
  594. /**
  595. * increase container content sequence
  596. *
  597. * @param Tinebase_Record_Interface $_record
  598. * @param string $action
  599. */
  600. protected function _increaseContainerContentSequence(Tinebase_Record_Interface $record, $action = NULL)
  601. {
  602. if ($record->has('container_id')) {
  603. Tinebase_Container::getInstance()->increaseContentSequence($record->container_id, $action, $record->getId());
  604. }
  605. }
  606. /**
  607. * Force modlog info if set
  608. *
  609. * @param Tinebase_Record_Interface $_record
  610. * @param Tinebase_Record_Interface $_origRecord
  611. * @param string $_action
  612. * @return void
  613. */
  614. protected function _forceModlogInfo(Tinebase_Record_Interface $_record, Tinebase_Record_Interface $_origRecord, $_action = NULL)
  615. {
  616. if ($this->_doForceModlogInfo && ! empty($_origRecord)) {
  617. // on create
  618. if ($_action == 'create') {
  619. if (! empty($_origRecord->created_by)) {
  620. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - created_by: ' . $_origRecord->created_by);
  621. $_record->created_by = $_origRecord->created_by;
  622. }
  623. if (! empty($_origRecord->creation_time)) {
  624. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - creation_time: ' . $_origRecord->creation_time);
  625. $_record->creation_time = $_origRecord->creation_time;
  626. }
  627. if (! empty($_origRecord->last_modified_by)) {
  628. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - last_modified_by: ' . $_origRecord->last_modified_by);
  629. $_record->last_modified_by = $_origRecord->last_modified_by;
  630. }
  631. if (! empty($_origRecord->last_modified_time)) {
  632. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - last_modified_time: ' . $_origRecord->last_modified_time);
  633. $_record->last_modified_time = $_origRecord->last_modified_time;
  634. }
  635. }
  636. // on update
  637. if ($_action == 'update') {
  638. if (! empty($_origRecord->last_modified_by)) {
  639. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - last_modified_by: ' . $_origRecord->last_modified_by);
  640. $_record->last_modified_by = $_origRecord->last_modified_by;
  641. }
  642. if (! empty($_origRecord->last_modified_time)) {
  643. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Force modlog - last_modified_time: ' . $_origRecord->last_modified_time);
  644. $_record->last_modified_time = $_origRecord->last_modified_time;
  645. }
  646. }
  647. }
  648. }
  649. /**
  650. * update one record
  651. *
  652. * @param Tinebase_Record_Interface $_record
  653. * @param boolean $_duplicateCheck
  654. * @return Tinebase_Record_Interface
  655. * @throws Tinebase_Exception_AccessDenied
  656. *
  657. * @todo fix duplicate check on update / merge needs to remove the changed record / ux discussion
  658. */
  659. public function update(Tinebase_Record_Interface $_record, $_duplicateCheck = TRUE)
  660. {
  661. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
  662. . ' Record to update: ' . print_r($_record->toArray(), TRUE));
  663. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  664. . ' Update ' . $this->_modelName);
  665. $db = (method_exists($this->_backend, 'getAdapter')) ? $this->_backend->getAdapter() : Tinebase_Core::getDb();
  666. try {
  667. $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
  668. $_record->isValid(TRUE);
  669. $currentRecord = $this->get($_record->getId());
  670. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  671. . ' Current record: ' . print_r($currentRecord->toArray(), TRUE));
  672. // add _doForceModlogInfo behavior
  673. $origRecord = clone ($_record);
  674. $this->_updateACLCheck($_record, $currentRecord);
  675. $this->_concurrencyManagement($_record, $currentRecord);
  676. $this->_forceModlogInfo($_record, $origRecord, 'update');
  677. $this->_inspectBeforeUpdate($_record, $currentRecord);
  678. // NOTE removed the duplicate check because we can not remove the changed record yet
  679. // if ($_duplicateCheck) {
  680. // $this->_duplicateCheck($_record);
  681. // }
  682. $updatedRecord = $this->_backend->update($_record);
  683. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  684. . ' Updated record: ' . print_r($updatedRecord->toArray(), TRUE));
  685. $this->_inspectAfterUpdate($updatedRecord, $_record, $currentRecord);
  686. $updatedRecordWithRelatedData = $this->_setRelatedData($updatedRecord, $_record, TRUE);
  687. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  688. . ' Updated record with related data: ' . print_r($updatedRecordWithRelatedData->toArray(), TRUE));
  689. $currentMods = $this->_writeModLog($updatedRecordWithRelatedData, $currentRecord);
  690. $this->_setNotes($updatedRecordWithRelatedData, $_record, Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $currentMods);
  691. if ($this->_sendNotifications && count($currentMods) > 0) {
  692. $this->doSendNotifications($updatedRecordWithRelatedData, Tinebase_Core::getUser(), 'changed', $currentRecord);
  693. }
  694. if ($_record->has('container_id') && $currentRecord->container_id !== $updatedRecord->container_id) {
  695. $this->_increaseContainerContentSequence($currentRecord, Tinebase_Model_ContainerContent::ACTION_DELETE);
  696. $this->_increaseContainerContentSequence($updatedRecord, Tinebase_Model_ContainerContent::ACTION_CREATE);
  697. } else {
  698. $this->_increaseContainerContentSequence($updatedRecord, Tinebase_Model_ContainerContent::ACTION_UPDATE);
  699. }
  700. Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
  701. } catch (Exception $e) {
  702. $this->_handleRecordCreateOrUpdateException($e);
  703. }
  704. if ($this->_clearCustomFieldsCache) {
  705. Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('customfields'));
  706. }
  707. return $this->get($updatedRecord->getId());
  708. }
  709. /**
  710. * do ACL check for update record
  711. *
  712. * @param Tinebase_Record_Interface $_record
  713. * @param Tinebase_Record_Interface $_currentRecord
  714. */
  715. protected function _updateACLCheck($_record, $_currentRecord)
  716. {
  717. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  718. . ' Doing ACL check ...');
  719. if ($_currentRecord->has('container_id') && $_currentRecord->container_id != $_record->container_id) {
  720. $this->_checkGrant($_record, 'create');
  721. $this->_checkRight('create');
  722. // NOTE: It's not yet clear if we have to demand delete grants here or also edit grants would be fine
  723. $this->_checkGrant($_currentRecord, 'delete');
  724. $this->_checkRight('delete');
  725. } else {
  726. $this->_checkGrant($_record, 'update', TRUE, 'No permission to update record.', $_currentRecord);
  727. $this->_checkRight('update');
  728. }
  729. }
  730. /**
  731. * concurrency management & history log
  732. *
  733. * @param Tinebase_Record_Interface $_record
  734. * @param Tinebase_Record_Interface $_currentRecord
  735. */
  736. protected function _concurrencyManagement($_record, $_currentRecord)
  737. {
  738. if (! $_record->has('created_by')) {
  739. return NULL;
  740. }
  741. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  742. . ' Doing concurrency check ...');
  743. $modLog = Tinebase_Timemachine_ModificationLog::getInstance();
  744. $modLog->manageConcurrentUpdates($_record, $_currentRecord);
  745. $modLog->setRecordMetaData($_record, 'update', $_currentRecord);
  746. }
  747. /**
  748. * get backend type
  749. *
  750. * @return string
  751. */
  752. protected function _getBackendType()
  753. {
  754. $type = (method_exists( $this->_backend, 'getType')) ? $this->_backend->getType() : 'Sql';
  755. return $type;
  756. }
  757. /**
  758. * write modlog
  759. *
  760. * @param Tinebase_Record_Interface $_newRecord
  761. * @param Tinebase_Record_Interface $_oldRecord
  762. * @return NULL|Tinebase_Record_RecordSet
  763. */
  764. protected function _writeModLog($_newRecord, $_oldRecord)
  765. {
  766. if (! $_newRecord->has('created_by') || $this->_omitModLog === TRUE) {
  767. return NULL;
  768. }
  769. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  770. . ' Writing modlog for ' . get_class($_newRecord));
  771. $currentMods = Tinebase_Timemachine_ModificationLog::getInstance()->writeModLog($_newRecord, $_oldRecord, $this->_modelName, $this->_getBackendType(), $_newRecord->getId());
  772. return $currentMods;
  773. }
  774. /**
  775. * set relations / tags / alarms
  776. *
  777. * @param Tinebase_Record_Interface $updatedRecord the just updated record
  778. * @param Tinebase_Record_Interface $record the update record
  779. * @param boolean $returnUpdatedRelatedData
  780. * @return Tinebase_Record_Interface
  781. */
  782. protected function _setRelatedData($updatedRecord, $record, $returnUpdatedRelatedData = FALSE)
  783. {
  784. if ($record->has('relations') && isset($record->relations) && is_array($record->relations)) {
  785. $type = $this->_getBackendType();
  786. Tinebase_Relations::getInstance()->setRelations($this->_modelName, $type, $updatedRecord->getId(), $record->relations);
  787. }
  788. if ($record->has('tags') && isset($record->tags) && (is_array($record->tags) || $record->tags instanceof Tinebase_Record_RecordSet)) {
  789. $updatedRecord->tags = $record->tags;
  790. Tinebase_Tags::getInstance()->setTagsOfRecord($updatedRecord);
  791. }
  792. if ($record->has('alarms') && isset($record->alarms)) {
  793. $this->_saveAlarms($record);
  794. }
  795. if ($returnUpdatedRelatedData) {
  796. $this->_getRelatedData($updatedRecord);
  797. }
  798. return $updatedRecord;
  799. }
  800. /**
  801. * set notes
  802. *
  803. * @param Tinebase_Record_Interface $_updatedRecord the just updated record
  804. * @param Tinebase_Record_Interface $_record the update record
  805. * @param string $_systemNoteType
  806. * @param Tinebase_Record_RecordSet $_currentMods
  807. */
  808. protected function _setNotes($_updatedRecord, $_record, $_systemNoteType = Tinebase_Model_Note::SYSTEM_NOTE_NAME_CREATED, $_currentMods = NULL)
  809. {
  810. if (! $_record->has('notes')) {
  811. return;
  812. }
  813. if (isset($_record->notes) && is_array($_record->notes)) {
  814. $_updatedRecord->notes = $_record->notes;
  815. Tinebase_Notes::getInstance()->setNotesOfRecord($_updatedRecord);
  816. }
  817. if(count($_currentMods) > 0 || $_systemNoteType != Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED){
  818. Tinebase_Notes::getInstance()->addSystemNote($_updatedRecord, Tinebase_Core::getUser(), $_systemNoteType, $_currentMods);
  819. }
  820. }
  821. /**
  822. * inspect update of one record (before update)
  823. *
  824. * @param Tinebase_Record_Interface $_record the update record
  825. * @param Tinebase_Record_Interface $_oldRecord the current persistent record
  826. * @return void
  827. */
  828. protected function _inspectBeforeUpdate($_record, $_oldRecord)
  829. {
  830. }
  831. /**
  832. * inspect update of one record (after update)
  833. *
  834. * @param Tinebase_Record_Interface $updatedRecord the just updated record
  835. * @param Tinebase_Record_Interface $record the update record
  836. * @param Tinebase_Record_Interface $currentRecord the current record (before update)
  837. * @return void
  838. */
  839. protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
  840. {
  841. }
  842. /**
  843. * update modlog / metadata / add systemnote for multiple records defined by filter
  844. *
  845. * NOTE: this should be done in a transaction because of the concurrency handling as
  846. * we want the same seq in the record and in the modlog
  847. *
  848. * @param Tinebase_Model_Filter_FilterGroup|array $_filterOrIds
  849. * @param array $_oldData
  850. * @param array $_newData
  851. */
  852. public function concurrencyManagementAndModlogMultiple($_filterOrIds, $_oldData, $_newData)
  853. {
  854. $ids = ($_filterOrIds instanceof Tinebase_Model_Filter_FilterGroup) ? $this->search($_filterOrIds, NULL, FALSE, TRUE, 'update') : $_filterOrIds;
  855. if (! is_array($ids) || count($ids) === 0) {
  856. return;
  857. }
  858. if ($this->_omitModLog !== TRUE) {
  859. $recordSeqs = $this->_backend->getPropertyByIds($ids, 'seq');
  860. list($currentAccountId, $currentTime) = Tinebase_Timemachine_ModificationLog::getCurrentAccountIdAndTime();
  861. $updateMetaData = array(
  862. 'last_modified_by' => $currentAccountId,
  863. 'last_modified_time' => $currentTime,
  864. 'seq' => new Zend_Db_Expr('seq + 1'),
  865. 'recordSeqs' => $recordSeqs, // is not written to DB yet
  866. );
  867. } else {
  868. $updateMetaData = array();
  869. }
  870. $this->_backend->updateMultiple($ids, $updateMetaData);
  871. if ($this->_omitModLog !== TRUE && is_object(Tinebase_Core::getUser())) {
  872. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  873. . ' Writing modlog for ' . count($ids) . ' records ...');
  874. $currentMods = Tinebase_Timemachine_ModificationLog::getInstance()->writeModLogMultiple($ids, $_oldData, $_newData, $this->_modelName, $this->_getBackendType(), $updateMetaData);
  875. Tinebase_Notes::getInstance()->addMultipleModificationSystemNotes($currentMods, $currentAccountId);
  876. }
  877. }
  878. /**
  879. * handles relations on update multiple
  880. * @param string $key
  881. * @param string $value
  882. * @throws Tinebase_Exception_Record_DefinitionFailure
  883. */
  884. protected function _handleRelations($key, $value)
  885. {
  886. $model = new $this->_modelName;
  887. $relConfig = $model::getRelatableConfig();
  888. unset($model);
  889. $getRelations = true;
  890. preg_match('/%(.+)-((.+)_Model_(.+))/', $key, $a);
  891. if(count($a) < 4) {
  892. throw new Tinebase_Exception_Record_DefinitionFailure('The relation to delete/set is not configured properly!');
  893. }
  894. // TODO: check config from foreign side
  895. // $relConfig = $a[2]::getRelatableConfig();
  896. $constrainsConfig = false;
  897. foreach($relConfig as $config) {
  898. if($config['relatedApp'] == $a[3] && $config['relatedModel'] == $a[4] && array_key_exists('config', $config) && is_array($config['config'])) {
  899. foreach($config['config'] as $constrain) {
  900. if($constrain['type'] == $a[1]) {
  901. $constrainsConfig = $constrain;
  902. break 2;
  903. }
  904. }
  905. }
  906. }
  907. if(!$constrainsConfig) {
  908. throw new Tinebase_Exception_Record_DefinitionFailure('No relation definition could be found for this model!');
  909. }
  910. $rel = array(
  911. 'own_model' => $this->_modelName,
  912. 'own_backend' => 'Sql',
  913. 'own_degree' =>array_key_exists('sibling', $constrainsConfig) ? $constrainsConfig['sibling'] : 'sibling',
  914. 'related_model' => $a[2],
  915. 'related_backend' => 'Sql',
  916. 'type' => array_key_exists('type', $constrainsConfig) ? $constrainsConfig['type'] : '-',
  917. 'remark' => array_key_exists('defaultRemark', $constrainsConfig) ? $constrainsConfig['defaultRemark'] : ' '
  918. );
  919. if(empty($value)) { // delete relations in iterator
  920. if(!$this->_removeRelations) $this->removeRelations = array();
  921. $this->_removeRelations[] = $rel;
  922. } else { // create relations in iterator
  923. if(! $this->_newRelations) $this->_newRelations = array();
  924. $rel['related_id'] = $value;
  925. $this->_newRelations[] = $rel;
  926. }
  927. }
  928. /**
  929. * update multiple records
  930. *
  931. * @param Tinebase_Model_Filter_FilterGroup $_filter
  932. * @param array $_data
  933. * @return integer number of updated records
  934. *
  935. * @todo add param $_returnFullResults (if false, do not return updated records in 'results')
  936. */
  937. public function updateMultiple($_filter, $_data)
  938. {
  939. $this->_checkRight('update');
  940. $this->checkFilterACL($_filter, 'update');
  941. $getRelations = false;
  942. $this->_newRelations = NULL;
  943. $this->_removeRelations = NULL;
  944. foreach ($_data as $key => $value) {
  945. if (stristr($key,'#')) {
  946. $_data['customfields'][substr($key,1)] = $value;
  947. unset($_data[$key]);
  948. }
  949. if (stristr($key, '%')) {
  950. $getRelations = true;
  951. $this->_handleRelations($key, $value);
  952. unset($_data[$key]);
  953. }
  954. }
  955. $this->_updateMultipleResult = array(
  956. 'results' => new Tinebase_Record_RecordSet($this->_modelName),
  957. 'exceptions' => new Tinebase_Record_RecordSet('Tinebase_Model_UpdateMultipleException'),
  958. 'totalcount' => 0,
  959. 'failcount' => 0,
  960. );
  961. $iterator = new Tinebase_Record_Iterator(array(
  962. 'iteratable' => $this,
  963. 'controller' => $this,
  964. 'filter' => $_filter,
  965. 'options' => array('getRelations' => $getRelations),
  966. 'function' => 'processUpdateMultipleIteration',
  967. ));
  968. $result = $iterator->iterate($_data);
  969. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updated ' . $this->_updateMultipleResult['totalcount'] . ' records.');
  970. if ($this->_clearCustomFieldsCache) {
  971. Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('customfields'));
  972. }
  973. return $this->_updateMultipleResult;
  974. }
  975. /**
  976. * iterate relations
  977. *
  978. * @param Tinebase_Record_Abstract $currentRecord
  979. * @return array
  980. */
  981. protected function _iterateRelations($currentRecord)
  982. {
  983. if(! $currentRecord->relations || get_class($currentRecord->relations) != 'Tinebase_Record_RecordSet') {
  984. $currentRecord->relations = new Tinebase_Record_RecordSet('Tinebase_Model_Relation');
  985. }
  986. // handle relations to remove
  987. if($this->_removeRelations) {
  988. if($currentRecord->relations->count()) {
  989. foreach($this->_removeRelations as $remRelation) {
  990. $removeRelations = $currentRecord->relations->filter('type', $remRelation['type']);
  991. $removeRelations = $removeRelations->filter('related_model', $remRelation['related_model']);
  992. $removeRelations = $removeRelations->filter('own_degree', $remRelation['own_degree']);
  993. $currentRecord->relations->removeRecords($removeRelations);
  994. }
  995. }
  996. }
  997. // handle new relations
  998. if($this->_newRelations) {
  999. $removeRelations = NULL;
  1000. foreach($this->_newRelations as $newRelation) {
  1001. $removeRelations = $currentRecord->relations->filter('type', $newRelation['type']);
  1002. $removeRelations = $removeRelations->filter('related_model', $newRelation['related_model']);
  1003. $removeRelations = $removeRelations->filter('own_degree', $newRelation['own_degree']);
  1004. $already = $removeRelations->filter('related_id', $newRelation['related_id']);
  1005. if($already->count() > 0) {
  1006. $removeRelations = NULL;
  1007. } else {
  1008. $newRelation['own_id'] = $currentRecord->getId();
  1009. $rel = new Tinebase_Model_Relation();
  1010. $rel->setFromArray($newRelation);
  1011. if($removeRelations) $currentRecord->relations->removeRecords($removeRelations);
  1012. $currentRecord->relations->addRecord($rel);
  1013. }
  1014. }
  1015. }
  1016. return $currentRecord->relations->toArray();
  1017. }
  1018. /**
  1019. * update multiple records in an iteration
  1020. * @see Tinebase_Record_Iterator / self::updateMultiple()
  1021. *
  1022. * @param Tinebase_Record_RecordSet $_records
  1023. * @param array $_data
  1024. */
  1025. public function processUpdateMultipleIteration($_records, $_data)
  1026. {
  1027. if (count($_records) === 0) {
  1028. return;
  1029. }
  1030. $bypassFilters = FALSE;
  1031. foreach ($_records as $currentRecord) {
  1032. $oldRecordArray = $currentRecord->toArray();
  1033. unset($oldRecordArray['relations']);
  1034. $data = array_merge($oldRecordArray, $_data);
  1035. if($this->_newRelations || $this->_removeRelations) {
  1036. $data['relations'] = $this->_iterateRelations($currentRecord);
  1037. }
  1038. try {
  1039. $record = new $this->_modelName($data, $bypassFilters);
  1040. $updatedRecord = $this->update($record, FALSE);
  1041. $this->_updateMultipleResult['results']->addRecord($updatedRecord);
  1042. $this->_updateMultipleResult['totalcount'] ++;
  1043. } catch (Tinebase_Exception_Record_Validation $e) {
  1044. if ($this->_updateMultipleValidateEachRecord === FALSE) {
  1045. throw $e;
  1046. }
  1047. $this->_updateMultipleResult['exceptions']->addRecord(new Tinebase_Model_UpdateMultipleException(array(
  1048. 'id' => $currentRecord->getId(),
  1049. 'exception' => $e,
  1050. 'record' => $currentRecord,
  1051. 'code' => $e->getCode(),
  1052. 'message' => $e->getMessage()
  1053. )));
  1054. $this->_updateMultipleResult['failcount'] ++;
  1055. }
  1056. if ($this->_updateMultipleValidateEachRecord === FALSE) {
  1057. // only validate the first record
  1058. $bypassFilters = TRUE;
  1059. }
  1060. }
  1061. }
  1062. /**
  1063. * Deletes a set of records.
  1064. *
  1065. * If one of the records could not be deleted, no record is deleted
  1066. *
  1067. * @param array $_ids array of record identifiers
  1068. * @return Tinebase_Record_RecordSet
  1069. * @throws Tinebase_Exception_NotFound|Tinebase_Exception
  1070. */
  1071. public function delete($_ids)
  1072. {
  1073. if ($_ids instanceof $this->_modelName) {
  1074. $_ids = (array)$_ids->getId();
  1075. }
  1076. $ids = $this->_inspectDelete((array) $_ids);
  1077. $records = $this->_backend->getMultiple((array)$ids);
  1078. if (count((array)$ids) != count($records)) {
  1079. Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Only ' . count($records) . ' of ' . count((array)$ids) . ' records exist.');
  1080. }
  1081. if (empty($records)) {
  1082. return $records;
  1083. }
  1084. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  1085. . ' Deleting ' . count($records) . ' of ' . $this->_modelName . ' records ...');
  1086. try {
  1087. $db = $this->_backend->getAdapter();
  1088. $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
  1089. $this->_checkRight('delete');
  1090. foreach ($records as $record) {
  1091. if ($this->sendNotifications()) {
  1092. $this->_getRelatedData($record);
  1093. }
  1094. $this->_deleteRecord($record);
  1095. }
  1096. Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
  1097. // send notifications
  1098. if ($this->sendNotifications()) {
  1099. foreach ($records as $record) {
  1100. $this->doSendNotifications($record, Tinebase_Core::getUser(), 'deleted');
  1101. }
  1102. }
  1103. } catch (Exception $e) {
  1104. Tinebase_TransactionManager::getInstance()->rollBack();
  1105. Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . print_r($e->getMessage(), true));
  1106. throw $e;
  1107. }
  1108. if ($this->_clearCustomFieldsCache) {
  1109. Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('customfields'));
  1110. }
  1111. // returns deleted records
  1112. return $records;
  1113. }
  1114. /**
  1115. * delete records by filter
  1116. *
  1117. * @param Tinebase_Model_Filter_FilterGroup $_filter
  1118. * @return Tinebase_Record_RecordSet
  1119. */
  1120. public function deleteByFilter(Tinebase_Model_Filter_FilterGroup $_filter)
  1121. {
  1122. $oldMaxExcecutionTime = ini_get('max_execution_time');
  1123. Tinebase_Core::setExecutionLifeTime(300); // 5 minutes
  1124. $ids = $this->search($_filter, NULL, FALSE, TRUE);
  1125. $deletedRecords = $this->delete($ids);
  1126. // reset max execution time to old value
  1127. Tinebase_Core::setExecutionLifeTime($oldMaxExcecutionTime);
  1128. return $deletedRecords;
  1129. }
  1130. /**
  1131. * inspects delete action
  1132. *
  1133. * @param array $_ids
  1134. * @return array of ids to actually delete
  1135. */
  1136. protected function _inspectDelete(array $_ids)
  1137. {
  1138. return $_ids;
  1139. }
  1140. /**
  1141. * move records to new container / folder / whatever
  1142. *
  1143. * @param mixed $_records (can be record set, filter, array, string)
  1144. * @param mixed $_target (string, container record, ...)
  1145. * @return array
  1146. */
  1147. public function move($_records, $_target, $_containerProperty = 'container_id')
  1148. {
  1149. $records = $this->_convertToRecordSet($_records);
  1150. $targetContainerId = ($_target instanceof Tinebase_Model_Container) ? $_target->getId() : $_target;
  1151. if ($this->_doContainerACLChecks) {
  1152. // check add grant in target container
  1153. if (! Tinebase_Core::getUser()->hasGrant($targetContainerId, Tinebase_Model_Grants::GRANT_ADD)) {
  1154. Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Permission denied to add records to container.');
  1155. throw new Tinebase_Exception_AccessDenied('You are not allowed to move records to this container');
  1156. }
  1157. // check delete grant in source container
  1158. $containerIdsWithDeleteGrant = Tinebase_Container::getInstance()->getContainerByACL(Tinebase_Core::getUser(), $this->_applicationName, Tinebase_Model_Grants::GRANT_DELETE, TRUE);
  1159. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->tra