MENU

プラグインの研究:Absolute Privacyを読んでみる5:メンバ関数を全部読む

2010 11/24

というわけで、一行目に戻って、プラグインの中の様々な関数の定義を読みます。

あと、前回勉強したプラグインがどうして動くのか?については、WordPress プラグイン作成時のノウハウがとっても分かりやすかったので、おすすめです。
この後編を読んで分かったのですが、

add_action( 'register_form', array(&$absolutePrivacy, 'registrationBox'));

の2個目の引数は、クラスを利用しているのでこういう書き方になっているのですね。

クラスの定義

if (!class_exists("absolutePrivacy")) {
class absolutePrivacy {

absolutePrivacyというクラスが存在していなければ、以下定義します。

メンバ変数

	//lets declare some variables
	var $capabilities;   //capabilities prefix
	var $role;           //role database name
	var $role_ref;		 //role database entry
	var $rolename;       //role name
	var $options;        //options
	var $default_role;   //default role on install

メンバ変数を宣言しています。

コンストラクタ

	function absolutePrivacy() {  //constructor
		global $wpdb;

		$this->capabilities = $wpdb->prefix . "capabilities";
		$this->role = "unapproved"; 							//do not change this or bad things will happen to good people
		$this->role_ref = "unapproved"; 	//leave it alone
		$this->rolename = "Unapproved User";					//Role name for unapproved users. Change this if you like (will require to deactivate and reactivate the plugin to register)
		$this->options = "absolute_privacy"; 					//name for options array();
		$this->default_role = "absolute_privacy_default"; 		//stores the default role on plugin installation (usually "Subscriber")
	}

コンストラクタです。メンバ関数に色々代入しています。

ひとつめのメンバ関数:createRole():管理権限を作ります

長いです。
コメントを見ると、

ロール生成関数。
プラグインの有効化にあたって新しいロールを作ります。
デフォルトのロールも参照するよ。

とのこと。

定義の冒頭で宣言されているグローバル変数の$wp_rolesは、日本語では見つからないですが、http://ifacethoughts.net/2006/04/06/wp_roles/を見る限り、管理者なのか購読者なのかというログインユーザの権限を保持している変数と思われます。
$defaultに代入しています。
WPの管理画面で設定しているものを、ここでも使うようです。

参照:プラグインの研究:Absolute Privacyを読んでみる3:フォームを見る

続きはソースの下に。

	/**
	 * createRole function.
	 * Creates a new role on plugin activation and keeps track of the default role
	 */
	function createRole(){
		global $wp_roles;
		$default = get_option('default_role');
		
		/* Let's set the default options if they don't exist */
		$options = get_option($this->options);
		if(!$options){

/* This section looks a little wonky here, but it has to for proper formatting in the textarea boxes */			
$to_update = array( 'members_enabled' => 'yes', // turn on the lockdown
'rss_control' => 'off', //disable the RSS
'pending_welcome_email_subject' => 'Your account with ' . stripslashes(get_option('blogname')) . ' is under review',
'pending_welcome_message' => 'Hi %name%, 
Thanks for registering for %blogname%! Your registration is currently being reviewed. You will not be able to login until it has been approved. You will receive an email at that time. Thanks for your patience. 

Sincerely,

%blogname%',
'account_approval_email_subject' => 'Your account has been approved!',
'account_approval_message' => 'Your registration with %blogname% has been approved!

Your may login using the following information:

Username: %username%
Password: (hidden)
URL: %blogurl%/wp-login.php',
'admin_approval_email_subject' => 'A new user is waiting approval',
'admin_approval_message' => 'A new user has registered for %blogname% and is waiting your approval. You may approve or delete them here: %approval_url%

This user cannot log in until you approve them.'		  
							);
							
			foreach($to_update as $key => $value){
				$options[$key] = $value;
			}
			update_option($this->options, $options);
		}
										
		$role = get_role($this->role);
		if(!$role) { 
			$wp_roles->add_role($this->role, $this->rolename); //create the unapproved role
			$role = get_role($this->role);
			$role->add_cap('level_0');  //give the unaproved role the 0 capability
			update_option($this->default_role, $default); //saves the user's default role preference
			 $this->_changeDefaultRole($enabled="yes");
			return true;
		}
		else return false;
	}

デフォルトのロールがなかったら

9行目からの

		/* Let's set the default options if they don't exist */
		$options = get_option($this->options);
		if(!$options){

は、デフォルトのロールがなかった場合にそれをセットしちゃおうというところです。

なんか無理やりたってるところ

コメントで、

ちょっとアレだけど

と言っています。
$to_updateという変数に連想配列を代入しています。

キーになっているmembers_enabledやrss_controlなどは、WPの管理画面で決定すべきところです。
これらがセットされてない場合には、ここでやるということです。

/* This section looks a little wonky here, but it has to for proper formatting in the textarea boxes */			
$to_update = array( 'members_enabled' => 'yes', // turn on the lockdown
'rss_control' => 'off', //disable the RSS
'pending_welcome_email_subject' => 'Your account with ' . stripslashes(get_option('blogname')) . ' is under review',
'pending_welcome_message' => 'Hi %name%, 
Thanks for registering for %blogname%! Your registration is currently being reviewed. You will not be able to login until it has been approved. You will receive an email at that time. Thanks for your patience. 

Sincerely,

%blogname%',
'account_approval_email_subject' => 'Your account has been approved!',
'account_approval_message' => 'Your registration with %blogname% has been approved!

Your may login using the following information:

Username: %username%
Password: (hidden)
URL: %blogurl%/wp-login.php',
'admin_approval_email_subject' => 'A new user is waiting approval',
'admin_approval_message' => 'A new user has registered for %blogname% and is waiting your approval. You may approve or delete them here: %approval_url%

This user cannot log in until you approve them.'		  
							);

これで、仮に管理画面でセットしていなくても、安全に動いてくれます。

上記のユーザ権限の設定をDBにセットする

			foreach($to_update as $key => $value){
				$options[$key] = $value;
			}
			update_option($this->options, $options);
		}

$roleがいい感じに決まります

		$role = get_role($this->role);
		if(!$role) { 
			$wp_roles->add_role($this->role, $this->rolename); //create the unapproved role
			$role = get_role($this->role);
			$role->add_cap('level_0');  //give the unaproved role the 0 capability
			update_option($this->default_role, $default); //saves the user's default role preference
			 $this->_changeDefaultRole($enabled="yes");
			return true;
		}
		else return false;
	}

これで、WP側で設定があってもなくても安全な新規ユーザの発行のための条件がセットされました。
$roleには、unapproved的なものが代入されています。

二つ目のメンバ関数:destroyRole():プラグインの停止時に、さっき作ったロールを無しにしてます。

	/**
	 * destroyRole function.
	 * Deletes role on plugin deactivation 
	 */
	function destroyRole(){
		global $wp_roles;
			
		$wp_roles->remove_role($this->role);
		$this->_changeDefaultRole($enabled="no");
	}

3つめのメンバ関数:_changeDefaultRole($enabled):デフォルトのロール権限を変更する関数。

	/**
	 * _changeDefaultRole function.
	 * Changes the default blog role 
	 */
	function _changeDefaultRole($enabled){
	
		$default = get_option($this->default_role);
		
		if($enabled == "yes"){
			update_option('default_role', $this->role);
		}
		else{
			update_option('default_role', $default); //change back to default
		}
		
	}

_changeDefaultRole($enabled)という関数です。
上の二つのメンバ関数で利用されています。
$enabledがyesであれば、プラグインが設定したロールに、
noであればデフォルトのロールに、変更する処理をしています。

4つ目のメンバ関数:registrationBox():登録フォームの出力

	/**
	 * registrationBox function.
	 * Echos input boxes for first name, last name, and password to 
	 * the registration box. 
	 */
	function registrationBox(){ 
		$options = get_option($this->options);
		$output = '<p><label>First Name:<br />
					<input type="text" name="first_name" id="first_name" class="input" value="' . (isset($_POST['first_name']) ? attribute_escape(stripslashes($_POST['first_name'])) : '' ) . '" size="25" tabindex="70" /></label></p>
					<p><label>Last Name:<br />
					<input type="text" name="last_name" id="last_name" class="input" value="' . (isset($_POST['last_name']) ? attribute_escape(stripslashes($_POST['last_name'])) : '' ) . '" size="25" tabindex="80" /></label></p>
				
					<p><label>Password:<br />
					<input type="password" name="pswd1" id="pswd1" class="input" size="25" tabindex="91"/></label></p>
					<p><label>Repeat Password:<br />
					<input type="password" name="pswd2" id="pswd2" class="input" size="25" tabindex="92" /></label></p>';
		
			$output .= "¥n" . '<p class="message register" style="margin-bottom: 8px;">Your account must be approved before you will be able to login. You will be emailed once it is approved.</p>';
	
		echo $output;
	}

苗字、名前、パスワード×2回の入力フォームを出力します。$outputに入れて全部入れてechoしてます。

5つめのメンバ関数:checkRegErrors($errors):エラーメッセージの設定

	/**
	 * checkRegErrors function.
	 * Adds error checks to registration form 
	 */
	function checkRegErrors($errors){

		if(empty($_POST['pswd1']) || empty($_POST['pswd2']) || $_POST['pswd1'] == '' || $_POST['pswd2'] == ''){
			$errors->add('password', __('<strong>ERROR</strong>: Please enter a password in both password boxes.'));
		}elseif ($_POST['pswd1'] != $_POST['pswd2']){
			$errors->add('password', __('<strong>ERROR</strong>: Passwords do not match.'));}
		if(empty($_POST['first_name']) || empty($_POST['last_name'])){
			$errors->add('name', __('<strong>ERROR</strong>: You must enter a first and last name'));}
					
		return $errors;
	}

苗字、名前、パスワード×2回の間違え方によって、エラーメッセージを決めて$errorsに納めて返してます。
どっかで使うんでしょう。

6つめのメンバ関数:regCSS():新しい部分にあてるためのCSSを出力

	/**
	 * regCSS function.
	 * Adds CSS for registration form 
	 */
	function regCSS(){
		echo '<style type="text/css">
		#invite_code, #first_name, #last_name, #pswd1, #pswd2{
			font-size: 24px;
			width: 97%;
			padding: 3px;
			margin-top: 2px;
			margin-right: 6px;
			margin-bottom: 16px;
			border: 1px solid #e5e5e5;
			background: #fbfbfb;
		}
		#reg_passmail{
			display:none;
		}
		</style>';
	}

