Problem/Motivation
In PHP 8.3, strrpos() no longer accepts null as parameter #1. In LeafletMap.php, when a View uses #rendered_view_fields for either Popup Content or Tooltips, strrpos(): Passing null to parameter #1 ($haystack) of type string is deprecated warnings are logged for every row, leading to watchdog spam and memory exhaustion on large datasets.
There are two distinct root causes in LeafletMap.php:
1. In processFeatureTooltip(): The render array key is written as "markup" instead of "#markup". Without the # prefix, Drupal's ThemeManager receives a NULL theme hook, triggering strrpos(null).
2. In getPopupContent(): $this->view->rowPlugin->render($result) returns either a string/Markup object or a render array whose #theme array may contain null suggestions. Passing this directly to
renderInIsolation() without checking types or filtering #theme causes strrpos() deprecations.
Steps to reproduce
1. Setup Drupal 11 on PHP 8.3.
2. Create a View using Leaflet Map style.
3. In style settings, set Popup Source or Tooltip Source to #rendered_view_fields.
4. View the map and observe watchdog logs for strrpos(): Passing null to parameter #1.
Proposed resolution
1. Fix the array key in processFeatureTooltip() from "markup" to "#markup".
2. In both getPopupContent() and processFeatureTooltip(), check if $rendered_output is an array:
- If array: sanitize #theme suggestions using array_filter($rendered_output['#theme'], 'is_string').
- If string/Markup: wrap it in ['#markup' => (string) $rendered_output].
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | leaflet-3612703-10.4.9-strrpos-null-deprecation.patch | 2.76 KB | konot |
Issue fork leaflet-3612703
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
konot commentedThere is a difference between 10.4.9 release and 10.4.x-dev, so I append here a patch specifically for compatibility with 10.4.9.
Comment #4
konot commentedComment #5
konot commentedComment #6
itamair commentedThanks @konot for reporting all this.
The issue report correctly identifies **one real bug** in `processFeatureTooltip()`.
But the popup path was never broken.
The MR fix works but is over-engineered and introduces a subtle new risk.
---
### Root Cause #1: The Tooltip Bug — Real and Clearly Broken
**Original code in `processFeatureTooltip()`** (upstream `10.4.x`):
```php
$render_row = [
"markup" => $this->view->rowPlugin->render($result),
];
$feature['tooltip']['value'] = $this->renderer->renderInIsolation($render_row);
```
This is **doubly wrong**:
1. `"markup"` (no `#` prefix) is not a recognized Drupal render property — the renderer ignores it, sees an element with no `#type`, `#theme`, `#markup`, or `#plain_text`, and has no idea what to render. In PHP 8.3, that broken render path ends up passing `null` as haystack to `strrpos()` inside Drupal's `ThemeManager`.
2. Even if the key were `"#markup"`, it would still be wrong, because `#markup` expects a string value — not the render array that `rowPlugin->render()` returns.
**This is a genuine bug that has been broken since this code was introduced.**
---
### Root Cause #2: The Popup Bug — Not Broken
**Original code in `getPopupContent()`**:
```php
$render_row = $this->view->rowPlugin->render($result);
$popup_content = $this->renderer->renderInIsolation($render_row);
```
This is **correct**. `rowPlugin->render()` returns a proper render array (for the Fields row plugin, it's `#theme => [array of theme suggestions]`, all strings derived from `buildThemeFunctions()`). Passing it straight to `renderInIsolation()` is exactly what the renderer expects. There is no PHP 8.3 issue here in normal operation.
---
### The `#theme` Null Filtering — Unnecessary Complexity
The MR filters `#theme` with `array_filter($rendered_output['#theme'], 'is_string')` in both paths. Looking at how Drupal core builds those theme suggestions (in `ViewExecutable::buildThemeFunctions()`):
```php
$themes[] = $hook . '__' . $id . '__' . $display['id'];
$themes[] = $hook . '__' . $display['id'];
// ...tags...
$themes[] = $hook . '__' . $id;
$themes[] = $hook;
```
These are all string concatenations. No `null` values can appear here unless `$hook` itself is null (which would be a different upstream bug entirely). The `is_string` filter solves a problem that doesn't exist in practice.
---
### The MR's Dangerous Edge Case
When `#theme` filtering produces an empty array, the MR does:
```php
unset($rendered_output['#theme']);
$rendered_output['#markup'] = $rendered_output['#markup'] ?? '';
```
This discards the entire render context (`#view`, `#options`, `#row`) and renders empty markup. If this branch ever executes (it won't in normal operation, but still), the popup/tooltip silently goes blank with no error. That's a hidden regression.
---
### The Correct Minimal Fix
The tooltip fix should simply mirror the popup code, which was already correct:
```php
// processFeatureTooltip() — was broken, correct fix:
$render_row = $this->view->rowPlugin->render($result);
$feature['tooltip']['value'] = (string) $this->renderer->renderInIsolation($render_row);
```
The `(string)` cast (which the MR does add everywhere, correctly) is a minor improvement since `renderInIsolation()` returns `MarkupInterface`, not a plain string.
For `getPopupContent()`, the original was fine — at most add the `(string)` cast.
Comment #7
itamair commentedIn the end, the correct minimal fix is two lines in `processFeatureTooltip()`:
drop the wrapping array entirely and pass `rowPlugin->render()` directly to `renderInIsolation()`, matching the popup code.
And this is exactly what have already been done/accomplished with this latest 10.4.x branch commit:
https://git.drupalcode.org/project/leaflet/-/commit/4eb392fa0d05e0b13190...
The popup path should not be touched.
Closing this as (already) Fixed".