latest version

Upload Process

When your theme is complete and demo page is working, there’s one more thing left to do before the upload. That is to set up a zip package containing theme contents with proper text domain, compiled styles, generated demo content, promo materials, documentation, languages etc.

Building a package

Packages are automatically built by Phing tasks. All you need to do is configure them in build.xml file. Then, ask a project manager to build a package.

Check project structure

Please follow the boilerplate structure: Theme structure

In particular, make sure you do have the following directories:

wp-content/
├-themes/
  ├-projectname/
    ├-build/                          # There should be build.xml and build.properties files already there
    ├-docs/                           # Put pdf documentation here (or create a .gitkeep file if needed)
    ├-theme/
    │  │--vendor/                     # Commerial plugins zip files (if required by the theme)
    │  │--languages/                  # Leave empty for autogenerated .pot file (create a .gitkeep file if needed)
    │  │--demo-content/               # Put all demo content manifest and screenshot. The content itself should be autogenerated.
    │  │  │--example-multipager/      # Local demo content directory, demo files will be generated here
    │  │  │  │--manifest.php          # Demo content name, image, preview link
    │  │  │  │--screenshot.png        # Screenshot
    │  │  │--example-remote-content/  # Remote demo content directory, downloaded outside of the package
    │  │  │  │--remote.php            # Demo content name, image, preview link (same as manifest.php)
    │  │  │  │--screenshot.png        # Screenshot
    │  │  │--example-onepager/
    │  │  │--...

Basic structure can be downloaded here: Theme boilerplate

Set up paths in build.properties

In build/build.properties file set up paths according to your project name and structure. Below, you can find an example from boilerplate theme with comments:

<!-- Base variables -->

# base directory, path relative to 'build' directory
base.dir = ./..

# project name = project directory = final text domain (no special chars are allowed)
base.projectName = boilerplatetheme

# project plugin slug = plugin directory
base.pluginName = theme-plugin

# project demo plugin slug = demo plugin directory
base.demoPluginName = theme-demo-plugin

# path to theme files
base.html = ${base.dir}/theme

# path to plugin files
base.pluginSrc = ${base.dir}/../../plugins/${base.pluginName}

# destination path to plugin zip
base.pluginDst = ${base.html}/vendor/${base.pluginName}

# path to demo plugin files
base.demoPluginSrc = ${base.dir}/../../plugins/${base.demoPluginName}

# theme demo website (for wp-cli tasks)
base.url = http://boilerplate.themeplayers.net

# theme flavors slugs (for wp-cli tasks)
base.flavors = flavor1, flavor2, flavor3

<!-- SCSS Compiler related -->

# path to compiled main css
base.css.path = assets/css/style.css

# path to main scss
base.scss.path = assets/sass/style.scss

# final css format
build.scss.formatter = compressed

<!-- The path to the publish directory. -->

# publisher's name (no need to change)
build.publishName = themeforest

# directory to hold all packages (no need to change)
build.dir = ${base.dir}/${build.publishName}

# temp directory for packing (no need to change)
build.tmp = ${build.dir}/tmp

# theme package name (no need to change)
build.zipNameFile = ${base.projectName}.zip

# theme package destination path (no need to change)
build.zipName = ${build.tmp}/${build.zipNameFile}

# theme package with promo materials destination path (no need to change)
build.zipNameFull = ${build.dir}/${base.projectName}_all.zip

# packing directory (no need to change)
build.html = ${build.tmp}/${base.projectName}

# theme flavors which will be available as remote demo content (for wp-cli tasks)
build.remotes = flavor1, flavor2

# remote demo content destination directory (for wp-cli tasks)
build.remotesDestination = ${build.dir}/demo-content

<!-- Documentation -->

# path to docs html directory on demo (no need to change)
base.doc = ${base.dir}/../../../documentation

# path to docs pdf inside package (no need to change)
build.doc = ${build.tmp}/Documentation

# slug of the docs file (without an extension)
build.docSlug = boilerplate

