SQLの窓

2020年06月21日


PHP : 選択した画像の表示 / ファイルアップロード ver.2

ver.1 では、選択したファイルが画像の場合どのような画像か確認できませんでしたが、FileReader を使用して画像を表示して確認できるようにしています。

control.php
      コントローラ

✅ model.php
      アップロード実行関数を定義しています

✅ view.php
      画面定義

✅ client.js
      FileReader オブジェクトを使用した、画像表示
      ※ 画像は1つしか表示できません( アップロードも一つです )



control.php
<?php
// 不必要なエラー表示を行わない
error_reporting( E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED );
// ブラウザのキャッシュに保存しない
session_cache_limiter('nocache');
// セッションの開始
session_start();

// この ページの MIME( ページの種類 ) の設定
header( "Content-Type: text/html; charset=utf-8" );
// ***********************************************

// 固有の処理
require_once("model.php");


$image_path = "./myimages";
$result_message = "";

// ***********************************************
// リンクで呼ばれたページは GET で呼ばれるので、
// FORM から POST で呼ばれた時だけ処理を行う為、
// $_SERVER というシステム変数をチェックします
// ***********************************************
if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {

	// ファイルアップロード
	file_upload();

}

// 画面
require_once("view.php");

// デバッグ
debug_print();

?>
model.php
<?php
// ********************************************
// このアプリケーション専用の処理
// ********************************************

// ********************************************
// ファイルをアップロードする
// ********************************************
function file_upload() {

	global $image_path;
	global $result_message;


	if ( !is_dir( $image_path ) ) {

		mkdir( $image_path );

	}

 
	$upload = realpath($image_path);
	$upload .= ( DIRECTORY_SEPARATOR . $_FILES['target']['name'] );

	// *******************************************************
	// アップロードされると、一旦一時ファイルとしてサーバに
	// 置かれるので、move_uploaded_file でアップロードされた
	// 一時ファイルが必要な場合に移動処理を行います
	// *******************************************************
	if ( move_uploaded_file($_FILES['target']['tmp_name'], $upload ) ) {
		$result_message = "<p>アップロードに成功しました</p>";
	}


}

// *******************************************************
// デバッグ
// *******************************************************
function debug_print() {

	print "<pre>";
	print_r( $_GET );
	print_r( $_POST );
	print_r( $_SESSION );
	print_r( $_FILES );
	print "</pre>";

}
?>

view.php

client.js がブラウザでキャッシュされないように、PHP の time 関数を使用して jQuery の $.get のようにパラメータを渡しています。

bootstrap.css は、ここではコンテンツのマージン指定とボタンの見栄え程度しか使用していません。

cdnjs twitter-bootstrap 4.5.0
Google Hosted Libraries : jQuery
<!DOCTYPE html>
<html>
<head>
	<meta content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
	<meta charset="UTF-8">
	<title>単純ファイルアップロード</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.css">

	<script src="client.js?_=<?= time() ?>"></script>
</head>
<body>
	<div id="head">
		<div id="title">
			<a href="./">ファイルをアップロード</a>
		</div>
	</div>
	<div id="content">
		<form enctype="multipart/form-data"
			method="POST">
			<p>
				<input type="hidden"
					name="MAX_FILE_SIZE"
					value="1000000">

				<input id="target"
					name="target"
					type="file"
					class="ml-1 mt-3 btn btn-outline-primary">
			</p>
			<p>
				<input type="submit"
					name="send"
					value="アップロード"
					class="ml-1 btn btn-outline-primary">
				<a class="ml-4 btn btn-info btn-sm" href="<?= $_SERVER["PHP_SELF"] ?>">リロード</a>
			</p>
			<div id="image"></div>
		</form>
	</div>
	<div id="result"><?= $result_message ?></div>
</body>
</html>
client.js
$(function(){

	// INPUT type="file" のファイル選択後のイベント
	$("#target").on("change", function(){

		// 画像表示部分をクリア
		$("#image").html("");

		// 選択されたファイルの情報
		console.dir( this.files );

		// ファイル参照用のクラス : FileReader
		var reader = new FileReader();

		// 表示用にプロパティを追加
		reader.name = this.files[0].name;
		reader.type = this.files[0].type;

		// 画像が読み込まれると実行されるイベント
		$(reader).on("load", function () {

			// FileReader の内容
			console.dir( this );

			if ( this.type.indexOf("image/") == 0 ) {
				$("<img>").appendTo("#image")
					.prop( {"src": this.result, "title": this.name + " : " + this.type } )
					.css( {"width": "160px", "margin": "10px","border": "1px solid #c0c0c0" } );
			}
			else {
				$("<img>").appendTo("#image")
					.prop( {"src": "./notimage.png", "title": this.name + " : " + this.type } )
					.css( {"width": "160px", "margin": "10px","border": "1px solid #c0c0c0" } );
			}

		});

		if (this.files[0]) {
			// 画像を読み込み
			reader.readAsDataURL(this.files[0]);
		}

	});
	
});





