[concrete5 サイト制作] 社会福祉法人 恩賜財団 東京都同胞援護会

社会福祉法人 恩賜財団 東京都同胞援護会のサイトを10年ぶりにリニューアルしました
http://doen.jp/

昔MTで作ったせいで更新がすごく大変だったものをconcrete5にしてクライアントの更新作業が楽になるように設計。
もうずっとリニューアルしたかったんですよ

Screen Shot 2017-04-03 at 16.15.24-fullpage

Screen Shot 2017-04-03 at 16.15.39-fullpage

Screen Shot 2017-04-03 at 16.16.09-fullpage

Screen Shot 2017-04-03 at 16.16.03-fullpage

やったこと

  • デザイン
  • HTML/CSS/JSコーディング
  • レスポンシブ
  • concrete5組み込み
  • フォーム作成(concrete5外)
  • 承認フロー
  • 運用マニュアル作成

つらかったこと

  • 同じフォーマットにひたすら住所やら電話番号やら座標やらを入力していく作業
    →xmlかcsvでインポーター使えばよかったと反省。あるいはデータ投稿だけしてもらうバイトを雇えばよかった。
  • 承認フローがなかなかうまく機能してくれなくてどハマり
    →参考文献がなかったのと、フォーラムに質問するということを考えなかったので反省。次からはググってもわからない問題はフォーラムに質問しよう。
  • さくらサーバーで通知メールが飛ばない問題
    →もうどうしようもなくてフォーラムに相談して解決してもらった。

更新性が高い設計にするために汎用性をもたせつつ、デザインを両立させるという作業に対して、非常にモチベーションが高く保てたかな、と。
今回制作ページは160ページほどで、concrete5で作らなかったらおそらく3倍くらいの作業時間が必要だったのではないかなーと思います。

リリースしたおかげで今月は暇になったからゆっくり療養します。もう体がバキバキ。

[concrete5] Ver8.0.3 さくらのレンタルサーバーだとメール通知が送れない問題[解決]

表題の通り、さくらのレンタルサーバーでメール通知が送れない問題が発生して、ログを見てもわからないし、さくらの管理画面にエラーログは出力されないしで、全く原因がわからず1日半くらい費やしてしまってからフォーラムに相談してやっと解決できたのでご紹介します。
フォーラムで教えてくださった方に感謝。

concrete5 JAPAN フォーラム コメント欄のメール通知が送られない

おそらくさくらのレンタルサーバーでのセキュリティで、送信元ドメインが不正だとphp関数からメールが送れない、というのが原因だったのかと思います。

環境は以下

  • さくらレンタルサーバー ビジネス
  • PHP 5.6.30
  • mySQL 5.5
  • concrete5 8.0.3

確認した送れない通知メールは以下

  • コメント欄ブロックの通知
  • メール送信テスト
  • パスワード再発行のメール

なお以下の通知メールは送れていました。

  • ワークフローの通知
  • フォームブロックからの投稿通知

application/config/generated_overrides/concrete.phpに以下を追記して対処しました。

<?php

/**
 * -----------------------------------------------------------------------------
 * Generated 2017-02-27T15:34:58+09:00
 *
 * DO NOT EDIT THIS FILE DIRECTLY
 *
 * @item      cache.full_page_lifetime_value
 * @group     concrete
 * @namespace null
 * -----------------------------------------------------------------------------
 */
return [
    'locale' => 'ja_JP',
    'version_installed' => '8.0.3',
    'version_db_installed' => '20161216000000',
    'misc' => [
        'login_redirect' => 'DESKTOP',
        'access_entity_updated' => 1488174725,
    ],
    'cache' => [
        'blocks' => false,
        'assets' => false,
        'theme_css' => false,
        'overrides' => false,
        'pages' => '0',
        'full_page_lifetime' => 'default',
        'full_page_lifetime_value' => null,
    ],
    'theme' => [
        'compress_preprocessor_output' => false,
        'generate_less_sourcemap' => false,
    ],
    
    
    // さくらレンタルサーバー用メール通知 ここから
    'email' => array(
        'enabled' => true,
        'default' => array(
            'address' => 'concrete5-noreply@' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'),
        )
    ),
    // さくらレンタルサーバー用メール通知 ここまで    
    
];

