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/estrategia-web-es/estrategia-web-wp-plugin/archive/refs/heads/main.zipEstrategia Web — the WordPress sites plugin, in modules
Everything that is not design on an Estrategia Web site, in a single
plugin with modules you switch on and off in Estrategia Web → Modules.
The design (templates, parts, patterns, theme.json) belongs to the
Estrategia Web theme, which
since its 1.1.0 does not register any block (a WordPress.org requirement).
David's decision of 14-10-2026: one theme + one plugin. Up to 1.0.0 (which was never published) there were three pieces: the «Estrategia Web Site» plugin (this repo), the «Estrategia Web Forms» plugin (armazon-wp-formularios, archived) and four blocks registered by the theme. The public names of 1.0.0 do not change (blocks, options, meta keys, tables, REST routes, filters): what changed is where the code lives. The general framework is in armazon/decisiones/2026-10-01-wordpress-tema-padre-compartido.md.
- Slug, folder and text domain:
estrategia-web(estrategia-web/estrategia-web.php) · WordPress 7.1+ · PHP 8.1+ · GPLv2+ · version 2.0.1 - The repo is still called
armazon-wp-sitio; the plugin folder on a site isestrategia-web/. - No dependencies, no
node_modules, no build step.
Why a plugin and not the parent theme: what is stored and what works cannot disappear when the theme changes. What is seen belongs to the theme; what is stored and what it does, to this plugin.
Modules
| Module (folder) | What it does | Default |
|---|---|---|
site |
company details (Block Bindings, [estrategia_web_company]), the details of one location (Block Bindings) and sanitized SVG: sections 1 to 4 below; the content types (clients, testimonials, locations, projects, portfolio) are content-types templates since 17-10-2026 |
on |
content-types |
the constructor: each site's own content types, fields and taxonomies, made in the panel or from templates (section «content-types module») | on (since 17-10-2026) |
matomo |
Matomo code, cookieless or hybrid (unreleased: previously inside site; requires site, stores in its option): section 5 |
on |
consent |
the cookie notice and its API; (unreleased) cookie register with detector and table for the policy: sections 6 and 6 bis | on |
google-tags |
(unreleased) GA4, Google Ads and Tag Manager with Consent Mode v2; requires consent: section 6 ter |
off |
webp |
image sizes in WebP: section 7 | on |
external-links |
links to another domain in a new tab: section 8 | on |
forms |
block-based forms, email notifications and submission log: modules/forms/README.md | on |
carousel |
estrategia-web/carousel block |
on |
map |
estrategia-web/map block (two-click map) |
on |
whatsapp |
estrategia-web/whatsapp block and its two options |
on |
language-switcher |
estrategia-web/language-switcher block (WPML) |
on |
social |
free list of networks (Estrategia Web → Social networks, option estrategia_web_social_links) and estrategia-web/social-links block; requires site. All of it in the header of modules/social/module.php |
on (unreleased) |
local-business |
(unreleased) schema.org structured data: the Organization on the front page and a LocalBusiness on each location's page and on the pages tied to one (address, hours, coordinates), inside Rank Math's or Yoast's graph when they are there; requires site and content-types: section «local-business module» |
on |
banners |
(unreleased) image or code banners by zone (eweb_banner, eweb_banner_zone), with dates, weight and daily views and clicks counted from the browser; estrategia-web/banner-zone block; wp estrategia-web banners migrate-adrotate. All of it in the header of modules/banners/module.php |
off |
redirects |
301/302/410 redirects editable in Estrategia Web → Redirects, CSV import/export, optional 404 log, wp estrategia-web redirects import\|list\|export: modules/redirects/module.php |
off |
On by default: the ones Lavandería already used (also matomo, which
came out of site): a site that updates does not change. Anything new that
changes what the visitor sees or what the site requires (redirects,
google-tags) starts off; in Lavandería the redirects are handled by Rank
Math. A module that is off loads nothing (not its PHP, nor its blocks,
nor its assets) and deletes nothing of what it stored.
site is core since 1.7.0: always loaded (header Core: yes), not on
the Modules screen, even if a saved estrategia_web_modules lacks it.
wpml-front-page was removed in 1.7.0: it papered over a WPML set up
without its step 1 (existing_content_language_verified), with which WPML
does not translate the front page. What to check on a WPML site:
docs/web-nueva.md, «5. WPML: yes or no». A saved list
that still names it is harmless: unknown slugs are ignored.
The admin (1.7.0)
One top-level item, Estrategia Web, at position 3 (right after the Dashboard), that opens on Company details:
| Submenu | URL (admin.php?page=…) |
What |
|---|---|---|
| Company details | estrategia-web-site |
Company and Contact (and local-business's type of business) |
| Content types | estrategia-web-content-types |
content-types module |
| Social networks | estrategia-web-social |
social module |
| Settings | estrategia-web-settings&tab=… |
tabs Cookies (consent), Analytics (google-tags + matomo), Media (SVG + uploads); a tab only if its module is on |
| Redirects | estrategia-web-redirects |
redirects module |
| Modules | estrategia-web |
always last, grouped by the modules' Category |
One look for every screen (2.0.1): the same header (title + one line saying what the screen is for), every section of a form in a white box with its title, the content types builder's tabs (also in Settings and Redirects) and the save button under the boxes it saves. The pieces and how to use them: includes/admin/class-admin-ui.php; the stylesheet: assets/admin.css; the check: tests/admin-look.test.mjs.
Forms keeps its own top-level item: Forms (the list of the site's forms, where each one is, its notifications; since 2.0.0 the form posts, with «Add form» and where each one is used), Submissions, Settings.
Old URLs: estrategia-web-cookies and estrategia-web-google redirect to
their tab; estrategia-web-site, estrategia-web and
estrategia-web-forms did not change. A module adds a Settings tab or
section through the registry in
includes/admin/class-settings-page.php
(its header: how, the edge case of two screens saving one option, and the
trap); the menu order and the old URLs, in
includes/admin/class-menu.php.
How to add a module: a modules/<slug>/ folder with its module.php,
which carries a header (name, description, whether it is on by default, what
it depends on, its category). It is discovered on its own: there is no central list.
Everything you need to know —fields, the edge case of a new module on a site
that already existed, the traps— is in the header of
includes/class-modules.php. The minimal example
is modules/webp/module.php; the complete one,
modules/forms/module.php.
estrategia-web.php entry point: constants and core bootstrap
includes/ the core, always loaded:
class-modules.php the loader (HOW TO ADD A MODULE)
class-plugin.php translations and the estrategia_web_activate / _deactivate hooks
class-blocks.php autoRegister labels, editor translations
admin/class-admin-ui.php one look for every plugin screen: header, boxes, tabs, save (2.0.1)
admin/class-menu.php the «Estrategia Web» menu, its order and the old URLs (1.7.0)
admin/class-settings-page.php «Estrategia Web › Settings» and its tab registry (1.7.0)
admin/class-modules-page.php the «Estrategia Web → Modules» screen
assets/admin.css the shared admin stylesheet (2.0.1; drawn by class-admin-ui.php)
modules/<slug>/ module.php (header + bootstrap), includes/, blocks/, assets/, tests/, uninstall.php
languages/ estrategia-web.pot · estrategia-web-es_ES.po · .l10n.php (one for everything)
tests/ runner, lib.mjs, blueprint and the core tests
tools/i18n.mjs extracts (module headers too), merges and compiles
tools/open-cookie-database.mjs regenerates the Open Cookie Database extract (consent module)
wpml-config.xml WPML only reads the one at the root: all modules' entries go here
uninstall.php calls each module's uninstall.php
The contract
This is what the parent theme, the child themes and the content loading read. These names do not change in a minor version (see «Versions»).
0. Public names, with their prefix (15-10-2026)
Everything this plugin leaves in WordPress's shared space (the database, the block and post type registries, the REST API, the browser) carries a prefix, so that it does not clash with another plugin, and for WordPress.org. This table is the contract: a new name goes in here in the same commit as the code, with the prefix of its row.
| What | Prefix | Today's names |
|---|---|---|
| Post types | eweb_ (≤ 20 characters in total) |
eweb_client, eweb_testimonial, eweb_location, eweb_project, eweb_portfolio (unreleased, 1.2.0; unprefixed up to 1.1.0); eweb_content_type (private: the content-types module's definitions, one per type); eweb_form (2.0.0, not public: the forms module's form posts, REST base eweb-forms); eweb_banner (unreleased, banners module, not public, REST base eweb-banners) |
| Taxonomies | eweb_ |
eweb_client_sector, eweb_project_category, eweb_portfolio_category (same); eweb_banner_zone (unreleased, banners) |
| Blocks | estrategia-web/, estrategia-web-site/, estrategia-web-forms/ |
estrategia-web/carousel, /map, /whatsapp, /language-switcher, /social-links, /cookie-table; estrategia-web-site/consent; estrategia-web-forms/form, /text, /email, /tel, /textarea, /select, /checkbox, /submit, /notification, /form-ref (2.0.0); estrategia-web/banner-zone (unreleased) |
| Options | estrategia_web_ |
estrategia_web_site, estrategia_web_modules, estrategia_web_modules_known, estrategia_web_forms_settings, estrategia_web_forms_db_version, estrategia_web_forms_last_purge, estrategia_web_forms_migrations_log, estrategia_web_cookies, estrategia_web_cookies_settings, estrategia_web_google_tags, estrategia_web_map_google_seen, estrategia_web_redirects_settings, estrategia_web_redirects_db_version, estrategia_web_social_links, estrategia_web_uploads, estrategia_web_whatsapp_phone, estrategia_web_whatsapp_message, estrategia_web_content_types_hash, estrategia_web_content_types_wpml_pending, estrategia_web_content_types_wpml_slugs, estrategia_web_banners_db_version |
| Post meta | estrategia_web_ |
estrategia_web_banner_link, _code, _start, _end, _weight, _adrotate (banners, unreleased); those of section 3, all of them (client_url, testimonial_role and testimonial_client, from before 1.0.0, became estrategia_web_…; their read aliases went in 2.0.1, see «Names») |
| Tables | {prefix}estrategia_web_ |
estrategia_web_forms_submissions, estrategia_web_forms_definitions, estrategia_web_redirects, estrategia_web_banners_stats (unreleased) |
| Transients and cron | estrategia_web_ |
estrategia_web_redirects_report_*, estrategia_web_cs_* (detector limit); events estrategia_web_forms_purge, estrategia_web_redirects_purge |
| Cookies | estrategia_web_ |
estrategia_web_consent (the old armazon_consent is read and carried over) |
| Hooks (filters and actions) | estrategia_web_ |
those of each section; the live list: grep -rhoE "(apply_filters\|do_action)\( *'estrategia_web[a-z_]*'" modules includes |
| REST routes | estrategia-web/v1, estrategia-web-forms/v1 |
estrategia-web/v1/cookies, /cookies/seen, /content-types, /content-types/<name>, /content-types/templates/<key>, /banners/hits (unreleased); estrategia-web-forms/v1/submit, /token; the post types' routes are core's, /wp/v2/eweb_client… |
| Block Bindings sources | estrategia-web-site/, estrategia-web/ |
estrategia-web-site/company, estrategia-web-site/location, estrategia-web/content-field (content-types module) |
| Shortcodes | estrategia_web_ |
[estrategia_web_company], [estrategia_web_consent_settings], [estrategia_web_cookie_table] |
| Global functions (for themes) | estrategia_web_ |
estrategia_web_type_name() (1.2.0) |
| PHP | namespace EstrategiaWeb\ |
EstrategiaWeb\Site\…, EstrategiaWeb\Forms\…… |
| JS and events | estrategiaWeb…, estrategia-web: |
window.estrategiaWebConsent, estrategia-web:consent, estrategia-web:consent-ready, estrategia-web:form-sent |
| WP-CLI | estrategia-web |
wp estrategia-web redirects …, migrate-location-data, migrate-forms (2.0.0), migrate-form-markers (2.0.1), banners migrate-adrotate (unreleased) |
| Text domain, folder | estrategia-web |
— |
Two prefixes, on purpose: estrategia_web_ / estrategia-web for
everything else, and eweb_ only for post types and taxonomies, because
WordPress cuts a post type name at 20 characters and
estrategia_web_testimonial is 26. Why eweb_ (David, 15-10-2026):
measured across the code of every plugin on WordPress.org (veloria.dev), no
plugin uses eweb_; ew_ is used by 13 (Easy Watermark, Essential
Widgets…) and ewp_ by 9 (ElasticPress), and they were discarded; estweb_
was not liked. The post type prefix lives in a single place
(class-type-names.php) and does
not change once published on a site (it would be a whole new migration).
1. Company details — option estrategia_web_site
Estrategia Web › Company details screen (capability manage_options;
since 1.7.0 only the company and contact keys: allow_svg is saved from
Settings › Media and matomo_* from Settings › Analytics, into this same
option — each screen sends only its keys and the merge keeps the rest). A
single option (array), registered with the Settings API and exposed in REST
with its schema: GET/POST /wp/v2/settings → key estrategia_web_site.
| Key | Type | Sanitizing |
|---|---|---|
company_name |
text | no HTML |
legal_name |
text | no HTML |
tax_id |
text | no HTML |
registry |
text (0.4.0): the company registry entry exactly as the legal notice states it, without «Inscrita en» («Registro Mercantil de Mallorca, tomo 1234, folio 56, hoja PM-7890, inscripción 1.ª»). A single key on purpose: the notice writes it as a sentence, and not every company is in the Mercantile Registry | no HTML |
email |
text | valid email; otherwise the previous one stays |
phone |
text | as written («971 12 34 56») |
phone_secondary |
text | same |
whatsapp |
text | stored as digits only, with the country code («+34 600…» → 34600…); invalid → the previous one stays |
whatsapp_message |
text | no HTML |
address |
multi-line text | no HTML, keeps line breaks |
schedule |
multi-line text | same |
map_url |
URL | http(s); invalid → the previous one stays |
social |
object: facebook, instagram, linkedin, x, youtube, tiktok |
http(s) URL each. Deprecated (unreleased), goes away in 2.0.0: it is no longer on the screen; the social module copies it once into its list. Networks are written in estrategia_web_social_links |
enabled_types |
array ⊂ client, testimonial, location, project, portfolio |
any other value → 400 for the whole request |
allow_svg |
boolean | |
rewrite_slugs |
object name → slug with the names client, client_sector, testimonial, location, project, project_category, portfolio, portfolio_category (0.2.0) |
sanitize_title («Mi Cliente!» → mi-cliente); '' = the usual one; any other name → 400 for the whole request. Merged per key. When it changes, the URL rules are rebuilt |
It is saved by merging: a POST with {"estrategia_web_site": {"phone": "…"}}
changes the phone and leaves the rest. That way the content loading fills it
in by parts.
With WPML (1.4.3), registry, whatsapp_message, address and
schedule are offered in WPML › String Translation (domain
admin_texts_estrategia_web_site); untranslated, every language shows the
original. The translation reaches the site through Company::value()
(shortcode, Block Bindings, WhatsApp button) on its own. The «Company
details» screen always shows and saves the original, whatever the admin
language; /wp/v2/settings?lang=en returns the translation, so a content
load reads and writes without ?lang. The rest of the details are the
same in every language. Criterion and edge case, in
wpml-config.xml.
# REST (as an administrator; Playground or a site with an application password)
curl -u admin:APP_PASS -X POST https://web.test/wp-json/wp/v2/settings \
-H 'Content-Type: application/json' \
-d '{"estrategia_web_site":{"phone":"971 12 34 56","enabled_types":["client","location"]}}'
# WP-CLI: goes through the same sanitizing (sanitize_option), so it merges too.
# (Deduced from the code, not tested: the tests go through REST.)
wp option update estrategia_web_site '{"phone":"971 12 34 56"}' --format=json
In PHP, a theme reads a value with \EstrategiaWeb\Site\Company::value( 'phone' )
(not with get_option, which skips the filter and the derived keys).
2. Block Bindings source estrategia-web-site/company
args.key = any key above (except enabled_types, allow_svg and
rewrite_slugs), social.<network> with a dot (alias until 2.0.0: with
the social module on, the link of the first network in its list with that
icon; see class-company.php), and
four derived keys:
| Key | Example |
|---|---|
phone_href |
tel:+34971123456 (country code 34 if it has none; filter estrategia_web_site_default_country_code) |
phone_secondary_href |
same, from phone_secondary (0.2.0) |
email_href |
mailto:hola@empresa.com |
whatsapp_href |
https://wa.me/34600111222?text=Hola%2C%20quer%C3%ADa… |
It works for the content of paragraphs and headings (escaped text;
multiple lines come out with <br>) and the url of buttons and links. In
the editor the block shows the value (or [Name of the detail] if empty)
and the source appears in the «Attributes» panel with the list of details; it
cannot be edited from the block.
A paragraph with the phone:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"estrategia-web-site/company","args":{"key":"phone"}}}}} -->
<p></p>
<!-- /wp:paragraph -->
A WhatsApp button (the button text is fixed; the url, bound):
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"metadata":{"bindings":{"url":{"source":"estrategia-web-site/company","args":{"key":"whatsapp_href"}}}}} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Escríbenos por WhatsApp</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
A «Call» button with text and url bound at the same time (the button's text
has no test of its own; the url does):
<!-- wp:button {"metadata":{"bindings":{"text":{"source":"estrategia-web-site/company","args":{"key":"phone"}},"url":{"source":"estrategia-web-site/company","args":{"key":"phone_href"}}}}} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button"></a></div>
<!-- /wp:button -->
2 bis. A detail inside a sentence: [estrategia_web_company key="…"] (0.4.0)
A binding replaces the whole paragraph (WP 7.1 has no inline bindings),
so for the legal notice (LSSI, art. 10) there is a shortcode. Same keys
as the source (Company::value(): also social.<network> and the derived
ones), escaped text and on one line: address and opening hours come out
joined with «, ». A key that does not exist or is empty prints nothing.
<!-- wp:paragraph -->
<p>Titular: [estrategia_web_company key="legal_name"], con NIF [estrategia_web_company key="tax_id"] y domicilio en [estrategia_web_company key="address"]. Teléfono: [estrategia_web_company key="phone"]. Correo: [estrategia_web_company key="email"]. Inscrita en el [estrategia_web_company key="registry"].</p>
<!-- /wp:paragraph -->
- In the editor you see the shortcode as is; the value shows on the site. A detail alone in its paragraph is better as a binding (which is visible in the editor).
- It works in the content and in block templates; not in a block
attribute nor in an
href(for links, the button binding). [[estrategia_web_company …]]with double brackets is WordPress's escape: it comes out literally. Why a shortcode and not a block, in the header of modules/site/includes/class-shortcode.php.
2 ter. Block Bindings source estrategia-web-site/location (0.2.0)
The details of one location (post type location, which must be
enabled). Two ways to say which, through the args:
| args | Which location |
|---|---|
{"slug": "maioris", "key": "…"} |
that one, from any page. The identifier is the location's slug (its «permalink»), not the title |
{"key": "…"} |
the one in context: inside a Query Loop of locations, the one of each row (postId). Outside a location, empty |
Keys:
| Key | What |
|---|---|
title |
the location's name |
estrategia_web_location_address |
derived (2.0.0): the address text, from street (1-2 lines), postal code, locality and region, in the format of the location's language. Multi-line (<br>) |
estrategia_web_location_schedule |
derived (2.0.0): the hours text, from the hours field («Abierto todos los días» / «De 8:00 a 23:00»). Multi-line |
estrategia_web_location_map_url, estrategia_web_location_map_embed |
derived (2.0.0): «Open in Google Maps» and the embed URL, from «What to search on Google Maps» (estrategia_web_location_map_query) |
estrategia_web_location_static_map |
derived (2.0.0): the URL of the media library image estrategia_web_location_static_map_id |
estrategia_web_location_phone, estrategia_web_location_email, every other field of the definition |
as stored |
The derived keys only exist in THIS source (2.0.0). They were stored
meta up to 1.7.0, and a theme that bound them with core/post-meta must
switch those bindings to estrategia-web-site/location (same key, no
slug inside a Query Loop) BEFORE running the data migration — after it the
meta is gone and core/post-meta shows nothing. Until the migration runs, the
old stored text is what these keys show, so updating the plugin changes nothing.
The wording of the derived texts is a per-language format
(Location_Data::FORMATS in
class-location-data.php:
es, ca, en; any other language uses en). A site changes it with a filter, in
its theme or a mu-plugin:
add_filter( 'estrategia_web_location_formats', function ( array $format, string $lang ) {
if ( 'es' === $lang ) {
$format['open'] = 'Abrimos {days}'; // first line when every open day has the same hours
$format['interval'] = 'de {open} a {close} h'; // each range
$format['address'] = "{street}\n{postal_code} {locality} ({region})";
}
return $format;
}, 10, 2 );
Keys: address (placeholders {street} {postal_code} {locality}
{region} {country}; an empty part takes its separator with it), open,
line ({days}: {times}, one per set of hours when there are several),
every_day, day_range ({from}/{to}, three days or more), list_and,
days (seven names, Monday first), interval ({open}/{close}),
times_and, time (a PHP date format: G:i, g:ia). The company's hours
(Company details) use the same format.
| estrategia_web_location_phone_href | tel:+34971112233, from estrategia_web_location_phone (same country code as the company) |
| estrategia_web_location_email_href | mailto:…, from estrategia_web_location_email |
Same treatment as the company source: escaped text with <br>, and in url
only http, https, tel and mailto. A location that does not exist,
is a draft or is private comes out empty (in the editor, [Name of the detail]). In the editor it shows the value of the locations published when
it was opened; a location created later shows in brackets until reloading.
Button with the phone of the «maioris» location (text and url):
<!-- wp:button {"metadata":{"bindings":{"text":{"source":"estrategia-web-site/location","args":{"slug":"maioris","key":"estrategia_web_location_phone"}},"url":{"source":"estrategia-web-site/location","args":{"slug":"maioris","key":"estrategia_web_location_phone_href"}}}}} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button"></a></div>
<!-- /wp:button -->
The address of each location inside a Query Loop ("postType":"location"):
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"estrategia-web-site/location","args":{"key":"estrategia_web_location_address"}}}}} -->
<p></p>
<!-- /wp:paragraph -->
3. Content types
Since 17-10-2026 they are templates of the content-types module (see
«content-types module: the constructor»): same type, taxonomy and meta names
as in the table below, stored as definitions in each site's database and
edited in Estrategia Web → Content types. A site that had them gets them
stored once, with the URLs it serves today; enabled_types and
rewrite_slugs still switch them on and set the site language's slug
(modules/content-types/includes/class-site-types.php). The editor panel and
the WPML config come from the definition. What follows is the contract as it
was, and still is, for those names.
Each one is registered only if it is in enabled_types. Disabling it
deletes nothing: its posts stay in the database and come back as they
were when it is re-enabled. All of them public, show_in_rest (REST route
= registered name: /wp/v2/eweb_client…), with archive, icon and labels in
Spanish on es_ES.
Key and registered name (unreleased, 1.2.0). In the table, the type's
key (client): it is what is ticked in enabled_types, what
rewrite_slugs carries and what the estrategia_web_site_rewrite_slug
filter receives. WordPress registers it with the prefix in front:
eweb_client, eweb_client_sector… (the prefix lives only in
modules/site/includes/class-type-names.php,
Type_Names::PREFIX; decided by David on 15-10-2026, the reason in section
0). That name is the one in wp_posts.post_type, a Query Loop's
"postType", the REST route and WPML. A theme asks for it with
estrategia_web_type_name( 'client' ). The public URLs (/clientes/) do not
change. Whatever was stored before with the unprefixed names was carried
over by the prefix migrator, removed in 2.0.1 (section «Names»).
Key (registered: eweb_ + key) |
Menu | Slug (es_ES) | Uses | Fields (register_post_meta, single, show_in_rest) |
Taxonomy |
|---|---|---|---|---|---|
client |
Clients | /clientes/ |
title = name, featured image = logo/photo, excerpt, manual order | estrategia_web_client_url (URL; client_url up to 1.4.3), estrategia_web_client_image_fit (cover by default or contain; any other value → cover; 0.2.0), estrategia_web_client_business_type (text: «agencia de alquiler vacacional», what used to go in brackets in the title; translated with WPML; unreleased) |
client_sector (hierarchical, slug /sector/) |
testimonial |
Testimonials | /opiniones/ |
title (internal), content = the testimonial, image = author's photo, order | estrategia_web_testimonial_author, estrategia_web_testimonial_role (text), estrategia_web_testimonial_rating (integer 0-5; 0 = no rating), estrategia_web_testimonial_client (id of a client, or 0) (the role and the client were testimonial_role / testimonial_client up to 1.4.3) |
— |
location |
Locations | /sedes/ |
title = location, content, image = static map, excerpt, order | 2.0.0, each datum once («A location's data stored once», at the end): estrategia_web_location_phone, _email, _street (1-2 lines), _postal_code, _locality, _region, _country, _hours, _map_query, _static_map_id (an image attachment; otherwise 0), _latitude, _longitude, _business_type. The address and hours texts, the Maps link, the embed and the static map URL are derived (section 2 ter). Up to 1.7.0 also _address, _schedule, _map_url, _map_embed, _static_map: removed by the migration |
— |
project |
Projects | /proyectos/ |
title, content, image, excerpt, order, revisions | — | project_category (/categoria-proyecto/) |
portfolio |
Portfolio | /portfolio/ |
same | — | portfolio_category (/categoria-portfolio/) |
Where they are edited (unreleased): on the location and testimonial
screens, the «Location details» / «Testimonial details» panel in the
editor sidebar; on the client one (classic screen: it has no editor), the
«Client details» box. With WPML, phone, email, links, map, image fit,
author and rating are copied from the original to the translation;
address, opening hours, business type and role are translated. The
criterion, in the header of
modules/site/includes/class-meta-panel.php.
With WPML, the types and taxonomies (1.4.3) are declared by the plugin's
wpml-config.xml, all as display-as-translated (translatable, showing the
original while there is no translation): a new language never empties a
list. WPML locks that setting on its screen; a site that needs another mode
for a type changes it with the wpml_config_array filter. The criterion,
in wpml-config.xml.
All the types have custom-fields, so the fields are rendered with the
core/post-meta source inside a Query Loop (the business type on a
client's page: a paragraph with
{"source":"core/post-meta","args":{"key":"estrategia_web_client_business_type"}}).
A Query Loop of the clients of one sector (id 3) with logo and name:
<!-- wp:query {"queryId":1,"query":{"perPage":12,"pages":0,"offset":0,"postType":"eweb_client","order":"asc","orderBy":"title","inherit":false,"taxQuery":{"eweb_client_sector":[3]}}} -->
<div class="wp-block-query"><!-- wp:post-template -->
<!-- wp:post-featured-image /-->
<!-- wp:post-title /-->
<!-- /wp:post-template --></div>
<!-- /wp:query -->
(Manual order —page-attributes, "orderBy":"menu_order"— is in the types
but has no test in a Query Loop yet: see TODO.md.)
A field inside the Query Loop (the author of a testimonial):
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"estrategia_web_testimonial_author"}}}}} -->
<p></p>
<!-- /wp:paragraph -->
The map of each location with estrategia-web/map, inside a Query Loop of
location — since 2.0.0 through THIS source (the four values are derived;
core/post-meta cannot show them):
<!-- wp:estrategia-web/map {"metadata":{"bindings":{"address":{"source":"estrategia-web-site/location","args":{"key":"estrategia_web_location_address"}},"embedUrl":{"source":"estrategia-web-site/location","args":{"key":"estrategia_web_location_map_embed"}},"mapsUrl":{"source":"estrategia-web-site/location","args":{"key":"estrategia_web_location_map_url"}},"staticMapUrl":{"source":"estrategia-web-site/location","args":{"key":"estrategia_web_location_static_map"}}}}} /-->
staticMapUrl gets the URL of the media library image
(estrategia_web_location_static_map_id); the block's own staticMapId
(a number) cannot be bound. The map block checks the URL again when
rendering (estrategia_web_map_static_url).
Public slugs per site: rewrite_slugs in the option (0.2.0). The routes
belong to the plugin, not the theme: a site with a page /clientes/
(which the type's archive would overwrite) moves the archive like this, via
REST or in its content loading:
curl -u admin:APP_PASS -X POST https://web.test/wp-json/wp/v2/settings \
-H 'Content-Type: application/json' \
-d '{"estrategia_web_site":{"rewrite_slugs":{"client":"cliente"}}}'
Order: the translated slug ← the one in rewrite_slugs if not empty ← the
filter estrategia_web_site_rewrite_slug( $slug, $type_or_taxonomy ), which
still rules (it already receives the option's one). A change through the
filter does not rebuild the rules on its own; one through the option does.
Classes the plugin puts on a post (post_class filter: they come out on
a Query Loop's <li> and on the single page). They describe the data; the
plugin adds no CSS, the look belongs to the theme:
| Class | When | What for (in the theme) |
|---|---|---|
has-image-fit-contain |
a client with estrategia_web_client_image_fit = contain (0.2.0) |
the image is a logo: it must not be cropped, e.g. .has-image-fit-contain .wp-block-post-featured-image img { object-fit: contain; } |
Only on the site: the Query Loop preview in the editor is rendered by JS and
does not go through post_class (deduced from core, not tested).
4. SVG
With allow_svg enabled, only whoever has unfiltered_html or
manage_options can upload .svg (not .svgz). It is always sanitized on
the server with an allowlist of elements and attributes
(modules/site/includes/class-svg-sanitizer.php):
out go `
This README is longer than the copy stored here. Read the rest on GitHub →