posted by lightbox at 2020-06-21 14:15 | PHP + WEBアプリ | このブログの読者になる | 更新情報をチェックする

PHP : ファイルアップロード ver.1



PHP のみで作るオーソドックスなファイルアップロードです。

control.php
      コントローラ

✅ model.php
      アップロード実行関数を定義しています

✅ view.php
      画面定義




control.php
<?php
// 不必要なエラー表示を行わない
error_reporting( E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED );
// ブラウザのキャッシュに保存しない
session_cache_limiter('nocache');
// セッションの開始
session_start();

// この ページの MIME( ページの種類 ) の設定
header( "Content-Type: text/html; charset=utf-8" );
// ***********************************************

// 固有の処理
require_once("model.php");


$image_path = "./myimages";
$result_message = "";

// ***********************************************
// リンクで呼ばれたページは GET で呼ばれるので、
// FORM から POST で呼ばれた時だけ処理を行う為、
// $_SERVER というシステム変数をチェックします
// ***********************************************
if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {

	// ファイルアップロード
	file_upload();

}

// 画面
require_once("view.php");

// デバッグ
debug_print();

?>


model.php
<?php
// ********************************************
// このアプリケーション専用の処理
// ********************************************

// ********************************************
// ファイルをアップロードする
// ********************************************
function file_upload() {

	global $image_path;
	global $result_message;

	if ( !is_dir( $image_path ) ) {

		mkdir( $image_path );

	}
 
	$upload = realpath($image_path);
	$upload .= ( DIRECTORY_SEPARATOR . $_FILES['target']['name'] );

	// *******************************************************
	// アップロードされると、一旦一時ファイルとしてサーバに
	// 置かれるので、move_uploaded_file でアップロードされた
	// 一時ファイルが必要な場合に移動処理を行います
	// *******************************************************
	if ( move_uploaded_file($_FILES['target']['tmp_name'], $upload ) ) {
		$result_message = "<p>アップロードに成功しました</p>";
	}


}

// *******************************************************
// デバッグ
// *******************************************************
function debug_print() {

	print "<pre>";
	print_r( $_GET );
	print_r( $_POST );
	print_r( $_SESSION );
	print_r( $_FILES );
	print "</pre>";

}
?>


view.php
<!DOCTYPE html>
<html>
<head>
	<meta content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
	<meta charset="UTF-8">
	<title>単純ファイルアップロード</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.css">
</head>
<body>
	<div id="head">
		<div id="title">
			<a href="./">ファイルをアップロード</a>
		</div>
	</div>
	<div id="content">
		<form enctype="multipart/form-data"
			method="POST">
			<p>
				<input type="hidden"
					name="MAX_FILE_SIZE"
					value="1000000">

				<input id="target"
					name="target"
					type="file"
					class="ml-1 mt-3 btn btn-outline-primary">
			</p>
			<p>
				<input type="submit"
					name="send"
					value="アップロード"
					class="ml-1 btn btn-outline-primary">
				<a class="ml-4 btn btn-info btn-sm" href="<?= $_SERVER["PHP_SELF"] ?>">リロード</a>
			</p>
			<div id="image"></div>
		</form>
	</div>
	<div id="result"><?= $result_message ?></div>
</body>
</html>


id=image の DIV は次のバージョンでアップロードする画像を表示する為に使用します。
bootstrap.css は、ここではコンテンツのマージン指定とボタンの見栄え程度しか使用していません。
jQuery もここでは特に使用していません。

cdnjs twitter-bootstrap 4.5.0
Google Hosted Libraries : jQuery


posted by lightbox at 2020-06-21 12:01 | PHP + WEBアプリ | このブログの読者になる | 更新情報をチェックする

2020年06月19日


超簡易掲示板 ( JSON ) : PHP / CSS でスマホ用レスポンシブ対応

▼ ノーマル

▼ スマホ




保存データを行単位で区切り文字で分けて投稿データを保存する方法は古くからありますが、JSON 形式で保存しておくと、投稿データ内の改行やクォートなどのデータを自分で処理する必要がなくなる上に、新しい項目も追加するのが容易になります。さらに、データが JSON で作られるので、そのまま http で他のアプリケーションからアクセスする事も容易になります

一応、MVC にのっとり、M(model.php) / V(view.php) / C(board.php) になっています