[concrete5]concrete5.6はphp5.6のサーバだと動かなかった

スクリーンショット 2016-01-04 4.33.46

もともとphp5.3の環境で動いていたconcrete5.6を、php5.6が動いているサーバーに移行したところ、
こんなエラーが出て使えなくなってしまったので、エラー文の部分をコメントアウトしたら直った。

/concrete/libraries/3rdparty/Zend/Cache/Core.php on line 361

 if ($this->_options['automatic_serialization']) {
            // we need to serialize datas before storing them
            $data = serialize($data); 
        } 


 if ($this->_options['automatic_serialization']) {
            // we need to serialize datas before storing them
            // $data = serialize($data); 
        } 

原因は調査中。

あんまりコアファイルをいじりたくないけど、もう5.6をこれ以上アップデートすることもないし、サイトリニューアルの時にはconcrete5.7でやりましょうと提案すれば問題はなさそう。

余談。5.6がゲシュタルト崩壊しそう

[Concrete5]上級権限モードとは別に、テーマ側でユーザ判別したい時。

昨日Concrete5部で上級権限モードの話をしていたので、僕も何か書こうと思います。

上級権限モードは、ページの編集権限から、個別のエリアの編集や追加権限など、非常に事細かに設定ができるすぐれものなのですが、設定が非常に大変です。権限の設定だけでひと財産築けるんじゃないかなって思うくらい大変です。ジョークです。

そして、今回は上級権限モードとは関係の無い話でした。

一体なにがしたいのか

下記のコードを実際に使っているのですが、一体何をしようとしているのか。
「編集モードだったら+ユーザーがadminだったら、エリアを表示する」
という書き方をしています。

理由は以下の通り

  • スマホ版のテーマが別にある(レスポンシブではない)。
  • スマホ版の編集もPCで行う。
  • PCとスマホで表示する内容が異なる。
  • このエリアが編集できるのは、「admin」さんだけ。

つまり、レスポンシブではなく、モバイル用のテンプレートを別で用意していて、それぞれに別の情報を表示したい、でもエリアを表示できるのはadminさんだけに限定したい、というとてもニッチな状況で下記のコードで対処しました。

PC側のテンプレート

/themes/pc/elements/header.php
<?php
if ($c->isEditMode() && $u->getUserName() == 'admin') {
global $c;
$a = new GlobalArea('sp area');
$a->display($c);
} else {
} ?>

スマホ側のテンプレート

/themes/sp/elements/header.php
<?php
$a = new GlobalArea('sp area');
$a->display($c);
?>

それで、できたあとに気づいたのですが、ブロックテンプレートにmobile_detectを使うとか、そもそもadminさんしか編集できないならテーマにハードコーディングしてしまえばよかったなと思いました。

追記

ツイッターでacliss19xxさんから教えてもらいました!

「admin」さんだけだったら、スーパーユーザーだけって書いた方がいいですね。

PC側のテンプレート

/themes/pc/elements/header.php
<?php
if ($c->isEditMode() && $u->isSuperUser()) {//←ここ
global $c;
$a = new GlobalArea('sp area');
$a->display($c);
} else {
} ?>

[Concrete5]ログイン時のアップデート画面を非表示に

バージョン5.6.x
Concrete5はログイン時に、アップデート画面が表示されますが、お客さんに納品後に消して欲しいという要望をいただきまして。
重要な情報が載っていることもあるので、あまり消さない方がいいんですが、消し方はありまぁす。

/config/site.phpに一行追加するだけ。

define('ENABLE_NEWSFLOW_OVERLAY', false);

こんな感じ。

[Concrete5]ログイン時や編集モード時で表示を切り替えよう[メモ]

バージョン:5.6.3.1

自分用メモ的な。

ログインしている or していない

ログインして”いる”時だけ表示

ログインは登録ユーザーしかできないので、クライアントへのメモ的な扱いで残しておくような用途で使えます。

