TRY AND ERROR

気になったこと、勉強したこと、その他雑記など色々メモしていきます。。Sometimes these posts will be written in English.,

最近話題の広告ブロックについて

面白い記事を見かけたので勝手に持論を展開!!
広告ブロッカーの検知と計測について - クックパッド開発者ブログ


WEB上のあらゆる所で目にする広告ですが、
これをブロックする機能を有したツールが出回っていますね。

WEB広告を生業とする企業にとって有害なものであることに
間違いありませんが、現在の日本の法律ではどうなるのか
気になって少し調べてみました。

広告ブロックは法的にまずい?

現在、日本には広告ブロックを直接的に禁止する法律はなさそうですね。
言うとすれば、著作権関連で同一性保持権(著作物の無断改変)があり、
これに該当するしないで争うことになるのではないかと勝手に想像。。

同一性保持権(どういつせいほじけん)は、著作者人格権の一種であり、著作物及びその題号につき著作者(著作権者ではないことに注意)の意に反して変更、切除その他の改変を禁止することができる権利のことをいう(日本の著作権法20条1項前段。以下、特に断らない限り、引用法令は日本のもの)。
同一性保持権 - Wikipedia

ただ、過去に判例がなさそうなのでなんとも言えませんが、
少なくとも刑事罰が課せられるということは考えにくいのでは??

民事責任を問われるか?

刑事罰の対象にならずとも、民事責任を問われるケースもありますね。
(犯罪じゃないけど違法です!的な...)
広告ビジネスを展開する企業にとって広告ブロックは死活問題と成り得るため、
サイトやサービスの利用規約に広告ブロックを禁止しているものも少なくありません。

また損害額を立証しやすいと思うので、
広告ブロックが使用されたことと売り上げ低下の因果関係が認められれば、
利用規約違反ということで民事上の損害賠償とかで刺されるケースも有り得るのではないでしょうか。


まあ何にせよコンテンツを見る見ないはユーザーの自由ですし、
フリーの動画やWEBサイトでは対価を支払っていないわけだから、「ウザイから広告をブロックします!」というのは倫理に反している気もしますね。。

PHPでGoogle Search ConsoleのAPI使ってみた

題名の通り、使ってみたのでめもします。。

やったこと

PHPのサーバアプリでGoogleSearchConsoleの検索アナリティクスのデータを取得。
google-api-php-clientを利用
複数アカウントのGoogleSearchConsoleに登録されている全てのURLの
データを取得
・アクセストークンとリフレッシュトークンをサーバに保存
・認証(アプリ使用許可)は初回のみ


スニペット

getData.php(検索アナリティクスデータ取得、表示)

これが実行ファイル。
初回認証画面で保存したアカウント毎にGoogleSearchConsoleのデータを
取得して表示する。

<?php

set_include_path(get_include_path() . PATH_SEPARATOR . "/path/to/google-api-php-client/src");
require_once __DIR__ ."/google-api-php-client/src/Google/autoload.php";
require(__DIR__ ."/Auth.php");

//3日前くらいのデータじゃないと反映されていないため
$targetDate = date("Y-m-d",strtotime("-3 day"));

try {
	$redirect_uri = "XXXX";

	$client = new Google_Client();
	$client->setAccessType('offline');
	$client->setApprovalPrompt('force');
	$client->setClientId('XXXX');
	$client->setClientSecret('XXXX');
	$client->setApplicationName("XXXX");
	$client->setDeveloperKey("XXXX"); //server key
	$client->addScope(Google_Service_Webmasters::WEBMASTERS_READONLY); //権限の許容範囲("webmasters.readonly")

	$googleUsers = array_filter(scandir(__DIR__ ."/auth"), function($file){
		return !in_array($file, array(".", "..", ".json"));
	});


	foreach ($googleUsers as $filename) {
		$auth = new AuthInfo($filename);
		$client->setAccessToken($auth->getAccessToken());

		//アクセストークンが有効期限切れの場合
		if($client->isAccessTokenExpired()) {
			$NewAccessToken = json_decode($auth->getAccessToken());	//アクセストークンをデコード
			$client->refreshToken($NewAccessToken->refresh_token);	//リフレッシュトークンを使ってアクセストークンの取り直し
			$auth->saveAccessToken($client->getAccessToken());		//外部ファイルに保存

		}

		//search anaryticsデータ取得
		if ($client->getAccessToken()) {
			getSearchAnalytics($client);
		}
	}



} catch (Google_Service_Exception $e) {
	echo "【Google_Service_Exception】”;
    var_dump($e);

} catch (Google_Exception $e) {
	echo "【Google_Exception】";
    var_dump($e);
}


