WPDb module now supporting attachments explicitly.
A fundamental post type
While WordPress installations can undergo a large amount of modifications aimed at adding and removing post types, functionalities and supports, usually a post type surviving modifications is the attachment
one.
The reason being that its inherent neutrality, it represents a generic attachment after all, and usefulness makes it one of the fundamental moving pieces in a CMS.
The WPDb
module, part of wp-browser, has explicitly supported the post
and page
post types so far but the time had come to support attachments too.
I could manage to test for and with attachments in acceptance and functional tests (or any level of testing where WordPress code was not loaded in the testing scope) using some wizardry, but it was not that easy.
the addition, some time ago, of the loadOnly
option to the WPLoader
module then has more than covered the issue so far.
Still: not that explicit and so easy.
An example usage
There are a number of things the WPDb
module new method, haveAttachmentInDatabase
, does under the hood; for all intents and purposes the method emulates what WordPress does when uploading an image.
In a default WordPress installation those operations are:
- moving the original uploaded file in the
uploads
folder - creating an image version for each size specified by the theme or plugins currently active if the attachment is an image
- create a bunch of entries in the database to keep track of the new attachment and its meta values
Keeping in mind the WPDb
module cannot, and should not, access WordPress code to do its job some additional work is required from the testers to make it behave exactly as intended.
Since the haveAttachmentInDatabase
will move files around it requires the WPFilesystem
module to work; an example suite configuration could look like this (I’m using Codeception dynamic parameter configuration in the example):
class_name: FunctionalTester
modules:
enabled:
- FunctionalHelper
- WPFilesystem
- WPDb
- Asserts
config:
WPFilesystem:
wpRootFolder: %WP_ROOT_FOLDER%
WPDb:
dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%'
user: %DB_USER%
password: %DB_PASSWORD%
dump: 'tests/_data/dump.sql'
populate: true
cleanup: true
reconnect: false
url: '%WP_URL%'
tablePrefix: 'wp_'
Once this requirement is met I can use the method in its most basic incarnation and let some reasonable defaults kick in:
public function test_basic_use(FunctionalTester $I) {
$file = codecept_data_dir('attachments/kitten.jpeg');
$id = $I->haveAttachmentInDatabase($file);
// ...
}
Provided just a file path the module will create a copy of the file in the uploads
folder, create a thumbnail
, medium
and large
version of it, and update the attachment metadata.
Here is the code that verifies the above behaviour in a test:
public function test_basic_use(FunctionalTester $I) {
$file = codecept_data_dir('attachments/kitten.jpeg');
$id = $I->haveAttachmentInDatabase($file);
$year = date('Y');
$month = date('m');
$criteria = [
'post_type' => 'attachment',
'post_title' => 'kitten',
'post_status' => 'inherit',
'post_name' => 'kitten',
'post_parent' => '0',
'guid' => $I->grabSiteUrl("/wp-content/uploads/{$year}/{$month}/kitten.jpeg"),
'post_mime_type' => 'image/jpeg',
];
foreach ($criteria as $key => $value) {
$I->seePostInDatabase(['ID' => $id, $key => $value]);
}
$I->seeUploadedFileFound('kitten.jpeg', 'now');
$I->seeUploadedFileFound('kitten-150x150.jpeg', 'now');
$I->seeUploadedFileFound('kitten-300x200.jpeg', 'now');
$I->seeUploadedFileFound('kitten-768x512.jpeg', 'now');
$I->seePostMetaInDatabase(['post_id' => $id, 'meta_key' => '_wp_attached_file', 'meta_value' => "{$year}/{$month}/kitten.jpeg"]);
$metadata = [
'width' => 1000,
'height' => 667,
'file' => "{$year}/{$month}/kitten.jpeg",
'sizes' => [
'thumbnail' => [
'file' => 'kitten-150x150.jpeg',
'width' => 150,
'height' => 150,
'mime-type' => 'image/jpeg',
],
'medium' => [
'file' => 'kitten-300x200.jpeg',
'width' => 300,
'height' => 200,
'mime-type' => 'image/jpeg',
],
'large' => [
'file' => 'kitten-768x512.jpeg',
'width' => 768,
'height' => 512,
'mime-type' => 'image/jpeg',
],
],
'image_meta' =>
[
'aperture' => '0',
'credit' => '',
'camera' => '',
'caption' => '',
'created_timestamp' => '0',
'copyright' => '',
'focal_length' => '0',
'iso' => '0',
'shutter_speed' => '0',
'title' => '',
'orientation' => '0',
'keywords' => [],
],
];
$I->seePostMetaInDatabase(['post_id' => $id, 'meta_key' => '_wp_attachment_metadata', 'meta_value' => serialize($metadata)]);
}
If the attachment is not an image then no additional versions of it will be generated and the method will only create the _wp_attached_file
meta entry:
public function test_non_image_attachments(FunctionalTester $I) {
$file = codecept_data_dir('attachments/pdf-doc.pdf');
$id = $I->haveAttachmentInDatabase($file);
$year = date('Y');
$month = date('m');
$criteria = [
'post_type' => 'attachment',
'post_title' => 'pdf-doc',
'post_status' => 'inherit',
'post_name' => 'pdf-doc',
'post_parent' => '0',
'guid' => $I->grabSiteUrl("/wp-content/uploads/{$year}/{$month}/pdf-doc.pdf"),
'post_mime_type' => 'application/pdf',
];
foreach ($criteria as $key => $value) {
$I->seePostInDatabase(['ID' => $id, $key => $value]);
}
$I->seeUploadedFileFound('pdf-doc.pdf', 'now');
$I->seePostMetaInDatabase(['post_id' => $id, 'meta_key' => '_wp_attached_file', 'meta_value' => "{$year}/${month}/pdf-doc.pdf"]);
}
Moar power
The method behaviour can be controlled with additional arguments to fine tune the attachment creation; tests can specify:
- a
date
argument to set the uploads year/month folder - an
overrides
argument to control the generated post fields - an
imageSizes
array to control what alternative sizes, if any, will be generated when adding the attachment
public function should_allow_definining_the_image_sizes_to_create(FunctionalTester $I) {
$file = codecept_data_dir('attachments/kitten.jpeg');
$date = '2016-01-01';
$imageSizes = [
'thumbnail' => [200, 200],
'normal' => 500,
'foo' => [450, 130],
];
$id = $I->haveAttachmentInDatabase($file, $date, ['post_title' => 'foo'], $imageSizes);
$I->seeUploadedFileFound('kitten.jpeg', $date);
$I->seeUploadedFileFound('kitten-200x200.jpeg', $date);
$I->seeUploadedFileFound('kitten-500x333.jpeg', $date);
$I->seeUploadedFileFound('kitten-450x130.jpeg', $date);
$I->seePostMetaInDatabase(['post_id' => $id, 'meta_key' => '_wp_attached_file', 'meta_value' => "2016/01/kitten.jpeg"]);
$metadata = [
'width' => 1000,
'height' => 667,
'file' => "2016/01/kitten.jpeg",
'sizes' => [
'thumbnail' => [
'file' => 'kitten-200x200.jpeg',
'width' => 200,
'height' => 200,
'mime-type' => 'image/jpeg',
],
'normal' => [
'file' => 'kitten-500x333.jpeg',
'width' => 500,
'height' => 333,
'mime-type' => 'image/jpeg',
],
'foo' => [
'file' => 'kitten-450x130.jpeg',
'width' => 450,
'height' => 130,
'mime-type' => 'image/jpeg',
],
],
'image_meta' =>
[
'aperture' => '0',
'credit' => '',
'camera' => '',
'caption' => '',
'created_timestamp' => '0',
'copyright' => '',
'focal_length' => '0',
'iso' => '0',
'shutter_speed' => '0',
'title' => '',
'orientation' => '0',
'keywords' => [],
],
];
$I->seePostMetaInDatabase(['post_id' => $id, 'meta_key' => '_wp_attachment_metadata', 'meta_value' => serialize($metadata)]);
}
The method comes with a bunch of utility others:
dontHaveAttachmentInDatabase
to remove an attachment database data, useWPFilesystem
module to remove the filesseeAttachmentInDatabase
to check that an attachment exists on a database leveldontSeeAttachmentInDatabase
to check that an attachment doesn’t exist on a database level
Attachment methods deal, in general, with the database side of things. Should I need to have database and file system management rolled into a method creating an ad-hoc module for the project is quite easy.