An Introduction to the WordPress Filesystem API

By Narayan Prusty

Writing to local files is one of the functionalities many plugins and themes need for various purposes. Security is the most important issue plugins and themes have to take care of while writing to local file systems. WordPress runs across various hosting services and configurations, therefore it becomes difficult for developers to create plugins and themes which access local file systems to work across all different kinds of environments without compromising security.

In this tutorial, we’ll learn how to use the WordPress Filesystem API to access local file systems which takes care of proper file permissions. In the process, we’ll create a plugin which displays a form with a textarea in an admin page that saves the content of the textarea to a file.

Why Use the WordPress Filesystem API?

You might be wondering why we don’t just use PHP’s file system functions to read and write local files instead of learning and using a whole new set of APIs?

The issue of using PHP file system APIs is that it doesn’t take care of file permissions automatically. Suppose you’re using a shared hosting service to host your WordPress site and your hosting web server is running as the “admin” operating system account. Whenever you create files using PHP, they’re owned as the “admin” user. Therefore, any other website hosted in the same shared hosting can also access your website files as they’re also running as the “admin” user, posing a security issue to your site. To protect us from this issue, you need to change the file owner and permissions manually using PHP.

But when you login using SSH or FTP/SFTP to create files then they are owned by the operating system user account that you are logged in as. If the FTP server is running as the “admin” user and you’re logged in as the “narayanprusty” user, then newly created files will have an owner as “narayanprusty”, not “admin”.

WordPress introduced the Filesystem API which can automatically takes care of file permissions. The Filesystem API was released in WordPress 2.6. WordPress actually released it to support its plugin, theme and core update system, but later on plugins and themes starting using it for their own purposes.

How Does the Filesystem API work?

The Filesystem API can write to file systems using system calls (i.e. direct, ftp, ftp socket or ssh2). It chooses one of the methods based on whichever method creates files with proper file permissions and which PHP extension is available. The Filesystem API first checks the direct method, then ftp and finally ssh2.

While using FTP or SSH, you’ll need to get the credentials from your user. The Filesystem API provides function makes it easier to display a form to take credentials and store them.

Creating Our Plugin Files and Directory

Now let’s create our plugin which displays a textarea in a page, where submitting the form saves the contents of the textarea to a local file.

Here’s the directory structure of our plugin:

--filesystem
	--filesystem.php
	--filesystem-demo
		--demo.txt

Create these files and directories in the wp-content/plugins directory of your WordPress installation.

To make the plugin installable, put this code in the filesystem.php file:

<?php
  
/*
Plugin Name: Filesystem API
Plugin URI: http://www.sitepoint.com
Description: A sample plugin to demonstrate Filesystem API
Version: 1.0
Author: Narayan Prusty
*/

Now visit your admin panel and install the plugin.

Creating an Admin Page

Next we need a page in our admin where our example will reside. Here’s the code to create this page and display the textarea. Just place this code in the filesystem.php file:

function menu_item()
{
  add_submenu_page("options-general.php", "Demo", "Demo", "manage_options", "demo", "demo_page"); 
}
 
add_action("admin_menu", "menu_item");

function demo_page()
{
  ?>
      <div class="wrap">
         <h1>Demo</h1>
         <form method="post">
          <?php
            $output = "";

            if(isset($_POST["file-data"]))
            {
              $output = write_file_demo($_POST["file-data"]);
            }
            else
            {
              $output = read_file_demo();
            }

            if(!is_wp_error($output))
            {
            	?>
            		<textarea name="file-data"><?php echo $output; ?></textarea>
		          	<?php wp_nonce_field("filesystem-nonce"); ?>
		          	<br>
		          	<input type="submit">
            	<?php
            }
            else
            {
              echo $output->get_error_message();
            }
          ?>
         </form>
      </div>
   <?php
}

Here’s how the code works:

  • First we added a page to the “Settings” menu. demo_page is the callback for displaying the page content.
  • Inside the page we’re displaying a HTML form with a textarea and nonce field. There’s also a submit button to submit the form. The textarea name is file-data. The nonce is added to prevent a CSRF attack.
  • When the page is open, then we’re retrieving the stored file data using the read_file_demo function. When the form is submitted, we’re storing the content of the textarea into a file using the write_file_demo function.
  • If read_file_demo or write_file_demo returns an instance of the WP_Error object, then we’ll display an error message instead.

Note, the above code will break your WordPress site, as we haven’t yet created the read_file_demo and write_file_demo functions. Let’s create them now!

Writing to a File

Here’s the implementation of our write_file_demo function:

function connect_fs($url, $method, $context, $fields = null)
{
  global $wp_filesystem;
  if(false === ($credentials = request_filesystem_credentials($url, $method, false, $context, $fields))) 
  {
    return false;
  }

  //check if credentials are correct or not.
  if(!WP_Filesystem($credentials)) 
  {
    request_filesystem_credentials($url, $method, true, $context);
    return false;
  }

  return true;
}

function write_file_demo($text)
{
  global $wp_filesystem;

  $url = wp_nonce_url("options-general.php?page=demo", "filesystem-nonce");
  $form_fields = array("file-data");

  if(connect_fs($url, "", WP_PLUGIN_DIR . "/filesystem/filesystem-demo", $form_fields))
  {
    $dir = $wp_filesystem->find_folder(WP_PLUGIN_DIR . "/filesystem/filesystem-demo");
    $file = trailingslashit($dir) . "demo.txt";
    $wp_filesystem->put_contents($file, $text, FS_CHMOD_FILE);

    return $text;
  }
  else
  {
    return new WP_Error("filesystem_error", "Cannot initialize filesystem");
  }
}

