GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

TbEditable::run()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
ccs 0
cts 14
cp 0
rs 9.2
cc 4
eloc 10
nc 4
nop 0
crap 20
1
<?php
2
/**
3
 * TbEditable class file.
4
 *
5
 * @author Vitaliy Potapov <[email protected]>
6
 * @link https://github.com/vitalets/x-editable-yii
7
 * @copyright Copyright &copy; Vitaliy Potapov 2012
8
 * @version 1.3.1
9
*/
10
11
/**
12
* TbEditable widget creates editable element on page (without linking to model attribute).
13
*
14
* @package widgets
15
*/
16
17
class TbEditable extends CWidget
18
{
19
    //note: only most usefull options are on first level of config.
20
21
    // --- start of X-editable options ----
22
    /**
23
    * @var string type of editable widget. Can be `text`, `textarea`, `select`, `date`, `checklist`, etc.
24
    * @see x-editable
25
    */
26
    public $type = null;
27
    /**
28
    * @var string url to submit value. Can be string or array containing Yii route, e.g. `array('site/updateUser')`
29
    * @see x-editable
30
    */
31
    public $url = null;
32
    /**
33
    * @var mixed primary key
34
    * @see x-editable
35
    */
36
    public $pk = null; 
37
    /**
38
    * @var string name of field
39
    * @see x-editable
40
    */
41
    public $name = null;        
42
    /**
43
    * @var array additional params to send on server
44
    * @see x-editable
45
    */
46
    public $params = null;
47
    /**
48
    * @var string css class of input. If `null` - default X-editable value is used: `input-medium`
49
    * @see x-editable
50
    */
51
    public $inputclass = null;
52
    /**
53
    * @var string mode of input: `inline` | `popup`. If not set - default X-editable value is used: `popup`.
54
    * @see x-editable
55
    */
56
    public $mode = null;
57
    /**
58
    * @var string text to be shown as element content
59
    */
60
    public $text = null;
61
    /**
62
    * @var mixed initial value. If not set - will be taken from text
63
    * @see x-editable
64
    */
65
    public $value = null;
66
    /**
67
    * @var string placement of popup. Can be `left`, `top`, `right`, `bottom`. If `null` - default X-editable value is used: `top`
68
    * @see x-editable
69
    */
70
    public $placement = null;
71
72
    /**
73
    * @var string text shown on empty field. If `null` - default X-editable value is used: `Empty`
74
    * @see x-editable
75
    */
76
    public $emptytext = null;
77
78
    /**
79
    * @var string visibility of buttons. Can be boolean `false|true` or string `bottom`.
80
    * @see x-editable
81
    */
82
    public $showbuttons = null;   
83
    
84
    /**
85
    * @var string Strategy for sending data on server. Can be `auto|always|never`.
86
    * When 'auto' data will be sent on server only if **pk** and **url** defined, otherwise new value will be stored locally.
87
    * @see x-editable
88
    */
89
    public $send = null;  
90
    
91
    /**
92
    * @var boolean will editable be initially disabled. It means editable plugin will be applied to element,
93
    * but you should call `.editable('enable')` method to activate it.
94
    * To totally disable applying 'editable' to element use **apply** option.
95
    * @see x-editable
96
    */
97
    public $disabled = false;
98
99
    //list
100
    /**
101
    * @var mixed source data for **select**, **checklist**. Can be string (url) or array in format: 
102
    * array( array("value" => 1, "text" => "abc"), ...)
103
    * @package list
104
    * @see x-editable
105
    */
106
    public $source = null;
107
108
    //date
109
    /**
110
    * @var string format to send date on server. If `null` - default X-editable value is used: `yyyy-mm-dd`.
111
    * @package date
112
    * @see x-editable
113
    */
114
    public $format = null;
115
    /**
116
    * @var string format to display date in element. If `null` - equals to **format** option.
117
    * @package date
118
    * @see x-editable
119
    */
120
    public $viewformat = null;
121
    /**
122
    * @var string template for **combodate** input. For details see http://vitalets.github.com/x-editable/docs.html#combodate.
123
    * @package combodate
124
    * @see x-editable
125
    */
126
    public $template = null;        
127
    /**
128
    * @var array full config for **combodate** input. For details see http://vitalets.github.com/combodate/#docs
129
    * @package combodate
130
    * @see x-editable
131
    */
132
    public $combodate = null;    
133
    /**
134
    * @var string separator used to display tags.
135
    * @package select2
136
    * @see x-editable
137
    */
138
    public $viewseparator = null;        
139
    /**
140
    * @var array full config for **select2** input. For details see http://ivaynberg.github.com/select2
141
    * @package select2
142
    * @see x-editable
143
    */
144
    public $select2 = null;    
145
    
146
    //methods
147
    /**
148
    * A javascript function that will be invoked to validate value.
149
    * Example:
150
    * <pre>
151
    * 'validate' => 'js: function(value) {
152
    *     if($.trim(value) == "") return "This field is required";
153
    * }'
154
    * </pre>
155
    *
156
    * @var string
157
    * @package callback
158
    * @see x-editable
159
    * @example
160
    */
161
    public $validate = null;
162
    /**
163
    * A javascript function that will be invoked to process successful server response.
164
    * Example:
165
    * <pre>
166
    * 'success' => 'js: function(response, newValue) {
167
    *     if(!response.success) return response.msg;
168
    * }'
169
    * </pre>
170
    *
171
    * @var string
172
    * @package callback
173
    * @see x-editable
174
    */
175
    public $success = null;
176
    /**
177
    * A javascript function that will be invoked to custom display value.
178
    * Example:
179
    * <pre>
180
    * 'display' => 'js: function(value, sourceData) {
181
    *      var escapedValue = $("&lt;div&gt;").text(value).html();
182
    *      $(this).html("&lt;b&gt;"+escapedValue+"&lt;/b&gt;");
183
    * }'
184
    * </pre>
185
    *
186
    * @var string
187
    * @package callback
188
    * @see x-editable
189
    */
190
    public $display = null;
191
    
192
    /**
193
    * DOM id of target where afterAjaxUpdate handler will call 
194
    * live update of editable element
195
    * 
196
    * @var string
197
    */
198
    public $liveTarget = null;
199
    /**
200
    * jQuery selector of elements to wich will be applied editable.
201
    * Usefull in combination of `liveTarget` when you want to keep field(s) editble
202
    * after ajaxUpdate
203
    * 
204
    * @var string
205
    */
206
    public $liveSelector = null;
207
208
    // --- X-editable events ---
209
    /**
210
    * A javascript function that will be invoked when editable element is initialized
211
    * @var string
212
    * @package event
213
    * @see x-editable
214
    */
215
    public $onInit;
216
    /**
217
    * A javascript function that will be invoked when editable form is shown
218
    * Example:
219
    * <pre>
220
    * 'onShown' => 'js: function() {
221
    *     var $tip = $(this).data("editableContainer").tip();
222
    *     $tip.find("input").val("overwriting value of input.");
223
    * }'
224
    * </pre>
225
    *
226
    * @var string
227
    * @package event
228
    * @see x-editable
229
    */
230
    public $onShown;
231
    /**
232
    * A javascript function that will be invoked when new value is saved
233
    * Example:
234
    * <pre>
235
    * 'onSave' => 'js: function(e, params) {
236
    *     alert("Saved value: " + params.newValue);
237
    * }'
238
    * </pre>
239
    *
240
    * @var string
241
    * @package event
242
    * @see x-editable
243
    */
244
    public $onSave;
245
    /**
246
    * A javascript function that will be invoked when editable form is hidden
247
    * Example:
248
    * <pre>
249
    * 'onHidden' => 'js: function(e, reason) {
250
    *    if(reason === "save" || reason === "cancel") {
251
    *        //auto-open next editable
252
    *        $(this).closest("tr").next().find(".editable").editable("show");
253
    *    }
254
    * }'
255
    * </pre>
256
    *
257
    * @var string
258
    * @package event
259
    * @see x-editable
260
    */
261
    public $onHidden;
262
263
    /**
264
    * @var array all config options of x-editable. See full list <a href="http://vitalets.github.com/x-editable/docs.html#editable">here</a>.
265
    */
266
    public $options = array();
267
268
    /**
269
    * @var array HTML options of element. In `TbEditableColumn` htmlOptions are PHP expressions 
270
    * so you can use `$data` to bind values to particular cell, e.g. `'data-categoryID' => '$data->categoryID'`.
271
    */
272
    public $htmlOptions = array();
273
274
    /**
275
    * @var boolean whether to HTML encode text on output
276
    */
277
    public $encode = true;
278
279
    /**
280
    * @var boolean whether to apply 'editable' js plugin to element. 
281
    * Only **safe** attributes become editable.
282
    */
283
    public $apply = null;
284
285
    /**
286
    * @var string title of popup. If `null` - will be generated automatically from attribute label.
287
    * Can have token {label} inside that will be replaced with actual attribute label.
288
    */
289
    public $title = null;
290
291
    //themeUrl, theme and cssFile copied from CJuiWidget to allow include custom theme for jQuery UI
292
    /**
293
     * @var string for jQuery UI only. The root URL that contains JUI theme folders.
294
     * If not set, default Yii's theme will be used.
295
    */
296
    public $themeUrl;
297
    /**
298
     * @var string for jQuery UI only. The JUI theme name.
299
     */
300
    public $theme='base';
301
    /**
302
     * @var mixed for jQuery UI only. The theme CSS file name. By default Yii's jquery UI css used.
303
    */
304
    public $cssFile='jquery-ui.css';
305
306
    protected $_prepareToAutotext = false;
307
308
    /**
309
    * initialization of widget
310
    *
311
    */
312
    public function init()
313
    {
314
        parent::init();
315
316
        if (!$this->name) {
317
            throw new CException('Parameter "name" should be provided for TbEditable widget');
318
        }
319
                
320
        /*
321
        If set this flag to true --> element content will stay empty 
322
        and value will be rendered to data-value attribute to apply autotext afterwards.
323
        */
324
        $this->_prepareToAutotext = self::isAutotext($this->options, $this->type);
325
        
326
        /*
327
        For `date` and `datetime` we need format to be on php side to make conversions.
328
        But we can not set default format as datepicker and combodate has different formats.
329
        So do it here:
330
        */
331
        if (!$this->format && $this->type == 'date') {
332
            $this->format = 'yyyy-mm-dd';
333
        }
334
        if (!$this->format && $this->type == 'datetime') {
335
            $this->format = 'yyyy-mm-dd hh:ii:ss';
336
        }
337
    }
338
339
    public function buildHtmlOptions()
340
    {
341
        //html options
342
        $htmlOptions = array(
343
            'href'      => '#',
344
            'rel'       => $this->liveSelector ? $this->liveSelector : $this->getSelector(),
345
        );
346
347
        //set data-pk 
348
        if($this->pk !== null) {
349
           $htmlOptions['data-pk'] = is_array($this->pk) ? CJSON::encode($this->pk) : $this->pk; 
350
        }
351
352
        //if input type assumes autotext (e.g. select) we define value directly in data-value 
353
        //and do not fill element contents
354
        if ($this->_prepareToAutotext) {
355
            //for date we use 'format' to put it into value (if text not defined)
356
            if ($this->type == 'date' || $this->type == 'datetime') {
357
                //if date comes as object OR timestamp, format it to string
358
                if($this->value instanceOf DateTime || is_long($this->value) || (is_string($this->value) && ctype_digit($this->value))) {
359
                    /*
360
                    * unfortunatly bootstrap datepicker's format does not match 
361
                    * Yii locale dateFormat, we need replacements below to convert 
362
                    * date correctly.
363
                    * 
364
                    * Yii format: 
365
                    * http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
366
                    * 
367
                    * Datepicker format: 
368
                    * https://github.com/eternicode/bootstrap-datepicker#format
369
                    * 
370
                    * Datetimepicker format: 
371
                    * https://github.com/smalot/bootstrap-datetimepicker#format 
372
                    */
373
                    //months: M --> MMM, m --> M
374
                    $count = 0;
375
                    $format = str_replace('MM', 'MMMM', $this->format, $count);
376
                    if(!$count) $format = str_replace('M', 'MMM', $format, $count);
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
377
                    if(!$count) $format = str_replace('m', 'M', $format);
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
378
                    if($this->type == 'datetime') {
379
                        //minutes: i --> m
380
                        $format = str_replace('i', 'm', $format); 
381
                        //hours: h --> H, H --> h
382
                        $count = 0;
383
                        $format = str_replace('h', 'H', $format, $count);
384
                        if(!$count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
385
                            $format = str_replace('H', 'h', $format);                        
386
                        }
387
                    }
388
                                        
389
                    if($this->value instanceof DateTime) {
390
                        $timestamp = $this->value->getTimestamp();
391
                    } else {
392
                        $timestamp = $this->value;
393
                    }
394
395
                    $this->value = Yii::app()->dateFormatter->format($format, $timestamp);
396
                }
397
            } 
398
399
            if(is_scalar($this->value)) {
400
                $this->htmlOptions['data-value'] = $this->value;
401
            }
402
            //if not scalar, value will be added to js options instead of html options
403
        }
404
405
        //merging options
406
        $this->htmlOptions = CMap::mergeArray($this->htmlOptions, $htmlOptions);
407
        
408
        //convert arrays to json string, otherwise yii can not render it: 
409
        //"htmlspecialchars() expects parameter 1 to be string, array given"  
410
        foreach($this->htmlOptions as $k => $v) {
411
            $this->htmlOptions[$k] = is_array($v) ? CJSON::encode($v) : $v;
412
        }
413
    }
414
415
    public function buildJsOptions()
416
    {
417
        //normalize url from array
418
        $this->url = CHtml::normalizeUrl($this->url);
419
420
        $options = array(
421
            'name'  => $this->name,
422
            'title' => CHtml::encode($this->title),
423
        );
424
        
425
        //if value needed for autotext and it's not scalar --> add it to js options
426
        if ($this->_prepareToAutotext && !is_scalar($this->value)) {
427
            $options['value'] = $this->value; 
428
        }
429
        
430
        //support of CSRF out of box, see https://github.com/vitalets/x-editable-yii/issues/38
431
        if (Yii::app()->request->enableCsrfValidation) {
432
            $csrfTokenName = Yii::app()->request->csrfTokenName;
433
            $csrfToken = Yii::app()->request->csrfToken;
434
            if((is_array($this->params) && !isset($this->params[$csrfTokenName])) || $this->params === null) {
435
                $this->params[$csrfTokenName] = $csrfToken;
436
            } elseif (strlen($this->params)) {
437
                $orig = $this->params;
438
                if (strpos($orig, 'js:') === 0) {
439
                    $orig = substr($orig, 3);
440
                }
441
                $orig = "params = ($orig).call(this, params);\n";
442
                $this->params = "js: function(params) {
0 ignored issues
show
Documentation Bug introduced by
It seems like 'js: function(params) { ...ms;\n }" of type string is incompatible with the declared type array of property $params.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
443
                    params['" . $csrfTokenName . "'] = '" . $csrfToken . "';
444
                    $orig
445
                    return params;
446
                }";
447
            }
448
        }        
449
450
        //simple options set directly from config
451
        foreach(array(
452
            'url', 
453
            'type', 
454
            'mode', 
455
            'placement', 
456
            'emptytext', 
457
            'params', 
458
            'inputclass', 
459
            'format', 
460
            'viewformat', 
461
            'template',
462
            'combodate', 
463
            'select2', 
464
            'viewseparator', 
465
            'showbuttons',
466
            'send',
467
            'disabled',
468
               ) as $option) {
469
            if ($this->$option !== null) {
470
                $options[$option] = $this->$option;
471
            }
472
        }
473
474
        if ($this->source) {
475
            //if source is array --> convert it to x-editable format.
476
            //Since 1.1.0 source as array with one element is NOT treated as Yii route!
477
            if(is_array($this->source)) {
478
                //if first elem is array assume it's normal x-editable format, so just pass it
479
                if(isset($this->source[0]) && is_array($this->source[0])) {
480
                    $options['source'] = $this->source;
481
                } else { //else convert to x-editable source format {value: 1, text: 'abc'}
482
                    $options['source'] = array();
483
                    foreach($this->source as $value => $text) {
484
                        $options['source'][] = array('value' => $value, 'text' => $text);
485
                    }
486
                }
487
            } else { //source is url string (or js function)
488
                $options['source'] = CHtml::normalizeUrl($this->source);
489
            }
490
        }
491
492
        //callbacks
493
        foreach(array('validate', 'success', 'display') as $method) {
494
            if(isset($this->$method)) {
495
                $options[$method]=(strpos($this->$method, 'js:') !== 0 ? 'js:' : '') . $this->$method;
496
            }
497
        }
498
        
499
        //merging options
500
        $this->options = CMap::mergeArray($this->options, $options);
501
        
502
        //i18n for `clear` in date and datetime
503
        if($this->type == 'date' || $this->type == 'datetime') {
504
            if(!isset($this->options['clear'])) {
505
                $this->options['clear'] = Yii::t('TbEditableField.editable', 'x clear');
506
            }
507
        }
508
    }
