wordpress外掛開發 簡易框架教學

當初自學wordpress外掛的時候遇到不少瓶頸,因為不熟悉wordpress的框架與運作模式,所以在外掛的開發上遇到不少問題,所以才有了撰寫這篇教學的想法,希望可以幫助到想撰寫wordpress外掛卻又不知從何下手的新手們 ,本篇教學是依據我本身開發經驗撰寫而成,觀念上可能有些許錯誤,還請各位多多包涵!

相信大家看教學最在意的就是範例程式碼,畢竟辛辛苦苦地跟著教學做了半天,如果最後弄出來無法執行,應該會很挫折吧!

下面的範例程式碼是經過實測過的,建議小夥伴們先下載下來配合著教學服用

範例程式碼:

https://drive.google.com/file/d/14Awz8BMIZdwPPRxUe1kUflViS6VSN2tL/view?usp=sharing

首先,建立一個外掛的根目錄下一定會有一個php檔,是用來載入這個外掛其他程式碼的切入點,以範例程式碼來說我將它取名為myplugin.php,讓我們先來看這支程式的內容

/*
 * Plugin Name: 外掛名稱
 * Description:外掛名稱 by 開發者名稱
 * Author: 開發者名稱
 * Plugin URI: 網址 不重要
 * Version: 版本(EX:1.0)
 */

//此區域是描述外掛的區域(必填)

//假設我的外掛名稱叫myplugin,我將其簡稱為MP(可依個人喜好)
if(!defined('MP_DIR')){
	define('MP_DIR', dirname(__FILE__));
}
//把MP_DIR定義為本外掛的根目錄,後面要呼叫相對路徑時會方便很多
define('MP_URL', plugin_dir_url(__FILE__));

//這邊要include主要是'後端處理'的php,我習慣將這些放在include資料夾內
function LoadMyplugin_Class(){
	include MP_DIR.'/includes/class_myplugin-main.php';
	include MP_DIR.'/includes/class_myplugin-cofunction.php';
	include MP_DIR.'/includes/class_myplugin-setting.php';
	$GLOBALS['MP']=MP();
}
//我自己會建議至少有這三支程式
//cofunction用來寫共用函式,setting則用來撰寫此外掛的設定頁面,會在之後的教學詳細說明
function MP(){
	return My_Plugin::instance();
}
add_action('plugins_loaded', 'LoadMyplugin_Class');
//綁定action讓wordpress載入本外掛

到這邊外掛的切入點主要就已經完成了,接下來讓我們看include資料夾內主要的class_myplugin-main.php這支程式

if(!class_exists('My_Plugin')):

class My_Plugin{
	public static $_instance=NULL;

	//我的理解是建構子
	function __construct(){
		global $wpdb;//讓外掛可以呼叫wp資料庫函式

		//這邊是判斷當前頁面是不是wp的後台,所以後台所需的東西要在這裡引入
		if(is_admin()){
			add_action('admin_menu', array($this, 'AdminMenu'), 1);
			//在後台頁面的左側加上自己設定的頁面的函式,如果外掛不需要做後台頁面就不用加
		}
		else{
			add_shortcode('mp-extend', array($this, 'RenderShortCode'));
			add_action('wp_head', array($this, 'AddScripts'));
			add_action('wp_head', array($this, 'AddStyles'));
			//這邊我習慣預設這三個
			//RenderShortCode是用來產生短代碼,在用elementor建構頁面時可以把外掛的頁面嵌入在適當的地方
			//AddScripts是用來引入全域會用到的js
			//AddStyles用來引入全域會用到的css
		}

		add_action('wp_ajax_nopriv_MPexecAjax', array($this, 'MPexecAjax'));
		add_action('wp_ajax_MPexecAjax', array($this, 'MPexecAjax'));
		//這邊先簡單說明一下,這兩行是前端用到ajax時url設為 admin_url('admin-ajax.php') 這個位置然後傳送到後端的data內action設為A
		//後端的add_action('wp_ajax_nopriv_A', array($this, 'Afunction'));只要在Afunction內做好對ajax送來的資料的處理就好
		//有興趣的之後會再開文章詳細說明
		//wp_ajax_MPexecAjax跟wp_ajax_nopriv_MPexecAjax的差別是前者是操作者有登入wordpress的情況才有權限
		//後者是沒有登入wordpress的操作者才有權限
		//所以如果是自製的外掛沒有要結合wp的會員機制的話,建議兩個都加比較保險
	}

