├── templates ├── partials │ ├── microdata.html.twig │ └── seo_data.html.twig └── forms │ └── fields │ ├── seobutton │ └── seobutton.html.twig │ ├── json │ └── json.html.twig │ ├── schema │ └── schema.html.twig │ ├── twitter │ └── twitter.html.twig │ ├── google │ └── google.html.twig │ └── facebook │ └── facebook.html.twig ├── assets ├── logoseo.png ├── twitter.gif ├── twitter.png ├── facebook.gif ├── gravlogo3.png ├── article_json.png ├── logofacebook.jpg └── demoseoplugin.gif ├── seo.yaml ├── LICENSE ├── languages ├── en.yaml ├── ro.yaml ├── nl.yaml └── de.yaml ├── blueprints.yaml ├── CHANGELOG.md ├── README.md ├── css └── yoast-seo.min.css ├── seo.php └── blueprints └── seo.yaml /templates/partials/microdata.html.twig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logoseo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/logoseo.png -------------------------------------------------------------------------------- /assets/twitter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/twitter.gif -------------------------------------------------------------------------------- /assets/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/twitter.png -------------------------------------------------------------------------------- /assets/facebook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/facebook.gif -------------------------------------------------------------------------------- /assets/gravlogo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/gravlogo3.png -------------------------------------------------------------------------------- /assets/article_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/article_json.png -------------------------------------------------------------------------------- /assets/logofacebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/logofacebook.jpg -------------------------------------------------------------------------------- /templates/forms/fields/seobutton/seobutton.html.twig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/demoseoplugin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/HEAD/assets/demoseoplugin.gif -------------------------------------------------------------------------------- /seo.yaml: -------------------------------------------------------------------------------- 1 | 2 | enabled: true # Set to false to disable this plugin completely 3 | article: true 4 | restaurant: true 5 | event: true 6 | organization: true 7 | musicevent: true 8 | person: true 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT LICENSE 3 | ----------- 4 | 5 | Copyright (c) 2017 Paul Massendari, https://github.com/paulmassen/grav-plugin-seo 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /templates/forms/fields/json/json.html.twig: -------------------------------------------------------------------------------- 1 | {% if field.security is empty or authorize(array(field.security)) %} 2 | 3 | {% if field.title or field.underline %} 4 |

{% if grav.twig.twig.filters['tu'] is defined %}{{ field.title|tu }}{% else %}{{ field.title|t }}{% endif %}

