diff --git a/classes/views/frm-fields/back-end/field-options.php b/classes/views/frm-fields/back-end/field-options.php index fb3abd0c02..3757f89a9a 100644 --- a/classes/views/frm-fields/back-end/field-options.php +++ b/classes/views/frm-fields/back-end/field-options.php @@ -12,7 +12,7 @@ FrmAppHelper::icon_by_class( 'frmfont frm_simple_pencil_icon frm_svg24', array( - 'echo' => true, + 'aria-label' => __( 'Bulk Edit', 'formidable' ), ) ); diff --git a/classes/views/frm-form-actions/on_submit_settings.php b/classes/views/frm-form-actions/on_submit_settings.php index a3092622fb..3a16199002 100644 --- a/classes/views/frm-form-actions/on_submit_settings.php +++ b/classes/views/frm-form-actions/on_submit_settings.php @@ -58,7 +58,7 @@ /> diff --git a/classes/views/shared/review.php b/classes/views/shared/review.php index 7f7ab5d7e1..617f39ff57 100644 --- a/classes/views/shared/review.php +++ b/classes/views/shared/review.php @@ -9,7 +9,7 @@ FrmAppHelper::icon_by_class( 'frmfont frm-flag frm_svg40 frm-review-notice-icon frm-text-success-500', array( - 'echo' => true, + 'aria-hidden' => 'true', ) ); @@ -31,7 +31,7 @@ FrmAppHelper::icon_by_class( 'frmfont frm-star-feedback-icon frm_svg40 frm-review-notice-icon frm-text-warning-500', array( - 'echo' => true, + 'aria-hidden' => 'true', ) ); @@ -60,7 +60,7 @@ FrmAppHelper::icon_by_class( 'frmfont frm-pencil-message-icon frm_svg40 frm-review-notice-icon frm-text-primary-500', array( - 'echo' => true, + 'aria-hidden' => 'true', ) ); diff --git a/classes/views/styles/_style-preview-container.php b/classes/views/styles/_style-preview-container.php index f3b06bcecb..4d5d8b0560 100644 --- a/classes/views/styles/_style-preview-container.php +++ b/classes/views/styles/_style-preview-container.php @@ -47,11 +47,11 @@ - true ) ); ?> + true. + * + * Since icon_by_class echoes by default, 'echo' => true is redundant and should be removed. + * + * @package Formidable\Sniffs\CodeAnalysis + */ + +namespace Formidable\Sniffs\CodeAnalysis; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +/** + * Detects and fixes redundant 'echo' => true in FrmAppHelper::icon_by_class calls. + */ +class RedundantIconEchoArgSniff implements Sniff { + + /** + * Returns an array of tokens this sniff wants to listen for. + * + * @return array + */ + public function register() { + return array( T_DOUBLE_COLON ); + } + + /** + * Processes this sniff when one of its tokens is encountered. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token. + * + * @return void + */ + public function process( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // Check if this is FrmAppHelper::icon_by_class. + $prevToken = $phpcsFile->findPrevious( T_WHITESPACE, $stackPtr - 1, null, true ); + + if ( false === $prevToken || $tokens[ $prevToken ]['code'] !== T_STRING ) { + return; + } + + if ( $tokens[ $prevToken ]['content'] !== 'FrmAppHelper' ) { + return; + } + + // Check the method name after ::. + $methodToken = $phpcsFile->findNext( T_WHITESPACE, $stackPtr + 1, null, true ); + + if ( false === $methodToken || $tokens[ $methodToken ]['code'] !== T_STRING ) { + return; + } + + if ( $tokens[ $methodToken ]['content'] !== 'icon_by_class' ) { + return; + } + + // Find the opening parenthesis. + $openParen = $phpcsFile->findNext( T_WHITESPACE, $methodToken + 1, null, true ); + + if ( false === $openParen || $tokens[ $openParen ]['code'] !== T_OPEN_PARENTHESIS ) { + return; + } + + $closeParen = $tokens[ $openParen ]['parenthesis_closer']; + + // Find 'echo' => true within the parentheses. + $echoInfo = $this->findEchoTrueArg( $phpcsFile, $openParen, $closeParen ); + + if ( false === $echoInfo ) { + return; + } + + $fix = $phpcsFile->addFixableError( + "Redundant 'echo' => true in FrmAppHelper::icon_by_class. The method echoes by default.", + $echoInfo['start'], + 'RedundantEchoArg' + ); + + if ( $fix ) { + $this->applyFix( $phpcsFile, $echoInfo ); + } + } + + /** + * Find 'echo' => true in the arguments. + * + * @param File $phpcsFile The file being scanned. + * @param int $openParen The position of the opening parenthesis. + * @param int $closeParen The position of the closing parenthesis. + * + * @return array|false Array with start/end positions or false if not found. + */ + private function findEchoTrueArg( File $phpcsFile, $openParen, $closeParen ) { + $tokens = $phpcsFile->getTokens(); + + // Look for array( or [ that contains 'echo' => true. + for ( $i = $openParen + 1; $i < $closeParen; $i++ ) { + // Look for 'echo' string. + if ( $tokens[ $i ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { + continue; + } + + $content = trim( $tokens[ $i ]['content'], "\"'" ); + + if ( $content !== 'echo' ) { + continue; + } + + // Find the double arrow after 'echo'. + $arrow = $phpcsFile->findNext( T_WHITESPACE, $i + 1, $closeParen, true ); + + if ( false === $arrow || $tokens[ $arrow ]['code'] !== T_DOUBLE_ARROW ) { + continue; + } + + // Find the value after =>. + $value = $phpcsFile->findNext( T_WHITESPACE, $arrow + 1, $closeParen, true ); + + if ( false === $value || $tokens[ $value ]['code'] !== T_TRUE ) { + continue; + } + + // Found 'echo' => true. Now determine the range to remove. + // First, find the array that contains this key-value pair. + $arrayInfo = $this->findContainingArray( $phpcsFile, $i, $openParen, $closeParen ); + + // Check if 'echo' => true is the only element in the array. + $isOnlyElement = $this->isOnlyArrayElement( $phpcsFile, $arrayInfo, $i, $value ); + + if ( $isOnlyElement ) { + // Remove the entire second argument (comma + array). + return array( + 'removeEntireArg' => true, + 'arrayStart' => $arrayInfo['start'], + 'arrayEnd' => $arrayInfo['end'], + 'start' => $i, + ); + } + + // Check if there's a comma before or after. + $start = $i; + $end = $value; + + // Look for leading comma (this arg comes after another). + $beforeStart = $phpcsFile->findPrevious( T_WHITESPACE, $i - 1, $openParen, true ); + $hasLeadingComma = ( false !== $beforeStart && $tokens[ $beforeStart ]['code'] === T_COMMA ); + + // Look for trailing comma. + $afterEnd = $phpcsFile->findNext( T_WHITESPACE, $value + 1, $closeParen, true ); + $hasTrailingComma = ( false !== $afterEnd && $tokens[ $afterEnd ]['code'] === T_COMMA ); + + if ( $hasLeadingComma ) { + // Include the leading comma and whitespace. + $start = $beforeStart; + } elseif ( $hasTrailingComma ) { + // Include the trailing comma. + $end = $afterEnd; + } + + return array( + 'removeEntireArg' => false, + 'start' => $start, + 'end' => $end, + 'hasLeadingComma' => $hasLeadingComma, + 'hasTrailingComma' => $hasTrailingComma, + 'echoKeyPos' => $i, + 'trueValuePos' => $value, + ); + } + + return false; + } + + /** + * Find the array that contains the given position. + * + * @param File $phpcsFile The file being scanned. + * @param int $pos Position inside the array. + * @param int $openParen The function's opening parenthesis. + * @param int $closeParen The function's closing parenthesis. + * + * @return array Array with 'start' and 'end' positions. + */ + private function findContainingArray( File $phpcsFile, $pos, $openParen, $closeParen ) { + $tokens = $phpcsFile->getTokens(); + + // Look backwards for array( or [. + for ( $i = $pos - 1; $i > $openParen; $i-- ) { + if ( $tokens[ $i ]['code'] === T_OPEN_SHORT_ARRAY ) { + // Short array syntax []. + return array( + 'start' => $i, + 'end' => $tokens[ $i ]['bracket_closer'], + ); + } + + if ( $tokens[ $i ]['code'] === T_ARRAY ) { + // Long array syntax array(). + $arrayOpen = $phpcsFile->findNext( T_OPEN_PARENTHESIS, $i + 1, $closeParen ); + + if ( false !== $arrayOpen ) { + return array( + 'start' => $i, + 'end' => $tokens[ $arrayOpen ]['parenthesis_closer'], + ); + } + } + } + + return array( + 'start' => $openParen, + 'end' => $closeParen, + ); + } + + /** + * Check if 'echo' => true is the only element in the array. + * + * @param File $phpcsFile The file being scanned. + * @param array $arrayInfo Array start/end info. + * @param int $keyPos Position of the 'echo' key. + * @param int $valuePos Position of the true value. + * + * @return bool + */ + private function isOnlyArrayElement( File $phpcsFile, $arrayInfo, $keyPos, $valuePos ) { + $tokens = $phpcsFile->getTokens(); + + // Determine the actual content start (after array( or [). + $contentStart = $arrayInfo['start'] + 1; + + if ( $tokens[ $arrayInfo['start'] ]['code'] === T_ARRAY ) { + $contentStart = $phpcsFile->findNext( T_OPEN_PARENTHESIS, $arrayInfo['start'] + 1 ) + 1; + } + + $contentEnd = $arrayInfo['end'] - 1; + + // Check if there are any other non-whitespace tokens besides 'echo' => true. + for ( $i = $contentStart; $i <= $contentEnd; $i++ ) { + if ( $tokens[ $i ]['code'] === T_WHITESPACE ) { + continue; + } + + // Skip the 'echo' => true tokens. + if ( $i >= $keyPos && $i <= $valuePos ) { + continue; + } + + // Skip tokens between key and value (whitespace and =>). + if ( $i > $keyPos && $i < $valuePos ) { + continue; + } + + // Found another token - not the only element. + return false; + } + + return true; + } + + /** + * Apply the fix by removing the 'echo' => true argument. + * + * @param File $phpcsFile The file being scanned. + * @param array $echoInfo Information about the echo arg position. + * + * @return void + */ + private function applyFix( File $phpcsFile, $echoInfo ) { + $tokens = $phpcsFile->getTokens(); + $phpcsFile->fixer->beginChangeset(); + + if ( ! empty( $echoInfo['removeEntireArg'] ) ) { + // Remove the entire second argument including the comma before it. + $arrayStart = $echoInfo['arrayStart']; + $arrayEnd = $echoInfo['arrayEnd']; + + // Find the comma before the array. + $comma = $phpcsFile->findPrevious( T_WHITESPACE, $arrayStart - 1, null, true ); + + if ( false !== $comma && $tokens[ $comma ]['code'] === T_COMMA ) { + // Remove from comma to end of array. + for ( $i = $comma; $i <= $arrayEnd; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + } else { + // Just remove the array. + for ( $i = $arrayStart; $i <= $arrayEnd; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + } + } else { + // Remove just the 'echo' => true element. + for ( $i = $echoInfo['start']; $i <= $echoInfo['end']; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + + // Also remove any whitespace after the removed content if we removed a trailing comma. + if ( ! empty( $echoInfo['hasTrailingComma'] ) ) { + $next = $echoInfo['end'] + 1; + + while ( isset( $tokens[ $next ] ) && $tokens[ $next ]['code'] === T_WHITESPACE ) { + // Only remove if it's just spaces, not newlines that are part of formatting. + if ( strpos( $tokens[ $next ]['content'], "\n" ) === false ) { + $phpcsFile->fixer->replaceToken( $next, '' ); + } + $next++; + } + } + } + + $phpcsFile->fixer->endChangeset(); + } +}