T.Sugiyama Official Blog

興味のあることを書いています。

【WordPress】カスタム投稿タイプでスケジュール表を作る

WordPress でカスタム投稿タイプを使ったので、メモ替わりにここに書いておきます。
普通の記事投稿画面では、タイトル、本文、サムネイル (画像)などが登録できるのですが、今回、ダンスレッスンのスケジュール表を作りたい!ということだったので、もっと簡単に登録できる画面が必要でした。

プラグインを使えば、使いやすい入力フォームでスケジュール表を作れるものもあるのですが、スケジュール表のデザイン、見た目の部分がなかなか思ったようにできず少し使い勝手が悪いなと思っていました。また、プラグインをカスタマイズするとなると、プラグインがアップデートされたときにどうなるんだろう?という心配もあったので、それならいっそのこと自分でそういうの作ろうと思い、調べてみました。
すると、出てきたワードが「カスタム投稿タイプ」。
これを使うと登録フォームをカスタマイズすることができるのだそうです。
そしてさらに、カスタム投稿タイプにカスタムフィールドを表示すれば、「名前」=>「値」を設定できて、
そうするとどうなるかというと、

「教室」=>「神戸」、
「講師」=>「◯◯先生」、
「レッスン」=>「HIP HOP初級」、
「曜日」=>「Mon」、
「開始時間」=>「20:00」、
「終了時間」=>「21:00」

というように、ラベルと情報を紐ずけられるのです。
そして、紐づけた情報によって、神戸教室だけのレッスンしか表示しない、この曜日のレッスンはここに表示する、開始時間が早い者順に表示する、などというように自由に情報を取ってこられるので、プラグインよりも自由なスケジュール表を作ることができるのです。
ただ、意気揚々と設定してから問題が!
試しに情報を登録したのですが、このカスタムフィールド、かなり使いにくい!

カスタムフィールド、かなり使いにくい...
カスタムフィールド、かなり使いにくい...
何が使いにくいかというと、いちいち毎回「名前」を入れないといけないのです。「値」は毎回でいいのですが、「名前」は毎回項目が決まっているのだから、いちいち入れるのはめんどくさい!
ということで辿り着いたのが、今回の方法です。
結論から言うと、↓ こう言う入力画面です。
入力フォームができました!
こういう入力画面を作りました
functions.php に ↓ こう言うコードを書きました。

/*
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
スケジュール
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
*/
//カスタム投稿タイプ(スケジュール)
  add_action( 'init', 'create_schedule' );
  function create_schedule() {
    $Supports = [
      'title',//名前
      'thumbnail',//顔写真
    ];
    register_post_type( 'schedule',
      array(
        'labels' => array(
          'name' => __( 'スケジュール' ),
          'singular_name' => __( 'Schedule' )
        ),
        'public' => true,
        'has_archive' => true,
        'menu_position' => 5,
        'supports' => $Supports
      )
    );
  }