	function AddScripts(){
		wp_register_script('my-plugin_script', MP_URL.'/js/my-plugin.js');
		wp_enqueue_script('my-plugin_script');

		wp_localize_script(	
			'my-plugin_script', 
			'MP_vars',
			array(
				'imgroot'		=>MP_URL.'/images/', 
				'templateroot'		=>MP_URL.'/templates/', 
				'ajaxurl'		=>admin_url('admin-ajax.php'),
			));
		//這是wordpress傳php值供前端js使用的方式
		//js的呼叫方式是 EX:MP_vars.ajaxurl,MP_vars.imgroot
		//imgroot跟templateroot建議可以先預設好,js在呼叫圖片或是跳轉頁面的時候比較方便
	}

	function AddStyles(){
		wp_register_style('my-plugin_style', MP_URL.'/css/my-plugin.css');
		wp_enqueue_style('my-plugin_style');
	}
    
    //設定後端管理頁面的時候用
	function AdminMenu(){	
		add_menu_page('我的外掛', __('我的外掛'), __('manage_options'), 'my-plugin_main', create_function('', 'require_once \''.MP_DIR.'/templates/my-plugin_admin.php\';'), 'dashicons-nametag', 56);
		// Add to admin_menu
		//是選單旁邊的符號,可以自己更換
		add_submenu_page('my-plugin_main', __('我的外掛功能一'), __('我的外掛功能一'), __('manage_options'), 'my-plugin_func1', create_function('', 'require_once \''.MP_DIR.'/templates/my-plugin_admin_func1.php\';'));
		//若要在主選單下面加上子選單可以使用此語法
		//這邊不是重點先簡易帶過,有興趣可以google add_menu_page跟add_submenu_page會有更詳細的介紹
	}

	public function RenderShortCode($args){
		if(!is_array($args))return;
		switch($args['type']){
			case 'example_page':
				$this->ExamplePage();
				break;
		}
	}
	//[mp-extend type='example_page'] 可在elementor以shortcode的方式直接插入自製頁面或功能
	//這是我目前覺得最好結合elementor的開發方式

	function ExamplePage(){
		include MP_DIR.'/templates/example_page.php';
	}
        //引入該頁面
	//這邊有需求還可引入只有該功能或頁面需要的特定css跟js

	//讓myplugin.php呼叫用
	public static function instance(){
		if(is_null(self::$_instance))self::$_instance=new self();
		return self::$_instance;
	}
}

endif;

這邊其實我多加了很多一般外掛教學沒有的部分,當初開發的時候照著別的外掛教學做完,雖然外掛是可以執行的,但還有許多常用的功能不知道要如何結合,所以這邊就把一些外掛基本上都會使用到的功能結合進來,建議大家可以稍微做修改拿來當作自己開發外掛的基礎模板

細心的小夥伴應該會發現範例程式碼有幾支引用的程式我都先留空,那是因為要在這篇內全部說明完可能會過於複雜,有興趣的人可以關注一下,之後會不定時更新剩餘部分的範例,當然如果對php及前端語言原本就有些底子的人就可以開始用這個框架玩玩看啦!

google recaptcha驗證串接教學

下載recaptcha
https://github.com/google/recaptcha
載完之後放好就可以了,只要記得呼叫相對路徑就好

先引入https://www.google.com/recaptcha/api.js

function LoginForm(){
		wp_register_script('pm-google_script','https://www.google.com/recaptcha/api.js');
		wp_enqueue_script('pm-google_script');
	}

前端html加入

<div class="g-recaptcha" data-callback="captcha_onclick" data-sitekey="your sitekey" style="margin-top:50px;"></div>
				<input type="hidden" name="recaptcha" id="recaptchaValidator" />

js部分

function captcha_onclick() {
	document.getElementById('recaptchaValidator').value = grecaptcha.getResponse();
}

後端驗證時加入

