Wonderful! WordPress

いまさらの Ajax でのコメントの編集機能プラグイン

Page No.2

option 設定用画面の管理画面への登録

上のオプションに関することと、この管理画面への登録に関することは、【wordpress管理画面にテーマのオプション設定ページを表示】に書いているのでそちらをご覧あれ。
まぁ、この部分もほとんど同じなのでいつもの使い回し。

<?php
		// ↓class 続き
		//↓ここから管理画面のメニューにオプション設定ページを登録する処理
		// action hook 'admin_menu'
		public function commedt_add_menu() {
			add_options_page( 'Simplistic Comment User Editable plugin Option', 'Simplistic Comment User Editable Option', 'administrator', 'splcomedt_plugin_options', array( $this, 'splcomedt_page_output' ) );
			add_action( 'admin_init', array( $this, 'register_splcomedt_settings' ) );
		}
 
		public function register_splcomedt_settings() {
			register_setting( 'splcomedt-settings-group', $this->option_name );
		}

		public function splcomedt_page_output() {

			// spam防御機能によりコメント送信に失敗したログファイルの読み出し
			$filnam = __DIR__ . '/checklist.php';
			$erlist = array();
			if ( file_exists( $filnam ) ) {
				$erlist = file( $filnam );
				$erlist = array_reverse( $erlist );
			}

			include_once 'explain.php';
		}
		//↑ここまで
		// ↓class 続く
?>
PHP
CopyExpand

で、上で include している、管理画面で表示している HTML の部分。

<?php
    $exp = array(
        '起動させないページのページidを "," で区切って指定<ul style="margin-left:10px;list-style-type:disc;"><li>固定ページ、個別投稿ページ(カスタム投稿含む)が対象</li><li>基本的にコメントの無いページではJavaScript ファイルはロードされませんが、<br>ここで指定したページはプラグインの全機能が作動しません</li></ul>',
        '編集可能な日数',
        'spam防御機能を使用、Yes : 使用、No : 非使用',
        'spam防御機能のエラー時のログを残す、Yes:残す、No:残さない',
        'コメントの削除の可否、Yes : 可、No : 否',
        'style sheetをロードする、1:ロードする、0:しない',
        'javascript fileをロードする、1:ロードする、0:しない',
        '編集、削除があった場合に管理者に通知メールをする、1:する、0:しない',
        'ip addressから作るダミーメールアドレスを入力する、1:する、0:しない',
        '編集用画面の背景色、0:えんじ、1:緑、2:青、3:深紫、4:黒',
    );
?>
    <div class="wrap">
        <h2>Simplistic Comment User Editable v3.1 plugin Option</h2>
        <h3>Option Config</h3>
        <form method="post" action="options.php">
            <?php
                settings_fields( 'splcomedt-settings-group' );
                do_settings_sections( 'splcomedt-settings-group' );
            ?>
            <table class="form-table">
                <tr><td>option</td><td>Yes</td><td>No</td></tr>
                <?php
                    $i = 0;
                    foreach( $this->ret_option as $key => $val ) {
                        if ( 'back_color' === $key ) {
                            echo '<tr><td>' . $exp[ $i ] . '</td><td><input type="number" name="smplstc_commedt_option[' . $key . ']" value="' . ( string )( ( int ) $val ) . '" min="0" max="4"></td><td></td></tr>';
                        } elseif ( 'edit_possible_period' === $key ) {
                            echo '<tr><td>' . $exp[ $i ] . '</td><td><input type="number" name="smplstc_commedt_option[' . $key . ']" value="' . ( string ) ( ( int ) $val ) . '" min="1"></td><td></td></tr>';
                        } elseif ( 'ignore_page' === $key ) {
                            echo '<tr><td>' . $exp[ $i ] . '</td><td><input type="text" name="smplstc_commedt_option[' . $key . ']" value="' . esc_attr( $val ) . '"></td><td></td></tr>';
                        } else {
                            if ( $val === '1' ) {
                                $ychecked = ' checked';
                                $nchecked = '';
                            } else {
                                $ychecked = '';
                                $nchecked = ' checked';
                            }
                            echo '<tr><td>' . $exp[ $i ] . '</td><td><input type="radio" name="smplstc_commedt_option[' . $key . ']" value="1"' . $ychecked . '></td><td><input type="radio" name="smplstc_commedt_option[' . $key . ']" value="0"' . $nchecked . '></td></tr>';
                        }
                        ++$i;
                    }
                ?> 
            </table>
            <?php submit_button(); ?>
        </form>
        <?php
           // spam防御機能によりコメント送信に失敗したログリストの表示
            if ( count( $erlist ) ) {
                echo '<dl><dt>Not checked list</dt><dd><ul>';
                foreach( $erlist as $val ) {
                    echo '<li>' . $val . '</li>';
                }
                echo '</ul></dd></dl>';
            }
        ?>
    </div>
