PHP 初心者が WordPress の プラグイン 作成 ! part56 ( 開発用のWordPressに組み込む )

この記事では PHP 初心者 が WordPress プラグイン を 作成 します。 part56 では開発用のWordPressにココナラヘルパを組み込んでいきたいと思います。

前回は久しぶりのプラグイン実装に入る前のおさらいを行いました。

PHP 初心者が WordPress の プラグイン 作成 ! part56 ( 開発用のWordPressに組み込むためのおさらい )

ココナラヘルパの現状

現時点でのココナラヘルパ(class.coconara-helper.php)は以下の通りです。

<?php
namespace com\ik_genety\plugin\coconara;

// autoloadを読み込む
require 'vendor/autoload.php';

use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Page;

use HeadlessChromium\Exception\OperationTimedOut;
use HeadlessChromium\Exception\NavigationExpired;

class Coconara_Helper {
    // ヘッドレスブラウザ
    private $browser;
    private $page;

    // デバッグモード
    private $debug;

    // ココナラアカウント
    private const USER_NAME = 'hoge';
    private const USER_PASSWORD = 'fuga';

    /**
     * コンストラクタ
     */
    function __construct(bool $debug = false) {
        // デバッグモードをセット
        $this->debug = $debug;

        // whereis chromiumでパスを探した
        $browserFactory = new BrowserFactory('/usr/bin/chromium');

        // for Docker option
        $options = [
            'headless' => true,
            'noSandbox' => true ,
            'sendSyncDefaultTimeout' => 100000,
            'windowSize'      => [1920, 1000],
            'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'enableImages'    => false,
        ];

        // ヘッドレスブラウザスタート
        $this->browser = $browserFactory->createBrowser($options);

        // 到達できない場合エラー
        if(!$this->reachable()) {
            throw new Exception('サイトに到達できませんでした。');
        }
    }

    // 到達確認
    private function reachable(): bool
    {
        $oya = get_parent_class($this);
        // トップページを取得する
        $tmpPage = $this->getFirstPage();

        // ページタイトル取得
        $pageTitle = $tmpPage->evaluate('document.title')->getReturnValue();
        
        //echo $pageTitle;

        // ブラウザからページを破棄
        $tmpPage->close();

        // タイトル確認
        return $pageTitle === 'ココナラ - みんなの得意を売り買い スキルマーケット';
    }

    // トップページを取得
    private function getFirstPage(): Page
    {
        // 新しいタブを作成しサイトにアクセス
        $tmpPage = $this->browser->createPage();
        $tmpPage->navigate('https://coconala.com/')->waitForNavigation();

        return $tmpPage;
    }
    

    // ログインを行う
    private function login() : bool
    {
        // トップページを取得する
        $this->page = $this->getFirstPage();

        // ココナラのログイン画面に遷移する(30秒待つ)
        $this->page->evaluate('document.querySelector("li.c-mainNavBeforeLogin_item-pc a:first-child").click()')->waitForPageReload(Page::LOAD, 30000);

        // アカウント入力
        $this->page->evaluate(
        '(() => {
            document.querySelector("#UserLoginEmail").value = "'.self::USER_NAME.'";
            document.querySelector("#UserLoginPassword").value = "'.self::USER_PASSWORD.'";
            document.querySelector("form#UserLoginForm button[type=\"submit\"]").removeAttribute("disabled");
        })()');

        // ログインを実施(エラーは全て無視)
        try{
            $evaluation = $this->page->evaluate('document.querySelector("form#UserLoginForm button[type=\"submit\"]").click()')->waitForPageReload(Page::LOAD, 30000);
        } catch(OperationTimedOut $e) {
        } catch(NavigationExpired $e) {
        }