7つ目のメンバ関数:addNewUser($user_id):DBに新しいユーザを加える

	/**
	 * addNewUser function.
	 * Adds new registrants name and password
	 * to the database 
	 */
	function addNewUser($user_id){ //adds user meta to the database on registration
		global $wpdb;
		$options = get_option($this->options);
	
		update_usermeta($user_id, 'first_name', attribute_escape(stripslashes($_POST['first_name'])));
		update_usermeta($user_id, 'last_name', attribute_escape(stripslashes($_POST['last_name'])));

		$user_role = new WP_User($user_id);
		$user_role->set_role($this->role);
			
		if(!empty($_POST['pswd1'])){
			$_POST['pswd1'] = wp_set_password(attribute_escape(stripslashes($_POST['pswd1'])), $user_id);
		}
		
		$_POST['pswd1'] = '';
		$_POST['pswd2'] = '';
		unset($_POST['pswd1']);
		unset($_POST['pswd2']);
	}

user_metaデータベースに色々書き込むための関数です。
どこかで実行されるんでしょう。
多分、認証用の管理画面で。

8つめのメンバ関数:installOptionsMenu() :

	/**
	 * installOptionsMenu function.
	 */
	function installOptionsMenu() {  // install the options menu
		if (function_exists('current_user_can')) {
			if (!current_user_can('manage_options')) return;
		} else {
			global $user_level;
			get_currentuserinfo();
			if ($user_level < 10) return;
		}
		if (function_exists('add_options_page')) {
			add_options_page(__('Absolute Privacy'), __('Absolute Privacy'), 1, __FILE__, array(&$this,'optionsPage'));
		}
	} 