<?php
$u = new User();
if ($u->isRegistered()) { ?>
ここに書いたものはログインして"いる"時だけ表示されます
<?php } ?>

ログインして”いない”時だけ表示

ログインしていない時なので、一般のユーザーが閲覧する際にだけ表示するものを指定できます。
あんまり意味がないと思われますが、最近使いました。

<?php
$u = new User();
if (!$u->isRegistered()) { ?>
ここに書いたものはログインして"いない"時だけ表示されます
<?php } ?>

編集モード or 非編集モード

編集モード時だけ表示

以前、[Concrete5]エリア内にブロックがある時にだけ表示する[メモ]でも使用しました。
編集モード時だけに表示したいものは結構あります。編集時にはヘッダーにメニューバーが表示されるので、メニューバー分の余白を開けるのにもデフォルトのテーマで設定されていますよね。

<?php if ($c->isEditMode()) { ?>
ここに書いたものは編集モード時だけ表示されます
<?php } ?>

非編集モード時だけ表示

編集モードで見えず非編集モードで見えるということは、編集モードでどう頑張っても消したりできないという、クライアントの混乱の元になるのでやめましょう。

<?php if (!$c->isEditMode()) { ?>
ここに書いたものは非編集モード時だけ表示されます
<?php } ?>

[EC-CUBE + Concrete5] ECキューブの商品情報をConcrete5で利用する

タイトル通りですが、もうちょっと詳しく説明しますと、
「ECキューブに商品登録されている商品情報をxmlで取得してきて、Concrete5の編集でブロックに個別の商品IDを入力すると表示する」といった感じでしょうか。
ECキューブのページ作成機能がお客さんには使いづらいという感想だったので、Concrete5で編集・商品リストの作成は任意で行ってもらうようにしました。

バージョン:
ECキューブ 2.13.3
Concrete5 5.6.3.1

ECキューブAPIの取得方法については、下記のサイトを参考にさせていただきました。
EC-CUBEのAjax APIを使う(2) −−− 商品情報取得

なお、今回は同一サーバー、同一ドメイン内にECキューブとConcrete5をインストールしています。

ECキューブのAPIを使う

ECキューブには最近のバージョンでAPI機能が追加されたので、APIを利用して商品情報を取得できるようにします。

管理画面の「システム設定>パラメーター設定」にある「API_ENABLE_FLAG」を「true」にします。

xml.php

ECキューブのAPIは以下のURLでリクエストが送れます。

http://ドメイン/api/出力フォーマット.php

出力フォーマットは、JSONとxmlとphpの三種類あります。
今回はxmlを使用しました。

http://ドメイン/api/xml.php

これだけだと、下記のようにFalseが返ってきてなにも表示することができませんので、パラメータを設定する必要があります。

パラメータを含めたリクエストURL

http://ドメイン/api/xml.php?Operation=ItemLookup&Service=abc&ItemId=商品ID

Operation オペレーションの種類を指定します
Service 指定しないとエラーになります。ただし何も使われていないので適当な値を入力すれば大丈夫

Operationの種類は多数あるのですが、今回は「ItemLookup」を使用しました。
ItemLookupは、商品IDをキーに、商品詳細情報を取得します。商品IDは「ItemId」へ入力します。

これで商品情報の取得が可能です

Concrete5での設定 Desginer content

Concrete5側では、お客さんに商品IDを入れてもらうだけにするために、「Designer content」アドオンを利用します。
インストール方法などは省きます。

Designer contentで、新しいブロックを作成します。

Block Handleとブロック名は任意で、Add Fieldから「Text Box」を選択し、「Editor Label」に「商品ID」と入力。ダッシュボードでのDesigner contentで行う作業はこれだけです。

作成したブロックをカスタマイズ

Designer contentで作成したブロックのフロントテンプレートは、コンクリインストールフォルダの「/blocks/」ディレクトリ内に作成されます。
上記の例だと「/blocks/product_item」というディレクトリができているかと思います。
この中にあるview.phpがフロントテンプレートになります。

view.phpには以下のphpが書かれていますので、これをどうにかして商品情報を表示するように設定します

<?php  defined('C5_EXECUTE') or die("Access Denied.");
?>

