List and show uploaded pdf files dynamically

Possibilities to fetch attachments

WordPress saves files not only to a physical disk, but as well as attachment post type into your MySQL/MariaDB database. This means you have two possibilities to query for uploaded files and you have to chose what you need in which scenario/context.

Filesystem only

To loop through all files or a specific type in a specific directory, you can use the \FilesystemIterator (PHP 5.3+ needed), then simply push the results to an Array/[]. In the following example we are looping through all PDF files in the directory determined by wp_upload_dir() source.

$extension = 'pdf';
$uploads = wp_upload_dir();

$files = new \FilesystemIterator( 
    $uploads['path'],
    \FilesystemIterator::SKIP_DOTS 
    | \FilesystemIterator::FOLLOW_SYMLINKS
);

$html = [];
foreach ( $files as $pdf )
{
    /** @noinspection PhpIncludeInspection */
    if ( 
        ! $files->isDir() 
        && $extension === $files->getExtension()
    )
        $html[] = $files->getRealPath();
}

You can then easily craft your final MarkUp using for e.g. native PHPs explode() function:

printf(
     "<ul><li>%s</li></ul>",
     explode( "</li><li>", $html )
);

Keep in mind that wp_upload_dir() does the following and therefore differs per environment and needs either the defaults or carefully set constant in your wp-config.php file.

Depending on single- vs. multisite installs, the uploads/ directory, determined by UPLOADBLOGSDIR, BLOGUPLOADSDIR, UPLOADS constants.

Also, in case you will want to loop more than one directory, you will want to use a RecursiveDirectoryIterator instead of the \FilesystemIterator. You can then wrap this Iterator in a RecursiveIteratorIterator for looping. The Directory Iterator inherits methods from \FilesystemIterator, which again inherits from \DirectoryIterator, so you should carefully read the PHP docs to see the full potential and possibilities you have at hand.

This means you can, as seen in the first example, symlink your uploads folder and keep it outside the WordPress core folders that you downloaded. Reason for that would be for e.g.

  • automated deployments
  • automated builds,
  • builds done using Composer,
  • version controled WordPress core,
  • separate Filesystem backups (or rsync-ed) for your uploads

All this of course highly depends on your setup and you will need to catch a lot of different setups here in case you plan to publicly distribute code that does what you need it to do.

ProTip: Consider returning just JSON data in case this is the result of an AJAX filesystem query.

ProTip: You can use \DirectoryIterators methods getMTime() to get the last time a file was modified and getATime() to get the last time a file was accessed.

Database and Filesystem query

The second method is to use a \WP_Query and query for a MIME-Type:

$files = new \WP_Query( [
    'post_type'      => 'attachment',
    'post_mime_type' => 'application/pdf',
] );

Now you are able to loop through all $files and actually have \WP_Post elements at your hands. This means you can use ever method that you can use on normal posts like get_the_ID(), the_title(), etc.

Point is, that you are not only hitting the filesystem now, but as well the database, which is slow. Keep that in mind when going down this route. You should probably not do that for all attachments at once. Better paginate the query using an offset and the appropriate pre_get_posts filter (and remove the callback right after that).

Also keep in mind that the application/pdf MIME type is the RFC 3778 standard, which means you can rely on it – as long as there is no extension to the PDF type and a non-standard “X”-token in use, which would render it “unregistered”, which is not that uncommon. More information in the RFC 2045 paper.

x-token := <The two characters “X-” or “x-” followed, with no intervening white space, by any token>

In full length:

Beyond this syntax, the only syntactic constraint on the definition
of subtype names is the desire that their uses must not conflict.
That is, it would be undesirable to have two different communities
using “Content-Type: application/foobar” to mean two different
things. The process of defining new media subtypes, then, is not
intended to be a mechanism for imposing restrictions, but simply a
mechanism for publicizing their definition and usage. There are,
therefore, two acceptable mechanisms for defining new media subtypes:

and then in detail:

Private values (starting with “X-“) may be defined
bilaterally between two cooperating agents without
outside registration or standardization. Such values
cannot be registered or standardized.

Conclusion

Personally I would only use the query for file types in case I need the title and meta data added by users when they upload files. From experience the UI still is confusing and people rarely complete those data sets. Also – again – only use it if you really need that data.

error code: 523