Thứ Tư, 15 tháng 9, 2010

Một chương trình download manager đơn giản bằng PHP trên webssite

http://phpcodevn.com/forum/viewtopic.php?f=29&t=1445

Giả sử bạn có 1 file document.zip trong cùng thư mục với file download.php. Nhưng chỉ mình bạn biết là file document.zip này nằm ở đâu, người khác muốn download thì phải truy cập vào file download.php của bạn. Mã nguồn của file download.php sẽ như sau:
//file download.php

<?php
$filename = "document.zip";
$fp = fopen($filename, "rb");
header("Content-type: application/octet-stream");
header("Content-length: " . filesize($filename));
fpassthru($fp);
fclose($fp);
?>

<?php
$file="upload/document.zip";
if (isset($file))
{
      header('Pragma: public');
      header('Expires: 0');
      header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
      header('Content-Type: application/octet-stream');// . getmimetype($file));
      header('Content-Disposition: attachment; filename='.basename($file));
      header('Content-Length: ' . filesize($file));
      readfile($file);
}
?>

Ủa, chỉ có nhiêu đó thôi à? Đúng vậy, chỉ có vỏn vẹn 10 dòng là có được 1 chương trình download manager đơn giản (dĩ nhiên là chương trình này chỉ cho phép download được mỗi 1 file document.zip, ta còn phải làm việc nhiều để cho chương trình hoàn thiện hơn!).
Đầu tiên, ta mở file document.zip để đọc ở chế độ nhị phân (binary):
$fp = fopen($filename, "rb");

header("Content-type: application/octet-stream");.


Đồng thời ta cũng báo cho browser biết dung lượng của file sẽ được tải xuống:

header("Content-length: " . filesize($filename));

Và cuối cùng là đọc nội dung file và echo lại cho browser download:

fpassthru($fp);

Lệnh fpassthru($fp); tương đương với 2 lệnh:
$content = fread($handle, filesize($filename));
echo $content;

Như vậy chương trình này cũng không có gì khác với các chương trình PHP thông thường ngoài 2 lệnh header(). Trong đó lệnh header("Content-length: " . filesize($filename)); cũng không có gì là khó hiểu, lệnh này báo cho browser biết dung lượng của file chuẩn bị download (thực ra không có lệnh này thì quá trình download vẫn diễn ra như bình thường). Vấn đề mấu chốt nằm ở lệnh header("Content-type: application/octet-stream");.
Lệnh header("Content-type: application/octet-stream"); sẽ báo cho browser biết là dữ liệu chuẩn bị load xuống là dữ liệu nhị phân. Vì là dữ liệu nhị phân nên browser sẽ thực hiện quá trình download và save file thay vì hiển thị lên browser như là 1 trang HTML thông thường.
Thử chạy ví dụ trên vài lần, thay thế file document.zip bằng một số file khác nhau (ví dụ file Word, PDF, Excel...) và thử trên vài browser khác nhau, bạn sẽ nhận thấy có vài chỗ hơi khó chịu:
·     Browser sẽ mặc định lưu file lên đĩa với tên là download.php. Bạn phải đổi tên file lại cho đúng trước khi mở file ra đọc.
· Trong một số trường hợp, thay vì hỏi save file lên đĩa, browser sẽ mở luôn file (ví dụ chạy Word hoặc Acrobat mở luôn file vừa được download).
Để giải quyết 2 vấn đề khó chịu trên cũng không có gì khó khăn lắm, ta chỉ cần thêm 1 lệnh header() nữa:
[PHP]header('Content-disposition: attachment; filename="'.$filename.'"');[/PHP]
Tham số "attachment" của header "Content-disposition" sẽ báo cho browser biết là nên download và save file thay vì open. Tham số "filename=" sẽ báo cho browser biết tên của file đang được download.
CẢI TIẾN CHƯƠNG TRÌNH
Cho tới bây giờ chương trình của chúng ta vẫn còn thô sơ, chúng ta vẫn cần them vào chức năng nữa để chương trình hoàn thiện hơn.
·     Cho phép người dùng chọn file để download. Hiện tại chương trình của chúng ta chỉ mới cho phép download 1 file duy nhất.
· Kiểm tra dữ liệu nhập từ người dùng. Đây là một việc luôn nên làm. Chương trình cho phép nguời dùng chọn file để download thì cũng cần nên kiểm tra kỹ kẻo chính file config, file source hoặc file password của bạn bị download thì phiền.
Cho phép người dùng chọn file để download: Thao tác này có lẽ khá đơn giản đối với bạn. Chương trình sẽ nhận vào tham số file từ URL (ví dụ: download.php?file=document.zip). Đoạn code xử lý như sau:
[PHP]$filename = isset($_GET['file'])?$_GET['file']:'';[/PHP]

