SilverStripeContext   C
last analyzed

Complexity

Total Complexity 77

Size/Duplication

Total Lines 515
Duplicated Lines 11.46 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 22
Bugs 3 Features 2
Metric Value
c 22
b 3
f 2
dl 59
loc 515
rs 5.4715
wmc 77
lcom 2
cbo 10

29 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setDatabase() 0 4 1
A setAjaxSteps() 0 6 2
A getAjaxSteps() 0 4 1
A setAjaxTimeout() 0 4 1
A getAjaxTimeout() 0 4 1
A setAdminUrl() 0 4 1
A getAdminUrl() 0 4 1
A setLoginUrl() 0 4 1
A getLoginUrl() 0 4 1
A setScreenshotPath() 0 4 1
A getScreenshotPath() 0 4 1
A getRegionMap() 0 4 1
A setRegionMap() 0 4 1
D getRegionObj() 0 43 9
D before() 0 34 9
A getTestSessionState() 0 12 1
A parseUrl() 0 13 3
B isCurrentUrlSimilarTo() 0 21 7
A getBaseUrl() 0 4 2
A joinUrlParts() 0 14 2
A canIntercept() 0 14 3
A theRedirectionsAreIntercepted() 0 8 2
B fillField() 16 22 4
A clickLink() 15 21 4
A givenTheCurrentDateIs() 14 15 4
A givenTheCurrentTimeIs() 14 15 4
A selectOption() 0 15 3
B selectOptionWithJavascript() 0 45 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SilverStripeContext often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SilverStripeContext, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\BehatExtension\Context;
4
5
use Behat\Behat\Context\Step;
6
use Behat\Behat\Event\FeatureEvent;
7
use Behat\Behat\Event\ScenarioEvent;
8
use Behat\Behat\Event\SuiteEvent;
9
use Behat\Gherkin\Node\PyStringNode;
10
use Behat\MinkExtension\Context\MinkContext;
11
use Behat\Mink\Driver\GoutteDriver;
12
use Behat\Mink\Driver\Selenium2Driver;
13
use Behat\Mink\Exception\UnsupportedDriverActionException;
14
use Behat\Mink\Exception\ElementNotFoundException;
15
16
use SilverStripe\BehatExtension\Context\SilverStripeAwareContextInterface;
17
18
use Symfony\Component\Yaml\Yaml;
19
20
// Mink etc.
21
require_once 'vendor/autoload.php';
22
23
require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';
24
25
/**
26
 * SilverStripeContext
27
 *
28
 * Generic context wrapper used as a base for Behat FeatureContext.
29
 */
