Lezione 29: Importare un articolo da un altro sito WordPress
Questa lezione del tutorial mostra come possiamo mantenere i siti WordPress sincronizzati, importando un articolo da un sito WordPress nel nostro sito WordPress locale.
Query GraphQL per importare un articolo da un altro sito WordPress
La query GraphQL qui sotto si connette all'endpoint GraphQL di un sito web a monte, recupera i dati di un articolo specifico e lo importa localmente. Il plugin Gato GraphQL (versione gratuita) deve essere installato anche sul sito web a monte.
(Se Gato GraphQL non è installato sul sito a monte, la query può essere adattata per connettersi ai suoi endpoint dell'API REST WP.)
Le risorse referenziate nell'articolo devono esistere tutte localmente:
- L'autore
- L'immagine in evidenza (se presente)
- Le categorie
- (Anche i tag, tuttavia se questi non esistono ancora, vengono creati insieme all'articolo, quindi non è un problema)
Come identificatore comune per le risorse tra i siti a monte e locali, utilizziamo:
- Slug per categorie, tag ed elementi multimediali
- Nomi utente per gli utenti
Se una delle risorse non esiste nel sito locale, la query GraphQL visualizza un errore e interrompe l'importazione.
query InitializeDynamicVariables
@configureWarningsOnExportingDuplicateVariable(enabled: false)
{
initVariablesWithFalse: _echo(value: false)
@export(as: "requestProducedErrors")
@export(as: "responseHasErrors")
@export(as: "postIsMissing")
@export(as: "postHasAuthor")
@export(as: "postHasFeaturedImage")
@export(as: "postHasCategories")
@export(as: "postHasTags")
@remove
initVariablesWithNull: _echo(value: null)
@export(as: "existingAuthorUsername")
@export(as: "existingFeaturedImageSlug")
@export(as: "featuredImageMutationInput")
@remove
initVariablesWithEmptyArray: _echo(value: [])
@export(as: "existingCategorySlugs")
@export(as: "existingTagSlugs")
@remove
}
query CheckIfPostExistsLocally($postSlug: String!)
@depends(on: "InitializeDynamicVariables")
{
localPost: post(
by: { slug: $postSlug }
status: any
) {
id
}
postAlreadyExists: _notNull(value: $__localPost)
@export(as: "postAlreadyExists")
}
query FailIfPostAlreadyExistsLocally($postSlug: String!)
@depends(on: "CheckIfPostExistsLocally")
@include(if: $postAlreadyExists)
{
errorMessage: _sprintf(
string: "Post with slug '%s' already exists locally",
values: [$postSlug]
) @remove
_fail(
message: $__errorMessage
data: {
slug: $postSlug
}
) @remove
createPost: _echo(value: null)
}
query ConnectToGraphQLAPI(
$upstreamServerGraphQLEndpointURL: String!
$postSlug: String!
)
@depends(on: "FailIfPostAlreadyExistsLocally")
@skip(if: $postAlreadyExists)
{
externalData: _sendGraphQLHTTPRequest(input:{
endpoint: $upstreamServerGraphQLEndpointURL,
query: """
query GetPost($postSlug: String!) {
post(by: { slug: $postSlug }) {
id
slug
rawTitle
rawContent
rawExcerpt
author {
id
username
}
featuredImage {
id
slug
}
categories {
id
slug
}
tags {
id
slug
}
}
}
""",
variables: [
{
name: "postSlug",
value: $postSlug
}
]
})
@export(as: "externalData")
requestProducedErrors: _isNull(value: $__externalData)
@export(as: "requestProducedErrors")
@remove
}
query ValidateResponse
@depends(on: "ConnectToGraphQLAPI")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
{
responseHasErrors: _propertyIsSetInJSONObject(
object: $externalData
by: {
key: "errors"
}
)
@export(as: "responseHasErrors")
@remove
postExists: _propertyIsSetInJSONObject(
object: $externalData
by: {
path: "data.post"
}
)
@remove
postIsMissing: _not(value: $__postExists)
@export(as: "postIsMissing")
@remove
}
query FailIfResponseHasErrors
@depends(on: "ValidateResponse")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $postIsMissing)
@include(if: $responseHasErrors)
{
errors: _objectProperty(
object: $externalData,
by: {
key: "errors"
}
) @remove
_fail(
message: "Executing the GraphQL query against the upstream webserver produced error(s)"
data: {
errors: $__errors
}
) @remove
createPost: _echo(value: null)
}
query FailIfPostNotExists($postSlug: String!)
@depends(on: "FailIfResponseHasErrors")
@skip(if: $requestProducedErrors)
@include(if: $postIsMissing)
{
errorMessage: _sprintf(
string: "There is no post with slug '%s' in the origin",
values: [$postSlug]
) @remove
_fail(
message: $__errorMessage
data: {
slug: $postSlug
}
) @remove
createPost: _echo(value: null)
}
query ExportInputs
@depends(on: "FailIfPostNotExists")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $responseHasErrors)
@skip(if: $postIsMissing)
{
postData: _objectProperty(
object: $externalData,
by: { path: "data.post" }
) @remove
postTitle: _objectProperty(
object: $__postData,
by: { key: "rawTitle" }
)
@export(as: "postTitle")
@remove
postContent: _objectProperty(
object: $__postData,
by: { key: "rawContent" }
)
@export(as: "postContent")
@remove
postExcerpt: _objectProperty(
object: $__postData,
by: { key: "rawExcerpt" }
)
@export(as: "postExcerpt")
@remove
postAuthorUsername: _objectProperty(
object: $__postData,
by: { key: "author" }
)
@passOnwards(
as: "author"
)
@applyField(
name: "_notNull",
arguments: {
value: $author
},
passOnwardsAs: "hasAuthor"
)
@if(condition: $hasAuthor)
@applyField(
name: "_objectProperty",
arguments: {
object: $author,
by: { key: "username" }
},
setResultInResponse: true
)
@export(as: "postAuthorUsername")
@remove
postHasAuthor: _notNull(
value: $__postAuthorUsername
)
@export(as: "postHasAuthor")
@remove
postFeaturedImageSlug: _objectProperty(
object: $__postData,
by: { key: "featuredImage" }
)
@passOnwards(
as: "featuredImage"
)
@applyField(
name: "_notNull",
arguments: {
value: $featuredImage
},
passOnwardsAs: "hasFeaturedImage"
)
@if(condition: $hasFeaturedImage)
@applyField(
name: "_objectProperty",
arguments: {
object: $featuredImage,
by: { key: "slug" }
},
setResultInResponse: true
)
@export(as: "postFeaturedImageSlug")
@remove
postHasFeaturedImage: _notNull(
value: $__postFeaturedImageSlug
)
@export(as: "postHasFeaturedImage")
@remove
postCategorySlugs: _objectProperty(
object: $__postData,
by: { key: "categories" }
)
@underEachArrayItem(
passValueOnwardsAs: "category"
)
@applyField(
name: "_objectProperty"
arguments: {
object: $category,
by: {
key: "slug"
}
}
setResultInResponse: true
)
@export(as: "postCategorySlugs")
@remove
postHasCategories: _notEmpty(
value: $__postCategorySlugs
)
@export(as: "postHasCategories")
@remove
postTagSlugs: _objectProperty(
object: $__postData,
by: { key: "tags" }
)
@underEachArrayItem(
passValueOnwardsAs: "tag"
)
@applyField(
name: "_objectProperty"
arguments: {
object: $tag,
by: {
key: "slug"
}
}
setResultInResponse: true
)
@export(as: "postTagSlugs")
@remove
postHasTags: _notEmpty(
value: $__postTagSlugs
)
@export(as: "postHasTags")
@remove
}
query ExportExistingResources
@depends(on: "ExportInputs")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $responseHasErrors)
@skip(if: $postIsMissing)
{
existingAuthorByUsername: user(by: { username: $postAuthorUsername })
@include(if: $postHasAuthor)
{
id
username @export(as: "existingAuthorUsername")
}
existingFeaturedImageBySlug: mediaItem(by: { slug: $postFeaturedImageSlug })
@include(if: $postHasFeaturedImage)
{
id
slug @export(as: "existingFeaturedImageSlug")
}
existingCategoriesBySlug: postCategories(filter: { slugs: $postCategorySlugs })
@include(if: $postHasCategories)
{
id
slug @export(as: "existingCategorySlugs", type: LIST)
}
existingTagsBySlug: postTags(filter: { slugs: $postTagSlugs })
@include(if: $postHasTags)
{
id
slug @export(as: "existingTagSlugs", type: LIST)
}
}
query ExportMissingResources
@depends(on: "ExportExistingResources")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $responseHasErrors)
@skip(if: $postIsMissing)
{
isAuthorMissing: _notEquals(
value1: $postAuthorUsername,
value2: $existingAuthorUsername
) @export(as: "isAuthorMissing")
isFeaturedImageMissing: _notEquals(
value1: $postFeaturedImageSlug,
value2: $existingFeaturedImageSlug
) @export(as: "isFeaturedImageMissing")
missingCategorySlugs: _arrayDiff(
arrays: [$postCategorySlugs, $existingCategorySlugs]
) @export(as: "missingCategorySlugs")
areCategoriesMissing: _notEmpty(
value: $__missingCategorySlugs
) @export(as: "areCategoriesMissing")
# missingTagSlugs: _arrayDiff(
# arrays: [$postTagSlugs, $existingTagSlugs]
# ) @export(as: "missingTagSlugs")
# areTagsMissing: _notEmpty(
# value: $__missingTagSlugs
# ) @export(as: "areTagsMissing")
isAnyResourceMissing: _or(
values: [
$__isAuthorMissing,
$__isFeaturedImageMissing,
$__areCategoriesMissing,
# $__areTagsMissing,
]
) @export(as: "isAnyResourceMissing")
}
query FailIfAnyResourceIsMissing
@depends(on: "ExportMissingResources")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $postIsMissing)
@skip(if: $responseHasErrors)
@include(if: $isAnyResourceMissing)
{
performingValidations: id
@if(condition: $isAuthorMissing)
@fail(
message: "Author is missing in local site"
data: {
missingAuthorByUsername: $postAuthorUsername
}
condition: ALWAYS
)
@if(condition: $isFeaturedImageMissing)
@fail(
message: "Featured image is missing in local site"
data: {
missingFeaturedImageBySlug: $postFeaturedImageSlug
}
condition: ALWAYS
)
@if(condition: $areCategoriesMissing)
@fail(
message: "Categories are missing in local site"
data: {
missingCategoriesBySlug: $missingCategorySlugs
}
condition: ALWAYS
)
# @if(condition: $areTagsMissing)
# @fail(
# message: "Tags are missing in local site"
# data: {
# missingTagBySlug: $missingTagSlugs
# }
# condition: ALWAYS
# )
createPost: _echo(value: null)
}
query ExportMutationInputs
@depends(on: "FailIfAnyResourceIsMissing")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $responseHasErrors)
@skip(if: $postIsMissing)
@skip(if: $isAnyResourceMissing)
{
featuredImageMutationInput: _echo(value: {
slug: $postFeaturedImageSlug
})
@include(if: $postHasFeaturedImage)
@export(as: "featuredImageMutationInput")
@remove
}
mutation ImportPost(
$postSlug: String!
)
@depends(on: "ExportMutationInputs")
@skip(if: $postAlreadyExists)
@skip(if: $requestProducedErrors)
@skip(if: $responseHasErrors)
@skip(if: $postIsMissing)
@skip(if: $isAnyResourceMissing)
{
createPost(input: {
status: draft,
slug: $postSlug
title: $postTitle
contentAs: {
html: $postContent
},
excerpt: $postExcerpt
authorBy: {
username: $postAuthorUsername
},
featuredImageBy: $featuredImageMutationInput,
categoriesBy: {
slugs: $postCategorySlugs
},
tagsBy: {
slugs: $postTagSlugs
}
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
id
date
status
slug
title
content
excerpt
author {
id
username
}
featuredImage {
id
slug
}
categories {
id
slug
}
tags {
id
slug
}
}
}
}(Come abbiamo visto nelle lezioni precedenti del tutorial) Utilizziamo la funzionalità Field to Input (con la sintassi $__field) per passare il valore risolto del campo a un campo contiguo.
Quando abbiamo bisogno di passare il valore risolto del campo a una direttiva, dobbiamo invece usare la direttiva @passOnwards (anch'essa fornita dall'estensione Field to Input).
Questa query:
{
posts {
id
hasComments
notHasComments: _not(value: $__hasComments)
}
}...è equivalente a questa query:
{
posts {
id
hasComments
notHasComments: hasComments
@passOnwards(as: "postHasComments")
@applyField(
name: "_not"
arguments: {
value: $postHasComments
},
setResultInResponse: true
)
}
}@passOnwards è utile per produrre un calcolo sul valore del campo. Ad esempio, nella query GraphQL qui sopra, viene utilizzata per verificare se la proprietà user non è null, prima di estrarne la proprietà username (e sovrascrivere il valore del campo con essa):
postAuthorUsername: _objectProperty(
object: $__postData,
by: { key: "author" }
)
@passOnwards(
as: "author"
)
@applyField(
name: "_notNull",
arguments: {
value: $author
},
passOnwardsAs: "hasAuthor"
)
@if(condition: $hasAuthor)
@applyField(
name: "_objectProperty",
arguments: {
object: $author,
by: { key: "username" }
},
setResultInResponse: true
)
@export(as: "postAuthorUsername")
@remove[WIP] Importazione automatica delle risorse mancanti
Lo schema di Gato GraphQL deve essere aggiornato con le mutation per:
Questo è un lavoro in corso. Una volta che queste mutation saranno supportate, potremo fare in modo che la query GraphQL importi automaticamente ciascuna delle risorse mancanti.