Kiểm tra dữ liệu nhập: Ta cần kiểm tra các điều kiện sau:
·     File có tồn tại và được phép đọc hay không? PHP cung cấp sẵn cho ta 2 hàm ls is_file() và is_readable() để làm việc này:
[PHP]if ( $filename == "" || !is_file($filename) || !is_readable($filename) ) {
          echo "Error: ...";
          exit(-1);
}[/PHP]
Đoạn mã trên sẽ kiểm tra nếu như tên file là rỗng, hoặc file không tồn tại hoặc không đọc được thì sẽ báo lỗi và thoát.
· Tên file có chứa các ký tự đặt biệc hay không? Thường các file upload sẽ được để trong 1 thư mục riêng. Như vậy tên file chỉ có thể chứa các ký tự a-z, 0-9, gạch ngang (-), gạch dưới (_), khoảng trắng và dấu chấm (.). Ta có thể xem như các kỹ tự khác trong tên file là không hợp lệ. Đoạn mã kiểm tra như sau:
[PHP]if ( !preg_match('/^[a-z0-9\_\-\. ]*$/i', $filename) ) {
          echo "Error: ...";
          exit(-1);
}[/PHP]
Đoạn mã trên sẽ kiểm tra nếu như trong file có chứa ký tự lạ (không phải là 0-9, a-z, gach dưới, gạch ngang, khoảng trắng, dấu chấm, khoảng trắng thì sẽ báo lỗi) và thoát chương trình. Ngoài ra, có một số file cấu hình đặt biệc của server được bắt đầu bằng dấu chấm (ví dụ .htaccess, .htpasswd), ta cũng phải kiểm tra xem ksy tự đầu tiên của tên file có phải là dấu chấm hay không, nói cách khác, ký tự đầu tiên của tên file phải là 1 chữ cái (a-z) hoặc chữ số (0-9) hoặc kỹ tự gạch ngang hoặc gạch dưới. Lệnh if trên được cài tiến lại như sau:
[PHP]if ( !preg_match('/^[a-z0-9\_\-][a-z0-9\_\-\. ]*$/i', $filename) ) {
     echo "Error: ...";
     exit(-1);
}[/PHP]Giép lại ta có chương trình được cải tiến như sau:
[PHP]<?php
//các file upload được để trong 1 thư mục riêng
$upload_dir = "../upload/";
//lấy tên file cần download từ URL
$filename = isset($_GET['file'])?$_GET['file']:'';
//thực hiện quá trình kiểm tra
if ( !preg_match('/^[a-z0-9\_\-][a-z0-9\_\-\. ]*$/i', $filename) )
          || !is_file($upload_dir.$filename) || !is_readable($upload_dir.$filename) ) {
     echo "Loi: Ten file khong hop le hoac file khong ton tai!";
     exit(-1);
} //end if
//mở file để đọc với chế độ nhị phân (binary)
$fp = fopen($upload_dir.$filename, "rb");
//gởi header đến cho browser
header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename="'.$filename.'"');
header('Content-length: ' . filesize($upload_dir.$filename));
//đọc file và trả dữ liệu về cho browser
fpassthru($fp);
fclose($fp);
?>
[/PHP]

Lưu ý rằng ta không cần kiểm tra tên file rỗng nữa vì ở trên ta đã kiểm tra ký tự đầu tiên của tên file, nên nếu tên file lã rỗng thì sẽ không có ký tự đầu tiên, do vậy điều kiện kiểm tra của hàm pregg_match sẽ bị sai rồi.
THẮC MẮC NHỎ CUỐI CÙNG
Tới đây xem như chương trình của chúng ta xem như là đã hoàn chỉnh. Tuy nhiên, có thể bạn sẽ còn một thắc mắc nho nhỏ: ngoài kiểu dữ liệu application/octet-stream thì còn kiểu dữ liệu nào khác không? và mặc định thì PHP sẽ dùng kiểu dữ liệu gì để trả về cho browser?
Mặc định PHP sẽ trả về dữ liệu kiểu text/html cho browser. Một số kiểu dữ liệu tương ứng với các kiểu file thông dụng:
.GIF: image/gif
.JPG: image/jpeg
.PNG: image/png
.WAV: audio/wav
.MP3: audio/mpeg3
.DOC: application/msword
.PDF: application/pdf
Tuỳ vào kiểu của dữ liệu trả về mà browser có thể sẽ có những cách ứng khác nhau. Ví dụ nếu dữ liệu trả về là file ảnh thì browser sẽ hiển thị luôn, nếu là file nhạc thì browser sẽ gọi WMP lên để play, nếu là file PDF thì chương trình Acrobat sẽ được gọi...
Kiểu application/octet-stream để chỉ dữ liệu nhị phân (binary) chung chung. Nếu bạn chỉ muốn browser download và save file thì bạn chỉ cần trả về kiểu dữ liệu application/octet-stream là đủ.
Vậy thì nảy sinh 1 vấn đề nữa: tìm danh sách các kiểu dữ liệu tương ứng với từng loại file ở đâu? Google là trợ thủ đắt lực của bạn