<?php  if (!empty($field_1_textbox_text)): ?>
	<?php  echo htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET); ?>
<?php  endif; ?>

「echo htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET);」が、ブロックに入力された文字を表示するので、ここに商品IDを入力してもらっていれば、phpからxmlへリクエストを送る際の必要な商品IDをそのまま利用できるということになります。

<?php
$productID = htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET); //編集画面で入力された商品ID
$xml = simplexml_load_file('/api/xml.php?Operation=ItemLookup&Service=abc&ItemId=' . $productID); //APIへのリクエスト


xmlから必要な情報を取り出して変数に入れる

<?php
$productURL = $xml->Item->DetailPageURL;
$productName = $xml->Item->Title;
$productThumb = $xml->Item->ItemAttributes->main_list_image;
$productPrice = $xml->Item->ItemAttributes->price02_max;
?>


htmlのマークアップはお好みでどうぞ。

<p class="item_image"><a href="<?php echo $productURL;?>"><img src="/upload/save_image/<?php echo $productThumb;?>" alt="<?php echo $productName;?>"></a></p>
<p class="checkItemname"><a href="<?php echo $productURL;?>"><?php echo $productName;?></a></p>
<p class="price"><em><?php echo $productPrice;?></em>円</p>

補足

ちなみに、同一ドメインでも、apiへのリクエストが「/api〜〜」になっていると「I/O warning : failed to load external entity」が出る事があったので、apiリクエストを「http〜〜」になるようにしました。

<?php
$domain = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"] ; //ドメイン取得
$productID = htmlentities($field_1_textbox_text, ENT_QUOTES, APP_CHARSET); //編集画面で入力された商品ID

$xml = simplexml_load_file($domain . '/api/xml.php?Operation=ItemLookup&Service=abc&ItemId=' . $productID); //APIへのリクエスト

[Concrete5]エリア内にブロックがある時にだけ表示する[メモ]

実は、Concrete5公式活用ガイドブックの172ページに記載があるのですが、ちょっと気になった部分がありました。

エリアが空の時には何も表示しない要素を作る記述で以下の様なコードが載っていたのですが、

<?php
$a = new Area('Main');
if ($a->getTotalBlocksInArea($c) > 0) : ?>
<h1>この見出しはエリア内に何かブロックが配置されているときだけ表示されます</h1>
<?php
endif;
$a->display($c);
?>

これだと初めての編集時にエリアが表示されなかったんです。
編集時にエリアが表示されない = いつまでも編集できない。
つまりこのエリアはいつになっても表示される事がなくなってしまうのではないか。

で、編集モード時には表示されれば問題がないので、$c->isEditMode() を使って下記のようにしました。

<?php 
$a = new Area('Main');
if ($c->isEditMode() | $a->getTotalBlocksInArea($c) > 0){ ?>
<h1>この見出しはエリア内に何かブロックが配置されているときだけ表示されます</h1>
<?php
$a->display($c);
}
?>

これで安心。夜も眠れます。

[追記]

if文の閉じ括弧の部分について、ご指摘ありがとうございました。

下記の様なデザインを想定した場合に、ブロックが無い場合にはsectionごと非表示という方法は、ガイドブックに載っている方法では対応できませんでした。

<?php 
$a = new Area('Main');
if ($c->isEditMode() | $a->getTotalBlocksInArea($c) > 0){ ?>
<section>
<h1>この見出しはエリア内に何かブロックが配置されているときだけ表示されます</h1>
<?php
$a->display($c); // *1
?>
</section> *2
<?php } ?>

*1より前に閉じ括弧があると、*2の</section>タグが出力されてしまいます。
phpの問題というより、htmlのマークアップの問題ですね。
このような状況を想定しているため、先述のコードとなりました。

[Concrete5]オートナビにページの説明文を表示[メモ]

Concrete5、すごく便利です。
ただ一点、日本語のドキュメントというか、まだユーザが少ないのでwordpressみたいにググればすぐにヒットするような状況ではなくて、一つ一つ手探りでやるんですが、それでもダメでいつもSさんに頼っている次第でございます。

