PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/Model/Behavior/SoftDeleteBehavior.php

http://github.com/CakeDC/utils
PHP | 369 lines | 219 code | 32 blank | 118 comment | 41 complexity | 4918da0f3c2aaa9374e1f789c66d4123 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Copyright 2009 - 2013, Cake Development Corporation (http://cakedc.com)
  4. *
  5. * Licensed under The MIT License
  6. * Redistributions of files must retain the above copyright notice.
  7. *
  8. * @copyright Copyright 2009 - 2013, Cake Development Corporation (http://cakedc.com)
  9. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  10. */
  11. /**
  12. * Utils Plugin
  13. *
  14. * Utils Soft Delete Behavior
  15. *
  16. * @package utils
  17. * @subpackage utils.models.behaviors
  18. */
  19. class SoftDeleteBehavior extends ModelBehavior {
  20. /**
  21. * Default settings
  22. *
  23. * @var array $default
  24. */
  25. public $default = array(
  26. 'deleted' => 'deleted_date'
  27. );
  28. /**
  29. * Holds activity flags for models
  30. *
  31. * @var array $runtime
  32. */
  33. public $runtime = array();
  34. /**
  35. * Setup callback
  36. *
  37. * @param Model $model
  38. * @param array $settings
  39. */
  40. public function setup(Model $model, $settings = array()) {
  41. if (empty($settings)) {
  42. $settings = $this->default;
  43. } elseif (!is_array($settings)) {
  44. $settings = array($settings);
  45. }
  46. $error = 'SoftDeleteBehavior::setup(): model ' . $model->alias . ' has no field ';
  47. $fields = $this->_normalizeFields($model, $settings);
  48. foreach ($fields as $flag => $date) {
  49. if ($model->hasField($flag)) {
  50. if ($date && !$model->hasField($date)) {
  51. trigger_error($error . $date, E_USER_NOTICE);
  52. return;
  53. }
  54. continue;
  55. }
  56. trigger_error($error . $flag, E_USER_NOTICE);
  57. return;
  58. }
  59. $this->settings[$model->alias] = $fields;
  60. $this->softDelete($model, true);
  61. }
  62. /**
  63. * Before find callback
  64. *
  65. * @param Model $model
  66. * @param array $query
  67. * @return array
  68. */
  69. public function beforeFind(Model $model, $query) {
  70. $runtime = $this->runtime[$model->alias];
  71. if ($runtime) {
  72. if (!is_array($query['conditions'])) {
  73. $query['conditions'] = array();
  74. }
  75. $conditions = array_filter(array_keys($query['conditions']));
  76. $fields = $this->_normalizeFields($model);
  77. foreach ($fields as $flag => $date) {
  78. if (true === $runtime || $flag === $runtime) {
  79. if (!in_array($flag, $conditions) && !in_array($model->name . '.' . $flag, $conditions)) {
  80. $query['conditions'][$model->alias . '.' . $flag] = false;
  81. }
  82. if ($flag === $runtime) {
  83. break;
  84. }
  85. }
  86. }
  87. return $query;
  88. }
  89. }
  90. /**
  91. * Check if a record exists for the given id
  92. *
  93. * @param Model $model
  94. * @param id
  95. * @return mixed
  96. */
  97. public function existsAndNotDeleted(Model $model, $id) {
  98. if ($id === null) {
  99. $id = $model->getID();
  100. }
  101. if ($id === false) {
  102. return false;
  103. }
  104. $exists = $model->find('count', array('conditions' => array($model->alias . '.' . $model->primaryKey => $id)));
  105. return ($exists ? true : false);
  106. }
  107. /**
  108. * Before delete callback
  109. *
  110. * @param Model $model
  111. * @param boolean $cascade
  112. * @return boolean
  113. */
  114. public function beforeDelete(Model $model, $cascade = true) {
  115. $runtime = $this->runtime[$model->alias];
  116. if ($runtime) {
  117. if ($model->beforeDelete($cascade)) {
  118. return $this->delete($model, $model->id);
  119. }
  120. return false;
  121. }
  122. return true;
  123. }
  124. /**
  125. * Mark record as deleted
  126. *
  127. * @param object $model
  128. * @param integer $id
  129. * @return boolean
  130. */
  131. public function delete($model, $id) {
  132. $runtime = $this->runtime[$model->alias];
  133. $data = array();
  134. $fields = $this->_normalizeFields($model);
  135. foreach ($fields as $flag => $date) {
  136. if (true === $runtime || $flag === $runtime) {
  137. $data[$flag] = true;
  138. if ($date) {
  139. $data[$date] = date('Y-m-d H:i:s');
  140. }
  141. if ($flag === $runtime) {
  142. break;
  143. }
  144. }
  145. }
  146. $record = $model->find('first', array(
  147. 'fields' => $model->primaryKey,
  148. 'conditions' => array($model->primaryKey => $id),
  149. 'recursive' => -1
  150. ));
  151. if (!empty($record)) {
  152. $model->set($model->primaryKey, $id);
  153. unset($model->data[$model->alias]['modified']);
  154. unset($model->data[$model->alias]['updated']);
  155. return $model->save(array($model->alias => $data), false, array_keys($data));
  156. }
  157. return true;
  158. }
  159. /**
  160. * Mark record as not deleted
  161. *
  162. * @param object $model
  163. * @param integer $id
  164. * @return boolean
  165. */
  166. public function undelete($model, $id) {
  167. $runtime = $this->runtime[$model->alias];
  168. $this->softDelete($model, false);
  169. $data = array();
  170. $fields = $this->_normalizeFields($model);
  171. foreach ($fields as $flag => $date) {
  172. if (true === $runtime || $flag === $runtime) {
  173. $data[$flag] = false;
  174. if ($date) {
  175. $data[$date] = null;
  176. }
  177. if ($flag === $runtime) {
  178. break;
  179. }
  180. }
  181. }
  182. $model->create();
  183. $model->set($model->primaryKey, $id);
  184. $result = $model->save(array($model->alias => $data), false, array_keys($data));
  185. $this->softDelete($model, $runtime);
  186. return $result;
  187. }
  188. /**
  189. * Enable/disable SoftDelete functionality
  190. *
  191. * Usage from model:
  192. * $this->softDelete(false); deactivate this behavior for model
  193. * $this->softDelete('field_two'); enabled only for this flag field
  194. * $this->softDelete(true); enable again for all flag fields
  195. * $config = $this->softDelete(null); for obtaining current setting
  196. *
  197. * @param object $model
  198. * @param mixed $active
  199. * @return mixed if $active is null, then current setting/null, or boolean if runtime setting for model was changed
  200. */
  201. public function softDelete($model, $active) {
  202. if (is_null($active)) {
  203. return isset($this->runtime[$model->alias]) ? @$this->runtime[$model->alias] : null;
  204. }
  205. $result = !isset($this->runtime[$model->alias]) || $this->runtime[$model->alias] !== $active;
  206. $this->runtime[$model->alias] = $active;
  207. $this->_softDeleteAssociations($model, $active);
  208. return $result;
  209. }
  210. /**
  211. * Returns number of outdated softdeleted records prepared for purge
  212. *
  213. * @param object $model
  214. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  215. * @return integer
  216. */
  217. public function purgeDeletedCount($model, $expiration = '-90 days') {
  218. $this->softDelete($model, false);
  219. return $model->find('count', array(
  220. 'conditions' => $this->_purgeDeletedConditions($model, $expiration),
  221. 'recursive' => -1,
  222. 'contain' => array()
  223. )
  224. );
  225. }
  226. /**
  227. * Purge table
  228. *
  229. * @param object $model
  230. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  231. * @return boolean if there were some outdated records
  232. */
  233. public function purgeDeleted($model, $expiration = '-90 days') {
  234. $this->softDelete($model, false);
  235. $records = $model->find('all', array(
  236. 'conditions' => $this->_purgeDeletedConditions($model, $expiration),
  237. 'fields' => array($model->primaryKey),
  238. 'recursive' => -1));
  239. if ($records) {
  240. foreach ($records as $record) {
  241. $model->delete($record[$model->alias][$model->primaryKey]);
  242. }
  243. return true;
  244. }
  245. return false;
  246. }
  247. /**
  248. * Returns conditions for finding outdated records
  249. *
  250. * @param object $model
  251. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  252. * @return array
  253. */
  254. protected function _purgeDeletedConditions($model, $expiration = '-90 days') {
  255. $purgeDate = date('Y-m-d H:i:s', strtotime($expiration));
  256. $conditions = array();
  257. foreach ($this->settings[$model->alias] as $flag => $date) {
  258. $conditions[$model->alias . '.' . $flag] = true;
  259. if ($date) {
  260. $conditions[$model->alias . '.' . $date . ' <'] = $purgeDate;
  261. }
  262. }
  263. return $conditions;
  264. }
  265. /**
  266. * Return normalized field array
  267. *
  268. * @param object $model
  269. * @param array $settings
  270. * @return array
  271. */
  272. protected function _normalizeFields($model, $settings = array()) {
  273. if (empty($settings)) {
  274. $settings = $this->settings[$model->alias];
  275. }
  276. $result = array();
  277. foreach ($settings as $flag => $date) {
  278. if (is_numeric($flag)) {
  279. $flag = $date;
  280. $date = false;
  281. }
  282. $result[$flag] = $date;
  283. }
  284. return $result;
  285. }
  286. /**
  287. * Modifies conditions of hasOne and hasMany associations
  288. *
  289. * If multiple delete flags are configured for model, then $active=true doesn't
  290. * do anything - you have to alter conditions in association definition
  291. *
  292. * @param object $model
  293. * @param mixed $active
  294. */
  295. protected function _softDeleteAssociations($model, $active) {
  296. if (empty($model->belongsTo)) {
  297. return;
  298. }
  299. $fields = array_keys($this->_normalizeFields($model));
  300. $parentModels = array_keys($model->belongsTo);
  301. foreach ($parentModels as $parentModel) {
  302. foreach (array('hasOne', 'hasMany') as $assocType) {
  303. if (empty($model->{$parentModel}->{$assocType})) {
  304. continue;
  305. }
  306. foreach ($model->{$parentModel}->{$assocType} as $assoc => $assocConfig) {
  307. $modelName = empty($assocConfig['className']) ? $assoc : @$assocConfig['className'];
  308. if ((!empty($model->plugin) && strstr($model->plugin . '.', $model->alias) === false ? $model->plugin . '.' : '') . $model->alias !== $modelName) {
  309. continue;
  310. }
  311. $conditions =& $model->{$parentModel}->{$assocType}[$assoc]['conditions'];
  312. if (!is_array($conditions)) {
  313. $model->{$parentModel}->{$assocType}[$assoc]['conditions'] = array();
  314. }
  315. $multiFields = 1 < count($fields);
  316. foreach ($fields as $field) {
  317. if ($active) {
  318. if (!isset($conditions[$field]) && !isset($conditions[$assoc . '.' . $field])) {
  319. if (is_string($active)) {
  320. if ($field == $active) {
  321. $conditions[$assoc . '.' . $field] = false;
  322. } elseif (isset($conditions[$assoc . '.' . $field])) {
  323. unset($conditions[$assoc . '.' . $field]);
  324. }
  325. } elseif (!$multiFields) {
  326. $conditions[$assoc . '.' . $field] = false;
  327. }
  328. }
  329. } elseif (isset($conditions[$assoc . '.' . $field])) {
  330. unset($conditions[$assoc . '.' . $field]);
  331. }
  332. }
  333. }
  334. }
  335. }
  336. }
  337. }