Pulling Ordinary Files from WordPress Media Library Using Gatsby Resolvers
Robert Marshall
Posted on February 17, 2020
Back in September 2019 I wrote an article that covered the downloading and caching of images from the WordPress Media Library into Gatsby. I was working with relatively normal Gatsby builds, that only needed access to images from WordPress.
Since then I was began work on a project that needed to pull in both images (JPGs/PNGs) and various other file types from the media library (SVGs/XSLs/PDFs).
I had initially thought that the resolver that pulled in the images from WordPress would also be able to access the files. This was not the case. This is due to several reasons.
sourceUrl and not mediaItemUrl
In the original resolver I had been using the sourceUrl value from WPGraphQL. This works perfectly for images. However, if this plugin sees that the file is not an image – this value does not get populated. Or it would be populated with a strange value. I.E. if calling a PDF, the URL would be of the PDFs cover image – not of the PDF file (helpful for the future, but not for now).
That is where mediaItemUrl comes in. After a little bit of digging I found this value that contained the file URL without mattering what the filetype was. Perfect.
Node Filetype
When pulling in only image files, the resolver uses the “file” node type, created by gatsby-source-filesystem. This is very useful! However when pulling in actual files, I did not need all the added extras. I just wanted a direct URL to where it was stored in my Gatsby Static folder.
I added a separate section of the resolver to introduce the “string” node type, and then used Node to copy the file from WordPress into the static folder and update the URL.
Here is the final code:
exports.createResolvers = ({
actions,
cache,
createNodeId,
createResolvers,
getNode,
store,
reporter,
getNodeAndSavePathDependency,
}) => {
const { createNode, touchNode, createNodeField } = actions
const supportedExtensions = ['jpeg', 'jpg', 'png', 'webp', 'tif', 'tiff']
const checkIfCorrectFileType = ({ source, processType }) => {
// If the file is not a supported image, and we are meant
// to be checking images, return nothing to the resolver
const fileExt = source.mediaItemUrl.split('.').pop()
if (processType === 'image' && supportedExtensions.includes(fileExt)) {
return true
}
if (
processType === 'document' &&
!supportedExtensions.includes(fileExt)
) {
return true
}
return false
}
const fileProcessor = async ({
processType,
source,
getNode,
store,
cache,
reporter,
touchNode,
createNodeId,
getNodeAndSavePathDependency,
createRemoteFileNode,
context,
createNodeField,
pathPrefix = '',
}) => {
if (source.mediaItemUrl) {
// Check the filetype here, rather than using the nodeFile extention
// function as it means we do not need to download the file twice
if (
checkIfCorrectFileType({
source,
processType,
})
) {
let fileNodeID
let fileNode
let sourceModified
// Set the file cacheID, get it (if it has already been set)
const mediaDataCacheKey = `wordpress-media-${source.mediaItemId}`
const cacheMediaData = await cache.get(mediaDataCacheKey)
if (source.modified) {
sourceModified = source.modified
}
// If we have cached media data and it wasn't modified, reuse
// previously created file node to not try to redownload
if (
cacheMediaData &&
sourceModified === cacheMediaData.modified
) {
fileNode = getNode(cacheMediaData.fileNodeID)
// check if node still exists in cache
// it could be removed if image was made private
if (fileNode) {
fileNodeID = cacheMediaData.fileNodeID
// https://www.gatsbyjs.org/docs/node-creation/#freshstale-nodes
touchNode({
nodeId: fileNodeID,
})
}
}
// If we don't have cached data, download the file
if (!fileNodeID) {
try {
// Get the filenode
fileNode = await createRemoteFileNode({
url: source.mediaItemUrl,
store,
cache,
createNode,
createNodeId,
auth: {
htaccess_user: process.env.BASIC_AUTH_USER,
htaccess_pass: process.env.BASIC_AUTH_PASS,
},
reporter,
})
if (fileNode) {
fileNodeID = fileNode.id
await cache.set(mediaDataCacheKey, {
fileNodeID,
modified: sourceModified,
})
}
} catch (e) {
// Ignore
console.log(e)
return null
}
}
// If this is a document, it needs to moved to the
// static folder if it has not been moved yet
if (processType === 'document') {
const fileName = `${fileNode.name}-${fileNode.internal.contentDigest}${fileNode.ext}`
const publicPath = path.join(
process.cwd(),
`public`,
`static`,
fileName
)
// if the file does not exist
if (!fs.existsSync(publicPath)) {
await fs.copy(
fileNode.absolutePath,
publicPath,
err => {
if (err) {
console.error(
`error copying file from ${fileNode.absolutePath} to ${publicPath}`,
err
)
}
}
)
}
return `${pathPrefix}/static/${fileName}`
}
if (fileNode) {
return fileNode
}
}
}
return null
}
createResolvers({
WPGraphQL_MediaItem: {
imageFile: {
type: `File`,
async resolve(source, args, context, info) {
return await fileProcessor({
processType: 'image',
source,
getNode,
touchNode,
store,
cache,
createNodeField,
reporter,
createNodeId,
getNodeAndSavePathDependency,
createRemoteFileNode,
context,
})
},
},
staticUrl: {
type: `String`,
async resolve(source, args, context, info) {
const outcome = await fileProcessor({
processType: 'document',
source,
getNode,
touchNode,
store,
cache,
createNodeField,
reporter,
createNodeId,
getNodeAndSavePathDependency,
createRemoteFileNode,
context,
})
return outcome
},
},
},
})
}
How To Use It
A few things are required when using this resolver. When querying an image in the GraphQL query the following options need to be included:
- sourceUrl mediaItemId modified
This means that a query to get a posts featured image would be written like this:
The ‘templateFile’ is a custom ACF field that we are grabbing the static file from.
query GET_POSTS {
posts {
edges {
node {
featuredImage {
sourceUrl
mediaItemId
modified
imageFile {
childImageSharp {
fluid(maxWidth: 650) {
base64
aspectRatio
src
srcSet
sizes
}
}
}
}
templateFiles {
fileURL
}
}
}
}
}
Posted on February 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.