diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index cd666ae..0778a21 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - master + - 1.x push: branches: - master + - 1.x env: SYMFONY_PHPUNIT_VERSION: 7.5 @@ -14,7 +16,7 @@ env: jobs: coding-standards: name: "CS Fixer & PHPStan" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" strategy: matrix: @@ -23,7 +25,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Install PHP" uses: "shivammathur/setup-php@v2" @@ -39,7 +41,7 @@ jobs: run: "composer require phpstan/phpstan phpstan/phpstan-phpunit --dev --no-progress --no-suggest" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v3" with: composer-options: "--optimize-autoloader --prefer-dist" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1424248..15e0217 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - "master" + - "1.x" push: branches: - "master" + - "1.x" env: fail-fast: true @@ -14,7 +16,7 @@ env: jobs: phpunit: name: "PHPUnit (PHP ${{ matrix.php }})" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" strategy: matrix: @@ -26,10 +28,14 @@ jobs: - "7.3" - "7.4" - "8.0" + - "8.1" + - "8.2" + - "8.3" + - "8.4" steps: - name: "Checkout" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" with: fetch-depth: 2 @@ -38,23 +44,17 @@ jobs: with: php-version: "${{ matrix.php }}" coverage: "none" - tools: composer:v1 + tools: composer:v2 extensions: tidy ini-values: "date.timezone=Europe/Paris" env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: "Force PHPUnit version" - if: matrix.php >= '7.2' - run: "echo $SYMFONY_PHPUNIT_VERSION" - env: - SYMFONY_PHPUNIT_VERSION: 7.5 - - name: "Remove useless deps" run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v3" with: composer-options: "--optimize-autoloader --prefer-dist" @@ -66,7 +66,7 @@ jobs: phpunit-coverage: name: "PHPUnit coverage (PHP ${{ matrix.php }})" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" strategy: matrix: @@ -75,7 +75,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" with: fetch-depth: 2 @@ -84,7 +84,7 @@ jobs: with: php-version: "${{ matrix.php }}" coverage: "xdebug" - tools: composer:v1 + tools: composer:v2 extensions: tidy ini-values: "date.timezone=Europe/Paris" env: @@ -94,7 +94,7 @@ jobs: run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v3" with: composer-options: "--optimize-autoloader --prefer-dist" @@ -103,8 +103,6 @@ jobs: - name: "Run PHPUnit (with coverage)" run: "php vendor/bin/simple-phpunit -v --coverage-clover build/logs/clover.xml" - env: - SYMFONY_PHPUNIT_VERSION: 7.5 - name: "Retrieve Coveralls phar" run: "wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.2/php-coveralls.phar" @@ -119,7 +117,7 @@ jobs: phpunit-lowest: name: "PHPUnit lowest deps (PHP ${{ matrix.php }})" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" strategy: matrix: @@ -128,7 +126,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" with: fetch-depth: 2 @@ -137,7 +135,7 @@ jobs: with: php-version: "${{ matrix.php }}" coverage: "none" - tools: composer:v1 + tools: composer:v2 extensions: tidy ini-values: "date.timezone=Europe/Paris" env: @@ -147,7 +145,7 @@ jobs: run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" + uses: "ramsey/composer-install@v3" with: composer-options: "--optimize-autoloader --prefer-dist" dependency-versions: "lowest" @@ -157,11 +155,9 @@ jobs: - name: "Run PHPUnit" run: "php vendor/bin/simple-phpunit -v" - env: - SYMFONY_PHPUNIT_VERSION: 7.5 phpunit-composerv2: - name: "PHPUnit with Composer v2 (PHP ${{ matrix.php }})" + name: "PHPUnit with Composer v1 (PHP ${{ matrix.php }})" runs-on: "ubuntu-20.04" strategy: @@ -180,7 +176,7 @@ jobs: with: php-version: "${{ matrix.php }}" coverage: "none" - tools: composer:v2 + tools: composer:v1 extensions: tidy ini-values: "date.timezone=Europe/Paris" env: @@ -199,5 +195,3 @@ jobs: - name: "Run PHPUnit" run: "php vendor/bin/simple-phpunit -v" - env: - SYMFONY_PHPUNIT_VERSION: 7.5 diff --git a/.gitignore b/.gitignore index 160e7cc..6a7bae5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ coverage/ composer.lock .php_cs.cache .phpunit.result.cache +phpstan.neon diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 19cd18d..93f51fe 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -26,6 +26,10 @@ return (new PhpCsFixer\Config()) 'strict_comparison' => true, 'strict_param' => true, 'concat_space' => ['spacing' => 'one'], + // Pulled in by @Symfony, we cannot add property types until we bump PHP to ≥ 7.4 + 'no_null_property_initialization' => false, + // Pulled in by @Symfony with `const` but const visibility requires PHP ≥ 7.1 + 'visibility_required' => ['elements' => ['method', 'property']], ]) ->setFinder($finder) ; diff --git a/composer.json b/composer.json index d32d576..b3116b7 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", "monolog/monolog": "^1.24|^2.1", - "symfony/phpunit-bridge": "^4.4|^5.3" + "symfony/phpunit-bridge": "^4.4|^5.3|^6.0|^7.0" }, "suggest": { "ext-tidy": "Used to clean up given HTML and to avoid problems with bad HTML structure." @@ -42,5 +42,10 @@ }, "autoload-dev": { "psr-4": { "Tests\\Readability\\": "tests/" } + }, + "scripts": { + "fix": "php-cs-fixer fix --verbose --diff", + "phpstan": "phpstan analyze --memory-limit 512M", + "test": "simple-phpunit -v" } } diff --git a/phpstan.neon b/phpstan.dist.neon similarity index 81% rename from phpstan.neon rename to phpstan.dist.neon index 7c1f51c..7e5d5d8 100644 --- a/phpstan.neon +++ b/phpstan.dist.neon @@ -6,7 +6,7 @@ parameters: # https://github.com/phpstan/phpstan/issues/694#issuecomment-350724288 bootstrapFiles: - - vendor/bin/.phpunit/phpunit-7.5-0/vendor/autoload.php + - vendor/bin/.phpunit/phpunit/vendor/autoload.php includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/JSLikeHTMLElement.php b/src/JSLikeHTMLElement.php index 3f382e1..18116a6 100644 --- a/src/JSLikeHTMLElement.php +++ b/src/JSLikeHTMLElement.php @@ -39,9 +39,9 @@ class JSLikeHTMLElement extends \DOMElement /** * Used for setting innerHTML like it's done in JavaScript:. * - * @code + * ```php * $div->innerHTML = '

