Skip to content
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,11 @@ add_filter('optml_dont_replace_url', function( $old, $url ) {

}, 10, 2);
</code>

= Can I keep the sizes attribute I wrote for an image ? =

Yes. Add the `data-optml-keep-sizes` attribute to the image tag and the page profiler won't add its own entries to the image's `sizes` attribute. Images lazy loaded by Optimole don't keep `srcset` or `sizes`, since the lazy loader doesn't need them. To keep the existing `sizes` attribute on every image, add this snippet:

<code>
add_filter( 'optml_keep_existing_sizes', '__return_true' );
</code>
28 changes: 25 additions & 3 deletions inc/attachment_cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
*/
class Optml_Attachment_Cache {
const CACHE_GROUP = 'om_att';
/**
* Most URLs kept in the in-memory map during one request.
*
* A page with many distinct image URLs would otherwise grow the map for the whole request.
* When it is full, the older half is dropped; those URLs are still in the persistent cache.
*/
const MAX_MEMORY_ENTRIES = 2000;
/**
* Local cache map.
*
Expand Down Expand Up @@ -32,10 +39,10 @@ public static function get_cached_attachment_id( $url ) {
return self::$cache_map[ $cache_key ];
}

$value = wp_using_ext_object_cache()
$value = wp_using_ext_object_cache()
? wp_cache_get( $cache_key, self::CACHE_GROUP )
: get_transient( self::CACHE_GROUP . $cache_key );
self::$cache_map[ $cache_key ] = $value;
self::remember( $cache_key, $value );

return $value;
}
Expand All @@ -51,7 +58,7 @@ public static function get_cached_attachment_id( $url ) {
public static function set_cached_attachment_id( $url, $id ) {
$cache_key = self::get_cache_key( $url );
// We cache also in memory to avoid calling DB every time when not using Object Cache.
self::$cache_map[ $cache_key ] = $id;
self::remember( $cache_key, $id );
// If the ID is not found we cache for 10 minutes, otherwise for a week.
// We try to reduce the cache time when is not found to
// avoid caching for situation when this might be temporary.
Expand All @@ -61,6 +68,21 @@ public static function set_cached_attachment_id( $url, $id ) {
: set_transient( self::CACHE_GROUP . $cache_key, $id, $expiration );
}

/**
* Keep a value in the in-memory map, dropping the older half when it is full.
*
* @param string $cache_key The cache key.
* @param int|numeric-string|false $value The attachment ID (a numeric string when read back from a transient), or false when it is not cached.
*
* @return void
*/
private static function remember( $cache_key, $value ) {
if ( ! isset( self::$cache_map[ $cache_key ] ) && count( self::$cache_map ) >= self::MAX_MEMORY_ENTRIES ) {
self::$cache_map = array_slice( self::$cache_map, (int) ( self::MAX_MEMORY_ENTRIES / 2 ), null, true );
}
self::$cache_map[ $cache_key ] = $value;
}

/**
* Generate cache key for URL.
*
Expand Down
48 changes: 47 additions & 1 deletion inc/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ final class Optml_Manager {
*/
private static $ob_processed = false;

/**
* Reads the last PHP error; tests swap it to simulate a fatal error.
*
* @var callable(): (array{type: int, message: string, file: string, line: int}|null)
*/
private static $error_reader = 'error_get_last';

/**
* Class instance method.
*
Expand Down Expand Up @@ -698,7 +705,9 @@ public static function parse_images_from_html( $content ) {
if ( OPTML_DEBUG ) {
do_action( 'optml_log', 'Content to parse images from: ' . $content );
}
$regex = '/(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag>(?:<noscript\s*>\s*)?<img[^>]*?\s?(?:' . implode( '|', array_merge( [ 'src' ], Optml_Tag_Replacer::possible_src_attributes() ) ) . ')=["\'\\\\]*?(?P<img_url>[' . Optml_Config::$chars . ']{10,}).*?>(?:\s*<\/noscript\s*>)?){1}(?:\s*<\/a>)?/ismu';
// A lazy-load attribute (e.g. data-src) holding a data: placeholder is skipped, so the real src is used.
// A data: src is still matched: the image then belongs to another lazy-load script.
$regex = '/(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag>(?:<noscript\s*>\s*)?<img[^>]*?(?:\ssrc=["\'\\\\]*?|\s?(?:' . implode( '|', array_merge( [ 'src' ], Optml_Tag_Replacer::possible_src_attributes() ) ) . ')=["\'\\\\]*?(?!data:))(?P<img_url>[' . Optml_Config::$chars . ']{10,}).*?>(?:\s*<\/noscript\s*>)?){1}(?:\s*<\/a>)?/ismu';

if ( preg_match_all( $regex, $content, $images, PREG_OFFSET_CAPTURE ) ) {
if ( OPTML_DEBUG ) {
Expand Down Expand Up @@ -983,6 +992,9 @@ public function handle_buffer_fallback( $content, $phase = 0 ) {
if ( self::$ob_processed || $content === '' ) {
return $content;
}
if ( self::request_had_fatal_error() ) {
return $content;
}
if ( ! $this->captures_at_shutdown() ) {
try {
return $this->replace_content( $content, self::is_ajax_request() );
Expand Down Expand Up @@ -1049,6 +1061,36 @@ public function close_final_buffer() {
$this->capture_and_process_buffer( false );
}

/**
* Whether the request already died with a fatal error.
*
* A fatal error (memory exhausted, time limit) that hits while MySQL is still
* sending a query's rows leaves the connection out of sync, so every later
* query in the request fails with "Commands out of sync". The page is then
* passed through unprocessed, without the profiler and cache reads that would
* each log that error.
*
* @param array<string, mixed>|null $error The last error, from error_get_last().
*
* @return bool
*/
public static function is_fatal_error( $error ) {
if ( ! is_array( $error ) || ! isset( $error['type'] ) ) {
return false;
}

return in_array( (int) $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ], true );
}

/**
* Whether the current request already hit a fatal error.
*
* @return bool
*/
private static function request_had_fatal_error() {
return self::is_fatal_error( call_user_func( self::$error_reader ) );
}

/**
* Capture our buffer, process it outside the display-handler context and echo the result.
*
Expand Down Expand Up @@ -1085,6 +1127,10 @@ private function capture_and_process_buffer( $is_page = true ) {
*/
$html = apply_filters( 'optml_captured_page_html', $html );
}
if ( self::request_had_fatal_error() ) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- full page HTML, passed through unprocessed.
return true;
}
echo $this->replace_content( $html, self::is_ajax_request() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- full page HTML, escaping would break the page.
}
return true;
Expand Down
Loading
Loading