509
510
    public function registerClientScript()
511
    {
512
        $selector = "a[rel=\"{$this->htmlOptions['rel']}\"]"; 
513
        if($this->liveTarget) {
514
            $selector = '#'.$this->liveTarget.' '.$selector;
515
        }
516
        $script = "$('".$selector."')";
517
        
518
		//check if script is already registered
519
		if(Yii::app()->getClientScript()->isScriptRegistered(__CLASS__ . '-' . $selector)) {
520
			return true;
521
		}
522
		
523
        //attach events
524
        foreach(array('init', 'shown', 'save', 'hidden') as $event) {
525
            $eventName = 'on'.ucfirst($event);
526
            if (isset($this->$eventName)) {
527
                $eventJs = (strpos($this->$eventName, 'js:') !== 0 ? 'js:' : '') . $this->$eventName;
528
                $script .= "\n.on('".$event."', ".CJavaScript::encode($eventJs).")";
529
            }
530
        }
531
532
        //apply editable
533
        $options = CJavaScript::encode($this->options);
534
        $script .= ".editable($options);";
535
536
        //wrap in anonymous function for live update
537
        if($this->liveTarget) {
538
            $script2 = "\n$('body').on('ajaxUpdate.editable',function(e){ if(e.target.id == '".$this->liveTarget."') yiiEditable2(); });";
539
            $script = "(function yiiEditable() {function yiiEditable2() {\n\t$script\n} $script2 yiiEditable2(); }\n());";
540
        }
541
        
542
        Yii::app()->getClientScript()->registerScript(__CLASS__ . '-' . $selector, $script);
543
544
        return $script;
545
    }
