Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
46d9f9b
chore: add server support checks
tijmenbruggeman Dec 22, 2025
37fe8a4
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Dec 22, 2025
d60d05f
chore: import server capabilities in root plugin file
tijmenbruggeman Dec 22, 2025
f8dc8e9
chore: refactor conversion settings into seperate views
tijmenbruggeman Dec 22, 2025
ce741e2
fix: echo enabled check
tijmenbruggeman Dec 22, 2025
31c82e2
fix: use correct value from convert_format options
tijmenbruggeman Dec 22, 2025
f6eaf25
chore: format phpcs
tijmenbruggeman Dec 22, 2025
2413f11
chore: add apache rewriter for webp and avif
tijmenbruggeman Jan 1, 2026
9719011
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Jan 11, 2026
34905ce
refactor: simplify Apache rewrite rules and rename methods
tijmenbruggeman Jan 12, 2026
3752cef
test: add delivery method option to conversion settings utility
tijmenbruggeman Jan 12, 2026
4061361
test: add data-testid attribute to radio button inputs
tijmenbruggeman Jan 12, 2026
c1b86eb
feat: add server-side image conversion via Apache rewrite rules
tijmenbruggeman Jan 12, 2026
4680f5c
release: 3.6.10
Sreini Feb 16, 2026
b11dadb
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Feb 16, 2026
e195502
Format
tijmenbruggeman Feb 16, 2026
864f3a3
chore: readd radio button
tijmenbruggeman Feb 16, 2026
c83f338
add conversio class to initialize picture or apache
tijmenbruggeman Feb 16, 2026
e154eb9
Merge branch 'master' of https://github.com/tinify/wordpress-plugin
tijmenbruggeman Feb 16, 2026
f53f00b
Merge branch 'tinify:master' into master
tijmenbruggeman Feb 17, 2026
840db6d
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Feb 17, 2026
76f4232
test and docs
tijmenbruggeman Feb 17, 2026
913b4f3
Uninstal rules on uninstall
tijmenbruggeman Feb 18, 2026
37129e2
format
tijmenbruggeman Feb 18, 2026
8f9c839
format
tijmenbruggeman Feb 18, 2026
d64f6e9
add tests
tijmenbruggeman Feb 18, 2026
f81e1bf
Remove cache and only vary accept when it is an image
tijmenbruggeman Feb 18, 2026
9e3b4ec
only write into uploads dir
tijmenbruggeman Feb 18, 2026
e053543
typo
tijmenbruggeman Feb 18, 2026
cbaf596
add query parameter for original bypass
tijmenbruggeman Feb 18, 2026
a49dd8f
Move hook to init
tijmenbruggeman Feb 18, 2026
9d433f4
Format
tijmenbruggeman Feb 18, 2026
4c03162
fix style
tijmenbruggeman Feb 18, 2026
bac9e7b
Lint
tijmenbruggeman Feb 18, 2026
1cf893d
format
tijmenbruggeman Feb 18, 2026
f2a0a91
Only apply convertion settings when conversion is enabled
tijmenbruggeman Feb 18, 2026
795b939
check delivery method incorrect
tijmenbruggeman Feb 18, 2026
a25c9a6
Resolve integration tests
tijmenbruggeman Feb 18, 2026
e94a2d3
chore: revert logger changes from conversion branch
tijmenbruggeman Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/class-tiny-apache-rewrite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php
/*
* Tiny Compress Images - WordPress plugin.
* Copyright (C) 2015-2018 Tinify B.V.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n*/