//カスタムフィールド追加
  add_action('admin_menu','add_schedule_fields');
  function add_schedule_fields(){
    add_meta_box('custom-place_name', '教室', 'create_place_name', 'schedule', 'normal');
    add_meta_box('custom-instructor_name', '講師', 'create_instructor_name', 'schedule', 'normal');
    add_meta_box('custom-class_name', 'レッスン', 'create_class_name', 'schedule', 'normal');
    add_meta_box('custom-day_week', '曜日', 'create_day_week', 'schedule', 'normal');
    add_meta_box('custom-start_time', '開始時間', 'create_start_time', 'schedule', 'normal');
    add_meta_box('custom-finish_time', '終了時間', 'create_finish_time', 'schedule', 'normal');
  }
  function create_place_name() {
    $keyname = 'place_name';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    $data = ['神戸','伊丹'];
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<select name="'.$keyname.'" autofocus required=true>';
    echo '<option value="">-----</option>';
    foreach ($data as $d) {
      $selected = '';
      if ($d === $get_value) {
        $selected = 'selected';
      }
      echo '<option value="'.$d.'"'.$selected.'>'.$d.'</option>';
    }
    echo '</select>';
  }
  function create_instructor_name() {
    $keyname = 'instructor_name';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<input name="'.$keyname.'"value="'.$get_value.'"autocomplete="off" required=true>';
  }
  function create_class_name() {
    $keyname = 'class_name';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<input name="'.$keyname.'"value="'.$get_value.'"autocomplete="off" required=true>';
  }
  function create_day_week() {
    $keyname = 'day_week';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    $data = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<select name="'.$keyname.'" required=true>';
    echo '<option value="">-----</option>';
    foreach ($data as $d) {
      $selected = '';
      if ($d === $get_value) {
        $selected = 'selected';
      }
      echo '<option value="'.$d.'"'.$selected.'>'.$d.'</option>';
    }
    echo '</select>';
  }
  function create_start_time() {
    $keyname = 'start_time';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    $t = strtotime('00:00');
    for ($i=0; $i <5*12*24 ; $i+=10) { 
      $data[] = date('H:i',strtotime("+{$i} minutes",$t));
    }
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<select name="'.$keyname.'" required=true>';
    echo '<option value="">-----</option>';
    foreach ($data as $d) {
      $selected = '';
      if ($d === $get_value) {
        $selected = 'selected';
      }
      echo '<option value="'.$d.'"'.$selected.'>'.$d.'</option>';
    }
    echo '</select>';
  }
  function create_finish_time() {
    $keyname = 'finish_time';
    global $post;
    $get_value = get_post_meta($post->ID, $keyname, true);
    $t = strtotime('00:00');
    for ($i=0; $i <5*12*24 ; $i+=10) {
      $data[] = date('H:i',strtotime("+{$i} minutes",$t));
    }
    wp_nonce_field('action-' . $keyname, 'nonce-' . $keyname);
    echo '<select name="'.$keyname.'" required=true>';
    echo '<option value="">-----</option>';
    foreach ($data as $d) {
      $selected = '';
      if ($d === $get_value) {
        $selected = 'selected';
      }
      echo '<option value="'.$d.'"'.$selected.'>'.$d.'</option>';
    }
    echo '</select>';
  }

  add_action( 'save_post', 'save_schedule_fields' );
  function save_schedule_fields($post_id){
    $schedule_fields = ['place_name','instructor_name','class_name','day_week','start_time','finish_time'];
    foreach ($schedule_fields as $d) {
      if (isset($_POST['nonce-'.$d])&&$_POST['nonce-'.$d]) {
        if (check_admin_referer('action-'.$d, 'nonce-'.$d)) {
          if (isset($_POST[$d])&&$_POST[$d]) {
            update_post_meta($post_id,$d,$_POST[$d]);
          }else{
            delete_post_meta($post_id,$d,get_post_meta($post_id,$d,true));
          }
        }
      }
    }
  }

↑ functions.php に記述したコード
そうすると、管理画面の左端のメニューに、「スケジュール」が表示されます。

カスタム投稿タイプ「スケジュール」が表示されました!
カスタム投稿タイプ「スケジュール」が表示されました!
そして、「スケジュール」を開くと、入力フォームができています!
入力フォームができました!
入力フォームができました!
ここに入力した情報を表示ために、今回は page-scheduletable_kobe.php に下のコードを書きました。

