From b465b5522aca7cf2441cfd7e6d08d4413252c8a9 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 12 Jun 2026 11:32:34 +0100 Subject: [PATCH 1/4] Add Share your stories blog menu item and WordPress deploy script. Expose the stories submission form under Blog on the main site and ship a server-side script for the WordPress hero CTA, taller hero layout, and blog menu link. Co-authored-by: Cursor --- resources/lang/en/menu.php | 1 + resources/views/layout/menu.blade.php | 12 +- scripts/deploy-blog-share-stories.sh | 175 ++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 4 deletions(-) create mode 100755 scripts/deploy-blog-share-stories.sh diff --git a/resources/lang/en/menu.php b/resources/lang/en/menu.php index 6dd69af3f..6609ab908 100644 --- a/resources/lang/en/menu.php +++ b/resources/lang/en/menu.php @@ -32,6 +32,7 @@ 'schools' => 'Schools', 'about' => 'About', 'blog' => 'Blog', + 'share_your_stories' => 'Share your stories', 'news' => 'News', 'search' => 'Type & hit Enter...', 'map' => 'Map', diff --git a/resources/views/layout/menu.blade.php b/resources/views/layout/menu.blade.php index 85aea65d1..98626bdea 100644 --- a/resources/views/layout/menu.blade.php +++ b/resources/views/layout/menu.blade.php @@ -288,14 +288,18 @@ class="cookweek-link hover-underline !text-[#1C4DA1] !text-[16px]" {{-- blog --}} - {{-- My account --}} diff --git a/scripts/deploy-blog-share-stories.sh b/scripts/deploy-blog-share-stories.sh new file mode 100755 index 000000000..dd943acb4 --- /dev/null +++ b/scripts/deploy-blog-share-stories.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# Run on the WordPress blog server (SSH as root): +# bash scripts/deploy-blog-share-stories.sh +# +# Adds hero CTA ACF fields, enlarges the blog hero, and adds "Share your stories" +# to the Blog menu dropdown. + +set -euo pipefail + +THEME="/var/www/html/wp-content/themes/eucodewe-1389" +WP_ROOT="/var/www/html" +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +SHARE_TEXT="Share your stories" +HERO_DESCRIPTION="Have a story, activity, or inspiring EU Code Week experience to share? We'd love to hear from you! Submit your experiences, highlights, and initiatives through the form and help us showcase the amazing work happening across the EU Code Week community." + +if [[ ! -d "$THEME" ]]; then + echo "Theme not found at $THEME" + exit 1 +fi + +cd "$THEME" +cp new.css "new.css.bak.$(date +%Y%m%d%H%M%S)" +[[ -f front-page.php ]] && cp front-page.php "front-page.php.bak.$(date +%Y%m%d%H%M%S)" + +# --- 1) Enlarge hero so description + CTA fit --- +python3 <<'PY' +from pathlib import Path + +css = Path("new.css") +text = css.read_text() + +text = text.replace( + ".hero-section {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n background: linear-gradient(332deg, #F95C22 30.37%, #FF885C 72.96%), #F95C22;\n background-position: center;\n padding: 0rem 0;\n position: relative;\n overflow: hidden;\n height: 250px;\n}", + ".hero-section {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n background: linear-gradient(332deg, #F95C22 30.37%, #FF885C 72.96%), #F95C22;\n background-position: center;\n padding: 2rem 0;\n position: relative;\n overflow: hidden;\n min-height: 420px;\n height: auto;\n}", + 1, +) + +text = text.replace("\t.hero-section {\n\t\tz-index: -2;\n\t\theight: 330px;\n\t}", "\t.hero-section {\n\t\tz-index: -2;\n\t\tmin-height: 400px;\n\t\theight: auto;\n\t}", 1) + +if ".hero-button {" in text and "margin-top:" not in text.split(".hero-button {", 1)[1].split("}", 1)[0]: + text = text.replace( + ".hero-button {\n background-color: #F95C22;", + ".hero-button {\n display: inline-block;\n margin-top: 1.5rem;\n background-color: #F95C22;", + 1, + ) + +css.write_text(text) +print("Updated new.css hero sizing") +PY + +# --- 2) ACF fields for hero CTA (editable in WP admin) --- +mkdir -p inc +cat > inc/acf-blog-hero-cta.php <<'PHP' + 'group_codeweek_blog_hero_cta', + 'title' => 'Blog homepage hero', + 'fields' => [ + [ + 'key' => 'field_blog_hero_title', + 'label' => 'Hero title', + 'name' => 'hero_title', + 'type' => 'text', + 'instructions' => 'Use HTML for highlighted words, e.g. Talk code to me', + ], + [ + 'key' => 'field_blog_hero_description', + 'label' => 'Hero description', + 'name' => 'hero_description', + 'type' => 'textarea', + 'rows' => 4, + 'new_lines' => '', + ], + [ + 'key' => 'field_blog_hero_cta_text', + 'label' => 'Hero CTA text', + 'name' => 'hero_cta_text', + 'type' => 'text', + 'default_value' => 'Share your stories', + ], + [ + 'key' => 'field_blog_hero_cta_link', + 'label' => 'Hero CTA link', + 'name' => 'hero_cta_link', + 'type' => 'url', + 'default_value' => 'https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true', + ], + [ + 'key' => 'field_blog_hero_image', + 'label' => 'Hero image', + 'name' => 'hero_image', + 'type' => 'image', + 'return_format' => 'url', + ], + ], + 'location' => [ + [ + [ + 'param' => 'page_type', + 'operator' => '==', + 'value' => 'front_page', + ], + ], + ], + 'position' => 'acf_after_title', + 'style' => 'default', + 'active' => true, +]); +PHP + +if ! grep -q "acf-blog-hero-cta.php" functions.php; then + printf "\n// Blog homepage hero CTA fields\nrequire_once get_template_directory() . '/inc/acf-blog-hero-cta.php';\n" >> functions.php + echo "Registered ACF field group in functions.php" +fi + +# --- 3) front-page.php: CTA button after hero description --- +if [[ -f front-page.php ]] && ! grep -q "hero_cta_text" front-page.php; then + python3 <<'PY' +import re +from pathlib import Path + +path = Path("front-page.php") +content = path.read_text() +snippet = """ + + + """ + +if "hero_cta_text" in content: + print("front-page.php already has hero CTA") +else: + match = re.search(r'(]*class="hero-description"[^>]*>.*?

)', content, re.S) + if not match: + raise SystemExit("Could not find hero-description paragraph in front-page.php") + insert_at = match.end() + content = content[:insert_at] + snippet + content[insert_at:] + path.write_text(content) + print("Inserted hero CTA into front-page.php") +PY +fi + +# --- 4) Default field values on homepage (page ID 2) --- +if command -v wp >/dev/null 2>&1; then + cd "$WP_ROOT" + wp option update blogdescription "Inspiring Digital Creativity – One Line of Code at a Time!" >/dev/null 2>&1 || true + + wp post meta update 2 hero_cta_text "$SHARE_TEXT" 2>/dev/null || wp acf update 2 hero_cta_text "$SHARE_TEXT" 2>/dev/null || true + wp post meta update 2 hero_cta_link "$SHARE_URL" 2>/dev/null || wp acf update 2 hero_cta_link "$SHARE_URL" 2>/dev/null || true + wp post meta update 2 hero_description "$HERO_DESCRIPTION" 2>/dev/null || wp acf update 2 hero_description "$HERO_DESCRIPTION" 2>/dev/null || true + + # Blog menu: add "Share your stories" under Blog (menu item 5643) + if ! wp menu item list primary --fields=db_id,title,url 2>/dev/null | grep -q "Share your stories"; then + BLOG_PARENT=$(wp menu item list primary --fields=db_id,title --format=csv 2>/dev/null | awk -F, '$2 ~ /Blog/ {print $1; exit}') + if [[ -n "${BLOG_PARENT:-}" ]]; then + wp menu item add-custom primary "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ + || wp menu item add-custom header "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ + || wp menu item add-custom main "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ + || echo "Could not auto-add menu item — add '$SHARE_TEXT' under Blog in Appearance → Menus" + else + echo "Could not find Blog menu item — add '$SHARE_TEXT' under Blog in Appearance → Menus" + fi + fi + + wp cache flush >/dev/null 2>&1 || true + echo "WP-CLI updates applied" +else + echo "wp-cli not found — set hero_cta_text / hero_cta_link on the homepage in WP admin" +fi + +echo "Done. Verify: https://codeweek.eu/blog/" From a8d8056a48752c8517be3cd07f47cc6d236cfa5a Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 12 Jun 2026 12:43:14 +0100 Subject: [PATCH 2/4] Simplify Blog nav dropdown to match Community pattern. Blog still navigates to the blog on click; hover now shows only Share your stories (Microsoft Forms, new tab) without duplicating the Blog link. Co-authored-by: Cursor --- resources/views/layout/menu.blade.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/views/layout/menu.blade.php b/resources/views/layout/menu.blade.php index 98626bdea..422fb9553 100644 --- a/resources/views/layout/menu.blade.php +++ b/resources/views/layout/menu.blade.php @@ -297,8 +297,7 @@ class="cookweek-link hover-underline !text-[#1C4DA1] !text-[16px] cursor-pointer From fe492a3b2d18c1f2047802b61cba70c6fa3f7ff6 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 12 Jun 2026 12:49:50 +0100 Subject: [PATCH 3/4] push live and add to menu --- ...de-feed-reply-malte-email-fields-email.txt | 63 ++++ ...deweek-de-feed-reply-malte-email-fields.md | 69 +++++ scripts/deploy-blog-share-stories.sh | 293 ++++++++++-------- scripts/fix-blog-chevron-and-button.sh | 90 ++++++ scripts/fix-blog-chevron-and-hero-cta.sh | 121 ++++++++ scripts/fix-blog-chevron-left-cta.sh | 89 ++++++ scripts/fix-blog-hero-cta-render.sh | 87 ++++++ scripts/patch-blog-hero-cta-and-menu-icon.sh | 139 +++++++++ scripts/patch-blog-hero-height.sh | 56 ++++ scripts/patch-blog-menu-share-stories-icon.sh | 55 ++++ 10 files changed, 938 insertions(+), 124 deletions(-) create mode 100644 docs/codeweek-de-feed-reply-malte-email-fields-email.txt create mode 100644 docs/codeweek-de-feed-reply-malte-email-fields.md create mode 100755 scripts/fix-blog-chevron-and-button.sh create mode 100755 scripts/fix-blog-chevron-and-hero-cta.sh create mode 100755 scripts/fix-blog-chevron-left-cta.sh create mode 100755 scripts/fix-blog-hero-cta-render.sh create mode 100755 scripts/patch-blog-hero-cta-and-menu-icon.sh create mode 100644 scripts/patch-blog-hero-height.sh create mode 100755 scripts/patch-blog-menu-share-stories-icon.sh diff --git a/docs/codeweek-de-feed-reply-malte-email-fields-email.txt b/docs/codeweek-de-feed-reply-malte-email-fields-email.txt new file mode 100644 index 000000000..6b07cf959 --- /dev/null +++ b/docs/codeweek-de-feed-reply-malte-email-fields-email.txt @@ -0,0 +1,63 @@ +Subject: Re: Feed export — type_of_organisation, email fields, and visibility + +Hi Malte, + +Happy to clarify: + +1) type_of_organisation — object or string? + +For the feed, please put the organizer type under user.type, not as a top-level field. + +Our importer accepts either: +- "user": { "type": { "identifier": "non profit" } } (preferred), or +- "user": { "type": "non profit" } (also works) + +The top-level type_of_organisation field is not read by our current importer, so you can remove it once it's mapped into user.type. + + +2) "Map type_of_organisation to user.type.identifier" — same value? + +Yes. Use the same values as in the Excel guide / registration form, for example: +- school +- library +- non profit +- private business +- other + + +3) Email fields — which is public? + +Your three-email model maps like this: + +1) Personal email of event creator + Feed field: user.email + Our DB field: user_email + Visibility: Not public — used internally to link/create the owner account; visible only to EU Code Week ambassadors/organisers for moderation + +2) Official org email (e.g. info@…) + Feed field: (no dedicated field) + Visibility: Not stored separately; use user.company for org name, user.www for website + +3) Public contact email for the event + Feed field: user.publicEmail + Our DB field: contact_person + Visibility: Public — this is what we display as the event contact email when provided + +So yes, it's fine to send the personal creator email in user.email even though it's not intended to be public. That matches how our registration form works (user_email = internal contact; contact_person = optional public email). + +Recommended mapping: + +"user": { + "company": "Organisation name", + "email": "creator-personal@example.org", + "publicEmail": "public-contact@example.org", + "www": "https://organisation.example.org", + "type": { "identifier": "non profit" } +} + +If there's no public contact email for an event, leave publicEmail empty. + +Let us know if anything is unclear. + +Best regards, +Bernard diff --git a/docs/codeweek-de-feed-reply-malte-email-fields.md b/docs/codeweek-de-feed-reply-malte-email-fields.md new file mode 100644 index 000000000..56fade22a --- /dev/null +++ b/docs/codeweek-de-feed-reply-malte-email-fields.md @@ -0,0 +1,69 @@ +# Reply to Malte — Feed format clarifications (email & organizer type) + +**Subject:** Re: Feed export — `type_of_organisation`, email fields, and visibility + +--- + +Hi Malte, + +Happy to clarify: + +## 1) `type_of_organisation` — object or string? + +For the feed, please put the organizer type under **`user.type`**, not as a top-level field. + +Our importer accepts **either**: + +- `"user": { "type": { "identifier": "non profit" } }` **(preferred)**, or +- `"user": { "type": "non profit" }` **(also works)** + +The top-level `type_of_organisation` field is **not read** by our current importer, so you can remove it once it’s mapped into `user.type`. + +## 2) “Map `type_of_organisation` → `user.type.identifier`” — same value? + +**Yes.** Use the same values as in the Excel guide / registration form, for example: + +- `school` +- `library` +- `non profit` +- `private business` +- `other` + +## 3) Email fields — which is public? + +Your three-email model maps like this: + +| Your field | Feed field | Our DB field | Visibility | +|---|---|---|---| +| 1) Personal email of event creator | `user.email` | `user_email` | **Not public** — used internally to link/create the owner account; visible only to EU Code Week ambassadors/organisers for moderation | +| 2) Official org email (e.g. info@…) | *(no dedicated field)* | — | Not stored separately; use `user.company` for org **name**, `user.www` for website | +| 3) Public contact email for the event | `user.publicEmail` | `contact_person` | **Public** — this is what we display as the event contact email when provided | + +So **yes**, it’s fine to send the personal creator email in `user.email` even though it’s not intended to be public. That matches how our registration form works (`user_email` = internal contact; `contact_person` = optional public email). + +### Recommended mapping + +```json +"user": { + "company": "Organisation name", + "email": "creator-personal@example.org", + "publicEmail": "public-contact@example.org", + "www": "https://organisation.example.org", + "type": { "identifier": "non profit" } +} +``` + +If there’s no public contact email for an event, leave `publicEmail` empty. + +Let us know if anything is unclear. + +Best regards, +Bernard + +--- + +## Internal reference + +- `user.email` is stripped from the public event page HTML for normal visitors (ambassadors/admins only for `user_email`). +- Optional public email: `user.publicEmail` → `contact_person` (displayed on event detail when provided). +- Importer source: `app/Console/Commands/api/GermanTraits.php` diff --git a/scripts/deploy-blog-share-stories.sh b/scripts/deploy-blog-share-stories.sh index dd943acb4..82f8d24c1 100755 --- a/scripts/deploy-blog-share-stories.sh +++ b/scripts/deploy-blog-share-stories.sh @@ -1,41 +1,103 @@ #!/usr/bin/env bash -# Run on the WordPress blog server (SSH as root): -# bash scripts/deploy-blog-share-stories.sh -# -# Adds hero CTA ACF fields, enlarges the blog hero, and adds "Share your stories" -# to the Blog menu dropdown. +# Production blog deploy — run on server as root: +# WP_ROOT=/var/www/html/blog bash deploy-blog-share-stories-inline.sh set -euo pipefail -THEME="/var/www/html/wp-content/themes/eucodewe-1389" -WP_ROOT="/var/www/html" -SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" -SHARE_TEXT="Share your stories" -HERO_DESCRIPTION="Have a story, activity, or inspiring EU Code Week experience to share? We'd love to hear from you! Submit your experiences, highlights, and initiatives through the form and help us showcase the amazing work happening across the EU Code Week community." +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}" if [[ ! -d "$THEME" ]]; then - echo "Theme not found at $THEME" - exit 1 + THEME=$(find /var/www -maxdepth 8 -type d -name 'eucodewe-1389' 2>/dev/null | head -1 || true) + if [[ -n "${THEME:-}" ]]; then + WP_ROOT="$(dirname "$(dirname "$(dirname "$THEME")")")" + fi fi +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +SHARE_TEXT="Share your stories" +HERO_DESCRIPTION="Have a story, activity, or inspiring EU Code Week experience to share? We'd love to hear from you! Submit your experiences, highlights, and initiatives through the form and help us showcase the amazing work happening across the EU Code Week community." + +[[ -d "$THEME" ]] || { echo "Theme eucodewe-1389 not found under /var/www"; find /var/www -maxdepth 6 -name wp-config.php 2>/dev/null; exit 1; } +echo "Using WP_ROOT=$WP_ROOT" cd "$THEME" -cp new.css "new.css.bak.$(date +%Y%m%d%H%M%S)" -[[ -f front-page.php ]] && cp front-page.php "front-page.php.bak.$(date +%Y%m%d%H%M%S)" +echo "Using THEME=$THEME" + +chmod u+w new.css front-page.php functions.php acf-json/group_5efc9e8719cc2.json 2>/dev/null || true +cp -a new.css "new.css.bak.$(date +%Y%m%d%H%M%S)" +cp -a front-page.php "front-page.php.bak.$(date +%Y%m%d%H%M%S)" -# --- 1) Enlarge hero so description + CTA fit --- +# --- 1) Enlarge hero so description + CTA fit comfortably --- python3 <<'PY' from pathlib import Path +import re css = Path("new.css") text = css.read_text() -text = text.replace( - ".hero-section {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n background: linear-gradient(332deg, #F95C22 30.37%, #FF885C 72.96%), #F95C22;\n background-position: center;\n padding: 0rem 0;\n position: relative;\n overflow: hidden;\n height: 250px;\n}", - ".hero-section {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n background: linear-gradient(332deg, #F95C22 30.37%, #FF885C 72.96%), #F95C22;\n background-position: center;\n padding: 2rem 0;\n position: relative;\n overflow: hidden;\n min-height: 420px;\n height: auto;\n}", - 1, +# Desktop hero section +text = re.sub( + r"(\.hero-section\s*\{[^}]*?)min-height:\s*\d+px;", + r"\1min-height: 580px;", + text, + count=1, + flags=re.S, +) +text = re.sub( + r"(\.hero-section\s*\{[^}]*?)height:\s*250px;", + r"\1min-height: 580px;\n height: auto;", + text, + count=1, + flags=re.S, +) +text = re.sub( + r"(\.hero-section\s*\{[^}]*?)padding:\s*[^;]+;", + r"\1padding: 3rem 0;", + text, + count=1, + flags=re.S, +) + +# Hero white card — more room for description + button +text = re.sub( + r"(\.hero-content\s*\{[^}]*?)padding:\s*2rem;", + r"\1padding: 2.5rem 3rem 3rem;", + text, + count=1, + flags=re.S, +) + +if ".hero-description {" in text and "margin-bottom:" not in text.split(".hero-description {", 1)[1].split("}", 1)[0]: + text = text.replace( + ".hero-description {\n color: var(--Slate-500, #333E48);", + ".hero-description {\n margin-bottom: 1.25rem;\n color: var(--Slate-500, #333E48);", + 1, + ) + +# Tablet breakpoint +text = re.sub( + r"(\t\.hero-section\s*\{[^}]*?)min-height:\s*\d+px;", + r"\1min-height: 520px;", + text, + count=1, + flags=re.S, +) +text = re.sub( + r"(\t\.hero-section\s*\{[^}]*?)height:\s*330px;", + r"\1min-height: 520px;\n\t\theight: auto;", + text, + count=1, + flags=re.S, ) -text = text.replace("\t.hero-section {\n\t\tz-index: -2;\n\t\theight: 330px;\n\t}", "\t.hero-section {\n\t\tz-index: -2;\n\t\tmin-height: 400px;\n\t\theight: auto;\n\t}", 1) +# Mobile breakpoint — stack layout +text = re.sub( + r"(\t\.hero-section\s*\{\n flex-direction: column-reverse;[^}]*?)padding:\s*[^;]+;", + r"\1padding: 0 0 3rem;\n min-height: 520px;", + text, + count=1, + flags=re.S, +) if ".hero-button {" in text and "margin-top:" not in text.split(".hero-button {", 1)[1].split("}", 1)[0]: text = text.replace( @@ -43,133 +105,116 @@ if ".hero-button {" in text and "margin-top:" not in text.split(".hero-button {" ".hero-button {\n display: inline-block;\n margin-top: 1.5rem;\n background-color: #F95C22;", 1, ) +elif ".hero-button {" in text: + text = re.sub( + r"(\.hero-button\s*\{[^}]*?)margin-top:\s*[^;]+;", + r"\1margin-top: 1.5rem;", + text, + count=1, + flags=re.S, + ) css.write_text(text) -print("Updated new.css hero sizing") +print("Updated new.css hero sizing (580px desktop)") PY -# --- 2) ACF fields for hero CTA (editable in WP admin) --- -mkdir -p inc -cat > inc/acf-blog-hero-cta.php <<'PHP' - 'group_codeweek_blog_hero_cta', - 'title' => 'Blog homepage hero', - 'fields' => [ - [ - 'key' => 'field_blog_hero_title', - 'label' => 'Hero title', - 'name' => 'hero_title', - 'type' => 'text', - 'instructions' => 'Use HTML for highlighted words, e.g. Talk code to me', - ], - [ - 'key' => 'field_blog_hero_description', - 'label' => 'Hero description', - 'name' => 'hero_description', - 'type' => 'textarea', - 'rows' => 4, - 'new_lines' => '', - ], - [ - 'key' => 'field_blog_hero_cta_text', - 'label' => 'Hero CTA text', - 'name' => 'hero_cta_text', - 'type' => 'text', - 'default_value' => 'Share your stories', - ], - [ - 'key' => 'field_blog_hero_cta_link', - 'label' => 'Hero CTA link', - 'name' => 'hero_cta_link', - 'type' => 'url', - 'default_value' => 'https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true', - ], - [ - 'key' => 'field_blog_hero_image', - 'label' => 'Hero image', - 'name' => 'hero_image', - 'type' => 'image', - 'return_format' => 'url', - ], - ], - 'location' => [ - [ - [ - 'param' => 'page_type', - 'operator' => '==', - 'value' => 'front_page', - ], - ], - ], - 'position' => 'acf_after_title', - 'style' => 'default', - 'active' => true, -]); -PHP - -if ! grep -q "acf-blog-hero-cta.php" functions.php; then - printf "\n// Blog homepage hero CTA fields\nrequire_once get_template_directory() . '/inc/acf-blog-hero-cta.php';\n" >> functions.php - echo "Registered ACF field group in functions.php" -fi +# --- 2) ACF JSON: add cta_button to hero_section --- +python3 <<'PY' +import json +from pathlib import Path +path = Path("acf-json/group_5efc9e8719cc2.json") +data = json.loads(path.read_text()) +for field in data.get("fields", []): + if field.get("name") != "hero_section": + continue + subs = field.setdefault("sub_fields", []) + if any(s.get("name") == "cta_button" for s in subs): + print("ACF JSON already has cta_button") + break + subs.append({ + "key": "field_codeweek_hero_cta_button", + "label": "Button", + "name": "cta_button", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": {"width": "", "class": "", "id": ""}, + "return_format": "array", + }) + path.write_text(json.dumps(data, indent=4) + "\n") + print("Added cta_button to ACF JSON") + break +else: + raise SystemExit("hero_section not found in ACF JSON") +PY -# --- 3) front-page.php: CTA button after hero description --- -if [[ -f front-page.php ]] && ! grep -q "hero_cta_text" front-page.php; then - python3 <<'PY' +# --- 3) front-page.php: render hero CTA (mirror info section button) --- +python3 <<'PY' import re from pathlib import Path - path = Path("front-page.php") content = path.read_text() snippet = """ - - + + """ -if "hero_cta_text" in content: - print("front-page.php already has hero CTA") +if "cta_button" in content: + print("front-page.php already references cta_button") else: match = re.search(r'(]*class="hero-description"[^>]*>.*?

)', content, re.S) if not match: - raise SystemExit("Could not find hero-description paragraph in front-page.php") - insert_at = match.end() - content = content[:insert_at] + snippet + content[insert_at:] + raise SystemExit("Could not find hero-description in front-page.php") + content = content[: match.end()] + snippet + content[match.end() :] path.write_text(content) - print("Inserted hero CTA into front-page.php") + print("Patched front-page.php") PY -fi -# --- 4) Default field values on homepage (page ID 2) --- +# --- 4) WP-CLI: defaults + menu --- if command -v wp >/dev/null 2>&1; then cd "$WP_ROOT" - wp option update blogdescription "Inspiring Digital Creativity – One Line of Code at a Time!" >/dev/null 2>&1 || true - - wp post meta update 2 hero_cta_text "$SHARE_TEXT" 2>/dev/null || wp acf update 2 hero_cta_text "$SHARE_TEXT" 2>/dev/null || true - wp post meta update 2 hero_cta_link "$SHARE_URL" 2>/dev/null || wp acf update 2 hero_cta_link "$SHARE_URL" 2>/dev/null || true - wp post meta update 2 hero_description "$HERO_DESCRIPTION" 2>/dev/null || wp acf update 2 hero_description "$HERO_DESCRIPTION" 2>/dev/null || true - - # Blog menu: add "Share your stories" under Blog (menu item 5643) - if ! wp menu item list primary --fields=db_id,title,url 2>/dev/null | grep -q "Share your stories"; then - BLOG_PARENT=$(wp menu item list primary --fields=db_id,title --format=csv 2>/dev/null | awk -F, '$2 ~ /Blog/ {print $1; exit}') + wp acf sync --allow-root 2>/dev/null || true + + HERO_DESC_JSON=$(HERO_DESCRIPTION="$HERO_DESCRIPTION" python3 -c 'import json,os; print(json.dumps(os.environ["HERO_DESCRIPTION"]))') + wp eval --allow-root " + \$hero = get_field('hero_section', 2) ?: []; + \$hero['description'] = ${HERO_DESC_JSON}; + \$hero['cta_button'] = ['title' => '$SHARE_TEXT', 'url' => '$SHARE_URL', 'target' => '_blank']; + update_field('hero_section', \$hero, 2); + " 2>/dev/null || true + + ARROW_HTML='arrow right' + SHARE_ID="" + for menu in primary header main "Menu 1" 2; do + SHARE_ID=$(wp menu item list "$menu" --fields=db_id,title --format=csv --allow-root 2>/dev/null \ + | awk -F, -v t="$SHARE_TEXT" '$2 ~ t {print $1; exit}') || true + if [[ -n "${SHARE_ID:-}" ]]; then + echo "Share your stories already in $menu (ID $SHARE_ID)" + break + fi + BLOG_PARENT=$(wp menu item list "$menu" --fields=db_id,title --format=csv --allow-root 2>/dev/null | awk -F, '$2 ~ /Blog/ {print $1; exit}') if [[ -n "${BLOG_PARENT:-}" ]]; then - wp menu item add-custom primary "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ - || wp menu item add-custom header "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ - || wp menu item add-custom main "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" 2>/dev/null \ - || echo "Could not auto-add menu item — add '$SHARE_TEXT' under Blog in Appearance → Menus" - else - echo "Could not find Blog menu item — add '$SHARE_TEXT' under Blog in Appearance → Menus" + SHARE_ID=$(wp menu item add-custom "$menu" "$SHARE_TEXT" "$SHARE_URL" --parent-id="$BLOG_PARENT" --porcelain --allow-root 2>/dev/null || true) + [[ -n "${SHARE_ID:-}" ]] && echo "Added menu item to $menu (ID $SHARE_ID)" && break fi + done + + if [[ -n "${SHARE_ID:-}" ]]; then + wp eval --allow-root " + \$id = (int) ${SHARE_ID}; + update_post_meta(\$id, '_menu_item_url', '${SHARE_URL}'); + update_post_meta(\$id, 'rt-wp-menu-custom-fields', [ + 'selected-feature' => 'html', + 'html' => ['custom-html' => '${ARROW_HTML}'], + ]); + delete_transient('rt-wp-menu-custom-fields-' . \$id); + echo \"Menu item \$id: URL + arrow icon meta set\n\"; + " 2>/dev/null || true fi - wp cache flush >/dev/null 2>&1 || true - echo "WP-CLI updates applied" -else - echo "wp-cli not found — set hero_cta_text / hero_cta_link on the homepage in WP admin" + wp cache flush --allow-root 2>/dev/null || true + echo "WP-CLI done" fi -echo "Done. Verify: https://codeweek.eu/blog/" +echo "Done. Verify https://codeweek.eu/blog/" diff --git a/scripts/fix-blog-chevron-and-button.sh b/scripts/fix-blog-chevron-and-button.sh new file mode 100755 index 000000000..78e75a712 --- /dev/null +++ b/scripts/fix-blog-chevron-and-button.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Fix Blog chevron (root cause: last-child rule) + hero button typography. +set -euo pipefail + +THEME="${THEME:-/var/www/html/blog/wp-content/themes/eucodewe-1389}" +[[ -d "$THEME" ]] || { echo "Theme not found"; exit 1; } + +cd "$THEME" +chmod u+w new.css 2>/dev/null || true + +python3 <<'PY' +from pathlib import Path +import re + +css = Path("new.css") +text = css.read_text() + +# ROOT FIX: don't hide chevron on last nav item when it has a dropdown +text = text.replace( + ".header__menu nav > ul > li:last-child a:after {\n display: none;\n}", + ".header__menu nav > ul > li:last-child:not(.menu-item-has-children) a:after {\n display: none;\n}", +) + +# Hero button: no top margin, 1.125rem font +text = re.sub( + r"(\.hero-button\s*\{[^}]*?)margin-top:\s*[^;]+;\s*", + r"\1", + text, + count=1, + flags=re.S, +) +if "font-size: 1.125rem" not in text.split(".hero-button {", 1)[1].split("}", 1)[0]: + text = re.sub( + r"(\.hero-button\s*\{[^}]*?font-weight:\s*600;\s*)", + r"\1\n font-size: 1.125rem;\n", + text, + count=1, + flags=re.S, + ) + +# Keep hero content left-aligned +if ".hero-content {\n text-align: left;" not in text: + text = re.sub( + r"(\.hero-content\s*\{[^}]*?)text-align:\s*center;", + r"\1text-align: left;", + text, + count=1, + flags=re.S, + ) + +# Strip redundant blog chevron override blocks (root fix handles it) +for pat in [ + r"\n/\* Blog nav: restore dropdown chevron.*?(?=\n/\* |\Z)", + r"\n/\* Blog nav: chevron on Blog.*?(?=\n/\* |\Z)", +]: + text = re.sub(pat, "\n", text, flags=re.S) + +# Ensure submenu left-align block exists (keep if already there) +submenu_marker = "/* Blog dropdown: Share your stories — left aligned */" +if submenu_marker not in text: + text = text.rstrip() + """ + +/* Blog dropdown: Share your stories — left aligned */ +.menu-item-5643 > .sub-menu > .menu-item-8142, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; +} +.menu-item-5643 > .sub-menu > .menu-item-8142 a, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 a { + text-align: left; +} +""" + +css.write_text(text) +print("Patched new.css") +PY + +echo "--- verify ---" +grep -n "last-child" new.css | head -5 +grep -n -A6 "^\.hero-button {" new.css | head -10 + +cd /var/www/html/blog +wp cache flush --allow-root 2>/dev/null | tail -1 || true +for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do + systemctl is-active --quiet "$svc" 2>/dev/null && systemctl reload "$svc" && echo "Reloaded $svc" && break +done +echo "Done. Hard-refresh https://codeweek.eu/blog/" diff --git a/scripts/fix-blog-chevron-and-hero-cta.sh b/scripts/fix-blog-chevron-and-hero-cta.sh new file mode 100755 index 000000000..dcfeb5260 --- /dev/null +++ b/scripts/fix-blog-chevron-and-hero-cta.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Blog nav chevron (Blog is last item) + hero ACF CTA button. +# Run on server: +# curl -fsSL "https://gist.githubusercontent.com/bernardhanna/REPLACE/raw/fix-blog-chevron-and-hero-cta.sh" | bash + +set -euo pipefail + +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}" +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +SHARE_TEXT="Share your stories" + +[[ -d "$THEME" ]] || { echo "Theme not found: $THEME"; exit 1; } +command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; } + +cd "$THEME" +chmod u+w new.css front-page.php 2>/dev/null || true + +# --- 1) Blog dropdown chevron (last nav item overrides :last-child { display:none }) --- +python3 <<'PY' +from pathlib import Path + +css = Path("new.css") +text = css.read_text() +marker = "/* Blog nav: restore dropdown chevron (last menu item) */" +block = """ +/* Blog nav: restore dropdown chevron (last menu item) */ +.header__menu nav > ul > li#menu-item-5643.menu-item-has-children > a:after, +#menu-item-5643.menu-item-has-children > a:after { + content: ""; + width: 10px; + height: 10px; + background: url(https://codeweek.eu/blog/wp-content/uploads/2025/01/Vector-6.svg); + display: block; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + transition: 0.3s; +} +.header__menu nav > ul > li#menu-item-5643.menu-item-has-children > a:hover:after, +#menu-item-5643.menu-item-has-children > a:hover:after { + transform: scale(-1); +} +""" +if marker in text: + print("Blog chevron CSS already present") +else: + css.write_text(text.rstrip() + "\n" + block) + print("Added Blog nav chevron CSS") +PY + +# --- 2) front-page.php: render hero ACF cta_button --- +cp -a front-page.php "front-page.php.bak.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true + +python3 <<'PY' +import re +from pathlib import Path + +path = Path("front-page.php") +content = path.read_text() + +snippet = """ + + + """ + +content = re.sub( + r"\s*<\?php if \(! empty\(\$hero\['cta_button'\]\)\).*?<\?php endif; \?>", + "", + content, + flags=re.S, +) +content = re.sub( + r"\s*<\?php\s+\$hero_cta = \$hero\['cta_button'\].*?<\?php endif; \?>", + "", + content, + flags=re.S, +) + +match = re.search(r'(]*class="hero-description"[^>]*>.*?

)', content, re.S) +if not match: + raise SystemExit("Could not find hero-description in front-page.php") + +if 'class="hero-button"' not in content[match.start():match.end() + 800]: + content = content[: match.end()] + snippet + content[match.end() :] + path.write_text(content) + print("Inserted hero CTA block after hero-description") +else: + print("Hero CTA block already present after hero-description") +PY + +echo "--- front-page.php hero lines ---" +grep -n "hero-description\|hero-button\|hero_cta\|cta_button" front-page.php | head -15 + +# --- 3) ACF data for hero button --- +cd "$WP_ROOT" +wp eval --allow-root " + \$hero = get_field('hero_section', 2) ?: []; + \$hero['cta_button'] = [ + 'title' => '${SHARE_TEXT}', + 'url' => '${SHARE_URL}', + 'target' => '_blank', + ]; + update_field('hero_section', \$hero, 2); + echo 'hero_section.cta_button saved on page 2' . PHP_EOL; +" 2>/dev/null | tail -1 + +wp cache flush --allow-root 2>/dev/null || true + +for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm php7.4-fpm; do + if systemctl is-active --quiet "$svc" 2>/dev/null; then + systemctl reload "$svc" 2>/dev/null && echo "Reloaded $svc" && break + fi +done + +echo "Done. Hard-refresh https://codeweek.eu/blog/" diff --git a/scripts/fix-blog-chevron-left-cta.sh b/scripts/fix-blog-chevron-left-cta.sh new file mode 100755 index 000000000..9384518be --- /dev/null +++ b/scripts/fix-blog-chevron-left-cta.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Fix Blog chevron (desktop uses class not id) + left-align hero CTA + submenu item. +set -euo pipefail + +THEME="${THEME:-/var/www/html/blog/wp-content/themes/eucodewe-1389}" +[[ -d "$THEME" ]] || { echo "Theme not found"; exit 1; } + +cd "$THEME" +chmod u+w new.css 2>/dev/null || true + +python3 <<'PY' +from pathlib import Path +import re + +css = Path("new.css") +text = css.read_text() + +# Strip old blog chevron / submenu patches +for pat in [ + r"\n/\* Blog nav: restore dropdown chevron.*?(?=\n/\* |\Z)", + r"\n/\* Blog nav: chevron on Blog.*?(?=\n/\* |\Z)", + r"\n/\* Blog dropdown: show Share your stories.*?(?=\n/\* |\Z)", + r"\n/\* Blog dropdown: Share your stories.*?(?=\n/\* |\Z)", + r"\n/\* Hero CTA: align left.*?(?=\n/\* |\Z)", +]: + text = re.sub(pat, "\n", text, flags=re.S) + +block = """ +/* Blog nav: chevron on Blog (last item; desktop uses .menu-item-5643 without id) */ +.header__menu nav > ul > li.menu-item-has-children:last-child > a:after, +.header__menu nav > ul > li.menu-item-5643.menu-item-has-children > a:after, +li#menu-item-5643.menu-item-has-children > a:after { + content: "" !important; + width: 10px; + height: 10px; + background: url(https://codeweek.eu/blog/wp-content/uploads/2025/01/Vector-6.svg); + display: block !important; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + transition: 0.3s; +} +.header__menu nav > ul > li.menu-item-has-children:last-child > a:hover:after, +.header__menu nav > ul > li.menu-item-5643.menu-item-has-children > a:hover:after, +li#menu-item-5643.menu-item-has-children > a:hover:after { + transform: scale(-1); +} + +/* Hero CTA: align left (hero-content defaults to text-align: center) */ +.hero-content { + text-align: left; +} + +/* Blog dropdown: Share your stories — left aligned */ +.menu-item-5643 > .sub-menu > .menu-item-8142, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; +} +.menu-item-5643 > .sub-menu > .menu-item-8142 a, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 a { + text-align: left; +} +.menu-item-5643 > .sub-menu > .menu-item-8142 .rt-wp-menu-custom-fields-wrapper, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 .rt-wp-menu-custom-fields-wrapper { + display: flex; + align-items: center; + flex-shrink: 0; +} +.menu-item-5643 > .sub-menu > .menu-item-8142 .rt-wp-menu-custom-fields-custom-html img, +li#menu-item-5643 > .sub-menu > li#menu-item-8142 .rt-wp-menu-custom-fields-custom-html img { + display: block; + width: 20px; + height: auto; +} +""" + +css.write_text(text.rstrip() + "\n" + block) +print("Patched new.css") +PY + +cd /var/www/html/blog +wp cache flush --allow-root 2>/dev/null | tail -1 || true +for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do + systemctl is-active --quiet "$svc" 2>/dev/null && systemctl reload "$svc" && echo "Reloaded $svc" && break +done +echo "Done. Hard-refresh https://codeweek.eu/blog/" diff --git a/scripts/fix-blog-hero-cta-render.sh b/scripts/fix-blog-hero-cta-render.sh new file mode 100755 index 000000000..aeca40320 --- /dev/null +++ b/scripts/fix-blog-hero-cta-render.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Force hero CTA to render on blog homepage. +# Run: bash fix-blog-hero-cta-render.sh + +set -euo pipefail + +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}" +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +SHARE_TEXT="Share your stories" + +cd "$THEME" +chmod u+w front-page.php 2>/dev/null || true +cp -a front-page.php "front-page.php.bak.$(date +%Y%m%d%H%M%S)" + +python3 <<'PY' +import re +from pathlib import Path + +path = Path("front-page.php") +content = path.read_text() + +snippet = """ + + + """ + +# Remove any prior cta_button block we may have inserted +content = re.sub( + r"\s*<\?php if \(! empty\(\$hero\['cta_button'\]\)\).*?<\?php endif; \?>", + "", + content, + flags=re.S, +) +content = re.sub( + r"\s*<\?php\s+\$hero_cta = \$hero\['cta_button'\].*?<\?php endif; \?>", + "", + content, + flags=re.S, +) + +match = re.search(r'(]*class="hero-description"[^>]*>.*?

)', content, re.S) +if not match: + raise SystemExit("Could not find hero-description paragraph in front-page.php") + +if 'class="hero-button"' in content[match.start():match.end() + 500]: + print("Hero button markup already follows hero-description") +else: + content = content[: match.end()] + snippet + content[match.end() :] + path.write_text(content) + print("Inserted hero CTA render block after hero-description") +PY + +echo "--- front-page.php (hero area) ---" +grep -n "hero-description\|hero-button\|cta_button\|hero_cta" front-page.php | head -20 + +cd "$WP_ROOT" +wp eval --allow-root " + \$hero = get_field('hero_section', 2) ?: []; + if (empty(\$hero['cta_button']['url'])) { + \$hero['cta_button'] = [ + 'title' => '${SHARE_TEXT}', + 'url' => '${SHARE_URL}', + 'target' => '_blank', + ]; + update_field('hero_section', \$hero, 2); + echo \"Set hero_section.cta_button on page 2\n\"; + } else { + echo \"cta_button already set: \" . \$hero['cta_button']['url'] . \"\n\"; + } +" 2>/dev/null | tail -3 + +wp cache flush --allow-root 2>/dev/null || true + +# Clear PHP opcache if FPM is available +for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm php7.4-fpm; do + if systemctl is-active --quiet "$svc" 2>/dev/null; then + systemctl reload "$svc" 2>/dev/null && echo "Reloaded $svc" && break + fi +done + +echo "Done. Hard-refresh https://codeweek.eu/blog/ and check for class=\"hero-button\"" diff --git a/scripts/patch-blog-hero-cta-and-menu-icon.sh b/scripts/patch-blog-hero-cta-and-menu-icon.sh new file mode 100755 index 000000000..afc4178bc --- /dev/null +++ b/scripts/patch-blog-hero-cta-and-menu-icon.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# Fix blog hero CTA button + Blog dropdown arrow visibility. +# Run on server: bash patch-blog-hero-cta-and-menu-icon.sh + +set -euo pipefail + +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}" +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +SHARE_TEXT="Share your stories" +HERO_DESCRIPTION="Have a story, activity, or inspiring EU Code Week experience to share? We'd love to hear from you! Submit your experiences, highlights, and initiatives through the form and help us showcase the amazing work happening across the EU Code Week community." + +[[ -d "$THEME" ]] || { echo "Theme not found: $THEME"; exit 1; } +command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; } + +cd "$THEME" +chmod u+w front-page.php new.css acf-json/group_5efc9e8719cc2.json 2>/dev/null || true + +# --- 1) front-page.php: render hero CTA after description --- +python3 <<'PY' +import re +from pathlib import Path + +path = Path("front-page.php") +content = path.read_text() +snippet = """ + + + """ + +if "cta_button" in content: + print("front-page.php already has cta_button") +else: + match = re.search(r'(]*class="hero-description"[^>]*>.*?

)', content, re.S) + if not match: + raise SystemExit("Could not find hero-description in front-page.php") + path.write_text(content[: match.end()] + snippet + content[match.end() :]) + print("Patched front-page.php with hero CTA") +PY + +# --- 2) ACF JSON: ensure cta_button sub-field exists --- +python3 <<'PY' +import json +from pathlib import Path + +path = Path("acf-json/group_5efc9e8719cc2.json") +data = json.loads(path.read_text()) +for field in data.get("fields", []): + if field.get("name") != "hero_section": + continue + subs = field.setdefault("sub_fields", []) + if any(s.get("name") == "cta_button" for s in subs): + print("ACF JSON already has cta_button") + break + subs.append({ + "key": "field_codeweek_hero_cta_button", + "label": "Button", + "name": "cta_button", + "type": "link", + "return_format": "array", + }) + path.write_text(json.dumps(data, indent=4) + "\n") + print("Added cta_button to ACF JSON") + break +PY + +# --- 3) CSS: Blog simple dropdown — flex layout so arrow img is visible --- +python3 <<'PY' +from pathlib import Path + +css = Path("new.css") +text = css.read_text() +block = """ +/* Blog dropdown: show Share your stories arrow icon */ +#menu-item-5643 > .sub-menu > #menu-item-8142 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} +#menu-item-5643 > .sub-menu > #menu-item-8142 .rt-wp-menu-custom-fields-wrapper { + display: flex; + align-items: center; + flex-shrink: 0; +} +#menu-item-5643 > .sub-menu > #menu-item-8142 .rt-wp-menu-custom-fields-custom-html img { + display: block; + width: 20px; + height: auto; +} +""" +marker = "/* Blog dropdown: show Share your stories arrow icon */" +if marker in text: + print("Menu arrow CSS already present") +else: + css.write_text(text.rstrip() + "\n" + block) + print("Added Blog dropdown arrow CSS") +PY + +# --- 4) WP-CLI: hero CTA data + menu arrow meta --- +cd "$WP_ROOT" +wp acf sync --allow-root 2>/dev/null || true + +HERO_DESC_JSON=$(HERO_DESCRIPTION="$HERO_DESCRIPTION" python3 -c 'import json,os; print(json.dumps(os.environ["HERO_DESCRIPTION"]))') +wp eval --allow-root " + \$hero = get_field('hero_section', 2) ?: []; + \$hero['description'] = ${HERO_DESC_JSON}; + \$hero['cta_button'] = [ + 'title' => '${SHARE_TEXT}', + 'url' => '${SHARE_URL}', + 'target' => '_blank', + ]; + update_field('hero_section', \$hero, 2); + echo 'Hero CTA field saved on page 2\n'; +" + +SHARE_ID=$(wp menu item list 2 --fields=db_id,title --format=csv --allow-root \ + | awk -F, -v t="$SHARE_TEXT" '$2 ~ t {print $1; exit}') + +if [[ -n "${SHARE_ID:-}" ]]; then + wp eval --allow-root " + \$ref = 5845; + \$target = (int) ${SHARE_ID}; + \$meta = get_post_meta(\$ref, 'rt-wp-menu-custom-fields', true); + if (empty(\$meta)) { + \$meta = [ + 'selected-feature' => 'html', + 'html' => ['custom-html' => '\"arrow'], + ]; + } + update_post_meta(\$target, '_menu_item_url', '${SHARE_URL}'); + update_post_meta(\$target, 'rt-wp-menu-custom-fields', \$meta); + delete_transient('rt-wp-menu-custom-fields-' . \$target); + echo \"Menu item \$target arrow meta applied\n\"; + " +fi + +wp cache flush --allow-root 2>/dev/null || true +echo "Done. Hard-refresh https://codeweek.eu/blog/" diff --git a/scripts/patch-blog-hero-height.sh b/scripts/patch-blog-hero-height.sh new file mode 100644 index 000000000..9923da093 --- /dev/null +++ b/scripts/patch-blog-hero-height.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Quick hero height bump — run on the blog server (SSH as root): +# bash patch-blog-hero-height.sh + +set -euo pipefail + +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}" + +if [[ ! -d "$THEME" ]]; then + THEME=$(find /var/www -maxdepth 8 -type d -name 'eucodewe-1389' 2>/dev/null | head -1 || true) +fi + +[[ -d "$THEME" ]] || { echo "Theme not found"; exit 1; } + +cd "$THEME" +chmod u+w new.css 2>/dev/null || true +cp -a new.css "new.css.bak.$(date +%Y%m%d%H%M%S)" + +python3 <<'PY' +from pathlib import Path +import re + +css = Path("new.css") +text = css.read_text() + +replacements = [ + (r"(\.hero-section\s*\{[^}]*?)min-height:\s*\d+px;", r"\1min-height: 580px;"), + (r"(\.hero-section\s*\{[^}]*?)padding:\s*[^;]+;", r"\1padding: 3rem 0;"), + (r"(\.hero-content\s*\{[^}]*?)padding:\s*2rem;", r"\1padding: 2.5rem 3rem 3rem;"), + (r"(\t\.hero-section\s*\{[^}]*?)min-height:\s*\d+px;", r"\1min-height: 520px;"), + (r"(\t\.hero-section\s*\{\n flex-direction: column-reverse;[^}]*?)padding:\s*[^;]+;", r"\1padding: 0 0 3rem;\n min-height: 520px;"), +] + +for pattern, repl in replacements: + text, n = re.subn(pattern, repl, text, count=1, flags=re.S) + if n: + print(f"Patched: {pattern[:40]}...") + +if ".hero-description {" in text and "margin-bottom:" not in text.split(".hero-description {", 1)[1].split("}", 1)[0]: + text = text.replace( + ".hero-description {\n color: var(--Slate-500, #333E48);", + ".hero-description {\n margin-bottom: 1.25rem;\n color: var(--Slate-500, #333E48);", + 1, + ) + print("Added hero-description margin-bottom") + +css.write_text(text) +print("Done — hero min-height is now 580px (desktop)") +PY + +if command -v wp >/dev/null 2>&1; then + wp cache flush --path="$WP_ROOT" --allow-root 2>/dev/null || true +fi + +echo "Verify: https://codeweek.eu/blog/" diff --git a/scripts/patch-blog-menu-share-stories-icon.sh b/scripts/patch-blog-menu-share-stories-icon.sh new file mode 100755 index 000000000..70d1d75df --- /dev/null +++ b/scripts/patch-blog-menu-share-stories-icon.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Fix "Share your stories" Blog submenu: arrow icon + Office form URL. +# Run on server: WP_ROOT=/var/www/html/blog bash patch-blog-menu-share-stories-icon.sh + +set -euo pipefail + +WP_ROOT="${WP_ROOT:-/var/www/html/blog}" +SHARE_TEXT="Share your stories" +SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true" +ARROW_HTML='arrow right' + +[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found: $WP_ROOT"; exit 1; } +command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; } + +cd "$WP_ROOT" + +SHARE_ID="" +for menu in primary header main "Menu 1" 2; do + SHARE_ID=$(wp menu item list "$menu" --fields=db_id,title --format=csv --allow-root 2>/dev/null \ + | awk -F, -v t="$SHARE_TEXT" '$2 ~ t {print $1; exit}') || true + [[ -n "${SHARE_ID:-}" ]] && break +done + +if [[ -z "${SHARE_ID:-}" ]]; then + echo "Menu item '$SHARE_TEXT' not found" + exit 1 +fi + +echo "Patching menu item ID $SHARE_ID" + +# Copy arrow icon meta from an existing submenu item that already shows the arrow +REF_ID=$(wp menu item list 2 --fields=db_id,title --format=csv --allow-root 2>/dev/null \ + | awk -F, '$2 ~ /^Activities$/ {print $1; exit}') || true +REF_ID="${REF_ID:-5845}" + +wp eval --allow-root " +\$ref = (int) ${REF_ID}; +\$target = (int) ${SHARE_ID}; +\$meta = get_post_meta(\$ref, 'rt-wp-menu-custom-fields', true); +if (empty(\$meta) || ! is_array(\$meta)) { + \$meta = [ + 'selected-feature' => 'html', + 'html' => [ + 'custom-html' => '${ARROW_HTML}', + ], + ]; +} +update_post_meta(\$target, '_menu_item_url', '${SHARE_URL}'); +update_post_meta(\$target, 'rt-wp-menu-custom-fields', \$meta); +delete_transient('rt-wp-menu-custom-fields-' . \$target); +echo \"Menu item \$target: URL + arrow icon meta applied (copied from \$ref)\n\"; +" + +wp cache flush --allow-root 2>/dev/null || true +echo "Done. Hard-refresh https://codeweek.eu/blog/ and open Blog dropdown." From a8351d67ea604382d4f86eaed4762805c3acc9e9 Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 12 Jun 2026 12:50:10 +0100 Subject: [PATCH 4/4] push live and add to menu --- ...de-feed-reply-malte-email-fields-email.txt | 63 ----------------- ...deweek-de-feed-reply-malte-email-fields.md | 69 ------------------- 2 files changed, 132 deletions(-) delete mode 100644 docs/codeweek-de-feed-reply-malte-email-fields-email.txt delete mode 100644 docs/codeweek-de-feed-reply-malte-email-fields.md diff --git a/docs/codeweek-de-feed-reply-malte-email-fields-email.txt b/docs/codeweek-de-feed-reply-malte-email-fields-email.txt deleted file mode 100644 index 6b07cf959..000000000 --- a/docs/codeweek-de-feed-reply-malte-email-fields-email.txt +++ /dev/null @@ -1,63 +0,0 @@ -Subject: Re: Feed export — type_of_organisation, email fields, and visibility - -Hi Malte, - -Happy to clarify: - -1) type_of_organisation — object or string? - -For the feed, please put the organizer type under user.type, not as a top-level field. - -Our importer accepts either: -- "user": { "type": { "identifier": "non profit" } } (preferred), or -- "user": { "type": "non profit" } (also works) - -The top-level type_of_organisation field is not read by our current importer, so you can remove it once it's mapped into user.type. - - -2) "Map type_of_organisation to user.type.identifier" — same value? - -Yes. Use the same values as in the Excel guide / registration form, for example: -- school -- library -- non profit -- private business -- other - - -3) Email fields — which is public? - -Your three-email model maps like this: - -1) Personal email of event creator - Feed field: user.email - Our DB field: user_email - Visibility: Not public — used internally to link/create the owner account; visible only to EU Code Week ambassadors/organisers for moderation - -2) Official org email (e.g. info@…) - Feed field: (no dedicated field) - Visibility: Not stored separately; use user.company for org name, user.www for website - -3) Public contact email for the event - Feed field: user.publicEmail - Our DB field: contact_person - Visibility: Public — this is what we display as the event contact email when provided - -So yes, it's fine to send the personal creator email in user.email even though it's not intended to be public. That matches how our registration form works (user_email = internal contact; contact_person = optional public email). - -Recommended mapping: - -"user": { - "company": "Organisation name", - "email": "creator-personal@example.org", - "publicEmail": "public-contact@example.org", - "www": "https://organisation.example.org", - "type": { "identifier": "non profit" } -} - -If there's no public contact email for an event, leave publicEmail empty. - -Let us know if anything is unclear. - -Best regards, -Bernard diff --git a/docs/codeweek-de-feed-reply-malte-email-fields.md b/docs/codeweek-de-feed-reply-malte-email-fields.md deleted file mode 100644 index 56fade22a..000000000 --- a/docs/codeweek-de-feed-reply-malte-email-fields.md +++ /dev/null @@ -1,69 +0,0 @@ -# Reply to Malte — Feed format clarifications (email & organizer type) - -**Subject:** Re: Feed export — `type_of_organisation`, email fields, and visibility - ---- - -Hi Malte, - -Happy to clarify: - -## 1) `type_of_organisation` — object or string? - -For the feed, please put the organizer type under **`user.type`**, not as a top-level field. - -Our importer accepts **either**: - -- `"user": { "type": { "identifier": "non profit" } }` **(preferred)**, or -- `"user": { "type": "non profit" }` **(also works)** - -The top-level `type_of_organisation` field is **not read** by our current importer, so you can remove it once it’s mapped into `user.type`. - -## 2) “Map `type_of_organisation` → `user.type.identifier`” — same value? - -**Yes.** Use the same values as in the Excel guide / registration form, for example: - -- `school` -- `library` -- `non profit` -- `private business` -- `other` - -## 3) Email fields — which is public? - -Your three-email model maps like this: - -| Your field | Feed field | Our DB field | Visibility | -|---|---|---|---| -| 1) Personal email of event creator | `user.email` | `user_email` | **Not public** — used internally to link/create the owner account; visible only to EU Code Week ambassadors/organisers for moderation | -| 2) Official org email (e.g. info@…) | *(no dedicated field)* | — | Not stored separately; use `user.company` for org **name**, `user.www` for website | -| 3) Public contact email for the event | `user.publicEmail` | `contact_person` | **Public** — this is what we display as the event contact email when provided | - -So **yes**, it’s fine to send the personal creator email in `user.email` even though it’s not intended to be public. That matches how our registration form works (`user_email` = internal contact; `contact_person` = optional public email). - -### Recommended mapping - -```json -"user": { - "company": "Organisation name", - "email": "creator-personal@example.org", - "publicEmail": "public-contact@example.org", - "www": "https://organisation.example.org", - "type": { "identifier": "non profit" } -} -``` - -If there’s no public contact email for an event, leave `publicEmail` empty. - -Let us know if anything is unclear. - -Best regards, -Bernard - ---- - -## Internal reference - -- `user.email` is stripped from the public event page HTML for normal visitors (ambassadors/admins only for `user_email`). -- Optional public email: `user.publicEmail` → `contact_person` (displayed on event detail when provided). -- Importer source: `app/Console/Commands/api/GermanTraits.php`