5 | {% endif %} 6 | 7 | {% set context = admin.page(true) %} 8 | 43 | Envoyer 44 |
45 | {% if field.text %} 46 | {% endif %} 47 | 48 | {% if field.fields %} 49 |
50 | {% for field in field.fields %} 51 | {% if field.type %} 52 | {% set value = data.value(field.name) %} 53 | {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} 54 | {% endif %} 55 | {% endfor %} 56 |
57 | {% endif %} 58 | 59 | {% endif %} -------------------------------------------------------------------------------- /templates/forms/fields/schema/schema.html.twig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/partials/seo_data.html.twig: -------------------------------------------------------------------------------- 1 | {% set settings = config.plugins.seo.header %} 2 | 3 | 4 | 5 | 6 | 7 | {# if settings.robots or page.header.robots is defined %} 8 | 9 | {% endif #} 10 | 11 | {##} 12 | {# twitter #} 13 | 14 | {% if page.header.facebookenable != "0" %} 15 | 16 | 17 | {% if page.header.facebookimg %} 18 | 19 | {% else %} 20 | 21 | {% endif %} 22 | 23 | {% endif %} 24 | 25 | 26 | {% if header.articleenabled %} 27 | {% set fullimagepath = page.url(true) ~ "/" ~ header.article.image.url %} 28 | {% set fulllogopath = page.url(true) ~ "/" ~ header.article.publisher.logo.url %} 29 | {% set article = header.article|merge({'image' : { 30 | 'url': fullimagepath, 31 | '@type': 'ImageObject', 32 | 'height': header.article.image.height, 33 | 'width': header.article.image.width 34 | 35 | } }) %} 36 | {% set article = article|merge({'publisher': { '@type': 'Organization', 'name': header.article.publisher.name, 'logo':{'@type': 'ImageObject', 'url': fulllogopath } } }) %} 37 | 40 | {% endif %} 41 | {% if header.personenabled %} 42 | 45 | {% endif %} 46 | {% if header.eventenabled %} 47 | 50 | {% endif %} 51 | {% if header.musicalbumenabled %} 52 | {% set fullalbumimgpath = page.url(true) ~ "/" ~ header.musicalbum.image %} 53 | {% set musicalbum = header.musicalbum|merge({'image' : fullalbumimgpath }) %} 54 | 57 | {% endif %} 58 | {% if header.restaurantenabled %} 59 | {% set fullrestaurantimgpath = page.url(true) ~ "/" ~ header.restaurant.image %} 60 | {% set restaurant = header.restaurant|merge({'image' : fullrestaurantimgpath }) %} 61 | 64 | {% endif %} 65 | {% if header.musiceventenabled %} 66 | 74 | {% endif %} 75 | 76 | -------------------------------------------------------------------------------- /templates/forms/fields/twitter/twitter.html.twig: -------------------------------------------------------------------------------- 1 | 2 | {% if admin.route %} 3 | {% set context = admin.page(true) %} 4 | {% endif %} 5 | {% if field.title or field.underline %} 6 |

{% if grav.twig.twig.filters['tu'] is defined %}{{ field.title|tu }}{% else %}{{ field.title|t }}{% endif %}

7 | {% endif %} 8 | 9 | {% set context = admin.page(true) %} 10 |
Twitter team
@twitter
21 |
22 | 23 | 24 | 25 | 44 | {% if field.text %} 45 | {% endif %} 46 | 47 | {% if field.fields %} 48 |
49 | {% for field in field.fields %} 50 | {% if field.type %} 51 | {% set value = data.value(field.name) %} 52 | {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} 53 | {% endif %} 54 | {% endfor %} 55 |
56 | {% endif %} 57 | -------------------------------------------------------------------------------- /templates/forms/fields/google/google.html.twig: -------------------------------------------------------------------------------- 1 | {% if admin.route %} 2 | {% set context = admin.page(true) %} 3 | {% endif %} 4 | {% set title = (context.header.title ?: context.title) %} 5 | {% if field.title or field.underline %} 6 |

{% if grav.twig.twig.filters['tu'] is defined %}{{ field.title|tu }}{% else %}{{ field.title|t }}{% endif %}

7 | {% endif %} 8 | 9 |
10 |
11 | 12 |

13 | {{ context.header.googletitle|default(context.title ~ " | " ~ site.title )}}

14 |
{{context.url(true)|replace({"//":"//", "/":" › " }) }}
15 |
{{ context.header.googledesc|default(context.summary|striptags|truncate(140)) }}
16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 | 36 | {% if field.text %} 37 | {{ field.text|raw }} 38 | {% endif %} 39 | 40 | {% if field.fields %} 41 |
42 | {% for field in field.fields %} 43 | {% if field.type %} 44 | {% set value = data.value(field.name) %} 45 | {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} 46 | {% endif %} 47 | {% endfor %} 48 |
49 | {% endif %} 50 | 51 | -------------------------------------------------------------------------------- /languages/en.yaml: -------------------------------------------------------------------------------- 1 | PLUGIN_SEO: 2 | ROBOTS: "Select robots meta value" 3 | CANONICAL_URL: "Canonical URL" 4 | PLUGIN_STATUS: "Plugin status" 5 | STATUS_HELP: "Set to false to disable this plugin completely." 6 | GOOGLE_DESC: "Override Description" 7 | GOOGLE_TITLE: "Override Title" 8 | GOOGLE_PREVIEWDESC: "Here is a preview of your page on Google results" 9 | TWITTER_ENABLE: "Activate twitter card" 10 | TWITTER_CARD_SELECT: "Select twitter card type" 11 | TWITTER_CARD_GALLERY: "Select " 12 | TWITTER_CARD_PHOTO: "Photo" 13 | TWITTER_CARD_PRODUCT: "Product" 14 | TWITTER_CARD_SUMMARY: "Summary" 15 | TWITTER_CARD_PLAYER: "Player" 16 | TWITTER_CARD_APP: "App" 17 | TWITTER_CARD_SUMLARGIMG: "Summary Large Image" 18 | TWITTER_CARD_SECTITLE: "Twitter Card" 19 | TWITTER_SHAREIMG: "Twitter Image" 20 | TWITTER_TITLE: "Twitter title" 21 | TWITTER_DESCRIPTION: "Twitter Description" 22 | TWITTER_PREVIEWDESC: "Here is a preview of your page as shared on twitter" 23 | FACEBOOK_ENABLE: "Enable facebook" 24 | FACEBOOK_TITLE: "Facebook Title" 25 | FACEBOOK_DESC: "Facebook Description" 26 | FACEBOOK_SHAREIMG: "Facebook Share Image" 27 | FACEBOOK_PREVIEWDESC: "Here is a preview of your page as shared on Facebook" 28 | IMAGE_HEIGHT_PX: "Image Height (px)" 29 | IMAGE_WIDTH_PX: "Image Width (px)" 30 | ARTICLE_IMAGE: "Article Image" 31 | DATE_PUBLISHED: "Published date" 32 | DATE_MODIFIED: "Modification date" 33 | ARTICLE_DESCRIPTION: "Article Description" 34 | HEADLINE: "Headline" 35 | ADD_ARTICLE_MICRODATA: "Add Article Microdata" 36 | ARTICLE_AUTHOR: "Article Author" 37 | ARTICLE_PLACEHOLDER: "Will use page title if left empty" 38 | ARTICLE_DESC_PLACEHOLDER: "Will use summary if left empty" 39 | ARTICLE_PUBLISHER: "Article Publisher" 40 | ENABLEMUSICEVENT: "Enable Music Event" 41 | PUBLISHER_LOGO: "Publisher Logo" 42 | PUBLISHER_NAME: "Publisher Name" 43 | ARTICLE_BODY: "Full Article Body" 44 | ADDMUSICEVENT: "Add Music Event Microdata" 45 | LOCATION_NAME: "Location Name" 46 | LOCATION_ADDRESS: "Location Address" 47 | MUSICEVENT_NAME: "Music Event Name" 48 | MUSICEVENT_DESCRIPTION: "Description" 49 | MUSICEVENT_IMAGE: "Event Image" 50 | MUSICEVENT_URL: "Music Event Url" 51 | PREORDER: "Preorder" 52 | OUTOFSTOCK: "Out of Stock" 53 | PRESALE: "Pre Sale" 54 | INSTOCK: "In Stock" 55 | DISCONTINUED: "Discontinued" 56 | INSTOREONLY: "In Store Only" 57 | SOLDOUT: "Sold Out" 58 | ONLINEONLY: "Online Only" 59 | TICKET_URL: "Ticket URL" 60 | VALID_FROM: "Valid From" 61 | MUSICEVENT_PRICE: "Price" 62 | PRICE_CURRENCY: "Price Currency" 63 | ADD_PERFORMER: "Add Performer" 64 | PERFORMER_TYPE: "Performer Type" 65 | MUSICGROUP: "Music Group" 66 | PERSON: "Person" 67 | PERFORMER_NAME: "Performer Name" 68 | MUSICEVENT_DATE_START: "Start Date" 69 | MUSICEVENT_DATE_END: "End Date" 70 | WORK_PERFORMED: "Works Performed" 71 | WORK_PERFORMED_NAME: "Name of work" 72 | ADD_PERSON: "Add Person Microdata" 73 | PERSON_NAME: "Person Name" 74 | PERSON_JOB_TITLE: "Job Title" 75 | PERSON_ADDRESS_LOCALITY: "Address Locality" 76 | PERSON_ADDRESS_REGION: "Address Region" 77 | ADD_EVENT: "Add Event Microdata" 78 | EVENT_NAME: "Event Name" 79 | ENABLEEVENT: "Enable Event Microdata" 80 | EVENT_START_DATE: "Event Start Date" 81 | EVENT_END_DATE: "Event End Date" 82 | EVENT_DESCRIPTION: "Event Description" 83 | EVENT_LOCATION_NAME: "Event Location Name" 84 | EVENT_LOCATION_STREET_ADDRESS: "Event Location Street Address" 85 | EVENT_LOCATION_ADDRESS_LOCALITY: "Event Location Address Locality" 86 | EVENT_LOCATION_ADDRESS_REGION: "Event Location Region" 87 | EVENT_OFFER_PRICE: "Event Offer Price" 88 | EVENT_OFFER_CURRENCY: "Event Offer Currency" 89 | EVENT_OFFER_URL: "Event Offer Url" 90 | ADD_MUSICALBUM: "Add Music Album Microdata" 91 | MUSICALBUM_NAME: "Music Album Name" 92 | MUSICALBUM_NAMEOFBAND: "Name of Band" 93 | MUSICALBUM_IMAGE: "Image" 94 | MUSICALBUM_URL: "Album URL" 95 | MUSICALBUM_GENRE: "Genre" 96 | MUSICALBUM_TRACK_NUMBER: "Track Number" 97 | MUSICALBUM_ADD_TRACK: "Add Track" 98 | MUSICALBUM_TRACK_POSITION: "Track Position" 99 | MUSICALBUM_TRACK_NAME: "Track Name" 100 | MUSICALBUM_TRACK_URL: "Track URL" 101 | MUSICALBUM_TRACK_DURATION: "Track Duration" 102 | LOCATION_URL: "Location Url" 103 | ADD_RESTAURANT: "Add Restaurant Microdata" 104 | RESTAURANT_NAME: "Restaurant Name" 105 | RESTAURANT_ACCEPT_RESERVATION: "Accept Reservation" 106 | RESTAURANT_MENU_URL: "Menu URL" 107 | RESTAURANT_IMAGE: "Image" 108 | RESTAURANT_CUISINE: "Cuisine" 109 | RESTAURANT_PRICE_RANGE: "Price Range" 110 | RESTAURANT_POSTAL_CODE: "Postal Code" 111 | RESTAURANT_STREET: "Street" 112 | RESTAURANT_LOCALITY: "Locality" 113 | LOCATION_URL_PLACEHOLDER: "Url to the venue website" 114 | RESTAURANT_REGION: "Region" 115 | RESTAURANT_TELEPHONE: "Telephone" 116 | ENABLE_PERSON: "Enable Person Microdata" 117 | ENABLEORGA: "Enable Organization Microdata" 118 | MUSICEVENT_URL_PLACEHOLDER: "Url to the official page of this music event" -------------------------------------------------------------------------------- /languages/ro.yaml: -------------------------------------------------------------------------------- 1 | PLUGIN_SEO: 2 | ROBOTS: "Selectează valoare pentru roboții meta" 3 | CANONICAL_URL: "URL canonical" 4 | PLUGIN_STATUS: "Status modul" 5 | STATUS_HELP: "Setează ”fals” pentru a dezactiva acest modul complet." 6 | GOOGLE_DESC: "Adaugă descrierea pentru Google" 7 | GOOGLE_TITLE: "Setează titlul pentru Google" 8 | GOOGLE_PREVIEWDESC: "Acesta este previzualizarea paginii așa cum apare în rezultatele Google" 9 | TWITTER_ENABLE: "Activează cardul Twitter" 10 | TWITTER_CARD_SELECT: "Selectează tipul de card Twitter" 11 | TWITTER_CARD_GALLERY: "Selectează " 12 | TWITTER_CARD_PHOTO: "Imagine" 13 | TWITTER_CARD_PRODUCT: "Produs" 14 | TWITTER_CARD_SUMMARY: "Rezumat" 15 | TWITTER_CARD_PLAYER: "Jucător" 16 | TWITTER_CARD_APP: "Aplicație" 17 | TWITTER_CARD_SUMLARGIMG: "Imagine mare pentu Rezumat" 18 | TWITTER_CARD_SECTITLE: "Cardul Twitter" 19 | TWITTER_SHAREIMG: "Imaginea Twitter" 20 | TWITTER_TITLE: "Titlul Twitter" 21 | TWITTER_DESCRIPTION: "Descrierea Twitter" 22 | TWITTER_PREVIEWDESC: "Acesta este previzualizarea paginii așa cum apare pe Twitter" 23 | FACEBOOK_ENABLE: "Activează Facebook" 24 | FACEBOOK_TITLE: "Titlul Facebook" 25 | FACEBOOK_DESC: "Descrierea Facebook" 26 | FACEBOOK_SHAREIMG: "Imaginea pentru Facebook" 27 | FACEBOOK_PREVIEWDESC: "Acesta este previzualizarea paginii așa cum apare pe Facebook" 28 | IMAGE_HEIGHT_PX: "Înălțimea Imaginii (px)" 29 | IMAGE_WIDTH_PX: "Lățimea Imaginii (px)" 30 | ARTICLE_IMAGE: "Imaginea Articolului" 31 | DATE_PUBLISHED: "Data publicării" 32 | DATE_MODIFIED: "Data modificării" 33 | ARTICLE_DESCRIPTION: "Descrierea articolului" 34 | HEADLINE: "Titlu" 35 | ADD_ARTICLE_MICRODATA: "Adaugă Microdata pentru articol" 36 | ARTICLE_AUTHOR: "Autorul articolului" 37 | ARTICLE_PUBLISHER: "Publicistul articolului" 38 | ENABLEMUSICEVENT: "Activează Evenimentul Muzică" 39 | PUBLISHER_LOGO: "Logo-ul publicistului" 40 | PUBLISHER_NAME: "Numele publicistului" 41 | ARTICLE_BODY: "Conținutul complet al articolului" 42 | ADDMUSICEVENT: "Adaugă Microdata pentru evenimentul muzical" 43 | LOCATION_NAME: "Numele locației" 44 | LOCATION_ADDRESS: "Adresa locației" 45 | MUSICEVENT_NAME: "Numele evenimentului muzical" 46 | MUSICEVENT_DESCRIPTION: "Descrierea" 47 | MUSICEVENT_IMAGE: "Imaginea evenimentului" 48 | MUSICEVENT_URL: "Link-ul evenimentului" 49 | PREORDER: "Precomandă" 50 | OUTOFSTOCK: "Nu este în stoc" 51 | PRESALE: "Prevânzare" 52 | INSTOCK: "În stoc" 53 | DISCONTINUED: "Nu mai este disponibil" 54 | INSTOREONLY: "Doar în magazin" 55 | SOLDOUT: "Toate copiile vândute" 56 | ONLINEONLY: "Doar pe internet" 57 | TICKET_URL: "Link pentru bilete" 58 | VALID_FROM: "Valid de la" 59 | MUSICEVENT_PRICE: "Preț" 60 | PRICE_CURRENCY: "Moneda prețului" 61 | ADD_PERFORMER: "Adaugă artist" 62 | PERFORMER_TYPE: "Tipul artisului" 63 | MUSICGROUP: "Grupul muzical" 64 | PERSON: "Persoană" 65 | PERFORMER_NAME: "Numele artistului" 66 | MUSICEVENT_DATE_START: "Ora de începere" 67 | MUSICEVENT_DATE_END: "Ora de terminare" 68 | WORK_PERFORMED: "Listă cântece" 69 | WORK_PERFORMED_NAME: "Numele cântecelor" 70 | ADD_PERSON: "Adaugă Microdata pentru persoană" 71 | PERSON_NAME: "Numele persoanei" 72 | PERSON_JOB_TITLE: "Funcția" 73 | PERSON_ADDRESS_LOCALITY: "Localitatea" 74 | PERSON_ADDRESS_REGION: "Regiunea / Județul" 75 | ADD_EVENT: "Adaugă Microdata pentru eveniment" 76 | EVENT_NAME: "Numele evenimentului" 77 | EVENT_START_DATE: "Data de începere" 78 | EVENT_END_DATE: "Data de terminare" 79 | EVENT_DESCRIPTION: "Descrierea evenimentului" 80 | EVENT_LOCATION_NAME: "Locația evenimentului" 81 | EVENT_LOCATION_STREET_ADDRESS: "Strada" 82 | EVENT_LOCATION_ADDRESS_LOCALITY: "Localitatea" 83 | EVENT_LOCATION_ADDRESS_REGION: "Regiunea / Județul" 84 | EVENT_OFFER_PRICE: "Oferta de preț pentru eveniment" 85 | EVENT_OFFER_CURRENCY: "Moneda prețului" 86 | EVENT_OFFER_URL: "Link-ul evenimentului" 87 | ADD_MUSICALBUM: "Adaugă Microdata pentru album" 88 | MUSICALBUM_NAME: "Numele albumului" 89 | MUSICALBUM_NAMEOFBAND: "Numele formației" 90 | MUSICALBUM_IMAGE: "Imaginea" 91 | MUSICALBUM_URL: "Link-ul album" 92 | MUSICALBUM_GENRE: "Tipul de muzică" 93 | MUSICALBUM_TRACK_NUMBER: "Numărul cântecului" 94 | MUSICALBUM_ADD_TRACK: "Adaugă cântec" 95 | MUSICALBUM_TRACK_POSITION: "Poziția cântecului" 96 | MUSICALBUM_TRACK_NAME: "Numele cântecului" 97 | MUSICALBUM_TRACK_URL: "Link-ul cântecului" 98 | MUSICALBUM_TRACK_DURATION: "Durata cântecului" 99 | LOCATION_URL: "Link-ul Locației" 100 | ADD_RESTAURANT: "Adaugă Microdata pentru Restaurant" 101 | RESTAURANT_NAME: "Numele Restaurantului" 102 | RESTAURANT_ACCEPT_RESERVATION: "Acceptă Rezervări" 103 | RESTAURANT_MENU_URL: "Link-ul meniului" 104 | RESTAURANT_IMAGE: "Imaginea" 105 | RESTAURANT_CUISINE: "Tip de bucătărie" 106 | RESTAURANT_PRICE_RANGE: "Prețuri" 107 | RESTAURANT_POSTAL_CODE: "Codul Poștal" 108 | RESTAURANT_STREET: "Strada" 109 | RESTAURANT_LOCALITY: "Localitatea" 110 | RESTAURANT_REGION: "Regiunea / Județul" 111 | RESTAURANT_TELEPHONE: "Număr de Telefon" 112 | -------------------------------------------------------------------------------- /languages/nl.yaml: -------------------------------------------------------------------------------- 1 | PLUGIN_SEO: 2 | ROBOTS: "Selecteer robots meta waarde" 3 | CANONICAL_URL: "Canonical URL" 4 | PLUGIN_STATUS: "Plugin status" 5 | STATUS_HELP: "Zet op false om deze plugin volledig stop te zetten." 6 | GOOGLE_DESC: "Overschrijf omschrijving" 7 | GOOGLE_TITLE: "Overschrijf titel" 8 | GOOGLE_PREVIEWDESC: "Hier is een preview van jouw pagina in de google resultaten." 9 | TWITTER_ENABLE: "Activeer twitter kaart" 10 | TWITTER_CARD_SELECT: "Selecteer twitter kaart type" 11 | TWITTER_CARD_GALLERY: "Selecteer" 12 | TWITTER_CARD_PHOTO: "Foto" 13 | TWITTER_CARD_PRODUCT: "Product" 14 | TWITTER_CARD_SUMMARY: "Samenvatting" 15 | TWITTER_CARD_PLAYER: "Speler" 16 | TWITTER_CARD_APP: "App" 17 | TWITTER_CARD_SUMLARGIMG: "Samenvatting grootte afbeelding" 18 | TWITTER_CARD_SECTITLE: "Twitter kaart" 19 | TWITTER_SHAREIMG: "Twitter Afbeelding" 20 | TWITTER_TITLE: "Twitter titel" 21 | TWITTER_DESCRIPTION: "Twitter Omschrijving" 22 | TWITTER_PREVIEWDESC: "Hier is een preview van jouw pagina op twitter" 23 | FACEBOOK_ENABLE: "Activeer facebook" 24 | FACEBOOK_TITLE: "Facebook Titel" 25 | FACEBOOK_DESC: "Facebook Omschrijving" 26 | FACEBOOK_SHAREIMG: "Facebook Deel Afbeelding" 27 | FACEBOOK_PREVIEWDESC: "Hier is een preview van jouw pagina op Facebook" 28 | IMAGE_HEIGHT_PX: "Hoogte afbeelding (px)" 29 | IMAGE_WIDTH_PX: "Afbeelding breedte (px)" 30 | ARTICLE_IMAGE: "Artikel afbeelding" 31 | DATE_PUBLISHED: "Publicatie datum" 32 | DATE_MODIFIED: "Modificatie datum" 33 | ARTICLE_DESCRIPTION: "Artikel Omschrijving" 34 | HEADLINE: "Kopstuk" 35 | ADD_ARTICLE_MICRODATA: "Voeg artike microdata toe" 36 | ARTICLE_AUTHOR: "Auteur artikel" 37 | ARTICLE_PLACEHOLDER: "Pagina titel zal gebruikt worden indien leeg" 38 | ARTICLE_DESC_PLACEHOLDER: "Samenvatting zal gebruikt worden indien leeg" 39 | ARTICLE_PUBLISHER: "Auteur artikel" 40 | ENABLEMUSICEVENT: "Activeer muziek event" 41 | PUBLISHER_LOGO: "Auteur Logo" 42 | PUBLISHER_NAME: "Auteur Naam" 43 | ARTICLE_BODY: "Artiekel inhoud" 44 | ADDMUSICEVENT: "Voeg muziek microdata event toe" 45 | LOCATION_NAME: "Locatie Naam" 46 | LOCATION_ADDRESS: "Locatie adres" 47 | MUSICEVENT_NAME: "Naam muziek event" 48 | MUSICEVENT_DESCRIPTION: "Omschrijving" 49 | MUSICEVENT_IMAGE: "Afbeelding event" 50 | MUSICEVENT_URL: "Muziek Event Url" 51 | PREORDER: "Preorder" 52 | OUTOFSTOCK: "Niet in Stock" 53 | PRESALE: "Voorverkoop" 54 | INSTOCK: "In Stock" 55 | DISCONTINUED: "Stopgezet" 56 | INSTOREONLY: "In Store Only" 57 | SOLDOUT: "Uitverkocht" 58 | ONLINEONLY: "Alleen online" 59 | TICKET_URL: "Ticket URL" 60 | VALID_FROM: "Geldig vanaf" 61 | MUSICEVENT_PRICE: "Prijs" 62 | PRICE_CURRENCY: "Prijs valuta" 63 | ADD_PERFORMER: "Voeg artiest toe" 64 | PERFORMER_TYPE: "Artiest Type" 65 | MUSICGROUP: "Muziek groep" 66 | PERSON: "Persoon" 67 | PERFORMER_NAME: "Artiest naam" 68 | MUSICEVENT_DATE_START: "Start datum" 69 | MUSICEVENT_DATE_END: "Eind Datum" 70 | WORK_PERFORMED: "Works Performed" 71 | WORK_PERFORMED_NAME: "Naam werk" 72 | ADD_PERSON: "Voeg persoon microdata toe" 73 | PERSON_NAME: "Persoon Naam" 74 | PERSON_JOB_TITLE: "Job Titel" 75 | PERSON_ADDRESS_LOCALITY: "Adres Locality" 76 | PERSON_ADDRESS_REGION: "Adres regio" 77 | ADD_EVENT: "Voeg Event Microdata toe" 78 | EVENT_NAME: "Event Naam" 79 | ENABLEEVENT: "Voeg Event Microdata toe" 80 | EVENT_START_DATE: "Event Start Datum" 81 | EVENT_END_DATE: "Event Eind Datum" 82 | EVENT_DESCRIPTION: "Event Omschrijving" 83 | EVENT_LOCATION_NAME: "Event locatie naam" 84 | EVENT_LOCATION_STREET_ADDRESS: "Event Locatie Straat adres" 85 | EVENT_LOCATION_ADDRESS_LOCALITY: "Event locatie adres Locality" 86 | EVENT_LOCATION_ADDRESS_REGION: "Event locatie regio" 87 | EVENT_OFFER_PRICE: "Event Prijs" 88 | EVENT_OFFER_CURRENCY: "Event Valuta" 89 | EVENT_OFFER_URL: "Event aanbieding Url" 90 | ADD_MUSICALBUM: "Voeg Music Album Microdata toe" 91 | MUSICALBUM_NAME: "Muziek Album Naam" 92 | MUSICALBUM_NAMEOFBAND: "Naam Band" 93 | MUSICALBUM_IMAGE: "Afbeelding" 94 | MUSICALBUM_URL: "Album URL" 95 | MUSICALBUM_GENRE: "Genre" 96 | MUSICALBUM_TRACK_NUMBER: "Muzieknummer" 97 | MUSICALBUM_ADD_TRACK: "Voeg Track toe" 98 | MUSICALBUM_TRACK_POSITION: "Track Positie" 99 | MUSICALBUM_TRACK_NAME: "Track Naam" 100 | MUSICALBUM_TRACK_URL: "Track URL" 101 | MUSICALBUM_TRACK_DURATION: "Track looptijd" 102 | LOCATION_URL: "Locatie Url" 103 | ADD_RESTAURANT: "Voeg Restaurant Microdata toe" 104 | RESTAURANT_NAME: "Restaurant Naam" 105 | RESTAURANT_ACCEPT_RESERVATION: "Accepteer Reservatie" 106 | RESTAURANT_MENU_URL: "Menu URL" 107 | RESTAURANT_IMAGE: "Afbeelding" 108 | RESTAURANT_CUISINE: "Keuken" 109 | RESTAURANT_PRICE_RANGE: "Prijs" 110 | RESTAURANT_POSTAL_CODE: "Post code" 111 | RESTAURANT_STREET: "Straat" 112 | RESTAURANT_LOCALITY: "Plaats" 113 | LOCATION_URL_PLACEHOLDER: "Url naar het menu op de website" 114 | RESTAURANT_REGION: "Regio" 115 | RESTAURANT_TELEPHONE: "Telefoon" 116 | ENABLE_PERSON: "Voeg persoon Microdata toe" 117 | ENABLEORGA: "Voeg Organisatie Microdata toe" 118 | MUSICEVENT_URL_PLACEHOLDER: "Url naar de officiële pagina van het muziek event" -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: "SEO" 2 | version: "3.0.0" 3 | description: "Manage your site’s meta tags for display in search engine results or social media networks. Includes an easy-to-use live preview feature." 4 | icon: "google" 5 | testing: true 6 | author: 7 | name: "Paul Massendari" 8 | email: "paul@massendari.com" 9 | homepage: "https://github.com/paulmassen/grav-plugin-seo" 10 | keywords: ["seo", "meta", "microdata", "schema", "plugin"] 11 | bugs: "https://github.com/paulmassen/grav-plugin-seo/issues" 12 | license: "MIT/GPL" 13 | dependencies: 14 | - { name: grav, version: '>=1.7.0' } 15 | - { name: admin, version: '>=1.10.0' } 16 | 17 | form: 18 | validation: strict 19 | fields: 20 | plugin_section: 21 | type: section 22 | title: General Settings 23 | underline: true 24 | enabled: 25 | type: toggle 26 | label: PLUGIN_ADMIN.PLUGIN_STATUS 27 | highlight: 1 28 | default: 1 29 | options: 30 | 1: PLUGIN_ADMIN.ENABLED 31 | 0: PLUGIN_ADMIN.DISABLED 32 | validate: 33 | type: bool 34 | facebookid: 35 | type: text 36 | label: Facebook App ID 37 | twitterid: 38 | type: text 39 | label: Twitter ID 40 | placeholder: '@yourusername' 41 | twitter_default: 42 | type: toggle 43 | label: On page creation, enable twitter metadata? 44 | default: 1 45 | highlight: 1 46 | options: 47 | 1: PLUGIN_ADMIN.ENABLED 48 | 0: PLUGIN_ADMIN.DISABLED 49 | facebook_default: 50 | type: toggle 51 | label: On page creation, enable facebook metadata? 52 | default: 1 53 | highlight: 1 54 | options: 55 | 1: PLUGIN_ADMIN.ENABLED 56 | 0: PLUGIN_ADMIN.DISABLED 57 | 58 | microdata_section: 59 | type: section 60 | title: Microdata Settings 61 | underline: true 62 | md_columns: 63 | type: columns 64 | fields: 65 | mdcolumn1: 66 | type: column 67 | fields: 68 | article: 69 | type: toggle 70 | label: Enable Article Microdata 71 | highlight: 1 72 | default: 1 73 | options: 74 | 1: PLUGIN_ADMIN.ENABLED 75 | 0: PLUGIN_ADMIN.DISABLED 76 | validate: 77 | type: bool 78 | event: 79 | type: toggle 80 | label: Enable Event Microdata 81 | highlight: 1 82 | default: 1 83 | options: 84 | 1: PLUGIN_ADMIN.ENABLED 85 | 0: PLUGIN_ADMIN.DISABLED 86 | validate: 87 | type: bool 88 | restaurant: 89 | type: toggle 90 | label: Enable Restaurant Microdata 91 | highlight: 1 92 | default: 1 93 | options: 94 | 1: PLUGIN_ADMIN.ENABLED 95 | 0: PLUGIN_ADMIN.DISABLED 96 | validate: 97 | type: bool 98 | musicevent: 99 | type: toggle 100 | label: Enable Music Event Microdata 101 | highlight: 1 102 | default: 1 103 | options: 104 | 1: PLUGIN_ADMIN.ENABLED 105 | 0: PLUGIN_ADMIN.DISABLED 106 | validate: 107 | type: bool 108 | customjson: 109 | type: toggle 110 | label: Enable Custom Json Microdata 111 | highlight: 1 112 | default: 1 113 | options: 114 | 1: PLUGIN_ADMIN.ENABLED 115 | 0: PLUGIN_ADMIN.DISABLED 116 | validate: 117 | type: bool 118 | mdcolumn2: 119 | type: column 120 | fields: 121 | person: 122 | type: toggle 123 | label: Enable Person Microdata 124 | highlight: 1 125 | default: 1 126 | options: 127 | 1: PLUGIN_ADMIN.ENABLED 128 | 0: PLUGIN_ADMIN.DISABLED 129 | validate: 130 | type: bool 131 | organization: 132 | type: toggle 133 | label: Enable Organization Microdata 134 | highlight: 1 135 | default: 1 136 | options: 137 | 1: PLUGIN_ADMIN.ENABLED 138 | 0: PLUGIN_ADMIN.DISABLED 139 | validate: 140 | type: bool 141 | musicalbum: 142 | type: toggle 143 | label: Enable Music Album Microdata 144 | highlight: 1 145 | default: 1 146 | options: 147 | 1: PLUGIN_ADMIN.ENABLED 148 | 0: PLUGIN_ADMIN.DISABLED 149 | validate: 150 | type: bool 151 | product: 152 | type: toggle 153 | label: Enable Product Microdata 154 | highlight: 1 155 | default: 1 156 | options: 157 | 1: PLUGIN_ADMIN.ENABLED 158 | 0: PLUGIN_ADMIN.DISABLED 159 | validate: 160 | type: bool 161 | 162 | -------------------------------------------------------------------------------- /languages/de.yaml: -------------------------------------------------------------------------------- 1 | PLUGIN_SEO: 2 | ROBOTS: "Robots-Meta-Wert auswählen" 3 | CANONICAL_URL: "Kanonische URL" 4 | PLUGIN_STATUS: "Plugin-Status" 5 | STATUS_HELP: "Auf false setzen, um dieses Plugin komplett zu deaktivieren" 6 | GOOGLE_DESC: "Beschreibung überschreiben" 7 | GOOGLE_TITLE: "Überschreibungs-Titel" 8 | GOOGLE_PREVIEWDESC: "Hier sehen Sie eine Vorschau auf Ihre Seite in den Google-Ergebnissen" 9 | TWITTER_ENABLE: "Twitter-Karte aktivieren" 10 | TWITTER_CARD_SELECT: "Twitter-Kartentyp auswählen" 11 | TWITTER_CARD_GALLERY: "Wählen Sie " 12 | TWITTER_CARD_PHOTO: "Foto" 13 | TWITTER_CARD_PRODUCT: "Produkt" 14 | TWITTER_CARD_SUMMARY: "Zusammenfassung" 15 | TWITTER_CARD_PLAYER: "Spieler" 16 | TWITTER_CARD_APP: "Anwendung" 17 | TWITTER_CARD_SUMLARGIMG: "Zusammenfassung großes Bild" 18 | TWITTER_CARD_SECTITLE: "Twitter-Karte" 19 | TWITTER_SHAREIMG: "Twitter-Bild" 20 | TWITTER_TITLE: "Twitter-Titel" 21 | TWITTER_DESCRIPTION: "Twitter-Beschreibung" 22 | TWITTER_PREVIEWDESC: "Hier ist eine Vorschau Ihrer Seite, wie sie auf Twitter geteilt wird" 23 | FACEBOOK_ENABLE: "Facebook aktivieren" 24 | FACEBOOK_TITLE: "Facebook-Titel" 25 | FACEBOOK_DESC: "Facebook Beschreibung" 26 | FACEBOOK_SHAREIMG: "Facebook-Freigabebild" 27 | FACEBOOK_PREVIEWDESC: "Hier ist eine Vorschau Ihrer Seite, wie sie auf Facebook geteilt wird" 28 | IMAGE_HEIGHT_PX: "Bildhöhe (px)" 29 | IMAGE_WIDTH_PX: "Bildbreite (px)" 30 | ARTICLE_IMAGE: "Artikel-Bild" 31 | DATE_PUBLISHED: "Veröffentlichungsdatum" 32 | DATE_MODIFIED: "Änderungsdatum" 33 | ARTICLE_DESCRIPTION: "Artikelbeschreibung" 34 | HEADLINE: "Überschrift" 35 | ADD_ARTICLE_MICRODATA: "Artikel-Mikrodaten hinzufügen" 36 | ARTICLE_AUTHOR: "Artikel-Autor" 37 | ARTICLE_PLACEHOLDER: "Wird Seitentitel verwenden, wenn leer gelassen" 38 | ARTICLE_DESC_PLACEHOLDER: "Verwendet Zusammenfassung, wenn leer gelassen" 39 | ARTICLE_PUBLISHER: "Artikel-Herausgeber" 40 | ENABLEMUSICEVENT: "Musik-Ereignis einschalten" 41 | PUBLISHER_LOGO: "Herausgeber-Logo" 42 | VERLAG_NAME: "Verlagsname" 43 | ARTICLE_BODY: "Vollständiger Artikeltext" 44 | ADDMUSICEVENT: "Musikereignis-Mikrodaten hinzufügen" 45 | LOCATION_NAME: "Ortsname" 46 | LOCATION_ADDRESS: "Location-Adresse" 47 | MUSIKEVENT_NAME: "Name des Musikevents" 48 | MUSICEVENT_DESCRIPTION: "Beschreibung" 49 | MUSICEVENT_IMAGE: "Event-Bild" 50 | MUSICEVENT_URL: "Musik-Event Url" 51 | PREORDER: "Vorbestellung" 52 | OUTOFSTOCK: "Nicht auf Lager" 53 | PRESALE: "Vorverkauf" 54 | INSTOCK: "Auf Lager" 55 | DISCONTINUED: "Abgekündigt" 56 | INSTOREONLY: "Nur im Lager" 57 | OUTOFSTOCK: "Nicht auf Lager" 58 | SOLDOUT: "Ausverkauft" 59 | ONLINEONLY: "Nur online" 60 | TICKET_URL: "Ticket-URL" 61 | VALID_FROM: "Gültig von" 62 | MUSICEVENT_PRICE: "Preis" 63 | PRICE_CURRENCY: "Preis-Währung" 64 | ADD_PERFORMER: "Darsteller hinzufügen" 65 | PERFORMER_TYPE: "Darsteller-Typ" 66 | MUSICGROUP: "Musikgruppe" 67 | PERSON: "Person" 68 | PERFORMER_NAME: "Name des Interpreten" 69 | MUSICEVENT_DATE_START: "Startdatum" 70 | MUSICEVENT_DATE_END: "Enddatum" 71 | WORK_PERFORMED: "Aufgeführte Werke" 72 | WERK_AUFGEFUEHRT_NAME: "Name der Arbeit" 73 | ADD_PERSON: "Personen-Mikrodaten hinzufügen" 74 | PERSON_NAME: "Name der Person" 75 | PERSON_JOB_TITLE: "Berufsbezeichnung" 76 | PERSON_ADDRESS_LOCALITY: "Ort" 77 | PERSON_ADDRESS_REGION: "Region" 78 | ADD_EVENT: "Ereignis-Mikrodaten hinzufügen" 79 | EVENT_NAME: "Ereignis-Name" 80 | ENABLEEVENT: "Veranstaltungs-Mikrodaten einschalten" 81 | EVENT_START_DATE: "Ereignis-Startdatum" 82 | EVENT_END_DATE: "Ereignis-Enddatum" 83 | EVENT_DESCRIPTION: "Ereignisbeschreibung" 84 | EVENT_LOCATION_NAME: "Name des Veranstaltungsortes" 85 | EVENT_LOCATION_STREET_ADDRESS: "Straße und Hausnummer Veranstaltungsortes" 86 | EVENT_LOCATION_ADDRESS_LOCALITY: "Postleitzahl und Ort der Veranstaltung" 87 | EVENT_LOCATION_ADDRESS_REGION: "Region des Veranstaltungsortes" 88 | EVENT_OFFER_PRICE: "Veranstaltungsangebotspreis" 89 | EVENT_OFFER_CURRENCY: "Veranstaltungsangebot Währung" 90 | EVENT_OFFER_URL: "Webseite der Veranstaltung" 91 | ADD_MUSICALBUM: "Musikalbum-Mikrodaten hinzufügen" 92 | MUSICALBUM_NAME: "Musikalbum-Name" 93 | MUSICALBUM_NAMEOFBAND: "Name der Band" 94 | MUSICALBUM_IMAGE: "Bild" 95 | MUSICALBUM_URL: "Album-URL" 96 | MUSICALBUM_GENRE: "Genre" 97 | MUSICALBUM_TRACK_NUMBER: "Titelnummer" 98 | MUSICALBUM_ADD_TRACK: "Track hinzufügen" 99 | MUSICALBUM_TRACK_POSITION: "Track-Position" 100 | MUSICALBUM_TRACK_NAME: "Track-Titel" 101 | MUSICALBUM_TRACK_URL: "Track-URL" 102 | MUSICALBUM_TRACK_DURATION: "Spieldauer" 103 | LOCATION_URL: "Standort-URL" 104 | ADD_RESTAURANT: "Restaurant-Mikrodaten hinzufügen" 105 | RESTAURANT_NAME: "Restaurantname" 106 | RESTAURANT_ACCEPT_RESERVATION: "Reservierung akzeptieren" 107 | RESTAURANT_MENU_URL: "Menü-URL" 108 | RESTAURANT_IMAGE: "Bild" 109 | RESTAURANT_CUISINE: "Küche" 110 | RESTAURANT_PRICE_RANGE: "Preisspanne" 111 | RESTAURANT_POSTAL_CODE: "Postleitzahl" 112 | RESTAURANT_STREET: "Straße" 113 | RESTAURANT_LOCALITY: "Ortschaft" 114 | LOCATION_URL_PLACEHOLDER: "Url zur Website des Veranstaltungsorts" 115 | RESTAURANT_REGION: "Region" 116 | RESTAURANT_TELEFON: "Telefon" 117 | ENABLE_PERSON: "Personen-Mikrodaten einschalten" 118 | ENABLEORGA: "Organisations-Mikrodaten aktivieren" 119 | MUSICEVENT_URL_PLACEHOLDER: "Url zur offiziellen Seite dieser Musikveranstaltung" 120 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.3.6 2 | ## 09/14/2018 3 | 1. [](#new) 4 | * New field allows to add previously generated metadata 5 | 6 | # v2.3.5 7 | ## 09/14/2018 8 | 1. [](#fix) 9 | * Changed the currency field from a select field to a text field to allow rare currencies 10 | * Fixed the button to validate the page microdata 11 | 12 | # v2.3.4 13 | ## 03/19/2018 14 | 1. [](#fix) 15 | * Fixed whoops on opengraph image 16 | 17 | # v2.3.2 18 | ## 01/26/2018 19 | 1. [](#fix) 20 | * Fixed opengraph and twitter error 21 | * Fixed facebook default not fetched from plugin configuration 22 | 23 | 24 | # v2.3.1 25 | ## 01/26/2018 26 | 1. [](#fix) 27 | * Fixed whoops on twitter image 28 | 29 | # v2.3.0 30 | ## 01/26/2018 31 | 1. [](#fix) 32 | * Fixed tag not stripped from description 33 | * Added areaserved to organization and restaurant microdata 34 | * Fixed event date problem 35 | 36 | # v2.2.0 37 | ## 01/26/2018 38 | 1. [](#new) 39 | * Fixed script and style tags in page content causing problem 40 | * Few languages fixes 41 | * Re-added date modified and date published that mysteriously disappeared 42 | 43 | # v2.1.9 44 | ## 01/09/2018 45 | 1. [](#new) 46 | * Fixed Changelog causing trouble with GPM 47 | 48 | # v2.1.8 49 | ## 01/08/2018 50 | 1. [](#new) 51 | * Added Nederland translation 52 | * Fixed founder field causing crash when empty 53 | * Fixed default twitter image not being displayed 54 | * Fixed default values for opengraph and twitter 55 | * Small changes in microdata blueprints 56 | 57 | # v2.1.7 58 | ## 12/17/2017 59 | 1. [](#new) 60 | * Added product microdata 61 | 62 | # v2.1.6 63 | ## 12/12/2017 64 | 1. [](#new) 65 | * Added better styling for the fieldset field 66 | * Added icons on fieldsets fields 67 | * Added some required fields to the organization microdata 68 | * Added button label on the microdata tabs. 69 | 70 | 1. [](#fix) 71 | * Fixed the error Invalid Index Type on blueprints that did not use a "content" field 72 | * Fixed the music album microdata not being displayed 73 | * Removed duplicate attribute on the blueprint 74 | 75 | # v2.1.5 76 | ## 12/07/2017 77 | 1. [](#new) 78 | * Removed the empty script tags that appeared on page that did not use microdata 79 | * Added some required fields to the organization microdata 80 | 81 | # v2.1.4 82 | ## 11/22/2017 83 | 1. [](#fix) 84 | * Fixed the url of structured data testing tool for non-english language 85 | 86 | # v2.1.3 87 | ## 11/22/2017 88 | 1. [](#new) 89 | * Added a button to validate the page microdata with Google structured data testing tool 90 | 91 | # v2.1.2 92 | ## 10/12/2017 93 | 1. [](#bugfix) 94 | * Fixed site_name open graph 95 | 96 | # v2.1.1 97 | ## 10/12/2017 98 | 1. [](#bugfix) 99 | * Fixed duplicate toggle 100 | 101 | # v2.1.0 102 | ## 10/12/2017 103 | 1. [](#bugfix) 104 | * Added highlight to Toggle fields 105 | * Few languages fixes 106 | * Added url for Organization microdata 107 | * Few css fixes 108 | * Added more placeholder 109 | 110 | # v2.0.9 111 | ## 10/04/2017 112 | 1. [](#bugfix) 113 | * Fixed string manipulation causing error on some install 114 | 115 | # v2.0.8 116 | ## 10/04/2017 117 | 1. [](#new) 118 | * Added Logo url to Organization Microdata 119 | 120 | # v2.0.7 121 | ## 10/03/2017 122 | 1. [](#new) 123 | * Added Organization Microdata 124 | 125 | # v2.0.6 126 | ## 09/10/2017 127 | 1. [](#bugfix) 128 | * Changed Description 129 | 130 | # v2.0.5 131 | ## 09/10/2017 132 | 1. [](#bugfix) 133 | * First Stable Release - Reads the docs before updating from a 1.0.x Version! 134 | * Added Person Microdata 135 | 136 | # v2.0.4 137 | ## 09/10/2017 138 | 1. [](#bugfix) 139 | * Microdata is now automatically added to pages 140 | * Page date and last modified is modified automatically in article microdata 141 | 142 | 143 | 144 | # v2.0.3 145 | ## 09/08/2017 146 | 1. [](#bugfix) 147 | * Fixed error on empty fields 148 | * Added function for getting image width and height 149 | 150 | 151 | # v2.0.2 152 | ## 09/08/2017 153 | 1. [](#bugfix) 154 | * Fixed missing language.yaml entry 155 | * Added style vertical to microdata for better readability 156 | * Fixed many bugs 157 | * Fixed bug when image on a modular folder 158 | 159 | # v2.0.0 160 | ## 09/05/2017 161 | 162 | 1. [](#new) 163 | * Complete Refactor of the plugin with proper metadata integration 164 | * Users now just have to add {{json}} in the head of their base.html.twig 165 | * Added configuration for enabling or disabling microdata fields 166 | * Use of the fieldset and conditional fields 167 | * Changed filepicker for the new mediapicker field 168 | 169 | # v1.0.6 170 | ## 06/07/2017 171 | 172 | 1. [](#bugfix) 173 | * Added Twitter URL Meta 174 | 175 | # v1.0.5 176 | ## 06/06/2017 177 | 178 | 1. [](#bugfix) 179 | * Fix Twitter title not displayed correctly 180 | 181 | # v1.0.4 182 | ## 06/02/2017 183 | 184 | 1. [](#new) 185 | * New Microdata type: Music Event, MusicAlbum 186 | * Toggle will now display or hide the related fields 187 | 188 | # v1.0.3 189 | ## 06/01/2017 190 | 191 | 1. [](#new) 192 | * New feature: Microdata generation 193 | 2. [](#bugfix) 194 | * Fix Page title not rendered correctly 195 | 196 | # v0.9.1 197 | ## 06/01/2017 198 | 199 | 1. [](#new) 200 | * Removed the site-wide configuration. 201 | * Multiples layout fix 202 | 2. [](#bugfix) 203 | * Fix blueprint causing toggle fields to duplicate. 204 | * Fix fallback value when fields are not filled. 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Grav SEO Plugin](https://github.com/paulmassen/grav-plugin-seo/blob/master/assets/logoseo.png?raw=true) 2 | 3 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MX77VZWPUKLTU) 4 | 5 | ##### Table of Contents: 6 | 7 | 8 | * [About](#about) 9 | * [Features](#features) 10 | * [Installation and Updates](#installation-and-updates) 11 | * [Requirements](#requirements) 12 | * [Usage](#usage) 13 | * [Contributing](#contributing) 14 | * [License](#license) 15 | 16 | 17 | ## About 18 | 19 | `Seo` is an user-friendly plugin for [GetGrav.org](http://getgrav.org) used to manage all your metatags in order to customize your pages appearance in Search Engine Results or social networks. The plugin also allows the generation of JSON-LD Microdata. 20 | 21 | ## Features 22 | 23 | ### Google 24 | You can see and customize how your page will look on Google Search Results. 25 | 26 | ![Grav SEO Plugin](https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/master/assets/demoseoplugin.gif) 27 | 28 | ### Twitter 29 | You can also preview how your page will look when shared on twitter 30 | 31 | ![Grav SEO Plugin](https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/master/assets/twitter.gif) 32 | 33 | 34 | ### Facebook 35 | And on Facebook 36 | 37 | ![Facebook Live Preview](https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/master/assets/facebook.gif) 38 | 39 | 40 | ### JSON-LD 41 | 42 | You can also generate Schema.org JSON Microdata from the admin. 43 | ![Article Microdata](https://raw.githubusercontent.com/paulmassen/grav-plugin-seo/master/assets/article_json.png) 44 | This will generate the following Json-ld between script tags 45 | ```JSON 46 | { 47 | "@context": "http://schema.org", 48 | "@type": "Article", 49 | "headline": "Article Title", 50 | "mainEntityOfPage": { 51 | "@type": "WebPage", 52 | "url": "http://yourwebsite.com" 53 | }, 54 | "articleBody": "Lorem Ipsum dolor sit amet", 55 | "datePublished": "2017-12-01T00:00:00+00:00", 56 | "dateModified": "2019-01-01T00:00:00+00:00", 57 | "description": "Description of the article", 58 | "author": "Steve Jobs", 59 | "publisher": { 60 | "@type": "Organization", 61 | "name": "Apple", 62 | "logo": { 63 | "@type": "ImageObject", 64 | "url": "http://yourwebsite.com/home/logo.png", 65 | "width": "200", 66 | "height": "100" 67 | } 68 | }, 69 | "image": { 70 | "@type": "ImageObject", 71 | "url": "http://yourwebsite.com/home/myimage.jpg", 72 | "width": "800", 73 | "height": "600" 74 | } 75 | } 76 | ``` 77 | 78 | ## Requirements 79 | 80 | In order to use the plugin with a custom template, there is two requirements, you must: 81 | - Include in your base template the metadata template that comes shipped with antimatter, such as: `{% include 'partials/metadata.html.twig' %}` 82 | - For Microdatas, you must use Grav's asset manager. If your template has a line with `{{ assets.js() }}`, it will works. 83 | - The SEO tab extends the default blueprint, if it does not appear, make sure your blueprint extends the default blueprint with `extends@: default` 84 | #### Feedback needed 85 | 86 | As this plugin is in its early stage, please do not hesitate to leave a feedback, to suggest modification or features. 87 | 88 | ### TO-DO 89 | 90 | - [ ] Add more Microdata type 91 | - [ ] Add Translations (Contribution welcomed!) 92 | - [x] Add the possibility to add multiple microdata of the same type 93 | - [ ] YoastSEO text analysis integration (WIP) 94 | - [ ] Add some microdata on every page 95 | 96 | 97 | 98 | 99 | ## Installation and Updates 100 | 101 | ### Updating from Previous releases 102 | 103 | As there is a lot of changes from previous releases, be careful when updating, as your previously set values might be lost. 104 | The previous version required to modify your base template, whereas the 2.0+ version of the plugin now adds metadata and microdata automatically to your existing Installation. 105 | 106 | Installing or updating the `SEO` plugin can be done in one of three ways. Using the GPM (Grav Package Manager) installation update method (i.e. `bin/gpm install seo`) or manual install by downloading [this plugin](https://github.com/paulmassen/grav-plugin-seo) and extracting all plugin files to 107 | 108 | /your/site/grav/user/plugins/seo 109 | 110 | Once installed, the plugin will automatically set the metadata and append the json-ld microdatas to your document. 111 | If you plan on using the Twitter feature, make sure to fill your user ID in tab Plugins > SEO > Twitter ID 112 | 113 | ## Configuration 114 | 115 | Configuration is done through the plugin configuration page, accessible by clicking on Plugins > Seo. On this page, you can choose to enable the microdata fields you will use. 116 | Make sure to fill the Facebook ID field as well as the Twitter ID field, in order for your meta tags to be validated. 117 | 118 | ## Usage 119 | 120 | The `SEO` plugin appends a SEO tab on every pages where you can manage and define how your website will look on search engine results and on social networks. 121 | 122 | 123 | ## Contributing 124 | 125 | You can contribute at any time! Before opening any issue, please search for existing issues! 126 | 127 | After that please note: 128 | 129 | * If you find a bug, would like to make a feature request or suggest an improvement, [please open a new issue][issues]. If you have any interesting ideas for additions to the syntax please do suggest them as well! 130 | * Feature requests are more likely to get attention if you include a clearly described use case. 131 | 132 | 133 | 134 | ## License 135 | 136 | -------------------------------------------------------------------------------- /templates/forms/fields/facebook/facebook.html.twig: -------------------------------------------------------------------------------- 1 | 2 | {% if admin.route %} 3 | {% set context = admin.page(true) %} 4 | {% endif %} 5 | {% if field.title or field.underline %} 6 |

{% if grav.twig.twig.filters['tu'] is defined %}{{ field.title|tu }}{% else %}{{ field.title|t }}{% endif %}

7 | {% endif %} 8 | 9 | {% set context = admin.page(true) %} 10 |
11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 |

Here is how this page will look when shared on facebook.

19 |
20 | 21 |
22 | 23 |
24 |
{{ context.header.facebooktitle|default(context.header.title ~ " | " ~ site.title )}}
25 |
{{ context.header.facebookdesc|default(context.summary|striptags) }}
26 |
{{uri.host|replace({"http://": "","/":" › " }) }}
27 |
28 |
29 |
30 | 31 | 35 |
36 |
37 |
38 | 197 | 214 | 227 | {% if field.text %} 228 | {% endif %} 229 | 230 | {% if field.fields %} 231 |
232 | {% for field in field.fields %} 233 | {% if field.type %} 234 | {% set value = data.value(field.name) %} 235 | {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} 236 | {% endif %} 237 | {% endfor %} 238 |
239 | {% endif %} 240 | -------------------------------------------------------------------------------- /css/yoast-seo.min.css: -------------------------------------------------------------------------------- 1 | .yoast-section{width:auto;max-width:640px}.snippet_container:before,.snippet_container:after{content:' ';display:table}.snippet_container:after{clear:both}.snippet-editor__view{clear:both;background:#fff}.snippet-editor__view-toggle{display:inline-block;background-color:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;margin-top:10px;margin-left:20px;vertical-align:top}.snippet-editor__view-icon{width:31px;height:31px;float:left;cursor:pointer;background-color:transparent;background-repeat:no-repeat;background-position:center, center;border:none;border-bottom:4px solid transparent;outline:none}.snippet-editor__view-icon:hover,.snippet-editor__view-icon:focus{background-color:#ffffff;border-color:#1074a8;transition:.15s color ease-in-out,.15s background-color ease-in-out,.15s border-color ease-in-out}.snippet-editor__view-icon--active{border-color:#555;transition:.15s color ease-in-out,.15s background-color ease-in-out,.15s border-color ease-in-out}.snippet-editor__view-icon.yoast-tooltip:before{bottom:-10px}.snippet-editor__view-icon.yoast-tooltip:after{margin-top:10px}.snippet-editor__view-icon-desktop{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M1728%20992v%2D832q0%2D13%2D9%2E5%2D22%2E5t%2D22%2E5%2D9%2E5h%2D1600q%2D13%200%2D22%2E5%209%2E5t%2D9%2E5%2022%2E5v832q0%2013%209%2E5%2022%2E5t22%2E5%209%2E5h1600q13%200%2022%2E5%2D9%2E5t9%2E5%2D22%2E5zm128%2D832v1088q0%2066%2D47%20113t%2D113%2047h%2D544q0%2037%2016%2077%2E5t32%2071%2016%2043%2E5q0%2026%2D19%2045t%2D45%2019h%2D512q%2D26%200%2D45%2D19t%2D19%2D45q0%2D14%2016%2D44t32%2D70%2016%2D78h%2D544q%2D66%200%2D113%2D47t%2D47%2D113v%2D1088q0%2D66%2047%2D113t113%2D47h1600q66%200%20113%2047t47%20113z%22%2F%3E%3C%2Fsvg%3E");background-size:18px}.snippet-editor__view-icon-desktop:hover,.snippet-editor__view-icon-desktop:focus{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%231074a8%22%20d%3D%22M1728%20992v%2D832q0%2D13%2D9%2E5%2D22%2E5t%2D22%2E5%2D9%2E5h%2D1600q%2D13%200%2D22%2E5%209%2E5t%2D9%2E5%2022%2E5v832q0%2013%209%2E5%2022%2E5t22%2E5%209%2E5h1600q13%200%2022%2E5%2D9%2E5t9%2E5%2D22%2E5zm128%2D832v1088q0%2066%2D47%20113t%2D113%2047h%2D544q0%2037%2016%2077%2E5t32%2071%2016%2043%2E5q0%2026%2D19%2045t%2D45%2019h%2D512q%2D26%200%2D45%2D19t%2D19%2D45q0%2D14%2016%2D44t32%2D70%2016%2D78h%2D544q%2D66%200%2D113%2D47t%2D47%2D113v%2D1088q0%2D66%2047%2D113t113%2D47h1600q66%200%20113%2047t47%20113z%22%2F%3E%3C%2Fsvg%3E")}.snippet-editor__view-icon-mobile{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M976%201408q0%2D33%2D23%2E5%2D56%2E5t%2D56%2E5%2D23%2E5%2D56%2E5%2023%2E5%2D23%2E5%2056%2E5%2023%2E5%2056%2E5%2056%2E5%2023%2E5%2056%2E5%2D23%2E5%2023%2E5%2D56%2E5zm208%2D160v%2D704q0%2D13%2D9%2E5%2D22%2E5t%2D22%2E5%2D9%2E5h%2D512q%2D13%200%2D22%2E5%209%2E5t%2D9%2E5%2022%2E5v704q0%2013%209%2E5%2022%2E5t22%2E5%209%2E5h512q13%200%2022%2E5%2D9%2E5t9%2E5%2D22%2E5zm%2D192%2D848q0%2D16%2D16%2D16h%2D160q%2D16%200%2D16%2016t16%2016h160q16%200%2016%2D16zm288%2D16v1024q0%2052%2D38%2090t%2D90%2038h%2D512q%2D52%200%2D90%2D38t%2D38%2D90v%2D1024q0%2D52%2038%2D90t90%2D38h512q52%200%2090%2038t38%2090z%22%2F%3E%3C%2Fsvg%3E");background-size:22px}.snippet-editor__view-icon-mobile:hover,.snippet-editor__view-icon-mobile:focus{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%231074a8%22%20d%3D%22M976%201408q0%2D33%2D23%2E5%2D56%2E5t%2D56%2E5%2D23%2E5%2D56%2E5%2023%2E5%2D23%2E5%2056%2E5%2023%2E5%2056%2E5%2056%2E5%2023%2E5%2056%2E5%2D23%2E5%2023%2E5%2D56%2E5zm208%2D160v%2D704q0%2D13%2D9%2E5%2D22%2E5t%2D22%2E5%2D9%2E5h%2D512q%2D13%200%2D22%2E5%209%2E5t%2D9%2E5%2022%2E5v704q0%2013%209%2E5%2022%2E5t22%2E5%209%2E5h512q13%200%2022%2E5%2D9%2E5t9%2E5%2D22%2E5zm%2D192%2D848q0%2D16%2D16%2D16h%2D160q%2D16%200%2D16%2016t16%2016h160q16%200%2016%2D16zm288%2D16v1024q0%2052%2D38%2090t%2D90%2038h%2D512q%2D52%200%2D90%2D38t%2D38%2D90v%2D1024q0%2D52%2038%2D90t90%2D38h512q52%200%2090%2038t38%2090z%22%2F%3E%3C%2Fsvg%3E")}.snippet-editor__view--desktop{width:640px;max-width:100%;padding:0 20px;overflow:auto}.snippet-editor__view .snippet_container__title{width:600px;white-space:nowrap}.snippet-editor__view .snippet_container__title .title{display:inline-block;max-width:600px;overflow:hidden;text-overflow:ellipsis;vertical-align:top}.snippet-editor__view .snippet_container__title .title#snippet_sitename{display:inline}.snippet-editor__view .snippet_container__url{width:600px;white-space:nowrap;line-height:1}.snippet-editor__view .snippet_container__url .urlFull{display:inline-block;max-width:586px;overflow:hidden;text-overflow:ellipsis;vertical-align:top;font-size:14px;line-height:16px}.snippet-editor__view .snippet_container .down_arrow{display:inline-block;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid #006621;margin-top:6px;margin-left:6px;vertical-align:top}.snippet-editor__view .snippet_container__meta{width:600px}.snippet-editor__view--mobile{box-shadow:0 1px 2px rgba(0,0,0,0.2);border-radius:2px;border-bottom:1px hidden #fff;margin:0 20px 10px;padding:11px 0}.snippet-editor__view--mobile .snippet_container{display:block;line-height:20px}.snippet-editor__view--mobile .snippet_container__title,.snippet-editor__view--mobile .snippet_container__url,.snippet-editor__view--mobile .snippet_container__meta{width:100%;box-sizing:border-box}.snippet-editor__view--mobile .snippet_container__title{padding:0 16px;white-space:normal;overflow:visible;word-wrap:break-word}.snippet-editor__view--mobile .snippet_container__title .title{font-size:16px;line-height:20px}.snippet-editor__view--mobile .snippet_container__url{border-bottom:1px solid #ebebeb;padding:0 16px 9px;margin-bottom:9px}.snippet-editor__view--mobile .snippet_container__url .urlFull{max-width:100%;font-size:14px;line-height:20px}.snippet-editor__view--mobile .snippet_container .down_arrow{display:none}.snippet-editor__view--mobile .snippet_container__meta{font-size:14px;line-height:20px;padding:0 16px}.snippet-editor__view--mobile .snippet_container .desc{font-size:14px;line-height:20px}.snippet-editor__is-scrollable-hintwrapper{display:none;margin:1em 0 5px;text-align:center}.snippet-editor__view--desktop.snippet-editor__view--desktop-has-scroll+.snippet-editor__is-scrollable-hintwrapper{display:block}.snippet-editor__is-scrollable-hint{display:inline-block}.snippet-editor__is-scrollable-hint:before{content:"\21c4";display:inline-block;margin-right:10px;font-size:20px;line-height:inherit;vertical-align:text-top}.snippet-editor__container{position:relative;cursor:pointer}.snippet-editor__container.editable-preview__container--no-caret{cursor:auto}.snippet-editor__heading{padding:8px 20px;font-size:0.9rem;margin:0 0 15px;font-family:"Open Sans", sans-serif;font-weight:300;color:#555}.snippet-editor__heading-editor{border-top:1px solid #f7f7f7}.snippet-editor__heading-icon{background-repeat:no-repeat;background-position:left 20px top 0.6em;background-size:16px;padding-left:45px}.snippet-editor__heading-icon-eye{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M1664%20960q%2D152%2D236%2D381%2D353%2061%20104%2061%20225%200%20185%2D131%2E5%20316%2E5t%2D316%2E5%20131%2E5%2D316%2E5%2D131%2E5%2D131%2E5%2D316%2E5q0%2D121%2061%2D225%2D229%20117%2D381%20353%20133%20205%20333%2E5%20326%2E5t434%2E5%20121%2E5%20434%2E5%2D121%2E5%20333%2E5%2D326%2E5zm%2D720%2D384q0%2D20%2D14%2D34t%2D34%2D14q%2D125%200%2D214%2E5%2089%2E5t%2D89%2E5%20214%2E5q0%2020%2014%2034t34%2014%2034%2D14%2014%2D34q0%2D86%2061%2D147t147%2D61q20%200%2034%2D14t14%2D34zm848%20384q0%2034%2D20%2069%2D140%20230%2D376%2E5%20368%2E5t%2D499%2E5%20138%2E5%2D499%2E5%2D139%2D376%2E5%2D368q%2D20%2D35%2D20%2D69t20%2D69q140%2D229%20376%2E5%2D368t499%2E5%2D139%20499%2E5%20139%20376%2E5%20368q20%2035%2020%2069z%22%20%2F%3E%3C%2Fsvg%3E")}.snippet-editor__container:hover:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23bfbfbf%22%20d%3D%22M1152%20896q0%2026%2D19%2045l%2D448%20448q%2D19%2019%2D45%2019t%2D45%2D19%2D19%2D45v%2D896q0%2D26%2019%2D45t45%2D19%2045%2019l448%20448q19%2019%2019%2045z%22%20%2F%3E%3C%2Fsvg%3E");position:absolute;content:'';top:-3px;left:-22px;display:block;width:24px;height:24px;background-size:25px}.snippet-editor__container--focus:hover:before,.snippet-editor__container--focus:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M1152%20896q0%2026%2D19%2045l%2D448%20448q%2D19%2019%2D45%2019t%2D45%2D19%2D19%2D45v%2D896q0%2D26%2019%2D45t45%2D19%2045%2019l448%20448q19%2019%2019%2045z%22%20%2F%3E%3C%2Fsvg%3E");position:absolute;content:'';top:-3px;left:-22px;display:block;width:24px;height:24px;background-size:25px}.snippet-editor__preview{padding:0 0 15px;max-width:640px}.snippet-editor__preview #help-yoast-snippetpreview{padding:0 20px}.snippet-editor__button{background:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;color:#555;padding:8px 10px;cursor:pointer;font-size:0.8rem;line-height:15px}.snippet-editor__edit-button{background:#f7f7f7 url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M491%201536l91%2D91%2D235%2D235%2D91%2091v107h128v128h107zm523%2D928q0%2D22%2D22%2D22%2D10%200%2D17%207l%2D542%20542q%2D7%207%2D7%2017%200%2022%2022%2022%2010%200%2017%2D7l542%2D542q7%2D7%207%2D17zm%2D54%2D192l416%20416%2D832%20832h%2D416v%2D416zm683%2096q0%2053%2D37%2090l%2D166%20166%2D416%2D416%20166%2D165q36%2D38%2090%2D38%2053%200%2091%2038l235%20234q37%2039%2037%2091z%22%20%2F%3E%3C%2Fsvg%3E") no-repeat;background-position:left 8px top 50%;background-size:16px;padding-left:32px;display:inline-block;box-sizing:border-box;margin-top:10px}.snippet-editor__form{padding:20px}.snippet-editor__label{display:block;width:100%;margin-top:1em;position:relative}.snippet-editor__label:first-child{margin-top:0}.snippet-editor__label--hover:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23bfbfbf%22%20d%3D%22M1152%20896q0%2026%2D19%2045l%2D448%20448q%2D19%2019%2D45%2019t%2D45%2D19%2D19%2D45v%2D896q0%2D26%2019%2D45t45%2D19%2045%2019l448%20448q19%2019%2019%2045z%22%20%2F%3E%3C%2Fsvg%3E");position:absolute;content:'';top:-3px;left:-22px;display:block;width:24px;height:24px;background-size:25px;top:20px}.snippet-editor__label--focus:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%231074a8%22%20d%3D%22M1152%20896q0%2026%2D19%2045l%2D448%20448q%2D19%2019%2D45%2019t%2D45%2D19%2D19%2D45v%2D896q0%2D26%2019%2D45t45%2D19%2045%2019l448%20448q19%2019%2019%2045z%22%20%2F%3E%3C%2Fsvg%3E");position:absolute;content:'';top:-3px;left:-22px;display:block;width:24px;height:24px;background-size:25px;top:20px}.snippet-editor__input{display:block;width:100%;margin-top:5px;border:1px solid #bfbfbf;max-width:100%;box-sizing:border-box}.snippet-editor__input:focus{border:1px solid #1074a8;outline:none}.rtl .snippet-editor__container:hover:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23bfbfbf%22%20d%3D%22M1216%20448v896q0%2026%2D19%2045t%2D45%2019%2D45%2D19l%2D448%2D448q%2D19%2D19%2D19%2D45t19%2D45l448%2D448q19%2D19%2045%2D19t45%2019%2019%2045z%22%2F%3E%3C%2Fsvg%3E")}.rtl .snippet-editor__container--focus:hover:before,.rtl .snippet-editor__container--focus:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M1216%20448v896q0%2026%2D19%2045t%2D45%2019%2D45%2D19l%2D448%2D448q%2D19%2D19%2D19%2D45t19%2D45l448%2D448q19%2D19%2045%2D19t45%2019%2019%2045z%22%2F%3E%3C%2Fsvg%3E")}.rtl .snippet-editor__label--hover:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23bfbfbf%22%20d%3D%22M1216%20448v896q0%2026%2D19%2045t%2D45%2019%2D45%2D19l%2D448%2D448q%2D19%2D19%2D19%2D45t19%2D45l448%2D448q19%2D19%2045%2D19t45%2019%2019%2045z%22%2F%3E%3C%2Fsvg%3E")}.rtl .snippet-editor__label--focus:before{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%231074a8%22%20d%3D%22M1216%20448v896q0%2026%2D19%2045t%2D45%2019%2D45%2D19l%2D448%2D448q%2D19%2D19%2D19%2D45t19%2D45l448%2D448q19%2D19%2045%2D19t45%2019%2019%2045z%22%2F%3E%3C%2Fsvg%3E")}.snippet-editor__meta-description{height:70px}.snippet-editor__submit{margin-top:1em}#snippet_cite{min-width:20px}#meta_container{clear:both;min-height:20px}.snippet_container .title{color:#1e0fbe;text-decoration:none;font-size:18px;line-height:1.2;font-weight:normal;margin:0}.snippet_container .desc{font-size:13px;line-height:1.4}.snippet_container .url{color:#006621;font-style:normal}.snippet_container .url strong{font-weight:700}.snippet_container .desc-default{color:#545454}.snippet_container .desc-default strong{color:#6a6a6a;font-weight:700}.snippet_container .desc-render{color:#777}.snippet_container .tooLong{color:#f00 !important}.snippet-editor__field--invalid{color:#f00}.snippet-editor__progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-sizing:border-box;display:block;margin-top:5px;height:8px;width:100%}.snippet-editor__progress::-webkit-progress-bar{background-color:#f7f7f7;height:8px;border:1px solid #bfbfbf}.snippet-editor__progress--fallback{background-color:#f7f7f7;height:8px;border:1px solid #bfbfbf}.snippet-editor__progress-bar{width:0;height:100%;max-width:100%}.snippet-editor__date{color:#808080;font-size:13px;line-height:1.4}.snippet-editor--hidden{display:none}.yoast-measurement-elements-holder{position:absolute;left:-9999em}.snippet-editor__progress--bad[value]{color:#dc3232}.snippet-editor__progress--bad[value]::-webkit-progress-value{background-color:#dc3232;transition:width 250ms}.snippet-editor__progress--bad[value]::-moz-progress-bar{background-color:#dc3232}.snippet-editor__progress--bad[value] .snippet-editor__progress-bar{background-color:#dc3232}.snippet-editor__progress--ok[value]{color:#ee7c1b}.snippet-editor__progress--ok[value]::-webkit-progress-value{background-color:#ee7c1b;transition:width 250ms}.snippet-editor__progress--ok[value]::-moz-progress-bar{background-color:#ee7c1b}.snippet-editor__progress--ok[value] .snippet-editor__progress-bar{background-color:#ee7c1b}.snippet-editor__progress--good[value]{color:#7ad03a}.snippet-editor__progress--good[value]::-webkit-progress-value{background-color:#7ad03a;transition:width 250ms}.snippet-editor__progress--good[value]::-moz-progress-bar{background-color:#7ad03a}.snippet-editor__progress--good[value] .snippet-editor__progress-bar{background-color:#7ad03a}.wpseoanalysis{padding-right:0px}.wpseo-score-text{float:left;width:86%;width:calc( 100% - 56px)}.assessment-results .wpseo-score-icon{float:left;width:12px;height:12px;margin:3px 10px 0 0;border-radius:50%;background:#888}.wpseo-score-icon.good{background-color:#7ad03a}.wpseo-score-icon.ok{background-color:#ee7c1b}.wpseo-score-icon.bad{background-color:#dc3232}.wpseo-score-icon.na{background-color:#999}.wpseo-score-icon.noindex{background-color:#1e8cbe}li.score{list-style-type:none !important;margin-bottom:6px}li.score:after{content:"";display:table;clear:both}.screen-reader-text{clip:rect(1px, 1px, 1px, 1px);position:absolute !important;height:1px;width:1px;overflow:hidden}.assessment-results{clear:both}.assessment-results__mark.icon-eye-inactive,.assessment-results__mark.icon-eye-active,.assessment-results__mark.icon-eye-disabled{float:left;border:0;width:28px;height:28px;margin:-5px 3px 0 3px;padding:4px;border-radius:100%;outline:none;background:no-repeat center;background-size:16px;cursor:pointer}.assessment-results__mark.icon-eye-inactive{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23555%22%20d%3D%22M1664%20960q%2D152%2D236%2D381%2D353%2061%20104%2061%20225%200%20185%2D131%2E5%20316%2E5t%2D316%2E5%20131%2E5%2D316%2E5%2D131%2E5%2D131%2E5%2D316%2E5q0%2D121%2061%2D225%2D229%20117%2D381%20353%20133%20205%20333%2E5%20326%2E5t434%2E5%20121%2E5%20434%2E5%2D121%2E5%20333%2E5%2D326%2E5zm%2D720%2D384q0%2D20%2D14%2D34t%2D34%2D14q%2D125%200%2D214%2E5%2089%2E5t%2D89%2E5%20214%2E5q0%2020%2014%2034t34%2014%2034%2D14%2014%2D34q0%2D86%2061%2D147t147%2D61q20%200%2034%2D14t14%2D34zm848%20384q0%2034%2D20%2069%2D140%20230%2D376%2E5%20368%2E5t%2D499%2E5%20138%2E5%2D499%2E5%2D139%2D376%2E5%2D368q%2D20%2D35%2D20%2D69t20%2D69q140%2D229%20376%2E5%2D368t499%2E5%2D139%20499%2E5%20139%20376%2E5%20368q20%2035%2020%2069z%22%20%2F%3E%3C%2Fsvg%3E")}.assessment-results__mark.icon-eye-active{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M1664%20960q%2D152%2D236%2D381%2D353%2061%20104%2061%20225%200%20185%2D131%2E5%20316%2E5t%2D316%2E5%20131%2E5%2D316%2E5%2D131%2E5%2D131%2E5%2D316%2E5q0%2D121%2061%2D225%2D229%20117%2D381%20353%20133%20205%20333%2E5%20326%2E5t434%2E5%20121%2E5%20434%2E5%2D121%2E5%20333%2E5%2D326%2E5zm%2D720%2D384q0%2D20%2D14%2D34t%2D34%2D14q%2D125%200%2D214%2E5%2089%2E5t%2D89%2E5%20214%2E5q0%2020%2014%2034t34%2014%2034%2D14%2014%2D34q0%2D86%2061%2D147t147%2D61q20%200%2034%2D14t14%2D34zm848%20384q0%2034%2D20%2069%2D140%20230%2D376%2E5%20368%2E5t%2D499%2E5%20138%2E5%2D499%2E5%2D139%2D376%2E5%2D368q%2D20%2D35%2D20%2D69t20%2D69q140%2D229%20376%2E5%2D368t499%2E5%2D139%20499%2E5%20139%20376%2E5%20368q20%2035%2020%2069z%22%20%2F%3E%3C%2Fsvg%3E");background-color:#a4286a}.assessment-results__mark.icon-eye-disabled{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%221792%22%20height%3D%221792%22%20viewBox%3D%220%200%201792%201792%22%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22%23e6e6e6%22%20d%3D%22M1664%20960q%2D152%2D236%2D381%2D353%2061%20104%2061%20225%200%20185%2D131%2E5%20316%2E5t%2D316%2E5%20131%2E5%2D316%2E5%2D131%2E5%2D131%2E5%2D316%2E5q0%2D121%2061%2D225%2D229%20117%2D381%20353%20133%20205%20333%2E5%20326%2E5t434%2E5%20121%2E5%20434%2E5%2D121%2E5%20333%2E5%2D326%2E5zm%2D720%2D384q0%2D20%2D14%2D34t%2D34%2D14q%2D125%200%2D214%2E5%2089%2E5t%2D89%2E5%20214%2E5q0%2020%2014%2034t34%2014%2034%2D14%2014%2D34q0%2D86%2061%2D147t147%2D61q20%200%2034%2D14t14%2D34zm848%20384q0%2034%2D20%2069%2D140%20230%2D376%2E5%20368%2E5t%2D499%2E5%20138%2E5%2D499%2E5%2D139%2D376%2E5%2D368q%2D20%2D35%2D20%2D69t20%2D69q140%2D229%20376%2E5%2D368t499%2E5%2D139%20499%2E5%20139%20376%2E5%20368q20%2035%2020%2069z%22%20%2F%3E%3C%2Fsvg%3E")}.assessment-results__mark:focus{border-radius:100%;box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px rgba(30,140,190,0.8)}.assessment-results__mark-container{display:inline-block;float:right;width:33px;min-height:1px}.YoastSEO_msg .right,.YoastSEO_msg .left{display:none}@keyframes animatedBackground{from{background-position:0 0}to{background-position:100% 0}}.YoastSEO_msg .bufferbar{display:block;width:100%;height:12px;background-image:linear-gradient(to left, #fff, #0063ff, #fff, #0063ff);background-size:300% 100%;background-position:0px 0px;margin:10px 0 10px 0;border:1px solid #dfdfdf;animation:animatedBackground 5s linear infinite}.yoast-tooltip{position:relative}button.yoast-tooltip{overflow:visible}.yoast-tooltip::after{display:none;position:absolute;z-index:1000000;padding:6px 8px 5px;border-radius:3px;opacity:0;color:#fff;background:rgba(0,0,0,0.8);text-shadow:none;font:normal normal 11px/1.45454545 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";text-align:center;white-space:pre;text-decoration:none;letter-spacing:normal;text-transform:none;word-wrap:break-word;content:attr(aria-label);pointer-events:none;-webkit-font-smoothing:subpixel-antialiased}.yoast-tooltip-alt::after{content:attr(data-label)}.yoast-tooltip::before{display:none;position:absolute;z-index:1000001;width:0;height:0;border:5px solid transparent;opacity:0;color:rgba(0,0,0,0.8);content:"\00a0";pointer-events:none}@keyframes yoast-tooltip-appear{from{opacity:0}to{opacity:1}}.yoast-tooltip:hover::before,.yoast-tooltip:hover::after,.yoast-tooltip:active::before,.yoast-tooltip:active::after,.yoast-tooltip:focus::before,.yoast-tooltip:focus::after{display:inline-block;text-decoration:none;animation-name:yoast-tooltip-appear;animation-duration:0.1s;animation-timing-function:ease-in;animation-delay:0.4s;animation-fill-mode:forwards}.yoast-tooltip-no-delay:hover::before,.yoast-tooltip-no-delay:hover::after,.yoast-tooltip-no-delay:active::before,.yoast-tooltip-no-delay:active::after,.yoast-tooltip-no-delay:focus::before,.yoast-tooltip-no-delay:focus::after{opacity:1;animation:none}.yoast-tooltip-multiline:hover::after,.yoast-tooltip-multiline:active::after,.yoast-tooltip-multiline:focus::after{display:table-cell}.yoast-tooltip-s::after,.yoast-tooltip-se::after,.yoast-tooltip-sw::after{top:100%;right:50%;margin-top:5px}.yoast-tooltip-s::before,.yoast-tooltip-se::before,.yoast-tooltip-sw::before{top:auto;right:50%;bottom:-5px;margin-right:-5px;border-bottom-color:rgba(0,0,0,0.8)}.yoast-tooltip-se::after{right:auto;left:50%;margin-left:-15px}.yoast-tooltip-sw::after{margin-right:-15px}.yoast-tooltip-n::after,.yoast-tooltip-ne::after,.yoast-tooltip-nw::after{right:50%;bottom:100%;margin-bottom:5px}.yoast-tooltip-n::before,.yoast-tooltip-ne::before,.yoast-tooltip-nw::before{top:-5px;right:50%;bottom:auto;margin-right:-5px;border-top-color:rgba(0,0,0,0.8)}.yoast-tooltip-ne::after{right:auto;left:50%;margin-left:-15px}.yoast-tooltip-nw::after{margin-right:-15px}.yoast-tooltip-s::after,.yoast-tooltip-n::after{-ms-transform:translateX(50%);transform:translateX(50%)}.yoast-tooltip-w::after{right:100%;bottom:50%;margin-right:5px;-ms-transform:translateY(50%);transform:translateY(50%)}.yoast-tooltip-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,0.8)}.yoast-tooltip-e::after{bottom:50%;left:100%;margin-left:5px;-ms-transform:translateY(50%);transform:translateY(50%)}.yoast-tooltip-e::before{top:50%;right:-5px;bottom:50%;margin-top:-5px;border-right-color:rgba(0,0,0,0.8)}.yoast-tooltip-multiline::after{width:250px;width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:250px;border-collapse:separate;white-space:pre-line;word-wrap:normal;word-break:break-word}.yoast-tooltip-multiline.yoast-tooltip-s::after,.yoast-tooltip-multiline.yoast-tooltip-n::after{right:auto;left:50%;-ms-transform:translateX(-50%);transform:translateX(-50%)}.yoast-tooltip-multiline.yoast-tooltip-w::after,.yoast-tooltip-multiline.yoast-tooltip-e::after{right:100%}@media screen and (min-width: 0\0){.yoast-tooltip-multiline::after{width:250px}}.yoast-tooltip-sticky::before,.yoast-tooltip-sticky::after{display:inline-block}.yoast-tooltip-sticky.yoast-tooltip-multiline::after{display:table-cell}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.yoast-tooltip-w::after{margin-right:4.5px}}.yoast-tooltip.yoast-tooltip-hidden::before,.yoast-tooltip.yoast-tooltip-hidden::after{display:none} 2 | -------------------------------------------------------------------------------- /seo.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Paul Massendari 13 | * @copyright 2020, Paul Massendari 14 | * @license MIT 15 | */ 16 | 17 | namespace Grav\Plugin; 18 | 19 | use Grav\Common\Plugin; 20 | use Grav\Common\Page\Page; 21 | use Grav\Common\Data\Blueprints; 22 | use Grav\Common\Page\Pages; 23 | use RocketTheme\Toolbox\Event\Event; 24 | use Grav\Common\Grav; 25 | use Grav\Common\Page\Media; 26 | use Grav\Common\Helpers\Exif; 27 | use Grav\Common\Page\Medium\AbstractMedia; 28 | use Grav\Common\Iterator; 29 | 30 | 31 | /** 32 | * SEO Plugin 33 | * 34 | * This plugin adds an user-friendly SEO tab for your user to manage metadata tags 35 | * and appearance on Search Engine Results and Social Networks. 36 | */ 37 | 38 | class SeoPlugin extends Plugin 39 | { 40 | 41 | /** ------------- 42 | * Public methods 43 | * -------------- 44 | */ 45 | 46 | /** 47 | * Return a list of subscribed events. 48 | * 49 | * @return array The list of events of the plugin of the form 50 | * 'name' => ['method_name', priority]. 51 | */ 52 | public static function getSubscribedEvents() 53 | { 54 | return [ 55 | 'onPluginsInitialized' => ['onPluginsInitialized', 0], 56 | 'onPageInitialized' => ['onPageInitialized', 0], 57 | // 'onPageContentRaw' => ['onPageContentRaw', 0], 58 | // 'onBlueprintCreated' => ['onBlueprintCreated', 0] 59 | ]; 60 | } 61 | 62 | private function cleanArray(array $array): array 63 | { 64 | foreach ($array as $key => &$value) { 65 | if (is_array($value)) { 66 | $value = $this->cleanArray($value); 67 | } 68 | 69 | if (empty($value) && $value !== 0 && $value !== '0') { 70 | unset($array[$key]); 71 | } 72 | } 73 | 74 | return $array; 75 | } 76 | 77 | /** 78 | * Récupère les métadonnées d'une image (dimensions et URL) 79 | * 80 | * @param string|null $imageUrl URL de l'image à analyser 81 | * @return array{width: string, height: string, url: string} 82 | */ 83 | private function seoGetImage(?string $imageUrl): array 84 | { 85 | // Si l'URL est vide, retourner directement les valeurs par défaut 86 | if (empty($imageUrl)) { 87 | return [ 88 | 'width' => '0', 89 | 'height' => '0', 90 | 'url' => '', 91 | ]; 92 | } 93 | 94 | try { 95 | // Extraction du chemin et du nom de fichier 96 | if (!preg_match('~((\/[^\/]+)+)\/([^\/]+)~', $imageUrl, $matches)) { 97 | throw new \RuntimeException('Format d\'URL invalide'); 98 | } 99 | 100 | $imagePath = $matches[1]; 101 | $imageName = $matches[3]; 102 | 103 | // Récupération de la page 104 | $page = $this->grav['page']->find($imagePath); 105 | if (!$page) { 106 | throw new \RuntimeException("Page non trouvée: $imagePath"); 107 | } 108 | 109 | // Vérification de la présence d'images 110 | $images = $page->media()->images(); 111 | if (empty($images)) { 112 | throw new \RuntimeException("Aucune image trouvée"); 113 | } 114 | 115 | // Recherche de l'image spécifique 116 | $availableImages = array_keys($images); 117 | $imageIndex = array_search($imageName, $availableImages); 118 | if ($imageIndex === false) { 119 | throw new \RuntimeException("Image spécifique non trouvée"); 120 | } 121 | 122 | $imageKey = $availableImages[$imageIndex]; 123 | $image = $images[$imageKey]; 124 | 125 | // Vérification du chemin de l'image 126 | if (!$image || !$image->path() || !file_exists($image->path())) { 127 | throw new \RuntimeException("Fichier image invalide ou inaccessible"); 128 | } 129 | 130 | $dimensions = @getimagesize($image->path()); 131 | if ($dimensions === false) { 132 | throw new \RuntimeException("Impossible de lire les dimensions de l'image"); 133 | } 134 | 135 | return [ 136 | 'width' => (string)$dimensions[0], 137 | 'height' => (string)$dimensions[1], 138 | 'url' => $image->url(), 139 | ]; 140 | 141 | } catch (\Exception $e) { 142 | // Log l'erreur mais ne casse pas le site 143 | $this->grav['log']->debug('SEO Plugin - Image Warning: ' . $e->getMessage()); 144 | 145 | return [ 146 | 'width' => '0', 147 | 'height' => '0', 148 | 'url' => '', 149 | ]; 150 | } 151 | } 152 | /** 153 | * Nettoie et convertit le texte Markdown en texte brut 154 | * 155 | * @param string $text Le texte Markdown à nettoyer 156 | * @param int $maxLength Longueur maximale du texte retourné (défaut: 320) 157 | * @return string Le texte nettoyé 158 | */ 159 | private const MARKDOWN_RULES = [ 160 | // Suppression des inclusions Twig 161 | '/{%[\s\S]*?%}[\s\S]*?/' => '', 162 | 163 | // Suppression des balises HTML spécifiques 164 | '/]*?>.*?<\/style>/si' => '', 165 | '/]*?>.*?<\/script>/si' => '', 166 | 167 | // Conversion de la syntaxe Markdown 168 | '/^#+\s*(.*)$/m' => '$1', // Titres 169 | '/^[*\-_]{3,}$/m' => '', // Lignes horizontales 170 | '/!\[([^\]]*)\]\([^)]+\)/' => '', // Images 171 | '/\[([^\]]+)\]\([^)]+\)/' => '$1', // Liens 172 | '/[*_]{2}(.*?)[*_]{2}/' => '$1', // Gras 173 | '/[*_](.*?)[*_]/' => '$1', // Italique 174 | '/~~(.*?)~~/' => '$1', // Barré 175 | '/:`(.*?)`/' => '$1', // Code inline 176 | '/^```[\s\S]*?```$/m' => '', // Blocs de code 177 | '/^[*\-+]\s+(.*)$/m' => '$1', // Listes non ordonnées 178 | '/^\d+\.\s+(.*)$/m' => '$1', // Listes ordonnées 179 | '/^>\s*(.*)$/m' => '$1', // Citations 180 | '//' => '', // Commentaires HTML 181 | ]; 182 | 183 | private function cleanMarkdown(string $text, int $maxLength = 320): string 184 | { 185 | 186 | // Nettoyage initial 187 | $text = strip_tags($text); 188 | 189 | // Application des règles de nettoyage Markdown 190 | foreach (self::MARKDOWN_RULES as $pattern => $replacement) { 191 | $text = preg_replace($pattern, $replacement, $text); 192 | } 193 | 194 | // Nettoyage final 195 | $text = preg_replace('/\s+/', ' ', $text); // Remplace les espaces multiples 196 | $text = str_replace(["\r", "\n"], ' ', $text); // Remplace les retours à la ligne 197 | $text = preg_replace('/\. \./', '.', $text); // Corrige la ponctuation 198 | $text = trim($text); // Supprime les espaces aux extrémités 199 | 200 | // Retourne le texte tronqué à la longueur maximale 201 | return mb_substr($text, 0, $maxLength); 202 | } 203 | 204 | 205 | /** 206 | * Initialize configuration 207 | */ 208 | public function onPluginsInitialized() 209 | { 210 | 211 | // Set default events 212 | $events = [ 213 | 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 214 | // 'onPageContentRaw' => ['onPageContentRaw', 0], 215 | ]; 216 | 217 | // Set admin specific events 218 | if ($this->isAdmin()) { 219 | $this->active = false; 220 | $events = [ 221 | 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 222 | 'onBlueprintCreated' => ['onBlueprintCreated', 0], 223 | // 'onPageContentRaw' => ['onPageContentRaw', 0], 224 | ]; 225 | } 226 | 227 | // Register events 228 | 229 | $this->enable($events); 230 | } 231 | public function onPageInitialized() 232 | { 233 | $page = $this->grav['page']; 234 | $config = $this->mergeConfig($page); 235 | $content = strip_tags($page->content()); 236 | $assets = $this->grav['assets']; 237 | $customjson = ""; 238 | $outputcustomjson = ""; 239 | $pattern = '~((\/[^\/]+)+)\/([^\/]+)~'; 240 | $replacement = '$1'; 241 | $outputjson = ""; 242 | $uri = $this->grav['uri']; 243 | $route = $this->config->get('plugins.admin.route'); 244 | $microdata = []; 245 | $meta = $page->metadata(null); 246 | $cleanedMarkdown = $this->cleanMarkdown($page->content()); 247 | 248 | if (isset($page->header()->googletitle)) { 249 | $page->header()->displaytitle = $page->header()->title; // Keep original title available for template use 250 | $page->header()->title = $page->header()->googletitle; 251 | }; 252 | if (isset($page->header()->googledesc)) { 253 | 254 | $meta['description']['name'] = 'description'; 255 | $meta['description']['content'] = $page->header()->googledesc; 256 | 257 | } else { 258 | $meta['description']['name'] = 'description'; 259 | $meta['description']['content'] = $cleanedMarkdown; 260 | }; 261 | 262 | /** 263 | * Set Twitter Metatags 264 | */ 265 | 266 | if (property_exists($page->header(),'twitterenable')) { 267 | if ($page->header()->twitterenable == 'true') { 268 | 269 | if (isset($config['twitterid'])) { 270 | $meta['twitter:site']['name'] = 'twitter:site'; 271 | $meta['twitter:site']['property'] = 'twitter:site'; 272 | $meta['twitter:site']['content'] = $config->twitterid; 273 | }; 274 | if (isset($page->header()->twittercardoptions)) { 275 | $meta['twitter:card']['name'] = 'twitter:card'; 276 | $meta['twitter:card']['property'] = 'twitter:card'; 277 | $meta['twitter:card']['content'] = $page->header()->twittercardoptions; 278 | } else { 279 | $meta['twitter:card']['name'] = 'twitter:card'; 280 | $meta['twitter:card']['property'] = 'twitter:card'; 281 | $meta['twitter:card']['content'] = 'summary_large_image'; 282 | }; 283 | 284 | if (isset($page->header()->twittertitle)) { 285 | $meta['twitter:title']['name'] = 'twitter:title'; 286 | $meta['twitter:title']['property'] = 'twitter:title'; 287 | $meta['twitter:title']['content'] = $page->header()->twittertitle; 288 | } else { 289 | $meta['twitter:title']['name'] = 'twitter:title'; 290 | $meta['twitter:title']['property'] = 'twitter:title'; 291 | $meta['twitter:title']['content'] = $page->title() . ' | ' . $this->config->get('site.title'); 292 | }; 293 | if (isset($page->header()->twitterdescription)) { 294 | $meta['twitter:description']['name'] = 'twitter:description'; 295 | $meta['twitter:description']['property'] = 'twitter:description'; 296 | $meta['twitter:description']['content'] = $page->header()->twitterdescription; 297 | } else { 298 | $meta['twitter:description']['name'] = 'twitter:description'; 299 | $meta['twitter:description']['property'] = 'twitter:description'; 300 | $meta['twitter:description']['content'] = $cleanedMarkdown; 301 | }; 302 | if (isset($page->header()->twittershareimg)) { 303 | $meta['twitter:image']['name'] = 'twitter:image'; 304 | $meta['twitter:image']['property'] = 'twitter:image'; 305 | $twittershareimg = $page->header()->twittershareimg; 306 | $imagedata = $this->seoGetimage($twittershareimg); 307 | $meta['twitter:image']['content'] = $this->grav['uri']->base() . $imagedata['url']; 308 | } elseif(!empty($page->media()->images())) { 309 | 310 | $meta['twitter:image']['name'] = 'twitter:image'; 311 | $meta['twitter:image']['property'] = 'twitter:image'; 312 | $imgobject = $page->media()->images(); 313 | $getfirst = array_shift($imgobject); 314 | $firstimage = $getfirst->url(); 315 | //$imagedata = $this->seoGetimage($firstimage); 316 | $meta['twitter:image']['content'] = $this->grav['uri']->base() . $firstimage; 317 | }; 318 | $meta['twitter:url']['name'] = 'twitter:url'; 319 | $meta['twitter:url']['property'] = 'twitter:url'; 320 | $meta['twitter:url']['content'] = $page->url(true); 321 | } 322 | } 323 | if (property_exists($page->header(),'facebookenable')){ 324 | if ($page->header()->facebookenable == 'true') { 325 | 326 | //$meta['og:sitename']['name'] = 'og:sitename'; 327 | $meta['og:site_name']['property'] = 'og:site_name'; 328 | $meta['og:site_name']['content'] = $this->config->get('site.title'); 329 | if (isset($page->header()->facebooktitle)) { 330 | //$meta['og:title']['name'] = 'og:title'; 331 | $meta['og:title']['property'] = 'og:title'; 332 | $meta['og:title']['content'] = $page->header()->facebooktitle; 333 | } else { 334 | // $meta['og:title']['name'] = 'og:title'; 335 | $meta['og:title']['property'] = 'og:title'; 336 | $meta['og:title']['content'] = $page->title(); 337 | } 338 | if (isset($config['facebookid'])) { 339 | //$meta['twitter:site']['name'] = 'twitter:site'; 340 | $meta['fb:app_id']['property'] = 'fb:app_id'; 341 | $meta['fb:app_id']['content'] = $config->facebookid; 342 | }; 343 | //$meta['og:type']['name'] = 'og:type'; 344 | $meta['og:type']['property'] = 'og:type'; 345 | $meta['og:type']['content'] = 'article'; 346 | // $meta['og:url']['name'] = 'og:url'; 347 | $meta['og:url']['property'] = 'og:url'; 348 | $meta['og:url']['content'] = $this->grav['page']->canonical(true); 349 | if (isset($page->header()->facebookdesc)) { 350 | //$meta['og:description']['name'] = 'og:description'; 351 | $meta['og:description']['property'] = 'og:description'; 352 | $meta['og:description']['content'] = substr($this->cleanMarkdown($page->header()->facebookdesc),0,320); 353 | } else { 354 | // $meta['og:description']['name'] = 'og:description'; 355 | $meta['og:description']['property'] = 'og:description'; 356 | $meta['og:description']['content'] = $cleanedMarkdown; 357 | } 358 | if (isset($page->header()->facebookauthor)) { 359 | // $meta['article:author']['name'] = 'article:author'; 360 | $meta['article:author']['property'] = 'article:author'; 361 | $meta['article:author']['content'] = $page->header()->facebookauthor; 362 | } 363 | if (isset($page->header()->facebookimg)) { 364 | // $meta['og:image']['name'] = 'og:image'; 365 | $meta['og:image']['property'] = 'og:image'; 366 | $facebookimg = $page->header()->facebookimg; 367 | $imagedata = $this->seoGetimage($facebookimg); 368 | $meta['og:image']['content'] = $this->grav['uri']->base() . $imagedata['url']; 369 | } elseif(!empty($page->media()->images())) { 370 | $meta['og:image']['property'] = 'og:image'; 371 | $imgobject = $page->media()->images(); 372 | $getfirst = array_shift($imgobject); 373 | $firstimage = $getfirst->url(); 374 | //$imagedata = $this->seoGetimage($firstimage); 375 | $meta['twitter:image']['content'] = $this->grav['uri']->base() . $firstimage; 376 | $meta['og:image']['content'] = $this->grav['uri']->base() . $firstimage; 377 | } 378 | 379 | } 380 | 381 | } 382 | // Add metadata 383 | $page->metadata($meta); 384 | // Set Json-Ld Microdata 385 | // Article Microdata 386 | if (property_exists($page->header(), 'musiceventenabled')) { 387 | if ($page->header()->musiceventenabled && $this->config['plugins']['seo']['musicevent']) { 388 | $musiceventsarray = $page->header()->musicevents ?? []; 389 | 390 | // Vérifier que nous avons un array valide et non vide 391 | if (is_array($musiceventsarray) && !empty($musiceventsarray)) { 392 | foreach ($musiceventsarray as $event) { 393 | $performerarray = []; // Initialiser pour chaque événement 394 | $workarray = []; // Initialiser pour chaque événement 395 | $musiceventimage = null; // Initialiser pour chaque événement 396 | 397 | // Gestion des performers 398 | if (!empty($event['musicevent_performer']) && is_array($event['musicevent_performer'])) { 399 | foreach ($event['musicevent_performer'] as $artist) { 400 | $performerarray[] = [ 401 | '@type' => $artist['performer_type'] ?? 'PerformingGroup', 402 | 'name' => $artist['name'] ?? '', 403 | 'sameAs' => $artist['sameAs'] ?? '', 404 | ]; 405 | } 406 | } 407 | 408 | // Gestion des œuvres interprétées 409 | if (!empty($event['musicevent_workPerformed']) && is_array($event['musicevent_workPerformed'])) { 410 | foreach ($event['musicevent_workPerformed'] as $work) { 411 | $workarray[] = [ 412 | 'name' => $work['name'] ?? '', 413 | 'sameAs' => $work['sameAs'] ?? '', 414 | ]; 415 | } 416 | } 417 | 418 | // Gestion de l'image 419 | if (!empty($event['musicevent_image'])) { 420 | $imagedata = $this->seoGetImage($event['musicevent_image']); 421 | if (!empty($imagedata['url'])) { 422 | $musiceventimage = [ 423 | '@type' => 'ImageObject', 424 | 'width' => $imagedata['width'], 425 | 'height' => $imagedata['height'], 426 | 'url' => $this->grav['uri']->base() . $imagedata['url'], 427 | ]; 428 | } 429 | } 430 | 431 | // Construction de l'événement 432 | $eventData = [ 433 | '@context' => 'http://schema.org', 434 | '@type' => 'MusicEvent', 435 | 'name' => $event['musicevent_location_name'] ?? '', 436 | 'location' => [ 437 | '@type' => 'MusicVenue', 438 | 'name' => $event['musicevent_location_name'] ?? '', 439 | 'address' => $event['musicevent_location_address'] ?? '', 440 | ], 441 | 'description' => $event['musicevent_description'] ?? '', 442 | 'url' => $event['musicevent_url'] ?? '', 443 | 'offers' => [ 444 | '@type' => 'Offer', 445 | 'price' => $event['musicevent_offers_price'] ?? '', 446 | 'priceCurrency' => $event['musicevent_offers_priceCurrency'] ?? '', 447 | 'url' => $event['musicevent_offers_url'] ?? '', 448 | ], 449 | ]; 450 | 451 | // Ajouter les champs optionnels seulement s'ils existent 452 | if (!empty($performerarray)) { 453 | $eventData['performer'] = $performerarray; 454 | } 455 | if (!empty($workarray)) { 456 | $eventData['workPerformed'] = $workarray; 457 | } 458 | if ($musiceventimage) { 459 | $eventData['image'] = $musiceventimage; 460 | } 461 | 462 | // Gestion des dates 463 | if (!empty($event['musicevent_startdate'])) { 464 | $eventData['startDate'] = date("c", strtotime($event['musicevent_startdate'])); 465 | } 466 | if (!empty($event['musicevent_enddate'])) { 467 | $eventData['endDate'] = date("c", strtotime($event['musicevent_enddate'])); 468 | } 469 | 470 | $microdata[] = $eventData; 471 | } 472 | } 473 | } 474 | } 475 | if (property_exists($page->header(), 'eventenabled')) { 476 | if ($page->header()->eventenabled && $this->config['plugins']['seo']['event']) { 477 | $eventsarray = $page->header()->addevent ?? []; 478 | 479 | // Vérifier que nous avons un array valide et non vide 480 | if (is_array($eventsarray) && !empty($eventsarray)) { 481 | foreach ($eventsarray as $event) { 482 | // Préparer l'adresse seulement si les données nécessaires existent 483 | $address = [ 484 | '@type' => 'PostalAddress', 485 | ]; 486 | 487 | // Ajouter les champs d'adresse seulement s'ils existent 488 | if (!empty($event['event_location_address_addressLocality'])) { 489 | $address['addressLocality'] = $event['event_location_address_addressLocality']; 490 | } 491 | if (!empty($event['event_location_address_addressRegion'])) { 492 | $address['addressRegion'] = $event['event_location_address_addressRegion']; 493 | } 494 | if (!empty($event['event_location_streetAddress'])) { 495 | $address['streetAddress'] = $event['event_location_streetAddress']; 496 | } 497 | 498 | // Préparer l'offre seulement si les données nécessaires existent 499 | $offers = [ 500 | '@type' => 'Offer', 501 | ]; 502 | if (!empty($event['event_offers_price'])) { 503 | $offers['price'] = $event['event_offers_price']; 504 | } 505 | if (!empty($event['event_offers_currency'])) { 506 | $offers['priceCurrency'] = $event['event_offers_currency']; 507 | } 508 | if (!empty($event['event_offers_url'])) { 509 | $offers['url'] = $event['event_offers_url']; 510 | } 511 | 512 | // Construction de l'événement de base 513 | $eventData = [ 514 | '@context' => 'http://schema.org', 515 | '@type' => 'Event', 516 | 'name' => $event['event_name'] ?? '', 517 | 'location' => [ 518 | '@type' => 'Place', 519 | 'name' => $event['event_location_name'] ?? '', 520 | 'address' => $address, 521 | ], 522 | ]; 523 | 524 | // Ajouter l'URL de la location si elle existe 525 | if (!empty($event['musicevent_location_url'])) { 526 | $eventData['location']['url'] = $event['musicevent_location_url']; 527 | } 528 | 529 | // Ajouter la description si elle existe 530 | if (!empty($event['event_description'])) { 531 | $eventData['description'] = $event['event_description']; 532 | } 533 | 534 | // Ajouter les offres si elles ne sont pas vides 535 | if (count(array_filter($offers)) > 1) { // > 1 car @type est toujours présent 536 | $eventData['offers'] = $offers; 537 | } 538 | 539 | // Gestion des dates 540 | if (!empty($event['event_startDate'])) { 541 | $startDate = strtotime($event['event_startDate']); 542 | if ($startDate) { 543 | $eventData['startDate'] = date("c", $startDate); 544 | } 545 | } 546 | if (!empty($event['event_endDate'])) { 547 | $endDate = strtotime($event['event_endDate']); 548 | if ($endDate) { 549 | $eventData['endDate'] = date("c", $endDate); 550 | } 551 | } 552 | 553 | $microdata[] = array_filter($eventData, function($value) { 554 | return $value !== null && $value !== ''; 555 | }); 556 | } 557 | } 558 | } 559 | } 560 | if (property_exists($page->header(), 'personenabled')) { 561 | if ($page->header()->personenabled && $this->config['plugins']['seo']['person']) { 562 | $personarray = $page->header()->addperson ?? []; 563 | 564 | // Vérification que $personarray est un array et n'est pas vide 565 | if (is_array($personarray) && !empty($personarray)) { 566 | foreach ($personarray as $person) { 567 | $microdata[] = [ 568 | '@context' => 'http://schema.org', 569 | '@type' => 'Person', 570 | 'name' => $person['person_name'] ?? null, 571 | 'address' => [ 572 | '@type' => 'PostalAddress', 573 | 'addressLocality' => $person['person_address_addressLocality'] ?? null, 574 | 'addressRegion' => $person['person_address_addressRegion'] ?? null, 575 | ], 576 | 'jobTitle' => $person['person_jobTitle'] ?? null, 577 | ]; 578 | } 579 | } 580 | } 581 | } 582 | if (property_exists($page->header(),'orgaenabled')){ 583 | if ($page->header()->orgaenabled and $this->config['plugins']['seo']['organization']) { 584 | if (isset($page->header()->orga['founders'])){ 585 | foreach ($page->header()->orga['founders'] as $founder){ 586 | $founderarray[] = [ 587 | '@type' => 'Person', 588 | 'name' => @$founder['name'], 589 | ]; 590 | } 591 | } 592 | if (isset($page->header()->orga['similar'])){ 593 | foreach ($page->header()->orga['similar'] as $similar){ 594 | $similararray[] = $similar['sameas']; 595 | } 596 | } 597 | if (isset($page->header()->orga['areaserved'])){ 598 | foreach ($page->header()->orga['areaserved'] as $areaserved){ 599 | $areaservedarray[] = $areaserved['area']; 600 | } 601 | } 602 | if (isset($page->header()->orga['openingHours'])){ 603 | foreach ($page->header()->orga['openingHours'] as $hours){ 604 | $openingHours[] = $hours['entry']; 605 | } 606 | } 607 | if (isset($page->header()->orga['offercatalog'])){ 608 | foreach ($page->header()->orga['offercatalog'] as $offer) { 609 | if (array_key_exists('offereditem', $offer)) { 610 | foreach ($offer['offereditem'] as $service) { 611 | $offerarray[] = [ 612 | '@type' => 'OfferCatalog', 613 | 'name' => @$offer['offer'], 614 | 'description' => @$offer['description'], 615 | 'url' => @$offer['url'], 616 | 'image' => @$offer['image'], 617 | 'itemListElement' => [ 618 | '@type' => 'Offer', 619 | 'itemOffered' => [ 620 | '@type' => 'Service', 621 | 'name' => @$service['name'], 622 | 'url' => @$service['url'], 623 | ], 624 | ], 625 | ]; 626 | } 627 | } else { 628 | $offerarray[] = [ 629 | '@type' => 'OfferCatalog', 630 | 'name' => @$offer['offer'], 631 | 'description' => @$offer['description'], 632 | 'url' => @$offer['url'], 633 | 'image' => @$offer['image'], 634 | ]; 635 | } 636 | } 637 | } 638 | 639 | if (property_exists($page->header(),'orgaratingenabled')){ 640 | 641 | if ($page->header()->orgaratingenabled){ 642 | $orgarating = [ 643 | '@type' => 'AggregateRating', 644 | 'ratingValue' => @$page->header()->orga['ratingValue'], 645 | 'reviewCount' => @$page->header()->orga['reviewCount'], 646 | ]; 647 | } 648 | 649 | } 650 | $microdata[] = [ 651 | '@context' => 'http://schema.org', 652 | '@type' => 'Organization', 653 | 'name' => @$page->header()->orga['name'], 654 | 'legalname' => @$page->header()->orga['legalname'], 655 | 'taxid' => @$page->header()->orga['taxid'], 656 | 'vatid' => @$page->header()->orga['vatid'], 657 | 'areaServed' => @$areaservedarray, 658 | 'description' => @$page->header()->orga['description'], 659 | 660 | 'address' => [ 661 | '@type' => 'PostalAddress', 662 | 'streetAddress' => @$page->header()->orga['streetaddress'], 663 | 'addressLocality' => @$page->header()->orga['city'], 664 | 'addressRegion' => @$page->header()->orga['state'], 665 | 'postalCode' => @$page->header()->orga['zipcode'], 666 | ], 667 | 'telephone' => @$page->header()->orga['phone'], 668 | 'logo' => @$page->header()->orga['logo'], 669 | 'url' => @$page->header()->orga['url'], 670 | 'openingHours' => @$openingHours, 671 | 'email' => @$page->header()->orga['email'], 672 | 'foundingDate' => @$page->header()->orga['foundingDate'], 673 | 'aggregateRating' => @$orgarating, 674 | 'paymentAccepted' => @$page->header()->orga['paymentAccepted'], 675 | 'founders' => @$founderarray, 676 | 'sameAs' => @$similararray, 677 | 'hasOfferCatalog' => @$offerarray 678 | ]; 679 | 680 | 681 | 682 | } 683 | } 684 | if (property_exists($page->header(),'restaurantenabled')){ 685 | if ($page->header()->restaurantenabled and $this->config['plugins']['seo']['restaurant']) { 686 | if (isset($page->header()->restaurant['image'])){ 687 | $imageurl = $page->header()->restaurant['image']; 688 | $imagedata = $this->seoGetimage($imageurl); 689 | $restaurantimage = [ 690 | 691 | '@type' => 'ImageObject', 692 | 'width' => $imagedata['width'], 693 | 'height' => $imagedata['height'], 694 | 'url' => $this->grav['uri']->base() . $imagedata['url'], 695 | 696 | ]; 697 | 698 | } 699 | $microdata[] = [ 700 | '@context' => 'http://schema.org', 701 | '@type' => 'Restaurant', 702 | 'name' => @$page->header()->restaurant['name'], 703 | 704 | 'address' => [ 705 | '@type' => 'PostalAddress', 706 | 'addressLocality' => @$page->header()->restaurant['address_addressLocality'], 707 | 'addressRegion' => @$page->header()->restaurant['address_addressRegion'], 708 | 'streetAddress' => @$page->header()->restaurant['address_streetAddress'], 709 | 'postalCode' => @$page->header()->restaurant['address_postalCode'], 710 | ], 711 | 'areaserved' => @$areaservedarray, 712 | 'servesCuisine' => @$page->header()->restaurant['servesCuisine'], 713 | 'priceRange' => @$page->header()->restaurant['priceRange'], 714 | 'image' => @$restaurantimage, 715 | 'telephone' => @$page->header()->restaurant['telephone'], 716 | 717 | ]; 718 | 719 | 720 | } 721 | } 722 | if (property_exists($page->header(),'productenabled')){ 723 | if ($page->header()->productenabled and $this->config['plugins']['seo']['product']) { 724 | if (isset($page->header()->product['image'])){ 725 | $productimagearray = []; 726 | $productimages = $page->header()->product['image']; 727 | 728 | 729 | foreach ($productimages as $key => $value){ 730 | $imagearray = $productimages[$key]; 731 | foreach($imagearray as $newkey => $newvalue){ 732 | $imagedata = $this->seoGetimage($imagearray[$newkey]); 733 | $productimage[] = 734 | $this->grav['uri']->base() . $imagedata['url']; 735 | 736 | }; 737 | 738 | }; 739 | } 740 | if (isset($page->header()->product['addoffer'])){ 741 | 742 | $offers = $page->header()->product['addoffer']; 743 | foreach ($offers as $key => $value){ 744 | $offer[$key] = [ 745 | '@type' => 'Offer', 746 | 'priceCurrency' => @$offers[$key]['offer_priceCurrency'], 747 | 'price' => @$offers[$key]['offer_price'], 748 | 'validFrom' => @$offers[$key]['offer_validFrom'], 749 | 'priceValidUntil' => @$offers[$key]['offer_validUntil'], 750 | 'availability' => @$offers[$key]['offer_availability'], 751 | ]; 752 | }; 753 | } 754 | else { $offer = ''; } 755 | 756 | $microdata[] = [ 757 | '@context' => 'http://schema.org', 758 | '@type' => 'Product', 759 | 'name' => @$page->header()->product['name'], 760 | 'category' => @$page->header()->product['category'], 761 | 'brand' => [ 762 | '@type' => 'Thing', 763 | 'name' => @$page->header()->product['brand'], 764 | ], 765 | 'offers' => $offer, 766 | 'description' => @$page->header()->product['description'], 767 | 'image' => @$productimage, 768 | 'aggregateRating' => [ 769 | '@type' => 'AggregateRating', 770 | 'ratingValue' => @$page->header()->product['ratingValue'], 771 | 'reviewCount' => @$page->header()->product['reviewCount'], 772 | 773 | ] 774 | ]; 775 | } 776 | } 777 | if (property_exists($page->header(),'articleenabled')){ 778 | if (isset($page->header()->article['headline'])){ 779 | $headline = $page->header()->article['headline']; 780 | } else { 781 | $headline = $page->title(); 782 | } 783 | if ($page->header()->articleenabled and $this->config['plugins']['seo']['article']) { 784 | $microdata['article'] = [ 785 | '@context' => 'http://schema.org', 786 | '@type' => 'Article', 787 | 'headline' => $headline, 788 | 'mainEntityOfPage' => [ 789 | "@type" => "WebPage", 790 | 'url' => $this->grav['uri']->base(), 791 | ], 792 | 'articleBody' => $this->cleanMarkdown($content), 793 | 'datePublished' => date("c", strtotime($page->header()->article['datePublished'] ?? '') ?: $page->date()), 794 | 'dateModified' => date("c", strtotime($page->header()->article['dateModified'] ?? '') ?: $page->date()), 795 | ]; 796 | if (isset($page->header()->article['description'])) { 797 | $microdata['article']['description'] = $page->header()->article['description']; 798 | } 799 | else { 800 | $microdata['article']['description'] = substr($content,0,140); 801 | }; 802 | 803 | if (isset($page->header()->article['author'])) { 804 | $microdata['article']['author'] = $page->header()->article['author']; 805 | }; 806 | if (isset($page->header()->article['publisher_name'])) { 807 | $microdata['article']['publisher']['@type'] = 'Organization'; 808 | $microdata['article']['publisher']['name'] = @$page->header()->article['publisher_name']; 809 | }; 810 | if (isset($page->header()->article['publisher_logo_url'])) { 811 | $publisherlogourl = $page->header()->article['publisher_logo_url']; 812 | $imagedata = $this->seoGetimage($publisherlogourl); 813 | $microdata['article']['publisher']['logo']['@type'] = 'ImageObject'; 814 | $microdata['article']['publisher']['logo']['url'] = $this->grav['uri']->base() . $imagedata['url']; 815 | $microdata['article']['publisher']['logo']['width'] = $imagedata['width']; 816 | $microdata['article']['publisher']['logo']['height'] = $imagedata['height']; 817 | 818 | }; 819 | if (isset($page->header()->article['image_url'])) { 820 | $microdata['article']['image']['@type'] = 'ImageObject'; 821 | $imageurl = $page->header()->article['image_url']; 822 | $imagedata = $this->seoGetimage($imageurl); 823 | $microdata['article']['image']['url'] = $this->grav['uri']->base() . $imagedata['url']; 824 | $microdata['article']['image']['width'] = $imagedata['width']; 825 | $microdata['article']['image']['height'] = $imagedata['height']; 826 | 827 | } 828 | } 829 | }; 830 | // Encode to json 831 | /*foreach ($microdata as $key => $value){ 832 | if ($value === null){ 833 | unset($microdata[$key]); 834 | } 835 | }*/ 836 | // $microdata = array_map('array_filter', $microdata); 837 | $microdata = $this->cleanArray($microdata); 838 | $customjson = @$page->header()->add_json; 839 | foreach ($microdata as $key => $value){ 840 | 841 | 842 | $jsonscript = PHP_EOL . ''; 843 | $outputjson = $outputjson . $jsonscript; 844 | } 845 | if(!empty($customjson)){ 846 | foreach($customjson as $json){ 847 | $buildjson = PHP_EOL . ''; 848 | $outputcustomjson = $outputcustomjson . $buildjson ; 849 | } 850 | $outputjson = $outputjson . $outputcustomjson; 851 | } 852 | 853 | 854 | $outputjson = '' . $outputjson . '