<?php
/*
Template Name: スケジュールテーブル(神戸)
*/
$days = ['mon','tue','wed','thu','fri','sat','sun'];
foreach ($days as $d) {
  $$d = [ 'post_type'=>'schedule',
          'meta_query' => [
            ['key' => 'place_name', 'value' => '神戸'],
            ['key' => 'day_week', 'value' => ucwords(strtolower($d))],
            'time_clause' => ['key' => 'start_time']
          ],
          'orderby' => ['time_clause' => 'ASC']
        ];
}
$i=0;
get_header();
?>
  <div id="primary" class="content-area">
    <main id="main" class="site-main mx-auto">
      <div id="schedule-title">
        <div class="page-title">
          <h1>SCHEDULE-KOBE</h1>
          <h2>神戸校スケジュール</h2>
        </div>
      </div>
      <div class="container">
      <?php custom_breadcrumb(); ?>
        <div class="row mx-auto my-md-4">
        <?php
          foreach($days as $day):
            $day = new WP_Query($$day);
        ?>
          <div class="day-col mx-auto text-center">
            <h2 class="text-light p-1 mb-3"><?= ucwords(strtolower($days[$i])); $i++; ?></h2>
            <?php
              if ($day->have_posts()): while($day->have_posts()): $day->the_post();
                $instructor_name = get_post_meta($day->post->ID, 'instructor_name', true);
                $class_name = get_post_meta($day->post->ID, 'class_name', true);
                $start_time = get_post_meta($day->post->ID, 'start_time', true);
                $finish_time = get_post_meta($day->post->ID, 'finish_time', true);
            ?>
            <div class="container my-3 border">
              <div class="row">
                <?php if (has_post_thumbnail()): ?><!-- サムネイル画像があれば表示する -->
                <div class="schedule-thumbnail p-0 col-4 col-md-12 mx-auto"><?php the_post_thumbnail(); ?></div><!-- /schedule-thumbnail -->
                <?php else: ?><!-- 画像がなければNo Imageと表示する -->
                <div class="bg-light border p-0 col-4 col-md-12"><span class="d-block schedule-thumbnail">No Image</span></div>
                <?php endif; ?>
                <div class="schedule-contents p-0 col-8 col-md-12 mx-auto">
                  <h3 class="my-2 my-md-1 h6 small font-weight-bold">
                    <?= ($instructor_name!=="")?esc_html($instructor_name):'no name'; ?>
                  </h3>
                  <p class="my-0 mx-3 mx-md-0 p-1 font-weight-bold bg-secondary text-white"><?= ($class_name!=="")?esc_html($class_name):'undefined lesson'; ?></p>
                  <p class="py-2 m-0">
                    <span><?= ($start_time!=="")?esc_html($start_time):'--:--'; ?></span>
                    <span>-</span>
                    <span><?= ($finish_time!=="")?esc_html($finish_time):'--:--'; ?></span>
                  </p>
                </div><!-- /schedule-contents -->
              </div><!-- /row -->
            </div><!-- /container -->
            <?php endwhile; ?>
            <?php else: ?>
              <div class="p-5 p-md-1"><span>レッスンはありません</span></div>

            <?php endif; ?>
          </div>
        <?php endforeach; ?>
        </div><!-- /row -->
      </div><!-- /container -->
    </main><!-- #main -->
  </div><!-- #primary -->
<?php
// get_sidebar();
get_footer();

↑ page-scheduletable_kobe.php に記述したコード
試しにこのフォームから登録してみると、こう。

スケジュールの登録
スケジュールの登録
ちゃんと教室や曜日なども選択できるようになっています。
そしてそれが表示されるページがこう。
スケジュールの表示
スケジュールの表示
サムネイル画像が登録されないと「No Image」と表示されるようにしています。
この方法を使うことによって、WordPress の投稿画面をもっと簡単に、めんどくさくないようにすることができ、もっと自由に思い通りのシステムが作れると思います。

Laravel超初級でつまづいたこと【解決済み】

PHPフレームワークの一つであるLaravelと、ユーザーインターフェイスを構築するためのプログレッシブフレームワークであるVue.jsを勉強しようと思い、こちらのブログを参考にさせて頂き、自分でもやってみました。↓↓↓

Laravelとvueの連携!データベース内の一覧を表示する方法 – console dot log

Laravelのマイグレーションを使ってお笑い芸人のコンビ名のデータベースを作成し、Vue.js で表示さよう!というものです。至ってシンプル!

 

ということで、早速やってみたのですがーー。

 

すぐつまづくんですよね。その辺の道で言うと、ほんのちょっとした段差やのに...。ゆっくりと、しっかりと足上げて歩いてるつもりなのになんでつまづくねん!と、自分の知識不足、力不足に辟易しながら、もう同じところでつまづかないようにここに書いて、段差を取り除いておこうと思います。

 

何につまづいたか?

 まずつまずいたのは、テストデータを作った後のマイグレーションの実行です。(その前にmysqlにデータベースを作っていなくてエラーが出たのは、ここでは割愛。)

php artisan migrate

 このコマンドでテーブルができるはずなのですが...

テーブルが作成されなかったのです!

よくよく見てみると...


[vagrant@toshikisugiyama laravel_vue]$ cat database/migrations/2018_12_10_010547_create_comedians_table.php
<?php 

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateComediansTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comedians', function (Blueprint $table) {
            $table-&gt;increments('id');
            $table-&gt;increments('name');
            $table-&gt;timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comedians');
    }
}

追加した名前フィールドがstringではなくincrementsになってる...。

stringにして再度php artisan migrate


