Automate WordPress Theme Compression and FTP Upload with Python

0

Managing WordPress themes often involves repetitive tasks that can be automated to save significant time. In this article, we explain how to use Python to automatically compress WordPress theme files and upload them to a server via FTP. We also cover how to handle encoding issues and file deletion problems that may arise during the process.

1. Preparation

First, install the necessary Python packages. We will use `python-dotenv` and `ftplib` to load environment variables and connect to the FTP server. Additionally, we need to install `execjs` and `uglify-js` for JavaScript file compression.

pip install python-dotenv execjs
npm install -g uglify-js

2. Create Environment Variable File

Create a `.env.development` file in the project root directory to set FTP server information and paths.

# .env.development File Example

# Theme Path Settings
SRC_THEME_DIR=wp-content/themes/sample_theme
DEST_THEME_DIR=.py_scripts/dest/sample_theme

# FTP Server Information
FTP_HOST=ftp.example.com
FTP_USER=ftp_user
FTP_PASSWORD=ftp_password
REMOTE_THEME_DIR=/www/wp-content/themes/sample_theme

3. Compress JavaScript Files

Write a Python class to compress JavaScript files using `execjs`. Use `utf-8-sig` encoding to solve encoding issues.

import execjs

class MinifyJS:
    def __init__(self):
        self.context = execjs.get().compile("""
            var uglifyjs = require('uglify-js');
            function minify(js_code) {
                return uglifyjs.minify(js_code).code;
            }
        """)

    def minify_file(self, input_file_path, output_file_path):
        try:
            with open(input_file_path, 'r', encoding='utf-8-sig') as js_file:
                js_content = js_file.read()

            minified_js = self.context.call("minify", js_content)
            with open(output_file_path, 'w', encoding='utf-8') as min_js_file:
                min_js_file.write(minified_js)
            print(f"Minified file created at: {output_file_path}")
        except Exception as e:
            print(f"Error minifying {input_file_path}: {e}")

4. Copy and Compress Theme

Copy the theme directory and compress JavaScript files. Exclude the SCSS folder.

import os
import shutil
from minify_js import MinifyJS

def copy_and_minify_theme(src_dir, dest_dir):
    minifier = MinifyJS()

    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir)

    for root, dirs, files in os.walk(src_dir):
        dirs[:] = [d for d in dirs if d != 'scss']
        
        rel_path = os.path.relpath(root, src_dir)
        dest_path = os.path.join(dest_dir, rel_path)

        if not os.path.exists(dest_path):
            os.makedirs(dest_path)

        for file in files:
            src_file = os.path.join(root, file)
            dest_file = os.path.join(dest_path, file)

            if file.endswith('.min.js'):
                shutil.copy2(src_file, dest_file)
            elif file.endswith('.js'):
                min_file = os.path.join(dest_path, os.path.splitext(file)[0] + '.min.js')
                minifier.minify_file(src_file, min_file)
            else:
                shutil.copy2(src_file, dest_file)

    print(f"Theme copied and JS files minified from {src_dir} to {dest_dir}")

5. FTP Upload

Write functions to delete the existing FTP directory and upload the new one.

import os
import ftplib

def ftp_delete_directory(ftp, remote_dir):
    try:
        file_list = ftp.nlst(remote_dir)
    except ftplib.error_perm as e:
        if str(e).startswith('550'):
            return
        else:
            raise

    for file in file_list:
        file_path = f"{remote_dir}/{os.path.basename(file)}"
        try:
            ftp.delete(file_path)
        except ftplib.error_perm:
            ftp_delete_directory(ftp, file_path)
    
    try:
        ftp.rmd(remote_dir)
    except ftplib.error_perm as e:
        print(f"Failed to remove directory {remote_dir}: {e}")

def upload_directory(ftp, local_dir, remote_dir):
    if not os.path.isdir(local_dir):
        return
    
    try:
        ftp.mkd(remote_dir)
    except ftplib.error_perm as e:
        if not str(e).startswith('550'):
            raise

    for item in os.listdir(local_dir):
        local_path = os.path.join(local_dir, item)
        remote_path = f"{remote_dir}/{item}"

        if os.path.isdir(local_path):
            upload_directory(ftp, local_path, remote_path)
        else:
            with open(local_path, 'rb') as file:
                ftp.storbinary(f'STOR {remote_path}', file)
                print(f'Uploaded {local_path} to {remote_path}')

def upload_theme_to_ftp(ftp_host, ftp_user, ftp_password, local_theme_dir, remote_theme_dir):
    with ftplib.FTP(ftp_host) as ftp:
        ftp.login(ftp_user, ftp_password)
        print(f"Connected to FTP server: {ftp_host}")

        ftp_delete_directory(ftp, remote_theme_dir)
        upload_directory(ftp, local_theme_dir, remote_theme_dir)
        print(f"Theme uploaded from {local_theme_dir} to {remote_theme_dir}")

6. Integration Script

Integrate all the functions to automate the theme copying, compression, and upload process.

import os
import shutil
from dotenv import load_dotenv
from theme_builder import copy_and_minify_theme
from theme_uploader import upload_theme_to_ftp

load_dotenv(dotenv_path='.env.development')

src_theme_dir = os.getenv('SRC_THEME_DIR')
dest_theme_dir = os.getenv('DEST_THEME_DIR')
ftp_host = os.getenv('FTP_HOST')
ftp_user = os.getenv('FTP_USER')
ftp_password = os.getenv('FTP_PASSWORD')
remote_theme_dir = os.getenv('REMOTE_THEME_DIR')

if __name__ == "__main__":
    if os.path.exists(dest_theme_dir):
        shutil.rmtree(dest_theme_dir)

    copy_and_minify_theme(src_theme_dir, dest_theme_dir)
    upload_theme_to_ftp(ftp_host, ftp_user, ftp_password, dest_theme_dir, remote_theme_dir)

Conclusion

By using Python, you can automate the compression of WordPress theme files and upload them to an FTP server. This process significantly improves the efficiency of theme management. Resolving encoding issues and recursively deleting FTP directories through automation can save a lot of time and reduce errors in web development and operations.

Leave a Reply