WebP Express CloudHost.es Fix v0.25.9-cloudhost

 Fixed bulk conversion getting stuck on missing files
 Added robust error handling and timeout protection
 Improved JavaScript response parsing
 Added file existence validation
 Fixed missing PHP class imports
 Added comprehensive try-catch error recovery

🔧 Key fixes:
- File existence checks before conversion attempts
- 30-second timeout protection per file
- Graceful handling of 500 errors and JSON parsing issues
- Automatic continuation to next file on failures
- Cache busting for JavaScript updates

🎯 Result: Bulk conversion now completes successfully even with missing files

🚀 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-23 10:22:32 +02:00
commit 37cf714058
553 changed files with 55249 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
#das_overlay {
background: #000;
opacity: 0.7;
filter: alpha(opacity=70);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100051;
}
.das-popup {
position: fixed;
background-color: #fff;
z-index: 100052;
visibility: hidden;
text-align: left;
top: 50%;
left: 50%;
-webkit-box-shadow: 0 3px 6px rgba( 0, 0, 0, 0.3 );
box-shadow: 0 3px 6px rgba( 0, 0, 0, 0.3 );
padding: 20px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<polygon fill="#FFFFFF" points="13,21 8,16 13,11 "/>
<polygon fill="#FFFFFF" points="19,11 24,16 19,21 "/>
</svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1,101 @@
#tc_content {
display: flex;
}
#tc_content > * {
width: 50%;
}
#tc_conversion_options label {
font-weight: bold;
}
#tc_conversion_options label + select {
margin-left: 6px;
}
@media (max-width:600px) {
#tc_content {
display: block;
}
}
/* Comparison slider *
------------------- */
.cd-image-container {
position: relative;
width: 100%;
background: #dc717d url(images/checker.png) repeat center center;
margin-bottom: 5px;
}
.cd-image-container img {
display: block;
}
.cd-image-label {
display: inline-block;
position: absolute;
z-index: 10;
color: #dc717d;
top: 10px;
font-weight: bold;
font-size: 18px;
/*text-shadow: 2px 2px 0px white;*/
padding: 2px 4px;
background-color: #eee;
border: 1px solid #ccc;
}
.cd-image-label.original {
left: 15px;
}
.cd-image-label.webp {
right: 15px;
}
.cd-resize-img {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
overflow: hidden;
/* Force Hardware Acceleration in WebKit */
transform: translateZ(0);
backface-visibility: hidden;
border-right: 2px dotted black;
}
.is-visible .cd-resize-img {
width: 50%;
/* bounce in animation of the modified image */
animation: cd-bounce-in 0.7s;
}
.cd-handle.draggable {
background-color: #445b7c;
}
.cd-handle {
position: absolute;
height: 44px;
width: 44px;
left: 50%;
top: 50%;
margin-left: -22px;
margin-top: -22px;
border-radius: 50%;
background: #dc717d url(images/drag-handle.svg) no-repeat center center;
cursor: move;
/*box-shadow: 0 0 0 2px rgba(0,0,0,.2), 0 0 4px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.3);*/
opacity: 100;
}
@keyframes cd-bounce-in {
0% {
width: 0;
}
60% {
width: 55%;
}
100% {
width: 50%;
}
}

View File

@@ -0,0 +1,653 @@
/*input[name=webp_express_converters] {
display: none;
width: 100%;
}*/
/* break long lines in pre (when we are showing .htaccess inside notice) */
.notice pre {
white-space: pre-wrap;
word-wrap: break-word;
}
/* Classes used in our pseudo markdown. */
.webpexpress.md h1 {
margin: 1em 0 1em;
font-size: 2em;
font-weight: normal;
}
.webpexpress.md h2 {
margin: 1em 0 0.5em;
font-size: 1.6em;
font-weight: normal;
text-transform: uppercase;
text-align: center;
padding: 10px 0;
background-color: #ccc;
}
.webpexpress.md h3 {
margin: 1em 0 0.5em;
font-size: 1.4em;
font-weight: normal;
}
.webpexpress.md h4 {
font-size: 1.0em;
margin: 1em 0 0.5em;
/*font-weight: 500;*/
font-weight: normal;
font-style: italic;
}
.webpexpress.md .warn {
background-color: yellow;
}
.webpexpress.md .ok {
color: green;
}
.webpexpress.md .error {
color: red;
}
/* remove the padding on the row with the hidden "webp_express_converters" setting.
it is the last row, we happen to know... */
/*
.form-table tr:last-of-type > * {
padding: 0;
}*/
@media (min-width: 782px) {
#webpexpress_settings .form-table > tbody > tr > th {
padding-top: 5px;
padding-bottom: 5px;
width: 200px;
}
#webpexpress_settings .form-table > tbody > tr > td {
padding-top: 1px;
padding-bottom: 5px;
}
}
#converters li {
border: 1px solid grey;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
line-height: 1.4em;
background-color: #fff;
padding: 6px 10px 0;
max-width: 400px;
min-height: 26px;
position: relative;
font-size: 12px;
margin-bottom: 4px;
}
#converters li {
cursor: move;
}
#converters li:hover {
border-color: #000;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
}
#converters li.deactivated {
border-color: #ccc;
background-color: #f0f0f0;
min-height: auto;
height: 22px;
padding-top: 3px;
}
#converters li.deactivated:hover {
border-color: #999;
}
#converters li.deactivated,
#converters li.deactivated a.configure-converter,
#converters li.deactivated a.test-converter,
#converters li.deactivated a.activate-converter {
color: #888;
}
#converters li.not-operational {
border-style: dotted;
border-color: #666;
}
#converters li.not-operational:hover {
border-style: solid;
}
#converters li a {
cursor: pointer;
}
#converters li[data-id='gmagick'] a.configure-converter {
visibility: hidden;
}
#converters li > * {
vertical-align: middle;
}
#converters li > div {
display: inline-block;
line-height: 1;
}
#converters li > .text {
padding-left: 10px;
width: 160px;
}
#converters li > a.btn {
/* border: 1px solid transparent;
border-radius: 8px;
*/
padding: 2px 6px;
margin-right: 4px;
text-decoration: none;
}
#converters li > a.btn:hover {
text-decoration: underline;
/*border-color: #666;
background-color: #eee;*/
}
#converters li > a.configure-converter {
}
#converters li .status {
font-size: 10px;
position: absolute;
right: 7px;
bottom: 3px;
}
#converters li .status svg {
padding: 0px;
}
#converters li svg#status_ok {
color: #008000;
}
#converters li.deactivated svg#status_ok {
color: #99cc99;
}
#converters li svg#status_not_ok {
color: #b11010; /* 444444 */
}
#converters li svg#status_warning {
color: #dc0;
}
#converters li.deactivated svg#status_not_ok {
color: #999999;
}
#converters li.deactivated .status {
bottom: unset;
}
#converters li.deactivated .status svg {
width: 15px;
padding: 0;
}
#converters li .popup,
.help .popup {
display: none;
position: absolute;
border: 1px solid #666;
z-index:2;
background-color: #ffffaa;
padding: 8px 10px;
/*left: 22px;
top: 20px;*/
margin-top: -5px;
/*white-space: nowrap;*/
min-width: 150px;
}
.popup,
.popup p {
font-weight: normal;
text-align: left;
line-height: 1.5;
font-size: 14px;
color: #000;
}
.help .popup.narrow {
width: 200px;
}
.help .popup {
width: 250px;
}
.help .popup.wide {
width: 350px;
}
.help .popup.wider {
width: 450px;
}
.help .popup.even-wider {
width: 600px;
}
.help .popup.widest {
width: 750px;
}
@media (max-width: 1150px) {
.help .popup {
max-width: 620px;
}
}
@media (max-width: 900px) {
.help .popup {
max-width: 560px;
}
}
@media (max-width: 830px) {
.help .popup {
max-width: 530px;
}
}
@media (max-width: 500px) {
.help .popup {
max-width: 380px;
}
}
@media (max-width: 400px) {
.help .popup {
max-width: 280px;
}
}
.help .popup > p:first-child {
margin-top: 0;
}
.help .popup > p:last-child {
margin-bottom: 0;
}
#converters li.operational .popup {
background-color: #80ff80;
}
#converters li.has-warnings .popup {
background-color: #ff5;
}
/* #converters li .status:hover .popup,*/
#converters li:hover .status .popup,
.help:hover .popup {
display: block;
}
#converters.dragging li:hover .status .popup {
display: none;
}
/*
#converters li > a.remove-converter {
color: red;
}*/
.converter-options label {
min-width: 80px;
display: inline-block;
padding-top: 2px;
vertical-align: top;
}
.converter-options p {
padding: 0;
}
.converter-options .info {
margin-bottom: 20px;
}
.converter-options button {
margin-top: 15px;
}
.converter-options button.button-primary {
margin-right: 10px;
}
.converter-options div {
margin-bottom: 15px;
}
.help {
text-align: center;
display: inline-block;
border-radius: 50%;
background-color: #00a0ee;
width: 16px;
height: 16px;
border: 0px solid #00a0ee;
color: white;
font-weight: bolder;
margin-left: 7px;
cursor: pointer;
font-size: 12px;
line-height: 16px;
vertical-align: top;
font-family: sans-serif;
position: relative;
font-style: normal;
pointer-events: all;
}
select + .help,
input + .help {
margin-left: 1px; /* bring help icons closer to select and input boxes */
margin-top: 3px;
}
.help { /* new look! */
background-color: transparent;
border: 1px solid #999;
color: #333;
}
.help.no-margin-left {
margin-left: 0px;
}
.help.set-margin-right {
margin-right: 7px;
}
/*
.converter-options.wpc #wpc_web_services_div > p {
margin-top: 0;
padding-top: 2px;
margin-bottom: 3px;
}
#wpc_web_services_div {
margin-bottom: 0px;
}*/
.converter-options.wpc div,
.converter-options.vips div,
.converter-options.imagemagick div,
.converter-options.graphicsmagick div {
margin-bottom: 5px;
}
.converter-options.vips label,
.converter-options.wpc label,
.converter-options.imagemagick label,
.converter-options.graphicsmagick label {
display: inline-block;
}
.converter-options.wpc label {
width: 110px;
}
.converter-options.vips label {
width: 143px;
}
.converter-options.imagemagick label,
.converter-options.graphicsmagick label {
width: 110px;
}
#wpc_url {
min-width: 400px;
}
#wpc_change_api_key,
#wpc_set_api_key {
display:inline-block;
line-height:22px
}
#whitelist_table {
max-width: 400px;
width: 100%;
}
.whitelist td {
padding: 5px 0;
}
.whitelist td.quota input{
max-width: 40px;
}
.whitelist td.remove {
padding-left: 8px;
}
.whitelist td.remove a {
color: red;
font-size: 12px;
}
.whitelist td.whitelist-add-site {
text-align: left;
}
#password_helptext,
#whitelist_site_helptext,
#whitelist_quota_helptext {
display: none;
}
.whitelist-popup-content label {
min-width: 125px;
display: inline-block;
}
/*
.popup-content {
padding-top: 20px;
}
.popup-content > div {
margin: 10px 0;
}
*/
#whitelist_div ul {
list-style: disc;
list-style-position: inside;
position: relative;
width: 400px;
max-width: 400px;
}
#whitelist_div .whitelist-links,
#wpc_web_services_div .wpc-links {
position: absolute;
right: 0;
text-align: right;
display: inline-block;
}
#whitelist_div .whitelist-links a,
#wpc_web_services_div .wpc-links a {
padding-left: 10px;
}
#whitelist_properties_popup label {
width: 110px;
display: inline-block;
}
#whitelist_change_api_key_div {
line-height: 35px;
}
.das-popup.mode-edit .hide-in-edit {
display: none;
}
.das-popup.mode-add .hide-in-add {
display: none;
}
/*
#wpc_web_services_div ul {
list-style: disc;
list-style-position: inside;
position: relative;
width: 300px;
max-width: 300px;
}
#wpc_properties_popup label {
width: 110px;
display: inline-block;
}
*/
#webpexpress_settings .block {
position: relative;
margin: 0 auto 0.5rem;
padding: 0 1rem;
box-sizing: border-box;
background-color: #fff;
box-shadow: 0 0 0 1px rgba(200,215,225,0.25), 0 1px 2px #e9eff3;
}
#webpexpress_settings .block.buttons {
padding: 10px;
position: sticky;
top: 30px;
z-index: 1;
}
@media (min-width: 768px) {
#webpexpress_settings .block {
padding: 10px 1.5rem 20px;
}
}
#webpexpress_settings .block.buttons p.submit {
margin: 0;
padding: 0;
}
#webpexpress_settings .block.buttons table {
float: right;
}
#alter_html_options_div > div > label {
font-style: italic;
}
/*
#alter_html_options_div > div {
margin-top: 15px;
}*/
.form-table table th {
font-weight: unset;
padding: 5px 0px;
width: auto;
}
.form-table table td {
padding: 0 10px;
width: auto;
}
.form-table td input[type=checkbox] {
margin-top: 6px;
}
.form-table h2 {
/*font-size: 1.6em;*/
text-transform: uppercase;
color: #222;
}
.form-table th[colspan='2'] {
font-weight: normal;
font-size: 14px;
}
.form-table th.header-section h2 {
margin-top: 30px;
margin-bottom: 10px;
}
.form-table th.header-section h2 + p {
margin-top: 10px;
}
.form-table th.header-section p,
.form-table th.header-section h2,
fieldset.block p,
fieldset.block div.p,
input {
max-width: 750px;
}
.toggler.effect-slider {
overflow-y: hidden;
transition: all 0.3s ease-in-out;
/*transition: all 0.5s cubic-bezier(0, 1, 0.5, 1);*/
}
.toggler.effect-slider.closed {
transition: all 0.5s cubic-bezier(0, 1, 0.5, 1);
max-height: 0px !important;
}
.toggler.effect-opacity.closed {
opacity: 0.5;
pointer-events: none;
font-weight: 300 !important;
}
.form-table .toggler.effect-opacity.closed th {
font-weight: 400 !important;
}
.toggler.effect-visibility {
/*position: static;
visibility: inherit;*/
}
.toggler.effect-visibility.closed {
visibility: hidden;
position: absolute;
}
.form-table h4 {
margin-bottom: 0;
text-decoration: underline;
}
table.designed {
border-collapse: collapse;
margin-bottom: 20px;
/*box-shadow: 2px 2px 5px 2px rgba(0, 0, 0, 0.1)*/
}
table.designed th {
font-weight: bold;
background-color: #fff;
}
table.designed td,
table.designed th {
text-align: left;
padding: 9px 17px;
border: 1px solid #999;
vertical-align: top;
}
table.designed td,
table.designed th,
table.designed p {
font-size: 12px;
line-height: 1.3;
}
table.designed td > p:first-child {
margin-top: 0;
}
table.designed tr:nth-child(odd) > td {
background-color: #eee;
}
table.designed tr:nth-child(even) > td {
background-color: #ddd;
}
table.designed th:first-child {
border-left-width: 0;
}
table.designed tr:first-child > th {
border-top-width: 0;
}
table.designed tr > *:last-child {
border-right-width: 0;
}
#hide_alterhtml_chart_btn {
margin-bottom: 20px;
}
/*table.designed tr:last-child > * {
border-bottom-width: 0;
}*/
ul.with-bullets {
padding-left: 20px;
list-style: unset;
}
#conversionlog_content {
overflow-y: scroll;
top: 20px;
bottom: 72px;
position: absolute;
right: 0;
left: 3%;
}

View File

@@ -0,0 +1,150 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
use \WebPExpress\Paths;
use \WebPExpress\Config;
// Note: $ver is added to querystring. However, when it is is critical that no-one gets the cached version,
// a change of filename is neccessary, as there is a plugin that strips version strings out there...!
// If only one file update is critical: change the name of the file
// If several files are critical: rename the folder (ie "js2")
$ver = '4-cloudhost'; // note: Minimum 1.
$jsDir = 'js';
if (!function_exists('webp_express_add_inline_script')) {
function webp_express_add_inline_script($id, $script, $position) {
if (function_exists('wp_add_inline_script')) {
// wp_add_inline_script is available from Wordpress 4.5
wp_add_inline_script($id, $script, $position);
} else {
echo '<script>' . $script . '</script>';
}
}
}
wp_register_script('sortable', plugins_url($jsDir . '/sortable.min.js', __FILE__), [], '1.9.0');
wp_enqueue_script('sortable');
wp_register_script('daspopup', plugins_url($jsDir . '/das-popup.js', __FILE__), [], $ver);
wp_enqueue_script('daspopup');
wp_register_script('escapehtml', plugins_url($jsDir . '/escapeHTML.js', __FILE__), [], $ver);
wp_enqueue_script('escapehtml');
$config = Config::getConfigForOptionsPage();
// selftest
wp_register_script('webpexpress_selftest', plugins_url($jsDir . '/self-test.js', __FILE__), ['escapehtml'], $ver);
wp_enqueue_script('webpexpress_selftest');
// Add converter, bulk convert and whitelist script, EXCEPT for "no conversion" mode
if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conversion'))) {
// Remove empty options arrays.
// These cause trouble in json because they are encoded as [] rather than {}
foreach ($config['converters'] as &$converter) {
if (isset($converter['options']) && (count(array_keys($converter['options'])) == 0)) {
unset($converter['options']);
}
}
// Converters
// ----------
wp_register_script('converters', plugins_url($jsDir . '/converters.js', __FILE__), ['sortable', 'daspopup', 'escapehtml'], $ver);
// PS: no escaping/sanitizing needed as json_encode always produces something safe
webp_express_add_inline_script(
'converters',
'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';',
'before'
);
// PS: no escaping/sanitizing needed as json_encode always produces something safe
webp_express_add_inline_script(
'converters',
'window.converters = ' . json_encode($config['converters']) . ';',
'before'
);
wp_enqueue_script('converters');
// Whitelist
// ---------
wp_register_script('whitelist', plugins_url($jsDir . '/whitelist.js', __FILE__), ['daspopup', 'escapehtml'], $ver);
// PS: no escaping/sanitizing needed as json_encode always produces something safe
webp_express_add_inline_script('whitelist', 'window.whitelist = ' . json_encode($config['web-service']['whitelist']) . ';', 'before');
wp_enqueue_script('whitelist');
// bulk convert
wp_register_script('bulkconvert', plugins_url($jsDir . '/bulk-convert.js', __FILE__), ['escapehtml'], $ver);
wp_enqueue_script('bulkconvert');
// test convert
wp_register_script('testconvert', plugins_url($jsDir . '/test-convert.js', __FILE__), ['escapehtml'], $ver);
$canDisplayWebp = (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false ));
/*
AlterHTMLHelper::getWebPUrlInImageRoot(
Paths::getPluginUrl() . '/webp-express', // source url
$baseId,
Paths::getPluginUrl(), // base url
Paths::getPluginDirAbs() // base dir
);
getRelUrlPath()*/
webp_express_add_inline_script('testconvert', 'window.canDisplayWebp = ' . ($canDisplayWebp ? 'true' : 'false') . ';', 'before');
wp_enqueue_script('testconvert');
wp_register_script('image-comparison-slider', plugins_url($jsDir . '/image-comparison-slider.js', __FILE__), [], $ver);
wp_enqueue_script('image-comparison-slider');
// purge cache
wp_register_script('purgecache', plugins_url($jsDir . '/purge-cache.js', __FILE__), [], $ver);
wp_enqueue_script('purgecache');
// purge log
wp_register_script('purgelog', plugins_url($jsDir . '/purge-log.js', __FILE__), [], $ver);
wp_enqueue_script('purgelog');
}
//wp_register_script('api_keys', plugins_url($jsDir . 'api-keys.js', __FILE__), ['daspopup'], '0.7.0-dev8');
//wp_enqueue_script('api_keys');
wp_register_script( 'page', plugins_url($jsDir . '/page.js', __FILE__), [], $ver);
// TODO: Add all vars needed to this array (whitelist, converters, etc)
$javascriptVars = [
'ajax-nonces' => [
'convert' => wp_create_nonce('webpexpress-ajax-convert-nonce'),
'list-unconverted-files' => wp_create_nonce('webpexpress-ajax-list-unconverted-files-nonce'),
'purge-cache' => wp_create_nonce('webpexpress-ajax-purge-cache-nonce'),
'purge-log' => wp_create_nonce('webpexpress-ajax-purge-log-nonce'),
'view-log' => wp_create_nonce('webpexpress-ajax-view-log-nonce'),
'self-test' => wp_create_nonce('webpexpress-ajax-self-test-nonce'),
],
'can-use-doc-root-for-structuring' => Paths::canUseDocRootForRelPaths()
];
webp_express_add_inline_script(
'page',
'window.webpExpress = ' . json_encode($javascriptVars, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK),
'before'
);
wp_enqueue_script('page');
// Register styles
wp_register_style('webp-express-options-page-css', plugins_url('css/webp-express-options-page.css', __FILE__), null, $ver);
wp_enqueue_style('webp-express-options-page-css');
wp_register_style('test-convert-css', plugins_url('css/test-convert.css', __FILE__), null, $ver);
wp_enqueue_style('test-convert-css');
wp_register_style('das-popup-css', plugins_url('css/das-popup.css', __FILE__), null, $ver);
wp_enqueue_style('das-popup-css');
add_thickbox();

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="50.000000pt" height="50.000000pt" viewBox="0 0 50.000000 50.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.15, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,50.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 345 l0 -25 250 0 250 0 0 25 0 25 -250 0 -250 0 0 -25z"/>
<path d="M0 250 c0 -19 7 -20 250 -20 243 0 250 1 250 20 0 19 -7 20 -250 20
-243 0 -250 -1 -250 -20z"/>
<path d="M0 155 l0 -25 250 0 250 0 0 25 0 25 -250 0 -250 0 0 -25z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 757 B

View File

@@ -0,0 +1,70 @@
/*function updateWhitelistInputValue() {
//var val = [];
document.getElementsByName('whitelist')[0].value = JSON.stringify(window.whitelist);
}
function setPassword(i) {
var password = window.prompt('Enter password' + i);
window.whitelist[i]['password_hashed'] = '';
window.whitelist[i]['new_password'] = password;
setWhitelistHTML();
}
*/
function setAuthorizedSitesHTML() {
var s = '';
if (window.authorizedSites && window.authorizedSites.length > 0) {
s+='<table class="authorized_sites_list" >';
s+='<tr>';
s+='<th>Site<span id="whitelist_site_helptext2"></span></th>'
//s+='<th>Salt<span id="salt_helptext2"></span></th>'
//s+='<th>Limit<span id="whitelist_quota_helptext2"></span></th>'
s+='</tr>';
s=='</th></tr>';
for (var i=0; i<window.authorizedSites.length; i++) {
s+='<tr><td>';
s+=window.authorizedSites[i]['id']
s+='</td></tr>';
}
} else {
s+='<i>No sites have been authorized to use this server yet.</i>';
}
s+='</table>';
s+='<button type="button" class="button button-secondary" id="server_listen_btn" onclick="whitelistAdd()">Connect website</button>';
/*
s+='<tr><td colspan="3" class="whitelist-add-site">';
s+='<button type="button" class="button button-secondary" id="whitelist_add" onclick="whitelistAdd()">+ Add site</button>';
s+='</td></tr>';
s+='</table>';*/
document.getElementById('authorized_sites_div').innerHTML = s;
}
/*
function whitelistAdd() {
window.whitelist.push({
site: '',
password_hashed: '',
new_password: '',
//quota: 60
});
setWhitelistHTML();
}
function whitelistRemoveRow(i) {
window.whitelist.splice(i, 1);
setWhitelistHTML();
}
*/
document.addEventListener('DOMContentLoaded', function() {
setAuthorizedSitesHTML();
});

View File

