QuictyによるPHP Webアプリの超高速開発-(6)画像ファイルのアップロード

5月 16th, 2008

 前回の「(5)tableのJOINとプルダウンメニュー」では、複数テーブルのJOINを扱った。
今回は、画像イメージをアップロードし、別テーブルで管理し、表示する際にJOINする方法を紹介する。

この例では、新にQuictyの以下の機能の使い方を紹介する。

  • ファイルのアップロード
  • ImageMagickによる画像のリサイズ

今回は、以下のページとデータセットを作成する。

データセット マスタメンテページクラス ページクラスモジュール
restaurant restaurant index.class.php
image image image.class.php

各データセットは以下のように設定する

restaurantデータセット

Name(カラム名) タイトル DB型 Quicty型
id - int id
name 店名 text text
image_file 画像ファイル - file(Virtual)
image_file 画像の説明 - text (Virtual)

※image_file、image_captionはVirtual指定なので、フォームに表示されるが、テーブルにカラムは作成されない。

imageデータセット

Name(カラム名) タイトル DB型 Quicty型
id - int id
image_file 画像ファイル - file(Virtual)
image_caption 料理ID int int
image_type 画像タイプ text text
image_dir 画像のディレクトリ text text
image_filename 画像のファイル名 text text
image_tag 画像タグ text textarea
thumbnail_tag サムネイルタグ text textarea
restaurant_id レストランID int int

※image_fileはVirtual指定なので、フォームに表示されるが、テーブルにカラムは作成されない。

サンプルプログラムコードrestaurant05_3.zip」(5月17日更新)
※使用法
(1) 展開したフォルダ「restaurant05」を「QT」フォルダに入れる
(2) Qtビルダーの「アプリケーションの読み込み」で
サイト名「レストラン05」
Name「restaurant05」
で、登録ボタンを押す。
(3) ソースコードが読み込まれて、Qtビルダーに登録される。
(4) 「restaurant05」の「全データセットの初期化」を実行。
以上で、使用可能になります。

ご注意:このブログのサンプルコードは、シングルクオートやダブルクオートなどがスマートクオートに変換されているため、コピペしてもそのままではエラーになることが多いです(エディタによる)。
なるべく、サンプルコードをダウンロードしてご利用ください。

ImageMagickのインストール

画像処理ソフトのImageMagickをインストールしておく。

以下はMac OS XでMacPortsを使ってインストールする場合。
————————————————
$ sudo port install imagemagick
————————————————

imagemagickの「convert」コマンドが入っているディレクトリをしらべ、Quictyアプリの設定ファイルに書き込んでおく。

設定ファイル「etc/conf/conf_values.conf」をテキストエディタで修正。
————————————————
$this->C['IMGMGK_BIN'] = ‘/opt/local/bin’;
————————————————
の1行を加える。
screenshot_04.RDhjOtTosLsY.jpg

WindowsにおけるImageMagickのインストール

以下を参考にしてください。

ImageMagick

Apache設定ファイルの変更

Apacheの設定ファイルに「Options FollowSymLinks」を設定し、シンボリックファイルを辿れるようにする。
(「MacでWeb開発-(10) XAMPPにQuictyフレームワークを設置」では
/Application/xampp/etc/extra/httpd-vhost.cof内に設定。)
———————————————————
<Directory “/Users/tomoyun/QT”>
AllowOverride None
Options None
Order deny,allow
Deny from all
Allow from localhost
Allow from quicty.local
</Directory>
——————————————————–
↓(修正)
——————————————————–
<Directory “/Users/tomoyun/QT”>
AllowOverride None
Options FollowSymLinks
Order deny,allow
Deny from all
Allow from localhost
Allow from quicty.local
</Directory>
——————————————————–
アプリの登録

サイト名「レストラン05」
Name「restaurant05」
でアプリケーションを登録。

screenshot_01.43eIwkRixvOk.jpg

画像をアップロードしたフォルダをWebブラウザで参照できるように、シンボリックリンクを作成。
————————————————————————————————
$ cd QT/restaurant05/htdocs/img
$ ln -s ../../var/upload/ upload
$ ls -la
$ mkdir upload/image
$ chmod a+w upload/image
————————————————————————————————

screenshot_13.PryT4uNxlXqH.jpg

「トップページに機能を追加」をクリック。
タイトル「レストラン」
Name「restaurant」
ページの種類「マスタメンテ/マルチページ」
で登録。
screenshot_02.q5n7xVZHjDCs.jpg

text型フィールドを1つ追加する。
タイトル「店名」
Name「name」
screenshot_03.FKnYNJy59YSz.jpg