/**
* Tiny_Apache_Rewrite
* class responsible for the apache rules for image delivery.
* - toggling the rules when saving settings
* - inserting/removing rules from htaccess
*
* We are only updating rules when:
* - updating the option convert_format
* - uninstalling the plug-in
*/
class Tiny_Apache_Rewrite extends Tiny_WP_Base {

/**
* seperator when rules are inserted
* @var string
*/
const MARKER = 'tiny-compress-images';

/**
* Installs or uninstalls the htaccess rules
* hooked into `update_option_tinypng_convert_format`
* https://developer.wordpress.org/reference/hooks/update_option_option/
*
*
* @param mixed $old_value
* @param mixed $value
* @param string $option
* @return void
*/
public static function toggle_rules( $old_value, $value, $option ) {
$old_delivery = isset( $old_value['delivery_method'] ) ?
$old_value['delivery_method'] : null;
$new_delivery = isset( $value['delivery_method'] ) ?
$value['delivery_method'] : null;

if ( $old_delivery === $new_delivery ) {
return;
}

if ( 'htaccess' === $new_delivery ) {
self::install_rules();
Tiny_Logger::debug( 'Installed image delivery rules' );
return;
}

// We only uninstall if we were previously using htaccess
if ( 'htaccess' === $old_delivery ) {
self::uninstall_rules();
Tiny_Logger::debug( 'Uninstalled image delivery rules' );
}
}

/**
* Generate .htaccess rewrite rules for serving WebP and AVIF images.
*
* @return string The .htaccess rules
*/
private static function get_rewrite_rules() {
$rules = array();
$rules[] = '<IfModule mod_rewrite.c>';
$rules[] = 'RewriteEngine On';
$rules[] = 'RewriteOptions Inherit';

$rules = array_merge( $rules, self::get_avif_rules() );
$rules = array_merge( $rules, self::get_webp_rules() );

$rules[] = '</IfModule>';

$rules[] = '<IfModule mod_headers.c>';
$rules[] = '<FilesMatch "\.(jpe?g|png|gif)$">';
$rules[] = 'Header append Vary Accept';
$rules[] = '</FilesMatch>';
$rules[] = '</IfModule>';

$rules[] = '<IfModule mod_mime.c>';
$rules[] = 'AddType image/webp .webp';
$rules[] = 'AddType image/avif .avif';
$rules[] = '</IfModule>';

return implode( "\n", $rules );
}

/**
* Generate AVIF rewrite rules.
*
* @return array[] AVIF rewrite rules
*/
private static function get_avif_rules() {
$rules = array();
$rules[] = 'RewriteCond %{HTTP_ACCEPT} image/avif';
$rules[] = 'RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png|gif)$';
$rules[] = 'RewriteCond %{DOCUMENT_ROOT}/%1.avif -f';
$rules[] = 'RewriteCond %{QUERY_STRING} !type=original';
$rules[] = 'RewriteRule (.+)\.(?:jpe?g|png|gif)$ $1.avif [T=image/avif,L]';
return $rules;
}

/**
* Generate WebP rewrite rules.
*
* @return array[] WebP rewrite rules
*/
private static function get_webp_rules() {
$rules = array();
$rules[] = 'RewriteCond %{HTTP_ACCEPT} image/webp';
$rules[] = 'RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png|gif)$';
$rules[] = 'RewriteCond %{DOCUMENT_ROOT}/%1.webp -f';
$rules[] = 'RewriteCond %{QUERY_STRING} !type=original';
$rules[] = 'RewriteRule (.+)\.(?:jpe?g|png|gif)$ $1.webp [T=image/webp,L]';

return $rules;
}

/**
* Install rewrite rules to .htaccess files.
*
* @return bool True on success, false otherwise
*/
private static function install_rules() {
$rules = self::get_rewrite_rules();

$upload_dir = wp_upload_dir();
if ( isset( $upload_dir['basedir'] ) && is_writable( $upload_dir['basedir'] ) ) {
$htaccess_file = $upload_dir['basedir'] . '/.htaccess';
insert_with_markers( $htaccess_file, self::MARKER, $rules );
}

return true;
}

/**
* Remove rewrite rules from .htaccess files.
*
* @return bool True on success, false otherwise
*/
public static function uninstall_rules() {
$upload_dir = wp_upload_dir();
if (
file_exists( $upload_dir['basedir'] . '/.htaccess' )
) {
$htaccess_file = $upload_dir['basedir'] . '/.htaccess';
insert_with_markers( $htaccess_file, self::MARKER, '' );
}

return true;
}
}
87 changes: 87 additions & 0 deletions src/class-tiny-conversion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php
/*
* Tiny Compress Images - WordPress plugin.
* Copyright (C) 2015-2018 Tinify B.V.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/**
* class managing conversion delivery method
*/
class Tiny_Conversion extends Tiny_WP_Base {

/**
* @var Tiny_Settings plug-in settings
*/
private $settings;

/**
* @param Tiny_Settings $settings
*/
public function __construct( $settings ) {
parent::__construct();
$this->settings = $settings;
}

/**
* will check if conversion is enabled,
* if true:
* - will enable the delivery method
* - will add hook to toggle rules
*
* hooked into `init`
*/
public function init() {
if ( ! $this->settings->get_conversion_enabled() ) {
return;
}

add_action(
'update_option_tinypng_convert_format',
'Tiny_Apache_Rewrite::toggle_rules',
20,
3
);

$delivery_method = $this->settings->get_conversion_delivery_method();

$this->init_image_delivery( $delivery_method );
}

/**
* Initializes the method of delivery for optimised images
*
* @param string $delivery_method 'picture' or 'htaccess'
* @return void
*/
private function init_image_delivery( $delivery_method ) {
/**
* Controls wether the page should replace <img> with <picture> elements
* converted sources.
*
* @since 3.7.0
*/
if ( 'htaccess' === $delivery_method && Tiny_Server_Capabilities::is_apache() ) {
new Tiny_Apache_Rewrite();
return;
}

if ( apply_filters( 'tiny_replace_with_picture', 'picture' === $delivery_method ) ) {
new Tiny_Picture( $this->settings, ABSPATH, array( get_site_url() ) );
return;
}
}
}
2 changes: 1 addition & 1 deletion src/class-tiny-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static function version() {
public function __construct() {
parent::__construct();
$this->settings = new Tiny_Settings();
new Tiny_Conversion( $this->settings );
}

public function set_compressor( $compressor ) {
Expand Down Expand Up @@ -72,7 +73,6 @@ public function init() {
dirname( plugin_basename( __FILE__ ) ) . '/languages'
);

new Tiny_Picture( $this->settings, ABSPATH, array( get_site_url() ) );
$this->tiny_compatibility();
}

Expand Down
116 changes: 116 additions & 0 deletions src/class-tiny-server-capabilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/*
* Tiny Compress Images - WordPress plugin.
* Copyright (C) 2015-2018 Tinify B.V.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/**
* class responsible for checking and managing server capabilities
*/
class Tiny_Server_Capabilities {

/**
* Detect the web server software using WordPress globals.
* WordPress sets $is_apache, $is_IIS, $is_iis7, and $is_nginx in wp-includes/vars.php
Copy link
Contributor

@rkoopmans rkoopmans Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You miss is_caddy. it is a very small market share, so no big deal but maybe add it for completeness

*
* @return string The server type: 'apache', 'nginx', 'iis', or 'unknown'
*/
private static function get_server_type() {
global $is_apache, $is_iis7, $is_nginx;

if ( $is_apache ) {
return 'apache';
}

if ( $is_nginx ) {
return 'nginx';
}

if ( $is_iis7 ) {
return 'iis';
}

return 'unknown';
}

/**
* Check if the server is Apache.
*
* @return bool True if running on Apache
*/
public static function is_apache() {
return self::get_server_type() === 'apache';
}

/**
* Check if mod_rewrite is available on Apache.
*
* @return bool True if mod_rewrite is available
*/
public static function has_mod_rewrite() {
if ( ! self::is_apache() ) {
return false;
}

if ( function_exists( 'apache_mod_loaded' ) ) {
return apache_mod_loaded( 'mod_rewrite' );
}

if ( function_exists( 'apache_get_modules' ) ) {
$modules = apache_get_modules();
return in_array( 'mod_rewrite', $modules, true );
}

if ( function_exists( 'insert_with_markers' ) ) {
$htaccess = wp_upload_dir() . '.htaccess';
return is_writable( dirname( $htaccess ) );
}

return false;
}

/**
* Check if the /uploads directory is writable for .htaccess.
*
* @return bool True if uploads directory is writable, false otherwise
*/
public static function uploads_htaccess_writable() {
$upload_dir = wp_upload_dir();
if ( isset( $upload_dir['basedir'] ) ) {
return is_writable( $upload_dir['basedir'] );
}
return false;
}

/**
* Get a detailed capabilities object.
*
* @return array Array with properties: server, is_apache, has_mod_rewrite,
* uploads_writable, htaccess_available
*/
public static function get_capabilities() {
return array(
'server' => self::get_server_type(),
'is_apache' => self::is_apache(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have is_apache seperately here. Can just as well call $is_apache in the code block where you use it

'has_mod_rewrite' => self::has_mod_rewrite(),
'uploads_writable' => self::uploads_htaccess_writable(),
'htaccess_available' => self::is_apache() &&
self::has_mod_rewrite() &&
self::uploads_htaccess_writable(),
);
}
}
Loading