WordPress テーマ _s にBootstrap3 を組み込んでみる

同僚の @motchi0214 が頑張ってる記事を見たのでフォロー

- [_s & Bootstrap でテーマを作るメモ](http://qiita.com/motchi0214/items/65e6b6a4b0dafeeed838)

Foundation6を組み込みたいなら http://qiita.com/gatespace/items/873887d4d8ba7ac36537 を参考に

# _s のダウンロード

- http://underscores.me/

# Bootstrap のCSSとJSをテーマ埋め込む

https://github.com/Automattic/_s/blob/master/functions.php#L105-L113

`functions.php` 104〜114行目の部分を以下に書き換え

```php:functions.php
function _s_scripts() {

	// Bootstrap
	wp_enqueue_style( 'bootstrap', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', array(), '3.3.6' );
	wp_enqueue_script( 'bootstrap', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js', array('jquery'), '3.3.6', false );

	wp_enqueue_style( '_s-style', get_stylesheet_uri(), array('bootstrap') );

	wp_enqueue_script( '_s-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20151215', true );

	if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
		wp_enqueue_script( 'comment-reply' );
	}
}
```

# レイアウト

- http://getbootstrap.com/css/#grid

上記を参考によしなに

# style.css の書き換え

- https://codex.wordpress.org/CSS

上記を参考にWordPressが付与するCSSクラスのみにしておく
具体的には以下のとおり

```css:style.css
/*--------------------------------------------------------------
# Accessibility
--------------------------------------------------------------*/
/* Text meant only for screen readers. */
.screen-reader-text {
	clip: rect(1px, 1px, 1px, 1px);
	position: absolute !important;
	height: 1px;
	width: 1px;
	overflow: hidden;
}

.screen-reader-text:focus {
	background-color: #f1f1f1;
	border-radius: 3px;
	box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
	clip: auto !important;
	color: #21759b;
	display: block;
	font-size: 14px;
	font-size: 0.875rem;
	font-weight: bold;
	height: auto;
	left: 5px;
	line-height: normal;
	padding: 15px 23px 14px;
	text-decoration: none;
	top: 5px;
	width: auto;
	z-index: 100000; /* Above WP toolbar. */
}

/* Do not show the outline on the skip link target. */
#content[tabindex="-1"]:focus {
	outline: 0;
}
/*--------------------------------------------------------------
# Alignments
--------------------------------------------------------------*/
.alignleft {
	display: inline;
	float: left;
	margin-right: 1.5em;
}

.alignright {
	display: inline;
	float: right;
	margin-left: 1.5em;
}

.aligncenter {
	clear: both;
	display: block;
	margin-left: auto;
	margin-right: auto;
}
/*--------------------------------------------------------------
# Media
--------------------------------------------------------------*/
.page-content .wp-smiley,
.entry-content .wp-smiley,
.comment-content .wp-smiley {
	border: none;
	margin-bottom: 0;
	margin-top: 0;
	padding: 0;
}

/* Make sure embeds and iframes fit their containers. */
embed,
iframe,
object {
	max-width: 100%;
}

/*--------------------------------------------------------------
## Captions
--------------------------------------------------------------*/
.wp-caption {
	margin-bottom: 1.5em;
	max-width: 100%;
}

.wp-caption img[class*="wp-image-"] {
	display: block;
	margin-left: auto;
	margin-right: auto;
}

.wp-caption .wp-caption-text {
	margin: 0.8075em 0;
}

.wp-caption-text {
	text-align: center;
}

/*--------------------------------------------------------------
## Galleries
--------------------------------------------------------------*/
.gallery {
	margin-bottom: 1.5em;
}

.gallery-item {
	display: inline-block;
	text-align: center;
	vertical-align: top;
	width: 100%;
}

.gallery-columns-2 .gallery-item {
	max-width: 50%;
}

.gallery-columns-3 .gallery-item {
	max-width: 33.33%;
}

.gallery-columns-4 .gallery-item {
	max-width: 25%;
}

.gallery-columns-5 .gallery-item {
	max-width: 20%;
}

.gallery-columns-6 .gallery-item {
	max-width: 16.66%;
}

.gallery-columns-7 .gallery-item {
	max-width: 14.28%;
}

.gallery-columns-8 .gallery-item {
	max-width: 12.5%;
}

.gallery-columns-9 .gallery-item {
	max-width: 11.11%;
}

.gallery-caption {
	display: block;
}
```

# ヘッダー＋メインナビゲージション部分を Bootstrap の Navbar に置き換え

- http://getbootstrap.com/components/#navbar

見た目はよしなに。

```php:header.php
<header id="masthead" class="site-header" role="banner">
	<nav class="navbar navbar-default">
		<div class="container-fluid">
			<!-- Brand and toggle get grouped for better mobile display -->
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
					<span class="sr-only">Toggle navigation</span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
				<a class="navbar-brand" href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php bloginfo( 'name' ); ?></a>
			</div>
			<!-- Collect the nav links, forms, and other content for toggling -->
			<?php
				wp_nav_menu( array(
					'theme_location'  => 'primary',
					'container_class' => 'collapse navbar-collapse',
					'container_id'    => 'bs-example-navbar-collapse-1',
					'menu_class'      => 'nav navbar-nav navbar-right',
					'fallback_cb'     => false,
					'walker'          => new Bootstrap_Walker_Nav_Menu()
				) );
			?>
		</div><!-- /.container-fluid -->
	</nav>
</header><!-- #masthead -->
```

`wp_nav_menu()` の実行結果を Navbar にするため、カスタム walker と幾つかのフィルッターフックを `functions.php` に追記。
なお、`fallback_cb` が `false` なのはフォールバック関数の `wp_page_menu()` がカスタムメニューとでhtmlコードのレンダリングが違うためうまいことメニューにCSSなどが当たらないから。
`wp_page_menu()` のカスタム walker をきっちり作れば対応可能だけど、公式テーマにしないのであれば運用でカバーでもいいと思う。


```php:navbar.php
/**
 * Custom walker class.
 */
class Bootstrap_Walker_Nav_Menu extends Walker_Nav_Menu {
	/**
	 * 2階層目以降のulのクラス
	 */
	public function start_lvl( &$output, $depth = 0, $args = array() ) {
		$indent = str_repeat("\t", $depth);
		$output .= "\n$indent<ul class=\"dropdown-menu\">\n";
	}
}
/**
 * 表示中のページに active クラスを付与
 * サブメニューがある場合 dropdown クラスを付与
 */
function bootstrap_special_nav_class( $classes, $item ) {
	if (in_array( 'current-menu-item', $classes)) {
		$classes[] = 'active';
	}
	if (in_array( 'menu-item-has-children', $classes)) {
		$classes[] = 'dropdown';
	}
    return $classes;
}
add_filter( 'nav_menu_css_class' , 'bootstrap_special_nav_class' , 10, 2 );

/**
 * サブメニューがある a タグの属性諸々追加
 */
function bootstrap_nav_menu_link_attributes( $atts, $item, $args, $depth ) {
	if ( in_array( 'menu-item-has-children', $item->classes ) && $depth < 1 ) {
		$atts['class']         = 'dropdown-toggle';
		$atts['data-toggle']   = 'dropdown';
		$atts['role']          = 'button';
		$atts['aria-haspopup'] = 'true';
		$atts['aria-expanded'] = 'false';
	}
	return $atts;
}
add_filter( 'nav_menu_link_attributes' , 'bootstrap_nav_menu_link_attributes' , 10, 4 );

/**
 * サブメニューがある a タグの後ろに caret アイコン追加
 */
function bootstrap_nav_menu_item_args( $args, $item, $depth ) {
	if ( in_array( 'menu-item-has-children', $item->classes ) && $depth < 1 ) {
		$args->link_after = ' <span class="caret"></span>';
	} else {
		$args->link_after = '';
	}
	return $args;
}
add_filter( 'nav_menu_item_args' , 'bootstrap_nav_menu_item_args' , 10, 3 );
```

# img タグに img-responsive クラスを追加

フィルターフックでえいやー

```php
function bootstrap_image_class( $content ) {
	$content = preg_replace(
		'/<img(.*)class=[\"|\'](.*?)[\"|\']([^>]*)>/i',
		'<img\1class="\2 img-responsive"\3>',
		$content
	);
	return $content;
}
add_filter( 'the_content' , 'bootstrap_image_class' );
```

# コメントフォームをBootstrap Horizontal form 仕様に

`comment_form()` の置き換えとフィルターフックで

```php:comment_form.php
<?php
$args = array(
	'class_form'    => 'form-horizontal',
	'class_submit'  => 'btn btn-primary',
	'submit_field'  => '<div class="form-group form-submit"> <div class="col-sm-offset-2 col-sm-10">%1$s %2$s</div></div>',
	'format'        => 'html5',
	'comment_field' =>  '<div class="comment-form-comment form-group"><label for="comment" class="col-sm-2 control-label">' . _x( 'Comment', 'noun' ) . '</label> <div class="col-sm-10"><textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" aria-required="true" required="required" class="form-control" ></textarea></div></div>',
);
comment_form( $args );
?>
```

```php:bootstrap_comment_form_default_fields.php
<?php
function bootstrap_comment_form_default_fields( $fields ) {
	
	foreach ( $fields as $field => $value ) {
		$value = preg_replace(
			'/comment-form-([^\"|^\']*)/i',
			'comment-form-\1 form-group',
			$value
		);

		$search  = array( '<p', '<label', '<input ', '</p>' );
		$replace = array( '<div', '<label class="col-sm-2 control-label"', '<div class="col-sm-10"><input class="form-control" ', '</div></div>');

		$value = str_replace( $search, $replace, $value);

		$fields[$field] = $value;
	}

	return $fields;
}
add_filter( 'comment_form_default_fields' , 'bootstrap_comment_form_default_fields' );
```
# 検索フォームをBootstrap Inline form 仕様に

```php:bootstrap_search_form.php
<?php
function bootstrap_search_form( $form ) {
	$form = '<form role="search" method="get" class="search-form form-inline" action="' . esc_url( home_url( '/' ) ) . '">
		<div class="form-group">
			<label class="screen-reader-text" for="search-field">' . _x( 'Search for:', 'label' ) . '</label>
			<input type="search" class="search-field form-control" placeholder="' . esc_attr_x( 'Search &hellip;', 'placeholder' ) . '" value="' . get_search_query() . '" name="s" id="search-field" />
		</div>
		<input type="submit" class="search-submit btn btn-primary" value="'. esc_attr_x( 'Search', 'submit button' ) .'" />
	</form>';
	return $form;
}
add_filter( 'get_search_form' , 'bootstrap_search_form' );
```

# カテゴリーウィジェットのドロップダウンをBootstrap仕様に

```php:bootstrap_widget_categories_dropdown_args.php
function bootstrap_widget_categories_dropdown_args( $cat_args ) {
	$cat_args['class'] = 'form-control';
	return $cat_args;
}
add_filter( 'widget_categories_dropdown_args' , 'bootstrap_widget_categories_dropdown_args' );
```

なお、アーカイブウィジェットのドロップダウンはフィルターなどあまり用意されていないので、 `WP_Widget_Archives` クラスを拡張して自作ウィジェットを作らないと無理。
もしくは jQueryで cssクラスを付与してしまうか。
この辺りは使うかどうかに応じての実装でいいと思う。

# 前後リンクとページネーション

- `the_post_navigation()` : https://developer.wordpress.org/reference/functions/the_post_navigation/
- `the_posts_navigation()` : https://developer.wordpress.org/reference/functions/the_posts_navigation/
- `the_posts_pagination()` https://developer.wordpress.org/reference/functions/the_posts_pagination/

フックが無かったりかかる労力を考えるとCSSを拡張したほうが幸せになれる。

```css:nav.css
.post-navigation .nav-links,
.posts-navigation .nav-links {
    padding-left: 0;
    margin: 20px 0;
    text-align: center;
    list-style: none;
}
.post-navigation .nav-links:before,
.post-navigation .nav-links:after,
.posts-navigation .nav-links:before,
.posts-navigation .nav-links:after {
    display: table;
    content: " ";
}

.post-navigation .nav-links .nav-previous,
.post-navigation .nav-links .nav-next,
.posts-navigation .nav-links .nav-previous,
.posts-navigation .nav-links .nav-next {
	display: inline;
}
.post-navigation .nav-links .nav-previous > a,
.post-navigation .nav-links .nav-next > a,
.posts-navigation .nav-links .nav-previous > a,
.posts-navigation .nav-links .nav-next > a {
	display: inline-block;
    padding: 5px 14px;
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 15px;
}
.post-navigation .nav-links .nav-previous > a,
.posts-navigation .nav-links .nav-previous > a {
    float: left;
}
.post-navigation .nav-links .nav-next > a,
.posts-navigation .nav-links .nav-next > a {
    float: right;
}

.pagination.navigation {
	display: block;
	margin: 0;
	border-radius: 0;
}
.pagination .nav-links {
    display: inline-block;
    margin: 20px 0;
    border-radius: 4px;
}
.pagination .nav-links > a,
.pagination .nav-links > span {
	display: inline;
    position: relative;
    float: left;
    padding: 6px 12px;
    margin-left: -1px;
    line-height: 1.42857143;
    color: #337ab7;
    text-decoration: none;
    background-color: #fff;
    border: 1px solid #ddd;
}
.pagination .nav-links > a:first-child,
.pagination .nav-links > span:first-child {
    margin-left: 0;
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
}
.pagination .nav-links > a:last-child,
.pagination .nav-links > span:last-child {
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
}
.pagination .nav-links > .current {
    z-index: 3;
    color: #fff;
    cursor: default;
    background-color: #337ab7;
    border-color: #337ab7;
}
.pagination .nav-links > .dots {
    color: #777;
    cursor: not-allowed;
}
```