部品テンプレート「input_form.inc」と「table_view.inc」を保存。
screenshot_20.VOjGG3HSpgSa.jpg
「ページを追加」で新しいページを追加。
タイトル「画像」
Name「image」
ページの種類「マスタメンテ/マルチページ」
で登録。
screenshot_05.91YOWma9gkfb.jpg

file型のフィールドを追加。
タイトル「画像ファイル」
Name「image_file」
Virtual Field「チェック」
で登録。
screenshot_08.v4OUlHOEb6Xx.jpg

以後、下の画像のように必要なフィールドを追加する。
screenshot_10.8ZEjcmiOVbh3.jpg

プログラムの修正

lib/Pages/image_classes/_base.class.php
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘image’);
$options = new image_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();

$result = $this->form->automatic_form_handler($condition);
$this->image = $result['image'] ? $result['image'] : $result['values'];
return $result;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘image’);
$options = new image_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();

$result = $this->form->automatic_form_handler($condition);
$this->image = $result['image'] ? $result['image'] : $result['values'];
if($this->QuictyStatus==’INSERT’ or $this->QuictyStatus==’UPDATE’) {
umask(0);
$basename = ‘image’.$this->image['id'];
$image_dir = ‘../var/upload/image’;
$ht_img_dir = quicty_base_path().’/img/upload/image’;

$dir1 = substr(sprintf(“%08d”,$this->image['id']),0,2);
if(!file_exists(“$image_dir/$dir1″)) mkdir(“$image_dir/$dir1″,0777);
$dir2 = substr(sprintf(“%08d”,$this->image['id']),2,2);
if(!file_exists(“$image_dir/$dir1/$dir2″)) mkdir(“$image_dir/$dir1/$dir2″,0777);
$dir3 = substr(sprintf(“%08d”,$this->image['id']),4,2);
if(!file_exists(“$image_dir/$dir1/$dir2/$dir3″)) mkdir(“$image_dir/$dir1/$dir2/$dir3″,0777);
$subdir = “$dir1/$dir2/$dir3″;

$file = $this->form->move_upload_file2dir(“$image_dir/$subdir”,$basename,$element=’image_file’);
if($file['result']) {
$this->resize_image(“$image_dir/$subdir”,”$ht_img_dir/$subdir”,$file,$basename);
unlink(“$image_dir/$subdir/”.$file['name']);
}
}
return $result;
}

function resize_image($image_dir,$ht_img_dir,$file,$basename) {
$file = $this->form->resize_image($file,64,64,$image_dir,$basename.’_t.’.$file['type'],’thumbnail’);
chmod($file['thumbnail_path'],0666);
$thumbnail_image = ““;

$file = $this->form->resize_image($file,160,160,$image_dir,$basename.’_n.’.$file['type'],’normal’);
chmod($file['normal_path'],0666);
$normal_image = ““;

$this->form->update_table(array(‘image_dir’=>$file['dir'],’image_filename’=>$file['name'],’image_tag’=>$normal_image,’thumbnail_tag’=>$thumbnail_image),”where id=”.$this->image['id']);
}
————————————————————————————————

(修正例)
screenshot_18.t5xt54HN9CYD.jpg
screenshot_19.S0n8N38n5Rmk.jpg

画像登録のテスト

imageクラスだけで画像登録の動作を確認する。

任意の画像ファイルを選択し、
画像の説明「画像テスト」
レストランID「1」
と入力して投稿。
screenshot_15.0UDEaabaYd0y.jpg

投稿後、一覧に画像が表示される。
screenshot_16.IaQbk0asIqn7.jpg

レストランの一覧に表示

imageテーブルの画像をレストランの一覧に表示できるようにプログラムを修正。