function getSearchAnalytics($client) {
	global $targetDate;
	$service = new Google_Service_Webmasters($client);
	$searchAnalytics = $service->searchanalytics;

	$targetSites = $service->sites->listSites()->siteEntry;

	$analyticsDatas = array();
	
	foreach ($targetSites as $targetSite) {
		$request = new Google_Service_Webmasters_SearchAnalyticsQueryRequest;
		$request->setStartDate($targetDate); //yyyy-mm-dd
		$request->setEndDate($targetDate); //yyyy-mm-dd
		$request->setDimensions(array("query"));
		$request->setRowLimit(5000);

		$q = $searchAnalytics->query($targetSite->siteUrl, $request);
		$rowData = $q->getRows();

		output(getTodaysFile($targetSite->siteUrl), $rowData);
	}

}


function output($rowData) {
	$cnt = 1;
    //kw,click数、impression数
	foreach ($rowData as $row) {
		echo $cnt ."    |    " .$row['keys'][0] ."    |    " .$row['clicks'] ."    |    "  .$row['impressions'] ."<br>";
		$cnt++;
	}
}

?>
Auth.php(認証情報クラス)

アクセストークンやリフレッシュトークンなどの認証情報を扱うクラス

<?php
class Auth {
	private $filename;
	private $accessToken;
	private $security;

	public function __construct($fnm) {
		$this->security = new Security();
		$this->accessToken = "";
		$this->filename = __DIR__ ."/auth/{$fnm}";
		if(file_exists($this->filename)){
			$this->accessToken = file_get_contents($this->filename);
		}
	}

	public function saveAccessToken($token) {
		if(file_exists($this->filename)){
			unlink($this->filename);
		}
		touch($this->filename);
		file_put_contents($this->filename, $token);
		$this->accessToken = $token;

	}

	public function getAccessToken() {
		return $this->accessToken;
	}
}
regist.php(初回認証画面)

初回のアカウント認証(アプリ使用許可)を行うGUI
認証情報をサーバにjsonで保存する。

<?php

set_include_path(get_include_path() . PATH_SEPARATOR . "/path/to/google-api-php-client/src");
require_once __DIR__ ."/google-api-php-client/src/Google/autoload.php";
require(__DIR__ ."/Auth.php");

session_start();

try {
	$users = getUsers();
	$redirect_uri = "XXXX";
	$client = new Google_Client();
	$client->setAccessType('offline');
	$client->setApprovalPrompt('force');
	$client->setClientId('XXXX');
	$client->setClientSecret('XXXX');
	$client->setApplicationName("XXXX");
	$client->setRedirectUri($redirect_uri);
	$client->setDeveloperKey("XXXX"); //server key
	$client->addScope(Google_Service_Webmasters::WEBMASTERS_READONLY); //権限の許容範囲("webmasters.readonly")

    $isAuthenticated = false;

    if (isset($_POST['userID'])) {
        //初回認証の場合
        $_SESSION['userID'] = $_POST['userID'];
        header('Location: ' . $client->createAuthUrl());

    } else {

        //アプリ使用許可からリダイレクトされてきた場合、認証コードがリクエストパラメータにセットされている
        if (isset($_GET['code'])) {
    		$client->authenticate($_GET['code']);	//アクセストークンと交換する
    		$auth = new AuthInfo($_SESSION['userID']);
    		$auth->saveAccessToken($client->getAccessToken());	//アクセストークンをファイルに保存
            $isAuthenticated = true;
     		unset($_SESSION['userID']);
     	}
    }

} catch (Google_Service_Exception $e) {
	echo "【Google_Service_Exception】”;
    var_dump($e);

} catch (Google_Exception $e) {
	echo "【Google_Exception】";
    var_dump($e);
}

