├── .codeclimate.yml
├── .phpcs
└── Barracuda
│ ├── Sniffs
│ ├── Commenting
│ │ ├── DisallowSingleLineMultiCommentsSniff.php
│ │ ├── DocCommentSniff.php
│ │ ├── FunctionCommentSniff.php
│ │ ├── SpaceAfterCommentSniff.php
│ │ └── VariableCommentSniff.php
│ ├── ControlStructures
│ │ ├── ControlSignatureSniff.php
│ │ └── NoInlineAssignmentSniff.php
│ ├── Formatting
│ │ └── SpaceUnaryOperatorSniff.php
│ └── Functions
│ │ └── FunctionDeclarationSniff.php
│ ├── bootstrap.php
│ └── ruleset.xml
├── CHANGELOG.txt
├── LICENSE
├── README.md
├── composer.json
├── extras
├── README
└── zip-appnote-6.3.1-20070411.txt
├── src
├── Archive.php
├── TarArchive.php
└── ZipArchive.php
└── test
└── index.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - ".phpcs/**/*"
3 | - "test/**/*"
4 | - "extras/**/*"
5 | engines:
6 | phpmd:
7 | enabled: false
8 | config:
9 | file_extensions: "php"
10 | rulesets: "unusedcode"
11 | phpcodesniffer:
12 | enabled: true
13 | config:
14 | file_extensions: "php"
15 | standard: "/code/.phpcs/Barracuda/ruleset.xml"
16 | ignore_warnings: true
17 | fixme:
18 | enabled: true
19 | ratings:
20 | paths:
21 | - "**/*.php"
22 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/DisallowSingleLineMultiCommentsSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
11 | * @version 1.0.00
12 | * @link http://pear.php.net/package/PHP_CodeSniffer
13 | */
14 |
15 | /**
16 | * This sniff prohibits the use of Perl style hash comments.
17 | *
18 | * An example of a hash comment is:
19 | *
20 | *
21 | * /* This is a single line multi line comment, which is prohibited. */
22 | /* $hello = 'hello';
23 | *
24 | *
25 | * @category PHP
26 | * @package PHP_CodeSniffer
27 | * @author Ryan Matthews
28 | * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
29 | * @version Release: 1.0.00
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 | class Barracuda_Sniffs_Commenting_DisallowSingleLineMultiCommentsSniff implements PHP_CodeSniffer_Sniff
33 | {
34 |
35 |
36 | /**
37 | * Returns the token types that this sniff is interested in.
38 | *
39 | * @return array(int)
40 | */
41 | public function register()
42 | {
43 | return array(T_COMMENT);
44 |
45 | }// end register()
46 |
47 |
48 | /**
49 | * Processes the tokens that this sniff is interested in.
50 | *
51 | * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found.
52 | * @param int $stackPtr The position in the stack where
53 | * the token was found.
54 | *
55 | * @return void
56 | */
57 | public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
58 | {
59 | $tokens = $phpcsFile->getTokens();
60 | if (preg_match('/\/\*[^\n]*\*\//', $tokens[$stackPtr]['content']))
61 | {
62 | $error = 'Multi line comments are prohibited on single lines; found %s';
63 | $data = array(trim($tokens[$stackPtr]['content']));
64 | $phpcsFile->addError($error, $stackPtr, 'Found', $data);
65 | }
66 |
67 | }// end process()
68 | }
69 | // end class
70 |
71 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/DocCommentSniff.php:
--------------------------------------------------------------------------------
1 | array(
13 | 'required' => false,
14 | 'allow_multiple' => false,
15 | ),
16 | '@package' => array(
17 | 'required' => false,
18 | 'allow_multiple' => false,
19 | ),
20 | '@subpackage' => array(
21 | 'required' => false,
22 | 'allow_multiple' => false,
23 | ),
24 | '@author' => array(
25 | 'required' => false,
26 | 'allow_multiple' => true,
27 | ),
28 | '@copyright' => array(
29 | 'required' => false,
30 | 'allow_multiple' => true,
31 | ),
32 | '@license' => array(
33 | 'required' => false,
34 | 'allow_multiple' => false,
35 | ),
36 | '@version' => array(
37 | 'required' => false,
38 | 'allow_multiple' => false,
39 | ),
40 | '@link' => array(
41 | 'required' => false,
42 | 'allow_multiple' => true,
43 | ),
44 | '@see' => array(
45 | 'required' => false,
46 | 'allow_multiple' => true,
47 | ),
48 | '@since' => array(
49 | 'required' => false,
50 | 'allow_multiple' => false,
51 | ),
52 | '@deprecated' => array(
53 | 'required' => false,
54 | 'allow_multiple' => false,
55 | ),
56 | );
57 |
58 |
59 | /**
60 | * Returns an array of tokens this test wants to listen for.
61 | *
62 | * @return array
63 | */
64 | public function register()
65 | {
66 | return array(T_OPEN_TAG);
67 |
68 | }//end register()
69 |
70 |
71 | /**
72 | * Processes this test, when one of its tokens is encountered.
73 | *
74 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
75 | * @param int $stackPtr The position of the current token
76 | * in the stack passed in $tokens.
77 | *
78 | * @return int
79 | */
80 | public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
81 | {
82 | $tokens = $phpcsFile->getTokens();
83 |
84 | // Find the next non whitespace token.
85 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
86 |
87 | // Allow declare() statements at the top of the file.
88 | if ($tokens[$commentStart]['code'] === T_DECLARE) {
89 | $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1));
90 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true);
91 | }
92 |
93 | // Ignore vim header.
94 | if ($tokens[$commentStart]['code'] === T_COMMENT) {
95 | if (strstr($tokens[$commentStart]['content'], 'vim:') !== false) {
96 | $commentStart = $phpcsFile->findNext(
97 | T_WHITESPACE,
98 | ($commentStart + 1),
99 | null,
100 | true
101 | );
102 | }
103 | }
104 |
105 | $errorToken = ($stackPtr + 1);
106 | if (isset($tokens[$errorToken]) === false) {
107 | $errorToken--;
108 | }
109 |
110 | if ($tokens[$commentStart]['code'] === T_CLOSE_TAG) {
111 | // We are only interested if this is the first open tag.
112 | return ($phpcsFile->numTokens + 1);
113 | } else if ($tokens[$commentStart]['code'] === T_COMMENT) {
114 | $error = 'You must use "/**" style comments for a file comment';
115 | $phpcsFile->addError($error, $errorToken, 'WrongStyle');
116 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');
117 | return ($phpcsFile->numTokens + 1);
118 | } else if ($commentStart === false
119 | || $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG
120 | ) {
121 | $phpcsFile->addError('Missing file doc comment', $errorToken, 'Missing');
122 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no');
123 | return ($phpcsFile->numTokens + 1);
124 | } else {
125 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');
126 | }
127 |
128 | // Check the PHP Version, which should be in some text before the first tag.
129 | $commentEnd = $tokens[$commentStart]['comment_closer'];
130 | $found = false;
131 | for ($i = ($commentStart + 1); $i < $commentEnd; $i++) {
132 | if ($tokens[$i]['code'] === T_DOC_COMMENT_TAG) {
133 | break;
134 | } else if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING
135 | && strstr(strtolower($tokens[$i]['content']), 'php version') !== false
136 | ) {
137 | $found = true;
138 | break;
139 | }
140 | }
141 |
142 | if ($found === false) {
143 | $error = 'PHP version not specified';
144 | $phpcsFile->addWarning($error, $commentEnd, 'MissingVersion');
145 | }
146 |
147 | // Check each tag.
148 | $this->processTags($phpcsFile, $stackPtr, $commentStart);
149 |
150 | // Ignore the rest of the file.
151 | return ($phpcsFile->numTokens + 1);
152 |
153 | }//end process()
154 |
155 |
156 | /**
157 | * Processes each required or optional tag.
158 | *
159 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
160 | * @param int $stackPtr The position of the current token
161 | * in the stack passed in $tokens.
162 | * @param int $commentStart Position in the stack where the comment started.
163 | *
164 | * @return void
165 | */
166 | protected function processTags(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
167 | {
168 | $tokens = $phpcsFile->getTokens();
169 |
170 | if (get_class($this) === 'PEAR_Sniffs_Commenting_FileCommentSniff') {
171 | $docBlock = 'file';
172 | } else {
173 | $docBlock = 'class';
174 | }
175 |
176 | $commentEnd = $tokens[$commentStart]['comment_closer'];
177 |
178 | $foundTags = array();
179 | $tagTokens = array();
180 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
181 | $name = $tokens[$tag]['content'];
182 | if (isset($this->tags[$name]) === false) {
183 | continue;
184 | }
185 |
186 | if ($this->tags[$name]['allow_multiple'] === false && isset($tagTokens[$name]) === true) {
187 | $error = 'Only one %s tag is allowed in a %s comment';
188 | $data = array(
189 | $name,
190 | $docBlock,
191 | );
192 | $phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data);
193 | }
194 |
195 | $foundTags[] = $name;
196 | $tagTokens[$name][] = $tag;
197 |
198 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd);
199 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) {
200 | $error = 'Content missing for %s tag in %s comment';
201 | $data = array(
202 | $name,
203 | $docBlock,
204 | );
205 | $phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data);
206 | continue;
207 | }
208 | }//end foreach
209 |
210 | // Check if the tags are in the correct position.
211 | $pos = 0;
212 | foreach ($this->tags as $tag => $tagData) {
213 | if (isset($tagTokens[$tag]) === false) {
214 | if ($tagData['required'] === true) {
215 | $error = 'Missing %s tag in %s comment';
216 | $data = array(
217 | $tag,
218 | $docBlock,
219 | );
220 | $phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data);
221 | }
222 |
223 | continue;
224 | } else {
225 | $method = 'process'.substr($tag, 1);
226 | if (method_exists($this, $method) === true) {
227 | // Process each tag if a method is defined.
228 | call_user_func(array($this, $method), $phpcsFile, $tagTokens[$tag]);
229 | }
230 | }
231 |
232 | if (isset($foundTags[$pos]) === false) {
233 | break;
234 | }
235 |
236 | if ($foundTags[$pos] !== $tag) {
237 | $error = 'The tag in position %s should be the %s tag';
238 | $data = array(
239 | ($pos + 1),
240 | $tag,
241 | );
242 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data);
243 | }
244 |
245 | // Account for multiple tags.
246 | $pos++;
247 | while (isset($foundTags[$pos]) === true && $foundTags[$pos] === $tag) {
248 | $pos++;
249 | }
250 | }//end foreach
251 |
252 | }//end processTags()
253 |
254 |
255 | /**
256 | * Process the category tag.
257 | *
258 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
259 | * @param array $tags The tokens for these tags.
260 | *
261 | * @return void
262 | */
263 | protected function processCategory(PHP_CodeSniffer_File $phpcsFile, array $tags)
264 | {
265 | $tokens = $phpcsFile->getTokens();
266 | foreach ($tags as $tag) {
267 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
268 | // No content.
269 | continue;
270 | }
271 |
272 | $content = $tokens[($tag + 2)]['content'];
273 | if (PHP_CodeSniffer::isUnderscoreName($content) !== true) {
274 | $newContent = str_replace(' ', '_', $content);
275 | $nameBits = explode('_', $newContent);
276 | $firstBit = array_shift($nameBits);
277 | $newName = ucfirst($firstBit).'_';
278 | foreach ($nameBits as $bit) {
279 | if ($bit !== '') {
280 | $newName .= ucfirst($bit).'_';
281 | }
282 | }
283 |
284 | $error = 'Category name "%s" is not valid; consider "%s" instead';
285 | $validName = trim($newName, '_');
286 | $data = array(
287 | $content,
288 | $validName,
289 | );
290 | $phpcsFile->addError($error, $tag, 'InvalidCategory', $data);
291 | }
292 | }//end foreach
293 |
294 | }//end processCategory()
295 |
296 |
297 | /**
298 | * Process the package tag.
299 | *
300 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
301 | * @param array $tags The tokens for these tags.
302 | *
303 | * @return void
304 | */
305 | protected function processPackage(PHP_CodeSniffer_File $phpcsFile, array $tags)
306 | {
307 | $tokens = $phpcsFile->getTokens();
308 | foreach ($tags as $tag) {
309 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
310 | // No content.
311 | continue;
312 | }
313 |
314 | $content = $tokens[($tag + 2)]['content'];
315 | if (PHP_CodeSniffer::isUnderscoreName($content) === true) {
316 | continue;
317 | }
318 |
319 | $newContent = str_replace(' ', '_', $content);
320 | $newContent = trim($newContent, '_');
321 | $newContent = preg_replace('/[^A-Za-z_]/', '', $newContent);
322 | $nameBits = explode('_', $newContent);
323 | $firstBit = array_shift($nameBits);
324 | $newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
325 | foreach ($nameBits as $bit) {
326 | if ($bit !== '') {
327 | $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
328 | }
329 | }
330 |
331 | $error = 'Package name "%s" is not valid; consider "%s" instead';
332 | $validName = trim($newName, '_');
333 | $data = array(
334 | $content,
335 | $validName,
336 | );
337 | $phpcsFile->addError($error, $tag, 'InvalidPackage', $data);
338 | }//end foreach
339 |
340 | }//end processPackage()
341 |
342 |
343 | /**
344 | * Process the subpackage tag.
345 | *
346 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
347 | * @param array $tags The tokens for these tags.
348 | *
349 | * @return void
350 | */
351 | protected function processSubpackage(PHP_CodeSniffer_File $phpcsFile, array $tags)
352 | {
353 | $tokens = $phpcsFile->getTokens();
354 | foreach ($tags as $tag) {
355 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
356 | // No content.
357 | continue;
358 | }
359 |
360 | $content = $tokens[($tag + 2)]['content'];
361 | if (PHP_CodeSniffer::isUnderscoreName($content) === true) {
362 | continue;
363 | }
364 |
365 | $newContent = str_replace(' ', '_', $content);
366 | $nameBits = explode('_', $newContent);
367 | $firstBit = array_shift($nameBits);
368 | $newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
369 | foreach ($nameBits as $bit) {
370 | if ($bit !== '') {
371 | $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
372 | }
373 | }
374 |
375 | $error = 'Subpackage name "%s" is not valid; consider "%s" instead';
376 | $validName = trim($newName, '_');
377 | $data = array(
378 | $content,
379 | $validName,
380 | );
381 | $phpcsFile->addError($error, $tag, 'InvalidSubpackage', $data);
382 | }//end foreach
383 |
384 | }//end processSubpackage()
385 |
386 | /**
387 | * Author tag must be 'Firstname Lastname '.
388 | *
389 | * @param int $errorPos The line number where the error occurs.
390 | *
391 | * @return void
392 | */
393 | protected function processAuthors($errorPos)
394 | {
395 | $authors = $this->commentParser->getAuthors();
396 | if (empty($authors) === false) {
397 | $author = $authors[0];
398 | $content = $author->getContent();
399 | if (empty($content) === true) {
400 | $error = 'Content missing for @author tag in file comment';
401 | $this->currentFile->addError($error, $errorPos, 'MissingAuthor');
402 | } else if (preg_match('/^(.*) \<.*\@.*\>$/', $content) === 0) {
403 | $error = 'Expected "Firstname Lastname " for author tag';
404 | $this->currentFile->addError($error, $errorPos, 'IncorrectAuthor');
405 | }
406 | }
407 | }//end processAuthors()
408 |
409 | /**
410 | * Copyright tag must be in the form 'xxxx-xxxx Barracuda Networks, Inc.'.
411 | *
412 | * @param int $errorPos The line number where the error occurs.
413 | *
414 | * @return void
415 | */
416 | protected function processCopyrights($errorPos)
417 | {
418 | $copyrights = $this->commentParser->getCopyrights();
419 | $copyright = $copyrights[0];
420 | if ($copyright !== null) {
421 | $content = $copyright->getContent();
422 | if (empty($content) === true) {
423 | $error = 'Content missing for @copyright tag in file comment';
424 | $this->currentFile->addError($error, $errorPos, 'MissingCopyright');
425 | } else if (preg_match('/^([0-9]{4})(-[0-9]{4})? (\.*\)$/', $content) === 0) {
426 | $error = 'Expected "xxxx-xxxx Barracuda Networks, Inc." for copyright declaration';
427 | $this->currentFile->addError($error, $errorPos, 'IncorrectCopyright');
428 | }
429 | }
430 | }//end processCopyrights()
431 |
432 | /**
433 | * Process the license tag.
434 | *
435 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
436 | * @param array $tags The tokens for these tags.
437 | *
438 | * @return void
439 | */
440 | protected function processLicense(PHP_CodeSniffer_File $phpcsFile, array $tags)
441 | {
442 | $tokens = $phpcsFile->getTokens();
443 | foreach ($tags as $tag) {
444 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
445 | // No content.
446 | continue;
447 | }
448 |
449 | $content = $tokens[($tag + 2)]['content'];
450 | $matches = array();
451 | preg_match('/^([^\s]+)\s+(.*)/', $content, $matches);
452 | if (count($matches) !== 3) {
453 | $error = '@license tag must contain a URL and a license name';
454 | $phpcsFile->addError($error, $tag, 'IncompleteLicense');
455 | }
456 | }
457 |
458 | }//end processLicense()
459 |
460 |
461 | /**
462 | * Process the version tag.
463 | *
464 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
465 | * @param array $tags The tokens for these tags.
466 | *
467 | * @return void
468 | */
469 | protected function processVersion(PHP_CodeSniffer_File $phpcsFile, array $tags)
470 | {
471 | $tokens = $phpcsFile->getTokens();
472 | foreach ($tags as $tag) {
473 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) {
474 | // No content.
475 | continue;
476 | }
477 |
478 | $content = $tokens[($tag + 2)]['content'];
479 | if (strstr($content, 'CVS:') === false
480 | && strstr($content, 'SVN:') === false
481 | && strstr($content, 'GIT:') === false
482 | && strstr($content, 'HG:') === false
483 | ) {
484 | $error = 'Invalid version "%s" in file comment; consider "CVS: " or "SVN: " or "GIT: " or "HG: " instead';
485 | $data = array($content);
486 | $phpcsFile->addWarning($error, $tag, 'InvalidVersion', $data);
487 | }
488 | }
489 |
490 | }//end processVersion()
491 |
492 |
493 | }//end class
494 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/FunctionCommentSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Marc McIntyre
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 | if (class_exists('PEAR_Sniffs_Commenting_FunctionCommentSniff', true) === false) {
17 | throw new PHP_CodeSniffer_Exception('Class PEAR_Sniffs_Commenting_FunctionCommentSniff not found');
18 | }
19 |
20 | /**
21 | * Parses and verifies the doc comments for functions.
22 | *
23 | * @category PHP
24 | * @package PHP_CodeSniffer
25 | * @author Greg Sherwood
26 | * @author Marc McIntyre
27 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 | * @version Release: @package_version@
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 | class Barracuda_Sniffs_Commenting_FunctionCommentSniff extends PEAR_Sniffs_Commenting_FunctionCommentSniff
33 | {
34 | protected $allowedTypes = array(
35 | 'int',
36 | 'bool',
37 | );
38 |
39 | public function __construct()
40 | {
41 | PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes);
42 | }
43 |
44 | /**
45 | * Process the return comment of this function comment.
46 | *
47 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
48 | * @param int $stackPtr The position of the current token
49 | * in the stack passed in $tokens.
50 | * @param int $commentStart The position in the stack where the comment started.
51 | *
52 | * @return void
53 | */
54 | protected function processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
55 | {
56 | $tokens = $phpcsFile->getTokens();
57 |
58 | // Skip constructor and destructor.
59 | $methodName = $phpcsFile->getDeclarationName($stackPtr);
60 | $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct');
61 |
62 | $return = null;
63 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
64 | if ($tokens[$tag]['content'] === '@return') {
65 | if ($return !== null) {
66 | $error = 'Only 1 @return tag is allowed in a function comment';
67 | $phpcsFile->addError($error, $tag, 'DuplicateReturn');
68 | return;
69 | }
70 |
71 | $return = $tag;
72 | }
73 | }
74 |
75 | if ($isSpecialMethod === true) {
76 | return;
77 | }
78 |
79 | if ($return !== null) {
80 | $content = $tokens[($return + 2)]['content'];
81 | if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) {
82 | $error = 'Return type missing for @return tag in function comment';
83 | $phpcsFile->addError($error, $return, 'MissingReturnType');
84 | } else {
85 | // Check return type (can be multiple, separated by '|').
86 | $typeNames = explode('|', $content);
87 | $suggestedNames = array();
88 | foreach ($typeNames as $i => $typeName) {
89 | $suggestedName = PHP_CodeSniffer::suggestType($typeName);
90 | if (in_array($suggestedName, $suggestedNames) === false) {
91 | $suggestedNames[] = $suggestedName;
92 | }
93 | }
94 |
95 | $suggestedType = implode('|', $suggestedNames);
96 | if ($content !== $suggestedType) {
97 | $error = 'Expected "%s" but found "%s" for function return type';
98 | $data = array(
99 | $suggestedType,
100 | $content,
101 | );
102 | $fix = $phpcsFile->addFixableError($error, $return, 'InvalidReturn', $data);
103 | if ($fix === true) {
104 | $phpcsFile->fixer->replaceToken(($return + 2), $suggestedType);
105 | }
106 | }
107 |
108 | // If the return type is void, make sure there is
109 | // no return statement in the function.
110 | if ($content === 'void') {
111 | if (isset($tokens[$stackPtr]['scope_closer']) === true) {
112 | $endToken = $tokens[$stackPtr]['scope_closer'];
113 | for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) {
114 | if ($tokens[$returnToken]['code'] === T_CLOSURE) {
115 | $returnToken = $tokens[$returnToken]['scope_closer'];
116 | continue;
117 | }
118 |
119 | if ($tokens[$returnToken]['code'] === T_RETURN
120 | || $tokens[$returnToken]['code'] === T_YIELD
121 | ) {
122 | break;
123 | }
124 | }
125 |
126 | if ($returnToken !== $endToken) {
127 | // If the function is not returning anything, just
128 | // exiting, then there is no problem.
129 | $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
130 | if ($tokens[$semicolon]['code'] !== T_SEMICOLON) {
131 | $error = 'Function return type is void, but function contains return statement';
132 | $phpcsFile->addError($error, $return, 'InvalidReturnVoid');
133 | }
134 | }
135 | }//end if
136 | } else if ($content !== 'mixed') {
137 | // If return type is not void, there needs to be a return statement
138 | // somewhere in the function that returns something.
139 | if (isset($tokens[$stackPtr]['scope_closer']) === true) {
140 | $endToken = $tokens[$stackPtr]['scope_closer'];
141 | $returnToken = $phpcsFile->findNext(array(T_RETURN, T_YIELD), $stackPtr, $endToken);
142 | if ($returnToken === false) {
143 | $error = 'Function return type is not void, but function has no return statement';
144 | $phpcsFile->addError($error, $return, 'InvalidNoReturn');
145 | } else {
146 | $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
147 | if ($tokens[$semicolon]['code'] === T_SEMICOLON) {
148 | $error = 'Function return type is not void, but function is returning void here';
149 | $phpcsFile->addError($error, $returnToken, 'InvalidReturnNotVoid');
150 | }
151 | }
152 | }
153 | }//end if
154 | }//end if
155 | } else {
156 | $error = 'Missing @return tag in function comment';
157 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn');
158 | }//end if
159 |
160 | }//end processReturn()
161 |
162 |
163 | /**
164 | * Process any throw tags that this function comment has.
165 | *
166 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
167 | * @param int $stackPtr The position of the current token
168 | * in the stack passed in $tokens.
169 | * @param int $commentStart The position in the stack where the comment started.
170 | *
171 | * @return void
172 | */
173 | protected function processThrows(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
174 | {
175 | $tokens = $phpcsFile->getTokens();
176 |
177 | $throws = array();
178 | foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
179 | if ($tokens[$tag]['content'] !== '@throws') {
180 | continue;
181 | }
182 |
183 | $exception = null;
184 | $comment = null;
185 | if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
186 | $matches = array();
187 | preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[($tag + 2)]['content'], $matches);
188 | $exception = $matches[1];
189 | if (isset($matches[2]) === true && trim($matches[2]) !== '') {
190 | $comment = $matches[2];
191 | }
192 | }
193 |
194 | if ($exception === null) {
195 | $error = 'Exception type and comment missing for @throws tag in function comment';
196 | $phpcsFile->addError($error, $tag, 'InvalidThrows');
197 | } else if ($comment === null) {
198 | $error = 'Comment missing for @throws tag in function comment';
199 | $phpcsFile->addError($error, $tag, 'EmptyThrows');
200 | } else {
201 | // Any strings until the next tag belong to this comment.
202 | if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
203 | $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
204 | } else {
205 | $end = $tokens[$commentStart]['comment_closer'];
206 | }
207 |
208 | for ($i = ($tag + 3); $i < $end; $i++) {
209 | if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
210 | $comment .= ' '.$tokens[$i]['content'];
211 | }
212 | }
213 |
214 | // Starts with a capital letter and ends with a fullstop.
215 | $firstChar = $comment{0};
216 | if (strtoupper($firstChar) !== $firstChar) {
217 | $error = '@throws tag comment must start with a capital letter';
218 | $phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital');
219 | }
220 |
221 | $lastChar = substr($comment, -1);
222 | if ($lastChar !== '.') {
223 | $error = '@throws tag comment must end with a full stop';
224 | $phpcsFile->addError($error, ($tag + 2), 'ThrowsNoFullStop');
225 | }
226 | }//end if
227 | }//end foreach
228 |
229 | }//end processThrows()
230 |
231 |
232 | /**
233 | * Process the function parameter comments.
234 | *
235 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
236 | * @param int $stackPtr The position of the current token
237 | * in the stack passed in $tokens.
238 | * @param int $commentStart The position in the stack where the comment started.
239 | *
240 | * @return void
241 | */
242 | protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
243 | {
244 | $tokens = $phpcsFile->getTokens();
245 |
246 | $params = array();
247 | $maxType = 0;
248 | $maxVar = 0;
249 | foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
250 | if ($tokens[$tag]['content'] !== '@param') {
251 | continue;
252 | }
253 |
254 | $type = '';
255 | $typeSpace = 0;
256 | $var = '';
257 | $varSpace = 0;
258 | $comment = '';
259 | $commentLines = array();
260 | if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
261 | $matches = array();
262 | preg_match('/([^$&]+)(?:((?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches);
263 |
264 | $typeLen = strlen($matches[1]);
265 | $type = trim($matches[1]);
266 | $typeSpace = ($typeLen - strlen($type));
267 | $typeLen = strlen($type);
268 | if ($typeLen > $maxType) {
269 | $maxType = $typeLen;
270 | }
271 |
272 | if (isset($matches[2]) === true) {
273 | $var = $matches[2];
274 | $varLen = strlen($var);
275 | if ($varLen > $maxVar) {
276 | $maxVar = $varLen;
277 | }
278 |
279 | if (isset($matches[4]) === true) {
280 | $varSpace = strlen($matches[3]);
281 | $comment = $matches[4];
282 | $commentLines[] = array(
283 | 'comment' => $comment,
284 | 'token' => ($tag + 2),
285 | 'indent' => $varSpace,
286 | );
287 |
288 | // Any strings until the next tag belong to this comment.
289 | if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
290 | $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
291 | } else {
292 | $end = $tokens[$commentStart]['comment_closer'];
293 | }
294 |
295 | for ($i = ($tag + 3); $i < $end; $i++) {
296 | if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
297 | $indent = 0;
298 | if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) {
299 | $indent = strlen($tokens[($i - 1)]['content']);
300 | }
301 |
302 | $comment .= ' '.$tokens[$i]['content'];
303 | $commentLines[] = array(
304 | 'comment' => $tokens[$i]['content'],
305 | 'token' => $i,
306 | 'indent' => $indent,
307 | );
308 | }
309 | }
310 | } else {
311 | $error = 'Missing parameter comment';
312 | $phpcsFile->addError($error, $tag, 'MissingParamComment');
313 | $commentLines[] = array('comment' => '');
314 | }//end if
315 | } else {
316 | $error = 'Missing parameter name';
317 | $phpcsFile->addError($error, $tag, 'MissingParamName');
318 | }//end if
319 | } else {
320 | $error = 'Missing parameter type';
321 | $phpcsFile->addError($error, $tag, 'MissingParamType');
322 | }//end if
323 |
324 | $params[] = array(
325 | 'tag' => $tag,
326 | 'type' => $type,
327 | 'var' => $var,
328 | 'comment' => $comment,
329 | 'commentLines' => $commentLines,
330 | 'type_space' => $typeSpace,
331 | 'var_space' => $varSpace,
332 | );
333 | }//end foreach
334 |
335 | $realParams = $phpcsFile->getMethodParameters($stackPtr);
336 | $foundParams = array();
337 |
338 | foreach ($params as $pos => $param) {
339 | // If the type is empty, the whole line is empty.
340 | if ($param['type'] === '') {
341 | continue;
342 | }
343 |
344 | // Check the param type value.
345 | $typeNames = explode('|', $param['type']);
346 | foreach ($typeNames as $typeName) {
347 | $suggestedName = PHP_CodeSniffer::suggestType($typeName);
348 | if ($typeName !== $suggestedName) {
349 | $error = 'Expected "%s" but found "%s" for parameter type';
350 | $data = array(
351 | $suggestedName,
352 | $typeName,
353 | );
354 |
355 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data);
356 | if ($fix === true) {
357 | $content = $suggestedName;
358 | $content .= str_repeat(' ', $param['type_space']);
359 | $content .= $param['var'];
360 | $content .= str_repeat(' ', $param['var_space']);
361 | if (isset($param['commentLines'][0]) === true) {
362 | $content .= $param['commentLines'][0]['comment'];
363 | }
364 |
365 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
366 | }
367 | } else if (count($typeNames) === 1) {
368 | // Check type hint for array and custom type.
369 | $suggestedTypeHint = '';
370 | if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') {
371 | $suggestedTypeHint = 'array';
372 | } else if (strpos($suggestedName, 'callable') !== false) {
373 | $suggestedTypeHint = 'callable';
374 | } else if (strpos($suggestedName, 'callback') !== false) {
375 | $suggestedTypeHint = 'callable';
376 | } else if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) {
377 | $suggestedTypeHint = $suggestedName;
378 | }
379 |
380 | if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) {
381 | $typeHint = $realParams[$pos]['type_hint'];
382 | if ($typeHint === '') {
383 | $error = 'Type hint "%s" missing for %s';
384 | $data = array(
385 | $suggestedTypeHint,
386 | $param['var'],
387 | );
388 | $phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data);
389 | } else if ($typeHint !== substr($suggestedTypeHint, (strlen($typeHint) * -1))) {
390 | $error = 'Expected type hint "%s"; found "%s" for %s';
391 | $data = array(
392 | $suggestedTypeHint,
393 | $typeHint,
394 | $param['var'],
395 | );
396 | $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data);
397 | }
398 | } else if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) {
399 | $typeHint = $realParams[$pos]['type_hint'];
400 | if ($typeHint !== '') {
401 | $error = 'Unknown type hint "%s" found for %s';
402 | $data = array(
403 | $typeHint,
404 | $param['var'],
405 | );
406 | $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data);
407 | }
408 | }//end if
409 | }//end if
410 | }//end foreach
411 |
412 | if ($param['var'] === '') {
413 | continue;
414 | }
415 |
416 | $foundParams[] = $param['var'];
417 |
418 | // Check number of spaces after the type.
419 | $spaces = ($maxType - strlen($param['type']) + 1);
420 | if ($param['type_space'] !== $spaces) {
421 | $error = 'Expected %s spaces after parameter type; %s found';
422 | $data = array(
423 | $spaces,
424 | $param['type_space'],
425 | );
426 |
427 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data);
428 | if ($fix === true) {
429 | $phpcsFile->fixer->beginChangeset();
430 |
431 | $content = $param['type'];
432 | $content .= str_repeat(' ', $spaces);
433 | $content .= $param['var'];
434 | $content .= str_repeat(' ', $param['var_space']);
435 | $content .= $param['commentLines'][0]['comment'];
436 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
437 |
438 | // Fix up the indent of additional comment lines.
439 | foreach ($param['commentLines'] as $lineNum => $line) {
440 | if ($lineNum === 0
441 | || $param['commentLines'][$lineNum]['indent'] === 0
442 | ) {
443 | continue;
444 | }
445 |
446 | $newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']);
447 | $phpcsFile->fixer->replaceToken(
448 | ($param['commentLines'][$lineNum]['token'] - 1),
449 | str_repeat(' ', $newIndent)
450 | );
451 | }
452 |
453 | $phpcsFile->fixer->endChangeset();
454 | }//end if
455 | }//end if
456 |
457 | // Make sure the param name is correct.
458 | if (isset($realParams[$pos]) === true) {
459 | $realName = $realParams[$pos]['name'];
460 | if ($realName !== $param['var']) {
461 | $code = 'ParamNameNoMatch';
462 | $data = array(
463 | $param['var'],
464 | $realName,
465 | );
466 |
467 | $error = 'Doc comment for parameter %s does not match ';
468 | if (strtolower($param['var']) === strtolower($realName)) {
469 | $error .= 'case of ';
470 | $code = 'ParamNameNoCaseMatch';
471 | }
472 |
473 | $error .= 'actual variable name %s';
474 |
475 | $phpcsFile->addError($error, $param['tag'], $code, $data);
476 | }
477 | } else if (substr($param['var'], -4) !== ',...') {
478 | // We must have an extra parameter comment.
479 | $error = 'Superfluous parameter comment';
480 | $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment');
481 | }//end if
482 |
483 | if ($param['comment'] === '') {
484 | continue;
485 | }
486 |
487 | // Check number of spaces after the var name.
488 | $spaces = ($maxVar - strlen($param['var']) + 1);
489 | if ($param['var_space'] !== $spaces) {
490 | $error = 'Expected %s spaces after parameter name; %s found';
491 | $data = array(
492 | $spaces,
493 | $param['var_space'],
494 | );
495 |
496 | $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data);
497 | if ($fix === true) {
498 | $phpcsFile->fixer->beginChangeset();
499 |
500 | $content = $param['type'];
501 | $content .= str_repeat(' ', $param['type_space']);
502 | $content .= $param['var'];
503 | $content .= str_repeat(' ', $spaces);
504 | $content .= $param['commentLines'][0]['comment'];
505 | $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
506 |
507 | // Fix up the indent of additional comment lines.
508 | foreach ($param['commentLines'] as $lineNum => $line) {
509 | if ($lineNum === 0
510 | || $param['commentLines'][$lineNum]['indent'] === 0
511 | ) {
512 | continue;
513 | }
514 |
515 | $newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['var_space']);
516 | $phpcsFile->fixer->replaceToken(
517 | ($param['commentLines'][$lineNum]['token'] - 1),
518 | str_repeat(' ', $newIndent)
519 | );
520 | }
521 |
522 | $phpcsFile->fixer->endChangeset();
523 | }//end if
524 | }//end if
525 |
526 | // Param comments must start with a capital letter and end with the full stop.
527 | $firstChar = $param['comment']{0};
528 | if (preg_match('|\p{Lu}|u', $firstChar) === 0) {
529 | $error = 'Parameter comment must start with a capital letter';
530 | $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital');
531 | }
532 |
533 | $lastChar = substr($param['comment'], -1);
534 | if ($lastChar !== '.') {
535 | $error = 'Parameter comment must end with a full stop';
536 | $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop');
537 | }
538 | }//end foreach
539 |
540 | $realNames = array();
541 | foreach ($realParams as $realParam) {
542 | $realNames[] = $realParam['name'];
543 | }
544 |
545 | // Report missing comments.
546 | $diff = array_diff($realNames, $foundParams);
547 | foreach ($diff as $neededParam) {
548 | $error = 'Doc comment for parameter "%s" missing';
549 | $data = array($neededParam);
550 | $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data);
551 | }
552 |
553 | }//end processParams()
554 |
555 |
556 | }//end class
557 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/SpaceAfterCommentSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
28 |
29 | $valid = false;
30 |
31 | if (preg_match('|//\s|', $tokens[$stackPtr]['content']))
32 | {
33 | $valid = true;
34 | }
35 |
36 | if (preg_match('|\*[\s/]|', $tokens[$stackPtr]['content']))
37 | {
38 | $valid = true;
39 | }
40 |
41 | if ($valid === false)
42 | {
43 | $error = 'A space is required at the start of the comment %s';
44 | $data = array(trim($tokens[$stackPtr]['content']));
45 | $phpcsFile->addError($error, $stackPtr, 'Found', $data);
46 | }
47 |
48 | }// end process()
49 | }
50 | // end class
51 |
52 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Commenting/VariableCommentSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Marc McIntyre
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 | if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) {
17 | throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found');
18 | }
19 |
20 | /**
21 | * Parses and verifies the variable doc comment.
22 | *
23 | * @category PHP
24 | * @package PHP_CodeSniffer
25 | * @author Greg Sherwood
26 | * @author Marc McIntyre
27 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 | * @version Release: @package_version@
30 | * @link http://pear.php.net/package/PHP_CodeSniffer
31 | */
32 |
33 | class Barracuda_Sniffs_Commenting_VariableCommentSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff
34 | {
35 | protected $allowedTypes = array(
36 | 'bool',
37 | 'int',
38 | );
39 |
40 | public function __construct()
41 | {
42 | PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes);
43 | }
44 |
45 | /**
46 | * Called to process class member vars.
47 | *
48 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
49 | * @param int $stackPtr The position of the current token
50 | * in the stack passed in $tokens.
51 | *
52 | * @return void
53 | */
54 | public function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
55 | {
56 | $tokens = $phpcsFile->getTokens();
57 | $commentToken = array(
58 | T_COMMENT,
59 | T_DOC_COMMENT_CLOSE_TAG,
60 | );
61 |
62 | $commentEnd = $phpcsFile->findPrevious($commentToken, $stackPtr);
63 | if ($commentEnd === false) {
64 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
65 | return;
66 | }
67 |
68 | if ($tokens[$commentEnd]['code'] === T_COMMENT) {
69 | $phpcsFile->addError('You must use "/**" style comments for a member variable comment', $stackPtr, 'WrongStyle');
70 | return;
71 | } else if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
72 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
73 | return;
74 | } else {
75 | // Make sure the comment we have found belongs to us.
76 | $commentFor = $phpcsFile->findNext(array(T_VARIABLE, T_CLASS, T_INTERFACE), ($commentEnd + 1));
77 | if ($commentFor !== $stackPtr) {
78 | $phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing');
79 | return;
80 | }
81 | }
82 |
83 | $commentStart = $tokens[$commentEnd]['comment_opener'];
84 |
85 | $foundVar = null;
86 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
87 | if ($tokens[$tag]['content'] === '@var') {
88 | if ($foundVar !== null) {
89 | $error = 'Only one @var tag is allowed in a member variable comment';
90 | $phpcsFile->addError($error, $tag, 'DuplicateVar');
91 | } else {
92 | $foundVar = $tag;
93 | }
94 | } else if ($tokens[$tag]['content'] === '@see') {
95 | // Make sure the tag isn't empty.
96 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd);
97 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) {
98 | $error = 'Content missing for @see tag in member variable comment';
99 | $phpcsFile->addError($error, $tag, 'EmptySees');
100 | }
101 | } else {
102 | $error = '%s tag is not allowed in member variable comment';
103 | $data = array($tokens[$tag]['content']);
104 | $phpcsFile->addWarning($error, $tag, 'TagNotAllowed', $data);
105 | }//end if
106 | }//end foreach
107 |
108 | // The @var tag is the only one we require.
109 | if ($foundVar === null) {
110 | $error = 'Missing @var tag in member variable comment';
111 | $phpcsFile->addError($error, $commentEnd, 'MissingVar');
112 | return;
113 | }
114 |
115 | $firstTag = $tokens[$commentStart]['comment_tags'][0];
116 | if ($foundVar !== null && $tokens[$firstTag]['content'] !== '@var') {
117 | $error = 'The @var tag must be the first tag in a member variable comment';
118 | $phpcsFile->addError($error, $foundVar, 'VarOrder');
119 | }
120 |
121 | // Make sure the tag isn't empty and has the correct padding.
122 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $foundVar, $commentEnd);
123 | if ($string === false || $tokens[$string]['line'] !== $tokens[$foundVar]['line']) {
124 | $error = 'Content missing for @var tag in member variable comment';
125 | $phpcsFile->addError($error, $foundVar, 'EmptyVar');
126 | return;
127 | }
128 |
129 | $varType = $tokens[($foundVar + 2)]['content'];
130 | $suggestedType = PHP_CodeSniffer::suggestType($varType);
131 | if ($varType !== $suggestedType) {
132 | $error = 'Expected "%s" but found "%s" for @var tag in member variable comment';
133 | $data = array(
134 | $suggestedType,
135 | $varType,
136 | );
137 | $phpcsFile->addError($error, ($foundVar + 2), 'IncorrectVarType', $data);
138 | }
139 |
140 | }//end processMemberVar()
141 |
142 |
143 | /**
144 | * Called to process a normal variable.
145 | *
146 | * Not required for this sniff.
147 | *
148 | * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
149 | * @param int $stackPtr The position where the double quoted
150 | * string was found.
151 | *
152 | * @return void
153 | */
154 | protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
155 | {
156 |
157 | }//end processVariable()
158 |
159 |
160 | /**
161 | * Called to process variables found in double quoted strings.
162 | *
163 | * Not required for this sniff.
164 | *
165 | * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
166 | * @param int $stackPtr The position where the double quoted
167 | * string was found.
168 | *
169 | * @return void
170 | */
171 | protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
172 | {
173 |
174 | }//end processVariableInString()
175 |
176 |
177 | }//end class
178 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/ControlStructures/ControlSignatureSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
30 |
31 | if (isset($tokens[($stackPtr + 1)]) === false) {
32 | return;
33 | }
34 |
35 | // Single space after the keyword.
36 | if (in_array($tokens[$stackPtr]['code'], array(T_CATCH, T_IF, T_WHILE, T_FOR, T_FOREACH, T_ELSEIF, T_SWITCH))) {
37 | $found = 1;
38 | if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
39 | $found = 0;
40 | } else if ($tokens[($stackPtr + 1)]['content'] !== ' ') {
41 | if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) {
42 | $found = 'newline';
43 | } else {
44 | $found = strlen($tokens[($stackPtr + 1)]['content']);
45 | }
46 | }
47 |
48 | if ($found !== 1) {
49 | $error = 'Expected 1 space after %s keyword; %s found';
50 | $data = array(
51 | strtoupper($tokens[$stackPtr]['content']),
52 | $found,
53 | );
54 |
55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data);
56 | if ($fix === true) {
57 | if ($found === 0) {
58 | $phpcsFile->fixer->addContent($stackPtr, ' ');
59 | } else {
60 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
61 | }
62 | }
63 | }
64 | }
65 |
66 | // Single newline after the keyword.
67 | if (in_array($tokens[$stackPtr]['code'], array(T_TRY, T_DO, T_ELSE))
68 | && isset($tokens[$stackPtr]['scope_opener']) === true
69 | ) {
70 | $opener = $tokens[$stackPtr]['scope_opener'];
71 | $found = ($tokens[$opener]['line'] - $tokens[$stackPtr]['line']);
72 | if ($found !== 1) {
73 | $error = 'Expected 1 newline after % keyword; %s found';
74 | $data = array(
75 | strtoupper($tokens[$stackPtr]['content']),
76 | $found,
77 | );
78 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterKeyword', $data);
79 | if ($fix === true) {
80 | $phpcsFile->fixer->beginChangeset();
81 | for ($i = ($stackPtr + 1); $i < $opener; $i++) {
82 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) {
83 | break;
84 | }
85 |
86 | $phpcsFile->fixer->replaceToken($i, '');
87 | }
88 |
89 | $phpcsFile->fixer->addContent($stackPtr, $phpcsFile->eolChar);
90 | $phpcsFile->fixer->endChangeset();
91 | }
92 | }//end if
93 | }//end if
94 |
95 | // Single newline after closing parenthesis.
96 | if (isset($tokens[$stackPtr]['parenthesis_closer']) === true
97 | && isset($tokens[$stackPtr]['scope_opener']) === true
98 | ) {
99 | $closer = $tokens[$stackPtr]['parenthesis_closer'];
100 | $opener = $tokens[$stackPtr]['scope_opener'];
101 | $found = ($tokens[$opener]['line'] - $tokens[$closer]['line']);
102 | if ($found !== 1) {
103 | $error = 'Expected 1 newline after closing parenthesis; %s found';
104 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterCloseParenthesis', array($found));
105 | if ($fix === true) {
106 | $phpcsFile->fixer->beginChangeset();
107 | for ($i = ($closer + 1); $i < $opener; $i++) {
108 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) {
109 | break;
110 | }
111 |
112 | $phpcsFile->fixer->replaceToken($i, '');
113 | }
114 |
115 | $phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar);
116 | $phpcsFile->fixer->endChangeset();
117 | }
118 | }//end if
119 | }//end if
120 |
121 | // Single newline after opening brace.
122 | if (isset($tokens[$stackPtr]['scope_opener']) === true) {
123 | $opener = $tokens[$stackPtr]['scope_opener'];
124 | for ($next = ($opener + 1); $next < $phpcsFile->numTokens; $next++) {
125 | $code = $tokens[$next]['code'];
126 |
127 | if ($code === T_WHITESPACE) {
128 | continue;
129 | }
130 |
131 | // Skip all empty tokens on the same line as the opener.
132 | if ($tokens[$next]['line'] === $tokens[$opener]['line']
133 | && (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true
134 | || $code === T_CLOSE_TAG)
135 | ) {
136 | continue;
137 | }
138 |
139 | // We found the first bit of a code, or a comment on the
140 | // following line.
141 | break;
142 | }
143 |
144 | $found = ($tokens[$next]['line'] - $tokens[$opener]['line']);
145 | if ($found !== 1) {
146 | $error = 'Expected 1 newline after opening brace; %s found';
147 | $data = array($found);
148 | $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace', $data);
149 | if ($fix === true) {
150 | $phpcsFile->fixer->beginChangeset();
151 | for ($i = ($opener + 1); $i < $next; $i++) {
152 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) {
153 | break;
154 | }
155 |
156 | $phpcsFile->fixer->replaceToken($i, '');
157 | }
158 |
159 | $phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar);
160 | $phpcsFile->fixer->endChangeset();
161 | }
162 | }
163 | } else if ($tokens[$stackPtr]['code'] === T_WHILE) {
164 | // Zero spaces after parenthesis closer.
165 | $closer = $tokens[$stackPtr]['parenthesis_closer'];
166 | $found = 0;
167 | if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
168 | if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
169 | $found = 'newline';
170 | } else {
171 | $found = strlen($tokens[($closer + 1)]['content']);
172 | }
173 | }
174 |
175 | if ($found !== 0) {
176 | $error = 'Expected 0 spaces before semicolon; %s found';
177 | $data = array($found);
178 | $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data);
179 | if ($fix === true) {
180 | $phpcsFile->fixer->replaceToken(($closer + 1), '');
181 | }
182 | }
183 | }//end if
184 |
185 | // Only want to check multi-keyword structures from here on.
186 | if ($tokens[$stackPtr]['code'] === T_DO) {
187 | if (isset($tokens[$stackPtr]['scope_closer']) === false) {
188 | return;
189 | }
190 |
191 | $closer = $tokens[$stackPtr]['scope_closer'];
192 | } else if ($tokens[$stackPtr]['code'] === T_ELSE
193 | || $tokens[$stackPtr]['code'] === T_ELSEIF
194 | || $tokens[$stackPtr]['code'] === T_CATCH
195 | ) {
196 | $closer = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
197 | if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) {
198 | return;
199 | }
200 | } else {
201 | return;
202 | }
203 |
204 | // Single space after closing brace.
205 | if ($tokens[$stackPtr]['code'] === T_DO) {
206 | $found = 1;
207 | if ($tokens[($closer + 1)]['code'] !== T_WHITESPACE) {
208 | $found = 0;
209 | } else if ($tokens[($closer + 1)]['content'] !== ' ') {
210 | if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
211 | $found = 'newline';
212 | } else {
213 | $found = strlen($tokens[($closer + 1)]['content']);
214 | }
215 | }
216 |
217 | if ($found !== 1) {
218 | $error = 'Expected 1 space after closing brace; %s found';
219 | $data = array($found);
220 | $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseBrace', $data);
221 | if ($fix === true) {
222 | if ($found === 0) {
223 | $phpcsFile->fixer->addContent($closer, ' ');
224 | } else {
225 | $phpcsFile->fixer->replaceToken(($closer + 1), ' ');
226 | }
227 | }
228 | }
229 | }
230 |
231 | // Single newline after closing brace.
232 | if ($tokens[$stackPtr]['code'] !== T_DO) {
233 | for ($next = ($closer + 1); $next < $phpcsFile->numTokens; $next++) {
234 | $code = $tokens[$next]['code'];
235 |
236 | if ($code === T_WHITESPACE) {
237 | continue;
238 | }
239 |
240 | // Skip all empty tokens on the same line as the closer.
241 | if ($tokens[$next]['line'] === $tokens[$closer]['line']
242 | && (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true
243 | || $code === T_CLOSE_TAG)
244 | ) {
245 | continue;
246 | }
247 |
248 | // We found the first bit of a code, or a comment on the
249 | // following line.
250 | break;
251 | }
252 |
253 | $found = ($tokens[$next]['line'] - $tokens[$closer]['line']);
254 | if ($found !== 1) {
255 | $error = 'Expected 1 newline after closing brace; %s found';
256 | $data = array($found);
257 | $fix = $phpcsFile->addFixableError($error, $closer, 'NewlineAfterCloseBrace', $data);
258 | if ($fix === true) {
259 | $phpcsFile->fixer->beginChangeset();
260 | for ($i = ($closer + 1); $i < $next; $i++) {
261 | if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) {
262 | break;
263 | }
264 |
265 | $phpcsFile->fixer->replaceToken($i, '');
266 | }
267 |
268 | $phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar);
269 | $phpcsFile->fixer->endChangeset();
270 | }
271 | }
272 | }
273 |
274 | }//end process()
275 | }
276 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/ControlStructures/NoInlineAssignmentSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
32 |
33 | $end_position = $tokens[$stackPtr]['parenthesis_closer'];
34 |
35 | // states: -1 = normal, 0 = start function call (probably), 1 = in function
36 | $function = -1;
37 |
38 | for ($position = $stackPtr; $position < $end_position; $position++)
39 | {
40 | if ($tokens[$position]['type'] == 'T_STRING')
41 | {
42 | $function = 0;
43 | continue;
44 | }
45 |
46 | if ($function === 0)
47 | {
48 | if ($tokens[$position]['type'] == 'T_OPEN_PARENTHESIS')
49 | {
50 | $function = 1;
51 | continue;
52 | }
53 | }
54 | elseif ($function !== 1)
55 | {
56 | $function = -1;
57 | if ($tokens[$position]['type'] == 'T_EQUAL')
58 | {
59 | $error = 'Inline assignment not allowed in if statements';
60 | $phpcsFile->addError($error, $stackPtr, 'IncDecLeft');
61 | return;
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Formatting/SpaceUnaryOperatorSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
28 |
29 | // Check decrement / increment.
30 | if ($tokens[$stackPtr]['code'] === T_DEC || $tokens[$stackPtr]['code'] === T_INC)
31 | {
32 | $modifyLeft = substr($tokens[($stackPtr - 1)]['content'], 0, 1) === '$' ||
33 | $tokens[($stackPtr + 1)]['content'] === ';';
34 |
35 | if ($modifyLeft === true && $tokens[($stackPtr - 1)]['code'] === T_WHITESPACE)
36 | {
37 | $error = 'There must not be a single space before a unary operator statement';
38 | $phpcsFile->addError($error, $stackPtr, 'IncDecLeft');
39 | return;
40 | }
41 |
42 | if ($modifyLeft === false && !in_array(substr($tokens[($stackPtr + 1)]['content'], 0, 1), array('$', ',')))
43 | {
44 | $error = 'A unary operator statement must not be followed by a single space';
45 | $phpcsFile->addError($error, $stackPtr, 'IncDecRight');
46 | return;
47 | }
48 | }
49 |
50 | // Check "!" operator.
51 | if ($tokens[$stackPtr]['code'] === T_BOOLEAN_NOT && $tokens[$stackPtr + 1]['code'] === T_WHITESPACE)
52 | {
53 | $error = 'A unary operator statement must not be followed by a space';
54 | $phpcsFile->addError($error, $stackPtr, 'BooleanNot');
55 | return;
56 | }
57 |
58 | // Find the last syntax item to determine if this is an unary operator.
59 | $lastSyntaxItem = $phpcsFile->findPrevious(
60 | array(T_WHITESPACE),
61 | $stackPtr - 1,
62 | ($tokens[$stackPtr]['column']) * -1,
63 | true,
64 | null,
65 | true
66 | );
67 | $operatorSuffixAllowed = in_array(
68 | $tokens[$lastSyntaxItem]['code'],
69 | array(
70 | T_LNUMBER,
71 | T_DNUMBER,
72 | T_CLOSE_PARENTHESIS,
73 | T_CLOSE_CURLY_BRACKET,
74 | T_CLOSE_SQUARE_BRACKET,
75 | T_VARIABLE,
76 | T_STRING,
77 | )
78 | );
79 |
80 | // Check plus / minus value assignments or comparisons.
81 | if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS)
82 | {
83 | if ($operatorSuffixAllowed === false
84 | && $tokens[($stackPtr + 1)]['code'] === T_WHITESPACE
85 | )
86 | {
87 | $error = 'A unary operator statement must not be followed by a space';
88 | $phpcsFile->addError($error, $stackPtr);
89 | }
90 | }
91 |
92 | } // end process()
93 | // end class
94 | }
95 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/Sniffs/Functions/FunctionDeclarationSniff.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
11 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 | * @link http://pear.php.net/package/PHP_CodeSniffer
13 | */
14 |
15 | if (class_exists('PEAR_Sniffs_Functions_FunctionDeclarationSniff', true) === false) {
16 | $error = 'Class PEAR_Sniffs_Functions_FunctionDeclarationSniff not found';
17 | throw new PHP_CodeSniffer_Exception($error);
18 | }
19 |
20 | /**
21 | * Barracuda_Sniffs_Functions_MultiLineFunctionDeclarationSniff.
22 | *
23 | * Ensure single and multi-line function declarations are defined correctly.
24 | *
25 | * @category PHP
26 | * @package PHP_CodeSniffer
27 | * @author Greg Sherwood
28 | * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
29 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
30 | * @version Release: 1.5.0RC4
31 | * @link http://pear.php.net/package/PHP_CodeSniffer
32 | */
33 | class Barracuda_Sniffs_Functions_FunctionDeclarationSniff extends PEAR_Sniffs_Functions_FunctionDeclarationSniff
34 | {
35 |
36 |
37 | /**
38 | * Processes multi-line declarations.
39 | *
40 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
41 | * @param int $stackPtr The position of the current token
42 | * in the stack passed in $tokens.
43 | * @param array $tokens The stack of tokens that make up
44 | * the file.
45 | *
46 | * @return void
47 | */
48 | public function processMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
49 | {
50 | // We need to work out how far indented the function
51 | // declaration itself is, so we can work out how far to
52 | // indent parameters.
53 | $functionIndent = 0;
54 | for ($i = ($stackPtr - 1); $i >= 0; $i--) {
55 | if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
56 | $i++;
57 | break;
58 | }
59 | }
60 |
61 | if ($tokens[$i]['code'] === T_WHITESPACE) {
62 | $functionIndent = strlen($tokens[$i]['content']);
63 | }
64 |
65 | // The closing parenthesis must be on a new line, even
66 | // when checking abstract function definitions.
67 | $closeBracket = $tokens[$stackPtr]['parenthesis_closer'];
68 | $prev = $phpcsFile->findPrevious(
69 | T_WHITESPACE,
70 | ($closeBracket - 1),
71 | null,
72 | true
73 | );
74 |
75 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
76 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
77 | $error = 'The closing parenthesis of a multi-line function declaration must be on a new line';
78 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine');
79 | if ($fix === true) {
80 | $phpcsFile->fixer->addNewlineBefore($closeBracket);
81 | }
82 | }
83 | }
84 |
85 | // If this is a closure and is using a USE statement, the closing
86 | // parenthesis we need to look at from now on is the closing parenthesis
87 | // of the USE statement.
88 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
89 | $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
90 | if ($use !== false) {
91 | $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
92 | $closeBracket = $tokens[$open]['parenthesis_closer'];
93 |
94 | $prev = $phpcsFile->findPrevious(
95 | T_WHITESPACE,
96 | ($closeBracket - 1),
97 | null,
98 | true
99 | );
100 |
101 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
102 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
103 | $error = 'The closing parenthesis of a multi-line use declaration must be on a new line';
104 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine');
105 | if ($fix === true) {
106 | $phpcsFile->fixer->addNewlineBefore($closeBracket);
107 | }
108 | }
109 | }
110 | }//end if
111 | }//end if
112 |
113 | // Each line between the parenthesis should be indented 4 spaces.
114 | $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
115 | $lastLine = $tokens[$openBracket]['line'];
116 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
117 | if ($tokens[$i]['line'] !== $lastLine) {
118 | if ($i === $tokens[$stackPtr]['parenthesis_closer']
119 | || ($tokens[$i]['code'] === T_WHITESPACE
120 | && (($i + 1) === $closeBracket
121 | || ($i + 1) === $tokens[$stackPtr]['parenthesis_closer']))
122 | ) {
123 | // Closing braces need to be indented to the same level
124 | // as the function.
125 | $expectedIndent = $functionIndent;
126 | } else {
127 | $expectedIndent = ($functionIndent + $this->indent);
128 | }
129 |
130 | // We changed lines, so this should be a whitespace indent token.
131 | if ($tokens[$i]['code'] !== T_WHITESPACE) {
132 | $foundIndent = 0;
133 | } else {
134 | $foundIndent = strlen($tokens[$i]['content']);
135 | }
136 |
137 | if ($expectedIndent !== $foundIndent) {
138 | $error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s';
139 | $data = array(
140 | $expectedIndent,
141 | $foundIndent,
142 | );
143 |
144 | $fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data);
145 | if ($fix === true) {
146 | $spaces = str_repeat(' ', $expectedIndent);
147 | if ($foundIndent === 0) {
148 | $phpcsFile->fixer->addContentBefore($i, $spaces);
149 | } else {
150 | $phpcsFile->fixer->replaceToken($i, $spaces);
151 | }
152 | }
153 | }
154 |
155 | $lastLine = $tokens[$i]['line'];
156 | }//end if
157 |
158 | if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
159 | // Skip arrays as they have their own indentation rules.
160 | if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
161 | $i = $tokens[$i]['bracket_closer'];
162 | } else {
163 | $i = $tokens[$i]['parenthesis_closer'];
164 | }
165 |
166 | $lastLine = $tokens[$i]['line'];
167 | continue;
168 | }
169 | }//end for
170 |
171 | if (isset($tokens[$stackPtr]['scope_opener']) === true) {
172 |
173 | // any scope opener, we get the next token (should be EOL)
174 | $next = $tokens[($closeBracket + 1)];
175 |
176 | // if the token is EOL, then no error
177 | if ($next['content'] === $phpcsFile->eolChar)
178 | {
179 | $length = -1;
180 | } elseif ($next['code'] == T_OPEN_CURLY_BRACKET) {
181 | $length = 0;
182 | } else {
183 | $length = strlen($next['content']);
184 | }
185 |
186 | // any length means a problem, even zero
187 | if ($length >= 0) {
188 | $data = array($length);
189 | $code = 'NewLineBeforeOpenBrace';
190 |
191 | $error = 'There must be a newline before the opening brace of a multi-line function declaration; found ';
192 |
193 | // if whitespace, then report it
194 | if ($length > 0) {
195 | $error .= '%s spaces';
196 | $code = 'SpaceBeforeOpenBrace';
197 | }
198 | // otherwise, no space but still brace on same line
199 | else
200 | {
201 | $error .= ' opening brace';
202 | }
203 |
204 | $fix = $phpcsFile->addFixableError($error, ($closeBracket + 1), $code, $data);
205 | if ($fix === true) {
206 |
207 | // remove whitespace
208 | if ($length > 0)
209 | {
210 | $phpcsFile->fixer->replaceToken($closeBracket + 1, '');
211 | }
212 |
213 | // add the EOL token
214 | $phpcsFile->fixer->addContent($closeBracket, $phpcsFile->eolChar);
215 | }
216 |
217 | return;
218 | }//end if
219 |
220 | // And just in case they do something funny before the brace...
221 | $next = $phpcsFile->findNext(
222 | T_WHITESPACE,
223 | ($closeBracket + 1),
224 | null,
225 | true
226 | );
227 |
228 | if ($next !== false && $tokens[$next]['code'] !== T_OPEN_CURLY_BRACKET) {
229 | $error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration';
230 | $phpcsFile->addError($error, $next, 'NoSpaceBeforeOpenBrace');
231 | }
232 | }//end if
233 |
234 | $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
235 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'function');
236 |
237 | if ($tokens[$stackPtr]['code'] !== T_CLOSURE) {
238 | return;
239 | }
240 |
241 | $use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']);
242 | if ($use === false) {
243 | return;
244 | }
245 |
246 | $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null);
247 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'use');
248 |
249 | // Also check spacing.
250 | if ($tokens[($use - 1)]['code'] === T_WHITESPACE) {
251 | $gap = strlen($tokens[($use - 1)]['content']);
252 | } else {
253 | $gap = 0;
254 | }
255 |
256 | }//end processMultiLineDeclaration()
257 |
258 |
259 | /**
260 | * Processes the contents of a single set of brackets.
261 | *
262 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
263 | * @param int $openBracket The position of the open bracket
264 | * in the stack passed in $tokens.
265 | * @param array $tokens The stack of tokens that make up
266 | * the file.
267 | * @param string $type The type of the token the brackets
268 | * belong to (function or use).
269 | *
270 | * @return void
271 | */
272 | public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket, $tokens, $type='function')
273 | {
274 | $errorPrefix = '';
275 | if ($type === 'use') {
276 | $errorPrefix = 'Use';
277 | }
278 |
279 | $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
280 |
281 | // The open bracket should be the last thing on the line.
282 | if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) {
283 | $next = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true);
284 | if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) {
285 | $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket';
286 | $phpcsFile->addError($error, $next, $errorPrefix.'FirstParamSpacing');
287 | }
288 | }
289 |
290 | // Each line between the brackets should contain a single parameter.
291 | $lastCommaLine = null;
292 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
293 | // Skip brackets, like arrays, as they can contain commas.
294 | if (isset($tokens[$i]['parenthesis_opener']) === true) {
295 | $i = $tokens[$i]['parenthesis_closer'];
296 | continue;
297 | }
298 |
299 | if ($tokens[$i]['code'] === T_COMMA) {
300 | if ($lastCommaLine !== null && $lastCommaLine === $tokens[$i]['line']) {
301 | $error = 'Multi-line '.$type.' declarations must define one parameter per line';
302 | $phpcsFile->addError($error, $i, $errorPrefix.'OneParamPerLine');
303 | } else {
304 | // Comma must be the last thing on the line.
305 | $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true);
306 | if ($tokens[$next]['line'] !== ($tokens[$i]['line'] + 1)) {
307 | $error = 'Commas in multi-line '.$type.' declarations must be the last content on a line';
308 | $phpcsFile->addError($error, $next, $errorPrefix.'ContentAfterComma');
309 | }
310 | }
311 |
312 | $lastCommaLine = $tokens[$i]['line'];
313 | }
314 | }
315 |
316 | }//end processBracket()
317 |
318 | /**
319 | * Processes single-line declarations.
320 | *
321 | * Just uses the Generic BSD-Allman brace sniff.
322 | *
323 | * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
324 | * @param int $stackPtr The position of the current token
325 | * in the stack passed in $tokens.
326 | * @param array $tokens The stack of tokens that make up
327 | * the file.
328 | *
329 | * @return void
330 | */
331 | public function processSingleLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
332 | {
333 | if (class_exists('Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff', true) === false) {
334 | throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff not found');
335 | }
336 |
337 | $sniff = new Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff();
338 |
339 | $sniff->process($phpcsFile, $stackPtr);
340 |
341 | }//end processSingleLineDeclaration()
342 |
343 |
344 | }//end class
345 |
346 | ?>
347 |
--------------------------------------------------------------------------------
/.phpcs/Barracuda/bootstrap.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/barracudanetworks/ArchiveStream-php/c66e749de07df3e3c8556c0c67fa59729f134e1a/.phpcs/Barracuda/bootstrap.php
--------------------------------------------------------------------------------
/.phpcs/Barracuda/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PSR2 with Barracuda exceptions
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
48 |
49 |
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [Unreleased]
8 |
9 | ## [1.0.4] - 2016-09-01
10 | ### Fixed
11 | - Adding a directory to ZIP file created a file instead
12 |
13 | ## [1.0.3] - 2016-05-18
14 | ### Fixed
15 | - GMP detection in PHP 5.6+ (#24)
16 |
17 | ## [1.0.2] - 2016-01-07
18 | ### Changed
19 | - Switched `ob_end_clean()` to `ob_end_flush()` to prevent errors if data was
20 | previously added to the buffer.
21 |
22 | ## [1.0.1] - 2016-01-06
23 | ### Fixed
24 | - Added `php-mbstring` requirement to `composer.json`
25 |
26 | ## [1.0.0] - 2015-12-04
27 | ### Changed
28 | - Namespaced classes under `Barracuda\ArchiveStream`
29 | - Added support for composer autoloading
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Original work Copyright 2007-2009 Paul Duncan
2 | Modified work Copyright 2013 Barracuda Networks, Inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArchiveStream 1.0.7
2 | [](https://codeclimate.com/github/barracudanetworks/ArchiveStream-php)
3 |
4 | A library for dynamically streaming dynamic tar or zip files without the need to have the complete file stored on the server. You can specify if you want a tar or a zip; or if you want to have the library figure out the best option based on the user agent string.
5 |
6 | ## Options
7 |
8 | ```php
9 | /**
10 | * Construct Parameters:
11 | *
12 | * $name - Name of output file (optional).
13 | * $opt - Hash of archive options (optional, see "Archive Options"
14 | * below).
15 | * $output_stream - Output stream for archive (optional - defaults to php://output)
16 | *
17 | * Archive Options:
18 | *
19 | * comment - Comment for this archive. (zip only)
20 | * content_type - HTTP Content-Type. Defaults to 'application/x-zip'.
21 | * content_disposition - HTTP Content-Disposition. Defaults to
22 | * 'attachment; filename=\"FILENAME\"', where
23 | * FILENAME is the specified filename.
24 | * large_file_size - Size, in bytes, of the largest file to try
25 | * and load into memory (used by
26 | * add_file_from_path()). Large files may also
27 | * be compressed differently; see the
28 | * 'large_file_method' option.
29 | * send_http_headers - Boolean indicating whether or not to send
30 | * the HTTP headers for this file.
31 | * large_files_only - Boolean indicating whether or not to assume
32 | * that all files we are sending are large.
33 | *
34 | * File Options:
35 | * time - Last-modified timestamp (seconds since the epoch) of
36 | * this file. Defaults to the current time.
37 | * comment - Comment related to this file. (zip only)
38 | * type - Type of file object. (tar only)
39 | *
40 | *
41 | * Note that content_type and content_disposition do nothing if you are
42 | * not sending HTTP headers.
43 | *
44 | * Large File Support:
45 | *
46 | * By default, the method add_file_from_path() will send send files
47 | * larger than 20 megabytes along raw rather than attempting to
48 | * compress them. You can change both the maximum size and the
49 | * compression behavior using the large_file_* options above, with the
50 | * following caveats:
51 | *
52 | * * For "small" files (e.g. files smaller than large_file_size), the
53 | * memory use can be up to twice that of the actual file. In other
54 | * words, adding a 10 megabyte file to the archive could potentially
55 | * occupty 20 megabytes of memory.
56 | *
57 | * * For "large" files we use the store method, meaning that the file is
58 | * not compressed at all, this is because there is not currenly a good way
59 | * to compress a stream within PHP
60 | *
61 | * Notes:
62 | *
63 | * If you do not set a filename, then this library _DOES NOT_ send HTTP
64 | * headers by default. This behavior is to allow software to send its
65 | * own headers (including the filename), and still use this library.
66 | */
67 | ```
68 |
69 | ## Usage
70 |
71 | ### Stream whole file at a time
72 |
73 | A fast and simple streaming archive files for PHP. Here's a simple example:
74 |
75 | ```php
76 | // Create a new archive stream object (tar or zip depending on user agent)
77 | $zip = \Barracuda\ArchiveStream\Archive::instance_by_useragent('example');
78 |
79 | // Create a file named 'hello.txt'
80 | $zip->add_file('hello.txt', 'This is the contents of hello.txt');
81 |
82 | // Add a file named 'image.jpg' from a local file 'path/to/image.jpg'
83 | $zip->add_file_from_path('image.jpg', 'path/to/image.jpg');
84 |
85 | // Finish the zip stream
86 | $zip->finish();
87 | ```
88 |
89 | ### Stream each file in parts
90 |
91 | This method can be used to serve files of any size (GB, TB).
92 |
93 | ```php
94 | // Create a new archive stream object (tar or zip depending on user agent)
95 | $zip = \Barracuda\ArchiveStream\Archive::instance_by_useragent('example');
96 |
97 | // Initiate the stream transfer of some_image.jpg with size 324134
98 | $zip->init_file_stream_transfer('some_image.jpg', 324134);
99 |
100 | // Stream part of the contents of some_image.jpg
101 | // This method should be called as many times as needed to send all of its data
102 | $zip->stream_file_part($data);
103 |
104 | // Send data descriptor header for file
105 | $zip->complete_file_stream();
106 |
107 | // Other files can be added here, simply run the three commands above for each file that is being sent
108 |
109 | // Explicitly add a directory to the zip (doesn't recurse - useful for empty
110 | // directories)
111 | $zip->add_directory('foo');
112 | $zip->add_directory('foo/bar');
113 |
114 | // Finish the zip stream
115 | $zip->finish();
116 | ```
117 |
118 | ## Installation
119 |
120 | Simply run `composer require barracudanetworks/archivestream-php` inside your project.
121 |
122 | ## Requirements
123 |
124 | * PHP >=5.1.2 (or the [Hash extension](http://php.net/hash)).
125 | * gmp extension
126 |
127 | ## Limitations
128 |
129 | * Only the Zip64 (version 4.5 of the Zip specification) format is supported.
130 | * Files cannot be resumed if a download fails before finishing.
131 |
132 | ### Other
133 |
134 | You can also add comments, modify file timestamps, and customize (or
135 | disable) the HTTP headers. See the class file for details.
136 |
137 | ## Contributors
138 | - Paul Duncan - Original author
139 | - Daniel Bergey
140 | - Andy Blyler
141 | - Tony Blyler
142 | - Andrew Borek
143 | - Rafael Corral
144 | - John Maguire
145 | - Zachery Stuart
146 |
147 | ## License
148 |
149 | Original work Copyright 2007-2009 Paul Duncan
150 | Modified work Copyright 2013-2015 Barracuda Networks, Inc.
151 |
152 | Licensed under the MIT License
153 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "barracudanetworks/archivestream-php",
3 | "type": "library",
4 | "description": "A library for dynamically streaming dynamic tar or zip files without the need to have the complete file stored on the server.",
5 | "keywords": ["zip", "tar", "archive", "stream", "php"],
6 | "homepage": "https://github.com/barracudanetworks/ArchiveStream-php",
7 | "license": "MIT",
8 | "require": {
9 | "php": ">=5.1.2",
10 | "ext-gmp": "*",
11 | "ext-mbstring": "*"
12 | },
13 | "autoload": {
14 | "psr-4": {
15 | "Barracuda\\ArchiveStream\\": "src/"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/extras/README:
--------------------------------------------------------------------------------
1 | Based on PKZIP appnotes, which are included here.
2 |
3 |
--------------------------------------------------------------------------------
/src/Archive.php:
--------------------------------------------------------------------------------
1 | output_stream = $output_stream;
71 |
72 | // save options
73 | $this->opt = $opt;
74 |
75 | // if a $base_path was passed set the protected property with that value, otherwise leave it empty
76 | $this->container_dir_name = isset($base_path) ? $base_path . '/' : '';
77 |
78 | // set large file defaults: size = 20 megabytes, method = store
79 | if (!isset($this->opt['large_file_size']))
80 | {
81 | $this->opt['large_file_size'] = 20 * 1024 * 1024;
82 | }
83 |
84 | if (!isset($this->opt['large_files_only']))
85 | {
86 | $this->opt['large_files_only'] = false;
87 | }
88 |
89 | $this->output_name = $name;
90 | if ($name || isset($opt['send_http_headers']))
91 | {
92 | $this->need_headers = true;
93 | }
94 |
95 | // turn off output buffering
96 | while (ob_get_level() > 0)
97 | {
98 | // throw away any output left in the buffer
99 | ob_end_clean();
100 | }
101 | }
102 |
103 | /**
104 | * Create instance based on useragent string
105 | *
106 | * @param string $base_filename A name for the resulting archive (without an extension).
107 | * @param array $opt Map of archive options (see above for list).
108 | * @param resource $output_stream Output stream for archive contents.
109 | * @return Zip|Tar for either zip or tar
110 | */
111 | public static function instance_by_useragent(
112 | $base_filename = null,
113 | array $opt = array(),
114 | $output_stream = null
115 | )
116 | {
117 | if ($output_stream === null) {
118 | // Output stream for cli and web server
119 | $output_stream = fopen('php://output', 'w');
120 | }
121 |
122 | $user_agent = (isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : '');
123 |
124 | // detect windows and use zip
125 | if (strpos($user_agent, 'windows') !== false)
126 | {
127 | $filename = (($base_filename === null) ? null : $base_filename . '.zip');
128 | return new Zip($filename, $opt, $base_filename, $output_stream);
129 | }
130 | // fallback to tar
131 | else
132 | {
133 | $filename = (($base_filename === null) ? null : $base_filename . '.tar');
134 | return new Tar($filename, $opt, $base_filename, $output_stream);
135 | }
136 | }
137 |
138 | /**
139 | * Add file to the archive
140 | *
141 | * Parameters:
142 | *
143 | * @param string $name Path of file in the archive (including directory).
144 | * @param string $data Contents of the file.
145 | * @param array $opt Map of file options (see above for list).
146 | * @return void
147 | */
148 | public function add_file($name, $data, array $opt = array())
149 | {
150 | // calculate header attributes
151 | $this->meth_str = 'deflate';
152 | $meth = 0x08;
153 |
154 | // send file header
155 | $this->init_file_stream_transfer($name, strlen($data), $opt, $meth);
156 |
157 | // send data
158 | $this->stream_file_part($data, $single_part = true);
159 |
160 | // complete the file stream
161 | $this->complete_file_stream();
162 | }
163 |
164 | /**
165 | * Add file by path
166 | *
167 | * @param string $name Name of file in archive (including directory path).
168 | * @param string $path Path to file on disk (note: paths should be encoded using
169 | * UNIX-style forward slashes -- e.g '/path/to/some/file').
170 | * @param array $opt Map of file options (see above for list).
171 | * @return void
172 | */
173 | public function add_file_from_path($name, $path, array $opt = array())
174 | {
175 | if ($this->opt['large_files_only'] || $this->is_large_file($path))
176 | {
177 | // file is too large to be read into memory; add progressively
178 | $this->add_large_file($name, $path, $opt);
179 | }
180 | else
181 | {
182 | // file is small enough to read into memory; read file contents and
183 | // handle with add_file()
184 | $data = file_get_contents($path);
185 | $this->add_file($name, $data, $opt);
186 | }
187 | }
188 |
189 | /**
190 | * Log an error to be added to the error log in the archive.
191 | *
192 | * @param string $message Error text to add to the log file.
193 | * @return void
194 | */
195 | public function push_error($message)
196 | {
197 | $this->errors[] = (string) $message;
198 | }
199 |
200 | /**
201 | * Set whether or not all elements in the archive will be placed within one container directory.
202 | *
203 | * @param bool $bool True to use contaner directory, false to prevent using one. Defaults to false.
204 | * @return void
205 | */
206 | public function set_use_container_dir($bool = false)
207 | {
208 | $this->use_container_dir = (bool) $bool;
209 | }
210 |
211 | /**
212 | * Set the name filename for the error log file when it's added to the archive
213 | *
214 | * @param string $name Filename for the error log.
215 | * @return void
216 | */
217 | public function set_error_log_filename($name)
218 | {
219 | if (isset($name))
220 | {
221 | $this->error_log_filename = (string) $name;
222 | }
223 | }
224 |
225 | /**
226 | * Set the first line of text in the error log file
227 | *
228 | * @param string $msg Message to display on the first line of the error log file.
229 | * @return void
230 | */
231 | public function set_error_header_text($msg)
232 | {
233 | if (isset($msg))
234 | {
235 | $this->error_header_text = (string) $msg;
236 | }
237 | }
238 |
239 | /***************************
240 | * PRIVATE UTILITY METHODS *
241 | ***************************/
242 |
243 | /**
244 | * Add a large file from the given path
245 | *
246 | * @param string $name Name of file in archive (including directory path).
247 | * @param string $path Path to file on disk (note: paths should be encoded using
248 | * UNIX-style forward slashes -- e.g '/path/to/some/file').
249 | * @param array $opt Map of file options (see above for list).
250 | * @return void
251 | */
252 | protected function add_large_file($name, $path, array $opt = array())
253 | {
254 | // send file header
255 | $this->init_file_stream_transfer($name, filesize($path), $opt);
256 |
257 | // open input file
258 | $fh = fopen($path, 'rb');
259 |
260 | // send file blocks
261 | while ($data = fread($fh, $this->block_size))
262 | {
263 | // send data
264 | $this->stream_file_part($data);
265 | }
266 |
267 | // close input file
268 | fclose($fh);
269 |
270 | // complete the file stream
271 | $this->complete_file_stream();
272 | }
273 |
274 | /**
275 | * Is this file larger than large_file_size?
276 | *
277 | * @param string $path Path to file on disk.
278 | * @return bool True if large, false if small.
279 | */
280 | protected function is_large_file($path)
281 | {
282 | $st = stat($path);
283 | return ($this->opt['large_file_size'] > 0) && ($st['size'] > $this->opt['large_file_size']);
284 | }
285 |
286 | /**
287 | * Send HTTP headers for this stream.
288 | *
289 | * @return void
290 | */
291 | private function send_http_headers()
292 | {
293 | // grab options
294 | $opt = $this->opt;
295 |
296 | // grab content type from options
297 | if (isset($opt['content_type']))
298 | {
299 | $content_type = $opt['content_type'];
300 | }
301 | else
302 | {
303 | $content_type = 'application/x-zip';
304 | }
305 |
306 | // grab content type encoding from options and append to the content type option
307 | if (isset($opt['content_type_encoding']))
308 | {
309 | $content_type .= '; charset=' . $opt['content_type_encoding'];
310 | }
311 |
312 | // grab content disposition
313 | $disposition = 'attachment';
314 | if (isset($opt['content_disposition']))
315 | {
316 | $disposition = $opt['content_disposition'];
317 | }
318 |
319 | if ($this->output_name)
320 | {
321 | $disposition .= "; filename=\"{$this->output_name}\"";
322 | }
323 |
324 | $headers = array(
325 | 'Content-Type' => $content_type,
326 | 'Content-Disposition' => $disposition,
327 | 'Pragma' => 'public',
328 | 'Cache-Control' => 'public, must-revalidate',
329 | 'Content-Transfer-Encoding' => 'binary',
330 | );
331 |
332 | foreach ($headers as $key => $val)
333 | {
334 | header("$key: $val");
335 | }
336 | }
337 |
338 | /**
339 | * Send string, sending HTTP headers if necessary.
340 | *
341 | * @param string $data Data to send.
342 | * @return void
343 | */
344 | protected function send($data)
345 | {
346 | if ($this->need_headers)
347 | {
348 | $this->send_http_headers();
349 | }
350 |
351 | $this->need_headers = false;
352 |
353 | do
354 | {
355 | $result = fwrite($this->output_stream, $data);
356 | $data = substr($data, $result);
357 | fflush($this->output_stream);
358 | } while ($data && $result !== false);
359 | }
360 |
361 | /**
362 | * If errors were encountered, add an error log file to the end of the archive
363 | * @return void
364 | */
365 | public function add_error_log()
366 | {
367 | if (!empty($this->errors))
368 | {
369 | $msg = $this->error_header_text;
370 | foreach ($this->errors as $err)
371 | {
372 | $msg .= "\r\n\r\n" . $err;
373 | }
374 |
375 | // stash current value so it can be reset later
376 | $temp = $this->use_container_dir;
377 |
378 | // set to false to put the error log file in the root instead of the container directory, if we're using one
379 | $this->use_container_dir = false;
380 |
381 | $this->add_file($this->error_log_filename, $msg);
382 |
383 | // reset to original value and dump the temp variable
384 | $this->use_container_dir = $temp;
385 | unset($temp);
386 | }
387 | }
388 |
389 | /**
390 | * Convert a UNIX timestamp to a DOS timestamp.
391 | *
392 | * @param int $when Unix timestamp.
393 | * @return string DOS timestamp
394 | */
395 | protected function dostime($when = 0)
396 | {
397 | // get date array for timestamp
398 | $d = getdate($when);
399 |
400 | // set lower-bound on dates
401 | if ($d['year'] < 1980)
402 | {
403 | $d = array(
404 | 'year' => 1980, 'mon' => 1, 'mday' => 1,
405 | 'hours' => 0, 'minutes' => 0, 'seconds' => 0
406 | );
407 | }
408 |
409 | // remove extra years from 1980
410 | $d['year'] -= 1980;
411 |
412 | // return date string
413 | return ($d['year'] << 25) | ($d['mon'] << 21) | ($d['mday'] << 16) |
414 | ($d['hours'] << 11) | ($d['minutes'] << 5) | ($d['seconds'] >> 1);
415 | }
416 |
417 | /**
418 | * Split a 64-bit integer to two 32-bit integers.
419 | *
420 | * @param mixed $value Integer or GMP resource.
421 | * @return array Containing high and low 32-bit integers.
422 | */
423 | protected function int64_split($value)
424 | {
425 | // gmp
426 | if (is_resource($value) || $value instanceof GMP)
427 | {
428 | $hex = str_pad(gmp_strval($value, 16), 16, '0', STR_PAD_LEFT);
429 |
430 | $high = $this->gmp_convert(substr($hex, 0, 8), 16, 10);
431 | $low = $this->gmp_convert(substr($hex, 8, 8), 16, 10);
432 | }
433 | // int
434 | else
435 | {
436 | $left = 0xffffffff00000000;
437 | $right = 0x00000000ffffffff;
438 |
439 | $high = ($value & $left) >>32;
440 | $low = $value & $right;
441 | }
442 |
443 | return array($low, $high);
444 | }
445 |
446 | /**
447 | * Create a format string and argument list for pack(), then call pack() and return the result.
448 | *
449 | * @param array $fields Key is format string and the value is the data to pack.
450 | * @return string Binary packed data returned from pack().
451 | */
452 | protected function pack_fields(array $fields)
453 | {
454 | $fmt = '';
455 | $args = array();
456 |
457 | // populate format string and argument list
458 | foreach ($fields as $field)
459 | {
460 | $fmt .= $field[0];
461 | $args[] = $field[1];
462 | }
463 |
464 | // prepend format string to argument list
465 | array_unshift($args, $fmt);
466 |
467 | // build output string from header and compressed data
468 | return call_user_func_array('pack', $args);
469 | }
470 |
471 | /**
472 | * Convert a number between bases via GMP.
473 | *
474 | * @param int $num Number to convert.
475 | * @param int $base_a Base to convert from.
476 | * @param int $base_b Base to convert to.
477 | * @return string Number in string format.
478 | */
479 | private function gmp_convert($num, $base_a, $base_b)
480 | {
481 | $gmp_num = gmp_init($num, $base_a);
482 |
483 | if (!(is_resource($gmp_num) || $gmp_num instanceof GMP))
484 | {
485 | // FIXME: Really? We just die here? Can we detect GMP in __constructor() instead maybe?
486 | die("gmp_convert could not convert [$num] from base [$base_a] to base [$base_b]");
487 | }
488 |
489 | return gmp_strval($gmp_num, $base_b);
490 | }
491 | }
492 |
--------------------------------------------------------------------------------
/src/TarArchive.php:
--------------------------------------------------------------------------------
1 | opt['content_type'] = 'application/x-tar';
28 | }
29 |
30 | /**
31 | * Explicitly adds a directory to the tar (necessary for empty directories).
32 | *
33 | * @param string $name Name (path) of the directory.
34 | * @param array $opt Additional options to set ("type" will be overridden).
35 | * @return void
36 | */
37 | public function add_directory($name, array $opt = array())
38 | {
39 | // calculate header attributes
40 | $this->meth_str = 'deflate';
41 | $meth = 0x08;
42 |
43 | $opt['type'] = self::DIRTYPE;
44 |
45 | // send header
46 | $this->init_file_stream_transfer($name, $size = 0, $opt, $meth);
47 |
48 | // complete the file stream
49 | $this->complete_file_stream();
50 | }
51 |
52 | /**
53 | * Initialize a file stream.
54 | *
55 | * @param string $name File path or just name.
56 | * @param int $size Size in bytes of the file.
57 | * @param array $opt Array containing time / type (optional).
58 | * @param int $meth Method of compression to use (ignored by TarArchive class).
59 | * @return void
60 | */
61 | public function init_file_stream_transfer($name, $size, array $opt = array(), $meth = null)
62 | {
63 | // try to detect the type if not provided
64 | $type = self::REGTYPE;
65 | if (isset($opt['type']))
66 | {
67 | $type = $opt['type'];
68 | }
69 | elseif (substr($name, -1) == '/')
70 | {
71 | $type = self::DIRTYPE;
72 | }
73 |
74 | $dirname = dirname($name);
75 | $name = basename($name);
76 |
77 | // Remove '.' from the current directory
78 | $dirname = ($dirname == '.') ? '' : $dirname;
79 |
80 | // if we're using a container directory, prepend it to the filename
81 | if ($this->use_container_dir)
82 | {
83 | // container directory will end with a '/' so ensure the lower level directory name doesn't start with one
84 | $dirname = $this->container_dir_name . preg_replace('/^\/+/', '', $dirname);
85 | }
86 |
87 | // Remove trailing slash from directory name, because tar implies it.
88 | if (substr($dirname, -1) == '/')
89 | {
90 | $dirname = substr($dirname, 0, -1);
91 | }
92 |
93 | // handle long file names via PAX
94 | if (strlen($name) > 99 || strlen($dirname) > 154)
95 | {
96 | $pax = $this->__pax_generate(array(
97 | 'path' => $dirname . '/' . $name
98 | ));
99 |
100 | $this->init_file_stream_transfer('', strlen($pax), array(
101 | 'type' => self::XHDTYPE
102 | ));
103 |
104 | $this->stream_file_part($pax, $single_part = true);
105 | $this->complete_file_stream();
106 | }
107 |
108 | // stash the file size for later use
109 | $this->file_size = $size;
110 |
111 | // process optional arguments
112 | $time = isset($opt['time']) ? $opt['time'] : time();
113 |
114 | // build data descriptor
115 | $fields = array(
116 | array('a100', substr($name, 0, 100)),
117 | array('a8', str_pad('777', 7, '0', STR_PAD_LEFT)),
118 | array('a8', decoct(str_pad('0', 7, '0', STR_PAD_LEFT))),
119 | array('a8', decoct(str_pad('0', 7, '0', STR_PAD_LEFT))),
120 | array('a12', decoct(str_pad($size, 11, '0', STR_PAD_LEFT))),
121 | array('a12', decoct(str_pad($time, 11, '0', STR_PAD_LEFT))),
122 | array('a8', ''),
123 | array('a1', $type),
124 | array('a100', ''),
125 | array('a6', 'ustar'),
126 | array('a2', '00'),
127 | array('a32', ''),
128 | array('a32', ''),
129 | array('a8', ''),
130 | array('a8', ''),
131 | array('a155', substr($dirname, 0, 155)),
132 | array('a12', ''),
133 | );
134 |
135 | // pack fields and calculate "total" length
136 | $header = $this->pack_fields($fields);
137 |
138 | // Compute header checksum
139 | $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, "0", STR_PAD_LEFT);
140 | for ($i=0; $i<6; $i++)
141 | {
142 | $header[(148 + $i)] = substr($checksum, $i, 1);
143 | }
144 |
145 | $header[154] = chr(0);
146 | $header[155] = chr(32);
147 |
148 | // print header
149 | $this->send($header);
150 | }
151 |
152 | /**
153 | * Stream the next part of the current file stream.
154 | *
155 | * @param string $data Raw data to send.
156 | * @param bool $single_part Used to determin if we can compress (not used in TarArchive class).
157 | * @return void
158 | */
159 | public function stream_file_part($data, $single_part = false)
160 | {
161 | // send data
162 | $this->send($data);
163 |
164 | // flush the data to the output
165 | flush();
166 | }
167 |
168 | /**
169 | * Complete the current file stream
170 | *
171 | * @return void
172 | */
173 | public function complete_file_stream()
174 | {
175 | // ensure we pad the last block so that it is 512 bytes
176 | $mod = ($this->file_size % 512);
177 | if ($mod > 0)
178 | {
179 | $this->send(pack('a' . (512 - $mod), ''));
180 | }
181 |
182 | // flush the data to the output
183 | flush();
184 | }
185 |
186 | /**
187 | * Finish an archive
188 | *
189 | * @return void
190 | */
191 | public function finish()
192 | {
193 | // adds an error log file if we've been tracking errors
194 | $this->add_error_log();
195 |
196 | // tar requires the end of the file have two 512 byte null blocks
197 | $this->send(pack('a1024', ''));
198 |
199 | // flush the data to the output
200 | flush();
201 | }
202 |
203 | /*******************
204 | * PRIVATE METHODS *
205 | *******************/
206 |
207 | /**
208 | * Generate unsigned checksum of header
209 | *
210 | * @param string $header File header.
211 | * @return string Unsigned checksum.
212 | * @access private
213 | */
214 | private function __computeUnsignedChecksum($header)
215 | {
216 | $unsigned_checksum = 0;
217 |
218 | for ($i = 0; $i < 512; $i++)
219 | {
220 | $unsigned_checksum += ord($header[$i]);
221 | }
222 |
223 | for ($i = 0; $i < 8; $i++)
224 | {
225 | $unsigned_checksum -= ord($header[148 + $i]);
226 | }
227 |
228 | $unsigned_checksum += ord(" ") * 8;
229 |
230 | return $unsigned_checksum;
231 | }
232 |
233 | /**
234 | * Generate a PAX string
235 | *
236 | * @param array $fields Key value mapping.
237 | * @return string PAX formated string
238 | * @link http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current Tar / PAX spec
239 | */
240 | private function __pax_generate(array $fields)
241 | {
242 | $lines = '';
243 | foreach ($fields as $name => $value)
244 | {
245 | // build the line and the size
246 | $line = ' ' . $name . '=' . $value . "\n";
247 | $size = strlen(strlen($line)) + strlen($line);
248 |
249 | // add the line
250 | $lines .= $size . $line;
251 | }
252 |
253 | return $lines;
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/ZipArchive.php:
--------------------------------------------------------------------------------
1 | opt['content_type'] = 'application/x-zip';
59 | call_user_func_array(array('parent', '__construct'), func_get_args());
60 | }
61 |
62 | /**
63 | * Explicitly adds a directory to the tar (necessary for empty directories)
64 | *
65 | * @param string $name Name (path) of the directory.
66 | * @param array $opt Additional options to set ("type" will be overridden).
67 | * @return void
68 | */
69 | public function add_directory($name, array $opt = array())
70 | {
71 | // calculate header attributes
72 | $this->meth_str = 'deflate';
73 | $meth = 0x08;
74 |
75 | if (substr($name, -1) != '/')
76 | {
77 | $name = $name . '/';
78 | }
79 |
80 | // send header
81 | $this->init_file_stream_transfer($name, $size = 0, $opt, $meth);
82 |
83 | // complete the file stream
84 | $this->complete_file_stream();
85 | }
86 |
87 | /**
88 | * Initialize a file stream
89 | *
90 | * @param string $name File path or just name.
91 | * @param int $size Size in bytes of the file.
92 | * @param array $opt Array containing time / type (optional).
93 | * @param int $meth Method of compression to use (defaults to store).
94 | * @return void
95 | */
96 | public function init_file_stream_transfer($name, $size, array $opt = array(), $meth = 0x00)
97 | {
98 | // if we're using a container directory, prepend it to the filename
99 | if ($this->use_container_dir)
100 | {
101 | // the container directory will end with a '/' so ensure the filename doesn't start with one
102 | $name = $this->container_dir_name . preg_replace('/^\\/+/', '', $name);
103 | }
104 |
105 | $algo = 'crc32b';
106 |
107 | // calculate header attributes
108 | $this->len = gmp_init(0);
109 | $this->zlen = gmp_init(0);
110 | $this->hash_ctx = hash_init($algo);
111 |
112 | // Send file header
113 | $this->add_stream_file_header($name, $size, $opt, $meth);
114 | }
115 |
116 | /**
117 | * Stream the next part of the current file stream.
118 | *
119 | * @param string $data Raw data to send.
120 | * @param bool $single_part Used to determine if we can compress.
121 | * @return void
122 | */
123 | public function stream_file_part($data, $single_part = false)
124 | {
125 | $this->len = gmp_add(gmp_init(strlen($data)), $this->len);
126 | hash_update($this->hash_ctx, $data);
127 |
128 | if ($single_part === true && isset($this->meth_str) && $this->meth_str == 'deflate')
129 | {
130 | $data = gzdeflate($data);
131 | }
132 |
133 | $this->zlen = gmp_add(gmp_init(strlen($data)), $this->zlen);
134 |
135 | // send data
136 | $this->send($data);
137 | flush();
138 | }
139 |
140 | /**
141 | * Complete the current file stream (zip64 format).
142 | *
143 | * @return void
144 | */
145 | public function complete_file_stream()
146 | {
147 | $crc = hexdec(hash_final($this->hash_ctx));
148 |
149 | // convert the 64 bit ints to 2 32bit ints
150 | list($zlen_low, $zlen_high) = $this->int64_split($this->zlen);
151 | list($len_low, $len_high) = $this->int64_split($this->len);
152 |
153 | // build data descriptor
154 | $fields = array( // (from V.A of APPNOTE.TXT)
155 | array('V', 0x08074b50), // data descriptor
156 | array('V', $crc), // crc32 of data
157 | array('V', $zlen_low), // compressed data length (low)
158 | array('V', $zlen_high), // compressed data length (high)
159 | array('V', $len_low), // uncompressed data length (low)
160 | array('V', $len_high), // uncompressed data length (high)
161 | );
162 |
163 | // pack fields and calculate "total" length
164 | $ret = $this->pack_fields($fields);
165 |
166 | // print header and filename
167 | $this->send($ret);
168 |
169 | // Update cdr for file record
170 | $this->current_file_stream[3] = $crc;
171 | $this->current_file_stream[4] = gmp_strval($this->zlen);
172 | $this->current_file_stream[5] = gmp_strval($this->len);
173 | $this->current_file_stream[6] += gmp_strval(gmp_add(gmp_init(strlen($ret)), $this->zlen));
174 | ksort($this->current_file_stream);
175 |
176 | // Add to cdr and increment offset - can't call directly because we pass an array of params
177 | call_user_func_array(array($this, 'add_to_cdr'), $this->current_file_stream);
178 | }
179 |
180 | /**
181 | * Finish an archive
182 | *
183 | * @return void
184 | */
185 | public function finish()
186 | {
187 | // adds an error log file if we've been tracking errors
188 | $this->add_error_log();
189 |
190 | // add trailing cdr record
191 | $this->add_cdr($this->opt);
192 | $this->clear();
193 | }
194 |
195 | /*******************
196 | * PRIVATE METHODS *
197 | *******************/
198 |
199 | /**
200 | * Add initial headers for file stream
201 | *
202 | * @param string $name File path or just name.
203 | * @param int $size Size in bytes of the file.
204 | * @param array $opt Array containing time.
205 | * @param int $meth Method of compression to use.
206 | * @return void
207 | */
208 | protected function add_stream_file_header($name, $size, array $opt, $meth)
209 | {
210 | // strip leading slashes from file name
211 | // (fixes bug in windows archive viewer)
212 | $name = preg_replace('/^\\/+/', '', $name);
213 | $extra = pack('vVVVV', 1, 0, 0, 0, 0);
214 |
215 | // create dos timestamp
216 | $opt['time'] = isset($opt['time']) ? $opt['time'] : time();
217 | $dts = $this->dostime($opt['time']);
218 |
219 | // Sets bit 3, which means CRC-32, uncompressed and compresed length
220 | // are put in the data descriptor following the data. This gives us time
221 | // to figure out the correct sizes, etc.
222 | $genb = 0x08;
223 |
224 | if (mb_check_encoding($name, "UTF-8") && !mb_check_encoding($name, "ASCII"))
225 | {
226 | // Sets Bit 11: Language encoding flag (EFS). If this bit is set,
227 | // the filename and comment fields for this file
228 | // MUST be encoded using UTF-8. (see APPENDIX D)
229 | $genb |= 0x0800;
230 | }
231 |
232 | // build file header
233 | $fields = array( // (from V.A of APPNOTE.TXT)
234 | array('V', 0x04034b50), // local file header signature
235 | array('v', self::VERSION), // version needed to extract
236 | array('v', $genb), // general purpose bit flag
237 | array('v', $meth), // compresion method (deflate or store)
238 | array('V', $dts), // dos timestamp
239 | array('V', 0x00), // crc32 of data (0x00 because bit 3 set in $genb)
240 | array('V', 0xFFFFFFFF), // compressed data length
241 | array('V', 0xFFFFFFFF), // uncompressed data length
242 | array('v', strlen($name)), // filename length
243 | array('v', strlen($extra)), // extra data len
244 | );
245 |
246 | // pack fields and calculate "total" length
247 | $ret = $this->pack_fields($fields);
248 |
249 | // print header and filename
250 | $this->send($ret . $name . $extra);
251 |
252 | // Keep track of data for central directory record
253 | $this->current_file_stream = array(
254 | $name,
255 | $opt,
256 | $meth,
257 | // 3-5 will be filled in by complete_file_stream()
258 | 6 => (strlen($ret) + strlen($name) + strlen($extra)),
259 | 7 => $genb,
260 | 8 => substr($name, -1) == '/' ? 0x10 : 0x20, // 0x10 for directory, 0x20 for file
261 | );
262 | }
263 |
264 | /**
265 | * Save file attributes for trailing CDR record.
266 | *
267 | * @param string $name Path / name of the file.
268 | * @param array $opt Array containing time.
269 | * @param int $meth Method of compression to use.
270 | * @param string $crc Computed checksum of the file.
271 | * @param int $zlen Compressed size.
272 | * @param int $len Uncompressed size.
273 | * @param int $rec_len Size of the record.
274 | * @param int $genb General purpose bit flag.
275 | * @param int $fattr File attribute bit flag.
276 | * @return void
277 | */
278 | private function add_to_cdr($name, array $opt, $meth, $crc, $zlen, $len, $rec_len, $genb = 0, $fattr = 0x20)
279 | {
280 | $this->files[] = array($name, $opt, $meth, $crc, $zlen, $len, $this->cdr_ofs, $genb, $fattr);
281 | $this->cdr_ofs += $rec_len;
282 | }
283 |
284 | /**
285 | * Send CDR record for specified file (Zip64 format).
286 | *
287 | * @see add_to_cdr() for options to pass in $args.
288 | * @param array $args Array of argumentss.
289 | * @return void
290 | */
291 | private function add_cdr_file(array $args)
292 | {
293 | list($name, $opt, $meth, $crc, $zlen, $len, $ofs, $genb, $file_attribute) = $args;
294 |
295 | // convert the 64 bit ints to 2 32bit ints
296 | list($zlen_low, $zlen_high) = $this->int64_split($zlen);
297 | list($len_low, $len_high) = $this->int64_split($len);
298 | list($ofs_low, $ofs_high) = $this->int64_split($ofs);
299 |
300 | // ZIP64, necessary for files over 4GB (incl. entire archive size)
301 | $extra_zip64 = '';
302 | $extra_zip64 .= pack('VV', $len_low, $len_high);
303 | $extra_zip64 .= pack('VV', $zlen_low, $zlen_high);
304 | $extra_zip64 .= pack('VV', $ofs_low, $ofs_high);
305 |
306 | $extra = pack('vv', 1, strlen($extra_zip64)) . $extra_zip64;
307 |
308 | // get attributes
309 | $comment = isset($opt['comment']) ? $opt['comment'] : '';
310 |
311 | // get dos timestamp
312 | $dts = $this->dostime($opt['time']);
313 |
314 | $fields = array( // (from V,F of APPNOTE.TXT)
315 | array('V', 0x02014b50), // central file header signature
316 | array('v', self::VERSION), // version made by
317 | array('v', self::VERSION), // version needed to extract
318 | array('v', $genb), // general purpose bit flag
319 | array('v', $meth), // compresion method (deflate or store)
320 | array('V', $dts), // dos timestamp
321 | array('V', $crc), // crc32 of data
322 | array('V', 0xFFFFFFFF), // compressed data length (zip64 - look in extra)
323 | array('V', 0xFFFFFFFF), // uncompressed data length (zip64 - look in extra)
324 | array('v', strlen($name)), // filename length
325 | array('v', strlen($extra)), // extra data len
326 | array('v', strlen($comment)), // file comment length
327 | array('v', 0), // disk number start
328 | array('v', 0), // internal file attributes
329 | array('V', $file_attribute), // external file attributes, 0x10 for dir, 0x20 for file
330 | array('V', 0xFFFFFFFF), // relative offset of local header (zip64 - look in extra)
331 | );
332 |
333 | // pack fields, then append name and comment
334 | $ret = $this->pack_fields($fields) . $name . $extra . $comment;
335 |
336 | $this->send($ret);
337 |
338 | // increment cdr length
339 | $this->cdr_len += strlen($ret);
340 | }
341 |
342 | /**
343 | * Adds Zip64 end of central directory record.
344 | *
345 | * @return void
346 | */
347 | private function add_cdr_eof_zip64()
348 | {
349 | $num = count($this->files);
350 |
351 | list($num_low, $num_high) = $this->int64_split($num);
352 | list($cdr_len_low, $cdr_len_high) = $this->int64_split($this->cdr_len);
353 | list($cdr_ofs_low, $cdr_ofs_high) = $this->int64_split($this->cdr_ofs);
354 |
355 | $fields = array( // (from V,F of APPNOTE.TXT)
356 | array('V', 0x06064b50), // zip64 end of central directory signature
357 | array('V', 44), // size of zip64 end of central directory record (low) minus 12 bytes
358 | array('V', 0), // size of zip64 end of central directory record (high)
359 | array('v', self::VERSION), // version made by
360 | array('v', self::VERSION), // version needed to extract
361 | array('V', 0x0000), // this disk number (only one disk)
362 | array('V', 0x0000), // number of disk with central dir
363 | array('V', $num_low), // number of entries in the cdr for this disk (low)
364 | array('V', $num_high), // number of entries in the cdr for this disk (high)
365 | array('V', $num_low), // number of entries in the cdr (low)
366 | array('V', $num_high), // number of entries in the cdr (high)
367 | array('V', $cdr_len_low), // cdr size (low)
368 | array('V', $cdr_len_high), // cdr size (high)
369 | array('V', $cdr_ofs_low), // cdr ofs (low)
370 | array('V', $cdr_ofs_high), // cdr ofs (high)
371 | );
372 |
373 | $ret = $this->pack_fields($fields);
374 | $this->send($ret);
375 | }
376 |
377 | /**
378 | * Add location record for ZIP64 central directory
379 | *
380 | * @return void
381 | */
382 | private function add_cdr_eof_locator_zip64()
383 | {
384 | list($cdr_ofs_low, $cdr_ofs_high) = $this->int64_split($this->cdr_len + $this->cdr_ofs);
385 |
386 | $fields = array( // (from V,F of APPNOTE.TXT)
387 | array('V', 0x07064b50), // zip64 end of central dir locator signature
388 | array('V', 0), // this disk number
389 | array('V', $cdr_ofs_low), // cdr ofs (low)
390 | array('V', $cdr_ofs_high), // cdr ofs (high)
391 | array('V', 1), // total number of disks
392 | );
393 |
394 | $ret = $this->pack_fields($fields);
395 | $this->send($ret);
396 | }
397 |
398 | /**
399 | * Send CDR EOF (Central Directory Record End-of-File) record. Most values
400 | * point to the corresponding values in the ZIP64 CDR. The optional comment
401 | * still goes in this CDR however.
402 | *
403 | * @param array $opt Options array that may contain a comment.
404 | * @return void
405 | */
406 | private function add_cdr_eof(array $opt = null)
407 | {
408 | // grab comment (if specified)
409 | $comment = '';
410 | if ($opt && isset($opt['comment']))
411 | {
412 | $comment = $opt['comment'];
413 | }
414 |
415 | $fields = array( // (from V,F of APPNOTE.TXT)
416 | array('V', 0x06054b50), // end of central file header signature
417 | array('v', 0xFFFF), // this disk number (0xFFFF to look in zip64 cdr)
418 | array('v', 0xFFFF), // number of disk with cdr (0xFFFF to look in zip64 cdr)
419 | array('v', 0xFFFF), // number of entries in the cdr on this disk (0xFFFF to look in zip64 cdr))
420 | array('v', 0xFFFF), // number of entries in the cdr (0xFFFF to look in zip64 cdr)
421 | array('V', 0xFFFFFFFF), // cdr size (0xFFFFFFFF to look in zip64 cdr)
422 | array('V', 0xFFFFFFFF), // cdr offset (0xFFFFFFFF to look in zip64 cdr)
423 | array('v', strlen($comment)), // zip file comment length
424 | );
425 |
426 | $ret = $this->pack_fields($fields) . $comment;
427 | $this->send($ret);
428 | }
429 |
430 | /**
431 | * Add CDR (Central Directory Record) footer.
432 | *
433 | * @param array $opt Options array that may contain a comment.
434 | * @return void
435 | */
436 | private function add_cdr(array $opt = null)
437 | {
438 | foreach ($this->files as $file)
439 | {
440 | $this->add_cdr_file($file);
441 | }
442 |
443 | $this->add_cdr_eof_zip64();
444 | $this->add_cdr_eof_locator_zip64();
445 |
446 | $this->add_cdr_eof($opt);
447 | }
448 |
449 | /**
450 | * Clear all internal variables.
451 | *
452 | * Note: the archive object is unusable after this.
453 | *
454 | * @return void
455 | */
456 | private function clear()
457 | {
458 | $this->files = array();
459 | $this->cdr_ofs = 0;
460 | $this->cdr_len = 0;
461 | $this->opt = array();
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/test/index.php:
--------------------------------------------------------------------------------
1 | 'this is a zip file comment. hello?'
22 | ));
23 | var_dump($zip);
24 |
25 | // common file options
26 | $file_opt = array(
27 | // file creation time (2 hours ago)
28 | 'time' => time() - 2 * 3600,
29 |
30 | // file comment
31 | 'comment' => 'this is a file comment. hi!',
32 | );
33 |
34 | // add files under folder 'asdf'
35 | foreach ($files as $file)
36 | {
37 | // build absolute path and get file data
38 | $path = ($file[0] == '/') ? $file : "$pwd/$file";
39 |
40 | // add file to archive
41 | $zip->add_file_from_path('asdf/' . basename($file), $path, $file_opt);
42 | }
43 |
44 | // add a long file name
45 | $zip->add_file('/long/' . str_repeat('a', 200) . '.txt', 'test');
46 | $zip->add_directory('/foo');
47 | $zip->add_directory('/foo/bar');
48 |
49 | // finish archive
50 | $zip->finish();
51 |
--------------------------------------------------------------------------------