Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/lai-team/bv_google_vision/archive/refs/heads/main.zipRuns every uploaded image through the Google Cloud Vision API on upload, and
uses the result to tag the attachment, flag NSFW content, and — when the image
contains a recognisable landmark and has no GPS EXIF — synthesise a
geom_point so it still appears on the map.
155 of the 1542 files here are ours. index.php is the plugin; everything
under vendor/ is Composer-installed third-party code.
Requirements
- PHP 7.4+ with the gRPC extension (or it falls back to REST via Guzzle)
- A Google Cloud project with the Vision API enabled, and a service account credential — see Credentials
voyage/bv_geotagged_mediaown the downstreamgeom_pointconsumers, though this plugin writes that meta independently
Credentials
The client is constructed as new ImageAnnotatorClient() with no arguments
(index.php:57), so it uses Application Default Credentials — it reads
the path in the GOOGLE_APPLICATION_CREDENTIALS environment variable. In this
install wp-config.php sets that from a GOOG_VISION_JSON constant.
The keyfile must live outside the web root. It is a private key, and anything under the document root is fetchable over HTTP unless something explicitly blocks it. A
config/directory inside the WordPress root is not safe — denying directory listing does not deny the file itself. Put it somewhere like/etc/gcp/, ownedroot:<web-user>with mode640, and point the constant at an absolute path. If PHP cannot then read it, checkopen_basedir, SELinux and AppArmor before loosening permissions.
.gitignore blocks *credentials*.json, *service-account*.json and GCP's
default <project>-<keyid>.json naming, so a keyfile dropped into this
directory will not be committed by accident.
How it works
wp_generate_attachment_metadata (priority 10) → bv_update_imagetags():
- takes the
largeimage URL and sends it to Vision with three features —LABEL_DETECTION,LANDMARK_DETECTION,SAFE_SEARCH_DETECTION - stores raw results in
google_labelsandgoogle_landmarkspost meta - tags the attachment with labels scoring above 0.95, minus
$BAD_TAGS - adds an
NSFWtag when adult or violence likelihood isVERY_LIKELY(the score is the Vision enum 0-5 divided by 5, compared against 0.9) - if the attachment has no
geom_pointand a landmark was located, writes a GeoJSON Point built from the landmark's lat/lng
$BAD_TAGS (index.php:23-28) is a stop-list of labels too generic to be
useful — water, nature, people, adult and similar.
Why vendor/ is committed
index.php:11 does require __DIR__ . '/vendor/autoload.php', and the plugin
is deployed by copying the directory, so a checkout without vendor/ fatals
on load. composer.lock is committed alongside it, so the tree is still
reproducible with composer install. The cost is a 13 MB repository and 27
third-party packages whose updates are invisible to dependency tooling.
Those packages are old — google/cloud-vision 1.0.0, grpc/grpc 1.27.0,
guzzlehttp/guzzle 6.5.3 — and have not been refreshed since the plugin was
written. Updating them is a separate exercise with real regression risk.
Fixed in fix/upload-resilience
- Uploads no longer fatal when Vision is unavailable.
new ImageAnnotatorClient()throws when the credential is missing, unreadable or expired, andannotateImage()throws on quota, permission and network errors. Neither was caught, so any of those took down the upload request. Both are now wrapped intry/catch ( \Throwable )—Throwable, notException, because the gRPC and protobuf layers raiseErrorsubclasses — the failure is logged, and an empty result is returned so the attachment is still created, just untagged. The client is closed in afinally. Reproduced before the fix: a throwing constructor propagated out ofbv_update_imagetags()as a fatal. - Non-image uploads no longer warn or make a billable API call.
wp_generate_attachment_metadatafires for every attachment;wp_get_attachment_image_src()returnsfalsefor a PDF or video, so[0]on it wasTrying to access array offset on falseandfalsewas then sent to Vision. Confirmed before the fix: one billable call per PDF. Now guarded withwp_attachment_is_image()plus anis_array()check on the result. - Removed
txt_domain_delete_fullsize_image(). It calledunlink( wp_get_original_image_path( $media_id ) )with no existence check, no capability check and no way back. Itsadd_filterwas commented out, so it never ran — but it sat one uncommented line away from permanently deleting every original upload. Removed rather than guarded: nothing in the install referenced it, and a disk-space problem should be solved with a deliberate, reversible media cleanup, not a filter on every upload. require→require_oncefor both includes; the manualImageAnnotatorClient.phpinclude was a duplicate-class fatal waiting on the autoloader resolving the class first.- Added
defined( 'ABSPATH' ) || exit;, and$GLOBALS['BAD_TAGS']is now read throughisset()+is_array().
Known issues
- Every upload still blocks on a synchronous three-feature API call, billed per feature per image. There is no caching, no batching, no per-mime opt-out and no way to reprocess without re-uploading. Making this asynchronous is a design change, not a fix.
- Thresholds are hardcoded — 0.95 for labels, 0.8 for landmarks, 0.9 for NSFW — with no filter or setting.
$BAD_TAGSis a bare file-scope global, the same unguarded-global pattern as$special_categoriesinbv_geotagged_media.- The vendored dependencies are years out of date —
google/cloud-vision1.0.0,grpc/grpc1.27.0,guzzlehttp/guzzle6.5.3. Updating them is a separate exercise with real regression risk. index.phpstill contains one ~300-column whitespace-padded line, the same mangled-paste pathology as the otherbv_*plugins.
License
The plugin is GPL-2.0-or-later; see LICENSE. Code under vendor/
is covered by its own licences, predominantly Apache-2.0 (Google, gRPC) and
MIT (Guzzle, Monolog, PSR).
Credits
Lai Consulting Team. Previously hosted at
gitlab.com/beauvoyage/bv_google_vision.