PHP
CopyExpand

スパム防御機能

ついでに付加した機能のスパムコメントロボット対策のチェックボックス。
随分と前に【スパムコメント対策】のページにて施したもの。
実際のところ、ほとんど防げているのではなかろうか。この機能で失敗したログのリストによって htaccess に次から次へとdeny from にリストしていったので、もちろんその効果によるところも大きいとは思うのだけれど。
で、実はこのプラグインのものは前のバージョンもだけど、【スパムコメント対策】のページのものから少し改良していて、チェックボックスを二つにしてる。

一つはコメントを送信するときにチェックが必要で、もう一つは逆にチェックしてあってはいけないということで判断すると。要は同じものが二つあって、一つは可、一つは否をロボットは判断できないだろうという目論見ですな。否の方は css で hidden にしておけば人間様には見えないのでチェックしようがないということです。

あと、コメント送信には JavaScript を必須にするというのも有効な手だてなのではないかと。たとえば、否の方のチェックボックスをデフォルトでチェックしておいて JavaScript で起動時に外すということにしておけばできるのではないかと。

と、いうことでそのスパム対策の部分。
で、ここでは、そのチェックボックスと一緒に、コメントユーザー認証用の editkey のテキストボックスも挿入してる。

<?php
		// class 続き↓
		// spam防止用のチェックボックスと認証用editkeyのテキストボックスを登録する関数
		// action hook 'comment_form_after_fields'
		public function add_comment_check() {
				if ( '1' === $this->ret_option['en_delete'] ) {
				$deletestr = array( '・削除', ' or delete' );
			} else {
				$deletestr = array( '', '' );
			}

			if ( 'ja' === $this->lang ) {
				$labelstr = array( '<span id="editkey-note">editkey を設定すると後で訂正' . $deletestr[0] . 'ができます。</span>', '※英字(大文字小文字)のみ5文字以上', 'コメント送信時にチェックしてください。スパムメール対策です。', '※承認後 ' . $this->ret_option['edit_possible_period'] . '日間、編集' . $deletestr[0] . 'が可能です' );
			} else {
				$labelstr = array( '<span id="editkey-note">If enter editkey, you can edit' . $deletestr[1] . ' later.</span>', '※Please enter more than 5 characters only alphabets.', 'Please check it at leave a comment.', '※Edit' . $deletestr[1] . ' are possible for ' . $this->ret_option['edit_possible_period'] . ' days after approval.' );
			}

            // 変数e_startupは編集機能を使用するか否か
			if ( 1 === $this->e_startup ) {
?>
				<p class="comment-form-editkey">
					<label for="editkey">editkey : <?php echo $labelstr[0]; ?></label>
					<input id="editkey" name="editkey" type="text" value="" size="30" /><span id="editkey-subnote"><?php echo $labelstr[1] . '<br>' . $labelstr[3]; ?></span>
				</p>
<?php
			}
            // 変数s_startupはspam防御機能を使用するか否か
			if ( 1 === $this->s_startup ) {
?>
				<p class="comment-form-check">
					<input type="checkbox" name="please_check" id="please_check" value="reply" style="visibility:hidden;" />
					<input type="checkbox" name="comment_check" id="comment_check" value="send" /><span class="required">*</span>
					<label  for="comment_check" id="comchecklbl"><?php echo $labelstr[2]; ?></label>
				</p>
				<p style="color:#0000dd;">♠Simplistic Comment User Editable v<?php echo $this->version; ?></p>
<?php
			}
		}

        // spam防止用のチェックボックスをコメントが保存される前にチェックする関数
		// action hook 'pre_comment_on_post'
		public function my_comment_spam_check( $comment_post_ID ) {
			// optionでスパム防御機能を使用する設定であれば
            if ( '1' === $this->ret_option['en_def_spam'] ) {

            	// comment_checkがチェックされていないかまたはplease_checkがチェックされている場合に
                // コメントを保存しない
				if ( ! isset( $_POST['comment_check'] ) or isset( $_POST['please_check'] ) ) {

					// 拒否したときのログを残すoptionの設定であれば
					// ファイルにログを残す処理
                    if ( '1' === $this->ret_option['save_check_er_log'] ) {
						$checkstr = '';
						if ( ! isset( $_POST['comment_check'] ) ) {
							$checkstr .= 'Comment_Check not checked';
						}
						if ( isset( $_POST['please_check'] ) ) {
							$checkstr .= ' Please_Check checked';
						}
						$cdt = $this->japtime( 'Y/m/d H:i:s' );
						$hname = gethostbyaddr( $this->ipadrs );
	
						$filnam = __DIR__ . '/checklist.php';
						$fp = fopen( $filnam, 'a+b' );
						flock( $fp, LOCK_EX );
						fputs( $fp, "$cdt  [$this->ipadrs]  [$hname] $checkstr\n" );
						fclose( $fp );
					}

					wp_die( 'ERROR' );
				}
			}
		}
        // ↓class続く