@@ -0,0 +1,485 @@
function openBulkConvertPopup() {
document.getElementById('bulkconvertlog').innerHTML = '';
document.getElementById('bulkconvertcontent').innerHTML = '<div>Receiving list of files to convert...</div>';
tb_show('Bulk Convert', '#TB_inline?inlineId=bulkconvertpopup');
var data = {
'action': 'list_unconverted_files',
'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
};
jQuery.ajax({
type: "POST",
url: ajaxurl,
data: data,
dataType: 'text',
timeout: 30000,
error: function (jqXHR, status, errorThrown) {
html = '<h1>Error: ' + status + '</h1>';
html += errorThrown;
document.getElementById('bulkconvertcontent').innerHTML = html;
},
success: function(response) {
if ((typeof response == 'object') && (response['success'] == false)) {
html = '<h1>Error</h1>';
if (response['data'] && ((typeof response['data']) == 'string')) {
html += webpexpress_escapeHTML(response['data']);
}
document.getElementById('bulkconvertcontent').innerHTML = html;
return;
}
if (response == '') {
html = '<h1>Error</h1>';
html += '<p>Could not fetch list of files to convert. The server returned nothing (which is unexpected - ' +
'it is not simply because there are no files to convert.)</p>';
document.getElementById('bulkconvertcontent').innerHTML = html;
return;
}
var responseObj;
try {
responseObj = JSON.parse(response);
} catch (e) {
html = '<h1>Error</h1>';
html += '<p>The ajax call did not return valid JSON, as expected.</p>';
html += '<p>Check the javascript console to see what was returned.</p>';
console.log('The ajax call did not return valid JSON, as expected');
console.log('Here is what was received:');
console.log(response);
document.getElementById('bulkconvertcontent').innerHTML = html;
return;
}
var bulkInfo = {
'groups': responseObj,
'groupPointer': 0,
'filePointer': 0,
'paused': false,
'webpTotalFilesize': 0,
'orgTotalFilesize': 0,
};
window.webpexpress_bulkconvert = bulkInfo;
// count files
var numFiles = 0;
for (var i=0; i<bulkInfo.groups.length; i++) {
numFiles += bulkInfo.groups[i].files.length;
}
//console.log(JSON.parse(response));
var html = '';
if (numFiles == 0) {
html += '<p>There are no unconverted files</p>';
} else {
html += '<div>'
html += '<p>There are ' + numFiles + ' unconverted files.</p>';
html += '<p><i>Note that in a typical setup, you will have redirect rules which trigger conversion when needed, ' +
'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
'more disk space than necessary. The bulk conversion feature was only added in order to make the plugin usable even when ' +
'there are problems with redirects (ie on Nginx in case you do not have access to the config or on Microsoft IIS). ' +
'</i></p><br>';
html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
html += '</div>';
}
document.getElementById('bulkconvertcontent').innerHTML = html;
}
});
}
function pauseBulkConversion() {
var bulkInfo = window.webpexpress_bulkconvert;
bulkInfo.paused = true;
}
function pauseOrResumeBulkConversion() {
var bulkInfo = window.webpexpress_bulkconvert;
bulkInfo.paused = !bulkInfo.paused;
document.getElementById('bulkPauseResumeBtn').innerText = (bulkInfo.paused ? 'Resume' : 'Pause');
if (!bulkInfo.paused) {
convertNextInBulkQueue();
}
}
function startBulkConversion() {
var html = '<br>';
html += '<style>' +
'.has-tip {cursor:pointer; position:static;}\n' +
'.has-tip .tip {display: none}\n' +
'.has-tip:hover .tip {display: block}\n' +
'.tip{padding: 5px 10px; background-color:#ff9;min-width:310px; font-size:10px; color: black; border:1px solid black; max-width:90%;z-index:10}\n' +
'.reduction {float:right;}\n' +
'</style>';
html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
//html += '<div id="conversionlog" class="das-popup">test</div>';
//html += '<div id="bulkconvertlog"></div>';
document.getElementById('bulkconvertcontent').innerHTML = html;
document.getElementById('bulkconvertlog').innerHTML = '';
convertNextInBulkQueue();
}
function convertDone() {
var bulkInfo = window.webpexpress_bulkconvert;
document.getElementById('bulkconvertlog').innerHTML += '<p><b>Done!</b></p>' +
'<p>Total reduction: ' + getReductionHtml(bulkInfo['orgTotalFilesize'], bulkInfo['webpTotalFilesize'], 'Total size of converted originals', 'Total size of converted webp files') + '</p>'
document.getElementById('bulkPauseResumeBtn').style.display = 'none';
}
function getPrintableSizeInfo(orgSize, webpSize) {
if (orgSize < 10000) {
return {
'org': orgSize + ' bytes',
'webp': webpSize + ' bytes'
};
} else {
return {
'org': Math.round(orgSize / 1024) + ' kb',
'webp': Math.round(webpSize / 1024) + ' kb'
};
}
}
function getReductionHtml(orgSize, webpSize, sizeOfOriginalText, sizeOfWebpText) {
var reduction = Math.round((orgSize - webpSize)/orgSize * 100);
var sizeInfo = getPrintableSizeInfo(orgSize, webpSize);
var hoverText = sizeOfOriginalText + ': ' + sizeInfo['org'] + '.<br>' + sizeOfWebpText + ': ' + sizeInfo['webp'];
// ps: this is all safe to print
return '<span class="has-tip reduction">' + reduction + '%' +
'<span class="tip">' + hoverText + '</span>' +
'</span><br>';
}
function logLn() {
var html = '';
for (i = 0; i < arguments.length; i++) {
html += arguments[i];
}
var spanEl = document.createElement('span');
spanEl.innerHTML = html;
document.getElementById('bulkconvertlog').appendChild(spanEl);
//document.getElementById('bulkconvertlog').innerHTML += html;
}
function webpexpress_viewLog(groupPointer, filePointer) {
/*
disabled until I am certain that security is in place.
var bulkInfo = window.webpexpress_bulkconvert;
var group = bulkInfo.groups[groupPointer];
var filename = group.files[filePointer];
var source = group.root + '/' + filename;
var w = Math.min(1200, Math.max(200, document.documentElement.clientWidth - 100));
var h = Math.max(250, document.documentElement.clientHeight - 80);
document.getElementById('conversionlog_content').innerHTML = 'loading log...'; // + source;
jQuery.ajax({
method: 'POST',
url: ajaxurl,
data: {
'action': 'webpexpress_view_log',
'nonce' : window.webpExpress['ajax-nonces']['view-log'],
'source': source
},
success: (response) => {
//alert(response);
if ((typeof response == 'object') && (response['success'] == false)) {
html = '<h1>Error</h1>';
if (response['data'] && ((typeof response['data']) == 'string')) {
html += webpexpress_escapeHTML(response['data']);
}
document.getElementById('conversionlog_content').innerHTML = html;
return;
}
var result = JSON.parse(response);
// the "log" result is a simply form of markdown, using just italic, bold and newlines.
// It ought not to return anything evil, but for good practice, let us encode.
result = webpexpress_escapeHTML(result);
var html = '<h1>Conversion log</h1><br>' + '<pre style="white-space:pre-wrap">' + result + '</pre>';
document.getElementById('conversionlog_content').innerHTML = html;
},
error: () => {
//responseCallback({requestError: true});
},
});
//<h1>Conversion log</h1>
//tb_show('Conversion log', '#TB_inline?inlineId=conversionlog');
openDasPopup('conversionlog', w, h);
*/
}
function convertNextInBulkQueue() {
var html;
var bulkInfo = window.webpexpress_bulkconvert;
//console.log('convertNextInBulkQueue', bulkInfo);
// Current group might contain 0, - skip if that is the case
while ((bulkInfo.groupPointer < bulkInfo.groups.length) && (bulkInfo.filePointer >= bulkInfo.groups[bulkInfo.groupPointer].files.length)) {
logLn(
'<h3>' + bulkInfo.groups[bulkInfo.groupPointer].groupName + '</h3>',
'<p>Nothing to convert</p>'
);
bulkInfo.groupPointer++;
bulkInfo.filePointer = 0;
}
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
convertDone();
return;
}
var group = bulkInfo.groups[bulkInfo.groupPointer];
var filename = group.files[bulkInfo.filePointer];
if (bulkInfo.filePointer == 0) {
logLn('<h3>' + group.groupName + '</h3>');
}
logLn('Converting <i>' + filename + '</i>');
var data = {
'action': 'convert_file',
'nonce' : window.webpExpress['ajax-nonces']['convert'],
'filename': group.root + '/' + filename
//'whatever': ajax_object.we_value // We pass php values differently!
};
function responseCallback(response){
try {
if ((typeof response == 'object') && (response['success'] == false)) {
html = '<h1>Error</h1>';
if (response['data'] && ((typeof response['data']) == 'string')) {
// disabled. Need to check if it is secure
//html += webpexpress_escapeHTML(response['data']);
}
logLn(html);
return
}
var result;
// Handle different types of responses safely
if (typeof response.requestError === 'boolean' && response.requestError) {
result = {
success: false,
msg: 'Request failed',
log: '',
};
} else if (typeof response === 'string') {
try {
result = JSON.parse(response);
} catch (e) {
result = {
success: false,
msg: 'Invalid response received from server',
log: '',
};
}
} else if (typeof response === 'object' && response !== null) {
// If it's already an object, check if it has the expected structure
if (typeof response.success !== 'undefined') {
result = response;
} else {
result = {
success: false,
msg: 'Invalid object response received from server',
log: '',
};
}
} else {
result = {
success: false,
msg: 'Unexpected response type: ' + typeof response,
log: '',
};
}
var bulkInfo = window.webpexpress_bulkconvert;
if (!bulkInfo || !bulkInfo.groups || bulkInfo.groupPointer >= bulkInfo.groups.length) {
logLn('<span style="color:red">Bulk conversion state is invalid</span><br>');
convertDone();
return;
}
var group = bulkInfo.groups[bulkInfo.groupPointer];
if (!group || !group.files || bulkInfo.filePointer >= group.files.length) {
logLn('<span style="color:red">Group or file index is invalid</span><br>');
convertDone();
return;
}
var filename = group.files[bulkInfo.filePointer];
//console.log(result);
var html = '';
var htmlViewLog = '';
// uncommented until I'm certain that security is in place
//var htmlViewLog = '&nbsp;&nbsp;<a style="cursor:pointer" onclick="webpexpress_viewLog(' + bulkInfo.groupPointer + ',' + bulkInfo.filePointer + ')">view log</a>';
if (result['success']) {
//console.log('nonce tick:' + result['nonce-tick']);
if (result['new-convert-nonce']) {
//console.log('new convert nonce:' + result['new-convert-nonce']);
window.webpExpress['ajax-nonces']['convert'] = result['new-convert-nonce'];
}
var orgSize = result['filesize-original'];
var webpSize = result['filesize-webp'];
var orgSizePrint, webpSizePrint;
bulkInfo['orgTotalFilesize'] += orgSize;
bulkInfo['webpTotalFilesize'] += webpSize;
//'- Saved at: ' + result['destination-path'] +
/*
html += ' <span style="color:green">ok</span></span>' +
htmlViewLog +
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')*/
html += ' <span style="color:green" class="has-tip">ok' +
'<span class="tip">' +
'<b>Destination:</b><br>' + result['destination-path'] + '<br><br>' +
'<b>Url:</b><br><a href="' + result['destination-url'] + '">' + result['destination-url'] + '<br>' +
'</span>' +
'</span>' +
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
} else {
html += ' <span style="color:red">failed</span>' + htmlViewLog;
if (result['msg']) {
// Show the error message but continue processing
html += '<br><span style="color:red; font-size:13px">' + webpexpress_escapeHTML(result['msg']) + '</span>';
}
// Only stop for critical errors (security nonce issues), not file-specific errors
if (result['stop'] && result['msg'] && result['msg'].indexOf('security nonce') !== -1) {
logLn(html);
logLn('<br><span style="color:red; font-weight:bold;">Bulk conversion stopped due to security error. Please reload the page (F5) and try again.</span>');
return;
}
html += '<br>';
}
logLn(html);
// Get next
bulkInfo.filePointer++;
if (bulkInfo.filePointer == group.files.length) {
bulkInfo.filePointer = 0;
bulkInfo.groupPointer++;
}
if (bulkInfo.groupPointer == bulkInfo.groups.length) {
convertDone();
} else {
if (bulkInfo.paused) {
document.getElementById('bulkconvertlog').innerHTML += '<p><i>on pause</i><br>' +
'Reduction this far: ' + getReductionHtml(bulkInfo['orgTotalFilesize'], bulkInfo['webpTotalFilesize'], 'Total size of originals this far', 'Total size of webp files this far') + '</p>'
bulkInfo['orgTotalFilesize'] += orgSize;
bulkInfo['webpTotalFilesize'] += webpSize;
} else {
convertNextInBulkQueue();
}
}
} catch (error) {
// Catch any unexpected errors in responseCallback
logLn(' <span style="color:red">JavaScript error in response processing: ' + error.message + '</span><br>');
// Try to continue with next file
var bulkInfo = window.webpexpress_bulkconvert;
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
bulkInfo.filePointer++;
if (bulkInfo.filePointer >= bulkInfo.groups[bulkInfo.groupPointer].files.length) {
bulkInfo.filePointer = 0;
bulkInfo.groupPointer++;
}
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
convertDone();
} else if (!bulkInfo.paused) {
convertNextInBulkQueue();
}
} else {
convertDone();
}
}
}
// jQuery.post(ajaxurl, data, responseCallback);
jQuery.ajax({
method: 'POST',
url: ajaxurl,
data: data,
timeout: 30000, // 30 second timeout per file
success: (response) => {
responseCallback(response);
},
error: (jqXHR, textStatus, errorThrown) => {
var errorMsg = 'unknown error';
if (textStatus === 'timeout') {
errorMsg = 'timeout (30s)';
} else if (textStatus === 'error') {
if (jqXHR.status === 500) {
errorMsg = 'server error (500)';
} else if (jqXHR.status === 0) {
errorMsg = 'connection failed';
} else {
errorMsg = 'error (' + jqXHR.status + ')';
}
} else {
errorMsg = 'error (' + textStatus + ')';
}
// Get current file info for logging
var bulkInfo = window.webpexpress_bulkconvert;
var filename = 'unknown file';
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
var group = bulkInfo.groups[bulkInfo.groupPointer];
if (group && group.files && bulkInfo.filePointer < group.files.length) {
filename = group.files[bulkInfo.filePointer];
}
}
logLn('Converting <i>' + filename + '</i> <span style="color:red">' + errorMsg + '</span><br>');
// Continue with next file instead of stopping
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
var group = bulkInfo.groups[bulkInfo.groupPointer];
// Get next
bulkInfo.filePointer++;
if (bulkInfo.filePointer >= group.files.length) {
bulkInfo.filePointer = 0;
bulkInfo.groupPointer++;
}
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
convertDone();
} else {
if (!bulkInfo.paused) {
convertNextInBulkQueue();
}
}
} else {
convertDone();
}
},
});
}

View File

@@ -0,0 +1,557 @@
// Map of converters (are updated with updateConvertersMap)
window.convertersMap = {};
window.currentlyEditing = '';
function getConversionMethodDescription(converterId) {
var descriptions = {
'cwebp': 'cwebp',
'wpc': 'Remote WebP Express',
'ewww': 'ewww cloud converter',
'gd': 'Gd extension',
'imagick': 'Imagick (PHP extension)',
'gmagick': 'Gmagick (PHP extension)',
'imagemagick': 'ImageMagick',
'graphicsmagick': 'GraphicsMagick',
'vips': 'Vips',
'ffmpeg': 'ffmpeg',
};
if (descriptions[converterId]) {
return descriptions[converterId];
}
return converterId;
}
function generateConverterHTML(converter) {
html = '<li data-id="' + converter['id'] + '" class="' + (converter.deactivated ? 'deactivated' : '') + ' ' + (converter.working ? 'operational' : 'not-operational') + ' ' + (converter.warnings ? 'has-warnings' : '') + '">';
//html += '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="17px" height="17px" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#444444" stroke="none"><path d="M415 920 l-80 -80 165 0 165 0 -80 80 c-44 44 -82 80 -85 80 -3 0 -41 -36 -85 -80z"/><path d="M0 695 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M0 500 l0 -40 500 0 500 0 0 40 0 40 -500 0 -500 0 0 -40z"/><path d="M0 305 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M418 78 l82 -83 82 83 83 82 -165 0 -165 0 83 -82z"/></g></svg>';
// html += '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>';
// html += '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#888888" stroke="none"><path d="M415 920 l-80 -80 165 0 165 0 -80 80 c-44 44 -82 80 -85 80 -3 0 -41 -36 -85 -80z"/><path d="M0 695 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M0 500 l0 -40 500 0 500 0 0 40 0 40 -500 0 -500 0 0 -40z"/><path d="M0 305 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M418 78 l82 -83 82 83 83 82 -165 0 -165 0 83 -82z"/></g></svg>';
html += '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><path d="M2 13.5h14V12H2v1.5zm0-4h14V8H2v1.5zM2 4v1.5h14V4H2z"/></svg>';
html += '<div class="text">';
html += getConversionMethodDescription(converter['id']);
html += '</div>';
html += '<a class="configure-converter btn" onclick="configureConverter(\'' + converter['id'] + '\')">configure</a>';
html += '<a class="test-converter btn" onclick="testConverter(\'' + converter['id'] + '\')">test</a>';
if (converter.deactivated) {
html += '<a class="activate-converter btn" onclick=activateConverter(\'' + converter['id'] + '\')>activate</a>';
}
else {
html += '<a class="deactivate-converter btn" onclick=deactivateConverter(\'' + converter['id'] + '\')>deactivate</a>';
}
html += '<div class="status">';
if (converter['error']) {
html += '<svg id="status_not_ok" width="19" height="19" title="not operational" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500.000000 500.000000" preserveAspectRatio="xMidYMid meet">';
html += '<g fill="currentcolor" stroke="none" transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"><path d="M2315 4800 c-479 -35 -928 -217 -1303 -527 -352 -293 -615 -702 -738 -1151 -104 -380 -104 -824 0 -1204 107 -389 302 -724 591 -1013 354 -354 785 -572 1279 -646 196 -30 476 -30 672 0 494 74 925 292 1279 646 354 354 571 784 646 1279 30 197 30 475 0 672 -75 495 -292 925 -646 1279 -289 289 -624 484 -1013 591 -228 62 -528 91 -767 74z m353 -511 c458 -50 874 -272 1170 -624 417 -497 536 -1174 308 -1763 -56 -145 -176 -367 -235 -434 -4 -4 -566 552 -1250 1236 l-1243 1243 94 60 c354 229 754 327 1156 282z m864 -3200 c-67 -59 -289 -179 -434 -235 -946 -366 -2024 172 -2322 1158 -47 155 -66 276 -73 453 -13 362 84 704 290 1023 l60 94 1243 -1243 c684 -684 1240 -1246 1236 -1250z"/></g></svg>';
html += '<div class="popup">';
html += webpexpress_escapeHTML(converter['error']);
html += '</div>';
} else if (converter['warnings']) {
/*html += '<svg id="status_warning" width="19" height="19" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 478.125 478.125">';
html += '<g fill="currentcolor"">';
html += '<circle cx="239.904" cy="314.721" r="35.878"/>';
html += '<path d="M256.657,127.525h-31.9c-10.557,0-19.125,8.645-19.125,19.125v101.975c0,10.48,8.645,19.125,19.125,19.125h31.9c10.48,0,19.125-8.645,19.125-19.125V146.65C275.782,136.17,267.138,127.525,256.657,127.525z"/>';
html += '<path d="M239.062,0C106.947,0,0,106.947,0,239.062s106.947,239.062,239.062,239.062c132.115,0,239.062-106.947,239.062-239.062S371.178,0,239.062,0z M239.292,409.734c-94.171,0-170.595-76.348-170.595-170.596c0-94.248,76.347-170.595,170.595-170.595s170.595,76.347,170.595,170.595C409.887,333.387,333.464,409.734,239.292,409.734z"/>';
html += '</g></svg>';*/
html += '<svg id="status_warning" width="19" height="19" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 123.996 123.996">';
html += '<circle cx="62" cy="67" r="35" color="black"/>';
html += '<g fill="currentcolor">';
html += '<path d="M9.821,118.048h104.4c7.3,0,12-7.7,8.7-14.2l-52.2-92.5c-3.601-7.199-13.9-7.199-17.5,0l-52.2,92.5C-2.179,110.348,2.521,118.048,9.821,118.048z M70.222,96.548c0,4.8-3.5,8.5-8.5,8.5s-8.5-3.7-8.5-8.5v-0.2c0-4.8,3.5-8.5,8.5-8.5s8.5,3.7,8.5,8.5V96.548z M57.121,34.048h9.801c2.699,0,4.3,2.3,4,5.2l-4.301,37.6c-0.3,2.7-2.1,4.4-4.6,4.4s-4.3-1.7-4.6-4.4l-4.301-37.6C52.821,36.348,54.422,34.048,57.121,34.048z"/>';
html += '</g></svg>';
html += '<div class="popup">';
if (converter['warnings'].join) {
if (converter['warnings'].filter) {
// remove duplicate warnings
converter['warnings'] = converter['warnings'].filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
}
html += '<p>Warnings were issued:</p>';
for (var i = 0; i<converter['warnings'].length; i++) {
html += '<p>' + webpexpress_escapeHTML(converter['warnings'][i]) + '</p>';
}
// TODO: Tell him to deactivate the converter - or perhaps do it automatically?
html += '<p>check conversion log for more insight (ie by clicking the "test" link a little left of this warning triangle)</p>';
}
html += '</div>';
} else if (converter.working) {
html += '<svg id="status_ok" width="19" height="19" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256.000000 256.000000" preserveAspectRatio="xMidYMid meet">';
html += '<g fill="currentcolor" stroke="none" transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"><path d="M1064 2545 c-406 -72 -744 -324 -927 -690 -96 -193 -127 -333 -127 -575 0 -243 33 -387 133 -585 177 -351 518 -606 907 -676 118 -22 393 -17 511 8 110 24 252 78 356 136 327 183 569 525 628 887 19 122 19 338 0 460 -81 498 -483 914 -990 1025 -101 22 -389 28 -491 10z m814 -745 c39 -27 73 -59 77 -70 9 -27 10 -25 -372 -590 -345 -510 -357 -524 -420 -512 -19 4 -98 74 -250 225 -123 121 -225 228 -228 238 -3 10 1 31 9 47 20 40 125 132 149 132 11 0 79 -59 162 -140 79 -77 146 -140 149 -140 3 0 38 48 78 108 95 143 465 678 496 720 35 46 64 42 150 -18z"/></g></svg>';
//html += '<div class="popup">' + converter['id'] + ' is operational</div>';
html += '<div class="popup">Operational</div>';
}
html += '</div>';
html += '</li>';
return html;
}
/* Set ids on global converters object */
function setTemporaryIdsOnConverters() {
if (window.converters == undefined) {
console.log('window.converters is undefined. Strange. Please report!');
return;
}
var numConverterInstances = [];
for (var i=0; i<window.converters.length; i++) {
var converter = converters[i]['converter'];
if (numConverterInstances[converter]) {
numConverterInstances[converter]++;
window.converters[i]['id'] = converter + '-' + numConverterInstances[converter];
}
else {
numConverterInstances[converter] = 1;
window.converters[i]['id'] = converter;
}
}
//alert(JSON.stringify(window.converters));
updateConvertersMap();
}
function updateConvertersMap() {
var map = {};
for (var i=0; i<window.converters.length; i++) {
var converter = window.converters[i];
map[converter['id']] = converter;
}
window.convertersMap = map;
}
function reorderConverters(order) {
// Create new converter array
var result = [];
for (var i=0; i<order.length; i++) {
result.push(window.convertersMap[order[i]]);
}
//alert(JSON.stringify(result));
window.converters = result;
updateInputValue();
}
/* Update the hidden input containing all the data */
function updateInputValue() {
document.getElementsByName('converters')[0].value = JSON.stringify(window.converters);
}
function setConvertersHTML() {
var html = '';
setTemporaryIdsOnConverters();
if (document.getElementById('converters') == null) {
alert('document.getElementById("converters") returns null. Strange! Please report.');
return;
}
for (var i=0; i<window.converters.length; i++) {
var converter = converters[i];
html += generateConverterHTML(converter);
}
var el = document.getElementById('converters');
el.innerHTML = html;
var sortable = Sortable.create(el, {
onChoose: function() {
document.getElementById('converters').className = 'dragging';
},
onUnchoose: function() {
document.getElementById('converters').className = '';
},
store: {
get: function() {
var order = [];
for (var i=0; i<window.converters.length; i++) {
order.push(window.converters[i]['id']);
}
return order;
},
set: function(sortable) {
var order = sortable.toArray();
reorderConverters(order);
}
}
});
updateInputValue();
}
document.addEventListener('DOMContentLoaded', function() {
setConvertersHTML();
});
function wpe_addCloudConverter(converter) {
}
function isConverterOptionSet(converter, optionName) {
if ((converter['options'] == undefined) || (converter['options'][optionName] == undefined)) {
return false;
}
return true;
}
function getConverterOption(converter, optionName, defaultValue) {
if ((converter['options'] == undefined) || (converter['options'][optionName] == undefined)) {
return defaultValue;
}
return converter['options'][optionName];
}
function setConverterOption(converter, optionName, value) {
if (converter['options'] == undefined) {
converter['options'] = {};
}
converter['options'][optionName] = value;
}
function deleteConverterOption(converter, optionName) {
if (converter['options'] == undefined) {
converter['options'] = {};
}
delete converter['options'][optionName];
}
function configureConverter(id) {
var converter = window.convertersMap[id];
window.currentlyEditing = id;
/*
Removed (#243)
var q = getConverterOption(converter, 'quality', 'auto');
if (document.getElementById(id + '_quality')) {
document.getElementById(id + '_quality').value = q;
document.getElementById(id + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
document.getElementById(id + '_max_quality').value = getConverterOption(converter, 'max-quality', 85);
}
*/
switch (converter['converter']) {
case 'ewww':
document.getElementById('ewww_api_key').value = getConverterOption(converter, 'api-key', '');
document.getElementById('ewww_api_key_2').value = getConverterOption(converter, 'api-key-2', '');
break;
case 'wpc':
document.getElementById('wpc_api_url').value = getConverterOption(converter, 'api-url', '');
/* api key in configuration file can be:
- never set (null)
- set to be empty ('')
- set to be something.
If never set, we show a password input.
If set to empty, we also show a password input.
There is no need to differentiate. between never set and empty
If set to something, we show a link "Change"
In Config::getConfigForOptionsPage, we remove the api key from javascript array.
if api key is non-empty, a "_api-key-non-empty" field is set.
*/
document.getElementById('wpc_new_api_key').value = '';
if (getConverterOption(converter, '_api-key-non-empty', false)) {
// api key is set to something...
document.getElementById('wpc_change_api_key').style.display = 'inline';
document.getElementById('wpc_new_api_key').style.display = 'none';
} else {
// api key is empty (or not set)
document.getElementById('wpc_new_api_key').style.display = 'inline';
document.getElementById('wpc_change_api_key').style.display = 'none';
}
apiVersion = getConverterOption(converter, 'api-version', 0);
// if api version isn't set, then either
// - It is running on old api 0. In that case, URL is set
// - Wpc has never been configured. In that case, URL is not set,
// and we should not mention api 0 (we should set apiVersion to 1)
if (!isConverterOptionSet(converter, 'api-version')) {
if (getConverterOption(converter, 'api-url', '') == '') {
apiVersion = 1;
}
}
document.getElementById('wpc_api_version').value = apiVersion.toString();
if (apiVersion != 0) {
}
if (apiVersion == 0) {
document.getElementById('wpc_secret').value = getConverterOption(converter, 'secret', '');
} else {
// Only show api version dropdown if configured to run on old api
// There is no going back!
document.getElementById('wpc_api_version_div').style.display = 'none';
}
document.getElementById('wpc_crypt_api_key_in_transfer').checked = getConverterOption(converter, 'crypt-api-key-in-transfer', true);
// Hide/show the fields for the api version
wpcApiVersionChanged();
//document.getElementById('wpc_secret').value = getConverterOption(converter, 'secret', '');
//document.getElementById('wpc_url_2').value = getConverterOption(converter, 'url-2', '');
//document.getElementById('wpc_secret_2').value = getConverterOption(converter, 'secret-2', '');
//wpcUpdateWebServicesHTML();
break;
case 'gd':
document.getElementById('gd_skip_pngs').checked = getConverterOption(converter, 'skip-pngs', false);
break;
case 'cwebp':
document.getElementById('cwebp_use_nice').checked = getConverterOption(converter, 'use-nice', true);
document.getElementById('cwebp_method').value = getConverterOption(converter, 'method', '');
document.getElementById('cwebp_try_common_system_paths').checked = getConverterOption(converter, 'try-common-system-paths', '');
document.getElementById('cwebp_skip_these_precompiled_binaries').value = getConverterOption(converter, 'skip-these-precompiled-binaries', '');
document.getElementById('cwebp_try_supplied_binary').checked = getConverterOption(converter, 'try-supplied-binary-for-os', '');
document.getElementById('cwebp_set_size').checked = getConverterOption(converter, 'set-size', '');
document.getElementById('cwebp_size_in_percentage').value = getConverterOption(converter, 'size-in-percentage', '');
document.getElementById('cwebp_command_line_options').value = getConverterOption(converter, 'command-line-options', '');
break;
case 'imagemagick':
document.getElementById('imagemagick_use_nice').checked = getConverterOption(converter, 'use-nice', true);
break;
case 'graphicsmagick':
document.getElementById('graphicsmagick_use_nice').checked = getConverterOption(converter, 'use-nice', true);
break;
case 'vips':
document.getElementById('vips_smart_subsample').checked = getConverterOption(converter, 'smart-subsample', false);
document.getElementById('vips_preset').value = getConverterOption(converter, 'preset', 'disable');
break;
case 'ffmpeg':
document.getElementById('ffmpeg_use_nice').checked = getConverterOption(converter, 'use-nice', true);
document.getElementById('ffmpeg_method').value = getConverterOption(converter, 'method', '');
break;
}
tb_show("Configure " + converter['id'] + ' converter', '#TB_inline?inlineId=' + converter['converter']);
}
function updateConverterOptions() {
var id = window.currentlyEditing;
var converter = window.convertersMap[id];
/*
Removed (#243)
if (document.getElementById(id + '_quality')) {
var q = document.getElementById(id + '_quality').value;
if (q == 'auto') {
setConverterOption(converter, 'quality', 'auto');
setConverterOption(converter, 'max-quality', document.getElementById(id + '_max_quality').value);
} else {
setConverterOption(converter, 'quality', 'inherit');
deleteConverterOption(converter, 'max-quality');
}
} else {
deleteConverterOption(converter, 'quality');
deleteConverterOption(converter, 'max-quality');
}
*/
switch (converter['converter']) {
case 'ewww':
setConverterOption(converter, 'api-key', document.getElementById('ewww_api_key').value);
setConverterOption(converter, 'api-key-2', document.getElementById('ewww_api_key_2').value);
break;
case 'wpc':
setConverterOption(converter, 'api-url', document.getElementById('wpc_api_url').value);
//setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
//setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
//setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);*/
var apiVersion = parseInt(document.getElementById('wpc_api_version').value, 10);
setConverterOption(converter, 'api-version', apiVersion);
if (apiVersion == '0') {
setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
} else {
deleteConverterOption(converter, 'secret');
setConverterOption(converter, 'crypt-api-key-in-transfer', document.getElementById('wpc_crypt_api_key_in_transfer').checked);
}
if (document.getElementById('wpc_new_api_key').style.display == 'inline') {
// password field is shown. Store the value
setConverterOption(converter, 'new-api-key', document.getElementById('wpc_new_api_key').value);
} else {
// password field is not shown. Remove "new-api-key" value, indicating there is no new value
//setConverterOption(converter, 'new-api-key', '');
deleteConverterOption(converter, 'new-api-key');
}
break;
case 'gd':
setConverterOption(converter, 'skip-pngs', document.getElementById('gd_skip_pngs').checked);
break;
case 'cwebp':
setConverterOption(converter, 'use-nice', document.getElementById('cwebp_use_nice').checked);
var methodString = document.getElementById('cwebp_method').value;
var methodNum = (methodString == '') ? 6 : parseInt(methodString, 10);
setConverterOption(converter, 'method', methodNum);
setConverterOption(converter, 'skip-these-precompiled-binaries', document.getElementById('cwebp_skip_these_precompiled_binaries').value);
setConverterOption(converter, 'try-common-system-paths', document.getElementById('cwebp_try_common_system_paths').checked);
setConverterOption(converter, 'try-supplied-binary-for-os', document.getElementById('cwebp_try_supplied_binary').checked);
setConverterOption(converter, 'set-size', document.getElementById('cwebp_set_size').checked);
var sizeInPercentageString = document.getElementById('cwebp_size_in_percentage').value;
var sizeInPercentageNumber = (sizeInPercentageString == '') ? '' : parseInt(sizeInPercentageString, 10);
setConverterOption(converter, 'size-in-percentage', sizeInPercentageNumber);
setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
break;
case 'imagemagick':
setConverterOption(converter, 'use-nice', document.getElementById('imagemagick_use_nice').checked);
break;
case 'graphicsmagick':
setConverterOption(converter, 'use-nice', document.getElementById('graphicsmagick_use_nice').checked);
break;
case 'vips':
setConverterOption(converter, 'smart-subsample', document.getElementById('vips_smart_subsample').checked);
var vipsPreset = document.getElementById('vips_preset').value;
if (vipsPreset == 'disable') {
deleteConverterOption(converter, 'preset');
} else {
setConverterOption(converter, 'preset', vipsPreset);
}
break;
case 'ffmpeg':
setConverterOption(converter, 'use-nice', document.getElementById('ffmpeg_use_nice').checked);
var methodString = document.getElementById('ffmpeg_method').value;
var methodNum = (methodString == '') ? 6 : parseInt(methodString, 10);
setConverterOption(converter, 'method', methodNum);
break;
}
updateInputValue();
tb_remove();
}
function updateConverterOptionsAndSave() {
updateConverterOptions();
document.getElementById('webpexpress_settings').submit();
}
/** Encode path before adding to querystring.
* Paths in querystring triggers LFI warning in Wordfence.
* By encoding it, Wordpfence will not detect our misdeed!
*
* see https://github.com/rosell-dk/webp-express/issues/87
*/
function encodePathforQS(path) {
return path.replace('/', '**');
}
function testConverter(id) {
openTestConvertPopup(id);
return;
}
/*
function removeConverter(id) {
for (var i=0; i<window.converters.length; i++) {
if (window.converters[i]['id'] == id) {
window.converters.splice(i, 1);
setConvertersHTML();
break;
}
}
}*/
function addConverter(id) {
window.converters.push({
converter: id
});
setConvertersHTML();
tb_remove();
}
function deactivateConverter(id) {
window.convertersMap[id].deactivated = true;
setConvertersHTML();
}
function activateConverter(id) {
delete window.convertersMap[id].deactivated
setConvertersHTML();
}
/* WPC */
/* ------------- */
/*
function converterQualityChanged(converterId) {
var q = document.getElementById(converterId + '_quality').value;
document.getElementById(converterId + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
}*/
function wpcShowAwaitingApprovalPopup() {
closeDasPopup();
openDasPopup('wpc_awaiting_approval_popup', 500, 350);
/*
window.pollRequestApprovalTid = window.setInterval(function() {
//openDasPopup('wpc_successfully_connected_popup', 500, 350);
}, 1500);*/
}
function wpcRequestAccess() {
var url = document.getElementById('wpc_request_access_url').value;
url = 'http://we0/wordpress/webp-express-server';
jQuery.post(window.ajaxurl, {
'action': 'webpexpress_request_access',
}, function(response) {
if (response && (response.substr(0,1) == '{')) {
var r = JSON.parse(response);
if (r['success']) {
wpcShowAwaitingApprovalPopup()
} else {
alert(r['errorMessage']);
}
}
});
}
function openWpcConnectPopup() {
openDasPopup('wpc_connect_popup', 500, 350);
}
function wpcChangeApiKey() {
document.getElementById('wpc_new_api_key').style.display = 'inline';
document.getElementById('wpc_change_api_key').style.display = 'none';
}
function wpcApiVersionChanged() {
var apiVersion = parseInt(document.getElementById('wpc_api_version').value, 10);
if (apiVersion == 0) {
document.getElementById('wpc_crypt_api_key_in_transfer_div').style.display = 'none';
document.getElementById('wpc_api_key_label_1').style.display = 'inline-block';
document.getElementById('wpc_api_key_label_2').style.display = 'none';
document.getElementById('wpc_secret_div').style.display = 'block';
document.getElementById('wpc_api_key_div').style.display = 'none';
} else {
document.getElementById('wpc_crypt_api_key_in_transfer_div').style.display = 'block';
document.getElementById('wpc_api_key_label_1').style.display = 'none';
document.getElementById('wpc_api_key_label_2').style.display = 'inline-block';
document.getElementById('wpc_secret_div').style.display = 'none';
document.getElementById('wpc_api_key_div').style.display = 'block';
}
}