ページリストでやれば説明文は簡単に出せる、でも。

ページリストブロック

Concrete5のページリストブロックには、デフォルトで個別のページの説明文を表示する記述がされているので、ブロック設置時点で表示非表示はワンクリックで出来てしまいます。すげー。

そう、ページリストを使えば全く問題なくページのリンクリストと説明文を表示出来てしまうのですが、ページリストはデフォルトでは階層構造が出せないんです。

↓こんなマークアップがしたいのに。

<ul>
  <li><a href="oya">親ページ</a>
    <ul>
      <li><a href="ko">子ページ</a>
      <ul>
        <li><a href="mago">孫ページ</a>
      </ul></li>
    </ul></li>
</ul>

ページリストだと「親ページ > 子ページ > 孫ページ」と言った階層順に上記のようにマークアップしつつ一覧にする事ができない。
実際に出てくるマークアップは、↓のようになってしまいます。
<ul>
<li><a href="oya">親ページ</a></li>
<li><a href="ko">子ページ</a></li>
<li><a href="mago">孫ページ</a></li>
</ul>

全部並列になってしまう。
ページリストで階層を出したい場合は、カスタムテンプレートをいじる必要があります。

じゃあ、オートナビを使いましょう

オートナビ

階層構造をマークアップしたいのであれば、ページリストのカスタマイズをするより、オートナビを使用すれば何も考えなくても勝手に階層順にしてくれます。

うほっ!超らくちん!

と最初わたしは思いましたが、今回の案件で、お客さんの要望としては、「最下層にあるページの説明をリストに追加したい。」ということですので、上記のマークアップの例で言うと、下記の3点が大きな要件でした。

  1. 孫ページに説明文を表示
  2. 孫ページに説明文が入力されていない場合は非表示
  3. 親と子ページには説明文を表示しない

オートナビに説明文を表示する機能はデフォルトでついていないので、どうにかしてデータを引っ張ってこないといけないのです。

さて、どうしたもんか。

やっぱりカスタムテンプレートは使わないといけないのであった。

オートナビのデフォルトテンプレートは以下のような記述になっています。

echo '<ul class="nav">'; //opens the top-level menu
foreach ($navItems as $ni) {
	echo '<li class="' . $ni->classes . '">'; //opens a nav item
	echo '<a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a>';
	if ($ni->hasSubmenu) {
		echo '<ul>'; //opens a dropdown sub-menu
	} else {
		echo '</li>'; //closes a nav item
		echo str_repeat('</ul></li>', $ni->subDepth); //closes dropdown sub-menu(s) and their top-level nav item(s)
	}
}
echo '</ul>'; //closes the top-level menu

