Skip to content
Open

Release #1195

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
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>
24 changes: 12 additions & 12 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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