diff --git a/.travis.yml b/.travis.yml index 3dbfb3888..bf56cf747 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 before_install: # turn off XDebug @@ -36,12 +36,9 @@ jobs: - stage: Static Analysis (informative) - install: - # Install PHPStan - - travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress - - travis_retry composer install --no-progress --prefer-dist + php: 7.4 script: - - php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 --configuration tests/phpstan.neon src + - composer run-script phpstan - stage: Code Coverage @@ -55,7 +52,6 @@ jobs: allow_failures: - stage: Static Analysis (informative) - stage: Code Coverage - - php: 7.4snapshot sudo: false diff --git a/composer.json b/composer.json index 9425fc5b4..2a1912e70 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ }, "require-dev": { "nette/tester": "~2.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.3", + "phpstan/phpstan": "^0.12" }, "suggest": { "ext-iconv": "to use Strings::webalize() and toAscii()", @@ -34,6 +35,10 @@ "classmap": ["src/"] }, "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src", + "tester": "tester tests -s" + }, "extra": { "branch-alias": { "dev-master": "3.0-dev" diff --git a/readme.md b/readme.md index 0de6f5223..b0a6e8fe5 100644 --- a/readme.md +++ b/readme.md @@ -38,4 +38,5 @@ The recommended way to install is via Composer: composer require nette/utils ``` -It requires PHP version 7.1 and supports PHP up to 7.3. +- Nette Utils 3.0 is compatible with PHP 7.1 to 7.4 +- Nette Utils 2.5 is compatible with PHP 5.6 to 7.4 diff --git a/src/Iterators/CachingIterator.php b/src/Iterators/CachingIterator.php index 2204e2354..c2393554e 100644 --- a/src/Iterators/CachingIterator.php +++ b/src/Iterators/CachingIterator.php @@ -41,11 +41,11 @@ public function __construct($iterator) do { $iterator = $iterator->getIterator(); } while ($iterator instanceof \IteratorAggregate); + assert($iterator instanceof \Iterator); + } elseif ($iterator instanceof \Iterator) { } elseif ($iterator instanceof \Traversable) { - if (!$iterator instanceof \Iterator) { - $iterator = new \IteratorIterator($iterator); - } + $iterator = new \IteratorIterator($iterator); } else { throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator))); } diff --git a/src/Utils/Html.php b/src/Utils/Html.php index 508adc9d2..0daccf026 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -16,13 +16,219 @@ /** * HTML helper. * - * - * $el = Html::el('a')->href($link)->setText('Nette'); - * $el->class = 'myclass'; - * echo $el; + * @property string|null $accept + * @property string|null $accesskey + * @property string|null $action + * @property string|null $align + * @property string|null $allow + * @property string|null $alt + * @property bool|null $async + * @property string|null $autocapitalize + * @property string|null $autocomplete + * @property bool|null $autofocus + * @property bool|null $autoplay + * @property string|null $charset + * @property bool|null $checked + * @property string|null $cite + * @property string|null $class + * @property int|null $cols + * @property int|null $colspan + * @property string|null $content + * @property bool|null $contenteditable + * @property bool|null $controls + * @property string|null $coords + * @property string|null $crossorigin + * @property string|null $data + * @property string|null $datetime + * @property string|null $decoding + * @property bool|null $default + * @property bool|null $defer + * @property string|null $dir + * @property string|null $dirname + * @property bool|null $disabled + * @property bool|null $download + * @property string|null $draggable + * @property string|null $dropzone + * @property string|null $enctype + * @property string|null $for + * @property string|null $form + * @property string|null $formaction + * @property string|null $formenctype + * @property string|null $formmethod + * @property bool|null $formnovalidate + * @property string|null $formtarget + * @property string|null $headers + * @property int|null $height + * @property bool|null $hidden + * @property float|null $high + * @property string|null $href + * @property string|null $hreflang + * @property string|null $id + * @property string|null $integrity + * @property string|null $inputmode + * @property bool|null $ismap + * @property string|null $itemprop + * @property string|null $kind + * @property string|null $label + * @property string|null $lang + * @property string|null $list + * @property bool|null $loop + * @property float|null $low + * @property float|null $max + * @property int|null $maxlength + * @property int|null $minlength + * @property string|null $media + * @property string|null $method + * @property float|null $min + * @property bool|null $multiple + * @property bool|null $muted + * @property string|null $name + * @property bool|null $novalidate + * @property bool|null $open + * @property float|null $optimum + * @property string|null $pattern + * @property string|null $ping + * @property string|null $placeholder + * @property string|null $poster + * @property string|null $preload + * @property string|null $radiogroup + * @property bool|null $readonly + * @property string|null $rel + * @property bool|null $required + * @property bool|null $reversed + * @property int|null $rows + * @property int|null $rowspan + * @property string|null $sandbox + * @property string|null $scope + * @property bool|null $selected + * @property string|null $shape + * @property int|null $size + * @property string|null $sizes + * @property string|null $slot + * @property int|null $span + * @property string|null $spellcheck + * @property string|null $src + * @property string|null $srcdoc + * @property string|null $srclang + * @property string|null $srcset + * @property int|null $start + * @property float|null $step + * @property string|null $style + * @property int|null $tabindex + * @property string|null $target + * @property string|null $title + * @property string|null $translate + * @property string|null $type + * @property string|null $usemap + * @property string|null $value + * @property int|null $width + * @property string|null $wrap * - * echo $el->startTag(), $el->endTag(); - * + * @method self accept(?string $val) + * @method self accesskey(?string $val, bool $state = null) + * @method self action(?string $val) + * @method self align(?string $val) + * @method self allow(?string $val, bool $state = null) + * @method self alt(?string $val) + * @method self async(?bool $val) + * @method self autocapitalize(?string $val) + * @method self autocomplete(?string $val) + * @method self autofocus(?bool $val) + * @method self autoplay(?bool $val) + * @method self charset(?string $val) + * @method self checked(?bool $val) + * @method self cite(?string $val) + * @method self class(?string $val, bool $state = null) + * @method self cols(?int $val) + * @method self colspan(?int $val) + * @method self content(?string $val) + * @method self contenteditable(?bool $val) + * @method self controls(?bool $val) + * @method self coords(?string $val) + * @method self crossorigin(?string $val) + * @method self datetime(?string $val) + * @method self decoding(?string $val) + * @method self default(?bool $val) + * @method self defer(?bool $val) + * @method self dir(?string $val) + * @method self dirname(?string $val) + * @method self disabled(?bool $val) + * @method self download(?bool $val) + * @method self draggable(?string $val) + * @method self dropzone(?string $val) + * @method self enctype(?string $val) + * @method self for(?string $val) + * @method self form(?string $val) + * @method self formaction(?string $val) + * @method self formenctype(?string $val) + * @method self formmethod(?string $val) + * @method self formnovalidate(?bool $val) + * @method self formtarget(?string $val) + * @method self headers(?string $val, bool $state = null) + * @method self height(?int $val) + * @method self hidden(?bool $val) + * @method self high(?float $val) + * @method self hreflang(?string $val) + * @method self id(?string $val) + * @method self integrity(?string $val) + * @method self inputmode(?string $val) + * @method self ismap(?bool $val) + * @method self itemprop(?string $val) + * @method self kind(?string $val) + * @method self label(?string $val) + * @method self lang(?string $val) + * @method self list(?string $val) + * @method self loop(?bool $val) + * @method self low(?float $val) + * @method self max(?float $val) + * @method self maxlength(?int $val) + * @method self minlength(?int $val) + * @method self media(?string $val) + * @method self method(?string $val) + * @method self min(?float $val) + * @method self multiple(?bool $val) + * @method self muted(?bool $val) + * @method self name(?string $val) + * @method self novalidate(?bool $val) + * @method self open(?bool $val) + * @method self optimum(?float $val) + * @method self pattern(?string $val) + * @method self ping(?string $val, bool $state = null) + * @method self placeholder(?string $val) + * @method self poster(?string $val) + * @method self preload(?string $val) + * @method self radiogroup(?string $val) + * @method self readonly(?bool $val) + * @method self rel(?string $val) + * @method self required(?bool $val) + * @method self reversed(?bool $val) + * @method self rows(?int $val) + * @method self rowspan(?int $val) + * @method self sandbox(?string $val, bool $state = null) + * @method self scope(?string $val) + * @method self selected(?bool $val) + * @method self shape(?string $val) + * @method self size(?int $val) + * @method self sizes(?string $val) + * @method self slot(?string $val) + * @method self span(?int $val) + * @method self spellcheck(?string $val) + * @method self src(?string $val) + * @method self srcdoc(?string $val) + * @method self srclang(?string $val) + * @method self srcset(?string $val) + * @method self start(?int $val) + * @method self step(?float $val) + * @method self style(?string $property, string $val = null) + * @method self tabindex(?int $val) + * @method self target(?string $val) + * @method self title(?string $val) + * @method self translate(?string $val) + * @method self type(?string $val) + * @method self usemap(?string $val) + * @method self value(?string $val) + * @method self width(?int $val) + * @method self wrap(?string $val) */ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString { diff --git a/src/Utils/Image.php b/src/Utils/Image.php index c9fb5e12f..bad03c391 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -62,19 +62,23 @@ * @method void flip(int $mode) * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null) * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method array getClip() * @method int interlace($interlace = null) * @method bool isTrueColor() * @method void layerEffect($effect) * @method void line($x1, $y1, $x2, $y2, $color) + * @method void openPolygon(array $points, int $num_points, int $color) * @method void paletteCopy(Image $source) * @method void paletteToTrueColor() * @method void polygon(array $points, $numPoints, $color) * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null) * @method void rectangle($x1, $y1, $x2, $y2, $col) + * @method mixed resolution(int $res_x = null, int $res_y = null) * @method Image rotate(float $angle, $backgroundColor) * @method void saveAlpha(bool $saveflag) * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) * @method void setBrush(Image $brush) + * @method void setClip(int $x1, int $y1, int $x2, int $y2) * @method void setPixel($x, $y, $color) * @method void setStyle(array $style) * @method void setThickness($thickness) @@ -262,8 +266,8 @@ public function getImageResource() /** * Resizes image. - * @param int|string $width in pixels or percent - * @param int|string $height in pixels or percent + * @param int|string|null $width in pixels or percent + * @param int|string|null $height in pixels or percent * @return static */ public function resize($width, $height, int $flags = self::FIT) @@ -293,20 +297,20 @@ public function resize($width, $height, int $flags = self::FIT) /** * Calculates dimensions of resized image. - * @param int|string $newWidth in pixels or percent - * @param int|string $newHeight in pixels or percent + * @param int|string|null $newWidth in pixels or percent + * @param int|string|null $newHeight in pixels or percent */ public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array { if (is_string($newWidth) && substr($newWidth, -1) === '%') { - $newWidth = (int) round($srcWidth / 100 * abs(substr($newWidth, 0, -1))); + $newWidth = (int) round($srcWidth / 100 * abs((int) substr($newWidth, 0, -1))); $percents = true; } else { $newWidth = (int) abs($newWidth); } if (is_string($newHeight) && substr($newHeight, -1) === '%') { - $newHeight = (int) round($srcHeight / 100 * abs(substr($newHeight, 0, -1))); + $newHeight = (int) round($srcHeight / 100 * abs((int) substr($newHeight, 0, -1))); $flags |= empty($percents) ? 0 : self::STRETCH; } else { $newHeight = (int) abs($newHeight); @@ -381,16 +385,16 @@ public function crop($left, $top, $width, $height) public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array { if (is_string($newWidth) && substr($newWidth, -1) === '%') { - $newWidth = (int) round($srcWidth / 100 * substr($newWidth, 0, -1)); + $newWidth = (int) round($srcWidth / 100 * (int) substr($newWidth, 0, -1)); } if (is_string($newHeight) && substr($newHeight, -1) === '%') { - $newHeight = (int) round($srcHeight / 100 * substr($newHeight, 0, -1)); + $newHeight = (int) round($srcHeight / 100 * (int) substr($newHeight, 0, -1)); } if (is_string($left) && substr($left, -1) === '%') { - $left = (int) round(($srcWidth - $newWidth) / 100 * substr($left, 0, -1)); + $left = (int) round(($srcWidth - $newWidth) / 100 * (int) substr($left, 0, -1)); } if (is_string($top) && substr($top, -1) === '%') { - $top = (int) round(($srcHeight - $newHeight) / 100 * substr($top, 0, -1)); + $top = (int) round(($srcHeight - $newHeight) / 100 * (int) substr($top, 0, -1)); } if ($left < 0) { $newWidth += $left; @@ -439,11 +443,11 @@ public function place(self $image, $left = 0, $top = 0, int $opacity = 100) $height = $image->getHeight(); if (is_string($left) && substr($left, -1) === '%') { - $left = (int) round(($this->getWidth() - $width) / 100 * substr($left, 0, -1)); + $left = (int) round(($this->getWidth() - $width) / 100 * (int) substr($left, 0, -1)); } if (is_string($top) && substr($top, -1) === '%') { - $top = (int) round(($this->getHeight() - $height) / 100 * substr($top, 0, -1)); + $top = (int) round(($this->getHeight() - $height) / 100 * (int) substr($top, 0, -1)); } $output = $input = $image->image; @@ -548,21 +552,21 @@ private function output(int $type, ?int $quality, string $file = null): void switch ($type) { case self::JPEG: $quality = $quality === null ? 85 : max(0, min(100, $quality)); - $success = imagejpeg($this->image, $file, $quality); + $success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception break; case self::PNG: $quality = $quality === null ? 9 : max(0, min(9, $quality)); - $success = imagepng($this->image, $file, $quality); + $success = @imagepng($this->image, $file, $quality); // @ is escalated to exception break; case self::GIF: - $success = imagegif($this->image, $file); + $success = @imagegif($this->image, $file); // @ is escalated to exception break; case self::WEBP: $quality = $quality === null ? 80 : max(0, min(100, $quality)); - $success = imagewebp($this->image, $file, $quality); + $success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception break; default: diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 234ff5ab5..74a34c1f9 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -104,8 +104,8 @@ public static function substring(string $s, int $start, int $length = null): str public static function normalize(string $s): string { // convert to compressed normal form (NFC) - if (class_exists('Normalizer', false)) { - $s = \Normalizer::normalize($s, \Normalizer::FORM_C); + if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { + $s = $n; } $s = self::normalizeNewLines($s); diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 0ce67d66a..12dc61b3f 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -1,5 +1,5 @@ parameters: - bootstrap: tests/phpstan-bootstrap.php + bootstrap: phpstan-bootstrap.php ignoreErrors: # PHPStan does not support dynamic by reference return used by Nette\Utils\Strings::pcre() @@ -7,6 +7,3 @@ parameters: # PHPStan does not support RecursiveIteratorIterator proxying unknown method calls to inner iterator - '#RecursiveIteratorIterator::getSubPathName\(\)#' - - # PHPStan does not support comparing null - - '#Nette\\Utils\\Strings::substring\(\) expects int, int\|null given#'