It is the story of how I made the WordPress plugin to automate the unzipping archives and creating posts. This one time, I was working on the website for the historical society. They had a lot of wills in PDF format, which had to be uploaded. In addition, I had to create posts for each of the will files. The post should have contained a year of the will, reference code and link to the PDF file. Of course, I could upload them manually, create new posts by clicking the button and fill inputs by typing the keyboard. But it’s too easy, and I wouldn’t get the coding experience. I decided to develop the plugin which helps me in automation this process. That’s when I remembered about the excellent Udemy course by Bruce Chamoff. It is entitled WordPress Plugin Development – Build 14 Plugins. Bruce told students about a similar plugin that coded him in the functional programming paradigm. But my idea was to create the plugin in OOP (Object-oriented programming).

.
├── Classes
│   ├── FileHelper.php
│   ├── File.php
│   ├── PostPdf.php
│   ├── Post.php
│   └── Render.php
├── media-file-unzipper.php
└── README.md

Project structure

Example with dummy PDF files

I made the plugin directory in the wp-content folder, where I created the PHP file named media-file-unzipper there. The file header contained not many fields because I planned to use the plugin only for private use.

<?php

/**
 * Plugin Name: Media File Unzipper And Post Creator
 * Description: WordPress plugin allows unzipping media files and creating posts.
 * Version: 1.0.0
 * Author: Anton Podlesnyy
 * Author URI: https://podlesnyy.ru
 */

The first thing I thought about was how to prevent public users from having direct access to my file. I know about two snippets that can help me to solve this problem:

if ( ! defined( 'ABSPATH' ) ) die;

and

if ( ! defined( 'WPINC' ) ) die;

Both techniques add an extra security layer by preventing direct access from my plugin files, and both use for the same purpose. I used the ABSPATH constant because it is defined before defining the WPINC constant in the WordPress core.

Instead of die() function I could use exit() or wp_die() function, by the way. The die() function is equivalent to exit(). The wp_die() function kills WordPress execution and displays an HTML page with an error message.

To avoid naming collisions, I checked the name of the class for the already taken class name.

if (!class_exists('UnzipFile')) {...}

addMenuPage()

The next task was to create the admin menu containing plugin control elements. I used the action type hook admin_menu, which fires before the administration menu loads. Using this hook, I called the callback function addMenuPage().

It should be noted we use the array callable syntax when we build plugins using classes in WordPress.

function __construct() {
  add_action( 'admin_menu', array( $this, 'addMenuPage' ) );
}

Inside the callback function, I put WordPress built-in function add_menu_page() , which includes seven parameters:

  1. $page_title
  2. $menu_title
  3. $capability
  4. $menu_slug
  5. $function
  6. $icon_url
  7. $position
class UnzipFile {

  private $slug = 'add_pdf_zip';

  function __construct() {
    add_action('admin_menu', array( $this, 'addMenuPage') );
  }

  public function addMenuPage() {
    add_menu_page(
     'Upload PDF',                   //page_title
     'Upload PDF',                   //menu_title
     'manage_options',               //capability
     $this->slug ,                   //menu_slug
     array( $this, 'pluginRender' ), //callback_function
     'dashicons-media-archive',      //icon
     10);                            //position
  }

...

}

I entered the slug in a private class parameter. The idea was to find the slug easily if plugin duplication was necessary. It would be required, for instance, to automate the unzipping archives and create other custom post types automatically.

pluginRender()

After the item and page creation, I started working on the upload form rendering implementation. For that purpose, I created the class named Render. The PHP class contained a form and message rendering function.

class Render {

  static function form($slug = 'history_upload_wills') {
    echo '
      <div class="wrap">
        <h1>Upload ZIP Archive with PDF files inside</h1>
        <form action="./admin.php?page=' . $slug . '" method="post" enctype="multipart/form-data" class="server-form">

          <p>
            <input type="file" name="fileToUpload" id="fileToUpload">
          </p>

          <p class="submit">
            <input type="submit" class="button button-primary" value="Upload and Create Posts" name="submit">
          </p>

        </form>
      </div>
    ';
  }

