ការអភិវឌ្ឍគេហទំព័រ Wordpress ជាមួយ Custom Theme និង ACF

1. Setup Environment

Setting up a consistent local environment ensures that what you build on your machine will work perfectly when you eventually push it to a live server.

1.1. Local Server Setup

We are using LocalWP because it simplifies the management of PHP versions, web servers (Nginx/Apache), and database access.

  1. Download & Install: Download the latest version of LocalWP from https://localwp.com.
  2. Create a New Site: * Open LocalWP and click the "+" button (bottom left).
    • Select "Create a new site" and click Continue.
    • Site Name: Enter your project name (e.g., my-custom-theme).
  3. Environment Settings:
    • Choose Preferred for the easiest setup, or Custom if you need a specific PHP version (7.4 or 8.x are standard for modern development).
  4. WordPress Credentials:
    • Set your Admin Username, Password, and Email. (Keep these handy!)
  5. Finalize: Click Add Site. LocalWP will provision the server and install WordPress for you.

1.2. Installing a Fresh WordPress

With LocalWP, the installation is handled during the site creation process. Once the site is "Running":

  • Admin Dashboard: Click the "WP Admin" button in LocalWP to log in.
  • Site View: Click "Open Site" to see the default front-end.
  • SSL: In the LocalWP dashboard, under the "Site Setup" tab, click Trust next to the SSL certificate. This prevents "Your connection is not private" browser warnings.

1.3. WordPress File Structure

Understanding where your code lives is vital. Below is a high-level view of a standard WordPress directory. Your work will almost exclusively happen inside the themes folder.

Directory/File Description
wp-admin/ Core WordPress dashboard files. Never edit.
wp-admin/ Core WordPress logic and libraries. Never edit.
wp-content/ The Storage for your site. This is where your custom work lives.
↳ plugins/ Where ACF Pro and other functionality will be installed.
wp-config.php The core configuration file (DB credentials, security keys).

1.4. Installing & Setup Custom Theme (_s)

Instead of building from scratch, we use Underscores as a "starter" theme. It provides the essential PHP structure so you don't have to reinvent the wheel.

  1. Generate the Theme:
    • Go to Underscores.me.
    • Theme Name: Your Custom Theme Name.
    • Slug: my-custom-theme (lowercase, no spaces).
    • Click Generate. A .zip file will download.
  2. Install the Theme:
    • In LocalWP, click the arrow next to your site path (e.g., ~/Local Sites/my-project) to open the folder.
    • Navigate to app/public/wp-content/themes/.
    • Extract your _s zip file into this folder.
  3. Activate:
    • Go to your WordPress Admin Dashboard.
    • Navigate to Appearance > Themes.
    • Locate your new theme and click Activate.

Note: Your site will look very plain at first. This is normal! Underscores is "naked" CSS-wise, giving you a clean slate to build your own design.

2. Theme Structure

2.1. Theme File Structure

Once you open your theme folder (found in wp-content/themes/your-theme-name ), you’ll see several files. Here is what they do:

2.2. Header & Menu

To make the header dynamic, we use specific WordPress functions so that changes in the Dashboard reflect on the site.

  • The Custom Logo: To display the logo uploaded in Appearance > Customize, use:
    <?php the_custom_logo() ?>
  • The Navigation Menu: Underscores already sets up a menu location. To output it, use:
    wp_nav_menu( array(
        'theme_location' => 'menu-1',
        'menu_id'        => 'primary-menu',
    ) );

2.3. Footer

The footer usually contains copyright info and the wp_footer() hook.

Important: Never remove <?php wp_footer(); ?>. It sits just before the closing </body> tag and is where WordPress injects scripts and the Admin Bar.

2.4. Sidebar

If your design requires a sidebar, it is controlled via sidebar.php.

  • Check for active sidebar: is_active_sidebar( 'sidebar-1' ) checks if any widgets are actually placed there.
  • Displaying it: dynamic_sidebar( 'sidebar-1' ); triggers the widgets you see in the WordPress Admin under Appearance > Widgets.