board.php

error_reporting(E_ALL & ~E_NOTICE); は、$_POST 等の変数の参照時に未定義(ブラウザから送られていない)時にでも、空文字列が入っているとみなして処理できるようにするものです。逆に、全てのエラーを出力するようにした場合、代入されていな い値を使用した場合は、警告を発生します( 必要であれば、php.ini で設定します )

<?php
error_reporting( E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED );
// **************************************
// php.ini の output_buffering をチェックして
// 有効になっていた場合は、header の前に出力可能です
// **************************************

// **************************************
// 通常の HTML として出力します
// **************************************
header( "Content-Type: text/html; charset=utf-8" );

// **************************************
// キャッシュを無効にするヘッダを出力する
// **************************************
session_cache_limiter('nocache');
session_start();


// **************************************
// グローバル変数
// **************************************
$logfile = "./board.log";
$log_text = "";

// **************************************
// 関数の定義を読み込みます
// **************************************
require_once("model.php");

// **************************************
// $_POST['send'] != "" は送信ボタンが
// クリックされた事を示します
// さらに、テキストエリアに何か入力され
// た場合に処理を行います
// **************************************
$_POST['text'] = preg_replace( "/^[ \s]+/u", "", $_POST['text'] );
$_POST['text'] = preg_replace( "/[ \s]+$/u", "", $_POST['text'] );
if ( $_POST['send'] != "" && $_POST['text'] != "" ) {

	// データの書き込み処理
	post_data();

}

// データの表示処理
disp_data();


// **************************************
// ▼ 以下は画面です。$log_text を
//    埋め込んでいます
// **************************************
require_once("view.php");
?>

FORM は一般的な POST メソッドで送信されます。なので、書き込んだ直後にリダイレクトして GET メソッドで呼び出しなおすという処理が入っています。タイトルの『超簡易掲示板 ( JSON )』をクリックすると、GET メソッドでの呼び出しであるリンクとなっています。

投稿データの表示内容は、いったん文字列で作成して後から view.php の該当部分に埋め込む形式です。最新のデータは、array_unshift によって、データの先頭に追加されます。

HTML 要素を無効にする方法としては、htmlentitieshtmlspecialchars がありますが、初心者向けとして最低限の置き換えを str_replace で実装しています。

json_encode による、オブジェクトから文字列の変換では、オプションの JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT でデバッグしやすいように可読性に重点を置いています。

model.php
<?php

// **************************************
// データの書き込み処理
// **************************************
function post_data() {

	global $logfile;

	// データを一括読み込み
	$log_text = @file_get_contents( $logfile );

	$json = json_decode( $log_text );
	// 空のファイルかまたは、JSON データでは無い場合
	if ( $json === null ) {

		// JSON 用クラス作成
		$json = new stdClass;
		// 行データを格納する配列を作成
		$json->item = [];

	}

	foreach( $_POST as $key => $value ) {

		// HTML 要素を無効にする
		$_POST[$key] = htmlspecialchars( $value );

	}

	// 改行コードを \n のみ(1バイト)にする
	$_POST['text'] = str_replace("\r","",$_POST['text']);

	// 新しい投稿用のクラス作成
	$board_data = new stdClass;

	// text プロパティに 入力された本文をセット
	$board_data->text = $_POST['text'];
	// subject プロパティに 入力されたタイトルをセット
	$board_data->subject = $_POST['subject'];
	// name プロパティに 入力された名前をセット
	$board_data->name = $_POST['name'];
	// subject プロパティに 入力されたタイトルをセット
	$board_data->datetime = $_POST['datetime'];

	// 配列の先頭に 新しい投稿データをセット
	array_unshift($json->item, $board_data);

	// 全ての投稿データを JSON として一括書き込み
	file_put_contents( $logfile, json_encode( $json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) );

	// GET メソッドで再表示します
	header( "Location: {$_SERVER["PHP_SELF"]}" );

	exit();


}

// **************************************
// データの表示処理
// **************************************
function disp_data() {

	global $logfile;

	// 埋め込み用データを global 宣言
	global $log_text;

	// データを一括読み込み
	$log_data = @file_get_contents( $logfile );
	// ファイルが存在しない場合
	if ( $log_data === false ) {
		$log_text = "ここに投稿データが表示されます";
		return;
	}

	$json = json_decode( $log_data );
	// 空のファイルかまたは、JSON データでは無い
	if ( $json === null ) {
		$log_text = "ここに投稿データが表示されます";
		return;
	}

	// 表示用の埋め込みに使用される文字列変数
	foreach( $json->item as $v ) {
	
		// **************************************
		// 本文の改行は br 要素で表現します
		// **************************************
		$v->text = str_replace("\n", "<br>\n", $v->text );

		// **************************************
		// 記事の境界を hr 要素で表現します
		// **************************************
		$v->text .= "<hr>\n";

		// **************************************
		// 行毎に表示 HTML を作成
		// **************************************
		$log_text .= "<div class='title'>【{$v->subject}】( {$v->name} : {$v->datetime} ) </div>" . $v->text;
	
	}


}

