Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db3658c422 | ||
|
|
6de5299323 | ||
|
|
43816fdecc | ||
|
|
6232333918 | ||
|
|
a1f6e54f9d |
+4
-1
@@ -4,7 +4,7 @@ Tags: woocommerce, discount, email, shipping, coupon
|
|||||||
Requires at least: 5.8
|
Requires at least: 5.8
|
||||||
Tested up to: 7.1
|
Tested up to: 7.1
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Stable tag: 1.4.3
|
Stable tag: 1.4.4
|
||||||
WC requires at least: 6.0
|
WC requires at least: 6.0
|
||||||
WC tested up to: 11.1.0
|
WC tested up to: 11.1.0
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
@@ -96,6 +96,9 @@ The most specific match wins: exact email, then domain, then partial match.
|
|||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.4.4 =
|
||||||
|
* A new version now shows up within about fifteen minutes instead of being hidden behind a six-hour cache. The plugin was holding on to its own answer for longer than WordPress waits before asking again, so a release could be published and the shop shown nothing.
|
||||||
|
|
||||||
= 1.4.3 =
|
= 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.
|
* The plugin is now credited to Kristian Wind, with a link to where its source lives. It previously carried a placeholder name.
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -18,7 +18,14 @@ while IFS= read -r line; do
|
|||||||
[ -n "$line" ] && patterns+=("${line%/}")
|
[ -n "$line" ] && patterns+=("${line%/}")
|
||||||
done < .distignore
|
done < .distignore
|
||||||
|
|
||||||
git ls-files | while IFS= read -r file; do
|
# -z, not plain `git ls-files`: git QUOTES any path containing a non-ASCII
|
||||||
|
# byte, so "TESTMILJØ.md" comes out as the literal 14-character string
|
||||||
|
# "TESTMILJ\303\230.md". That form matches no pattern in .distignore, so the
|
||||||
|
# file is kept — an internal document walking straight into the release zip
|
||||||
|
# with nothing said. (-z also covers paths with spaces.) Found in a sibling
|
||||||
|
# plugin that has exactly such a filename; this repo has none today, which is
|
||||||
|
# precisely why it would have sat here until someone added one.
|
||||||
|
git ls-files -z | while IFS= read -r -d '' file; do
|
||||||
keep=1
|
keep=1
|
||||||
for p in "${patterns[@]}"; do
|
for p in "${patterns[@]}"; do
|
||||||
# A bare name excludes the file or directory wherever it appears, which is
|
# A bare name excludes the file or directory wherever it appears, which is
|
||||||
|
|||||||
@@ -46,33 +46,51 @@ ci_plugin_header() {
|
|||||||
"$(ci_repo_root)/$PLUGIN_MAIN" | head -1 | sed -E 's/[[:space:]]+$//'
|
"$(ci_repo_root)/$PLUGIN_MAIN" | head -1 | sed -E 's/[[:space:]]+$//'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Run a shell script against a checkout of this repository, inside a container.
|
||||||
|
#
|
||||||
|
# The repository goes in over a tarball and `docker cp`, never a bind mount: a
|
||||||
|
# Gitea Actions job runs inside a container talking to the HOST's docker
|
||||||
|
# daemon, so a host path here lands on an empty directory nobody created and
|
||||||
|
# the check passes by checking nothing. The symptom reads like a typo in a path.
|
||||||
|
#
|
||||||
|
# Two details are load-bearing. The script is copied in as a FILE rather than
|
||||||
|
# interpolated into `sh -c`, so it can contain $, quotes and newlines without
|
||||||
|
# being mangled — which matters the moment it is more than one command. And
|
||||||
|
# --entrypoint sh is not decoration: some images (composer:2, for one) have an
|
||||||
|
# entrypoint that swallows its arguments.
|
||||||
|
#
|
||||||
|
# Taken from the subscriptions port, which needed it for composer.
|
||||||
|
ci_in_container() { # image script-text
|
||||||
|
local image="$1" script="$2"
|
||||||
|
local dir cid code=0
|
||||||
|
|
||||||
|
dir=$(mktemp -d -t wpci-XXXXXX)
|
||||||
|
( cd "$(ci_repo_root)" && git ls-files -z | tar --null -T - -cf "$dir/src.tar" )
|
||||||
|
printf '%s\n' "$script" > "$dir/run.sh"
|
||||||
|
|
||||||
|
cid=$(docker create --entrypoint sh -w /src "$image" \
|
||||||
|
-c 'mkdir -p /src && tar -xf /tmp/src.tar -C /src && exec sh /tmp/run.sh')
|
||||||
|
docker cp "$dir/src.tar" "$cid:/tmp/src.tar" >/dev/null
|
||||||
|
docker cp "$dir/run.sh" "$cid:/tmp/run.sh" >/dev/null
|
||||||
|
docker start -a "$cid" || code=$?
|
||||||
|
docker rm -f "$cid" >/dev/null
|
||||||
|
rm -rf "$dir"
|
||||||
|
return "$code"
|
||||||
|
}
|
||||||
|
|
||||||
# Run one of this repository's PHP scripts.
|
# Run one of this repository's PHP scripts.
|
||||||
#
|
#
|
||||||
# Uses the local php when there is one — a laptop, GitHub's hosted runners —
|
# Uses the local php when there is one — a laptop, GitHub's hosted runners —
|
||||||
# and a container otherwise. The Gitea runner image has no php, and losing a
|
# and a container otherwise. The Gitea runner image has no php, and losing a
|
||||||
# build to "php: command not found" after everything else passed is a silly way
|
# build to "php: command not found" after everything else passed is a silly way
|
||||||
# to spend an afternoon. Measured: it happened on the very first Gitea run.
|
# to spend an afternoon. Measured: it happened on the very first Gitea run.
|
||||||
#
|
|
||||||
# The repository goes in over a tarball and `docker cp`, never a bind mount: a
|
|
||||||
# Gitea Actions job runs inside a container talking to the HOST's docker
|
|
||||||
# daemon, so a host path here lands on an empty directory nobody created.
|
|
||||||
ci_php() {
|
ci_php() {
|
||||||
if command -v php >/dev/null 2>&1; then
|
if command -v php >/dev/null 2>&1; then
|
||||||
php "$@"
|
php "$@"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local tarball cid code=0
|
ci_in_container "php:8.4-cli" "exec php $(printf '%q ' "$@")"
|
||||||
tarball=$(mktemp -t wpci-php-XXXXXX.tar)
|
|
||||||
( cd "$(ci_repo_root)" && git ls-files -z | tar --null -T - -cf "$tarball" )
|
|
||||||
|
|
||||||
cid=$(docker create "php:8.4-cli" sh -c \
|
|
||||||
"mkdir -p /src && tar -xf /tmp/src.tar -C /src && cd /src && exec php $(printf '%q ' "$@")")
|
|
||||||
docker cp "$tarball" "$cid:/tmp/src.tar" >/dev/null
|
|
||||||
docker start -a "$cid" || code=$?
|
|
||||||
docker rm -f "$cid" >/dev/null
|
|
||||||
rm -f "$tarball"
|
|
||||||
return "$code"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ci_step() { printf '\n\033[1m→ %s\033[0m\n' "$*"; }
|
ci_step() { printf '\n\033[1m→ %s\033[0m\n' "$*"; }
|
||||||
|
|||||||
+53
-2
@@ -65,6 +65,35 @@ tested_wc=$(ci_plugin_header 'WC tested up to')
|
|||||||
body=$(printf '%s\n\n---\n\nTested up to: %s\nWC tested up to: %s\n' \
|
body=$(printf '%s\n\n---\n\nTested up to: %s\nWC tested up to: %s\n' \
|
||||||
"$notes" "${tested_wp:-unknown}" "${tested_wc:-unknown}")
|
"$notes" "${tested_wp:-unknown}" "${tested_wc:-unknown}")
|
||||||
|
|
||||||
|
# Read one top-level field out of a JSON response.
|
||||||
|
#
|
||||||
|
# Not sed. A greedy `.*"id":([0-9]+)` matches the LAST id in the payload, and a
|
||||||
|
# Gitea release carries both an embedded "author" object and, once anything is
|
||||||
|
# attached, an "assets" array — each with an id of its own. Measured on this
|
||||||
|
# repo: the four published releases have ids 416/417/419/421, and the greedy
|
||||||
|
# pattern returns 1/2/3/4, which are the asset ids.
|
||||||
|
#
|
||||||
|
# Those four releases were nonetheless published correctly, and only by luck:
|
||||||
|
# the author id on this instance is **-1**, and the minus sign stops `[0-9]+`
|
||||||
|
# from matching, so on a create response — which has no assets yet — the last
|
||||||
|
# numeric id happened to be the release's. A real author id, or a re-run
|
||||||
|
# against a release that already has its zip, and it picks the wrong number.
|
||||||
|
#
|
||||||
|
# Found by the session porting this into subscriptions, where it picked the
|
||||||
|
# author id and the upload 404'd. python3 is already a required tool here.
|
||||||
|
json_field() { # field (JSON on stdin)
|
||||||
|
python3 -c '
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load( sys.stdin )
|
||||||
|
except Exception:
|
||||||
|
sys.exit( 0 )
|
||||||
|
value = data.get( sys.argv[1] )
|
||||||
|
if value is not None:
|
||||||
|
print( value )
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
api() { # method path [data]
|
api() { # method path [data]
|
||||||
curl -fsS -X "$1" \
|
curl -fsS -X "$1" \
|
||||||
-H "Authorization: token $GH_TOKEN" \
|
-H "Authorization: token $GH_TOKEN" \
|
||||||
@@ -94,20 +123,42 @@ print(json.dumps({
|
|||||||
# A re-run of a release must not fail on "already exists".
|
# A re-run of a release must not fail on "already exists".
|
||||||
existing=$(curl -fsS -H "Authorization: token $GH_TOKEN" \
|
existing=$(curl -fsS -H "Authorization: token $GH_TOKEN" \
|
||||||
"$GITHUB_API_URL/repos/$owner/$repo/releases/tags/$TAG" 2>/dev/null || true)
|
"$GITHUB_API_URL/repos/$owner/$repo/releases/tags/$TAG" 2>/dev/null || true)
|
||||||
id=$(printf '%s' "$existing" | sed -n -E 's/.*"id":[[:space:]]*([0-9]+).*/\1/p' | head -1)
|
id=$(printf '%s' "$existing" | json_field id)
|
||||||
|
|
||||||
if [ -z "$id" ]; then
|
if [ -z "$id" ]; then
|
||||||
created=$(api POST "$GITHUB_API_URL/repos/$owner/$repo/releases" "$payload")
|
created=$(api POST "$GITHUB_API_URL/repos/$owner/$repo/releases" "$payload")
|
||||||
id=$(printf '%s' "$created" | sed -n -E 's/.*"id":[[:space:]]*([0-9]+).*/\1/p' | head -1)
|
id=$(printf '%s' "$created" | json_field id)
|
||||||
fi
|
fi
|
||||||
[ -n "$id" ] || { echo "Could not create or find the release for $TAG." >&2; exit 1; }
|
[ -n "$id" ] || { echo "Could not create or find the release for $TAG." >&2; exit 1; }
|
||||||
echo " release id $id"
|
echo " release id $id"
|
||||||
|
|
||||||
|
# Before uploading anything to it, confirm the id really is this tag's. The
|
||||||
|
# failure being guarded against is not a 404 — that is loud — but an id that
|
||||||
|
# exists and belongs to something else, which would attach this plugin's zip
|
||||||
|
# to an unrelated release with every check still green.
|
||||||
|
release=$(curl -fsS -H "Authorization: token $GH_TOKEN" \
|
||||||
|
"$GITHUB_API_URL/repos/$owner/$repo/releases/$id")
|
||||||
|
actual=$(printf '%s' "$release" | json_field tag_name)
|
||||||
|
if [ "$actual" != "$TAG" ]; then
|
||||||
|
echo "Release id $id is tagged '$actual', not '$TAG'. Refusing to upload the zip to it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A re-run must not fail on the half that already succeeded: the upload
|
||||||
|
# rejects a duplicate asset name.
|
||||||
|
if printf '%s' "$release" | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
data = json.load( sys.stdin )
|
||||||
|
sys.exit( 0 if any( a.get( "name" ) == sys.argv[1] for a in data.get( "assets", [] ) ) else 1 )
|
||||||
|
' "$PLUGIN_SLUG.zip"; then
|
||||||
|
echo " $PLUGIN_SLUG.zip is already attached — leaving it alone"
|
||||||
|
else
|
||||||
curl -fsS -X POST \
|
curl -fsS -X POST \
|
||||||
-H "Authorization: token $GH_TOKEN" \
|
-H "Authorization: token $GH_TOKEN" \
|
||||||
-F "attachment=@$zip" \
|
-F "attachment=@$zip" \
|
||||||
"$GITHUB_API_URL/repos/$owner/$repo/releases/$id/assets?name=$PLUGIN_SLUG.zip" >/dev/null
|
"$GITHUB_API_URL/repos/$owner/$repo/releases/$id/assets?name=$PLUGIN_SLUG.zip" >/dev/null
|
||||||
echo " attached $PLUGIN_SLUG.zip"
|
echo " attached $PLUGIN_SLUG.zip"
|
||||||
|
fi
|
||||||
|
|
||||||
release_api="$GITHUB_API_URL/repos/$owner/$repo/releases/latest"
|
release_api="$GITHUB_API_URL/repos/$owner/$repo/releases/latest"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+4
-2
@@ -76,11 +76,13 @@ before=$fails
|
|||||||
if git ls-files -z '*.php' | xargs -0 grep -nP '<\?(?!php|=|xml)' ; then
|
if git ls-files -z '*.php' | xargs -0 grep -nP '<\?(?!php|=|xml)' ; then
|
||||||
note_fail "Use <?php, not short open tags."
|
note_fail "Use <?php, not short open tags."
|
||||||
fi
|
fi
|
||||||
while read -r f; do
|
# -z: git quotes non-ASCII paths, and the quoted form is not a path that
|
||||||
|
# exists, so this loop would silently skip the file it was meant to check.
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
[ -n "$f" ] || continue
|
[ -n "$f" ] || continue
|
||||||
[ "$(tail -c 3 "$f" | tr -d '[:space:]')" = "?>" ] \
|
[ "$(tail -c 3 "$f" | tr -d '[:space:]')" = "?>" ] \
|
||||||
&& { ci_annotate error "$f" 1 "Remove the trailing ?> at the end of the file."; fails=$((fails + 1)); }
|
&& { ci_annotate error "$f" 1 "Remove the trailing ?> at the end of the file."; fails=$((fails + 1)); }
|
||||||
done <<< "$(git ls-files '*.php')"
|
done < <(git ls-files -z '*.php')
|
||||||
[ "$fails" = "$before" ] && echo " clean"
|
[ "$fails" = "$before" ] && echo " clean"
|
||||||
|
|
||||||
ci_step "text domain"
|
ci_step "text domain"
|
||||||
|
|||||||
+35
-5
@@ -71,11 +71,14 @@ Three things in that file are load-bearing and none of them look it:
|
|||||||
WordPress 5.8. Remove the header and a .org plugin that happens to share the
|
WordPress 5.8. Remove the header and a .org plugin that happens to share the
|
||||||
slug could be pushed onto a shop running yours.
|
slug could be pushed onto a shop running yours.
|
||||||
|
|
||||||
The answer is cached for six hours, which is right for the check WordPress runs
|
🔴 **Keep the feed cache short — WordPress is already the rate limiter.** It
|
||||||
twice a day and wrong for the **Check again** link — so `forget_on_force_check()`
|
gates its own check by screen: one minute on Dashboard → Updates, one hour on
|
||||||
drops the cache on `force-check`. Without it, someone who has just published a
|
the Plugins screen, 2–12 hours in the background. This filter only runs when
|
||||||
release clicks that link, sees nothing, and concludes the whole mechanism is
|
that check runs, so any cache longer than that cadence simply hides new
|
||||||
broken.
|
releases underneath it. Ours was six hours "because WordPress checks twice a
|
||||||
|
day anyway", and the symptom was a release going out and the shop being shown
|
||||||
|
nothing. It is fifteen minutes now, and `forget_on_force_check()` drops it
|
||||||
|
entirely on the **Check again** link.
|
||||||
|
|
||||||
**6. Make the Gitea repository public**, or point `Update URI` at a separate
|
**6. Make the Gitea repository public**, or point `Update URI` at a separate
|
||||||
public releases-only repository. Every shop reads that feed without
|
public releases-only repository. Every shop reads that feed without
|
||||||
@@ -105,6 +108,24 @@ it cannot.
|
|||||||
- `ci/workflows-in-sync.sh` — fails when the two workflow roots drift, which is
|
- `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.
|
the only way to notice that a workflow stopped running on Gitea.
|
||||||
|
|
||||||
|
## Running these locally: `git add` first
|
||||||
|
|
||||||
|
Every one of these scripts derives "what ships" from `git ls-files`, which
|
||||||
|
reads the **index**. An uncommitted file is therefore invisible to
|
||||||
|
`ci/dist-files.sh`, to the release zip, and to the copy that goes into the test
|
||||||
|
site.
|
||||||
|
|
||||||
|
That is deliberate — it is what catches a file that was written, referenced and
|
||||||
|
never committed, which is a release that is missing a file while every local
|
||||||
|
test passes because the file is sitting right there in the working tree. But it
|
||||||
|
also means running any of this before `git add` tests a release without your
|
||||||
|
newest file, and the failure is silent: the check does not complain, it just
|
||||||
|
measures something else. It cost two rounds here, once on
|
||||||
|
`includes/class-updater.php` and once on `assets/icon.svg`.
|
||||||
|
|
||||||
|
If a script reports something you just fixed as still missing, `git status`
|
||||||
|
before you debug the script.
|
||||||
|
|
||||||
## Two guards worth understanding before you move them
|
## Two guards worth understanding before you move them
|
||||||
|
|
||||||
**`.gitea/workflows/` hides `.github/workflows/` completely.** Gitea uses the
|
**`.gitea/workflows/` hides `.github/workflows/` completely.** Gitea uses the
|
||||||
@@ -118,6 +139,15 @@ and a release with no zip attached both produce a page that looks perfect to
|
|||||||
anyone logged in, and an update that no shop is ever offered. A check that
|
anyone logged in, and an update that no shop is ever offered. A check that
|
||||||
sends a token is the false positive that hides exactly that.
|
sends a token is the false positive that hides exactly that.
|
||||||
|
|
||||||
|
**Do not parse a forge's JSON with `sed`.** A greedy `.*"id":([0-9]+)` matches
|
||||||
|
the *last* id in the payload, and a release object carries an embedded
|
||||||
|
`author` and, once anything is attached, an `assets` array — each with an id of
|
||||||
|
its own. `ci/publish-release.sh` uses a four-line `json_field()` over `python3`
|
||||||
|
instead. Measured here: the greedy pattern returns the asset ids 1/2/3/4 for
|
||||||
|
releases whose real ids are 416/417/419/421. Four releases were published
|
||||||
|
correctly anyway, purely because this instance's author id is **-1** and the
|
||||||
|
minus sign stops `[0-9]+` from matching. That is not a safety margin.
|
||||||
|
|
||||||
**A forge's own download URL is not the address your users can reach.**
|
**A forge's own download URL is not the address your users can reach.**
|
||||||
`browser_download_url` is built from the forge's configured root URL;
|
`browser_download_url` is built from the forge's configured root URL;
|
||||||
`gitea.nolimit.dk` reports `http://57.129.89.117:25001/...` there. The updater
|
`gitea.nolimit.dk` reports `http://57.129.89.117:25001/...` there. The updater
|
||||||
|
|||||||
@@ -185,11 +185,21 @@ the real list table and asserts the flag rather than trusting the reasoning.
|
|||||||
|
|
||||||
### "Check again" really checks again
|
### "Check again" really checks again
|
||||||
|
|
||||||
The feed is cached for six hours. That is right for the check WordPress runs on
|
The feed is cached for fifteen minutes, and the cache is dropped entirely when
|
||||||
its own twice a day, and wrong for a link whose entire promise is that it looks
|
wp-admin is loaded with `force-check`.
|
||||||
*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
|
It was six hours, on the reasoning that WordPress checks twice a day anyway.
|
||||||
nothing for up to six hours, which reads exactly like a broken updater.
|
That reasoning was wrong. WordPress gates its own check **by screen**:
|
||||||
|
|
||||||
|
| Screen | How often WordPress re-checks |
|
||||||
|
| --- | --- |
|
||||||
|
| Dashboard → Updates | every minute |
|
||||||
|
| Plugins | every hour |
|
||||||
|
| Background / cron | every 2–12 hours |
|
||||||
|
|
||||||
|
This filter only runs when that check runs, so WordPress is already the rate
|
||||||
|
limiter. A cache longer than its cadence does nothing but hide new releases
|
||||||
|
underneath it — a release published, and a shop shown nothing.
|
||||||
|
|
||||||
From the command line, `wp transient delete wc_edm_release_feed` does the same.
|
From the command line, `wp transient delete wc_edm_release_feed` does the same.
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,24 @@ class WC_EDM_Updater {
|
|||||||
const TRANSIENT = 'wc_edm_release_feed';
|
const TRANSIENT = 'wc_edm_release_feed';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long that cache lives. WordPress checks for updates twice a day; a
|
* How long that cache lives.
|
||||||
* shorter window would only add requests, a longer one would delay a fix.
|
*
|
||||||
|
* Six hours, originally, on the reasoning that WordPress checks twice a
|
||||||
|
* day anyway. That reasoning was wrong, and the symptom was a shop that
|
||||||
|
* had just been told a new version existed and was shown nothing.
|
||||||
|
*
|
||||||
|
* WordPress does not check twice a day when somebody is looking. It gates
|
||||||
|
* its own check by screen — one minute on Dashboard → Updates, one hour on
|
||||||
|
* the Plugins screen, twelve hours in the background (wp_update_plugins(),
|
||||||
|
* switch on current_filter()). This filter only runs when that check runs,
|
||||||
|
* so WordPress is already the rate limiter, and a cache longer than its
|
||||||
|
* cadence does nothing but hide new releases underneath it.
|
||||||
|
*
|
||||||
|
* Fifteen minutes is left only to absorb a burst — several calls in one
|
||||||
|
* request, or other code calling wp_update_plugins() with extra stats,
|
||||||
|
* which bypasses the gate above.
|
||||||
*/
|
*/
|
||||||
const TTL = 6 * HOUR_IN_SECONDS;
|
const TTL = 15 * MINUTE_IN_SECONDS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook into the update check for our own host only.
|
* Hook into the update check for our own host only.
|
||||||
|
|||||||
+53
-7
@@ -14,7 +14,29 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$wc_edm_failures = 0;
|
/*
|
||||||
|
* The counter lives in $GLOBALS on purpose, and the four lines below prove it
|
||||||
|
* reaches the verdict before anything relies on it.
|
||||||
|
*
|
||||||
|
* `wp eval-file` runs this file through eval() INSIDE A METHOD, so the top
|
||||||
|
* level here is function scope, not global scope. A plain `$failures = 0` at
|
||||||
|
* the top and a `global $failures` in a helper below are therefore two
|
||||||
|
* different variables: the helpers increment the real global, the verdict at
|
||||||
|
* the bottom reads the local, and the local is always zero. Every assertion
|
||||||
|
* fails, every FAIL line prints, the file ends with "All assertions passed"
|
||||||
|
* and exits 0 — and CI goes green. That is not reasoning, it is what this file
|
||||||
|
* did until it was measured.
|
||||||
|
*
|
||||||
|
* This is the one file whose entire job is to be able to go red, so it checks
|
||||||
|
* that it can.
|
||||||
|
*/
|
||||||
|
$GLOBALS['wc_edm_failures'] = 0;
|
||||||
|
( static function () { ++$GLOBALS['wc_edm_failures']; } )();
|
||||||
|
if ( 1 !== $GLOBALS['wc_edm_failures'] ) {
|
||||||
|
echo "The failure counter does not reach the verdict: this file cannot go red.\n";
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
$GLOBALS['wc_edm_failures'] = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert a condition and keep going, so one run reports every failure.
|
* Assert a condition and keep going, so one run reports every failure.
|
||||||
@@ -24,14 +46,12 @@ $wc_edm_failures = 0;
|
|||||||
* @param string $detail Shown when it fails.
|
* @param string $detail Shown when it fails.
|
||||||
*/
|
*/
|
||||||
function wc_edm_ok( $what, $condition, $detail = '' ) {
|
function wc_edm_ok( $what, $condition, $detail = '' ) {
|
||||||
global $wc_edm_failures;
|
|
||||||
|
|
||||||
if ( $condition ) {
|
if ( $condition ) {
|
||||||
printf( " ok %s\n", $what );
|
printf( " ok %s\n", $what );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
++$wc_edm_failures;
|
++$GLOBALS['wc_edm_failures'];
|
||||||
printf( " FAIL %s%s\n", $what, '' !== $detail ? " — {$detail}" : '' );
|
printf( " FAIL %s%s\n", $what, '' !== $detail ? " — {$detail}" : '' );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +365,18 @@ wc_edm_is( 'the current version is answered too, not passed over', WC_EDM_VERSIO
|
|||||||
* WordPress does with our answer, and a test that needs the internet fails for
|
* WordPress does with our answer, and a test that needs the internet fails for
|
||||||
* reasons that have nothing to do with it.
|
* reasons that have nothing to do with it.
|
||||||
*/
|
*/
|
||||||
// "Check again" must discard the six-hour cache, or the button lies: someone
|
// The cache must not outlive WordPress's own check cadence. It gates by screen
|
||||||
|
// — one minute on Dashboard → Updates, one hour on the Plugins screen — and
|
||||||
|
// this filter only runs when that check runs, so anything longer here hides a
|
||||||
|
// new release underneath a cache the shop owner cannot see. This was six hours
|
||||||
|
// and produced exactly that: a release published, and a shop shown nothing.
|
||||||
|
wc_edm_ok(
|
||||||
|
'the cache is shorter than the interval WordPress re-checks on',
|
||||||
|
WC_EDM_Updater::TTL <= 15 * MINUTE_IN_SECONDS,
|
||||||
|
'TTL is ' . WC_EDM_Updater::TTL . 's; WordPress re-checks every 60s on Dashboard → Updates'
|
||||||
|
);
|
||||||
|
|
||||||
|
// "Check again" must discard the cache, or the button lies: someone
|
||||||
// who has just published a release clicks it, sees nothing, and concludes the
|
// who has just published a release clicks it, sees nothing, and concludes the
|
||||||
// whole mechanism is broken.
|
// whole mechanism is broken.
|
||||||
set_transient( WC_EDM_Updater::TRANSIENT, array( 'version' => '1.0.0', 'package' => 'x', 'url' => 'x', 'tested' => '' ), 600 );
|
set_transient( WC_EDM_Updater::TRANSIENT, array( 'version' => '1.0.0', 'package' => 'x', 'url' => 'x', 'tested' => '' ), 600 );
|
||||||
@@ -368,6 +399,21 @@ delete_transient( WC_EDM_Updater::TRANSIENT );
|
|||||||
// strlen(null) deprecation from class-wp-plugins-list-table.php. It is
|
// 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
|
// WordPress's, not ours, which is why the deprecation check filters on this
|
||||||
// plugin's directory rather than failing on everything it sees.
|
// plugin's directory rather than failing on everything it sees.
|
||||||
|
// Primed again deliberately. The force-check assertions above deleted it, and
|
||||||
|
// without this offer_update() makes a real HTTP request — which passes only
|
||||||
|
// because this plugin's feed repo happens to be public. That would make the
|
||||||
|
// assertion measure the environment rather than the code under test.
|
||||||
|
set_transient(
|
||||||
|
WC_EDM_Updater::TRANSIENT,
|
||||||
|
array(
|
||||||
|
'version' => WC_EDM_VERSION,
|
||||||
|
'package' => 'https://gitea.nolimit.dk/kw/demo/releases/download/v1/demo.zip',
|
||||||
|
'url' => 'https://gitea.nolimit.dk/kw/demo',
|
||||||
|
'tested' => '7.1',
|
||||||
|
),
|
||||||
|
600
|
||||||
|
);
|
||||||
|
|
||||||
delete_site_transient( 'update_plugins' );
|
delete_site_transient( 'update_plugins' );
|
||||||
wp_update_plugins();
|
wp_update_plugins();
|
||||||
$transient = get_site_transient( 'update_plugins' );
|
$transient = get_site_transient( 'update_plugins' );
|
||||||
@@ -407,8 +453,8 @@ delete_transient( WC_EDM_Updater::TRANSIENT );
|
|||||||
/* --------------------------------------------------------------- verdict -- */
|
/* --------------------------------------------------------------- verdict -- */
|
||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
if ( $wc_edm_failures > 0 ) {
|
if ( $GLOBALS['wc_edm_failures'] > 0 ) {
|
||||||
printf( "%d assertion(s) failed on WordPress %s.\n", $wc_edm_failures, get_bloginfo( 'version' ) );
|
printf( "%d assertion(s) failed on WordPress %s.\n", $GLOBALS['wc_edm_failures'], get_bloginfo( 'version' ) );
|
||||||
exit( 1 );
|
exit( 1 );
|
||||||
}
|
}
|
||||||
printf(
|
printf(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Plugin URI: https://gitea.nolimit.dk/kw/woocommerce-email-discount-manager
|
* Plugin URI: https://gitea.nolimit.dk/kw/woocommerce-email-discount-manager
|
||||||
* Update 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).
|
* Description: Apply discounts and show/hide shipping methods based on the customer's email address (full address, domain or partial match).
|
||||||
* Version: 1.4.3
|
* Version: 1.4.4
|
||||||
* Author: Kristian Wind
|
* Author: Kristian Wind
|
||||||
* Author URI: https://gitea.nolimit.dk/kw
|
* Author URI: https://gitea.nolimit.dk/kw
|
||||||
* License: GPL-2.0-or-later
|
* License: GPL-2.0-or-later
|
||||||
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'WC_EDM_VERSION', '1.4.3' );
|
define( 'WC_EDM_VERSION', '1.4.4' );
|
||||||
define( 'WC_EDM_PLUGIN_FILE', __FILE__ );
|
define( 'WC_EDM_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'WC_EDM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'WC_EDM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'WC_EDM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
define( 'WC_EDM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
|||||||
Reference in New Issue
Block a user