') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat: `spark filter:check` shows filter classnames by kenjis · Pull Request #8985 · codeigniter4/CodeIgniter4 · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions phpstan-baseline.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -877,18 +877,6 @@
'count' => 3,
'path' => __DIR__ . '/system/Commands/Utilities/Environment.php',
];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Method CodeIgniter\\\\Commands\\\\Utilities\\\\FilterCheck\\:\\:addRequiredFilters\\(\\) has parameter \\$filters with no value type specified in iterable type array\\.$#',
'count' => 1,
'path' => __DIR__ . '/system/Commands/Utilities/FilterCheck.php',
];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Method CodeIgniter\\\\Commands\\\\Utilities\\\\FilterCheck\\:\\:addRequiredFilters\\(\\) return type has no value type specified in iterable type array\\.$#',
'count' => 1,
'path' => __DIR__ . '/system/Commands/Utilities/FilterCheck.php',
];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Method CodeIgniter\\\\Commands\\\\Utilities\\\\Namespaces\\:\\:outputAllNamespaces\\(\\) has parameter \\$params with no value type specified in iterable type array\\.$#',
Expand Down
108 changes: 76 additions & 32 deletions system/Commands/Utilities/FilterCheck.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,13 +73,7 @@ class FilterCheck extends BaseCommand
*/
public function run(array $params)
{
$tbody = [];
if (! isset($params[0], $params[1])) {
CLI::error('You must specify a HTTP verb and a route.');
CLI::write(' Usage: ' . $this->usage);
CLI::write('Example: filter:check GET /');
CLI::write(' filter:check PUT products/1');

if (! $this->checkParams($params)) {
return EXIT_ERROR;
}

Expand DownExpand Up@@ -107,49 +101,99 @@ public function run(array $params)
return EXIT_ERROR;
}

$filters = $this->addRequiredFilters($filterCollector, $filters);
$this->showTable($filterCollector, $filters, $method, $route);
$this->showFilterClasses($filterCollector, $method, $route);

$tbody[] = [
strtoupper($method),
$route,
implode(' ', $filters['before']),
implode(' ', $filters['after']),
];
return EXIT_SUCCESS;
}

/**
* @param array<int|string, string|null> $params
*/
private function checkParams(array $params): bool
{
if (! isset($params[0], $params[1])) {
CLI::error('You must specify a HTTP verb and a route.');
CLI::write(' Usage: ' . $this->usage);
CLI::write('Example: filter:check GET /');
CLI::write(' filter:check PUT products/1');

return false;
}

return true;
}

/**
* @param array{before: list<string>, after: list<string>} $filters
*/
private function showTable(
FilterCollector $filterCollector,
array $filters,
string $method,
string $route
): void {
$thead = [
'Method',
'Route',
'Before Filters',
'After Filters',
];

CLI::table($tbody, $thead);
$required = $filterCollector->getRequiredFilters();

return EXIT_SUCCESS;
$coloredRequired = $this->colorItems($required);

$before = array_merge($coloredRequired['before'], $filters['before']);
$after = array_merge($filters['after'], $coloredRequired['after']);

$tbody = [];
$tbody[] = [
strtoupper($method),
$route,
implode(' ', $before),
implode(' ', $after),
];

CLI::table($tbody, $thead);
}

private function addRequiredFilters(FilterCollector $filterCollector, array $filters): array
/**
* Color all elements of the array.
*
* @param array<array-key, mixed> $array
*
* @return array<array-key, mixed>
*/
private function colorItems(array $array): array
{
$output = [];
return array_map(function ($item) {
if (is_array($item)) {
return $this->colorItems($item);
}

$required = $filterCollector->getRequiredFilters();
return CLI::color($item, 'yellow');
}, $array);
}