Đếm số người online dùng session

//---------------------------------
// Tình hình truy cập: http://phpcodevn.com/forum/viewtopic.php?f=29&t=1487
//---------------------------------
$sid = session_id();
    $CurrentTime = time();
    $TimeOut = $CurrentTime - 300; // kiểm tra mỗi 5 phút.
    //Xóa các record da truy cap truoc do 5 phut
    mysql_query("DELETE FROM online WHERE time < $TimeOut");
    $UserName = $_SESSION['name'];// Biến tên của User đã đăng nhập.
    mysql_query("DELETE FROM online WHERE name='' and sid=\"$sid\"");
    //Kiễm tra Session này có trong Database không?
    $check_sid_exist = mysql_num_rows(mysql_query("SELECT * FROM online WHERE sid = \"$sid\""));
    if ($check_sid_exist!=0)
    {
        //User này đã có trong bảng Online thì cập nhật thời gian online
        mysql_query("UPDATE online SET time = \"$CurrentTime\" WHERE sid = \"$sid\"");
    }
    else
    {
        //Nếu không tạo Record mới
        mysql_query("INSERT INTO online(name, sid, time)
                                            VALUES (\"$UserName\", \"$sid\", \"$CurrentTime\")");
    }
    //Tổng số Guest đang online
    $totalguest_qr = mysql_query("SELECT count(*) AS gnum FROM online WHERE name = ''");
    while ($gnum = mysql_fetch_array($totalguest_qr))
    { $ttg = $gnum['gnum']; }
    //Tổng số User đang online
    $totaluser_qr = mysql_query("SELECT count(DISTINCT name) AS unum FROM online WHERE name != ''");
    while ($unum = mysql_fetch_array($totaluser_qr))
    { $ttu = $unum['unum']; }
    //Danh sách user đang truy cập
    $userlist_qr = mysql_query("SELECT name FROM online WHERE name != ''");
    while ($ulist = mysql_fetch_array($userlist_qr))
    { $userlist .= $ulist['name'].", "; }
//--------------------------------- [/PHP]
Nguồn: http://freecodevn.com/for@um/showthread.php?t=53149

Lưu nội dung file ảnh vào CSDL MYSQL

Nhu cầu phát triển web của PHP là lưu nội dung của file ảnh vào trong một cơ sở dữ liệu (CSDL) và sau đó là đọc nội dung của file ảnh đã lưu trong CSDL ra và hiển thị ra trình duyệt. Nhu cầu này có một số đặc điểm sau:
TỔNG QUAN
Nhu cầu phát triển web của PHP là lưu nội dung của file ảnh vào trong một cơ sở dữ liệu (CSDL) và sau đó là đọc nội dung của file ảnh đã lưu trong CSDL ra và hiển thị ra trình duyệt. Nhu cầu này có một số đặc điểm sau:
● Lưu nội dung file ảnh vào CSDL thì sẽ không phụ thuộc vào đường dẫn đến thư mục chứa hình. Nếu bạn dùng theo cách hiển thị nội dung hình ảnh theo cách truyền thống thì đường đến file hình thật chính xác thì file hình mới hiển thị ra, chẳng may thư cấu trúc thư mục đó bị thay đổi thì chúng ta phải cập nhật lại rất nhiều đường dẫn trên website, hay file ảnh có thể bị xóa mất hay bạn restore hay backup bị thiếu....Chỉ thiếu một phần nhỏ là file hình không hiển thị được rồi. Để giảm được những rủi ro này bạn dùng cách như thế này là lưu nội dung file ảnh vào CSDL như vậy là không phụ thuộc vào đường dẫn hay cấu trúc thư mục... rất là lôi thôi. Chúng ta chỉ phụ thuộc vào nơi chứa nội dung chủa file hình.
● Lưu nội dung file ảnh vào CSDL thì rất thuận tiện cho việc backup và restore. Trong đó dùng cách truy cập trực tiếp đến hình thì khi backup hay restore thì chúng ta phải backup/restore rất nhiều file nhỏ rất là mệt. Trong khi đó dùng cách lưu nội dung file ảnh vào CSDL thì chúng ta chỉ cần backup/restore lại một file là đủ, thông thường là file SQL.
● Nếu chúng ta dùng cách truy cập trực đến file hình thì khi chúng ta upload hình lên server qua (form upload) thì một số host không cho ta quyền ghi vào thư mục (write) thì chúng ta không đưa hình lên trang web của mình được. Lưu file trong CSDL là một giải pháp khá thích hợp trong hoàn cảnh này.
● Không chỉ lưu giới hạn file ảnh vào CSDL mà chúng ta còn lưu rất nhiều file khác nhau vào CSDL. Để đơn giản chúng ta sẽ tìm hiểu cách lưu file ảnh vào CSDL MySQL và ngôn ngữ thực hiện là PHP.
THIẾT KẾ CSDL
Chúng ta chỉ cần một bảng để lưu nội dung của một fila ảnh và trong bảng này chúng ta sẽ thiết kế như sau:
●    Cột nhận dạng (ID) của file ảnh, chúng ta dùng kiểu dữ liệu AUTO_INCREMENT để làm ID cho các file ảnh đã lưu trong CSDL.
● Cột lưu nội dung file ảnh, đây là cột quan trọng quyết định hình ảnh sẽ hiển thị lên trình duyệt. Nội dung của file ảnh cũa là một chuỗi nhưng dữ liệu nhị phân. Vì vậy chúng ta sẽ sử dụng kiểu dữ liệu mediumblob để lưu nội dung của file ảnh (kiểu dữ liệu blob cũng là chuỗi nhị phân, nhưng chiều dài tối đa chỉ có 65Kb, sẽ không đủ cho chúng ta muốn lưu một fila ảnh lớn).
●    Và bảng chúng ta sẽ có cấu trúc như sau

Quote:

CREATE TABLE imgData (
imgID               int             NOT NULL AUTO_INCREMENT,
imgContents         mediumblob      NOT NULL,
PRIMARY KEY (imgID)
);

[size=2]
LƯU NỘI DUNG FILE ẢNH VÀO CSDL
Chúng ta xem như đã có trên server. Và đoạn chương trình sau sẽ mở file ảnh thông qua tên file để đọc nội dung của file ảnh và lưu nội dung file ảnh vào trong CSDL.

Quote:

[size=2] //connect vào CSDL MySQL
//host = localhost
//username = root
//password = root
$conn = mysql_connect("localhost", "root", "root");
//chọn database làm việc
mysql_select_db("imgSave", $conn);
//tên file ảnh
$imgFilename = "imgData.jpg";
//mở file ảnh để đọc với chế độ đọc binary
$f = fopen($imgFilename, "rb");
$imgContents = fread($f, filesize($imgFilename));
fclose($f);
//chèn nội dung file ảnh vào table imgConetnts
$sql = "INSERT INTO imgData (imgContents) VALUES('" .     mysql_real_escape_string($imgContents, $conn) . "')";
mysql_query($sql, $conn);
?>

[size=2]ĐỌC NỘI DUNG FILE ẢNH TỪ CSDL
Để đọc nội dung của file ảnh từ CSDL, ta cần ID của file ảnh nếu chúng ta muốn hiển thị tấm hình nào. Đoạn mã sau sẽ minh hoạ chi tiết, nhưng ở đây ta chỉ lấy 1 tấm hình có chỉ số ID = 1.

Quote:

//connect vào CSDL MySQL
//host = localhost
//username = root
//password = root
$conn = mysql_connect("localhost", "root", "root");
//chọn database làm việc
mysql_select_db("imgSave", $conn);
//ID của file ảnh
$imgID = 1;
//đọc nội dung file ảnh từ table tblImage
$sql = "SELECT * FROM imgData WHERE imgID=" . $imgID;
$result = mysql_query($sql, $conn);
while($row = mysql_fetch_array($result)){
$imgData = $row['imgContents'];
}
?>

[size=2]
Biến $imgData sẽ chứa nội dung của file ảnh. Bước tiếp theo, ta xuất nội dung của file ảnh ra browser. Quá trình xuất nội dung của file ảnh ra browser. Nhưng chúng ta phải báo cho Browser biết nội dung của chúng ta xuất ra là hình ảnh, đơn giản là chúng ta chỉ chèn vào một dòng
header("Content-type: image/jpeg"); là được. Và sau đây là quy trình xuất ra nội dung một file ảnh như sau.
header("Content-type: image/jpeg");
echo $imgData;
?>
Như bạn đã thấy rồi đó lưu file ảnh vào CSDL và đọc nội dung của một file ảnh từ CSDL để xuất ra Browser thật là không dễ chút nào. Nhưng chúng ta chỉ cần lưu ý các bước sau đây.
·    Đọc nội dung file vào 1 biến, bạn nhớ phải mở file với chế độ đọc binary.
·    Xử lý các ký tự đặc biệt trước khi lưu vào database (sử dụng hàm mysql_real_escape_string).
· Trước khi xuất nội dung file ra browser, bạn nhớ dùng hàm header để báo cho browser biết kiểu file mà chương trình của bạn chuẩn bị trả về là một file ảnh.
KẾT LUẬN
Chúng ta có thể dùng cách này để viết một chương trình quản lý download trên website của mình. Tuy nhiên chúng ta cũng cần có một vài điều chú ý như sau:
·    Làm như vậy thì server sẽ xử lý nhiều công việc hơn cách download file trực tiếp.
·    Tốc độ download sẽ chậm hơn rất nhiều so với download file trực tiếp.
· Nội dung file thì được lưu trữ trong CSDL mà CSDL cho số lượng người kết nối vào có hạn, trong khí đó với 1 file có dung lượng lớn thì chúng ta có thể mất rất nhiều thời gian để download
MỘT THÍ DỤ VỀ CHƯƠNG TRÌNH DOWNLOAD
Giả sử chúng ta có 1 file example.zip nằm trong cùng một thư mục với file download.php. Nhưng chỉ người viết mới biết là file này nằm ở đâu, người truy cập muốn lấy file example.zip về thì phải truy cập vào trang download.php và trang download.php sẽ có nội dung như sau:

Quote:

//file download.php
$filename = "example.zip";
$f = fopen($filename, "rb");
header("Content-type: application/octet-stream");
header("Content-length: " . filesize($filename));
fpassthru($f);
fclose($f);
?>

[size=2]
Chúng ta chỉ cần bấy nhiêu là hoàn thành một trang download. Nhưng nếu muốn cho trang download chúng ta đẹp hơn, hoàn thiện hơn thì chúng ta phải cải tiến thêm.
Giải thích những câu lệnh trên
Đầu tiên, ta mở file example.zip để đọc ở chế độ nhị phân (binary):

Quote:

$f = fopen($filename, "rb");

[size=2]
Tiếp theo ta báo cho browser biết data trả về từ server là dữ liệu nhị phân chứ không phải là văn bản HTML như thông thường:

Quote:

header("Content-type: application/octet-stream");

[size=2] Đồng thời ta cũng báo cho browser biết dung lượng của file sẽ được tải xuống:

Quote:

header("Content-length: " . filesize($filename));

[size=2] Và cuối cùng là đọc nội dung file và echo lại cho browser download:

Quote:

fpassthru($f);.
Lệnh fpassthru($f); tương đương với 2 lệnh:
$content = fread($f, filesize($filename));
echo $content;

[size=2]
Và như thế chúng ta có thể thí dụ chạy trên nhiều kiểu file khác nhau như (word, excel, PDF...) và bạn sẽ thấy có một vài chỗ thật khó chịu vì browser mặc định sẽ lưu file của bạn lên đĩa với tên là download.php bạn phải đổi tên lại cho đúng và trước khi mở file đó ra đọc.
Để giải quyết vấn đề trên ta chỉ cần thêm một lệnh  header() nữa là được.
header('Content-disposition: attachment; filename="'.$filename.'"');
Tham số "attachment" của header "Content-disposition" sẽ báo cho browser biết là nên download và save file thay vì open. Tham số "filename=" sẽ báo cho browser biết tên của file đang được download.
Tới đây chúng ta có thể nâng cấp trang web của mình lên cho người dùng chọn file để download. Có nghĩa là chúng ta hiển thị ra 1 list các tập tin cho người dùng download và liên kết các file download đều đến file download.php?filename=”Tên file cần download”. Trong file download.php chỉ cần $_GET['filename'] là chúng ta có thể download về được.
Và file download.php sẽ có nội dung như sau:

Quote:

$filename = isset($_GET['file'])?$_GET['file']:'';
$upload_dir = "../upload/";
//Mở file với chế độ nhị phân
$fp = fopen($upload_dir.$filename, "rb");
//gởi header đến cho browser
header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename="'.$filename.'"');
header('Content-length: ' . filesize($upload_dir.$filename));
//đọc file và trả dữ liệu về cho browser
fpassthru($fp);
fclose($fp);

Download tại đây
Password: vietnamdiemhen

http://phpcodevn.com/forum/viewforum.php?f=29&sid=4cee6fd496a6522ff25fd45d8e29817c