Completed
Pull Request — master (#392)
by
unknown
01:27
created

RawMailContext::getMailManager()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace Drupal\DrupalExtension\Context;
4
5
use Drupal\DrupalMailManager;
6
7
/**
8
 * Provides helper methods for interacting with mail.
9
 */
10
class RawMailContext extends RawDrupalContext {
11
12
  /**
13
   * The mail manager.
14
   *
15
   * @var \Drupal\DrupalMailManagerInterface
16
   */
17
  protected $mailManager;
18
19
  /**
20
   * The number of mails received so far in this scenario, for each mail store.
21
   *
22
   * @var array
23
   */
24
  protected $mailCount = [];
25
26
  /**
27
   * Get the mail manager service that handles stored test mail.
28
   * 
29
   * @return \Drupal\DrupalMailManagerInterface
30
   *   The mail manager service.
31
   */
32
  protected function getMailManager() {
33
    // Persist the mail manager between invocations. This is necessary for
34
    // remembering and reinstating the original mail backend.
35
    if (is_null($this->mailManager)) {
36
      $this->mailManager = new DrupalMailManager($this->getDriver());
37
    }
38
    return $this->mailManager;
39
  }
40
41
  /**
42
   * Get collected mail, matching certain specifications.
43
   *
44
   * @param array $matches
45
   *   Associative array of mail fields and the values to filter by.
46
   * @param bool $new
47
   *   Whether to ignore previously seen mail.
48
   * @param null|int $index
49
   *   A particular mail to return, e.g. 0 for first or -1 for last.
50
   * @param string $store
51
   *   The name of the mail store to get mail from.
52
   *
53
   * @return \stdClass[]
54
   *   An array of mail, each formatted as a Drupal 8
55
   * \Drupal\Core\Mail\MailInterface::mail $message array.
56
   */
57
  protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = 'default') {
58
    $mail = $this->getMailManager()->getMail($store);
59
    $previousMailCount = $this->getMailCount($store);
60
    $this->mailCount[$store] = count($mail);
61
62
    // Ignore previously seen mail.
63
    if ($new) {
64
      $mail = array_slice($mail, $previousMailCount);
65
    }
66
67
    // Filter mail based on $matches; keep only mail where each field mentioned
68
    // in $matches contains the value specified for that field.
69
    $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) {
70
      return ($this->matchesMail($singleMail, $matches));
71
    }));
72
73
    // Return an individual mail if specified by an index.
74
    if (is_null($index) || count($mail) === 0) {
75
      return $mail;
76
    }
77
    else {
78
      return array_slice($mail, $index, 1)[0];
79
    }
80
  }
81
82
  /**
83
   * Get the number of mails received in a particular mail store.
84
   *
85
   * @return int
86
   *   The number of mails received during this scenario.
87
   */
88
  protected function getMailCount($store) {
89
    if (array_key_exists($store, $this->mailCount)) {
90
      $count = $this->mailCount[$store];
91
    }
92
    else {
93
      $count = 0;
94
    }
95
    return $count;
96
  }
97
98
  /**
99
   * Determine if a mail meets criteria.
100
   *
101
   * @param array $mail
102
   *   The mail, as an array of mail fields.
103
   * @param array $matches
104
   *   The criteria: an associative array of mail fields and desired values.
105
   *
106
   * @return bool
107
   *   Whether the mail matches the criteria.
108
   */
109
  protected function matchesMail($mail = [], $matches = []) {
110
    // Discard criteria that are just zero-length strings.
111
    $matches = array_filter($matches, 'strlen');
112
    // For each criteria, check the specified mail field contains the value.
113
    foreach($matches as $field => $value) {
114
      // Case insensitive.
115
      if (stripos($mail[$field], $value) === FALSE) {
116
        return FALSE;
117
      }
118
    }
119
    return TRUE;
120
  }
121
122
  /**
123
   * Compare actual mail with expected mail.
124
   *
125
   * @param array $actualMail
126
   *   An array of actual mail.
127
   * @param array $expectedMail
128
   *   An array of expected mail.
129
   */
