├── assets ├── css │ └── styles.css └── js │ ├── main.js │ ├── optimize.js │ └── settings.js ├── components ├── MediaInfo.php └── Optimizer.php ├── controllers ├── ConnectController.php ├── DashboardController.php ├── LogController.php ├── MigrateController.php └── SettingsController.php ├── core ├── Autoload.php ├── Component.php ├── Migration.php ├── Model.php ├── PluginLoader.php └── Singleton.php ├── functions.php ├── index.php ├── just-image-optimizer.php ├── migrations ├── m0x100.php ├── m0x110.php └── m1x000.php ├── models ├── Connect.php ├── Log.php ├── Media.php ├── Migrate.php └── Settings.php ├── readme.txt ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── services ├── GooglePagespeed.php ├── ImageOptimizerFactory.php └── ImageOptimizerInterface.php └── views ├── _tabs.php ├── dashboard ├── _requirements.php ├── connect.php ├── index.php └── settings.php ├── log ├── index.php └── single-log.php ├── media ├── column.php └── meta-box.php ├── migrate ├── index.php └── upgraded.php ├── optimize └── google-page-speed.php └── redirect.php /assets/css/styles.css: -------------------------------------------------------------------------------- 1 | #google_insights { 2 | display: none; 3 | } 4 | 5 | .notice-error { 6 | color: red; 7 | } 8 | 9 | .jio-admin-page .update-nag { 10 | display: block; 11 | } 12 | .jio-admin-page .update-nag.error-nag { 13 | border-left: 4px solid red !important; 14 | } 15 | .jio-admin-page .update-nag.success-nag { 16 | border-left: 4px solid green !important; 17 | } 18 | 19 | .loader { 20 | border: 10px solid #f3f3f3; 21 | border-radius: 50%; 22 | border-top: 10px solid #3498db; 23 | width: 15px; 24 | height: 15px; 25 | -webkit-animation: spin 2s linear infinite; 26 | animation: spin 2s linear infinite; 27 | margin: 20px; 28 | } 29 | 30 | @-webkit-keyframes spin { 31 | 0% { -webkit-transform: rotate(0deg); } 32 | 100% { -webkit-transform: rotate(360deg); } 33 | } 34 | 35 | @keyframes spin { 36 | 0% { transform: rotate(0deg); } 37 | 100% { transform: rotate(360deg); } 38 | } 39 | 40 | .column { 41 | float: left; 42 | width: 40%; 43 | min-width: 400px; 44 | } 45 | 46 | .column-l { 47 | float: left; 48 | width: 20%; 49 | } 50 | 51 | /* Clear floats */ 52 | .row:after { 53 | content: ""; 54 | display: table; 55 | clear: both; 56 | } -------------------------------------------------------------------------------- /assets/js/main.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function ($) { 2 | checkboxChecker(); 3 | $('input:checkbox').on('click', function() { 4 | var $box = $(this); 5 | var $selector_block = '#' + $box.val(); 6 | if ($box.is(":checked")) { 7 | var group = "input:checkbox[name='" + $box.attr("name") + "']"; 8 | $(group).prop("checked", false); 9 | $box.prop("checked", true); 10 | $($selector_block).css('display', 'block'); 11 | } else { 12 | $box.prop("checked", false); 13 | $($selector_block).css('display', 'none'); 14 | } 15 | }); 16 | $('#api_key').on('change', function(e) { 17 | $('#api_connect').show(); 18 | $('#submit-connect').prop('disabled', true); 19 | $('.notice-error').html(''); 20 | }) 21 | $('#api_connect').on('click', function(e) { 22 | e.preventDefault(); 23 | $('#api_connect').text('Connecting...'); 24 | var $api_key = $('#api_key').val(); 25 | var $service = $('#service').val(); 26 | var data = { 27 | action: 'joi_check_api_connect', 28 | api_key: $api_key, 29 | service: $service 30 | }; 31 | $.post( ajaxurl, data, function( response ) { 32 | //console.log(response); 33 | if( response == '1') { 34 | $('.notice-error').html('Connected'); 35 | $('#api_connect').hide(); 36 | $('#submit-connect').prop('disabled', false); 37 | } else { 38 | $('.notice-error').html('API key is invalid'); 39 | } 40 | $('#api_connect').text('Connect'); 41 | }); 42 | 43 | }); 44 | function checkboxChecker(){ 45 | var $box = $('input:checkbox'); 46 | var $selector_block = '#' + $box.val(); 47 | if($box.is(":checked")) { 48 | $($selector_block).css('display', 'block'); 49 | } 50 | } 51 | $('#find_api').on('click', function(e) { 52 | e.preventDefault(); 53 | var x = document.getElementById("instructions-api"); 54 | if (x.style.display === "none") { 55 | x.style.display = "block"; 56 | } else { 57 | x.style.display = "none"; 58 | } 59 | 60 | }); 61 | }); -------------------------------------------------------------------------------- /assets/js/optimize.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function ($) { 2 | $('.optimize-now').on('click', function(e) { 3 | e.preventDefault(); 4 | var attach_id = $(this).data('attach-id'); 5 | $(this).parent().html('
'); 6 | var data = { 7 | action: 'manual_optimize', 8 | attach_id: attach_id 9 | }; 10 | $.post( ajaxurl, data, function( response ) { 11 | $('.loader').parent().html('

' + response.saving_percent + '% saved (' + response.saving_size + ')

' + 12 | '

disk usage: ' + response.total_size + ' (' + response.count_images + ' images)

'); 13 | }); 14 | }); 15 | }); 16 | jQuery(document).ready(function ($) { 17 | $('.optimize-now-meta').on('click', function(e) { 18 | e.preventDefault(); 19 | var attach_id = $(this).data('attach-id'); 20 | $(this).parent().append('
'); 21 | $('td .optimize-now-meta').remove(); 22 | var data = { 23 | action: 'manual_optimize', 24 | attach_id: attach_id 25 | }; 26 | $.post( ajaxurl, data, function( response ) { 27 | $(".loader").remove(); 28 | $('.optimize-stats').html('Image Optimization' + 29 | '

' + response.saving_percent + ' saved (' + response.saving_size + ')

' + 30 | '

disk usage: ' + response.total_size + ' (' + response.count_images + ' images)

