ポートフォリオに戻る

🔒 守秘義務案件のため、クライアント名・URL・スクリーンショット等の詳細情報を省略しています。

ヘアサロングループ公式サイト(首都圏 10 店舗展開)

Wordpress Mobable Type HTML CSS JavaScript

構成:WordPress マルチサイト+ 静的サブサイト

本案件は 2017年より継続して運用が続くグループサイトで、MovableType 時代の遺構ディレクトリを多数含む。

現行の開発対象はWordpressマルチサイト本体と、代表プロフィールの静的サブサイトの 2 系統。


アーキテクチャ面

マルチサイト内の二層テーマ戦略

単一の WordPress マルチサイト(MULTISITE=true・パスベース)の中に、性質の異なる 2 種類のサイト群を同居させる設計にした。

「店舗サイト」は親子テーマ継承で差分を最小化し、「コンテンツ系ブランドサイト」は共通の内部設計パターン(CPT クラス × SCF 動的登録)を踏襲した独立フルテーマとして横展開している。

目的が異なる 2 パターンを使い分けることで、量産型の店舗追加とブランド単位の独自機能開発の両方に対応できる構造にした。

レイヤーテーマ数継承関係主な内容
親テーマ salon-name/119 種の CPT/オプションページ定義・全テンプレート・SCSS・JS
店舗チャイルドテーマ
salon-name-xxxx
10Template: salon-namestyle.css 十数行のカラー上書きのみ(後述)
コンテンツ系独立テーマ
salon-name-ent / -model / -trend / -recruit / -magazine / -items
9独立(継承なし)各テーマが個別に post-type/ を持ち、親テーマと同じ CPT クラス設計を踏襲

カスタム投稿タイプ設計(19 モデル)

サロン運営に必要なコンテンツを投稿タイプ・タクソノミー・オプションページ単位で独立モデル化。post-type/ 配下にクラスファイルとして分離し、CPT 登録・タクソノミー登録・SCF フィールド定義の責務を分離した。

投稿タイプスラッグタクソノミー/特記事項
スタッフstaff役職・所属店舗タクソノミー、25 SCFフィールド+7カテゴリのランキング用フィールド
ヘアスタイルhairstyleスタイル・年代・テイスト・所属店舗・タグの5タクソノミー
クーポンcoupon3タクソノミー(カテゴリー/タイプ/メニュー)
メニューmenu公開終了日・表示順を管理画面の一覧列&クイック編集で直接編集可能に
口コミreview口コミカテゴリータクソノミー
ブログpost(標準)保存時にコンテンツ系サブサイトへタクソノミーを自動同期(後述)
こだわり/コラム/動画/サロン/キャンペーン等各種ページ単位・オプションページ単位で個別モデル化
店舗情報/トップページ/SEO設定オプションページページタイプ別に og: タグ・title・description を編集可能に

CPT クラス × SCF 動的登録パターン

フィールド定義をクラスの内部プロパティとして保持し、get_fields() で返す設計にすることで、CPT 登録・タクソノミー登録・SCF 登録の 3 責務を 1 クラスに閉じ込めた。新しい投稿タイプの追加は「クラスを1つ作って functions.php に2行足すだけ」で完結する。

// post-type/cpt-staff.php:フィールド定義をクラスで保持
class CPT_Staff {
    const SLUG = 'staff';
    const SCF_ID = 'scf-staff1';
    const SCF_ID2 = 'scf-staff-ranking'; // 部門別ランキング用に別セッティングを分離

    public function get_fields(){ return $this->fields; }  // 25フィールド
    public function get_ranking(){ return $this->ranking; } // 7カテゴリKPI
}

// functions.php 側:CPT登録
$staff = new CPT_Staff();
register_post_type(CPT_Staff::SLUG, $staff->get_param());
register_taxonomy(CPT_Staff::TAXONOMY, CPT_Staff::SLUG, $staff->get_tax());

// functions.php 側:SCFへの登録(19モデル分を1つのフィルタで集約)
add_filter('smart-cf-register-fields', 'my_register_fields', 10, 4);
function my_register_fields($settings, $type, $id, $meta_type) {
    if ($meta_type == 'post' && $type == CPT_Staff::SLUG) :
        $cpt_obj = new CPT_Staff();
        $Setting = SCF::add_setting(CPT_Staff::SCF_ID, CPT_Staff::SCF_TITLE);
        foreach ($cpt_obj->get_fields() as $group) :
            $Setting->add_group($group['group'], $group['repeat'], $group['settings']);
        endforeach;
        $settings[] = $Setting;
    elseif (...) : // 以下 18 モデル分、同一パターンで分岐
    endif;
    return $settings;
}

WordPress マルチサイト連携(ブランドサイトへのタクソノミー自動同期)