        // ダッシュボードのリンク確認を行ってログイン状態か確認する
        $isLogin = $this->page->evaluate('document.querySelector(".c-mainNavProviderLeft_item") != null ? true : false;')->getReturnValue();
        return $isLogin;

    }

    /**
     * 画面遷移をする
     * エラーを無視しない場合、Exceptionが発生した場合は失敗となる。
     * @param string $query セレクタ
     * @param int $waitTime 待ち時間
     * @param ignoreError エラーを無視する
     * @return true 成功, false 失敗
     */
    private function moveAndWait(string $query, int $waitTime = 30000, bool $ignoreError = true) : bool {
        $result = false;

        // 遷移
        try{
            $this->page->evaluate($query)->waitForPageReload(Page::LOAD, $waitTime);
            $result = true;
        } catch(\Throwable  $e) {
            if($ignoreError) {
                $result = true;
            } else {
                //echo $ex->getMessage();
                $result = false;
            }
        } 
        if($this->debug) {
            // 年月日_時分秒(YYYYMMDD_hhmmss)
            $t = date('Ymd_His');
            // screenshot(10秒待機)
            $this->page->screenshot()->saveToFile("/var/www/html/moveAndWait_{$t}.png", 10000);
        }

        return $result;
    }

    /**
     * 処理を実行をする
     * @param string $query セレクタ
     */
    private function execute(string $query) : void {
        
        // 実行
        $this->page->evaluate($query);
        
        if($this->debug) {
            // 年月日_時分秒(YYYYMMDD_hhmmss)
            $t = date('Ymd_His');
            // screenshot(10秒待機)
            $this->page->screenshot()->saveToFile("/var/www/html/execute_{$t}.png", 10000);
        }
    }


    /**
     * 投稿する
     * @param string $title タイトル
     * @param string $contents コンテンツ
     * @return bool 投稿成功・失敗
     */
    public function post(string $title, string $contents) : bool {
        $result = false;

        // ログイン
        if($this->login()) {
            // ブログ画面に遷移(ブログを投稿する→記事の種類選択で編集画面に遷移)
            if($this->moveAndWait('document.querySelector("a.c-subLink_item-blog").click()')
                && $this->moveAndWait('document.querySelector("button.button.is-primary").click()', waitTime:10000)) {


                    // ブログを編集する

                    // 本文を分解する
                    $fixedRetuenCodeContents = str_replace(array("\r\n", "\r", "\n"), "\n", $contents);
                    $contentArray = explode("\n", $fixedRetuenCodeContents);
                    
                    // 値の生成
                    $contentForBody = "";
                    $contentForBodyText = "";
                    foreach ($contentArray as $item) {
                        // body用の値
                        $contentForBody .= "<div data-v-22617325=\\\"\\\" class=\\\"c-blogBody_text\\\">{$item}</div>";

                        // bodyText用の値
                        $contentForBodyText .= "{$item}\\n";
                    }
                     
                    // ヘッドレスブラウザに渡すクエリの作成
                    $titleAndContentsQuery = 
                    '(() => {
                        obj = document.querySelector("[data-v-46ae5ace]");
                        obj.__vue__.title = "'.$title.'";
                        obj.__vue__.body = "'.$contentForBody.'";
                        obj.__vue__.bodyText = "'.$contentForBodyText.'";
                        obj.__vue__.blogEmpty = false;
                    })()';

                    // 処理実行
                    $this->execute($titleAndContentsQuery);


                    // 公開画面を編集し投稿する

                    // 画面表示
                    if($this->moveAndWait('document.querySelector("button.button.c-blogPost_triggerPublish").click();', waitTime:10000)) {

                        // カテゴリとハッシュタグを指定
                        $categoryAndHashTagQuery = 
                        '(() => {
                            // カテゴリの選択
                            document.querySelector("div.c-blogPublishing_category select option[value=\"9\"").selected = true;
                            document.querySelector("div.c-blogPublishing_category select").dispatchEvent(new Event("change"));
                      
                            // ハッシュタグの指定
                            obj = document.querySelector("[data-v-46ae5ace]");
                            obj.__vue__.blogTagNames.push(" IT");
                            obj.__vue__.blogTagNames.push(" WordPress");
                    
                        })()';

                        // 処理実行
                        $this->execute($categoryAndHashTagQuery);


                        // 投稿!
                        if($this->moveAndWait('document.querySelector("button[data-v-6c22d71e].button.is-primary").click();', waitTime:10000)) {
                            $result = true;
                        } else {
                            $result = false;
                        }
                    } else {
                        $result = false;
                    }
            } else {
                $result = false;
            }
        } else {
            $result = false;
        }

        if($this->debug) {
            // screenshot(10秒待機)
            $this->page->screenshot()->saveToFile('/var/www/html/post.png', 10000);
        }
        return $result;
    }

    
    
    /**
     * デストラクタ
     */
    function __destruct() {
        // ヘッドレスブラウザを閉じる
        $this->browser->close();
    }
}

 

