Skip to content

Commit c190595

Browse files
committed
Defer translation loading from the callback detection logic.
1 parent 7846dfe commit c190595

File tree

5 files changed

+124
-37
lines changed

5 files changed

+124
-37
lines changed

classes/Util.php

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ public static function get_file_component( $file ) {
300300
* line?: string|false,
301301
* error?: WP_Error,
302302
* component?: QM_Component,
303+
* callback_type: string,
304+
* start_line?: int,
305+
* display_file?: string,
303306
* }
304307
*/
305308
public static function populate_callback( array $callback ) {
@@ -321,9 +324,11 @@ public static function populate_callback( array $callback ) {
321324
if ( is_object( $callback['function'][0] ) ) {
322325
$class = get_class( $callback['function'][0] );
323326
$access = '->';
327+
$callback['callback_type'] = 'method';
324328
} else {
325329
$class = $callback['function'][0];
326330
$access = '::';
331+
$callback['callback_type'] = 'static_method';
327332
}
328333

329334
$callback['name'] = self::shorten_fqn( $class . $access . $callback['function'][1] ) . '()';
@@ -338,46 +343,35 @@ public static function populate_callback( array $callback ) {
338343
if ( 0 === strpos( $file, '/' ) ) {
339344
$file = basename( $filename );
340345
}
341-
$callback['name'] = sprintf(
342-
/* translators: A closure is an anonymous PHP function. 1: Line number, 2: File name */
343-
__( 'Closure on line %1$d of %2$s', 'query-monitor' ),
344-
$ref->getStartLine(),
345-
$file
346-
);
346+
$callback['callback_type'] = 'closure';
347+
$callback['start_line'] = $ref->getStartLine();
348+
$callback['display_file'] = $file;
347349
} else {
348-
/* translators: A closure is an anonymous PHP function */
349-
$callback['name'] = __( 'Unknown closure', 'query-monitor' );
350+
$callback['callback_type'] = 'unknown_closure';
350351
}
351352
} else {
352353
// the object should have a __invoke() method
353354
$class = get_class( $callback['function'] );
354355
$callback['name'] = self::shorten_fqn( $class ) . '->__invoke()';
356+
$callback['callback_type'] = 'invokable';
355357
$ref = new ReflectionMethod( $class, '__invoke' );
356358
}
357359
} else {
358360
$callback['name'] = self::shorten_fqn( $callback['function'] ) . '()';
361+
$callback['callback_type'] = 'function';
359362
$ref = new ReflectionFunction( $callback['function'] );
360363
}
361364

362365
$callback['file'] = $ref->getFileName();
363366
$callback['line'] = $ref->getStartLine();
364367

