[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('ハンドル')

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

[concrete5]悲劇的!Designer Contentの落とし穴

結論

いきなり結論です。

Designer contentを使用してブロックを作成し、間違えて作成したブロックを削除する際には、
「FTP上のblock/”Dir”」と、「データベース内のblocktypeにある”Dir”」の両方を削除してください。
※”Dir”はブロックタイプ作成時の任意です。

実ファイルとデータベースのテーブルを削除しないと思わぬエラーの原因となります。

なお、以下は今日起こった現象の振り返りなので、あまり意味がありませんのであしからず。

ブロックが追加出来ないよ!?

本日、concrete5をご利用のクライアントのサーバー移行作業を行っていた所、「ブロック追加」が一切機能しなくなりました。
「あれれー、おかしいなぁー」と思い、管理画面からブロックタイプを開こうとした所、Internal Server Error 500との事。

はてさて、一体何なんでしょうか。
何はなくともエラーログを見る習慣付けは必要だと思いました。全く関係のないアドオンを消したりしてまた入れ直したり、とても面倒な事をしてしまいました。
エラーログを拝見いたしました所、2つのエラーの記録がございました。

エラーの内容

[code]
[error] [client xxx.x.x.x] PHP Warning: require_once(/home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/blocks/links/controller.php): failed to open stream: No such file or directory in /home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/core/libraries/loader.php on line 205, referer: https://xxx.xxx.xxx.xx/index.php/dashboard/

[error] [client 219.8.4.64] PHP Fatal error: require_once(): Failed opening required ‘/home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/blocks/links/controller.php’ (include_path=’/home/httpd/public_html/libraries/3rdparty:/home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/libraries/3rdparty:.:/usr/share/pear:/usr/share/php’) in /home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/core/libraries/loader.php on line 205, referer: https://xxx.xxx.xxx.xx/index.php/dashboard/
[/code]

サーバー移行と同時にconcreteのアップデートも行ったのですが、アップデート前は問題なく利用出来てたというところがポイントです。

で。

エラーの内容は、「そもそも/home/httpd/public_html/updates/concrete5.6.0.2.ja/concrete/blocks/links/controller.phpってファイルがおかしいじゃんか、無いじゃんかー。」って仰るわけです。
私としましても、「うーん、そんなブロックは使ってないよなぁー。」と頭を抱えるしかございませんでした。

救世主は移行前のサイトでした

Designer contentは非常に便利なアドオンで、ブロックの内容を自由に組み立てる事ができます。
ただ、弱点が一つあって、Designer contentでブロック作成をする際に、内容を間違えて登録してしまった時です。
間違えて登録してしまった後に、管理画面から修正を行おうとしても出来ないという弱点があって、簡単に順番の入れ替えや、静的タグの追加くらいなら、直接生成されたphpファイルをいじれば直せるのですが、テキストフィールドの追加を忘れてしまった場合は別のブロックタイプをDesigner contentで新たに作成し直すしかないのです(一応方法はあるのでしょうが、私はphpに詳しくないので作り直した方が速い。)

そう、過去の負の遺産が残っておりました。

間違えて作成してしまったブロックタイプを、FTPからディレクトリごと削除していました。
移行前の方では、ディレクトリが存在していないにも関わらず、動作していました。

でも、データベースには作成したブロックタイプは残っているので、concreteのcoreにある「/concrete/core/libraries/loader.php」は居なくなってしまった「/block/links」を探し続けてしまいます。

ここからは推測ですが、管理画面からアップデートをかけた際に、concreteのcoreファイルのパスが変わる関係で、アップデート前ではディレクトリが無くてもごまかして動いていたものが、動かなくなってしまったと考えています。

Designer contentを使用してブロックを作成し、間違えて作成したブロックを削除する際には、
「FTP上のblock/”Dir”」と、「データベース内のblocktypeにある”Dir”」の両方を削除してください。
※”Dir”はブロックタイプ作成時の任意です。