Files
resourcespace/include/EscapeLanguageStringsRector.php
2025-07-18 16:20:14 +07:00

74 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Montala\ResourceSpace\Utils\Rector;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\Node\Name;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class EscapeLanguageStringsRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Change instances where $lang is directly echoed without escaping for XSS.'
[new CodeSample('echo $lang["string"];', 'escape($lang["string"]);')]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
// What node types are we looking for? Pick from https://github.com/rectorphp/php-parser-nodes-docs/
return [Echo_::class];
}
/**
* @param Echo_ $node
*/
public function refactor(Node $node): ?Node
{
$expr = $node->exprs[0];
$var_name = $expr->var->name;
$dim_value = $expr->dim->value;
// Only look for this use case form: echo $lang['home'];
if (!(is_a($expr, ArrayDimFetch::class, false) && $var_name === 'lang')) {
return null;
}
// Only load the en version because we assume other translations follow its format (e.g. if a string contains
// HTML tags, all the other translations should do too).
require dirname(__DIR__) . '/languages/en.php';
$plugins_path = dirname(__DIR__) . '/plugins';
$valid_plugins = scandir($plugins_path) ?: [];
$plugin = array_pop(
array_reverse(
array_diff(explode('/', $this->file->getFilePath()), explode('/', $plugins_path))
)
);
$plugin_en_file = "{$plugins_path}/{$plugin}/languages/en.php";
if (in_array($plugin, $valid_plugins) && file_exists($plugin_en_file)) {
require $plugin_en_file;
}
if (!isset($lang[$dim_value])) {
return null;
}
$fct_name = $lang[$dim_value] !== strip_tags($lang[$dim_value]) ? 'strip_tags_and_attributes' : 'escape';
return new Echo_([new FuncCall(new Name($fct_name), [new Arg($expr)])]);
}
}