'); 31 | }); 32 | }); 33 | }); -------------------------------------------------------------------------------- /assets/js/settings.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function ($) { 2 | checkboxChecker(); 3 | $("#check_all_size").click(function(){ 4 | var $box = $('input:checkbox[name="image_sizes_all"]'); 5 | $('.image_sizes_set input:checkbox').not(this).prop('checked', this.checked); 6 | if ($box.is(":checked")) { 7 | $('.size_checked').css('display', 'none'); 8 | } else { 9 | $('.size_checked').css('display', 'block'); 10 | } 11 | }); 12 | function checkboxChecker(){ 13 | var $box = $('input:checkbox[name="image_sizes_all"]'); 14 | if ($box.is(":checked")) { 15 | $('.size_checked').css('display', 'none'); 16 | $('input:checkbox[name="image_sizes[]"]').attr('checked','checked'); 17 | } else { 18 | $('.size_checked').css('display', 'block'); 19 | } 20 | } 21 | }); -------------------------------------------------------------------------------- /components/MediaInfo.php: -------------------------------------------------------------------------------- 1 | clean_statistics( $attachment_id ); 53 | } 54 | return $metadata; 55 | } 56 | 57 | /** 58 | * Initialize WordPress media hooks 59 | */ 60 | public function hook_new_media_columns() { 61 | add_filter( 'manage_media_columns', array( $this, 'optimize_column' ) ); 62 | add_action( 'manage_media_custom_column', array( $this, 'optimize_column_display' ), 10, 2 ); 63 | add_filter( 'manage_upload_sortable_columns', array( $this, 'optimize_column_sortable' ) ); 64 | } 65 | 66 | /** 67 | * Register Assets 68 | */ 69 | public function registerAssets() { 70 | wp_enqueue_script( 71 | 'just_img_manual_js', 72 | plugins_url( 'assets/js/optimize.js', dirname( __FILE__ ) ), 73 | array( 'jquery' ) 74 | ); 75 | wp_enqueue_style( 'just_img_opt_css', plugins_url( 'assets/css/styles.css', dirname( __FILE__ ) ) ); 76 | } 77 | 78 | /** 79 | * Add the column. 80 | * 81 | * @param array $cols Cols Array. 82 | * 83 | * @return mixed 84 | */ 85 | public function optimize_column( $cols ) { 86 | $cols["optimize"] = "Optimize"; 87 | 88 | return $cols; 89 | } 90 | 91 | /** 92 | * Display column content. 93 | * 94 | * @param string $column_name Column Name. 95 | * @param integer $id Attachment id. 96 | * 97 | * @return mixed 98 | */ 99 | public function optimize_column_display( $column_name, $id ) { 100 | $model = new Media(); 101 | $this->render( 'media/column', array( 102 | 'id' => $id, 103 | 'column_name' => $column_name, 104 | 'model' => $model, 105 | 'allowed_images' => $this->allowed_images, 106 | ) ); 107 | } 108 | 109 | /** 110 | * Register the column as sortable & sort by name. 111 | * 112 | * @param array $cols Cols Array. 113 | * 114 | * @return mixed 115 | */ 116 | public function optimize_column_sortable( $cols ) { 117 | $cols["optimize"] = "name"; 118 | 119 | return $cols; 120 | } 121 | 122 | /** 123 | * Register metabox for media type image. 124 | * 125 | * @param \WP_Post $post Post object. 126 | */ 127 | public function add_optimize_meta_boxes( $post ) { 128 | if ( ! in_array( get_post_mime_type( $post->ID ), $this->allowed_images ) ) { 129 | return; 130 | } 131 | add_meta_box( 'jri-attachement-meta-info', __( 'Image Sizes' ), array( $this, 'render_meta_info' ) ); 132 | } 133 | 134 | /** 135 | * Calculate common divider. 136 | * 137 | * @param int $a First integer. 138 | * @param int $b Second integer. 139 | * 140 | * @return int 141 | */ 142 | public function common_divisor( $a, $b ) { 143 | $a = (int) $a; 144 | $b = (int) $b; 145 | 146 | return ( 0 === $b ) ? $a : $this->common_divisor( $b, $a % $b ); 147 | } 148 | 149 | /** 150 | * Get meta information and render. 151 | * 152 | * @param \WP_Post $post Post object. 153 | */ 154 | public function render_meta_info( $post ) { 155 | $meta = wp_get_attachment_metadata( $post->ID ); 156 | $model = new Media(); 157 | 158 | if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) { 159 | $meta['gcd'] = $this->common_divisor( $meta['width'], $meta['height'] ); 160 | $meta['x_ratio'] = (int) $meta['width'] / $meta['gcd']; 161 | $meta['y_ratio'] = (int) $meta['height'] / $meta['gcd']; 162 | if ( 20 < $meta['x_ratio'] || 20 < $meta['y_ratio'] ) { 163 | $meta['x_ratio'] = round( $meta['x_ratio'] / 10 ); 164 | $meta['y_ratio'] = round( $meta['y_ratio'] / 10 ); 165 | $meta['avr_ratio'] = true; 166 | } 167 | } 168 | 169 | $this->render( 'media/meta-box', array( 170 | 'meta' => $meta, 171 | 'model' => $model, 172 | 'id' => $post->ID, 173 | 'allowed_images' => $this->allowed_images, 174 | ) 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /components/Optimizer.php: -------------------------------------------------------------------------------- 1 | setup_cron(); 22 | } 23 | 24 | /** 25 | * Run cron job by Settings param 26 | */ 27 | protected function setup_cron() { 28 | if ( \JustImageOptimizer::$settings->saved() 29 | && \JustImageOptimizer::$settings->auto_optimize 30 | && \JustImageOptimizer::$settings->check_requirements() 31 | ) { 32 | add_action( 'init', array( $this, 'check_cron_scheduled' ) ); 33 | add_filter( 'cron_schedules', array( $this, 'init_cron_schedule' ) ); 34 | add_action( 'just_image_optimizer_autorun', array( $this, 'auto_optimize' ) ); 35 | } 36 | } 37 | 38 | /** 39 | * Add Optimizer Image cron interval function. 40 | * 41 | * @param array $schedules An array of non-default cron schedules. Default empty. 42 | * 43 | * @return array 44 | */ 45 | public function init_cron_schedule( $schedules ) { 46 | $schedules['just_image_optimizer'] = array( 47 | 'interval' => 60 * 5, // 5 minutes 48 | 'display' => 'Image optimizer background optimization', 49 | ); 50 | 51 | return $schedules; 52 | } 53 | 54 | /** 55 | * Re-schedule our auto optimizer background job if needed. 56 | */ 57 | public function check_cron_scheduled() { 58 | if ( ! wp_next_scheduled( 'just_image_optimizer_autorun' ) ) { 59 | wp_schedule_event( time(), 'just_image_optimizer', 'just_image_optimizer_autorun' ); 60 | } 61 | } 62 | 63 | /** 64 | * Set uploaded attachment in queue 65 | * 66 | * @param int $post_id Attachment id. 67 | */ 68 | public function set_attachment_in_queue( $post_id ) { 69 | update_post_meta( $post_id, '_just_img_opt_status', Media::STATUS_IN_QUEUE ); 70 | } 71 | 72 | 73 | /** 74 | * Auto optimizer cron job. 75 | */ 76 | public function auto_optimize() { 77 | $attach_ids = array(); 78 | 79 | $queue_args = array( 80 | 'post_type' => 'attachment', 81 | 'post_status' => 'inherit', 82 | 'post_mime_type' => array( 'image/jpg', 'image/jpeg', 'image/gif', 'image/png' ), 83 | 'posts_per_page' => \JustImageOptimizer::$settings->image_limit, 84 | 'orderby' => 'id', 85 | 'order' => 'ASC', 86 | 'meta_query' => array( 87 | 'relation' => 'OR', 88 | array( 89 | 'key' => '_just_img_opt_status', 90 | 'value' => Media::STATUS_IN_QUEUE, 91 | ), 92 | array( 93 | 'key' => '_just_img_opt_status', 94 | 'compare' => 'NOT EXISTS', 95 | 'value' => '', 96 | ), 97 | ), 98 | ); 99 | $set_queue = new \WP_Query( $queue_args ); 100 | 101 | while ( $set_queue->have_posts() ) { 102 | $set_queue->the_post(); 103 | $attach_ids[] = get_the_ID(); 104 | update_post_meta( get_the_ID(), '_just_img_opt_status', Media::STATUS_IN_QUEUE ); 105 | } 106 | 107 | // do not run optimization and any logs if we don't have images to optimize. 108 | if ( empty( $attach_ids ) ) { 109 | return; 110 | } 111 | 112 | require_once ABSPATH . 'wp-admin/includes/file.php'; 113 | $tries = 0; 114 | do { 115 | $this->optimize_images( $attach_ids ); 116 | 117 | // remove processed attachments from list. 118 | foreach ( $attach_ids as $key => $attach_id ) { 119 | $status = (int) get_post_meta( $attach_id, '_just_img_opt_status' ); 120 | if ( Media::STATUS_PROCESSED === $status ) { 121 | unset( $attach_ids[ $key ] ); 122 | } 123 | } 124 | } while ( ! empty( $attach_ids ) && \JustImageOptimizer::$settings->tries_count > $tries ++ ); 125 | } 126 | 127 | /** 128 | * Function for init filesystem accesses 129 | * 130 | * @return string 131 | */ 132 | public function filesystem_direct() { 133 | return 'direct'; 134 | } 135 | 136 | /** 137 | * Ajax function for manual image optimize 138 | */ 139 | public function manual_optimize() { 140 | $attach_id = (int) $_POST['attach_id']; 141 | $model = new Media(); 142 | 143 | $tries = 1; 144 | do { 145 | $this->optimize_images( [ $attach_id ] ); 146 | $optimize_status = $model->check_optimization_status( $attach_id ); 147 | } while ( Media::STATUS_PROCESSED !== $optimize_status && \JustImageOptimizer::$settings->tries_count > $tries ++ ); 148 | 149 | $attach_stats = $model->get_total_attachment_stats( $attach_id ); 150 | $data_statistics = array( 151 | 'saving_percent' => ( ! empty( $attach_stats[0]->percent ) ? $attach_stats[0]->percent : 0 ), 152 | 'saving_size' => ( ! empty( $attach_stats[0]->saving_size ) ? jio_size_format( $attach_stats[0]->saving_size ) : 0 ), 153 | 'total_size' => ( ! empty( $attach_stats[0]->disk_usage ) ? jio_size_format( $attach_stats[0]->disk_usage ) : 0 ), 154 | 'count_images' => $model->get_count_images( $attach_id ), 155 | ); 156 | header( 'Content-Type: application/json; charset=' . get_bloginfo( 'charset' ) ); 157 | echo wp_json_encode( $data_statistics ); 158 | wp_die(); 159 | } 160 | 161 | /** 162 | * Function for optimize images 163 | * 164 | * @param array $attach_ids Attachment ids. 165 | * 166 | * @return boolean 167 | */ 168 | protected function optimize_images( array $attach_ids ) { 169 | /* @var \WP_Filesystem_Direct $wp_filesystem */ 170 | global $wp_filesystem; 171 | $media = new Media(); 172 | $log = new Log(); 173 | $attach_ids = $media->size_limit( $attach_ids ); 174 | // add filter for WP_FIlesystem permission. 175 | add_filter( 'filesystem_method', array( $this, 'filesystem_direct' ) ); 176 | WP_Filesystem(); 177 | // set statistics and status before replace images. 178 | $request_id = $log->start_request(); 179 | 180 | foreach ( $attach_ids as $key => $attach_id ) { 181 | $optimize_status = (int) get_post_meta( $attach_id, '_just_img_opt_status' ); 182 | if ( Media::STATUS_PROCESSED === $optimize_status ) { 183 | unset( $attach_ids[ $key ] ); 184 | continue; 185 | } 186 | 187 | $file_sizes = $media->get_file_sizes( $attach_id, 'detailed' ); 188 | $media->save_stats( $attach_id, $file_sizes ); 189 | $log->save_details( $request_id, $attach_id, $file_sizes ); 190 | update_post_meta( $attach_id, '_just_img_opt_status', Media::STATUS_IN_PROCESS ); 191 | } 192 | // upload images from service. 193 | $dir = WP_CONTENT_DIR . '/tmp/'; 194 | $wp_filesystem->is_dir( $dir ) || $wp_filesystem->mkdir( $dir ); 195 | $status = \JustImageOptimizer::$service->upload_optimize_images( $attach_ids, $dir, $log ); 196 | 197 | // if folder empty - then optimization failed, reset stats. 198 | $image_files = scandir( $dir ); 199 | if ( ! $status || 0 === count( glob( $dir ) ) || empty( $image_files ) ) { 200 | foreach ( $attach_ids as $attach_id ) { 201 | $log->update_status( $attach_id, $request_id, Log::STATUS_REMOVED ); 202 | $optimize_status = $media->check_optimization_status( $attach_id ); 203 | update_post_meta( $attach_id, '_just_img_opt_status', $optimize_status ); 204 | } 205 | $wp_filesystem->rmdir( $dir, true ); 206 | 207 | return false; 208 | } 209 | 210 | $get_path = $media->get_uploads_path(); 211 | 212 | // process image replacement. 213 | foreach ( $image_files as $key => $file ) { 214 | if ( $wp_filesystem->is_file( $dir . $file ) ) { 215 | foreach ( $get_path as $path ) { 216 | if ( $wp_filesystem->exists( $path . '/' . $file ) ) { 217 | $optimize_image_size = getimagesize( $dir . $file ); 218 | if ( 25 < $optimize_image_size[0] && 25 < $optimize_image_size[1] ) { 219 | $wp_filesystem->copy( $dir . $file, $path . '/' . $file, true ); 220 | $log->save_status( $request_id, $file, Log::STATUS_OPTIMIZED ); 221 | } else { 222 | $log->save_status( $request_id, $file, Log::STATUS_ABORTED ); 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | // set statistics and status after replace images. 230 | foreach ( $attach_ids as $attach_id ) { 231 | $file_sizes = $media->get_file_sizes( $attach_id, 'detailed' ); 232 | $media->update_stats( $attach_id, $file_sizes ); 233 | $log->update_details( $request_id, $attach_id, $file_sizes ); 234 | $optimize_status = $media->check_optimization_status( $attach_id ); 235 | update_post_meta( $attach_id, '_just_img_opt_status', $optimize_status ); 236 | } 237 | 238 | $wp_filesystem->rmdir( $dir, true ); 239 | 240 | return true; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /controllers/ConnectController.php: -------------------------------------------------------------------------------- 1 | load( $_POST ) && $saved = $model->save() ) { 47 | if ( ! \JustImageOptimizer::$settings->saved() ) { 48 | $this->render( 'redirect', array( 49 | 'redirect_url' => admin_url() . 'upload.php?page=just-img-opt-settings', 50 | ) ); 51 | } 52 | } 53 | $this->render( 'dashboard/connect', array( 54 | 'model' => $model, 55 | 'tab' => 'connect', 56 | 'saved' => ( isset( $saved ) ? $saved : '' ), 57 | ) ); 58 | } 59 | 60 | /** 61 | * Register Assets 62 | */ 63 | public function registerAssets() { 64 | wp_enqueue_script( 65 | 'just_img_opt_js', 66 | plugins_url( 'assets/js/main.js', dirname( __FILE__ ) ), 67 | array( 'jquery' ) 68 | ); 69 | wp_enqueue_style( 'just_img_opt_css', plugins_url( 'assets/css/styles.css', dirname( __FILE__ ) ) ); 70 | } 71 | 72 | /** 73 | * Ajax function for check valid API key 74 | */ 75 | public function check_api_connect() { 76 | try { 77 | $service = services\ImageOptimizerFactory::create( 78 | sanitize_key( $_POST['service'] ), 79 | sanitize_text_field( $_POST['api_key'] ) 80 | ); 81 | $connection_status = $service->check_api_key(); 82 | echo esc_attr( $connection_status ); 83 | } catch ( \Exception $e ) { 84 | echo '0'; 85 | } 86 | exit(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /controllers/DashboardController.php: -------------------------------------------------------------------------------- 1 | render( 'redirect', array( 64 | 'redirect_url' => admin_url() . 'upload.php?page=just-img-opt-connection', 65 | ) ); 66 | } 67 | 68 | $model = new Media(); 69 | $this->render( 'dashboard/index', array( 70 | 'tab' => 'dashboard', 71 | 'model' => $model, 72 | ) ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /controllers/LogController.php: -------------------------------------------------------------------------------- 1 | render( 'log/single-log', array( 44 | 'model' => $model, 45 | 'request_id' => $request_id, 46 | 'log' => $model->find( $request_id ), 47 | 'tab' => 'log', 48 | ) ); 49 | } else { 50 | $this->render( 'log/index', array( 51 | 'model' => $model, 52 | 'tab' => 'log', 53 | ) ); 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /controllers/MigrateController.php: -------------------------------------------------------------------------------- 1 | actionUpgraded(); 48 | } 49 | 50 | $migrations = $model->findMigrations(); 51 | 52 | // check form submit and migrate 53 | if ( $model->load( $_POST ) ) { 54 | if ( $model->migrate( $migrations ) ) { 55 | return $this->actionUpgraded(); 56 | } else { 57 | $errors = $model->migrate( $migrations ); 58 | } 59 | } // if no submit we test migrate to show possible warnings 60 | else { 61 | $warnings = $model->testMigrate( $migrations ); 62 | } 63 | 64 | return $this->render( 'migrate/index', array( 65 | 'tab' => 'migrate', 66 | 'migrations' => $migrations, 67 | 'warnings' => $warnings, 68 | 'errors' => $errors, 69 | ) ); 70 | } 71 | 72 | /** 73 | * Success page 74 | * 75 | * @return bool 76 | */ 77 | public function actionUpgraded() { 78 | return $this->render( 'migrate/upgraded', array( 79 | 'tab' => 'migrate', 80 | ) ); 81 | } 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /controllers/SettingsController.php: -------------------------------------------------------------------------------- 1 | render( 'redirect', array( 56 | 'redirect_url' => admin_url() . 'upload.php?page=just-img-opt-connection', 57 | ) ); 58 | } 59 | 60 | $model = \JustImageOptimizer::$settings; 61 | $model->load( $_POST ) && $saved = $model->save(); 62 | $this->render( 'dashboard/settings', array( 63 | 'tab' => 'settings', 64 | 'model' => $model, 65 | 'sizes' => Media::image_dimensions(), 66 | 'saved' => ( isset( $saved ) ? $saved : false ), 67 | ) ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /core/Autoload.php: -------------------------------------------------------------------------------- 1 | get_class( $this ), 27 | '{file}' => $__view, 28 | ) ); 29 | throw new \Exception( $ml_message ); 30 | } 31 | 32 | if ( ! empty( $data ) && is_array( $data ) ) { 33 | extract( $data ); 34 | } 35 | 36 | include( $__view ); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/Migration.php: -------------------------------------------------------------------------------- 1 | mode !== self::MODE_UPDATE; 55 | } 56 | 57 | /** 58 | * Run compatibility data test 59 | * 60 | * @return array 61 | */ 62 | public function runTest() { 63 | 64 | return $this->test(); 65 | } 66 | 67 | /** 68 | * Update data to match new format 69 | * 70 | * @param string $mode 71 | * 72 | * @return \bool 73 | */ 74 | public function runUpdate( $mode = 'test' ) { 75 | $this->mode = ( $mode == self::MODE_UPDATE ) ? self::MODE_UPDATE : self::MODE_TEST; 76 | 77 | $this->updated = $this->update(); 78 | 79 | return true; 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /core/Model.php: -------------------------------------------------------------------------------- 1 | set_attributes( $params ); 24 | 25 | return true; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | /** 32 | * Set attributes to model 33 | * 34 | * @param array $params Input data. 35 | */ 36 | public function set_attributes( $params ) { 37 | $self = get_class( $this ); 38 | foreach ( $params as $key => $value ) { 39 | if ( property_exists( $self, $key ) ) { 40 | $this->$key = $this->sanitize_attribute( $key, $value ); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Sanitize input to be sure it's safe 47 | * 48 | * @param string $attr Attribute. 49 | * @param mixed $value Input data. 50 | * 51 | * @return array|mixed 52 | */ 53 | public function sanitize_attribute( $attr, $value ) { 54 | $attr_key = is_array( $value ) ? "$attr.*" : $attr; 55 | 56 | $sanitize_func = 'sanitize_text_field'; 57 | if ( ! empty( $this->sanitize[ $attr_key ] ) ) { 58 | $sanitize_func = 'sanitize_' . $this->sanitize[ $attr_key ]; 59 | } 60 | 61 | if ( is_array( $value ) ) { 62 | $safe_value = array(); 63 | foreach ( $value as $key => $val ) { 64 | $safe_value[ $key ] = call_user_func_array( $sanitize_func, array( $val ) ); 65 | } 66 | } else { 67 | $safe_value = call_user_func_array( $sanitize_func, array( $value ) ); 68 | } 69 | 70 | return $safe_value; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /core/PluginLoader.php: -------------------------------------------------------------------------------- 1 | migrate( $migrate->findMigrations() ); 18 | } 19 | 20 | /** 21 | * Check plugin version 22 | * 23 | * @return bool 24 | */ 25 | public function check_migrations_available() { 26 | if ( version_compare( \JustImageOptimizer::$opt_version, \JustImageOptimizer::$version, '<' ) ) { 27 | // print notice if we're not on migrate page 28 | if ( empty( $_GET['page'] ) || $_GET['page'] != 'just-img-opt-migrate' ) { 29 | add_action( 'admin_notices', array( 30 | '\JustCoded\WP\ImageOptimizer\models\Migrate', 31 | 'adminUpgradeNotice', 32 | ) ); 33 | } 34 | 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /core/Singleton.php: -------------------------------------------------------------------------------- 1 | check_migrations_available() ) { 95 | new controllers\MigrateController(); 96 | } else { 97 | // init global static objects. 98 | if ( is_admin() ) { 99 | new controllers\ConnectController(); 100 | new controllers\SettingsController(); 101 | new controllers\DashboardController(); 102 | new controllers\LogController(); 103 | } 104 | } 105 | } 106 | } 107 | 108 | JustImageOptimizer::run(); 109 | -------------------------------------------------------------------------------- /migrations/m0x100.php: -------------------------------------------------------------------------------- 1 | get_charset_collate(); 29 | $table_stats = $wpdb->prefix . models\Media::TABLE_IMAGE_STATS; 30 | $table_posts = $wpdb->posts; 31 | $sql = "CREATE TABLE $table_stats ( 32 | id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, 33 | attach_id BIGINT(20) UNSIGNED NOT NULL, 34 | attach_name VARCHAR(255) NOT NULL, 35 | image_size VARCHAR(255) NOT NULL, 36 | bytes_before VARCHAR(255) NOT NULL, 37 | bytes_after VARCHAR(255) NOT NULL, 38 | PRIMARY KEY (id), 39 | INDEX ix_attach_id(attach_id), 40 | INDEX ix_attach_name(attach_name), 41 | INDEX ix_image_size(image_size), 42 | FOREIGN KEY (attach_id) 43 | REFERENCES $table_posts(ID) 44 | ON DELETE CASCADE 45 | ) $charset_collate;"; 46 | 47 | require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); 48 | dbDelta( $sql ); 49 | 50 | return true; 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /migrations/m0x110.php: -------------------------------------------------------------------------------- 1 | get_charset_collate(); 29 | $table_log_details = $wpdb->prefix . models\Log::TABLE_IMAGE_LOG_DETAILS; 30 | $table_log = $wpdb->prefix . models\Log::TABLE_IMAGE_LOG; 31 | $table_posts = $wpdb->posts; 32 | 33 | $sql_log = "CREATE TABLE $table_log ( 34 | request_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, 35 | service VARCHAR(255) NOT NULL, 36 | image_limit VARCHAR(255) NOT NULL, 37 | size_limit VARCHAR(255) NOT NULL, 38 | time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, 39 | info LONGTEXT, 40 | PRIMARY KEY (request_id) 41 | ) $charset_collate;"; 42 | 43 | $sql_details = "CREATE TABLE $table_log_details ( 44 | id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, 45 | request_id BIGINT(20) UNSIGNED NOT NULL, 46 | attach_id BIGINT(20) UNSIGNED NOT NULL, 47 | image_size VARCHAR(255) NOT NULL, 48 | bytes_before VARCHAR(255) NOT NULL, 49 | bytes_after VARCHAR(255) NOT NULL, 50 | attach_name VARCHAR(255) NOT NULL, 51 | status VARCHAR(255) NOT NULL, 52 | PRIMARY KEY (id), 53 | INDEX ix_request_id(request_id), 54 | INDEX ix_attach_id(attach_id), 55 | FOREIGN KEY (request_id) 56 | REFERENCES $table_log(request_id) 57 | ON DELETE CASCADE, 58 | FOREIGN KEY (attach_id) 59 | REFERENCES $table_posts(ID) 60 | ON DELETE CASCADE 61 | ) $charset_collate;"; 62 | 63 | require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); 64 | dbDelta( $sql_log ); 65 | dbDelta( $sql_details ); 66 | 67 | return true; 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /migrations/m1x000.php: -------------------------------------------------------------------------------- 1 | prefix . models\Media::TABLE_IMAGE_STATS; 30 | $table_log_details = $wpdb->prefix . models\Log::TABLE_IMAGE_LOG_DETAILS; 31 | $table_log = $wpdb->prefix . models\Log::TABLE_IMAGE_LOG; 32 | 33 | $wpdb->query( "ALTER TABLE `{$table_stats}` CHANGE `bytes_before` `bytes_before` BIGINT(20) NOT NULL;" ); 34 | $wpdb->query( "ALTER TABLE `{$table_stats}` CHANGE `bytes_after` `bytes_after` BIGINT(20) NOT NULL;" ); 35 | 36 | $wpdb->query( "ALTER TABLE `{$table_log_details}` CHANGE `bytes_before` `bytes_before` BIGINT(20) NOT NULL;" ); 37 | $wpdb->query( "ALTER TABLE `{$table_log_details}` CHANGE `bytes_after` `bytes_after` BIGINT(20) NOT NULL;" ); 38 | 39 | return true; 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /models/Connect.php: -------------------------------------------------------------------------------- 1 | 'key', 47 | 'api_key' => 'text_field', 48 | 'status' => 'int', 49 | ); 50 | 51 | /** 52 | * Construct for Connect model 53 | */ 54 | public function __construct() { 55 | $this->reset(); 56 | } 57 | 58 | /** 59 | * Set connect options values 60 | */ 61 | public function reset() { 62 | $this->api_key = get_option( self::DB_OPT_API_KEY ); 63 | $this->service = get_option( self::DB_OPT_SERVICE ); 64 | $this->status = get_option( self::DB_OPT_STATUS ); 65 | } 66 | 67 | /** 68 | * Update Settings 69 | * 70 | * @return bool 71 | */ 72 | public function save() { 73 | $service = services\ImageOptimizerFactory::create( $this->service, $this->api_key ); 74 | if ( $service && $service->check_api_key() ) { 75 | update_option( self::DB_OPT_API_KEY, $this->api_key ); 76 | update_option( self::DB_OPT_SERVICE, $this->service ); 77 | update_option( self::DB_OPT_STATUS, '1' ); 78 | $this->reset(); 79 | 80 | flush_rewrite_rules(); 81 | 82 | return true; 83 | } else { 84 | update_option( self::DB_OPT_STATUS, '2' ); 85 | $this->reset(); 86 | 87 | return false; 88 | } 89 | } 90 | 91 | /** 92 | * Check API connect 93 | * 94 | * @return bool Return true or false. 95 | */ 96 | public static function connected() { 97 | return get_option( self::DB_OPT_STATUS ) === '1'; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /models/Log.php: -------------------------------------------------------------------------------- 1 | 'Request sent', 57 | 'aborted' => 'Optimization aborted. Image was 25x25', 58 | 'optimized' => 'Optimized', 59 | 'removed' => 'Removed from service request', 60 | ); 61 | if ( isset( $statuses[ $status ] ) ) { 62 | return $statuses[ $status ]; 63 | } else { 64 | return (string) $status; 65 | } 66 | } 67 | 68 | /** 69 | * Save optimization request start 70 | * 71 | * @return int Request log ID. 72 | */ 73 | public function start_request() { 74 | global $wpdb; 75 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG; 76 | $connect = new Connect(); 77 | $wpdb->insert( 78 | $table_name, 79 | array( 80 | self::COL_SERVICE => $connect->service, 81 | self::COL_IMAGE_LIMIT => \JustImageOptimizer::$settings->image_limit, 82 | self::COL_SIZE_LIMIT => \JustImageOptimizer::$settings->size_limit, 83 | self::COL_TIME => current_time( 'mysql' ), 84 | ) 85 | ); 86 | 87 | $this->request_id = $wpdb->insert_id; 88 | return $this->request_id; 89 | } 90 | 91 | /** 92 | * Store log info as multiline log 93 | * 94 | * @param string $line Line to add to request log info field. 95 | * 96 | * @return bool 97 | */ 98 | public function update_info( $line ) { 99 | global $wpdb; 100 | $table = $wpdb->prefix . self::TABLE_IMAGE_LOG; 101 | $request = $this->find( $this->request_id ); 102 | if ( ! $request ) { 103 | return false; 104 | } 105 | return $wpdb->update( 106 | $table, 107 | array( 108 | self::COL_INFO => $request->info . "\n" . $line, 109 | ), 110 | array( 111 | self::COL_REQUEST_ID => $this->request_id, 112 | ) 113 | ); 114 | } 115 | 116 | /** 117 | * Find log record 118 | * 119 | * @param int $request_id Request ID 120 | * 121 | * @return object 122 | */ 123 | public function find( $request_id ) { 124 | global $wpdb; 125 | $table = $wpdb->prefix . self::TABLE_IMAGE_LOG; 126 | return $wpdb->get_row( $wpdb->prepare( 127 | "SELECT * FROM $table WHERE " . self::COL_REQUEST_ID . " = %d", 128 | $request_id 129 | ) ); 130 | } 131 | 132 | /** 133 | * Save attachment stats before optimize 134 | * 135 | * @param int $request_id Request log ID. 136 | * @param int $attach_id Attach ID. 137 | * @param array $stats Array with stats attachments. 138 | */ 139 | public function save_details( $request_id, $attach_id, $stats ) { 140 | global $wpdb; 141 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 142 | 143 | $settings = \JustImageOptimizer::$settings; 144 | $not_optimized = Media::get_queued_image_sizes( $attach_id ); 145 | 146 | foreach ( $stats as $size => $file_size ) { 147 | // skip image sizes which we do not optimize by settings or they are optimized already. 148 | if ( ! in_array( $size, $not_optimized, true ) 149 | || ( ! $settings->image_sizes_all && ! in_array( $size, $settings->image_sizes, true ) ) 150 | ) { 151 | continue; 152 | } 153 | 154 | $image_data = image_get_intermediate_size( $attach_id, $size ); 155 | 156 | $wpdb->insert( 157 | $table_name, 158 | array( 159 | self::COL_ATTACH_ID => $attach_id, 160 | self::COL_TRY_ID => $request_id, 161 | self::COL_IMAGE_SIZE => $size, 162 | self::COL_BYTES_BEFORE => $file_size, 163 | self::COL_BYTES_AFTER => $file_size, 164 | self::COL_ATTACH_NAME => $image_data['file'], 165 | self::COL_STATUS => static::STATUS_PENDING, 166 | ) 167 | ); 168 | } 169 | } 170 | 171 | /** 172 | * Save specific file status 173 | * 174 | * @param int $request_id Request ID. 175 | * @param string $attach_name Attach name. 176 | * @param string $status Log status 177 | */ 178 | public function save_status( $request_id, $attach_name, $status ) { 179 | global $wpdb; 180 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 181 | $wpdb->update( 182 | $table_name, 183 | array( 184 | self::COL_STATUS => $status, 185 | ), 186 | array( 187 | self::COL_TRY_ID => $request_id, 188 | self::COL_ATTACH_NAME => $attach_name, 189 | ) 190 | ); 191 | } 192 | 193 | /** 194 | * Update specific file status 195 | * 196 | * @param int $attach_id Attach ID. 197 | * @param int $request_id Request ID. 198 | * @param string $status Log status 199 | */ 200 | public function update_status( $attach_id, $request_id, $status ) { 201 | global $wpdb; 202 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 203 | $wpdb->update( 204 | $table_name, 205 | array( 206 | self::COL_STATUS => $status, 207 | ), 208 | array( 209 | self::COL_ATTACH_ID => $attach_id, 210 | self::COL_TRY_ID => $request_id, 211 | ) 212 | ); 213 | } 214 | 215 | /** 216 | * Update attachment stats after optimization 217 | * 218 | * @param int $request_id Request ID. 219 | * @param int $attach_id Attach ID. 220 | * @param array $stats Array with stats attachments. 221 | */ 222 | public function update_details( $request_id, $attach_id, $stats ) { 223 | global $wpdb; 224 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 225 | foreach ( $stats as $size => $file_size ) { 226 | $wpdb->update( 227 | $table_name, 228 | array( 229 | self::COL_BYTES_AFTER => $file_size, 230 | ), 231 | array( 232 | self::COL_TRY_ID => $request_id, 233 | self::COL_ATTACH_ID => $attach_id, 234 | self::COL_IMAGE_SIZE => $size, 235 | ) 236 | ); 237 | } 238 | } 239 | 240 | /** 241 | * Get log store data 242 | * 243 | * @return array 244 | */ 245 | public function get_requests() { 246 | global $wpdb; 247 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG; 248 | $table_name2 = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 249 | $result = array(); 250 | $items_per_page = self::ITEMS_PER_PAGE; 251 | $page = isset( $_GET['offset'] ) ? abs( (int) $_GET['offset'] ) : 1; 252 | $offset = ( $page * $items_per_page ) - $items_per_page; 253 | $query = 'SELECT store.*, sum(log.' . self::COL_BYTES_BEFORE . ' - log.' . self::COL_BYTES_AFTER . ') as total_save, 254 | COUNT(log.id) as total_count 255 | FROM ' . $table_name . ' AS store 256 | LEFT JOIN ' . $table_name2 . ' AS log 257 | ON log.' . self::COL_TRY_ID . ' = store.' . self::COL_REQUEST_ID . ' 258 | GROUP BY store.' . self::COL_REQUEST_ID . ' 259 | '; 260 | $total_query = "SELECT COUNT(1) FROM (${query}) AS total_log"; 261 | $total = $wpdb->get_var( $total_query ); 262 | 263 | $log_store = $wpdb->get_results( $query . ' ORDER BY ' . self::COL_REQUEST_ID . ' DESC LIMIT ' . $offset . ', ' . $items_per_page, ARRAY_A ); 264 | 265 | $pagination = array( 266 | 'base' => add_query_arg( 'offset', '%#%' ), 267 | 'format' => '', 268 | 'prev_text' => __( '«' ), 269 | 'next_text' => __( '»' ), 270 | 'total' => ceil( $total / $items_per_page ), 271 | 'current' => $page, 272 | ); 273 | $result = array( 274 | 'rows' => $log_store, 275 | 'pagination' => $pagination, 276 | ); 277 | 278 | return $result; 279 | } 280 | 281 | /** 282 | * Get dashboard attachment stats 283 | * 284 | * @param int $request_id Request Log ID. 285 | * 286 | * @return array 287 | */ 288 | public function get_request_details( $request_id ) { 289 | global $wpdb; 290 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 291 | $query = 'SELECT * FROM ' . $table_name . ' 292 | WHERE ' . self::COL_TRY_ID . ' = ' . $request_id; 293 | 294 | $result = $wpdb->get_results( $query . ' ORDER BY id ASC ', ARRAY_A ); 295 | 296 | return $result; 297 | } 298 | 299 | /** 300 | * Get Attachments count by request 301 | * 302 | * @param int $try_id Store log ID. 303 | * 304 | * @return array 305 | */ 306 | public function attach_count( $try_id ) { 307 | global $wpdb; 308 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 309 | $attach_stat = $wpdb->get_results( $wpdb->prepare( 310 | " 311 | SELECT DISTINCT(" . self::COL_ATTACH_ID . ") 312 | FROM $table_name as log 313 | WHERE " . self::COL_TRY_ID . " = %d 314 | ", 315 | $try_id 316 | ) ); 317 | 318 | return count( $attach_stat ); 319 | } 320 | 321 | /** 322 | * Get Attachment file count stats 323 | * 324 | * @param int $try_id Store log ID. 325 | * @param string $status Attachment optimize status. 326 | * 327 | * @return array 328 | */ 329 | public function files_count_stat( $try_id, $status ) { 330 | global $wpdb; 331 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_LOG_DETAILS; 332 | $attach_stat = $wpdb->get_var( $wpdb->prepare( 333 | " 334 | SELECT COUNT(log.id) 335 | FROM $table_name as log 336 | WHERE " . self::COL_TRY_ID . " = %d 337 | AND " . self::COL_STATUS . " = %s 338 | ", 339 | $try_id, 340 | $status 341 | ) ); 342 | 343 | return $attach_stat; 344 | } 345 | } -------------------------------------------------------------------------------- /models/Media.php: -------------------------------------------------------------------------------- 1 | 'attachment', 34 | 'post_status' => 'inherit', 35 | 'post_mime_type' => array( 'image/jpg', 'image/jpeg', 'image/gif', 'image/png' ), 36 | 'posts_per_page' => - 1, 37 | 'orderby' => 'id', 38 | 'order' => 'ASC', 39 | ); 40 | 41 | /** 42 | * Save attachment stats before optimize 43 | * 44 | * @param int $attach_id Attach ID. 45 | * @param array $stats Array with stats attachments. 46 | */ 47 | public function save_stats( $attach_id, $stats ) { 48 | global $wpdb; 49 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 50 | 51 | $exists = $wpdb->get_col( $wpdb->prepare( 52 | "SELECT " . self::COL_IMAGE_SIZE . " FROM $table_name WHERE " . self::COL_ATTACH_ID . " = %d", 53 | $attach_id 54 | )); 55 | 56 | foreach ( $stats as $size => $file_size ) { 57 | $image_data = image_get_intermediate_size( $attach_id, $size ); 58 | // check row exist first. 59 | if ( in_array( $size, $exists ) ) { 60 | continue; 61 | } 62 | $wpdb->insert( 63 | $table_name, 64 | array( 65 | self::COL_ATTACH_ID => $attach_id, 66 | self::COL_ATTACH_NAME => $image_data['file'], 67 | self::COL_IMAGE_SIZE => $size, 68 | self::COL_BYTES_BEFORE => $file_size, 69 | self::COL_BYTES_AFTER => $file_size, 70 | ) 71 | ); 72 | } 73 | } 74 | 75 | /** 76 | * Update attachment stats after optimize 77 | * 78 | * @param int $attach_id Attach ID. 79 | * @param array $stats Array with stats attachments. 80 | */ 81 | public function update_stats( $attach_id, $stats ) { 82 | global $wpdb; 83 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 84 | foreach ( $stats as $size => $file_size ) { 85 | $wpdb->update( 86 | $table_name, 87 | array( 88 | self::COL_BYTES_AFTER => $file_size, 89 | ), 90 | array( 91 | self::COL_ATTACH_ID => $attach_id, 92 | self::COL_IMAGE_SIZE => $size, 93 | ) 94 | ); 95 | } 96 | } 97 | 98 | /** 99 | * Find image stats row (attach_id - image_size) by ID 100 | * 101 | * @param int $id Stats table ID. 102 | * 103 | * @return array|null|object|void 104 | */ 105 | public static function find_stats_by_id( $id ) { 106 | global $wpdb; 107 | $table = $wpdb->prefix . self::TABLE_IMAGE_STATS; 108 | return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE `id` = %d", $id ) ); 109 | } 110 | 111 | /** 112 | * Find image stats row (attach_id - image_size) by ID 113 | * 114 | * @param int $id Stats table ID. 115 | * 116 | * @return array|null|object|void 117 | */ 118 | public static function find_stats( $attach_id, $image_size ) { 119 | global $wpdb; 120 | $table = $wpdb->prefix . self::TABLE_IMAGE_STATS; 121 | return $wpdb->get_row( $wpdb->prepare( 122 | "SELECT * FROM $table WHERE `attach_id` = %d AND `image_size` = %s", 123 | $attach_id, 124 | $image_size 125 | ) ); 126 | } 127 | 128 | /** 129 | * Get total attachment stats 130 | * 131 | * @param int $attach_id Attach ID. 132 | * 133 | * @return object 134 | */ 135 | public function get_total_attachment_stats( $attach_id ) { 136 | global $wpdb; 137 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 138 | $total_stats = $wpdb->get_results( $wpdb->prepare( 139 | " 140 | SELECT ( sum( " . self::COL_BYTES_BEFORE . " ) 141 | - sum( " . self::COL_BYTES_AFTER . " ) ) AS saving_size, 142 | round( ( ( sum( " . self::COL_BYTES_BEFORE . " ) 143 | - sum( " . self::COL_BYTES_AFTER . " ) ) 144 | / sum( " . self::COL_BYTES_BEFORE . " ) * 100 ), 2 ) as percent, 145 | sum( " . self::COL_BYTES_AFTER . " ) as disk_usage 146 | FROM ( 147 | SELECT * FROM $table_name 148 | WHERE " . self::COL_ATTACH_ID . " = %s 149 | GROUP BY " . self::COL_ATTACH_NAME . " 150 | ) io 151 | ", 152 | $attach_id 153 | ), OBJECT ); 154 | 155 | return $total_stats; 156 | } 157 | 158 | /** 159 | * Get single attachment stats 160 | * 161 | * @param int $attach_id Attach ID. 162 | * @param string $size Attachment size. 163 | * 164 | * @return object 165 | */ 166 | public function get_attachment_stats( $attach_id, $size ) { 167 | global $wpdb; 168 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 169 | $stats = $wpdb->get_results( $wpdb->prepare( 170 | " 171 | SELECT ( " . self::COL_BYTES_BEFORE . " - " . self::COL_BYTES_AFTER . " ) AS saving_size, 172 | round( ( ( " . self::COL_BYTES_BEFORE . " 173 | - " . self::COL_BYTES_AFTER . " ) 174 | / " . self::COL_BYTES_BEFORE . " * 100 ), 2 ) as percent 175 | FROM ( 176 | SELECT * FROM $table_name 177 | WHERE " . self::COL_ATTACH_ID . " = %s 178 | AND " . self::COL_IMAGE_SIZE . " = %s 179 | GROUP BY " . self::COL_ATTACH_NAME . " 180 | ) io", 181 | $attach_id, 182 | $size 183 | ), OBJECT ); 184 | 185 | return $stats; 186 | } 187 | 188 | /** 189 | * Get dashboard attachment stats 190 | * 191 | * @return object 192 | */ 193 | public function get_dashboard_attachment_stats() { 194 | global $wpdb; 195 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 196 | $media_disk_usage = $this->get_images_disk_usage(); 197 | $dashboard_stats = $wpdb->get_results( $wpdb->prepare( 198 | " 199 | SELECT ( sum( " . self::COL_BYTES_BEFORE . " ) 200 | - sum( " . self::COL_BYTES_AFTER . " ) ) AS saving_size, 201 | round( ( sum( " . self::COL_BYTES_BEFORE . " ) 202 | - sum( " . self::COL_BYTES_AFTER . " ) ) 203 | / %s * 100, 2 ) AS percent 204 | FROM ( 205 | SELECT * FROM $table_name 206 | GROUP BY " . self::COL_ATTACH_NAME . " 207 | ) io", 208 | $media_disk_usage 209 | ), OBJECT ); 210 | 211 | return $dashboard_stats; 212 | } 213 | 214 | /** 215 | * Clear statistics after Regenerate Thumbnails 216 | * 217 | * @param int $attach_id Attachment ID. 218 | */ 219 | public function clean_statistics( $attach_id ) { 220 | global $wpdb; 221 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 222 | $wpdb->delete( 223 | $table_name, 224 | array( self::COL_ATTACH_ID => $attach_id ) 225 | ); 226 | update_post_meta( $attach_id, '_just_img_opt_status', self::STATUS_IN_QUEUE ); 227 | } 228 | 229 | /** 230 | * Get all date upload dir 231 | * 232 | * @return array 233 | */ 234 | public static function get_uploads_path() { 235 | $path = array(); 236 | // TODO: (after launch) check this with multisite. 237 | foreach ( glob( wp_upload_dir()['basedir'] . '/*', GLOB_ONLYDIR ) as $upload ) { 238 | foreach ( glob( $upload . '/*', GLOB_ONLYDIR ) as $upload_dir ) { 239 | $path[] = $upload_dir; 240 | } 241 | } 242 | 243 | return $path; 244 | } 245 | 246 | /** 247 | * Get filesize attachment in Kb 248 | * 249 | * @param string $attach_file Attachment file. 250 | * 251 | * @return float|int 252 | */ 253 | public function get_filesize( $attach_file ) { 254 | $attach_filesize = filesize( $attach_file ); 255 | 256 | return $attach_filesize; 257 | } 258 | 259 | /** 260 | * Get total|single filesizes attachments in bytes 261 | * 262 | * @param int $id Attachment ID. 263 | * @param string $type For get total size = 'total' or sizes array = 'detailed'. 264 | * 265 | * @return int|float|boolean|array 266 | */ 267 | public function get_file_sizes( $id, $type = 'detailed' ) { 268 | global $wp_filesystem; 269 | WP_Filesystem(); 270 | $total_size = 0; 271 | $sizes_array = array(); 272 | $meta = wp_get_attachment_metadata( $id ); 273 | $get_path = $this->get_uploads_path(); 274 | if ( ! $meta ) { 275 | return 0; 276 | } 277 | 278 | foreach ( $meta['sizes'] as $size_key => $attachment ) { 279 | if( basename( $meta['file'] ) !== $attachment['file'] ) { 280 | foreach ( $get_path as $path ) { 281 | if ( $wp_filesystem->exists( $path . '/' . $attachment['file'] ) ) { 282 | $sizes_array[ $size_key ] = $this->get_filesize( $path . '/' . $attachment['file'] ); 283 | } 284 | } 285 | } 286 | } 287 | foreach ( $sizes_array as $size ) { 288 | $total_size = $total_size + $size; 289 | } 290 | if ( 'detailed' === $type ) { 291 | return $sizes_array; 292 | } else { 293 | return $total_size; 294 | } 295 | } 296 | 297 | /** 298 | * Get count additional sizes images 299 | * 300 | * @param int $id Attachment ID. 301 | * 302 | * @return float|null 303 | */ 304 | public function get_count_images( $id ) { 305 | $count = 0; 306 | $files = array(); 307 | $get_metadata = wp_get_attachment_metadata( $id ); 308 | if ( $get_metadata ) { 309 | foreach ( $get_metadata['sizes'] as $size ) { 310 | if ( ! isset( $size['file'] ) ) { 311 | continue; 312 | } 313 | $files[ $size['file'] ] = $size; 314 | } 315 | $count = count( $files ); 316 | 317 | return $count; 318 | } 319 | 320 | return $count; 321 | } 322 | 323 | /** 324 | * Get additional sizes images 325 | * 326 | * @return array 327 | */ 328 | public static function image_dimensions() { 329 | global $_wp_additional_image_sizes; 330 | $additional_sizes = get_intermediate_image_sizes(); 331 | $sizes = array(); 332 | // Create the full array with sizes and crop info. 333 | foreach ( $additional_sizes as $_size ) { 334 | if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) { 335 | $sizes[ $_size ]['width'] = get_option( $_size . '_size_w' ); 336 | $sizes[ $_size ]['height'] = get_option( $_size . '_size_h' ); 337 | $sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' ); 338 | } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) { 339 | $sizes[ $_size ] = array( 340 | 'width' => $_wp_additional_image_sizes[ $_size ]['width'], 341 | 'height' => $_wp_additional_image_sizes[ $_size ]['height'], 342 | 'crop' => $_wp_additional_image_sizes[ $_size ]['crop'], 343 | ); 344 | } 345 | } 346 | // Medium Large. 347 | if ( ! isset( $sizes['medium_large'] ) || empty( $sizes['medium_large'] ) ) { 348 | $width = intval( get_option( 'medium_large_size_w' ) ); 349 | $height = intval( get_option( 'medium_large_size_h' ) ); 350 | 351 | $sizes['medium_large'] = array( 352 | 'width' => $width, 353 | 'height' => $height, 354 | ); 355 | } 356 | 357 | return apply_filters( 'jio_settings_image_sizes', $sizes ); 358 | 359 | } 360 | 361 | /** 362 | * Get Count Images with status in_queue or count all images 363 | * 364 | * @param bool $all Check all images or in queue. 365 | * 366 | * @return int 367 | */ 368 | public function get_images_stat( $all = false ) { 369 | $args = $this->query_args; 370 | if ( false === $all ) { 371 | $args['meta_query'] = array( 372 | array( 373 | 'key' => '_just_img_opt_status', 374 | 'value' => Media::STATUS_IN_QUEUE, 375 | ), 376 | ); 377 | } 378 | $query = new \WP_Query( $args ); 379 | 380 | return $query->post_count; 381 | } 382 | 383 | /** 384 | * Get Total image size 385 | * 386 | * @return int 387 | */ 388 | public function get_images_disk_usage() { 389 | $disk_usage = 0; 390 | // Init ms-functions for get_dirsize. 391 | require_once ABSPATH . WPINC .'/ms-functions.php'; 392 | $get_path = self::get_uploads_path(); 393 | foreach( $get_path as $path ) { 394 | $disk_usage = $disk_usage + get_dirsize( $path ); 395 | } 396 | 397 | return $disk_usage; 398 | } 399 | 400 | /** 401 | * Get count images with status processed 402 | * 403 | * @return int 404 | */ 405 | public function get_count_images_processed() { 406 | $args = $args = $this->query_args; 407 | $args['meta_query'] = array( 408 | array( 409 | 'key' => '_just_img_opt_status', 410 | 'value' => Media::STATUS_PROCESSED, 411 | ), 412 | ); 413 | $query = new \WP_Query( $args ); 414 | 415 | return $query->post_count; 416 | } 417 | 418 | /** 419 | * Get count images in queue from total count 420 | * 421 | * @return int|float 422 | */ 423 | public function get_in_queue_image_count() { 424 | return $this->get_images_stat( true ) - $this->get_count_images_processed(); 425 | } 426 | 427 | /** 428 | * Get saving sizes from space size 429 | * 430 | * @return int|float 431 | */ 432 | public function get_disk_space_size() { 433 | $total_size = $this->get_images_disk_usage(); 434 | $saving_size = $this->get_dashboard_attachment_stats(); 435 | if ( ! empty( $saving_size[0]->saving_size ) ) { 436 | $space_size = $total_size - $saving_size[0]->saving_size; 437 | } else { 438 | $space_size = $total_size; 439 | } 440 | 441 | return $space_size; 442 | } 443 | 444 | /** 445 | * Get size format without units 446 | * 447 | * @param int $bytes Size in bytes. 448 | * 449 | * @return array 450 | */ 451 | public function size_format_explode( $bytes ) { 452 | $size = array( 453 | 'bytes' => $bytes, 454 | 'unit' => jio_size_format( $bytes ), 455 | ); 456 | 457 | return $size; 458 | } 459 | 460 | /** 461 | * Check size limit images optimization 462 | * 463 | * @param array $attach_ids Array attach ids. 464 | * 465 | * @return array Array attach ids. 466 | */ 467 | public function size_limit( array $attach_ids ) { 468 | $size_limit = 0; 469 | $size_array = array(); 470 | $array_ids = array(); 471 | if ( 0 < \JustImageOptimizer::$settings->size_limit ) { 472 | foreach ( $attach_ids as $attach_id ) { 473 | $size_array[ $attach_id ] = $this->get_file_sizes( $attach_id, 'total' ); 474 | } 475 | foreach ( $attach_ids as $attach_id ) { 476 | if ( (int) number_format_i18n( $size_limit / 1048576 ) >= (int) \JustImageOptimizer::$settings->size_limit ) { 477 | return $array_ids; 478 | } 479 | $size_limit = $size_limit + $size_array[ $attach_id ]; 480 | $array_ids[ $attach_id ] = $attach_id; 481 | } 482 | } 483 | 484 | return $attach_ids; 485 | } 486 | 487 | /** 488 | * Check optimization table and define what is the real optimization status. 489 | * 490 | * @param int $attach_id Attachment ID to check. 491 | * 492 | * @return int real image optimization status 493 | */ 494 | public static function check_optimization_status( int $attach_id ) { 495 | global $wpdb; 496 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 497 | 498 | $tried_images = $wpdb->get_var( $wpdb->prepare( 499 | " 500 | SELECT COUNT(`id`) 501 | FROM $table_name 502 | WHERE `attach_id` = %d 503 | ", 504 | $attach_id 505 | ) ); 506 | 507 | $failed_images = $wpdb->get_var( $wpdb->prepare( 508 | " 509 | SELECT COUNT(`id`) 510 | FROM $table_name 511 | WHERE `attach_id` = %d 512 | AND `bytes_before` <= `bytes_after` 513 | ", 514 | $attach_id 515 | ) ); 516 | 517 | $status = static::STATUS_IN_QUEUE; 518 | if ( $tried_images ) { 519 | $status = static::STATUS_PROCESSED; 520 | if ( $failed_images ) { 521 | $status = static::STATUS_PARTIALY_PROCESSED; 522 | } 523 | } 524 | 525 | return $status; 526 | } 527 | 528 | /** 529 | * Get queued image sizes, which are not optimized yet. 530 | * 531 | * @param int $attach_id Attachment ID to get image sizes in queue. 532 | * 533 | * @return string[] image sizes names 534 | */ 535 | public static function get_queued_image_sizes( int $attach_id ) { 536 | global $wpdb; 537 | $table_name = $wpdb->prefix . self::TABLE_IMAGE_STATS; 538 | $queued_images = $wpdb->get_col( $wpdb->prepare( 539 | " 540 | SELECT image_size 541 | FROM $table_name 542 | WHERE `attach_id` = %d 543 | AND `bytes_before` <= `bytes_after` 544 | ", 545 | $attach_id 546 | ) ); 547 | return $queued_images; 548 | } 549 | 550 | } 551 | -------------------------------------------------------------------------------- /models/Migrate.php: -------------------------------------------------------------------------------- 1 | load() 11 | * 12 | * @var string 13 | */ 14 | public $upgrade_storage; 15 | 16 | /** 17 | * HTML error message with link to admin upgrade page 18 | */ 19 | public static function adminUpgradeNotice() { 20 | $link_text = __( 'Update migrations', \JustImageOptimizer::TEXTDOMAIN ); 21 | $link = '' . $link_text . ''; 22 | 23 | $warning = __( 'You need to update your migrations to continue using the plugin Just Image Optimizer. {link}', \JustImageOptimizer::TEXTDOMAIN ); 24 | $warning = str_replace( '{link}', $link, $warning ); 25 | 26 | printf( '

%2$s

', 'notice notice-error', $warning ); 27 | } 28 | 29 | /** 30 | * Search available migrations 31 | * set protected $_version, $_migrations properties 32 | * 33 | * @return Migration[] 34 | */ 35 | public function findMigrations() { 36 | $migrations = array(); 37 | if ( $migration_files = $this->_getMigrationFiles( \JustImageOptimizer::$opt_version ) ) { 38 | foreach ( $migration_files as $ver => $file ) { 39 | $class_name = '\\JustCoded\\WP\\ImageOptimizer\\migrations\\' . preg_replace( '/\.php$/', '', basename( $file ) ); 40 | 41 | require_once $file; 42 | $migrations[ $ver ] = new $class_name(); 43 | } 44 | } 45 | 46 | return $migrations; 47 | } 48 | 49 | /** 50 | * Scan migrations directory and filter outdated migration based on current version 51 | * 52 | * @param float $version 53 | * 54 | * @return array 55 | */ 56 | protected function _getMigrationFiles( $version ) { 57 | $folder = JUSTIMAGEOPTIMIZER_ROOT . '/migrations'; 58 | $files = scandir( $folder ); 59 | 60 | $migrations = array(); 61 | 62 | foreach ( $files as $key => $file ) { 63 | if ( $file == '.' || $file == '..' || ! is_file( $folder . '/' . $file ) 64 | || ! preg_match( '/^m([\dx]+)/', $file, $match ) 65 | ) { 66 | continue; 67 | } 68 | 69 | $mig_version = str_replace( 'x', '.', $match[1] ); 70 | if ( version_compare( $mig_version, $version, '<=' ) ) { 71 | continue; 72 | } 73 | 74 | $migrations[ $mig_version ] = $folder . '/' . $file; 75 | } 76 | ksort( $migrations ); 77 | 78 | return $migrations; 79 | } 80 | 81 | /** 82 | * Do test run to check that we can migrate or need to show warnings 83 | * 84 | * @param Migration[] $migrations 85 | * 86 | * @return array 87 | */ 88 | public function testMigrate( $migrations ) { 89 | $warnings = array(); 90 | 91 | foreach ( $migrations as $ver => $m ) { 92 | if ( $warning = $m->runTest() ) { 93 | $warnings[ $ver ] = $warning; 94 | } 95 | } 96 | 97 | return $warnings; 98 | } 99 | 100 | /** 101 | * Run migrations 102 | * 103 | * @param Migration[] $migrations 104 | * 105 | * @return boolean 106 | */ 107 | public function migrate( $migrations ) { 108 | if ( ! empty( $migrations ) ) { 109 | set_time_limit( 0 ); 110 | 111 | foreach ( $migrations as $ver => $m ) { 112 | $m->runUpdate( Migration::MODE_UPDATE ); 113 | } 114 | // TODO: (future releases) check wpdb error inside the migrations and return migration status to not update version in case of error. 115 | update_option( \JustImageOptimizer::OPT_VERSION, \JustImageOptimizer::$version ); 116 | } else { 117 | update_option( \JustImageOptimizer::OPT_VERSION, \JustImageOptimizer::$version ); 118 | } 119 | 120 | return true; 121 | } 122 | } -------------------------------------------------------------------------------- /models/Settings.php: -------------------------------------------------------------------------------- 1 | 'int', 86 | 'image_sizes.*' => 'text_field', 87 | 'auto_optimize' => 'int', 88 | 'image_limit' => 'int', 89 | 'size_limit' => 'int', 90 | 'tries_count' => 'int', 91 | 'before_regen' => 'int', 92 | 'keep_origin' => 'int', 93 | ); 94 | 95 | /** 96 | * Construct for Settings model 97 | */ 98 | public function __construct() { 99 | $this->reset(); 100 | } 101 | 102 | /** 103 | * Set setting options values 104 | */ 105 | public function reset() { 106 | $this->image_sizes = maybe_unserialize( get_option( self::DB_OPT_IMAGE_SIZES, array() ) ); 107 | $this->auto_optimize = get_option( self::DB_OPT_AUTO_OPTIMIZE, '1' ); 108 | $this->image_limit = get_option( self::DB_OPT_IMAGE_LIMIT, 5 ); 109 | $this->size_limit = get_option( self::DB_OPT_SIZE_LIMIT, 10 ); 110 | $this->tries_count = get_option( self::DB_OPT_TRIES_COUNT, 5 ); 111 | $this->before_regen = get_option( self::DB_OPT_BEFORE_REGEN ); 112 | $this->image_sizes_all = get_option( self::DB_OPT_SIZE_CHECKED, '1' ); 113 | $this->keep_origin = get_option( self::DB_OPT_KEEP_ORIGIN ); 114 | } 115 | 116 | /** 117 | * Set attributes processor 118 | * 119 | * @param array $params Input data. 120 | */ 121 | public function set_attributes( $params ) { 122 | parent::set_attributes( $params ); 123 | if ( empty( $this->image_sizes ) ) { 124 | $this->image_sizes = array(); 125 | } 126 | } 127 | 128 | /** 129 | * Update options 130 | */ 131 | public function save() { 132 | update_option( self::DB_OPT_IMAGE_SIZES, $this->image_sizes ); 133 | update_option( self::DB_OPT_IMAGE_LIMIT, $this->image_limit ); 134 | update_option( self::DB_OPT_SIZE_LIMIT, $this->size_limit ); 135 | update_option( self::DB_OPT_BEFORE_REGEN, $this->before_regen ); 136 | update_option( self::DB_OPT_AUTO_OPTIMIZE, $this->auto_optimize ); 137 | update_option( self::DB_OPT_SIZE_CHECKED, $this->image_sizes_all ); 138 | update_option( self::DB_OPT_KEEP_ORIGIN, $this->keep_origin ); 139 | update_option( self::DB_OPT_TRIES_COUNT, $this->tries_count ); 140 | $this->reset(); 141 | 142 | flush_rewrite_rules(); 143 | 144 | return true; 145 | } 146 | 147 | /** 148 | * Check first saved setting options 149 | * 150 | * @return bool true or false. 151 | */ 152 | public function saved() { 153 | return get_option( self::DB_OPT_KEEP_ORIGIN ) === '1'; 154 | } 155 | 156 | /** 157 | * Check requirements for accesses wp-content. 158 | * 159 | * @param bool $force_check Ignore caches within requriements check. 160 | * 161 | * @return bool true or false. 162 | */ 163 | public function check_requirements( $force_check = false ) { 164 | $php_vers = version_compare( phpversion(), '7.0', '>' ); 165 | $wp_content = wp_is_writable( WP_CONTENT_DIR ); 166 | $service = \JustImageOptimizer::$service && \JustImageOptimizer::$service->check_availability( $force_check ); 167 | return $php_vers && $wp_content && $service; 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Just Image Optimizer === 2 | Contributors: aprokopenko 3 | Plugin Name: Just Image Optimizer 4 | Version: 1.1.2 5 | Description: Compress image files, improve performance and boost your SEO rank using Google Page Speed Insights compression and optimization. 6 | Tags: image, resize, optimize, optimise, compress, performance, optimisation, optimise JPG, pictures, optimizer, Google Page Speed 7 | Author: JustCoded 8 | Author URI: https://justcoded.com 9 | Requires at least: 4.5 10 | Tested up to: 5.0.1 11 | Requires PHP: >=5.6 12 | License: GPLv2 or later 13 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 14 | Stable tag: trunk 15 | 16 | Just Image Optimizer uses Google Page Speed Insights API to compress image files, improve performance and boost your SEO rank. 17 | 18 | == Description == 19 | 20 | It's the only plugin that will help you pass Google Page Speed image size optimization test. Furthermore, it compresses image file size, so you get a performance boost and improve your page rank in Google. 21 | 22 | The plugin uses Google Page Speed Insights API to optimize images. All you need is a Google console account and an API key. 23 | 24 | Image Optimization runs in the background on Google servers, so the site will keep its performance intact. There are no special server requirements. 25 | 26 | = Requirements = 27 | 28 | * Site should be available for Google Page Speed test 29 | * PHP 7 30 | 31 | = Issues tracker = 32 | 33 | If you have any feedback or ideas, please raise an issue in our GitHub repository: 34 | https://github.com/justcoded/just-image-optimizer/issues 35 | 36 | = Plugin compatibility = 37 | 38 | Plugin is compatible with such plugins: 39 | 40 | * [Regenerate Thumbnails](https://wordpress.org/plugins/regenerate-thumbnails/) 41 | * [Just Responsive Images](https://wordpress.org/plugins/just-responsive-images/) 42 | 43 | In the upcoming releases, we plan to add compatibility with WordPress MultiSite installation. 44 | 45 | == Installation == 46 | 47 | 1. Upload `just-image-optimizer` plugin to your `/wp-content/plugins/` directory. 48 | 2. Activate the plugin through 'Plugins' menu in WordPress. 49 | 3. Configure your API key and desired settings via the `Media -> Just Image Optimizer` settings page. 50 | 4. Done! 51 | 52 | == Frequently Asked Questions == 53 | 54 | = My image optimization is always "0.00% saved" = 55 | 56 | * Please open plugin Settings page. It will check for plugin requirements on your server. 57 | * If you don't have any errors on Settings page, then please check logs. You should see that plugin try to optimize at least 1 attachment. 58 | * Next step is to check that Optimize request URL is accessible by Google Page Speed (just copy it and try to test it with the service). 59 | * If nothing help, please write to us on Github with screenshots of your Settings page, Log page and last Log details page. 60 | 61 | = Can I revert original images quality? = 62 | 63 | To revert original quality of the images, you can use [Regenerate Thumbnails](https://wordpress.org/plugins/regenerate-thumbnails/) plugin. 64 | 65 | Just regenerate a single image or all images at once to create new images from the source. 66 | 67 | == Screenshots == 68 | 69 | 1. Optimization service Connect options. 70 | 2. Plugin Settings page. 71 | 3. Plugin Dashboard with optimization statistics. 72 | 4. Media library optimization info. 73 | 74 | == Upgrade Notice == 75 | 76 | No special actions are required during the upgrade. 77 | 78 | == Changelog == 79 | 80 | = 1.1.3 = 81 | * Update: Skip log if no attachments found. 82 | 83 | = 1.1.2 = 84 | * New: Added notice if site is not available online. 85 | * New: Added notice if PHP version is below 7.0. 86 | * Bug fix: Dashboard image size statistics fatal error. 87 | 88 | = 1.1.1 = 89 | * New: Added notice if wp-content is not writable, cause it's required for storing files. 90 | * Bug fix: Fake cron run without plugin settings saved. 91 | 92 | = 1.1 = 93 | * Added compatibility with Just Responsive Images plugin (v1.5+) 94 | * Added compatibility with Regenerate Thumbnails plugin (v3+) 95 | 96 | = 1.0 = 97 | * Upgraded optimization logic to continuosly optimizing images with several tries, due to unstable Google Page Speed API responses. 98 | * Improved Log. 99 | 100 | = 0.9 = 101 | * First beta version of the plugin. We still work on compatibility with 3rd party plugins and WordPress MultiSite installation. 102 | -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-image-optimizer/47a671c4166f6bc26c35a7590c500a9d7c94ea72/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-image-optimizer/47a671c4166f6bc26c35a7590c500a9d7c94ea72/screenshot-2.png -------------------------------------------------------------------------------- /screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-image-optimizer/47a671c4166f6bc26c35a7590c500a9d7c94ea72/screenshot-3.png -------------------------------------------------------------------------------- /screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-image-optimizer/47a671c4166f6bc26c35a7590c500a9d7c94ea72/screenshot-4.png -------------------------------------------------------------------------------- /services/GooglePagespeed.php: -------------------------------------------------------------------------------- 1 | api_key . ''; 55 | $response = wp_remote_get( $url_req, array( 'timeout' => 60 ) ); 56 | $code = wp_remote_retrieve_response_code( $response ); 57 | return ( 200 === $code ); 58 | } 59 | 60 | 61 | /** 62 | * Check Service availability (that it has correct mode or site access) 63 | * 64 | * @param bool $force_check Ignore caches within requriements check. 65 | * 66 | * @return bool 67 | */ 68 | public function check_availability( $force_check = false ) { 69 | // generate unique transient key based on domain, this will minimize requests on same domain. 70 | $transient_key = 'jio_service_availability.google_page_speed.' . home_url(); 71 | 72 | $status = get_transient( $transient_key ); 73 | if ( false === $status || $force_check ) { 74 | $url_req = self::API_URL . 'url=' . home_url() . '&key=' . $this->api_key . ''; 75 | $response = wp_remote_get( $url_req, array( 'timeout' => 60 ) ); 76 | $code = wp_remote_retrieve_response_code( $response ); 77 | 78 | $status = (int) ( 200 === $code ); 79 | set_transient( $transient_key, $status, 3600 * 60 * 24 ); 80 | } 81 | return $status; 82 | } 83 | 84 | /** 85 | * Optimize images and save to destination directory 86 | * 87 | * @param int[] $attach_ids Attachment ids to optimize. 88 | * @param string $dst Directory to save image to. 89 | * @param models\Log $log Log object. 90 | * 91 | * @return mixed 92 | */ 93 | public function upload_optimize_images( $attach_ids, $dst, $log ) { 94 | /* @var $wp_filesystem \WP_Filesystem_Direct */ 95 | global $wp_filesystem; 96 | 97 | $base_attach_ids = base64_encode( implode( ',', $attach_ids ) ); 98 | $upload_dir = WP_CONTENT_DIR; 99 | $google_img_path = $dst . '/image/'; 100 | $wp_filesystem->is_dir( $google_img_path ) || $wp_filesystem->mkdir( $google_img_path ); 101 | 102 | $images_url = home_url( '/just-image-optimize/google/' . $base_attach_ids ) . '?' . rand( 0, 10000 ); 103 | $log->update_info( 'Optimize request: ' . $images_url ); 104 | 105 | // download archive file with optimized images. 106 | $archive_file = $upload_dir . '/optimize_contents.zip'; 107 | $source = self::OPTIMIZE_CONTENTS . 'key=' . $this->api_key . '&url=' . rawurlencode( $images_url ) . '&strategy=desktop'; 108 | 109 | $response = wp_remote_get( $source, array( 110 | 'httpversion' => '1.1', 111 | 'sslverify' => false, 112 | 'timeout' => 120, 113 | 'stream' => true, 114 | 'filename' => $archive_file, 115 | ) ); 116 | if ( is_wp_error( $response ) ) { 117 | $log->update_info( 'WP Error: ' . $response->get_error_message() ); 118 | return false; 119 | } 120 | 121 | $log->update_info( 'Downloaded: ' . $archive_file . ', ' . ( (int) @filesize( $archive_file ) ) . 'B' ); 122 | 123 | // optimized images are placed under /image folder inside the archive, so $google_img_path = $dst . '/image'. 124 | $unzipfile = unzip_file( $archive_file, $dst ); 125 | if ( ! is_wp_error( $unzipfile ) ) { 126 | // Get array of all source files. 127 | $files = scandir( $google_img_path ); 128 | $counter = 0; 129 | foreach ( $files as $file ) { 130 | if ( in_array( $file, array( '.', '..' ), true ) || 131 | ! preg_match( '/([\d]+)\.(.+)/', $file, $match ) 132 | ) { 133 | continue; 134 | } 135 | // find media stats row corresponding to this image. 136 | if ( ! $stats_row = models\Media::find_stats_by_id( $match[1] ) ) { 137 | continue; 138 | } 139 | 140 | // copy optimized image under real filename. 141 | if ( ! $wp_filesystem->is_file( $dst . $stats_row->attach_name ) ) { 142 | copy( $google_img_path . $file, $dst . $stats_row->attach_name ); 143 | } 144 | $counter ++; 145 | } 146 | if ( is_dir( $google_img_path ) ) { 147 | $wp_filesystem->rmdir( $google_img_path, true ); 148 | } 149 | unlink( $archive_file ); 150 | 151 | $log->update_info( 'Extracted: ' . $counter . ' files' ); 152 | return $counter; 153 | } else { 154 | $log->update_info( 'WP Error: ' . $unzipfile->get_error_message() ); 155 | return false; 156 | } 157 | } 158 | 159 | /** 160 | * Add custom rewrite url. 161 | */ 162 | public function add_rewrite_rules() { 163 | add_rewrite_rule( '^just-image-optimize/google/image/([\d]+)', 'index.php?just-image-optimize=google-image&image_size_id=$matches[1]', 'top' ); 164 | add_rewrite_rule( '^just-image-optimize/google/(.+)', 'index.php?just-image-optimize=google-page&attach_ids=$matches[1]', 'top' ); 165 | } 166 | 167 | /** 168 | * Add custom query vars. 169 | * 170 | * @param array $query_vars Array with WordPress query_vars. 171 | * 172 | * @return array Array with new query_vars. 173 | */ 174 | public function query_vars( $query_vars ) { 175 | $query_vars[] = 'just-image-optimize'; 176 | $query_vars[] = 'attach_ids'; 177 | $query_vars[] = 'image_size_id'; 178 | 179 | return $query_vars; 180 | } 181 | 182 | /** 183 | * Render optimize page for upload images 184 | */ 185 | public function parse_request() { 186 | global $wp; 187 | if ( ! empty( $wp->query_vars['just-image-optimize'] ) ) { 188 | switch ( $wp->query_vars['just-image-optimize'] ) { 189 | case 'google-page': 190 | $this->render_images_page( $wp->query_vars['attach_ids'] ); 191 | break; 192 | 193 | case 'google-image': 194 | $this->render_image_proxy( $wp->query_vars['image_size_id'] ); 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Print a page with all image attachment sizes to scan it with google. 201 | * 202 | * @param array $attach_ids Attachment IDs for optimization. 203 | */ 204 | protected function render_images_page( $attach_ids ) { 205 | require ABSPATH . 'wp-admin/includes/file.php'; 206 | 207 | // extract attach ids. 208 | $attach_ids = base64_decode( $attach_ids ); 209 | $attach_ids = explode( ',', $attach_ids ); 210 | 211 | (new Component())->render( 'optimize/google-page-speed', array( 212 | 'attach_ids' => $attach_ids, 213 | 'service' => $this, 214 | 'media' => new models\Media(), 215 | 'settings' => \JustImageOptimizer::$settings, 216 | ) ); 217 | exit; 218 | } 219 | 220 | /** 221 | * Generate image proxy URL to be able identify images after optimization. 222 | * 223 | * @param int $attach_id Attach to optimize. 224 | * @param string $image_size Image size to optimize. 225 | * 226 | * @return string Image proxy URL. 227 | */ 228 | public function get_image_proxy_url( $attach_id, $image_size ) { 229 | if ( $row = models\Media::find_stats( $attach_id, $image_size ) ) { 230 | // small cache to skip duplicated filenames. 231 | if ( ! isset( $this->attach_filenames[ $row->attach_name ] ) ) { 232 | $filename_parts = explode( '.', $row->attach_name ); 233 | $extension = end( $filename_parts ); 234 | 235 | $this->attach_filenames[ $row->attach_name ] = $row->attach_name; 236 | return home_url( "/just-image-optimize/google/image/{$row->id}.{$extension}" ); 237 | } 238 | } 239 | } 240 | 241 | /** 242 | * Proxy to print real attachment image under custom URL with ID. 243 | * 244 | * @param int $image_size_id Image stats size ID. 245 | */ 246 | protected function render_image_proxy( $image_size_id ) { 247 | if ( $row = models\Media::find_stats_by_id( $image_size_id ) ) { 248 | $metadata = wp_get_attachment_metadata( $row->attach_id ); 249 | $path = get_attached_file( $row->attach_id, true ); 250 | if ( ! empty( $metadata['sizes'][ $row->image_size ] ) ) { 251 | $mime_type = $metadata['sizes'][ $row->image_size ]['mime-type']; 252 | $file = dirname( $path ) . '/' . $row->attach_name; 253 | 254 | header( 'Content-type: ' . $mime_type ); 255 | readfile( $file ); 256 | } 257 | } 258 | exit; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /services/ImageOptimizerFactory.php: -------------------------------------------------------------------------------- 1 | service ); 25 | if ( $service ) { 26 | switch ( $service ) { 27 | case 'google_insights': 28 | $google_insights = new GooglePagespeed(); 29 | $google_insights->api_key = ( $api_key ? $api_key : $connect->api_key ); 30 | 31 | return $google_insights; 32 | default: 33 | throw new \Exception( "Service \"{$service}\" does not exists." ); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services/ImageOptimizerInterface.php: -------------------------------------------------------------------------------- 1 | 4 |

5 | -------------------------------------------------------------------------------- /views/dashboard/_requirements.php: -------------------------------------------------------------------------------- 1 | 6 | check_requirements( $force_requirements_check ) ) : ?> 7 |
8 | Please check that all requirements are met: 9 | 23 |
24 | -------------------------------------------------------------------------------- /views/dashboard/connect.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | Connection options updated! 6 |
7 | 8 | 9 |
10 | API key is invalid! 11 |
12 | 13 |
14 | 15 | 16 | 17 | 27 | 28 |
26 |
29 |
30 |