365-
// https://github.com/facebook/hhvm/issues/5856
368+
// Handle legacy create_function() lambdas (PHP 7.4 only, removed in PHP 8.0)
366369
$name = trim( $ref->getName() );
367-
368-
if ( '__lambda_func' === $name || 0 === strpos( $name, 'lambda_' ) ) {
369-
if ( $callback['file'] && preg_match( '|(?P<file>.*)\((?P<line>[0-9]+)\)|', $callback['file'], $matches ) ) {
370-
$callback['file'] = $matches['file'];
371-
$callback['line'] = $matches['line'];
372-
$file = trim( self::standard_dir( $callback['file'], '' ), '/' );
373-
/* translators: 1: Line number, 2: File name */
374-
$callback['name'] = sprintf( __( 'Anonymous function on line %1$d of %2$s', 'query-monitor' ), $callback['line'], $file );
375-
} else {
376-
// https://github.com/facebook/hhvm/issues/5807
377-
unset( $callback['line'], $callback['file'] );
378-
$callback['name'] = $name . '()';
379-
$callback['error'] = new WP_Error( 'unknown_lambda', __( 'Unable to determine source of lambda function', 'query-monitor' ) );
380-
}
370+
if ( 0 === strpos( $name, 'lambda_' ) ) {
371+
// Just use the lambda name as-is, these are from deprecated create_function()
372+
$callback['name'] = $name . '()';
373+
$callback['callback_type'] = 'lambda';
374+
unset( $callback['file'], $callback['line'] );
381375
}
382376

383377
if ( ! empty( $callback['file'] ) ) {
@@ -388,6 +382,7 @@ public static function populate_callback( array $callback ) {
388382
} catch ( ReflectionException $e ) {
389383

390384
$callback['error'] = new WP_Error( 'reflection_exception', $e->getMessage() );
385+
$callback['callback_type'] = 'unknown';
391386

392387
}
393388

@@ -397,6 +392,45 @@ public static function populate_callback( array $callback ) {
397392

398393
}
399394

395+
/**
396+
* Generate the translated callback name from callback properties.
397+
*
398+
* @param array<string, mixed> $callback Callback data array.
399+
* @return string Translated callback name.
400+
*/
401+
public static function get_callback_name( array $callback ) {
402+
if ( isset( $callback['name'] ) ) {
403+
return $callback['name'];
404+
}
405+
406+
if ( ! isset( $callback['callback_type'] ) ) {
407+
return '';
408+
}
409+
410+
switch ( $callback['callback_type'] ) {
411+
case 'closure':
412+
return sprintf(
413+
/* translators: A closure is an anonymous PHP function. 1: Line number, 2: File name */
414+
__( 'Closure on line %1$d of %2$s', 'query-monitor' ),
415+
$callback['start_line'],
416+
$callback['display_file']
417+
);
418+
419+
case 'unknown_closure':
420+
/* translators: A closure is an anonymous PHP function */
421+
return __( 'Unknown closure', 'query-monitor' );
422+
423+
case 'function':
424+
case 'method':
425+
case 'static_method':
426+
case 'invokable':
427+
case 'lambda':
428+
case 'unknown':
429+
default:
430+
return $callback['name'] ?? '';
431+
}
432+
}
433+
400434
/**
401435
* @return bool
402436
*/

output/Html.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,5 @@ public static function has_clickable_links() {
646646
return ( false !== self::get_file_link_format() );
647647
}
648648

649+
649650
}

output/html/block_editor.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,20 +252,20 @@ protected static function render_block( $i, array $block, QM_Data_Block_Editor $
252252
if ( isset( $block['callback']['file'] ) ) {
253253
if ( self::has_clickable_links() ) {
254254
echo '<td class="qm-nowrap qm-ltr' . esc_attr( $class ) . '">';
255-
echo self::output_filename( $block['callback']['name'], $block['callback']['file'], $block['callback']['line'] ); // WPCS: XSS ok.
255+
echo self::output_filename( QM_Util::get_callback_name( $block['callback'] ), $block['callback']['file'], $block['callback']['line'] ); // WPCS: XSS ok.
256256
echo '</td>';
257257
} else {
258258
echo '<td class="qm-nowrap qm-ltr qm-has-toggle' . esc_attr( $class ) . '">';
259259
echo self::build_toggler(); // WPCS: XSS ok;
260260
echo '<ol>';
261261
echo '<li>';
262-
echo self::output_filename( $block['callback']['name'], $block['callback']['file'], $block['callback']['line'] ); // WPCS: XSS ok.
262+
echo self::output_filename( QM_Util::get_callback_name( $block['callback'] ), $block['callback']['file'], $block['callback']['line'] ); // WPCS: XSS ok.
263263
echo '</li>';
264264
echo '</ol></td>';
265265
}
266266
} else {
267267
echo '<td class="qm-ltr qm-nowrap' . esc_attr( $class ) . '">';
268-
echo '<code>' . esc_html( $block['callback']['name'] ) . '</code>';
268+
echo '<code>' . esc_html( QM_Util::get_callback_name( $block['callback'] ) ) . '</code>';
269269

270270
if ( isset( $block['callback']['error'] ) ) {
271271
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

output/html/hooks.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,20 @@ public static function output_hook_table( array $hooks, bool $all_hooks ) {
185185
if ( isset( $action['callback']['file'] ) ) {
186186
if ( self::has_clickable_links() ) {
187187
echo '<td class="qm-nowrap qm-ltr' . esc_attr( $class ) . '">';
188-
echo self::output_filename( $action['callback']['name'], $action['callback']['file'], $action['callback']['line'] ); // WPCS: XSS ok.
188+
echo self::output_filename( QM_Util::get_callback_name( $action['callback'] ), $action['callback']['file'], $action['callback']['line'] ); // WPCS: XSS ok.
189189
echo '</td>';
190190
} else {
191191
echo '<td class="qm-nowrap qm-ltr qm-has-toggle' . esc_attr( $class ) . '">';
192192
echo self::build_toggler(); // WPCS: XSS ok;
193193
echo '<ol>';
194194
echo '<li>';
195-
echo self::output_filename( $action['callback']['name'], $action['callback']['file'], $action['callback']['line'] ); // WPCS: XSS ok.
195+
echo self::output_filename( QM_Util::get_callback_name( $action['callback'] ), $action['callback']['file'], $action['callback']['line'] ); // WPCS: XSS ok.
196196
echo '</li>';
197197
echo '</ol></td>';
198198
}
199199
} else {
200200
echo '<td class="qm-ltr qm-nowrap' . esc_attr( $class ) . '">';
201-
echo '<code>' . esc_html( $action['callback']['name'] ) . '</code>';
201+
echo '<code>' . esc_html( QM_Util::get_callback_name( $action['callback'] ) ) . '</code>';
202202

203203
if ( isset( $action['callback']['error'] ) ) {
204204
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

tests/integration/CallbacksTest.php

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,17 @@ public function testCallbackIsCorrectlyPopulatedWithClosure(): void {
114114

115115
$ref = new \ReflectionFunction( $function );
116116
$actual = \QM_Util::populate_callback( $callback );
117-
$name = sprintf(
118-
'Closure on line %1$d of %2$s',
119-
$ref->getStartLine(),
120-
'wp-content/plugins/query-monitor/tests/integration/includes/dummy-closures.php'
121-
);
122117

123-
self::assertArrayHasKey( 'name', $actual );
118+
// Test deferred translation properties instead of translated name
119+
self::assertArrayHasKey( 'callback_type', $actual );
120+
self::assertArrayHasKey( 'start_line', $actual );
121+
self::assertArrayHasKey( 'display_file', $actual );
124122
self::assertArrayHasKey( 'file', $actual );
125123
self::assertArrayHasKey( 'line', $actual );
126-
self::assertSame( $name, $actual['name'] );
127-
self::assertSame( $ref->getFileName(), $actual['file'] );
124+
self::assertSame( 'closure', $actual['callback_type'] );
125+
self::assertSame( $ref->getStartLine(), $actual['start_line'] );
126+
self::assertSame( 'wp-content/plugins/query-monitor/tests/integration/includes/dummy-closures.php', $actual['display_file'] );
127+
self::assertSame( $ref->getFileName(), $actual['file'] );
128128
self::assertSame( $ref->getStartLine(), $actual['line'] );
129129

130130
}
@@ -214,4 +214,56 @@ public function testCallbackIsCorrectlyPopulatedWithInvalidStaticClassString():
214214

215215
}
216216

217+
public function testGetCallbackNameWithName(): void {
218+
$callback = array(
219+
'name' => 'test_function()',
220+
'callback_type' => 'function'
221+
);
222+
223+
$result = \QM_Util::get_callback_name( $callback );
224+
225+
self::assertSame( 'test_function()', $result );
226+
}
227+
228+
public function testGetCallbackNameWithClosure(): void {
229+
$callback = array(
230+
'callback_type' => 'closure',
231+
'start_line' => 42,
232+
'display_file' => 'test.php'
233+
);
234+
235+
$result = \QM_Util::get_callback_name( $callback );
236+
237+
self::assertStringContainsString( 'Closure on line 42 of test.php', $result );
238+
}
239+
240+
public function testGetCallbackNameWithUnknownClosure(): void {
241+
$callback = array(
242+
'callback_type' => 'unknown_closure'
243+
);
244+
245+
$result = \QM_Util::get_callback_name( $callback );
246+
247+
self::assertStringContainsString( 'Unknown closure', $result );
248+
}
249+
250+
public function testGetCallbackNameWithoutCallbackType(): void {
251+
$callback = array();
252+
253+
$result = \QM_Util::get_callback_name( $callback );
254+
255+
self::assertSame( '', $result );
256+
}
257+
258+
public function testGetCallbackNameWithUnknownType(): void {
259+
$callback = array(
260+
'callback_type' => 'unknown',
261+
'name' => 'unknown_callback()'
262+
);
263+
264+
$result = \QM_Util::get_callback_name( $callback );
265+
266+
self::assertSame( 'unknown_callback()', $result );
267+
}
268+
217269
}

0 commit comments

Comments
 (0)