Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48119f2034 | ||
|
|
056be0e917 | ||
|
|
426015bb12 | ||
|
|
15f88db2c4 | ||
|
|
c729e972bf | ||
|
|
200f791f40 | ||
|
|
cef52734ee | ||
|
|
47f147895d | ||
|
|
0ac0e35787 |
+12
-2
@@ -1,10 +1,10 @@
|
||||
=== WooCommerce Email Discount Manager ===
|
||||
Contributors: wedsmp
|
||||
Contributors: kristianwind
|
||||
Tags: woocommerce, discount, email, shipping, coupon
|
||||
Requires at least: 5.8
|
||||
Tested up to: 7.1
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 1.4.0
|
||||
Stable tag: 1.4.3
|
||||
WC requires at least: 6.0
|
||||
WC tested up to: 11.1.0
|
||||
License: GPLv2 or later
|
||||
@@ -96,6 +96,16 @@ The most specific match wins: exact email, then domain, then partial match.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.4.3 =
|
||||
* The plugin is now credited to Kristian Wind, with a link to where its source lives. It previously carried a placeholder name.
|
||||
|
||||
= 1.4.2 =
|
||||
* The plugin now has an icon, so it no longer shows up as a generic grey plug under Dashboard → Updates.
|
||||
* "Check again" on the updates screen really does check again. It previously reused a cached answer for up to six hours, which made a brand new version look like it had not been published.
|
||||
|
||||
= 1.4.1 =
|
||||
* The update check now builds the download address itself instead of using the one the repository reports. The repository was reporting a plain-HTTP address on a raw IP, which would have meant downloading plugin code over an unencrypted connection to an address that is free to change. Anyone on 1.4.0 gets this automatically with the next update.
|
||||
|
||||
= 1.4.0 =
|
||||
* The plugin now updates itself. WordPress checks the plugin's own release feed and offers new versions in Plugins and Dashboard → Updates, so an update no longer means uploading a zip by hand. The per-plugin "Enable auto-updates" link works too.
|
||||
* Tested against WordPress 7.1 and WooCommerce 11.1, and against the next release of each, so an update to either is checked before it reaches a shop rather than after.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="WooCommerce Email Discount Manager">
|
||||
<!-- Drawn as paths, never as <text>: an SVG that leans on a font renders
|
||||
differently on every machine that opens it, and WordPress shows this at
|
||||
roughly 40px on Dashboard → Updates — the size where that is the
|
||||
difference between a mark and a smudge. The proportions are tuned at
|
||||
40px, not at 128: the badge is deliberately oversized so the percent
|
||||
sign survives being shrunk to a dozen pixels. -->
|
||||
<rect width="128" height="128" rx="28" fill="#7f54b3"/>
|
||||
<rect x="13" y="30" width="74" height="52" rx="7" fill="#ffffff"/>
|
||||
<path d="M13 36 L50 62 L87 36" fill="none" stroke="#7f54b3" stroke-width="7"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="90" cy="90" r="32" fill="#ffffff"/>
|
||||
<g stroke="#7f54b3" stroke-width="6" fill="none" stroke-linecap="round">
|
||||
<circle cx="80" cy="80" r="6"/>
|
||||
<circle cx="100" cy="100" r="6"/>
|
||||
<path d="M76 104 L104 76"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -141,6 +141,30 @@ for readme in readme.txt README.txt; do
|
||||
|| note_fail "$readme has 'Stable tag: $stable' but the plugin header says $version."
|
||||
done
|
||||
|
||||
ci_step "no placeholders left in the plugin header"
|
||||
# The header is the plugin's business card: wp-admin shows the author and the
|
||||
# links to whoever installs it, so a placeholder there survives for as long as
|
||||
# nobody happens to look at that screen. This plugin shipped with
|
||||
# "https://example.com/" as its Plugin URI for several releases.
|
||||
#
|
||||
# It catches the generic markers only. A made-up vendor name — this one went
|
||||
# out as "WEDSMP" — looks exactly like a real one to any check, so an empty
|
||||
# field is warned about but a wrong-but-plausible value cannot be.
|
||||
before=$fails
|
||||
for field in 'Plugin Name' 'Description' 'Author' 'Author URI' 'Plugin URI'; do
|
||||
value=$(ci_plugin_header "$field")
|
||||
if [ -z "$value" ]; then
|
||||
echo "::warning::The plugin header has no '$field:'."
|
||||
continue
|
||||
fi
|
||||
case "$value" in
|
||||
*example.com*|*example.org*|*yourname*|*YOUR_*|*TODO*|*FIXME*|*changeme*|*'<'*'>'*)
|
||||
note_fail "$field is still a placeholder: $value"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[ "$fails" = "$before" ] && echo " name, description, author and links are all filled in"
|
||||
|
||||
ci_step "the compatibility headers still tell the truth"
|
||||
# Advisory, never fatal. WordPress ships a new version whether or not anything
|
||||
# here is wrong, and a build that goes red on release day for a header nobody
|
||||
|
||||
@@ -52,6 +52,31 @@ and instantiate the updater **before** any "is WooCommerce active" gate — a
|
||||
shop with WooCommerce switched off for an afternoon must not quietly stop
|
||||
receiving fixes.
|
||||
|
||||
Three things in that file are load-bearing and none of them look it:
|
||||
|
||||
- 🔴 **`offer_update()` answers even when the installed version is already
|
||||
current.** It is tempting to return `false` there — there is no update, after
|
||||
all — and that one line is what silently removes the **Enable auto-updates**
|
||||
link from the Plugins screen. `WP_Plugins_List_Table` sets
|
||||
`update-supported` only for plugins it finds in the update transient's
|
||||
`response` **or** `no_update` list; a plugin that stays silent is in neither,
|
||||
and wp-admin says "Auto-updates are not available for this plugin." The
|
||||
plugin looks perfectly configured and can never auto-update.
|
||||
`tests/smoke.php` asserts the rendered table, not the intention.
|
||||
- **Set `icons`.** Without it Dashboard → Updates shows a generic grey plug.
|
||||
Point it at the shop's own copy (`PLUGIN_URL . 'assets/icon.svg'`), not the
|
||||
release host: one fewer thing that must be reachable for wp-admin to render,
|
||||
and it cannot go mixed-content on an HTTPS shop.
|
||||
- **`Update URI` also switches wordpress.org off** for this plugin, from
|
||||
WordPress 5.8. Remove the header and a .org plugin that happens to share the
|
||||
slug could be pushed onto a shop running yours.
|
||||
|
||||
The answer is cached for six hours, which is right for the check WordPress runs
|
||||
twice a day and wrong for the **Check again** link — so `forget_on_force_check()`
|
||||
drops the cache on `force-check`. Without it, someone who has just published a
|
||||
release clicks that link, sees nothing, and concludes the whole mechanism is
|
||||
broken.
|
||||
|
||||
**6. Make the Gitea repository public**, or point `Update URI` at a separate
|
||||
public releases-only repository. Every shop reads that feed without
|
||||
credentials; `ci/publish-release.sh` fails the release with this instruction if
|
||||
@@ -74,6 +99,9 @@ it cannot.
|
||||
credentials to prove a shop can download it.
|
||||
- `ci/update-smoke.sh` — install the previous version into a throwaway shop and
|
||||
let WordPress update it by itself. The only check that proves the whole chain.
|
||||
- Assertions that the **Enable auto-updates** link is actually offered, built
|
||||
from the real `WP_Plugins_List_Table`, and that the update offer carries an
|
||||
icon that exists in the release.
|
||||
- `ci/workflows-in-sync.sh` — fails when the two workflow roots drift, which is
|
||||
the only way to notice that a workflow stopped running on Gitea.
|
||||
|
||||
|
||||
@@ -155,11 +155,57 @@ That means all of this works with no extra code:
|
||||
|
||||
- the update notice on **Plugins** and **Dashboard → Updates**
|
||||
- the **update now** link
|
||||
- the per-plugin **Enable auto-updates** link — WordPress builds that from the
|
||||
plugins it has an answer for, which is why the updater answers even when the
|
||||
installed version is already current
|
||||
- the per-plugin **Enable auto-updates** link
|
||||
- `wp plugin update woocommerce-email-discount-manager` over WP-CLI
|
||||
|
||||
### Turning on automatic updates
|
||||
|
||||
Plugins screen → the **Automatic updates** column on the right → **Enable
|
||||
auto-updates**. WordPress then installs new versions by itself on its
|
||||
twice-daily check.
|
||||
|
||||
That link is not free for a plugin outside wordpress.org.
|
||||
`WP_Plugins_List_Table` marks a plugin as `update-supported` only if it finds
|
||||
it in the update transient's `response` list (an update is waiting) or its
|
||||
`no_update` list (we were asked, there is none):
|
||||
|
||||
```php
|
||||
// wp-admin/includes/class-wp-plugins-list-table.php
|
||||
$plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
|
||||
$plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
|
||||
} elseif ( empty( $plugin_data['update-supported'] ) ) {
|
||||
$plugin_data['update-supported'] = false;
|
||||
```
|
||||
|
||||
A plugin in neither list gets "Auto-updates are not available for this plugin"
|
||||
and no toggle — while looking entirely healthy otherwise. That is why
|
||||
`offer_update()` answers even when the installed version is already current:
|
||||
the answer lands in `no_update`, and the link stays. `tests/smoke.php` builds
|
||||
the real list table and asserts the flag rather than trusting the reasoning.
|
||||
|
||||
### "Check again" really checks again
|
||||
|
||||
The feed is cached for six hours. That is right for the check WordPress runs on
|
||||
its own twice a day, and wrong for a link whose entire promise is that it looks
|
||||
*now* — so the cache is dropped when wp-admin is loaded with `force-check`.
|
||||
Without that, publishing a release and then clicking **Check again** shows
|
||||
nothing for up to six hours, which reads exactly like a broken updater.
|
||||
|
||||
From the command line, `wp transient delete wc_edm_release_feed` does the same.
|
||||
|
||||
### The icon
|
||||
|
||||
`assets/icon.svg`, handed to WordPress in the offer's `icons` key. Without it
|
||||
Dashboard → Updates shows a generic grey plug. WordPress takes the first of
|
||||
`svg`, `2x`, `1x`, `default` that is set.
|
||||
|
||||
It is served from the shop's own copy of the plugin, not from the release host:
|
||||
one fewer thing that has to be reachable for wp-admin to render correctly, and
|
||||
it cannot go mixed-content on an HTTPS shop. The mark is drawn as paths rather
|
||||
than `<text>`, because an SVG that leans on a font renders differently
|
||||
everywhere — and this one is shown at about 40px, which is the size where that
|
||||
matters.
|
||||
|
||||
`Update URI` earns its place a second way: since WordPress 5.8, a plugin that
|
||||
declares one is **never asked about on wordpress.org**. Without it, a .org
|
||||
plugin that happened to share this slug could be pushed onto a shop running
|
||||
|
||||
@@ -55,6 +55,9 @@ class WC_EDM_Updater {
|
||||
// update, and the plugins screen keeps offering a version already
|
||||
// installed.
|
||||
add_action( 'upgrader_process_complete', array( $this, 'forget' ), 10, 0 );
|
||||
|
||||
// And "Check again" has to actually check again.
|
||||
add_action( 'admin_init', array( $this, 'forget_on_force_check' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +126,18 @@ class WC_EDM_Updater {
|
||||
'tested' => $release['tested'],
|
||||
'requires' => isset( $plugin_data['RequiresWP'] ) ? $plugin_data['RequiresWP'] : '',
|
||||
'requires_php' => isset( $plugin_data['RequiresPHP'] ) ? $plugin_data['RequiresPHP'] : '',
|
||||
// Without this, Dashboard → Updates shows the generic grey plug
|
||||
// next to the plugin. WordPress takes the first of svg, 2x, 1x,
|
||||
// default that is set.
|
||||
//
|
||||
// Served from the shop's own copy of the plugin rather than from
|
||||
// the release host: it is one fewer thing that has to be up and
|
||||
// reachable for wp-admin to render correctly, it cannot go mixed
|
||||
// content on an HTTPS shop, and the icon is in the zip anyway.
|
||||
'icons' => array(
|
||||
'svg' => WC_EDM_PLUGIN_URL . 'assets/icon.svg',
|
||||
'default' => WC_EDM_PLUGIN_URL . 'assets/icon.svg',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,6 +148,27 @@ class WC_EDM_Updater {
|
||||
delete_transient( self::TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Honour the "Check again" link on Dashboard → Updates.
|
||||
*
|
||||
* The six-hour cache is right for the check WordPress runs twice a day on
|
||||
* its own, and wrong for a button whose entire promise is that it looks
|
||||
* right now. Without this, someone who has just published a release clicks
|
||||
* it, sees nothing, and concludes the update mechanism is broken.
|
||||
*
|
||||
* No nonce to verify: this reads a link WordPress itself renders, and the
|
||||
* only effect is discarding a cache — the worst a forged request achieves
|
||||
* is one extra HTTP GET.
|
||||
*/
|
||||
public function forget_on_force_check() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_GET['force-check'] ) || ! current_user_can( 'update_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the newest release from the repository.
|
||||
*
|
||||
|
||||
@@ -292,6 +292,19 @@ wc_edm_is(
|
||||
'woocommerce-email-discount-manager',
|
||||
is_array( $offered ) ? $offered['slug'] : null
|
||||
);
|
||||
// Dashboard → Updates falls back to a generic grey plug when this is missing,
|
||||
// and a 404 here is invisible until someone looks at that screen.
|
||||
wc_edm_ok(
|
||||
'the offer carries an icon',
|
||||
is_array( $offered ) && ! empty( $offered['icons']['svg'] ),
|
||||
'without it wp-admin shows the generic plugin placeholder'
|
||||
);
|
||||
wc_edm_ok(
|
||||
'and the icon is a file that is actually in the release',
|
||||
file_exists( WC_EDM_PLUGIN_DIR . 'assets/icon.svg' ),
|
||||
'the offer would point wp-admin at a 404'
|
||||
);
|
||||
|
||||
wc_edm_ok(
|
||||
'the offer names a downloadable package',
|
||||
is_array( $offered ) && ! empty( $offered['package'] ),
|
||||
@@ -314,6 +327,81 @@ set_transient(
|
||||
$offered = wc_edm()->updater->offer_update( false, get_plugin_data( WC_EDM_PLUGIN_FILE, false, false ) );
|
||||
wc_edm_is( 'the current version is answered too, not passed over', WC_EDM_VERSION, is_array( $offered ) ? $offered['version'] : null );
|
||||
|
||||
/*
|
||||
* The "Enable auto-updates" link on the Plugins screen.
|
||||
*
|
||||
* It is not automatic for a plugin that does not come from wordpress.org.
|
||||
* WP_Plugins_List_Table sets 'update-supported' => true only for plugins it
|
||||
* finds in the update transient's `response` list (an update is waiting) or
|
||||
* its `no_update` list (we asked, there is none). A plugin in neither gets
|
||||
* "Auto-updates are not available for this plugin." and no toggle.
|
||||
*
|
||||
* That is the whole reason offer_update() answers even when the installed
|
||||
* version is already current. Staying silent there is how a plugin ends up
|
||||
* unable to auto-update while looking perfectly configured — so this asserts
|
||||
* the outcome, not the intention.
|
||||
*
|
||||
* The feed stays stubbed through the transient above: this is about what
|
||||
* WordPress does with our answer, and a test that needs the internet fails for
|
||||
* reasons that have nothing to do with it.
|
||||
*/
|
||||
// "Check again" must discard the six-hour cache, or the button lies: someone
|
||||
// who has just published a release clicks it, sees nothing, and concludes the
|
||||
// whole mechanism is broken.
|
||||
set_transient( WC_EDM_Updater::TRANSIENT, array( 'version' => '1.0.0', 'package' => 'x', 'url' => 'x', 'tested' => '' ), 600 );
|
||||
wp_set_current_user( 1 );
|
||||
$_GET['force-check'] = '1';
|
||||
wc_edm()->updater->forget_on_force_check();
|
||||
unset( $_GET['force-check'] );
|
||||
wc_edm_ok( 'Check again discards the cached feed', false === get_transient( WC_EDM_Updater::TRANSIENT ) );
|
||||
|
||||
// And a visitor with no business updating plugins does not get to clear it.
|
||||
set_transient( WC_EDM_Updater::TRANSIENT, array( 'version' => '1.0.0', 'package' => 'x', 'url' => 'x', 'tested' => '' ), 600 );
|
||||
wp_set_current_user( 0 );
|
||||
$_GET['force-check'] = '1';
|
||||
wc_edm()->updater->forget_on_force_check();
|
||||
unset( $_GET['force-check'] );
|
||||
wc_edm_ok( 'but a caller who cannot update plugins does not clear it', is_array( get_transient( WC_EDM_Updater::TRANSIENT ) ) );
|
||||
delete_transient( WC_EDM_Updater::TRANSIENT );
|
||||
|
||||
// Rendering the list table in a CLI context makes core itself emit a
|
||||
// strlen(null) deprecation from class-wp-plugins-list-table.php. It is
|
||||
// WordPress's, not ours, which is why the deprecation check filters on this
|
||||
// plugin's directory rather than failing on everything it sees.
|
||||
delete_site_transient( 'update_plugins' );
|
||||
wp_update_plugins();
|
||||
$transient = get_site_transient( 'update_plugins' );
|
||||
$basename = plugin_basename( WC_EDM_PLUGIN_FILE );
|
||||
|
||||
wc_edm_ok(
|
||||
'WordPress files the plugin under response or no_update',
|
||||
isset( $transient->response[ $basename ] ) || isset( $transient->no_update[ $basename ] ),
|
||||
'in neither, so wp-admin would say "Auto-updates are not available for this plugin"'
|
||||
);
|
||||
|
||||
// And the list table itself, which is what the shop owner actually looks at.
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-plugins-list-table.php';
|
||||
wp_set_current_user( 1 );
|
||||
set_current_screen( 'plugins' );
|
||||
|
||||
$table = new WP_Plugins_List_Table( array( 'screen' => get_current_screen() ) );
|
||||
$table->prepare_items();
|
||||
|
||||
$row = null;
|
||||
foreach ( $table->items as $file => $data ) {
|
||||
if ( $file === $basename ) {
|
||||
$row = $data;
|
||||
}
|
||||
}
|
||||
|
||||
wc_edm_ok( 'the plugin is on the Plugins screen at all', null !== $row );
|
||||
wc_edm_ok(
|
||||
'and WordPress marks it as supporting updates',
|
||||
$row && ! empty( $row['update-supported'] ),
|
||||
'without this the auto-update column reads "Auto-updates are not available for this plugin."'
|
||||
);
|
||||
|
||||
delete_transient( WC_EDM_Updater::TRANSIENT );
|
||||
|
||||
/* --------------------------------------------------------------- verdict -- */
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* Plugin URI: https://gitea.nolimit.dk/kw/woocommerce-email-discount-manager
|
||||
* Update URI: https://gitea.nolimit.dk/kw/woocommerce-email-discount-manager
|
||||
* Description: Apply discounts and show/hide shipping methods based on the customer's email address (full address, domain or partial match).
|
||||
* Version: 1.4.0
|
||||
* Author: WEDSMP
|
||||
* Version: 1.4.3
|
||||
* Author: Kristian Wind
|
||||
* Author URI: https://gitea.nolimit.dk/kw
|
||||
* License: GPL-2.0-or-later
|
||||
* Text Domain: wc-email-discount
|
||||
* Requires at least: 5.8
|
||||
@@ -27,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define( 'WC_EDM_VERSION', '1.4.0' );
|
||||
define( 'WC_EDM_VERSION', '1.4.3' );
|
||||
define( 'WC_EDM_PLUGIN_FILE', __FILE__ );
|
||||
define( 'WC_EDM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'WC_EDM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
||||
Reference in New Issue
Block a user