1<?php
2/**
3 * Generic_Sniffs_PHP_DisallowShortOpenTagSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16/**
17 * Generic_Sniffs_PHP_DisallowShortOpenTagSniff.
18 *
19 * Makes sure that shorthand PHP open tags are not used.
20 *
21 * @category  PHP
22 * @package   PHP_CodeSniffer
23 * @author    Greg Sherwood <gsherwood@squiz.net>
24 * @author    Marc McIntyre <mmcintyre@squiz.net>
25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
26 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
27 * @version   Release: @package_version@
28 * @link      http://pear.php.net/package/PHP_CodeSniffer
29 */
30class Generic_Sniffs_PHP_DisallowShortOpenTagSniff implements PHP_CodeSniffer_Sniff
31{
32
33
34    /**
35     * Returns an array of tokens this test wants to listen for.
36     *
37     * @return array
38     */
39    public function register()
40    {
41        $targets = array(
42                    T_OPEN_TAG,
43                    T_OPEN_TAG_WITH_ECHO,
44                   );
45
46        $shortOpenTags = (boolean) ini_get('short_open_tag');
47        if ($shortOpenTags === false) {
48            $targets[] = T_INLINE_HTML;
49        }
50
51        return $targets;
52
53    }//end register()
54
55
56    /**
57     * Processes this test, when one of its tokens is encountered.
58     *
59     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
60     * @param int                  $stackPtr  The position of the current token
61     *                                        in the stack passed in $tokens.
62     *
63     * @return void
64     */
65    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
66    {
67        $tokens = $phpcsFile->getTokens();
68        $token  = $tokens[$stackPtr];
69
70        if ($token['code'] === T_OPEN_TAG && $token['content'] === '<?') {
71            $error = 'Short PHP opening tag used; expected "<?php" but found "%s"';
72            $data  = array($token['content']);
73            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'Found', $data);
74            if ($fix === true) {
75                $correctOpening = '<?php';
76                if (isset($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
77                    // Avoid creation of invalid open tags like <?phpecho if the original was <?echo .
78                    $correctOpening .= ' ';
79                }
80
81                $phpcsFile->fixer->replaceToken($stackPtr, $correctOpening);
82            }
83
84            $phpcsFile->recordMetric($stackPtr, 'PHP short open tag used', 'yes');
85        } else {
86            $phpcsFile->recordMetric($stackPtr, 'PHP short open tag used', 'no');
87        }
88
89        if ($token['code'] === T_OPEN_TAG_WITH_ECHO) {
90            $nextVar = $tokens[$phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true)];
91            $error   = 'Short PHP opening tag used with echo; expected "<?php echo %s ..." but found "%s %s ..."';
92            $data    = array(
93                        $nextVar['content'],
94                        $token['content'],
95                        $nextVar['content'],
96                       );
97            $fix     = $phpcsFile->addFixableError($error, $stackPtr, 'EchoFound', $data);
98            if ($fix === true) {
99                if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
100                    $phpcsFile->fixer->replaceToken($stackPtr, '<?php echo ');
101                } else {
102                    $phpcsFile->fixer->replaceToken($stackPtr, '<?php echo');
103                }
104            }
105        }
106
107        if ($token['code'] === T_INLINE_HTML) {
108            $content     = $token['content'];
109            $openerFound = strpos($content, '<?');
110
111            if ($openerFound === false) {
112                return;
113            }
114
115            $closerFound = false;
116
117            // Inspect current token and subsequent inline HTML token to find a close tag.
118            for ($i = $stackPtr; $i < $phpcsFile->numTokens; $i++) {
119                if ($tokens[$i]['code'] !== T_INLINE_HTML) {
120                    break;
121                }
122
123                $closerFound = strrpos($tokens[$i]['content'], '?>');
124                if ($closerFound !== false) {
125                    if ($i !== $stackPtr) {
126                        break;
127                    } else if ($closerFound > $openerFound) {
128                        break;
129                    } else {
130                        $closerFound = false;
131                    }
132                }
133            }
134
135            if ($closerFound !== false) {
136                $error   = 'Possible use of short open tags detected; found: %s';
137                $snippet = $this->getSnippet($content, '<?');
138                $data    = array('<?'.$snippet);
139
140                $phpcsFile->addWarning($error, $stackPtr, 'PossibleFound', $data);
141
142                // Skip forward to the token containing the closer.
143                if (($i - 1) > $stackPtr) {
144                    return $i;
145                }
146            }
147        }//end if
148
149    }//end process()
150
151
152    /**
153     * Get a snippet from a HTML token.
154     *
155     * @param string $content The content of the HTML token.
156     * @param string $start   Partial string to use as a starting point for the snippet.
157     * @param int    $length  The target length of the snippet to get. Defaults to 40.
158     *
159     * @return string
160     */
161    protected function getSnippet($content, $start='', $length=40)
162    {
163        $startPos = 0;
164
165        if ($start !== '') {
166            $startPos = strpos($content, $start);
167            if ($startPos !== false) {
168                $startPos += strlen($start);
169            }
170        }
171
172        $snippet = substr($content, $startPos, $length);
173        if ((strlen($content) - $startPos) > $length) {
174            $snippet .= '...';
175        }
176
177        return $snippet;
178
179    }//end getSnippet()
180
181
182}//end class
183