?>


投稿時の日付データは、ブラウザ側でセットするようにしています。特に日付に関しては JavaScript ではスマートな方法が無いので、学習のきっかけ用としてこのようになっています。また、送信時のイベント処理としても重要なサンプルとなり、jQuery の基本サンプルでもあります。

※ jQuery は、Google のホスティングを使用しています。

view.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-alpha1/css/bootstrap.min.css" />
<link rel="stylesheet" href="board.css?_=<?= time() ?>">

<script>

$( function(){

	// フォーム送信イベント
	$("form").on("submit", function(){

		// 日付文字列をクライアントで作成して送信
		var dateNow = new Date();
		var dateString = 
			dateNow.getFullYear() + "/" + 
			("0"+(dateNow.getMonth()+1)).slice(-2)+ "/" + 
			("0"+(dateNow.getDate())).slice(-2);
		var timeString = 
			("0"+(dateNow.getHours())).slice(-2) + ":" + 
			("0"+(dateNow.getMinutes())).slice(-2) + ":" + 
			("0"+(dateNow.getSeconds())).slice(-2);

		// hidden フィールドにセット
		$("#datetime").val( dateString + " " + timeString );

	});
});

</script>
</head>

<body>
<div id="bbs">
	<h3><a href="board.php" style="color:black;">超簡易掲示板 ( JSON )</a></h3>

	<form method="POST">
		<div>タイトル <input type="text" name="subject"></div>
		<div>名  前 <input type="text" name="name"></div>
		<div><textarea name="text"></textarea>
		<input type="hidden" name="datetime" id="datetime"></div>
		<div><input type="submit" name="send" value="送信"></div>
	</form>
	<br>

	<?= $log_text ?>

</div>
</body>
</html>

board.css
@charset "utf-8";

* {
	font-family: "ヒラギノ角ゴPro W3","Hiragino Kaku Gothic Pro","メイリオ",Meiryo,"MS Pゴシック",Verdana,Arial,Helvetica,sans-serif;
}

textarea {
	height: 100px;
}

/* PC 用 */
@media screen and ( min-width:480px ) {

	#bbs {
		padding: 20px;
	}

	input {
		width: 400px;
	}
	textarea {
		width: 500px;
	}
}

/* スマホ 用 */
@media screen and ( max-width:479px ) {

	#bbs {
		padding: 0px;
	}

	input,textarea {
		width:100%;
	}

}

.title {
	border: 1px solid #aaa;
	padding: 4px;
	margin-bottom: 6px;
}



JSON は、item プロパティが配列になり、複数項目の投稿データが格納されます。

JSON データ
{
    "item": [
        {
            "text": "最低限の機能を持った掲示板です。\nデータ形式は JSON でとても拡張しやすく便利です。",
            "subject": "こんにちは",
            "name": "山田 タロウ",
            "datetime": "2019\/02\/22 13:48:00"
        }
    ]
}




タグ:PHP 掲示板
posted by lightbox at 2020-06-19 11:30 | PHP + WEBアプリ | このブログの読者になる | 更新情報をチェックする

2020年06月17日


TCPDF で昔ながらのオーソドックスな印字処理

余計なメソッドは使わずに構成しています、TCPDF を継承して Cell を使った位置指定印字を可能にしているので、右寄せの印字は簡単に行えます。また、行の高さを使用しているフォントから取得しているので、フォントに依存せずに行の高さが自動的に利用されます。( 但し、フォントサイズを変更すると横の開始位置は変化しないので変更は必要になります )

本来は、行コントロール(1ページに何行を印字するか)を行うところですが、PDF では 罫線や画像も簡単に重ねる事ができるので、座標で行方向のコントロールをしています。

『設定』は、テンプレートなので最低限の設定で、本来の印字内容に影響を与えないように設定しています。

フォントは、メイリオを使用しています。『TCPDF で非埋め込み型として『メイリオ』を使う手順』を参照して下さい。
<?php
// ***********************************************
//
//  プログラム名 : 社員一覧印刷
//  作   成   者 : lightbox
//  作   成   日 : 2014/06/06
//
//  概要 : 
//  印刷処理の為のテンプレート
//
// ***********************************************
header( "pragma: no-cache" );
header( "Expires: Wed, 31 May 2000 14:59:58 GMT" );
header( "Cache-control: no-cache" );