[vagrant@toshikisugiyama laravel_vue]$ php artisan migrate;
Migrating: 2018_12_10_010547_create_comedians_table
Migrated:  2018_12_10_010547_create_comedians_table
[vagrant@toshikisugiyama laravel_vue]$

いけました。


mysql> show tables;
+--------------------------+
| Tables_in_laravel_vue_db |
+--------------------------+
| comedians                |
| migrations               |
+--------------------------+
2 rows in set (0.00 sec)
mysql> desc comedians;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255)     | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql>

データも入っています!

 

しかしそれだけでは終わらなかった。

次はphp artisan db:seedです。


[vagrant@toshikisugiyama laravel_vue]$ php artisan db:seed
Seeding: ComediansTableSeeder

   ReflectionException  : Class ComediansTableSeeder does not exist

  at /home/vagrant/laravel_vue_lesson/laravel_vue/vendor/laravel/framework/src/Illuminate/Container/Container.php:779
    775|         if ($concrete instanceof Closure) {
    776|             return $concrete($this, $this->getLastParameterOverride());
    777|         }
    778|
  > 779|         $reflector = new ReflectionClass($concrete);
    780|
    781|         // If the type is not instantiable, the developer is attempting to resolve
    782|         // an abstract type such as an Interface of Abstract Class and there is
    783|         // no binding registered for the abstractions so we need to bail out.

  Exception trace:

  1   ReflectionClass::__construct("ComediansTableSeeder")
      /home/vagrant/laravel_vue_lesson/laravel_vue/vendor/laravel/framework/src/Illuminate/Container/Container.php:779

  2   Illuminate\Container\Container::build("ComediansTableSeeder")
      /home/vagrant/laravel_vue_lesson/laravel_vue/vendor/laravel/framework/src/Illuminate/Container/Container.php:658

  Please use the argument -v to see more details.

こうですね。

ComediansTableSeederクラスが無いようです。でも、作ってるんです。

調べると、どうやらcomposer dump-autoloadするといいようです。


[vagrant@toshikisugiyama laravel_vue]$ composer dump-autoload
-bash: composer: コマンドが見つかりません

でもcomposerコマンドが見つからない!


[vagrant@toshikisugiyama laravel_vue]$ php composer.phar
Could not open input file: composer.phar
[vagrant@toshikisugiyama laravel_vue]$ which composer
/usr/bin/which: no composer in (/home/vagrant/.pyenv/plugins/pyenv-virtualenv/shims:/home/vagrant/.pyenv/shims:/home/vagrant/.pyenv/bin:/home/vagrant/.rbenv/shims:/home/vagrant/.rbenv/bin:/home/vagrant/.nodebrew/current/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin)
[vagrant@toshikisugiyama laravel_vue]$ echo $PATH
/home/vagrant/.pyenv/plugins/pyenv-virtualenv/shims:/home/vagrant/.pyenv/shims:/home/vagrant/.pyenv/bin:/home/vagrant/.rbenv/shims:/home/vagrant/.rbenv/bin:/home/vagrant/.nodebrew/current/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin

パスが通ってないのか?


[vagrant@toshikisugiyama laravel_vue]$ ls /usr/local/bin/composer
composer.phar

解決方法は↓これでした。

composer: command not found と Could not open input file :/usr/local/bin/composer.phar からの対処 - Qiita


[vagrant@toshikisugiyama laravel_vue]$ alias composer="php /usr/local/bin/composer/composer.phar"

再度、


[vagrant@toshikisugiyama laravel_vue]$ composer dump-autoload
Generating optimized autoload files> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: beyondcode/laravel-dump-server
Discovered Package: fideloper/proxy
Discovered Package: laravel/nexmo-notification-channel
Discovered Package: laravel/slack-notification-channel
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
Generated optimized autoload files containing 3743 classes

そして、


[vagrant@toshikisugiyama laravel_vue]$ php artisan db:seed
Seeding: ComediansTableSeeder
Database seeding completed successfully.

たどり着きました!


mysql> select * from comedians;
+----+-----------------------------------+---------------------+---------------------+
| id | name                              | created_at          | updated_at          |
+----+-----------------------------------+---------------------+---------------------+
|  1 | ビートたけし                      | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  2 | タモリ                            | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  3 | 明石家さんま                      | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  4 | ダウンタウン                      | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  5 | とんねるず                        | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  6 | ウッチャンナンチャン              | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  7 | 爆笑問題                          | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  8 | ナインティナイン                  | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
|  9 | ロンドンブーツ1号2号            | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
| 10 | キングコング                      | 2018-12-10 02:35:37 | 2018-12-10 02:35:37 |
+----+-----------------------------------+---------------------+---------------------+
10 rows in set (0.00 sec)