?>
PHP
CopyExpand

アクションフック wp_set_comment_status とフィルターフック preprocess_comment

コメントが承認されたときに、アクションフック wp_set_comment_status を使って、commnet_date をその時の日時に更新。これは、コメントの編集・削除に制限期間を設けていることで必要になってくること。承認されてからの期間ということです。ここでも、wp_update_comment() を使って comment_date の値を更新してるわけだけれど、前述したとおり、WordPresscurrent_time( ‘mysql’ ) を使っているので、不思議に思いながらもそれを踏襲してる。で、これも前述したけれど、comment_date_gmt の方は、wp_update_comment() で自動的に更新してくれるので、わざわざこちらで処理する必要はないのです。
フィルターフック preprocess_comment の方は、mail address に ip address から作ったダミーアドレスをセットするのに使用。

<?php
		// class 続き↓
        // アクションフック wp_set_comment_status を使って、コメントが承認されたときに comment_date を更新
        public function set_approve_date( $comment_ID, $comment_status ) {
			if ( 'approve' === $comment_status ) {
				$com_data = array(
					'comment_ID' => $comment_ID,
					'comment_date' => current_time( 'mysql' ),
				);
				wp_update_comment( $com_data );
			}
		}

		// mail addressにip addressから作ったダミーアドレスをセットする関数
		// filter hook 'preprocess_comment
		public function set_dummy_mailaddress( $commentdata ) {

			$iptmp = explode( '.', $this->ipadrs );
			$commentdata['comment_author_email'] = $iptmp[0] . $iptmp[1] . '@' . $iptmp[2] . '.' . $iptmp[3];
			return $commentdata;
		}
		// ↓class続く
?>
PHP
CopyExpand

起動時やら JavaScript ファイルのロードとかいろいろ