546
547
    public function registerAssets() {
548
    	
549
    	$booster = Booster::getBooster();
550
    	
551
   		if ($this->type == 'date' || $this->type == 'combodate') {
552
            /** @var $widget TbDatePicker */
553
            $widget = Yii::app()->widgetFactory->createWidget(
554
                $this->getOwner(),
555
                'booster.widgets.TbDatePicker',
556
                array('options' => isset($this->options['datepicker']) ? $this->options['datepicker'] : array())
557
            );
558
            $widget->registerLanguageScript();
559
        } elseif ($this->type == 'datetime') {
560
            $booster->registerPackage('datetimepicker');
561
562
            /** @var $widget TbDateTimePicker */
563
            $widget = Yii::app()->widgetFactory->createWidget(
564
                $this->getOwner(),
565
                'booster.widgets.TbDateTimePicker',
566
                array('options' => isset($this->options['datetimepicker']) ? $this->options['datetimepicker'] : array())
567
            );
568
            $widget->registerLanguageScript();
569
        }
570
        
571
        if ($this->type == 'combodate') { // include moment.js if needed
572
            $booster->registerPackage('moment');
573
        } elseif ($this->type == 'select2') { //include select2 if needed
574
            $booster->registerPackage('select2');
575
        }
576
        
577
        $booster->registerPackage('x-editable');
578
    	
579
        return;
580
    	
581
    	/* TODO original */
582
        $am = Yii::app()->getAssetManager();
0 ignored issues
show
Unused Code introduced by
/* TODO original */ $am ...p()->getAssetManager(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
583
        $cs = Yii::app()->getClientScript();
584
        $form = yii::app()->editable->form;
585
        $mode = $this->mode ? $this->mode : yii::app()->editable->defaults['mode'];
586
587
        // bootstrap
588
        if($form === EditableConfig::FORM_BOOTSTRAP) {
589
            if (($bootstrap = yii::app()->getComponent('bootstrap'))) {
590
                $bootstrap->registerCoreCss();
591
                $bootstrap->registerCoreScripts();
592
            } else {
593
                throw new CException('You need to setup Yii-bootstrap extension first.');
594
            }
595
596
            $assetsUrl = $am->publish(Yii::getPathOfAlias('editable.assets.bootstrap-editable'));
597
            $js = 'bootstrap-editable.js';
598
            $css = 'bootstrap-editable.css';
599
        // jqueryui
600
        } elseif($form === EditableConfig::FORM_JQUERYUI) {
601
            if($mode === EditableConfig::POPUP && Yii::getVersion() < '1.1.13' ) {
602
                throw new CException('jQuery UI editable popup supported from Yii 1.1.13+');
603
            }
604
605
            //register jquery ui
606
            $this->registerJQueryUI();
607
608
            $assetsUrl = $am->publish(Yii::getPathOfAlias('editable.assets.jqueryui-editable'));
609
            $js = 'jqueryui-editable.js';
610
            $css = 'jqueryui-editable.css';
611
        // plain jQuery
612
        } else {
613
            $assetsUrl = $am->publish(Yii::getPathOfAlias('editable.assets.jquery-editable'));
614
            $js = 'jquery-editable-poshytip.js';
615
            $css = 'jquery-editable.css';
616
617
            //publish & register poshytip for popup version
618
            if($mode === EditableConfig::POPUP) {
619
                $poshytipUrl = $am->publish(Yii::getPathOfAlias('editable.assets.poshytip'));
620
                $cs->registerScriptFile($poshytipUrl . '/jquery.poshytip.js');
621
                $cs->registerCssFile($poshytipUrl . '/tip-yellowsimple/tip-yellowsimple.css');
622
            }
623
624
            //register jquery ui for datepicker
625
            if($this->type == 'date' || $this->type == 'dateui') {
626
                $this->registerJQueryUI();
627
            }
628
        }
629
630
        //register assets
631
        $cs->registerCssFile($assetsUrl.'/css/'.$css);
632
        $cs->registerScriptFile($assetsUrl.'/js/'.$js, CClientScript::POS_END);
633
634
        //include moment.js for combodate 
635
        if($this->type == 'combodate') {
636
            $momentUrl = $am->publish(Yii::getPathOfAlias('editable.assets.moment'));
637
            $cs->registerScriptFile($momentUrl.'/moment.min.js');          
638
        }
639
        
640
        //include select2 lib for select2 type
641
        if($this->type == 'select2') {
642
            $select2Url = $am->publish(Yii::getPathOfAlias('editable.assets.select2'));
643
            $cs->registerScriptFile($select2Url.'/select2.min.js');  
644
            $cs->registerCssFile($select2Url.'/select2.css');        
645
        }  
646
        
647
        //include bootstrap-datetimepicker
648
        if($this->type == 'datetime') {
649
            $url = $am->publish(Yii::getPathOfAlias('editable.assets.bootstrap-datetimepicker'));
650
            $cs->registerScriptFile($url.'/js/bootstrap-datetimepicker.js');  
651
            $cs->registerCssFile($url.'/css/datetimepicker.css');        
652
        }               
653
        
654
        //TODO: include locale for datepicker
655
        //may be do it manually?
656
        /*
657
        if ($this->type == 'date' && $this->language && substr($this->language, 0, 2) != 'en') {
658
             //todo: check compare dp locale name with yii's
659
             $localesUrl = Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('ext.editable.assets.js.locales'));
660
             Yii::app()->clientScript->registerScriptFile($localesUrl . '/bootstrap-datepicker.'. str_replace('_', '-', $this->language).'.js', CClientScript::POS_END);
661
        }
662
        */
663
    }
664
665
    public function run()
666
    {
667
        //Register script (even if apply = false to support live update)
668
        if($this->apply !== false || $this->liveTarget) {
669
            $this->buildHtmlOptions();
670
            $this->buildJsOptions();
671
            $this->registerAssets();               
672
            $this->registerClientScript();
673
        }
674
        
675
        if($this->apply !== false) {
676
            $this->renderLink();
677
        } else {
678
            $this->renderText();
679
        }
680
    }
681
682
    public function renderLink()
683
    {
684
        echo CHtml::openTag('a', $this->htmlOptions);
685
        $this->renderText();
686
        echo CHtml::closeTag('a');
687
    }
688
689
    public function renderText()
690
    {
691
        echo $this->encode ? CHtml::encode($this->text) : $this->text;
692
    }
693
694
    public function getSelector()
695
    {
696
        //for live updates selector should not contain pk
697
        if($this->liveTarget) {
698
            return $this->name;
699
        }
700
        
701
        $pk = $this->pk;
702
        if($pk === null) {
703
            $pk = 'new';
704
        } else {
705
            //support of composite keys: convert to string: e.g. 'id-1_lang-ru'
706
            if(is_array($pk)) {
707
                $buffer = array();
708
                foreach($pk as $k => $v) {
709
                    $buffer[] = $k.'-'.$v;
710
                }
711
                $pk = join('_', $buffer);
712
            }       
713
        }
714
        
715
        
716
        return $this->name.'_'.$pk;
717
    }
718
719
	/**
720
	 * Returns is autotext should be applied to widget:
721
	 * e.g. for 'select' to display text for id
722
	 *
723
	 * @param mixed $options
724
	 * @param mixed $type
725
	 *
726
	 * @return bool
727
	 */
728
    public static function isAutotext($options, $type) 
729
    {
730
         return (!isset($options['autotext']) || $options['autotext'] !== 'never') 
731
         && in_array($type, array(
732
            'select', 
733
            'checklist', 
734
            'date', 
735
            'datetime', 
736
            'dateui', 
737
            'combodate', 
738
            'select2'
739
         ));
740
    }
741
742
	/**
743
	 * Returns php-array as valid x-editable source in format:
744
	 * [{value: 1, text: 'text1'}, {...}]
745
	 *
746
	 * See https://github.com/vitalets/x-editable-yii/issues/37
747
	 *
748
	 * @param mixed $models
749
	 * @param mixed $valueField
750
	 * @param mixed $textField
751
	 * @param mixed $groupField
752
	 * @param mixed $groupTextField
753
	 *
754
	 * @return array
755
	 */
756
    public static function source($models, $valueField='', $textField='', $groupField='', $groupTextField='')
757
    {
758
        $listData=array();
759
        
760
        $first = reset($models);
761
        
762
        //simple 1-dimensional array: 0 => 'text 0', 1 => 'text 1'
763
        if($first && (is_string($first) || is_numeric($first))) {
764
            foreach($models as $key => $text) {
765
                $listData[] = array('value' => $key, 'text' => $text);
766
            }
767
            return $listData;
768
        } 
769
        
770
        // 2-dimensional array or dataset
771
        if($groupField === '') {
772
            foreach($models as $model) {
773
                $value = CHtml::value($model, $valueField);
774
                $text = CHtml::value($model, $textField);
775
                $listData[] = array('value' => $value, 'text' => $text);
776
            }
777
        } else {
778
            if(!$groupTextField) {
779
                $groupTextField = $groupField;
780
            }
781
            $groups = array();
782
            foreach($models as $model) {
783
                $group=CHtml::value($model,$groupField);
784
                $groupText=CHtml::value($model,$groupTextField);
785
                $value=CHtml::value($model,$valueField);
786
                $text=CHtml::value($model,$textField);
787
                if($group === null) {
788
                    $listData[] = array('value' => $value, 'text' => $text);
789
                } else {
790
                    if(!isset($groups[$group])) {
791
                        $groups[$group] = array('value' => $group, 'text' => $groupText, 'children' => array(), 'index' => count($listData));
792
                        $listData[] = 'group'; //placeholder, will be replaced in future
793
                    }
794
                    $groups[$group]['children'][] = array('value' => $value, 'text' => $text);
795
                }
796
            }
797
 
798
            //fill placeholders with group data           
799
            foreach($groups as $group) {
800
                $index = $group['index'];
801
                unset($group['index']);
802
                $listData[$index] = $group;                      
803
            }
804
        }
805
        
806
        return $listData;
807
    }
808
809
    /**
810
    * injects ajaxUpdate event into widget
811
    *
812
    * @param TbGridView $widget
813
    */
814
    public static function attachAjaxUpdateEvent($widget)
815
    {
816
        $trigger = '$("#'.$widget->id.'").trigger("ajaxUpdate.editable");';
817
818
        //check if trigger already inserted by another column
819
        if(strpos($widget->afterAjaxUpdate, $trigger) !== false) return;
820
821
        //inserting trigger
822
        if(strlen($widget->afterAjaxUpdate)) {
823
            $orig = $widget->afterAjaxUpdate;
824
            if(strpos($orig, 'js:')===0) $orig = substr($orig,3);
825
            $orig = "\n($orig).apply(this, arguments);";
826
        } else {
827
            $orig = '';
828
        }
829
        $widget->afterAjaxUpdate = "js: function(id, data) {
830
            $trigger $orig
831
        }";
832
833
        $widget->registerClientScript();
834
    }    
835
    
836
837
    /**
838
    * method to register jQuery UI with build-in or custom theme
839
    *
840
    */
841
    protected function registerJQueryUI()
842
    {
843
        $cs=Yii::app()->getClientScript();
844
        if($this->themeUrl===null) {
845
            $this->themeUrl=$cs->getCoreScriptUrl().'/jui/css';
846
        }
847
        $cs->registerCssFile($this->themeUrl.'/'.$this->theme.'/'.$this->cssFile);
848
        $cs->registerPackage('jquery.ui');
849
    }
850
}
851