mb_language( "ja" );
mb_internal_encoding("UTF-8");

// *********************************************************
// TCPDF インストール場所の設定
// *********************************************************
$path = "C:\\user\\web\\tcpdf";
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
require_once('examples/config/tcpdf_config_alt.php');	// 定数定義
require_once('tcpdf.php');

// *********************************************************
// 拡張 TCPDF
// ※ クラス定義は、処理より前に必要です
// *********************************************************
class USER_TCPDF extends TCPDF {

	// *************************************
	// 位置指定 Cell 印字
	// ※ 内部印字位置は保存( 元に戻す )
	// *************************************
	public function user_text( $x=0, $y=0, $txt='', $w=1, $h=0, $p="L" ) {

		$a = $this->GetX();
		$b = $this->GetY();

		$this->SetXY( $x, $y );
		$this->Cell($w, $h, $txt, 0, 0, $p);

		$y += $this->getLastH();

		$this->SetXY($a,$b);

		return $y;

	}

}

// ***********************************************
// データベース
// ***********************************************
$server = 'localhost';
$db_name = 'lightbox';
$user = 'root';
$password = 'パスワード';

$connect = @ new mysqli($server, $user, $password, $db_name);
if ($connect->connect_error) {
	die('Connect Error (' . $connect->connect_errno . ') '
	. $connect->connect_error);
}


// ***********************************************
// 主処理
// ***********************************************
query_print();


// ***********************************************
// データベース読み出しと印字
// ***********************************************
function query_print() {

	global $connect;

	//****************************************
	// インスタンス作成
	//****************************************
	$pdf = new USER_TCPDF(
		"P",
		"mm",
		"A4",
		true,
		"UTF-8",
		false,
		false		// PDF/A モード
	);

	//****************************************
	// 設定
	//****************************************
	$pdf->setFontSubsetting(false);
	$pdf->setPrintHeader(false);
	$pdf->setPrintFooter(false);
	// デフォルトが true なので、デバッグ時に混乱しないように false に設定
	$pdf->SetAutoPageBreak(false);

	// フォントサイズ
	$pdf->SetFont('meiryo001', '', 12);

	//****************************************
	// 最初のページを作成
	// ※これを実行しないと、ページ情報が無い
	//****************************************
	$pdf->AddPage();

	$query = apply_value("select.sql");
	$result = $connect->query($query); 

	// ブラウザに PDF を出力しているので、デバッグ出力はファイルに行う
	file_put_contents("log.txt",print_r($result,true));

	$counter = 0;

	if ( $result->num_rows != 0 ) {
		$cur_position = print_header( $pdf );
		while ($row = $result->fetch_array(MYSQLI_BOTH)) {

			$counter++;
			if ( $counter > 40 ) {
				$counter = 0;
				$pdf->AddPage();
				$cur_position = print_header( $pdf );
			}

			$next_position = $pdf->user_text( 10, $cur_position, $row['社員コード'] );
			$pdf->user_text( 28, $cur_position, $row['氏名'] );
			$pdf->user_text( 58, $cur_position, $row['フリガナ'] );
			$pdf->user_text( 110, $cur_position, number_format($row['給与']), 20, 0, "R" );
			$pdf->user_text( 130, $cur_position, number_format($row['手当']+0), 26, 0, "R" );
			$pdf->user_text( 160, $cur_position, substr($row['生年月日'],0,10));

			$cur_position = $next_position;

		}
	}
	else {
		$page_info = $pdf->getPageDimensions();
		$cur_position = $page_info['tm'];	// トップマージン
		$pdf->user_text( 10, $cur_position, "データが存在しません" );
	}

	// ブラウザへ PDF を出力します
	$pdf->Output("syain.pdf", "I");

}

// ***********************************************
// ヘッダ印字
// ***********************************************
function print_header( $pdf ) {

	$page_info = $pdf->getPageDimensions();
	$row_position = $page_info['tm'];	// トップマージン

	// 1行目
	$next_position = $pdf->user_text( 75, $row_position, "※※ 社員マスター一覧表 ※※" );
	$row_position = $next_position;

	// 2行目
	$next_position = $pdf->user_text( 0, $row_position, "" );
	$row_position = $next_position;

	// 3行目
	$next_position = $pdf->user_text( 10, $row_position, "コード" );
	$pdf->user_text( 28, $row_position, "名前" );
	$pdf->user_text( 58, $row_position, "フリガナ" );
	$pdf->user_text( 110, $row_position, "給与", 20, 0, "R" );
	$pdf->user_text( 130, $row_position, "手当", 26, 0, "R" );
	$pdf->user_text( 160, $row_position, "生年月日");
	$row_position = $next_position;

	return $row_position;

}