長い!約200行ほどでしょうか。
そろそろGitHubか何かでソース管理しないといけませんかね😅

このコードを組み込んでいこうと思います。

組み込み

ココナラヘルパを組み込むのはいたって簡単です。
ただPHPファイルを配置してindex.phpでの実装のように使用すればいいわけですから。

index.php

<?php
require_once 'class.coconara-helper.php';

// クラスをまとめてインポート(PHP7.0.0から)
use com\ik_genety\plugin\coconara\{Coconara_Helper};

$coconara = new Coconara_Helper(true);

$title = "ヘッドレスブラウザ順調に(?)実装中!";
$contents = <<<_EOS
WordPressからココナラブログへの書き込み計画・・・
なんとか進めています!
_EOS;

if($coconara->post("{$title}", "{$contents}",true)) {
    echo '投稿成功!';
} else {
    echo '投稿失敗!';
}
?>

心配なのはchrome-phpをComposerで正しくインストールできるのかという点です。
とにかくやってみます!

chrome-phpをインストール

chrome-phpのインストールはVS Codeのターミナルを使用して行います。

chrome-phpのインストールは開発しているプラグインフォルダ配下に入っていないといけないと思っています。
なのでターミナルを使用して移動します。

cd /var/www/html/wordpress/wp-content/plugins/wp_to_coconara

ここでchrome-phpをインストールです。

composer require chrome-php/chrome

こんなメッセージが出ました。

「composer.jsonファイルが無いから/var/www/htmlの下にあるcomposer.jsonでも読み込むかい?」ということのようです。
プラグイン配下に新たにcomposer.json作ってほしいのですが・・・・。nにすると作ってくれるのでしょうか🤔
nを押してみます。

正解でした!

ヒヤヒヤしましたがインストール成功のようです😄

ココナラヘルパの引っ越し

ココナラヘルパを開発中のプラグインフォルダに持ってきます。

移動でもよいのですがコピーをすることにしました。

PHP8で使用するにあたって

一つ大事なことを思い出しました。
今回PHPを使用しているのですが、chrome-phpで使用するライブラリの中にPHP8非対応の実装があったと思います。
そのファイルの修正を行う必要があります。

PHP 初心者が WordPress の プラグイン 作成 ! part17 ( PHPからヘッドレスブラウザ操作 また失敗 )

vendor/wrench/wrench/lib/Wrench/Protocol/Protocol.php が該当のファイルとなります。

//.../vendor/wrench/wrench/lib/Wrench/Protocol/Protocol.php(316)
return implode($handshake, "\r\n") . "\r\n\r\n";
//↓
return implode("\r\n", $handshake) . "\r\n\r\n";


//.../vendor/wrench/wrench/lib/Wrench/Protocol/Protocol.php(382)
return implode($handshake, "\r\n") . "\r\n\r\n";
//↓
return implode("\r\n", $handshake) . "\r\n\r\n";

まとめ

時間が来てしまいました。
今回は開発用のプラグインにchrome-phpのインストールとライブラリの修正、ココナラヘルパを導入を行いました。

次回はプラグインファイルからココナラヘルパを呼び出してみたいと思います。

今日はここまで!