$colored = [];
private function showFilterClasses(
FilterCollector $filterCollector,
string $method,
string $route
): void {
$requiredFilterClasses = $filterCollector->getRequiredFilterClasses();
$filterClasses = $filterCollector->getClasses($method, $route);

foreach ($required['before'] as $filter) {
$filter = CLI::color($filter, 'yellow');
$colored[] = $filter;
}
$output['before'] = array_merge($colored, $filters['before']);
$coloredRequiredFilterClasses = $this->colorItems($requiredFilterClasses);

$colored = [];
$classList = [
'before' => array_merge($coloredRequiredFilterClasses['before'], $filterClasses['before']),
'after' => array_merge($filterClasses['after'], $coloredRequiredFilterClasses['after']),
];

foreach ($required['after'] as $filter) {
$filter = CLI::color($filter, 'yellow');
$colored[] = $filter;
foreach ($classList as $position => $classes) {
CLI::write(ucfirst($position) . ' Filter Classes:', 'cyan');
CLI::write(implode(' → ', $classes));
}
$output['after'] = array_merge($filters['after'], $colored);

return $output;
}
}
64 changes: 62 additions & 2 deletions system/Commands/Utilities/Routes/FilterCollector.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,7 @@ public function __construct(
* @param string $method HTTP verb like `GET`,`POST` or `CLI`.
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of alias/classname:args
*/
public function get(string $method, string $uri): array
{
Expand DownExpand Up@@ -79,10 +79,52 @@ public function get(string $method, string $uri): array
return $finder->find($uri);
}

/**
* Returns filter classes for the URI
*
* @param string $method HTTP verb like `GET`,`POST` or `CLI`.
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of classname:args
*/
public function getClasses(string $method, string $uri): array
{
if ($method === strtolower($method)) {
@trigger_error(
'Passing lowercase HTTP method "' . $method . '" is deprecated.'
. ' Use uppercase HTTP method like "' . strtoupper($method) . '".',
E_USER_DEPRECATED
);
}

/**
* @deprecated 4.5.0
* @TODO Remove this in the future.
*/
$method = strtoupper($method);

if ($method === 'CLI') {
return [
'before' => [],
'after' => [],
];
}

$request = Services::incomingrequest(null, false);
$request->setMethod($method);

$router = $this->createRouter($request);
$filters = $this->createFilters($request);

$finder = new FilterFinder($router, $filters);

return $finder->findClasses($uri);
}

/**
* Returns Required Filters
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of aliases
*/
public function getRequiredFilters(): array
{
Expand All@@ -97,6 +139,24 @@ public function getRequiredFilters(): array
return $finder->getRequiredFilters();
}

/**
* Returns Required Filter class list
*
* @return array{before: list<string>, after: list<string>} array of classnames
*/
public function getRequiredFilterClasses(): array
{
$request = Services::incomingrequest(null, false);
$request->setMethod(Method::GET);

$router = $this->createRouter($request);
$filters = $this->createFilters($request);

$finder = new FilterFinder($router, $filters);

return $finder->getRequiredFilterClasses();
}

private function createRouter(Request $request): Router
{
$routes = service('routes');
Expand Down
92 changes: 86 additions & 6 deletions system/Commands/Utilities/Routes/FilterFinder.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,23 +46,20 @@ private function getRouteFilters(string $uri): array
/**
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of alias/classname:args
*/
public function find(string $uri): array
{
$this->filters->reset();

// Add route filters
try {
// Add route filters
$routeFilters = $this->getRouteFilters($uri);

$this->filters->enableFilters($routeFilters, 'before');

$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
if (! $oldFilterOrder) {
$routeFilters = array_reverse($routeFilters);
}

$this->filters->enableFilters($routeFilters, 'after');

$this->filters->initialize($uri);
Expand All@@ -81,10 +78,66 @@ public function find(string $uri): array
}
}

/**
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of classname:args
*/
public function findClasses(string $uri): array
{
$this->filters->reset();

try {
// Add route filters
$routeFilters = $this->getRouteFilters($uri);
$this->filters->enableFilters($routeFilters, 'before');
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
if (! $oldFilterOrder) {
$routeFilters = array_reverse($routeFilters);
}
$this->filters->enableFilters($routeFilters, 'after');

$this->filters->initialize($uri);

$filterClassList = $this->filters->getFiltersClass();

$filterClasses = [
'before' => [],
'after' => [],
];

foreach ($filterClassList['before'] as $classInfo) {
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
: $classInfo[0] . ':' . implode(',', $classInfo[1]);

$filterClasses['before'][] = $classWithArguments;
}

foreach ($filterClassList['after'] as $classInfo) {
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
: $classInfo[0] . ':' . implode(',', $classInfo[1]);

$filterClasses['after'][] = $classWithArguments;
}

return $filterClasses;
} catch (RedirectException) {
return [
'before' => [],
'after' => [],
];
} catch (BadRequestException|PageNotFoundException) {
return [
'before' => ['<unknown>'],
'after' => ['<unknown>'],
];
}
}

/**
* Returns Required Filters
*
* @return array{before: list<string>, after:list<string>}
* @return array{before: list<string>, after:list<string>} array of aliases
*/
public function getRequiredFilters(): array
{
Expand All@@ -96,4 +149,31 @@ public function getRequiredFilters(): array
'after' => $requiredAfter,
];
}

/**
* Returns Required Filter classes
*
* @return array{before: list<string>, after:list<string>}
*/
public function getRequiredFilterClasses(): array
{
$before = $this->filters->getRequiredClasses('before');
$after = $this->filters->getRequiredClasses('after');

$requiredBefore = [];
$requiredAfter = [];

foreach ($before as $classInfo) {
$requiredBefore[] = $classInfo[0];
}

foreach ($after as $classInfo) {
$requiredAfter[] = $classInfo[0];
}

return [
'before' => $requiredBefore,
'after' => $requiredAfter,
];
}
}
Loading