# release of the docs file (eg. latest, master)
build.docRelease = latest

# documentation pdf download link
build.docUrlPdf = https://media.readthedocs.org/pdf/${build.docSlug}/${build.docRelease}/${build.docSlug}.pdf

# documentation html download link
build.docUrlHtml = https://media.readthedocs.org/htmlzip/${build.docSlug}/${build.docRelease}/${build.docSlug}.zip

Set up tasks in build.xml

Once properties properly set, build.xml tasks need only little changes. Here are some sections higlighted, and below you can find a whole file example

Tasks sets

These are main two task sets. One for creating a package, and another one for updating demo website. Here, you can add or remove tasks defined below in build.xml, however the default set up should be enough

<target name="envato"
        depends="base.cleanup, base.update, build.delete, build.plugin, build.demoPlugin, build.createDir, build.vctemplates, build.generateDemoContent, build.deleteThumbnails, build.moveRemotes, copy.all, build.removeDemo, build.changeTextDomain, build.pot, build.doc, build.pack, build.cleanup">
    <echo message="Envato product version completed."/>
</target>

<target name="demo"
        depends="base.update, build.plugin, build.demoPlugin, build.vctemplates">
    <echo message="Envato demo completed."/>
</target>

build.scss

build.scss task calls ct_fw_ext_sass_compiler_cli_compile_save function located in demo plugin. Make sure you have configured SCSS compiler in your project. Or if you don’t use it, remove the task from task sets above.

<target name="build.scss">
    <echo>Compiling SCSS to CSS...</echo>
    <exec
            command="wp eval 'ct_fw_ext_sass_compiler_cli_compile_save();'"
    />
</target>

build.generateDemoContent

build.generateDemoContent task calls ct_fw_ext_backups_do_cli_backup function located in demo plugin. The –url parameter needs to point to a particular site (if multisite install) to generate content from, so you will need to modify the base.flavors variable in build.properties. Read more about WP-CLI: WP-CLI global parameters

Also make sure Demo Plugin settings (demo content directory in particular) are properly set on demo website.

<target name="build.generateDemoContent">
    <echo>Generating demo content...</echo>
    <exec
            command="wp eval --user=1 --url=${base.url} 'fw_ext_ct_demo_content_do_cli_backup();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
    <foreach list="${base.flavors}" target="build.flavorDemoContent" param="flavorSlug" />
</target>

<target name="build.flavorDemoContent">
    <echo msg="Generating demo content for flavor: ${flavorSlug}" />
    <exec
            command="wp eval --user=1 --url=${base.url}/${flavorSlug} 'fw_ext_ct_demo_content_do_cli_backup();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
</target>

An example build.xml file

<?xml version="1.0"?>

<project name="boilerplate" default="envato">

<import file="lib/build.common.xml"/>

<property file="build.properties"/>
<property name="build.jsPath" value="${build.html}/${base.js.path}"/>
<property name="build.cssPath" value="${build.html}/${base.css.path}"/>

<taskdef name="lineend" classname="lib.converter.ctLineEndingConverter"/>
<taskdef name="jsobfuscate" classname="lib.obfuscator.JsObfuscatorTask"/>
<taskdef name="frameadd" classname="lib.frame.themewoodmen.ctThemeforestThemewoodmenFrame"/>
<taskdef name="switcher" classname="lib.switcher.ctColorSwitcherTask"/>

<target name="envato"
        depends="base.cleanup, base.update, build.delete, build.plugin, build.demoPlugin, build.createDir, build.vctemplates, build.generateDemoContent, build.deleteThumbnails, build.moveRemotes, copy.all, build.removeDemo, build.changeTextDomain, build.pot, build.doc, build.pack, build.cleanup">
    <echo message="Envato product version completed."/>
</target>

<target name="demo"
        depends="base.update, build.plugin, build.demoPlugin, build.vctemplates">
    <echo message="Envato demo completed."/>
</target>