// **********************************************************
// テキストファイル内の文字列を取得
// **********************************************************
function apply_value( $path ) {

	$value = file_get_contents( $path );
	$value = str_replace('"', '\\"', $value );
	eval("\$value = \"$value\";");

	return $value;

}

?>



実行結果




オンラインマニュアルについて

残念ながら、オリジナルはかなり間違いが多いので使えません。日本語訳をしてくれている、TCPDFマニュアル(勝手訳)がとても良くできています。オリジナルの間違いは見事に訂正されていました。

ただ、コンストラクタが無かったので作りました。

コンストラクタ
コンストラクタ TCPDF::__construct (
  $orientation = 'P',
  $unit = 'mm',
  $format = 'A4',
  $unicode = true,
  $encoding = 'UTF-8',
  $diskcache = false,
  $pdfa = false
)  
引数
$orientation
(string)
用紙の向き
P or Portrait (default) : 縦
L or Landscape : 横
$unit
(string)
長さの単位
pt: ポイント
mm: ミリメートル (default)
cm: センチメートル
in: インチ
$format
(string)
用紙サイズ
$unicode
(boolean) 
TRUE の場合は、入力データは Unicode
$encoding
(string)
キャラクタセット。省略すると UTF-8.
$diskcache
(boolean)
ディスクキャッシュを使うかどうか
$pdfa
(boolean)
PDF/A mode を使うかどうか( 絶対使いません )


タグ:PHP TCPDF
posted by lightbox at 2020-06-17 14:59 | PHP + PDF | このブログの読者になる | 更新情報をチェックする

PHP のファイルアップロードで画像ファイルを限定で行う為のテンプレートと注意事項と解説

Webアプリのサンプルソースとして基本的な事

● キャラクタセットは utf-8 で、http ヘッダも meta 要素も統一してどちらも記述します

● イザと言う時の為に、jquery のライブラリは読み込んでいます。これは、デベロッパーツールのコンソールで何かしたい時にとても有効です。

● スマホ表示用として、meta 要素の viewport は記述しておきます。

● デバッグ表示は、print_r( $変数, true ) を pre 要素で挟んで表示します

● コメントを好きな場所に書いて、結果が消えるように <?php // コメントの記述 ?> というような記述方法を使っています。

ブラウザキャッシュについて

PHP の関数として用意されている session_cache_limiter('nocache'); を使用しています。ただ、これを使うには、セッションを開始する必要があります。ここでは、セッションは使用していませんが、いつでも使用できるようにしても損はありません。

ファイルのアップロード処理として2アクションを使用

画面には、アップロードする為の UI に記述を限定しています。実装としてはHTML でも良いのですが、後々の事や、キャッシュやキャラクタセット、そして PHP コメントの使用を考えれば、PHP で記述するほうが得です。但し、機能としてはほぼ、クライアントの処理のみに限定しています。

さらに、IFRAME を使用して、結果によって画面を崩さないようにしています。実用的に考えた場合、この画面自体を IFRAME にして、自由に開いたり閉じたりする UI を想定しています。その場合、3階層のウインドウになりますが、同一ドメインであれば相互の処理は jQuery で容易に行えるので、現在デバッグ情報を表示している最下層の IFRAME で jQuery を出力すれば、フォーム画面の表示調整は容易です。

2アクション目の、upload.php の役目も、ファイルの処理のみに限定でき、メンテナンスや拡張も容易になるはずです。

ファイルアップロード時のエラーコード

PHP のドキュメントを参照して下さい。通常、2(MAX_FILE_SIZE オーバー) か 4 のエラーになると思います

$_FILES 情報の type のウソについて

この情報は正確ではありません。拡張子より決定しているようなので、exif_imagetype で正しいフォーマットを決定して使用しています。Windows 環境では、php.ini で php_exif.dll を有効にする必要があるので注意して下さい。

日本語ファイル名について

日本語部分を urlencode する事によって、一般的に WEB環境のディスクに保存できるようになります。但し、その文字のまま、WEB で呼び出そうとすると Not Found となるので注意して下さい。ブラウザ等から呼び出す場合、urlencode 部分が元に戻ってサーバに渡されます。なので、WEBアプリのクライアント側で扱う文字列としては、もう一度 urlencode しておく必要があります。

同一ファイルのアップロードについて

