diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dc45f2c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: PHPUnit / PHPCS / Phan +on: + pull_request: + branches: [ "*" ] + + push: + branches: [ main ] + +jobs: + build: + strategy: + matrix: + php_version: ['8.2'] + mw: ['REL1_43', 'REL1_44', 'master'] + + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + coverage: none + extensions: ast + - uses: actions/checkout@v3 + + - name: Checkout Mediawiki + uses: actions/checkout@v3 + with: + repository: wikimedia/mediawiki + ref: ${{ matrix.mw }} + + - name: Checkout TableProgressTracking extension + uses: actions/checkout@v3 + with: + path: extensions/TableProgressTracking + + - name: Cache TableProgressTracking composer dependencies + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-v3-${{ hashFiles('extensions/TableProgressTracking/composer.lock') }} + + - name: Install TableProgresssTracking composer dependencies + working-directory: ./extensions/TableProgressTracking + run: composer install --prefer-dist --no-progress + + - name: Run PHPCS and minus-x + working-directory: ./extensions/TableProgressTracking + run: composer test + + - name: Run Phan static analysis + working-directory: ./extensions/TableProgressTracking + run: composer phan + + - name: Start MySQL + run: sudo systemctl start mysql.service + + - name: Install MediaWiki composer dependencies + run: composer update --prefer-dist --no-progress + + - name: Install & configure MediaWiki + run: | + php maintenance/install.php --dbtype mysql --dbuser root --dbpass root --pass TestPassword testwiki TestAdmin + + echo 'error_reporting( E_ALL | E_STRICT );' >> LocalSettings.php + echo 'ini_set( "display_errors", 1 );' >> LocalSettings.php + echo '$wgShowExceptionDetails = true;' >> LocalSettings.php + echo '$wgShowDBErrorBacktrace = true;' >> LocalSettings.php + echo '$wgDevelopmentWarnings = true;' >> LocalSettings.php + + echo 'wfLoadExtension( "TableProgressTracking" );' >> LocalSettings.php + + - name: Run parser tests + run: | + for file in extensions/TableProgressTracking/tests/parser/*.txt; do + echo "Running parser tests from $file" + php tests/parser/parserTests.php --file="$file" + done \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88e99d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock \ No newline at end of file diff --git a/.phpcs.xml b/.phpcs.xml new file mode 100644 index 0000000..9ed2260 --- /dev/null +++ b/.phpcs.xml @@ -0,0 +1,7 @@ + + + + . + + + \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9d6ec3f --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "require-dev": { + "mediawiki/mediawiki-codesniffer": "46.0.0", + "mediawiki/mediawiki-phan-config": "0.15.1", + "mediawiki/minus-x": "1.1.3", + "php-parallel-lint/php-console-highlighter": "1.0.0", + "php-parallel-lint/php-parallel-lint": "1.4.0" + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor --exclude node_modules", + "@phpcs", + "minus-x check ." + ], + "fix": [ + "minus-x fix .", + "phpcbf" + ], + "phan": "phan -d . --long-progress-bar", + "phpcs": "phpcs -sp --cache" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} \ No newline at end of file diff --git a/extension.json b/extension.json index e78ca32..8487100 100644 --- a/extension.json +++ b/extension.json @@ -7,7 +7,7 @@ "descriptionmsg": "tableprogresstracking-desc", "license-name": "Apache-2.0", "type": "parserhook", - "version": "1.1.1", + "version": "1.2.0", "requires": { "MediaWiki": ">= 1.43.0" }, diff --git a/includes/Hooks.php b/includes/Hooks.php index 42ce952..27c5691 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -6,7 +6,11 @@ use MediaWiki\Installer\Hook\LoadExtensionSchemaUpdatesHook; use MediaWiki\Storage\Hook\MultiContentSaveHook; -class Hooks implements ParserFirstCallInitHook, LoadExtensionSchemaUpdatesHook, MultiContentSaveHook { +class Hooks implements + ParserFirstCallInitHook, + LoadExtensionSchemaUpdatesHook, + MultiContentSaveHook +{ /** * @inheritDoc * @@ -40,5 +44,7 @@ public function onMultiContentSave( $renderedRevision, $user, $summary, $flags, $status->fatal( 'tableprogresstracking-duplicate-tables' ); return false; } + + return true; } } diff --git a/includes/ProgressTableProcessor.php b/includes/ProgressTableProcessor.php index 6dd07fd..96ee2a0 100644 --- a/includes/ProgressTableProcessor.php +++ b/includes/ProgressTableProcessor.php @@ -9,6 +9,7 @@ use MediaWiki\Html\Html; use MediaWiki\Parser\Parser; use MediaWiki\Parser\PPFrame; +use Wikimedia\AtEase\AtEase; class ProgressTableProcessor { @@ -70,13 +71,13 @@ public function __construct( string $wikitext, array $args, Parser $parser, PPFr // Only set the unique column index if it is provided in the arguments // if not, we validate later that each row passes its own data-row-id - // note we must - 1 from the value the user passsed as an argument as + // note we must - 1 from the value the user passsed as an argument as // DOMNodeList::item() is zero-based and if the user passed 1 wanting the first column // they would get the second if ( isset( $this->args['unique-column-index'] ) ) { $this->uniqueColumnIndex = intval( $this->args['unique-column-index'] ) - 1; } - + // check the table-id argument is set, if not, we can't do much herer if ( empty( $this->args['table-id'] ) ) { $this->errorMessage = 'The table-id argument is required.'; @@ -94,7 +95,7 @@ public function __construct( string $wikitext, array $args, Parser $parser, PPFr private function loadAndValidateHtml(): void { // first parse our wikitext so we can get the HTML representation if it; // we use ->recursiveTagParseFully here as we need the final HTML version of the - // table so that we can ensure if unique-column-index is used, and the content of the + // table so that we can ensure if unique-column-index is used, and the content of the // cell is a link, or any other HTML code, such as bold, then we get the right content // in the data-row-id. If we use ->recursiveTagParse(), then we end up with parser strip tags // such as and there is no easy way to get the link object from the @@ -110,16 +111,19 @@ private function loadAndValidateHtml(): void { // Suppress warnings for potentially malformed HTML from wikitext. // there must be a better way to do this?!! Can't find it at present, though?!>!?! - @$this->dom->loadHTML( + AtEase::suppressWarnings(); + $this->dom->loadHTML( mb_convert_encoding( $tableHtml, 'HTML-ENTITIES', 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); + AtEase::restoreWarnings(); $tableNode = $this->dom->getElementsByTagName( 'table' )->item( 0 ); if ( !$tableNode ) { $this->parser->getOutput()->updateCacheExpiry( 0 ); - $this->errorMessage = 'No table was provided for progress tracking. Please include a table between the tags.'; + $this->errorMessage = 'No table was provided for progress tracking.' . + 'Please include a table between the tags.'; return; } @@ -133,8 +137,9 @@ private function loadAndValidateHtml(): void { /** * Validates that the unique-column-index is within the valid range for the table - * @todo there is an error here, if someone passes wikitext to the column which has the unique-column-index, it falls - * back to the row index. + * @todo there is an error here, if someone passes wikitext to the column which has the unique-column-index, + * it causes the fallback to the row index. + * @return void */ private function validateUniqueColumnIndex(): void { if ( $this->uniqueColumnIndex < 0 ) { @@ -153,7 +158,8 @@ private function validateUniqueColumnIndex(): void { } if ( $this->uniqueColumnIndex >= $maxColumns ) { - $this->errorMessage = "unique-column-index ({$this->uniqueColumnIndex}) is out of range. Table has {$maxColumns} columns (0-" . ( $maxColumns - 1 ) . ")."; + $this->errorMessage = "unique-column-index ({$this->uniqueColumnIndex}) is out of range." . + "Table has {$maxColumns} columns (0-" . ( $maxColumns - 1 ) . ")."; return; } } @@ -168,7 +174,8 @@ private function validateDataRowIds(): bool { foreach ( $dataRows as $row ) { $rowId = $this->extractDataRowId( $row ); if ( empty( $rowId ) ) { - $this->errorMessage = 'When unique-column-index is not provided, all data rows must have a data-row-id attribute.'; + $this->errorMessage = 'When unique-column-index is not provided, + all data rows must have a data-row-id attribute.'; return false; } } @@ -272,7 +279,8 @@ private function addProgressHeader(): void { */ private function processDataRows(): void { $xpath = new DOMXPath( $this->dom ); - // this is fucked, but this should be better than just trying to get the tr element with ->getElementByTagName('tr') as that will return all tr elements, including the header ones + // this is fucked, but this should be better than just trying to get the tr element with + // ->getElementByTagName('tr') as that will return all tr elements, including the header ones $dataRows = $xpath->query( './/tr[not(th)]', $this->table ); $rowIndex = 0; @@ -306,8 +314,8 @@ private function addCheckboxCellToRow( DOMElement $row, int $rowIndex ): void { $checkBoxInput->setAttribute( 'data-row-id', $rowId ); $checkBoxInput->setAttribute( 'id', $rowId ); // disable the checkbox by default, when the JS runs, it will remove the disabled attribute. - // this is to ensure that no checkbox is selected before the JS initialises (or in the case of an unregistered user, - // the checkbox will remain disabled) + // this is to ensure that no checkbox is selected before the JS initialises (or in the case of an unregistered + // user, the checkbox will remain disabled) $checkBoxInput->setAttribute( 'disabled', 'disabled' ); // empty span for the icon as per: diff --git a/includes/Rest/TrackProgressHandler.php b/includes/Rest/TrackProgressHandler.php index e1e76dd..3ea299c 100644 --- a/includes/Rest/TrackProgressHandler.php +++ b/includes/Rest/TrackProgressHandler.php @@ -65,7 +65,7 @@ public function run( int $articleId, int $tableId ): Response { $response = $this->getResponseFactory()->create(); $response->setStatus( 201 ); - + return $response; } diff --git a/includes/TableGenerator.php b/includes/TableGenerator.php index d9dc7af..ab8d2c9 100644 --- a/includes/TableGenerator.php +++ b/includes/TableGenerator.php @@ -70,10 +70,10 @@ public static function hasDuplicateTables( RenderedRevision $revision ): bool { return false; } - $text= $content->getText(); + $text = $content->getText(); // match all tags with a table-id attribute, in any order - preg_match_all( + preg_match_all( '/]*\btable-id\s*=\s*["\']?([^"\'>\s]+)["\']?[^>]*>/i', $text, $matches diff --git a/tests/parser/singleTable.txt b/tests/parser/singleTable.txt new file mode 100644 index 0000000..ef2188d --- /dev/null +++ b/tests/parser/singleTable.txt @@ -0,0 +1,48 @@ +!! version 2 +!! hooks +table-progress-tracking +!! endhooks +!! test +A singular table on an article with the default check icon +!! wikitext + +{| class="wikitable" +! Column 1 !! Column 2 !! Column 3 +|- +| Row1-1 || Row1-2 || Row1-3 +|- +| Row2-1 || Row2-2 || Row2-3 +|- +| Row3-1 || Row3-2 || Row3-3 +|- +| Row4-1 || Row4-2 || Row4-3 +|} + +!! html + + + + + + + + + + + + + + + + + + + + +
Column 1Column 2Column 3 +
Row1-1Row1-2Row1-3 +
Row2-1Row2-2Row2-3 +
Row3-1Row3-2Row3-3 +
Row4-1Row4-2Row4-3 +
+!! end \ No newline at end of file diff --git a/tests/parser/tableWithCustomHeader.txt b/tests/parser/tableWithCustomHeader.txt new file mode 100644 index 0000000..09c9ee3 --- /dev/null +++ b/tests/parser/tableWithCustomHeader.txt @@ -0,0 +1,48 @@ +!! version 2 +!! hooks +table-progress-tracking +!! endhooks +!! test +A table with a customised header +!! wikitext + +{| class="wikitable" +! Column 1 !! Column 2 !! Column 3 +|- +| Row1-1 || Row1-2 || Row1-3 +|- +| Row2-1 || Row2-2 || Row2-3 +|- +| Row3-1 || Row3-2 || Row3-3 +|- +| Row4-1 || Row4-2 || Row4-3 +|} + +!! html + + + + + + + + + + + + + + + + + + + + +
TitleColumn 1Column 2Column 3 +
Row1-1Row1-2Row1-3 +
Row2-1Row2-2Row2-3 +
Row3-1Row3-2Row3-3 +
Row4-1Row4-2Row4-3 +
+!! end \ No newline at end of file