<?php
		// class 続き↓
               // page 読み込み時にアクションフック wp にてプラグインの動作を設定する関数
		public function branch_start() {
			$ignore_page = array();

			if ( '' !== $this->ret_option['ignore_page'] ) {
				$ignore_page = explode( ',', $this->ret_option['ignore_page'] );
				foreach( $ignore_page as $val ) {
					$intvalue = ( int ) $val;
                    // is_single() はカスタム投稿でも有効
					if( is_page( $intvalue ) or is_single( $intvalue ) ) {
						$this->e_startup = 0;
						$this->s_startup = 0;
					}
				}
			}

			if ( '0' === $this->ret_option['en_def_spam'] ) {
				$this->s_startup = 0;
			}
		}

              //プラグインの javascript ファイルとスタイルシートを登録する関数
		public function load_css_jq() {
			if ( 1 === $this->s_startup or 1 === $this->e_startup ) {
				if ( '1' === $this->ret_option['load_js'] ) {
					wp_enqueue_script( 'splstc_comedit_js',plugins_url( 'splstc_comedit.js', __FILE__ ), false, date( 'YmdHis', filemtime(plugin_dir_path( __FILE__ ).'splstc_comedit.js' )),true);
				} else {
                    // スタイルシートは js ファイルをロードしないときには設定により必要になる場合がある
					if ( '1' === $this->ret_option['load_style_sheet'] ) {
						$stylesheet = 'splstc_comedit_r.css';
						wp_enqueue_style( 'splstc_comedit_style', plugins_url( $stylesheet, __FILE__ ), false, date( 'YmdHis', filemtime(plugin_dir_path( __FILE__ ).$stylesheet )) );
					}
				}
			}
		}

		//編集フォーム用ポップアップウィンドウを表示するためにwp_footerに登録されるhtml
		public function regist_footer() {
			if ( 'ja' === $this->lang ) {
				$exp = array( '処理をキャンセルして通常表示に戻ります',
					'コメントID',
					'コメント送信者',
					'編集ボタンで編集した結果を保存します。',
					'このコメントを削除します。戻すことは出来ませんのでご注意ください。'
				);
			} else {
				$exp = array( 'cancel processing and return',
					'Comment ID',
					'Comment Author',
					'save edited comment with an edit button.',
					'Delete this comment. Because you cannot go back up, please be careful.'
				);
			}

			$colorary = array( 'r', 'g', 'n', 'w', 'b' );
			$colorclass = $colorary[ $this->ret_option['back_color'] ] . 'color';
                   // javascript からスタイルを設定するか否かのフラグ、enstyle:設定する
			$stylechoice= array( 'disstyle', 'enstyle');
			$en_style = $stylechoice[ ( ( int ) $this->ret_option['load_style_sheet'] & 1 ) 
?>
			<section id="comeditsec" class="<?php echo $colorclass; ?>">
				<div id="comeditfrm" data-style="<?php echo $en_style; ?>">
					<div class="lascomfrm">
						<p id="erasecomfrm" title="<?php echo $exp[0]; ?>">×</p>
						<p id="lastchckfrmp"><?php echo $exp[1]; ?> : <span id="lcomid"></span><br>
							<?php echo $exp[2]; ?> : <span id="lcomauth"></span></p>
						<form name="comeditform" action="" method="post">
							<input type="hidden" id="lascont" name="lascont" value=""><br>
<?php
							//wp nonce フィールドを表示
							wp_nonce_field( wp_create_nonce( __FILE__ ), 'com_nonce' );
?>
							<textarea id="lasthon" name="lasthon" rows="20" cols="80" required></textarea><br>
							<p><?php echo $exp[3]; ?></p>
							<button type="button" id="editlast" name="editlast">edit</button>
							<p><?php echo $exp[4]; ?></p>
							<button type="button" id="dellast" name="dellast">delete</button>
						</form>
						<p>Simplistic Comment User Editable v3.0</p>
					</div>
				</div>
			</section>
<?php
		}

		public function splstc_comedit_resist_script() {
	
			//↓ポップアップウィンドウを表示するための HTML をwp_footerに登録
			add_action( 'wp_footer', array( $this, 'regist_footer' ) );
		}
		// ↓class 続く
?>
PHP
CopyExpand

editkey 関連

そして、やっと editkey の周辺。

<?php
		// class 続き↓
		// ekitkeyが入力されているそれぞれのコメントにコメントIDとエディとボタンを表示する関数
        // filter hook 'comment_text'
		public function add_commentlist_edit_bt( $comment_text, $comment =null, $args = null ) {
			if ( 1 === $this->e_startup and ! is_admin() ) {
				if ( ! is_null( $comment ) ) {

                    //現在の時間とコメント送信時間を比較して編集可能期間かを調べる
                    $nowtime = $this->japtime();
                    $comtime = strtotime( $comment->comment_date );
                    $timedif = floor( ( $nowtime - $comtime ) / (60 * 60 * 24 ) );
                    $timelimit = ( int ) $this->ret_option['edit_possible_period'];
                    $timedir = $timedif <= $timelimit ? true : false; 

					// コメントIDからcommentmetaテーブルにあるeditkeyを取得
                    $comid = $comment->comment_ID;
                    $editkey = get_comment_meta( $comid, 'my_comment_editkey' );

                    // editkeyがあり編集可能期間内である場合にそのコメントにコメントID、または編集指令ボタンを表示
                    if ( $editkey and $timedir ) {
                        $comment_text = '<p id="comcont-' . $comid . '">' . $comment_text . '</p><div id="cmeditbt-' . $comid . '" class="comeditorderbt" title="Java script is necessary" data-auth="' . $comment->comment_author . '">edit</div>';
 
                        // 初めてエディットボタンを表示する時に JavaScript ファイルのロード、
                        // ポップアップウィンドウのフックへの登録をする
                        // ここでフックへの登録をすれば、編集可能なコメントが無い場合、 js ファイルはロードされない
                        if ( $this->is_first ) {
                            $this->load_css_jq();
                            $this->splstc_comedit_resist_script();
                            $this->is_first = false;					
                        }
                    }
				}
			}
			return $comment_text;
		}

		// コメントが保存された時にeditkeyを保存する関数
        // action hook 'comment_post'
		public function save_comment_editkey( $comment_id ) {
			if ( isset( $_POST['editkey'] ) and $_POST['editkey'] ) {
				$editkey = md5( $_POST['editkey'] );
				add_comment_meta( $comment_id, 'my_comment_editkey', $editkey );
			} else {
				return;
			}
		}

        // コメントが削除された時にeditkeyも削除する関数
        // action hook 'deleted_comment'
		public function delete_editkey( $comment_id ) {
			delete_comment_meta( $comment_id, 'my_comment_editkey' );
		}

		// ↓class 続く
?>
PHP
CopyExpand

Ajax 関連

そして、最後に Ajax の関数を2つとプラグインの締め。
と、いうところで、こういうユーザーからの入力を表示したり保存したりする時に、必ずついてまわってくるのが、そのデータのサニタイズ。HTML の規律を壊しうる文字に対し、最低でもエスケイプは必須なのだけれど、form から submit させて ページ遷移し post で送信したデータなら、WordPress 側で全部処理してくれるので、ほぼ考える必要はないと思うのであるけれど。
Ajax の場合は、ちょっとそのあたりのことを面倒みなくてはいけなくなるわけで。いつもこれで考えこんでしまうが、どうするのが最良なのだろうかと。問題は、表示されているページの、今、まさに Ajax において更新されたコメント欄において、古いコメント文を更新されたコメント文に入れ替えるところになる。まぁ、それでも大方のところは WordPress 側に任せてしまえば良いように思う。ちょっと回りくどいことになるけれど。

<?php
		// class 続き↓
        // Ajax editkey 認証
        public function verifyeditkey() {

            // $this->server の各値は $this->lang_recognize() にて設定してある
			if ( false !== strpos( $this->server['HTTP_REFERER'], $this->server['HTTPS'] ) ) {

				$posts = array(
					'editkey' => '',
					'id' => '',
				);
				$posts = array_merge( $posts, $_POST );
	
				if ( $posts['id'] and $posts['editkey'] ) {
					$editkey_hashed = md5( $posts['editkey'] );
					$password_hashed = trim( get_comment_meta( ( int ) $posts['id'], 'my_comment_editkey', true ) );

                    if ( $editkey_hashed === $password_hashed ) {

                        // editkey が認証されたらコメントの本文を取得して javascript 側へ返す
                       $comment = get_comment( $post['id'] );
                       echo $comment->comment_content;
                    } else {
                        echo 'error:incorrect editkey!';
                    }
                } else {
                    echo 'error:no data';
                }
            } else {
                echo 'error:different host';
            }
            exit;
        }

        // Ajax comment update
         public function updatecom() {

            // $this->server の各値は $this->lang_recognize() にて設定してある
            if ( false !== strpos( $this->server['HTTP_REFERER'], $this->server['HTTPS'] ) ) {

                // $_POST にて送信されているはずの各値を isset で確認する処理を回避するために
                // あらかじめそれら送信が想定される値の初期値を配列に設定しておき $_POST でarrray_merge させ上書きする
                $posts = array(
                    'editkey' => '',
                    'id' => '',
                    'nonce' => null,
                    'ope' => '',
                    'comid' => '',
                    'auth'=> '',
                    'cont' => '',
                    'lcont' => 'no-data',
                );
                $posts = array_merge( $posts, $_POST );

                // 取得しているnonceで認証を得る
                 if ( wp_verify_nonce( $posts['nonce'], wp_create_nonce( __FILE__ ) ) ) {

                    $ope = $posts['ope'];
                    $comid = $posts['comid'];
                    $editkey = $posts['editkey'];
                    $auth = $posts['auth'];
                    $cont = $posts['cont'];
                    $lcont = $posts['lcont'];

                    if ( $comid and $editkey ) {
                        $editkey_hashed = md5( $editkey );
                        $password_hashed = trim( get_comment_meta( ( int ) $comid, 'my_comment_editkey', true ) );

                        if ( $editkey_hashed === $password_hashed ) {
                            if ( $cont ) {
                                $upcomres = false;
                                $sendstr = '';
                                // $cont =  str_replace( array( '\"', '\\\'', '\\\\' ), array( '"', '\'', '\\' ), $cont );
                                $cont =  urldecode( $cont );

                                if ( 'edit' === $ope ) {

                                    $comarr = array(
                                        'comment_ID' => $comid,
                                        'comment_content' => $cont,
                                    );
        
                                    $upcomres = wp_update_comment( $comarr );
                                    $sendstr = 'Edit comment - author : ' . $auth. ' comment ID : ' . $comid . "\n\nbefore : \n" . $lcont ."\n\nafter : \n" . $cont;
                                    $newcomment = get_comment( $comid );
                                    $cont = $newcomment->comment_content; 

                                } elseif ( 'delete' === $ope ) {
                                    if ( '1' === $this->ret_option['en_delete'] ) {
                                        $upcomres = wp_delete_comment( $comid );
                                        $sendstr = 'Delete comment - author : ' . $auth. ' comment ID : ' . $comid;
                                        $cont = 'Success delete comment!';
                                    } else {
                                        echo 'error:Comment deletion is not allowed';
                                        exit;
                                    }	
                                } else {
                                    echo 'error:No direct operation.';
                                    exit;
                                }

                                if ( $upcomres ) {
                                    echo $cont;

                                    if ( '1' === $this->ret_option['en_notice_mail'] ) {
                                        $admin_email = get_option( 'admin_email' );
                                        wp_mail( $admin_email, 'Edit or Delete comment', $sendstr );
                                    }
                                } else {
                                    echo 'error:Failed update comment!';
                                }
                            } else {
                                echo 'error:No data update comment content.';
                            }	
                        } else {
                            echo 'error:incorrect editkey!';
                        }
                    } else {
                        echo 'error:no data';
                    }
                } else {
                    echo 'error:Rejected by nonce!';
                }
            } else {
                echo 'error:different host';
            }
            exit;
        }
    }
    //↑classここまで

    $simplistic_comment_edit_start = new Simplistic_Comment_User_Editable();

    //↓管理画面のメニューにオプション設定ページを登録する処理
    add_action( 'admin_menu', array( $simplistic_comment_edit_start, 'commedt_add_menu' ) );
?>
PHP
CopyExpand

と、いうところでプラグインの php 部分は終わり。

Sanbanse Funabashi
2011.01.01 sunrise

Top

スクロールさせるか画像をクリックすると元に戻ります。