31 | 32 | 33 | 34 | 76 | 77 | 78 | 79 | 89 | 90 |
35 | 36 | 39 |

where i can find my API key?

40 |
41 | 74 |
75 |
80 | 81 | status === '1' ) : ?> 82 | 83 |

Connected

84 | 85 | 86 |

87 | 88 |
91 |
92 | status === '1' ? '' : 'disabled' ); ?> 93 | type="submit" name="submit-connect" id="submit-connect" 94 | class="button button-primary" value="Save"> 95 |
96 |
97 | -------------------------------------------------------------------------------- /views/dashboard/index.php: -------------------------------------------------------------------------------- 1 | get_dashboard_attachment_stats(); 4 | $dash_saving_size = ( ! empty( $dash_stats[0]->saving_size ) ? $dash_stats[0]->saving_size : 0 ); 5 | $dash_saving_percent = ( ! empty( $dash_stats[0]->percent ) ? $dash_stats[0]->percent : 0 ); 6 | $chart_saving = $model->size_format_explode( $dash_saving_size ); 7 | $chart_disk_space = $model->size_format_explode( $model->get_disk_space_size() ); 8 | ?> 9 |
10 | 11 | 12 | 13 | auto_optimize ) ) : ?> 14 |
15 | Automatic image optimization is disabled. Please check 16 | Settings 17 | tab to enable it. 18 |