View File

@@ -0,0 +1,47 @@
function openDasPopup(id, w, h) {
// Create overlay, if it isn't already created
if (!document.getElementById('das_overlay')) {
var el = document.createElement('div');
el.setAttribute('id', 'das_overlay');
document.body.appendChild(el);
}
// Show overlay
document.getElementById('das_overlay').style['display'] = 'block';
// Set width and height on popup
var popupEl = document.getElementById(id);
popupEl.style['width'] = w + 'px';
popupEl.style['margin-left'] = -Math.floor(w/2) + 'px';
popupEl.style['height'] = h + 'px';
popupEl.style['margin-top'] = -Math.floor(h/2) + 'px';
// Show popup
popupEl.style['visibility'] = 'visible';
window.currentDasPopupId = id;
}
function closeDasPopup() {
if (document.getElementById('das_overlay')) {
document.getElementById('das_overlay').style['display'] = 'none';
}
if (window.currentDasPopupId) {
document.getElementById(window.currentDasPopupId).style['visibility'] = 'hidden';
}
}
document.onkeydown = function(evt) {
evt = evt || window.event;
var isEscape = false;
if ("key" in evt) {
isEscape = (evt.key == "Escape" || evt.key == "Esc");
} else {
isEscape = (evt.keyCode == 27);
}
if (isEscape) {
closeDasPopup();
}
};

View File

@@ -0,0 +1,34 @@
/*
function htmlEscape(str) {
return str
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
*/
function webpexpress_escapeHTML(s)
{
return s.replace(/./gm, function(s) {
var safe = /[0-9a-zA-Z\!]/;
if (safe.test(s.charAt(0))) {
return s.charAt(0);
}
switch (s.charAt(0)) {
case '*':
case '#':
case ' ':
case '{':
case '}':
case ':':
case '.':
case '`':
return s.charAt(0);
default:
return "&#" + s.charCodeAt(0) + ";";
}
});
}

View File

