1: <?php
2:
3: App::import('Lib', 'StuffreposBase.ArrayUtil');
4:
5: class JournalizedBehavior extends ModelBehavior {
6:
7: private $rowsBeforeSave = array();
8:
9: public function setup(\Model $model, $config = array()) {
10: parent::setup($model, $config);
11: $model->bindModel(
12: array(
13: 'hasMany' => array(
14: 'Journal' => array(
15: 'className' => 'Journaling.Journal',
16: 'foreignKey' => 'journalized_id',
17: 'conditions' => array(
18: 'Journal.journalized_type' => $model->name
19: ),
20: 'dependent' => false,
21: )
22: )
23: ),
24: false
25: );
26: }
27:
28: public function beforeSave(\Model $model, $options = array()) {
29: if (!parent::beforeSave($model)) {
30: return false;
31: }
32:
33: $this->storeRowPreviousValues($model);
34: }
35:
36: public function afterSave(\Model $model, $created, $options = array()) {
37: if (!parent::afterSave($model, $created, $options)) {
38: return false;
39: }
40:
41: if ($created) {
42: return $this->createJournal(
43: $model
44: , 'create'
45: , $this->emptyFields($model)
46: , $model->data
47: );
48: } else {
49: return $this->createJournal(
50: $model
51: , 'update'
52: , $this->rowBeforeSave($model)
53: , $model->data
54: );
55: }
56: }
57:
58: public function beforeDelete(\Model $model, $cascade = true) {
59: if (!parent::beforeDelete($model, $cascade)) {
60: return false;
61: }
62:
63: $this->storeRowPreviousValues($model);
64: }
65:
66: public function afterDelete(\Model $model) {
67: parent::afterDelete($model);
68:
69: return $this->createJournal(
70: $model
71: , 'delete'
72: , $this->rowBeforeSave($model)
73: , $this->emptyFields($model)
74: );
75: }
76:
77: private function rowBeforeSave(Model $model) {
78: if ($this->primaryKeyValue($model, false)) {
79: $path = array($model->name, $this->primaryKeyValue($model));
80:
81: if (ArrayUtil::hasArrayIndex($this->rowsBeforeSave, $path)) {
82: return ArrayUtil::arrayIndex($this->rowsBeforeSave, $path);
83: } else {
84: throw new Exception(
85: "Previous value not found. " . print_r(array(
86: 'modelName' => $model->name,
87: 'modelAlias' => $model->alias,
88: 'rowsBeforeSave' => $this->rowsBeforeSave,
89: 'primaryKeyValue' => $this->primaryKeyValue($model),
90: 'path' => $path
91: ), true));
92: }
93: } else {
94: throw new Exception("No primary key found. {$model->alias}: {$model->name}.");
95: }
96: }
97:
98: private function storeRowPreviousValues(Model $model) {
99: if ($this->primaryKeyValue($model, false)) {
100: $row = $model->find('first', array(
101: 'conditions' => array(
102: "{$model->alias}.{$model->primaryKey}" => $this->primaryKeyValue($model),
103: )
104: ));
105:
106: if (!$row) {
107: throw new Exception("Row not found.");
108: }
109:
110: $this->rowsBeforeSave[$model->name][$this->primaryKeyValue($model)] =
111: $row;
112: }
113: }
114:
115: private function primaryKeyValue(Model $model, $required = true) {
116: if (!empty($model->data[$model->alias][$model->primaryKey])) {
117: return $model->data[$model->alias][$model->primaryKey];
118: } else if (!empty($model->id)) {
119: return $model->id;
120: } else if ($required) {
121: throw new Exception("{$model->alias} has no primary key current value.");
122: } else {
123: return null;
124: }
125: }
126:
127: 128: 129: 130: 131: 132: 133: 134: 135:
136: private function createJournal(Model $model, $type, $oldValues, $values) {
137: $diffValues = $this->diffValues($model, $oldValues, $values);
138:
139: if ($type == 'update' && empty($diffValues)) {
140: return true;
141: }
142:
143: $model->Journal->create();
144: if (!$model->Journal->save(array(
145: 'Journal' => array(
146: 'type' => $type,
147: 'journalized_type' => $model->name,
148: 'journalized_id' => $model->id,
149: )
150: ))) {
151: throw new Exception("Could not save Journal. " . print_r($model->Journal->validationErrors, true));
152: }
153:
154: foreach ($diffValues as $field => $change) {
155: $model->Journal->JournalDetail->create();
156: if (!$model->Journal->JournalDetail->save(array(
157: 'JournalDetail' => array(
158: 'property' => $field,
159: 'old_value' => $change['old'],
160: 'value' => $change['current'],
161: 'journal_id' => $model->Journal->id,
162: )
163: ))) {
164: throw new Exception("Could not save JournalDetail. " . print_r($model->Journal->JournalDetail->validationErrors, true));
165: }
166: }
167:
168: return true;
169: }
170:
171: public function diffValues(Model $model, $oldValues, $values) {
172: $changedFields = array();
173:
174: foreach ($this->journalizedFields($model) as $field) {
175: $index = array($model->alias, $field);
176: $old = ArrayUtil::hasArrayIndex($oldValues, $index) ?
177: ArrayUtil::arrayIndex($oldValues, $index) :
178: null;
179: $current = ArrayUtil::hasArrayIndex($values, $index) ?
180: ArrayUtil::arrayIndex($values, $index) :
181: $old;
182:
183: if ($old === null && $current === null) {
184: $changed = false;
185: } else if ($old === null || $current === null) {
186: $changed = true;
187: } else {
188: $changed = $old != $current;
189: }
190:
191: if ($changed) {
192: $changedFields[$field] = compact('old', 'current');
193: }
194: }
195:
196: return $changedFields;
197: }
198:
199: public function emptyFields(Model $model) {
200: $fields = array();
201:
202: foreach ($this->journalizedFields($model) as $field) {
203: $fields[$field] = null;
204: }
205:
206: return array(
207: $model->alias => $fields
208: );
209: }
210:
211: private function journalizedFields(Model $model) {
212: $fields = array();
213: foreach (array_keys($model->schema()) as $field) {
214: switch ($field) {
215: case $model->primaryKey:
216: case 'created':
217: case 'modified':
218: case 'updated':
219: continue;
220:
221: default:
222: $fields[] = $field;
223: }
224: }
225: return $fields;
226: }
227:
228: }