├── 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( '', '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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/views/dashboard/_requirements.php:
--------------------------------------------------------------------------------
1 |
6 | check_requirements( $force_requirements_check ) ) : ?>
7 |
8 |
Please check that all requirements are met:
9 |
10 | PHP version should be at least 7.0 (you have ).
11 |
12 |
20 |
21 | Your site is online and can be accessible by name() ); ?>
22 |
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 |
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 |
104 |
105 |
--------------------------------------------------------------------------------
/views/log/index.php:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
List of optimization request to the 3rd-party services.
12 |
13 |
14 |
15 | Log ID
16 | Date
17 | Service
18 | Image/Size Limits
19 | Attachments #
20 | Img Sizes #
21 | Optimized / Failed
22 | Saved Size
23 |
24 |
25 |
26 | get_requests(); ?>
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | attachm. / MB
40 | attach_count( $request_id ) ); ?>
41 |
42 |
43 | files_count_stat( $request_id, Log::STATUS_OPTIMIZED ) ); ?>
44 | /
45 |
46 |
47 |
48 |
49 |
50 |
51 | Log is empty.
52 |
53 |
54 |
55 |
56 |
57 | Log ID
58 | Date
59 | Service
60 | Image/Size Limits
61 | Attachments #
62 | Img Sizes #
63 | Optimized / Failed
64 | Saved Size
65 |
66 |
67 |
68 |
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 | Attachment ID
23 | Size
24 | File Name
25 | Size Before
26 | Size After
27 | Status
28 |
29 |
30 |
31 | get_request_details( $request_id ) ) : ?>
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | get_status_message( $row[ Log::COL_STATUS ] ) ); ?>
44 |
45 |
46 |
47 |
48 | Log is empty.
49 |
50 |
51 |
52 |
53 |
54 | Attachment ID
55 | Size
56 | File Name
57 | Size Before
58 | Size After
59 | Status
60 |
61 |
62 |
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 |
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 |
39 | $m ) : ?>
40 | v upgrade
41 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
--------------------------------------------------------------------------------
/views/migrate/upgraded.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Upgrade settings
4 |
5 |
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 |
--------------------------------------------------------------------------------