「lib/Pages/index_classes/list_options.class.php」内のmake_where_condition()
————————————————————————————————
function make_where_condition($add_condition=”,$cond=’AND’) {
$search_system_file = $this->page->home_dir.’/etc/search_view/restaurant.sys’;
if(file_exists($search_system_file)) {
$search_system = read_data_array($search_system_file);
} else {
$search_system = array(
‘keyword’=>array(‘title’=>’restaurant.name’,'url’=>’restaurant.url’), // keyword search target column
‘condition’=>array(‘id’=>’restaurant.id’,'application_id’=>’restaurant.application_id’), // id search target column
//’period’=>array(‘start_date’=>array(‘column’=>’date’,'end’=>’end_date’))
);
}
return $this->list->make_condition($search_system,$add_condition,$cond);
}
————————————————————————————————
↓(修正)
————————————————————————————————
unction make_where_condition($add_condition=”,$cond=’AND’) {
$search_system = array(
‘keyword’=>array(‘title’=>’restaurant.name’,),
);
return $this->list->make_condition($search_system,$add_condition,$cond);
}
————————————————————————————————

「lib/Pages/index_classes/list_options.class.php」内のmake_join_condition()
————————————————————————————————
function make_join_condition() {
//$this->list->real_count = true; // set this flag if join other table
//$joins[] = “LEFT JOIN {table_name} ON restaurant.{table_name}_id={table_name}.id”;
//$join_condition = implode(‘ ‘,$joins);
return $join_condition;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_join_condition() {
//$this->list->real_count = true; // set this flag if join other table
$joins[] = “LEFT JOIN image ON restaurant.id=image.restaurant_id”;
$join_condition = implode(‘ ‘,$joins);
return $join_condition;
}
————————————————————————————————

「lib/Pages/index_classes/list_options.class.php」内のmake_select_fields()
————————————————————————————————
function make_select_fields() {
$this->select_system = array(
‘restaurant’=>’*', // specify all column
//’restaurant’=>array(‘id’,'name’,'url’), // specify each column
//’restaurant’=>array(‘id’=>’restaurant_id’), //specify another column name
);
return $this->list->make_select_fields($this->select_system);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_select_fields() {
$this->select_system = array(
‘restaurant’=>’*', // specify all column
‘image’=>array(‘image_caption’,'image_tag’,'thumbnail_tag’),
);
return $this->list->make_select_fields($this->select_system);
}
————————————————————————————————

「lib/Pages/index_classes/list_options.class.php」内のmake_sort_condition()
————————————————————————————————
function make_sort_condition() {
$sort_system = array(
‘default’=>’ order by id’,
// sort keys
‘columns’=>’*', // specify all column
//’columns’=>array(‘id’,'name’,'url’), // specify each column
);
if($this->page->sort_option) {
$this->list->set_sort_option($this->page->sort_option,$this->page->order_option);
}
return $this->list->make_order($sort_system);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_sort_condition() {
$sort_system = array(
‘default’=>’ order by restaurant.id’,
‘columns’=>’*', // specify all column
);
if($this->page->sort_option) {
$this->list->set_sort_option($this->page->sort_option,$this->page->order_option);
}
return $this->list->make_order($sort_system);
}
————————————————————————————————

「view/index.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_table_view.inc’}
{*include file=’includes/restaurant/table_view.inc’*}
{include file=’includes/common/auto_pager.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/table_view.inc’}
{include file=’includes/common/auto_pager.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
「view/includes/restaurant/table_view.inc」
————————————————————————————————
{if $smarty.foreach.restaurant_view.first}
<tr>
<th>&nbsp;</th>
<th class=”col-name”>{$row.name.label}<span id=”asc-name”><a href=”./index.html?sort=name&amp;order=asc”>{$W.asc_mark}</a></span><span id=”desc-name”><a href=”./index.html?sort=name&amp;order=desc”>{$W.desc_mark}</a></span></th>
</tr>
{/if}
<tr>
<td><a href=”{$display_cmd}?id={$row.id.body}”>{$row.sequence.value}</a></td>
<td>{$row.name.html}</td>
</tr>
————————————————————————————————
↓(修正)
————————————————————————————————
{if $smarty.foreach.restaurant_view.first}
<tr>
<th>&nbsp;</th>
<th class=”col-name”>{$row.name.label}<span id=”asc-name”><a href=”./index.html?sort=name&amp;order=asc”>{$W.asc_mark}</a></span><span id=”desc-name”><a href=”./index.html?sort=name&amp;order=desc”>{$W.desc_mark}</a></span></th>
<th>Image</th>
</tr>
{/if}
<tr>
<td><a href=”{$display_cmd}?id={$row.id.body}”>{$row.sequence.value}</a></td>
<td>{$row.name.html}</td>
<td>{$row.thumbnail_tag.value}</td>
</tr>
————————————————————————————————

レストラン一覧の動作確認

「レストラン 追加」でお店を1軒目のお店を登録する。
screenshot_21.zRe6jlHHoiZZ.jpg

先ほど登録した画像は、レストランID=1としておいたので、ここでJOINされてサムネイルが表示される。
screenshot_23.4ofrRnjW7NkM.jpg

レストランの詳細ページに画像を表示する

「view/info.html」を複製し、「view/edit.html」を作成しておく。
screenshot_24.cG6n7TbOgbdD.jpg

各プログラムとテンプレートを修正する。

「lib/Pages/index.class.php」
————————————————————————————————
function dispatch_info() {
$this->assign_search_form();
$result = $this->display_record();

$this->assign_form(‘input_form’,$this->form);
$this->assign_page_and_pathlist($this->W['restaurant'].’ ‘.$this->W['display']);
return $this->display($this->current_template);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function dispatch_info() {
$this->assign_search_form();
$result = $this->display_record();
$list_table = $this->list_records(”);

$this->assign(‘form_view’,$this->list->display_table_value($list_table));
$this->assign_form(‘input_form’,$this->form);
$this->assign_page_and_pathlist($this->W['restaurant'].’ ‘.$this->W['display']);
return $this->display($this->current_template);
}
————————————————————————————————

「view/info.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_input_form.inc’}
{*include file=’includes/restaurant/input_form.inc’*}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/form_view.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————

「view/includes/restaurant/form_view.inc」
————————————————————————————————
<table cellspacing=”0″ summary=”form view”>

<tr>
<th><label for=”name”>{$restaurant_form.name.label}</label></th>
<td>{$restaurant_form.name.html}</td>
</tr>
</table>
————————————————————————————————
↓(修正)
————————————————————————————————
<table cellspacing=”0″ summary=”form view”>
<tr>
<th><label for=”name”>{$restaurant_form.name.label}</label></th>
<td>{$restaurant_form.name.html}</td>
</tr>
<tr>
<th><label for=”image_tag”>{$restaurant_form.image_tag.label}</label></th>
<td>{$restaurant_form.image_tag.html}</td>
</tr>
</table>
————————————————————————————————

動作確認

レストラン一覧でお店の番号をクリックすると、画像付きで表示される。
(サンプル画像なし)

restaurantデータセットを拡張し、お店と画像を同時投稿を可能にする

restaurantデータセットにfile型フィールドを追加。
タイトル「画像ファイル」
Name「image_file」
Virtual Field「チェック」
screenshot_25.GefxPGFuHmvW.jpg

続けてtext型フィールドを追加。
タイトル「画像の説明」
Name「image_caption」
Virtual Field「チェック」
screenshot_27.oNO9M3noO6FV.jpg

下の画面と同じようになっていることを確認。
screenshot_28.kZULDNxd86ZO.jpg

部品テンプレート「input_form.inc」を保存。
screenshot_30.diAyGMKgXEnq.jpg

プログラムとテンプレートの修正

「lib/Pages/index_classes/_base.class.php」
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘restaurant’);
$options = new index_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();

$result = $this->form->automatic_form_handler($condition);
$this->restaurant = $result['restaurant'] ? $result['restaurant'] : $result['values'];
return $result;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘restaurant’);
$options = new index_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();

$result = $this->form->automatic_form_handler($condition);
$this->restaurant = $result['restaurant'] ? $result['restaurant'] : $result['values'];

require_once ‘Pages/image.class.php’;
$image = new image($this->home_dir);
$image->QuictyStatus = $this->QuictyStatus;
if($this->QuictyStatus==’INSERT’ ) {
$_POST['restaurant_id'] = $this->restaurant['id'];
$image->display_record();
} elseif ($this->QuictyStatus==’UPDATE’ OR $this->QuictyStatus==’DELETE’) {
$_POST['restaurant_id'] = $_POST['id'];
$condition = ‘restaurant_id=’.$_POST['id'];
$image->display_record($condition);
}
return $result;
}
————————————————————————————————

「view/edit.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_input_form.inc’}
{*include file=’includes/restaurant/input_form.inc’*}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/input_form.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
「view/includes/restaurant/form_view.inc」
————————————————————————————————
<tr>
<th><label for=”image_file”>{$restaurant_form.image_file.label}</label></th>
<td>{$restaurant_form.image_file.html}</td>
</tr>
————————————————————————————————
↓(修正)
————————————————————————————————
<tr>
<th><label for=”image_file”>{$W.image}</label></th>
<td>{$restaurant_form.image_tag.html}</td>
</tr>
————————————————————————————————
動作確認

「レストラン 追加」で店名、画像ファイル、画像の説明を投稿。
screenshot_32.JGGFiH3bqrBV.jpg

レストラン一覧に画像付きで表示される。
screenshot_33.0Vhzv0ZKDYss.jpg

レストランの番号をクリックすれば大きめな画像付きで表示。
screenshot_34.axf9BO8YyV4C.jpg

以上。

ヒント

ファイルがアップロードできない場合は、PHPの設定ファイル「php.ini」の「upload_tmp_dir =」の項目に明示的にテンポラリディレクトリを記述する。
————————————————————————————————
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;

; Whether to allow HTTP file uploads.
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
;upload_tmp_dir =

; Maximum allowed size for uploaded files.
upload_max_filesize = 2M
————————————————————————————————
↓(修正)
————————————————————————————————
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;

; Whether to allow HTTP file uploads.
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
upload_tmp_dir = “/tmp”

; Maximum allowed size for uploaded files.
upload_max_filesize = 2M
————————————————————————————————

関連記事

カテゴリー: PHP, Quicty

コメントする

コメントする

Feed

http://www.zubapita.jp / QuictyによるPHP Webアプリの超高速開発-(6)画像ファイルのアップロード