function getUsers() {
	$googleUsers = array_filter(scandir(__DIR__ ."/auth"), function($file){
		return !in_array($file, array(".", "..", ".json"));
	});
	return $googleUsers;
}

?>

<html>
<head>
</head>
<body>
    <div>
        <div>
            <?php
                if (!$isAuthenticated) {
                    echo "<form id='fm' action='./' method='POST'>";
                    echo "<input type='text' id='userID' name='userID'>";
                    echo "<input type='submit' id='btn' value='認証'>";
                    echo "</form>";
                } 
            ?>
        </div>
    </div>
</body>


</html>

↑はスニペットなのでざっくりとしか書いていませんが、
本来は認証情報を可逆暗号化して保存したりデータを日次で取得してCSV
はきだしたりといったことをやりました。


というか検索アナリティクスのデータは2〜3日前くらいのものしか
とれないというのは改善されないのかなあ。。。

古いバージョンのPHPでついやってしまうあの構文

wordpressのテーマを作成する案件があり、テストサーバで動作確認したものを

クライアントのサーバに導入しテーマを適用ところ、画面が真っ白に。。。


とりあえずini_set()でエラーを確認してみると、
どうやら配列を返すメソッドをコールしたところで落ちていました。


原因はPHPバージョン差異による構文エラー。


クライアントサーバのPHPは5.3.3で、
PHP5.4より前のバージョンでは以下のようにメソッドの戻り値の配列
にそのままアクセスするような書き方はできないとのことです。
なので、一度変数に入れてからアクセスすることで解決。

echo func()[0]; //PHP5.4より前のバージョンでこれはsyntax errorになります

$arr = func(); //一度変数に格納すればおk
echo $arr[0];

function func() {
  return 配列;
}


PHPのバージョン上げろや!って簡単に言えない場面では
この手の構文エラーにも注意しておかなきゃですね~

cronを実行するときのユーザー

先日、GoogleAPIを使って取得した検索アナリティクスのデータを
サーバに保存するphpを書きました。

ブラウザでphpを叩いて一通りテストした後、
cronで日次で処理しようとしたのですが上手くいかず。。。


ログを見てみるとファイルやディレクトリのパーミッション関連で
エラーが出ている模様。


PHPで作成、編集しようとするファイルやディレクトリやらの所有者がapacheになっており、
cronでは一般ユーザで実行していたためエラーが出ていました。

なので、apacheユーザーでcrontabを設定しなおすことで解決。

crontab -u apache task.txt


余談ですが、crontabのオプションで-e(エディタ起動)と-r(タスク削除)があるのですが、
エディタなど使用率の高いオプションをeにバインドし、キーボード上隣り合うrに削除系のオプションをバインドしておくのは危険だなあと思いました。。

(´・ω・`)ヤレヤレ

PHPでrequireしたファイルが読み込めない時!

とあるPHPの実行ファイルがあって、その中で別のクラスを読み込んでいます。
読み込んだクラスの中でこれまた別のjsonファイルを読み込もうとするが、
なぜか読み込めずにちょいハマり。。。


ディレクトリ構造はこんな感じ

[DocumentRoot]/index.php
[DocumentRoot]/auth/Auth.php  ...クラス
[DocumentRoot]/json/data.json  ...jsonファイル


index.php(実行ファイル)

require("./auth/Auth.php");

Auth.php

var_dump(file_exist("../json/data.json"));

実行するとfalseが出力され、ファイルが存在しないことになっていました。


理由は簡単、PHPでは実行ファイルのディレクトリがカレントになるというルールがあるからです。
file_existはAuth.phpのディレクトリではなく実行ファイルであるindex.phpのディレクトリを基に、[DocumentRoot]/../json/data.jsonを参照しようとしてnon existになっていた、ということでした。


なので、理屈上はこのように書かないとダメみたいです。
Auth.php

var_dump(file_exist("./json/data.json"));

※includeでも同じ


ただしクラスの性質を考えると、__DIR__やdirname(__FILE__) といったPHPのコアで定義されている定数を使って上手く
表現したほうがベターだと思います。
PHP: 自動的に定義される定数 - Manual


ちょっと前に職場の上司が言っていた「cronでPHPを叩くときは絶対パスで書けよー」という言葉を思い出したのが
解決のきっかけだったのですが、何がともあれ基礎って大事だなあとつくづく思いましたね(´・ω・`)ノ

wordpressのカスタムメニューをjqueryで開閉したい!

wordpressのサイドバーにカスタムメニューを置いたとき、
ネストしているメニューの親にマウスオーバーすると子が開くようにしたかった
というのが今回の話題。

※実際にはUXを考慮してマウスエンターイベントに変更しています。


実際に生成されるHTMLはこんな感じ。

<li class="menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-home menu-item-has-children menu-item-120">
    <a>親</a>
    <ul>
        <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-149">子</li>
        <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-150">子</li>
        <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-160">子</li>
    </ul>
</li>

親のliにマウスエンターすると中身のulが表示され、再度親部分のみマウスエンターすると子が閉じる、ということがやりたかったのですが、中身のulにマウスエンターしても開閉してしまうので、中身のulのマウスエンターイベントで何もさせないようにしました。

jQuery(function($) {
	$("li.menu-item-has-children").mouseenter(
		function(){
			if ($(this).children("ul.sub-menu").is(":hidden")) {
				$(this).children("ul").slideDown();
			} else {
				$(this).children("ul").slideUp();
			}
		}
	);

        //↓これで子要素の動作をキャンセル
	$("li.menu-item-has-children ul").mouseenter(
		function(){
			return false;
		}
	);

});

このようなネスト関係がある場合、子要素のイベントが親要素に伝播するイベントバブリングが発生するので、適宜カットする必要があります。


バブリングの対処には以下の方法があり、適宜使い分ける必要があります。

・EventObject.preventDefault()
対象要素のイベントをキャンセル

・EventObject.stopPropagation()
親要素への伝播をキャンセル

・return false
上記2つの両方を行う

PHPで作ったcsvをブラウザからDLさせてエクセルで開くときの文字化け

こんなときのベストプラクティスって何だろうか。。


例えば、DBから取ってきたデータをcsvにしてDLさせたい場合など、
UTF-8エンコードされたデータを扱うことが多いかと思います。

そのままDLさせてエクセルで開くと文字化けすますね。
エクセルがCP932(Shift_JIS)で開こうとするから起こる問題です。

エクセルで開いてから文字コードを指定して保存し直すのは
運用上手間がかかるので避けたい、といった場合にPHPで対処する際の
ベストプラクティスが知りたかったというのがきっかけ。。


ちなみに、今回はこんな感じでマルチバイトが入る可能性のあるデータを
エンコードするようにしました。

foreach ($ret as $row) {
    fputcsv($fp, array(mb_convert_encoding($row['col1'], "SJIS-win","UTF-8"),$row['col2'], $row['col3']));
}

mb_convert_variablesで配列を一気にエンコードしてもいいのですが、
マニュアルを見ると戻り値として変換前のエンコーディングを返したり、以下のような注記があったりと色々気持ち悪かったので使わず・・・

mb_convert_variables() は、エンコーディング検出のために Array または Object の文字列を結合します。これは、 エンコーディング検出は短い文字列では失敗する傾向があるためです。このため、 1 つの配列またはオブジェクトで異なるエンコーディングを混ぜることはできません。

マニュアル - mb_convert_variables


もやもや(´・ω・`)