Chapter 2

The story begins...

'; - * @endcode + * ``` */ public function __set($name, $value) { @@ -79,14 +79,13 @@ class JSLikeHTMLElement extends \DOMElement } else { // $value is probably ill-formed $f = new \DOMDocument(); - $value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8'); // Using will generate a warning, but so will bad HTML // (and by this point, bad HTML is what we've got). // We use it (and suppress the warning) because an HTML fragment will // be wrapped around tags which we don't really want to keep. // Note: despite the warning, if loadHTML succeeds it will return true. - $result = $f->loadHTML('' . $value . ''); + $result = $f->loadHTML('' . $value . ''); if ($result) { $import = $f->getElementsByTagName('htmlfragment')->item(0); @@ -105,9 +104,9 @@ class JSLikeHTMLElement extends \DOMElement /** * Used for getting innerHTML like it's done in JavaScript:. * - * @code + * ```php * $string = $div->innerHTML; - * @endcode + * ``` */ public function __get($name) { diff --git a/src/Readability.php b/src/Readability.php index 8c1e62b..1d2d2f5 100644 --- a/src/Readability.php +++ b/src/Readability.php @@ -144,7 +144,7 @@ class Readability implements LoggerAwareInterface // HACK: replace linebreaks plus br's with p's '!(]*>[ \r\n\s]*){2,}!i' => '

