Skip to content
Closed
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
45 changes: 42 additions & 3 deletions src/Model/TemplateEngine/Decorator/InspectorHints.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,28 +58,66 @@
* @param array $dictionary
* @return string
*/
public function render(BlockInterface $block, $templateFile, array $dictionary = []): string
{
$result = $this->subject->render($block, $templateFile, $dictionary);
$trimmedResult = trim($result);

// Only inject attributes if there's actual HTML content
if (empty(trim($result))) {
if (empty($trimmedResult)) {
return $result;
}

// Do not wrap if content looks like JSON (also check for common JSON patterns)
if (str_starts_with($trimmedResult, '{') ||
str_starts_with($trimmedResult, '[') ||
(str_starts_with($trimmedResult, '"') && str_contains($trimmedResult, '":{"'))) {
return $result;
}

// Do not wrap if content is a script tag (start allows attributes or multiple tags)
if (stripos($trimmedResult, '<script') === 0) {
return $result;
}

// Do not wrap if template path suggests partial/JS/JSON or direct Magewire component files
// Only block very specific risky paths, rely on content checks for Magewire detection
$lowerTemplatePath = strtolower($templateFile);
if (strpos($lowerTemplatePath, '/js/') !== false ||
strpos($lowerTemplatePath, '/json/') !== false ||
strpos($lowerTemplatePath, '/magewire/') !== false ||
strpos($lowerTemplatePath, '/livewire/') !== false) {
return $result;
}

// Do not wrap if content seems to be a Magewire/Livewire component (to prevent DOM diffing issues)
// Check for any wire: attributes or Magewire initialization
if (stripos($result, 'wire:') !== false ||
stripos($result, 'livewire') !== false ||
stripos($result, 'magewire') !== false ||
stripos($result, 'x-data="initMagewire') !== false ||
stripos($result, '@entangle') !== false) {
return $result;
}

// Do not wrap if content seems to be a template for JS templating engines (e.g. Mustache/Hogan)
if (str_contains($result, '{{') && str_contains($result, '}}')) {
return $result;
}

return $this->injectInspectorAttributes($result, $block, $templateFile);
}

/**
* Inject MageForge inspector comment markers into HTML
*
* @param string $html
* @param BlockInterface $block
* @param string $templateFile
* @return string
*/
private function injectInspectorAttributes(string $html, BlockInterface $block, string $templateFile): string
{

Check notice on line 120 in src/Model/TemplateEngine/Decorator/InspectorHints.php

View check run for this annotation

codefactor.io / CodeFactor

src/Model/TemplateEngine/Decorator/InspectorHints.php#L61-L120

Complex Method
$wrapperId = 'mageforge-' . $this->random->getRandomString(16);

// Get block class name
Expand Down Expand Up @@ -115,9 +153,10 @@
// Escape any comment terminators in JSON to prevent breaking out of comment
$jsonMetadata = str_replace('-->', '--&gt;', $jsonMetadata);

// Wrap content with comment markers
// Wrap content with comment markers without creating new lines
// New lines can break JavaScript strings if this block is rendered inside a JS variable
$wrappedHtml = sprintf(
"<!-- MAGEFORGE_START %s -->\n%s\n<!-- MAGEFORGE_END %s -->",
"<!-- MAGEFORGE_START %s -->%s<!-- MAGEFORGE_END %s -->",
$jsonMetadata,
$html,
$wrapperId
Expand Down
19 changes: 18 additions & 1 deletion src/Model/TemplateEngine/Plugin/InspectorHints.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Magento\Developer\Helper\Data as DevHelper;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Request\Http;
use Magento\Framework\App\State;
use Magento\Framework\View\TemplateEngineFactory;
use Magento\Framework\View\TemplateEngineInterface;
Expand All @@ -32,25 +33,30 @@ class InspectorHints

private State $state;

private Http $request;

/**
* @param ScopeConfigInterface $scopeConfig
* @param StoreManagerInterface $storeManager
* @param DevHelper $devHelper
* @param InspectorHintsFactory $inspectorHintsFactory
* @param State $state
* @param Http $request
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
StoreManagerInterface $storeManager,
DevHelper $devHelper,
InspectorHintsFactory $inspectorHintsFactory,
State $state
State $state,
Http $request
) {
$this->scopeConfig = $scopeConfig;
$this->storeManager = $storeManager;
$this->devHelper = $devHelper;
$this->inspectorHintsFactory = $inspectorHintsFactory;
$this->state = $state;
$this->request = $request;
}

/**
Expand All @@ -70,6 +76,17 @@ public function afterCreate(
return $invocationResult;
}

// Disable for AJAX/API requests (including Magewire)
if ($this->request->isXmlHttpRequest()) {
return $invocationResult;
}

// Block any magewire/livewire AJAX endpoints only
$requestUri = $this->request->getRequestUri();
if ($requestUri && preg_match('/\/(magewire|livewire)\//i', $requestUri)) {
return $invocationResult;
}

// Check if inspector is enabled in configuration
$storeCode = $this->storeManager->getStore()->getCode();
if (!$this->scopeConfig->isSetFlag(self::XML_PATH_INSPECTOR_ENABLED, ScopeInterface::SCOPE_STORE, $storeCode)) {
Expand Down
12 changes: 11 additions & 1 deletion src/view/frontend/web/js/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ document.addEventListener('alpine:init', () => {
this.isPickerActive = true;
document.addEventListener('mousemove', this.mouseMoveHandler);
document.addEventListener('click', this.clickHandler, false); // Don't use capture
document.body.style.cursor = 'crosshair';
// Cursor is set dynamically in handleMouseMove based on inspectability
},

/**
Expand Down Expand Up @@ -443,16 +443,26 @@ document.addEventListener('alpine:init', () => {

// Don't update if mouse is over the floating button
if (this.floatingButton && this.floatingButton.contains(e.target)) {
document.body.style.cursor = 'crosshair';
return;
}

// Don't update if mouse is over the info badge
if (this.infoBadge && this.infoBadge.contains(e.target)) {
document.body.style.cursor = 'crosshair';
return;
}

const element = this.findInspectableElement(e.target);

// Update cursor based on inspectability
if (element) {
document.body.style.cursor = 'crosshair';
} else if (e.target && !e.target.classList.contains('mageforge-inspector') && !e.target.closest('.mageforge-inspector')) {
// Show"not-allowed" cursor for non-inspectable elements (protected Magewire components)
document.body.style.cursor = 'not-allowed';
}

// Clear any existing hover timeout
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
Expand Down
Loading