require(PM_DIR.'/recaptcha/src/autoload.php');
//記得改成最前面那包recaptcha的相對路徑
		// _GOOGLE_RECAPTCHA_SEC_KEY 就是 google 給的 Secret Key
		$recaptcha = new \ReCaptcha\ReCaptcha('6LeXo70UAAAAAORn5xn-s9QFPvBwAj2xOQKXhY63');
		$gRecaptchaResponse = $_POST['recaptcha'];
		$remoteIp = $_SERVER['REMOTE_ADDR'];
		$resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp);
		if(!$resp->isSuccess()){
			echo json_encode(array('error'=>'請先證明您不是機器人'));
			exit();
		}else{
                  //驗證成功時做的事情
                }

記得回傳失敗的時候需要做以下處理,否則登入失敗時驗證區塊會卡死

grecaptcha.reset();

三竹簡訊API開發分享

當初串接三竹簡訊API的時候遇到了不少坑,所以撰寫這篇文章記錄一下看看能不能幫助到迷途的各位,我寫成function的形式方便各位開發,下面程式碼是有包含簡訊及簡易的手機認證

實作部分分為兩個function:

mxp_mitake_sms_text_webhook()是用來檢查使用者的認證及註冊狀況然後發送簡訊

sms_verify()則是用來驗證認證碼是否正確

邏輯部分:
前端填寫完手機號碼並按發送-> mxp_mitake_sms_text_webhook() ->使用者填寫完驗證碼之後按下驗證-> sms_verify()

//簡訊用fumction
	function mxp_mitake_sms_text_webhook() {
		global $wpdb;
		date_default_timezone_set("Asia/Taipei");
                //設定時間
		$phonenum=$_POST["phonenum"];
                //由前端傳送的手機號碼
		if (strpos($phonenum, '09') !== 0  || strlen($phonenum) !== 10) {
			echo json_encode(array('error'=>'手機格式錯誤'));
			exit();
		}
                //手機號碼格式檢查
		$verifyCode=substr(md5(uniqid(rand(), true)),0,5);
		$token_exptime = time()+60*10;//過期時間為10分鐘後
                //驗證時間設為十分鐘
		$cond = $wpdb->prepare(' AND phone_num = %s ', $phonenum); //記得AND前面要留空
		$sql = "SELECT verify_stat FROM {$wpdb->prefix}phoneverify where 1=1";
		$sql.=$cond;
		$verifyStat=$wpdb->get_var($sql);
		if($verifyStat=="T"){
			$cond = $wpdb->prepare(' AND 電話 = %s ', $phonenum); //記得AND前面要留空
			$sql = "SELECT count(*) FROM {$wpdb->prefix}coserRegister where 1=1";
			$sql.=$cond;
			$regflag=$wpdb->get_var($sql);
			if($regflag!=0){
				echo json_encode(array('error'=>'此號碼已註冊'));
				exit();
			}
                        //檢查號碼是否已註冊成功
			else{
				$sql="DELETE  from `{$wpdb->prefix}phoneverify` WHERE phone_num = '".$phonenum."'";
				$intReturn = $wpdb->query($sql);
			}
		}	
		$cond = $wpdb->prepare(' AND phone_num = %s ', $phonenum); //記得AND前面要留空
		$sql = "SELECT count(phone_num) FROM {$wpdb->prefix}phoneverify where 1=1";
		$sql.=$cond;
		$smsCount=$wpdb->get_var($sql);
                //檢查是否已發送認證簡訊
		if($smsCount>0){
			$cond = $wpdb->prepare(' AND phone_num = %s ', $phonenum); //記得AND前面要留空
			$sql = "SELECT verify_time FROM {$wpdb->prefix}phoneverify where 1=1";
			$sql.=$cond;
			$smsTime=$wpdb->get_var($sql);
			$nowtime = time();
			if((intval($nowtime)-intval($smsTime))<30){
				echo json_encode(array('error'=>'簡訊已送出囉!請耐心等待,若30秒後未收到請再發送一次!'));
				exit();
			}
			else{
				$sql="UPDATE `{$wpdb->prefix}phoneverify` SET `phone_verify`='".$verifyCode."',`verify_time`= '".$token_exptime."' WHERE phone_num = '".$phonenum."'";
				$intReturn = $wpdb->query($sql);
				if($intReturn==0){
					echo json_encode(array('error'=>'系統出錯囉,請聯絡管理員!'));
					exit();
				}
			}
		}
		else{
			$sql = "INSERT INTO {$wpdb->prefix}phoneverify (`phone_num`, `phone_verify`, `verify_time`)
     VALUES ('".$phonenum."','".$verifyCode."','".$token_exptime."')";
			$intReturn=$wpdb->query($sql);
			if($intReturn==0){
				echo json_encode(array('error'=>'系統出錯囉,請聯絡管理員'));
				exit();
			}
		}
		$url = '三竹簡訊api網址';
                //三竹簡訊api網址,要跟克服申請後才會拿到,好像是不固定的
		$url .= '&username=$username';//$username請換成自己的username
		$url .= '&password=$password';//$password請換成自己的username
		$url .= '&dstaddr='.$phonenum;
		$url .= '&smbody='.urlencode('您的認證碼是:'.$verifyCode);
		$url .= '&CharsetURL=UTF-8';
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		$output = curl_exec($curl);
		curl_close($curl);
		$sms_sec = explode(PHP_EOL,$output);
		$sms_stat = explode("=",$sms_sec[2]);
		$smsStat=$sms_stat[1];
		//回傳的狀態碼
		$sql="UPDATE {$wpdb->prefix}phoneverify SET `sms_stat`= '".$smsStat."' WHERE phone_num = '".$phonenum."'";
		$intReturn = $wpdb->query($sql);
		if((int)$smsStat == 0 || (int)$smsStat==1 || (int)$smsStat == 2 || (int)$smsStat== 4){
			echo json_encode(array('success'=>'認證碼已發送至您的手機!'));
			exit();
		}
		else{
			echo json_encode(array('error'=>'認證碼未發送,請檢查手機號碼是否填寫正確!'));
			exit();
		}
	}