30
class SilverStripeContext extends MinkContext implements SilverStripeAwareContextInterface
31
{
32
    protected $databaseName;
33
34
    /**
35
     * @var Array Partial string match for step names
36
     * that are considered to trigger Ajax request in the CMS,
37
     * and hence need special timeout handling.
38
     * @see \SilverStripe\BehatExtension\Context\BasicContext->handleAjaxBeforeStep().
39
     */
40
    protected $ajaxSteps;
41
42
    /**
43
     * @var Int Timeout in milliseconds, after which the interface assumes
44
     * that an Ajax request has timed out, and continues with assertions.
45
     */
46
    protected $ajaxTimeout;
47
48
    /**
49
     * @var String Relative URL to the SilverStripe admin interface.
50
     */
51
    protected $adminUrl;
52
53
    /**
54
     * @var String Relative URL to the SilverStripe login form.
55
     */
56
    protected $loginUrl;
57
58
    /**
59
     * @var String Relative path to a writeable folder where screenshots can be stored.
60
     * If set to NULL, no screenshots will be stored.
61
     */
62
    protected $screenshotPath;
63
64
    protected $context;
65
66
    protected $testSessionEnvironment;
67
68
69
    /**
70
     * Initializes context.
71
     * Every scenario gets it's own context object.
72
     *
73
     * @param   array   $parameters     context parameters (set them up through behat.yml)
74
     */
75
    public function __construct(array $parameters)
76
    {
77
        // Initialize your context here
78
        $this->context = $parameters;
79
        $this->testSessionEnvironment = new \TestSessionEnvironment();
80
    }
81
82
    public function setDatabase($databaseName)
83
    {
84
        $this->databaseName = $databaseName;
85
    }
86
87
    public function setAjaxSteps($ajaxSteps)
88
    {
89
        if ($ajaxSteps) {
90
            $this->ajaxSteps = $ajaxSteps;
91
        }
92
    }
93
94
    public function getAjaxSteps()
95
    {
96
        return $this->ajaxSteps;
97
    }
98
99
    public function setAjaxTimeout($ajaxTimeout)
100
    {
101
        $this->ajaxTimeout = $ajaxTimeout;
102
    }
103
104
    public function getAjaxTimeout()
105
    {
106
        return $this->ajaxTimeout;
107
    }
108
109
    public function setAdminUrl($adminUrl)
110
    {
111
        $this->adminUrl = $adminUrl;
112
    }
113
114
    public function getAdminUrl()
115
    {
116
        return $this->adminUrl;
117
    }
118
119
    public function setLoginUrl($loginUrl)
120
    {
121
        $this->loginUrl = $loginUrl;
122
    }
123
124
    public function getLoginUrl()
125
    {
126
        return $this->loginUrl;
127
    }
128
129
    public function setScreenshotPath($screenshotPath)
130
    {
131
        $this->screenshotPath = $screenshotPath;
132
    }
133
134
    public function getScreenshotPath()
135
    {
136
        return $this->screenshotPath;
137
    }
138
139
    public function getRegionMap()
140
    {
141
        return $this->regionMap;
0 ignored issues
show
Bug introduced by
The property regionMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
142
    }
143
144
    public function setRegionMap($regionMap)
145
    {
146
        $this->regionMap = $regionMap;
147
    }
148
149
    /**
150
     * Returns MinkElement based off region defined in .yml file.
151
     * Also supports direct CSS selectors and regions identified by a "data-title" attribute.
152
     * When using the "data-title" attribute, ensure not to include double quotes.
153
     *
154
     * @param String $region Region name or CSS selector
155
     * @return MinkElement|null
156
     */
157
    public function getRegionObj($region)
158
    {
159
        // Try to find regions directly by CSS selector.
160
        try {
161
            $regionObj = $this->getSession()->getPage()->find(
162
                'css',
163
                // Escape CSS selector
164
                (false !== strpos($region, "'")) ? str_replace("'", "\'", $region) : $region
165
            );
166
            if ($regionObj) {
167
                return $regionObj;
168
            }
169
        } catch (\Symfony\Component\CssSelector\Exception\SyntaxErrorException $e) {
170
            // fall through to next case
171
        }
172
173
        // Fall back to region identified by data-title.
174
        // Only apply if no double quotes exist in search string,
175
        // which would break the CSS selector.
176
        if (false === strpos($region, '"')) {
177
            $regionObj = $this->getSession()->getPage()->find(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $regionObj is correct as $this->getSession()->get...le="' . $region . '"]') (which targets Behat\Mink\Element\Element::find()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
178
                'css',
179
                '[data-title="' . $region . '"]'
180
            );
181
            if ($regionObj) {
182
                return $regionObj;
183
            }
184
        }
185
186
        // Look for named region
187
        if (!$this->regionMap) {
188
            throw new \LogicException("Cannot find 'region_map' in the behat.yml");
189
        }
190
        if (!array_key_exists($region, $this->regionMap)) {
191
            throw new \LogicException("Cannot find the specified region in the behat.yml");
192
        }
193
        $regionObj = $this->getSession()->getPage()->find('css', $region);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $regionObj is correct as $this->getSession()->get...)->find('css', $region) (which targets Behat\Mink\Element\Element::find()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
194
        if (!$regionObj) {
195
            throw new ElementNotFoundException("Cannot find the specified region on the page");
0 ignored issues
show
Documentation introduced by
'Cannot find the specified region on the page' is of type string, but the function expects a object<Behat\Mink\Session>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
196
        }
197
198
        return $regionObj;
199
    }
200
201
    /**
202
     * @BeforeScenario
203
     */
