├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── Adapter
├── AbstractAdapter.php
├── Console.php
├── Exception
│ ├── ExceptionInterface.php
│ ├── InvalidArgumentException.php
│ └── RuntimeException.php
├── JsPull.php
└── JsPush.php
├── Exception
├── ExceptionInterface.php
├── InvalidArgumentException.php
├── OutOfRangeException.php
├── PhpEnvironmentException.php
└── RuntimeException.php
├── ProgressBar.php
└── Upload
├── AbstractUploadHandler.php
├── ApcProgress.php
├── SessionProgress.php
├── UploadHandlerInterface.php
└── UploadProgress.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file, in reverse chronological order by release.
4 |
5 | ## 2.7.1 - TBD
6 |
7 | ### Added
8 |
9 | - Nothing.
10 |
11 | ### Changed
12 |
13 | - Nothing.
14 |
15 | ### Deprecated
16 |
17 | - Nothing.
18 |
19 | ### Removed
20 |
21 | - Nothing.
22 |
23 | ### Fixed
24 |
25 | - Nothing.
26 |
27 | ## 2.7.0 - 2019-10-17
28 |
29 | ### Added
30 |
31 | - [#27](https://github.com/zendframework/zend-progressbar/pull/27) adds support for PHP 7.3.
32 |
33 | ### Changed
34 |
35 | - Nothing.
36 |
37 | ### Deprecated
38 |
39 | - Nothing.
40 |
41 | ### Removed
42 |
43 | - [#27](https://github.com/zendframework/zend-progressbar/pull/27) removes support for zend-stdlib v2 releases.
44 |
45 | ### Fixed
46 |
47 | - Nothing.
48 |
49 | ## 2.6.0 - 2018-04-30
50 |
51 | ### Added
52 |
53 | - [#25](https://github.com/zendframework/zend-progressbar/pull/25) adds support for PHP 7.1 and 7.2.
54 |
55 | - [#17](https://github.com/zendframework/zend-progressbar/pull/17) adds and
56 | publishes the documentation to https://docs.zendframework.com/zend-progressbar/
57 |
58 | ### Changed
59 |
60 | - Nothing.
61 |
62 | ### Deprecated
63 |
64 | - Nothing.
65 |
66 | ### Removed
67 |
68 | - [#25](https://github.com/zendframework/zend-progressbar/pull/25) removes support for PHP 5.5.
69 |
70 | - [#25](https://github.com/zendframework/zend-progressbar/pull/25) removes support for HHVM.
71 |
72 | ### Fixed
73 |
74 | - Nothing.
75 |
76 | ## 2.5.2 - 2016-03-01
77 |
78 | ### Added
79 |
80 | - Nothing.
81 |
82 | ### Deprecated
83 |
84 | - Nothing.
85 |
86 | ### Removed
87 |
88 | - Nothing.
89 |
90 | ### Fixed
91 |
92 | - [#10](https://github.com/zendframework/zend-progressbar/pull/10) updates
93 | dependencies to allow forward-compatibility with zend-stdlib 3.
94 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | - Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its
15 | contributors may be used to endorse or promote products derived from this
16 | software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zend-progressbar
2 |
3 | > ## Repository abandoned 2019-12-31
4 | >
5 | > This repository has moved to [laminas/laminas-progressbar](https://github.com/laminas/laminas-progressbar).
6 |
7 | [](https://secure.travis-ci.org/zendframework/zend-progressbar)
8 | [](https://coveralls.io/github/zendframework/zend-progressbar?branch=master)
9 |
10 | zend-progressbar is a component to create and update progress bars in different
11 | environments. It consists of a single backend, which outputs the progress through
12 | one of the multiple adapters. On every update, it takes an absolute value and
13 | optionally a status message, and then calls the adapter with some precalculated
14 | values like percentage and estimated time left.
15 |
16 | ## Installation
17 |
18 | Run the following to install this library:
19 |
20 | ```bash
21 | $ composer require zendframework/zend-progressbar
22 | ```
23 |
24 | ## Documentation
25 |
26 | Browse the documentation online at https://docs.zendframework.com/zend-progressbar/
27 |
28 | ## Support
29 |
30 | * [Issues](https://github.com/zendframework/zend-progressbar/issues/)
31 | * [Chat](https://zendframework-slack.herokuapp.com/)
32 | * [Forum](https://discourse.zendframework.com/)
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zendframework/zend-progressbar",
3 | "description": "Create and update progress bars in different environments",
4 | "license": "BSD-3-Clause",
5 | "keywords": [
6 | "zf",
7 | "zendframework",
8 | "progressbar"
9 | ],
10 | "support": {
11 | "docs": "https://docs.zendframework.com/zend-progressbar/",
12 | "issues": "https://github.com/zendframework/zend-progressbar/issues",
13 | "source": "https://github.com/zendframework/zend-progressbar",
14 | "rss": "https://github.com/zendframework/zend-progressbar/releases.atom",
15 | "chat": "https://zendframework-slack.herokuapp.com",
16 | "forum": "https://discourse.zendframework.com/c/questions/components"
17 | },
18 | "require": {
19 | "php": "^5.6 || ^7.0",
20 | "zendframework/zend-stdlib": "^3.2.1"
21 | },
22 | "require-dev": {
23 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4",
24 | "zendframework/zend-coding-standard": "~1.0.0",
25 | "zendframework/zend-json": "^2.6.1",
26 | "zendframework/zend-session": "^2.8.5"
27 | },
28 | "suggest": {
29 | "zendframework/zend-json": "Zend\\Json component",
30 | "zendframework/zend-session": "To support progressbar persistent"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "Zend\\ProgressBar\\": "src/"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "ZendTest\\ProgressBar\\": "test/"
40 | }
41 | },
42 | "config": {
43 | "sort-packages": true
44 | },
45 | "extra": {
46 | "branch-alias": {
47 | "dev-master": "2.7.x-dev",
48 | "dev-develop": "2.8.x-dev"
49 | }
50 | },
51 | "scripts": {
52 | "check": [
53 | "@cs-check",
54 | "@test"
55 | ],
56 | "cs-check": "phpcs",
57 | "cs-fix": "phpcbf",
58 | "test": "phpunit --colors=always",
59 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Adapter/AbstractAdapter.php:
--------------------------------------------------------------------------------
1 | setOptions($options);
45 | }
46 | }
47 |
48 | /**
49 | * Set options via an array
50 | *
51 | * @param array $options
52 | * @return AbstractAdapter
53 | */
54 | public function setOptions(array $options)
55 | {
56 | foreach ($options as $key => $value) {
57 | if (in_array(strtolower($key), $this->skipOptions)) {
58 | continue;
59 | }
60 |
61 | $method = 'set' . ucfirst($key);
62 | if (method_exists($this, $method)) {
63 | $this->$method($value);
64 | }
65 | }
66 |
67 | return $this;
68 | }
69 |
70 | /**
71 | * Notify the adapter about an update
72 | *
73 | * @param float $current Current progress value
74 | * @param float $max Max progress value
75 | * @param float $percent Current percent value
76 | * @param int $timeTaken Taken time in seconds
77 | * @param int $timeRemaining Remaining time in seconds
78 | * @param string $text Status text
79 | * @return void
80 | */
81 | abstract public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text);
82 |
83 | /**
84 | * Called when the progress is explicitly finished
85 | *
86 | * @return void
87 | */
88 | abstract public function finish();
89 | }
90 |
--------------------------------------------------------------------------------
/src/Adapter/Console.php:
--------------------------------------------------------------------------------
1 | width === null) {
149 | $this->setWidth();
150 | }
151 | }
152 |
153 | /**
154 | * Close local stdout, when open
155 | */
156 | public function __destruct()
157 | {
158 | if ($this->outputStream !== null) {
159 | fclose($this->outputStream);
160 | }
161 | }
162 |
163 | /**
164 | * Set a different output-stream
165 | *
166 | * @param string $resource
167 | * @throws Exception\RuntimeException
168 | * @return \Zend\ProgressBar\Adapter\Console
169 | */
170 | public function setOutputStream($resource)
171 | {
172 | ErrorHandler::start();
173 | $stream = fopen($resource, 'w');
174 | $error = ErrorHandler::stop();
175 |
176 | if ($stream === false) {
177 | throw new Exception\RuntimeException('Unable to open stream', 0, $error);
178 | }
179 |
180 | if ($this->outputStream !== null) {
181 | fclose($this->outputStream);
182 | }
183 |
184 | $this->outputStream = $stream;
185 | }
186 |
187 | /**
188 | * Get the current output stream
189 | *
190 | * @return resource
191 | */
192 | public function getOutputStream()
193 | {
194 | if ($this->outputStream === null) {
195 | if (! defined('STDOUT')) {
196 | $this->outputStream = fopen('php://stdout', 'w');
197 | } else {
198 | return STDOUT;
199 | }
200 | }
201 |
202 | return $this->outputStream;
203 | }
204 |
205 | /**
206 | * Set the width of the progressbar
207 | *
208 | * @param int $width
209 | * @return \Zend\ProgressBar\Adapter\Console
210 | */
211 | public function setWidth($width = null)
212 | {
213 | if ($width === null || ! is_int($width)) {
214 | if (substr(PHP_OS, 0, 3) === 'WIN') {
215 | // We have to default to 79 on windows, because the windows
216 | // terminal always has a fixed width of 80 characters and the
217 | // cursor is counted to the line, else windows would line break
218 | // after every update.
219 | $this->width = 79;
220 | } else {
221 | // Set the default width of 80
222 | $this->width = 80;
223 |
224 | // Try to determine the width through stty
225 | ErrorHandler::start();
226 | if (preg_match('#\d+ (\d+)#', shell_exec('stty size'), $match) === 1) {
227 | $this->width = (int) $match[1];
228 | } elseif (preg_match('#columns = (\d+);#', shell_exec('stty'), $match) === 1) {
229 | $this->width = (int) $match[1];
230 | }
231 | ErrorHandler::stop();
232 | }
233 | } else {
234 | $this->width = (int) $width;
235 | }
236 |
237 | $this->_calculateBarWidth();
238 |
239 | return $this;
240 | }
241 |
242 | /**
243 | * Set the elements to display with the progressbar
244 | *
245 | * @param array $elements
246 | * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid element is found
247 | * in the array
248 | * @return \Zend\ProgressBar\Adapter\Console
249 | */
250 | public function setElements(array $elements)
251 | {
252 | $allowedElements = [self::ELEMENT_PERCENT,
253 | self::ELEMENT_BAR,
254 | self::ELEMENT_ETA,
255 | self::ELEMENT_TEXT];
256 |
257 | if (count(array_diff($elements, $allowedElements)) > 0) {
258 | throw new Exception\InvalidArgumentException('Invalid element found in $elements array');
259 | }
260 |
261 | $this->elements = $elements;
262 |
263 | $this->_calculateBarWidth();
264 |
265 | return $this;
266 | }
267 |
268 | /**
269 | * Set the left-hand character for the bar
270 | *
271 | * @param string $char
272 | * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
273 | * @return \Zend\ProgressBar\Adapter\Console
274 | */
275 | public function setBarLeftChar($char)
276 | {
277 | if (empty($char)) {
278 | throw new Exception\InvalidArgumentException('Character may not be empty');
279 | }
280 |
281 | $this->barLeftChar = (string) $char;
282 |
283 | return $this;
284 | }
285 |
286 | /**
287 | * Set the right-hand character for the bar
288 | *
289 | * @param string $char
290 | * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
291 | * @return \Zend\ProgressBar\Adapter\Console
292 | */
293 | public function setBarRightChar($char)
294 | {
295 | if (empty($char)) {
296 | throw new Exception\InvalidArgumentException('Character may not be empty');
297 | }
298 |
299 | $this->barRightChar = (string) $char;
300 |
301 | return $this;
302 | }
303 |
304 | /**
305 | * Set the indicator character for the bar
306 | *
307 | * @param string $char
308 | * @return \Zend\ProgressBar\Adapter\Console
309 | */
310 | public function setBarIndicatorChar($char)
311 | {
312 | $this->barIndicatorChar = (string) $char;
313 |
314 | return $this;
315 | }
316 |
317 | /**
318 | * Set the width of the text element
319 | *
320 | * @param int $width
321 | * @return \Zend\ProgressBar\Adapter\Console
322 | */
323 | public function setTextWidth($width)
324 | {
325 | $this->textWidth = (int) $width;
326 |
327 | $this->_calculateBarWidth();
328 |
329 | return $this;
330 | }
331 |
332 | /**
333 | * Set the charset of the text element
334 | *
335 | * @param string $charset
336 | */
337 | public function setCharset($charset)
338 | {
339 | $this->charset = $charset;
340 | }
341 |
342 | /**
343 | * Set the finish action
344 | *
345 | * @param string $action
346 | * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid action is specified
347 | * @return \Zend\ProgressBar\Adapter\Console
348 | */
349 | public function setFinishAction($action)
350 | {
351 | $allowedActions = [self::FINISH_ACTION_CLEAR_LINE,
352 | self::FINISH_ACTION_EOL,
353 | self::FINISH_ACTION_NONE];
354 |
355 | if (! in_array($action, $allowedActions)) {
356 | throw new Exception\InvalidArgumentException('Invalid finish action specified');
357 | }
358 |
359 | $this->finishAction = $action;
360 |
361 | return $this;
362 | }
363 |
364 | /**
365 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
366 | *
367 | * @param float $current Current progress value
368 | * @param float $max Max progress value
369 | * @param float $percent Current percent value
370 | * @param int $timeTaken Taken time in seconds
371 | * @param int $timeRemaining Remaining time in seconds
372 | * @param string $text Status text
373 | * @return void
374 | */
375 | public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
376 | {
377 | // See if we must clear the line
378 | if ($this->outputStarted) {
379 | $data = str_repeat("\x08", $this->width);
380 | } else {
381 | $data = '';
382 | $this->outputStarted = true;
383 | }
384 |
385 | // Build all elements
386 | $renderedElements = [];
387 |
388 | foreach ($this->elements as $element) {
389 | switch ($element) {
390 | case self::ELEMENT_BAR:
391 | $visualWidth = $this->barWidth - 2;
392 | $bar = '[';
393 |
394 | $indicatorWidth = strlen($this->barIndicatorChar);
395 |
396 | $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent));
397 | if ($doneWidth > 0) {
398 | $bar .= substr(
399 | str_repeat($this->barLeftChar, ceil($doneWidth / strlen($this->barLeftChar))),
400 | 0,
401 | $doneWidth
402 | );
403 | }
404 |
405 | $bar .= $this->barIndicatorChar;
406 |
407 | $leftWidth = $visualWidth - $doneWidth - $indicatorWidth;
408 | if ($leftWidth > 0) {
409 | $bar .= substr(
410 | str_repeat($this->barRightChar, ceil($leftWidth / strlen($this->barRightChar))),
411 | 0,
412 | $leftWidth
413 | );
414 | }
415 |
416 | $bar .= ']';
417 |
418 | $renderedElements[] = $bar;
419 | break;
420 |
421 | case self::ELEMENT_PERCENT:
422 | $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT) . '%';
423 | break;
424 |
425 | case self::ELEMENT_ETA:
426 | // In the first 5 seconds we don't get accurate results,
427 | // this skipping technique is found in many progressbar
428 | // implementations.
429 | if ($timeTaken < 5) {
430 | $renderedElements[] = str_repeat(' ', 12);
431 | break;
432 | }
433 |
434 | if ($timeRemaining === null || $timeRemaining > 86400) {
435 | $etaFormatted = '??:??:??';
436 | } else {
437 | $hours = floor($timeRemaining / 3600);
438 | $minutes = floor(($timeRemaining % 3600) / 60);
439 | $seconds = ($timeRemaining % 3600 % 60);
440 |
441 | $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
442 | }
443 |
444 | $renderedElements[] = 'ETA ' . $etaFormatted;
445 | break;
446 |
447 | case self::ELEMENT_TEXT:
448 | $wrapper = StringUtils::getWrapper($this->charset);
449 | $renderedElements[] = $wrapper->strPad(
450 | $wrapper->substr($text, 0, $this->textWidth),
451 | $this->textWidth,
452 | ' ',
453 | STR_PAD_RIGHT
454 | );
455 | break;
456 | }
457 | }
458 |
459 | $data .= implode(' ', $renderedElements);
460 |
461 | // Output line data
462 | $this->_outputData($data);
463 | }
464 |
465 | /**
466 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
467 | *
468 | * @return void
469 | */
470 | public function finish()
471 | {
472 | switch ($this->finishAction) {
473 | case self::FINISH_ACTION_EOL:
474 | $this->_outputData(PHP_EOL);
475 | break;
476 |
477 | case self::FINISH_ACTION_CLEAR_LINE:
478 | if ($this->outputStarted) {
479 | $data = str_repeat("\x08", $this->width)
480 | . str_repeat(' ', $this->width)
481 | . str_repeat("\x08", $this->width);
482 |
483 | $this->_outputData($data);
484 | }
485 | break;
486 |
487 | case self::FINISH_ACTION_NONE:
488 | break;
489 | }
490 | }
491 |
492 | /**
493 | * Calculate the bar width when other elements changed
494 | *
495 | * @return void
496 | */
497 | // @codingStandardsIgnoreStart
498 | protected function _calculateBarWidth()
499 | {
500 | // @codingStandardsIgnoreEnd
501 | if (in_array(self::ELEMENT_BAR, $this->elements)) {
502 | $barWidth = $this->width;
503 |
504 | if (in_array(self::ELEMENT_PERCENT, $this->elements)) {
505 | $barWidth -= 4;
506 | }
507 |
508 | if (in_array(self::ELEMENT_ETA, $this->elements)) {
509 | $barWidth -= 12;
510 | }
511 |
512 | if (in_array(self::ELEMENT_TEXT, $this->elements)) {
513 | $barWidth -= $this->textWidth;
514 | }
515 |
516 | $this->barWidth = $barWidth - (count($this->elements) - 1);
517 | }
518 | }
519 |
520 | /**
521 | * Outputs given data to STDOUT.
522 | *
523 | * This split-off is required for unit-testing.
524 | *
525 | * @param string $data
526 | * @return void
527 | */
528 | // @codingStandardsIgnoreStart
529 | protected function _outputData($data)
530 | {
531 | // @codingStandardsIgnoreEnd
532 | fwrite($this->getOutputStream(), $data);
533 | }
534 | }
535 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | exitAfterSend = $exitAfterSend;
36 | }
37 |
38 | /**
39 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
40 | *
41 | * @param float $current Current progress value
42 | * @param float $max Max progress value
43 | * @param float $percent Current percent value
44 | * @param int $timeTaken Taken time in seconds
45 | * @param int $timeRemaining Remaining time in seconds
46 | * @param string $text Status text
47 | * @return void
48 | */
49 | public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
50 | {
51 | $arguments = [
52 | 'current' => $current,
53 | 'max' => $max,
54 | 'percent' => ($percent * 100),
55 | 'timeTaken' => $timeTaken,
56 | 'timeRemaining' => $timeRemaining,
57 | 'text' => $text,
58 | 'finished' => false
59 | ];
60 |
61 | $data = Json::encode($arguments);
62 |
63 | // Output the data
64 | $this->_outputData($data);
65 | }
66 |
67 | /**
68 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
69 | *
70 | * @return void
71 | */
72 | public function finish()
73 | {
74 | $data = Json::encode(['finished' => true]);
75 |
76 | $this->_outputData($data);
77 | }
78 |
79 | /**
80 | * Outputs given data the user agent.
81 | *
82 | * This split-off is required for unit-testing.
83 | *
84 | * @param string $data
85 | * @return void
86 | */
87 | // @codingStandardsIgnoreStart
88 | protected function _outputData($data)
89 | {
90 | // @codingStandardsIgnoreEnd
91 | echo $data;
92 |
93 | if ($this->exitAfterSend) {
94 | exit;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Adapter/JsPush.php:
--------------------------------------------------------------------------------
1 | updateMethodName = $methodName;
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * Set the finish method name
49 | *
50 | * @param string $methodName
51 | * @return \Zend\ProgressBar\Adapter\JsPush
52 | */
53 | public function setFinishMethodName($methodName)
54 | {
55 | $this->finishMethodName = $methodName;
56 |
57 | return $this;
58 | }
59 |
60 | /**
61 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
62 | *
63 | * @param float $current Current progress value
64 | * @param float $max Max progress value
65 | * @param float $percent Current percent value
66 | * @param int $timeTaken Taken time in seconds
67 | * @param int $timeRemaining Remaining time in seconds
68 | * @param string $text Status text
69 | * @return void
70 | */
71 | public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
72 | {
73 | $arguments = [
74 | 'current' => $current,
75 | 'max' => $max,
76 | 'percent' => ($percent * 100),
77 | 'timeTaken' => $timeTaken,
78 | 'timeRemaining' => $timeRemaining,
79 | 'text' => $text
80 | ];
81 |
82 | $data = '';
85 |
86 | // Output the data
87 | $this->_outputData($data);
88 | }
89 |
90 | /**
91 | * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
92 | *
93 | * @return void
94 | */
95 | public function finish()
96 | {
97 | if ($this->finishMethodName === null) {
98 | return;
99 | }
100 |
101 | $data = '';
104 |
105 | $this->_outputData($data);
106 | }
107 |
108 | /**
109 | * Outputs given data the user agent.
110 | *
111 | * This split-off is required for unit-testing.
112 | *
113 | * @param string $data
114 | * @return void
115 | */
116 | // @codingStandardsIgnoreStart
117 | protected function _outputData($data)
118 | {
119 | // @codingStandardsIgnoreEnd
120 | // 1024 padding is required for Safari, while 256 padding is required
121 | // for Internet Explorer. The
is required so Safari actually
122 | // executes the
123 | echo str_pad($data . '
', 1024, ' ', STR_PAD_RIGHT) . "\n";
124 |
125 | flush();
126 | ob_flush();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | $max) {
81 | throw new Exception\OutOfRangeException('$max must be greater than $min');
82 | }
83 |
84 | $this->min = (float) $min;
85 | $this->max = (float) $max;
86 | $this->current = (float) $min;
87 |
88 | // See if we have to open a session namespace
89 | if ($persistenceNamespace !== null) {
90 | $this->persistenceNamespace = new Session\Container($persistenceNamespace);
91 | }
92 |
93 | // Set adapter
94 | $this->adapter = $adapter;
95 |
96 | // Track the start time
97 | $this->startTime = time();
98 |
99 | // See If a persistenceNamespace exists and handle accordingly
100 | if ($this->persistenceNamespace !== null) {
101 | if (isset($this->persistenceNamespace->isSet)) {
102 | $this->startTime = $this->persistenceNamespace->startTime;
103 | $this->current = $this->persistenceNamespace->current;
104 | $this->statusText = $this->persistenceNamespace->statusText;
105 | } else {
106 | $this->persistenceNamespace->isSet = true;
107 | $this->persistenceNamespace->startTime = $this->startTime;
108 | $this->persistenceNamespace->current = $this->current;
109 | $this->persistenceNamespace->statusText = $this->statusText;
110 | }
111 | } else {
112 | $this->update();
113 | }
114 | }
115 |
116 | /**
117 | * Get the current adapter
118 | *
119 | * @return Adapter\AbstractAdapter
120 | */
121 | public function getAdapter()
122 | {
123 | return $this->adapter;
124 | }
125 |
126 | /**
127 | * Update the progressbar
128 | *
129 | * @param float $value
130 | * @param string $text
131 | * @return void
132 | */
133 | public function update($value = null, $text = null)
134 | {
135 | // Update value if given
136 | if ($value !== null) {
137 | $this->current = min($this->max, max($this->min, $value));
138 | }
139 |
140 | // Update text if given
141 | if ($text !== null) {
142 | $this->statusText = $text;
143 | }
144 |
145 | // See if we have to update a namespace
146 | if ($this->persistenceNamespace !== null) {
147 | $this->persistenceNamespace->current = $this->current;
148 | $this->persistenceNamespace->statusText = $this->statusText;
149 | }
150 |
151 | // Calculate percent
152 | if ($this->min === $this->max) {
153 | $percent = false;
154 | } else {
155 | $percent = (float) ($this->current - $this->min) / ($this->max - $this->min);
156 | }
157 |
158 | // Calculate ETA
159 | $timeTaken = time() - $this->startTime;
160 |
161 | if ($percent === .0 || $percent === false) {
162 | $timeRemaining = null;
163 | } else {
164 | $timeRemaining = round(((1 / $percent) * $timeTaken) - $timeTaken);
165 | }
166 |
167 | // Poll the adapter
168 | $this->adapter->notify($this->current, $this->max, $percent, $timeTaken, $timeRemaining, $this->statusText);
169 | }
170 |
171 | /**
172 | * Update the progressbar to the next value
173 | *
174 | * @param int $diff
175 | * @param string $text
176 | * @return void
177 | */
178 | public function next($diff = 1, $text = null)
179 | {
180 | $this->update(max($this->min, min($this->max, $this->current + $diff)), $text);
181 | }
182 |
183 | /**
184 | * Call the adapters finish() behaviour
185 | *
186 | * @return void
187 | */
188 | public function finish()
189 | {
190 | if ($this->persistenceNamespace !== null) {
191 | unset($this->persistenceNamespace->isSet);
192 | }
193 |
194 | $this->adapter->finish();
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/Upload/AbstractUploadHandler.php:
--------------------------------------------------------------------------------
1 | setOptions($options);
41 | }
42 | }
43 |
44 | /**
45 | * Set options for a upload handler. Accepted options are:
46 | * - session_namespace: session namespace for upload progress
47 | * - progress_adapter: progressbar adapter to use for updating progress
48 | *
49 | * @param array|Traversable $options
50 | * @return AbstractUploadHandler
51 | * @throws Exception\InvalidArgumentException
52 | */
53 | public function setOptions($options)
54 | {
55 | if ($options instanceof Traversable) {
56 | $options = ArrayUtils::iteratorToArray($options);
57 | } elseif (! is_array($options)) {
58 | throw new Exception\InvalidArgumentException(
59 | 'The options parameter must be an array or a Traversable'
60 | );
61 | }
62 |
63 | if (isset($options['session_namespace'])) {
64 | $this->setSessionNamespace($options['session_namespace']);
65 | }
66 | if (isset($options['progress_adapter'])) {
67 | $this->setProgressAdapter($options['progress_adapter']);
68 | }
69 |
70 | return $this;
71 | }
72 |
73 | /**
74 | * @param string $sessionNamespace
75 | * @return AbstractUploadHandler|UploadHandlerInterface
76 | */
77 | public function setSessionNamespace($sessionNamespace)
78 | {
79 | $this->sessionNamespace = $sessionNamespace;
80 | return $this;
81 | }
82 |
83 | /**
84 | * @return string
85 | */
86 | public function getSessionNamespace()
87 | {
88 | return $this->sessionNamespace;
89 | }
90 |
91 | /**
92 | * @param AbstractProgressAdapter|ProgressBar $progressAdapter
93 | * @return AbstractUploadHandler|UploadHandlerInterface
94 | */
95 | public function setProgressAdapter($progressAdapter)
96 | {
97 | $this->progressAdapter = $progressAdapter;
98 | return $this;
99 | }
100 |
101 | /**
102 | * @return AbstractProgressAdapter|ProgressBar
103 | */
104 | public function getProgressAdapter()
105 | {
106 | return $this->progressAdapter;
107 | }
108 |
109 | /**
110 | * @param string $id
111 | * @return array
112 | */
113 | public function getProgress($id)
114 | {
115 | $status = [
116 | 'total' => 0,
117 | 'current' => 0,
118 | 'rate' => 0,
119 | 'message' => 'No upload in progress',
120 | 'done' => true
121 | ];
122 | if (empty($id)) {
123 | return $status;
124 | }
125 |
126 | $newStatus = $this->getUploadProgress($id);
127 | if (false === $newStatus) {
128 | return $status;
129 | }
130 | $status = $newStatus;
131 | if ('' === $status['message']) {
132 | $status['message'] = $this->toByteString($status['current']) .
133 | " - " . $this->toByteString($status['total']);
134 | }
135 | $status['id'] = $id;
136 |
137 | $adapter = $this->getProgressAdapter();
138 | if (isset($adapter)) {
139 | if ($adapter instanceof AbstractProgressAdapter) {
140 | $adapter = new ProgressBar($adapter, 0, $status['total'], $this->getSessionNamespace());
141 | $this->setProgressAdapter($adapter);
142 | }
143 |
144 | if (! $adapter instanceof ProgressBar) {
145 | throw new Exception\RuntimeException('Unknown Adapter type given');
146 | }
147 |
148 | if ($status['done']) {
149 | $adapter->finish();
150 | } else {
151 | $adapter->update($status['current'], $status['message']);
152 | }
153 | }
154 |
155 | return $status;
156 | }
157 |
158 | /**
159 | * @param string $id
160 | * @return array|bool
161 | */
162 | abstract protected function getUploadProgress($id);
163 |
164 | /**
165 | * Returns the formatted size
166 | *
167 | * @param int $size
168 | * @return string
169 | */
170 | protected function toByteString($size)
171 | {
172 | $sizes = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
173 | for ($i = 0; $size >= 1024 && $i < 9; $i++) {
174 | $size /= 1024;
175 | }
176 |
177 | return round($size, 2) . $sizes[$i];
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Upload/ApcProgress.php:
--------------------------------------------------------------------------------
1 | isApcAvailable()) {
27 | throw new Exception\PhpEnvironmentException('APC extension is not installed');
28 | }
29 |
30 | $uploadInfo = apc_fetch(ini_get('apc.rfc1867_prefix') . $id);
31 | if (! is_array($uploadInfo)) {
32 | return false;
33 | }
34 |
35 | $status = [
36 | 'total' => 0,
37 | 'current' => 0,
38 | 'rate' => 0,
39 | 'message' => '',
40 | 'done' => false
41 | ];
42 | $status = $uploadInfo + $status;
43 | if (! empty($status['cancel_upload'])) {
44 | $status['done'] = true;
45 | $status['message'] = 'The upload has been canceled';
46 | }
47 |
48 | return $status;
49 | }
50 |
51 | /**
52 | * Checks for the APC extension
53 | *
54 | * @return bool
55 | */
56 | public function isApcAvailable()
57 | {
58 | return (bool) ini_get('apc.enabled')
59 | && (bool) ini_get('apc.rfc1867')
60 | && is_callable('apc_fetch');
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Upload/SessionProgress.php:
--------------------------------------------------------------------------------
1 | isSessionUploadProgressAvailable()) {
27 | throw new Exception\PhpEnvironmentException(
28 | 'Session Upload Progress is not available'
29 | );
30 | }
31 |
32 | $sessionKey = ini_get('session.upload_progress.prefix') . $id;
33 | $uploadInfo = (isset($_SESSION[$sessionKey])) ? $_SESSION[$sessionKey] : null;
34 | if (! is_array($uploadInfo)) {
35 | return false;
36 | }
37 |
38 | $status = [
39 | 'total' => 0,
40 | 'current' => 0,
41 | 'rate' => 0,
42 | 'message' => '',
43 | 'done' => false,
44 | ];
45 | $status = $uploadInfo + $status;
46 | $status['total'] = $status['content_length'];
47 | $status['current'] = $status['bytes_processed'];
48 |
49 | $time = time() - $status['start_time'];
50 | $status['rate'] = ($time > 0) ? $status['bytes_processed'] / $time : 0;
51 |
52 | if (! empty($status['cancel_upload'])) {
53 | $status['done'] = true;
54 | $status['message'] = 'The upload has been canceled';
55 | }
56 |
57 | return $status;
58 | }
59 |
60 | /**
61 | * Checks if Session Upload Progress is available
62 | *
63 | * @return bool
64 | */
65 | public function isSessionUploadProgressAvailable()
66 | {
67 | return (bool) ini_get('session.upload_progress.enabled');
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Upload/UploadHandlerInterface.php:
--------------------------------------------------------------------------------
1 | isUploadProgressAvailable()) {
27 | throw new Exception\PhpEnvironmentException(
28 | 'UploadProgress extension is not installed'
29 | );
30 | }
31 |
32 | $uploadInfo = uploadprogress_get_info($id);
33 | if (! is_array($uploadInfo)) {
34 | return false;
35 | }
36 |
37 | $status = [
38 | 'total' => 0,
39 | 'current' => 0,
40 | 'rate' => 0,
41 | 'message' => '',
42 | 'done' => false
43 | ];
44 | $status = $uploadInfo + $status;
45 | $status['total'] = $status['bytes_total'];
46 | $status['current'] = $status['bytes_uploaded'];
47 | $status['rate'] = $status['speed_average'];
48 | if ($status['total'] == $status['current']) {
49 | $status['done'] = true;
50 | }
51 |
52 | return $status;
53 | }
54 |
55 | /**
56 | * Checks for the UploadProgress extension
57 | *
58 | * @return bool
59 | */
60 | public function isUploadProgressAvailable()
61 | {
62 | return is_callable('uploadprogress_get_info');
63 | }
64 | }
65 |
--------------------------------------------------------------------------------