  static function message($messages) {
    foreach($messages as $message) {
      if ($message->type == 'success') {
        echo '<p style="color: green; padding-left: 1rem;">'. $message->text .'</p>';
      }
      elseif ($message->type == 'error') {
        echo '<p style="color: red; padding-left: 1rem;">'. $message->text .'</p>';
      }
      elseif ($message->type == 'info') {
        echo '<p style="color: blue; padding-left: 1rem;">'. $message->text .'</p>';
      }
    }
  }

}

I put Render class in the pluginRender function and called static function form(). Also, If the superglobal variable FILE was declared and contained the ['fileToUpload'] property, I called the function unzipAndPost().

public function pluginRender() {
  Render::form( $this->slug );

  if( isset( $_FILES['fileToUpload'] ) ) {
    $this->unzipAndPost();
  }
}

unzipAndPost()

Inside of this function, I put the class File containing plugin logic. The function unzipAndPost starts the process of unzipping and post creation.

private function unzipAndPost() {
 File::unzipAndPost();
}

The logic of this process consists of the stages:

  1. Get upload directory name with year and month
  2. Get file pathname
  3. Get file name for the notification
class File {

...

  private static function init() {
    //Get upload directory with year and month
    self::$dir = "../wp-content/uploads" . wp_upload_dir()['subdir'];

    //Get file path
    self::$file = self::$dir . '/' . basename($_FILES["fileToUpload"]["name"]);

    //Get filename
    self::$fileName = basename( $_FILES["fileToUpload"]["name"]);

  }

...

}

4. Upload zip archive

  private static function uploadZip() {
    return move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], self::$file);
  }

5. Check if the file is uploaded. If it is uploaded, start unzipping the file, else show an error message.

static function unzipAndPost() {
  self::init();

  if(self::uploadZip()){
    self::extractZip();
  } else {

    $message[] = (object)[
      'type' => 'error',
      'text' => 'No file chosen...'
    ];

    return Render::message($message);
  }

}

6. Extract the PDF files. During files extraction, we are creating a new instance of ZipArchive. It is the built-in PHP class that contains properties and methods of manipulation on zip archives. The program will display an error message if failed to unzip the archive.

private static function extractZip() {
    //Create instance of ZIP
    $zip = new ZipArchive;

    //Attempt to open the zip file.
    if($zip->open(self::$file) !== true) {

      $message[] = (object)[
        'type' => 'error',
        'text' => 'The zip file was NOT successfully unzipped'
      ];

      $zip->close();
      Render::message($message);
      exit;
    }

    self::$numFiles = $zip->numFiles;

    if(self::$numFiles > self::$maxFiles) {
      $message[] = (object)[
        'type' => 'error',
        'text' => 'Files quantity must be no greater than ' . $maxFiles . '.'
      ];

      $zip->close();
      Render::message($message);
      exit;
    }

    //Extract zip if it can be open
    $zip->extractTo(self::$dir);

    self::$zip = $zip;

    $message = [];

    array_push($message, (object)[
      'type' => 'success',
      'text' => 'The zip file' . self::$fileName . '  was successfully unzipped to ' . wp_upload_dir()['url']
    ]);

    array_push($message, (object)[
      'type' => 'info',
      'text' => 'There are ' . self::$numFiles. ' files in this zip file.'
    ]);

    Render::message($message);

    self::addToLibAndPost();
  }

7. Add each extracted PDF file to the WordPress media library. Before adding the file, we check its type, create attachment information. Then we insert the attachment, generate attachment metadata and update it.

8. Create blog post “Will” for each PDF file. I made the custom post type “Will” in advance. It included such custom fields as will year, will reference, held by and PDF file URL.