Here’s how the code works:

  • First we referenced the global $wp_filesystem object inside the function. This object is an instance of the WP_Filesystem class. It’s responsible for exposing various methods for reading, creating, writing and deleting files.
  • Next, we’re creating a nonce URL of our form page and an array with the field names of our form.
  • Finally, we connect to the filesystem using the connect_fs function.
  • connect_fs uses the request_filesystem_credentials function provided by WordPress to find an appropriate method to connect to the file system (direct, FTP or SSH). In case of FTP or SSH it will echo a form to ask for the credentials from the user. In the case of direct method, it simply returns true.
  • When the textarea form is submitted and the request_filesystem_credentials chooses FTP or SSH method then we display the credentials form and hides the file-data field in the credentials form.
  • request_filesystem_credentials function’s first parameter takes an URL where to redirect once it has got the correct credentials. The redirect is of type POST request. The request_filesystem_credentials last parameter is an array of the field names to post to the redirect URL.
  • Once the credentials form is submitted, then it redirects back to the original form submit URL with proper field names and the values that were entered by the user.
  • Again, the whole process starts and we execute the write_file_demo. This time request_filesystem_credentials has the credentials therefore it will simply return true.
  • Then we use $wp_filesystem->find_folder to reference to the folder. Then we build the complete path of the demo.txt file.
  • We used $wp_filesystem->put_contents to write data to the file.

Note: If you try to use the $wp_filesystem object’s methods without requesting and verifying credentials then they will not work.

Reading a File

Here is the implementation of the read_file_demo function.

function read_file_demo()
{
  global $wp_filesystem;

  $url = wp_nonce_url("options-general.php?page=demo", "filesystem-nonce");

  if(connect_fs($url, "", WP_PLUGIN_DIR . "/filesystem/filesystem-demo"))
  {
    $dir = $wp_filesystem->find_folder(WP_PLUGIN_DIR . "/filesystem/filesystem-demo");
    $file = trailingslashit($dir) . "demo.txt";

    if($wp_filesystem->exists($file))
    {
      $text = $wp_filesystem->get_contents($file);
      if(!$text)
      {
        return "";
      }
      else
      {
        return $text;
      }
    } 
    else
    {
      return new WP_Error("filesystem_error", "File doesn't exist");      
    } 
  }
  else
  {
    return new WP_Error("filesystem_error", "Cannot initialize filesystem");
  }
}

Here is how the code works:

  • While reading the demo.txt file, we first connect to the file system using the request_filesystem_credentials function.
  • This time we aren’t passing any form fields in the last parameter because the form is not submitted. We’re just passing the redirect URL so that it’s redirected once the credentials are retrieved.
  • We’re then checking if the file exists using the $wp_filesystem->exists function. It file doesn’t exist we display an error. Otherwise we’re reading the file using the $wp_filesystem->get_contents function and returning the content.

Assuming WordPress has chosen FTP as the suitable method for creating files, here are the screenshots of the whole process:

First when we open the demo page we will see this form:

Demo - Connection Information

Here, we need to enter FTP or FTPS credentials and submit it. Once we submit it, we’ll see this form:

Demo - Hello World

An empty textarea was displayed. Enter the text “Hello World!!!” submit the form. You will again see the credentials form.

Demo - Connection Information

You have to fill it again because WordPress doesn’t store the FTP password by default (you can do this in wp-config.php, more on that later). So every time your plugin needs to work with a file system, it must ask credentials. Now submitting it will redirect back to the redirect URL with field names and values submitted earlier. Here is how the textarea appears:

Demo - Hello World

Here, we read the contents of the file and display it.

Other Methods of the $wp_filesystem Object

The $wp_filesystem object provides many other methods to perform various other operations on files and directories. We just saw writing and reading n file. You can find the whole list of what you can do at the WP_Filesystem_Base () documentation page.

Let’s check out some of the important ones:

  • $wp_filesystem->delete: delete is used to delete a file or directory. You need to pass a string representing the path.
  • $wp_filesystem->mkdir: mkdir is used to create a directory. It takes a string representing the parent directory.
  • $wp_filesystem->move: move is used to move file It takes two parameters i.e., the first one is the path of the file and second one is the directory where to move it to.
  • $wp_filesystem->size: size returns the size of a file in bytes. You need to pass path of a file.
  • $wp_filesystem->chmod: chmod is used to change permissions of a file. It takes three arguments i.e., path of the file, the permission octal number and boolean representing recursion.

You can find which method of connection is used by WordPress to access filesystem using $wp_filesystem->method public property.

Storing Credentials Permanently

We saw that WordPress doesn’t store the FTP or SSH credentials permanently. It’s not user friendly to ask for details again and again. There is a way to store the credentials permanently using the wp-config.php file.

Use these options to store the FTP and SSH credentials:

  • FTP_HOST: The host name of the server.
  • FTP_USER: The username to use while connecting.
  • FTP_PASS: The password to use while connecting
  • FTP_PUBKEY: The path of the public key which will be used while using SSH2 connection.
  • FTP_PRIKEY: The path of the private key which will be used while using SSH2 connection.

Conclusion

In this article we saw the process of designing an admin page that accesses our file system using the WordPress Filesystem API. In case you’re trying to access a file system in a background process (such as using a cron job), then its not possible to display the credentials form if required, in that case you’ll need to make sure you notify your user to place the credential constants in the wp-config.php file. You can go ahead and experiment further with this API and share your experiences with us below.

Recommended
Sponsors
Get the latest in WordPress, once a week, for free.