@@ -0,0 +1,68 @@
function initComparisonSlider($) {
//function to check if the .cd-image-container is in the viewport here
// ...
var w = $('.cd-image-container').width();
$('.cd-image-container img').each(function(){
$(this).css('max-width', w + 'px');
});
$('.cd-image-container img').first().on('load', function() {
$('.cd-image-container').width($(this).width());
})
//make the .cd-handle element draggable and modify .cd-resize-img width according to its position
$('.cd-image-container').each(function(){
var actual = $(this);
drags(actual.find('.cd-handle'), actual.find('.cd-resize-img'), actual);
});
//draggable funtionality - credits to http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/
function drags(dragElement, resizeElement, container) {
dragElement.on("mousedown vmousedown", function(e) {
dragElement.addClass('draggable');
resizeElement.addClass('resizable');
var dragWidth = dragElement.outerWidth(),
xPosition = dragElement.offset().left + dragWidth - e.pageX,
containerOffset = container.offset().left,
containerWidth = container.outerWidth(),
minLeft = containerOffset + 10,
maxLeft = containerOffset + containerWidth - dragWidth - 10;
dragElement.parents().on("mousemove vmousemove", function(e) {
leftValue = e.pageX + xPosition - dragWidth;
//constrain the draggable element to move inside its container
if(leftValue < minLeft ) {
leftValue = minLeft;
} else if ( leftValue > maxLeft) {
leftValue = maxLeft;
}
widthValue = (leftValue + dragWidth/2 - containerOffset)*100/containerWidth+'%';
$('.draggable').css('left', widthValue).on("mouseup vmouseup", function() {
$(this).removeClass('draggable');
resizeElement.removeClass('resizable');
});
$('.resizable').css('width', widthValue);
//function to upadate images label visibility here
// ...
}).on("mouseup vmouseup", function(e){
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
e.preventDefault();
}).on("mouseup vmouseup", function(e) {
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
};
};

297
lib/options/js/page.js Normal file
View File

@@ -0,0 +1,297 @@
function toggleVisibility(elmId, show) {
var elm = document.getElementById(elmId);
if (!elm) {
return;
}
elm.classList.add('toggler');
/*
if (!elm.style.maxHeight) {
elm.style['maxHeight'] = (elm.clientHeight + 40) + 'px';
}*/
if (show) {
elm.classList.remove('closed');
} else {
elm.classList.add('closed');
}
}
function updateAlterHTMLChartVisibility(show) {
function el(elmId) {
return document.getElementById(elmId);
}
var elm = el('alter_html_comparison_chart');
//elm.style['maxHeight'] = (elm.clientHeight + 40) + 'px';
//elm.style['maxHeight'] = '600px';
//elm.style.display = (show ? 'block' : 'none');
if (show) {
elm.classList.remove('closed');
} else {
elm.classList.add('closed');
}
el('hide_alterhtml_chart_btn').style.display = (show ? 'block' : 'none');
el('show_alterhtml_chart_btn').style.display = (show ? 'none' : 'inline-block');
el('ui_show_alter_html_chart').value = (show ? 'true' : 'false');
}
document.addEventListener('DOMContentLoaded', function() {
//resetToDefaultConverters();
function el(elmId) {
return document.getElementById(elmId);
}
if (el('cache_control_select') && el('cache_control_custom_div') && el('cache_control_set_div')) {
el('cache_control_custom_div').classList.add('effect-visibility');
el('cache_control_set_div').classList.add('effect-visibility');
function updateCacheControlCustomVisibility() {
var cacheControlValue = document.getElementById('cache_control_select').value;
/*
var customEl = document.getElementById('cache_control_custom');
if (cacheControlValue == 'custom') {
customEl.setAttribute('type', 'text');
} else {
customEl.setAttribute('type', 'hidden');
}*/
toggleVisibility('cache_control_custom_div', (cacheControlValue == 'custom'));
toggleVisibility('cache_control_set_div', (cacheControlValue == 'set'));
}
updateCacheControlCustomVisibility();
el('cache_control_select').addEventListener('change', function() {
updateCacheControlCustomVisibility();
});
}
// In "No conversion" and "CDN friendly" mode, toggle cache control div when redirect is enabled/disabled
if (el('operation_mode') && (el('operation_mode').value == 'no-conversion')) {
if (el('redirect_to_existing_in_htaccess') && el('cache_control_div')) {
el('cache_control_div').classList.add('effect-opacity');
function updateCacheControlHeaderVisibility() {
toggleVisibility('cache_control_div', el('redirect_to_existing_in_htaccess').checked);
}
updateCacheControlHeaderVisibility();
el('redirect_to_existing_in_htaccess').addEventListener('change', function() {
updateCacheControlHeaderVisibility();
});
}
}
if (el('only_redirect_to_converter_for_webp_enabled_browsers_row') && el('enable_redirection_to_converter')) {
el('only_redirect_to_converter_for_webp_enabled_browsers_row').classList.add('effect-opacity');
el('only_redirect_to_converter_on_cache_miss_row').classList.add('effect-opacity');
function updateRedirectionOnlyWebPVisibility() {
toggleVisibility('only_redirect_to_converter_for_webp_enabled_browsers_row', el('enable_redirection_to_converter').checked);
toggleVisibility('only_redirect_to_converter_on_cache_miss_row', el('enable_redirection_to_converter').checked);
}
updateRedirectionOnlyWebPVisibility();
el('enable_redirection_to_converter').addEventListener('change', function() {
updateRedirectionOnlyWebPVisibility();
});
}
// Toggle Quality (auto / specific)
if (el('quality_auto_select') && el('max_quality_div') && el('quality_specific_div')) {
function updateQualityVisibility() {
var qualityAutoValue = el('quality_auto_select').value;
toggleVisibility('max_quality_div', el('quality_auto_select').value == 'auto_on');
toggleVisibility('quality_specific_div', el('quality_auto_select').value != 'auto_on');
/*
if (qualityAutoValue == 'auto_on') {
el('max_quality_div').style['display'] = 'inline-block';
el('quality_specific_div').style['display'] = 'none';
} else {
el('max_quality_div').style['display'] = 'none';
el('quality_specific_div').style['display'] = 'inline-block';
}*/
}
updateQualityVisibility();
el('quality_auto_select').addEventListener('change', function() {
updateQualityVisibility();
});
}
// Jpeg encoding changing
if (el('jpeg_encoding_select') && el('jpeg_quality_lossless_div')) {
function updateJpgEncoding() {
toggleVisibility('jpeg_quality_lossless_div', el('jpeg_encoding_select').value != 'lossy');
}
updateJpgEncoding();
el('jpeg_encoding_select').addEventListener('change', function() {
updateJpgEncoding();
});
}
// Jpeg near-lossless toggling
if (el('jpeg_enable_near_lossless') && el('jpeg_near_lossless_div')) {
function updateNearLosslessVisibilityJpeg() {
toggleVisibility('jpeg_near_lossless_div', el('jpeg_enable_near_lossless').value == 'on');
}
updateNearLosslessVisibilityJpeg();
el('jpeg_enable_near_lossless').addEventListener('change', function() {
updateNearLosslessVisibilityJpeg();
});
}
// PNG encoding changing
if (el('image_types') && el('png_row')) {
function updatePngAndJpgRowVisibilities() {
var imageTypes = parseInt(el('image_types').value, 10);
var pngEnabled = (imageTypes & 2);
var jpegEnabled = (imageTypes & 1);
toggleVisibility('png_row', pngEnabled);
toggleVisibility('jpeg_row', jpegEnabled);
}
updatePngAndJpgRowVisibilities();
el('image_types').addEventListener('change', function() {
updatePngAndJpgRowVisibilities();
});
}
// PNG encoding changing
if (el('png_encoding_select') && el('png_quality_lossy_div')) {
function updatePngEncoding() {
toggleVisibility('png_quality_lossy_div', el('png_encoding_select').value != 'lossless');
}
updatePngEncoding();
el('png_encoding_select').addEventListener('change', function() {
updatePngEncoding();
});
}
// PNG near-lossless toggling
if (el('png_enable_near_lossless') && el('png_near_lossless_div')) {
function updateNearLosslessVisibilityPng() {
toggleVisibility('png_near_lossless_div', el('png_enable_near_lossless').value == 'on');
}
updateNearLosslessVisibilityPng();
el('png_enable_near_lossless').addEventListener('change', function() {
updateNearLosslessVisibilityPng();
});
}
// If "doc-root" cannot be used for structuring, disable the option and set to "image-roots"
if (!window.webpExpress['can-use-doc-root-for-structuring']) {
el('destination_structure').classList.add('effect-opacity');
toggleVisibility('destination_structure', false);
if (el('destination_structure').value == 'doc-root') {
el('destination_structure').value = 'image-roots';
}
}
// Toggle File Extension (only show when "mingled" is selected)
if (el('destination_folder') && el('destination_extension')) {
el('destination_extension').classList.add('effect-opacity');
function updateDestinationExtensionVisibility() {
toggleVisibility('destination_extension', el('destination_folder').value == 'mingled');
if (el('destination_folder').value == 'separate') {
el('destination_extension').value = 'append';
}
/*
if (document.getElementById('destination_folder').value == 'mingled') {
el('destination_extension_row').style['display'] = 'table-row';
} else {
el('destination_extension_row').style['display'] = 'none';
}*/
}
updateDestinationExtensionVisibility();
document.getElementById('destination_folder').addEventListener('change', function() {
updateDestinationExtensionVisibility();
});
}
// Toggle webservice
if (el('web_service_enabled') && el('whitelist_div')) {
el('whitelist_div').classList.add('effect-opacity');
function updateServerSettingsVisibility() {
toggleVisibility('whitelist_div', el('web_service_enabled').checked);
//document.getElementById('whitelist_div').style['display'] = (el('web_service_enabled').checked ? 'block' : 'none');
}
updateServerSettingsVisibility();
document.getElementById('web_service_enabled').addEventListener('change', function() {
updateServerSettingsVisibility();
});
}
// Toggle Alter HTML options
if (el('alter_html_enabled') && (el('alter_html_options_div'))) {
el('alter_html_options_div').classList.add('effect-opacity');
el('alter_html_comparison_chart').classList.add('effect-slider');
function updateAlterHTMLVisibility() {
toggleVisibility('alter_html_options_div', el('alter_html_enabled').checked);
// toggleVisibility('alter_html_comparison_chart', el('alter_html_enabled').checked);
}
updateAlterHTMLVisibility();
el('alter_html_enabled').addEventListener('change', function() {
updateAlterHTMLVisibility();
});
}
// Show/hide "Only do the replacements in webp enabled browsers" when "What to replace" is changed
if (el('alter_html_replacement_url') && el('alter_html_url_options_div')) {
el('alter_html_url_options_div').classList.add('effect-opacity');
el('alter_html_picture_options_div').classList.add('effect-opacity');
function updateAlterHTMLReplaceVisibility() {
toggleVisibility('alter_html_url_options_div', el('alter_html_replacement_url').checked);
toggleVisibility('alter_html_picture_options_div', el('alter_html_replacement_picture').checked);
}
updateAlterHTMLReplaceVisibility();
el('alter_html_replacement_url').addEventListener('change', function() {
updateAlterHTMLReplaceVisibility();
});
el('alter_html_replacement_picture').addEventListener('change', function() {
updateAlterHTMLReplaceVisibility();
});
}
if (el('ui_show_alter_html_chart') && el('alter_html_comparison_chart')) {
var elm = el('alter_html_comparison_chart');
elm.style['maxHeight'] = (elm.clientHeight + 80) + 'px';
updateAlterHTMLChartVisibility(el('ui_show_alter_html_chart').value == 'true');
}
document.getElementById('change_operation_mode').addEventListener('change', function() {
var msg;
if (document.getElementById('operation_mode').value == 'tweaked') {
msg = 'Save configuration and change mode? Any tweaks will be lost';
} else {
if (document.getElementById('change_operation_mode').value == 'tweaked') {
msg = 'Save configuration and change to tweaked mode? No options are lost when changing to tweaked mode (it will behave the same way as currently, until you start tweaking)';
} else {
msg = 'Save configuration and change mode?';
}
}
if (confirm(msg)) {
document.getElementById('webpexpress_settings').submit();
} else {
// undo select box change
document.getElementById('change_operation_mode').value = document.getElementById('operation_mode').value;
return;
}
});
});

View File

@@ -0,0 +1,50 @@
function openDeleteConvertedFilesPopup() {
var html = '';
html += '<p>To delete all converted files, click this button:<br>';
html += '<button onclick="purgeCache(false)" class="button button-secondary" type="button">Delete all converted files</button>';
html += '</p>';
html += '<p>Or perhaps, you only want to delete the converted <i>PNGs</i>? Then this button is for you:<br>';
html += '<button onclick="purgeCache(true)" class="button button-secondary" type="button">Delete converted PNGs</button>';
html += '</p>';
document.getElementById('purgecachecontent').innerHTML = html;
tb_show('Purge cache', '#TB_inline?inlineId=purgecachepopup');
// purgeCache();
}
function purgeCache(onlyPng) {
var data = {
'action': 'webpexpress_purge_cache',
'nonce' : window.webpExpress['ajax-nonces']['purge-cache'],
'only-png': onlyPng
};
jQuery.post(ajaxurl, data, function(response) {
if ((typeof response == 'object') && (response['success'] == false)) {
if (response['data'] && ((typeof response['data']) == 'string')) {
alert(response['data']);
} else {
alert('Something failed');
}
return;
}
var result = JSON.parse(response);
//console.log(result);
if (result['fail-count'] == 0) {
if (result['delete-count'] == 0) {
alert('No webp files were found, so none was deleted.');
} else {
alert('Successfully deleted ' + result['delete-count'] + ' webp files');
}
} else {
if (result['delete-count'] == 0) {
alert('Failed deleting ' + result['fail-count'] + ' webp files. None was deleted, in fact.');
} else {
alert('Deleted ' + result['delete-count'] + ' webp files. However, failed deleting ' + result['fail-count'] + ' webp files.');
}
}
});
}

View File

@@ -0,0 +1,74 @@
function openDeleteLogFilesPopup() {
var html = '';
html += '<p><b>Are you sure you want to do that?</b></p>';
html += '<p>' +
'The log files contain information about the conversion settings used for each webp and the libraries ' +
'used, and the versions. This information will be visible in the conversion manager in a not too far future. ' +
'The information might also be used to notify you if the libraries / version of a library you have is ' +
'significantly better than the one used for the conversion. ' +
'If you delete the log files, you will not benefit from this functionality. ' +
'</p>';
html += '<p>' +
'The log files are btw located in <i>wp-content/webp-express/log/</i>, if you want to have a closer look.' +
'</p>';
//html += '<p>In a not too far future, the log files will be used in the conversion manager.</p>'
//html += 'They could become handy.</p>'
/*
html += '<p>This action cannot be reversed. Your log files will be gone. '
html += 'Dead. Completely. Forever. '
html += '(Unless of course you have a backup. Or, of course, there are ways of recovery... Anyway...). '
html += 'Ok, sorry for the babbeling. The dialog seemed bare without text.</p>';*/
html += '<button onclick="purgeLog()" class="button button-secondary" type="button">Yes, delete!</button>';
document.getElementById('purgelogcontent').innerHTML = '<div>' + html + '</div>';
tb_show('Delete all log Files?', '#TB_inline?inlineId=purgelogpopup&height=320&width=450');
}
function closePurgeLogDialog() {
tb_remove();
}
function purgeLog() {
var data = {
'action': 'webpexpress_purge_log',
'nonce' : window.webpExpress['ajax-nonces']['purge-log'],
};
jQuery.post(ajaxurl, data, function(response) {
if ((typeof response == 'object') && (response['success'] == false)) {
if (response['data'] && ((typeof response['data']) == 'string')) {
alert(response['data']);
} else {
alert('Something failed');
}
return;
}
var result = JSON.parse(response);
//console.log(result);
var html = '<div><p>';
if (result['fail-count'] == 0) {
if (result['delete-count'] == 0) {
html += 'No log files were found, so none was deleted.';
} else {
html += 'Successfully deleted ' + result['delete-count'] + ' log files';
}
} else {
if (result['delete-count'] == 0) {
html += 'Failed deleting ' + result['fail-count'] + ' log files. None was deleted, in fact.';
} else {
html += 'Deleted ' + result['delete-count'] + ' log files. However, failed deleting ' + result['fail-count'] + ' log files.';
}
}
html += '</p>';
html += '<button onclick="closePurgeLogDialog()" class="button button-secondary" type="button">Ok</button>';
html += '</div>';
document.getElementById('purgelogcontent').innerHTML = html;
});
}

160
lib/options/js/self-test.js Normal file
View File

@@ -0,0 +1,160 @@
if (typeof WebPExpress == 'undefined') {
WebPExpress = {};
}
WebPExpress.SelfTest = {
'clear': function() {
document.getElementById('webpexpress_test_redirection_content').innerHTML = '';
},
'write': function(html) {
//var el = document.getElementById('webpexpress_test_redirection_content');
//el.innerHTML += html;
var el = document.createElement('div');
el.innerHTML = html;
document.getElementById('webpexpress_test_redirection_content').appendChild(el);
},
'simpleMdToHtml': function(line) {
//return s.replace(/./gm, function(s) {
if (line.substr(0, 3) == '```') {
return '<pre>' + line.replace(/\`/gm, '') + '</pre>';
}
// Bold with inline attributtes (ie: "hi **bold**{: .red}")
line = line.replace(/\*\*([^\*]+)\*\*\{:\s([^}]+)\}/gm, function(s, g1, g2) {
// g2 is the inline attributes.
// right now we only support classes, and only ONE class.
// so it is easy.
var className = g2.substr(1);
return '<b class="' + className + '">' + g1 + '</b>';
//return '<b>' + s.substr(2, s.length - 4) + '</b>';
});
// Bold
line = line.replace(/(\*\*[^\*]+\*\*)/gm, function(s) {
return '<b>' + s.substr(2, s.length - 4) + '</b>';
});
// Italic
line = line.replace(/(\*[^\*]+\*)/gm, function(s) {
return '<i>' + s.substr(1, s.length - 2) + '</i>';
});
// Headline
if (line.substr(0, 2) == '# ') {
line = '<h1>' + line.substr(2) + '</h1>';
}
if (line.substr(0, 3) == '## ') {
line = '<h2>' + line.substr(3) + '</h2>';
}
if (line.substr(0, 4) == '### ') {
line = '<h3>' + line.substr(4) + '</h3>';
}
if (line.substr(0, 5) == '#### ') {
line = '<h4>' + line.substr(5) + '</h4>';
}
if (line.substr(0, 15) == '&#39;&#39;&#39;') {
line = '<pre>' + line.substr(15) + '</pre>';
}
// Empty line
if (line == '') {
line = '<br>';
}
// "ok!" green
line = line.replace(/ok\!/gmi, function(s) {
return '<span style="color:green; font-weight: bold;">ok</span>';
});
// "great" green
/*
line = line.replace(/great/gmi, function(s) {
return '<span style="color:green; font-weight: bold;">' + s + '</span>';
});*/
// "failed" red
line = line.replace(/failed/gmi, function(s) {
return '<span style="color:red; font-weight:bold">FAILED</span>';
});
return '<div class="webpexpress md">' + line + '</div>';
},
'responseHandler': function(response) {
if ((typeof response == 'object') && (response['success'] == false)) {
html = '<h1>Error</h1>';
if (response['data'] && ((typeof response['data']) == 'string')) {
html += webpexpress_escapeHTML(response['data']);
}
WebPExpress.SelfTest.write(html);
document.getElementById('bulkconvertcontent').innerHTML = html;
return;
}
var responseObj = JSON.parse(response);
var result = responseObj['result'];
if (typeof result == 'string') {
result = [result];
}
for (var i=0; i<result.length; i++) {
var line = result[i];
if (typeof line != 'string') {
continue;
}
line = webpexpress_escapeHTML(line);
line = WebPExpress.SelfTest.simpleMdToHtml(line);
WebPExpress.SelfTest.write(line);
}
//result = result.join('<br>');
var next = responseObj['next'];
if (next == 'done') {
//WebPExpress.SelfTest.write('<br>done');
} else if (next == 'break') {
WebPExpress.SelfTest.write('breaking');
} else {
WebPExpress.SelfTest.runTest(next);
}
},
'runTest': function(testId) {
var data = {
'action': 'webpexpress_self_test',
'testId': testId,
'nonce' : window.webpExpress['ajax-nonces']['self-test'],
};
jQuery.ajax({
method: 'POST',
url: ajaxurl,
data: data,
success: WebPExpress.SelfTest.responseHandler,
error: function() {
WebPExpress.SelfTest.write('PHP error. Check your debug.log for more info (make sure debugging is enabled)');
},
});
},
'openPopup': function(testId) {
WebPExpress.SelfTest.clear();
var w = Math.min(1000, Math.max(200, document.documentElement.clientWidth - 100));
var h = Math.max(250, document.documentElement.clientHeight - 80);
var title = 'Self testing';
if (testId == 'redirectToExisting') {
title = 'Testing redirection to existing webp';
}
tb_show(title, '#TB_inline?inlineId=webpexpress_test_redirection_popup&width=' + w + '&height=' + h);
WebPExpress.SelfTest.runTest(testId);
}
}

3
lib/options/js/sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,294 @@
function openTestConvertPopup(converterId) {
var html = '<div id="tc_conversion_options">options</div><div><div id="tc_conversion_result"><h2>Result</h2>wait...</div></div>'
document.getElementById('tc_content').innerHTML = html;
var w = Math.min(1200, Math.max(200, document.documentElement.clientWidth - 100));
var h = Math.max(250, document.documentElement.clientHeight - 80);
tb_show('Testing conversion', '#TB_inline?inlineId=tc_popup&width=' + w + '&height=' + h);
webpexpress_createTCOptions(converterId);
}
function webpexpress_createTCOptions(converterId) {
var html = '';
html += '<h2>Options</h2>'
html += '<form>';
html += '<div style="display:inline-block; margin-right: 20px;"><label>Converter:</label><select id="converter" name="converter">';
for (var i=0; i<window.converters.length; i++) {
var c = window.converters[i];
var cid = c['converter'];
html += '<option value="' + cid + '"' + (cid == converterId ? ' selected' : '') + '>' + cid + '</option>';
}
html += '</select></div>'
html += '<div style="display:inline-block;"><label>Test image:</label><select id="test_image" name="image">';
//html += '<option value="dice.png">dice.png</option>';
html += '<option value="alphatest.png">alphatest.png</option>';
html += '<option value="test-pattern-tv.jpg">test-pattern-tv.jpg</option>';
html += '<option value="dice.png">dice.png</option>';
//html += '<option value="alphatest.png">alphatest.png</option>';
html += '<option value="palette-based-colors.png">palette-based-colors.png</option>';
//html += '<option value="test.png">test.png</option>';
html += '<option value="architecture-q85-w600.jpg">architecture-q85-w600.jpg</option>';
html += '</select></div>';
// html += '<h3>Conversion options</h3>'
html += '<div id="tc_png" class="toggler effect-visibility"><h3>Conversion options (PNG)</h3><div id="tc_png_options"></div></div>';
html += '<div id="tc_jpeg" class="toggler effect-visibility"><h3>Conversion options (JPEG)</h3><div id="tc_jpeg_options"></div></div>';
// html += '<div id="tc_jpeg_options" class="toggler effect-visibility"></div>';
html += '<div><label>Metadata:</label><div id="tc_metadata" style="display: inline-block"></div></div>';
html += '<button onclick="runTestConversion()" class="button button-primary" type="button" style="margin-top:25px">Convert</button>';
html += '</form>';
document.getElementById('tc_conversion_options').innerHTML = html;
// Append PNG
document.getElementById('tc_png_options').appendChild(
document.getElementById('png_td').cloneNode(true)
);
// Append Jpeg
document.getElementById('tc_jpeg_options').appendChild(
document.getElementById('jpeg_td').cloneNode(true)
);
// Append Metadata
document.getElementById('tc_metadata').appendChild(
document.getElementById('metadata').cloneNode(true)
);
// change ids. All id's will get appended "tc_" - unless they already have
document.querySelectorAll('#tc_conversion_options [id]').forEach(function(el) {
el.value = document.getElementById(el.id).value;
if (el.id.indexOf('tc_') != 0) {
el.id = 'tc_' + el.id;
}
});
// listen to all select box changes
document.querySelectorAll('#tc_conversion_options select').forEach(function(el) {
el.addEventListener('change', function() {
webpexpress_updateVisibilities();
});
});
webpexpress_updateVisibilities();
runTestConversion();
}
function webpexpress_updateVisibilities() {
// toggleVisibility('png_row', el('image_types').value != '1');
function el(elmId) {
return document.getElementById(elmId);
}
var testImage = el('tc_test_image').value;
var isPng = (testImage.toLowerCase().indexOf('.png') != -1);
toggleVisibility('tc_png', isPng);
toggleVisibility('tc_jpeg', !isPng);
toggleVisibility('tc_png_quality_lossy_div', el('tc_png_encoding_select').value != 'lossless');
toggleVisibility('tc_png_near_lossless_div', el('tc_png_enable_near_lossless').value == 'on');
console.log('value:' + el('tc_quality_auto_select').value);
toggleVisibility('tc_max_quality_div', el('tc_quality_auto_select').value == 'auto_on');
toggleVisibility('tc_quality_specific_div', el('tc_quality_auto_select').value != 'auto_on');
}
function runTestConversion() {
var html = '';
function elTxt(elmName) {
//var el = document.getElementById('tc_' + elmId);
var el = document.querySelector('#tc_conversion_options [name=' + elmName + ']');
if (!el) {
alert('Error: Could not find element with name: "' + elmName + '"');
}
return el.value;
}
function elInt(elmName) {
return parseInt(elTxt(elmName), 10);
}
var configOverrides = {
"jpeg-encoding": elTxt("jpeg-encoding"),
"jpeg-enable-near-lossless": (elTxt("jpeg-enable-near-lossless") == 'on'),
"jpeg-near-lossless": elInt('jpeg-near-lossless'),
"quality-auto": (elTxt("quality-auto") == 'auto_on'),
"max-quality": elInt('max-quality'),
"quality-specific": (elTxt("quality-auto") == 'auto_on' ? elInt('quality-fallback') : elInt('quality-specific')),
"png-encoding": elTxt("png-encoding"),
"png-enable-near-lossless": true,
"png-near-lossless": elInt("png-near-lossless"),
"png-quality": elInt("png-quality"),
"alpha-quality": elInt("alpha-quality"),
"metadata": elTxt('metadata'),
"log-call-arguments": true,
};
var data = {
'action': 'convert_file',
'nonce': window.webpExpress['ajax-nonces']['convert'],
'filename': window.webpExpressPaths['filePaths']['webpExpressRoot'] + '/test/' + elTxt('image'),
"converter": elTxt("converter"),
'config-overrides': JSON.stringify(configOverrides)
}
//html = JSON.stringify(data);
//html = 'Converting...';
document.getElementById('tc_conversion_result').innerHTML = html;
jQuery.ajax({
method: 'POST',
url: ajaxurl,
data: data,
//dataType: 'json',
success: (response) => {
convertResponseCallback(response);
},
error: () => {
convertResponseCallback({requestError: true});
},
});
}
function processLogMoveOptions(thelog) {
var pos1 = thelog.indexOf('Options:<br>---');
if (pos1 >= 0) {
var pos2 = thelog.indexOf('<br>', pos1 + 12) + 4;
//pos2+=8;
/*if (thelog.indexOf('<br>', pos2) < 2) {
pos2 = thelog.indexOf('<br>', pos2) + 4;
}*/
var pos3 = thelog.indexOf('----<br>', pos2) + 8;
// Remove empty line after "Conversion log:"
var pos4 = thelog.indexOf('<br>', pos3);
if (pos4-pos3 < 2) {
pos3 = pos4 + 4;
}
//pos3+=4;
return thelog.substr(0, pos1) +
thelog.substr(pos3) +
//'-------------------------------------------<br>' +
'<h3>Options:</h3>' +
thelog.substr(pos2, pos3-pos2);
}
return thelog;
/*
return thelog.substr(0, pos1) +
'Click to view options' +
'<div style="display:none">' + thelog.substr(pos1, pos2-pos1) + '</div>' +
thelog.substr(pos2);
*/
}
function convertResponseCallback(response){
if (typeof response.requestError == 'boolean') {
document.getElementById('tc_conversion_result').innerHTML = '<h1 style="color:red">An error occured!</h1>';
//console.log('response', response);
return;
}
if ((response['success'] === false) && response['data']) {
document.getElementById('tc_conversion_result').innerHTML = '<h1 style="color:red">An error occured</h1>' + response['data'];
return;
}
if ((typeof response == 'string') && (response[0] != '{')) {
document.getElementById('tc_conversion_result').innerHTML =
'<h1 style="color:red">Response was not JSON</h1><p>The following was returned:</p>' + response;
return;
}
var result = JSON.parse(response);
//result['log'] = processLogMoveOptions(result['log']);
//var html = document.getElementById('tc_conversion_result').innerHTML;
var html = '';
if (result['success'] === true) {
html += '<h2>Result: <span style="color:green;margin-bottom:2px">Success</span></h2>';
// sizes
var orgSize = result['filesize-original'];
var webpSize = result['filesize-webp'];
html += '<b>Reduction: ' + Math.round((orgSize - webpSize)/orgSize * 100) + '% ';
if (orgSize < 10000) {
orgSizeStr = orgSize + ' bytes';
webpSizeStr = webpSize + ' bytes';
} else {
orgSizeStr = Math.round(orgSize / 1024) + ' K';
webpSizeStr = Math.round(webpSize / 1024) + ' K';
}
html += '(from ' + orgSizeStr.replace('K', 'kb') + ' to ' + webpSizeStr.replace('K', 'kb') + ')';
html += '</b><br><br>'
if (window.canDisplayWebp) {
var filename = document.querySelector('#tc_conversion_options [name=image]').value;
var srcUrl = '/' + window.webpExpressPaths['urls']['webpExpressRoot'] + '/test/' + filename;
//html += '<img src="/' + srcUrl + '" style="width:100%">';
// TODO: THIS DOES NOT WORK. NEEDS ATTENTION!
/*
var webpUrl = '/' + window.webpExpressPaths['urls']['content'] +
'/webp-express/webp-images/doc-root/' +
window.webpExpressPaths['filePaths']['pluginRelToDocRoot'] + '/' +
'webp-express/' +
'test/' +
filename + '.webp';
*/
//html += '<img src="' + webpUrl + '" style="width:100%">';
var webpUrl = result['destination-url'];
html += '<div class="cd-image-container">';
html += ' <div class="cd-image-label webp">WebP: ' + webpSizeStr + '</div>';
html += ' <div class="cd-image-label original">' + (filename.toLowerCase().indexOf('png') > 0 ? 'PNG' : 'JPEG') + ': ' + orgSizeStr + '</div>';
html += ' <img src="' + webpUrl + '" alt="Converted Image" style="max-width:100%">';
html += ' <div class="cd-resize-img"> <!-- the resizable image on top -->';
html += ' <img src="' + srcUrl + '" alt="Original Image">';
html += ' </div>';
html += ' <span class="cd-handle"></span> <!-- slider handle -->';
html += '</div> <!-- cd-image-container -->';
html += '<i>Drag the slider above to compare original vs webp</i><br><br>'
}
html += '<h3>Conversion log:</h3>';
// the "log" result is a simple form of markdown, using just italic, bold and newlines.
// It ought not to return anything evil, but safety first
html += '<pre style="white-space:pre-wrap">' + webpexpress_escapeHTML(result['log']) + '</pre>';
document.getElementById('tc_conversion_result').innerHTML = html;
initComparisonSlider(jQuery);
} else {
html += '<h2>Result: <span style="color:red;margin-bottom:2px">Failure</span></h2>';
if (result['msg'] != '') {
html += ' <h3>Message: <span style="color:red; font-weight: bold">' + webpexpress_escapeHTML(result['msg']) + '</span></h3>';
}
if (result['log'] != '') {
html += '<h3>Conversion log:</h3>';
html += '<pre style="white-space:pre-wrap">' + webpexpress_escapeHTML(result['log']) + '</pre>';
}
document.getElementById('tc_conversion_result').innerHTML = html;
}
//html = result['log'];
}

203
lib/options/js/whitelist.js Normal file
View File

@@ -0,0 +1,203 @@
function updateWhitelistInputValue() {
if (document.getElementById('whitelist') == null) {
console.log('document.getElementById("whitelist") returns null. Strange! Please report.');
return;
}
document.getElementById('whitelist').value = JSON.stringify(window.whitelist);
}
function whitelistStartPolling() {
jQuery.post(window.ajaxurl, {
'action': 'webpexpress_start_listening',
}, function(response) {
window.whitelistTid = window.setInterval(function() {
jQuery.post(window.ajaxurl, {
'action': 'webpexpress_get_request',
}, function(response) {
if (response && (response.substr(0,1) == '{')) {
var r = JSON.parse(response);
window.webpexpress_incoming_request = r;
//console.log(r);
window.clearInterval(window.whitelistTid);
closeDasPopup();
// Show request
openDasPopup('whitelist_accept_request', 300, 200);
var s = '';
s += 'Website: ' + r['label'] + '<br>';
s += 'IP: ' + r['ip'] + '<br>';
document.getElementById('request_details').innerHTML = s;
} else {
console.log('Got this from the server: ' + response);
}
}
);
}, 2000);
}
);
}
function whitelistCancelListening() {
/*
jQuery.post(window.ajaxurl, {
'action': 'webpexpress_stop_listening',
}, function(response) {}
);
*/
}
function whitelistCreateUid() {
var timestamp = (new Date()).getTime();
var randomNumber = Math.floor(Math.random() * 10000);
return (timestamp * 10000 + randomNumber).toString(36);
}
/*
function whitelistAcceptRequest() {
whitelistCancelListening();
closeDasPopup();
var r = window.webpexpress_incoming_request;
window.whitelist.push({
uid: whitelistCreateUid(),
label: r['label'],
'new-api-key': r['api-key'],
ip: r['ip'],
// new_password: '',
//quota: 60
});
updateWhitelistInputValue();
whitelistSetHTML();
}
function whitelistDenyRequest() {
whitelistCancelListening();
closeDasPopup();
}*/
function whitelistAddSite() {
whitelistStartPolling();
openDasPopup('whitelist_listen_popup', 400, 300);
}
function whitelistRemoveEntry(i) {
window.whitelist.splice(i, 1);
whitelistSetHTML();
}
function whitelistSetHTML() {
updateWhitelistInputValue();
var s = '';
if (window.whitelist && window.whitelist.length > 0) {
s+='<br><i>Authorized web sites:</i>';
s+='<ul>';
for (var i=0; i<window.whitelist.length; i++) {
s+='<li>';
s+= webpexpress_escapeHTML(window.whitelist[i]['label']);
s+='<div class="whitelist-links">'
s+='<a href="javascript:whitelistEditEntry(' + i + ')">edit</a>';
s+='<a href="javascript:whitelistRemoveEntry(' + i + ')">remove</a>';
s+='</div>'
s+='</li>';
}
s+='</ul>';
} else {
s+='<p style="margin:12px 0"><i>No sites have been authorized to use the web service yet.</i></p>';
}
s+='<button type="button" class="button button-secondary" id="server_listen_btn" onclick="whitelistAddManually()">+ Authorize website</button>';
document.getElementById('whitelist_div').innerHTML = s;
}
function whitelistClearWhitelistEntryForm() {
document.getElementById('whitelist_label').value = '';
document.getElementById('whitelist_ip').value = '';
document.getElementById('whitelist_api_key').value = '';
document.getElementById('whitelist_require_api_key_to_be_crypted_in_transfer').checked = true;
}
function whitelistAddWhitelistEntry() {
if (document.getElementById('whitelist_label').value == '') {
alert('Label must be filled out');
return;
}
if (document.getElementById('whitelist_ip').value == '') {
alert('IP must be filled out. To allow any IP, enter "*"');
return;
}
// TODO: Validate IP syntax
if (document.getElementById('whitelist_api_key').value == '') {
alert('API key must be filled in');
return;
}
window.whitelist.push({
uid: whitelistCreateUid(),
label: document.getElementById('whitelist_label').value,
ip: document.getElementById('whitelist_ip').value,
'new-api-key': document.getElementById('whitelist_api_key').value,
'require-api-key-to-be-crypted-in-transfer': document.getElementById('whitelist_require_api_key_to_be_crypted_in_transfer').checked,
// new_password: '',
//quota: 60
});
updateWhitelistInputValue();
whitelistSetHTML();
closeDasPopup();
}
function whitelistAddManually() {
// alert('not implemented yet');
whitelistClearWhitelistEntryForm();
document.getElementById('whitelist_properties_popup').className = 'das-popup mode-add';
// whitelistCancelListening();
// closeDasPopup();
openDasPopup('whitelist_properties_popup', 400, 300);
}
function whitelistChangeApiKey() {
document.getElementById('whitelist_api_key').value = prompt('Enter new api key');
}
function whitelistUpdateWhitelistEntry() {
var i = parseInt(document.getElementById('whitelist_i').value, 10);
window.whitelist[i]['uid'] = document.getElementById('whitelist_uid').value;
window.whitelist[i]['label'] = document.getElementById('whitelist_label').value;
window.whitelist[i]['ip'] = document.getElementById('whitelist_ip').value;
if (document.getElementById('whitelist_api_key').value != '') {
window.whitelist[i]['new-api-key'] = document.getElementById('whitelist_api_key').value;
}
window.whitelist[i]['require-api-key-to-be-crypted-in-transfer'] = document.getElementById('whitelist_require_api_key_to_be_crypted_in_transfer').checked;
whitelistSetHTML();
closeDasPopup();
}
function whitelistEditEntry(i) {
var entry = window.whitelist[i];
whitelistClearWhitelistEntryForm();
document.getElementById('whitelist_properties_popup').className = 'das-popup mode-edit';
document.getElementById('whitelist_uid').value = entry['uid'];
document.getElementById('whitelist_i').value = i;
document.getElementById('whitelist_label').value = entry['label'];
document.getElementById('whitelist_ip').value = entry['ip'];
document.getElementById('whitelist_api_key').value = '';
document.getElementById('whitelist_require_api_key_to_be_crypted_in_transfer').checked = entry['require-api-key-to-be-crypted-in-transfer'];
openDasPopup('whitelist_properties_popup', 400, 300);
}
document.addEventListener('DOMContentLoaded', function() {
updateWhitelistInputValue();
whitelistSetHTML();
});

View File

@@ -0,0 +1,235 @@
<?php
namespace WebPExpress;
?>
<tr>
<th scope="row">Alter HTML?</span><?php echo helpIcon(
'<p>Alter HTML to either use picture tag syntax for pointing to webp versions or point directly to webps.</p>' .
'<p>Pro picture tag syntax: The browser selects the webp if it supports it.</p>' .
'<p>Pro image urls: Also works on inline styles</p>' .
(($config['operation-mode'] == 'varied-image-responses') ?
'<p>You do not need to enable this in <i>Varied image responses</i> operation mode, but enabling it has some benefits: ' .
'Caching is improved if you are on a CDN as the webp images that are requested directly does not need to be keyed by the Accept header. ' .
'Also, when a user downloads an image, it will have the correct extension.</p>' : '')
);
?>
</th>
<td>
<input
id="alter_html_enabled"
name="alter-html-enabled"
<?php echo ($config['alter-html']['enabled'] ? 'checked="checked"' : '') ?>
value="true"
type="checkbox"
>
</td>
</tr>
<tr>
<th scope="row" colspan=2>
<div id="alter_html_options_div">
<p>
Two distinct methods for altering HTML are supported. <a id="show_alterhtml_chart_btn" href="javascript:updateAlterHTMLChartVisibility(true);">View comparison chart</a>
<input type="hidden" name="ui-show-alter-html-chart" id="ui_show_alter_html_chart" value="false">
</p>
<div id="alter_html_comparison_chart" name="alter-html-comparison-chart" class="toggler effect-slider">
<table class="designed">
<tr>
<th></th>
<th>Method 1: Replacing &lt;img&gt; tags with &lt;picture&gt; tags</th>
<th>Method 2: Replacing image URLs</th>
</tr>
<tr>
<th>How it works</th>
<td>
<p>
It replaces &lt;img&gt; tags with &lt;picture&gt; tags, adding two &lt;source&gt; tags - one for the original image(s), and one
for the webp image(s).
Browsers that supports webp picks the &lt;source&gt; tag with <i>type</i> attribute set to "image/webp".
A small javascript can be optionally added for dynamically loading picturefill.js on browsers that doesn't support the picture tag.
</p>
<p>
We are using <a target="_blank" href="https://github.com/rosell-dk/dom-util-for-webp">this library</a>.
You can visit it for more information.
</p>
</td>
<td>
It replaces any image url it can find.
We are using <a target="_blank" href="https://github.com/rosell-dk/dom-util-for-webp">this library</a>.
You can visit it for more information.
</td>
</tr>
<tr>
<th>Page caching</th>
<td>Works great with page caching, because all browsers are served the same HTML</td>
<td>
<p>
<b>Does not work with page caching</b> &ndash; <i>unless</i> you are using the <i>Cache Enabler</i> plugin,
which is able to maintain two cached versions of each page.<br><br>
<span style="font-size:10px">
Note: <i>Cache Enabler</i> also has HTML altering, but their implementation has <a target="_blank" href="https://github.com/keycdn/cache-enabler/issues/51">limitations</a>.
It for example doesn't replace background images in inline styles and it does not look for all common lazy load attributes. It also has some problems in edge cases.
For this reason, I recommend activating HTML altering in WebP Express, even when <i>Cache Enabler</i> is used.
By doing that, both plugins will have a go at it (WebP Express comes first).
At least it will take care of the limitations in <i>Cache Enabler</i>.
It does however not cure the edge cases where Cache Enabler replaces things it should not, or <a href="https://wordpress.org/support/topic/why-optimus-with-cache-enabler/">crashes</a>
</span>
</p>
</td>
</tr>
<tr>
<th><nobr>Styling and javascript</nobr></th>
<td>May break because of changed HTML structure.
A selector such as "div > img" will no longer match the image because the immidiate parent is now "picture".
However, a selector such as "div img" will still work.
Luckily, the picture element is not meant to be styled - one still has to target the contained img element.
</td>
<td>No problems</td>
</tr>
<tr>
<th>Comprehensiveness</th>
<td>Only replaces &lt;img&gt; tags - other images are untouched</td>
<td>Very comprehensive. Replaces images in inline styles, image urls in lazy load attributes set in &lt;div&gt; or &lt;li&gt; tags, etc.</td>
</tr>
</table>
</div>
<a id="hide_alterhtml_chart_btn" href="javascript:updateAlterHTMLChartVisibility(false);">Hide comparison chart</a>
<div>
<label>What to replace:</label>
<ul style="margin-left: 20px; margin-top: 5px"><li>
<?php
webpexpress_radioButton(
'alter-html-replacement',
'picture',
'Replace &lt;img&gt; tags with &lt;picture&gt; tags, adding the webp to srcset.',
$config['alter-html']['replacement'], // PS: the function takes care of the escaping
'<p>"Picture tag" replaces &lt;img&gt tags with &lt;picture&gt; tags and adds the webp as an extra source. ' .
'This effectively points webp-enabled browsers to the webp variant and other browsers to ' .
'the original image.</p>' .
'<p><em>Beware that this structural change may break styling!</em><br>' .
'(a selector such as "div > img" will no longer match the image because the immidiate parent is now "picture". However, ' .
'a selector such as "div img" will still work)' .
'</p>' .
'<p>PS: This functionality is handled by ' .
'<a target="_blank" href="https://github.com/rosell-dk/dom-util-for-webp">this external library</a>' .
' (I have pushed the code to an external library so it can be used by other projects besides this plugin)</p>'
);
?>
</li>
<li><div id="alter_html_picture_options_div" style="margin-left:18px; margin-bottom: 13px;">
<?php
webpexpress_checkbox(
'alter-html-add-picturefill-js',
$config['alter-html']['alter-html-add-picturefill-js'], // PS: the function takes care of the escaping
'Dynamically load picturefill.js on older browsers',
'If you enable this option, a small script will be added which detects if picture tags are supported and loads ' .
'<a href="https://github.com/scottjehl/picturefill" target="_blank">picturefill.js</a> if not. ' .
'It is recommended to activate this option, unless your theme or another plugin adds such a script. ' .
'Picture tag support is currently <a href="https://caniuse.com/#feat=picture" target="_blank">~94%</a>'
);
?>
</div></li>
<li>
<?php
webpexpress_radioButton(
'alter-html-replacement',
'url',
'Replace image URLs',
$config['alter-html']['replacement'], // PS: the function takes care of the escaping
'<p>"Image URLs" replaces the image URLs to point to ' .
'the webp <i>rather than</i> the original. Handles src, srcset, common lazy-load attributes and even ' .
'inline styles</p>' .
'<p><b>Does not work with page caching</b> &ndash; <i>unless</i> you are using the <i>Cache Enabler</i> plugin</p>' .
'<p>Note that you will have to do something for the browsers that does not support webp. ' .
'And that something is in most cases to enable the <i>Only do the replacements in webp enabled browsers</i> option, which ' .
'will show up when you enable this option. ' .
'Or you can experiment with javascript solutions. There is for example the <a href="https://webpjs.appspot.com/">webpjs</a> javascript library. ' .
'But it does not support srcset, which is a showstopper. There are other libraries out there. ' .
'<p>PS: This replace functionality is handled by ' .
'<a target="_blank" href="https://github.com/rosell-dk/dom-util-for-webp">this external library</a>' .
', created for the purpose.</p>'
);
?>
</li>
<li><div id="alter_html_url_options_div" style="margin-left:18px; margin-bottom: 13px;">
<?php
webpexpress_checkbox(
'alter-html-only-for-webp-enabled-browsers',
$config['alter-html']['only-for-webp-enabled-browsers'] == true,
'Only do the replacements in webp enabled browsers',
'If you enable this option, the replacements will only be made, when the request is from ' .
'a browser that supports webp. Note that this will not play well with plugins that caches ' .
'the page. To overcome that, you can use the Cache Enabler plugin, '
);
?>
</div></li>
</ul>
</div>
<?php
if ($config['operation-mode'] != 'no-conversion') {
echo '<div><div style="margin-left: 11px">';
webpexpress_checkbox(
'alter-html-for-webps-that-has-yet-to-exist',
(!$config['alter-html']['only-for-webps-that-exists']),
'Reference webps that haven\'t been converted yet',
'If you enable this option, there will be references to webp files that doesnt exist yet. ' .
'And that will be ok! - Just make sure to enable the option to convert missing webp files ' .
'upon request in the "Redirection rules" section.'
);
echo '</div></div>';
}
?>
<div style="margin-top: 20px">
<label>How to replace: <?php echo helpIcon(
'<p>WebP Express can either trigger the HTML altering by hooking into ' .
'standard content filtering hooks, or it can do it on the whole page. ' .
'Usually, you should select the first option, as it is lighter to ' .
'do string manipulation on selected content than on the whole page. ' .
'Also, in order to protect from memory problems, the HTML altering is ' .
'bypassed when the HTML is larger than 600 kb. ' .
'However, not all themes and plugins uses the standard hooks, so in some ' .
'cases, you will have to resort to the second option. As of 0.17.5, ' .
'hooks has been added for some popular plugins, such as <i>ACF</i> and ' .
'<i>Woo Commerce Product Image</i>. ' .
'<p><i>Use content filtering hooks</i> currently supports the following hooks: ' .
'<i>the_content</i>, <i>the_excerpt</i>, <i>post_thumbnail_html</i>, <i>get_avatar</i>' .
', <i>woocommerce_product_get_image</i>, <i>acf_the_content</i>, <i>dynamic_sidebar_before</i>, <i>dynamic_sidebar_after</i>' .
'. Let me know if you need another content filtering hook to be added.' .
'</p>',
'no-margin-left set-margin-right');
?></label>
<?php
webpexpress_radioButtons('alter-html-hooks', $config['alter-html']['hooks'], [
'content-hooks' => 'Use content filtering hooks (the_content, the_excerpt, etc)',
'ob' => 'The complete page (using output buffering)</em>',
], [
]
);
?>
</div>
<div style="margin-top: 20px">
<label>CDN hostname(s) / hostname alias(es) <?php echo helpIcon(
'<p>If images can be reached on another hostname than that of your site' .
(Multisite::isMultisite() ? '' : ' ("' . Paths::getHostNameOfUrl(Paths::getHomeUrl()) . '")') .
', you should specify that hostname here. ' .
'This ensures that image URLs pointing to that hostname also gets replaced.</p>' .
'<p>PS: If you wonder why your images URLs to your CDN gets processed by WebP Express even though you ' .
'have not entered anything here, the answer is probably that you are using a plugin that changes ' .
'your URLs to point to the CDN and that WebP Express gets to do the replacement before your plugin. ' .
'Do not rely on this. A future update to your plugin could change that order.</p>'
);
?></label>
</div>
<?php
$aliases = $config['alter-html']['hostname-aliases'];
$aliasInputs = array_merge($aliases, ['']);
foreach ($aliasInputs as $i => $alias) {
?>
http(s)://<input name="alter-html-hostname-alias-<?php echo $i ?>" value="<?php echo esc_attr($alias); ?>"/><br>
<?php
}
?>
</div>
</th>
</tr>

View File

@@ -0,0 +1,16 @@
<fieldset class="block">
<h3>Alter HTML</h3>
<p>
Enabling this alters the HTML code such that webp images are served to browsers that supports webp.
It is <i>recommended to enable</i> this even when the redirection is also enabled, so the varied image responses are only used for
those images that cannot be replaced in HTML. The varied responses generally leads to poorer caching in proxies and CDN's. And if
users download those images, they will have jpg/png extension, even though they are webp.
</p>
<table class="form-table">
<tbody>
<?php
include_once 'alter-html-options.inc';
?>
</tbody>
</table>
</fieldset>

View File

@@ -0,0 +1,30 @@
<tr>
<th scope="row">
Bulk convert
<?php
echo helpIcon(
'Click the button to open dialog for bulk converting. PS: The bulk conversion is using the last saved settings.</p>'
);
?>
</th>
<td>
<div id="conversionlog" class="das-popup">
<div id="conversionlog_content">
</div>
<button onclick="closeDasPopup()" class="button button-primary" type="button" style="position:absolute; bottom:20px">
Close
</button>
</div>
<div>
<button onclick="openBulkConvertPopup()" class="button button-secondary" type="button">Bulk Convert</button>
<div id="bulkconvertpopup" style="display:none;">
<div id="bulkconvertcontent"></div>
<div id="bulkconvertlog"></div>
</div>
<button onclick="openDeleteConvertedFilesPopup()" class="button button-secondary" type="button">Delete converted files</button>
<div id="purgecachepopup" style="display:none;">
<div id="purgecachecontent"></div>
</div>
</div>
</td>
</tr>

View File

@@ -0,0 +1,18 @@
<fieldset class="block">
<h3>Conversion</h3>
<!--<p><i>The options here affects the conversion process</i></p>-->
<table class="form-table">
<tbody>
<?php
// include_once 'quality.inc';
include_once 'jpeg.inc';
include_once 'png.inc';
include_once 'metadata.inc';
include_once 'converters.inc';
include_once 'convert-on-upload.inc';
include_once 'logging.inc';
include_once 'bulk-convert.inc';
?>
</tbody>
</table>
</fieldset>

View File

@@ -0,0 +1,18 @@
<tr>
<th scope="row">
Convert on upload
<?php
echo helpIcon(
'<p>Convert images at the moment they have been uploaded through the media library ' .
'(of course, the "Image types to work on" setting is respected). ' .
'Be aware that this may slow the down the experience of uploading in the media library, ' .
'especially if your theme creates many thumbnails.</p>' .
'<p>Technically, we are hooking into the <i>handle_upload</i> filter to trigger conversion of the image ' .
'and the <i>image_make_intermediate_size</i> filter for the thumbnails.</p>'
);
?>
</th>
<td>
<input type="checkbox" id="convert_on_upload" name="convert-on-upload" value="true" <?php echo ($config['convert-on-upload'] ? 'checked="checked"' : '') ?> >
</td>
</tr>

View File

@@ -0,0 +1,67 @@
<div id="cwebp" style="display:none;">
<div class="cwebp converter-options">
<h3>cweb options</h3>
<div>
<label for="cwebp_use_nice">Use nice</label>
<input type="checkbox" id="cwebp_use_nice">
<br>Enabling this option saves system resources at the cost of slightly slower conversion
</div>
<div>
<label for="cwebp_try_common_system_paths">Try to execute cweb binary at common locations</label>
<input type="checkbox" id="cwebp_try_common_system_paths">
<br>If checked, we will look for binaries in common locations, such as <i>/usr/bin/cwebp</i>
</div>
<div>
<label for="cwebp_try_supplied_binary">Try precompiled cwebp</label>
<input type="checkbox" id="cwebp_try_supplied_binary">
<br>This plugin ships with precompiled cweb binaries for different platforms. If checked, and we have a precompiled binary for your OS, we will try to exectute it
</div>
<div>
<label for="cwebp_skip_these_precompiled_binaries">Skip these precompiled cwebp</label>
<input type="text" size="40" id="cwebp_skip_these_precompiled_binaries" style="width:100%">
<br>To skip precompiled binaries that are known not to work on current system (check the conversion log).
This will cut down on conversion time. Separate values with comma.
</div>
<div>
<label for="cwebp_method">Method (0-6)</label>
<input type="text" size="2" id="cwebp_method">
<br>This parameter controls the trade off between encoding speed and the compressed file size and quality.
Possible values range from 0 to 6. 0 is fastest. 6 results in best quality.
</div>
<div>
<label for="cwebp_set_size">Set size option (and ignore quality option)</label>
<input type="checkbox" id="cwebp_set_size">
<br>This option activates the size option below.
<?php
if ($canDetectQuality) {
echo 'As you have quality detection working on your server, it is probably best to use that, rather ';
echo 'than the "size" option. Using the size option takes more ressources (it takes about 2.5 times ';
echo 'longer for cwebp to do a a conversion with the size option than the quality option). Long ';
echo 'story short, you should probably <i>not</i> activate the size option.';
} else {
echo 'As you do not have quality detection working on your server, it is probably a good ';
echo 'idea to use the size option to avoid making conversions with a higher quality setting ';
echo 'than the source image. ';
echo 'Beware, though, that cwebp takes about 2.5 times longer to do a a conversion with the size option set.';
}
?>
</div>
<div>
<label for="cwebp_size_in_percentage">Size (in percentage of source)</label>
<input type="text" size="2" id="cwebp_size_in_percentage">
<br>Set the cwebp should aim for, in percentage of the original.
Usually cwebp can reduce to ~45% of original without loosing quality.
</div>
<div>
<label for="cwebp_command_line_options">Extra command line options</label><br>
<input type="text" size="40" id="cwebp_command_line_options" style="width:100%">
<br>This allows you to set any parameter available for cwebp in the same way as
you would do when executing <i>cwebp</i>. As a syntax example, you could ie. set it to
"-low_memory -af -f 50 -sharpness 0 -mt -crop 10 10 40 40" (do not include the quotes).
Read more about all the available parameters in
<a target="_blank" href="https://developers.google.com/speed/webp/docs/cwebp">the docs</a>
</div>
<br>
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,23 @@
<div id="ewww" style="display:none;">
<div class="ewww converter-options">
<h3>Ewww</h3>
<p>
ewww is a cloud service for converting images.
To use it, you need to purchase a key <a target="_blank" href="https://ewww.io/plans/">here</a>.
They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)
</p>
<h3>Options</h3>
<div>
<label for="ewww_api_key">API Key</label>
<input type="text" id="ewww_api_key" placeholder="Your API key here">
</div>
<br>
<h4>Fallback (optional)</h4>
<div>
<label for="ewww_api_key_2">API Key</label>
<input type="text" id="ewww_api_key_2" placeholder="In case the first one expires...">
</div>
<br>
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,35 @@
<div id="ffmpeg" style="display:none;">
<div class="ffmpeg converter-options">
<h3>ffmpeg options</h3>
<p>This conversion method works by executing ffmpeg binary.</p>
<div>
<label for="ffmpeg_use_nice">
Use nice
<?php echo helpIcon(
'Enabling this option saves system resources at the cost of slightly slower conversion.'
); ?>
</label>
<input type="checkbox" id="ffmpeg_use_nice">
</div>
<div>
<label for="ffmpeg_method">Method (0-6)</label>
<input type="text" size="2" id="ffmpeg_method">
<br>This parameter controls the trade off between encoding speed and the compressed file size and quality.
Possible values range from 0 to 6. 0 is fastest. 6 results in best quality.
</div>
<br>
<?php
/*
Removed (#243)
if (!$canDetectQuality) {
printAutoQualityOptionForConverter('imagemagick');
}*/
?>
<!--
<button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
-->
<!-- <a href="javascript: tb_remove();">close</a> -->
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div id="gd" style="display:none;">
<div class="gd converter-options">
<h3>Gd options</h3>
<div>
<label for="gd_skip_pngs">Skip PNGs</label>
<input type="checkbox" id="gd_skip_pngs">
<br>
<p>
You can choose to skip PNG's for Gd (which means the next working and active converter in the stack will handle it,
if there is any). There can be two reasons to do that:
<ul class="with-bullets">
<li>In our first implementation, Gd had problems with transparency. This should however be solved now. </li>
<li>Gd does not compress PNGs very effeciently</li>
</ul>
</p>
</div>
<br>
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div id="graphicsmagick" style="display:none;">
<div class="graphicsmagick converter-options">
<h3>Gmagick binary options</h3>
<p>This conversion method works by executing gmagick binary (the 'gm convert' command).</p>
<div>
<label for="graphicsmagick_use_nice">
Use nice
<?php echo helpIcon(
'Enabling this option saves system resources at the cost of slightly slower conversion.'
); ?>
</label>
<input type="checkbox" id="graphicsmagick_use_nice">
<br>
</div>
<br>
<?php
/*
Removed (#243)
if (!$canDetectQuality) {
printAutoQualityOptionForConverter('graphicsmagick');
}*/
?>
<!--
<button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
-->
<!-- <a href="javascript: tb_remove();">close</a> -->
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,30 @@
<div id="imagemagick" style="display:none;">
<div class="imagemagick converter-options">
<h3>ImageMagick options</h3>
<p>This conversion method works by executing imagemagick binary (the 'convert' command).</p>
<div>
<label for="imagemagick_use_nice">
Use nice
<?php echo helpIcon(
'Enabling this option saves system resources at the cost of slightly slower conversion.'
); ?>
</label>
<input type="checkbox" id="imagemagick_use_nice">
</div>
<br>
<?php
/*
Removed (#243)
if (!$canDetectQuality) {
printAutoQualityOptionForConverter('imagemagick');
}*/
?>
<!--
<button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
-->
<!-- <a href="javascript: tb_remove();">close</a> -->
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div id="imagick" style="display:none;">
<div class="imagick converter-options">
<h3>Imagick options</h3>
<?php
echo '<div class="info">imagick has no special options.</div>';
/*
Removed (#243)
if ($canDetectQuality) {
echo '<div class="info">imagick has no special options.</div>';
} else {
echo '<br>';
printAutoQualityOptionForConverter('imagick');
}*/
?>
<!--
<button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
-->
<!-- <a href="javascript: tb_remove();">close</a> -->
</div>
</div>

View File

@@ -0,0 +1,42 @@
<div id="vips" style="display:none;">
<div class="vips converter-options">
<h3>Vips options</h3>
<div>
<label for="vips_smart_subsample">
Smart subsample
<?php echo helpIcon(
'According to <a target="_blank" href="https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave">the docs</a>, ' .
'this option "enables high quality chroma subsampling".'
); ?>
</label>
<input type="checkbox" id="vips_smart_subsample">
</div>
<div>
<label for="vips_preset">
Preset
<?php echo helpIcon(
'Using a preset will set many of the other options to suit a particular type of source material. ' .
'It even overrides them. It does however not override the quality option.'
); ?>
</label>
<select id="vips_preset">
<?php
webpexpress_selectBoxOptions('default', [
'none' => 'Do not use a preset',
'default' => 'Default',
'photo' => 'Photo',
'picture' => 'Picture',
'drawing' => 'Drawing',
'icon' => 'Icon',
'text' => 'Text'
]);
?>
</select>
<br>
</div>
<br>
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,88 @@
<div id="wpc" style="display:none;">
<div class="wpc converter-options">
<h3>Remote WebP Express</h3>
Use a WebP Express installed on another Wordpress site to convert. Remote WepP Express is based
on <a href="https://github.com/rosell-dk/webp-convert-cloud-service" target="blank">WPC</a>,
and you can use it to connect to WPC as well.
<?php
if ((!extension_loaded('curl')) || (!function_exists('curl_init'))) {
echo '<p><b style="color:red">Your server does not have curl installed. Curl is required!</b></p>';
}
?>
<h3>Options</h3>
<!--
<div>
<label for="wpc_web_services">Web Services</label>
<div style="display:inline-block">
<div id="wpc_web_services_div"></div>
<button type="button" id="wpc_web_services_request" onclick="openWpcConnectPopup()" class="button button-secondary" >Add web service</button>
</div>
</div>
-->
<div id="wpc_api_version_div">
<label for="wpc_api_version">
Api version
<?php echo helpIcon('Select 1, if connecting to a remote webp-express. Api 0 was never used with this plugin, and should only be used to connect to webp-convert-cloud-service v.0.1 instances'); ?>
</label>
<select id="wpc_api_version" onchange="wpcApiVersionChanged()">
<option value="0">0</option>
<option value="1">1</option>
</select>
</div>
<div>
<label for="wpc_api_url">
URL
<?php echo helpIcon('The endpoint of the web service. Copy it from the remote setup.'); ?>
</label>
<input type="text" id="wpc_api_url" placeholder="Url to your Remote WebP Express" autocomplete="off">
</div>
<div id="wpc_secret_div">
<label for="wpc_secret">
Secret
<?php echo helpIcon('Must match the one set up in webp-convert-cloud-service v0.1'); ?>
</label>
<input type="text" id="wpc_secret" placeholder="" autocomplete="off">
</div>
<div id="wpc_api_key_div">
<label id="wpc_api_key_label_1" for="wpc_api_key">
Secret
<?php echo helpIcon('The secret set up on the wpc server. Copy that.'); ?>
</label>
<label id="wpc_api_key_label_2" for="wpc_api_key">
Api key
<?php echo helpIcon('The API key is set up on the remote. Copy that.'); ?>
</label>
<input id="wpc_new_api_key" type="password" autocomplete="off">
<a id="wpc_change_api_key" href="javascript:wpcChangeApiKey()">
Click to change
</a>
</div>
<div id="wpc_crypt_api_key_in_transfer_div">
<label for="wpc_crypt_api_key_in_transfer">
Crypt api key in transfer?
<?php echo helpIcon('If checked, the api key will be crypted in requests. Crypting the api-key protects it from being stolen during transfer.'); ?>
</label>
<input id="wpc_crypt_api_key_in_transfer" type="checkbox">
</div>
<?php
/*
Removed (#243)
if (!$canDetectQuality) {
printAutoQualityOptionForConverter('wpc');
}*/
?>
<p>
<b>Psst. The IP of your website is: <?php echo $_SERVER['SERVER_ADDR']; ?>.</b>
</p>
<?php webp_express_printUpdateButtons() ?>
</div>
</div>

View File

@@ -0,0 +1,38 @@
<tr>
<th scope="row">
Conversion method
<?php
echo helpIcon(
'Drag to reorder. The conversion method on top will first be tried. ' .
'Should it fail, the next will be used, etc. To learn more about the conversion methods, ' .
'<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/converting/converters.md">Go here</a>'
); ?>
</th>
<td>
<input type='text' name='converters' value='' style='visibility:hidden; height:0' />
<ul id="converters" style="margin-top: -13px"></ul>
<div id="tc_popup" style="display:none;">
<div id="tc_content"></div>
</div>
<?php
function webp_express_printUpdateButtons() {
?>
<button onclick="updateConverterOptionsAndSave()" class="button button-primary" type="button">Update and save settings</button>
<button onclick="updateConverterOptions()" class="button button-secondary" type="button">Update, but do not save yet</button>
<?php
//echo '<a href="javascript: tb_remove();">close</a>';
}
include 'converter-options/cwebp.php';
include 'converter-options/vips.php';
include 'converter-options/gd.php';
include 'converter-options/imagick.php';
include 'converter-options/ewww.php';
include 'converter-options/wpc.php';
include 'converter-options/imagemagick.php';
include 'converter-options/graphicsmagick.php';
include 'converter-options/ffmpeg.php';
?>
</td>
</tr>

View File

@@ -0,0 +1,164 @@
<?php
/*
jpg
Encoding: Lossy / Auto
Quality for lossy: Auto / specific
Near-Lossless quality (0-100) (default: 60)
png
Encoding: Always lossless / Auto
Quality for lossy (0-100)
Near-Lossless quality (0-100) (default: 70)
Alhpa quality (0-100)
*/
// Quality - jpeg
// --------------------
//$canDetectQuality = false;
?>
<tr id="jpeg_row" class="toggler effect-opacity">
<th scope="row" colspan=1>
Jpeg options
<?php echo helpIcon(
'The "jpeg" settings applies when the image being converted is a jpeg.'
);?>
</th>
<td id="jpeg_td">
<div>
<label>
WebP encoding:
</label>
<select id="jpeg_encoding_select" name="jpeg-encoding">
<?php
webpexpress_selectBoxOptions($config['jpeg-encoding'], [
'lossy' => 'Lossy',
'auto' => 'Auto',
]);
?>
</select>
<?php echo helpIcon(
'<p>The WebP format supports two types of encoding: lossy and lossless.</p>' .
'<p>If you select "Auto", WebP Express will try converting to both encodings ' .
'and select one that resulted in the smallest file.</p>' .
'<p>Note that Gd and Ewww does not support the "Auto" feature. ' .
'Gd can only produce lossy, and will simply do that. ' .
'Ewww can not be configured to use a certain encoding, but automatically chooses lossless encoding for PNGs and lossy for JPEGs.' .
'</p>' .
//'<p>Also note that Remote WebP Express (if the WebP Express you are ' .
//'connecting to are using one of these, and you are using version 0.14+)' .
'<p>You can read more about the option <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding">here</a></p>'
);?>
</div>
<div>
<label>
Quality for lossy:
</label>
<select id="quality_auto_select" name="quality-auto">
<option value="auto_on" <?php echo ($config['quality-auto'] ? 'selected' : ''); ?>>Same as the jpeg</option>
<option value="auto_off" <?php echo (!$config['quality-auto'] ? 'selected' : ''); ?>>Fixed quality</option>
</select>
<?php echo helpIcon(
'<p>If you select the "Same as the jpeg" option, WebP Express will try to determine the quality of ' .
'the jpeg and use that for the webp - unless the quality is higher than the limit entered, in which ' .
'case the limit is used.</p>' .
'<p>Ouality detection requires imagick, imagemagick or gmagick (not neccessarily compiled with webp). ' .
'In case quality detection is not available, the fallback quality is used.</p>' .
(
$canDetectQuality ?
'<p><i>Quality detection is working, btw :)</i></p>' :
'<p><b>Quality detection is not currently available on your system.</b> ' .
((extension_loaded('imagick') && class_exists('\\Imagick')) ?
'You have imagick, but you need a newer version (You need PECL >= 2.2.2). ' : ''
) .
'However, you can still have it by using the <i>Remote WebP Express</i> conversion method (the request for ' .
'using same quality as the jpeg, as well as the <i>limit</i> and <i>fallback</i> settings will be forwarded to the remote)' .
'</p>'
)
);
/*
if ($canDetectQuality) {
echo helpIcon('All converted images will be encoded with this quality');
} else {
echo helpIcon('All converted images will be encoded with this quality. ' .
'For Remote WebP Express and Imagick, you however have the option to use override this, and use ' .
'"auto". With some setup, you can get quality detection working and you will then be able to set ' .
'quality to "auto" generally. For that you either need to get the imagick extension running ' .
'(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
);
}
*/
?>
<div id="max_quality_div" style="margin-left:10px;display:inline-block" class="toggler effect-visibility">
<label>
Limit:
</label>
<input type="text" size=3 id="max_quality" name="max-quality" value="<?php echo esc_attr($config['max-quality']); ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php echo helpIcon(
'Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
'This option allows you to limit the quality to whatever is lowest: ' .
'the quality of the jpeg or the limit entered here. Recommended value: Somewhere between 50-85'
);?>
<label style="display:inline-block; margin-left:10px">
Fallback:
</label>
<input type="text" size=3 name="quality-fallback" value="<?php echo esc_attr($config['quality-specific']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon(
'Fallback quality in case quality detection is not available or should fail for some reason (which has yet to be seen). ' .
(
$canDetectQuality ?
'You have quality detection working, btw, so this setting will probably never be used' :
'<i>You do not have quality detection working, btw</i> - which means that all conversions will have the fixed quality entered here'
)
);
?>
</div>
<div id="quality_specific_div" style="display:inline-block" class="toggler effect-visibility">
<input type="text" size=3 name="quality-specific" value="<?php echo esc_attr($config['quality-specific']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon('Enter number (0 - 100)');
?>
</div>
</div>
<div id="jpeg_quality_lossless_div" class="toggler effect-visibility">
<label>
Quality for lossless:
</label>
<select id="jpeg_enable_near_lossless" name="jpeg-enable-near-lossless">
<?php
webpexpress_selectBoxOptions($config['jpeg-enable-near-lossless'] ? 'on' : 'off', [
'on' => 'Apply preprocessing',
'off' => '100% lossless',
]);
?>
</select>
<?php
echo helpIcon(
'<p>What? Lossless is lossless, right?. Well, that depends on how you look at it. ' .
'The webp conversion library has this nifty option called "near lossless preprocessing". The preproccesing manipulates ' .
'the image before encoding in order to help compressibility.</p>' .
'<p>Note that the near-lossless option only is supported by the <i>Cwebp</i> and <i>Vips</i> conversion methods.</p>' .
'<p>Read more about the feature <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#near-lossless">here</a></p>'
);
?>
<div id="jpeg_near_lossless_div" style="display:inline-block; margin-left:10px" class="toggler effect-visibility">
<label>
"Near lossless" quality:
</label>
<input type="text" size=3 name="jpeg-near-lossless" value="<?php echo esc_attr($config['jpeg-near-lossless']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon(
'The level of near-lossless image preprocessing (when trying lossless). ' .
'You can think of it as "quality" for lossless. The range is 0 (maximum preprocessing) to 100 (no preprocessing). ' .
'<a href="https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek">Read this</a> to get an informed opinion about appropriate setting.'
);
?>
</div>
</div>
</td>
</tr>

View File

@@ -0,0 +1,24 @@
<tr>
<th scope="row">
Enable logging
<?php
echo helpIcon(
'<p>Store conversion results in log files. ' .
'This can be useful in order to find out what went wrong in case a conversion failed or ' .
'the result is poor quality. ' .
'The log files reside in <i>wp-content/webp-express/log/conversions/</i>. ' .
'In not too far a future, the conversion logs will be accessible in the File Manager too.' .
'</p>'
);
?>
</th>
<td>
<input type="checkbox" id="enable_logging" name="enable-logging" value="true" <?php echo ($config['enable-logging'] ? 'checked="checked"' : '') ?> >
&nbsp;
<button onclick="openDeleteLogFilesPopup()" class="button button-secondary" type="button">Delete log files</button>
<div id="purgelogpopup" style="display:none;">
<div id="purgelogcontent"></div>
</div>
</td>
</tr>

View File

@@ -0,0 +1,18 @@
<?php
// Metadata
// --------------------
$metadata = $config['metadata'];
echo '<tr><th scope="row">Metadata';
echo helpIcon(
'Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, ' .
'as it is not possible to copy the metadata with the Gd extension.'
);
echo '</th><td>';
echo '<select id="metadata" name="metadata">';
echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
echo '</select>';
echo '</td></tr>';
// echo '<tr><td colspan=2><p>Converted jpeg images will get same quality as original, but not more than this setting. Something between 70-85 is recommended for most websites.</p></td></tr>';

View File

@@ -0,0 +1,99 @@
<tr id="png_row" class="toggler effect-opacity">
<th scope="row" colspan=1>
PNG options
<?php echo helpIcon(
'The "png" settings applies when the image being converted is a png.'
);?>
</th>
<td id="png_td">
<div>
<label>
WebP encoding:
</label>
<select id="png_encoding_select" name="png-encoding">
<?php
webpexpress_selectBoxOptions($config['png-encoding'], [
'lossless' => 'Lossless',
'auto' => 'Auto',
]);
?>
</select>
<?php echo helpIcon(
'<p>The WebP format supports two types of encoding: lossy and lossless. ' .
'With WebP you can have both transparency AND lossy encoding, so "lossy" encoding is not out of the question, just ' .
'because the converted file is a PNG.</p>' .
'<p>There is no "lossy" option in the combobox, because converting all PNGs to lossy would probably be a bad idea. ' .
'However, in many cases you get better compression with lossy. So this is what the "auto" option is for. ' .
'With "Auto", WebP Express will try converting to both encodings ' .
'and select one that resulted in the smallest file.</p>' .
'<p>Note that Gd and Ewww neither supports "Lossless" or "Auto". ' .
'Gd can only produce lossy, and will simply do that. ' .
'Ewww can not be configured to use a certain encoding, but automatically chooses lossless encoding for PNGs and lossy for JPEGs.' .
'</p>' .
'<p>You can read more about the option <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding">here</a></p>'
);?>
</div>
<div id="png_quality_lossy_div" class="toggler effect-visibility">
<label>
Quality for lossy:
</label>
<input type="text" size=3 name="png-quality" value="<?php echo esc_attr($config['png-quality']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon(
'<p>You probably want to set this value a bit higher than the quality for JPEGs. PNGs are often used for icons and graphics, ' .
'which perhaps demands a bit more quality than photos, which jpegs often are used for.</p>' .
'<p>Note that there is no "Same as PNG" option available here as PNGs are lossless</p>'
);
?>
<label style="margin-left:10px">
Alpha quality:
</label>
<input type="text" size=3 name="alpha-quality" value="<?php echo esc_attr($config['alpha-quality']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon(
'The alpha quality is the quality of the alpha channel (the tranparency layer). ' .
'The option is only relevant for images with alpha channel, and only relevant in lossy encoding. ' .
'<p>Note that Gd and Ewww does not support the "Alpha quality" feature. ' .
'They simply ignore the option and converts the alpha channel losslessly. ' .
'</p>'
);
?>
</div>
<div>
<label>
Quality for lossless:
</label>
<select id="png_enable_near_lossless" name="png-enable-near-lossless">
<?php
webpexpress_selectBoxOptions($config['png-enable-near-lossless'] ? 'on' : 'off', [
'on' => 'Apply preprocessing',
'off' => '100% lossless',
]);
?>
</select>
<?php
echo helpIcon(
'<p>What? Lossless is lossless, right?. Well, that depends on how you look at it. ' .
'The webp conversion library has this nifty option called "near lossless preprocessing". The preproccesing manipulates ' .
'the image before encoding in order to help compressibility.</p>' .
'<p>Note that the near-lossless option only is supported by the <i>Cwebp</i> and <i>Vips</i> conversion methods.</p>' .
'<p>Read more about the feature <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#near-lossless">here</a></p>'
);
?>
<div id="png_near_lossless_div" style="display:inline-block; margin-left:10px" class="toggler effect-visibility">
<label>
"Near lossless" quality:
</label>
<input type="text" size=3 name="png-near-lossless" value="<?php echo esc_attr($config['png-near-lossless']) ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
<?php
echo helpIcon(
'The level of near-lossless image preprocessing (when trying lossless). ' .
'You can think of it as "quality" for lossless. The range is 0 (maximum preprocessing) to 100 (no preprocessing). ' .
'<a href="https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek">Read this</a> to get an informed opinion about appropriate setting.'
);
?>
</div>
</div>
</td>
</tr>

View File

@@ -0,0 +1,95 @@
<?php
/*
jpg
Encoding: Always lossy / Auto
Quality for lossy: Auto / specific
Near-Lossless quality (0-100) (default: 60)
png
Encoding: Always lossless / Auto
Quality for lossy (0-100)
Near-Lossless quality (0-100) (default: 70)
Alhpa quality (0-100)
*/
// Quality - jpeg
// --------------------
//$canDetectQuality = false;
echo '<tr><th scope="row">Quality (jpeg -> webp)';
if ($canDetectQuality) {
echo helpIcon(
'Quality of webp, when the image being converted is a jpeg. If "Same as the jpeg" is selected, the converted image ' .
'will get same quality as source. Auto is recommended!'
);
} else {
echo helpIcon(
'<p>Quality of webp, when converting jpegs (0-100)</p>' .
'<p>Note: If your system had the capability to detect the quality ' .
'of jpeg images, you would have had the option to choose "Same as jpeg". To get that option, you ' .
'either need to get the imagick extension running (PECL >= 2.2.2) or imagick or gmagick installed plus ' .
'exec() rights.</p>' .
'<p>Note: If you use the <i>Remote WebP Express</i> converter, you can configure it to ask the remote ' .
'to do the automatic quality detection for jpegs. This will override the value entered here.</p>'
);
}
echo '</th><td>';
if ($canDetectQuality) {
$qualityAuto = $config['quality-auto'];;
echo '<select id="quality_auto_select" name="quality-auto" style="margin-right: 15px;">';
echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Same as the jpeg</option>';
echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Fixed quality</option>';
echo '</select>';
// Max quality
// --------------------
$maxQuality = $config['max-quality'];
// echo '<tr id="max_quality_div"><th scope="row">Max quality (0-100)';
echo '<div id="max_quality_div">Max ';
//echo '</th><td>';
echo '<input type="text" size=3 id="max_quality" name="max-quality" value="' . esc_attr($maxQuality) . '">';
echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
'This option allows you to limit the quality to whatever is lowest: ' .
'the quality of the jpeg or the limit entered here. Recommended value: Somewhere between 50-85');
//echo '</td></tr>';
echo '</div>';
}
// Quality - specific
// --------------------
$qualitySpecific = $config['quality-specific'];
echo '<div id="quality_specific_div">';
/*
if ($canDetectQuality) {
echo helpIcon('All converted images will be encoded with this quality');
} else {
echo helpIcon('All converted images will be encoded with this quality. ' .
'For Remote WebP Express and Imagick, you however have the option to use override this, and use ' .
'"auto". With some setup, you can get quality detection working and you will then be able to set ' .
'quality to "auto" generally. For that you either need to get the imagick extension running ' .
'(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
);
}
*/
//echo '</th><td>';
echo '<input type="text" size=3 id="quality_specific" name="quality-specific" value="' . esc_attr($qualitySpecific) . '">';
echo helpIcon('Enter number (0 - 100)');
echo '</div>';
echo '</td></tr>';
// Quality - PNG
// --------------------
echo '<tr id="quality_png"><th scope="row">Quality (png -> webp)';
echo helpIcon(
'Quality of webp, when the image that is converted is a png.'
);
echo '</th><td>';
echo '<input type="text" size=3 id="quality_png" name="quality-png" value="' . esc_attr($config['quality-png']) . '">';
echo helpIcon('Enter number (0 - 100). Recommended value: Somewhere between 60-90');
echo '</td></tr>';

View File

@@ -0,0 +1,112 @@
<?php
$cacheControl = $config['cache-control'];
$cacheControlCustom = $config['cache-control-custom'];
$cacheControlMaxAge = $config['cache-control-max-age'];
$cacheControlPublic = $config['cache-control-public'];
?>
<tr id="cache_control_div">
<th scope="row">Cache-Control header <?php
switch ($config['operation-mode']) {
case 'no-conversion':
echo helpIcon('<p>Optionally set cache-control header for the internally redirected images ' .
'(recommended!)</p>');
break;
case 'cdn-friendly':
echo helpIcon('<p>Optionally set cache-control header for webp images');
break;
default:
echo helpIcon('<p>Controls the cache-control header on successful conversion and direct redirection to converted ' .
'image in .htaccess. In case of convert failure, headers will be sent to prevent caching.</p>' .
'<p>PS: In order to set <i>stale-while-revalidate</i> and <i>stale-if-error directives<i>, you must ' .
'currently choose "Custom". <a target="_blank" href="https://www.fastly.com/blog/stale-while-revalidate-stale-if-error-available-today">It is a good idea to set these</a>.' .
'</p>');
break;
}
?>
</th>
<td>
<select id="cache_control_select" name="cache-control">
<option value="no-header" <?php if ($cacheControl == 'no-header') echo ' selected' ?>>Do not set</option>
<option value="set" <?php if ($cacheControl == 'set') echo ' selected' ?>>Set</option>
<option value="custom" <?php if ($cacheControl == 'custom') echo ' selected' ?>>Custom</option>
</select>
<div id="cache_control_custom_div" style="display:inline-block;">
<input type="text" id="cache_control_custom" name="cache-control-custom" value="<?php echo esc_attr($cacheControlCustom) ?>">
<?php echo helpIcon(
'You can read about possible options ' .
'<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control">here</a>',
'no-margin-left'
);?>
</div>
<div id="cache_control_set_div" style="display:inline-block;">
<select id="cache_control_public" name="cache-control-public">
<?php
webpexpress_selectBoxOptions(($cacheControlPublic ? 'public' : 'private'), [
'public' => 'Public',
'private' => 'Private',
]);
?>
</select>
<?php echo helpIcon(
'<p>Set either the "public" or "private" directive. Setting this to public means that you are allow caching in shared caches. ' .
'Only do this, if you are sure your CDN or reverse proxy can handle that the ' .
'response varies depending on the Accept header.</p>' .
'<p>Note: I am not completely sure that all forward proxies handles varied responses. ' .
'This is discussed <a target="_blank" href="https://github.com/rosell-dk/webp-express/issues/144">here</a>.</p>'
,
'no-margin-left set-margin-right'
);
?>
</select>
<select id="cache_control_max_age" name="cache-control-max-age">
<?php
webpexpress_selectBoxOptions($cacheControlMaxAge, [
'one-second' => 'One second',
'one-minute' => 'One minute',
'one-hour' => 'One hour',
'one-day' => 'One day',
'one-week' => 'One week',
'one-month' => 'One month',
'one-year' => 'One year',
]);
?>
</select>
<?php echo helpIcon(
'This sets the max-age value. If want to set s-maxage, or generally need more control, ' .
'choose "custom" in the first combobox (You can read about possible options ' .
'<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control">here</a>)',
'no-margin-left'
);?>
</div>
<br>
<!--
<table>
<tbody>
<tr>
<td style="font-weight: bold; margin:0; padding:0">max-age</td>
<td>
</td>
</tr>
<tr>
<th scope="row">
Allow caching in shared caches (ie CDNs or reverse proxies)
<?php echo helpIcon(''); ?>
</th>
<td>
<input
name="cache-control-public"
<?php echo ($config['cache-control-public'] ? 'checked="checked"' : '') ?>
value="true"
type="checkbox"
>
</td>
</tr>
</tbody>
</table>
-->
</td>
</tr>

View File

@@ -0,0 +1,61 @@
<tr id="destination_extension_row"><?php
if ($config['operation-mode'] == 'no-conversion') {
echo '<th scope="row">Filename of the webp files';
echo helpIcon(
'<p>Select under which naming convention the webp files are stored. ' .
'It is assumed that webp files are located in the same folder as the originals.</p>' .
'<style>' .
'#plugin-conventions th {font-style:italic;}' .
'#plugin-conventions td, #plugin-conventions th {padding:0;}' .
'#plugin-conventions td:last-child, #plugin-conventions th:last-child {padding-left:10px;}' .
'</style>' .
'<table id="plugin-conventions"><tbody>' .
'<tr><th>Plugin</td><th>Convention</th></tr>' .
//'<tr><td><a target="blank" href="https://wordpress.org/plugins/cache-enabler/">Cache enabler</a></td><td>Replaces extension</td></tr>' .
'<tr><td><a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a></td><td>Replaces extension</td></tr>' .
'<tr><td><a target="_blank" href="https://wordpress.org/plugins/ewww-image-optimizer/">Ewww</a></td><td>Appends extension</td></tr>' .
'<tr><td><a target="_blank" href="https://optimus.io/en/">Optimus HQ</a></td><td>Replaces extension</td></tr>' .
// todo:
'</tbody></table>'
);
} else {
echo '<th scope="row">File extension';
echo helpIcon(
'<p>Controls the filename of the converted file.</p>' .
'<p>The "Append" option result in file names such as "image.png.webp". ' .
'The "Set" option results in file names such as "image.webp". ' .
'Note that if you choose "Set", it will be a problem if you ie both have a logo.jpg and a logo.png in the same folder. ' .
'If you are using WebP Express together with <a target="blank" href="https://wordpress.org/plugins/cache-enabler/">Cache enabler</a> ' .
'or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, set this option to Set"</p>' .
(($config['operation-mode'] == 'cdn-friendly') ? '<p>In this mode, the webp files will be stored in the same folder as the originals, except for images that are not inside the uploads folder (these are stored in wp-content/webp-express/webp-images/doc-root).</p>' : '') .
'<p>Changing this option will cause existing webp images to be renamed (only those in the upload folder, and only those that has a ' .
'corresponding source image)</p>' .
'<p>Note that this option only applies to the webp images stored in the uploads folder (mingled).</p>'
);
}
?>
</th>
<td>
<?php
if ($config['operation-mode'] == 'no-conversion') {
webpexpress_radioButtons('destination-extension', $config['destination-extension'], [
'append' => 'Appended ".webp" (ie "image.jpg.webp")',
'set' => 'Replaced extension (ie "image.webp")',
], [
'append' => 'Original extension is kept and ".webp" is appended. ',
'set' => 'Original extension is replaced with ".webp".'
], 'margin-left: 0px; margin-top: 5px');
} else {
echo '<select name="destination-extension" id="destination_extension">';
webpexpress_selectBoxOptions($config['destination-extension'], [
'append' => 'Append ".webp"',
'set' => 'Set to ".webp"',
]);
echo '</select>';
}
?>
</td>
</tr>

View File

@@ -0,0 +1,26 @@
<tr>
<th scope="row">Destination folder<?php
echo helpIcon('<p>' .
'<span style="text-decoration:underline">Mingled:</span><br>' .
'When "Mingled" is selected, the webp images will be put in the same folder as the original <i>but only for images in the uploads folder</i>. ' .
'Other images, such as theme images are stored separately.<br>' .
'If you are using WebP Express together with <a target="blank" href="https://da.wordpress.org/plugins/cache-enabler/">Cache enabler</a> or ' .
'<a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, choose this option<br><br>' .
'<span style="text-decoration:underline">In separate folder:</span><br>' .
'Images are stored in a separate folder ' .
'(wp-content/webp-express/webp-images/doc-root).' .
'<p><i>Note: Changing this option will cause existing webp images to be moved</i></p>');
?></th>
<td>
<select name="destination-folder" id="destination_folder">
<?php
webpexpress_selectBoxOptions($config['destination-folder'], [
'separate' => 'In separate folder',
'mingled' => 'Mingled',
]);
?>
</select>
</td>
</tr>

View File

@@ -0,0 +1,42 @@
<tr id="destination_structure_row">
<th scope="row">Destination structure<?php
echo helpIcon(
'<p>This setting determines how the converted files are structured within the folder that WebP Express ' .
'uses for storing webp images (from here on called "the cache root")</p>' .
'<p><span style="text-decoration:underline">"document root"</span><br>' .
'When "document root" is selected, the webp images will be stored in:<br>' .
'<span style="white-space:pre-wrap;font-family:monospace">[cache root]/doc-root/[relative path of source image, from document root].</span><br>' .
'</p>' .
'<p><span style="text-decoration:underline">"image roots"</span><br>' .
'A Wordpress site has images stored in different locations. Uploaded files are for example stored in the <i>uploads</i> folder, which is ' .
'usually - but not always - located in the "wp-content" folder. I call the uploads folder an "image root". Other roots are: ' .
'"themes", "plugins", "wp-content" and "index". When the "image roots" setting is selected, the webp files ' .
'are stored in a structure that mirrors the relative path of the source image within its image root. ' .
'For "uploads", that location is:<br>' .
'<span style="white-space:pre-wrap;font-family:monospace">[cache root]/uploads/[relative path of source image, from uploads root].</span><br>' .
'More generally we have:<br>' .
'<span style="white-space:pre-wrap;font-family:monospace"">[cache root]/[image root]/[relative path of source image, from its image root].</span><br>' .
'</p>' .
'<p><span style="text-decoration:underline">Which option is best?</span><br>' .
'Well, in most cases it does not matter. However, there are hosts out there that have set the document root up incorrectly, ' .
'so I would generally recommend "Image roots". ' .
'On Nginx, I however recommend "Document root", as it requires fewer rewrite rules.</p>'
);
?></th>
<td>
<!--
The "image roots" option was added to make WebP Express work on systems where the document root is set up incorrectly or
is outside opendir restriction or an image root is located outside document root.
WebP Express automatically detects this and will not allow you to select "document root" in those cases.
-->
<select name="destination-structure" id="destination_structure">
<?php
webpexpress_selectBoxOptions($config['destination-structure'], [
'doc-root' => 'Document root',
'image-roots' => 'Image roots',
]);
?>
</select>
</td>
</tr>

View File

@@ -0,0 +1,36 @@
<fieldset class="block">
<button onclick="WebPExpress.SelfTest.openPopup('allInfo')" class="button button-secondary" type="button" style="position: absolute; right: 15px; top:15px">
System info
</button>
<h3>General</h3>
<!--<div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('And so does some other options. If "Redirect directly to converted image" is set, the "Destination folder" and "File Extension" and "Caching" options will be used'); ?></i></div>-->
<table class="form-table">
<tbody>
<?php
if (($config['operation-mode'] != 'no-conversion')) {
include_once 'scope.inc';
}
include_once 'image-types.inc';
if (($config['operation-mode'] != 'no-conversion')) {
include_once 'destination-folder.inc';
}
include_once 'destination-extension.inc';
if (($config['operation-mode'] != 'no-conversion')) {
include_once 'destination-structure.inc';
}
// TODO:
// Perhaps also show cache control in cdn-friendly mode?
if (($config['operation-mode'] == 'tweaked') || ($config['operation-mode'] == 'varied-image-responses') || ($config['operation-mode'] == 'cdn-friendly')) {
include_once 'cache-control.inc';
}
include_once 'prevent-using-webps-larger-than-original.inc';
?>
</tbody>
</table>
</fieldset>

View File

@@ -0,0 +1,40 @@
<?php
// Image types
// ------------
echo '<tr><th scope="row">';
switch ($config['operation-mode']) {
case 'varied-image-responses':
echo 'Image types to work on';
break;
case 'cdn-friendly':
echo 'Image types to convert';
break;
case 'no-conversion':
echo 'Image types to work on';
break;
case 'tweaked':
echo 'Image types to send to the converter';
break;
}
if ($config['operation-mode'] == 'no-conversion') {
echo helpIcon('<p>Select which types of images you would like to redirect and/or have altered in the HTML</p>');
} else {
echo helpIcon('<p>Beware that some has reported problems with Gd and PNG, but it has not been pinned down when it happens. It is probably on older versions of Gd. Please report any problems with Gd!</p>');
}
echo '</th><td>';
// bitmask
// 1: JPEGs
// 2: PNG's
// Converting only jpegs is thus "1"
// Converting both jpegs and pngs is (1+2) = 3
$imageTypes = $config['image-types'];
echo '<select name="image-types" id="image_types">';
echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
echo '<option value="2"' . ($imageTypes == 2 ? ' selected' : '') . '>Only pngs</option>';
echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
echo '</select>';
echo '</td></tr>';

View File

@@ -0,0 +1,23 @@
<tr>
<th scope="row">Prevent using webps larger than original<?php
echo helpIcon(
'<p>For some images, the converted webp might turns out bigger than the original. ' .
'These are always kept on disk. However, with this option, you can choose whether they should be used or not. ' .
'If you are motivated by limiting bandwidth usage and having a fast website, keep this option enabled. ' .
'If you are more concerned about SEO and a penalty for serving jpegs and pngs rather than webps, disable it.' .
'</p>' .
'<p>The option is used both when generating .htaccess rules and in Alter HTML.</p>' .
'<p>Note: If you are using Alter HTML with picture tags and have images with a srcset ' .
'attribute, WebP Express generates a source element with type set to "image/webp". This source ' .
'thus points to a bundle of webp images. From 0.25.5 and forward, when this option is enabled, ' .
'WebP Express will only add the source element when ALL those webp images are smaller than their originals. ' .
'In such a situation (using Alter HTML with picture tags), it might be a better strategy to disable this option. ' .
'Especially if your images generally have the srcset attribute set. Chances are that only a few of the webps in ' .
'the source set are bigger than the corresponding originals, and chances are that they are only slightly bigger.' .
'</p>'
);
?></th>
<td>
<input type="checkbox" id="prevent_using_webps_larger_than_original" name="prevent-using-webps-larger-than-original" value="true" <?php echo ($config['prevent-using-webps-larger-than-original'] ? 'checked="checked"' : '') ?> >
</td>
</tr>

View File

@@ -0,0 +1,59 @@
<tr>
<th scope="row">Scope<?php
echo helpIcon(
'<p>This setting determines which folders WebP Express is operational in. ' .
'If for example "Uploads only" is selected, WebP Express will only convert the upload images and only put ' .
'an <i>.htaccess</i> file in the <i>uploads</i> folder (if needed). Also, <i>Alter HTML</i> will limit itself to that area.' .
'</p>' .
'<p>The "All content" setting will work on <i>uploads</i>, <i>themes</i>, <i>plugins</i> - anything in the "wp-content" ' .
'(or whatever it has been renamed to). It will work on uploads, even if the uploads folder has been ' .
'configured to reside outside of wp-content - and on <i>plugins<i>, even if plugins has been moved.</p>'
);
?></th>
<td>
<select name="scope" id="scope">
<?php
/*webpexpress_selectBoxOptions($config['destination-structure'], [
'doc-root' => 'Document root',
'image-roots' => 'Image roots',
]);
*/
$imageTypes = $config['scope'];
sort($imageTypes, SORT_STRING);
$imageTypesString = implode(',', $imageTypes);
$options = [
['uploads', 'Uploads only'],
['themes', 'Themes only'],
['themes,uploads', 'Uploads and themes'],
['plugins,themes,uploads,wp-content', 'All content'],
['index,plugins,themes,uploads,wp-content', 'Everything (including wp-admin)'],
];
$otherValidOptions = ['index', 'wp-content'];
if (in_array($imageTypesString, $otherValidOptions)) {
$options[] = [$imageTypesString, $imageTypesString];
}
$hasSelected = false;
foreach ($options as list($optionId, $optionName)) {
$selected = ($imageTypesString == $optionId);
if ($selected) {
$hasSelected = true;
}
echo '<option value="' . $optionId . '"' . ($selected ? ' selected' : '') . '>' . $optionName . '</option>';
}
if (!$hasSelected) {
// Non-default option is "selected".
// This can be done in migrate11.php or user could have edited config.php manually.
// PS: We can rest assure that scope is sanitized - any invalid rootId is filtered out in Config::fix.
echo '<option value="' . $imageTypesString . '" selected>' .
'Custom: ' . implode(', ', explode(',', $imageTypesString)) .
'</option>';
}
?>
</select>
</td>
</tr>

View File

@@ -0,0 +1,70 @@
<?php
$operationMode = $config['operation-mode'];
?>
<fieldset class="block">
<h3>Operation mode: <?php echo helpIcon(
'<p>Think of the operation modes as presets that matches normal use-cases. ' .
'Usually you want to stick with <i>Varied image responses</i> or perhaps <i>CDN friendly</i>. ' .
'The Tweaked mode has no presets. That is: Here, you can set all options.</p>' .
'<p>Changing from ie. "Varied image responses" mode to "Tweaked" mode enables you to see the underlying options for that mode (and tweak them). ' .
'Changing back will override the tweaks (you will lose them).</p>' .
'<p>You will never loose your converter configurations by changing mode</p>');
?>
<input type="hidden" name="operation-mode" id="operation_mode" value="<?php echo esc_attr($operationMode); ?>">
<select name="change-operation-mode" id="change_operation_mode">
<option value="varied-image-responses"<?php if ($operationMode == 'varied-image-responses') echo ' selected'?>>Varied image responses</option>
<option value="cdn-friendly"<?php if ($operationMode == 'cdn-friendly') echo ' selected'?>>CDN friendly</option>
<option value="no-conversion"<?php if ($operationMode == 'no-conversion') echo ' selected'?>>No conversion</option>
<option value="tweaked"<?php if ($operationMode == 'tweaked') echo ' selected'?>>Tweaked</option>
</select></h3>
<?php if ($config['operation-mode'] == 'varied-image-responses') : ?>
<p><div class="p">
<i>In the "Varied image responses" mode, WebP Express creates redirection rules for images, such that a request for a jpeg will
result in a webp &ndash; but only if the request comes from a webp-enabled browser. Note that not all CDN's handles varied responses well (see FAQ).
</i>
</div></p>
<?php endif; ?>
<?php if ($config['operation-mode'] == 'cdn-friendly') : ?>
<p><div class="p">
<i>In "CDN friendly" mode, a jpeg is always served as a jpeg.
Instead of varying the image response, WebP Express alters the HTML for webp usage.</i>
<div class="help">?<div class="popup">
<p>
Benefits of not varying image responses:
<ol>
<li>CDN's will not have to be set up to forward the Accept header</li>
<li>Better HIT ratio on CDNs, if the CDN had to be configured to vary on the whole Accept header.</li>
<li>If a user downloads an image, it will not have wrong file extension</li>
</ol>
PS: This mode also works great with <a target="_blank" href="https://da.wordpress.org/plugins/cache-enabler/">Cache Enabler</a>. Instructions are found in the FAQ.
</p>
<p>
PPS: Images refererenced from external CSS or added dynamically with javascript will not be served as webp in this mode.
</p>
</div>
</div>
<br><br>
<i>A couple of options are available for automatically triggering webp conversion.</i>
</div></p>
<?php endif; ?>
<?php if ($config['operation-mode'] == 'no-conversion') : ?>
<p>
<div class="p">
<i>The "No conversion" mode is for scenarios where you are using another plugin for converting images.
Perhaps the other plugin doesn't have the redirection or alter HTML feature, or perhaps it doesn't do it as well
as WebP Express does. PS: The two methods below works great in tandem.</i>
</div>
</p>
<?php endif; ?>
</fieldset>
<!--
<p><div>
<i>WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.</i>
<div class="help">?<div class="popup"><ol>
<li>Some redirect rules set up in <i>.htaccess</i> redirects (unconverted) jpeg/png images to a PHP script for handling.</li>
<li>The PHP script reads the options and passes them to the <i><a target="_blank" href="https://github.com/rosell-dk/webp-convert/">WebP Convert</a></i> library for converting <i>and</i> serving.</li>
<li>If WebP Convert finds that the image already is converted, it will be served immediately (unless it is bigger than the original, or the original has been modified). Otherwise it will be converted and then served</li>
</ol></div></div>
</div></p>
-->

View File

@@ -0,0 +1,12 @@
<!--
We do not need this setting currently.
We will rather let this be decided by the success-response option
<tr>
<th scope="row">
Add Vary:Accept Header for the images <?php echo helpIcon('The Vary:Accept header tells browsers (and CDNs) that the response depends on the Accept header (which is the header that browsers use to indicate that they accept webp images). Usually you should leave this on. But if you are using the Cache Enabler plugin, check it off. '); ?>
</th>
<td>
<input type="checkbox" id="add_vary_header_in_htaccess" name="add-vary-header-in-htaccess" value="true" <?php echo ($config['add-vary-header-in-htaccess'] ? 'checked="checked"' : '') ?> >
</td>
</tr>
-->

View File

@@ -0,0 +1,11 @@
<?php
echo '<tr><th scope="row">Do not pass source in Query String';
echo helpIcon(
'You can try unchecking this, if you are experiencing that no images are converted. In v0.8 and below, the <i>.htaccess</i> ' .
'always passed the filename of the image to the script through the query string. ' .
'It however seems that passing through an environment variable instead works just fine. ' .
'As passing it through the query string can cause some firewalls to block the request, we no longer do this per default.'
);
echo '</th><td>';
echo '<input type="checkbox" id="do_not_pass_source_in_query_string" name="do-not-pass-source-in-query-string" value="true" ' . ($config['do-not-pass-source-in-query-string'] ? 'checked="checked"' : '') . '">';
echo '</td></tr>';

View File

@@ -0,0 +1,25 @@
<tr>
<th scope="row">
Enable redirection to converter?
<?php echo helpIcon(
'<p>This will add rules in the <i>.htaccess</i> that redirects images (jpeg/png) to the conversion script ("webp-on-demand.php") ' .
'for browsers that supports webp.</p>' .
'<p>If the script detects that the webp already exists, and it is smaller and newer than the original, the ' .
'webp is served directly. Otherwise the original image is converted and served.</p>' .
'<p>The redirect rule is placed below the rule that redirects directly to existing ' .
'webp files, which means that conversion will only be triggered once.</p>'
); ?>
</th>
<td>
<input
id="enable_redirection_to_converter"
name="enable-redirection-to-converter"
<?php echo ($config['enable-redirection-to-converter'] ? 'checked="checked"' : '') ?>
value="true"
type="checkbox"
>
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToConverter')" class="button button-secondary" type="button">
Live test
</button>
</td>
</tr>

View File

@@ -0,0 +1,32 @@
<tr>
<th scope="row">
Create webp files upon request?
<?php echo helpIcon(
'<p>Enabling this option will add lines in the .htaccess which redirects requests for non-existing webp-files to ' .
'the converter script (webp-realizer.php). ' .
'<i>This way you can reference webps before they actually exists.</i></p>' .
'<p>The feature works the following way:' .
'<ol>' .
'<li>WebP adds rules in the <i>.htaccess</i> that redirects requests for non-existing webp files to <i>webp-realizer.php</i></li>' .
'<li><i>webp-realizer.php</i> looks for a corresponding jpg/png. ' .
'If found, it is converted, saved and served. In case no corresponding jpg/png is found, a 404 is issued</li>' .
'</ol>' .
'</p>' .
'This only happens once per image. The next time the webp is requested, the rule will not trigger because the webp now exists.'
// '<p>This feature allows you to reference webp images before they actually exists. You can ie write:' .
// "<pre>&lt;picture&gt;\n &lt;source srcset=\"image.jpg.webp\" type=\"image/webp\" /&gt;\n &lt;img src=\"image.jpg\" /&gt;&lt;/picture&gt;"
); ?>
</th>
<td>
<input
name="enable-redirection-to-webp-realizer"
<?php echo ($config['enable-redirection-to-webp-realizer'] ? 'checked="checked"' : '') ?>
value="true"
type="checkbox"
>
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToWebPRealizer')" class="button button-secondary" type="button">
Live test
</button>
</td>
</tr>

View File

@@ -0,0 +1,16 @@
<tr id="only_redirect_to_converter_for_webp_enabled_browsers_row">
<th scope="row">
</th>
<td>
Only redirect to converter for webp-enabled browsers?<?php echo helpIcon(
'If checked, a condition is added to the .htaccess, that the <i>Accept</i> header contains "image/webp"'
); ?>
<input
name="only-redirect-to-converter-for-webp-enabled-browsers"
id="only_redirect_to_converter_for_webp_enabled_browsers"
<?php echo ($config['only-redirect-to-converter-for-webp-enabled-browsers'] ? 'checked="checked"' : '') ?>
value="true"
type="checkbox"
>
</td>
</tr>

View File

@@ -0,0 +1,18 @@
<tr id="only_redirect_to_converter_on_cache_miss_row">
<th scope="row">
</th>
<td>
Only redirect to converter if no webp exists <?php
echo helpIcon(
'<p>This extra condition is not needed if you enabled the ' .
'<i>Redirect directly to converted image when available</i> option.</p>' .
'<p>The option was created in order to make it possible to achieve the functionality behind the ' .
'<i>Redirect requests for non-existing webp-files to converter</i> option found in the ' .
'"CDN friendly" operation mode.</p>'
);
?>
<input type="checkbox" name="only-redirect-to-converter-on-cache-miss" value="true" <?php
echo ($config['only-redirect-to-converter-on-cache-miss'] ? 'checked="checked"' : '')
?> >
</td>
</tr>

View File

@@ -0,0 +1,40 @@
<tr>
<th scope="row">
<?php
if ($config['operation-mode'] == 'no-conversion') {
echo 'Activate redirection';
echo helpIcon(
'This will add rules in the .htaccess that redirects directly to existing converted files ' .
'(note that it is an internal redirect, which is much faster than a 301 or 302 redirect).'
);
} else {
echo 'Enable direct redirection to existing converted images?';
echo helpIcon(
'<p>This will add rules in the .htaccess that redirects directly to existing converted files, for ' .
'browsers that supports webp.</p>' .
'<p>The rule is placed above the rule that redirects to the converter.</p>'
);
}
?>
</th>
<td>
<input type="checkbox" id="redirect_to_existing_in_htaccess" name="redirect-to-existing-in-htaccess" value="true" <?php
echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '')
?> >
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToExisting')" class="button button-secondary" type="button">
Live test
</button>
<?php
/*if ($config['operation-mode'] == 'no-conversion') {
// Cache control header
echo '<div id="cache_control_div"><table style="margin-top: 10px">';
include_once __DIR__ . '/../serve-options/cache-control.inc';
echo '</table></div>';
}*/
?>
</td>
</tr>

View File

@@ -0,0 +1,67 @@
<fieldset class="block">
<?php if ($config['operation-mode'] == 'varied-image-responses') : ?>
<h2><i>.htaccess</i> rules for webp generation</h2>
<p>
Usually, in this operation mode, you would enable the two first options, which effectively
enables the varied image responses (such that a request for a jpeg/png will result in a webp on browsers that supports webp).
The first option makes this happen for images that are already converted. The second option makes it happen even for
images that has not yet been converted, by redirecting the request to the converter, which converts, saves and delivers.</p>
<p>The third option is only relevant if you are using Alter HTML and want to reference webps that haven't been converted yet.</p>
<?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
<h2><i>.htaccess</i> rules for webp generation</h2>
<?php elseif ($config['operation-mode'] == 'no-conversion') : ?>
<h2>Redirecting jpeg/png to existing webp (varied image response)</h2>
<p>
Enabling this adds rules to the <i>.htaccess</i> which internally redirects jpg/pngs to webp
and sets the <i>Vary:Accept</i> response header.
<i>Beware that special attention is needed if you are using a CDN (see FAQ).</i>
</p>
<?php else : ?>
If a webp already exists, it is served immediately. Otherwise it is converted and then served.
<h3>Redirection rules</h3>
<div><i>The options here creates redirection rules in the .htaccess. The two first are used to If you are planning to serve You do not have to enable any of them,
as you can rely solely on altering enable any of them, you Disabling The options here affects the rules created in the .htaccess. <?php
echo helpIcon('Note: The general options also affects the rules.');
?></i></div>
<?php endif; ?>
<table class="form-table">
<tbody>
<?php
switch ($config['operation-mode']) {
case 'tweaked':
include_once 'enable-redirection-to-converter.inc';
include_once 'only-redirect-to-converter-for-webp-enabled-browsers.inc';
include_once 'only-redirect-to-converter-on-cache-miss.inc';
include_once 'do-not-pass-source-path-in-query-string.inc';
include_once 'redirect-to-existing.inc';
include_once 'enable-redirection-to-webp-realizer.inc';
break;
case 'no-conversion':
include_once 'redirect-to-existing.inc';
include_once __DIR__. '/../general/cache-control.inc';
break;
case 'cdn-friendly':
//include_once 'redirect-to-existing.inc';
include_once 'enable-redirection-to-webp-realizer.inc';
// ps: we call it "auto convert", when in this mode
//include_once 'enable-redirection-to-converter.inc';
break;
case 'varied-image-responses':
include_once 'redirect-to-existing.inc';
include_once 'enable-redirection-to-converter.inc';
include_once 'enable-redirection-to-webp-realizer.inc';
break;
default:
echo 'Error: unknown operation mode! Try saving, it should fix it';
break;
}
?>
</tbody>
</table>
<div id="webpexpress_test_redirection_popup" style="display:none;">
<div id="webpexpress_test_redirection_content"></div>
</div>
</fieldset>

View File

@@ -0,0 +1,16 @@
<?php
// Response on failure
// --------------------
echo '<tr><th scope="row">Response on failure';
echo helpIcon('Determines what to serve in case the image conversion should fail.');
echo '</th><td>';
$fail = $config['fail'];
echo '<select name="fail">';
echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
//echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
echo '</select>';
echo '</td></tr>';
// echo '<tr><td colspan=2>Determines what the converter should serve, in case the image conversion should fail. For production servers, recommended value is "Original image". For development servers, choose anything you like, but that</td></tr>';

View File

@@ -0,0 +1,16 @@
<?php
echo '<tr><th scope="row">Response on success';
echo helpIcon(
'<p>Determines what to serve when conversion is a success. If you are using the Cache Enabler plugin, set to "Original image", ' .
'otherwise you would normally set it to "Converted image".</p>' .
'<p>If set to "Converted image", a Vary:Accept header will be sent to indicate that the response depends on the Accept header ' .
'(which indicates if a browser supports webp images or not)</p><p>If set to "Original image", make sure to disable the "Redirect ' .
'directly to converted image when available" option in the Redirect rules</p>');
echo '</th><td>';
$successResponse = $config['success-response'];
echo '<select name="success-response">';
echo '<option value="original"' . ($successResponse == 'original' ? ' selected' : '') . '>Original image</option>';
echo '<option value="converted"' . ($successResponse == 'converted' ? ' selected' : '') . '>Converted image</option>';
echo '</select>';
echo '</td></tr>';

View File

@@ -0,0 +1,16 @@
<?php if ($config['operation-mode'] == 'tweaked') : ?>
<fieldset class="block">
<h3>Serve options</h3>
<p><i>The options here affects how the image is served after a successful / unsuccessful conversion</i></p>
<table class="form-table">
<tbody>
<?php
include_once 'response-on-failure.inc';
include_once 'response-on-success.inc';
?>
</tbody>
</table>
</fieldset>
<?php
endif;
?>

View File

@@ -0,0 +1,10 @@
<fieldset class="block">
<h3>Web service</h3>
<table class="form-table">
<tbody>
<?php
include_once 'web-service.inc';
?>
</tbody>
</table>
</fieldset>

View File

@@ -0,0 +1,67 @@
<?php
include_once __DIR__ . '/../../../classes/Paths.php';
use \WebPExpress\Paths;
//$whitelist = $config['web-service']['whitelist'];
//echo '<script>window.whitelist = ' . json_encode($whitelist) . '</script>';
?>
<tr id="share">
<th scope="row">Enable web service?<?php echo helpIcon(
'Enabling the web service will allow selected sites to convert webp-images through this site (more options will appear, if you enable)'
); ?></th>
<td>
<input type="checkbox" id="web_service_enabled" name="web-service-enabled" value="true" <?php
echo ($config['web-service']['enabled'] ? 'checked="checked"' : '')
?>>
<input type='text' name='whitelist' id='whitelist' value='' style='visibility:hidden; height:0' />
<div id="whitelist_div"></div>
<div id="whitelist_properties_popup" class="das-popup">
<h3 class="hide-in-edit">Authorize website</h3>
<h3 class="hide-in-add">Edit authorized website</h3>
<input type="hidden" id="whitelist_uid">
<input type="hidden" id="whitelist_i">
<div>
<label for="whitelist_label">
Label
<?php echo helpIcon('The label is purely for your own reference'); ?>
</label>
<input id="whitelist_label" type="text">
</div>
<div>
<label for="whitelist_ip">
IP
<?php echo helpIcon('IP to allow access to service. You can use *, ie "212.91.*", or even "*"'); ?>
</label>
<input id="whitelist_ip" type="text">
</div>
<div>
<label for="whitelist_api_key">
Api key
<?php echo helpIcon('Who says api keys must be dull-looking meaningless sequences of random ' .
'characters? Here you get to shape your key to your liking. Enter any phrase you want'); ?>
</label>
<input id="whitelist_api_key" type="password" class="hide-in-edit">
<a href="javascript:whitelistChangeApiKey()" class="hide-in-add" style="line-height:34px">Change api key</a>
</div>
<div>
<label for="whitelist_require_api_key_to_be_crypted_in_transfer">
Require api-key to be crypted in transfer?
<?php echo helpIcon(
'If checked, the web service will only accept crypted api keys. Crypting the api-key protects it from being ' .
'stolen during transfer. On a few older server setups, clients do not have the capability to crypt');
?>
</label>
<input id="whitelist_require_api_key_to_be_crypted_in_transfer" type="checkbox">
</div>
<p style="margin-top: 15px">Psst: The endpoint of the web service is: <b><?php
echo esc_url(Paths::getWebServiceUrl())
?></b></p>
<button id="whitelist_properties_add_button" onclick="whitelistAddWhitelistEntry()" class="hide-in-edit button button-primary" type="button" style="position:absolute; bottom:20px">
Add
</button>
<button id="whitelist_properties_update_button" onclick="whitelistUpdateWhitelistEntry()" class="hide-in-add button button-primary" type="button" style="position:absolute; bottom:20px">
Update
</button>
</div>
</td>
</tr>

View File

@@ -0,0 +1,374 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
use \WebPExpress\HTAccessCapabilityTestRunner;
use \WebPExpress\Config;
use \WebPExpress\ConvertersHelper;
use \WebPExpress\DismissableMessages;
use \WebPExpress\FileHelper;
use \WebPExpress\HTAccess;
use \WebPExpress\HTAccessRules;
use \WebPExpress\Messenger;
use \WebPExpress\Paths;
use \WebPExpress\PlatformInfo;
use \WebPExpress\State;
// TODO: Move most of this file into a ProblemDetector class (SystemHealth)
if (!(State::getState('configured', false))) {
include __DIR__ . "/page-welcome.php";
if (PlatformInfo::isNginx()) {
DismissableMessages::addDismissableMessage('0.16.0/nginx-link-to-faq');
}
}
$storedCapTests = $config['base-htaccess-on-these-capability-tests'];
/*
if (HTAccessCapabilityTestRunner::modRewriteWorking()) {
echo 'mod rewrite works. that is nice';
}*/
/*if (HTAccessCapabilityTestRunner::modHeaderWorking() === true) {
//echo 'nice!';
}*/
// Dissmiss page messages for which the condition no longer applies
if ($config['image-types'] != 1) {
DismissableMessages::dismissMessage('0.14.0/suggest-enable-pngs');
}
//DismissableMessages::dismissAll();
//DismissableMessages::addDismissableMessage('0.14.0/suggest-enable-pngs');
//DismissableMessages::addDismissableMessage('0.14.0/suggest-wipe-because-lossless');
//DismissableMessages::addDismissableMessage('0.14.0/say-hello-to-vips');
DismissableMessages::printMessages();
//$dismissableMessageIds = ['suggest-enable-pngs'];
$firstActiveAndWorkingConverterId = ConvertersHelper::getFirstWorkingAndActiveConverterId($config);
$workingIds = ConvertersHelper::getWorkingConverterIds($config);
$cacheEnablerActivated = in_array('cache-enabler/cache-enabler.php', get_option('active_plugins', []));
if ($cacheEnablerActivated) {
$cacheEnablerSettings = get_option('cache-enabler', []);
$webpEnabled = (isset($cacheEnablerSettings['webp']) && $cacheEnablerSettings['webp']);
}
if ($cacheEnablerActivated && !$webpEnabled) {
Messenger::printMessage(
'warning',
'You are using Cache Enabler, but have not enabled the webp option, so Cache Enabler is not operating with a separate cache ' .
'for webp-enabled browsers.'
);
}
/*
Commented out
In newer PHP, it generates a fatal (uncatchable) error:
Fatal error: Uncaught Error: Call to a member function is_feature_active() on null
See #562
$elementorActivated = in_array('elementor/elementor.php', get_option('active_plugins', []));
if ($elementorActivated) {
try {
// The following is wrapped in a try statement because it depends on Elementor classes which might be subject to change
if (\Elementor\Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_loading' ) === false) {
if ($config['redirect-to-existing-in-htaccess'] === false) {
DismissableMessages::addDismissableMessage('0.23.0/elementor');
}
}
} catch (\Exception $e) {
// Well, just bad luck.
}
}
*/
if (($config['operation-mode'] == 'cdn-friendly') && !$config['alter-html']['enabled']) {
//echo print_r(get_option('cache-enabler'), true);
if ($cacheEnablerActivated) {
if ($webpEnabled) {
Messenger::printMessage(
'info',
'You should consider enabling Alter HTML. This is not necessary, as you have <i>Cache Enabler</i> enabled, which alters HTML. ' .
'However, it is a good idea because currently <i>Cache Enabler</i> does not replace as many URLs as WebP Express (ie ' .
'background images in inline styles)'
);
}
} else {
Messenger::printMessage(
'warning',
'You are in CDN friendly mode but have not enabled Alter HTML (and you are not using Cache Enabler either). ' .
'This is usually a misconfiguration because in this mode, the only way to get webp files delivered ' .
'is by referencing them in the HTML.'
);
}
}
/*
if (!$anyRedirectionToConverterEnabled && ($config['operation-mode'] == 'cdn-friendly')) {
// this can not happen in varied image responses. it is ok in no-conversion, and also tweaked, because one could wish to tweak the no-conversion mode
Messenger::printMessage(
'warning',
'You have not enabled any of the redirects to the converter. ' .
'At least one of the redirects is required for triggering WebP generation.'
);
}*/
if ($config['alter-html']['enabled'] && !$config['alter-html']['only-for-webps-that-exists'] && !$config['enable-redirection-to-webp-realizer']) {
Messenger::printMessage(
'warning',
'You have configured Alter HTML to make references to WebP files that are yet to exist, ' .
'<i>but you have not enabled the option that makes these files come true when requested</i>. Do that!'
);
}
if ($config['enable-redirection-to-webp-realizer'] && $config['alter-html']['enabled'] && $config['alter-html']['only-for-webps-that-exists']) {
Messenger::printMessage(
'warning',
'You have enabled the option that redirects requests for non-existing webp files to the converter, ' .
'<i>but you have not enabled the option to point to these in Alter HTML</i>. Please do that!'
);
}
if ($config['image-types'] == 3) {
$workingConverters = ConvertersHelper::getWorkingAndActiveConverters($config);
if (count($workingConverters) == 1) {
if (ConvertersHelper::getConverterId($workingConverters[0]) == 'gd') {
if (isset($workingConverters[0]['options']['skip-pngs']) && $workingConverters[0]['options']['skip-pngs']) {
Messenger::printMessage(
'warning',
'You have enabled PNGs, but configured Gd to skip PNGs, and Gd is your only active working converter. ' .
'This is a bad combination!'
);
}
}
}
}
/*
if (Config::isConfigFileThereAndOk() ) { // && PlatformInfo::definitelyGotModEnv()
if (!isset($_SERVER['HTACCESS'])) {
Messenger::printMessage(
'warning',
"Using rewrite rules in <i>.htaccess</i> files seems to be disabled " .
"(The <i>AllowOverride</i> directive is probably set to <i>None</i>. " .
"It needs to be set to <i>All</i>, or at least <i>FileInfo</i> to allow rewrite rules in <i>.htaccess</i> files.)<br>" .
"Disabled <i>.htaccess</i> files is actually a good thing, both performance-wise and security-wise. <br> " .
"But it means you will have to insert the following rules into your apache configuration manually:" .
"<pre>" . htmlentities(print_r(Config::hmmm(), true)) . "</pre>"
);
}
}*/
if (!Paths::createContentDirIfMissing()) {
Messenger::printMessage(
'error',
'WebP Express needs to create a directory "webp-express" under your wp-content folder, but does not have permission to do so.<br>' .
'Please create the folder manually, or change the file permissions of your wp-content folder (failed to create this folder: ' .
esc_html(Paths::getWebPExpressContentDirAbs()) . ')'
);
} else {
if (!Paths::createConfigDirIfMissing()) {
Messenger::printMessage(
'error',
'WebP Express needs to create a directory "webp-express/config" under your wp-content folder, but does not have permission to do so.<br>' .
'Please create the folder manually, or change the file permissions.'
);
}
if (!Paths::createCacheDirIfMissing()) {
Messenger::printMessage(
'error',
'WebP Express needs to create a directory "webp-express/webp-images" under your wp-content folder, but does not have permission to do so.<br>' .
'Please create the folder manually, or change the file permissions.'
);
}
}
if (Config::isConfigFileThere()) {
if (!Config::isConfigFileThereAndOk()) {
$json = FileHelper::loadFile(Paths::getConfigFileName());
if ($json === false) {
Messenger::printMessage(
'warning',
'Warning: The configuration file is not ok! (cant be read).<br>' .
'file: "' . esc_html(Paths::getConfigFileName()) . '"'
);
} else {
Messenger::printMessage(
'warning',
'Warning: The configuration file is not ok! (not valid json).<br>' .
'file: "' . esc_html(Paths::getConfigFileName()) . '"'
);
}
} else {
if ($config['redirect-to-existing-in-htaccess']) {
if (PlatformInfo::isApacheOrLiteSpeed() && !(HTAccessCapabilityTestRunner::modHeaderWorking())) {
Messenger::printMessage(
'warning',
'It seems your server setup does not support headers in <i>.htaccess</i>. You should either fix this (install <i>mod_headers</i>) <i>or</i> ' .
'deactivate the "Enable direct redirection to existing converted images?" option. Otherwise the <i>Vary:Accept</i> header ' .
'will not be added and this can result in problems for users behind proxy servers (ie used in larger companies)'
);
}
}
$anyRedirectionToConverterEnabled = (($config['enable-redirection-to-converter']) || ($config['enable-redirection-to-webp-realizer']));
$anyRedirectionEnabled = ($anyRedirectionToConverterEnabled || $config['redirect-to-existing-in-htaccess']);
if ($anyRedirectionEnabled) {
if (PlatformInfo::isApacheOrLiteSpeed() && PlatformInfo::definitelyNotGotModRewrite()) {
Messenger::printMessage(
'warning',
"Rewriting isn't enabled on your server. " .
'You must either switch to "CDN friendly" mode or enable rewriting. ' .
"Tell your host or system administrator to enable the 'mod_rewrite' module. " .
'If you are on a shared host, chances are that mod_rewrite can be turned on in your control panel.'
);
}
}
if ($anyRedirectionToConverterEnabled) {
$canRunInWod = HTAccessCapabilityTestRunner::canRunTestScriptInWOD();
$canRunInWod2 = HTAccessCapabilityTestRunner::canRunTestScriptInWOD2();
if (!$canRunInWod && !$canRunInWod2) {
$turnedOn = [];
if ($config['enable-redirection-to-converter']) {
$turnedOn[] = '"Enable redirection to converter"';
}
if ($config['enable-redirection-to-webp-realizer']) {
$turnedOn[] = '"Create webp files upon request?""';
}
Messenger::printMessage(
'warning',
'<p>You have turned on ' . implode(' and ', $turnedOn) .
'. However, ' . (count($turnedOn) == 2 ? 'these features' : 'this feature') .
' does not work on your current server settings / wordpress setup, ' .
' because the PHP scripts in the plugin folder (in the "wod" and "wod2" subfolders) fails to run ' .
' when requested directly. You can try to fix the problem or simply turn ' .
(count($turnedOn) == 2 ? 'them' : 'it') .
' off and rely on "Convert on upload" and "Bulk Convert" to get the images converted.</p>' .
'<p>If you are going to try to solve the problem, you need at least one of the following pages ' .
'to display "pong": ' .
'<a href="' . Paths::getWebPExpressPluginUrl() . '/wod/ping.php" target="_blank">wod-test</a>' .
' or <a href="' . Paths::getWebPExpressPluginUrl() . '/wod2/ping.php" target="_blank">wod2-test</a>' .
'. The problem will typically be found in the server configuration or a security plugin. ' .
'If one of the links results in a 403 Permission denied, look out for "deny" and "denied" in ' .
'httpd.conf, /etc/apache/sites-enabled/your-site.conf and in parent .htaccess files.' .
'</p>.'
);
}
// We currently allow the "canRunTestScriptInWOD" test not to be stored,
// If it is not stored, it means .htaccess files are pointing to "wod"
// PS: the logic of where it is stored happens in HTAccessRules::getWodUrlPath
// - we mimic it here.
$pointingToWod = true; // true = pointing to "wod", false = pointing to "wod2"
$hasWODTestBeenRun = isset($storedCapTests['canRunTestScriptInWOD']);
if ($hasWODTestBeenRun && !($storedCapTests['canRunTestScriptInWOD'])) {
$pointingToWod = false;
}
$canOnlyRunInWod = $canRunInWod && !$canRunInWod2;
if ($canOnlyRunInWod && !$pointingToWod) {
Messenger::printMessage(
'warning',
'The conversion script cannot currently be run. ' .
'However, simply click "Save settings <b>and force new .htaccess rules</b>" to fix it. ' .
'(this will point to the script in the "wod" folder rather than "wod2")'
);
}
$canOnlyRunInWod2 = $canRunInWod2 && !$canRunInWod;
if ($canOnlyRunInWod2 && $pointingToWod) {
Messenger::printMessage(
'warning',
'The conversion script cannot currently be run. ' .
'However, simply click "Save settings <b>and force new .htaccess rules</b>" to fix it. ' .
'(this will point to the script in the "wod2" folder rather than "wod")'
);
}
}
if (HTAccessRules::arePathsUsedInHTAccessOutdated()) {
$pathsGoingToBeUsedInHtaccess = [
'wod-url-path' => Paths::getWodUrlPath(),
];
$config2 = Config::loadConfig();
if ($config2 === false) {
Messenger::printMessage(
'warning',
'Warning: Config file cannot be loaded. Perhaps clicking ' .
'<i>Save settings</i> will solve it<br>'
);
}
$warningMsg = 'Warning: Wordpress paths have changed since the last time the Rewrite Rules was generated. The rules ' .
'needs updating! (click <i>Save settings</i> to do so)<br><br>' .
'The following have changed:<br>';
foreach ($config2['paths-used-in-htaccess'] as $prop => $value) {
if (isset($pathsGoingToBeUsedInHtaccess[$prop])) {
if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
$warningMsg .= '- ' . $prop . '(was: ' . $value . '- but is now: ' . $pathsGoingToBeUsedInHtaccess[$prop] . ')<br>';
}
}
}
Messenger::printMessage(
'warning',
$warningMsg
);
}
}
}
$haveRulesInIndexDir = HTAccess::haveWeRulesInThisHTAccessBestGuess(Paths::getIndexDirAbs() . '/.htaccess');
$haveRulesInContentDir = HTAccess::haveWeRulesInThisHTAccessBestGuess(Paths::getContentDirAbs() . '/.htaccess');
if ($haveRulesInIndexDir && $haveRulesInContentDir) {
// TODO: Use new method for determining if htaccess contains rules.
// (either haveWeRulesInThisHTAccessBestGuess($filename) or haveWeRulesInThisHTAccess($filename))
if (!HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false)) {
Messenger::printMessage(
'warning',
'Warning: WebP Express have rules in both your wp-content folder and in your Wordpress folder.<br>' .
'Please remove those in the <i>.htaccess</i> in your Wordress folder manually, or let us handle it, by granting us write access'
);
}
}
$ht = FileHelper::loadFile(Paths::getIndexDirAbs() . '/.htaccess');
if ($ht !== false) {
$posWe = strpos($ht, '# BEGIN WebP Express');
$posWo = strpos($ht, '# BEGIN WordPress');
if (($posWe !== false) && ($posWo !== false) && ($posWe > $posWo)) {
$haveRulesInIndexDir = HTAccess::haveWeRulesInThisHTAccessBestGuess(Paths::getIndexDirAbs() . '/.htaccess');
if ($haveRulesInIndexDir) {
Messenger::printMessage(
'warning',
'Problem detected. ' .
'In order for the "Convert non-existing webp-files upon request" functionality to work, you need to either:<br>' .
'- Move the WebP Express rules above the Wordpress rules in the .htaccess file located in your root dir<br>' .
'- Grant the webserver permission to your wp-content dir, so it can create its rules there instead.'
);
}
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace WebPExpress;
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
$indexDir = Paths::getIndexDirAbs();
$homeDir = Paths::getHomeDirAbs();
$wpContentDir = Paths::getContentDirAbs();
$pluginDir = Paths::getPluginDirAbs();
$uploadDir = Paths::getUploadDirAbs();
$weKnowThereAreNoWorkingConverters = false;
if ($testResult !== false) {
$workingConverters = $testResult['workingConverters'];
$weKnowThereAreNoWorkingConverters = (count($workingConverters) == 0);
}
$bgColor = ($weKnowThereAreNoWorkingConverters ? '#fff' : '#cfc');
echo '<div style="background-color: ' . $bgColor . '; padding: 10px 20px; border: 1px solid #ccc; color: black; margin-top:15px">';
echo '<h3>Welcome!</h3>';
//if ($localQualityDetectionWorking) {
//echo 'Local quality detection working :)';
//}
if ($weKnowThereAreNoWorkingConverters) {
// server does not meet the requirements for converting images to webp without resorting to cloud conversion
echo '<p>Unfortunately your server cannot convert webp files in PHP without resorting to cloud conversion.</p>' .
'<p>But do not despear! - You have options!</p>' .
'<ol style="list-style-position:outside">' .
'<li>You can install this plugin on another website, which supports a "local" webp conversion method and connect to that using the "Remote WebP Express" conversion method' .
'<li>You can purchase a key for the <a target="_blank" href="https://ewww.io/plans/">ewww cloud converter</a>. They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)</li>' .
'<li>I have written a <a target="_blank" href="https://github.com/rosell-dk/webp-convert/wiki/A-template-letter-for-shared-hosts">template letter</a> which you can try sending to your webhost</li>' .
'<li>You can set up a <a target="_blank" href="https://github.com/rosell-dk/webp-convert-cloud-service">webp-convert-cloud-service</a> on another server and connect to that. Its open source.</li>' .
'<li>You can try to meet the server requirements of cwebp, imagick, vips, gmagick, ffmpeg or gd. Check out <a target="_blank" href="https://github.com/rosell-dk/webp-convert/wiki/Meeting-the-requirements-of-the-converters">this wiki page</a> on how to do that</li>' .
'</ol>' .
'<p>Of course, there is also the option of using another plugin altogether. ' .
'I can recommend <i>Optimole</i>. ' .
'If you want to try that out and want to support me in the process, ' .
'<a href="https://optimole.pxf.io/20b0M">follow this link</a> ' .
'(it will give me a reward in case you decide to sign up).' .
'</p>' .
"<p>Btw, don't worry, your images still works. The rewrite rules will not be saved until you click the " .
'"Save settings" button.</p>';
//'(and you also have "Response on failure" set to "Original image", so they will work even if you click save)</p>';
} else {
echo '<p>The rewrite rules are not active yet. They will be activated the first time you click the "Save settings" button.</p>';
}
//echo 'working converters:';
//print_r($workingConverters);
//echo '<p>Before you do that, I suggest you find out which converters that works. Start from the top. Click "test" next to a converter to test it. Try also clicking the "configure" buttons</p>';
/*
if (Paths::isWPContentDirMovedOutOfAbsPath()) {
if (!Paths::canWriteHTAccessRulesHere($wpContentDir)) {
echo '<p><b>Oh, one more thing</b>. Unless you are going to put the rewrite rules into your configuration manually, ';
echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file in your <i>wp-content</i> directory ';
echo '(we need to store them there rather than in your root, because you have moved your wp-content folder out of the Wordpress root). ';
echo 'Please adjust the file permissions of your <i>wp-content</i> dir. ';
if (Paths::isPluginDirMovedOutOfWpContent()) {
echo '<br>But that is not all. Besides moving your wp-content dir, you have <i>also</i> moved your plugin dir... ';
echo 'If you want WebP-Express to work on the images delivered by your plugins, you must also grant write access to your plugin dir (you can revoke the access after we have written the rules).<br>';
}
echo 'You can reload this page aftewards, and this message should be gone</p>';
} else {
if (Paths::isPluginDirMovedOutOfWpContent()) {
echo '<p><b>Oh, one more thing</b>. I can see that your plugin dir has been moved out of your wp-content folder. ';
echo 'If you want WebP-Express to work on the images delivered by your plugins, you must grant write access to your ';
echo 'plugin dir (you can revoke the access after we have written the rules, but beware that the plugin may need ';
echo 'access rights again. Some of the options affects the .htaccess rules. And WebP Express also have to remove the rules if the plugin is disabled)';
}
}
if (Paths::isUploadDirMovedOutOfWPContentDir()) {
if (!Paths::canWriteHTAccessRulesHere($uploadDir)) {
echo '<p><b>Oh, one more thing</b>. We also need to write rules to your uploads dir (because you have moved it). ';
echo 'Please grant us write access to your ';
if (FileHelper::fileExists($uploadDir . '/.htaccess')) {
echo '<i>.htaccess</i> file in your upload dir';
} else {
echo 'upload dir, so we can plant an <i>.htaccess</i> there';
}
echo '. Your upload dir is: <i>' . $uploadDir . '</i>. ';
echo '- Or alternatively, you can leave it be and update the rules manually, whenever they need to be changed. ';
}
}
} else {
$firstWritable = Paths::returnFirstWritableHTAccessDir([$wpContentDir, $indexDir]);
if ($firstWritable === false) {
echo '<p><b>Oh, one more thing</b>. Unless you are going to put the rewrite rules into your configuration manually, ';
echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file. ';
echo 'However, your current file permissions does not allow that. ';
echo '<i>WebP Express</i> would prefer to put the rewrite rules into your <i>wp-content</i> folder, but ';
echo 'will store them in your main <i>.htaccess</i> file if it can write to that, but not your wp-content. ';
echo '(The preference for storing in wp-content is simply that it minimizes the risk of conflicts with rules from other plugins. ';
echo 'deeper <i>.htaccess</i> files takes precedence). ';
echo 'Anyway: Could you please adjust the file permissions of either your main <i>.htaccess</i> file or your wp-content dir?';
echo 'You can reload this page aftewards, and this message should be gone</p>';
} else {
if ($firstWritable != $wpContentDir) {
echo '<p>Oh, one more thing. Unless you are going to put the rewrite rules into your configuration manually, ';
echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file. ';
echo 'Your current file permissions <i>does</i> allow us to store rules in your main <i>.htaccess</i> file. ';
echo 'However, <i>WebP Express</i> would prefer to put the rewrite rules into your <i>wp-content</i> folder. ';
echo 'Putting them there will minimize the risk of conflict with rules from other plugins, as ';
echo 'deeper <i>.htaccess</i> files takes precedence. ';
echo 'If you would like the <i>.htaccess</i> file to be stored in your wp-content folder, please adjust your file permissions. ';
echo 'You can reload this page aftewards, and this message should be gone</p>';
}
}
if (Paths::isUploadDirMovedOutOfWPContentDir()) {
if (!Paths::canWriteHTAccessRulesHere($uploadDir)) {
echo '<p><b>Oh, one more thing</b>. We also need to write rules to your uploads dir (because you have moved it). ';
echo 'Please grant us write access to your ';
if (FileHelper::fileExists($uploadDir . '/.htaccess')) {
echo '<i>.htaccess</i> file in your upload dir';
} else {
echo 'upload dir, so we can plant an <i>.htaccess</i> there';
}
echo '. Your upload dir is: <i>' . $uploadDir . '</i>. ';
echo '- Or alternatively, you can leave it and update the rules manually, whenever they need to be changed. ';
}
}
if (Paths::isPluginDirMovedOutOfAbsPath()) {
if (!Paths::canWriteHTAccessRulesHere($pluginDir)) {
echo '<p>Oh, one more thing. I see you have moved your plugins dir out of your root. ';
echo 'If you want WebP-Express to work on the images delivered by your plugins, you must also grant write access ';
echo 'to your ';
if (FileHelper::fileExists($pluginDir . '/.htaccess')) {
echo '<i>.htaccess</i> file in your plugin dir';
} else {
echo 'plugin dir, so we can plant an <i>.htaccess</i> there';
}
echo ' (you can revoke the access after we have written the rules).';
echo '</p>';
}
}
}
*/
/*
if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
if ($firstWritable === false) {
echo 'Actually, WebP Express does not have permission to write to your main <i>.htaccess</i> either. Please fix. Preferably ';
}
$firstWritable = Paths::returnFirstWritableHTAccessDir([$indexDir, $homeDir]);
if ($firstWritable === false) {
echo 'Actually, WebP Express does not have permission to write to your main <i>.htaccess</i> either. Please fix. Preferably ';
}
if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
echo '<i>WebP Express</i> however does have rights to write to your main <i>.htaccess</i>. It will work too - probably. But to minimize risk of conflict with rules from other plugins, I recommended you to adjust the file permissions to allow us to write to a <i>.htaccess</i> file in your <i>wp-content dir</i>';
}
echo '</p>';
}*/
echo '</div>';

261
lib/options/page.php Normal file
View File

@@ -0,0 +1,261 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
use \WebPExpress\Config;
use \WebPExpress\ConvertersHelper;
use \WebPExpress\FileHelper;
use \WebPExpress\HTAccess;
use \WebPExpress\Messenger;
use \WebPExpress\Multisite;
use \WebPExpress\Paths;
use \WebPExpress\PlatformInfo;
use \WebPExpress\State;
use \WebPExpress\TestRun;
if (!current_user_can('manage_options')) {
wp_die('You do not have sufficient permissions to access this page.');
}
?>
<div class="wrap">
<h2>WebP Express Settings<?php echo Multisite::isNetworkActivated() ? ' (network)' : ''; ?></h2>
<?php
function webpexpress_converterName($converterId) {
if ($converterId == 'wpc') {
return 'Remote WebP Express';
}
return $converterId;
}
/*
Removed (#243)
function printAutoQualityOptionForConverter($converterId) {
?>
<div>
<label for="<?php echo $converterId; ?>_quality">
Quality
<?php echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!'); ?>
</label>
<select id="<?php echo $converterId; ?>_quality" onchange="converterQualityChanged('<?php echo $converterId; ?>')">
<option value="inherit">Use global settings</option>
<option value="auto">Auto</option>
</select>
</div>
<div id="<?php echo $converterId; ?>_max_quality_div">
<label>
Max quality
<?php echo helpIcon('Enter number (0-100). Converted images will be encoded with same quality as the source image, but not more than this setting'); ?>
</label>
<input type="text" size=3 id="<?php echo $converterId; ?>_max_quality">
</div>
<?php
}
*/
$canDetectQuality = TestRun::isLocalQualityDetectionWorking();
$testResult = TestRun::getConverterStatus();
$config = Config::getConfigForOptionsPage();
State::setState('workingConverterIds', ConvertersHelper::getWorkingConverterIds($config));
State::setState('workingAndActiveConverterIds', ConvertersHelper::getWorkingAndActiveConverterIds($config));
//State::setState('last-ewww-optimize-attempt', 0);
//State::setState('last-ewww-optimize', 0);
\WebPExpress\KeepEwwwSubscriptionAlive::keepAliveIfItIsTime($config);
if (!$testResult) {
Messenger::printMessage(
'error',
'WebP Express cannot save a test conversion, because it does not have write ' .
'access to your upload folder, nor your wp-content folder. Please provide!'
);
}
include __DIR__ . "/page-messages.php";
/*
foreach (Paths::getHTAccessDirs() as $dir) {
echo $dir . ':' . (Paths::canWriteHTAccessRulesHere($dir) ? 'writable' : 'not writable') . '<br>';
//Paths::canWriteHTAccessRulesHere($dir);
}*/
//echo '<pre>' . print_r($config['converters'], true) . '</pre>';
//echo 'Working converters:' . print_r($workingConverters, true) . '<br>';
// Generate a custom nonce value.
$webpexpressSaveSettingsNonce = wp_create_nonce('webpexpress-save-settings-nonce');
?>
<?php
//echo get_theme_root_uri();
//include_once __DIR__ . '/../classes/AlterHtmlHelper.php';
//$actionUrl = Multisite::isNetworkActivated() ? network_admin_url( 'admin-post.php' ) : admin_url( 'admin-post.php' );
$actionUrl = admin_url('admin-post.php');
echo '<form id="webpexpress_settings" action="' . esc_url($actionUrl) . '" method="post" >';
?>
<input type="hidden" name="action" value="webpexpress_settings_submit">
<input type="hidden" name="_wpnonce" value="<?php echo $webpexpressSaveSettingsNonce ?>" />
<fieldset class="block buttons">
<table>
<tr>
<td style="padding-right:20px"><?php submit_button('Save settings', 'primary', 'mysubmit'); ?></td>
<td><?php submit_button('Save settings and force new .htaccess rules', 'secondary', 'force'); ?></td>
</tr>
</table>
</fieldset>
<?php
function helpIcon($text, $customClass = '') {
$className = '';
if (strlen($text) < 80) {
$className = 'narrow';
}
if (strlen($text) > 150) {
if (strlen($text) > 300) {
if (strlen($text) > 500) {
if (strlen($text) > 1000) {
$className = 'widest';
} else {
$className = 'even-wider';
}
} else {
$className = 'wider';
}
} else {
$className = 'wide';
}
}
return '<div class="help ' . $customClass . '">?<div class="popup ' . $className . '">' . $text . '</div></div>';
}
function webpexpress_selectBoxOptions($selected, $options) {
foreach ($options as $optionValue => $text) {
echo '<option value="' . esc_attr($optionValue) . '"' . ($optionValue == $selected ? ' selected' : '') . '>';
echo esc_html($text);
echo '</option>';
}
}
function webpexpress_radioButton($optionName, $optionValue, $label, $selectedValue, $helpText = null) {
$id = esc_attr(str_replace('-', '_', $optionName . '_' . $optionValue));
echo '<input type="radio" id="' . $id . '"';
if ($optionValue == $selectedValue) {
echo ' checked="checked"';
}
echo ' name="' . esc_attr($optionName) . '" value="' . esc_attr($optionValue) . '" style="margin-right: 10px">';
echo '<label for="' . $id . '">';
echo $label;
if (!is_null($helpText)) {
echo helpIcon($helpText);
}
echo '</label>';
}
function webpexpress_radioButtons($optionName, $selected, $options, $helpTexts = [], $style='margin-left: 20px; margin-top: 5px') {
echo '<ul style="' . $style . '">';
foreach ($options as $optionValue => $label) {
echo '<li>';
webpexpress_radioButton($optionName, $optionValue, $label, $selected, isset($helpTexts[$optionValue]) ? $helpTexts[$optionValue] : null);
echo '</li>';
}
echo '</ul>';
}
function webpexpress_checkbox($optionName, $checked, $label, $helpText = '') {
$id = esc_attr(str_replace('-', '_', $optionName));
echo '<div style="margin:10px 0 0 10px;">';
echo '<input value="true" type="checkbox" style="margin-right: 10px" ';
echo 'name="' . esc_attr($optionName) . '"';
echo 'id="' . $id . '"';
if ($checked) {
echo ' checked="checked"';
}
echo '>';
echo '<label for="' . $id . '">';
echo $label . '</label>';
if ($helpText != '') {
echo helpIcon($helpText);
}
echo '</div>';
}
include_once 'options/operation-mode.inc';
include_once 'options/general/general.inc';
/*
idea:
$options = [
'tweaked' => [
'general' => [
'image-types',
'destination-folder',
'destination-extension',
'cache-control'
]
],
...
];
*/
if ($config['operation-mode'] != 'tweaked') {
// echo '<fieldset class="block">';
// echo '<table class="form-table"><tbody>';
}
if ($config['operation-mode'] == 'no-conversion') {
// General
/*
echo '<tr><th colspan=2>';
echo '<h2>General</h2>';
echo '</th></tr>';
include_once 'options/conversion-options/destination-extension.inc';
include_once 'options/general/image-types.inc';
*/
include_once 'options/redirection-rules/redirection-rules.inc';
include_once 'options/alter-html/alter-html.inc';
} else {
include_once 'options/redirection-rules/redirection-rules.inc';
include_once 'options/conversion-options/conversion-options.inc';
//include_once 'options/conversion-options/destination-extension.inc';
include_once 'options/serve-options/serve-options.inc';
include_once 'options/alter-html/alter-html.inc';
/*
if ($config['operation-mode'] == 'cdn-friendly') {
include_once 'options/redirection-rules/enable-redirection-to-webp-realizer.inc';
// ps: we call it "auto convert", when in this mode
include_once 'options/redirection-rules/enable-redirection-to-converter.inc';
}
if ($config['operation-mode'] == 'varied-image-responses') {
include_once 'options/redirection-rules/enable-redirection-to-webp-realizer.inc';
}
*/
include_once 'options/web-service-options/web-service-options.inc';
}
if ($config['operation-mode'] != 'tweaked') {
// echo '</tbody></table>';
// echo '</fieldset>';
}
?>
</form>
</div>

807
lib/options/submit.php Normal file
View File

@@ -0,0 +1,807 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
use \WebPExpress\CacheMover;
use \WebPExpress\Config;
use \WebPExpress\ConvertersHelper;
use \WebPExpress\DismissableMessages;
use \WebPExpress\HTAccess;
use \WebPExpress\HTAccessRules;
use \WebPExpress\Messenger;
use \WebPExpress\PathHelper;
use \WebPExpress\Paths;
// TODO: Move this code to a class
check_admin_referer('webpexpress-save-settings-nonce');
DismissableMessages::dismissMessage('0.14.0/say-hello-to-vips');
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-no-uploads');
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-index');
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-content');
DismissableMessages::dismissMessage('0.15.1/problems-with-mingled-set');
/*
--------------------------------
Custom functions for sanitizing
--------------------------------
*/
/**
* Get sanitized text (NUL removed too)
*
* General purpose for getting textual values from $_POST.
* If the POST value is not set, the fallback is returned
*
* For sanitizing, the wordpress function "sanitize_text_field" is used. However, before doing that, we
* remove any NUL characters. NUL characters can be used to trick input validation, so we better get rid of those
* right away
*
* @param string $keyInPOST key in $_POST
* @param int $fallback value to return if the POST does not match any in the set, or it is not send at all
* @param array $acceptableValues the set of values that we have to choose between
*
* @return string sanitized text, or fallback if value isn't set
*/
function webpexpress_getSanitizedText($keyInPOST, $fallbackValue = '') {
if (!isset($_POST[$keyInPOST])) {
return $fallbackValue;
}
$value = $_POST[$keyInPOST];
// Keep in mind checking for NUL when dealing with user input
// see https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP
$value = str_replace(chr(0), '', $value);
return sanitize_text_field($value);
}
/**
* Get sanitized value from a set of values.
*
* Only allows values in the set given. If the value does not match, the fallback will be returned.
*
* @param string $keyInPOST key in $_POST
* @param int $fallback value to return if the POST does not match any in the set, or it is not send at all
* @param array $acceptableValues the set of values that we have to choose between
*
* @return mixed one of the items in the set - or fallback (which is usually also one in the set)
*/
function webpexpress_getSanitizedChooseFromSet($keyInPOST, $fallbackValue, $acceptableValues) {
$value = webpexpress_getSanitizedText($keyInPOST, $fallbackValue);
if (in_array($value, $acceptableValues)) {
return $value;
}
return $fallbackValue;
}
function webpexpress_getSanitizedCacheControlHeader($keyInPOST) {
$value = webpexpress_getSanitizedText($keyInPOST);
// Example of valid header: "public, max-age=31536000, stale-while-revalidate=604800, stale-if-error=604800"
$value = strtolower($value);
return preg_replace('#[^a-z0-9=,\s_\-]#', '', $value);
}
/**
* Get sanitized integer
*
* @param string $keyInPOST key in $_POST
* @param int $fallback fallback in case nothing in POST or if we cannot parse it as int
*
* @return int the sanitized int value.
*/
function webpexpress_getSanitizedInt($keyInPOST, $fallback=0) {
$value = webpexpress_getSanitizedText($keyInPOST, strval($fallback));
// strip anything after and including comma
$value = preg_replace('#[\.\,].*#', '', $value);
// remove anything but digits
$value = preg_replace('#[^0-9]#', '', $value);
if ($value == '') {
return $fallback;
}
return intval($value);
}
/**
* Get sanitized quality (0-100).
*
* @param string $keyInPOST key in $_POST
*
* @return int quality (0-100)
*/
function webpexpress_getSanitizedQuality($keyInPOST, $fallback = 75) {
$q = webpexpress_getSanitizedInt($keyInPOST, $fallback);
// return value between 0-100
return max(0, min($q, 100));
}
function webpexpress_getSanitizedScope() {
$scopeText = webpexpress_getSanitizedText('scope');
if ($scopeText == '') {
$scopeText = 'uploads';
}
$scope = explode(',', $scopeText);
$allowed = Paths::getImageRootIds();
$result = [];
foreach ($scope as $imageRootId) {
if (in_array($imageRootId, $allowed)) {
$result[] = $imageRootId;
}
}
return $result;
}
/**
* Get sanitized whitelist
*
* @return array Sanitized array of the whitelist json array received in $_POST
*/
function webpexpress_getSanitizedWhitelist() {
$whitelistPosted = (isset($_POST['whitelist']) ? $_POST['whitelist'] : '[]');
$whitelistPosted = json_decode(wp_unslash($whitelistPosted), true);
// TODO: check for json decode error
$whitelistSanitized = [];
// Sanitize whitelist
foreach ($whitelistPosted as $whitelist) {
if (
isset($whitelist['label']) &&
isset($whitelist['ip'])
// note: api-key is not neccessarily set
) {
$obj = [
'label' => sanitize_text_field($whitelist['label']),
'ip' => sanitize_text_field($whitelist['ip']),
];
if (isset($whitelist['new-api-key'])) {
$obj['new-api-key'] = sanitize_text_field($whitelist['new-api-key']);
}
if (isset($whitelist['uid'])) {
$obj['uid'] = sanitize_text_field($whitelist['uid']);
}
if (isset($whitelist['require-api-key-to-be-crypted-in-transfer'])) {
$obj['require-api-key-to-be-crypted-in-transfer'] = ($whitelist['require-api-key-to-be-crypted-in-transfer'] === true);
}
$whitelistSanitized[] = $obj;
}
}
return $whitelistSanitized;
}
/**
* Get sanitized converters.
*
* @return array Sanitized array of the converters json array received in $_POST
*/
function webpexpress_getSanitizedConverters() {
$convertersPosted = (isset($_POST['converters']) ? $_POST['converters'] : '[]');
$convertersPosted = json_decode(wp_unslash($convertersPosted), true); // holy moly! Wordpress automatically adds slashes to the global POST vars- https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
$convertersSanitized = [];
// Get list of possible converter ids.
$availableConverterIDs = ConvertersHelper::getDefaultConverterNames();
// Add converters one at the time.
foreach ($convertersPosted as $unsanitizedConverter) {
if (!isset($unsanitizedConverter['converter'])) {
continue;
}
// Only add converter if its ID is a known converter.
if (!in_array($unsanitizedConverter['converter'], $availableConverterIDs)) {
continue;
}
$sanitizedConverter = [];
$sanitizedConverter['converter'] = $unsanitizedConverter['converter'];
// Sanitize and add expected fields ("options", "working", "deactivated" and "error")
// "options"
if (isset($unsanitizedConverter['options'])) {
$sanitizedConverter['options'] = [];
// Sanitize all (string) options individually
foreach ($unsanitizedConverter['options'] as $optionName => $unsanitizedOptionValue) {
$acceptedOptions = [
// vips
'smart-subsample' => 'boolean',
'preset' => 'string',
// gd
'skip-pngs' => 'boolean',
// in multiple
"use-nice" => 'boolean',
// cwebp
"try-common-system-paths" => 'boolean',
"try-supplied-binary-for-os" => 'boolean',
"skip-these-precompiled-binaries" => 'string',
"method" => 'integer', // 0-6,
"size-in-percentage" => 'integer', // 0-100
"low-memory" => 'boolean',
"command-line-options" => 'string', // webp-convert takes care of sanitizing this very carefully!
"set-size" => 'boolean',
// wpc
"api-url" => 'string',
"api-version" => 'integer',
"crypt-api-key-in-transfer" => 'boolean',
"new-api-key" => 'string',
//ewww
"api-key" => 'string',
"api-key-2" => 'string',
];
// check that it is an accepted option name
if (!isset($acceptedOptions[$optionName])) {
continue;
}
// check that type is as expected
$expectedType = $acceptedOptions[$optionName];
if (gettype($unsanitizedOptionValue) != $expectedType) {
continue;
}
if ($expectedType == 'string') {
$sanitizedOptionValue = sanitize_text_field($unsanitizedOptionValue);
} else {
// integer and boolean are completely safe!
$sanitizedOptionValue = $unsanitizedOptionValue;
}
if (($optionName == "size-in-percentage") && ($sanitizedOptionValue == '')) {
continue;
}
$sanitizedConverter['options'][$optionName] = $sanitizedOptionValue;
}
}
// "working" (bool)
if (isset($unsanitizedConverter['working'])) {
$sanitizedConverter['working'] = ($unsanitizedConverter['working'] === true);
}
// "deactivated" (bool)
if (isset($unsanitizedConverter['deactivated'])) {
$sanitizedConverter['deactivated'] = ($unsanitizedConverter['deactivated'] === true);
}
$convertersSanitized[] = $sanitizedConverter;
}
return $convertersSanitized;
}
/**
* Get sanitized converters.
*
* @return array Sanitized array of the converters json array received in $_POST
*/
function webpexpress_getSanitizedAlterHtmlHostnameAliases() {
$index = 0;
$result = [];
while (isset($_POST['alter-html-hostname-alias-' . $index])) {
$alias = webpexpress_getSanitizedText('alter-html-hostname-alias-' . $index, '');
$alias = preg_replace('#^https?\\:\\/\\/#', '', $alias);
//$alias .= 'hm';
if ($alias != '') {
$result[] = $alias;
}
$index++;
}
return $result;
}
/*
------------------------------------------------------
Create a sanitized object from the POST data
It reflects the POST data - it has same keys and values - except that the values have been sanitized.
After this, there must be no more references to $_POST
------------------------------------------------------
*/
// Sanitizing
$sanitized = [
// Force htaccess rules
'force' => isset($_POST['force']),
// Operation mode
// --------------
// Note that "operation-mode" is actually the old mode. The new mode is posted in "change-operation-mode"
'operation-mode' => webpexpress_getSanitizedChooseFromSet('operation-mode', 'varied-image-responses', [
'varied-image-responses',
'cdn-friendly',
'no-conversion',
'tweaked'
]),
'change-operation-mode' => webpexpress_getSanitizedChooseFromSet('change-operation-mode', 'varied-image-responses', [
'varied-image-responses',
'cdn-friendly',
'no-conversion',
'tweaked'
]),
// General
// --------
'image-types' => intval(webpexpress_getSanitizedChooseFromSet('image-types', '3', [
'0',
'1',
'2',
'3'
])),
'scope' => webpexpress_getSanitizedScope(),
'destination-folder' => webpexpress_getSanitizedChooseFromSet('destination-folder', 'separate', [
'separate',
'mingled',
]),
'destination-extension' => webpexpress_getSanitizedChooseFromSet('destination-extension', 'append', [
'append',
'set',
]),
'destination-structure' => webpexpress_getSanitizedChooseFromSet('destination-structure', 'doc-root', [
'doc-root',
'image-roots',
]),
'cache-control' => webpexpress_getSanitizedChooseFromSet('cache-control', 'no-header', [
'no-header',
'set',
'custom'
]),
'cache-control-max-age' => webpexpress_getSanitizedChooseFromSet('cache-control-max-age', 'one-hour', [
'one-second',
'one-minute',
'one-hour',
'one-day',
'one-week',
'one-month',
'one-year',
]),
'cache-control-public' => webpexpress_getSanitizedChooseFromSet('cache-control-public', 'public', [
'public',
'private',
]),
'cache-control-custom' => webpexpress_getSanitizedCacheControlHeader('cache-control-custom'),
'prevent-using-webps-larger-than-original' => isset($_POST['prevent-using-webps-larger-than-original']),
// Redirection rules
// -----------------
'redirect-to-existing-in-htaccess' => isset($_POST['redirect-to-existing-in-htaccess']),
'enable-redirection-to-converter' => isset($_POST['enable-redirection-to-converter']),
'only-redirect-to-converter-for-webp-enabled-browsers' => isset($_POST['only-redirect-to-converter-for-webp-enabled-browsers']),
'only-redirect-to-converter-on-cache-miss' => isset($_POST['only-redirect-to-converter-on-cache-miss']),
'do-not-pass-source-in-query-string' => isset($_POST['do-not-pass-source-in-query-string']),
'enable-redirection-to-webp-realizer' => isset($_POST['enable-redirection-to-webp-realizer']),
// Conversion options
// ------------------
'metadata' => webpexpress_getSanitizedChooseFromSet('metadata', 'none', [
'none',
'all'
]),
'jpeg-encoding' => webpexpress_getSanitizedChooseFromSet('jpeg-encoding', 'auto', [
'lossy',
'auto'
]),
'jpeg-enable-near-lossless' => webpexpress_getSanitizedChooseFromSet('jpeg-enable-near-lossless', 'on', [
'on',
'off'
]),
'quality-auto' => webpexpress_getSanitizedChooseFromSet('quality-auto', 'auto_on', [
'auto_on',
'auto_off'
]),
'max-quality' => webpexpress_getSanitizedQuality('max-quality', 80),
'jpeg-near-lossless' => webpexpress_getSanitizedQuality('jpeg-near-lossless', 60),
'quality-specific' => webpexpress_getSanitizedQuality('quality-specific', 70),
'quality-fallback' => webpexpress_getSanitizedQuality('quality-fallback', 70),
'png-near-lossless' => webpexpress_getSanitizedQuality('png-near-lossless', 60),
'png-enable-near-lossless' => webpexpress_getSanitizedChooseFromSet('png-enable-near-lossless', 'on', [
'on',
'off'
]),
'png-quality' => webpexpress_getSanitizedQuality('png-quality', 85),
'png-encoding' => webpexpress_getSanitizedChooseFromSet('png-encoding', 'auto', [
'lossless',
'auto'
]),
'alpha-quality' => webpexpress_getSanitizedQuality('alpha-quality', 80),
'convert-on-upload' => isset($_POST['convert-on-upload']),
'enable-logging' => isset($_POST['enable-logging']),
'converters' => webpexpress_getSanitizedConverters(),
// Serve options
// ---------------
'fail' => webpexpress_getSanitizedChooseFromSet('fail', 'original', [
'original',
'404',
'report'
]),
'success-response' => webpexpress_getSanitizedChooseFromSet('success-response', 'original', [
'original',
'converted',
]),
// Alter html
// ----------
'alter-html-enabled' => isset($_POST['alter-html-enabled']),
'alter-html-only-for-webp-enabled-browsers' => isset($_POST['alter-html-only-for-webp-enabled-browsers']),
'alter-html-add-picturefill-js' => isset($_POST['alter-html-add-picturefill-js']),
'alter-html-for-webps-that-has-yet-to-exist' => isset($_POST['alter-html-for-webps-that-has-yet-to-exist']),
'alter-html-replacement' => webpexpress_getSanitizedChooseFromSet('alter-html-replacement', 'picture', [
'picture',
'url'
]),
'alter-html-hooks' => webpexpress_getSanitizedChooseFromSet('alter-html-hooks', 'content-hooks', [
'content-hooks',
'ob'
]),
'alter-html-hostname-aliases' => webpexpress_getSanitizedAlterHtmlHostnameAliases(),
// Web service
// ------------
'web-service-enabled' => isset($_POST['web-service-enabled']),
'whitelist' => webpexpress_getSanitizedWhitelist(),
];
if (!Paths::canUseDocRootForRelPaths()) {
$sanitized['destination-structure'] = 'image-roots';
}
/*
------------------------------------------------------
Lets begin working on the data.
Remember: Use $sanitized instead of $_POST
------------------------------------------------------
*/
$config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
$oldConfig = $config;
// Set options that are available in all operation modes
$config = array_merge($config, [
'operation-mode' => $sanitized['operation-mode'],
'scope' => $sanitized['scope'],
'image-types' => $sanitized['image-types'],
'forward-query-string' => true,
]);
// Set options that are available in ALL operation modes
$config['cache-control'] = $sanitized['cache-control'];
switch ($sanitized['cache-control']) {
case 'no-header':
break;
case 'set':
$config['cache-control-max-age'] = $sanitized['cache-control-max-age'];
$config['cache-control-public'] = ($sanitized['cache-control-public'] == 'public');
break;
case 'custom':
$config['cache-control-custom'] = $sanitized['cache-control-custom'];
break;
}
$config['prevent-using-webps-larger-than-original'] = $sanitized['prevent-using-webps-larger-than-original'];
// Alter HTML
$config['alter-html'] = [];
$config['alter-html']['enabled'] = $sanitized['alter-html-enabled'];
if ($sanitized['alter-html-replacement'] == 'url') {
$config['alter-html']['only-for-webp-enabled-browsers'] = $sanitized['alter-html-only-for-webp-enabled-browsers'];
} else {
$config['alter-html']['only-for-webp-enabled-browsers'] = false;
}
if ($sanitized['alter-html-replacement'] == 'picture') {
$config['alter-html']['alter-html-add-picturefill-js'] = $sanitized['alter-html-add-picturefill-js'];
}
if ($sanitized['operation-mode'] != 'no-conversion') {
$config['alter-html']['only-for-webps-that-exists'] = (!$sanitized['alter-html-for-webps-that-has-yet-to-exist']);
} else {
$config['alter-html']['only-for-webps-that-exists'] = true;
}
$config['alter-html']['replacement'] = $sanitized['alter-html-replacement'];
$config['alter-html']['hooks'] = $sanitized['alter-html-hooks'];
$config['alter-html']['hostname-aliases'] = $sanitized['alter-html-hostname-aliases'];
// Set options that are available in all operation modes, except the "no-conversion" mode
if ($sanitized['operation-mode'] != 'no-conversion') {
$config['enable-redirection-to-webp-realizer'] = $sanitized['enable-redirection-to-webp-realizer'];
// Metadata
// --------
$config['metadata'] = $sanitized['metadata'];
// Jpeg
// --------
$config['jpeg-encoding'] = $sanitized['jpeg-encoding'];
$auto = ($sanitized['quality-auto'] == 'auto_on');
$config['quality-auto'] = $auto;
if ($auto) {
$config['max-quality'] = $sanitized['max-quality'];
$config['quality-specific'] = $sanitized['quality-fallback'];
} else {
$config['max-quality'] = 80;
$config['quality-specific'] = $sanitized['quality-specific'];
}
$config['jpeg-enable-near-lossless'] = ($sanitized['jpeg-enable-near-lossless'] == 'on');
$config['jpeg-near-lossless'] = $sanitized['jpeg-near-lossless'];
// Png
// --------
$config['png-encoding'] = $sanitized['png-encoding'];
$config['png-quality'] = $sanitized['png-quality'];
$config['png-enable-near-lossless'] = ($sanitized['png-enable-near-lossless'] == 'on');
$config['png-near-lossless'] = $sanitized['png-near-lossless'];
$config['alpha-quality'] = $sanitized['alpha-quality'];
// Other conversion options
$config['convert-on-upload'] = $sanitized['convert-on-upload'];
$config['enable-logging'] = $sanitized['enable-logging'];
// Web Service
// -------------
$config['web-service'] = [
'enabled' => $sanitized['web-service-enabled'],
'whitelist' => $sanitized['whitelist']
];
// Set existing api keys in web service (we removed them from the json array, for security purposes)
if (isset($oldConfig['web-service']['whitelist'])) {
foreach ($oldConfig['web-service']['whitelist'] as $existingWhitelistEntry) {
foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
if ($whitelistEntry['uid'] == $existingWhitelistEntry['uid']) {
$whitelistEntry['api-key'] = $existingWhitelistEntry['api-key'];
}
}
}
}
// Set changed api keys
foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
if (!empty($whitelistEntry['new-api-key'])) {
$whitelistEntry['api-key'] = $whitelistEntry['new-api-key'];
unset($whitelistEntry['new-api-key']);
}
}
// Converters
// -------------
$config['converters'] = $sanitized['converters'];
// remove converter ids
foreach ($config['converters'] as &$converter) {
unset ($converter['id']);
}
// Get existing wpc api key from old config
$existingWpcApiKey = '';
foreach ($oldConfig['converters'] as &$converter) {
if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
if (isset($converter['options']['api-key'])) {
$existingWpcApiKey = $converter['options']['api-key'];
}
}
}
// Set wpc api key in new config
// - either to the existing, or to a new
foreach ($config['converters'] as &$converter) {
if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
unset($converter['options']['_api-key-non-empty']);
if (isset($converter['options']['new-api-key'])) {
$converter['options']['api-key'] = $converter['options']['new-api-key'];
unset($converter['options']['new-api-key']);
} else {
$converter['options']['api-key'] = $existingWpcApiKey;
}
}
}
}
$config['destination-structure'] = $sanitized['destination-structure'];
switch ($sanitized['operation-mode']) {
case 'varied-image-responses':
$config = array_merge($config, [
'redirect-to-existing-in-htaccess' => $sanitized['redirect-to-existing-in-htaccess'],
'destination-folder' => $sanitized['destination-folder'],
'destination-extension' => (($sanitized['destination-folder'] == 'mingled') ? $sanitized['destination-extension'] : 'append'),
'enable-redirection-to-converter' => $sanitized['enable-redirection-to-converter'],
]);
break;
case 'cdn-friendly':
$config = array_merge($config, [
'destination-folder' => $sanitized['destination-folder'],
'destination-extension' => (($sanitized['destination-folder'] == 'mingled') ? $sanitized['destination-extension'] : 'append'),
'enable-redirection-to-converter' => $sanitized['enable-redirection-to-converter'], // PS: its called "autoconvert" in this mode
]);
break;
case 'no-conversion':
$config = array_merge($config, [
'redirect-to-existing-in-htaccess' => $sanitized['redirect-to-existing-in-htaccess'],
'destination-extension' => $sanitized['destination-extension'],
]);
break;
case 'tweaked':
$config = array_merge($config, [
'enable-redirection-to-converter' => $sanitized['enable-redirection-to-converter'],
'only-redirect-to-converter-for-webp-enabled-browsers' => $sanitized['only-redirect-to-converter-for-webp-enabled-browsers'],
'only-redirect-to-converter-on-cache-miss' => $sanitized['only-redirect-to-converter-on-cache-miss'],
'do-not-pass-source-in-query-string' => $sanitized['do-not-pass-source-in-query-string'],
'redirect-to-existing-in-htaccess' => $sanitized['redirect-to-existing-in-htaccess'],
'destination-folder' => $sanitized['destination-folder'],
'destination-extension' => (($sanitized['destination-folder'] == 'mingled') ? $sanitized['destination-extension'] : 'append'),
'fail' => $sanitized['fail'],
'success-response' => $sanitized['success-response'],
]);
break;
}
if ($sanitized['operation-mode'] != $sanitized['change-operation-mode']) {
// Operation mode changed!
$config['operation-mode'] = $sanitized['change-operation-mode'];
$config = Config::applyOperationMode($config);
if ($config['operation-mode'] == 'varied-image-responses') {
// changing to "varied image responses" mode should enable
// the redirect-to-existing-in-htaccess option
$config['redirect-to-existing-in-htaccess'] = true;
}
if ($config['operation-mode'] == 'no-conversion') {
// No conversion probably means that there are webps in the system not generated by
// webp express. Schedule a task to mark those that are bigger than originals
wp_schedule_single_event(time() + 30, 'webp_express_task_bulk_update_dummy_files');
}
}
// If we are going to save .htaccess, run and store capability tests first
// (we should only store results when .htaccess is updated as well)
if ($sanitized['force'] || HTAccessRules::doesRewriteRulesNeedUpdate($config)) {
Config::runAndStoreCapabilityTests($config);
}
$config['environment-when-config-was-saved'] = [
'doc-root-available' => PathHelper::isDocRootAvailable(),
'doc-root-resolvable' => PathHelper::isDocRootAvailableAndResolvable(),
'doc-root-usable-for-structuring' => Paths::canUseDocRootForRelPaths(),
'image-roots' => Paths::getImageRootsDef(),
'document-root' => null,
];
if (PathHelper::isDocRootAvailable()) {
$config['document-root'] = $_SERVER['DOCUMENT_ROOT'];
}
// SAVE!
// -----
$result = Config::saveConfigurationAndHTAccess($config, $sanitized['force']);
// Handle results
// ---------------
if (!$result['saved-both-config']) {
if (!$result['saved-main-config']) {
Messenger::addMessage(
'error',
'Failed saving configuration file.<br>' .
'Current file permissions are preventing WebP Express to save configuration to: "' . Paths::getConfigFileName() . '"'
);
} else {
Messenger::addMessage(
'error',
'Failed saving options file. Check file permissions<br>' .
'Tried to save to: "' . Paths::getWodOptionsFileName() . '"'
);
}
} else {
$changeFolder = ($config['destination-folder'] != $oldConfig['destination-folder']);
$changeExtension = ($config['destination-extension'] != $oldConfig['destination-extension']);
$changeStructure = ($config['destination-structure'] != $oldConfig['destination-structure']);
if ($changeFolder || $changeExtension || $changeStructure) {
$relocate = $changeFolder || $changeStructure;
$rename = $changeExtension;
$actionPastTense = '';
if ($rename && $relocate) {
$actionPastTense = 'relocated and renamed';
$actionPresentTense = 'relocate and rename';
} else {
if ($rename) {
$actionPastTense = 'renamed';
$actionPresentTense = 'rename';
} else {
$actionPastTense = 'relocated';
$actionPresentTense = 'relocate';
}
}
list($numFilesMoved, $numFilesFailedMoving) = CacheMover::move($config, $oldConfig);
if ($numFilesFailedMoving == 0) {
if ($numFilesMoved == 0) {
Messenger::addMessage(
'notice',
'No cached webp files needed to be ' . $actionPastTense
);
} else {
Messenger::addMessage(
'success',
'The webp files was ' . $actionPastTense . ' (' . $actionPastTense . ' ' . $numFilesMoved . ' images)'
);
}
} else {
if ($numFilesMoved == 0) {
Messenger::addMessage(
'warning',
'No webp files could not be ' . $actionPastTense . ' (failed to ' . $actionPresentTense . ' ' . $numFilesFailedMoving . ' images)'
);
} else {
Messenger::addMessage(
'warning',
'Some webp files could not be ' . $actionPastTense . ' (failed to ' . $actionPresentTense . ' ' . $numFilesFailedMoving . ' images, but successfully ' . $actionPastTense . ' ' . $numFilesMoved . ' images)'
);
}
}
}
if (!$result['rules-needed-update']) {
Messenger::addMessage(
'success',
'Configuration saved. Rewrite rules did not need to be updated. ' . HTAccess::testLinks($config)
);
} else {
Messenger::addMessage(
'success',
'Configuration was saved.'
);
HTAccess::showSaveRulesMessages($result['htaccess-result']);
}
}
wp_redirect(Paths::getSettingsUrl());
exit();