メインサイトでブログ記事を保存すると、switch_to_blog() で提携ブランドサイト(求人連携先「Entrance」等、サブサイトID指定)に切り替え、月別・カテゴリー・タグのタクソノミーを自動生成して restore_current_blog() で復元する。編集者はメインサイト側の操作だけで、連携先サイトのアーカイブ構造が自動的に追随する。

// inc/admin-functions.php
function after_post($post_ID, $post_after, $update){
    $entrance_ids = array(1, 11); // 連携先サブサイトID
    if ($post_after->post_type === 'post'):
        $ym_text = get_the_date('Ym', $post_after->ID);
        foreach ($entrance_ids as $ent_id):
            switch_to_blog($ent_id);
            if (!term_exists($ym_text, 'blog-month')) {
                wp_insert_term(get_the_date('Y年n月', $post_after->ID), 'blog-month', ['slug' => $ym_text]);
            }
            // カテゴリー・タグも同様に同期
            restore_current_blog();
        endforeach;
    endif;
}
add_action('save_post', 'after_post', 100, 3);

WordPress カスタム開発

functions.php 主要実装

1. 管理画面の一覧列・クイック編集拡張

メニュー・クーポンの「公開終了日」「表示順」を、投稿一覧のソート可能列として追加し、記事詳細を開かずに一覧のクイック編集から直接更新できるようにした。ソートは meta_value_date / meta_value_num による meta_query 変換で対応。

function custom_orderby_columns($vars) {
    if (isset($vars['orderby']) && 'display_order' == $vars['orderby']) {
        $vars = array_merge($vars, array(
            'orderby' => 'meta_value_num',
            'meta_query' => array(array(
                'key' => 'display_order', 'value' => array(0), 'compare' => '>',
            )),
        ));
    }
    return $vars;
}
add_filter('request', 'custom_orderby_columns');

// クイック編集欄にカスタムフィールドを直接表示
add_action('quick_edit_custom_box', 'display_my_quickmenu', 10, 2);
add_action('bulk_edit_custom_box', 'display_my_custom_quickedit', 10, 2);
add_action('save_post', 'save_custom_meta'); // nonce検証つき保存処理

2. 管理画面UIの不要要素排除・カスタムCSS注入

投稿編集画面から不要なメタボックス(カスタムフィールド・抜粋・コメント・リビジョン・SEOパックのメタボックス等)を一括削除し、管理画面専用CSSを admin_head にインライン注入することでプラグインUIの一部を編集者向けに調整。

function remove_default_post_screen_metaboxes() {
    remove_meta_box('postcustom', 'post', 'normal');
    remove_meta_box('aiosp', 'post', 'advanced'); // All in One SEO Pack
    // 他 6 種のメタボックスを削除
}
add_action('admin_menu', 'remove_default_post_screen_metaboxes');

3. ページタイプ別 SEO メタデータ生成

salon-name_get_metadata() が投稿タイプ(ブログ/クーポン/スタッフ/メニュー等)・タクソノミー・固定ページごとに条件分岐し、SCFオプションページで編集者が管理する title / description / keyword / h1 を組み立てて header.php で出力する。個別記事では抜粋自動生成、一覧ページではオプション設定値、というように出し分けている。

function salon-name_get_metadata() {
    $ret = array('title' => SCF::get_option_meta('scfex-shopinfo', 'scfex-shopinfo-shop-meta-title'), ...);
    if (is_post_type_archive('post')):
        $ret['title'] = SCF::get_option_meta('scfex-opt', 'scfex-opt-blog-title');
    elseif (is_singular('post')):
        $ret['title'] = get_the_title() . '|' . SCF::get_option_meta('scfex-opt', 'scfex-opt-blog-title');
        $ret['description'] = my_excerpt(160);
    elseif (is_post_type_archive('coupon') || is_singular('coupon')):
        // ... クーポン用の title/keyword/description
    elseif (is_tax('coupon-category') || is_tax('coupon-type') || is_tax('coupon-menu')):
        // ... タクソノミーアーカイブ用(10種類以上の分岐が続く)
    endif;
    return $ret;
}

スタッフ・スタイル ランキングシステム

スタッフ投稿タイプに、総合・ブログ閲覧数・アシスタント総合・口コミ・新規指名数・リピート率・指名数・シャンプー指名数という 8 種類の数値 KPI フィールドを SCF セッティングとして本体フィールドから分離登録(scf-staff-ranking)。page-ranking.php / page-staff-ranking.php / page-hairstyle-ranking.php で降順ソート表示し、店舗サイトと提携ブランドサイト(Entrance)で同じ数値を共有する運用にした。

管理者向け内製プラグイン

マルチサイト特有の運用課題に対応するため、テーマ内の機能追加に留まらず独立プラグインとして 2 本を内製。

