1
|
|
|
<?php
|
2
|
|
|
/**
|
3
|
|
|
* @author Sergey Glagolev <[email protected]>, Nikita Melnikov <[email protected]>
|
4
|
|
|
* @link https://github.com/shogodev/argilla/
|
5
|
|
|
* @copyright Copyright © 2003-2014 Shogo
|
6
|
|
|
* @license http://argilla.ru/LICENSE
|
7
|
|
|
* @package backend.components.db
|
8
|
|
|
*/
|
9
|
|
|
abstract class BActiveRecord extends CActiveRecord implements IHasFrontendModel
|
10
|
|
|
{
|
11
|
|
|
protected $_hints;
|
12
|
|
|
|
13
|
|
|
protected $_popupHints;
|
14
|
|
|
|
15
|
|
|
/**
|
16
|
|
|
* @param string $className
|
17
|
|
|
*
|
18
|
|
|
* @return BActiveRecord|CActiveRecord
|
19
|
|
|
*/
|
20
|
63 |
|
public static function model($className = __CLASS__)
|
21
|
|
|
{
|
22
|
63 |
|
return parent::model(get_called_class());
|
23
|
|
|
}
|
24
|
|
|
|
25
|
|
|
/**
|
26
|
|
|
* Получение CHtml::listData по стандартным ключам $key => $value
|
27
|
|
|
*
|
28
|
|
|
* @param string $key
|
29
|
|
|
* @param string|callable $value
|
30
|
|
|
* @param CDbCriteria $criteria
|
31
|
|
|
*
|
32
|
|
|
* @return array
|
33
|
|
|
*/
|
34
|
3 |
|
public static function listData($key = 'id', $value = 'name', CDbCriteria $criteria = null)
|
35
|
|
|
{
|
36
|
2 |
|
if( is_null($criteria) )
|
37
|
2 |
|
$criteria = new CDbCriteria();
|
38
|
|
|
/**
|
39
|
|
|
* @var BActiveRecord
|
40
|
|
|
*/
|
41
|
2 |
|
$model = static::model();
|
42
|
|
|
|
43
|
2 |
|
if( empty($criteria->order) && Arr::get($model->tableSchema->columns, 'name') )
|
44
|
2 |
|
$criteria->order = $model->getTableAlias(false, false).'.name';
|
45
|
|
|
|
46
|
2 |
|
$data = array();
|
47
|
2 |
|
foreach($model->findAll($criteria) as $entry)
|
48
|
|
|
{
|
49
|
2 |
|
$data[$entry->{$key}] = is_callable($value) ? $value($entry) : $entry->{$value};
|
50
|
3 |
|
}
|
51
|
|
|
|
52
|
2 |
|
return $data;
|
53
|
|
|
}
|
54
|
|
|
|
55
|
|
|
/**
|
56
|
|
|
* Получение имени таблицы по имени текущей модели в виде {{class_name}}
|
57
|
|
|
*
|
58
|
|
|
* @return string
|
59
|
|
|
*/
|
60
|
55 |
|
public function tableName()
|
61
|
|
|
{
|
62
|
55 |
|
return '{{'.Utils::toSnakeCase(BApplication::cutClassPrefix(get_class($this))).'}}';
|
63
|
|
|
}
|
64
|
|
|
|
65
|
|
|
/**
|
66
|
|
|
* @param bool $runValidation
|
67
|
|
|
* @param null $attributes
|
68
|
|
|
* @return bool
|
69
|
|
|
*/
|
70
|
29 |
|
public function save($runValidation = true, $attributes = null)
|
71
|
|
|
{
|
72
|
29 |
|
if( $this->isNestedSetModel() )
|
73
|
29 |
|
{
|
74
|
|
|
if( $this->isNewRecord )
|
75
|
|
|
{
|
76
|
|
|
$modelName = get_class($this);
|
77
|
|
|
$parent = $modelName::model()->findByPk($this->parent);
|
78
|
1 |
|
$result = $this->appendTo($parent);
|
79
|
1 |
|
}
|
80
|
1 |
|
else
|
81
|
1 |
|
$result = $this->saveNode($runValidation, $attributes);
|
82
|
|
|
|
83
|
|
|
return $result;
|
84
|
|
|
}
|
85
|
|
|
|
86
|
29 |
|
return parent::save($runValidation, $attributes);
|
87
|
|
|
}
|
88
|
|
|
|
89
|
|
|
/**
|
90
|
|
|
* Сохраняем данные в моделях, связанных через отношение
|
91
|
|
|
*
|
92
|
|
|
* @param $relationName
|
93
|
|
|
* @param array $relatedData
|
94
|
|
|
* @param bool $ignoreEmptyItems
|
95
|
|
|
*
|
96
|
|
|
* @return bool
|
97
|
|
|
* @throws CHttpException
|
98
|
|
|
*/
|
99
|
|
|
public function saveRelatedModels($relationName, array $relatedData, $ignoreEmptyItems = true)
|
100
|
|
|
{
|
101
|
|
|
$models = $this->prepareModels($relationName, $relatedData, $ignoreEmptyItems);
|
102
|
|
|
|
103
|
|
|
if( !$this->validateRelatedModels($models) )
|
104
|
|
|
return false;
|
105
|
|
|
|
106
|
|
|
foreach($models as $id => $model)
|
107
|
|
|
{
|
108
|
|
|
if( !$model->save(false) )
|
109
|
|
|
throw new CHttpException(500, "Не удается сохранить зависимую модель ".get_class($model));
|
110
|
|
|
}
|
111
|
|
|
|
112
|
|
|
return true;
|
113
|
|
|
}
|
114
|
|
|
|
115
|
|
|
/**
|
116
|
|
|
* @param BActiveRecord[] $models
|
117
|
|
|
*
|
118
|
|
|
* @return bool
|
119
|
|
|
*/
|
120
|
|
|
public function validateRelatedModels(array $models)
|
121
|
|
|
{
|
122
|
|
|
$validationError = false;
|
123
|
|
|
|
124
|
|
|
foreach($models as $id => $model)
|
125
|
|
|
{
|
126
|
|
|
if( !$model->validate() )
|
127
|
|
|
{
|
128
|
|
|
foreach($model->errors as $errors)
|
129
|
|
|
foreach($errors as $value)
|
130
|
|
|
$this->addError('relatedModelErrors', $value);
|
131
|
|
|
|
132
|
|
|
$validationError = true;
|
133
|
|
|
}
|
134
|
|
|
}
|
135
|
|
|
|
136
|
|
|
return !$validationError;
|
137
|
|
|
}
|
138
|
|
|
|
139
|
|
|
/**
|
140
|
|
|
* @param $relationName
|
141
|
|
|
* @param array $relatedData
|
142
|
|
|
* @param bool|true $ignoreEmptyItems
|
143
|
|
|
*
|
144
|
|
|
* @return BActiveRecord[]
|
145
|
|
|
*/
|
146
|
|
|
public function prepareModels($relationName, array $relatedData, $ignoreEmptyItems = true)
|
147
|
|
|
{
|
148
|
|
|
$models = array();
|
149
|
|
|
$relation = $this->getActiveRelation($relationName);
|
150
|
|
|
$className = $relation ? $relation->className : null;
|
151
|
|
|
|
152
|
|
|
if( is_null($className) )
|
153
|
|
|
return $models;
|
154
|
|
|
|
155
|
|
|
if( is_array(reset($relatedData)) )
|
156
|
|
|
{
|
157
|
|
|
foreach($relatedData as $id => $attributes)
|
158
|
|
|
{
|
159
|
|
|
$value = trim(implode("", $attributes));
|
160
|
|
|
|
161
|
|
|
if( empty($value) && $ignoreEmptyItems )
|
162
|
|
|
continue;
|
163
|
|
|
|
164
|
|
|
$models[$id] = $this->prepareModel($className, $relation, $id, $attributes);
|
165
|
|
|
}
|
166
|
|
|
}
|
167
|
|
|
else
|
168
|
|
|
{
|
169
|
|
|
if( $relatedModel = $this->{$relationName} )
|
170
|
|
|
{
|
171
|
|
|
$models[$relatedModel->primaryKey] = $this->prepareModel($className, $relation, $relatedModel->primaryKey, $relatedData);
|
172
|
|
|
}
|
173
|
|
|
else
|
174
|
|
|
{
|
175
|
|
|
$models[] = $this->prepareModel($className, $relation, null, $relatedData);
|
176
|
|
|
}
|
177
|
|
|
}
|
178
|
|
|
|
179
|
|
|
return $models;
|
180
|
|
|
}
|
181
|
|
|
|
182
|
|
|
/**
|
183
|
|
|
* @param string $className
|
184
|
|
|
* @param CActiveRelation $relation
|
185
|
|
|
* @param integer $id
|
186
|
|
|
* @param array $attributes
|
187
|
|
|
*
|
188
|
|
|
* @return BActiveRecord
|
189
|
|
|
*/
|
190
|
5 |
|
private function prepareModel($className, $relation, $id, $attributes)
|
191
|
|
|
{
|
192
|
|
|
/**
|
193
|
|
|
* @var BActiveRecord $model
|
194
|
|
|
*/
|
195
|
|
|
$model = $className::model()->findByPk($id);
|
196
|
|
|
|
197
|
|
|
if( !$model )
|
198
|
|
|
{
|
199
|
|
|
$model = new $className();
|
200
|
|
|
$model->{$relation->foreignKey} = $this->getPrimaryKey();
|
201
|
|
|
}
|
202
|
|
|
|
203
|
5 |
|
$model->setAttributes(Arr::trim($attributes));
|
204
|
|
|
|
205
|
5 |
|
return $model;
|
206
|
|
|
}
|
207
|
|
|
|
208
|
8 |
|
public function getFormId()
|
209
|
|
|
{
|
210
|
8 |
|
return get_called_class().'-form';
|
211
|
|
|
}
|
212
|
|
|
|
213
|
5 |
|
public function yesNoList()
|
214
|
|
|
{
|
215
|
|
|
return array(
|
216
|
|
|
array('id' => 1, 'name' => 'Да'),
|
217
|
5 |
|
array('id' => 0, 'name' => 'Нет'),
|
218
|
5 |
|
);
|
219
|
|
|
}
|
220
|
|
|
|
221
|
|
|
public function getImageTypes()
|
222
|
|
|
{
|
223
|
|
|
return array();
|
224
|
|
|
}
|
225
|
|
|
|
226
|
14 |
|
public function relations()
|
227
|
|
|
{
|
228
|
|
|
return array(
|
229
|
14 |
|
'associations' => array(self::HAS_MANY, 'BAssociation', 'src_id', 'on' => 'src="'.get_class($this).'"'),
|
230
|
14 |
|
);
|
231
|
|
|
}
|
232
|
|
|
|
233
|
|
|
/**
|
234
|
|
|
* @param CDbCriteria $criteria
|
235
|
|
|
*
|
236
|
|
|
* @return BActiveDataProvider
|
237
|
|
|
*/
|
238
|
7 |
|
public function search(CDbCriteria $criteria = null)
|
239
|
|
|
{
|
240
|
7 |
|
if( !isset($criteria) )
|
241
|
7 |
|
$criteria = new CDbCriteria();
|
242
|
|
|
|
243
|
7 |
|
$this->onBeforeSearch(new CEvent($this, array('criteria' => $criteria)));
|
244
|
|
|
|
245
|
7 |
|
$criteria = $this->getSearchCriteria($criteria);
|
246
|
7 |
|
$params = $this->getSearchParams();
|
247
|
|
|
|
248
|
7 |
|
return new BActiveDataProvider($this, CMap::mergeArray(array(
|
249
|
7 |
|
'criteria' => $criteria), $params
|
250
|
7 |
|
));
|
251
|
|
|
}
|
252
|
|
|
|
253
|
|
|
/**
|
254
|
|
|
* @param $event
|
255
|
|
|
*/
|
256
|
7 |
|
public function onBeforeSearch($event)
|
257
|
|
|
{
|
258
|
7 |
|
$this->raiseEvent('onBeforeSearch', $event);
|
259
|
7 |
|
}
|
260
|
|
|
|
261
|
7 |
|
public function attributeLabels()
|
262
|
|
|
{
|
263
|
|
|
return array(
|
264
|
7 |
|
'id' => '#',
|
265
|
7 |
|
'section_id' => 'Раздел',
|
266
|
7 |
|
'category_id' => 'Категория',
|
267
|
7 |
|
'type_id' => 'Тип',
|
268
|
7 |
|
'collection_id' => 'Коллекция',
|
269
|
7 |
|
'country_id' => 'Страна',
|
270
|
7 |
|
'year_id' => 'Год',
|
271
|
7 |
|
'position' => 'Позиция',
|
272
|
7 |
|
'url' => 'Url',
|
273
|
7 |
|
'reference' => 'Ссылка',
|
274
|
7 |
|
'date' => 'Дата',
|
275
|
7 |
|
'notice' => 'Анонс',
|
276
|
7 |
|
'name' => 'Заголовок',
|
277
|
7 |
|
'articul' => 'Артикул',
|
278
|
7 |
|
'content' => 'Полный текст',
|
279
|
7 |
|
'img' => 'Изображение',
|
280
|
7 |
|
'upload' => 'Изображения',
|
281
|
7 |
|
'template' => 'Шаблон',
|
282
|
7 |
|
'visible' => 'Вид',
|
283
|
7 |
|
'main' => 'На главной',
|
284
|
7 |
|
'novelty' => 'Новинка',
|
285
|
7 |
|
'discount' => 'Скидка',
|
286
|
7 |
|
'delivery' => 'Доставка',
|
287
|
7 |
|
'dump' => 'Наличие',
|
288
|
7 |
|
'siblings' => 'Соседи',
|
289
|
7 |
|
'children' => 'Потомки',
|
290
|
7 |
|
'menu' => 'В меню',
|
291
|
7 |
|
'sitemap' => 'На карту сайта',
|
292
|
7 |
|
'tag' => 'Теги',
|
293
|
7 |
|
'price' => 'Цена',
|
294
|
7 |
|
'price_old' => 'Старая цена',
|
295
|
7 |
|
'key' => 'Ключ',
|
296
|
7 |
|
'location' => 'Расположение',
|
297
|
7 |
|
'group' => 'Группа',
|
298
|
7 |
|
'date_from' => 'Дата с...',
|
299
|
7 |
|
'date_to' => 'по ...',
|
300
|
7 |
|
'comment' => 'Комментарий',
|
301
|
7 |
|
'sum' => 'Сумма',
|
302
|
7 |
|
'email' => 'E-mail',
|
303
|
7 |
|
'phone' => 'Телефон',
|
304
|
7 |
|
'address' => 'Адрес',
|
305
|
7 |
|
'date_create' => 'Дата создания',
|
306
|
7 |
|
'status' => 'Статус',
|
307
|
7 |
|
'subject' => 'Тема',
|
308
|
7 |
|
'message' => 'Сообщение',
|
309
|
7 |
|
'password' => 'Пароль',
|
310
|
7 |
|
'type' => 'Тип',
|
311
|
7 |
|
'sysname' => 'Системное имя',
|
312
|
7 |
|
'model' => 'Модель',
|
313
|
|
|
'attribute' => 'Атрибут'
|
314
|
7 |
|
);
|
315
|
|
|
}
|
316
|
|
|
|
317
|
|
|
/**
|
318
|
|
|
* Задает popup подсказки
|
319
|
|
|
* @return array
|
320
|
|
|
*/
|
321
|
1 |
View Code Duplication |
public function getPopupHints()
|
|
|
|
|
322
|
|
|
{
|
323
|
1 |
|
if( $this->_popupHints === null )
|
324
|
1 |
|
{
|
325
|
1 |
|
$items = BHint::model()->findAllByAttributes(
|
326
|
|
|
array(
|
327
|
1 |
|
'model' => get_class($this),
|
328
|
|
|
'popup' => '1'
|
329
|
1 |
|
));
|
330
|
|
|
|
331
|
1 |
|
$this->_popupHints = CHtml::listData($items, 'attribute', 'content');
|
332
|
1 |
|
}
|
333
|
|
|
|
334
|
1 |
|
return $this->_popupHints;
|
335
|
|
|
}
|
336
|
|
|
|
337
|
|
|
/**
|
338
|
|
|
* Получает popup подсказку для атрибута
|
339
|
|
|
* @param $attribute
|
340
|
|
|
* @return null
|
341
|
|
|
*/
|
342
|
1 |
|
public function getPopupHint($attribute)
|
343
|
|
|
{
|
344
|
1 |
|
$hints = $this->popupHints;
|
345
|
|
|
|
346
|
1 |
|
return isset($hints[$attribute]) ? $hints[$attribute] : null;
|
347
|
|
|
}
|
348
|
|
|
|
349
|
|
|
/**
|
350
|
|
|
* Задает подсказки
|
351
|
|
|
* @return array
|
352
|
|
|
*/
|
353
|
2 |
View Code Duplication |
public function getHints()
|
|
|
|
|
354
|
|
|
{
|
355
|
2 |
|
if( $this->_hints === null )
|
356
|
2 |
|
{
|
357
|
2 |
|
$items = BHint::model()->findAllByAttributes(
|
358
|
|
|
array(
|
359
|
2 |
|
'model' => get_class($this),
|
360
|
|
|
'popup' => '0'
|
361
|
2 |
|
));
|
362
|
|
|
|
363
|
2 |
|
$this->_hints = CHtml::listData($items, 'attribute', 'content');
|
364
|
2 |
|
}
|
365
|
|
|
|
366
|
2 |
|
return $this->_hints;
|
367
|
|
|
}
|
368
|
|
|
|
369
|
|
|
/**
|
370
|
|
|
* Получает подсказку для атрибута
|
371
|
|
|
* @param $attribute
|
372
|
|
|
* @return null
|
373
|
|
|
*/
|
374
|
2 |
|
public function getHint($attribute)
|
375
|
|
|
{
|
376
|
2 |
|
$hints = $this->hints;
|
377
|
|
|
|
378
|
2 |
|
return isset($hints[$attribute]) ? $hints[$attribute] : null;
|
379
|
|
|
}
|
380
|
|
|
|
381
|
|
|
/**
|
382
|
|
|
* @return string
|
383
|
|
|
*/
|
384
|
5 |
|
public function getFrontendModelName()
|
385
|
|
|
{
|
386
|
5 |
|
return preg_replace('/^(B)/', '', get_class($this));
|
387
|
|
|
}
|
388
|
|
|
|
389
|
|
|
/**
|
390
|
|
|
* @param CDbCriteria $criteria
|
391
|
|
|
*
|
392
|
|
|
* @return CDbCriteria
|
393
|
|
|
*/
|
394
|
|
|
protected function getSearchCriteria(CDbCriteria $criteria)
|
395
|
|
|
{
|
396
|
|
|
return $criteria;
|
397
|
|
|
}
|
398
|
|
|
|
399
|
|
|
/**
|
400
|
|
|
* @return array of search params
|
401
|
|
|
*/
|
402
|
2 |
|
protected function getSearchParams()
|
403
|
|
|
{
|
404
|
2 |
|
return array();
|
405
|
|
|
}
|
406
|
|
|
|
407
|
|
|
/**
|
408
|
|
|
* Проверка на поддержку моделью nested set
|
409
|
|
|
* @return bool
|
410
|
|
|
*/
|
411
|
29 |
|
private function isNestedSetModel()
|
412
|
|
|
{
|
413
|
29 |
|
return $this->asa('nestedSetBehavior') !== null;
|
414
|
|
|
}
|
415
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.