簡単に uniqid() の結果を頭に付けてファイル名を決定しています。ブラウザ側で、ギャラリーとして表示する場合は、uniqid() 部分を取り除いて表示する事によってオリジナルファイル名となります。しかし、ここでもしそのファイルが日本語の場合、urlencode されているので、urldecode する必要がありますが、そのままでは Windows 環境からアップロードされたものは、SHIFT_JIS のままだという事に注意して下さい。ギャラリーのページのキャラクタセットが SHIFT_JIS 以外の場合は、mb_convert_encoding で変換が必要になります

未実装の処理について

このままでは、ファイルがアップロードされて images フォルダに増えて行くばかりとなります。画像という前提の為、『ギャラリー』を作成する必要がありますが、画像の表示には、メモリを圧迫しない小さな画像ファイル(サムネイル)が必要になります。その為には、GD を使用してアップロードが成功した後に、縮小したファイルを作成する必要があります。

サムネイルが作成されれば、一覧表示をファイルシステム関数を使用して表示するだけですが、オリジナル画像を参照する為に、ポピューラーな Lightbox2 ライブラリ等を実装して使用する事となります。

アップロードフォーム
▼ file_upload.php
<?php
// *************************************
// キャラクタセット
// *************************************
header( "Content-Type: text/html; charset=utf-8" );
// *************************************
// キャッシュ無効
// *************************************
session_cache_limiter('nocache');
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<?php // キャラクタセット ?>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<?php // スマホ用 ?>
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<?php // イザと言う時の為 ?>
<script src="https//ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>

<style>
body {
	margin: 0;
	padding: 30px;
	background-color: #d0d0d0;
}
body,pre,input {
	font-size: 16px;

<?php // よくある(ポピュラーな)設定 ?>
	font-family: "ヒラギノ角ゴPro W3","Hiragino Kaku Gothic Pro","メイリオ",Meiryo,"MS Pゴシック",Verdana,Arial,Helvetica,sans-serif;
}
</style>

</head>

<body>
<form
<?php // ファイルアップロードで必須 ?>
	enctype="multipart/form-data"
	method="post"

<?php // ファイルがアップロードされる php ?>
	action="upload.php"

<?php // 結果が表示される IFRAME の名称 ?>
	target="result_window">

<?php // php の画像サイズ制限の設定 ?>
	<input type="hidden" name="MAX_FILE_SIZE" value="1000000">

<?php // ローカルファイル参照用 ?>
	<input type="file" id="file" name="file" size="40">
	<br>
	<br>
	<input
		type="submit"
		name="send"
		value="開始">
	<br>
	<br>
</form>

<?php // 結果が表示される IFRAME ?>
<iframe
	name="result_window"
	frameborder="1"
	scrolling="yes"
	width="100%"
	height="400"
	style='background-color:white;'
></iframe>


</body>
</html>


アップロードされたファイルの処理
▼ upload.php
<?php
// *************************************
// キャラクタセット
// *************************************
header( "Content-Type: text/html; charset=utf-8" );
// *************************************
// キャッシュ無効
// *************************************
session_cache_limiter('nocache');
session_start();

// ファイルを移動するフォルダ
$target_folder = "./images/";