プラグイン用途
image-associateアップロード済み画像とヘアスタイル投稿データを一括関連付けする管理画面ツール
style-migrateヘアスタイル投稿データ(hairstyle-shop タクソノミー単位)をサブサイト間で移行するバッチ処理ツール。WP_Query のバッチ取得+タイムアウト・メモリ上限の調整・処理ログの永続化を実装

テンプレート構成

テンプレート役割
single-staff.php / archive-staff.phpスタッフ詳細・一覧
single-hairstyle.php / archive-hairstyle.phpスタイル詳細(Slickギャラリー)・カタログ一覧
taxonomy-hairstyle-length.php / -new.php長さ/新着カテゴリー別絞り込み
page-ranking.php / page-staff-ranking.php / page-hairstyle-ranking.phpKPIランキング表示(SCF数値フィールドでソート)
archive-coupon.php / archive-menu.phpクーポン・メニュー一覧
page-movie.php / page-hairstyle-new.php特集ページ群

SCSS ビルド設計

vw相対計算 mixin と最小差分の子テーマ

フォントサイズや余白を vw 相対値に変換する mixin/function を _function.scss に集約し、320px幅を基準にスケーリングする設計にした。店舗チャイルドテーマは配色変数の上書きのみに絞り込み、8,600行超の親テーマに対して1店舗あたり十数行の差分で店舗ごとの見た目を表現している。

// _function.scss
@mixin tab { @media (min-width: 768px) { @content; } }

@mixin wfz($size) {
    $winW: 320;
    font-size: $size * (100 / $winW) * 1vw;  // SP基準のvw相対フォントサイズ
}

@function size($size) {
    @return $size * (100 / 1280) * 1%;  // PC基準の%相対サイズ
}
/* salon-name-xxxx/style.css(子テーマ全文の一部・17行) */
Theme Name:salon-name-xxxx
Template:salon-name

.header .btn a { background: #a49578; }
.footer .infoArea ul.btns li a { background: #a49578; }

ビルドコマンド

{
  "scripts": {
    "build-style.css": "node-sass scss/style.scss style.css -w --output-style expanded"
  }
}

JavaScript インタラクション

jQuery ベースで $.util 名前空間に共通処理(ロールオーバー画像・スムーズスクロール・フェード・ハンバーガーメニュー)を集約し、ページタイプごとに専用JSファイルを分離。functions.php 側の条件分岐エンキューで、必要なページにだけ Slick カルーセル等を読み込む構成にした。

ファイル役割
util.js全ページ共通($.util.rollover / scroll / fade / menu
index.jsトップページ Slick スライダー・Instafeed 初期化
styledetail.js / style.jsヘアスタイル詳細ギャラリー
menu.js / salon.js / movie.jsメニュー一覧・サロンページ・動画ページ個別の挙動
admin_quickedit.js管理画面クイック編集用(coupon/menu 投稿タイプのみ読み込み)
// util.js:名前空間パターンで機能を分離
$.util = {
    menu: function () {
        var $triger = $(".header #menu");
        var $nav = $(".header nav#navigation");
        $triger.click(function () {
            $nav.css("display") == "none" ? $nav.slideDown() : $nav.slideUp();
            return false;
        });
    },
    rollover: function (options) { /* data-role属性ベースのホバー画像切替 */ },
};

サブサイト実装:代表プロフィールページ

代表美容師のプロフィール・メディア掲載実績を紹介する自己完結型の静的ページ。

マルチサイト本体とは別運用だが、ヘッダー画像には usemap によるイメージマップを設置し、クリック領域からメイン WordPress サイトの記事URL(/topics/...)に直接遷移させることで、静的サイトと WordPress 側のコンテンツを違和感なく橋渡ししている。


まとめ

項目内容
種別ヘアサロングループ公式サイト(10店舗超・首都圏)
技術スタックWordPress マルチサイト + PHP / カスタムテーマ(フルスクラッチ)+ 内製プラグイン2本
テーマ設計親テーマ1 + 店舗チャイルドテーマ10(配色差分のみ)+ コンテンツ系独立テーマ9(同一設計パターンを横展開)
WordPress開発19投稿モデル・SCFクラス動的登録・switch_to_blogによるブランドサイト間タクソノミー自動同期・8KPIランキングシステム
管理画面UX一覧列ソート・クイック編集拡張・メタボックス整理・内製バッチ移行ツール
SCSSvw/%相対計算mixin・SPファースト設計・子テーマ差分最小化(数千行→十数行)
フロントエンドjQuery名前空間パターン + Slick + 条件付きエンキュー
運用範囲2016〜2025年の長期運用資産。稼働中領域(/wp//tanimoto/)と旧CMS遺構を明確に切り分けて保守