19 | 20 | image_sizes ) ) : ?> 21 |
22 | Image sizes for optimization are not selected. Please check 23 | Settings 24 | tab to select sizes. 25 |
26 | 27 |
28 |
29 |

Progress

30 |

get_images_stat( false ) === '0' ? 31 | $model->get_images_stat( false ) : 32 | $model->get_in_queue_image_count() ); ?> images 33 | of get_images_stat( true ) ); ?> 34 | are in queue.

35 |
36 |
37 |
38 |

Disk Space Saving

39 |

% saved (), 40 | disk usage: get_images_disk_usage() ); ?>

41 |
42 |
43 | 44 |
45 |

We recommend

46 |

To optimize your site and get high score 47 | on Google PageSpeed Insight we 48 | recommend such plugins:

49 | 56 |
57 |
58 | 110 |
111 | -------------------------------------------------------------------------------- /views/dashboard/settings.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | saved() && $model->check_requirements() ) : ?> 6 |
7 | Please confirm the settings below and Save them. 8 |

9 | 10 | 11 |
12 | Settings options updated! 13 | Go to Dashboard page 14 | to view the general statistics. 15 |
16 | 17 |
18 | 19 | 20 | 23 | 30 | 31 | 32 | 35 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 85 | 86 | 89 | 90 | 98 | 99 | */ ?> 100 |
21 | 22 | 24 | 25 | auto_optimize ); ?> 26 | type="checkbox" 27 | name="auto_optimize" 28 | value="1"> 29 |
33 | 34 | 36 | 37 | 42 |
43 | 44 | 45 | $dimensions ) : ?> 46 | 53 | 54 |
55 |
60 | 61 |
66 | 68 |