データも入ってます!

その後、php artisan serve --host=192.168.33.10 --port=8000 のところや、「viewが見つかりません」のエラーを乗り越え、

無事、

お笑い芸人のコンビ名リストができました!

お笑い芸人のコンビ名リストができました!

できました!

今回使ったのは、Laravel、Vue.js、bootstrap です。

ホテルの朝食から始まる

ホテルに泊まると朝食無料券が貰えました。ドリンクのオプションはコーヒーかオレンジジュース。コーヒーを注文すると、出て来たのは、お湯とレトルトコーヒーの粉。あとはトースト、目玉焼き、ベーコン。

f:id:ToshikiSugiyama:20180402103940j:plainf:id:ToshikiSugiyama:20180402104028j:plain

f:id:ToshikiSugiyama:20180402104137j:plainf:id:ToshikiSugiyama:20180402104232j:plain

昼前にチェックアウトし、歩いて空港へ。空港までは15分といったところでしたが、歩いているとタクシーが乗らないか?とクラクションを何回も鳴らされました。

 

空港に着いたら昼ご飯。ということで、ジョリービーでバーガーとポテトを食べました。これで81php。確かに安い。

f:id:ToshikiSugiyama:20180402104425j:plainf:id:ToshikiSugiyama:20180402104509j:plain

集合場所は空港のセブンイレブン前。7人一緒にバンに乗り、宿泊施設へ。

着いたら、その内の3人と辺りを探索し、夕食。ITパークの中の店で、ビッグなバーガー食べました。 

f:id:ToshikiSugiyama:20180402104858j:plain

 

ちなみにルームメイトは台湾人のtienさん。すごい良い人。

 

9時半から歓迎会。メンバーズ、ギークスの2社の研修生も合わせて約5060人。

今までで一番多いみたい。

個人で来た人は、やっぱり起業を考えてる人もいて、いろいろ面白そう。

関空からセブへ

Jetster Asia airwaysとPhilipine Airlinesを乗り継ぎ、約7時間掛けてセブに来ました。

預ける荷物は予約もできるのですが今回は予約せず、関空5,000円、クラーク国際空港で3,000フィリピンペソ (php) 払いました。

30日以上の滞在は、ビザの延長が必須です。

ビザの延長は日本でも出来るし、1ヶ月のセブ滞在後にも出来ます。

ちなみに、クラーク国際空港での荷物預けはクレジットカード使えません。両替しときましょう。

ゲートが開くまで、喉が渇いていたので、ヤクルトミルクティーのライチ味を飲みました。ゲートの前に23店舗ドリンクが売ってる店が出ています。

f:id:ToshikiSugiyama:20180401120740j:plain

そしていよいよゲートからバスに乗って乗り込みへ。ゲートの前で偶然出会った、とんねるず木梨風の男性がいろいろと気にかけてくれました。彼はセブ出身らしいのですが、とても気さくで親切でした。セブの人すごい親切!セブに着いてもチラチラとこちらを気に掛けてくれました。

優しかったといえば、セブに着いてから荷物受取所で出会った女性。木梨さんとバトンタッチするかのように現れた彼女でしたが、僕の予約しているホテルを友人に電話できいてくれたり、タクシー乗り場まで案内してくれたり、しまいにはタクシーの運転手に場所を言ってくれて、僕は乗って着いたらお金を払うだけでオッケーというところまでしてくれました。優しい。

タクシーの運転手はセブ語を教えてくれたり、夜ご飯が食べられそうな店を教えてくれたり、そうこうしてるうちにホテルへ到着。100 php でした。

結局ホテルの向かいのレストランで食事。ちょうど一緒に入った、フランスからの兄妹と食べました。食べたのはフィリピンの麺とビール。味が濃くて油の多い焼きそばっていう感じでした。

そう言えば、後から来て、隣にいたカップルもフランス人。セブ島、フランス人に人気なのか?それともこのホテルがフランス人に人気なのか?それはさておき、とりあえずセブ入りには成功しました。