2.5. Custom CSS, JS & PHP

For a clean project, don't just dump everything into style.css. Organization is key.

Where to put files:

  1. Create a folder named assets in your theme root.
  2. inside, create subfolders: assets/css/and assets/js/.
  3. Place your custom-styles.css and custom-script.js there.

How to Enqueue (Connect) them: Open functions.php and look for the function ending in _scripts. Add your files using wp_enqueue_style and wp_enqueue_script:

function my_theme_scripts() {
    // Enqueue Custom CSS
    wp_enqueue_style( 'my-custom-style', get_template_directory_uri() . '/assets/css/custom-styles.css', array(), '1.0.0' );

    // Enqueue Custom JS (The 'true' at the end puts it in the footer)
    wp_enqueue_script( 'my-custom-js', get_template_directory_uri() . '/assets/js/custom-script.js', array('jquery'), '1.0.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_theme_scripts' );

2.6. Specific Page Templates (Static naming)

Instead of selecting a template from a dropdown, WordPress allows you to target specific pages based on their slug or ID using its naming hierarchy. This is the preferred method for developers who want to keep the design locked into the code.

The "Front Page" (Home)

To create a unique design for your homepage that is different from your blog index:

  • Create a file named front-page.php.
  • WordPress will automatically use this file for whatever page you set as the "Static Front Page" in Settings > Reading.

Targeting Specific Pages by Slug

If you have a page in the dashboard with the URL slug contact or about-us, you can create dedicated files for them:

  • page-about-us.php
  • page-contact.php

How to Structure the Code

Since we are avoiding the Gutenberg/Block editor, your code in these files will typically look like this:

<?php get_header(); ?>

<main id="primary" class="site-main">
    <?php
    while ( have_posts() ) :
        the_post();
        
        // 1. Static HTML/CSS structure goes here
        ?>
        <section class="hero-banner">
            <h1><?php the_title(); ?></h1>
        </section>

        <section class="custom-content">
            <?php the_content(); // Pulls from the editor if needed ?>
        </section>
        <?php

    endwhile; 
    ?>
</main>

<?php get_footer(); ?>

Why use this method?

  • Design Integrity: Clients cannot accidentally break the layout by moving blocks around in the editor.
  • Performance: You only load the CSS and JS needed for that specific page.
  • Clean Code: You have full control over every <div>and class name, making it easier to integrate custom Javascript libraries (like GSAP or Swiper.js).

3. Advanced Custom Field

3.1. What is ACF?

ACF allows you to add "Field Groups" to specific pages or post types. Instead of typing everything into a big text box, you create structured data (e.g., a field specifically for a "Price" or a "Hero Image").

The Core Rule:

  • To get a field: get_field('field_name');
  • To display a field: the_field('field_name');

3.2. Text & Text area

  • Text: Best for headlines or short strings.
  • Text Area: Best for short descriptions.
    • Tip: Use the "New Lines" setting to "Automatically add <br>" if you want to preserve line breaks without using a full editor.

3.3. Image

When setting up an Image field, always set the Return Format to Image Array. This gives you access to the URL, Alt text, and specific sizes.

<?php 
$image = get_field('hero_image');
if( !empty( $image ) ): ?>
    <img src="<?php echo esc_url($image['url']); ?>" alt="<?php echo esc_attr($image['alt']); ?>" />
<?php endif; ?>

3.4. Wysiwyg Editor

The "What You See Is What You Get" editor. Use this for the main body of content where the user might need to bold text or add lists.

3.5. Icon Picker

Note: ACF does not have a native icon picker. You usually use a plugin like "ACF Font Awesome" or a "Select" field where the values are CSS class names.

Implementation: Store the class name (e.g., fa-star) and output it: <i class="fa <?php the_field('icon_class'); ?>"></i>.

3.6. Repeater (Pro Only)

The most powerful field. It allows you to create a set of sub-fields that can be repeated infinitely (e.g., a "Team Members" list).

<?php if( have_rows('team_members') ): ?>
    <div class="team-grid">
    <?php while( have_rows('team_members') ): the_row(); ?>
        <div class="member">
            <h3><?php the_sub_field('name'); ?></h3>
            <p><?php the_sub_field('role'); ?></p>
        </div>
    <?php endwhile; ?>
    </div>
<?php endif; ?>

3.7. Gallery

Similar to the Image field but returns an array of multiple images. Perfect for sliders or portfolios. Use a foreach loop to display them.

3.8. oEmbed

Allows users to paste a YouTube, Vimeo, or Twitter link, and WordPress automatically converts it into an iframe.

<div class="video-container">
    <?php the_field('video_link'); ?>
</div>

3.9. Email, Number & URL

These fields have built-in validation.

  • Email: Ensures a valid @ format.
  • URL: Ensures http:// is present.
  • Number: Useful for data you might want to calculate or use in scripts.

3.10. Tab & Accordion

These are Layout Fields. They don't store data themselves; they organize your other fields in the WordPress Admin so the user isn't overwhelmed by a giant list.

  • Tabs: Good for separating "Header Settings," "Content," and "Footer Settings" within the same page editor.

3.11. Global Elements (Options Page)

Sometimes you need a field that appears on every page (like a phone number in the footer or social media links). For this, we create an Options Page.

  1. Register it in functions.php:
    if( function_exists('acf_add_options_page') ) {
        acf_add_options_page(array(
            'page_title'    => 'Global Settings',
            'menu_title'    => 'Global Settings',
            'menu_slug'     => 'global-settings',
            'capability'    => 'edit_posts',
            'redirect'      => false
        ));
    }
  2. Retrieve the data: You must add 'options' as the second parameter: the_field('phone_number', 'options');

4. Post Type & Custom Post Type

4.1. Post (Blog)

By default, WordPress comes with "Posts." These are designed for chronological content and come pre-loaded with Categories and Tags. In a custom theme, these usually power your "News" or "Blog" section.

4.2. Custom Post Type (CPT)

Sometimes "Posts" aren't enough. If you are building a real estate site, you need a "Properties" post type. If it's a portfolio, you need "Projects."

To register a CPT, add this to your functions.php. (While plugins like CPT UI exist, coding it keeps your theme lightweight):

function register_custom_post_types() {
    register_post_type('projects', array(
        'labels' => array(
            'name' => 'Projects',
            'singular_name' => 'Project',
        ),
        'public'      => true,
        'has_archive' => true,
        'supports'    => array('title', 'editor', 'thumbnail'),
        'menu_icon'   => 'dashicons-portfolio', // Icon in sidebar
        'rewrite'     => array('slug' => 'projects'),
    ));
}
add_action('init', 'register_custom_post_types');

4.3. Post Query (The Loop)

To display these posts on a page (like a "Latest Projects" section on your Home Page), we use WP_Query. This is the most important tool in your kit.

$args = array(
    'post_type'      => 'projects',
    'posts_per_page' => 3,
    'orderby'        => 'date',
    'order'          => 'DESC',
);

$project_query = new WP_Query($args);

if ($project_query->have_posts()) :
    while ($project_query->have_posts()) : $project_query->the_post();
        // Your HTML & ACF fields go here
        the_title('<h3>', '</h3>');
        the_post_thumbnail('medium');
    endwhile;
    wp_reset_postdata(); // Essential! Restores the global $post variable
endif;

4.4. Archive Page

An Archive is the automated list view of a post type (e.g., yourdomain.com/projects/).

  • Filename: archive-projects.php
  • WordPress automatically recognizes this file for your CPT. It uses a standard "Loop" without needing a custom WP_Query.

4.5. Single Page

The Single template is the layout for an individual post (e.g., the specific project page).

  • Filename: single-projects.php
  • This is where you usually go heavy with ACF. You’ll use get_field() to pull in specific project details like "Client Name," "Year," or "Project Gallery."

4.6. Filters & Search

To let users find specific content, you need to interact with the WordPress Search engine.

  • Search Form: Create a searchform.php to customize the look of your search bar.
  • Results Page: Use search.php to display the results.
  • The "Secret" Filter: If you want to filter by ACF fields (e.g., "Show only projects where 'Service' is 'Design'"), you modify the query using the meta_query argument in your WP_Query.

Pro Tip: For advanced filtering (AJAX filters where the page doesn't refresh), most developers use the "Search & Filter Pro" plugin or custom JavaScript fetching.

5. Taxonomies

5.1. What is a Taxonomy?

In WordPress, "Taxonomy" is a fancy word for a grouping mechanism.

  • Default Taxonomies: Categories (hierarchical) and Tags (flat).
  • Custom Taxonomies: These allow you to create specific labels for your Custom Post Types. For a "Books" post type, you might create a "Genre" or "Author" taxonomy.

5.2. Registering a Custom Taxonomy

Like CPTs, you register these in functions.php. You must link the taxonomy to a specific post type during registration.

function register_custom_taxonomies() {
    register_taxonomy('project_type', 'projects', array(
        'labels' => array(
            'name' => 'Project Types',
            'singular_name' => 'Project Type',
        ),
        'hierarchical' => true, // True = like Categories, False = like Tags
        'show_admin_column' => true,
        'rewrite' => array('slug' => 'project-type'),
    ));
}
add_action('init', 'register_custom_taxonomies');

5.3. Taxonomy Archive (taxonomy-{taxonomy}.php)

When a user clicks on a "Web Design" category link, WordPress looks for a specific file to display all projects in that category.

  • Filename: taxonomy-project_type.php
  • If that file doesn't exist, it falls back to archive.php, and eventually index.php.

5.4. Displaying Terms on a Single Post

To show which "Project Types" are assigned to a specific project on the single-projects.php page, use the get_the_terms() function.

<?php
$terms = get_the_terms( get_the_ID(), 'project_type' );

if ( $terms && ! is_wp_error( $terms ) ) : 
    foreach ( $terms as $term ) {
        echo '<span class="term-badge">' . esc_html( $term->name ) . '</span>';
    }
endif;
?>

5.5. Querying by Taxonomy (tax_query)

If you want to create a "Related Projects" section that only shows projects from the same "Project Type," you use a tax_query inside your WP_Query arguments.

$args = array(
    'post_type' => 'projects',
    'tax_query' => array(
        array(
            'taxonomy' => 'project_type',
            'field'    => 'slug',
            'terms'    => 'web-design', // Show only Web Design projects
        ),
    ),
);
$query = new WP_Query( $args );

6. Contact Form (CF7)

Contact Form 7 is the industry standard for custom themes because it stays out of your way and gives you raw HTML to style.

6.1. Why CF7 for Custom Themes?

Unlike "drag-and-drop" builders, CF7 allows you to write your own markup. This ensures the form fields match your theme’s CSS classes perfectly.

6.2. Structuring the Form Markup

In the CF7 editor, you can wrap your inputs in custom <div> containers to match your grid system:

<div class="form-row">
    <div class="column">[text* your-name placeholder "Name"]</div>
    <div class="column">[email* your-email placeholder "Email"]</div>
</div>
<div class="form-row">
    [textarea your-message placeholder "Message"]
</div>
[submit "Send Message"]

6.3. Displaying the Form in PHP

Once you create a form, CF7 provides a shortcode. To keep it dynamic, you can use an ACF URL or Text field for the shortcode, or hardcode it into your template:

<section class="contact-section">
    <h2>Get in Touch</h2>
    <?php echo do_shortcode('[contact-form-7 id="123" title="Contact form 1"]'); ?>
</section>

6.4. Styling CF7

CF7 forms are notorious for looking like 1995 HTML out of the box. Use your assets/css/custom-styles.css to target them:

.wpcf7-form input[type="text"],
.wpcf7-form input[type="email"] {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.wpcf7-submit {
    background-color: #0073aa;
    color: #fff;
    cursor: pointer;
}