<target name="build.plugin">
    <echo>git reset --hard</echo>
    <exec command="git reset --hard" dir="${base.pluginSrc}"/>
    <echo>git pull</echo>
    <exec command="git pull" dir="${base.pluginSrc}"/>
    <echo>composer update</echo>
    <exec command="composer update" dir="${base.pluginSrc}"/>
    <delete file="${base.html}/vendor/${base.pluginName}.zip"/>
    <delete file="${base.pluginDst}/${base.pluginName}.zip"/>
    <delete dir="${base.pluginDst}"/>
    <copy todir="${base.pluginDst}/${base.pluginName}">
        <fileset dir="${base.pluginSrc}">
            <exclude name="**/*.svn/"/>
            <exclude name="**/*.git/"/>
            <exclude name="**/*.git*"/>
            <exclude name="**/*.idea/"/>
            <exclude name="**/*.htaccess"/>
            <exclude name="**/composer.*"/>
        </fileset>
    </copy>
    <zip destfile="${base.pluginDst}/${base.pluginName}.zip" description="dump zip to tmp">
        <fileset dir="${base.pluginDst}"/>
    </zip>
    <delete dir="${base.pluginDst}/${base.pluginName}"/>
</target>

<target name="build.demoPlugin">
    <echo>git reset --hard</echo>
    <exec command="git reset --hard" dir="${base.demoPluginSrc}"/>
    <echo>git pull</echo>
    <exec command="git pull" dir="${base.demoPluginSrc}"/>
</target>

<target name="build.update">
    <echo>git pull ${base.dir}</echo>
    <exec command="git reset --hard" dir="${base.dir}" checkreturn="true"/>
    <exec command="git pull" dir="${base.dir}"/>
</target>

<target name="build.removeDemo">
    <reflexive description="Strip demo content if required">
        <fileset dir="${build.html}" includes="**/*.php"/>
        <filterchain description="cleans demo code">
            <replaceregexp>
                <regexp pattern="//\s*demo\s*[\s\S]+?//\s*end demo" replace=""/>
            </replaceregexp>
        </filterchain>
    </reflexive>
</target>

<target name="build.changeTextDomain">
    <reflexive description="Change text domain to project specific">
        <fileset dir="${build.html}" includes="**/*.php"/>
        <filterchain description="switches text domain">
            <replaceregexp>
                <regexp pattern="ct_theme" replace="${base.projectName}"/>
            </replaceregexp>
        </filterchain>
    </reflexive>
</target>

<target name="build.eol">
    <lineend path="${build.html}/${base.css.path}/style.css"/>
</target>

<target name="build.scss">
    <echo>Compiling SCSS to CSS...</echo>
    <exec
            command="wp eval 'fw_ext_ct_demo_content_cli_compile_save();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
    <foreach list="${base.flavors}" target="build.flavorScss" param="flavorSlug" />
</target>

<target name="build.flavorScss">
    <echo msg="Compiling SCSS for flavor: ${flavorSlug}" />
    <exec
            command="wp eval --user=1 --url=${base.url}/${flavorSlug} 'fw_ext_ct_demo_content_cli_compile_save();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
</target>

<target name="build.generateDemoContent">
    <echo>Generating demo content...</echo>
    <exec
            command="wp eval --user=1 --url=${base.url} 'fw_ext_ct_demo_content_do_cli_backup();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
    <foreach list="${base.flavors}" target="build.flavorDemoContent" param="flavorSlug" />
</target>

<target name="build.flavorDemoContent">
    <echo msg="Generating demo content for flavor: ${flavorSlug}" />
    <exec
            command="wp eval --user=1 --url=${base.url}/${flavorSlug} 'fw_ext_ct_demo_content_do_cli_backup();'"
            outputProperty="execOutput"
    />
    <echo msg="Generated output: ${execOutput}"/>
</target>

<target name="build.moveRemotes">
    <echo msg="Moving remotes: ${build.remotes} to destination: ${build.remotesDestination}" />
    <foreach list="${build.remotes}" target="build.moveSingleRemote" param="flavorSlug" />
