From 892e679d7d4bd54fe45aa679a18ec53e69f06d3f Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Wed, 3 Oct 2018 15:44:40 +0300 Subject: [PATCH 01/55] chore: added support for whitelist and generate url w/o. tokens --- inc/replacer.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/inc/replacer.php b/inc/replacer.php index 39aaba41..99bfdd03 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -47,6 +47,13 @@ class Optml_Replacer { */ protected $cdn_secret = null; + /** + * Domains Whitelist. + * + * @var array + */ + protected $whitelist = array(); + /** * Defines which is the maximum width accepted in the optimization process. * @@ -158,6 +165,7 @@ protected function set_properties() { return; } $this->cdn_secret = $cdn_secret; + $this->whitelist = isset( $service_data['whitelist'] ) ? $service_data['whitelist'] : array(); $this->cdn_url = sprintf( 'https://%s.%s', strtolower( $cdn_key ), @@ -378,6 +386,20 @@ protected function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'heig ); ksort( $payload ); + if( ! empty( $this->whitelist ) ) { + $new_url = sprintf( + '%s/%s/%s/%s/%s/%s/%s', + $this->cdn_url, + (string) $args['width'], + (string) $args['height'], + (string) $compress_level, + $scheme, + $path + ); + + return $new_url; + } + $values = array_values( $payload ); $payload = implode( '', $values ); $hash = hash_hmac( 'md5', $payload, $this->cdn_secret ); From a682d56c8b30d9f5bd220e0a2ae8b16a8e001b97 Mon Sep 17 00:00:00 2001 From: selu91 Date: Mon, 8 Oct 2018 15:47:09 +0300 Subject: [PATCH 02/55] Improves srcset generation. --- inc/replacer.php | 54 ++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/inc/replacer.php b/inc/replacer.php index 0a7fd542..4620672d 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -7,13 +7,6 @@ * @author Optimole */ class Optml_Replacer { - /** - * Cached object instance. - * - * @var Optml_Replacer - */ - protected static $instance = null; - /** * A list of allowd extensions. * @@ -25,7 +18,12 @@ class Optml_Replacer { 'webp' => 'image/webp', 'svg' => 'image/svg+xml', ); - + /** + * Cached object instance. + * + * @var Optml_Replacer + */ + protected static $instance = null; /** * Holds an array of image sizes. * @@ -410,9 +408,7 @@ public function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'height' $args['height'] = 'auto'; } } - if ( $args['width'] !== 'auto' && $args['height'] !== 'auto' ) { - $path = str_replace( '-' . $args['width'] . 'x' . $args['height'] . '.', '.', $path ); - } + $path = $this->strip_size_from_path( $path, $args['width'], $args['height'] ); $payload = array( 'path' => $this->urlception_encode( $path ), 'scheme' => $scheme, @@ -488,6 +484,23 @@ private function normalize_quality( $quality ) { return 60; } + /** + * Strip sizes attributes from url path. + * + * @param string $path Raw path. + * @param int $width Width. + * @param int $height Height. + * + * @return mixed Stripped path. + */ + private function strip_size_from_path( $path, $width, $height ) { + if ( $width !== 'auto' && $height !== 'auto' ) { + $path = str_replace( '-' . $width . 'x' . $height . '.', '.', $path ); + } + + return $path; + } + /** * Ensures that an url parameter can stand inside an url. * @@ -514,7 +527,6 @@ public function filter_the_content( $content ) { if ( empty( $images ) ) { return $content; // simple. no images } - $image_sizes = self::image_sizes(); foreach ( $images[0] as $index => $tag ) { $width = $height = false; @@ -609,20 +621,21 @@ protected static function parse_images_from_html( $content ) { /** * Replace image URLs in the srcset attributes and in case there is a resize in action, also replace the sizes. * - * @param array $sources Array of image sources. - * @param array $size_array Array of width and height values in pixels (in that order). - * @param array $image_src The 'src' of the image. - * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. - * @param int $attachment_id Image attachment ID. + * @param array $sources Array of image sources. + * @param array $size_array Array of width and height values in pixels (in that order). + * @param string $image_src The 'src' of the image. + * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. + * @param int $attachment_id Image attachment ID. * * @return array */ - public function filter_srcset_attr( $sources = array(), $size_array = array(), $image_src = array(), $image_meta = array(), $attachment_id = 0 ) { + public function filter_srcset_attr( $sources = array(), $size_array = array(), $image_src = '', $image_meta = array(), $attachment_id = 0 ) { if ( ! is_array( $sources ) ) { return $sources; } $used = array(); $new_sources = array(); + var_dump($sources); foreach ( $sources as $i => $source ) { list( $width, $height ) = self::parse_dimensions_from_filename( $source['url'] ); @@ -634,8 +647,9 @@ public function filter_srcset_attr( $sources = array(), $size_array = array(), $ $height = $image_meta['height']; } - $new_sizes = $this->validate_image_sizes( $width, $height ); - $new_url = $this->get_imgcdn_url( $source['url'], $new_sizes ); + $source['url'] = $this->strip_size_from_path( $source['url'], $width, $height ); + $new_sizes = $this->validate_image_sizes( $width, $height ); + $new_url = $this->get_imgcdn_url( $source['url'], $new_sizes ); if ( isset( $used[ md5( $new_url ) ] ) ) { continue; } From 3300bda1fc8923a23ac2ad0feadb7765522ac2ba Mon Sep 17 00:00:00 2001 From: selu91 Date: Mon, 8 Oct 2018 16:00:41 +0300 Subject: [PATCH 03/55] adds full image to comparison. --- inc/replacer.php | 1 - inc/rest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/replacer.php b/inc/replacer.php index 4620672d..0469891a 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -635,7 +635,6 @@ public function filter_srcset_attr( $sources = array(), $size_array = array(), $ } $used = array(); $new_sources = array(); - var_dump($sources); foreach ( $sources as $i => $source ) { list( $width, $height ) = self::parse_dimensions_from_filename( $source['url'] ); diff --git a/inc/rest.php b/inc/rest.php index 4c69fe8b..903e42a1 100644 --- a/inc/rest.php +++ b/inc/rest.php @@ -207,7 +207,7 @@ public function get_sample_rate( WP_REST_Request $request ) { $width = 'auto'; $height = 'auto'; - $size = 'medium_large'; + $size = 'full'; if ( isset( $metadata['sizes'] ) && isset( $metadata['sizes'][ $size ] ) ) { $width = $metadata['sizes'][ $size ]['width']; $height = $metadata['sizes'][ $size ]['height']; From 91b37fb1ecbfe5875f774b51140ca3573027adc1 Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Tue, 9 Oct 2018 16:26:00 +0300 Subject: [PATCH 04/55] chore: primary lazy load functionality --- assets/replacer.js | 27 ++++++++++++++++++++ inc/api.php | 3 ++- inc/replacer.php | 63 +++++++++++++++++++++++++++++++--------------- 3 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 assets/replacer.js diff --git a/assets/replacer.js b/assets/replacer.js new file mode 100644 index 00000000..261c9a5f --- /dev/null +++ b/assets/replacer.js @@ -0,0 +1,27 @@ + +let images = document.getElementsByTagName('img'); + +for( let image of images ) { + + let containerWidth = image.clientWidth + let optWidth = image.dataset.optWidth + let optHeight = image.dataset.optHeight + + let containerHeight = parseInt( optHeight/optWidth * containerWidth ) + + image.style.width = containerWidth + "px"; + image.style.height = containerHeight + "px"; + + let optSrc = image.dataset.optSrc + + console.log( optSrc ); + + let downloadingImage = new Image(); + + downloadingImage.onload = function(){ + if ( this.complete ) { + image.src = this.src; + } + }; + downloadingImage.src = optSrc; +} \ No newline at end of file diff --git a/inc/api.php b/inc/api.php index c966e22d..170ac95e 100644 --- a/inc/api.php +++ b/inc/api.php @@ -13,7 +13,8 @@ final class Optml_Api { * * @var string Api root. */ - private $api_root = 'https://dashboard.optimole.com/api/optml/v1/'; + //private $api_root = 'https://dashboard.optimole.com/api/optml/v1/'; + private $api_root = 'http://localhost:8000/api/optml/v1/'; /** * Hold the user api key. * diff --git a/inc/replacer.php b/inc/replacer.php index 99bfdd03..40260a1f 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -54,6 +54,8 @@ class Optml_Replacer { */ protected $whitelist = array(); + protected $lazyload = true; + /** * Defines which is the maximum width accepted in the optimization process. * @@ -118,6 +120,8 @@ function init() { add_filter( 'init', array( $this, 'filter_options_and_mods' ) ); add_action( 'template_redirect', array( $this, 'init_html_replacer' ), PHP_INT_MAX ); add_action( 'get_post_metadata', array( $this, 'replace_meta' ), PHP_INT_MAX, 4 ); + + wp_enqueue_script( 'test_opt', OPTML_URL . 'assets/replacer.js', array(), false, true ); } /** @@ -166,11 +170,13 @@ protected function set_properties() { } $this->cdn_secret = $cdn_secret; $this->whitelist = isset( $service_data['whitelist'] ) ? $service_data['whitelist'] : array(); - $this->cdn_url = sprintf( - 'https://%s.%s', - strtolower( $cdn_key ), - 'i.optimole.com' - ); +// $this->cdn_url = sprintf( +// 'https://%s.%s', +// strtolower( $cdn_key ), +// 'i.optimole.com' +// ); + + $this->cdn_url = 'http://test.opt.loc'; } /** @@ -369,7 +375,7 @@ protected function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'heig $compress_level = 55; // this will authorize the image $url_parts = explode( '://', $url ); - $scheme = $url_parts[0]; + $scheme = trim( $url_parts[0] ); $path = $url_parts[1]; if ( $args['width'] !== 'auto' ) { $args['width'] = round( $args['width'], 0 ); @@ -404,6 +410,11 @@ protected function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'heig $payload = implode( '', $values ); $hash = hash_hmac( 'md5', $payload, $this->cdn_secret ); +// if ( $this->lazyload ) { +// $args['width'] = 1; +// $args['height'] = 1; +// } + $new_url = sprintf( '%s/%s/%s/%s/%s/%s/%s', $this->cdn_url, @@ -459,6 +470,7 @@ protected function urlception_encode( $url ) { */ public function filter_the_content( $content ) { $images = self::parse_images_from_html( $content ); + //var_dump( $images ); die(); if ( empty( $images ) ) { return $content; // simple. no images @@ -474,14 +486,14 @@ public function filter_the_content( $content ) { continue; } - if ( false !== strpos( $src, 'i.optimole.com' ) ) { - continue; // we already have this - } - - // we handle only images uploaded to this site. - if ( false === strpos( $src, $this->upload_dir ) ) { - continue; - } +// if ( false !== strpos( $src, 'i.optimole.com' ) ) { +// continue; // we already have this +// } +// +// // we handle only images uploaded to this site. +// if ( false === strpos( $src, $this->upload_dir ) ) { +// continue; +// } // try to get the declared sizes from the img tag if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) ) { @@ -517,11 +529,20 @@ public function filter_the_content( $content ) { } } - // replace the new sizes - $new_tag = str_replace( 'width="' . $width . '"', 'width="' . $new_sizes['width'] . '"', $new_tag ); - $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); - // replace the new url - $new_tag = str_replace( 'src="' . $src . '"', 'src="' . $new_url . '"', $new_tag ); + if ( $this->lazyload ) { + $one_px_url = $this->get_imgcdn_url( $src, [ 'width' => 1, 'height' => 1 ] ); + // replace the new sizes + $new_tag = str_replace( 'width="' . $width . '"', 'width="' . $new_sizes['width'] . '" data-opt-width="' . $width . '"', $new_tag ); + $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '" data-opt-height="' . $height . '"', $new_tag ); + // replace the new url + $new_tag = str_replace( 'src="' . $src . '"', 'src="' . $one_px_url . '" data-opt-src="'. $new_url .'"', $new_tag ); + } else { + // replace the new sizes + $new_tag = str_replace( 'width="' . $width . '"', 'width="' . $new_sizes['width'] . '"', $new_tag ); + $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); + // replace the new url + $new_tag = str_replace( 'src="' . $src . '"', 'src="' . $new_url . '"', $new_tag ); + } $content = str_replace( $tag, $new_tag, $content ); } @@ -570,6 +591,9 @@ public function filter_srcset_attr( $sources = array(), $size_array = array(), $ if ( ! is_array( $sources ) ) { return $sources; } + if ( $this->lazyload ) { + return array(); + } $used = array(); $new_sources = array(); foreach ( $sources as $i => $source ) { @@ -773,7 +797,6 @@ public function replace_urls( $html, $context = 'raw' ) { ); $urls = array_map( array( $this, 'get_imgcdn_url' ), $urls ); - return str_replace( array_keys( $urls ), array_values( $urls ), $html ); } From 9b094d7bf15830646ecb38e3f2bedc08cc822284 Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Wed, 10 Oct 2018 17:01:24 +0300 Subject: [PATCH 05/55] chore: lazy load and lazy display images --- assets/replacer.css | 12 ++++++++++++ assets/replacer.js | 15 +++++++++++---- inc/replacer.php | 15 +++++++++------ 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 assets/replacer.css diff --git a/assets/replacer.css b/assets/replacer.css new file mode 100644 index 00000000..e4f8e749 --- /dev/null +++ b/assets/replacer.css @@ -0,0 +1,12 @@ +.optml_lazyload_img { + animation: lazyloadTransition 1s; +} + +@-webkit-keyframes lazyloadTransition { + 0% { + filter: grayscale(0.9) blur(10px); + } + 100% { + filter: grayscale(0) blur(0); + } +} \ No newline at end of file diff --git a/assets/replacer.js b/assets/replacer.js index 261c9a5f..eda5bcad 100644 --- a/assets/replacer.js +++ b/assets/replacer.js @@ -4,8 +4,8 @@ let images = document.getElementsByTagName('img'); for( let image of images ) { let containerWidth = image.clientWidth - let optWidth = image.dataset.optWidth - let optHeight = image.dataset.optHeight + let optWidth = image.attributes.width.value + let optHeight = image.attributes.height.value let containerHeight = parseInt( optHeight/optWidth * containerWidth ) @@ -14,14 +14,21 @@ for( let image of images ) { let optSrc = image.dataset.optSrc - console.log( optSrc ); + console.log( containerWidth ); + console.log( containerHeight ); + console.log( optWidth ); + console.log( optHeight ); let downloadingImage = new Image(); - downloadingImage.onload = function(){ + downloadingImage.onload = async function(){ if ( this.complete ) { + image.classList.add('optml_lazyload_img'); image.src = this.src; } }; + optSrc = optSrc.replace(`/${optWidth}/`, `/${containerWidth}/`) + optSrc = optSrc.replace(`/${optHeight}/`, `/${containerHeight}/`) + console.log( optSrc ); downloadingImage.src = optSrc; } \ No newline at end of file diff --git a/inc/replacer.php b/inc/replacer.php index 40260a1f..2a8d3b37 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -122,6 +122,7 @@ function init() { add_action( 'get_post_metadata', array( $this, 'replace_meta' ), PHP_INT_MAX, 4 ); wp_enqueue_script( 'test_opt', OPTML_URL . 'assets/replacer.js', array(), false, true ); + wp_enqueue_style( 'test_opt_style', OPTML_URL . 'assets/replacer.css' ); } /** @@ -394,7 +395,7 @@ protected function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'heig if( ! empty( $this->whitelist ) ) { $new_url = sprintf( - '%s/%s/%s/%s/%s/%s/%s', + '%s/%s/%s/%s/%s/%s', $this->cdn_url, (string) $args['width'], (string) $args['height'], @@ -486,10 +487,11 @@ public function filter_the_content( $content ) { continue; } -// if ( false !== strpos( $src, 'i.optimole.com' ) ) { + //if ( false !== strpos( $src, 'i.optimole.com' ) ) { +// if ( false !== strpos( $src, 'opt.loc' ) ) { // continue; // we already have this // } -// + // // we handle only images uploaded to this site. // if ( false === strpos( $src, $this->upload_dir ) ) { // continue; @@ -530,10 +532,11 @@ public function filter_the_content( $content ) { } if ( $this->lazyload ) { - $one_px_url = $this->get_imgcdn_url( $src, [ 'width' => 1, 'height' => 1 ] ); + $one_px_url = "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; + // replace the new sizes - $new_tag = str_replace( 'width="' . $width . '"', 'width="' . $new_sizes['width'] . '" data-opt-width="' . $width . '"', $new_tag ); - $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '" data-opt-height="' . $height . '"', $new_tag ); + $new_tag = str_replace( 'width="' . $width . '"', 'width="' . $new_sizes['width'] . '"', $new_tag ); + $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); // replace the new url $new_tag = str_replace( 'src="' . $src . '"', 'src="' . $one_px_url . '" data-opt-src="'. $new_url .'"', $new_tag ); } else { From 00647f055dea9c82f429020dcc6e24b335f692db Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Wed, 10 Oct 2018 17:51:06 +0300 Subject: [PATCH 06/55] chore: added toggle for lazyload in settings --- assets/js/bundle.js | 333 ++++++++++++++++-------------- assets/js/bundle.min.js | 8 +- assets/vue/components/options.vue | 27 +++ inc/admin.php | 2 + inc/replacer.php | 18 +- inc/rest.php | 1 + inc/settings.php | 19 ++ 7 files changed, 248 insertions(+), 160 deletions(-) diff --git a/assets/js/bundle.js b/assets/js/bundle.js index f40b6798..893a99a7 100644 --- a/assets/js/bundle.js +++ b/assets/js/bundle.js @@ -67,56 +67,56 @@ /* 0 */ /***/ (function(module, exports) { -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ -// css base code, injected by the css-loader -module.exports = function() { - var list = []; - - // return the list of modules as css string - list.toString = function toString() { - var result = []; - for(var i = 0; i < this.length; i++) { - var item = this[i]; - if(item[2]) { - result.push("@media " + item[2] + "{" + item[1] + "}"); - } else { - result.push(item[1]); - } - } - return result.join(""); - }; - - // import a list of modules into the list - list.i = function(modules, mediaQuery) { - if(typeof modules === "string") - modules = [[null, modules, ""]]; - var alreadyImportedModules = {}; - for(var i = 0; i < this.length; i++) { - var id = this[i][0]; - if(typeof id === "number") - alreadyImportedModules[id] = true; - } - for(i = 0; i < modules.length; i++) { - var item = modules[i]; - // skip already imported module - // this implementation is not 100% perfect for weird media query combinations - // when a module is imported multiple times with different media queries. - // I hope this will never occur (Hey this way we have smaller bundles) - if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { - if(mediaQuery && !item[2]) { - item[2] = mediaQuery; - } else if(mediaQuery) { - item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; - } - list.push(item); - } - } - }; - return list; -}; +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +// css base code, injected by the css-loader +module.exports = function() { + var list = []; + + // return the list of modules as css string + list.toString = function toString() { + var result = []; + for(var i = 0; i < this.length; i++) { + var item = this[i]; + if(item[2]) { + result.push("@media " + item[2] + "{" + item[1] + "}"); + } else { + result.push(item[1]); + } + } + return result.join(""); + }; + + // import a list of modules into the list + list.i = function(modules, mediaQuery) { + if(typeof modules === "string") + modules = [[null, modules, ""]]; + var alreadyImportedModules = {}; + for(var i = 0; i < this.length; i++) { + var id = this[i][0]; + if(typeof id === "number") + alreadyImportedModules[id] = true; + } + for(i = 0; i < modules.length; i++) { + var item = modules[i]; + // skip already imported module + // this implementation is not 100% perfect for weird media query combinations + // when a module is imported multiple times with different media queries. + // I hope this will never occur (Hey this way we have smaller bundles) + if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { + if(mediaQuery && !item[2]) { + item[2] = mediaQuery; + } else if(mediaQuery) { + item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; + } + list.push(item); + } + } + }; + return list; +}; /***/ }), @@ -11342,27 +11342,27 @@ Vue.compile = compileToFunctions; /* 3 */ /***/ (function(module, exports) { -var g; - -// This works in non-strict mode -g = (function() { - return this; -})(); - -try { - // This works if eval is allowed (see CSP) - g = g || Function("return this")() || (1,eval)("this"); -} catch(e) { - // This works if the window reference is available - if(typeof window === "object") - g = window; -} - -// g can still be undefined, but nothing to do about it... -// We return undefined, instead of nothing here, so it's -// easier to handle this case. if(!global) { ...} - -module.exports = g; +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || Function("return this")() || (1,eval)("this"); +} catch(e) { + // This works if the window reference is available + if(typeof window === "object") + g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; /***/ }), @@ -11569,7 +11569,7 @@ if (false) {(function () { module.hot.accept() var hotAPI = require("vue-hot-reload-api") hotAPI.install(require("vue"), true) if (!hotAPI.compatible) return - var id = "/Users/selul/dev/optimole-wp/assets/vue/components/api-key-form.vue" + var id = "/var/www/html/wp-minions/wp-content/plugins/optimole-wp/assets/vue/components/api-key-form.vue" if (!module.hot.data) { hotAPI.createRecord(id, module.exports) } else { @@ -13466,7 +13466,7 @@ if (false) {(function () { module.hot.accept() var hotAPI = require("vue-hot-reload-api") hotAPI.install(require("vue"), true) if (!hotAPI.compatible) return - var id = "/Users/selul/dev/optimole-wp/assets/vue/components/main.vue" + var id = "/var/www/html/wp-minions/wp-content/plugins/optimole-wp/assets/vue/components/main.vue" if (!module.hot.data) { hotAPI.createRecord(id, module.exports) } else { @@ -13490,8 +13490,8 @@ if(content.locals) module.exports = content.locals; if(false) { // When the styles change, update the + '; + const WRONG_EXTENSION = ' http://example.org/wp-content/themes/twentyseventeen/assets/images/header.gif '; + const IMAGE_SIZE_DATA = ' + http://example.org/wp-content/uploads/optimole-wp/assets/img/logo-282x123.png + http://example.org/wp-content/plugins/optimole-wp/assets/img/test-282x123.png + '; + + const ELEMENTOR_DATA = '[{"id":"428f250c","elType":"section","settings":{"structure":"33","content_width":{"unit":"px","size":1140},"content_position":"middle","gap":"extended","padding":{"unit":"px","top":"10","right":"0","bottom":"10","left":"0","isLinked":false},"padding_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true}},"elements":[{"id":"1b041a88","elType":"column","settings":{"_column_size":25,"_inline_size":20.66,"_inline_size_tablet":25,"_inline_size_mobile":50,"content_position":"top"},"elements":[{"id":"34d685ef","elType":"widget","settings":{"image":{"id":36009,"url":"https:\/\/www.codeinwp.com\/wp-content\/uploads\/2018\/05\/codeinwp-logo.svg"},"image_size":"full","link_to":"custom","link":{"url":"https:\/\/www.codeinwp.com\/","is_external":"","nofollow":""},"align":"left","width":{"unit":"px","size":120},"space":{"unit":"%","size":100},"opacity":{"unit":"px","size":1},"_margin":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":false},"_element_id":"logo"},"elements":[],"widgetType":"image"}],"isInner":false},{"id":"437f5756","elType":"column","settings":{"_column_size":50,"_inline_size":71.992000000000004,"_inline_size_tablet":70,"_inline_size_mobile":40,"padding_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true}},"elements":[{"id":"3c7d3ebf","elType":"widget","settings":{"align_items":"right","pointer":"none","color_menu_item":"#0a4266","menu_typography_typography":"custom","menu_typography_font_weight":"bold","menu_typography_text_transform":"lowercase","color_menu_item_hover":"#ec4646","color_menu_item_active":"#ec4646","menu_typography_font_size":{"unit":"px","size":18},"_margin":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":false},"indicator":"none","dropdown":"mobile","full_width":"stretch","menu_typography_font_size_tablet":{"unit":"px","size":18},"padding_horizontal_menu_item_tablet":{"unit":"px","size":14},"toggle_size":{"unit":"px","size":25},"_padding":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":false},"color_dropdown_item":"#0a4266","color_dropdown_item_hover":"#ec4646","background_color_dropdown_item_hover":"rgba(0,0,0,0)","dropdown_typography_typography":"custom","dropdown_typography_font_family":"proxima-nova","dropdown_typography_font_size":{"unit":"px","size":16},"menu_typography_font_size_mobile":{"unit":"px","size":18},"dropdown_typography_font_size_mobile":{"unit":"px","size":25},"dropdown_typography_text_transform":"lowercase","menu":"main-menu-homepage"},"elements":[],"widgetType":"nav-menu"}],"isInner":false},{"id":"7fafd26c","elType":"column","settings":{"_column_size":25,"_inline_size":7.3479999999999999,"_inline_size_tablet":5,"_inline_size_mobile":2},"elements":[{"id":"8f16004","elType":"widget","settings":{"image":{"url":"https:\/\/www.codeinwp.com\/wp-content\/uploads\/2018\/05\/test.png","id":36135},"image_size":"custom","width":{"unit":"px","size":30},"space":{"unit":"%","size":30},"_element_id":"header-trigger"},"elements":[],"widgetType":"image"}],"isInner":false}],"isInner":false}]'; + + public static $sample_post; + public static $sample_attachement; + + public function setUp() { + parent::setUp(); + $settings = new Optml_Settings(); + $settings->update( 'service_data', [ + 'cdn_key' => 'test123', + 'cdn_secret' => '12345', + 'whitelist' => [ 'example.com' ], + + ] ); + + Optml_Replacer::instance()->init(); + + self::$sample_post = self::factory()->post->create( [ + 'post_title' => 'Test post', + 'post_content' => self::IMG_TAGS + ] + ); + self::$sample_attachement = self::factory()->attachment->create_upload_object( OPTML_PATH . 'assets/img/logo.png' ); + + } + + public function test_image_tags() { + + $found_images = Optml_Replacer::parse_images_from_html( self::IMG_TAGS ); + + $this->assertCount( 4, $found_images ); + $this->assertCount( 1, $found_images['img_url'] ); + + $replaced_content = Optml_Replacer::instance()->filter_the_content( self::IMG_TAGS ); + + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertContains( '/2000/', $replaced_content ); + $this->assertContains( '/1200/', $replaced_content ); + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertNotContains( 'http://example.org', $replaced_content ); + } + + public function test_optimization_url() { + $replaced_content = Optml_Replacer::instance()->replace_urls( self::IMG_TAGS ); + + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertNotContains( 'http://example.org', $replaced_content ); + + $replaced_content = Optml_Replacer::instance()->replace_urls( self::IMG_URLS ); + + $this->assertEquals( substr_count( $replaced_content, 'i.optimole.com' ), 3 ); + } + + public function test_style_replacement() { + $replaced_content = Optml_Replacer::instance()->replace_urls( self::CSS_STYLE ); + + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertNotContains( 'http://example.org', $replaced_content ); + + } + + public function test_non_allowed_extensions() { + $replaced_content = Optml_Replacer::instance()->replace_urls( ( self::CSS_STYLE . self::IMG_TAGS . self::WRONG_EXTENSION ) ); + $this->assertContains( 'i.optimole.com', $replaced_content ); + //Test if wrong extension is still present in the output. + $this->assertContains( 'http://example.org/wp-content/themes/twentyseventeen/assets/images/header.gif', $replaced_content ); + + } + + public function test_elementor_data() { + $replaced_content = Optml_Replacer::instance()->replace_urls( ( self::ELEMENTOR_DATA ), 'elementor' ); + $this->assertContains( 'i.optimole.com', $replaced_content ); + //Test if wrong extension is still present in the output. + $this->assertNotContains( "https:\/\/www.codeinwp.com\/wp-content", $replaced_content ); + } + + public function test_max_size_height() { + $new_url = Optml_Replacer::instance()->get_imgcdn_url( 'http://example.org/wp-content/themes/test/assets/images/header.png', [ + 'width' => 99999, + 'height' => 99999 + ] ); + $this->assertContains( 'i.optimole.com', $new_url ); + //Test if wrong extension is still present in the output. + $this->assertNotContains( '99999', $new_url ); + + } + + public function test_post_content() { + Optml_Replacer::instance()->init(); + + $content = apply_filters( 'the_content', get_post_field( 'post_content', self::$sample_post ) ); + + $this->assertContains( 'i.optimole.com', $content ); + } + + public function test_strip_image_size() { + $replaced_content = Optml_Replacer::instance()->replace_urls( self::IMAGE_SIZE_DATA ); + + //Test fake sample image size. + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertContains( '282x123', $replaced_content ); + + //Test valid wordpress image size, it should strip the size suffix. + $attachement_url = wp_get_attachment_image_src( self::$sample_attachement, 'medium' ); + $replaced_content = Optml_Replacer::instance()->replace_urls( $attachement_url[0] ); + + $this->assertNotContains( '282x123', $replaced_content ); + } + + + public function test_custom_domain() { + define( 'OPTML_SITE_MIRROR', 'https://mycnd.com' ); + Optml_Replacer::instance()->init(); + + $replaced_content = Optml_Replacer::instance()->replace_urls( self::IMG_TAGS ); + + //Test custom source. + $this->assertContains( 'i.optimole.com', $replaced_content ); + $this->assertNotContains( 'http://example.org', $replaced_content ); + $this->assertNotContains( 'example.org', $replaced_content ); + $this->assertContains( 'mycnd.com', $replaced_content ); + + } } \ No newline at end of file From 90969a5986a558a5f382592fabe58823ca952f67 Mon Sep 17 00:00:00 2001 From: selul Date: Thu, 1 Nov 2018 16:50:36 +0200 Subject: [PATCH 28/55] fix phpcs errors. --- inc/admin.php | 2 +- inc/api.php | 2 +- inc/replacer.php | 13 +++++++------ inc/rest.php | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index ddf5be0e..d8a1f2ec 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -51,7 +51,7 @@ public function frontend_scripts() { if ( ! $this->settings->use_lazyload() ) { return; } - wp_enqueue_script( 'optm_lazyload_replacer_js', "https://" . OPTML_JS_CDN . '/latest/optimole_lib' . ( ! OPTML_DEBUG ? '.min' : '' ) . '.js', array(), OPTML_VERSION, true ); + wp_enqueue_script( 'optm_lazyload_replacer_js', 'https://' . OPTML_JS_CDN . '/latest/optimole_lib' . ( ! OPTML_DEBUG ? '.min' : '' ) . '.js', array(), OPTML_VERSION, true ); } diff --git a/inc/api.php b/inc/api.php index c694060a..126504ab 100644 --- a/inc/api.php +++ b/inc/api.php @@ -57,7 +57,7 @@ private function request( $path, $method = 'GET', $params = array() ) { // Grab the url to which we'll be making the request. $url = $this->api_root; $headers = array( - 'Optml-Site' => get_site_url() + 'Optml-Site' => get_site_url(), ); if ( ! empty( $this->api_key ) ) { $headers['Authorization'] = 'Bearer ' . $this->api_key; diff --git a/inc/replacer.php b/inc/replacer.php index d5eabc99..f74b5109 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -23,7 +23,7 @@ class Optml_Replacer { * * @var string Base64 image. */ - public static $one_px_url = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + public static $one_px_url = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; /** * Cached object instance. * @@ -149,7 +149,7 @@ function init() { $this->lazyload = true; } - self::$site_mirror = defined( "OPTML_SITE_MIRROR" ) ? OPTML_SITE_MIRROR : ""; + self::$site_mirror = defined( 'OPTML_SITE_MIRROR' ) ? OPTML_SITE_MIRROR : ''; self::$siteurl = get_site_url(); add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), PHP_INT_MAX, 3 ); add_filter( 'the_content', array( $this, 'filter_the_content' ), PHP_INT_MAX ); @@ -476,7 +476,7 @@ public function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'height' if ( empty( $this->whitelist ) ) { $values = array_values( $payload ); $payload = implode( '', $values ); - $hash = sprintf( "/%s", hash_hmac( 'md5', $payload, $this->cdn_secret ) ); + $hash = sprintf( '/%s', hash_hmac( 'md5', $payload, $this->cdn_secret ) ); } $new_url = sprintf( '%s%s/%s/%s/%s/%s/%s', @@ -630,7 +630,7 @@ public static function parse_dimensions_from_filename( $src ) { /** * Checks if the file is a image size and return the full url. * - * @param string $src The image URL + * @param string $src The image URL. * * @return string **/ @@ -645,7 +645,7 @@ protected function strip_image_size_maybe( $src ) { } $file_path = substr( $stripped_src, strlen( $upload_dir['baseurl'] ) ); - if ( file_exists( $upload_dir["basedir"] . $file_path ) ) { + if ( file_exists( $upload_dir['basedir'] . $file_path ) ) { $src = $stripped_src; } } @@ -794,7 +794,8 @@ function ( $url ) { } return $new_url; - }, $urls + }, + $urls ); return str_replace( array_keys( $urls ), array_values( $urls ), $html ); diff --git a/inc/rest.php b/inc/rest.php index 8fe3fdb8..892898c7 100644 --- a/inc/rest.php +++ b/inc/rest.php @@ -140,7 +140,7 @@ public function connect( WP_REST_Request $request ) { $settings = new Optml_Settings(); $settings->update( 'service_data', $data ); $settings->update( 'api_key', $api_key ); - + return $this->response( $data ); } From d5bd6a127bc4dfcdc8f9cd50bb64f1f7881e176c Mon Sep 17 00:00:00 2001 From: selul Date: Thu, 1 Nov 2018 18:15:14 +0200 Subject: [PATCH 29/55] bump version. --- optimole-wp.php | 4 ++-- package.json | 2 +- themeisle-hash.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/optimole-wp.php b/optimole-wp.php index 812e0b85..21440582 100644 --- a/optimole-wp.php +++ b/optimole-wp.php @@ -2,7 +2,7 @@ /** * Plugin Name: Image optimization service by Optimole * Description: Complete handling of your website images. - * Version: 1.0.5 + * Version: 1.1.0 * Author: Optimole * Author URI: https://optimole.com * License: GPL-2.0+ @@ -45,7 +45,7 @@ function optml() { define( 'OPTML_URL', plugin_dir_url( __FILE__ ) ); define( 'OPTML_JS_CDN', 'd5jmkjjpb7yfg.cloudfront.net' ); define( 'OPTML_PATH', plugin_dir_path( __FILE__ ) ); - define( 'OPTML_VERSION', '1.0.5' ); + define( 'OPTML_VERSION', '1.1.0' ); define( 'OPTML_NAMESPACE', 'optml' ); define( 'OPTML_BASEFILE', __FILE__ ); if ( ! defined( 'OPTML_DEBUG' ) ) { diff --git a/package.json b/package.json index 2ca9d09c..5a79dc22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "optimole-wp", - "version": "1.0.5", + "version": "1.1.0", "description": "Image optimization & CDN by Optimole", "repository": { "type": "git", diff --git a/themeisle-hash.json b/themeisle-hash.json index 6034c027..6b5b3a32 100644 --- a/themeisle-hash.json +++ b/themeisle-hash.json @@ -1 +1 @@ -{"optimole-wp.php":"cca2ed00c0c1db13bcdace98e18feefd"} \ No newline at end of file +{"optimole-wp.php":"31cc25019bb631f81a5e92222753b2cb"} \ No newline at end of file From 3afb73d3a191aafe6794bdc062f0a8346d0d1de7 Mon Sep 17 00:00:00 2001 From: selul Date: Fri, 2 Nov 2018 14:59:57 +0200 Subject: [PATCH 30/55] Adds optin notice when redirect is not done. --- inc/admin.php | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/inc/admin.php b/inc/admin.php index d8a1f2ec..f34f750a 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -29,7 +29,10 @@ public function __construct() { $this->settings = new Optml_Settings(); add_action( 'admin_menu', array( $this, 'add_dashboard_page' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), PHP_INT_MIN ); + add_action( 'admin_notices', array( $this, 'add_notice' ) ); + add_filter( 'admin_body_class', array( $this, 'add_body_class' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) ); add_action( 'admin_bar_menu', array( $this, 'add_traffic_node' ), 9999 ); add_filter( 'wp_resource_hints', array( $this, 'add_dns_prefetch' ), 10, 2 ); @@ -43,6 +46,87 @@ public function __construct() { } } + /** + * Adds optimole optin class. + * + * @return string Optimole class. + */ + public function add_body_class( $classes ) { + + if ( ! $this->should_show_notice() ) { + return $classes; + } + $classes .= ' optimole-optin-show '; + + return $classes; + } + + /** + * Check if we should show the notice. + * + * @return bool Should show? + */ + public function should_show_notice() { + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return false; + } + + if ( is_network_admin() ) { + return false; + } + + if ( $this->settings->is_connected() ) { + return false; + } + $current_screen = get_current_screen(); + if ( empty( $current_screen ) ) { + return false; + } + static $allowed_base = array( + 'plugins' => true, + 'upload' => true, + 'media' => true, + 'themes' => true, + 'appearance_page_tgmpa-install-plugins' => true, + ); + $screen_slug = isset( $current_screen->parent_base ) ? $current_screen->parent_base : isset( $current_screen->base ) ? $current_screen->base : ''; + + if ( empty( $screen_slug ) || + ( ! isset( $allowed_base[ $screen_slug ] ) ) ) { + return false; + } + if ( ! current_user_can( 'manage_options' ) ) { + return false; + } + if ( ( get_option( 'optml_notice_optin', 'no' ) === 'yes' ) ) { + return false; + } + + return true; + } + + + /** + * Adds optin notice. + */ + public function add_notice() { + if ( ! $this->should_show_notice() ) { + return; + } + ?> +
+

', '', '', '' ); ?>

+

+ + +

+
+
@@ -169,6 +261,7 @@ public function render_dashboard_page() { * Enqueue scripts needed for admin functionality. */ public function enqueue() { + $current_screen = get_current_screen(); if ( ! isset( $current_screen->id ) ) { return; From a2dbec333fdc28e2e73046404d5238134c4eb923 Mon Sep 17 00:00:00 2001 From: selul Date: Fri, 2 Nov 2018 20:29:19 +0200 Subject: [PATCH 31/55] improve replacer. --- inc/replacer.php | 123 ++++++++++++++++++++++---------------------- themeisle-hash.json | 2 +- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/inc/replacer.php b/inc/replacer.php index f74b5109..2c51f2cb 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -234,6 +234,8 @@ public function init_html_replacer() { if ( is_admin() ) { return; } + //We no longer need this if the handler was started. + remove_filter( 'the_content', array( $this, 'filter_the_content' ), PHP_INT_MAX ); ob_start( array( &$this, 'replace_urls' ) ); @@ -253,7 +255,9 @@ public function filter_image_downsize( $image, $attachment_id, $size ) { if ( is_admin() ) { return $image; } - + if ( $this->lazyload ) { + return $image; + } $image_url = wp_get_attachment_url( $attachment_id ); if ( $image_url ) { @@ -464,16 +468,16 @@ public function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'height' } } - $payload = array( - 'path' => $this->urlception_encode( $path ), - 'scheme' => $scheme, - 'width' => (string) $args['width'], - 'height' => (string) $args['height'], - 'quality' => (string) $compress_level, - ); - ksort( $payload ); $hash = ''; if ( empty( $this->whitelist ) ) { + $payload = array( + 'path' => $this->urlception_encode( $path ), + 'scheme' => $scheme, + 'width' => (string) $args['width'], + 'height' => (string) $args['height'], + 'quality' => (string) $compress_level, + ); + ksort( $payload ); $values = array_values( $payload ); $payload = implode( '', $values ); $hash = sprintf( '/%s', hash_hmac( 'md5', $payload, $this->cdn_secret ) ); @@ -769,21 +773,14 @@ function replace_meta( $metadata, $object_id, $meta_key, $single ) { * @return mixed Filtered content. */ public function replace_urls( $html, $context = 'raw' ) { - + $html = $this->filter_the_content( $html ); + $old_urls = $this->extract_non_replaced_urls( $html ); + $urls = array_combine( $old_urls, $old_urls ); switch ( $context ) { case 'elementor': - $old_urls = $this->extract_non_replaced_urls( $html ); - $urls = array_map( 'wp_unslash', $old_urls ); - $urls = array_combine( $old_urls, $urls ); + $urls = array_map( 'wp_unslash', $urls ); // return $html; break; - case 'raw': - default: - $html = $this->filter_the_content( $html ); - $urls = $this->extract_non_replaced_urls( $html ); - $urls = array_combine( $urls, $urls ); - break; - } $urls = array_map( function ( $url ) { @@ -801,39 +798,6 @@ function ( $url ) { return str_replace( array_keys( $urls ), array_values( $urls ), $html ); } - /** - * Extract slashed urls from content. - * - * @param string $content Content to parse. - * - * @return array Urls found. - */ - private function extract_non_replaced_urls( $content ) { - /** - * Regex rule to match slashed urls, i.e -> http:\/\/optimole.com\/wp-content\/uploads\/2018\/09\/picture.jpg - * Based on the extract_url patter. - * - * @var string Regex rule string. - */ - $regex = '/(?:http(?:s?):)(?:[\/\\\\|.|\w|\s|-](?!i.optimole.com))*\.(?:' . implode( '|', array_keys( self::$extensions ) ) . ')/'; - preg_match_all( - $regex, - $content, - $urls - ); - - $urls = array_map( - function ( $value ) { - return rtrim( html_entity_decode( $value ), '\\' ); - }, - $urls[0] - ); - - $urls = array_unique( $urls ); - - return array_values( $urls ); - } - /** * Identify images in post content. * @@ -842,7 +806,9 @@ function ( $value ) { * @return string */ public function filter_the_content( $content ) { - + if ( $this->is_amp() ) { + return $content; + } $images = self::parse_images_from_html( $content ); if ( empty( $images ) ) { @@ -853,11 +819,8 @@ public function filter_the_content( $content ) { foreach ( $images[0] as $index => $tag ) { $width = $height = false; $new_tag = $tag; - $src = $tmp = $images['img_url'][ $index ]; + $src = $tmp = wp_unslash( $images['img_url'][ $index ] ); - if ( $this->is_amp() ) { - continue; - } if ( apply_filters( 'optml_imgcdn_disable_optimization_for_link', false, $src ) ) { continue; } @@ -909,9 +872,15 @@ public function filter_the_content( $content ) { $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); if ( $this->lazyload ) { - $new_tag = str_replace( 'src="' . $src . '"', 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"', $new_tag ); + $new_tag = str_replace( array( + 'src="' . $images['img_url'][ $index ] . '"', + 'src=\"' . $images['img_url'][ $index ] . '"' + ), array( + 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"', + wp_slash( 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"' ) + ), $new_tag ); } else { - $new_tag = str_replace( 'src="' . $src . '"', 'src="' . $new_url . '"', $new_tag ); + $new_tag = str_replace( 'src="' . $images['img_url'][ $index ] . '"', 'src="' . $new_url . '"', $new_tag ); } $content = str_replace( $tag, $new_tag, $content ); @@ -932,7 +901,7 @@ public function filter_the_content( $content ) { public static function parse_images_from_html( $content ) { $images = array(); - if ( preg_match_all( '#(?:]+?href=["|\'](?P[^\s]+?)["|\'][^>]*?>\s*)?(?P]*?\s+?src=["|\'](?P[^\s]+?)["|\'].*?>){1}(?:\s*)?#is', $content, $images ) ) { + if ( preg_match_all( '/(?:]+?href=["|\'](?P[^\s]+?)["|\'][^>]*?>\s*)?(?P]*?\s+?src=\\\\?["|\'](?P[^\s]+?)["|\'].*?>){1}(?:\s*<\/a>)?/ism', $content, $images ) ) { foreach ( $images as $key => $unused ) { // Simplify the output as much as possible, mostly for confirming test results. if ( is_numeric( $key ) && $key > 0 ) { @@ -946,6 +915,38 @@ public static function parse_images_from_html( $content ) { return array(); } + /** + * Extract slashed urls from content. + * + * @param string $content Content to parse. + * + * @return array Urls found. + */ + private function extract_non_replaced_urls( $content ) { + /** + * Based on the extract_url patter. + * + * @var string Regex rule string. + */ + $regex = '/(?:http(?:s?):)(?:[\/\\\\|.|\w|\s|-](?!i.optimole.com))*\.(?:' . implode( '|', array_keys( self::$extensions ) ) . ')/'; + preg_match_all( + $regex, + $content, + $urls + ); + + $urls = array_map( + function ( $value ) { + return rtrim( html_entity_decode( $value ), '\\' ); + }, + $urls[0] + ); + + $urls = array_unique( $urls ); + + return array_values( $urls ); + } + /** * Throw error on object clone * diff --git a/themeisle-hash.json b/themeisle-hash.json index 6b5b3a32..061c3454 100644 --- a/themeisle-hash.json +++ b/themeisle-hash.json @@ -1 +1 @@ -{"optimole-wp.php":"31cc25019bb631f81a5e92222753b2cb"} \ No newline at end of file +{"optimole-wp.php":"abb54d3f46ded4432dc4dc1cef0f8bb5"} \ No newline at end of file From 2d42ce96fb1678134410e9d0e0e7dc34efbc38a6 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Sat, 3 Nov 2018 15:12:02 +0200 Subject: [PATCH 32/55] Fix indentation & redirect admin bar button on the settings page. --- inc/admin.php | 12 ++++++------ inc/replacer.php | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index f34f750a..eee59f0f 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -84,11 +84,11 @@ public function should_show_notice() { return false; } static $allowed_base = array( - 'plugins' => true, - 'upload' => true, - 'media' => true, - 'themes' => true, - 'appearance_page_tgmpa-install-plugins' => true, + 'plugins' => true, + 'upload' => true, + 'media' => true, + 'themes' => true, + 'appearance_page_tgmpa-install-plugins' => true, ); $screen_slug = isset( $current_screen->parent_base ) ? $current_screen->parent_base : isset( $current_screen->base ) ? $current_screen->base : ''; @@ -416,7 +416,7 @@ public function add_traffic_node( $wp_admin_bar ) { $args = array( 'id' => 'optml_image_quota', 'title' => 'Optimole' . __( ' Image Traffic', 'optimole-wp' ) . ': ' . number_format( floatval( ( $service_data['usage'] / 1000 ) ), 3 ) . ' / ' . number_format( floatval( ( $service_data['quota'] / 1000 ) ), 0 ) . 'GB', - 'href' => 'https://dashboard.optimole.com/', + 'href' => admin_url( 'upload.php?page=optimole' ), 'meta' => array( 'target' => '_blank', 'class' => $should_load !== 'enabled' ? 'hidden' : '', diff --git a/inc/replacer.php b/inc/replacer.php index 2c51f2cb..0a4e3e36 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -234,7 +234,7 @@ public function init_html_replacer() { if ( is_admin() ) { return; } - //We no longer need this if the handler was started. + // We no longer need this if the handler was started. remove_filter( 'the_content', array( $this, 'filter_the_content' ), PHP_INT_MAX ); ob_start( array( &$this, 'replace_urls' ) @@ -872,13 +872,17 @@ public function filter_the_content( $content ) { $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); if ( $this->lazyload ) { - $new_tag = str_replace( array( - 'src="' . $images['img_url'][ $index ] . '"', - 'src=\"' . $images['img_url'][ $index ] . '"' - ), array( - 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"', - wp_slash( 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"' ) - ), $new_tag ); + $new_tag = str_replace( + array( + 'src="' . $images['img_url'][ $index ] . '"', + 'src=\"' . $images['img_url'][ $index ] . '"', + ), + array( + 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"', + wp_slash( 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"' ), + ), + $new_tag + ); } else { $new_tag = str_replace( 'src="' . $images['img_url'][ $index ] . '"', 'src="' . $new_url . '"', $new_tag ); } From 254fe60a961d74c8793ebad30b30c9a07767be8a Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Sat, 3 Nov 2018 15:21:01 +0200 Subject: [PATCH 33/55] add listing settings link. --- inc/admin.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index eee59f0f..e58d0302 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -27,7 +27,7 @@ class Optml_Admin { */ public function __construct() { $this->settings = new Optml_Settings(); - + add_action( 'plugin_action_links_' . plugin_basename( OPTML_BASEFILE ), array( $this, 'add_action_links' ) ); add_action( 'admin_menu', array( $this, 'add_dashboard_page' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), PHP_INT_MIN ); add_action( 'admin_notices', array( $this, 'add_notice' ) ); @@ -46,6 +46,23 @@ public function __construct() { } } + /** + * Add settings links in the plugin listing page. + * + * @param array $links Old plugin links. + * + * @return array Altered links. + */ + function add_action_links( $links ) { + if ( ! is_array( $links ) ) { + return $links; + } + + return array_merge( $links, array( + '' . __( 'Settings', 'optimole-wp' ) . '', + ) ); + } + /** * Adds optimole optin class. * @@ -93,7 +110,7 @@ public function should_show_notice() { $screen_slug = isset( $current_screen->parent_base ) ? $current_screen->parent_base : isset( $current_screen->base ) ? $current_screen->base : ''; if ( empty( $screen_slug ) || - ( ! isset( $allowed_base[ $screen_slug ] ) ) ) { + ( ! isset( $allowed_base[ $screen_slug ] ) ) ) { return false; } if ( ! current_user_can( 'manage_options' ) ) { From 0a48caf70ce297f70d5ddf376101d587f856dbb0 Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Mon, 5 Nov 2018 17:32:52 +0200 Subject: [PATCH 34/55] chore: bump version for js lib --- optimole-wp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimole-wp.php b/optimole-wp.php index 21440582..eabb4e11 100644 --- a/optimole-wp.php +++ b/optimole-wp.php @@ -45,7 +45,7 @@ function optml() { define( 'OPTML_URL', plugin_dir_url( __FILE__ ) ); define( 'OPTML_JS_CDN', 'd5jmkjjpb7yfg.cloudfront.net' ); define( 'OPTML_PATH', plugin_dir_path( __FILE__ ) ); - define( 'OPTML_VERSION', '1.1.0' ); + define( 'OPTML_VERSION', '1.2.0' ); define( 'OPTML_NAMESPACE', 'optml' ); define( 'OPTML_BASEFILE', __FILE__ ); if ( ! defined( 'OPTML_DEBUG' ) ) { From 780e99dbb9c157cba43b575bb59d945f9f395754 Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Wed, 7 Nov 2018 14:03:32 +0200 Subject: [PATCH 35/55] chore: optimizations for eco lazyload images #28 --- inc/replacer.php | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/inc/replacer.php b/inc/replacer.php index 0a4e3e36..c449b668 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -8,7 +8,7 @@ */ class Optml_Replacer { /** - * A list of allowd extensions. + * A list of allowed extensions. * * @var array */ @@ -18,12 +18,6 @@ class Optml_Replacer { 'webp' => 'image/webp', 'svg' => 'image/svg+xml', ); - /** - * One pixel image code. - * - * @var string Base64 image. - */ - public static $one_px_url = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; /** * Cached object instance. * @@ -103,7 +97,7 @@ class Optml_Replacer { */ protected $upload_dir = null; /** - * Setings handler. + * Settings handler. * * @var Optml_Settings $settings */ @@ -416,7 +410,7 @@ protected function validate_image_sizes( $width, $height ) { * * @return string */ - public function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'height' => 'auto' ) ) { + public function get_imgcdn_url( $url, $args = array( 'width' => 'auto', 'height' => 'auto', 'quality' => 'auto' ) ) { if ( apply_filters( 'optml_dont_replace_url', false, $url ) ) { return $url; } @@ -528,6 +522,9 @@ private function normalize_quality( $quality ) { return intval( $quality ); } $quality = trim( $quality ); + if ( $quality === 'eco' ) { + return 'eco'; + } if ( $quality === 'auto' ) { return 'auto'; } @@ -828,9 +825,6 @@ public function filter_the_content( $content ) { if ( false !== strpos( $src, 'i.optimole.com' ) ) { continue; // we already have this } - if ( $src === self::$one_px_url ) { - continue; - } if ( false === strpos( $src, self::$siteurl ) ) { continue; } @@ -872,17 +866,33 @@ public function filter_the_content( $content ) { $new_tag = str_replace( 'height="' . $height . '"', 'height="' . $new_sizes['height'] . '"', $new_tag ); if ( $this->lazyload ) { + $new_sizes['quality'] = 'eco'; + $low_url = $this->get_imgcdn_url( $tmp, $new_sizes ); + + $noscript_tag = str_replace( + array( + 'src="' . $images['img_url'][ $index ] . '"', + 'src=\"' . $images['img_url'][ $index ] . '"', + ), + array( + 'src="' . $new_url . '"', + wp_slash( 'src="' . $new_url . '"' ), + ), + $new_tag + ); $new_tag = str_replace( array( 'src="' . $images['img_url'][ $index ] . '"', 'src=\"' . $images['img_url'][ $index ] . '"', ), array( - 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"', - wp_slash( 'src="' . self::$one_px_url . '" data-opt-src="' . $new_url . '"' ), + 'src="' . $low_url . '" data-opt-src="' . $new_url . '"', + wp_slash( 'src="' . $low_url . '" data-opt-src="' . $new_url . '"' ), ), $new_tag ); + + $new_tag .= ''; } else { $new_tag = str_replace( 'src="' . $images['img_url'][ $index ] . '"', 'src="' . $new_url . '"', $new_tag ); } From 3a086b23e7f1c77b0294ff33eb9bb20c1757cce9 Mon Sep 17 00:00:00 2001 From: Bogdan Preda Date: Wed, 7 Nov 2018 15:33:57 +0200 Subject: [PATCH 36/55] fix: noscript display @selul --- inc/admin.php | 14 +++++++++++++- inc/replacer.php | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index e58d0302..6519215b 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -44,6 +44,15 @@ public function __construct() { wp_schedule_event( time() + 10, 'daily', 'optml_daily_sync', array() ); } } + + if ( $this->settings->use_lazyload() ) { + add_filter( 'body_class', array( $this, 'optimole_body_classes' ) ); + } + } + + public function optimole_body_classes( $classes ) { + $classes[] = 'optimole-no-script'; + return $classes; } /** @@ -153,7 +162,10 @@ public function frontend_scripts() { return; } wp_enqueue_script( 'optm_lazyload_replacer_js', 'https://' . OPTML_JS_CDN . '/latest/optimole_lib' . ( ! OPTML_DEBUG ? '.min' : '' ) . '.js', array(), OPTML_VERSION, true ); - + wp_add_inline_script( 'optm_lazyload_replacer_js', 'document.body.className = document.body.className.replace("optimole-no-script","");' ); + wp_register_style( 'optm_lazyload_noscript_style', false ); + wp_enqueue_style( 'optm_lazyload_noscript_style' ); + wp_add_inline_style( 'optm_lazyload_noscript_style', '.optimole-no-script img[data-opt-src] { display: none !important; }' ); } /** diff --git a/inc/replacer.php b/inc/replacer.php index c449b668..7d4c2f73 100644 --- a/inc/replacer.php +++ b/inc/replacer.php @@ -892,7 +892,7 @@ public function filter_the_content( $content ) { $new_tag ); - $new_tag .= ''; + $new_tag = '' . $new_tag; } else { $new_tag = str_replace( 'src="' . $images['img_url'][ $index ] . '"', 'src="' . $new_url . '"', $new_tag ); } From 4119816fb7665d34b47f2bc9763c96fae27de2bf Mon Sep 17 00:00:00 2001 From: selul Date: Mon, 12 Nov 2018 11:49:23 +0200 Subject: [PATCH 37/55] fix: phpcs sniffer tests fix: consistency in versions across the plugin --- inc/admin.php | 18 ++++++++++++++---- optimole-wp.php | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index 6519215b..d1627c4b 100644 --- a/inc/admin.php +++ b/inc/admin.php @@ -50,6 +50,13 @@ public function __construct() { } } + /** + * Adds body class for no-js. + * + * @param array $classes No js class. + * + * @return array + */ public function optimole_body_classes( $classes ) { $classes[] = 'optimole-no-script'; return $classes; @@ -67,9 +74,12 @@ function add_action_links( $links ) { return $links; } - return array_merge( $links, array( - '' . __( 'Settings', 'optimole-wp' ) . '', - ) ); + return array_merge( + $links, + array( + '' . __( 'Settings', 'optimole-wp' ) . '', + ) + ); } /** @@ -119,7 +129,7 @@ public function should_show_notice() { $screen_slug = isset( $current_screen->parent_base ) ? $current_screen->parent_base : isset( $current_screen->base ) ? $current_screen->base : ''; if ( empty( $screen_slug ) || - ( ! isset( $allowed_base[ $screen_slug ] ) ) ) { + ( ! isset( $allowed_base[ $screen_slug ] ) ) ) { return false; } if ( ! current_user_can( 'manage_options' ) ) { diff --git a/optimole-wp.php b/optimole-wp.php index eabb4e11..21440582 100644 --- a/optimole-wp.php +++ b/optimole-wp.php @@ -45,7 +45,7 @@ function optml() { define( 'OPTML_URL', plugin_dir_url( __FILE__ ) ); define( 'OPTML_JS_CDN', 'd5jmkjjpb7yfg.cloudfront.net' ); define( 'OPTML_PATH', plugin_dir_path( __FILE__ ) ); - define( 'OPTML_VERSION', '1.2.0' ); + define( 'OPTML_VERSION', '1.1.0' ); define( 'OPTML_NAMESPACE', 'optml' ); define( 'OPTML_BASEFILE', __FILE__ ); if ( ! defined( 'OPTML_DEBUG' ) ) { From 5eb1c9e2c4fee0fbfb94e4751ded635221e8ae0c Mon Sep 17 00:00:00 2001 From: selul Date: Mon, 12 Nov 2018 12:48:22 +0200 Subject: [PATCH 38/55] adds new logo & branding. --- assets/css/style.scss | 6 +-- assets/img/logo.png | Bin 47621 -> 18210 bytes assets/js/bundle.js | 70 +++++++++++++-------------- assets/js/bundle.min.js | 8 +-- assets/vue/components/app-header.vue | 4 +- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/assets/css/style.scss b/assets/css/style.scss index 612c5881..cb776bde 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -2,10 +2,10 @@ #optimole-app { padding: 0 30px 0 20px; - $primary: #e7602a; + $primary: #EF686B; $success: #34a85e; - $danger: #d54222; - $info: #008ec2; + $danger: #D54222; + $info: #5180C1; @import "~bulma/bulma"; diff --git a/assets/img/logo.png b/assets/img/logo.png index 1a63da607a3832ae5ede73978ca23b124604fe35..e0f058b247af27d445d3b394944d11dca010774b 100644 GIT binary patch literal 18210 zcmV*5Ky<%}P)b0R)X@ZIuDF#K6UZl4` z8l?AR`ZM$Na@yYSAI~I)6q1mM;CuPyGc%bp&)H|6y?$$#wbxo3-o?9k7w_U-yo-17 zF5boeIf(6DTr%wbtLyQWkV@z*Skl9VpZh9GDGbASyQ{P{#tTi%5JJq5o*VSfmm-~;TvS~5T;4KTqYike9?2gzW`6}pq$CT&w&o$Cg5j# ztHSRRV9((fLcC9C+9#!yN2fD6tf35O8eQW$IQYaPIBfbP-gC@hgf+p57k!2P!8ED* z)Q6=fw(6K7^*d zzmUs^bG-wYu?VLezCWj*dK_mSc`&W@aRAm;VP(d{m7tWuUvF^GK2snV!g8GspKcrg zyafDUC(q36t(SS10J{c1aGq`&pL8Afu#LR~P%P8fkmQmJ&*Fn;oWTAE%peR{K*52* zvPa1SQ-G;~Qi351rc9f_#EIiryRnC+#`;sFlrI5S1K-s&4W$%HsjZ*?>fU;>cM0(3 zXcR(xK-cxlij~Uv;dR|mt}uPy>3rn0WBA}%C)3%O!U9(2!Ejv~f*L{qqSn6ysM@a# z7ZkJV{e1A0qxtTSu17RAoG662R|u`iwks418GODV;Yb45OoS3Uo-FP$-X*{r(F7a{ z91Bb_Ok-BLTy0FRTZ1ng;`AdAM~34wyY1{C*tQJJhyH^NPg`v1p2E(+_8OxQG6}b;J`52m}iC7%;fEUie)C zz)avY;3(h_6mi36VA~GsSFAknJlE#=Xa2&% z`A-rEg%ODR_vXw0Unao&fsX^nQ8V12X&RcYv0+0GDqrHXQ;y=w&tAX*Gp7KMbD?TU zG!1k>2(Ua=BY;*T0ScZ+Mk?hTL+KtGnmU=W&k-zI@FY@7bWMMDZ#KgJMFN~Jgt*-EoLMM^ruzVeX_{mU zMRF_FFmC!Jetg}PoOR5h01VsUx)M!*J~FUt0z3(ptc~wJz4p_QP#4v4st(6};4{p9 zejbDkDCG%`o58bDrOnzTMN_(B|V4LE9YZbWkTWD3Mu81z1bN5 zmk4l>5aMd3)QOf=BoK(>^TqJo0=8qZZs{tFXqYd4;Ud2A`HxW_36QnH9ud5@)vk#K zl)M@VGyy(Mpn!l+BMAJ*W!EtGg?U6LwospHW!CIt=o~wZNURK`D}T6ES^Zs+B(0_w=B$MNU8ENWOLH z$2f3W7wale%6r6pVjID4iA~+VDZtdhRf41eH_dsNhyQdRzA<%7n|v_Go_rBLUywp! zh|F*wLVyq&y1qs8D}~P=q*Bh)-?t9GKZKHU?%r;g|KAA^0C5+n)2&JdpFc)J>%O>7 zk<9QidX}xEvAvV)uKgMxJmqLgz`|iLg-3Hx;BB=WR|

7)DLoEh+JoLKgx9F#Q4# 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

-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 Z{|DIB&O}GJY)SwC002ovPDHLkV1fXTW90w< literal 47621 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 V#S^KmL_vTeEv_I|BVri*{{ho8{{sL3 diff --git a/assets/js/bundle.js b/assets/js/bundle.js index d01602ef..a60d209e 100644 --- a/assets/js/bundle.js +++ b/assets/js/bundle.js @@ -11569,7 +11569,7 @@ if (false) {(function () { module.hot.accept() var hotAPI = require("vue-hot-reload-api") hotAPI.install(require("vue"), true) if (!hotAPI.compatible) return - var id = "/Users/selul/dev/optimole-wp/assets/vue/components/api-key-form.vue" + var id = "D:\\local\\optimolewp\\app\\public\\wp-content\\plugins\\optimole-wp\\assets\\vue\\components\\api-key-form.vue" if (!module.hot.data) { hotAPI.createRecord(id, module.exports) } else { @@ -13466,7 +13466,7 @@ if (false) {(function () { module.hot.accept() var hotAPI = require("vue-hot-reload-api") hotAPI.install(require("vue"), true) if (!hotAPI.compatible) return - var id = "/Users/selul/dev/optimole-wp/assets/vue/components/main.vue" + var id = "D:\\local\\optimolewp\\app\\public\\wp-content\\plugins\\optimole-wp\\assets\\vue\\components\\main.vue" if (!module.hot.data) { hotAPI.createRecord(id, module.exports) } else { @@ -13490,8 +13490,8 @@ if(content.locals) module.exports = content.locals; if(false) { // When the styles change, update the