How many Media can be optimized at a time

69 |
74 | 75 |

Filesize limit for one optimization request, in MB.

76 |

* set 0 to turn off this limit.

77 |
82 | 83 |

Tries count for optimization request

84 |
91 | 92 | before_regen ); ?> 93 | type="checkbox" 94 | name="before_regen" 95 | value="1"> 96 |

Can affect server performance if you upload images very often

97 |
101 | check_requirements() ? '' : 'disabled' ); ?> 102 | type="submit" name="submit-settings" class="button button-primary" value="Save"> 103 |
104 |
105 | -------------------------------------------------------------------------------- /views/log/index.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 |

List of optimization request to the 3rd-party services.

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | get_requests(); ?> 27 | 28 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
Log IDDateServiceImage/Size LimitsAttachments #Img Sizes #Optimized / FailedSaved Size
33 | 34 | 35 | 36 | attachm. / MBattach_count( $request_id ) ); ?> 43 | files_count_stat( $request_id, Log::STATUS_OPTIMIZED ) ); ?> 44 | / 45 |
Log is empty.
Log IDDateServiceImage/Size LimitsAttachments #Img Sizes #Optimized / FailedSaved Size
68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 | -------------------------------------------------------------------------------- /views/log/single-log.php: -------------------------------------------------------------------------------- 1 | 8 |
9 | 10 |