//認證簡訊用function
function sms_verify(){
		global $wpdb;
		date_default_timezone_set("Asia/Taipei");
		$phonenum=$_POST["phonenum"];
		$phoneVerify=$_POST["phoneVerify"];
		$cond = $wpdb->prepare(' AND phone_num = %s ', $phonenum); //記得AND前面要留空
		$sql = "SELECT * FROM {$wpdb->prefix}phoneverify where 1=1";
		$sql.=$cond;
		$dbResult = $wpdb->get_results($sql);
		if($dbResult){
			foreach($dbResult as $value){
				$dbphoneVerify=$value->phone_verify;
				$verifyTime=$value->verify_time;
			}
		}
		$nowtime = time();
		if($dbphoneVerify==$phoneVerify&&intval($nowtime)<=intval($verifyTime)){
			$sql="UPDATE `{$wpdb->prefix}phoneverify` SET `verify_stat`= 'T' WHERE phone_num = '".$phonenum."'";
			$intReturn = $wpdb->query($sql);
			if($intReturn==0){
				echo json_encode(array('error'=>'系統出錯囉,請聯絡管理員'));
				exit();
			}
			echo json_encode(array('success'=>'認證成功!'));
			exit();
		}
		else{
			echo json_encode(array('error'=>'認證失敗!'));
			exit();
		}
	}
?

如果只是要純粹發送簡訊的話只需要這一段

                $url = '三竹簡訊api網址';
                //三竹簡訊api網址,要跟客服申請後才會拿到,好像是不固定的
		$url .= '&username=$username';//$username請換成自己的username
		$url .= '&password=$password';//$password請換成自己的password
		$url .= '&dstaddr='.$phonenum;
		$url .= '&smbody='.urlencode('您的認證碼是:'.$verifyCode);
		$url .= '&CharsetURL=UTF-8';
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		$output = curl_exec($curl);
		curl_close($curl);
		$sms_sec = explode(PHP_EOL,$output);
		$sms_stat = explode("=",$sms_sec[2]);
		$smsStat=$sms_stat[1];
		//回傳的狀態碼
                if((int)$smsStat == 0 || (int)$smsStat==1 || (int)$smsStat == 2 || (int)$smsStat== 4){
			echo json_encode(array('success'=>'認證碼已發送至您的手機!'));
			exit();
		}
		else{
			echo json_encode(array('error'=>'認證碼未發送,請檢查手機號碼是否填寫正確!'));
			exit();
		}