/models/behaviors/soft_delete.php

https://github.com/sc0ttyd/utils · PHP · 330 lines · 190 code · 29 blank · 111 comment · 35 complexity · 5e15747523dd478b55afe512138c6884 MD5 · raw file

  1. <?php
  2. /**
  3. * Copyright 2007-2010, 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 2007-2010, 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('deleted' => 'deleted_date');
  26. /**
  27. * Holds activity flags for models
  28. *
  29. * @var array $runtime
  30. */
  31. public $runtime = array();
  32. /**
  33. * Setup callback
  34. *
  35. * @param object $model
  36. * @param array $settings
  37. */
  38. public function setup(&$model, $settings = array()) {
  39. if (empty($settings)) {
  40. $settings = $this->default;
  41. } elseif (!is_array($settings)) {
  42. $settings = array($settings);
  43. }
  44. $error = 'SoftDeleteBehavior::setup(): model ' . $model->alias . ' has no field ';
  45. $fields = $this->_normalizeFields($model, $settings);
  46. foreach ($fields as $flag => $date) {
  47. if ($model->hasField($flag)) {
  48. if ($date && !$model->hasField($date)) {
  49. trigger_error($error . $date, E_USER_NOTICE);
  50. return;
  51. }
  52. continue;
  53. }
  54. trigger_error($error . $flag, E_USER_NOTICE);
  55. return;
  56. }
  57. $this->settings[$model->alias] = $fields;
  58. $this->softDelete($model, true);
  59. }
  60. /**
  61. * Before find callback
  62. *
  63. * @param object $model
  64. * @param array $query
  65. * @return array
  66. */
  67. public function beforeFind(&$model, $query) {
  68. $runtime = $this->runtime[$model->alias];
  69. if ($runtime) {
  70. $query['conditions'] = ife(is_array($query['conditions']), $query['conditions'], array());
  71. $conditions = array_filter(array_keys($query['conditions']));
  72. $fields = $this->_normalizeFields($model);
  73. foreach ($fields as $flag => $date) {
  74. if (true === $runtime || $flag === $runtime) {
  75. if (!in_array($flag, $conditions) && !in_array($model->name . '.' . $flag, $conditions)) {
  76. $query['conditions'][$model->alias . '.' . $flag] = false;
  77. }
  78. if ($flag === $runtime) {
  79. break;
  80. }
  81. }
  82. }
  83. return $query;
  84. }
  85. }
  86. /**
  87. * Before delete callback
  88. *
  89. * @param object $model
  90. * @param array $query
  91. * @return boolean
  92. */
  93. public function beforeDelete(&$model) {
  94. $runtime = $this->runtime[$model->alias];
  95. if ($runtime) {
  96. $this->delete($model, $model->id);
  97. return false;
  98. }
  99. }
  100. /**
  101. * Mark record as deleted
  102. *
  103. * @param object $model
  104. * @param integer $id
  105. * @return boolean
  106. */
  107. public function delete(&$model, $id) {
  108. $runtime = $this->runtime[$model->alias];
  109. $data = array();
  110. $fields = $this->_normalizeFields($model);
  111. foreach ($fields as $flag => $date) {
  112. if (true === $runtime || $flag === $runtime) {
  113. $data[$flag] = true;
  114. if ($date) {
  115. $data[$date] = date('Y-m-d H:i:s');
  116. }
  117. if ($flag === $runtime) {
  118. break;
  119. }
  120. }
  121. }
  122. $model->create();
  123. $model->set($model->primaryKey, $id);
  124. return $model->save(array($model->alias => $data), false, array_keys($data));
  125. }
  126. /**
  127. * Mark record as not deleted
  128. *
  129. * @param object $model
  130. * @param integer $id
  131. * @return boolean
  132. */
  133. public function undelete(&$model, $id) {
  134. $runtime = $this->runtime[$model->alias];
  135. $this->softDelete($model, false);
  136. $data = array();
  137. $fields = $this->_normalizeFields($model);
  138. foreach ($fields as $flag => $date) {
  139. if (true === $runtime || $flag === $runtime) {
  140. $data[$flag] = false;
  141. if ($date) {
  142. $data[$date] = null;
  143. }
  144. if ($flag === $runtime) {
  145. break;
  146. }
  147. }
  148. }
  149. $model->create();
  150. $model->set($model->primaryKey, $id);
  151. $result = $model->save(array($model->alias => $data), false, array_keys($data));
  152. $this->softDelete($model, $runtime);
  153. return $result;
  154. }
  155. /**
  156. * Enable/disable SoftDelete functionality
  157. *
  158. * Usage from model:
  159. * $this->softDelete(false); deactivate this behavior for model
  160. * $this->softDelete('field_two'); enabled only for this flag field
  161. * $this->softDelete(true); enable again for all flag fields
  162. * $config = $this->softDelete(null); for obtaining current setting
  163. *
  164. * @param object $model
  165. * @param mixed $active
  166. * @return mixed if $active is null, then current setting/null, or boolean if runtime setting for model was changed
  167. */
  168. public function softDelete(&$model, $active) {
  169. if (is_null($active)) {
  170. return ife(isset($this->runtime[$model->alias]), @$this->runtime[$model->alias], null);
  171. }
  172. $result = !isset($this->runtime[$model->alias]) || $this->runtime[$model->alias] !== $active;
  173. $this->runtime[$model->alias] = $active;
  174. $this->_softDeleteAssociations($model, $active);
  175. return $result;
  176. }
  177. /**
  178. * Returns number of outdated softdeleted records prepared for purge
  179. *
  180. * @param object $model
  181. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  182. * @return integer
  183. */
  184. public function purgeDeletedCount(&$model, $expiration = '-90 days') {
  185. $this->softDelete($model, false);
  186. return $model->find('count', array(
  187. 'conditions' => $this->_purgeDeletedConditions($model, $expiration),
  188. 'recursive' => -1));
  189. }
  190. /**
  191. * Purge table
  192. *
  193. * @param object $model
  194. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  195. * @return boolean if there were some outdated records
  196. */
  197. public function purgeDeleted(&$model, $expiration = '-90 days') {
  198. $this->softDelete($model, false);
  199. $records = $model->find('all', array(
  200. 'conditions' => $this->_purgeDeletedConditions($model, $expiration),
  201. 'fields' => array($model->primaryKey),
  202. 'recursive' => -1));
  203. if ($records) {
  204. foreach ($records as $record) {
  205. $model->delete($record[$model->alias][$model->primaryKey]);
  206. }
  207. return true;
  208. }
  209. return false;
  210. }
  211. /**
  212. * Returns conditions for finding outdated records
  213. *
  214. * @param object $model
  215. * @param mixed $expiration anything parseable by strtotime(), by default '-90 days'
  216. * @return array
  217. */
  218. protected function _purgeDeletedConditions(&$model, $expiration = '-90 days') {
  219. $purgeDate = date('Y-m-d H:i:s', strtotime($expiration));
  220. $conditions = array();
  221. foreach ($this->settings[$model->alias] as $flag => $date) {
  222. $conditions[$model->alias . '.' . $flag] = true;
  223. if ($date) {
  224. $conditions[$model->alias . '.' . $date . ' <'] = $purgeDate;
  225. }
  226. }
  227. return $conditions;
  228. }
  229. /**
  230. * Return normalized field array
  231. *
  232. * @param object $model
  233. * @param array $settings
  234. * @return array
  235. */
  236. protected function _normalizeFields(&$model, $settings = array()) {
  237. $settings = ife(empty($settings), @$this->settings[$model->alias], $settings);
  238. $result = array();
  239. foreach ($settings as $flag => $date) {
  240. if (is_numeric($flag)) {
  241. $flag = $date;
  242. $date = false;
  243. }
  244. $result[$flag] = $date;
  245. }
  246. return $result;
  247. }
  248. /**
  249. * Modifies conditions of hasOne and hasMany associations
  250. *
  251. * If multiple delete flags are configured for model, then $active=true doesn't
  252. * do anything - you have to alter conditions in association definition
  253. *
  254. * @param object $model
  255. * @param mixed $active
  256. */
  257. protected function _softDeleteAssociations(&$model, $active) {
  258. if (empty($model->belongsTo)) {
  259. return;
  260. }
  261. $fields = array_keys($this->_normalizeFields($model));
  262. $parentModels = array_keys($model->belongsTo);
  263. foreach ($parentModels as $parentModel) {
  264. foreach (array('hasOne', 'hasMany') as $assocType) {
  265. if (empty($model->{$parentModel}->{$assocType})) {
  266. continue;
  267. }
  268. foreach ($model->{$parentModel}->{$assocType} as $assoc => $assocConfig) {
  269. $modelName = ife(empty($assocConfig['className']), $assoc, @$assocConfig['className']);
  270. if ($model->alias != $modelName) {
  271. continue;
  272. }
  273. $conditions =& $model->{$parentModel}->{$assocType}[$assoc]['conditions'];
  274. if (!is_array($conditions)) {
  275. $model->{$parentModel}->{$assocType}[$assoc]['conditions'] = array();
  276. }
  277. $multiFields = 1 < count($fields);
  278. foreach ($fields as $field) {
  279. if ($active) {
  280. if (!isset($conditions[$field]) && !isset($conditions[$assoc . '.' . $field])) {
  281. if (is_string($active)) {
  282. if ($field == $active) {
  283. $conditions[$assoc . '.' . $field] = false;
  284. }
  285. elseif (isset($conditions[$assoc . '.' . $field])) {
  286. unset($conditions[$assoc . '.' . $field]);
  287. }
  288. }
  289. elseif (!$multiFields) {
  290. $conditions[$assoc . '.' . $field] = false;
  291. }
  292. }
  293. } elseif (isset($conditions[$assoc . '.' . $field])) {
  294. unset($conditions[$assoc . '.' . $field]);
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. }