add_options_page()という関数は、管理画面にメニューを増やすための関数です。フックとかそういうのを利用する感じだと思います。

9つ目のメンバ関数:optionsPage():管理画面の設定反映

プラグインの研究:Absolute Privacyを読んでみる3:フォームを見るで見たフォームのところです。前半の本体部分を見ます。

ここで、プラグインのフォルダに同梱されているap_mod_email.phpがインクルードされています。

	function optionsPage(){
		
		if( isset($_GET['mode']) && ($_GET['mode'] == "moderate") ) {
			include('ap_mod_email.php');
		return;
		}
		
		global $wpdb;
		$plugin_path = get_bloginfo('wpurl') . '/wp-content/plugins/' . dirname(plugin_basename(__FILE__));
		
		if (isset($_POST['update_options'])) {
	   		$options['members_enabled'] = trim($_POST['members_enabled'],'{}');
	   		$options['redirect_page'] = trim($_POST['redirect_page'],'{}');
	   		$options['allowed_pages'] = trim($_POST['allowed_pages'],'{}');
	   		$options['admin_block'] = trim($_POST['admin_block'], '{}');
	   		$options['rss_control'] = trim($_POST['rss_control'], '{}');
	   		$options['rss_characters'] = trim($_POST['rss_characters'], '{}');
	   		
	   		$options['pending_welcome_email_subject'] = trim(stripslashes($_POST['pending_welcome_email_subject']), '{}');
	   		$options['pending_welcome_message'] = trim(stripslashes($_POST['pending_welcome_message']), '{}');
	   		$options['account_approval_email_subject'] = trim(stripslashes($_POST['account_approval_email_subject']), '{}');
	   		$options['account_approval_message'] = trim(stripslashes($_POST['account_approval_message']), '{}');
	   		$options['admin_approval_email_subject'] = trim(stripslashes($_POST['admin_approval_email_subject']), '{}');
	   		$options['admin_approval_message'] = trim(stripslashes($_POST['admin_approval_message']), '{}');

	   		update_option($this->options, $options);
		
		// Show a message to say we've done something
		echo '<div class="updated"><p>' . __('Options saved') . '</p></div>';
		} else {
		
		$options = get_option($this->options);
		}

下のフォームでポストされたものをそれぞれの変数に代入です。

10個目のメンバ関数:handleEmail($user_id, $type):色んなメールを送信

ユーザIDとどんな段階なのかを示すtypeによって、色々出し分けます。

	function handleEmail($user_id, $type){

		$options = get_option($this->options);
		$user = get_userdata($user_id); //object with user info

		switch($type){
			case('pending_welcome'):
				$to_email = $user->user_email;
				$subject = $options['pending_welcome_email_subject'];
				$message = $options['pending_welcome_message'];
				break;
		
			case('account_approved'):
				$to_email = $user->user_email;
				$subject = $options['account_approval_email_subject'];
				$message = $options['account_approval_message'];
				break;
		
			case('admin_notification'):
				$to_email = get_bloginfo('admin_email');
				$subject = $options['admin_approval_email_subject'];
				$message = $options['admin_approval_message'];
				break;
		}
		
	
		$replace = array('%username%' => $user->user_login,
						 '%name%' => $user->display_name,
						 '%blogname%' =>  get_bloginfo('name'),
						 '%blogurl%' => get_bloginfo('url'),
						 '%approval_url%' => get_bloginfo('url') . '/wp-admin/options-general.php?page=' . dirname(plugin_basename(__FILE__)) . '/absolute_privacy.php&mode=moderate&id='.$user_id
						 );
					 
		$email_body = strtr(stripslashes($message), $replace); //get email body and replace variables
	
		$headers = "MIME-Version: 1.0¥n" .
				   "From: " . get_option('blogname') . " <" . get_option('admin_email') . ">";   
		wp_mail( $to_email, $subject, $email_body, $headers);
		
		return;
}

11個目のメンバ関数:moderateMenu():管理画面にアクセスできるか

	/**
	 * moderateMenu function.
	 * installes the "Moderate Users" page, which displays all users currently not approved on the blog
	 * @access public
	 * @return void
	 */
	function moderateMenu(){ 
			if (function_exists('current_user_can')) {
				if (!current_user_can('manage_options')) return;
			} else {
				global $user_level;
				get_currentuserinfo();
				if ($user_level < 10) return;
			}

			add_submenu_page('users.php', 'Moderate Users', 'Moderate Users', 'edit_themes', basename(__FILE__), array(&$this,'moderateUsers'));
	}

ユーザがadmin権限を持っていれば、管理画面にサブページを加えるです。

12個目のメンバ関数:moderateUsers():

長いです。

この画面を出力します。
フォームです。

	/**
	 * moderateUsers function.
	 * handles the moderate users function
	 */
	function moderateUsers(){
		global $wpdb;
		$options = get_option($this->options);
		
		if (function_exists('current_user_can')) {
			if (!current_user_can('manage_options')) wp_die('You are not able to do that');
		} else {
			global $user_level;
			get_currentuserinfo();
			if ($user_level < 10) wp_die('You are not able to do that');
		}

		//get all users who are unapproved
		$query = "SELECT user_id FROM ".$wpdb->usermeta." WHERE meta_key = '" . $this->capabilities . "' AND meta_value LIKE '%" . $this->role_ref . "%';";
		$unapproved = $wpdb->get_col($query);
		
		if (isset($_POST['update_options'])) {
		
			if ($_POST['update_options'] == "Delete Selected Users"){
				foreach($_POST['users'] as $user){
					if (!current_user_can('delete_user', $user)){
						wp_die(__('You can&#8217;t delete that user.'));
					}
					if($user == $current_user->ID) {
						wp_die('You cannot delete yourself.');
					}
			
					wp_delete_user($user);
				}
			// Show a message to say we've done something
			echo '<div class="updated"><p>' . __('User(s) deleted') . '</p></div>';
			return;
			}
			if ($_POST['update_options'] == "Approve Selected Users"){
				foreach($_POST['users'] as $user){
					$user = get_userdata($user);
					$user_role = new WP_User($user->ID);

					$user_role->set_role("subscriber");
					
					$this->handleEmail($user->ID, $type= 'account_approved');
					
				}
					// Show a message to say we've done something
					echo '<div class="updated"><p>' . __('User(s) Approved. Notifications sent via email.') . '</p></div>';
					return;
			}
		}
	
		$output = '<div class="wrap">
		    		<h2>Absolute Privacy: Moderate Users</h2>
					<form method="post" action="">
 					<table id="tablo" class="widefat fixed" cellspacing="0">
				<p id="tablo_para">The following users have registered but not been approved to login.</p>

				<thead>
				<tr class="thead">
				<th scope="col"  class="manage-column column-cb check-column" style=""><input type="checkbox" /></th>
				<th scope="col" id="cb" class="manage-column column-cb check-column" style="">ID</th>
				<th scope="col" id="username" class="manage-column column-username" style="">Username</th>
				<th scope="col" id="name" class="manage-column column-name" style="">Name</th>
				<th scope="col" id="email" class="manage-column column-email" style="">E-mail</th>
				<th scope="col" id="role" class="manage-column column-role" style="">Status</th>
				<th scope="col" id="role" class="manage-column column-role" style="">Registration Date</th>
				</tr>
				</thead>

				<tfoot>
				<tr class="thead">
				<th scope="col"  class="manage-column column-cb check-column" style=""><input type="checkbox" /></th>
				<th scope="col" id="cb" class="manage-column column-cb check-column" style="">ID</th>
				<th scope="col" id="username" class="manage-column column-username" style="">Username</th>
				<th scope="col" id="name" class="manage-column column-name" style="">Name</th>
				<th scope="col" id="email" class="manage-column column-email" style="">E-mail</th>
				<th scope="col" id="role" class="manage-column column-role" style="">Status</th>
				<th scope="col" id="role" class="manage-column column-role" style="">Registration Date</th>
			</tr>
			</tfoot>
			<tbody id="users" class="list:user user-list">';
		
		echo $output;

		$i=0;
		$state="class='alternate'";
 	
 		foreach($unapproved as $user_id){
			$user = get_userdata($user_id);
			$capability = $this->capabilities;
			$a = $user->$capability;
			$i++;

			echo "<tr id='user-$i' $state>
			  <th scope='row' class='check-column'><input type='checkbox' name='users[]' id='$user_id' class='administrator' value='$user_id' /></th>
			  <th scope='row' class='check-column'>$user_id</th>
			  <td class='username column-username'><strong><a href='user-edit.php?user_id=$user_id'>{$user->user_login}</a></strong></td>
			  <td class='name column-name'>{$user->user_firstname} {$user->user_lastname} </td><td class='email column-email'><a href='mailto:{$user->user_email}' title='e-mail: {$user->user_email}'>{$user->user_email}</a></td>
			  <td class='role column-role'>$this->rolename</td>
			  <td class='column-name'>{$user->user_registered}</td>
			  </tr>";
			
			if($state == "class='alternate'"){ $state = ''; continue;}
			if($state == ''){ $state = "class='alternate'"; continue;}
		 }

		if($i == 0){ echo "</table><script type='text/javascript'>document.getElementById('tablo').style.display = 'none'; document.getElementById('tablo_para').style.display = 'none';</script><p><strong>No users are waiting moderation</strong></p>"; 
			echo "</table></form></div>"; 
			return; 
			}

		$output = '</table>
					<p style="margin-bottom: 0px;">Approved users will receive an email notification of their approval.</p>
					<div class="submit" style="float: left;"><input type="submit" name="update_options" value="Delete Selected Users"  onClick="return confirm(¥'Really Delete? (This cannot be undone)¥')" style="font-weight:bold; color: red; float: left;" /> </div>
					<div class="submit" style="float: left;"><input type="submit" name="update_options" value="Approve Selected Users"  style="font-weight:bold; float: left;" /> </div>
					</form>';
		echo $output;
	}

DBに書き込んだり色々です。

13個目のメンバ関数:check_is_feed($content):フィードへのアクセスを許すかどうか

	function check_is_feed($content){
		$options = get_option($this->options);
		if(is_feed()) :
			switch($options['rss_control']) {
				case "on":
					//allow full RSS
					break;
				
				case "headline":
					$content = '';
					break;
			
				case "excerpt":
					$content = substr(strip_tags(get_the_content()), 0, $options['rss_characters']) . "...";
					break;		
			}
		endif;
		
		return $content;
	}

14個目のメンバ関数:lockDown():ログインしてないと入れません

入れませんが、一部OKだったりとかそういう機能があったり、どこにリダイレクトをさせるのかなどを設定できるので、細かく処理を分岐させてます。

	/**
	 * lockDown function.
	 * redirects non-logged users if setting is enabled 
	 */
	function lockDown(){
		global $wp_version;

		$options = get_option($this->options);
		
		if(is_feed() && $options['rss_control'] != "off") return; //allow RSS feed to be handled by check_is_feed() function unless the RSS feed is disabled.
					
		if(($options['members_enabled'] == "yes") && (!is_user_logged_in()) ){
			
			if( isset($options['allowed_pages']) && $options['allowed_pages'] != '' ){
				$allowed_pages = explode(',', $options['allowed_pages']);
				if(is_page($allowed_pages) || is_single($allowed_pages) ) return;  //let them visit the allowed pages
			
			}
		
			if( (isset($options['redirect_page'])) && ($options['redirect_page'] != '') ){
			
				if(is_single($options['redirect_page']) || is_page($options['redirect_page'])) return; //end the function is the visitor is already on the redirect_page page
				
				$requested_url = get_permalink($options['redirect_page']);
				
				if($wp_version < 2.8){
					$requested_url = urlencode($requested_url); //WP 2.8+ encodes the URL
				}	
				
				$url = $requested_url;
							
			}else{
	 			$requested_url = (!empty($_SERVER['HTTPS'])) ? "https://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'] : "http://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
		
				if($wp_version < 2.8){
					$requested_url = urlencode($requested_url); //WP 2.8+ encodes the URL
				}
		 		
		 		$url = wp_login_url($requested_url); 
			}
			
				wp_redirect($url, 302);
				exit();	
		}
		
		return;
	}

15個目のメンバ関数:adminLockDown():管理者ページも入れません

色々段階的なアクセス権限があるので、細かく処理を分岐させてます。

	function adminLockDown(){
		global $userdata, $userlevel;
		
		if(!is_admin() || !(is_user_logged_in()) ) return; 
		//if it's not an admin page or the user isn't logged in at all, we don't need this
		
		$options= get_option($this->options);
		
		$user_role = new WP_User($userdata->ID);
		$capabilities = $this->capabilities;
		 
		if ($options['admin_block'] == "yes" && array_key_exists('subscriber', $user_role->$capabilities)){
	 		$url = get_bloginfo('url'); 
			wp_redirect($url, 302);
			exit();	
		}		
	}

めでたく全メンバ関数を読みました。

} // end class declaration
} // end !class_exists check

ふう。

この記事が気に入ったら
フォローしてね!

著者について

コメント

コメントする

目次