// *************************************
// アップロードされたファイル
// *************************************
$ok = ( $_FILES['file']['error'] == 0 );
if ( $ok ) {

	// *************************************
	// 以降、アップロードそのものは成功
	// *************************************

	// *************************************
	// 1) 画像フォーマットの取得
	// *************************************
	$type_string = image_type_to_mime_type( exif_imagetype( $_FILES['file']['tmp_name'] ) );

	// *************************************
	// 2) オリジナルファイル名の取得
	// *************************************
	$file = explode(".", $_FILES['file']['name']);

	// *************************************
	// 3) 日本語ファイル名対応
	// *************************************
	$file_name = urlencode( $file[0] );

	// *************************************
	// 4) 保存ファイル名を作成
	//   a) 拡張子決定
	//   b) uniqid() でファイル目をユニーク
	// *************************************
	$target = "";
	if ( $type_string == "image/jpeg" ) {
		$target = uniqid() . "_{$file_name}.jpg";
	}
	if ( $type_string == "image/gif" ) {
		$target = uniqid() . "_{$file_name}.gif";
	}
	if ( $type_string == "image/png" ) {
		$target = uniqid() . "_{$file_name}.png";
	}
	if ( $target == "" ) {
		$result = "アップロードできないフォーマットです\n";
	}
	else {
		// *************************************
		// アップロードファイルの保存
		// *************************************
		if ( @move_uploaded_file( $_FILES['file']['tmp_name'], $target_folder . $target ) ) {
			$result = "アップロードに成功しました\n";
		}
		else {
			// なんらかの環境エラー
			$result = "アップロードに失敗しました\n";
		}
		
	}
}
else {
	$result = "ファイルはアップロードされていません";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>

<style>
body {
	margin: 0;
	padding: 40px;
	background-color: #ffffff;
}
body,pre {
	font-size: 16px;
	font-family: "ヒラギノ角ゴPro W3","Hiragino Kaku Gothic Pro","メイリオ",Meiryo,"MS Pゴシック",Verdana,Arial,Helvetica,sans-serif;
}
</style>

</head>

<body>
<h3><?= $result ?></h3>

<?php // 情報を最も簡単に表示する方法 ?>
<pre>
<?= print_r( $_FILES, true ) ?>
<?= print_r( $_POST, true ) ?>
</pre>
<?php // urlencode は、日本語のファイル名に必要です ?>
<img src="<?= $target_folder . urlencode( $target ) ?>" style='width:90%;'>
</body>
</html>


オリジナルファイル名については、. がファイル名に含まれないように考慮しています。


posted by lightbox at 2020-06-17 05:06 | PHP + 特記事項 | このブログの読者になる | 更新情報をチェックする

PHP : 指定ファイル名でダウンロード 『application/octet-stream』 と 『Content-disposition: attachment』

application/octet-stream を使う事によって、通常はブラウザに表示されてしまうようなファイルをダウンロードさせる事ができます。

また、保存時のファイル名を正しくする為に Content-disposition を使用しています。

PHP のオンラインマニュアル にはそのものズバリのサンプルが readfile のページにあります。

例1 readfile() によるダウンロードの強制
<?php
$file = 'monkey.gif';

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename='.basename($file));
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    readfile($file);
    exit;
}
?>
リンク先のマニュアルでも説明されていますが、readfile はそもそもこのような目的で使われる事を想定しているようで、『メモリに関する問題はなく、 巨大なファイルを送ってもかまいません。』とあります。特殊な目的で出力する為に内容に加工を加えるような場合は、後述のような内容で読み込めばいいはずです。

但し、キャッシュ制御が有効の場合は際限なく読み込んでしまって out of memory エラーが出る場合があるので、ob_end_clean であらかじめキャンセルしておくといいと思います。

システム書き込みバッファの内容を出力したい場合、キャッシュ制御が有効の場合は flash と ob_flush の両方が必要です

ファイルサイズは無くてもダウンロード可能ですが、ただ、存在しないとクライアント側で何パーセント処理済みかの表示が不可能になります。

fread を使用して読み込む
<?
header( "Content-Type: application/octet-stream" );
header( "Content-disposition: attachment; filename={$_GET['download_target']}" );

$path = '../download/' . $_GET['download_target'];

$size = filesize( $path );

header( "Content-Length: $size" );

$fp = fopen( $path, 'rb' );

if ( $fp ) {

	while( TRUE ) {
		if ( feof( $fp ) ) {
			break;
		}
		$ret = fread( $fp, 1024 );
		print $ret;
	}

	fclose( $fp );
}

?>


ファイルの取得方法では、小さいファイルならば、file_get_contents で十分ですが、大きなファイルではメモリが大量に消費させる可能性を考慮する必要があります。さらに、大きなファイルでは PHP 側のタイムアウトの設定も必要になる可能性があります。また、他のサーバーから取り込む場合は、ファイルサイズを取得するのに cURL 関数が必要になります。( PHP 5.0.0 以降 では filesize がそのまま使えるようなニュアンスですが、stat() を http がサポートしていないので使えません )

※ 参考 : max_execution_time (30/PHP_INI_ALL)

▼ PHP マニュアルの投稿の引用
<?php
$remoteFile = 'http://us.php.net/get/php-5.2.10.tar.bz2/from/this/mirror';
$ch = curl_init($remoteFile);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //not necessary unless the file redirects (like the PHP example we're using here)
$data = curl_exec($ch);
curl_close($ch);
if ($data === false) {
  echo 'cURL failed';
  exit;
}

$contentLength = 'unknown';
$status = 'unknown';
if (preg_match('/^HTTP\/1\.[01] (\d\d\d)/', $data, $matches)) {
  $status = (int)$matches[1];
}
if (preg_match('/Content-Length: (\d+)/', $data, $matches)) {
  $contentLength = (int)$matches[1];
}

echo 'HTTP Status: ' . $status . "\n";
echo 'Content-Length: ' . $contentLength;
?>

Result:

HTTP Status: 302
Content-Length: 8808759
cURL を使ったサンプル

PHP : WEBでもコマンドラインでもHTTPでファイルをダウンロードする



タグ:PHP readfile fread
posted by lightbox at 2020-06-17 04:37 | TrackBack(0) | PHP + 特記事項 | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり