Changing the Divi projects custom post type to anything you want

I’m currently building a website for a friend of Jane, using the Divi theme from Elegant Themes. The website is for a holiday property letting company. This post explains how I changed the built-in Projects content type to Properties, and how you can change it to anything you want.

The problem

Divi is a great theme to use: it’s very flexible, it’s responsive (so it works equally well on smartphones as well as huge desktop monitors), and it has the easiest, drag-and-drop editor that I’ve ever used for WordPress.

Divi comes with a built in content type called Projects; WordPress calls them ‘custom post types’. I use this content type on my own website to list the various projects that I’ve been involved in over the years.

As you can see from the WordPress admin menu ‘Projects’ appears on the list beneath Posts, Media, Pages, and Comments:

WordPress menu with Divi installed shows Projects
WordPress menu with Divi installed

Divi also ships with a number of attractive ways to display your projects using its Portfolio and Filtered Portfolio modules. You can even display these full-width or as a grid, such as this:

Demo of Divi's Filtered Portfolio module displayed as a grid.
Demo of Divi’s Filtered Portfolio module displayed as a grid.

These are exactly the features that I’d like to use on the property letting website:

  • Keep properties separate from pages and posts, using a custom post type.
  • Display all properties in a grid.
  • Allow users to filter properties based on the categories that are assigned to them.

So, I want all the features of Divi’s built-in Projects custom post type, but I don’t want them to be called Projects. I want them to be called Properties.

Use a child theme

First, I strongly recommend that you use a child theme when customising Divi (or indeed any other WordPress theme). A child theme inherits the functionality and styling of another theme, called the parent theme, and allows you to make local customisations to it which will not be overwritten when the theme updates.

Elegant Themes have a useful walkthrough on how to create a child theme, and why you should be using one.

The WordPress Codex also has useful information about child themes.

How to do it

This very useful post on the Elegant Tweaks blog: “Change Divi Projects URL-permalink” got me started, and about 95% of the way.

I copied the code, added it to the functions.php file in my child theme, and set about editing it.

remove_action / add_action

In a nutshell the code from Elegant Tweaks does two things:

  1. It defines a new function — called child_et_pb_register_posttypes() — that will redefine the characteristics of the Projects content type.
  2. It removes the default Projects custom post type contained in Divi, and replaces it with our one in the child theme.

This last point, I believe, is simply to be tidy: rather than clumsily overwriting the existing ‘project’ custom post type it gracefully removes the old one, and creates a redefined version in its place.

Labels

In that Elegant Themes post the author was only concerned with changing the URL from /projects/ to /photos/. So in his example, the names used in the WordPress admin screens still referred to projects: Edit Project, Add New Project, etc. But I want to change these too.

In the code for a custom post type these are referred to as ‘labels’ and are defined in the $labels array. This is what my code looks like now:

<?php
function child_et_pb_register_posttypes() {
    $labels = array(
        'add_new'            => __( 'Add New', 'Divi' ),
        'add_new_item'       => __( 'Add New Property', 'Divi' ),
        'all_items'          => __( 'All Properties', 'Divi' ),
        'edit_item'          => __( 'Edit Property', 'Divi' ),
        'menu_name'          => __( 'Properties', 'Divi' ),
        'name'               => __( 'Properties', 'Divi' ),
        'new_item'           => __( 'New Property', 'Divi' ),
        'not_found'          => __( 'Nothing found', 'Divi' ),
        'not_found_in_trash' => __( 'Nothing found in Trash', 'Divi' ),
        'parent_item_colon'  => '',
        'search_items'       => __( 'Search Properties', 'Divi' ),
        'singular_name'      => __( 'Property', 'Divi' ),
        'view_item'          => __( 'View Property', 'Divi' ),
    );

As you can see, something I find useful is to list the elements alphabetically. Personally, I find it easier to work this way; your mileage may vary.

Obviously, if you are customising this for your own requirements simply edit this to reflect your needs.

Custom post type options

Next, we define the arguments to be passed to the register_post_type function. These define not only how the custom post type is used but also how it is displayed in the WordPress admin menu: where it sits and what icon it uses.

Slug

The most important option here, for our purpose of customising it, is the 'slug' key. You must set its value (in single quotes) to whatever you need it to be. In my case 'slug' => 'property'. I’ve highlighted this in the snippet below.

Just make sure you don’t set the slug to the same name as an existing page.

Menu icon and position

One useful new addition to the code provided by Elegant Tweaks are the options to set the menu icon and where it sits on the menu.

As these are properties I decided to use the home dashicon.

House icon
dashicons-admin-home

I also decided to move it up a bit, from beneath Comments to immediately below Posts. WordPress uses numbers to specify where custom post types should sit, e.g.

  • 5 — below Posts (this is where I want it to appear)
  • 10 — below Media
  • 15 — below Links
  • 20 — below Pages
  • 25 — below Comments

A full list can be found in the WordPress Codex.

So, here is the code I now have; I’ve highlighed these new menu options plus the ‘slug’ (how it will appear in the URL):

    $args = array(
        'can_export'         => true,
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'labels'             => $labels,
        'menu_icon'          => 'dashicons-admin-home',
        'menu_position'      => 5,
        'public'             => true,
        'publicly_queryable' => true,
        'query_var'          => true,
        'show_in_nav_menus'  => true,
        'show_ui'            => true,
        'rewrite'            => apply_filters(
            'et_project_posttype_rewrite_args', array(
            'feeds'          => true,
            'slug'           => 'property',
            'with_front'     => false,
        )),
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments', 'revisions', 'custom-fields' ),
    );

Register the post type

The next line now does the grunt work and registers this custom post type with WordPress.

    register_post_type( 'project', apply_filters(
        'et_project_posttype_args', $args )
    );

This tells WordPress to apply all of these options to the ‘project’ custom post type.

Because we are redefining this existing custom post type (by changing the URL, the menu labels, the menu icon and position) it means that everything else (the default project page layouts and portfolio modules) will work as expected without any further customization.

Categories and tags

The rest of the code I left untouched. This code defines the categories and tags to be used with the projects/properties custom post type.

How it looks now

Adding all the code (see below for the complete script) this is what my WordPress admin menu looks like:

Divi theme now with Properties instead of Projects
Divi theme now with Properties instead of Projects

That’s now working as I expect it. Job done.

Complete code

Here is the full code that I have in my child theme’s functions.php file:

<?php
function child_et_pb_register_posttypes() {
    $labels = array(
        'add_new'            => __( 'Add New', 'Divi' ),
        'add_new_item'       => __( 'Add New Property', 'Divi' ),
        'all_items'          => __( 'All Properties', 'Divi' ),
        'edit_item'          => __( 'Edit Property', 'Divi' ),
        'menu_name'          => __( 'Properties', 'Divi' ),
        'name'               => __( 'Properties', 'Divi' ),
        'new_item'           => __( 'New Property', 'Divi' ),
        'not_found'          => __( 'Nothing found', 'Divi' ),
        'not_found_in_trash' => __( 'Nothing found in Trash', 'Divi' ),
        'parent_item_colon'  => '',
        'search_items'       => __( 'Search Properties', 'Divi' ),
        'singular_name'      => __( 'Property', 'Divi' ),
        'view_item'          => __( 'View Property', 'Divi' ),
    );

    $args = array(
        'can_export'         => true,
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'labels'             => $labels,
        'menu_icon'          => 'dashicons-admin-home',
        'menu_position'      => 5,
        'public'             => true,
        'publicly_queryable' => true,
        'query_var'          => true,
        'show_in_nav_menus'  => true,
        'show_ui'            => true,
        'rewrite'            => apply_filters( 'et_project_posttype_rewrite_args', array(
            'feeds'          => true,
            'slug'           => 'property',
            'with_front'     => false,
        )),
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments', 'revisions', 'custom-fields' ),
    );

    register_post_type( 'project', apply_filters( 'et_project_posttype_args', $args ) );

    $labels = array(
        'name'              => _x( 'Categories', 'Property category name', 'Divi' ),
        'singular_name'     => _x( 'Category', 'Property category singular name', 'Divi' ),
        'search_items'      => __( 'Search Categories', 'Divi' ),
        'all_items'         => __( 'All Categories', 'Divi' ),
        'parent_item'       => __( 'Parent Category', 'Divi' ),
        'parent_item_colon' => __( 'Parent Category:', 'Divi' ),
        'edit_item'         => __( 'Edit Category', 'Divi' ),
        'update_item'       => __( 'Update Category', 'Divi' ),
        'add_new_item'      => __( 'Add New Category', 'Divi' ),
        'new_item_name'     => __( 'New Category Name', 'Divi' ),
        'menu_name'         => __( 'Categories', 'Divi' ),
    );

    register_taxonomy( 'project_category', array( 'project' ), array(
        'hierarchical'      => true,
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
    ) );

    $labels = array(
        'name'              => _x( 'Tags', 'Property Tag name', 'Divi' ),
        'singular_name'     => _x( 'Tag', 'Property tag singular name', 'Divi' ),
        'search_items'      => __( 'Search Tags', 'Divi' ),
        'all_items'         => __( 'All Tags', 'Divi' ),
        'parent_item'       => __( 'Parent Tag', 'Divi' ),
        'parent_item_colon' => __( 'Parent Tag:', 'Divi' ),
        'edit_item'         => __( 'Edit Tag', 'Divi' ),
        'update_item'       => __( 'Update Tag', 'Divi' ),
        'add_new_item'      => __( 'Add New Tag', 'Divi' ),
        'new_item_name'     => __( 'New Tag Name', 'Divi' ),
        'menu_name'         => __( 'Tags', 'Divi' ),
    );

    register_taxonomy( 'project_tag', array( 'project' ), array(
        'hierarchical'      => false,
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
    ) );

    $labels = array(
        'name'               => _x( 'Layouts', 'Layout type general name', 'Divi' ),
        'singular_name'      => _x( 'Layout', 'Layout type singular name', 'Divi' ),
        'add_new'            => _x( 'Add New', 'Layout item', 'Divi' ),
        'add_new_item'       => __( 'Add New Layout', 'Divi' ),
        'edit_item'          => __( 'Edit Layout', 'Divi' ),
        'new_item'           => __( 'New Layout', 'Divi' ),
        'all_items'          => __( 'All Layouts', 'Divi' ),
        'view_item'          => __( 'View Layout', 'Divi' ),
        'search_items'       => __( 'Search Layouts', 'Divi' ),
        'not_found'          => __( 'Nothing found', 'Divi' ),
        'not_found_in_trash' => __( 'Nothing found in Trash', 'Divi' ),
        'parent_item_colon'  => '',
    );

    $args = array(
        'labels'             => $labels,
        'public'             => false,
        'can_export'         => true,
        'query_var'          => false,
        'has_archive'        => false,
        'capability_type'    => 'post',
        'hierarchical'       => false,
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments', 'revisions', 'custom-fields' ),
    );

    register_post_type( 'et_pb_layout', apply_filters( 'et_pb_layout_args', $args ) );
}

function remove_et_pb_actions() {
    remove_action( 'init', 'et_pb_register_posttypes', 0 );
}

add_action( 'init', 'remove_et_pb_actions');
add_action( 'init', 'child_et_pb_register_posttypes', 1 );
?>

Final thoughts

Like many things on my blog I’m primarily putting it here for my own reference, but if you find it useful — or would like to suggest improvements or additional features — please leave a comment below.

Update

Friday 20 March 2015

I meant to say this in the article above. Sometimes WordPress gets a bit muddled when you play around with custom post types.

The way to fix this is to go to Settings > Permalinks > Save Changes.

That’s enough to flush the permalinks and your custom post type should work. I had to do that a couple of times while figuring out how to do this.

Update 2

Monday 20 April 2015

A few days ago I discovered this plugin: Divi Page Building for Custom Content Types.

This plugin allows you to use the Divi builder for Posts, or indeed any custom post type.

 

The wait is over, time now to lose the weight

Dumbbells

Between 2006–2007 I lost six inches (15 cm) off my waist, through a combination of changing what I ate, lifting weights, and regular cycling. My motivation was to get fit in anticipation of our IVF treatment working and us having children; we now have three.

Fast forward seven years and sadly I’ve put it all back on again. A combination of being on the parent-of-twins’ sleep deprivation programme, two back injuries (from lifting babies and pushing buggies), two neck injuries (what happens when twins jump onto your head from behind), and last year’s episode of meningitis.

Back in September my GP told me not to push myself: meningitis takes it out of you. He predicted that my stamina might return in January or February of this year. Now we’re approaching the end of February I feel it’s time to start working myself a little harder. The fact that it’s Lent — traditionally a time of increased discipline — should also help.

My plan is that I’m going to start gently and gradually build up my level of fitness. My immediate ground rules are:

  • Drink more water
  • Go to bed earlier (sleep is really important)
  • No chocolate
  • No fizzy drinks
  • Lift weights (dumb bells) 2–3 times a week
  • Cycling 1–2 times a week

I have to admit to feeling a little nervous. I know that I’ve done this before, but back then I was younger and I didn’t so easily experience the back and neck pain that I can now. I’ve never really been good at pacing myself, it’s time for a crash course (I guess, without actually crashing).

I’ll report back with my progress.

The boys ‘helping’ me with my studies

Boys on the desk
Reuben, Joshua and Isaac ‘helping’ me with my studies

 

For the next few days I’m on a course at work looking at DSDM Atern agile project management. It’s certified, so I have exams on Wednesday morning (foundation) and Thursday afternoon (practitioner).

When I got home last night, after dinner, I decided to sneak upstairs and get about 45 minutes of study in before the boys had to go to bed.

It turns out Reuben, Joshua, Isaac and monkey had different ideas and came to ‘help’ me study.

NYCGB alumni in Sheffield

NYCGB alumni in Sheffield (Photo by Rob Colbert)
NYCGB alumni in Sheffield (Photo by Rob Colbert)

Last weekend I travelled down to Sheffield to meet up with about 40 other alumni of the National Youth Choirs of Great Britain ()for a weekend of singing, reminiscing and a lot of laughter.

Last year we had our first get-together and concert in Spitalfields in London, and decided that this year we ought to meet in “the north”.

Friday

I arrived in Sheffield on Friday afternoon, after a five hours’ train journey south to reach the north; remarkably there was a direct, cross-country train from Cupar to Sheffield.

After buying a hat (to replace the one I accidentally left in my car in Cupar) and having been accosted by a couple of “chuggers” both on my way to and from Marks & Spencer, I made my way up the hill to Broomhill to check in at the Rutland Hotel on Glossop Road.

Rutland Weekend Wallpaper
Rutland Weekend wallpaper

The room was… interesting. A kind of modern, 70s retro with a photograph of a giant woman’s head on the wall behind the bed. Other friends staying there reported similar photographs in their rooms. I guess you can never really feel lonely in those rooms.

In the evening I met up with my friend Simon (aka Goose) and we took a walk over to the Ranmoor area of Sheffield to meet up with more friends (Mike and Rachel, Duncan, Simon W) at the Ranmoor Inn on Fulwood Road, and yet another friend (Sworrell) at the Ranmoor Tandoori a few doors down.

What fun and jolly japes we had. Although, the chicken dopiaza wasn’t nearly as good as from our local Indian restaurant (the signature onions were not cooked enough). I finally crawled into bed around 01:30.

Saturday

Rehearsals began shortly after noon, in St Mark’s church, Broomhill which was conveniently right next door to the hotel.

It was so good to catch up with people, some of whom I’ve not seen for 15 or 20 years. And yet we just picked up from where we left off, and soon the years disappeared and there we all were like teenagers again sitting in rehearsals… and misbehaving!

I sat on the back row (of course!) between my good friend Andy and a guy called Will who left the National Youth Training Choir last year. It was so good that we had alumni there from all eras of the choir, from when it started in 1983 right to last year.

Ben Parry conducting our rehearsal
Ben Parry conducting our rehearsal

There is something wonderful about creating music as a choir, creating something out of nothing using only our voices. There is something intimately personal about that because our voices are so unique to each of us, and in the choir we listen to one another and blend our voices together in music. And there is something magical about the sound that NYCGB makes.

We rehearsed for about four hours and I must have smiled and laughed through most of those 240 minutes. The small, informal concert that we put on at the end of the day (which I meant to record but erm… forgot that I needed to press record TWICE on the Zoom H2 digital recorder), even with so little rehearsal, still sounded better than every other choir that I’ve sung in… even when we busked elements of it (I’m looking at you, page 7 of “Butterfly”).

Our programme:

  1. My Love Dwelt in a Northern Land—Elgar
  2. L’amour de Moi—arr. Swingle
  3. Wie Liegt die stadt—Mauersberger
  4. Sourwood Mountain—Rutter
  5. Three Shakespeare Songs—Vaughan Williams
  6. And So It Goes—Billy Joel arr. The King’s Singers
  7. Butterfly—Makaroff
  8. The Bluebird—CV Stanford
  9. Shenandoah—arr. Erb

Songs rehearsed but not performed

  • Hymn to St Cecilia—Britten
  • Evening Song—Kodaly

In the evening we piled back to the Rutland for dinner, which I didn’t particularly enjoy but at the end of the day it wasn’t about the food but the company. We inevitably retired to the bar for more chat, memories, and laughter and I finally found my bed sometime after 02:00.

More photos are on the NYCGB alumni site.

Sunday

Survivors' breakfast
Survivors’ breakfast

The following morning the survivors’ met for a hearty breakfast before returning to our own particular corners of the UK.

Goose kindly dropped me at Sheffield station where I caught the train to Edinburgh… and stood most of the way due to a lack of seats. Or rather, it had a lot of seats—it’s just there were other people sitting in them.

Many thanks

A huge thanks to everyone who made the weekend possible and such a success. Thanks to Ben Parry and the staff at NYCGB HQ, particularly Emily. Thanks to Mike Jeremiah for his local knowledge and helping finalise the venue. And finally thanks to all the alumni who gave up a weekend to relive their youth.

Next year…

Well, that was fun. Let’s do it again next year. I propose back in London. Maybe we could even get the Royal Albert Hall. It would be fun to perform there again.

Backpack hack

Cabin Max Tallinn
Cabin Max Tallinn – Flight Approved Backpack for EasyJet & BA hand luggage

About a year ago I bought myself a new backpack, the Cabin Max Tallinn, for about £25. The reviews were favourable (average of 4/5 stars) and when it arrived I was delighted with it: mainly because it was more compact than the large rucksack that I bought for a trip to California about a decade ago.

I packed it and headed off to Glasgow and then London to seek my fame and fortune attend the first NYCGB Alumni choir singing day. It was a timely opportunity to road test the bag.

That four day trip identified two main issues. This wasn’t quite the bag that I thought it was.

However, I don’t like throwing stuff away, and I don’t like sending stuff back because it’s not 100% what I want it to be. This bag was about 95% the way there. I like the whole computer hacker culture (not to be confused with the illegal ‘cracker’) so…

Open zipped pocket

Zipped pocket that I've now sewn up
Zipped pocket that I’ve now sewn up

The first issue was that in the middle compartment there was a small, meshed pocket with a zip. I looked at that and thought it was the perfect size to store a passport, for example.

There was one small snag: the top of the zip wasn’t sewn down. So even when the zip was closed you could still slide items into the meshed pocket beneath the zip.

Who designed that?! It was like a shirt pocket with a redundant zip sewn into the top seam.

I wrote to Cabin Max and asked if this was a fault or a feature. It turned out to be a feature. I told them this was ridiculous and whoever it was I corresponded with agreed and said that she would pass on my feedback.

So I got my sewing kit out and completed the job: I sewed the zip down so that when the zip was closed it was… well, closed.

No inner straps

Cabin Max Tallinn inner straps
Cabin Max Tallinn inner straps

It wasn’t until a later trip last year that I realised there was another problem: if I didn’t pack the back completely full (as I had done for the London trip) then my clothes and whatever else I put in the large, main compartment just rattles around in there.

What this bag was missing, that every other rucksack or suitcase I own has, were straps inside that would allow me to tie down whatever I place into the main compartment.

So today I added my own. Having bought a couple of quick release tie-down straps online last week—the kind that people use for strapping things to their golf caddies (I believe)—this evening I measured them up (using the straps in my giant rucksack as a template) glued them in and sewed them down. Job done.

This weekend I’m heading to Sheffield for the second NYCGB alumni concert. I’ll report back how I get on with my two alterations to my bag.

Migration complete… but where are the images?

Trello board tracking the migration of my websites
Trello board tracking the migration of my websites

For much of the last two weeks I’ve focussed on two things:

  1. Redesign my website (garethjmsaunders.co.uk)
  2. Migrate that site, this blog, my SEC digital calendar site, and the NYCGB alumni website to a new web host (SiteGround).

I’ve managed to complete the project three days early… well, kind of.

WordPress… we have a problem

One unforeseen snag has been to do with the media (images, PDFs, zip files, etc.) on this blog.

I’ve been using WordPress since version 0.7 in 2003. During that time I’ve been uploading image after image, and as WordPress changed the way that it stored images I’ve experimented with different ways of organising it—even simply uploading the images to my server via FTP. I must have tried about four or five different arrangements.

For the most part, though, I’ve been uploading files directly into /wp-content. Occasionally I’d switch on the “organise my uploads into month- and year-based folders” option.

In short the organisation of media on this blog has been a mess, and I’ve always shied away from addressing it because… well, it worked.

When I came to consider migrating this blog from Heart Internet to SiteGround I did think about the media: would it be a problem if I simply transferred everything over as is and sort it out there.

I was a fairly tight schedule (it had to be completed by 20 January so that my Heart Internet hosting account wasn’t renewed) and I reckoned that since it worked fine at Heart Internet then it should work at SiteGround.

I was wrong.

cPanel and the mystery of the 1,998 files

SiteGround uses cPanel. As Wikipedia explains, “cPanel is a Linux-based web hosting control panel that provides a graphical interface and automation tools designed to simplify the process of hosting a web site.”

cPanel uses Pure-FTPd, a free (BSD licence) FTP server which by default shows up to 2,000 files in each folder. I found that out after the event tucked away in the cPanel documentation.

I had 3,688 files plus 10 directories in my /wp-content folder and I couldn’t figure out why it would only display 1,998 files and the previously  visible directories, such as /plugins and /themes had disappeared.

So…

I am manually working my way through the media library. Uploading files into the appropriate /wp-content/uploads/<year>/<month> directories and updating the database to tell WordPress where the files are.

For those files that were uploaded before there was such a good media library I’m using the Add From Server plugin to quickly import media into the WordPress uploads manager.

This is going to take a while, so please bear with me.

Update

Monday 19 January 2015

I’m making good progress already. I’ve fixed 360/700 images in the media library. That’s 51%, just over the halfway mark.

I’m finding it strangely satisfying getting this sorted out. A bit of website gardening.