-
テンプレート・クラスの実装
連載が完結したら、Gitにでも上げておいたほうが良いのでしょうかね。
まあ、要望があれば…
これまでの説明で実際に読んだ方の手元で、動いているのかどうか…「動かねーぞ」みたいなコメントもないから動いているのかな。
今回はテンプレート・クラスを作成します。
##テンプレート・エンジン
「テンプレート」とは、ロジックとビューを分離するために利用されると説明されることも多いのですが、ここではHTML
と PHP
を明確に分けるという意味に定義しましょう。
テンプレート・エンジン
として古くから使われているものに Smarty
、比較的最近の主流は Twig
となるでしょう。フレームワークである、Laravel
が人気をた高めていることもあって、Blade
もシェアを大きくしつつある状況でしょう。
ここでは、私自身が Twig や Blade を熟知しているわけではありませんので、使い慣れた Smarty
を組み込んでゆきます。
Smarty
の歴史は古く、設計思想も今時のものではありません。$smarty
という変数がグローバル空間に定義されてしまいます。Smarty
を利用するシステムでは、$smarty
という名前で変数を利用することはご法度です。
※Smarty
やTwig
はテンプレート・エンジン
であって、フレームワーク
ではありません。混同している方も見受けられるので、注意が必要です。
ログイン・ページやその他のページを作成するにあたり、私はデザインのセンスのかけらもありませんので、AdminLTE を利用します。
PREVIEW ボタンからサンプルを見ることができます。
ログインページ https://almsaeedstudio.com/themes/AdminLTE/pages/examples/login.html
ブランクページ https://almsaeedstudio.com/themes/AdminLTE/pages/examples/blank.html
以上の2ページを拝借して、作成してゆきます。
ドキュメントに書かれているように、dist
ディレクトリとplugins
ディレクトリをwebroot
以下に配置します。
##設計方針
ログインページはさておき、ブランクページ(下図)において、赤枠部分
と青枠部分
を分離したいと思います。
赤枠部分は、ページが増えても、他のページで流用できますし、青枠のコンテンツ部分は、ページを増やす際には、ページ固有のものとなるはずです。青枠部分は、どうやっても手作業は避けられませんので、やむを得ませんが、赤枠部分を切り出しておくことで、 無用なミスを防ぎ、バグを注入してしまうリスクを軽減 しておきましょう。
また、WEBアプリケーション特有の問題として、XSS
対策については万全でなくてはなりません。
##Smartyのインストール
smarty-php/smarty にあるように、composer
でインストールします。
プロジェクト・ディレクトリ直下に以下の composer.json
を作成します。
{
"name": "MyApp",
"description": "login",
"require": {
"php": ">=5.6.0",
"smarty/smarty": "~3.1"
}
}
次に以下のコマンドで、インストールされます。
$ composer install
common.php
に以下の一行を追記します。
require_once BASE_DIR . '/vendor/autoload.php';
##テンプレート・クラスの実装
テンプレートの継承に書かれている「Example 15.7. テンプレートリソース extends: による継承」の部分を参照してください。
以下のように display()
に引数を渡すことで、テンプレートを、「レイアウト部分」「コンテンツ部分」に分けて指定することができます。
$smarty->display('extends:赤枠.tpl|青枠.tpl');
webroot
以下に、index.php
(ログインページ)を作成します。Template.class.php
はSmartyのラッパークラスでもありますので、メソッド名などは、Smartyの実装を踏襲します。
Template::display()
メソッドには引数を設定する必要を省きました。index.php
のテンプレートは、拡張子を変えるだけでアクセスできるようにし、自動的にindex.tpl
を利用するように定義しています。
赤枠部分は define('LAYOUT', 'index');
とレイアウト部分を設定して利用することにします。
###ログイン・ページ webroot/index.php
<?php
/**
* index.php
*/
namespace MyApp;
use MyApp\controller\LoginController;
use MyApp\common\Template;
define('LAYOUT', 'index');
try {
require_once '../common.php';
// ログイン
LoginController::login();
} catch (\Exception $e) {
// 例外をテンプレートにアサインする
Template::exception($e);
} finally {
// テンプレートを表示
Template::display();
}
###トップ・ページ webroot/dashboard.php
<?php
/**
* dashboard.php
*/
namespace MyApp;
use MyApp\common\Template;
use MyApp\controller\LoginController;
define('LAYOUT', 'main');
try {
require_once '../common.php';
// ログインチェック
LoginController::checkLogin();
} catch (\Exception $e) {
Template::exception($e);
} finally {
Template::display();
}
###テンプレートクラス classes/common/Template.class.php
XSS対策として重要なのは、
self::getInstance()->escape_html = true;
の記述です。Smarty
はこれを明示しない場合、htmlspecialchars()
はデフォルトでは有効にならないので注意です。
<?php
namespace MyApp\common;
use MyApp\common\SystemErrorException;
/**
* Template.class.php
*/
class Template
{
/**
* テンプレートディレクトリ
*/
const TEMPLATE_DIR = BASE_DIR . '/smarty/templates';
/**
* コンパイルディレクトリ
*/
const COMPILE_DIR = BASE_DIR . '/smarty/templates_c';
/**
* プラグインディレクトリ
*/
const PLUGINS_DIR = BASE_DIR . '/smarty/plugins';
/**
* コンフィグディレクトリ
*/
const CONFIGS_DIR = BASE_DIR . '/smarty/configs';
/**
* インスタンス
*/
private static $instance = null;
/**
* コンストラクタ
*/
private function __construct()
{
}
/**
* インスタンスを取得する
* @return self::$instance[cassName]
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new \Smarty();
self::init();
}
return self::$instance;
}
/**
* クローン
* @throws \Exception
*/
public final function __clone()
{
throw new \Exception('Clone is not allowed against' . get_class($this));
}
/**
* 初期設定
*/
private static function init()
{
// ディレクトリ設定
self::getInstance()->setTemplateDir(self::TEMPLATE_DIR);
self::getInstance()->setCompileDir(self::COMPILE_DIR);
self::getInstance()->addPluginsDir(self::PLUGINS_DIR);
self::getInstance()->addConfigDir(self::CONFIGS_DIR);
// XSS 対策
self::getInstance()->escape_html = true;
}
/**
* テンプレートに変数をアサインする
* @param string $key
* @param mixed $value
*/
public static function assign($key, $value)
{
if ($key == 'exception') {
throw new SystemErrorException(
ExceptionCode::TEMPLATE_ARG_ERR, ['exception']
);
}
self::getInstance()->assign($key, $value);
}
/**
* 例外をテンプレートにアサインする
* @param \Exception $e
*/
public static function exception(\Exception $e)
{
self::getInstance()->assign('exception', $e);
}
/**
* テンプレートを表示する
*/
public static function display($template = null)
{
if (is_null($template)) {
$template = preg_replace("/\/(.+)\.php/"
, self::TEMPLATE_DIR . "/$1.tpl"
, filter_input(INPUT_SERVER, 'SCRIPT_NAME')
);
}
if (!file_exists($template)) {
// 「テンプレート[$template]が見つかりません。」
throw new SystemErrorException(
ExceptionCode::TEMPLATE_ERR, [$template]
);
}
$temp = $template;
if (defined('LAYOUT')) {
$layout = sprintf("%s/layout/%s.tpl", self::TEMPLATE_DIR, LAYOUT);
if (!file_exists($layout)) {
// 「テンプレート[$layout]が見つかりません。」
throw new SystemErrorException(
ExceptionCode::TEMPLATE_ERR, [$layout]
);
}
$temp = "extends:{$layout}|{$template}";
}
self::getInstance()->display($temp);
}
}
関連する Exception
関連のクラスに追記します。
<?php
namespace MyApp\common;
/**
* SystemErrorException.php
*
* @since 2015/07/24
*/
class SystemErrorException extends \Exception
{
/**
* コンストラクタ
* @param type $code
*/
public function __construct($code, array $args = [])
{
$message = ExceptionCode::getMessage($code);
self::writeLog(vsprintf($message, $args));
self::sendMail(vsprintf($message, $args));
parent::__construct('システムエラーが発生しました。', $code);
}
/**
* 管理者へメール
* @param type $message
*/
static private function sendMail($message)
{
//Mail::send($message);
}
/**
* ログを書く
* @param type $message
*/
static private function writeLog($message)
{
//Log::write($message, \LoggerLevel::ERROR);
}
}
<?php
namespace MyApp\common;
/**
* ExceptionCode.class.php
* @since 2015/07/25
*/
class ExceptionCode
{
/**
* エラーコード
*/
const INVALID_ERR = 1000;
const INVALID_LOCK = 1001;
const INVALID_LOGIN_FAIL = 1002;
const APPLICATION_ERR = 2000;
const SYSTEM_ERR = 3000;
const TEMPLATE_ERR = 3001;
const TEMPLATE_ARG_ERR = 3002;
/**
* エラーメッセージ
* @var array<string>
*/
private static $_arrMessage = array(
self::INVALID_ERR => 'エラーが発生しました。'
, self::INVALID_LOCK => 'アカウントがロックされています。'
, self::INVALID_LOGIN_FAIL => 'ログインに失敗しました。'
, self::APPLICATION_ERR => 'アプリケーション・エラーが発生しました。'
, self::SYSTEM_ERR => 'システム・エラーが発生しました。'
, self::TEMPLATE_ERR => 'テンプレート[%s]が見つかりません。'
, self::TEMPLATE_ARG_ERR => '引数に[%s]は利用できません。'
);
/**
* エラーメッセージを取得する
* @param int $intCode
* @return string
*/
static public function getMessage($intCode)
{
if (array_key_exists($intCode, self::$_arrMessage)) {
return self::$_arrMessage[$intCode];
}
}
}
##テンプレートファイルの作成
以下のように2ページ分のレイアウトファイルとコンテンツファイルのセットを作成しました。
###レイアウト smarty/templates/layout/index.tpl
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
<link rel="stylesheet" href="dist/css/AdminLTE.min.css">
<link rel="stylesheet" href="plugins/iCheck/square/blue.css">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
{block name='meta'}
{/block}
</head>
<body class="hold-transition login-page">
{include file='widget/exception.tpl'}
{block name='content'}
{/block}
<script src="plugins/jQuery/jquery-2.2.3.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
{block name='script'}
{/block}
</body>
</html>
###コンテンツ smarty/templates/index.tpl
{block name='meta'}
<title>ログイン</title>
<style type="text/css">
.login-box, .register-box {
margin: 20px auto;
}
</style>
{/block}
{block name='content'}
<div class="login-box">
<div class="login-logo">
<a href="/">
<b>Admin</b>
LTE
</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
<p class="login-box-msg">サインインしてセッションを開始する</p>
<form action="" method="post">
<div class="form-group has-feedback">
<input type="text" name="email" class="form-control" placeholder="メールアドレス">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" name="password" class="form-control" placeholder="パスワード">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-5 col-xs-offset-7">
<button type="submit" class="btn btn-primary btn-block btn-flat">
サインイン
</button>
</div>
</div>
</form>
<a href="remind.php">パスワードを忘れましたか?</a>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
{/block}
{block name='script'}
{/block}
###レイアウト smarty/templates/layout/main.tpl
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>AdminLTE 2 | Blank Page</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.6 -->
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="dist/css/AdminLTE.min.css">
<!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. -->
<link rel="stylesheet" href="dist/css/skins/_all-skins.min.css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
{block name='meta'}
{/block}
</head>
<body class="hold-transition skin-blue sidebar-mini">
<!-- Site wrapper -->
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="index2.html" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b>A</b>LT</span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg"><b>Admin</b>LTE</span>
</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-envelope-o"></i>
<span class="label label-success">4</span>
</a>
<ul class="dropdown-menu">
<li class="header">You have 4 messages</li>
<li>
<!-- inner menu: contains the actual data -->
<ul class="menu">
<li><!-- start message -->
<a href="#">
<div class="pull-left">
<img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
</div>
<h4>
Support Team
<small><i class="fa fa-clock-o"></i> 5 mins</small>
</h4>
<p>Why not buy a new awesome theme?</p>
</a>
</li>
<!-- end message -->
</ul>
</li>
<li class="footer"><a href="#">See All Messages</a></li>
</ul>
</li>
<!-- Notifications: style can be found in dropdown.less -->
<li class="dropdown notifications-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-warning">10</span>
</a>
<ul class="dropdown-menu">
<li class="header">You have 10 notifications</li>
<li>
<!-- inner menu: contains the actual data -->
<ul class="menu">
<li>
<a href="#">
<i class="fa fa-users text-aqua"></i> 5 new members joined today
</a>
</li>
</ul>
</li>
<li class="footer"><a href="#">View all</a></li>
</ul>
</li>
<!-- Tasks: style can be found in dropdown.less -->
<li class="dropdown tasks-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-flag-o"></i>
<span class="label label-danger">9</span>
</a>
<ul class="dropdown-menu">
<li class="header">You have 9 tasks</li>
<li>
<!-- inner menu: contains the actual data -->
<ul class="menu">
<li><!-- Task item -->
<a href="#">
<h3>
Design some buttons
<small class="pull-right">20%</small>
</h3>
<div class="progress xs">
<div class="progress-bar progress-bar-aqua" style="width: 20%" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
<span class="sr-only">20% Complete</span>
</div>
</div>
</a>
</li>
<!-- end task item -->
</ul>
</li>
<li class="footer">
<a href="#">View all tasks</a>
</li>
</ul>
</li>
<!-- User Account: style can be found in dropdown.less -->
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img src="dist/img/user2-160x160.jpg" class="user-image" alt="User Image">
<span class="hidden-xs">Alexander Pierce</span>
</a>
<ul class="dropdown-menu">
<!-- User image -->
<li class="user-header">
<img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
<p>
Alexander Pierce - Web Developer
<small>Member since Nov. 2012</small>
</p>
</li>
<!-- Menu Body -->
<li class="user-body">
<div class="row">
<div class="col-xs-4 text-center">
<a href="#">Followers</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">Sales</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">Friends</a>
</div>
</div>
<!-- /.row -->
</li>
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="#" class="btn btn-default btn-flat">Profile</a>
</div>
<div class="pull-right">
<a href="#" class="btn btn-default btn-flat">Sign out</a>
</div>
</li>
</ul>
</li>
<!-- Control Sidebar Toggle Button -->
<li>
<a href="#" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a>
</li>
</ul>
</div>
</nav>
</header>
<!-- =============================================== -->
<!-- Left side column. contains the sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel -->
<div class="user-panel">
<div class="pull-left image">
<img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
</div>
<div class="pull-left info">
<p>Alexander Pierce</p>
<a href="#"><i class="fa fa-circle text-success"></i> Online</a>
</div>
</div>
<!-- search form -->
<form action="#" method="get" class="sidebar-form">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Search...">
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
<!-- /.search form -->
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="header">MAIN NAVIGATION</li>
<li class="treeview active">
<a href="#">
<i class="fa fa-dashboard"></i> <span>Dashboard</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li class="active"><a href="index.html"><i class="fa fa-circle-o"></i> Dashboard v1</a></li>
<li><a href="index2.html"><i class="fa fa-circle-o"></i> Dashboard v2</a></li>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-files-o"></i>
<span>Layout Options</span>
<span class="pull-right-container">
<span class="label label-primary pull-right">4</span>
</span>
</a>
<ul class="treeview-menu">
<li><a href="../layout/top-nav.html"><i class="fa fa-circle-o"></i> Top Navigation</a></li>
<li><a href="../layout/boxed.html"><i class="fa fa-circle-o"></i> Boxed</a></li>
<li><a href="../layout/fixed.html"><i class="fa fa-circle-o"></i> Fixed</a></li>
<li><a href="../layout/collapsed-sidebar.html"><i class="fa fa-circle-o"></i> Collapsed Sidebar</a></li>
</ul>
</li>
<li>
<a href="../widgets.html">
<i class="fa fa-th"></i> <span>Widgets</span>
<span class="pull-right-container">
<small class="label pull-right bg-green">Hot</small>
</span>
</a>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-pie-chart"></i>
<span>Charts</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="../charts/chartjs.html"><i class="fa fa-circle-o"></i> ChartJS</a></li>
<li><a href="../charts/morris.html"><i class="fa fa-circle-o"></i> Morris</a></li>
<li><a href="../charts/flot.html"><i class="fa fa-circle-o"></i> Flot</a></li>
<li><a href="../charts/inline.html"><i class="fa fa-circle-o"></i> Inline charts</a></li>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-laptop"></i>
<span>UI Elements</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="../UI/general.html"><i class="fa fa-circle-o"></i> General</a></li>
<li><a href="../UI/icons.html"><i class="fa fa-circle-o"></i> Icons</a></li>
<li><a href="../UI/buttons.html"><i class="fa fa-circle-o"></i> Buttons</a></li>
<li><a href="../UI/sliders.html"><i class="fa fa-circle-o"></i> Sliders</a></li>
<li><a href="../UI/timeline.html"><i class="fa fa-circle-o"></i> Timeline</a></li>
<li><a href="../UI/modals.html"><i class="fa fa-circle-o"></i> Modals</a></li>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-edit"></i> <span>Forms</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="../forms/general.html"><i class="fa fa-circle-o"></i> General Elements</a></li>
<li><a href="../forms/advanced.html"><i class="fa fa-circle-o"></i> Advanced Elements</a></li>
<li><a href="../forms/editors.html"><i class="fa fa-circle-o"></i> Editors</a></li>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-table"></i> <span>Tables</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="../tables/simple.html"><i class="fa fa-circle-o"></i> Simple tables</a></li>
<li><a href="../tables/data.html"><i class="fa fa-circle-o"></i> Data tables</a></li>
</ul>
</li>
<li>
<a href="../calendar.html">
<i class="fa fa-calendar"></i> <span>Calendar</span>
<span class="pull-right-container">
<small class="label pull-right bg-red">3</small>
<small class="label pull-right bg-blue">17</small>
</span>
</a>
</li>
<li>
<a href="../mailbox/mailbox.html">
<i class="fa fa-envelope"></i> <span>Mailbox</span>
<span class="pull-right-container">
<small class="label pull-right bg-yellow">12</small>
<small class="label pull-right bg-green">16</small>
<small class="label pull-right bg-red">5</small>
</span>
</a>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-folder"></i> <span>Examples</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="invoice.html"><i class="fa fa-circle-o"></i> Invoice</a></li>
<li><a href="profile.html"><i class="fa fa-circle-o"></i> Profile</a></li>
<li><a href="login.html"><i class="fa fa-circle-o"></i> Login</a></li>
<li><a href="register.html"><i class="fa fa-circle-o"></i> Register</a></li>
<li><a href="lockscreen.html"><i class="fa fa-circle-o"></i> Lockscreen</a></li>
<li><a href="404.html"><i class="fa fa-circle-o"></i> 404 Error</a></li>
<li><a href="500.html"><i class="fa fa-circle-o"></i> 500 Error</a></li>
<li><a href="blank.html"><i class="fa fa-circle-o"></i> Blank Page</a></li>
<li><a href="pace.html"><i class="fa fa-circle-o"></i> Pace Page</a></li>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-share"></i> <span>Multilevel</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="#"><i class="fa fa-circle-o"></i> Level One</a></li>
<li>
<a href="#"><i class="fa fa-circle-o"></i> Level One
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="#"><i class="fa fa-circle-o"></i> Level Two</a></li>
<li>
<a href="#"><i class="fa fa-circle-o"></i> Level Two
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="#"><i class="fa fa-circle-o"></i> Level Three</a></li>
<li><a href="#"><i class="fa fa-circle-o"></i> Level Three</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#"><i class="fa fa-circle-o"></i> Level One</a></li>
</ul>
</li>
<li><a href="documentation/index.html"><i class="fa fa-book"></i> <span>Documentation</span></a></li>
<li class="header">LABELS</li>
<li><a href="#"><i class="fa fa-circle-o text-red"></i> <span>Important</span></a></li>
<li><a href="#"><i class="fa fa-circle-o text-yellow"></i> <span>Warning</span></a></li>
<li><a href="#"><i class="fa fa-circle-o text-aqua"></i> <span>Information</span></a></li>
</ul>
</section>
<!-- /.sidebar -->
</aside>
<!-- =============================================== -->
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Blank page
<small>it all starts here</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>
<li><a href="#">Examples</a></li>
<li class="active">Blank page</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
{include file='widget/exception.tpl'}
{block name='content'}
{/block}
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>Version</b> 2.3.5
</div>
<strong>Copyright © 2014-2016 <a href="http://almsaeedstudio.com">Almsaeed Studio</a>.</strong> All rights
reserved.
</footer>
<!-- Add the sidebar's background. This div must be placed
immediately after the control sidebar -->
<div class="control-sidebar-bg"></div>
</div>
<!-- ./wrapper -->
<!-- jQuery 2.2.3 -->
<script src="plugins/jQuery/jquery-2.2.3.min.js"></script>
<!-- Bootstrap 3.3.6 -->
<script src="bootstrap/js/bootstrap.min.js"></script>
<!-- SlimScroll -->
<script src="plugins/slimScroll/jquery.slimscroll.min.js"></script>
<!-- FastClick -->
<script src="plugins/fastclick/fastclick.js"></script>
<!-- AdminLTE App -->
<script src="dist/js/app.min.js"></script>
<!-- AdminLTE for demo purposes -->
<script src="dist/js/demo.js"></script>
{block name='script'}
{/block}
</body>
</html>
###コンテンツ smarty/templates/dashborad.tpl
{block name='meta'}
{/block}
{block name='content'}
<div class="row">
<div class="col-sm-12">
</div>
</div>
{/block}
{block name='script'}
{/block}
###共通部品 smarty/templates/widget/exception.tpl
{if $exception|default:null}
<div class="alert alert-danger">
<h4>
<i class="icon fa fa-ban"></i>
{$exception->getCode()|default:''}
</h4>
<p>{$exception->getMessage()|default:''}</p>
</div>
{/if}