private static function addToLibAndPost() {
  for($i=0; $i < self::$numFiles; $i++) {

    $message = [];
    $message[] = (object)[
      'type' => 'info',
      'text' => '========================'
    ];
    Render::message($message);

    $title = preg_replace('/\.[^.]+$/', '', self::$zip->getNameIndex($i));

    if(!FileHelper::isTitleUnique($title)) {
      $message = [];
      $message[] = (object)[
        'type' => 'error',
        'text' => 'File title: ' . $title . ' not unique'
      ];
      Render::message($message);
    }

    //Get the URL of the media file.
    $fileUrl = wp_upload_dir()['url'] . '/' . self::$zip->getNameIndex($i);

    $message = [];
    $message[] = (object)[
      'type' => 'info',
      'text' => 'File url: ' . $fileUrl
    ];
    Render::message($message);

    //Get the file type
    $fileType 	= wp_check_filetype( basename( $fileUrl ), null );

    $message = [];
    $message[] = (object)[
      'type' => 'info',
      'text' => 'File type: ' . $fileType['type']
    ];
    Render::message($message);

    //Check the type
    if(in_array($fileType['type'], self::$allowedFileTypes)) {

      $message = [];
      $message[] = (object)[
        'type' => 'success',
        'text' => '<a href="' . $fileUrl . '" target="_blank"> '. $fileUrl . '</a> File type: ' . $fileType['type']
      ];
      Render::message($message);

      //Attachment information
      $attachment = array(
        'guid'           => $fileUrl,
        'post_mime_type' => $fileType['type'],
        'post_title'     => $title,
        'post_content'   => '',
        'post_status'    => 'inherit'
      );

      //Absolute path to file
      $pathToFile = self::$dir . '/' . self::$zip->getNameIndex($i);

      //Insert the attachment.
      $attachId = wp_insert_attachment( $attachment, self::$dir . '/' . self::$zip->getNameIndex($i) );

      //Generate attachment metadata
      $attachData = wp_generate_attachment_metadata($attachId, self::$dir . '/' . self::$zip->getNameIndex($i));

      //Update metadata for an attachment.
      wp_update_attachment_metadata( $attachId, $attachData );

      //Create post
      $post = new PostPdf($title, '', 'publish', 1, 'will', 'custom_pdf', $fileUrl, $pathToFile);
      $postId = $post->createPost();

      if($postId) {
        $message = [];
        $message[] = (object)[
          'type' => 'success',
          'text' => 'Post was successfully created. ID ' . $postId
        ];
        Render::message($message);
      } else {
        $message = [];
        $message[] = (object)[
          'type' => 'error',
          'text' => 'Post was not successfully created. Something went wrong...'
        ];
        Render::message($message);
      }

    } else {

      $message = [];
      $message[] = (object)[
        'type' => 'error',
        'text' => self::$zip->getNameIndex($i) . ' could not be uploaded. Its file type of  ' . $fileType['type'] . ' is not allowed'
      ];
      Render::message($message);
    }
  }
}

9. Separate the name of the file into separate parts and put them in post fields.

Imagic

I used Imagic to create post thumbnails. Imagic is a native PHP extension to create and modify images using the ImageMagick API. The first page of each PDF file was converted into a JPG image using this extension.

private function createThumbnail() {

  $im = new imagick($this->pathToFile);
  $im->clear();
  $im->destroy();

  //Attachment information
  $attachment = array(
    'guid'           => $this->transformIntoUrl(),
    'post_mime_type' => 'image/jpeg',
    'post_title'     => $this->transformTitle(),
    'post_content'   => '',
    'post_status'    => 'inherit'
  );

  //Insert the attachment.
  $this->thumbnailId = wp_insert_attachment( $attachment, "../wp-content/uploads" . wp_upload_dir()['subdir'] . '/' . $this->imgUrl);


  //Generate attachment metadata
  $imageData = wp_generate_attachment_metadata($this->thumbnailId,  "../wp-content/uploads" . wp_upload_dir()['subdir'] . '/' . $this->imgUrl);

  //Update metadata for an attachment.
  wp_update_attachment_metadata( $this->thumbnailId, $imageData );

}

There is a source of code in my GitHub repository.

https://github.com/antlogist/media-file-unzipper-and-post-creator