All Requests » 11 | Request # details

12 | 13 |

Details

14 |

15 | info) ); ?> 16 |

17 | 18 |

Attachments

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | get_request_details( $request_id ) ) : ?> 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
Attachment IDSizeFile NameSize BeforeSize AfterStatus
35 | 36 | 37 | 38 | get_status_message( $row[ Log::COL_STATUS ] ) ); ?>
Log is empty.
Attachment IDSizeFile NameSize BeforeSize AfterStatus
63 |
-------------------------------------------------------------------------------- /views/media/column.php: -------------------------------------------------------------------------------- 1 | get_total_attachment_stats( $id ); 13 | ?> 14 | 15 | percent ) ) : ?> 16 |

percent ); ?>% saved 17 | (saving_size ) ? jio_size_format( $total_stats[0]->saving_size ) : 0 ); ?>) 18 |

19 |

disk usage: disk_usage ); ?> 20 | (get_count_images( $id ) ); ?> images)

21 | 22 |   * can be better, 23 | 24 | try again 25 | 26 | 27 | 28 | 29 | 30 |

Queued (#)

31 | optimize 32 | now 33 | 34 | optimize 35 | now 36 | -------------------------------------------------------------------------------- /views/media/meta-box.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 15 | 26 | 27 | 28 | 29 | 43 | get_total_attachment_stats( $id ); ?> 44 | percent ) ) : ?> 45 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | $params ) : ?> 60 | 61 | 62 | 63 | get_attachment_stats( $id, $key ); ?> 64 | saving_size ) ) : ?> 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 |
Full size 14 | px 16 | 17 | ( 18 | 23 | ) 24 | 25 |
Image Optimization 30 | 33 | 34 |
Optimizing is in progress... 35 | 38 |
39 | 40 | 41 | 42 |
46 |