</target>

<target name="build.moveSingleRemote">
    <zip destfile="${base.html}/demo-content/${flavorSlug}/${flavorSlug}.zip" description="pack flavor content">
        <fileset dir="${base.html}/demo-content/${flavorSlug}"/>
    </zip>
    <copy todir="${build.remotesDestination}/${flavorSlug}">
        <fileset dir="${base.html}/demo-content/${flavorSlug}">
            <include name="*.zip" />
            <include name="*.php" />
            <include name="*.png" />
        </fileset>
    </copy>
    <delete includeemptydirs="true" description="remove remote demo files from theme but leave manifest and screenshot">
        <fileset dir="${base.html}/demo-content/${flavorSlug}">
            <exclude name="remote.php" />
            <exclude name="manifest.php" />
            <exclude name="screenshot.*" />
        </fileset>
    </delete>
</target>

<target name="js.obfuscate">
    <jsobfuscate targetdir="${build.html}/${base.js.path}">
        <fileset dir="${build.html}/${base.js.path}">
            <include name="ct*.js"/>
        </fileset>
    </jsobfuscate>
</target>

<target name="base.cleanup">
    <echo>Deleting vendor zip files...</echo>
    <delete>
        <fileset dir="${base.html}/vendor">
            <include name="*.zip"/> <!-- delete old style vendor files-->
        </fileset>
    </delete>
    <echo>Deleting wrong dir for demo content...</echo>
    <delete dir="${base.html}/demo-content/test-dir" />
</target>

<!-- Delete the Publish directory to start from scratch -->
<target name="build.delete">
    <echo>build.delete</echo>
    <delete file="${build.zipNameFull}"/>
    <delete file="${build.dir}/${build.zipNameFile}"/>
    <delete dir="${build.tmp}"/>
</target>
<target name="build.cleanup">
    <delete dir="${build.tmp}"/>
</target>

<!-- Create the Build Directory -->
<target name="build.createDir">
    <mkdir dir="${build.tmp}"/>
</target>

<target name="copy.all">
    <!--Main HTML-->
    <copy todir="${build.html}">
        <fileset dir="${base.html}">
            <exclude name="npm/"/>
            <exclude name="**/*.svn/"/>
            <exclude name="**/*.git/"/>
            <exclude name="**/*.git*"/>
            <exclude name="**/*.idea/"/>
            <exclude name="**/*.htaccess"/>
            <exclude name="**/composer.*"/>
        </fileset>
    </copy>
</target>

<target name="build.pack">
    <copy todir="${build.tmp}/packing/${base.projectName}">
        <fileset dir="${build.html}"/>
    </copy>
    <zip destfile="${build.zipName}" description="dump zip to tmp">
        <fileset dir="${build.tmp}/packing/"/>
    </zip>
    <delete dir="${build.tmp}/${base.projectName}" description="remove theme"/>
    <delete dir="${build.tmp}/packing" description="remove temp zip dir"/>
    <zip destfile="${build.zipNameFull}" description="packs everything (with theme zip)">
        <fileset dir="${build.tmp}"/>
    </zip>
    <copy todir="${build.dir}">
        <fileset dir="${build.tmp}">
            <include name="${build.zipNameFile}"/>
        </fileset>
    </copy>
</target>

<target name="build.pot" description="generate po i18n file">

    <delete includeemptydirs="false">
        <fileset dir="${build.html}/languages">
        </fileset>
    </delete>

    <echo message="php lib/pot/makepot.php wp-theme ${build.html} ${build.html}/languages/${base.projectName}.pot"/>
    <exec command="php lib/pot/makepot.php wp-theme ${build.html} ${build.html}/languages/${base.projectName}.pot"
          description="generate po file. LANG dir must exist"/>
</target>

