Security update for permissions_by_term
[yaffs-website] / vendor / behat / behat / src / Behat / Behat / Definition / Pattern / Policy / TurnipPatternPolicy.php
1 <?php
2
3 /*
4  * This file is part of the Behat.
5  * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 namespace Behat\Behat\Definition\Pattern\Policy;
12
13 use Behat\Behat\Definition\Pattern\Pattern;
14 use Behat\Behat\Definition\Exception\InvalidPatternException;
15 use Behat\Transliterator\Transliterator;
16
17 /**
18  * Defines a way to handle turnip patterns.
19  *
20  * @author Konstantin Kudryashov <ever.zet@gmail.com>
21  */
22 final class TurnipPatternPolicy implements PatternPolicy
23 {
24     const TOKEN_REGEX = "[\"']?(?P<%s>(?<=\")[^\"]*(?=\")|(?<=')[^']*(?=')|\-?[\w\.\,]+)['\"]?";
25
26     const PLACEHOLDER_REGEXP = "/\\\:(\w+)/";
27     const OPTIONAL_WORD_REGEXP = '/(\s)?\\\\\(([^\\\]+)\\\\\)(\s)?/';
28     const ALTERNATIVE_WORD_REGEXP = '/(\w+)\\\\\/(\w+)/';
29
30     /**
31      * @var string[]
32      */
33     private $regexCache = array();
34
35     /**
36      * @var string[]
37      */
38     private static $placeholderPatterns = array(
39         "/(?<!\w)\"[^\"]+\"(?!\w)/",
40         "/(?<!\w)'[^']+'(?!\w)/",
41         "/(?<!\w|\.|\,)\-?\d+(?:[\.\,]\d+)?(?!\w|\.|\,)/"
42     );
43
44     /**
45      * {@inheritdoc}
46      */
47     public function supportsPatternType($type)
48     {
49         return null === $type || 'turnip' === $type;
50     }
51
52     /**
53      * {@inheritdoc}
54      */
55     public function generatePattern($stepText)
56     {
57         $count = 0;
58         $pattern = $stepText;
59         foreach (self::$placeholderPatterns as $replacePattern) {
60             $pattern = preg_replace_callback(
61                 $replacePattern,
62                 function () use (&$count) { return ':arg' . ++$count; },
63                 $pattern
64             );
65         }
66         $pattern = $this->escapeAlternationSyntax($pattern);
67         $canonicalText = $this->generateCanonicalText($stepText);
68
69         return new Pattern($canonicalText, $pattern, $count);
70     }
71
72     /**
73      * {@inheritdoc}
74      */
75     public function supportsPattern($pattern)
76     {
77         return true;
78     }
79
80     /**
81      * {@inheritdoc}
82      */
83     public function transformPatternToRegex($pattern)
84     {
85         if (!isset($this->regexCache[$pattern])) {
86             $this->regexCache[$pattern] = $this->createTransformedRegex($pattern);
87         }
88         return $this->regexCache[$pattern];
89     }
90
91     /**
92      * @param string $pattern
93      * @return string
94      */
95     private function createTransformedRegex($pattern)
96     {
97         $regex = preg_quote($pattern, '/');
98
99         $regex = $this->replaceTokensWithRegexCaptureGroups($regex);
100         $regex = $this->replaceTurnipOptionalEndingWithRegex($regex);
101         $regex = $this->replaceTurnipAlternativeWordsWithRegex($regex);
102
103         return '/^' . $regex . '$/i';
104     }
105
106     /**
107      * Generates canonical text for step text.
108      *
109      * @param string $stepText
110      *
111      * @return string
112      */
113     private function generateCanonicalText($stepText)
114     {
115         $canonicalText = preg_replace(self::$placeholderPatterns, '', $stepText);
116         $canonicalText = Transliterator::transliterate($canonicalText, ' ');
117         $canonicalText = preg_replace('/[^a-zA-Z\_\ ]/', '', $canonicalText);
118         $canonicalText = str_replace(' ', '', ucwords($canonicalText));
119
120         return $canonicalText;
121     }
122
123     /**
124      * Replaces turnip tokens with regex capture groups.
125      *
126      * @param string $regex
127      *
128      * @return string
129      */
130     private function replaceTokensWithRegexCaptureGroups($regex)
131     {
132         $tokenRegex = self::TOKEN_REGEX;
133
134         return preg_replace_callback(
135             self::PLACEHOLDER_REGEXP,
136             array($this, 'replaceTokenWithRegexCaptureGroup'),
137             $regex
138         );
139     }
140
141     private function replaceTokenWithRegexCaptureGroup($tokenMatch)
142     {
143         if (strlen($tokenMatch[1]) >= 32) {
144             throw new InvalidPatternException(
145                 "Token name should not exceed 32 characters, but `{$tokenMatch[1]}` was used."
146             );
147         }
148
149         return sprintf(self::TOKEN_REGEX, $tokenMatch[1]);
150     }
151
152     /**
153      * Replaces turnip optional ending with regex non-capturing optional group.
154      *
155      * @param string $regex
156      *
157      * @return string
158      */
159     private function replaceTurnipOptionalEndingWithRegex($regex)
160     {
161         return preg_replace(self::OPTIONAL_WORD_REGEXP, '(?:\1)?(?:\2)?(?:\3)?', $regex);
162     }
163
164     /**
165      * Replaces turnip alternative words with regex non-capturing alternating group.
166      *
167      * @param string $regex
168      *
169      * @return string
170      */
171     private function replaceTurnipAlternativeWordsWithRegex($regex)
172     {
173         $regex = preg_replace(self::ALTERNATIVE_WORD_REGEXP, '(?:\1|\2)', $regex);
174         $regex = $this->removeEscapingOfAlternationSyntax($regex);
175
176         return $regex;
177     }
178
179     /**
180      * Adds escaping to alternation syntax in pattern.
181      *
182      * By default, Turnip treats `/` as alternation syntax. Meaning `one/two` for Turnip
183      * means either `one` or `two`. Sometimes though you'll want to use slash character
184      * with different purpose (URL, UNIX paths). In this case, you would escape slashes
185      * with backslash.
186      *
187      * This method adds escaping to all slashes in generated snippets.
188      *
189      * @param string $pattern
190      *
191      * @return string
192      */
193     private function escapeAlternationSyntax($pattern)
194     {
195         return str_replace('/', '\/', $pattern);
196     }
197
198     /**
199      * Removes escaping of alternation syntax from regex.
200      *
201      * This method removes those escaping backslashes from your slashes, so your steps
202      * could be matched against your escaped definitions.
203      *
204      * @param string $regex
205      *
206      * @return string
207      */
208     private function removeEscapingOfAlternationSyntax($regex)
209     {
210         return str_replace('\\\/', '/', $regex);
211     }
212 }