階層に応じて、ifがあって、表示する階層分だけ繰り返すように書かれています。
concrete5のブロックというか、テンプレのファイルには結構な量のコメントが書かれていて、それを読めばどうやってデータを取得出来るかがわかりやすいです。
コメント内に、下記のようなインフォメーションが記載されているので、それを参照しつつ、テンプレートを書き換えます。

 * Each nav item object contains the following information:
 *	$navItem->url        : URL to the page
 *	$navItem->name       : page title (already escaped for html output)
 *	$navItem->target     : link target (e.g. "_self" or "_blank")
 *	$navItem->level      : number of levels deep the current menu item is from the top (top-level nav items are 1, their sub-items are 2, etc.)
 *	$navItem->subDepth   : number of levels deep the current menu item is *compared to the next item in the list* (useful for determining how many <ul>'s to close in a nested list)
 *	$navItem->hasSubmenu : true/false -- if this item has one or more sub-items (sometimes useful for CSS styling)
 *	$navItem->isFirst    : true/false -- if this is the first nav item *in its level* (for example, the first sub-item of a top-level item is TRUE)
 *	$navItem->isLast     : true/false -- if this is the last nav item *in its level* (for example, the last sub-item of a top-level item is TRUE)
 *	$navItem->isCurrent  : true/false -- if this nav item represents the page currently being viewed
 *	$navItem->inPath     : true/false -- if this nav item represents a parent page of the page currently being viewed (also true for the page currently being viewed)
 *	$navItem->attrClass  : Value of the 'nav_item_class' custom page attribute (if it exists and is set)
 *	$navItem->isHome     : true/false -- if this nav item represents the home page
 *	$navItem->cID        : collection id of the page this nav item represents
 *	$navItem->cObj       : collection object of the page this nav item represents (use this if you need to access page properties and attributes that aren't already available in the $navItem object)

今回使用するのは、最後の行の「$navItem->cObj」で、英語がよくわからないながらも読んでみると、どうやら「
ナビアイテムに該当するページのコレクションオブジェクト(既に用意されている$navItemオブジェクト以外でページプロパティとか属性が必要だったらこれを使え)」って書いてあるっぽいです。なんとなく雰囲気で和訳してるので多少の間違いは勘弁してください。

で、コードを追加しますた↓

echo '<ul class="nav">'; //opens the top-level menu
foreach ($navItems as $ni) {
	echo '<li class="' . $ni->classes . '">'; //opens a nav item
	echo '<a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a>';
	if ($ni->hasSubmenu) {
		echo '<ul>'; //opens a dropdown sub-menu
	} else {
			$cobj = $ni->cObj; // ← $navItems->cObjを$cobjに入れる
			if($ni->level == 2 && $cobj->getCollectionDescription()) { //後述
			echo '<p>' . $cobj->getCollectionDescription() . '</p>';
		}
		echo '</li>'; //closes a nav item
		echo str_repeat('</ul></li>', $ni->subDepth); //closes dropdown sub-menu(s) and their top-level nav item(s)
	}
}
echo '</ul>'; //closes the top-level menu

順を追って説明すると、該当箇所は「$cobj = $ni->cObj;」から始まる4行です。

「$cobj = $ni->cObj;」で、インフォメーションにあった$navItems->cObjを変数にしました。

さらに、次の行のifの条件ですが、「$ni->level」を使用すると階層の深さが0から順に取得できますので、「$ni->level == 2」とすることで、「孫ページのみ」の条件指定ができます。汎用性持たせるなら、levelの値を数値じゃなくて、「一番大きい値」とか「最後の値」とか変数にしておいた方がいいかもですね。

さらに先ほど変数にした$cobjから「$cobj->getCollectionDescription()」を記述する事で、説明文が存在するかどうかの判別をしています。なので条件が「if($ni->level == 2 && $cobj->getCollectionDescription())」となっています。

で、最後にechoするだけ。なんということでしょう。今になって考えればこんなに簡単な事を、全然わからなくて2日くらいずっとやってました。アホです。

最終的に上記のカスタムテンプレートで、↓のようなマークアップになります。

<ul>
  <li><a href="oya">親ページ</a>
    <ul>
      <li><a href="ko">子ページ</a>
      <ul>
        <li><a href="mago">孫ページ</a>
     <p>説明文テキスト説明文テキスト説明文テキスト説明文テキスト説明文テキスト</p>
      </ul></li>
    </ul></li>
</ul>

以上。

メモ:concrete5のページ属性で指定した画像を、カスタムテンプレートから表示する方法

ページリストで、記事ページのサムネイルブロックを表示する方法もあるのですが、ケースバイケースで属性から画像入れた方が良い場合もございまして、
以前テーマファイルに属性の画像を表示する方法を書きました。
メモ:concrete5のページ属性で指定した画像を表示する方法

以前の記事の内容ではちょっと問題がございました。
↓以前の書き方

<img src="<?php echo ($c->getAttribute('ハンドル')->getVersion()->getRelativePath());?>" alt="" />

これ、カスタム属性に画像ファイルが入ってないとエラーが出てしまいます。

↓なので、個人的には画像があるときだけ表示するという方法を取るのが望ましいのかと思いますね。

<?php
  $変数 = $c->getAttribute('ハンドル');
  if($変数) {
    echo '<img src="' . $変数->getVersion()->getRelativePath() . '" alt="" />';
  }
?>

そしてカスタムテンプレートで表示する場合は、

$c->getAttribute('ハンドル')

を↓

$cobj->getAttribute('ハンドル')

に書き換えると対応できました。