PageRenderTime 57ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/Model/Behavior/SoftDeleteBehavior.php

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