204
    public function before(ScenarioEvent $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
205
    {
206
        if (!isset($this->databaseName)) {
207
            throw new \LogicException(
208
                'Context\'s $databaseName has to be set when implementing SilverStripeAwareContextInterface.'
209
            );
210
        }
211
212
        $state = $this->getTestSessionState();
213
        $this->testSessionEnvironment->startTestSession($state);
214
215
        // Optionally import database
216
        if (!empty($state['importDatabasePath'])) {
217
            $this->testSessionEnvironment->importDatabase(
218
                $state['importDatabasePath'],
219
                !empty($state['requireDefaultRecords']) ? $state['requireDefaultRecords'] : false
220
            );
221
        } elseif (!empty($state['requireDefaultRecords']) && $state['requireDefaultRecords']) {
222
            $this->testSessionEnvironment->requireDefaultRecords();
223
        }
224
225
        // Fixtures
226
        $fixtureFile = (!empty($state['fixture'])) ? $state['fixture'] : null;
227
        if ($fixtureFile) {
228
            $this->testSessionEnvironment->loadFixtureIntoDb($fixtureFile);
229
        }
230
231
        if ($screenSize = getenv('BEHAT_SCREEN_SIZE')) {
232
            list($screenWidth, $screenHeight) = explode('x', $screenSize);
233
            $this->getSession()->resizeWindow((int)$screenWidth, (int)$screenHeight);
234
        } else {
235
            $this->getSession()->resizeWindow(1024, 768);
236
        }
237
    }
238
239
    /**
240
     * Returns a parameter map of state to set within the test session.
241
     * Takes TESTSESSION_PARAMS environment variable into account for run-specific configurations.
242
     *
243
     * @return array
244
     */
245
    public function getTestSessionState()
246
    {
247
        $extraParams = array();
248
        parse_str(getenv('TESTSESSION_PARAMS'), $extraParams);
249
        return array_merge(
250
            array(
251
                'database' => $this->databaseName,
252
                'mailer' => 'SilverStripe\BehatExtension\Utility\TestMailer',
253
            ),
254
            $extraParams
255
        );
256
    }
257
258
    /**
259
     * Parses given URL and returns its components
260
     *
261
     * @param $url
262
     * @return array|mixed Parsed URL
263
     */
264
    public function parseUrl($url)
265
    {
266
        $url = parse_url($url);
267
        $url['vars'] = array();
268
        if (!isset($url['fragment'])) {
269
            $url['fragment'] = null;
270
        }
271
        if (isset($url['query'])) {
272
            parse_str($url['query'], $url['vars']);
273
        }
274
275
        return $url;
276
    }
277
278
    /**
279
     * Checks whether current URL is close enough to the given URL.
280
     * Unless specified in $url, get vars will be ignored
281
     * Unless specified in $url, fragment identifiers will be ignored
282
     *
283
     * @param $url string URL to compare to current URL
284
     * @return boolean Returns true if the current URL is close enough to the given URL, false otherwise.
285
     */
286
    public function isCurrentUrlSimilarTo($url)
287
    {
288
        $current = $this->parseUrl($this->getSession()->getCurrentUrl());
289
        $test = $this->parseUrl($url);
290
291
        if ($current['path'] !== $test['path']) {
292
            return false;
293
        }
294
295
        if (isset($test['fragment']) && $current['fragment'] !== $test['fragment']) {
296
            return false;
297
        }
298
299
        foreach ($test['vars'] as $name => $value) {
300
            if (!isset($current['vars'][$name]) || $current['vars'][$name] !== $value) {
301
                return false;
302
            }
303
        }
304
305
        return true;
306
    }
307
308
    /**
309
     * Returns base URL parameter set in MinkExtension.
310
     * It simplifies configuration by allowing to specify this parameter
311
     * once but makes code dependent on MinkExtension.
312
     *
313
     * @return string
314
     */
315
    public function getBaseUrl()
316
    {
317
        return $this->getMinkParameter('base_url') ?: '';
318
    }
319
320
    /**
321
     * Joins URL parts into an URL using forward slash.
322
     * Forward slash usages are normalised to one between parts.
323
     * This method takes variable number of parameters.
324
     *
325
     * @param $...
326
     * @return string
327
     * @throws \InvalidArgumentException
328
     */
329
    public function joinUrlParts()
330
    {
331
        if (0 === func_num_args()) {
332
            throw new \InvalidArgumentException('Need at least one argument');
333
        }
334
335
        $parts = func_get_args();
336
        $trimSlashes = function (&$part) {
337
            $part = trim($part, '/');
338
        };
339
        array_walk($parts, $trimSlashes);
340
341
        return implode('/', $parts);
342
    }
343
344
    public function canIntercept()
345
    {
346
        $driver = $this->getSession()->getDriver();
347
        if ($driver instanceof GoutteDriver) {
348
            return true;
349
        } else {
350
            if ($driver instanceof Selenium2Driver) {
351
                return false;
352
            }
353
        }
354
355
        throw new UnsupportedDriverActionException('You need to tag the scenario with "@mink:goutte" or
356
			"@mink:symfony". Intercepting the redirections is not supported by %s', $driver);
357
    }
358
359
    /**
360
     * @Given /^(.*) without redirection$/
361
     */
362
    public function theRedirectionsAreIntercepted($step)
363
    {
364
        if ($this->canIntercept()) {
365
            $this->getSession()->getDriver()->getClient()->followRedirects(false);
0 ignored issues
show
Bug introduced by
The method getClient() does not seem to exist on object<Behat\Mink\Driver\DriverInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
366
        }
367
368
        return new Step\Given($step);
369
    }
370
371
    /**
372
     * Fills in form field with specified id|name|label|value.
373
     * Overwritten to select the first *visible* element, see https://github.com/Behat/Mink/issues/311
374
     */
375 View Code Duplication
    public function fillField($field, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
376
    {
377
        $value = $this->fixStepArgument($value);
378
        $fields = $this->getSession()->getPage()->findAll('named', array(
379
            'field', $this->getSession()->getSelectorsHandler()->xpathLiteral($field)
380
        ));
381
        if ($fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type Behat\Mink\Element\NodeElement[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
382
            foreach ($fields as $f) {
383
                if ($f->isVisible()) {
384
                    $f->setValue($value);
385
                    return;
386
                }
387
            }
388
        }
389
390
        throw new ElementNotFoundException(
391
            $this->getSession(),
392
            'form field',
393
            'id|name|label|value',
394
            $field
395
        );
396
    }
397
398
    /**
399
     * Overwritten to click the first *visable* link the DOM.
400
     */
401 View Code Duplication
    public function clickLink($link)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
402
    {
403
        $link = $this->fixStepArgument($link);
404
        $links = $this->getSession()->getPage()->findAll('named', array(
405
            'link', $this->getSession()->getSelectorsHandler()->xpathLiteral($link)
406
        ));
407
        if ($links) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $links of type Behat\Mink\Element\NodeElement[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
408
            foreach ($links as $l) {
409
                if ($l->isVisible()) {
410
                    $l->click();
411
                    return;
412
                }
413
            }
414
        }
415
        throw new ElementNotFoundException(
416
            $this->getSession(),
417
            'link',
418
            'id|name|label|value',
419
            $link
420
        );
421
    }
422
423
     /**
424
     * Sets the current date. Relies on the underlying functionality using
425
     * {@link SS_Datetime::now()} rather than PHP's system time methods like date().
426
     * Supports ISO fomat: Y-m-d
427
     * Example: Given the current date is "2009-10-31"
428
     *
429
     * @Given /^the current date is "([^"]*)"$/
430
     */
431 View Code Duplication
    public function givenTheCurrentDateIs($date)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
432
    {
433
        $newDatetime = \DateTime::createFromFormat('Y-m-d', $date);
434
        if (!$newDatetime) {
435
            throw new InvalidArgumentException(sprintf('Invalid date format: %s (requires "Y-m-d")', $date));
436
        }
437
438
        $state = $this->testSessionEnvironment->getState();
439
        $oldDatetime = \DateTime::createFromFormat('Y-m-d H:i:s', isset($state->datetime) ? $state->datetime : null);
440
        if ($oldDatetime) {
441
            $newDatetime->setTime($oldDatetime->format('H'), $oldDatetime->format('i'), $oldDatetime->format('s'));
442
        }
443
        $state->datetime = $newDatetime->format('Y-m-d H:i:s');
444
        $this->testSessionEnvironment->applyState($state);
445
    }
446
447
    /**
448
     * Sets the current time. Relies on the underlying functionality using
449
     * {@link \SS_Datetime::now()} rather than PHP's system time methods like date().
450
     * Supports ISO fomat: H:i:s
451
     * Example: Given the current time is "20:31:50"
452
     *
453
     * @Given /^the current time is "([^"]*)"$/
454
     */
455 View Code Duplication
    public function givenTheCurrentTimeIs($time)
0 ignored issues
show
Unused Code introduced by
The parameter $time is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
456
    {
457
        $newDatetime = \DateTime::createFromFormat('H:i:s', $date);
0 ignored issues
show
Bug introduced by
The variable $date does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
458
        if (!$newDatetime) {
459
            throw new InvalidArgumentException(sprintf('Invalid date format: %s (requires "H:i:s")', $date));
460
        }
461
462
        $state = $this->testSessionEnvironment->getState();
463
        $oldDatetime = \DateTime::createFromFormat('Y-m-d H:i:s', isset($state->datetime) ? $state->datetime : null);
464
        if ($oldDatetime) {
465
            $newDatetime->setDate($oldDatetime->format('Y'), $oldDatetime->format('m'), $oldDatetime->format('d'));
466
        }
467
        $state->datetime = $newDatetime->format('Y-m-d H:i:s');
468
        $this->testSessionEnvironment->applyState($state);
469
    }
470
471
    /**
472
     * Selects option in select field with specified id|name|label|value.
473
     *
474
     * @override /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
475
     */
476
    public function selectOption($select, $option)
477
    {
478
        // Find field
479
        $field = $this
480
            ->getSession()
481
            ->getPage()
482
            ->findField($this->fixStepArgument($select));
483
484
        // If field is visible then select it as per normal
485
        if ($field && $field->isVisible()) {
486
            parent::selectOption($select, $option);
487
        } else {
488
            $this->selectOptionWithJavascript($select, $option);
489
        }
490
    }
491
492
    /**
493
     * Selects option in select field with specified id|name|label|value using javascript
494
     * This method uses javascript to allow selection of options that may be
495
     * overridden by javascript libraries, and thus hide the element.
496
     *
497
     * @When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)" with javascript$/
498
     */
499
    public function selectOptionWithJavascript($select, $option)
500
    {
501
        $select = $this->fixStepArgument($select);
502
        $option = $this->fixStepArgument($option);
503
        $page = $this->getSession()->getPage();
504
505
        // Find field
506
        $field = $page->findField($select);
507
        if (null === $field) {
508
            throw new ElementNotFoundException($this->getSession(), 'form field', 'id|name|label|value', $select);
509
        }
510
511
        // Find option
512
        $opt = $field->find('named', array(
513
            'option', $this->getSession()->getSelectorsHandler()->xpathLiteral($option)
514
        ));
515
        if (null === $opt) {
516
            throw new ElementNotFoundException($this->getSession(), 'select option', 'value|text', $option);
517
        }
518
519
        // Merge new option in with old handling both multiselect and single select
520
        $value = $field->getValue();
521
        $newValue = $opt->getAttribute('value');
522
        if (is_array($value)) {
523
            if (!in_array($newValue, $value)) {
524
                $value[] = $newValue;
525
            }
526
        } else {
527
            $value = $newValue;
528
        }
529
        $valueEncoded = json_encode($value);
530
531
        // Inject this value via javascript
532
        $fieldID = $field->getAttribute('ID');
533
        $script = <<<EOS
534
			(function($) {
535
				$("#$fieldID")
536
					.val($valueEncoded)
537
					.change()
538
					.trigger('liszt:updated')
539
					.trigger('chosen:updated');
540
			})(jQuery);
541
EOS;
542
        $this->getSession()->getDriver()->executeScript($script);
543
    }
544
}
545