LoginSignup
12
14

More than 5 years have passed since last update.

PHPでログイン機能を実装するチュートリアル #7

Last updated at Posted at 2016-11-02
  1. 基本設計、ユーザーモデル
  2. オートローダー
  3. 例外・ログ
  4. PDO シングルトン SQLインジェクション

  5. ユーザーモデルの作成

  6. クラスの継承

  7. テンプレート・クラスの実装

  8. アカウントロック機能の実装

  9. メール送信機能の実装

  10. アカウントロック解除機能の実装

  11. CSRF対策

連載が完結したら、Gitにでも上げておいたほうが良いのでしょうかね。
まあ、要望があれば…

これまでの説明で実際に読んだ方の手元で、動いているのかどうか…「動かねーぞ」みたいなコメントもないから動いているのかな。

今回はテンプレート・クラスを作成します。

テンプレート・エンジン

「テンプレート」とは、ロジックとビューを分離するために利用されると説明されることも多いのですが、ここではHTMLPHP を明確に分けるという意味に定義しましょう。

テンプレート・エンジンとして古くから使われているものに Smarty、比較的最近の主流は Twig となるでしょう。フレームワークである、Laravel が人気をた高めていることもあって、Blade もシェアを大きくしつつある状況でしょう。

ここでは、私自身が Twig や Blade を熟知しているわけではありませんので、使い慣れた Smarty を組み込んでゆきます。

Smartyの歴史は古く、設計思想も今時のものではありません。$smartyという変数がグローバル空間に定義されてしまいます。Smartyを利用するシステムでは、$smarty という名前で変数を利用することはご法度です。

SmartyTwigテンプレート・エンジンであって、フレームワークではありません。混同している方も見受けられるので、注意が必要です。

ログイン・ページやその他のページを作成するにあたり、私はデザインのセンスのかけらもありませんので、AdminLTE を利用します。

almsaeedstudio.com_2016-11-02_04-20-03.png

PREVIEW ボタンからサンプルを見ることができます。

ログインページ https://almsaeedstudio.com/themes/AdminLTE/pages/examples/login.html
ブランクページ https://almsaeedstudio.com/themes/AdminLTE/pages/examples/blank.html

以上の2ページを拝借して、作成してゆきます。
ドキュメントに書かれているように、distディレクトリとpluginsディレクトリをwebroot以下に配置します。

スクリーンショット 2016-11-02 4.27.45.png

設計方針

ログインページはさておき、ブランクページ(下図)において、赤枠部分青枠部分を分離したいと思います。
赤枠部分は、ページが増えても、他のページで流用できますし、青枠のコンテンツ部分は、ページを増やす際には、ページ固有のものとなるはずです。青枠部分は、どうやっても手作業は避けられませんので、やむを得ませんが、赤枠部分を切り出しておくことで、 無用なミスを防ぎ、バグを注入してしまうリスクを軽減 しておきましょう。

また、WEBアプリケーション特有の問題として、XSS対策については万全でなくてはなりません。

almsaeedstudio.com_2016-11-02_04-34-52.png

Smartyのインストール

smarty-php/smarty にあるように、composer でインストールします。

プロジェクト・ディレクトリ直下に以下の composer.json を作成します。

composer.json
{
    "name": "MyApp",
    "description": "login",
    "require": {
        "php": ">=5.6.0",
        "smarty/smarty": "~3.1"
    }
}

次に以下のコマンドで、インストールされます。

$ composer install

common.php に以下の一行を追記します。

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

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

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() はデフォルトでは有効にならないので注意です。

classes/common/Template.class.php
<?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 関連のクラスに追記します。

classes/common/SystemErrorException.php
<?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);
    }

}
classes/common/ExceptionCode.class.php
<?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

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

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

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 &copy; 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

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

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}
12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14