130
  protected function compareMail($actualMail, $expectedMail) {
131
    // Make sure there is the same number of actual and expected.
132
    $expectedCount = count($expectedMail);
133
    $this->assertMailCount($actualMail, $expectedCount);
134
135
    // For each row of expected mail, check the corresponding actual mail.
136
    // Make the comparison insensitive to the order mails were sent.
137
    $actualMail = $this->sortMail($actualMail);
138
    $expectedMail = $this->sortMail($expectedMail);
139
    foreach ($expectedMail as $index => $expectedMailItem) {
140
      // For each column of the expected, check the field of the actual mail.
141
      foreach ($expectedMailItem as $fieldName => $fieldValue) {
142
        $expectedField = [$fieldName => $fieldValue];
143
        $match = $this->matchesMail($actualMail[$index], $expectedField);
144
        if (!$match) {
145
          throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "...")));
146
        }
147
      }
148
    }
149
  }
150
151
  /**
152
   * Assert there is the expected number of mails, or that there are some mails
153
   * if the exact number expected is not specified.
154
   *
155
   * @param array $actualMail
156
   *   An array of actual mail.
157
   * @param int $expectedCount
158
   *   Optional. The number of mails expected.
159
   */
160
  protected function assertMailCount($actualMail, $expectedCount = NULL) {
161
    $actualCount = count($actualMail);
162
    if (is_null($expectedCount)) {
163
      // If number to expect is not specified, expect more than zero.
164
     if ($actualCount === 0) {
165
       throw new \Exception("Expected some mail, but none found.");
166
     }
167
    }
168
    else {
169
      if ($expectedCount != $actualCount) {
170
        // Prepare a simple list of actual mail.
171
        $prettyActualMail = [];
172
        foreach ($actualMail as $singleActualMail) {
173
          $prettyActualMail[] = [
174
            'to' => $singleActualMail['to'],
175
            'subject' => $singleActualMail['subject'],
176
          ];
177
        }
178
        throw new \Exception(sprintf("Expected %s mail, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE)));
179
      }
180
    }
181
  }
182
  
183
  /**
184
   * Sort mail by to, subject and body.
185
   *
186
   * @param array $mail
187
   *   An array of mail to sort.
188
   *
189
   * @return array
190
   *   The same mail, but sorted.
191
   */
192
  protected function sortMail($mail) {
193
    // Can't sort an empty array.
194
    if (count($mail) === 0) {
195
      return [];
196
    }
197
198
    // To, subject and body keys must be present.
199
    // Empty strings are ignored when matching so adding them is harmless.
200
    foreach ($mail as $key => $row) {
201
      if (!array_key_exists('to',$row)) {
202
        $mail[$key]['to'] = '';
203
      }
204
      if (!array_key_exists('subject',$row)) {
205
        $mail[$key]['subject'] = '';
206
      }
207
      if (!array_key_exists('body',$row)) {
208
        $mail[$key]['body'] = '';
209
      }
210
    }
211
212
    // Obtain a list of columns.
213
    foreach ($mail as $key => $row) {
214
      if (array_key_exists('to',$row)) {
215
        $to[$key] = $row['to'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$to was never initialized. Although not strictly required by PHP, it is generally a good practice to add $to = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
216
      }
217
      if (array_key_exists('subject',$row)) {
218
        $subject[$key] = $row['subject'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$subject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $subject = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
219
      }
220
      if (array_key_exists('body',$row)) {
221
        $body[$key] = $row['body'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
222
      }
223
    }
224
225
    // Add $mail as the last parameter, to sort by the common key.
226
    array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail);
0 ignored issues
show
Bug introduced by
The variable $subject does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $body does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
227
    return $mail;
228
  }
229
230
  /**
231
   * Get the mink context, so we can visit pages using the mink session.
232
   */
233
  protected function getMinkContext() {
234
    $minkContext =  $this->getContext('\Behat\MinkExtension\Context\RawMinkContext');
235
    if ($minkContext === FALSE) {
236
      throw new \Exception(sprintf('No mink context found.'));
237
    }
238
    return $minkContext;
239
  }
240
241
}
242