percent ); ?>% saved 47 | (saving_size ) ? jio_size_format( $total_stats[0]->saving_size ) : 0 ); ?>)

48 |

disk usage: disk_usage ); ?> 49 | (get_count_images( $id ) ); ?> images)

50 |
0% saved
Additional sizes
pxsaving_size ); ?> 66 | , percent ); ?>% saved0% saved
No additional sizes.
75 | It seems you didn't configure your image sizes correctly. 76 | You should configure responsive image sizes and regenerate thumnbails after that. 77 | 78 |
82 | -------------------------------------------------------------------------------- /views/migrate/index.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |

7 | 8 | 9 | 10 |
11 | You need to Upgrade DataBase to continue using the plugin 12 |

13 | 14 | 15 |

We found out that you upgraded the plugin to the newer version. Your DataBase needs to be upgraded to continue 16 | using the plugin.

17 | 18 | 19 |
20 |

Just click the button below to upgrade.

21 |
22 | 23 | 24 | 25 |
26 |

Warning! There are some problems with the upgrade.

27 | 28 | $warning ) : ?> 29 |

v upgrade

30 | 31 | 32 |
33 | 34 | 35 | 36 |
37 |

We will launch several upgrade scripts:

38 | 43 |
44 | 45 | 46 |
47 | 51 | /> 52 |
53 | 54 | -------------------------------------------------------------------------------- /views/migrate/upgraded.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Upgrade settings

4 | 5 |
6 |

All data upgraded. View 7 | settings

8 |
9 |
10 | -------------------------------------------------------------------------------- /views/optimize/google-page-speed.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | > 14 | 15 | 16 | 17 | 18 | 19 | 20 | > 21 |
22 | get_queued_image_sizes( $attach_id ) ) { 25 | foreach ( $image_sizes as $image_size ) { 26 | if ( $settings->image_sizes_all || in_array( $image_size, $settings->image_sizes, true ) ) { 27 | if ( $img_src = $service->get_image_proxy_url( $attach_id, $image_size ) ) { 28 | echo ''; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | ?> 35 |
36 | 37 | -------------------------------------------------------------------------------- /views/redirect.php: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------