<target name="build.vctemplates" description="re generate vc templates for every page">

    <echo>Generating VC templates...</echo>
    <exec
            command="wp eval --user=1 --url=${base.url} 'fw_ext_ct_demo_content_update_vc_templates();'"
    />
    <foreach list="${base.flavors}" target="build.flavorVctemplates" param="flavorSlug" />

</target>

<target name="build.flavorVctemplates">
    <echo msg="Generating VC templates for flavor: ${flavorSlug}" />
    <exec
            command="wp eval --user=1 --url=${base.url}/${flavorSlug} 'fw_ext_ct_demo_content_update_vc_templates();'"
    />
</target>

<target name="build.deleteThumbnails" description="delete all custom image sizes in demo content">

    <exec
            command="find ${base.html}/demo-content -type f -regex '.+-[0-9]+x[0-9]+\.jpg' -o -regex '.+-[0-9]+x[0-9]+\.jpeg' -o -regex '.+-[0-9]+x[0-9]+\.png' -o -regex '.+-[0-9]+x[0-9]+\.gif' | wc -l"
            outputProperty="filesToDelete"
    />
    <echo msg="Deleting ${filesToDelete} thumbnails..."/>

    <exec
            command="find ${base.html}/demo-content -type f -regex '.+-[0-9]+x[0-9]+\.jpg' -delete"
    />

    <exec
            command="find ${base.html}/demo-content -type f -regex '.+-[0-9]+x[0-9]+\.jpeg' -delete"
    />

    <exec
            command="find ${base.html}/demo-content -type f -regex '.+-[0-9]+x[0-9]+\.png' -delete"
    />

    <exec
            command="find ${base.html}/demo-content -type f -regex '.+-[0-9]+x[0-9]+\.gif' -delete"
    />

</target>

<target name="build.doc">

    <!-- pdf docs for the package -->
    <mkdir dir="${build.doc}"/>
    <httpget url="${build.docUrlPdf}" dir="${build.doc}" filename="Documentation.pdf"/>

    <!-- html docs for the demo -->
    <mkdir dir="${base.doc}"/>
    <httpget url="${build.docUrlHtml}" dir="${base.doc}" filename="${build.docSlug}-${build.docRelease}.zip"
             description="download docs"/>
    <unzip todir="${base.doc}" file="${base.doc}/${build.docSlug}-${build.docRelease}.zip"
           description="unzip docs"/>
    <copy todir="${base.doc}" overwrite="true">
        <fileset dir="${base.doc}/${build.docSlug}-${build.docRelease}">
            <include name="**/*.*"/>
        </fileset>
    </copy>
    <delete dir="${base.doc}/${build.docSlug}-${build.docRelease}" />
    <delete file="${base.doc}/${build.docSlug}-${build.docRelease}.zip" description="deleting doc zip"/>

</target>

</project>

Generate Package

To locally test the package build, download phing tool, and run phing command in build directory. Some task won’t work here, eg. wp cli based tasks, but the zip file should be built inside themeforest directory. When succeeded, commit and push changes in build.xml. Ask a project manager to update demo website and generate the package.

Note

In order for changes in build.xml to take place you may need to run build twice (build.xml is pulled from git after tasks run init)

Package QA

Test Wordpress install

If you don’t have it already, create a fresh wordpress install just for testing packages. It can be a single site installation in a subdirectory of your current wordpress.

  1. Download wordpress from wordpress.org/download

  2. Unpack contents to a new subdirectory of your phinky main wordpress directory, eg. phinky.createit/www/ctXX/wp/wptest

  3. Run wordpress install. See screenshot below. Important: select unique table prefix, eg. wptest_

    ../_images/wordpress-test-install.png
  4. Log in to your new install url: http://ctXX.phinky.createit/wptest/

Package install

  1. Make sure you have a fresh wordpress test installation. If not, reset tables using WordPress Reset plugin, delete all plugins, themes, uploads
  2. Install the package using wp-admin theme install button “Upload theme”
  3. Check everything (Theme Check, Unit Test, general display etc.) on Check list

Upload

If everything checks, let know the project manager the package is ready to be uploaded for a review.