', // replace noscripts - //'!!is' => '', + // '!!is' => '', // replace fonts to spans '!<(/?)font[^>]*>!is' => '<\\1span>', ]; @@ -155,8 +155,8 @@ class Readability implements LoggerAwareInterface // replace empty tags that break layouts '!<(?:a|div|p|figure)[^>]+/>!is' => '', // remove all attributes on text tags - //'!<(\s*/?\s*(?:blockquote|br|hr|code|div|article|span|footer|aside|p|pre|dl|li|ul|ol)) [^>]+>!is' => "<\\1>", - //single newlines cleanup + // '!<(\s*/?\s*(?:blockquote|br|hr|code|div|article|span|footer|aside|p|pre|dl|li|ul|ol)) [^>]+>!is' => "<\\1>", + // single newlines cleanup "/\n+/" => "\n", // modern web... '!]*>\s* 'setAttribute('style', 'color: inherit; text-decoration: none;'); $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount); $footnote->setInnerHtml('^ '); - $footnoteLink->setInnerHtml(('' !== $footnoteLink->getAttribute('title') ? $footnoteLink->getAttribute('title') : $linkText)); + $footnoteLink->setInnerHtml('' !== $footnoteLink->getAttribute('title') ? $footnoteLink->getAttribute('title') : $linkText); $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount); $footnote->appendChild($footnoteLink); @@ -796,7 +796,7 @@ class Readability implements LoggerAwareInterface */ public function addFlag($flag) { - $this->flags = $this->flags | $flag; + $this->flags |= $flag; } /** @@ -806,13 +806,14 @@ class Readability implements LoggerAwareInterface */ public function removeFlag($flag) { - $this->flags = $this->flags & ~$flag; + $this->flags &= ~$flag; } /** * Debug. * * @deprecated use $this->logger->debug() instead + * * @codeCoverageIgnore */ protected function dbg($msg) @@ -824,6 +825,7 @@ class Readability implements LoggerAwareInterface * Dump debug info. * * @deprecated since Monolog gather log, we don't need it + * * @codeCoverageIgnore */ protected function dump_dbg() @@ -973,11 +975,11 @@ class Readability implements LoggerAwareInterface * Using a variety of metrics (content score, classname, element types), find the content that is * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. * - * @param \DOMElement $page + * @param ?\DOMElement $page * * @return \DOMElement|false */ - protected function grabArticle(\DOMElement $page = null) + protected function grabArticle($page = null) { if (!$page) { $page = $this->dom; @@ -992,7 +994,7 @@ class Readability implements LoggerAwareInterface $allElements = $page->getElementsByTagName('*'); - for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); ++$nodeIndex) { + for ($nodeIndex = 0; $node = $allElements->item($nodeIndex); ++$nodeIndex) { $tagName = $node->tagName; $nodeContent = $node->getInnerHTML(); @@ -1136,9 +1138,9 @@ class Readability implements LoggerAwareInterface // Remove unlikely candidates $unlikelyMatchString = $node->getAttribute('class') . ' ' . $node->getAttribute('id') . ' ' . $node->getAttribute('style'); - if (mb_strlen($unlikelyMatchString) > 3 && // don't process "empty" strings - preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) && - !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) + if (mb_strlen($unlikelyMatchString) > 3 // don't process "empty" strings + && preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) + && !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) ) { $this->logger->debug('Removing unlikely candidate (using conf) ' . $node->getNodePath() . ' by "' . $unlikelyMatchString . '" with readability ' . ($node->hasAttribute('readability') ? (int) $node->getAttributeNode('readability')->value : 0)); $node->parentNode->removeChild($node); @@ -1289,8 +1291,8 @@ class Readability implements LoggerAwareInterface // To ensure a node does not interfere with readability styles, remove its classnames & ids. // Now done via RegExp post_filter. - //$nodeToAppend->removeAttribute('class'); - //$nodeToAppend->removeAttribute('id'); + // $nodeToAppend->removeAttribute('class'); + // $nodeToAppend->removeAttribute('id'); // Append sibling and subtract from our list as appending removes a node. $articleContent->appendChild($nodeToAppend); } @@ -1430,7 +1432,7 @@ class Readability implements LoggerAwareInterface unset($tidy); } - $this->html = mb_convert_encoding($this->html, 'HTML-ENTITIES', 'UTF-8'); + $this->html = '' . (string) $this->html; if ('html5lib' === $this->parser || 'html5' === $this->parser) { $this->dom = (new HTML5())->loadHTML($this->html); diff --git a/tests/ReadabilityTest.php b/tests/ReadabilityTest.php index a8e8cfb..b190a8e 100644 --- a/tests/ReadabilityTest.php +++ b/tests/ReadabilityTest.php @@ -325,35 +325,38 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase } // dummy function to be used to the next test - public function error2Exception($code, $string, $file, $line, $context) + public function error2Exception($code, $string, $file, $line) { throw new \Exception($string, $code); } public function testAutoClosingIframeNotThrowingException() { - error_reporting(\E_ALL | \E_STRICT); - ini_set('display_errors', true); - set_error_handler([$this, 'error2Exception'], \E_ALL | \E_STRICT); - - $data = ' - - - - - - - - -

-
-
-

3D Touch — будущее мобильных игр

- -
-

Компания Apple представила новую технологию 3D Touch, которая является прямым потомком более ранней версии Force Touch — последняя, напомним, используется сейчас в трекпадах Macbook Pro и Macbook 2015. Теперь управлять устройством стало в разы проще, и Force Touch открывает перед пользователями новые возможности, но при этом 3D Touch — это про другое. Дело в том, что теперь и на мобильных устройствах интерфейс будет постепенно меняться, кардинальные перемены ждут мобильный гейминг, потому что здесь разработчики действительно могут разгуляться.

-

-

Итак, просто представьте себе, что iPhone 6S — это, по большому счету, отличная игровая приставка, которую вы носите с собой, а еще она может выдавать невероятной красоты картинку. Но проблема заключается, пожалуй, в том, что управлять персонажем в играх довольно трудно — он неповоротлив, обладает заторможенной реакцией, а игровой клиент зачастую требует перегруза интерфейса для того, чтобы обеспечить максимально большое количество возможностей. Благодаря трехуровневому нажатию можно избавиться от лишних кнопок и обеспечить более качественный обзор местности, и при этом пользователь будет закрывать пальцами минимальное пространство.

+ $oldErrorReporting = error_reporting(\E_ALL); + $oldDisplayErrors = ini_set('display_errors', true); + set_error_handler([$this, 'error2Exception']); + + try { + $data = ' + + + + + + + + +
+
+
+

3D Touch — будущее мобильных игр

+ +
+

Компания Apple представила новую технологию 3D Touch, которая является прямым потомком более ранней версии Force Touch — последняя, напомним, используется сейчас в трекпадах Macbook Pro и Macbook 2015. Теперь управлять устройством стало в разы проще, и Force Touch открывает перед пользователями новые возможности, но при этом 3D Touch — это про другое. Дело в том, что теперь и на мобильных устройствах интерфейс будет постепенно меняться, кардинальные перемены ждут мобильный гейминг, потому что здесь разработчики действительно могут разгуляться.

+

+

Итак, просто представьте себе, что iPhone 6S — это, по большому счету, отличная игровая приставка, которую вы носите с собой, а еще она может выдавать невероятной красоты картинку. Но проблема заключается, пожалуй, в том, что управлять персонажем в играх довольно трудно — он неповоротлив, обладает заторможенной реакцией, а игровой клиент зачастую требует перегруза интерфейса для того, чтобы обеспечить максимально большое количество возможностей. Благодаря трехуровневому нажатию можно избавиться от лишних кнопок и обеспечить более качественный обзор местности, и при этом пользователь будет закрывать пальцами минимальное пространство.

+
+
@@ -361,16 +364,23 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase '; - $readability = $this->getReadability($data, 'http://iosgames.ru/?p=22030'); - $readability->debug = true; - - $res = $readability->init(); - - $this->assertTrue($res); - $this->assertInstanceOf('Readability\JSLikeHTMLElement', $readability->getContent()); - $this->assertInstanceOf('Readability\JSLikeHTMLElement', $readability->getTitle()); - $this->assertStringContainsString('', $readability->getContent()->getInnerHtml()); - $this->assertStringContainsString('3D Touch', $readability->getTitle()->getInnerHtml()); + $readability = $this->getReadability($data, 'http://iosgames.ru/?p=22030'); + $readability->debug = true; + + $res = $readability->init(); + + $this->assertTrue($res); + $this->assertInstanceOf('Readability\JSLikeHTMLElement', $readability->getContent()); + $this->assertInstanceOf('Readability\JSLikeHTMLElement', $readability->getTitle()); + $this->assertStringContainsString('', $readability->getContent()->getInnerHtml()); + $this->assertStringContainsString('3D Touch', $readability->getTitle()->getInnerHtml()); + } finally { + restore_error_handler(); + if (false !== $oldDisplayErrors) { + ini_set('display_errors', $oldDisplayErrors); + } + error_reporting($oldErrorReporting); + } } /**