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.controllers
|
8
|
|
|
*
|
9
|
|
|
* @property BModule $module
|
10
|
|
|
* @mixin TextBlockBehavior
|
11
|
|
|
* @mixin CommonBehavior
|
12
|
|
|
*/
|
13
|
|
|
abstract class BController extends CController
|
14
|
|
|
{
|
15
|
|
|
public $enabled = true;
|
16
|
|
|
|
17
|
|
|
public $position = 0;
|
18
|
|
|
|
19
|
|
|
public $name = '[Не задано]';
|
20
|
|
|
|
21
|
|
|
public $layout = '//layouts/column1';
|
22
|
|
|
|
23
|
|
|
/**
|
24
|
|
|
* @var array
|
25
|
|
|
*/
|
26
|
|
|
public $breadcrumbs = array();
|
27
|
|
|
|
28
|
|
|
public $modelClass = 'BActiveRecord';
|
29
|
|
|
|
30
|
|
|
public $popup = false;
|
31
|
|
|
|
32
|
|
|
public $moduleMenu;
|
33
|
|
|
|
34
|
|
|
public $showInMenu = true;
|
35
|
|
|
|
36
|
40 |
|
public function behaviors()
|
37
|
|
|
{
|
38
|
|
|
return array(
|
39
|
40 |
|
'textBlock' => array('class' => 'TextBlockBehavior'),
|
40
|
40 |
|
'common' => array('class' => 'CommonBehavior'),
|
41
|
40 |
|
);
|
42
|
|
|
}
|
43
|
|
|
|
44
|
|
|
public function getViewPath()
|
45
|
|
|
{
|
46
|
|
|
if( ($module = $this->getModule()) === null )
|
47
|
|
|
$module = Yii::app();
|
48
|
|
|
|
49
|
|
|
$mappedId = array_search(get_class($this), $module->controllerMap);
|
50
|
|
|
$id = $mappedId ? $mappedId : $this->getId();
|
51
|
|
|
|
52
|
|
|
$submoduleViewPath = $module->getViewPath().DIRECTORY_SEPARATOR.$id;
|
53
|
|
|
|
54
|
|
|
return file_exists($submoduleViewPath) ? $submoduleViewPath : $module->getViewPath();
|
55
|
|
|
}
|
56
|
|
|
|
57
|
|
|
public function filters()
|
58
|
|
|
{
|
59
|
|
|
return array(
|
60
|
|
|
'postOnly + delete',
|
61
|
|
|
);
|
62
|
|
|
}
|
63
|
|
|
|
64
|
1 |
|
public function beforeAction($action)
|
65
|
|
|
{
|
66
|
1 |
|
if( !AccessHelper::checkAccessByClasses($this->module, $this) )
|
67
|
1 |
|
{
|
68
|
1 |
|
if( !Yii::app()->user->isGuest )
|
69
|
1 |
|
throw new CHttpException(403, 'Доступ запрещен.');
|
70
|
|
|
else
|
71
|
|
|
{
|
72
|
1 |
|
if( Yii::app()->request->isAjaxRequest )
|
73
|
1 |
|
throw new CHttpException(401, 'Требуется авторизация');
|
74
|
|
|
|
75
|
1 |
|
Yii::app()->user->setReturnUrl(Yii::app()->request->requestUri);
|
76
|
1 |
|
$this->redirect(Yii::app()->baseUrl . '/base');
|
77
|
|
|
}
|
78
|
|
|
}
|
79
|
|
|
|
80
|
|
|
if( in_array($action->id, array('index')) )
|
81
|
|
|
{
|
82
|
|
|
$url = Yii::app()->request->url;
|
83
|
|
|
Yii::app()->user->setState($this->uniqueId, $url);
|
84
|
|
|
}
|
85
|
|
|
|
86
|
|
|
if( Yii::app()->request->getQuery('popup', false) )
|
87
|
|
|
{
|
88
|
|
|
$this->popup = true;
|
89
|
|
|
$this->layout = '//layouts/popup';
|
90
|
|
|
}
|
91
|
|
|
|
92
|
|
|
Yii::app()->registerAjaxUpdateError();
|
|
|
|
|
93
|
|
|
|
94
|
|
|
return parent::beforeAction($action);
|
95
|
|
|
}
|
96
|
|
|
|
97
|
1 |
|
public function getBackUrl()
|
98
|
|
|
{
|
99
|
1 |
|
$url = Yii::app()->user->getState($this->uniqueId);
|
100
|
1 |
|
if( !$url )
|
101
|
1 |
|
$url = Yii::app()->createUrl($this->module->id.'/'.$this->id);
|
102
|
|
|
|
103
|
1 |
|
return Utils::cutQueryParams($url, array('ajax'));
|
104
|
|
|
}
|
105
|
|
|
|
106
|
6 |
|
public function actions()
|
107
|
|
|
{
|
108
|
|
|
return array(
|
109
|
6 |
|
'delete' => 'BDefaultActionDelete',
|
110
|
6 |
|
'deleteRelated' => 'BRelatedActionDelete',
|
111
|
6 |
|
'association' => 'BSaveAssociationAction',
|
112
|
6 |
|
'switch' => 'ext.jtogglecolumn.SwitchAction',
|
113
|
6 |
|
'toggle' => 'ext.jtogglecolumn.ToggleAction',
|
114
|
6 |
|
'onflyedit' => 'ext.onflyedit.OnFlyEditAction',
|
115
|
6 |
|
'upload' => 'upload.actions.UploadAction',
|
116
|
6 |
|
'directory' => 'backend.modules.directory.actions.DirectoryAction',
|
117
|
6 |
|
);
|
118
|
|
|
}
|
119
|
|
|
|
120
|
|
|
/**
|
121
|
|
|
* Делаем редирект.
|
122
|
|
|
* Если приложение запущено с тестовым конфигом, то бросаем эксепшн
|
123
|
|
|
*
|
124
|
|
|
* @param mixed $url
|
125
|
|
|
* @param bool $terminate
|
126
|
|
|
* @param integer $statusCode
|
127
|
|
|
*
|
128
|
|
|
* @throws BTestRedirectException
|
129
|
|
|
*/
|
130
|
4 |
|
public function redirect($url, $terminate = true, $statusCode = 302)
|
131
|
|
|
{
|
132
|
4 |
|
if( Yii::app()->params['mode'] === 'test' )
|
133
|
4 |
|
{
|
134
|
4 |
|
throw new BTestRedirectException(200, 'Location: '.$url, $statusCode);
|
135
|
|
|
}
|
136
|
|
|
else
|
137
|
|
|
{
|
138
|
|
|
parent::redirect($url, $terminate, $statusCode);
|
139
|
|
|
}
|
140
|
|
|
}
|
141
|
|
|
|
142
|
|
|
/**
|
143
|
|
|
* @param string $view
|
144
|
|
|
* @param null $data
|
145
|
|
|
* @param bool $return
|
146
|
|
|
*
|
147
|
|
|
* @return string|void
|
148
|
|
|
*/
|
149
|
1 |
|
public function render($view, $data = null, $return = false)
|
150
|
|
|
{
|
151
|
1 |
|
if( Yii::app()->params['mode'] === 'test' )
|
152
|
1 |
|
{
|
153
|
1 |
|
Yii::app()->user->setFlash('render', $data);
|
154
|
1 |
|
}
|
155
|
|
|
else
|
156
|
|
|
{
|
157
|
|
|
parent::render($view, $data, $return);
|
158
|
|
|
}
|
159
|
1 |
|
}
|
160
|
|
|
|
161
|
|
|
/**
|
162
|
|
|
* @param $id
|
163
|
|
|
* @param string $modelClass
|
164
|
|
|
*
|
165
|
|
|
* @return mixed
|
166
|
|
|
* @throws CHttpException
|
167
|
|
|
*/
|
168
|
8 |
|
public function loadModel($id, $modelClass = null)
|
169
|
|
|
{
|
170
|
8 |
|
$class = $modelClass ? $modelClass : $this->modelClass;
|
171
|
8 |
|
$model = $class::model()->findByPk($id);
|
172
|
|
|
|
173
|
8 |
|
if( $model === null )
|
174
|
8 |
|
throw new CHttpException(404, 'The requested page does not exist.');
|
175
|
|
|
|
176
|
7 |
|
return $model;
|
177
|
|
|
}
|
178
|
|
|
|
179
|
1 |
|
public function actionIndex()
|
180
|
|
|
{
|
181
|
1 |
|
$model = $this->createFilterModel();
|
182
|
|
|
|
183
|
1 |
|
$this->render('index', array(
|
184
|
1 |
|
'model' => $model,
|
185
|
1 |
|
'dataProvider' => $model->search(),
|
186
|
1 |
|
));
|
187
|
1 |
|
}
|
188
|
|
|
|
189
|
|
|
public function actionCreate()
|
190
|
|
|
{
|
191
|
|
|
$model = new $this->modelClass;
|
192
|
|
|
$model->attributes = Yii::app()->request->getQuery(get_class($model));
|
193
|
|
|
$this->actionSave($model);
|
194
|
|
|
}
|
195
|
|
|
|
196
|
|
|
/**
|
197
|
|
|
* @param $id
|
198
|
|
|
*/
|
199
|
|
|
public function actionUpdate($id)
|
200
|
|
|
{
|
201
|
|
|
$this->actionSave($this->loadModel($id));
|
202
|
|
|
}
|
203
|
|
|
|
204
|
|
|
public function isUpdate()
|
205
|
|
|
{
|
206
|
|
|
return $this->action->id == 'update' ? true : false;
|
207
|
|
|
}
|
208
|
|
|
|
209
|
|
|
/**
|
210
|
|
|
* Стандартный автокомплит для моделей
|
211
|
|
|
*
|
212
|
|
|
* $_GET['model'] - название модели для поиска
|
213
|
|
|
* $_GET['field'] - поле для поиска
|
214
|
|
|
* $_GET['q'] - значение
|
215
|
|
|
*
|
216
|
|
|
* @return void
|
217
|
|
|
*/
|
218
|
|
|
public function actionAutocomplete()
|
219
|
|
|
{
|
220
|
|
|
if( !Yii::app()->request->isAjaxRequest )
|
221
|
|
|
return;
|
222
|
|
|
|
223
|
|
|
$modelClass = Yii::app()->request->getParam('model');
|
224
|
|
|
$field = Yii::app()->request->getParam('field');
|
225
|
|
|
$value = Yii::app()->request->getParam('q');
|
226
|
|
|
|
227
|
|
|
if( $modelClass && $field && $value )
|
228
|
|
|
{
|
229
|
|
|
$criteria = new CDbCriteria();
|
230
|
|
|
$criteria->addSearchCondition($field, $value);
|
231
|
|
|
$criteria->limit = 10;
|
232
|
|
|
|
233
|
|
|
$data = $modelClass::model()->findAll($criteria);
|
234
|
|
|
$answer = array();
|
235
|
|
|
|
236
|
|
|
foreach( $data as $item )
|
237
|
|
|
if( !in_array($item->$field, $answer) )
|
238
|
|
|
$answer[] = $item->$field;
|
239
|
|
|
|
240
|
|
|
echo implode("\n", $answer);
|
241
|
|
|
}
|
242
|
|
|
}
|
243
|
|
|
|
244
|
|
|
/**
|
245
|
|
|
* @return BActiveRecord
|
246
|
|
|
*/
|
247
|
1 |
|
protected function createFilterModel()
|
248
|
|
|
{
|
249
|
1 |
|
$attributes = Yii::app()->request->getQuery($this->modelClass);
|
250
|
1 |
|
$model = new $this->modelClass('search');
|
251
|
1 |
|
$model->unsetAttributes();
|
252
|
|
|
|
253
|
1 |
|
if( !empty($attributes) )
|
254
|
1 |
|
$model->attributes = $attributes;
|
255
|
|
|
|
256
|
1 |
|
return $model;
|
257
|
|
|
}
|
258
|
|
|
|
259
|
|
|
/**
|
260
|
|
|
* @param BActiveRecord $model
|
261
|
|
|
*/
|
262
|
1 |
|
protected function saveModel($model)
|
263
|
|
|
{
|
264
|
1 |
|
$this->performAjaxValidation($model);
|
265
|
1 |
|
$attributes = Yii::app()->request->getPost(get_class($model));
|
266
|
|
|
|
267
|
1 |
|
if( isset($attributes) )
|
268
|
1 |
|
{
|
269
|
1 |
|
$model->setAttributes($attributes);
|
270
|
|
|
|
271
|
1 |
|
if( $model->save() )
|
272
|
1 |
|
{
|
273
|
1 |
|
$this->redirectAfterSave($model);
|
274
|
|
|
}
|
275
|
|
|
}
|
276
|
|
|
}
|
277
|
|
|
|
278
|
|
|
/**
|
279
|
|
|
* Проводим валидацию и сохраняем несколько связанных моделей
|
280
|
|
|
* Все модели должны быть связаны по первичному ключу
|
281
|
|
|
*
|
282
|
|
|
* @param BActiveRecord[] $models
|
283
|
|
|
* @param bool $extendedSave пытаемся сохранить все данные post, вызывая соответствующие методы контроллера
|
284
|
|
|
* @param bool $redirectToUpdate перенаправление на action update
|
285
|
|
|
*
|
286
|
|
|
* @throws CDbException
|
287
|
|
|
* @throws CHttpException
|
288
|
|
|
*/
|
289
|
1 |
|
protected function saveModels($models, $extendedSave = true, $redirectToUpdate = true)
|
290
|
|
|
{
|
291
|
1 |
|
$this->performAjaxValidationForSeveralModels($models);
|
292
|
|
|
|
293
|
1 |
|
if( Yii::app()->request->isPostRequest && $this->validateModels($models) )
|
294
|
1 |
|
{
|
295
|
1 |
|
Yii::app()->db->beginTransaction();
|
296
|
|
|
|
297
|
|
|
$modelNames = array_map(function($data){return get_class($data);}, $models);
|
298
|
1 |
|
$unsavedKeys = array_diff(array_keys($_POST), $modelNames);
|
299
|
1 |
|
$primaryModel = $models[0];
|
300
|
|
|
|
301
|
1 |
|
foreach($models as $key => $model)
|
302
|
|
|
{
|
303
|
1 |
|
if( $key !== 0 )
|
304
|
1 |
|
$model->setPrimaryKey($primaryModel->getPrimaryKey());
|
305
|
|
|
|
306
|
1 |
View Code Duplication |
if( !$model->save(false) )
|
|
|
|
|
307
|
1 |
|
{
|
308
|
|
|
Yii::app()->db->currentTransaction->rollback();
|
309
|
|
|
throw new CHttpException(500, 'Can`t save '.get_class($model).' model');
|
310
|
|
|
}
|
311
|
1 |
|
}
|
312
|
|
|
|
313
|
|
|
if( $extendedSave )
|
314
|
1 |
|
{
|
315
|
1 |
|
$resultSaveMaximumPostData = $this->saveMaximumPostData($unsavedKeys, $primaryModel);
|
316
|
|
|
|
317
|
1 |
|
if( !$resultSaveMaximumPostData )
|
318
|
1 |
|
{
|
319
|
|
|
Yii::app()->db->currentTransaction->rollback();
|
320
|
|
|
return;
|
321
|
|
|
}
|
322
|
1 |
|
}
|
323
|
|
|
|
324
|
1 |
|
Yii::app()->db->currentTransaction->commit();
|
325
|
|
|
|
326
|
|
|
if( $redirectToUpdate )
|
327
|
1 |
|
$this->redirectAfterSave($primaryModel);
|
328
|
|
|
}
|
329
|
|
|
}
|
330
|
|
|
|
331
|
|
|
/**
|
332
|
|
|
* @param BActiveRecord[] $models
|
333
|
|
|
*
|
334
|
|
|
* @return bool
|
335
|
|
|
*/
|
336
|
2 |
|
protected function validateModels($models)
|
337
|
|
|
{
|
338
|
2 |
|
$valid = !empty($models);
|
339
|
|
|
|
340
|
2 |
|
foreach($models as $model)
|
341
|
|
|
{
|
342
|
2 |
|
$post = Yii::app()->request->getPost(get_class($model));
|
343
|
2 |
|
$model->setAttributes($post);
|
344
|
2 |
|
$valid = $model->validate() && $valid && !empty($post) && $this->validateRelatedModels($model);
|
345
|
2 |
|
}
|
346
|
|
|
|
347
|
2 |
|
return $valid;
|
348
|
|
|
}
|
349
|
|
|
|
350
|
|
|
/**
|
351
|
|
|
* @param BActiveRecord $model
|
352
|
|
|
*
|
353
|
|
|
* @return bool
|
354
|
|
|
*/
|
355
|
2 |
|
protected function validateRelatedModels($model)
|
356
|
|
|
{
|
357
|
2 |
|
$success = true;
|
358
|
|
|
|
359
|
2 |
|
foreach($this->getModelsAllowedForSave() as $relationName => $modelName)
|
360
|
|
|
{
|
361
|
|
|
if( !($postData = Yii::app()->request->getPost($modelName, null)) )
|
362
|
|
|
continue;
|
363
|
|
|
|
364
|
|
|
$models = $model->prepareModels($relationName, $postData);
|
365
|
|
|
$success = $model->validateRelatedModels($models);
|
366
|
|
|
|
367
|
|
|
if( !$success )
|
368
|
|
|
break;
|
369
|
2 |
|
}
|
370
|
|
|
|
371
|
2 |
|
return $success;
|
372
|
|
|
}
|
373
|
|
|
|
374
|
|
|
/**
|
375
|
|
|
* @param BActiveRecord $model
|
376
|
|
|
*/
|
377
|
2 |
|
protected function redirectAfterSave($model)
|
378
|
|
|
{
|
379
|
2 |
|
Yii::app()->user->setFlash('success', 'Запись успешно '.($model->isNewRecord ? 'создана' : 'сохранена').'.');
|
380
|
|
|
|
381
|
2 |
|
if( Yii::app()->request->getParam('action') )
|
382
|
2 |
|
$this->redirect($this->getBackUrl());
|
383
|
|
|
else
|
384
|
|
|
{
|
385
|
2 |
|
$redirectData = CMap::mergeArray(array('id' => $model->getPrimaryKey()), $_GET);
|
386
|
2 |
|
$redirectUrl = $this->createUrl($this->id.'/update', $redirectData);
|
387
|
|
|
|
388
|
2 |
|
$this->redirect($redirectUrl);
|
389
|
|
|
}
|
390
|
|
|
}
|
391
|
|
|
|
392
|
|
|
/**
|
393
|
|
|
* @param BActiveRecord $model
|
394
|
|
|
*/
|
395
|
|
|
protected function actionSave($model)
|
396
|
|
|
{
|
397
|
|
|
$this->saveModels(array($model));
|
398
|
|
|
$this->render('_form', array('model' => $model));
|
399
|
|
|
}
|
400
|
|
|
|
401
|
|
|
/**
|
402
|
|
|
* Performs the AJAX validation.
|
403
|
|
|
*
|
404
|
|
|
* @param BActiveRecord $model the model to be validated
|
405
|
|
|
*/
|
406
|
3 |
|
protected function performAjaxValidation($model)
|
407
|
|
|
{
|
408
|
3 |
|
if( Yii::app()->request->getPost('ajax') === $model->getFormId() )
|
409
|
3 |
|
{
|
410
|
2 |
|
echo CActiveForm::validate($model);
|
411
|
2 |
|
Yii::app()->end();
|
|
|
|
|
412
|
|
|
}
|
413
|
1 |
|
}
|
414
|
|
|
|
415
|
|
|
/**
|
416
|
|
|
* @param BActiveRecord[] $models
|
417
|
|
|
*/
|
418
|
3 |
|
protected function performAjaxValidationForSeveralModels($models)
|
419
|
|
|
{
|
420
|
3 |
|
if( Yii::app()->request->getPost('ajax') === $models[0]->getFormId() )
|
421
|
3 |
|
{
|
422
|
2 |
|
$result = array();
|
423
|
|
|
|
424
|
2 |
|
foreach($models as $model)
|
425
|
|
|
{
|
426
|
2 |
|
$errors = CJavaScript::jsonDecode(CActiveForm::validate($model));
|
427
|
2 |
|
$result = CMap::mergeArray($result, $errors);
|
428
|
2 |
|
}
|
429
|
|
|
|
430
|
2 |
|
echo CJavaScript::jsonEncode($result);
|
431
|
2 |
|
Yii::app()->end();
|
|
|
|
|
432
|
|
|
}
|
433
|
1 |
|
}
|
434
|
|
|
|
435
|
|
|
/**
|
436
|
|
|
* Проверям наличие в контроллере методов, чтобы сохранить данные из post
|
437
|
|
|
*
|
438
|
|
|
* @param array $unsavedKeys
|
439
|
|
|
* @param BActiveRecord $primaryModel
|
440
|
|
|
*
|
441
|
|
|
* @return bool
|
442
|
|
|
*/
|
443
|
1 |
|
protected function saveMaximumPostData(array $unsavedKeys, BActiveRecord $primaryModel)
|
444
|
|
|
{
|
445
|
1 |
|
foreach($unsavedKeys as $key)
|
446
|
|
|
{
|
447
|
|
|
$method = 'save'.$key;
|
448
|
|
|
$data = Yii::app()->request->getPost($key);
|
449
|
|
|
|
450
|
|
|
if( empty($data) || !is_array($data) || !in_array($key, $this->getModelsAllowedForSave()) )
|
451
|
|
|
continue;
|
452
|
|
|
|
453
|
|
|
if( method_exists($this, $method) )
|
454
|
|
|
$result = call_user_func_array(array($this, $method), array($data, $primaryModel));
|
455
|
|
|
else
|
456
|
|
|
$result = $primaryModel->saveRelatedModels(array_search($key, $this->getModelsAllowedForSave()), $data);
|
457
|
|
|
|
458
|
|
|
if( !$result )
|
459
|
|
|
return false;
|
460
|
1 |
|
}
|
461
|
|
|
|
462
|
1 |
|
return true;
|
463
|
|
|
}
|
464
|
|
|
|
465
|
|
|
/**
|
466
|
|
|
* Возвращает массив разрешенных для сохнанеия релейшенов в формате 'relationName' => 'postPrefix'
|
467
|
|
|
* пример: array('variants' => 'BProductParamVariant').
|
468
|
|
|
* Если в котроллере есть метод с именем save{postPrefix}($data, BActiveRecord $parentModel),
|
469
|
|
|
* то для сохнания данных $_POST[{postPrefix}} будет вызван он.
|
470
|
|
|
*
|
471
|
|
|
* @return array
|
472
|
|
|
*/
|
473
|
2 |
|
protected function getModelsAllowedForSave()
|
474
|
|
|
{
|
475
|
2 |
|
return array();
|
476
|
|
|
}
|
477
|
|
|
} |
It seems like the method you are trying to call exists only in some of the possible types.
Let’s take a look at an example:
Available Fixes
Add an additional type-check:
Only allow a single type to be passed if the variable comes from a parameter: