Completed
Push — 146-drush-9 ( 7bf876 )
by Jonathan
01:23
created

DrushDriver::nodeDelete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Drupal\Driver;
4
5
use Drupal\Component\Utility\Random;
6
use Drupal\Driver\Exception\BootstrapException;
7
8
use Symfony\Component\Process\Process;
9
10
/**
11
 * Implements DriverInterface.
12
 */
13
class DrushDriver extends BaseDriver {
14
  /**
15
   * Store a drush alias for tests requiring shell access.
16
   *
17
   * @var string
18
   */
19
  public $alias;
20
21
  /**
22
   * Stores the root path to a Drupal installation.
23
   *
24
   * This is an alternative to using drush aliases.
25
   *
26
   * @var string
27
   */
28
  public $root;
29
30
  /**
31
   * Store the path to drush binary.
32
   *
33
   * @var string
34
   */
35
  public $binary;
36
37
  /**
38
   * Track bootstrapping.
39
   *
40
   * @var bool
41
   */
42
  private $bootstrapped = FALSE;
43
44
  /**
45
   * Random generator.
46
   *
47
   * @var \Drupal\Component\Utility\Random
48
   */
49
  private $random;
50
51
  /**
52
   * Global arguments or options for drush commands.
53
   *
54
   * @var string
55
   */
56
  private $arguments = '';
57
58
  /**
59
   * Tracks version.
60
   */
61
  protected static $isLegacyDrush;
62
63
  /**
64
   * Set drush alias or root path.
65
   *
66
   * @param string $alias
67
   *   A drush alias.
68
   * @param string $root_path
69
   *   The root path of the Drupal install. This is an alternative to using
70
   *   aliases.
71
   * @param string $binary
72
   *   The path to the drush binary.
73
   * @param \Drupal\Component\Utility\Random $random
74
   *   Random generator.
75
   *
76
   * @throws \Drupal\Driver\Exception\BootstrapException
77
   *   Thrown when a required parameter is missing.
78
   */
79
  public function __construct($alias = NULL, $root_path = NULL, $binary = 'drush', Random $random = NULL) {
80
    if (!empty($alias)) {
81
      // Trim off the '@' symbol if it has been added.
82
      $alias = ltrim($alias, '@');
83
84
      $this->alias = $alias;
85
    }
86
    elseif (!empty($root_path)) {
87
      $this->root = realpath($root_path);
88
    }
89
    else {
90
      throw new BootstrapException('A drush alias or root path is required.');
91
    }
92
93
    $this->binary = $binary;
94
95
    if (!isset($random)) {
96
      $random = new Random();
97
    }
98
    $this->random = $random;
99
  }
100
101
  /**
102
   * {@inheritdoc}
103
   */
104
  public function getRandom() {
105
    return $this->random;
106
  }
107
108
  /**
109
   * {@inheritdoc}
110
   */
111
  public function bootstrap() {
112
    // Check that the given alias works.
113
    // @todo check that this is a functioning alias.
114
    // See http://drupal.org/node/1615450
115
    if (!isset($this->alias) && !isset($this->root)) {
116
      throw new BootstrapException('A drush alias or root path is required.');
117
    }
118
119
    // Determine if drush version is legacy.
120
    if (!isset(self::$isLegacyDrush)) {
121
      self::$isLegacyDrush = $this->isLegacyDrush();
122
    }
123
124
    $this->bootstrapped = TRUE;
125
  }
126
127
  /**
128
   * Determine if drush is a legacy version.
129
   *
130
   * @return bool
131
   *   Returns TRUE if drush is older than drush 9.
132
   */
133
  protected function isLegacyDrush() {
134
    try {
135
      // Try for a drush 9 version.
136
      $version = unserialize($this->drush('version', [], ['format' => 'php']));
137
      return version_compare($version['drush-version'], '9', '<=');
138
    }
139
    catch (\RuntimeException $e) {
140
      return TRUE;
141
    }
142
  }
143
144
  /**
145
   * {@inheritdoc}
146
   */
147
  public function isBootstrapped() {
148
    return $this->bootstrapped;
149
  }
150
151
  /**
152
   * {@inheritdoc}
153
   */
154
  public function userCreate(\stdClass $user) {
155
    $arguments = array(
156
      sprintf('"%s"', $user->name),
157
    );
158
    $options = array(
159
      'password' => $user->pass,
160
      'mail' => $user->mail,
161
    );
162
    $this->drush('user-create', $arguments, $options);
163
    if (isset($user->roles) && is_array($user->roles)) {
164
      foreach ($user->roles as $role) {
165
        $this->userAddRole($user, $role);
166
      }
167
    }
168
  }
169
170
  /**
171
   * {@inheritdoc}
172
   */
173
  public function userDelete(\stdClass $user) {
174
    $arguments = array(sprintf('"%s"', $user->name));
175
    $options = array(
176
      'yes' => NULL,
177
      'delete-content' => NULL,
178
    );
179
    $this->drush('user-cancel', $arguments, $options);
180
  }
181
182
  /**
183
   * {@inheritdoc}
184
   */
185
  public function userAddRole(\stdClass $user, $role) {
186
    $arguments = array(
187
      sprintf('"%s"', $role),
188
      sprintf('"%s"', $user->name),
189
    );
190
    $this->drush('user-add-role', $arguments);
191
  }
192
193
  /**
194
   * {@inheritdoc}
195
   */
196
  public function fetchWatchdog($count = 10, $type = NULL, $severity = NULL) {
197
    $options = array(
198
      'count' => $count,
199
      'type' => $type,
200
      'severity' => $severity,
201
    );
202
    return $this->drush('watchdog-show', array(), $options);
203
  }
204
205
  /**
206
   * {@inheritdoc}
207
   */
208
  public function clearCache($type = 'all') {
209
    $type = array($type);
210
    return $this->drush('cache-clear', $type, array());
211
  }
212
213
  /**
214
   * {@inheritdoc}
215
   */
216
  public function clearStaticCaches() {
217
    // The drush driver does each operation as a separate request;
218
    // therefore, 'clearStaticCaches' can be a no-op.
219
  }
220
221
  /**
222
   * Decodes JSON object returned by Drush.
223
   *
224
   * It will clean up any junk that may have appeared before or after the
225
   * JSON object. This can happen with remote Drush aliases.
226
   *
227
   * @param string $output
228
   *   The output from Drush.
229
   *
230
   * @return object
231
   *   The decoded JSON object.
232
   */
233
  protected function decodeJsonObject($output) {
234
    // Remove anything before the first '{'.
235
    $output = preg_replace('/^[^\{]*/', '', $output);
236
    // Remove anything after the last '}'.
237
    $output = preg_replace('/[^\}]*$/s', '', $output);
238
    return json_decode($output);
239
  }
240
241
  /**
242
   * {@inheritdoc}
243
   */
244
  public function createNode($node) {
245
    $result = $this->drush('behat', array('create-node', escapeshellarg(json_encode($node))), array());
246
    return $this->decodeJsonObject($result);
247
  }
248
249
  /**
250
   * {@inheritdoc}
251
   */
252
  public function nodeDelete($node) {
253
    $this->drush('behat', array('delete-node', escapeshellarg(json_encode($node))), array());
254
  }
255
256
  /**
257
   * {@inheritdoc}
258
   */
259
  public function createTerm(\stdClass $term) {
260
    $result = $this->drush('behat', array('create-term', escapeshellarg(json_encode($term))), array());
261
    return $this->decodeJsonObject($result);
262
  }
263
264
  /**
265
   * {@inheritdoc}
266
   */
267
  public function termDelete(\stdClass $term) {
268
    $this->drush('behat', array('delete-term', escapeshellarg(json_encode($term))), array());
269
  }
270
271
  /**
272
   * {@inheritdoc}
273
   */
274
  public function isField($entity_type, $field_name) {
275
    // If the Behat Drush Endpoint is not installed on the site-under-test,
276
    // then the drush() method will throw an exception. In this instance, we
277
    // want to treat all potential fields as non-fields.  This allows the
278
    // Drush Driver to work with certain built-in Drush capabilities (e.g.
279
    // creating users) even if the Behat Drush Endpoint is not available.
280
    try {
281
      $value = array($entity_type, $field_name);
282
      $arguments = array('is-field', escapeshellarg(json_encode($value)));
283
      $result = $this->drush('behat', $arguments, array());
284
      return strpos($result, "true\n") !== FALSE;
285
    }
286
    catch (\Exception $e) {
287
      return FALSE;
288
    }
289
  }
290
291
  /**
292
   * Sets common drush arguments or options.
293
   *
294
   * @param string $arguments
295
   *   Global arguments to add to every drush command.
296
   */
297
  public function setArguments($arguments) {
298
    $this->arguments = $arguments;
299
  }
300
301
  /**
302
   * Get common drush arguments.
303
   */
304
  public function getArguments() {
305
    return $this->arguments;
306
  }
307
308
  /**
309
   * Parse arguments into a string.
310
   *
311
   * @param array $arguments
312
   *   An array of argument/option names to values.
313
   *
314
   * @return string
315
   *   The parsed arguments.
316
   */
317
  protected static function parseArguments(array $arguments) {
318
    $string = '';
319
    foreach ($arguments as $name => $value) {
320
      if (is_null($value)) {
321
        $string .= ' --' . $name;
322
      }
323
      else {
324
        $string .= ' --' . $name . '=' . $value;
325
      }
326
    }
327
    return $string;
328
  }
329
330
  /**
331
   * Execute a drush command.
332
   */
333
  public function drush($command, array $arguments = array(), array $options = array()) {
334
    $arguments = implode(' ', $arguments);
335
336
    // Disable colored output from drush.
337
    if (isset(static::$isLegacyDrush) && static::$isLegacyDrush) {
338
      $options['nocolor'] = TRUE;
339
    }
340
    else {
341
      $options['no-ansi'] = NULL;
342
    }
343
    $string_options = $this->parseArguments($options);
344
345
    $alias = isset($this->alias) ? "@{$this->alias}" : '--root=' . $this->root;
346
347
    // Add any global arguments.
348
    $global = $this->getArguments();
349
350
    $process = new Process("{$this->binary} {$alias} {$string_options} {$global} {$command} {$arguments}");
351
    $process->setTimeout(3600);
352
    $process->run();
353
354
    if (!$process->isSuccessful()) {
355
      throw new \RuntimeException($process->getErrorOutput());
356
    }
357
358
    // Some drush commands write to standard error output (for example enable
359
    // use drush_log which default to _drush_print_log) instead of returning a
360
    // string (drush status use drush_print_pipe).
361
    if (!$process->getOutput()) {
362
      return $process->getErrorOutput();
363
    }
364
    else {
365
      return $process->getOutput();
366
    }
367
368
  }
369
370
  /**
371
   * {@inheritdoc}
372
   */
373
  public function processBatch() {
374
    // Do nothing. Drush should internally handle any needs for processing
375
    // batch ops.
376
  }
377
378
  /**
379
   * {@inheritdoc}
380
   */
381
  public function runCron() {
382
    $this->drush('cron');
383
  }
384
385
  /**
386
   * Run Drush commands dynamically from a DrupalContext.
387
   */
388
  public function __call($name, $arguments) {
389
    return $this->drush($name, $arguments);
390
  }
391
392
}
393