pixmicat

Subversion Repositories:
Compare Path: Rev
With Path: Rev
/release/ @ 455  →  /release/ @ 456
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/log2pio.php
@@ -0,0 +1,84 @@
<?php
/**
* Pixmicat! PIO 公用程式 - Pixmicat!-Log -> Pixmciat-PIO (Log) 資料格式轉換器
*
* 本程式可以自 Log 版轉換格式自 PIO 版 Log 資料來源。
*
* 注意:本程式是給 Log 版舊程式使用以轉換,非直接用在 PIO 新版上面
*
* @package PMCUtility
* @version $Id$
* @date $Date$
*/
include_once('./config.php');
define('DEL_ZOMBIE', true); // 如果有文章沒有出現在樹狀結構,是否不要轉換直接刪除?
define('SAVE_LOG', true); // 是否儲存新結構 (舊結構將保留並更名)
 
// 各資料儲存檔位置
$logimg = file(LOGFILE); $logimg_cnt = count($logimg);
$trees = array(); // 文章回應對應編號陣列 (回應No. => 首篇No.)
$logtree = array_map('rtrim', file(TREEFILE));
foreach($logtree as $treeline){ // 解析樹狀結構製成對應索引
if($treeline=='') continue;
$tline = explode(',', $treeline); $tline_cnt = count($tline);
$trees[$tline[0]] = 0;
for($t = 1; $t < $tline_cnt; $t++){ $trees[$tline[$t]] = $tline[0]; }
}
unset($logtree);
 
// 圖檔存放位置
$dirimg = realpath('.').DIRECTORY_SEPARATOR.IMG_DIR;
 
/* 取得圖檔的寬長以存入資料 */
function getImageWH($imgname){
global $dirimg;
 
$imgpath = $dirimg.$imgname;
if(!file_exists($imgpath)) return array(0, 0);
list($width, $height,) = getimagesize($imgpath);
return array($width, $height); // 回傳寬高陣列
}
 
/* 取得圖檔的檔案大小字串 (單位 KB) */
function getImageSizeText($imgname){
global $dirimg;
 
$imgpath = $dirimg.$imgname;
if(!file_exists($imgpath)) return false;
$imgsize = filesize($imgpath);
return ($imgsize >= 1024 ? (int)($imgsize / 1024).' KB' : $imgsize.' B'); // 回傳檔案大小字串
}
 
/* 取得回應的對象編號 */
function getReplyTargetNo($no){
global $trees;
 
if(isset($trees[$no])) return $trees[$no];
return false;
}
 
/* 更改 log 檔結構 */
// OLD: 編號,時間 (ID),名稱,E-Mail,標題,內文,狀態旗標,主機位置,編碼後文章密碼,附加圖檔類型,預覽圖寬,預覽圖長,Unix時間撮記,附加圖檔MD5,
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
// NEW: 編號,回應目標編號,附加圖檔MD5,類別標籤,Unix時間撮記,附加圖檔類型,圖檔寬,圖檔長,圖檔大小,預覽圖寬,預覽圖長,編碼後文章密碼,時間 (ID),名稱,E-mail,標題,內文,主機位置,狀態旗標,
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
header('Content-Type: text/plain; charset=utf-8');
$newLine = array(); // 新資料格式
if(count(explode(',', $logimg[0])) != 15) die('File structure error. maybe it\'s already a PIO structure.');
for($i = 0; $i < $logimg_cnt; $i++){
$l = explode(',', $logimg[$i]); // 舊資料格式 (用逗號拆開)
$s = getImageWH($l[12].$l[9]); // 圖檔寬長 (寬, 長)
if(!DEL_ZOMBIE || getReplyTargetNo($l[0]) !== false) $newLine[] = implode(',', array($l[0], getReplyTargetNo($l[0]), $l[13], '', $l[12], $l[9], $s[0], $s[1], getImageSizeText($l[12].$l[9]), (int) $l[10], (int) $l[11], $l[8], $l[1], $l[2], $l[3], $l[4], $l[5], $l[7], $l[6], ''))."\r\n";
}
$writeContent = implode('', $newLine);
 
if(SAVE_LOG){
rename(LOGFILE, LOGFILE.'.old');
$fs = fopen(LOGFILE, 'w');
fwrite($fs, $writeContent);
fclose($fs);
die('File save OK. The old file already renamed.');
}else{
echo $writeContent;
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/mysql2pio.php
@@ -0,0 +1,68 @@
<?php
/**
* Pixmicat! PIO 公用程式 - Pixmicat!-MySQL -> Pixmciat-PIO (MySQL) 資料格式轉換器
*
* 本程式可以自 MySQL 版轉換格式自 PIO 版 MySQL 資料來源。
*
* 注意:本程式是給 MySQL 版舊程式使用以轉換,非直接用在 PIO 新版上面
*
* @package PMCUtility
* @version $Id$
* @date $Date$
*/
include_once('./config.php');
 
$dirimg = realpath('.').DIRECTORY_SEPARATOR.IMG_DIR; // 圖檔存放位置
 
/* 取得圖檔的寬長以存入資料 */
function getImageWH($imgname){
global $dirimg;
 
$imgpath = $dirimg.$imgname;
if(!file_exists($imgpath)) return array(0, 0);
list($width, $height,) = getimagesize($imgpath);
return array($width, $height); // 回傳寬高陣列
}
 
/* 取得圖檔的檔案大小字串 (單位 KB) */
function getImageSizeText($imgname){
global $dirimg;
 
$imgpath = $dirimg.$imgname;
if(!file_exists($imgpath)) return false;
$imgsize = filesize($imgpath);
return ($imgsize >= 1024 ? (int)($imgsize / 1024).' KB' : $imgsize.' B'); // 回傳檔案大小字串
}
 
 
if(@!$con=mysql_connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD)){
echo 'It occurred a fatal error when connecting to the MySQL server.<p>';
echo 'Check your MySQL login setting in config file or the MySQL server status.';
exit;
}
mysql_select_db(MYSQL_DBNAME, $con);
@mysql_query("SET NAMES 'utf8'"); // MySQL資料以UTF-8模式傳送
 
if(($result = mysql_query("SHOW COLUMNS FROM ".SQLLOG." LIKE 'category'")) && mysql_num_rows($result) == 0){ // 更新資料表結構
mysql_query('ALTER TABLE '.SQLLOG.' ADD category VARCHAR(255) NOT NULL AFTER md5, COMMENT = "For Pixmicat!-PIO [Structure V2]"'); // category
mysql_query('ALTER TABLE '.SQLLOG.' ADD imgw SMALLINT(1) NOT NULL AFTER ext'); // imgw
mysql_query('ALTER TABLE '.SQLLOG.' ADD imgh SMALLINT(1) NOT NULL AFTER imgw'); // imgh
mysql_query('ALTER TABLE '.SQLLOG.' ADD imgsize VARCHAR(10) NOT NULL AFTER imgh'); // imgsize
mysql_query('ALTER TABLE '.SQLLOG.' CHANGE md5 md5chksum VARCHAR(32) NOT NULL'); // md5chksum
mysql_query('ALTER TABLE '.SQLLOG.' CHANGE w tw SMALLINT(1) NOT NULL'); //tw
mysql_query('ALTER TABLE '.SQLLOG.' CHANGE h th SMALLINT(1) NOT NULL'); //th
mysql_free_result($result);
 
$tmpSQL = 'SELECT no,tim,ext FROM '.SQLLOG.' WHERE ext <> "" ORDER BY no';
if(!$result2=mysql_query($tmpSQL)) echo "sql失敗814<br>";
while(list($dno, $dtim, $dext)=mysql_fetch_row($result2)){ // 個別跑迴圈
$s = getImageWH($dtim.$dext); // 圖檔寬長
mysql_query('UPDATE '.SQLLOG.' SET imgsize = "'.getImageSizeText($dtim.$dext).'", imgw = '.$s[0].', imgh = '.$s[1].' WHERE no = '.$dno);
}
mysql_free_result($result2);
echo 'done.';
}else{
echo 'It seems already done.';
}
mysql_close($con);
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/satellite/satellite.php
@@ -0,0 +1,117 @@
<?php
/**
* Pixmicat! PIO 公用程式 - FileIO Satellite PHP
*
* 利用此一放置於外部空間的衛星程式,可以讓 FileIO 利用外部空間存放圖檔。
*
* @package PMCUtility
* @version $Id$
* @date $Date$
*/
 
define('TRANSPORT_KEY', '12345678'); // 傳輸認證金鑰
define('USER_AGENT', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0'); // Just for fun ;-)
define('STORAGE_DIRECTORY', 'src/'); // 圖檔儲存目錄
$mode = isset($_POST['mode']) ? $_POST['mode'] : ''; // 要求模式
$Tkey = isset($_POST['key']) ? $_POST['key'] : ''; // 對方送來傳輸金鑰
$imgname = isset($_POST['imgname']) ? $_POST['imgname'] : ''; // 圖檔名稱
 
switch($mode){
case 'init': // 初始化
DoConstruct() ? DoOK() : DoError();
break;
case 'transload': // 遠端抓取
DoTransload($imgname) ? DoOK() : DoError();
break;
case 'upload': // 上傳檔案
DoUpload($imgname) ? DoOK() : DoError();
break;
case 'delete': // 刪除檔案
DoDelete($imgname) ? DoOK() : DoError();
break;
default:
DoNotFound();
}
 
/* 初始化 */
function DoConstruct(){
global $Tkey;
if($Tkey != TRANSPORT_KEY) return false; // 金鑰不符
 
if(!is_dir(STORAGE_DIRECTORY)){ mkdir(STORAGE_DIRECTORY); @chmod(STORAGE_DIRECTORY, 0777); }
return true;
}
 
/* 進行遠端抓取檔案並儲存 */
function DoTransload($imgname){
$imgurl = isset($_POST['imgurl']) ? parse_url($_POST['imgurl']) : false; // 圖檔遠端URL位置
if(!is_dir(STORAGE_DIRECTORY)) DoConstruct();
 
if(!($fp = @fsockopen($imgurl['host'], 80))) return false;
 
$out = 'GET '.$imgurl['path']." HTTP/1.1\r\n";
$out .= 'Host: '.$imgurl['host']."\r\n";
$out .= 'User-Agent: '.USER_AGENT."\r\n\r\n";
fwrite($fp, $out);
$result = '';
while(!feof($fp)){ $result .= fgets($fp, 128); }
fclose($fp);
 
$result = explode("\r\n\r\n", $result); // 將檔頭和內容分隔開
if(strpos($result[0], '200 OK')===false) return false; // 檔案不存在或伺服器出現問題
 
$fs = fopen(STORAGE_DIRECTORY.$imgname, "wb"); // 二進位儲存
if(fwrite($fs, $result[1])===false) return false; // 寫入錯誤
chmod(STORAGE_DIRECTORY.$imgname, 0666);
fclose($fs);
 
return true;
}
 
/* 接受上傳檔案並儲存 */
function DoUpload($imgname){
$imgfile = isset($_FILES['imgfile']['tmp_name']) ? $_FILES['imgfile']['tmp_name'] : false;
if(!$imgfile) return false;
if(!is_dir(STORAGE_DIRECTORY)) DoConstruct();
 
$result = move_uploaded_file($imgfile, realpath('.').DIRECTORY_SEPARATOR.STORAGE_DIRECTORY.$imgname); // 搬移上傳檔案
if($result) chmod(STORAGE_DIRECTORY.$imgname, 0666);
 
return $result;
}
 
/* 刪除檔案 */
function DoDelete($imgname){
global $Tkey;
if($Tkey != TRANSPORT_KEY) return false;
 
return @unlink(STORAGE_DIRECTORY.$imgname);
}
 
/* 阻止閒雜人士進入 */
function DoNotFound(){
header('HTTP/1.1 404 Not Found');
echo '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>404 - Not Found</title>
</head>
<body>
<h1>404 - Not Found</h1>
</body>
 
</html>';
}
 
/* 操作成功,回傳成功訊息 */
function DoOK(){
header('HTTP/1.1 202 Accepted');
}
 
/* 操作失敗,回傳錯誤訊息 */
function DoError(){
header('HTTP/1.1 403 Forbidden');
}
?>
New file
/release/PIO-v4/Utilities/satellite/cgi-lib.pl
@@ -0,0 +1,471 @@
# Perl Routines to Manipulate CGI input
# cgi-lib@pobox.com
# $Id: cgi-lib.pl,v 2.18 1999/02/23 08:16:43 brenner Exp $
#
# Copyright (c) 1993-1999 Steven E. Brenner
# Unpublished work.
# Permission granted to use and modify this library so long as the
# copyright above is maintained, modifications are documented, and
# credit is given for any use of the library.
#
# Thanks are due to many people for reporting bugs and suggestions
 
# For more information, see:
# http://cgi-lib.stanford.edu/cgi-lib/
 
$cgi_lib'version = sprintf("%d.%02d", q$Revision: 2.18 $ =~ /(\d+)\.(\d+)/);
 
 
# Parameters affecting cgi-lib behavior
# User-configurable parameters affecting file upload.
$cgi_lib'maxdata = 131072; # maximum bytes to accept via POST - 2^17
$cgi_lib'writefiles = 0; # directory to which to write files, or
# 0 if files should not be written
$cgi_lib'filepre = "cgi-lib"; # Prefix of file names, in directory above
 
# Do not change the following parameters unless you have special reasons
$cgi_lib'bufsize = 8192; # default buffer size when reading multipart
$cgi_lib'maxbound = 100; # maximum boundary length to be encounterd
$cgi_lib'headerout = 0; # indicates whether the header has been printed
 
 
# ReadParse
# Reads in GET or POST data, converts it to unescaped text, and puts
# key/value pairs in %in, using "\0" to separate multiple selections
 
# Returns >0 if there was input, 0 if there was no input
# undef indicates some failure.
 
# Now that cgi scripts can be put in the normal file space, it is useful
# to combine both the form and the script in one place. If no parameters
# are given (i.e., ReadParse returns FALSE), then a form could be output.
 
# If a reference to a hash is given, then the data will be stored in that
# hash, but the data from $in and @in will become inaccessable.
# If a variable-glob (e.g., *cgi_input) is the first parameter to ReadParse,
# information is stored there, rather than in $in, @in, and %in.
# Second, third, and fourth parameters fill associative arrays analagous to
# %in with data relevant to file uploads.
 
# If no method is given, the script will process both command-line arguments
# of the form: name=value and any text that is in $ENV{'QUERY_STRING'}
# This is intended to aid debugging and may be changed in future releases
 
sub ReadParse {
# Disable warnings as this code deliberately uses local and environment
# variables which are preset to undef (i.e., not explicitly initialized)
local ($perlwarn);
$perlwarn = $^W;
$^W = 0;
 
local (*in) = shift if @_; # CGI input
local (*incfn, # Client's filename (may not be provided)
*inct, # Client's content-type (may not be provided)
*insfn) = @_; # Server's filename (for spooled files)
local ($len, $type, $meth, $errflag, $cmdflag, $got, $name);
binmode(STDIN); # we need these for DOS-based systems
binmode(STDOUT); # and they shouldn't hurt anything else
binmode(STDERR);
# Get several useful env variables
$type = $ENV{'CONTENT_TYPE'};
$len = $ENV{'CONTENT_LENGTH'};
$meth = $ENV{'REQUEST_METHOD'};
if ($len > $cgi_lib'maxdata) { #'
&CgiDie("cgi-lib.pl: Request to receive too much data: $len bytes\n");
}
if (!defined $meth || $meth eq '' || $meth eq 'GET' ||
$meth eq 'HEAD' ||
$type eq 'application/x-www-form-urlencoded') {
local ($key, $val, $i);
# Read in text
if (!defined $meth || $meth eq '') {
$in = $ENV{'QUERY_STRING'};
$cmdflag = 1; # also use command-line options
} elsif($meth eq 'GET' || $meth eq 'HEAD') {
$in = $ENV{'QUERY_STRING'};
} elsif ($meth eq 'POST') {
if (($got = read(STDIN, $in, $len) != $len))
{$errflag="Short Read: wanted $len, got $got\n";};
} else {
&CgiDie("cgi-lib.pl: Unknown request method: $meth\n");
}
 
@in = split(/[&;]/,$in);
push(@in, @ARGV) if $cmdflag; # add command-line parameters
 
foreach $i (0 .. $#in) {
# Convert plus to space
$in[$i] =~ s/\+/ /g;
 
# Split into key and value.
($key, $val) = split(/=/,$in[$i],2); # splits on the first =.
 
# Convert %XX from hex numbers to alphanumeric
$key =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
$val =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
 
# Associate key and value
$in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator
$in{$key} .= $val;
}
 
} elsif ($ENV{'CONTENT_TYPE'} =~ m#^multipart/form-data#) {
# for efficiency, compile multipart code only if needed
$errflag = !(eval <<'END_MULTIPART');
 
local ($buf, $boundary, $head, @heads, $cd, $ct, $fname, $ctype, $blen);
local ($bpos, $lpos, $left, $amt, $fn, $ser);
local ($bufsize, $maxbound, $writefiles) =
($cgi_lib'bufsize, $cgi_lib'maxbound, $cgi_lib'writefiles);
 
 
# The following lines exist solely to eliminate spurious warning messages
$buf = '';
 
($boundary) = $type =~ /boundary="([^"]+)"/; #"; # find boundary
($boundary) = $type =~ /boundary=(\S+)/ unless $boundary;
&CgiDie ("Boundary not provided: probably a bug in your server")
unless $boundary;
$boundary = "--" . $boundary;
$blen = length ($boundary);
 
if ($ENV{'REQUEST_METHOD'} ne 'POST') {
&CgiDie("Invalid request method for multipart/form-data: $meth\n");
}
 
if ($writefiles) {
local($me);
stat ($writefiles);
$writefiles = "/tmp" unless -d _ && -w _;
# ($me) = $0 =~ m#([^/]*)$#;
$writefiles .= "/$cgi_lib'filepre";
}
 
# read in the data and split into parts:
# put headers in @in and data in %in
# General algorithm:
# There are two dividers: the border and the '\r\n\r\n' between
# header and body. Iterate between searching for these
# Retain a buffer of size(bufsize+maxbound); the latter part is
# to ensure that dividers don't get lost by wrapping between two bufs
# Look for a divider in the current batch. If not found, then
# save all of bufsize, move the maxbound extra buffer to the front of
# the buffer, and read in a new bufsize bytes. If a divider is found,
# save everything up to the divider. Then empty the buffer of everything
# up to the end of the divider. Refill buffer to bufsize+maxbound
# Note slightly odd organization. Code before BODY: really goes with
# code following HEAD:, but is put first to 'pre-fill' buffers. BODY:
# is placed before HEAD: because we first need to discard any 'preface,'
# which would be analagous to a body without a preceeding head.
 
$left = $len;
PART: # find each part of the multi-part while reading data
while (1) {
die $@ if $errflag;
 
$amt = ($left > $bufsize+$maxbound-length($buf)
? $bufsize+$maxbound-length($buf): $left);
$errflag = (($got = read(STDIN, $buf, $amt, length($buf))) != $amt);
die "Short Read: wanted $amt, got $got\n" if $errflag;
$left -= $amt;
 
$in{$name} .= "\0" if defined $in{$name};
$in{$name} .= $fn if $fn;
 
$name=~/([-\w]+)/; # This allows $insfn{$name} to be untainted
if (defined $1) {
$insfn{$1} .= "\0" if defined $insfn{$1};
$insfn{$1} .= $fn if $fn;
}
BODY:
while (($bpos = index($buf, $boundary)) == -1) {
if ($left == 0 && $buf eq '') {
foreach $value (values %insfn) {
unlink(split("\0",$value));
}
&CgiDie("cgi-lib.pl: reached end of input while seeking boundary " .
"of multipart. Format of CGI input is wrong.\n");
}
die $@ if $errflag;
if ($name) { # if no $name, then it's the prologue -- discard
if ($fn) { print FILE substr($buf, 0, $bufsize); }
else { $in{$name} .= substr($buf, 0, $bufsize); }
}
$buf = substr($buf, $bufsize);
$amt = ($left > $bufsize ? $bufsize : $left); #$maxbound==length($buf);
$errflag = (($got = read(STDIN, $buf, $amt, length($buf))) != $amt);
die "Short Read: wanted $amt, got $got\n" if $errflag;
$left -= $amt;
}
if (defined $name) { # if no $name, then it's the prologue -- discard
if ($fn) { print FILE substr($buf, 0, $bpos-2); }
else { $in {$name} .= substr($buf, 0, $bpos-2); } # kill last \r\n
}
close (FILE);
last PART if substr($buf, $bpos + $blen, 2) eq "--";
substr($buf, 0, $bpos+$blen+2) = '';
$amt = ($left > $bufsize+$maxbound-length($buf)
? $bufsize+$maxbound-length($buf) : $left);
$errflag = (($got = read(STDIN, $buf, $amt, length($buf))) != $amt);
die "Short Read: wanted $amt, got $got\n" if $errflag;
$left -= $amt;
 
 
undef $head; undef $fn;
HEAD:
while (($lpos = index($buf, "\r\n\r\n")) == -1) {
if ($left == 0 && $buf eq '') {
foreach $value (values %insfn) {
unlink(split("\0",$value));
}
&CgiDie("cgi-lib: reached end of input while seeking end of " .
"headers. Format of CGI input is wrong.\n$buf");
}
die $@ if $errflag;
$head .= substr($buf, 0, $bufsize);
$buf = substr($buf, $bufsize);
$amt = ($left > $bufsize ? $bufsize : $left); #$maxbound==length($buf);
$errflag = (($got = read(STDIN, $buf, $amt, length($buf))) != $amt);
die "Short Read: wanted $amt, got $got\n" if $errflag;
$left -= $amt;
}
$head .= substr($buf, 0, $lpos+2);
push (@in, $head);
@heads = split("\r\n", $head);
($cd) = grep (/^\s*Content-Disposition:/i, @heads);
($ct) = grep (/^\s*Content-Type:/i, @heads);
 
($name) = $cd =~ /\bname="([^"]+)"/i; #";
($name) = $cd =~ /\bname=([^\s:;]+)/i unless defined $name;
 
($fname) = $cd =~ /\bfilename="([^"]*)"/i; #"; # filename can be null-str
($fname) = $cd =~ /\bfilename=([^\s:;]+)/i unless defined $fname;
$incfn{$name} .= (defined $in{$name} ? "\0" : "") .
(defined $fname ? $fname : "");
 
($ctype) = $ct =~ /^\s*Content-type:\s*"([^"]+)"/i; #";
($ctype) = $ct =~ /^\s*Content-Type:\s*([^\s:;]+)/i unless defined $ctype;
$inct{$name} .= (defined $in{$name} ? "\0" : "") . $ctype;
 
if ($writefiles && defined $fname) {
$ser++;
$fn = $writefiles . ".$$.$ser";
open (FILE, ">$fn") || &CgiDie("Couldn't open $fn\n");
binmode (FILE); # write files accurately
}
substr($buf, 0, $lpos+4) = '';
undef $fname;
undef $ctype;
}
 
1;
END_MULTIPART
if ($errflag) {
local ($errmsg, $value);
$errmsg = $@ || $errflag;
foreach $value (values %insfn) {
unlink(split("\0",$value));
}
&CgiDie($errmsg);
} else {
# everything's ok.
}
} else {
&CgiDie("cgi-lib.pl: Unknown Content-type: $ENV{'CONTENT_TYPE'}\n");
}
 
# no-ops to avoid warnings
$insfn = $insfn;
$incfn = $incfn;
$inct = $inct;
 
$^W = $perlwarn;
 
return ($errflag ? undef : scalar(@in));
}
 
 
# PrintHeader
# Returns the magic line which tells WWW that we're an HTML document
 
sub PrintHeader {
return "Content-type: text/html\n\n";
}
 
 
# HtmlTop
# Returns the <head> of a document and the beginning of the body
# with the title and a body <h1> header as specified by the parameter
 
sub HtmlTop
{
local ($title) = @_;
 
return <<END_OF_TEXT;
<html>
<head>
<title>$title</title>
</head>
<body>
<h1>$title</h1>
END_OF_TEXT
}
 
 
# HtmlBot
# Returns the </body>, </html> codes for the bottom of every HTML page
 
sub HtmlBot
{
return "</body>\n</html>\n";
}
 
 
# SplitParam
# Splits a multi-valued parameter into a list of the constituent parameters
 
sub SplitParam
{
local ($param) = @_;
local (@params) = split ("\0", $param);
return (wantarray ? @params : $params[0]);
}
 
 
# MethGet
# Return true if this cgi call was using the GET request, false otherwise
 
sub MethGet {
return (defined $ENV{'REQUEST_METHOD'} && $ENV{'REQUEST_METHOD'} eq "GET");
}
 
 
# MethPost
# Return true if this cgi call was using the POST request, false otherwise
 
sub MethPost {
return (defined $ENV{'REQUEST_METHOD'} && $ENV{'REQUEST_METHOD'} eq "POST");
}
 
 
# MyBaseUrl
# Returns the base URL to the script (i.e., no extra path or query string)
sub MyBaseUrl {
local ($ret, $perlwarn);
$perlwarn = $^W; $^W = 0;
$ret = 'http://' . $ENV{'SERVER_NAME'} .
($ENV{'SERVER_PORT'} != 80 ? ":$ENV{'SERVER_PORT'}" : '') .
$ENV{'SCRIPT_NAME'};
$^W = $perlwarn;
return $ret;
}
 
 
# MyFullUrl
# Returns the full URL to the script (i.e., with extra path or query string)
sub MyFullUrl {
local ($ret, $perlwarn);
$perlwarn = $^W; $^W = 0;
$ret = 'http://' . $ENV{'SERVER_NAME'} .
($ENV{'SERVER_PORT'} != 80 ? ":$ENV{'SERVER_PORT'}" : '') .
$ENV{'SCRIPT_NAME'} . $ENV{'PATH_INFO'} .
(length ($ENV{'QUERY_STRING'}) ? "?$ENV{'QUERY_STRING'}" : '');
$^W = $perlwarn;
return $ret;
}
 
 
# MyURL
# Returns the base URL to the script (i.e., no extra path or query string)
# This is obsolete and will be removed in later versions
sub MyURL {
return &MyBaseUrl;
}
 
 
# CgiError
# Prints out an error message which which containes appropriate headers,
# markup, etcetera.
# Parameters:
# If no parameters, gives a generic error message
# Otherwise, the first parameter will be the title and the rest will
# be given as different paragraphs of the body
 
sub CgiError {
local (@msg) = @_;
local ($i,$name);
 
if (!@msg) {
$name = &MyFullUrl;
@msg = ("Error: script $name encountered fatal error\n");
};
 
if (!$cgi_lib'headerout) { #')
print &PrintHeader;
print "<html>\n<head>\n<title>$msg[0]</title>\n</head>\n<body>\n";
}
print "<h1>$msg[0]</h1>\n";
foreach $i (1 .. $#msg) {
print "<p>$msg[$i]</p>\n";
}
 
$cgi_lib'headerout++;
}
 
 
# CgiDie
# Identical to CgiError, but also quits with the passed error message.
 
sub CgiDie {
local (@msg) = @_;
&CgiError (@msg);
die @msg;
}
 
 
# PrintVariables
# Nicely formats variables. Three calling options:
# A non-null associative array - prints the items in that array
# A type-glob - prints the items in the associated assoc array
# nothing - defaults to use %in
# Typical use: &PrintVariables()
 
sub PrintVariables {
local (*in) = @_ if @_ == 1;
local (%in) = @_ if @_ > 1;
local ($out, $key, $output);
 
$output = "\n<dl compact>\n";
foreach $key (sort keys(%in)) {
foreach (split("\0", $in{$key})) {
($out = $_) =~ s/\n/<br>\n/g;
$output .= "<dt><b>$key</b>\n <dd>:<i>$out</i>:<br>\n";
}
}
$output .= "</dl>\n";
 
return $output;
}
 
# PrintEnv
# Nicely formats all environment variables and returns HTML string
sub PrintEnv {
&PrintVariables(*ENV);
}
 
 
# The following lines exist only to avoid warning messages
$cgi_lib'writefiles = $cgi_lib'writefiles;
$cgi_lib'bufsize = $cgi_lib'bufsize ;
$cgi_lib'maxbound = $cgi_lib'maxbound;
$cgi_lib'version = $cgi_lib'version;
$cgi_lib'filepre = $cgi_lib'filepre;
 
1; #return true
 
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/satellite/satellite.cgi
@@ -0,0 +1,193 @@
#!/usr/bin/perl -w
 
##
# Pixmicat! PIO 公用程式 - FileIO Satellite Perl
#
# 利用此一放置於外部空間的衛星程式,可以讓 FileIO 利用外部空間存放圖檔。
#
# @package PMCUtility
# @version $Id$
# @date $Date$
#
 
require './cgi-lib.pl';
 
$cgi_lib'maxdata = 2097152; # 上傳檔案大小上限
$TRANSPORT_KEY = '12345678'; # 傳輸認證金鑰
$USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0'; # Just for fun ;-)
$STORAGE_DIRECTORY = 'src/'; # 圖檔儲存目錄
 
&ReadParse;
$EOL = "\015\012";
$BLANK = $EOL x 2;
 
sub parse_url {
local($url) = @_;
 
$ftp_port = 21;
$http_port = 80;
$gopher_port = 70;
$telnet_port = 23;
$wais_port = 210;
$news_port = 119;
 
if ($url =~ m#^(\w+):#) {
$protocol = $1;
$protocol =~ tr/A-Z/a-z/;
} else {
return undef;
}
 
# URL of type: http://host[:port]/path[?search-string]
 
if ($protocol eq "http") {
if ($url =~ m#^\s*\w+://([\w-\.]+):?(\d*)([^ \t]*)$#) {
$server = $1;
$server =~ tr/A-Z/a-z/;
$port = ($2 ne "" ? $2 : $http_port);
$path = ( $3 ? $3 : '/');
return { protocol => $protocol, host => $server, port => $port, path => $path }; # Return by reference
}
return undef;
}
 
}
 
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
$mode = defined($in{'mode'}) ? $in{'mode'} : ''; # 要求模式
$Tkey = defined($in{'key'}) ? $in{'key'} : ''; # 對方送來傳輸金鑰
$imgname = defined($in{'imgname'}) ? $in{'imgname'} : ''; # 圖檔名稱
}
 
if ($mode eq 'init') { # 初始化
DoConstruct() ? DoOK() : DoError();
} elsif ($mode eq 'transload') { # 遠端抓取
DoTransload($imgname) ? DoOK() : DoError();
} elsif ($mode eq 'upload') { # 上傳檔案
DoUpload($imgname) ? DoOK() : DoError();
} elsif ($mode eq 'delete') { # 刪除檔案
DoDelete($imgname) ? DoOK() : DoError();
} else {
DoNotFound();
}
 
### 初始化
sub DoConstruct{
return undef if $Tkey != $TRANSPORT_KEY; # 金鑰不符
 
if(! -d $STORAGE_DIRECTORY){ mkdir($STORAGE_DIRECTORY); chmod($STORAGE_DIRECTORY, 0777); }
return 1;
}
 
### 進行遠端抓取檔案並儲存
sub DoTransload{
use Socket;
my $imgname=$_[0];
$imgurl = defined($in{'imgurl'}) ? parse_url($in{'imgurl'}) : undef; # 圖檔遠端URL位置
if(! -d $STORAGE_DIRECTORY){ DoConstruct(); }
 
my ($remote,$port,$doc) = ($$imgurl{'host'},$$imgurl{'port'},$$imgurl{'path'}); # $$var = dereference
$sockaddr = 'S n a4 x8';
 
if ($port =~ /\D/) { $port = getservbyname($port, 'tcp'); }
return undef unless $port;
$thataddr = gethostbyname($remote);
$that = pack($sockaddr, AF_INET, $port, $thataddr);
$proto = (getprotobyname('tcp'))[2] || 6;
socket(SOCK, PF_INET, SOCK_STREAM, $proto) || return undef;
connect(SOCK, $that) || return undef;
binmode(SOCK);
$ofh = select(SOCK); $| = 1; select($ofh); # flush buffer on every write
 
print SOCK "GET $doc HTTP/1.1".$EOL.
"Host: $remote".$EOL.
"User-Agent: $USER_AGENT".$BLANK;
 
vec($rin='', fileno(SOCK), 1) = 1;
select($rin, undef, undef, 20) || return undef; # no response from server
 
while( <SOCK> ) {
s/\r\n/\n/g;
s/\r/\n/g;
if ( /HTTP([\/\.\d]+)\s+(\d+)\s+(.*)\n/i ) { $status = $2; }
if ( /Content-Length: (\s*)(\d+)\n/i ) { $content_length = $2; }
last if $_ =~ /^$/;
}
 
$content='';
if ($content_length) {
read(SOCK, $content, $content_length);
} else {
while ( <SOCK> ) { $content .= $_; }
}
close(SOCK);
select($ofh);
 
return undef if $status ne "200"; # 檔案不存在或伺服器出現問題
 
open(FS,">$STORAGE_DIRECTORY$imgname") || return undef;
binmode(FS);
print FS $content;
chmod($STORAGE_DIRECTORY.$imgname, 0666);
close(FS);
 
return 1;
}
 
### 接受上傳檔案並儲存
sub DoUpload{
my $imgname=$_[0];
$imgfile = defined($in{'imgfile'}) ? $in{'imgfile'} : undef;
if(!$imgfile){ return undef; }
if(! -d $STORAGE_DIRECTORY){ DoConstruct(); }
 
open(FS,">$STORAGE_DIRECTORY$imgname") || return undef;
binmode(FS);
print FS $imgfile;
chmod($STORAGE_DIRECTORY.$imgname, 0666);
close(FS);
 
return 1;
}
 
### 刪除檔案
sub DoDelete{
my $imgname=$_[0];
return undef if $Tkey != $TRANSPORT_KEY; # 金鑰不符
 
return unlink($STORAGE_DIRECTORY.$imgname);
}
 
### 阻止閒雜人士進入
sub DoNotFound{
print "Status: 404 Not Found".$EOL.
"Content-type: text/html".$BLANK.
"<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n".
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n".
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n".
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n".
" <head>\n".
" <title>404 - Not Found</title>\n".
" </head>\n".
" <body>\n".
" <h1>404 - Not Found</h1>\n".
" </body>\n".
"</html>";
}
 
### 操作成功,回傳成功訊息
sub DoOK{
print "Status: 202 Accepted".$EOL.
"Content-type: text/plain".$BLANK.
"Succeed.";
 
}
 
### 操作失敗,回傳錯誤訊息
sub DoError{
print "Status: 403 Forbidden".$EOL.
"Content-type: text/plain".$BLANK.
"Failed.";
}
 
__END__
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/pio2pio.php
@@ -0,0 +1,94 @@
<?php
/**
* Pixmicat! PIO 公用程式 - PIO 匯入匯出轉換器
*
* 這是 PIO 額外提供的功能,可以執行匯出備份、匯入備份、來源轉換等動作。
* 請修改下方 PIO_ANOTHER_CONNSTR PIO 連線字串 (匯入及轉換動作才需要,匯入則直接使用設定檔設定)
*
* @package PMCUtility
* @version $Id$
* @date $Date$
*/
include('./config.php');
define('PIO_ANOTHER_CONNSTR', 'sqlite://pixmicat.db/imglog/'); // Another-PIO 連線字串 (此來源必須無任何資料,全新)
$PIOEnv = array( // PIO 環境常數
'BOARD' => '.',
'LUTCACHE' => './lutcache.dat',
'NONAME' => DEFAULT_NONAME,
'NOTITLE' => DEFAULT_NOTITLE,
'NOCOMMENT' => DEFAULT_NOCOMMENT,
'LOG_MAX' => LOG_MAX,
'PERIOD.POST' => RENZOKU,
'PERIOD.IMAGEPOST' => RENZOKU2
);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-tw">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Language" content="zh-tw" />
<title>PIO -> PIO Im/Exporter</title>
</head>
<body>
 
<div id="main">
請選擇下列一項進行操作:
<ul>
<li><a href="?mode=export">匯出目前 PIO 資料成中介檔案</a></li>
<li><a href="?mode=import">匯入目前中介檔案到 Another-PIO (見此頁原始碼設定)</a></li>
<li><a href="?mode=convert">將目前 PIO 資料轉換到 Another-PIO</a></li>
</ul>
<hr />
<div id="result">
<?php
$mode = isset($_GET['mode']) ? $_GET['mode'] : '';
switch($mode){
case 'export': doExport(); break;
case 'import': doImport(); break;
case 'convert': doConvert(); break;
}
 
function doExport(){
global $PIOEnv;
if(preg_match('/^(.*):\/\//i', CONNECTION_STRING, $backend)) define('PIO_FROM', $backend[1]);
include('./lib/pio/pio.'.PIO_FROM.'.php');
$pio1 = 'PIO'.PIO_FROM; $PIO = new $pio1(CONNECTION_STRING, $PIOEnv);
 
$gp = gzopen('piodata.log.gz', 'w9');
gzwrite($gp, $PIO->dbExport());
gzclose($gp);
echo '<a href="piodata.log.gz">下載 piodata.log.gz 中介檔案</a>';
}
 
function doImport(){
global $PIOEnv;
if(preg_match('/^(.*):\/\//i', PIO_ANOTHER_CONNSTR, $backend)) define('PIO_FROM', $backend[1]);
include('./lib/pio/pio.'.PIO_FROM.'.php');
$pio1 = 'PIO'.PIO_FROM; $PIO = new $pio1(PIO_ANOTHER_CONNSTR, $PIOEnv);
 
if(!file_exists('piodata.log.gz')){ echo '檔案不存在,請先放置在相同目錄。'; return; }
$data = '';
$gp = gzopen('piodata.log.gz', 'r');
while(!gzeof($gp)) $data .= gzread($gp, 4096);
gzclose($gp);
echo $PIO->dbImport($data) ? '匯入成功' : '匯入失敗';
}
 
function doConvert(){
global $PIOEnv;
if(preg_match('/^(.*):\/\//i', CONNECTION_STRING, $backend)) define('PIO_FROM', $backend[1]);
if(preg_match('/^(.*):\/\//i', PIO_ANOTHER_CONNSTR, $backend)) define('PIO_TO', $backend[1]);
 
include('./lib/pio/pio.'.PIO_FROM.'.php');
include('./lib/pio/pio.'.PIO_TO.'.php');
 
$pio1 = 'PIO'.PIO_FROM; $pio2 = 'PIO'.PIO_TO;
$PIOa = new $pio1(CONNECTION_STRING, $PIOEnv); $PIOb = new $pio2(PIO_ANOTHER_CONNSTR, $PIOEnv);
echo $PIOb->dbImport($PIOa->dbExport()) ? '轉換成功' : '轉換失敗'; // PIOa -> PIOb
}
?>
</div>
</div>
 
</body>
</html>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/Utilities/rebuildtree.php
@@ -0,0 +1,46 @@
<?php
/**
* Pixmicat! PIO 公用程式 - 重建 tree 樹狀結構檔
*
* 本公用程式可嘗試修復毀損的樹狀結構檔。原理是 PIO Log 來源已有內建一些相關樹狀結構供恢復用,
* 此程式可以將這些資訊統整後再輸出,即成為樹狀結構檔。
*
* 注意:若原文章儲存檔已有部分毀損,恕無法正確重建資訊。另原文章 sage 效果亦無法重現,一切按時間排序。
*
* @package PMCUtility
* @version $Id$
* @date $Date$
*/
include('./config.php');
if(preg_match('/^log:\/\/(.*)\:(.*)\/$/i', CONNECTION_STRING, $linkinfos)){
$logfile = './'.$linkinfos[1]; // 投稿文字記錄檔檔名
$treefile = './'.$linkinfos[2]; // 樹狀結構記錄檔檔名
}else{
exit('PIO Connection String Error! ("log://" Expected).');
}
 
$tree = array(); // 樹狀結構陣列
$treeline = ''; // 樹狀結構資料
$f = file($logfile);
$f_cnt = count($f);
for($i = 0; $i < $f_cnt; $i++){
$line = explode(',', $f[$i]);
if($line[1]==0){ // 首篇
if(!isset($tree[$line[0]])) $tree[$line[0]] = array($line[0]); // 僅自身一篇
else array_unshift($tree[$line[0]], $line[0]);
continue;
}
if(!isset($tree[$line[1]])) $tree[$line[1]] = array();
array_unshift($tree[$line[1]], $line[0]);
}
 
foreach($tree as $t){ $treeline .= implode(',', $t)."\r\n"; } // 自陣列整理成文字檔形式
$fp = fopen($treefile.'.new', 'w');
stream_set_write_buffer($fp, 0);
fwrite($fp, $treeline);
fclose($fp);
unset($fp);
@chmod($treefile.'.new', 0666);
 
echo '重建完成,檔案名稱為 "'.$treefile.'.new",請自行更名為 "'.$treefile.'"。以下是預覽:<hr/><pre>'.$treeline.'</pre>';
?>
New file
/release/PIO-v4/config.php
@@ -0,0 +1,134 @@
<?php
/*
Pixmicat! : 圖咪貓貼圖版程式
http://pixmicat.openfoundry.org/
版權所有 © 2005-2007 Pixmicat! Development Team
 
版權聲明:
此程式是基於レッツPHP!<http://php.s3.to/>的gazou.php、
双葉ちゃん<http://www.2chan.net>的futaba.php所改寫之衍生著作程式,屬於自由軟體,
以The Clarified Artistic License作為發佈授權條款。
您可以遵照The Clarified Artistic License來自由使用、散播、修改或製成衍生著作。
更詳細的條款及定義請參考隨附"LICENSE"條款副本。
 
發佈這一程式的目的是希望它有用,但沒有任何擔保,甚至沒有適合特定目的而隱含的擔保。
關於此程式相關的問題請不要詢問レッツPHP!及双葉ちゃん。
 
如果您沒有隨著程式收到一份The Clarified Artistic License副本,
請瀏覽http://pixmicat.openfoundry.org/license/以取得一份。
*/
/*---- Part 1:程式基本設定 ----*/
// 伺服器常態設定
define("PHP_SELF", basename($_SERVER['PHP_SELF'])); // 此程式名 (此欄不需修改)
define("TIME_ZONE", '+8'); // 時區設定 (GMT時區,參照 http://wwp.greenwichmeantime.com/ )
define("PIXMICAT_LANGUAGE", 'zh_TW'); // 語系語定
define("HTTP_UPLOAD_DIFF", 50); // HTTP上傳所有位元組與實際位元組之允許誤差值
ini_set("memory_limit", '32M'); // PHP運行的最大記憶體使用量 (php內定8M / 建議32M)
 
// FileIO設定
define("FILEIO_BACKEND", 'normal'); // FileIO後端指定
define("FILEIO_INDEXLOG", 'fileioindex.dat'); // FileIO索引記錄檔 (儲存在本機端)
define("FILEIO_PARAMETER", ''); // FileIO參數 (本機端儲存)
//define("FILEIO_PARAMETER", serialize(array('ftp.example.com', 21, 'demo', 'demo', 'PASV', '/pwd/', 'http://www.example.com/~demo/pwd/'))); // FileIO參數 (FTP)
//define("FILEIO_PARAMETER", serialize(array('00000000000000000000000000000000'))); // FileIO參數 (ImageShack)
//define("FILEIO_PARAMETER", serialize(array('http://www.example.com/~demo/satellite.cgi', true, '12345678', 'http://www.example.com/~demo/src/'))); // FileIO參數 (Satellite)
 
// PIO資料來源設定
define("CONNECTION_STRING", 'log://img.log:tree.log/'); // PIO 連線字串 (Log)
//define("CONNECTION_STRING", 'mysql://pixmicat:pass@localhost/test/imglog/'); // PIO 連線字串 (MySQL)
//define("CONNECTION_STRING", 'sqlite://pixmicat.db/imglog/'); // PIO 連線字串 (SQLite)
//define("CONNECTION_STRING", 'pgsql://pixmicat:1234@localhost/pixmicat_use/imglog/'); // PIO 連線字串 (PostgreSQL)
 
/*---- Part 2:板面各項細部功能設定 ----*/
define("IMG_DIR", 'src/'); // 圖片存放目錄
define("THUMB_DIR", 'thumb/'); // 預覽圖存放目錄
define("PHP_SELF2", 'index.htm'); // 入口檔名
define("PHP_EXT", '.htm'); // 第一頁以後生成檔案之副檔名
define("TITLE", 'Pixmicat!-PIO'); // 網頁標題
define("HOME", '../'); // 回首頁的連結
define("TOP_LINKS", ''); // 頁面右上方的額外連結,請直接以[<a href="網址" rel="_blank">名稱</a>]格式鍵入,如果不需要開新視窗可刪除rel一段
define("ADMIN_PASS", 'futaba'); // 管理員密碼
define("IDSEED", 'id種'); // 生成ID之隨機種子
 
// 管理員キャップ(Cap)設定 (啟用條件:開啟使用;名稱輸入識別名稱,E-mail輸入#啟動密碼)
define("CAP_ENABLE", 1); // 是否使用管理員キャップ (使用:1 不使用:0)
define("CAP_NAME", 'futaba'); // 管理員キャップ識別名稱
define("CAP_PASS", 'futaba'); // 管理員キャップ啟動密碼 (在E-mail一欄輸入#啟動密碼)
define("CAP_SUFFIX", ' ★'); // 管理員キャップ後綴字元 (請務必有★以便程式防止偽造,或可自行修改程式的防偽造部份)
define("CAP_ISHTML", 1); // 管理員キャップ啟動後內文是否接受HTML標籤 (是:1 否:0)
 
// 功能切換
define("USE_THUMB", 1); // 使用預覽圖機能 (使用:1 不使用:0)
define("USE_FLOATFORM", 1); // 新增文章表單使用自動隱藏 (是:1 否:0)
define("USE_SEARCH", 1); // 開放搜尋功能 (是:1 否:0)
define("USE_UPSERIES", 1); // 是否啟用連貼機能 [開主題後自動指向到主題下以方便連貼] (是:1 否:0)
define("RESIMG", 1); // 回應附加圖檔機能 (開啟:1 關閉:0)
define("AUTO_LINK", 1); // 討論串文字內的URL是否自動作成超連結 (是:1 否:0)
define("KILL_INCOMPLETE_UPLOAD", 1); // 自動刪除上傳不完整附加圖檔 (是:1 否:0)
define("ALLOW_NONAME", 1); // 是否接受匿名發送 (強制砍名:2 是:1 否:0)
define("DISP_ID", 2); // 顯示ID (強制顯示:2 選擇性顯示:1 永遠不顯示:0)
define("CLEAR_SAGE", 0); // 使用不推文模式時清除E-mail中的「sage」關鍵字 (是:1 否:0)
define("USE_QUOTESYSTEM", 1); // 是否打開引用瀏覽系統 [自動轉換>>No.xxx文字成連結並導引] (是:1 否:0)
define("SHOW_IMGWH", 1); // 是否顯示附加圖檔之原檔長寬尺寸 (是:1 否:0)
define("USE_CATEGORY", 1); // 是否開啟使用類別標籤分類功能 (是:1 否:0)
 
// 模組載入
$ModuleList = array();
//$ModuleList[] = 'mod_rss';
//$ModuleList[] = 'mod_catalog';
 
// 封鎖設定
define("BAN_CHECK", 0); // 綜合性封鎖檢查功能 (關閉:0, 開啟:1)
$BANPATTERN = array(); // IP/Hostname封鎖黑名單
$DNSBLservers = array(0, 'sbl-xbl.spamhaus.org', 'list.dsbl.org', 'bl.blbl.org', 'bl.spamcop.net'); // DNSBL伺服器列表 (首項:使用伺服器個數)
$DNSBLWHlist = array(); // DNSBL白名單 (請輸入IP位置)
$BAD_STRING = array("dummy_string","dummy_string2"); // 限制出現之文字
$BAD_FILEMD5 = array("dummy","dummy2"); // 限制上傳附加圖檔之MD5檢查碼
 
// 附加圖檔限制
define("MAX_KB", 2000); // 附加圖檔上傳容量限制KB (php內定為最高2MB)
define("STORAGE_LIMIT", 1); // 附加圖檔總容量限制功能 (啟動:1 關閉:0)
define("STORAGE_MAX", 30000); // 附加圖檔總容量限制上限大小 (單位:KB)
define("ALLOW_UPLOAD_EXT", 'GIF|JPG|PNG|BMP|SWF'); // 接受之附加圖檔副檔名 (送出前表單檢查用,用 | 分隔)
 
// 連續投稿時間限制
define("RENZOKU", 10); // 連續投稿間隔秒數
define("RENZOKU2", 10); // 連續貼圖間隔秒數
 
// 預覽圖片相關限制
define("MAX_W", 250); // 討論串本文預覽圖片寬度 (超過則自動縮小)
define("MAX_H", 250); // 討論串本文預覽圖片高度
define("MAX_RW", 125); // 討論串回應預覽圖片寬度 (超過則自動縮小)
define("MAX_RH", 125); // 討論串回應預覽圖片高度
define("THUMB_Q", 75); // 預覽圖片之品質 (1-100, 建議預設75,越高品質越好但檔案也越大)
 
// 外觀設定
$ADDITION_INFO = ""; // 可在表單下顯示額外文字
define("TEMPLATE_FILE", 'inc_pixmicat.tpl'); // 樣板位置
define("LOG_MAX", 500); // 記錄檔保留之最大資料筆數
define("PAGE_DEF", 15); // 一頁顯示幾篇討論串
define("ADMIN_PAGE_DEF", 20); // 管理模式下,一頁顯示幾筆資料
define("RE_DEF", 10); // 一篇討論串最多顯示之回應筆數 (超過則自動隱藏,全部隱藏:0)
define("RE_PAGE_DEF", 30); // 回應模式一頁顯示幾筆回應內容 (分頁用,全部顯示:0)
define("MAX_RES", 30); // 回應筆數超過多少則不自動推文 (關閉:0)
define("MAX_AGE_TIME", 0); // 討論串可接受推文的時間範圍 (單位:小時,討論串存在超過此時間則回應皆不再自動推文 關閉:0)
define("COMM_MAX", 2000); // 內文接受Bytes數 (注意:中文字為2Bytes)
define("BR_CHECK", 0); // 文字換行行數上限 (不限:0)
define("STATIC_HTML_UNTIL", -1); // 更新文章時自動生成的靜態網頁至第幾頁止 (全部生成:-1 僅入口頁:0)
define("GZIP_COMPRESS_LEVEL", 3); // PHP動態輸出頁面使用Gzip壓縮層級 (關閉:0 啟動:1~9,推薦值:3)
define("DEFAULT_NOTITLE", '無標題'); // 預設文章標題
define("DEFAULT_NONAME", '無名氏'); // 預設文章名稱
define("DEFAULT_NOCOMMENT", '無內文'); // 預設文章內文
 
/*---- Part 3:Anti-SPAM 防止垃圾訊息機器人發文 ----*/
/* 欄位陷阱 (Field Trap)
介紹:
機器人會針對常見的欄位名稱送出垃圾資料,將這些常見的欄位製成陷阱,
另設名稱怪異的欄位為正確欄位,以避免直接的攻擊。
防止機器人學習的可能,請隔一段時間修改底下欄位值,建議英數大小寫隨機6~10個 (避免特殊符號、第一位不能為數字)。
*/
define("FT_NAME", 'bvUFbdrIC'); // 名稱欄位
define("FT_EMAIL", 'ObHGyhdTR'); // E-mail欄位
define("FT_SUBJECT", 'SJBgiFbhj'); // 標題欄位
define("FT_COMMENT", 'pOBvrtyJK'); // 內文欄位
?>
New file
/release/PIO-v4/mainstyle.css
@@ -0,0 +1,61 @@
@media all{ /* 通用設定 */
html { background: #FFE; color: #800000; } /* 基本網頁背景樣式 */
a:link { color: #00E; } /* 正常連結樣式 */
a:hover { color: #D00; } /* hover時連結樣式 */
a:visited { color: #00E; } /* 已拜訪連結樣式 */
a.qlink { text-decoration: none; } /* 引用用連結樣式 */
small { font-size: 0.8em; } /* 小字樣式(eg.[以預覽圖顯示]) */
hr { clear: left; } /* 分隔線樣式 */
img { border: 0; } /* 圖片顯示樣式 */
 
#toplink { text-align: right; } /* 頁首連結條樣式 */
h1 { color: #800000; text-align: center; margin: 0 auto; } /* 網頁主標題樣式 */
hr.top { width: 90%; height: 1px; } /* 主標題下分隔線樣式 */
 
.Form_bg { background: #EA8; } /* 送出表單左方欄位之底色 */
.hide_btn { float: right; width: 4em; height: 1.25em; overflow: hidden; text-align: center; background: #F0E0D6; } /* 表單收縮按鈕樣式 */
.show { color: #00E; }
.hide { display: none; }
#postinfo { font-size: 0.8em; } /* 上傳說明樣式 */
form { padding: 0; margin: 0; } /* 修正表單標籤造成的排版問題 */
 
.threadpost { } /* 討論串首篇樣式 */
.reply { display: table; margin: 0.5ex 1em 0 1em; background: #F0E0D6; } /* 討論串回應樣式 */
.reply_hl { background: #F0D5B7; } /* 討論串回應背景標亮樣式 */
.name { color: #117743; font-weight: bold; } /* 文章張貼者名稱樣式 */
.admin_cap { color: #0000FF; } /* 管理員キャップ樣式設定 */
.img { float: left; margin: 1ex 2ex; } /* 討論串圖片顯示樣式 */
.title { color: #CC1105; font-size: 1.125em; font-weight: bold; } /* 討論串標題樣式 */
.nor { font-weight: normal; } /* Trip取消粗體用 */
.quote { margin: 1em 2em; } /* 討論串內文縮排樣式 */
.resquote { color: #789922; } /* 標註引用回文顏色 */
.category { font-size: 0.8em; color: gray; } /* 類別標籤顯示樣式 */
 
.warn_txt { color: #F00000; font-weight: bold; } /* 討論串狀態警告文字(eg.文章即將被刪除) */
.warn_txt2 { color: #707070; } /* 討論串狀態提示文字(eg.回應幾篇被隱藏) */
#footer { text-align: center; clear: both; } /* 頁尾樣式 */
.bar_reply { background: #E04000; color: #FFF; font-weight: bold; text-align: center; } /* 回應模式樣式標題列 */
.bar_admin { background: #E08000; color: #FFF; font-weight: bold; text-align: center; } /* 管理模式樣式標題列 */
.ListRow1_bg { background: #D6D6F6; } /* 管理模式欄位背景顏色1(輪替出現) */
.ListRow2_bg { background: #F6F6F6; } /* 管理模式欄位背景顏色2(輪替出現) */
}
 
@media screen{ /* 標準顯示(一般顯示器)模式附加規則 */
 
}
 
@media handheld{ /* 手持設備(手機,PDA等)附加規則 */
html { max-width: 320px; margin: auto; font-size: 9px; } /* 320px為多數PDA的顯示寬度 */
input { max-width: 200px; }
textarea { max-width: 200px; height: 40px; }
 
.img { margin: 0.5ex 0.5ex; max-width: 125px; } /* 討論串圖片顯示樣式 */
.reply { clear: left; margin: 0.5ex 0 0 0; } /* 討論串回應樣式 */
.quote { margin: 1ex 1ex; } /* 討論串內文縮排樣式 */
#page_switch { clear: right; } /* 避免頁尾表格交疊 */
}
 
@media print{ /* 列印,預覽列印附加規則 */
html { background: #FFF; color: #000; } /* 網頁改成白底黑字,節省彩色列印成本 */
#postform_main { display: none; } /* 貼文表單在列印模式沒有用處,故不顯示以節省空間 */
}
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes:

Name: svn:mime-type
+ application/octet-stream

/release/PIO-v4/nothumb.gif
New file
/release/PIO-v4/iedivfix.js
@@ -0,0 +1,10 @@
// IE不支援display: table,故用此方法
document.write('<style type="text/css">.reply { display: inline ; zoom: 1; }</style>');
 
// 解決IE顯示回應區塊時排在同一行的問題
function IEdivfix(){
var divs=document.getElementsByTagName('div'),divs_cnt=divs.length;
for(i=0;i<divs_cnt;i++)
if(divs[i].className.substr(0,5)=='reply') divs[i].insertAdjacentHTML('afterEnd','<br />');
}
hookPresetFunction(IEdivfix); // Hook on
New file
/release/PIO-v4/mainscript.js
@@ -0,0 +1 @@
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('8 V=14;8 K=0;8 G=[];8 15=[[2h,24,20],[1R,1I,1F],[1A,2t,4]];8 11=[[29,23,22,1Z,1X,1Q],[1L,1H,1G,1E,1C,1z]];h $g(i){t o.2g(i)}h 2c(){8 N=I(\'1q\'),E=I(\'21\'),x;5((x=$g(\'H\'))){x.k=N}5((x=$g(\'1T\'))){x.k=E}}h 1P(){8 P=I(\'1K\'),d=o,1g=d.A.u;v(8 i=0;i<1g;i++){5(d.A[i].1a){d.A[i].1a.k=P}}}h I(a){8 b,z,w=0,y=0,F;b=\' \'+o.17+\';\';8 c=b.u;2q(w<c){y=b.Z(\';\',w);z=b.10(w+1,y);F=z.Z(\'=\');5(z.10(0,F)==a){t 2i(z.10(F+1,y-w-1))}w=y+1}t\'\'}h 1s(a,b){8 c=12 2f();c.2d(c.2b()+2a*7);o.17=a+\'=\'+1w(b)+\'; 26=\'+c.25()}h c(){8 a,j,J,Y;1p{5(!$g(\'1n\')){t R}a=$g(\'1n\').k;5(!a&&!$g(\'Q\').k){X(W[0]);t 14}5(a){J=0;Y=1m.u;v(j=0;j<Y;j++){5(a.1Y(a.u-3,3).1W()==1m[j]){J=1;1U}}5(!J){X(W[1]);t 14}}D(\'Q\');D(\'H\');D(\'1S\');5(1k.1O){o.A[0].1N.k=a}o.A[0].1M.1J=R}18(e){}5($g(\'H\').k){1s(\'1q\',$g(\'H\').k)}}h 1b(){5(!o.1f){t}8 a,U=o.1f(\'a\');8 b=U.u;v(8 i=0;i<b;i++){a=U[i];5(a.T(\'1e\')){5(a.T(\'1d\')==\'1i\'){a.1c=\'1i\'}5(a.T(\'1d\')==\'1h\'){a.1c=\'1h\'}}}}h 1D(){$g("1j").m=\'\';$g("19").m=\'\';$g("B").m=\'C\';$g("C").m=\'B\'}h 1B(){$g("1j").m=\'1V\';$g("19").m=\'B\';$g("B").m=\'B\';$g("C").m=\'C\'}h 1l(a){1p{$g(\'Q\').1y()}18(e){}$g(\'Q\').k+=\'>>1x.\'+a+"\\r\\n"}h S(a,b){8 c=$g(\'r\'+a);5(c){5(b){c.m=c.m.16(\' 1o\',\'\')}2s{5(K){S(K,R)}K=a;c.m+=\' 1o\'}}}h D(a){8 b=$g(a);8 c=1w(b.k).2r();8 d=/%2p(6[2n]|7[0-1v-f]|2l)[0-1v-f]/;5(c.13(d)!==2j){X(W[2]);1r(b)}}h 1r(a){8 b=a.k,i=0,p=0;8 c=15.u,s;v(i=0;i<c;i++){s=15[i];v(p=0;p<=s[2];p++){b=b.16(12 M(O.L(s[0]+p),\'g\'),O.L(s[1]+p))}}c=11[0].2e;v(i=0;i<c;i++){s=11;b=b.16(12 M(O.L(s[0][i]),\'g\'),O.L(s[1][i]))}a.k=b}h 2k(a){5(1u a==\'h\'){G.2m(a)}}h 1t(){5(V){t}8 i,l=G.u,f;V=R;1b();v(i=0;i<l;i++){f=G[i];5(1u f==\'h\'){f()}}8 a=2o.1e;5(a.Z(\'?28=\')){5(a.13(/#[27]([0-9]+)$/)){S(M.$1)}5(a.13(/#q([0-9]+)$/)){1l(M.$1)}}}1k.2u=1t;',62,155,'|||||if|||var|||||||||function|||value||className||document||||Tbl|return|length|for|xx1|obj|xx2|tmp2|forms|hide|show|check_sakura||xx3|arrPresetFunc|fname|getCookie|ext_allowed|previous_replyhlno|fromCharCode|RegExp||String||fcom|true|replyhl|getAttribute|anchors|preset_sensor|msgs|alert|ext_length|indexOf|substring|arrSakuraTblsp|new|match|false|arrSakuraTbl|replace|cookie|catch|postform_tbl|pwd|fixalllinks|target|rel|href|getElementsByTagName|forms_length|_blank|_top|postform|window|quote|ext|fupfile|reply_hl|try|namec|replace_sakura|setCookie|preset|typeof|9a|escape|No|focus|14446|63486|hideform|12445|showform|12542|85|12541|12540|12449|disabled|pwdc|12293|sendbtn|upfile_path|clipboardData|l2|63213|63306|fsub|femail|break|hide_btn|toUpperCase|63212|substr|63211|82|emailc|63210|63219|12353|toGMTString|expires|rq|res|63216|86400000|getTime|l1|setTime|legnth|Date|getElementById|63223|unescape|null|hookPresetFunction|80|push|ef|location|uf|while|toLowerCase|else|12535|onload'.split('|'),0,{}))
New file
/release/PIO-v4/pixmicat.php
@@ -0,0 +1,1146 @@
<?php
define("PIXMICAT_VER", 'Pixmicat!-PIO 4th.Release (v070617)'); // 版本資訊文字
/*
Pixmicat! : 圖咪貓貼圖版程式
http://pixmicat.openfoundry.org/
版權所有 © 2005-2007 Pixmicat! Development Team
 
版權聲明:
此程式是基於レッツPHP!<http://php.s3.to/>的gazou.php、
双葉ちゃん<http://www.2chan.net>的futaba.php所改寫之衍生著作程式,屬於自由軟體,
以The Clarified Artistic License作為發佈授權條款。
您可以遵照The Clarified Artistic License來自由使用、散播、修改或製成衍生著作。
更詳細的條款及定義請參考隨附"LICENSE"條款副本。
 
發佈這一程式的目的是希望它有用,但沒有任何擔保,甚至沒有適合特定目的而隱含的擔保。
關於此程式相關的問題請不要詢問レッツPHP!及双葉ちゃん。
 
如果您沒有隨著程式收到一份The Clarified Artistic License副本,
請瀏覽http://pixmicat.openfoundry.org/license/以取得一份。
 
最低運行需求:
PHP 4.3.0 / 27 December 2002
GD Version 2.0.28 / 21 July 2004
 
建議運行環境:
PHP 5.1.x 或更高版本並開啟GD和zlib支援
安裝PHP編譯快取套件 (如eAccelerator, XCache, APC) 或其他快取套件 (如memcached) 更佳
 
設置方法:
根目錄的權限請設為777,
首先將pixmicat.php執行過一遍,必要的檔案和資料夾權限皆會自動設定,
自動設定完成後請刪除或註解起來此檔案底部之init(); // ←■■!程式環境初始化(略)一行,
然後再執行一遍pixmicat.php,即完成初始化程序,可以開始使用。
 
細部的設定請打開config.php參考註解修改。
*/
 
include_once('./config.php'); // 引入設定檔
include_once('./lib/lib_language.php'); // 引入語系
include_once('./lib/lib_common.php'); // 引入共通函式檔案
include_once('./lib/lib_fileio.php'); // 引入FileIO
include_once('./lib/lib_pio.php'); // 引入PIO
include_once('./lib/lib_pms.php'); // 引入PMS
include_once('./lib/lib_pte.php'); // 引入PTE外部函式庫
 
$PTE = new PTELibrary(TEMPLATE_FILE); // PTE Library
 
/* 更新記錄檔檔案/輸出討論串 */
function updatelog($resno=0,$page_num=0){
global $PIO, $FileIO, $PTE, $PMS, $language;
 
$page_start = $page_end = 0; // 靜態頁面編號
$inner_for_count = 1; // 內部迴圈執行次數
$kill_sensor = $old_sensor = false; // 預測系統啟動旗標
$arr_kill = $arr_old = array(); // 過舊編號陣列
$pte_vals = array('{$THREADFRONT}'=>'','{$THREADREAR}'=>'','{$SELF}'=>PHP_SELF);
 
if(!$resno){
if($page_num==0){ // remake模式 (PHP動態輸出多頁份)
$threads = $PIO->fetchThreadList(); // 取得全討論串列表
$threads_count = count($threads);
$inner_for_count = $threads_count > PAGE_DEF ? PAGE_DEF : $threads_count;
$page_end = ceil($threads_count / PAGE_DEF) - 1; // 頁面編號最後值
}else{ // 討論串分頁模式 (PHP動態輸出一頁份)
$threads_count = $PIO->threadCount(); // 討論串個數
if($page_num < 0 || ($page_num * PAGE_DEF) >= $threads_count) error(_T('page_not_found')); // $page_num超過範圍
$page_start = $page_end = $page_num; // 設定靜態頁面編號
$threads = $PIO->fetchThreadList($page_num * PAGE_DEF, PAGE_DEF); // 取出分頁後的討論串首篇列表
$inner_for_count = count($threads); // 討論串個數就是迴圈次數
}
}else{ if(!$PIO->isThread($resno)){ error(_T('thread_not_found')); } }
 
// 預測過舊文章和將被刪除檔案
if($PIO->postCount() >= LOG_MAX * 0.95){
$old_sensor = true; // 標記打開
$arr_old = array_flip($PIO->fetchPostList()); // 過舊文章陣列
}
$tmp_total_size = total_size(); // 目前附加圖檔使用量
$tmp_STORAGE_MAX = STORAGE_MAX * (($tmp_total_size >= STORAGE_MAX) ? 1 : 0.95); // 預估上限值
if(STORAGE_LIMIT && ($tmp_total_size >= $tmp_STORAGE_MAX)){
$kill_sensor = true; // 標記打開
$arr_kill = $PIO->delOldAttachments($tmp_total_size, $tmp_STORAGE_MAX); // 過舊附檔陣列
}
 
// 生成靜態頁面一頁份內容
for($page = $page_start; $page <= $page_end; $page++){
$dat = '';
head($dat);
form($dat, $resno);
$pte_vals['{$THREADS}'] = '';
$PMS->useModuleMethods('ThreadFront', array(&$pte_vals['{$THREADFRONT}'])); // "ThreadFront" Hook Point
// 輸出討論串內容
for($i = 0; $i < $inner_for_count; $i++){
// 取出討論串編號
if($resno) $tID = $resno; // 單討論串輸出 (回應模式)
elseif($page_start==$page_end) $tID = $threads[$i]; // 一頁內容 (一般模式)
else{ // 多頁內容 (remake模式)
if(($page * PAGE_DEF + $i) >= $threads_count) break; // 超出索引代表已全部完成
$tID = $threads[$page * PAGE_DEF + $i];
}
// 取出討論串結構及回應個數等資訊
$tree = $PIO->fetchPostList($tID); // 整個討論串樹狀結構
$tree_count = count($tree) - 1; // 討論串回應個數
// 計算回應分頁範圍
$RES_start = $RES_amount = 0;
$hiddenReply = 0; // 被隱藏回應數
if($resno){ // 回應模式
if($tree_count && RE_PAGE_DEF){ // 有回應且RE_PAGE_DEF > 0才做分頁動作
if($page_num==='all'){ // show all
$page_num = 0;
$RES_start = 1; $RES_amount = $tree_count;
}else{
if($page_num==='RE_PAGE_MAX') $page_num = ceil($tree_count / RE_PAGE_DEF) - 1; // 特殊值:最末頁
if($page_num < 0) $page_num = 0; // 負數
if($page_num * RE_PAGE_DEF >= $tree_count) error(_T('page_not_found'));
$RES_start = $page_num * RE_PAGE_DEF + 1; // 開始
$RES_amount = RE_PAGE_DEF; // 取幾個
}
}elseif($page_num > 0) error(_T('page_not_found')); // 沒有回應的情況只允許page_num = 0 或負數
else{ $RES_start = 1; $RES_amount = $tree_count; } // 輸出全部回應
}else{ // 一般模式下的回應隱藏
$RES_start = $tree_count - RE_DEF + 1; if($RES_start < 1) $RES_start = 1; // 開始
$RES_amount = RE_DEF; // 取幾個
$hiddenReply = $RES_start - 1; // 被隱藏回應數
}
// $RES_start, $RES_amount 拿去算新討論串結構 (分頁後, 部分回應隱藏)
$tree_cut = array_slice($tree, $RES_start, $RES_amount); array_unshift($tree_cut, $tID); // 取出特定範圍回應
$posts = $PIO->fetchPosts($tree_cut); // 取得文章架構內容
$pte_vals['{$THREADS}'] .= arrangeThread($PTE, $tree, $tree_cut, $posts, $hiddenReply, $resno, $arr_kill, $arr_old, $kill_sensor, $old_sensor); // 交給這個函式去搞討論串印出
}
$PMS->useModuleMethods('ThreadRear', array(&$pte_vals['{$THREADREAR}'])); // "ThreadRear" Hook Point
$pte_vals += array('{$DEL_HEAD_TEXT}' => '<input type="hidden" name="mode" value="usrdel" />'._T('del_head'),
'{$DEL_IMG_ONLY_FIELD}' => '<input type="checkbox" name="onlyimgdel" id="onlyimgdel" value="on" />',
'{$DEL_IMG_ONLY_TEXT}' => _T('del_img_only'),
'{$DEL_PASS_TEXT}' => _T('del_pass'),
'{$DEL_PASS_FIELD}' => '<input type="password" name="pwd" size="8" maxlength="8" value="" />',
'{$DEL_SUBMIT_BTN}' => '<input type="submit" value="'._T('del_btn').'" />');
 
$pte_vals['{$PAGENAV}'] = '<div id="page_switch">';
 
// 換頁判斷
$prev = ($resno ? $page_num : $page) - 1;
$next = ($resno ? $page_num : $page) + 1;
if($resno){ // 回應分頁
if(RE_PAGE_DEF > 0){ // 回應分頁開啟
$AllRes = isset($_GET['page_num']) && $_GET['page_num']=='all'; // 是否使用 ALL 全部輸出
$pte_vals['{$PAGENAV}'] .= '<table border="1"><tr>';
if($prev >= 0) $pte_vals['{$PAGENAV}'] .= '<td><form action="'.PHP_SELF.'?res='.$resno.'&amp;page_num='.$prev.'" method="post"><div><input type="submit" value="'._T('prev_page').'" /></div></form></td>';
else $pte_vals['{$PAGENAV}'] .= '<td style="white-space: nowrap;">'._T('first_page').'</td>';
$pte_vals['{$PAGENAV}'] .= "<td>";
if($tree_count==0) $pte_vals['{$PAGENAV}'] .= '[<b>0</b>] '; // 無回應
else{
for($i = 0; $i < $tree_count ; $i += RE_PAGE_DEF){
if(!$AllRes && $page_num==$i/RE_PAGE_DEF) $pte_vals['{$PAGENAV}'] .= '[<b>'.$i/RE_PAGE_DEF.'</b>] ';
else $pte_vals['{$PAGENAV}'] .= '[<a href="'.PHP_SELF.'?res='.$resno.'&amp;page_num='.$i/RE_PAGE_DEF.'">'.$i/RE_PAGE_DEF.'</a>] ';
}
$pte_vals['{$PAGENAV}'] .= $AllRes ? '[<b>'._T('all_pages').'</b>] ' : ($tree_count > RE_PAGE_DEF ? '[<a href="'.PHP_SELF.'?res='.$resno.'&amp;page_num=all">'._T('all_pages').'</a>] ' : '');
}
$pte_vals['{$PAGENAV}'] .= '</td>';
if(!$AllRes && $tree_count > $next * RE_PAGE_DEF) $pte_vals['{$PAGENAV}'] .= '<td><form action="'.PHP_SELF.'?res='.$resno.'&amp;page_num='.$next.'" method="post"><div><input type="submit" value="'._T('next_page').'" /></div></form></td>';
else $pte_vals['{$PAGENAV}'] .= '<td style="white-space: nowrap;">'._T('last_page').'</td>';
$pte_vals['{$PAGENAV}'] .= '</tr></table>'."\n";
}
}else{ // 一般分頁
$pte_vals['{$PAGENAV}'] .= '<table border="1"><tr>';
if($prev >= 0){
if($prev==0) $pte_vals['{$PAGENAV}'] .= '<td><form action="'.PHP_SELF2.'" method="get">';
else{
if((STATIC_HTML_UNTIL != -1) && ($prev > STATIC_HTML_UNTIL)) $pte_vals['{$PAGENAV}'] .= '<td><form action="'.PHP_SELF.'?page_num='.$prev.'" method="post">';
else $pte_vals['{$PAGENAV}'] .= '<td><form action="'.$prev.PHP_EXT.'" method="get">';
}
$pte_vals['{$PAGENAV}'] .= '<div><input type="submit" value="'._T('prev_page').'" /></div></form></td>';
}else $pte_vals['{$PAGENAV}'] .= '<td style="white-space: nowrap;">'._T('first_page').'</td>';
$pte_vals['{$PAGENAV}'] .= '<td>';
for($i = 0; $i < $threads_count ; $i += PAGE_DEF){
if($page==$i/PAGE_DEF) $pte_vals['{$PAGENAV}'] .= "[<b>".$i/PAGE_DEF."</b>] ";
else{
if($i==0) $pte_vals['{$PAGENAV}'] .= '[<a href="'.PHP_SELF2.'?">0</a>] ';
elseif(STATIC_HTML_UNTIL != -1 && $i/PAGE_DEF > STATIC_HTML_UNTIL) $pte_vals['{$PAGENAV}'] .= '[<a href="'.PHP_SELF.'?page_num='.$i/PAGE_DEF.'">'.$i/PAGE_DEF.'</a>] ';
else $pte_vals['{$PAGENAV}'] .= '[<a href="'.$i/PAGE_DEF.PHP_EXT.'?">'.$i/PAGE_DEF.'</a>] ';
}
}
$pte_vals['{$PAGENAV}'] .= '</td>';
if($threads_count > $next * PAGE_DEF){
if((STATIC_HTML_UNTIL != -1) && ($next > STATIC_HTML_UNTIL)) $pte_vals['{$PAGENAV}'] .= '<td><form action="'.PHP_SELF.'?page_num='.$next.'" method="post">';
else $pte_vals['{$PAGENAV}'] .= '<td><form action="'.$next.PHP_EXT.'" method="get">';
$pte_vals['{$PAGENAV}'] .= '<div><input type="submit" value="'._T('next_page').'" /></div></form></td>';
}else $pte_vals['{$PAGENAV}'] .= '<td style="white-space: nowrap;">'._T('last_page').'</td>';
$pte_vals['{$PAGENAV}'] .= '</tr></table>'."\n";
}
$pte_vals['{$PAGENAV}'] .= '<br style="clear: left;" />
</div>';
$dat .= $PTE->ParseBlock('MAIN',$pte_vals);
foot($dat);
 
// 存檔 / 輸出
if(!$page_num){ // 非使用php輸出方式,而是靜態生成
if($resno){ echo $dat; break; } // 回應分頁第0頁
if($page==0) $logfilename = PHP_SELF2;
else $logfilename = $page.PHP_EXT;
$fp = fopen($logfilename, 'w');
stream_set_write_buffer($fp, 0);
fwrite($fp, $dat);
fclose($fp);
@chmod($logfilename, 0666);
}else{ // php輸出
print $dat;
break; // 只執行一次迴圈,即印出一頁內容
}
if((STATIC_HTML_UNTIL != -1) && STATIC_HTML_UNTIL==$page) break; // 生成靜態頁面數目限制
}
}
 
/* 輸出討論串架構 */
function arrangeThread($PTE, $tree, $tree_cut, $posts, $hiddenReply, $resno=0, $arr_kill, $arr_old, $kill_sensor, $old_sensor, $showquotelink=true){
global $PIO, $FileIO, $PMS, $language;
 
$thdat = ''; // 討論串輸出碼
$posts_count = count($posts); // 迴圈次數
// $i = 0 (首篇), $i = 1~n (回應)
for($i = 0; $i < $posts_count; $i++){
$imgsrc = $img_thumb = $imgwh_bar = '';
$IMG_BAR = $REPLYBTN = $QUOTEBTN = $WARN_OLD = $WARN_BEKILL = $WARN_ENDREPLY = $WARN_HIDEPOST = '';
extract($posts[$i]); // 取出討論串文章內容設定變數
 
// 設定欄位值
$name = str_replace('&'._T('trip_pre'), '&amp;'._T('trip_pre'), $name); // 避免 &#xxxx; 後面被視為 Trip 留下 & 造成解析錯誤
if(CLEAR_SAGE) $email = preg_replace('/^sage( *)/i', '', trim($email)); // 清除E-mail中的「sage」關鍵字
if(ALLOW_NONAME==2){ // 強制砍名
$name = preg_match('/(\\'._T('trip_pre').'.{10})/', $name, $matches) ? '<span class="nor">'.$matches[1].'</span>' : '';
if($email) $now = "<a href=\"mailto:$email\">$now</a>";
}else{
$name = preg_replace('/(\\'._T('trip_pre').'.{10})/', '<span class="nor">$1</span>', $name); // Trip取消粗體
if($email) $name = "<a href=\"mailto:$email\">$name</a>";
}
if(AUTO_LINK) $com = auto_link($com);
$com = quoteLight($com);
if(USE_QUOTESYSTEM && $i){ // 啟用引用瀏覽系統
if(preg_match_all('/((?:&gt;|>)+)(?:No\.)?(\d+)/i', $com, $matches, PREG_SET_ORDER)){ // 找尋>>No.xxx
foreach($matches as $val){
if($r_page=array_search($val[2], $tree)){ // $r_page !==0 (首篇) 就算找到
// 在顯示區間內,輸出錨點即可
// $tree_cut 目前頁面顯示文章+回應
if(array_search($val[2], $tree_cut)) $com = str_replace($val[0], '<a class="qlink" href="#r'.$val[2].'" onclick="replyhl('.$val[2].');">'.$val[0].'</a>', $com);
// 非顯示區間,輸出頁面導引及錨點
else $com = str_replace($val[0], '<a class="qlink" href="'.PHP_SELF.'?res='.$tree[0].(RE_PAGE_DEF ? '&amp;page_num='.floor(($r_page - 1) / RE_PAGE_DEF) : '').'#r'.$val[2].'">'.$val[0].'</a>', $com);
}
}
}
}
 
// 設定附加圖檔顯示
if($ext && $FileIO->imageExists($tim.$ext)){
$imageURL = $FileIO->getImageURL($tim.$ext); // image URL
$thumbURL = $FileIO->getImageURL($tim.'s.jpg'); // thumb URL
 
$imgsrc = '<a href="'.$imageURL.'" rel="_blank"><img src="nothumb.gif" class="img" alt="'.$imgsize.'" title="'.$imgsize.'" /></a>'; // 預設顯示圖樣式 (無預覽圖時)
if($tw && $th){
if($FileIO->imageExists($tim.'s.jpg')){ // 有預覽圖
$img_thumb = '<small>'._T('img_sample').'</small>';
$imgsrc = '<a href="'.$imageURL.'" rel="_blank"><img src="'.$thumbURL.'" style="width: '.$tw.'px; height: '.$th.'px;" class="img" alt="'.$imgsize.'" title="'.$imgsize.'" /></a>';
}elseif($ext=='.swf') $imgsrc = ''; // swf檔案不需預覽圖
}
if(SHOW_IMGWH) $imgwh_bar = ', '.$imgw.'x'.$imgh; // 顯示附加圖檔之原檔長寬尺寸
$IMG_BAR = _T('img_filename').'<a href="'.$imageURL.'" rel="_blank">'.$tim.$ext.'</a>-('.$imgsize.$imgwh_bar.') '.$img_thumb;
}
 
// 設定回應 / 引用連結
if($resno){ // 回應模式
if($showquotelink) $QUOTEBTN = '<a href="javascript:quote('.$no.');" class="qlink">No.'.$no.'</a>';
else $QUOTEBTN = '<a href="#" class="qlink">No.'.$no.'</a>';
}else{
if(!$i) $REPLYBTN = '[<a href="'.PHP_SELF.'?res='.$no.'">'._T('reply_btn').'</a>]'; // 首篇
$QUOTEBTN = '<a href="'.PHP_SELF.'?res='.$tree[0].'#q'.$no.'" class="qlink">No.'.$no.'</a>';
}
 
// 設定討論串屬性
if(STORAGE_LIMIT && $kill_sensor) if(isset($arr_kill[$no])) $WARN_BEKILL = '<span class="warn_txt">'._T('warn_sizelimit').'</span><br />'."\n"; // 預測刪除過大檔
if(!$i){ // 首篇 Only
if($old_sensor) if($arr_old[$no] + 1 >= LOG_MAX * 0.95) $WARN_OLD = '<span class="warn_txt">'._T('warn_oldthread').'</span><br />'."\n"; // 快要被刪除的提示
if($PIO->getPostStatus($status, 'TS')) $WARN_ENDREPLY = '<span class="warn_txt">'._T('warn_locked').'</span><br />'."\n"; // 被標記為禁止回應
if($hiddenReply) $WARN_HIDEPOST = '<span class="warn_txt2">'._T('notice_omitted',$hiddenReply).'</span><br />'."\n"; // 有隱藏的回應
}
// 對類別標籤作自動連結
if(USE_CATEGORY){
$ary_category = explode(',', str_replace('&#44;', ',', $category)); $ary_category = array_map('trim', $ary_category);
$ary_category_count = count($ary_category);
$ary_category2 = array();
for($p = 0; $p < $ary_category_count; $p++){
if($c = $ary_category[$p]) $ary_category2[] = '<a href="'.PHP_SELF.'?mode=category&amp;c='.urlencode($c).'">'.$c.'</a>';
}
$category = implode(', ', $ary_category2);
}else $category = '';
 
// 最終輸出處
if($i){ // 回應
$arrLabels = array('{$NO}'=>$no, '{$SUB}'=>$sub, '{$NAME}'=>$name, '{$NOW}'=>$now, '{$COM}'=>$com, '{$CATEGORY}'=>$category, '{$QUOTEBTN}'=>$QUOTEBTN, '{$IMG_BAR}'=>$IMG_BAR, '{$IMG_SRC}'=>$imgsrc, '{$WARN_BEKILL}'=>$WARN_BEKILL, '{$QUOTEBTN}'=>$QUOTEBTN, '{$NAME_TEXT}'=>_T('post_name'), '{$CATEGORY_TEXT}'=>_T('post_category'));
$PMS->useModuleMethods('ThreadReply', array(&$arrLabels, $posts[$i], $resno)); // "ThreadReply" Hook Point
$thdat .= $PTE->ParseBlock('REPLY',$arrLabels);
}else{ // 首篇
$arrLabels = array('{$NO}'=>$no, '{$SUB}'=>$sub, '{$NAME}'=>$name, '{$NOW}'=>$now, '{$COM}'=>$com, '{$CATEGORY}'=>$category, '{$QUOTEBTN}'=>$QUOTEBTN, '{$REPLYBTN}'=>$REPLYBTN, '{$IMG_BAR}'=>$IMG_BAR, '{$IMG_SRC}'=>$imgsrc, '{$WARN_OLD}'=>$WARN_OLD, '{$WARN_BEKILL}'=>$WARN_BEKILL, '{$WARN_ENDREPLY}'=>$WARN_ENDREPLY, '{$WARN_HIDEPOST}'=>$WARN_HIDEPOST, '{$NAME_TEXT}'=>_T('post_name'), '{$CATEGORY_TEXT}'=>_T('post_category'));
$PMS->useModuleMethods('ThreadPost', array(&$arrLabels, $posts[$i], $resno)); // "ThreadPost" Hook Point
$thdat .= $PTE->ParseBlock('THREAD',$arrLabels);
}
}
$thdat .= $PTE->ParseBlock('SEPARATE',array());
return $thdat;
}
 
/* 寫入記錄檔 */
function regist(){
global $path, $PIO, $FileIO, $PMS, $language, $BAD_STRING, $BAD_FILEMD5, $BAD_IPADDR;
$dest = ''; $mes = ''; $up_incomplete = 0; $is_admin = false;
 
if($_SERVER['REQUEST_METHOD'] != 'POST') error(_T('regist_notpost')); // 非正規POST方式
 
$name = isset($_POST[FT_NAME]) ? $_POST[FT_NAME] : '';
$email = isset($_POST[FT_EMAIL]) ? $_POST[FT_EMAIL] : '';
$sub = isset($_POST[FT_SUBJECT]) ? $_POST[FT_SUBJECT] : '';
$com = isset($_POST[FT_COMMENT]) ? $_POST[FT_COMMENT] : '';
$pwd = isset($_POST['pwd']) ? $_POST['pwd'] : '';
$category = isset($_POST['category']) ? $_POST['category'] : '';
$resto = isset($_POST['resto']) ? $_POST['resto'] : 0;
$upfile = isset($_FILES['upfile']['tmp_name']) ? $_FILES['upfile']['tmp_name'] : '';
$upfile_path = isset($_POST['upfile_path']) ? $_POST['upfile_path'] : '';
$upfile_name = isset($_FILES['upfile']['name']) ? $_FILES['upfile']['name'] : false;
$upfile_status = isset($_FILES['upfile']['error']) ? $_FILES['upfile']['error'] : 4;
$pwdc = isset($_COOKIE['pwdc']) ? $_COOKIE['pwdc'] : '';
 
// 欄位陷阱
$FTname = isset($_POST['name']) ? $_POST['name'] : '';
$FTemail = isset($_POST['email']) ? $_POST['email'] : '';
$FTsub = isset($_POST['sub']) ? $_POST['sub'] : '';
$FTcom = isset($_POST['com']) ? $_POST['com'] : '';
$FTreply = isset($_POST['reply']) ? $_POST['reply'] : '';
if($FTname != 'spammer' || $FTemail != 'foo@foo.bar' || $FTsub != 'DO NOT FIX THIS' || $FTcom != 'EID OG SMAPS' || $FTreply != '') error(_T('regist_nospam'));
 
// 封鎖:IP/Hostname/DNSBL 檢查機能
$ip = $_SERVER["REMOTE_ADDR"]; $host = gethostbyaddr($ip); $baninfo = '';
if(BanIPHostDNSBLCheck($ip, $host, $baninfo)) error(_T('regist_ipfiltered', $baninfo));
// 封鎖:限制出現之文字
foreach($BAD_STRING as $value){
if(strpos($com, $value)!==false || strpos($sub, $value)!==false || strpos($name, $value)!==false || strpos($email, $value)!==false){
error(_T('regist_wordfiltered'));
}
}
$PMS->useModuleMethods('RegistBegin', array(&$name, &$email, &$sub, &$com, (isset($_FILES['upfile']) ? $_FILES['upfile'] : array()), array($ip, $host), $_POST)); // "RegistBegin" Hook Point
 
// 檢查是否輸入櫻花日文假名
$chkanti = array($name, $email, $sub, $com);
foreach($chkanti as $anti) if(anti_sakura($anti)) error(_T('regist_sakuradetected'));
 
// 時間
$time = time();
$tim = $time.substr(microtime(),2,3);
 
// 判斷上傳狀態
switch($upfile_status){
case 1:
error(_T('regist_upload_exceedphp'));
break;
case 2:
error(_T('regist_upload_exceedcustom'));
break;
case 3:
error(_T('regist_upload_incompelete'));
break;
case 6:
error(_T('regist_upload_direrror'));
break;
case 4: // 無上傳
if(!$resto && !isset($_POST['noimg'])) error(_T('regist_upload_noimg'));
break;
case 0: // 上傳正常
default:
}
 
// 如果有上傳檔案則處理附加圖檔
if($upfile && is_file($upfile)){
// 一‧先儲存檔案
$dest = $path.$tim.'.tmp';
@move_uploaded_file($upfile, $dest);
@chmod($dest, 0666);
if(!is_file($dest)) error(_T('regist_upload_filenotfound'), $dest);
 
// 二‧判斷上傳附加圖檔途中是否有中斷
$upsizeTTL = $_SERVER['CONTENT_LENGTH'];
$upsizeHDR = 0;
// 檔案路徑:IE附完整路徑,故得從隱藏表單取得
$tmp_upfile_path = $upfile_name;
if($upfile_path) $tmp_upfile_path = get_magic_quotes_gpc() ? stripslashes($upfile_path) : $upfile_path;
list(,$boundary) = explode('=', $_SERVER['CONTENT_TYPE']);
foreach($_POST as $header => $value){ // 表單欄位傳送資料
$upsizeHDR += strlen('--'.$boundary."\r\n");
$upsizeHDR += strlen('Content-Disposition: form-data; name="$header"'."\r\n\r\n".(get_magic_quotes_gpc()?stripslashes($value):$value)."\r\n");
}
// 附加圖檔欄位傳送資料
$upsizeHDR += strlen('--'.$boundary."\r\n");
$upsizeHDR += strlen('Content-Disposition: form-data; name="upfile"; filename="'.$tmp_upfile_path."\"\r\n".'Content-Type: '.$_FILES['upfile']['type']."\r\n\r\n");
$upsizeHDR += strlen("\r\n--".$boundary."--\r\n");
$upsizeHDR += $_FILES['upfile']['size']; // 傳送附加圖檔資料量
// 上傳位元組差值超過 HTTP_UPLOAD_DIFF:上傳附加圖檔不完全
if(($upsizeTTL - $upsizeHDR) > HTTP_UPLOAD_DIFF){
if(KILL_INCOMPLETE_UPLOAD){
unlink($dest);
die(_T('regist_upload_killincomp')); // 給瀏覽器的提示,假如使用者還看的到的話才不會納悶
}else $up_incomplete = 1;
}
 
// 三‧檢查是否為可接受的檔案
$size = @getimagesize($dest);
if(!is_array($size)) error(_T('regist_upload_notimage'), $dest); // $size不為陣列就不是圖檔
$imgsize = @filesize($dest); // 檔案大小
$imgsize = ($imgsize>=1024) ? (int)($imgsize/1024).' KB' : $imgsize.' B'; // KB和B的判別
switch($size[2]){ // 判斷上傳附加圖檔之格式
case 1 : $ext = ".gif"; break;
case 2 : $ext = ".jpg"; break;
case 3 : $ext = ".png"; break;
case 4 : $ext = ".swf"; break;
case 5 : $ext = ".psd"; break;
case 6 : $ext = ".bmp"; break;
case 13 : $ext = ".swf"; break;
default : $ext = ".xxx"; error(_T('regist_upload_notsupport'), $dest);
}
$allow_exts = explode('|', strtolower(ALLOW_UPLOAD_EXT)); // 接受之附加圖檔副檔名
if(array_search(substr($ext, 1), $allow_exts)===false) error(_T('regist_upload_notsupport'), $dest); // 並無在接受副檔名之列
// 封鎖設定:限制上傳附加圖檔之MD5檢查碼
$md5chksum = md5_file($dest); // 檔案MD5
if(array_search($md5chksum, $BAD_FILEMD5)!==FALSE) error(_T('regist_upload_blocked'), $dest); // 在封鎖設定內則阻擋
 
// 四‧計算附加圖檔圖檔縮圖顯示尺寸
$W = $imgW = $size[0];
$H = $imgH = $size[1];
$MAXW = $resto ? MAX_RW : MAX_W;
$MAXH = $resto ? MAX_RH : MAX_H;
if($W > $MAXW || $H > $MAXH){
$W2 = $MAXW / $W;
$H2 = $MAXH / $H;
$key = ($W2 < $H2) ? $W2 : $H2;
$W = ceil($W * $key);
$H = ceil($H * $key);
}
$mes = _T('regist_uploaded',CleanStr($upfile_name));
}
 
// 檢查表單欄位內容並修整
if(!$name || ereg("^[ | |]*$", $name)){
if(ALLOW_NONAME) $name = DEFAULT_NONAME;
else error(_T('regist_withoutname'), $dest);
}
if(!$com && $upfile_status==4) error(_T('regist_withoutcomment'));
if(!$com || ereg("^[ | |\t]*$", $com)) $com = DEFAULT_NOCOMMENT;
if(!$sub || ereg("^[ | |]*$", $sub)) $sub = DEFAULT_NOTITLE;
if(strlen($name) > 100) error(_T('regist_nametoolong'), $dest);
if(strlen($email) > 100) error(_T('regist_emailtoolong'), $dest);
if(strlen($sub) > 100) error(_T('regist_topictoolong'), $dest);
if(strlen($resto) > 10) error(_T('regist_longthreadnum'), $dest);
 
$email = CleanStr($email); $email = str_replace("\r\n", '', $email);
$sub = CleanStr($sub); $sub = str_replace("\r\n", '', $sub);
$resto = CleanStr($resto); $resto = str_replace("\r\n", '', $resto);
// 名稱修整
$name = CleanStr($name);
$name = str_replace(_T('trip_pre'), _T('trip_pre_fake'), $name); // 防止トリップ偽造
$name = str_replace(CAP_SUFFIX, _T('cap_char_fake'), $name); // 防止管理員キャップ偽造
$name = str_replace("\r\n", '', $name);
$nameOri = $name; // 名稱
if(preg_match('/(.*?)[##](.*)/u', $name, $regs)){ // トリップ(Trip)機能
$name = $nameOri = $regs[1]; $cap = strtr($regs[2], array('&amp;'=>'&'));
$salt = preg_replace('/[^\.-z]/', '.', substr($cap.'H.', 1, 2));
$salt = strtr($salt, ':;<=>?@[\\]^_`', 'ABCDEFGabcdef');
$name = $name._T('trip_pre').substr(crypt($cap, $salt), -10);
}
if(CAP_ENABLE && preg_match('/(.*?)[##](.*)/', $email, $aregs)){ // 管理員キャップ(Cap)機能
$acap_name = $nameOri; $acap_pwd = strtr($aregs[2], array('&amp;'=>'&'));
if($acap_name==CAP_NAME && $acap_pwd==CAP_PASS){
$name = '<span class="admin_cap">'.$name.CAP_SUFFIX.'</span>';
$is_admin = true;
$email = $aregs[1]; // 去除 #xx 密碼
}
}
if(!$is_admin){ // 非管理員
$name = str_replace(_T('admin'), '"'._T('admin').'"', $name);
$name = str_replace(_T('deletor'), '"'._T('deletor').'"', $name);
}
$name = str_replace('&◆', '&amp;◆', $name); // 避免 &#xxxx; 後面被視為 Trip 留下 & 造成解析錯誤
// 內文修整
if((strlen($com) > COMM_MAX) && !$is_admin) error(_T('regist_commenttoolong'), $dest);
$com = CleanStr($com, $is_admin); // 引入$is_admin參數是因為當管理員キャップ啟動時,允許管理員依config設定是否使用HTML
$com = str_replace("\r\n","\n", $com);
$com = str_replace("\r","\n", $com);
$com = ereg_replace("\n(( | )*\n){3,}", "\n", $com);
if(!BR_CHECK || substr_count($com,"\n") < BR_CHECK) $com = nl2br($com); // 換行字元用<br />代替
$com = str_replace("\n",'', $com); // 若還有\n換行字元則取消換行
if($category && USE_CATEGORY){ // 修整標籤樣式
$category = explode(',', $category); // 把標籤拆成陣列
$category = ','.implode(',', array_map('trim', $category)).','; // 去空白再合併為單一字串 (左右含,便可以直接以,XX,形式搜尋)
}else{ $category = ''; }
if($up_incomplete) $com .= '<br /><br /><span class="warn_txt">'._T('notice_incompletefile').'</span>'; // 上傳附加圖檔不完全的提示
 
// 密碼和時間的樣式
if($pwd=='') $pwd = ($pwdc=='') ? substr(rand(),0,8) : $pwdc;
$pass = $pwd ? substr(md5($pwd), 2, 8) : '*'; // 生成真正儲存判斷用的密碼
$youbi = array(_T('sun'),_T('mon'),_T('tue'),_T('wed'),_T('thu'),_T('fri'),_T('sat'));
$yd = $youbi[gmdate('w', $time+TIME_ZONE*60*60)];
$now = gmdate('y/m/d', $time+TIME_ZONE*60*60).'('.(string)$yd.')'.gmdate('H:i', $time+TIME_ZONE*60*60);
if(DISP_ID){ // 顯示ID
if($email && DISP_ID==1) $now .= ' ID:???';
else $now .= ' ID:'.substr(crypt(md5($_SERVER['REMOTE_ADDR'].IDSEED.gmdate('Ymd', $time+TIME_ZONE*60*60)),'id'), -8);
}
 
// 連續投稿 / 相同附加圖檔檢查
$checkcount = 50; // 預設檢查50筆資料
$pwdc = substr(md5($pwdc), 2, 8); // Cookies密碼
if($PIO->isSuccessivePost($checkcount, $com, $time, $pass, $pwdc, $host, $upfile_name)) error(_T('regist_successivepost'), $dest); // 連續投稿檢查
if($dest){ if($PIO->isDuplicateAttechment($checkcount, $md5chksum)) error(_T('regist_duplicatefile'), $dest); } // 相同附加圖檔檢查
 
if($resto) $ThreadExistsBefore = $PIO->isThread($resto);
// 記錄檔行數已達上限:刪除過舊檔
if($PIO->postCount() >= LOG_MAX){
$PMS->useModuleMethods('UsageExceed', array()); // "UsageExceed" Hook Point
$files = $PIO->delOldPostes();
if(count($files)) $FileIO->deleteImage($files);
}
 
// 附加圖檔容量限制功能啟動:刪除過大檔
if(STORAGE_LIMIT){
$tmp_total_size = total_size(); // 取得目前附加圖檔使用量
if($tmp_total_size >= STORAGE_MAX){
$files = $PIO->delOldAttachments($tmp_total_size, STORAGE_MAX, false);
$FileIO->deleteImage($files);
}
}
 
// 判斷欲回應的文章是不是剛剛被刪掉了
if($resto){
if($ThreadExistsBefore){ // 欲回應的討論串是否存在 (看逆轉換成功與否)
if(!$PIO->isThread($resto)){ // 被回應的討論串存在但已被刪
// 提前更新資料來源,此筆新增亦不紀錄
$PIO->dbCommit();
updatelog();
error(_T('regist_threaddeleted'), $dest);
}else{ // 檢查是否討論串被設為禁止回應 (順便取出原討論串的貼文時間)
$post = $PIO->fetchPosts($resto); // [特殊] 取單篇文章內容,但是回傳的$post同樣靠[$i]切換文章!
list($chkstatus, $chktime) = array($post[0]['status'], $post[0]['tim']);
$chktime = substr($chktime, 0, -3); // 拿掉微秒 (後面三個字元)
if($PIO->getPostStatus($chkstatus, 'TS')) error(_T('regist_threadlocked'), $dest);
}
}else error(_T('thread_not_found'), $dest); // 不存在
}
 
// 計算某些欄位值
$no = $PIO->getLastPostNo('beforeCommit') + 1;
isset($ext) ? 0 : $ext = '';
isset($imgW) ? 0 : $imgW = 0;
isset($imgH) ? 0 : $imgH = 0;
isset($imgsize) ? 0 : $imgsize = '';
isset($W) ? 0 : $W = 0;
isset($H) ? 0 : $H = 0;
isset($md5chksum) ? 0 : $md5chksum = '';
$age = false;
if($resto){
if(!stristr($email, 'sage') && ($PIO->postCount($resto) <= MAX_RES || MAX_RES==0)){
if(!MAX_AGE_TIME || (($time - $chktime) < (MAX_AGE_TIME * 60 * 60))) $age = true; // 討論串並無過期,推文
}
}
$PMS->useModuleMethods('RegistBeforeCommit', array(&$name, &$email, &$sub, &$com, &$category, &$age, $dest, array($W, $H, $imgW, $imgH))); // "RegistBeforeCommit" Hook Point
 
// 正式寫入儲存
$PIO->addPost($no,$resto,$md5chksum,$category,$tim,$ext,$imgW,$imgH,$imgsize,$W,$H,$pass,$now,$name,$email,$sub,$com,$host,$age);
$PIO->dbCommit();
 
// Cookies儲存:密碼與E-mail部分,期限是一週
setcookie('pwdc', $pwd, time()+7*24*3600);
setcookie('emailc', $email, time()+7*24*3600);
 
if($dest && is_file($dest)){
rename($dest, $path.IMG_DIR.$tim.$ext);
if(USE_THUMB) thumb($path.IMG_DIR, $tim, $ext, $imgW, $imgH, $W, $H); // 使用GD製作縮圖
}
 
if($FileIO->uploadImage()){ // 支援上傳圖片至其他伺服器
if(file_exists($path.IMG_DIR.$tim.$ext)) $FileIO->uploadImage($tim.$ext, $path.IMG_DIR.$tim.$ext, filesize($path.IMG_DIR.$tim.$ext));
if(file_exists($path.THUMB_DIR.$tim.'s.jpg')) $FileIO->uploadImage($tim.'s.jpg', $path.THUMB_DIR.$tim.'s.jpg', filesize($path.THUMB_DIR.$tim.'s.jpg'));
}
 
// 刪除舊容量快取
total_size(true);
updatelog();
 
// 引導使用者至新頁面
$RedirURL = PHP_SELF2.'?'.$tim; // 定義儲存資料後轉址目標
if(isset($_POST['up_series'])){ // 勾選連貼機能
if($resto) $RedirURL = PHP_SELF.'?res='.$resto.'&amp;upseries=1'; // 回應後繼續轉回此主題下
else{
$lastno = $PIO->getLastPostNo('afterCommit'); // 取得此新文章編號
$RedirURL = PHP_SELF.'?res='.$lastno.'&amp;upseries=1'; // 新增主題後繼續轉到此主題下
}
}
$RedirforJS = strtr($RedirURL, array("&amp;"=>"&")); // JavaScript用轉址目標
 
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
echo <<< _REDIR_
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-tw">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Refresh" content="1;URL=$RedirURL" />
<script type="text/javascript">
// Redirection (use JS)
// <![CDATA[
function redir(){
location.href = "$RedirforJS";
}
setTimeout("redir()", 1000);
// ]]>
</script>
</head>
<body>
<div>
_REDIR_;
echo _T('regist_redirect',$mes,$RedirURL).'</div>
</body>
</html>';
}
 
/* 使用者刪除 */
function usrdel(){
global $PIO, $FileIO, $language;
// $pwd: 使用者輸入值, $pwdc: Cookie記錄密碼
$pwd = isset($_POST['pwd']) ? $_POST['pwd'] : '';
$pwdc = isset($_COOKIE['pwdc']) ? $_COOKIE['pwdc'] : '';
$onlyimgdel = isset($_POST['onlyimgdel']) ? $_POST['onlyimgdel'] : '';
 
if($pwd=='' && $pwdc!='') $pwd = $pwdc;
$pwd_md5 = substr(md5($pwd),2,8);
$host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
$search_flag = $delflag = false;
$delno = array();
reset($_POST);
while($item = each($_POST)) if($item[1]=='delete') array_push($delno, $item[0]);
if(!count($delno)) error(_T('del_notchecked'));
 
$delposts = array(); // 真正符合刪除條件文章
$posts = $PIO->fetchPosts($delno);
foreach($posts as $post){
if($pwd_md5==$post['pwd'] || $host==$post['host'] || $pwd==ADMIN_PASS){
$search_flag = true; // 有搜尋到
array_push($delposts, $post['no']);
}
}
if($search_flag){
$files = $onlyimgdel ? $PIO->removeAttachments($delposts) : $PIO->removePosts($delposts);
$FileIO->deleteImage($files);
total_size(true); // 刪除容量快取
$PIO->dbCommit();
}else error(_T('del_wrongpwornotfound'));
}
 
/* 管理員密碼認證 */
function valid(){
global $language;
$pass = isset($_POST['pass']) ? $_POST['pass'] : ''; // 管理者密碼
if($pass && $pass != ADMIN_PASS) error(_T('admin_wrongpassword'));
$dat = '';
head($dat);
$dat .= '<div id="banner">
[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>][<a href="'.PHP_SELF.'?mode=remake">'._T('admin_remake').'</a>]
<div class="bar_admin">'._T('admin_top').'</div>
</div>
<form action="'.PHP_SELF.'" method="post">
<div id="admin-check" style="text-align: center;">
';
echo $dat;
// 登錄用表單
if(!$pass){
echo '<br />
<input type="radio" name="admin" value="del" checked="checked" />'._T('admin_manageposts').'
<input type="radio" name="admin" value="opt" />'._T('admin_optimize').'<p />
<input type="hidden" name="mode" value="admin" />
<input type="password" name="pass" size="8" />
<input type="submit" value="'._T('admin_verify_btn').'" />
</div>
</form>';
die("\n</body>\n</html>");
}
}
 
/* 管理文章模式 */
function admindel(){
global $PIO, $FileIO, $PMS, $language;
 
$pass = isset($_POST['pass']) ? $_POST['pass'] : ''; // 管理者密碼
$page = isset($_POST['page']) ? $_POST['page'] : 0; // 切換頁數
$onlyimgdel = isset($_POST['onlyimgdel']) ? $_POST['onlyimgdel'] : ''; // 只刪圖
$modFunc = '';
$delno = $thsno = array();
$delflag = isset($_POST['delete']); // 是否有「刪除」勾選
$thsflag = isset($_POST['stop']); // 是否有「停止」勾選
$is_modified = false; // 是否改寫檔案
 
// 刪除文章區塊
if($delflag){
$delno = array_merge($delno, $_POST['delete']);
$files = ($onlyimgdel != 'on') ? $PIO->removePosts($delno) : $PIO->removeAttachments($delno);
$FileIO->deleteImage($files);
total_size(true); // 刪除容量快取
$is_modified = TRUE;
}
// 討論串停止區塊
if($thsflag){
$thsno = array_merge($thsno, $_POST['stop']);
$threads = $PIO->fetchPosts($thsno); // 取得文章
$tstatus = $tstatusType = $tsval = array();
foreach($threads as $th){
array_push($tstatus, $th['status']);
array_push($tstatusType, 'TS');
array_push($tsval, ($PIO->getPostStatus($th['status'], 'TS')==1 ? 0 : 1));
}
$PIO->setPostStatus($thsno, $tstatus, $tstatusType, $tsval);
$is_modified = true;
}
if(($delflag || $thsflag) && $is_modified) $PIO->dbCommit(); // 無論如何都有檔案操作,回寫檔案
 
$line = $PIO->fetchPostList(0, $page * ADMIN_PAGE_DEF, ADMIN_PAGE_DEF); // 分頁過的文章列表
$posts_count = count($line); // 迴圈次數
$posts = $PIO->fetchPosts($line); // 文章內容陣列
 
// 印出刪除表格
echo '<script type="text/javascript">
// <![CDATA[
function ChangePage(page){
document.forms[0].page.value = page;
document.forms[0].submit();
}
// ]]>
</script>
<input type="hidden" name="mode" value="admin" />
<input type="hidden" name="admin" value="del" />
<input type="hidden" name="pass" value="'.$pass.'" />
<input type="hidden" name="page" value="'.$page.'" />
<div style="text-align: left;">'._T('admin_notices').'</div>
<p><input type="submit" value="'._T('admin_submit_btn').'" /> <input type="reset" value="'._T('admin_reset_btn').'" /> [<input type="checkbox" name="onlyimgdel" id="onlyimgdel" value="on" /><label for="onlyimgdel">'._T('del_img_only').'</label>]</p>
<table border="1" cellspacing="0" style="margin: 0px auto;">
<tr style="background-color: #6080f6;">'._T('admin_list_header').'</tr>
';
 
for($j = 0; $j < $posts_count; $j++){
$bg = ($j % 2) ? 'ListRow1_bg' : 'ListRow2_bg'; // 背景顏色
extract($posts[$j]);
 
// 修改欄位樣式
$now = preg_replace('/.{2}\/(.{5})\(.+?\)(.{5}).*/', '$1 $2', $now);
$name = htmlspecialchars(str_cut(html_entity_decode(strip_tags($name)), 8));
$sub = htmlspecialchars(str_cut(html_entity_decode($sub), 8));
if($email) $name = "<a href=\"mailto:$email\">$name</a>";
$com = str_replace('<br />',' ',$com);
$com = htmlspecialchars(str_cut(html_entity_decode($com), 20));
 
// 討論串首篇停止勾選框 及 模組功能
$modFunc = $THstop = ' ';
$PMS->useModuleMethods('AdminList', array(&$modFunc, $posts[$j], $resto)); // "AdminList" Hook Point
if($resto==0){ // $resto = 0 (即討論串首篇)
$THstop = '<input type="checkbox" name="stop[]" value="'.$no.'" />'.(($PIO->getPostStatus($status, 'TS')==1)?_T('admin_stop_btn'):'');
}
 
// 從記錄抽出附加圖檔使用量並生成連結
if($ext && $FileIO->imageExists($tim.$ext)){
$clip = '<a href="'.$FileIO->getImageURL($tim.$ext).'" rel="_blank">'.$tim.$ext.'</a>';
$size = $FileIO->getImageFilesize($tim.$ext);
if($FileIO->imageExists($tim.'s.jpg')) $size += $FileIO->getImageFilesize($tim.'s.jpg');
}else{
$clip = $md5chksum = '--';
$size = 0;
}
 
// 印出介面
echo <<< _ADMINEOF_
<tr class="$bg" align="left">
<th align="center">$modFunc</th><th align="center">$THstop</th><th><input type="checkbox" name="delete[]" value="$no" />$no</th><td><small>$now</small></td><td>$sub</td><td><b>$name</b></td><td><small>$com</small></td><td>$host</td><td align="center">$clip ($size)<br />$md5chksum</td>
</tr>
 
_ADMINEOF_;
}
echo '</table>
<p><input type="submit" value="'._T('admin_submit_btn').'" /> <input type="reset" value="'._T('admin_reset_btn').'" /></p>
<p>'._T('admin_totalsize',total_size()).'</p>
<hr />
';
 
$countline = $PIO->postCount(); // 總文章數
$page_max = ceil($countline / ADMIN_PAGE_DEF) - 1; // 總頁數
echo '<table border="1" style="float: left;"><tr>';
if($page) echo '<td><input type="button" value="'._T('prev_page').'" onclick="ChangePage('.($page - 1).');" /></td>';
else echo '<td style="white-space: nowrap;">'._T('first_page').'</td>';
echo '<td>';
for($i = 0; $i <= $page_max; $i++){
if($i==$page) echo '[<b>'.$i.'</b>] ';
else echo '[<a href="javascript:ChangePage('.$i.');">'.$i.'</a>] ';
}
echo '</td>';
if($page < $page_max) echo '<td><input type="button" value="'._T('next_page').'" onclick="ChangePage('.($page + 1).');" /></td>';
else echo '<td style="white-space: nowrap;">'._T('last_page').'</td>';
die('</tr></table>
</div>
</form>
</body>
</html>');
}
 
/* 計算目前附加圖檔使用容量 (單位:KB) */
function total_size($isupdate=false){
global $PIO, $FileIO;
 
$size = 0; $all = 0;
$cache_file = "./sizecache.dat"; // 附加圖檔使用容量值快取檔案
 
if($isupdate){ // 刪除舊快取
if(is_file($cache_file)) unlink($cache_file);
return;
}
if(!is_file($cache_file)){ // 無快取,新增
$line = $PIO->fetchPostList(); // 取出所有文章編號
$posts = $PIO->fetchPosts($line);
$linecount = count($posts);
for($i = 0; $i < $linecount; $i++){
extract($posts[$i]);
// 從記錄檔抽出計算附加圖檔使用量
if($ext && $FileIO->imageExists($tim.$ext)) $all += $FileIO->getImageFilesize($tim.$ext); // 附加圖檔合計計算
if($FileIO->imageExists($tim.'s.jpg')) $all += $FileIO->getImageFilesize($tim.'s.jpg'); // 預覽圖合計計算
}
$sp = fopen($cache_file, 'w');
stream_set_write_buffer($sp, 0);
fwrite($sp, $all); // 寫入目前使用容量值
fclose($sp);
@chmod($cache_file, 0666);
}else{ // 使用快取
$sp = file($cache_file);
$all = $sp[0];
unset($sp);
}
return (int)($all / 1024);
}
 
/* 搜尋(全文檢索)功能 */
function search(){
global $PTE, $PIO, $FileIO, $language;
 
if(!USE_SEARCH) error(_T('search_disabled'));
$searchKeyword = isset($_POST['keyword']) ? trim($_POST['keyword']) : ''; // 欲搜尋的文字
$dat = '';
head($dat);
$dat .= '<div id="banner">
[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>]
<div class="bar_admin">'._T('search_top').'</div>
</div>
';
echo $dat;
if($searchKeyword==''){
echo '<form action="'.PHP_SELF.'" method="post">
<div id="search">
<input type="hidden" name="mode" value="search" />
';
echo '<ul>'._T('search_notice').'<input type="text" name="keyword" size="30" />
'._T('search_target').'<select name="field"><option value="com" selected="selected">'._T('search_target_comment').'</option><option value="name">'._T('search_target_name').'</option><option value="sub">'._T('search_target_topic').'</option><option value="no">'._T('search_target_number').'</option></select>
'._T('search_method').'<select name="method"><option value="AND" selected="selected">'._T('search_method_and').'</option><option value="OR">'._T('search_method_or').'</option></select>
<input type="submit" value="'._T('search_submit_btn').'" />
</li>
</ul>
</div>
</form>';
}else{
$searchField = $_POST['field']; // 搜尋目標 (no:編號, name:名稱, sub:標題, com:內文)
$searchMethod = $_POST['method']; // 搜尋方法
$searchKeyword = preg_split('/( | )+/', trim($searchKeyword)); // 搜尋文字用空格切割
$hitPosts = $PIO->searchPost($searchKeyword, $searchField, $searchMethod); // 直接傳回符合的文章內容陣列
 
echo '<div id="search_result">
';
$resultlist = '';
foreach($hitPosts as $post){
extract($post);
$arrLabels = array('{$NO}'=>$no, '{$SUB}'=>$sub, '{$NAME}'=>$name, '{$NOW}'=>$now, '{$COM}'=>$com, '{$CATEGORY}'=>$category, '{$NAME_TEXT}'=>_T('post_name'), '{$CATEGORY_TEXT}'=>_T('post_category'));
$resultlist .= $PTE->ParseBlock('SEARCHRESULT',$arrLabels);
}
echo $resultlist ? $resultlist : '<div style="text-align: center">'._T('search_notfound').'<br/><a href="?mode=search">'._T('search_back').'</a></div>';
echo "</div>";
}
echo "</body>\n</html>";
}
 
/* 利用類別標籤搜尋符合的文章 */
function searchCategory(){
global $PTE, $PIO, $FileIO, $language;
$category = isset($_GET['c']) ? strtolower(strip_tags(trim($_GET['c']))) : ''; // 搜尋之類別標籤
$category_enc = urlencode($category); // URL 編碼後字串
$page = isset($_GET['p']) ? @intval($_GET['p']) : 1; // 目前瀏覽頁數
$isrecache = isset($_GET['recache']) ? true : false; // 是否強制重新生成快取
if($page < 1) $page = 1;
if(!$category) error(_T('category_nokeyword'));
 
// 利用Session快取類別標籤出現篇別以減少負擔
session_start(); // 啟動Session
if(!isset($_SESSION['loglist_'.$category]) || $isrecache){
$loglist = $PIO->searchCategory($category);
$_SESSION['loglist_'.$category] = serialize($loglist);
}else $loglist = unserialize($_SESSION['loglist_'.$category]);
$loglist_count = count($loglist);
if(!$loglist_count) error(_T('category_notfound'));
$page_max = ceil($loglist_count / PAGE_DEF); if($page > $page_max) $page = $page_max; // 總頁數
 
// 分割陣列取出適當範圍作分頁之用
$loglist_cut = array_slice($loglist, PAGE_DEF * ($page - 1), PAGE_DEF); // 取出特定範圍文章
$loglist_cut_count = count($loglist_cut);
 
$dat = '';
head($dat);
$dat .= '<div>[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>][<a href="'.PHP_SELF.'?mode=category&amp;c='.$category_enc.'&amp;recache=1">'._T('category_recache').'</a>]</div>'."\n";
for($i = 0; $i < $loglist_cut_count; $i++){
$posts = $PIO->fetchPosts($loglist_cut[$i]); // 取得文章內容
$dat .= arrangeThread($PTE, 0, 0, $posts, 0, $loglist_cut[$i], 0, 0, 0, 0, false); // 逐個輸出 (引用連結不顯示)
}
 
$dat .= '<table border="1"><tr>';
if($page > 1) $dat .= '<td><form action="'.PHP_SELF.'?mode=category&amp;c='.$category_enc.'&amp;p='.($page - 1).'" method="post"><div><input type="submit" value="'._T('prev_page').'" /></div></form></td>';
else $dat .= '<td style="white-space: nowrap;">'._T('first_page').'</td>';
$dat .= '<td>';
for($i = 1; $i <= $page_max ; $i++){
if($i==$page) $dat .= "[<b>".$i."</b>] ";
else $dat .= '[<a href="'.PHP_SELF.'?mode=category&amp;c='.$category_enc.'&amp;p='.$i.'">'.$i.'</a>] ';
}
$dat .= '</td>';
if($page < $page_max) $dat .= '<td><form action="'.PHP_SELF.'?mode=category&amp;c='.$category_enc.'&amp;p='.($page + 1).'" method="post"><div><input type="submit" value="'._T('next_page').'" /></div></form></td>';
else $dat .= '<td style="white-space: nowrap;">'._T('last_page').'</td>';
$dat .= '</tr></table>'."\n";
 
foot($dat);
echo $dat;
}
 
/* 顯示已載入模組資訊 */
function listModules(){
global $PMS, $language;
$dat = '';
head($dat);
$dat .= '<div id="banner">
[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>]
<div class="bar_admin">'._T('module_info_top').'</div>
</div>
 
<div id="modules">
';
/* Module Loaded */
$dat .= _T('module_loaded').'<ul>'."\n";
foreach($PMS->getLoadedModules() as $m){
$dat .= '<li>'.$m."</li>\n";
}
$dat .= "</ul><hr />\n";
 
/* Module Infomation */
$dat .= _T('module_info').'<ul>'."\n";
foreach($PMS->moduleInstance as $m){
$dat .= '<li>'.$m->getModuleName().'<div style="padding-left:2em;">'.$m->getModuleVersionInfo()."</div></li>\n";
}
$dat .= '</ul><hr />
</div>
 
';
foot($dat);
echo $dat;
}
 
/* 顯示系統各項資訊 */
function showstatus(){
global $PIO, $FileIO, $language;
$countline = $PIO->postCount(); // 計算投稿文字記錄檔目前資料筆數
$counttree = $PIO->threadCount(); // 計算樹狀結構記錄檔目前資料筆數
$tmp_total_size = total_size(); // 附加圖檔使用量總大小
$tmp_log_ratio = $countline / LOG_MAX; // 記錄檔使用量
$tmp_ts_ratio = $tmp_total_size / STORAGE_MAX; // 附加圖檔使用量
 
// 決定「記錄檔使用量」提示文字顏色
if($tmp_log_ratio < 0.3 ) $clrflag_log = '235CFF';
elseif($tmp_log_ratio < 0.5 ) $clrflag_log = '0CCE0C';
elseif($tmp_log_ratio < 0.7 ) $clrflag_log = 'F28612';
elseif($tmp_log_ratio < 0.9 ) $clrflag_log = 'F200D3';
else $clrflag_log = 'F2004A';
 
// 決定「附加圖檔使用量」提示文字顏色
if($tmp_ts_ratio < 0.3 ) $clrflag_sl = '235CFF';
elseif($tmp_ts_ratio < 0.5 ) $clrflag_sl = '0CCE0C';
elseif($tmp_ts_ratio < 0.7 ) $clrflag_sl = 'F28612';
elseif($tmp_ts_ratio < 0.9 ) $clrflag_sl = 'F200D3';
else $clrflag_sl = 'F2004A';
 
// 判斷是否開啟GD模組、取出GD版本號及功能是否正常
$func_gd = '<span style="color: red;">'._T('info_disabled').'</span>';
$func_gdver = '(No info)';
if(extension_loaded('gd')){
$func_gd = '<span style="color: blue;">'._T('info_enabled').'</span>';
if($func_gdver = @gd_info()) $func_gdver = $func_gdver['GD Version'];
}
$thumb_IsAvailable = function_exists('ImageCreateTrueColor') ? '<span style="color: blue;">'._T('info_functional').'</span>' : '<span style="color: red">'._T('info_nonfunctional').'</span>';
 
$dat = '';
head($dat);
$dat .= '<div id="banner">
[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>]
[<a href="'.PHP_SELF.'?mode=moduleloaded">'._T('module_info_top').'</a>]
<div class="bar_admin">'._T('info_top').'</div>
</div>
';
 
$dat .= '
<div id="status-table" style="text-align: center;">
<table border="1" style="margin: 0px auto; text-align: left;">
<tr><td align="center" colspan="3">'._T('info_basic').'</td></tr>
<tr><td style="width: 240px;">'._T('info_basic_ver').'</td><td colspan="2"> '.PIXMICAT_VER.' </td></tr>
<tr><td>'._T('info_basic_pio').'</td><td colspan="2"> '.PIXMICAT_BACKEND.' : '.$PIO->pioVersion().'</td></tr>
<tr><td>'._T('info_basic_threadsperpage').'</td><td colspan="2"> '.PAGE_DEF.' '._T('info_basic_threads').'</td></tr>
<tr><td>'._T('info_basic_postsperpage').'</td><td colspan="2"> '.RE_DEF.' '._T('info_basic_posts').'</td></tr>
<tr><td>'._T('info_basic_postsinthread').'</td><td colspan="2"> '.RE_PAGE_DEF.' '._T('info_basic_posts').' '._T('info_basic_posts_showall').'</td></tr>
<tr><td>'._T('info_basic_bumpposts').'</td><td colspan="2"> '.MAX_RES.' '._T('info_basic_posts').' '._T('info_basic_0disable').'</td></tr>
<tr><td>'._T('info_basic_bumphours').'</td><td colspan="2"> '.MAX_AGE_TIME.' '._T('info_basic_hours').' '._T('info_basic_0disable').'</td></tr>
<tr><td>'._T('info_basic_urllinking').'</td><td colspan="2"> '.AUTO_LINK.' '._T('info_0no1yes').'</td></tr>
<tr><td>'._T('info_basic_com_limit').'</td><td colspan="2"> '.COMM_MAX._T('info_basic_com_after').'</td></tr>
<tr><td>'._T('info_basic_anonpost').'</td><td colspan="2"> '.ALLOW_NONAME.' '._T('info_basic_anonpost_opt').'</td></tr>
<tr><td>'._T('info_basic_del_incomplete').'</td><td colspan="2"> '.KILL_INCOMPLETE_UPLOAD.' '._T('info_0no1yes').'</td></tr>
<tr><td>'._T('info_basic_use_sample',THUMB_Q).'</td><td colspan="2"> '.USE_THUMB.' '._T('info_0notuse1use').'</td></tr>';
if(USE_THUMB) $dat .= '<tr><td>'._T('info_basic_use_sample_func').'</td><td colspan="2"> '.$thumb_IsAvailable.' </td></tr>'."\n";
$dat .= '<tr><td>'._T('info_basic_useblock').'</td><td colspan="2"> '.BAN_CHECK.' '._T('info_0disable1enable').'</td></tr>
<tr><td>'._T('info_basic_showid').'</td><td colspan="2"> '.DISP_ID.' '._T('info_basic_showid_after').'</td></tr>
<tr><td>'._T('info_basic_cr_limit').'</td><td colspan="2"> '.BR_CHECK._T('info_basic_cr_after').'</td></tr>
<tr><td>'._T('info_basic_timezone').'</td><td colspan="2"> GMT '.TIME_ZONE.'</td></tr>
<tr><td>'._T('info_basic_threadcount').'</td><td colspan="2"> '.$counttree.' '._T('info_basic_threads').'</td></tr>
<tr><td align="center" colspan="3">'._T('info_dsusage_top').'</td></tr>
<tr align="center"><td>'._T('info_dsusage_max').'</td><td>'.LOG_MAX.'</td><td rowspan="2">'._T('info_dsusage_usage').'<br /><span style="color: #'.$clrflag_log.';">'.substr(($tmp_log_ratio * 100), 0, 6).'</span> %</td></tr>
<tr align="center"><td>'._T('info_dsusage_count').'</td><td><span style="color: #'.$clrflag_log.';">'.$countline.'</span></td></tr>
<tr><td align="center" colspan="3">'._T('info_fileusage_top').STORAGE_LIMIT.' '._T('info_0disable1enable').'</td></tr>';
 
if(STORAGE_LIMIT){
$dat .= '
<tr align="center"><td>'._T('info_fileusage_limit').'</td><td>'.STORAGE_MAX.' KB</td><td rowspan="2">'._T('info_dsusage_usage').'<br /><span style="color: #'.$clrflag_sl.'">'.substr(($tmp_ts_ratio * 100), 0, 6).'</span> %</td></tr>
<tr align="center"><td>'._T('info_fileusage_count').'</td><td><span style="color: #'.$clrflag_sl.'">'.$tmp_total_size.' KB</span></td></tr>';
}else{
$dat .= '
<tr align="center"><td>'._T('info_fileusage_count').'</td><td>'.$tmp_total_size.' KB</td><td>'._T('info_dsusage_usage').'<br /><span style="color: green;">'._T('info_fileusage_unlimited').'</span></td></tr>';
}
 
$dat .= '
<tr><td align="center" colspan="3">'._T('info_server_top').'</td></tr>
<tr align="center"><td colspan="2">'._T('info_server_gd').$func_gdver.'</td><td>'.$func_gd.'</td></tr>
</table>
<hr />
</div>'."\n";
 
foot($dat);
echo $dat;
}
 
/* 程式首次執行之初始化 */
function init(){
global $PIO, $FileIO, $language;
if(!is_writable(realpath('./'))) error(_T('init_permerror'));
 
$chkfolder = array(IMG_DIR, THUMB_DIR);
// 逐一自動建置IMG_DIR和THUMB_DIR
foreach($chkfolder as $value) if(!is_dir($value)){ mkdir($value); @chmod($value, 0777); } // 沒有就建立
 
$PIO->dbInit(); // PIO Init
$FileIO->init(); // FileIO Init
 
error(_T('init_inited'));
}
 
/*-----------程式各項功能主要判斷-------------*/
if(GZIP_COMPRESS_LEVEL && ($Encoding = CheckSupportGZip())){ ob_start(); ob_implicit_flush(0); } // 支援且開啟Gzip壓縮就設緩衝區
$path = realpath("./").'/'; // 此資料夾的絕對位置
$mode = isset($_GET['mode']) ? $_GET['mode'] : ''; // 目前執行模式
if($mode=='' && isset($_POST['mode'])) $mode = $_POST['mode']; // 如果GET找不到,就用POST
 
//init(); // ←■■!程式環境初始化,跑過一次後請刪除此行!■■
switch($mode){
case 'regist':
regist();
break;
case 'admin':
$admin = isset($_POST['admin']) ? $_POST['admin'] : ''; // 管理者執行模式
valid();
if($admin=='del') admindel();
if($admin=='opt'){
if(!$PIO->dbOptimize()) echo _T('action_opt_notsupport');
else echo _T('action_opt_optimize').($PIO->dbOptimize(true)?_T('action_opt_success'):_T('action_opt_failed'));
die("</div></form></body>\n</html>");
}
break;
case 'search':
search();
break;
case 'status':
showstatus();
break;
case 'category':
searchCategory();
break;
case 'module':
$loadModule = isset($_GET['load']) ? $_GET['load'] : '';
if(array_search($loadModule, $PMS->hookPoints['ModulePage'])!==false){
$PMS->moduleInstance[$loadModule]->ModulePage();
}else{
echo '404 Not Found';
}
break;
case 'moduleloaded':
listModules();
break;
case 'usrdel':
usrdel();
case 'remake':
updatelog();
header('HTTP/1.1 302 Moved Temporarily');
header('Location: '.fullURL().PHP_SELF2.'?'.time());
break;
default:
$res = isset($_GET['res']) ? $_GET['res'] : 0; // 欲回應編號
if($res){ // 回應模式輸出
$page = isset($_GET['page_num']) ? $_GET['page_num'] : 'RE_PAGE_MAX';
if(!($page=='all' || $page=='RE_PAGE_MAX')) $page = intval($_GET['page_num']);
updatelog($res, $page); // 實行分頁
}elseif(@intval($_GET['page_num']) > 0){ // PHP動態輸出一頁
updatelog(0, intval($_GET['page_num']));
}else{ // 導至靜態庫存頁
if(!is_file(PHP_SELF2)) updatelog();
header('HTTP/1.1 302 Moved Temporarily');
header('Location: '.fullURL().PHP_SELF2.'?'.time());
}
}
if($Encoding && GZIP_COMPRESS_LEVEL){ // 有啟動Gzip
if(!ob_get_length()) exit; // 沒內容不必壓縮
header('Content-Encoding: '.$Encoding);
header('X-Content-Encoding-Level: '.GZIP_COMPRESS_LEVEL);
header('Vary: Accept-Encoding');
print gzencode(ob_get_clean(), GZIP_COMPRESS_LEVEL); // 壓縮內容
}
?>
New file
/release/PIO-v4/inc_pixmicat.tpl
@@ -0,0 +1,157 @@
<!--&HEADER--><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-tw">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="Sat, 1 Jan 2000 00:00:00 GMT" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Language" content="zh-tw" />
<title>{$TITLE}</title>
<link rel="stylesheet" type="text/css" href="mainstyle.css" />
<!--/&HEADER-->
 
<!--&JSHEADER-->
<script type="text/javascript">
// <![CDATA[
var msgs=['{$JS_REGIST_WITHOUTCOMMENT}','{$JS_REGIST_UPLOAD_NOTSUPPORT}','{$JS_CONVERT_SAKURA}'];
var ext="{$ALLOW_UPLOAD_EXT}".toUpperCase().split("|");
// ]]>
</script>
<script type="text/javascript" src="mainscript.js"></script>
<!--[if IE]><script type="text/javascript" src="iedivfix.js"></script><![endif]-->
<!--/&JSHEADER-->
 
<!--&TOPLINKS-->
<div id="toplink">
{$HOME} {$SEARCH} {$HOOKLINKS} {$TOP_LINKS} {$STATUS} {$ADMIN} {$REFRESH}
</div>
<!--/&TOPLINKS-->
 
<!--&BODYHEAD-->
<body>
 
<div id="header">
<!--&TOPLINKS/-->
<br />
<h1>{$TITLE}</h1>
<hr class="top" />
</div>
<!--/&BODYHEAD-->
 
<!--&POSTFORM-->
<form action="{$SELF}" method="post" enctype="multipart/form-data" onsubmit="return c();" id="postform_main">
<div id="postform">
<!--&IF($FORMTOP,'{$FORMTOP}','')-->
<input type="hidden" name="mode" value="regist" />
<input type="hidden" name="MAX_FILE_SIZE" value="{$MAX_FILE_SIZE}" />
<input type="hidden" name="upfile_path" value="" />
<!--&IF($RESTO,'{$RESTO}','')-->
<div style="text-align: center;">
<table cellpadding="1" cellspacing="1" id="postform_tbl" style="margin: 0px auto; text-align: left;">
<tr><td class="Form_bg"><b>{$FORM_NAME_TEXT}</b></td><td>{$FORM_NAME_FIELD}</td></tr>
<tr><td class="Form_bg"><b>{$FORM_EMAIL_TEXT}</b></td><td>{$FORM_EMAIL_FIELD}</td></tr>
<tr><td class="Form_bg"><b>{$FORM_TOPIC_TEXT}</b></td><td>{$FORM_TOPIC_FIELD}{$FORM_SUBMIT}</td></tr>
<tr><td class="Form_bg"><b>{$FORM_COMMENT_TEXT}</b></td><td>{$FORM_COMMENT_FIELD}</td></tr>
<!--&IF($FORM_ATTECHMENT_FIELD,'<tr><td class="Form_bg"><b>{$FORM_ATTECHMENT_TEXT}</b></td><td>{$FORM_ATTECHMENT_FIELD}[{$FORM_NOATTECHMENT_FIELD}<label for="noimg">{$FORM_NOATTECHMENT_TEXT}</label>]','')-->
<!--&IF($FORM_CONTPOST_FIELD,'[{$FORM_CONTPOST_FIELD}<label for="up_series">{$FORM_CONTPOST_TEXT}</label>]','')-->
<!--&IF($FORM_ATTECHMENT_FIELD,'</td></tr>','')-->
<!--&IF($FORM_CATEGORY_FIELD,'<tr><td class="Form_bg"><b>{$FORM_CATEGORY_TEXT}</b></td><td>{$FORM_CATEGORY_FIELD}<small>{$FORM_CATEGORY_NOTICE}</small></td></tr>','')-->
<tr><td class="Form_bg"><b>{$FORM_DELETE_PASSWORD_TEXT}</b></td><td>{$FORM_DELETE_PASSWORD_FIELD}<small>{$FORM_DELETE_PASSWORD_NOTICE}</small></td></tr>
{$FORM_EXTRA_COLUMN}
<tr><td colspan="2">
<div id="postinfo">
<ul>{$FORM_NOTICE}
<!--&IF($FORM_NOTICE_STORAGE_LIMIT,'{$FORM_NOTICE_STORAGE_LIMIT}','')-->
{$HOOKPOSTINFO}
{$ADDITION_INFO}
</ul>
<noscript><div>{$FORM_NOTICE_NOSCRIPT}</div></noscript>
</div>
</td></tr>
</table>
</div>
<script type="text/javascript">l1();</script>
<hr />
</div>
</form>
<!--&IF($FORMBOTTOM,'{$FORMBOTTOM}','')-->
<!--/&POSTFORM-->
 
<!--&FOOTER-->
<div id="footer">
{$FOOTER}
<script type="text/javascript">preset();</script>
</div>
 
</body>
</html>
<!--/&FOOTER-->
 
<!--&ERROR-->
<div id="error">
<div style="text-align: center; font-size: 1.5em; font-weight: bold;">
<span style="color: red;">{$MESG}</span><p />
<a href="{$SELF2}">{$RETURN_TEXT}</a> <a href="javascript:history.back();">{$BACK_TEXT}</a>
</div>
<hr />
</div>
<!--/&ERROR-->
 
 
<!--&THREAD-->
<div class="threadpost">
{$IMG_BAR}<!--&IF($IMG_BAR,'<br />','')-->{$IMG_SRC}<input type="checkbox" name="{$NO}" value="delete" /><span class="title">{$SUB}</span>
{$NAME_TEXT}<span class="name">{$NAME}</span> [{$NOW}] {$QUOTEBTN}&nbsp;{$REPLYBTN}
<div class="quote">{$COM}</div>
<!--&IF($CATEGORY,'<div class="category">{$CATEGORY_TEXT}{$CATEGORY}</div>','')-->
{$WARN_OLD}{$WARN_BEKILL}{$WARN_ENDREPLY}{$WARN_HIDEPOST}</div>
<!--/&THREAD-->
 
<!--&REPLY-->
<div class="reply" id="r{$NO}">
<input type="checkbox" name="{$NO}" value="delete" /><span class="title">{$SUB}</span> {$NAME_TEXT}<span class="name">{$NAME}</span> [{$NOW}] {$QUOTEBTN} &nbsp;<!--&IF($IMG_BAR,'<br />&nbsp;','')-->{$IMG_BAR} {$IMG_SRC}
<div class="quote">{$COM}</div>
<!--&IF($CATEGORY,'<div class="category">{$CATEGORY_TEXT}{$CATEGORY}</div>','')-->
{$WARN_BEKILL}</div>
<!--/&REPLY-->
 
<!--&SEARCHRESULT-->
<div class="threadpost">
<span class="title">{$SUB}</span>
{$NAME_TEXT}<span class="name">{$NAME}</span> [{$NOW}] No.{$NO}
<div class="quote">{$COM}</div>
<!--&IF($CATEGORY,'<div class="category">{$CATEGORY_TEXT}{$CATEGORY}</div>','')-->
</div>
<!--&SEPARATE/-->
<!--/&SEARCHRESULT-->
 
 
<!--&SEPARATE-->
<hr />
<!--/&SEPARATE-->
 
<!--&DELFORM-->
<div id="del">
<table style="float: right;">
<tr><td align="center" style="white-space: nowrap;">
{$DEL_HEAD_TEXT}[{$DEL_IMG_ONLY_FIELD}<label for="onlyimgdel">{$DEL_IMG_ONLY_TEXT}</label>]<br />
{$DEL_PASS_TEXT}{$DEL_PASS_FIELD}{$DEL_SUBMIT_BTN}
</td></tr>
</table>
</div>
<!--/&DELFORM-->
 
<!--&MAIN-->
<div id="contents">
<form action="{$SELF}" method="POST">
<div id="threads">
{$THREADFRONT}
{$THREADS}
{$THREADREAR}
</div>
<!--&DELFORM/-->
<script type="text/javascript">l2();</script>
</form>
{$PAGENAV}
</div>
<!--/&MAIN-->
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_common.php
@@ -0,0 +1,329 @@
<?php
/**
* Pixmicat! Common Library
*
* 存放常用函式供主程式引入
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
/* 輸出表頭 */
function head(&$dat,$use_js=true){
global $PTE, $PMS, $language;
header('Content-Type: '.((strpos($_SERVER['HTTP_ACCEPT'],'application/xhtml+xml')!==FALSE) ? 'application/xhtml+xml' : 'text/html').'; charset=utf-8'); // 如果瀏覽器支援XHTML標準MIME就輸出
$pte_vals = array('{$TITLE}'=>TITLE);
$dat .= $PTE->ParseBlock('HEADER',$pte_vals);
$PMS->useModuleMethods('Head', array(&$dat)); // "Head" Hook Point
if($use_js) {
$pte_vals+=array('{$ALLOW_UPLOAD_EXT}' => ALLOW_UPLOAD_EXT,
'{$JS_REGIST_WITHOUTCOMMENT}' => str_replace('\'', '\\\'', _T('regist_withoutcomment')),
'{$JS_REGIST_UPLOAD_NOTSUPPORT}' => str_replace('\'', '\\\'', _T('regist_upload_notsupport')),
'{$JS_CONVERT_SAKURA}' => str_replace('\'', '\\\'', _T('js_convert_sakura')));
$dat .= $PTE->ParseBlock('JSHEADER',$pte_vals);
}
$dat .= '</head>';
$pte_vals += array('{$TOP_LINKS}' => TOP_LINKS,
'{$HOME}' => '[<a href="'.HOME.'" rel="_top">'._T('head_home').'</a>]',
'{$STATUS}' => '[<a href="'.PHP_SELF.'?mode=status">'._T('head_info').'</a>]',
'{$ADMIN}' => '[<a href="'.PHP_SELF.'?mode=admin">'._T('head_admin').'</a>]',
'{$REFRESH}' => '[<a href="'.PHP_SELF2.'?">'._T('head_refresh').'</a>]',
'{$SEARCH}' => (USE_SEARCH) ? '[<a href="'.PHP_SELF.'?mode=search">'._T('head_search').'</a>]' : '',
'{$HOOKLINKS}' => '');
$PMS->useModuleMethods('Toplink', array(&$pte_vals['{$HOOKLINKS}'])); // "Toplink" Hook Point
$dat .= $PTE->ParseBlock('BODYHEAD',$pte_vals);
}
 
/* 發表用表單輸出 */
function form(&$dat, $resno){
global $PTE, $PMS, $ADDITION_INFO, $language;
$pte_vals = array('{$SELF}'=>PHP_SELF, '{$FORMTOP}'=>'');
if($resno){
$pte_vals['{$FORMTOP}'] = '[<a href="'.PHP_SELF2.'?'.time().'">'._T('return').'</a>]
<div class="bar_reply">'._T('form_top').'</div>';
}
if(USE_FLOATFORM && !$resno) $pte_vals['{$FORMTOP}'] .= "\n".'[<span id="show" class="hide" onmouseover="showform();" onclick="showform();">'._T('form_showpostform').'</span><span id="hide" class="show" onmouseover="hideform();" onclick="hideform();">'._T('form_hidepostform').'</span>]';
$pte_vals += array('{$MAX_FILE_SIZE}' => MAX_KB * 1024,
'{$RESTO}' => $resno ? '<input type="hidden" name="resto" value="'.$resno.'" />' : '',
'{$FORM_NAME_TEXT}' => _T('form_name'),
'{$FORM_NAME_FIELD}' => '<input class="hide" type="text" name="name" value="spammer" /><input type="text" name="'.FT_NAME.'" id="fname" size="28" />',
'{$FORM_EMAIL_TEXT}' => _T('form_email'),
'{$FORM_EMAIL_FIELD}' => '<input type="text" name="'.FT_EMAIL.'" id="femail" size="28" /><input type="text" class="hide" name="email" value="foo@foo.bar" />',
'{$FORM_TOPIC_TEXT}' => _T('form_topic'),
'{$FORM_TOPIC_FIELD}' => '<input class="hide" value="DO NOT FIX THIS" type="text" name="sub" /><input type="text" name="'.FT_SUBJECT.'" id="fsub" size="28" />',
'{$FORM_SUBMIT}' => '<input type="submit" name="sendbtn" value="'._T('form_submit_btn').'" />',
'{$FORM_COMMENT_TEXT}' => _T('form_comment'),
'{$FORM_COMMENT_FIELD}' => '<textarea name="'.FT_COMMENT.'" id="fcom" cols="48" rows="4" style="width: 400px; height: 80px;"></textarea><textarea name="com" class="hide" cols="48" rows="4">EID OG SMAPS</textarea>',
'{$FORM_DELETE_PASSWORD_FIELD}' => '<input type="password" name="pwd" size="8" maxlength="8" value="" />',
'{$FORM_DELETE_PASSWORD_TEXT}' => _T('form_delete_password'),
'{$FORM_DELETE_PASSWORD_NOTICE}' => _T('form_delete_password_notice'),
'{$FORM_EXTRA_COLUMN}' => '',
'{$FORM_NOTICE}' => _T('form_notice',MAX_KB,MAX_W,MAX_H),
'{$HOOKPOSTINFO}' => '',
'{$ADDITION_INFO}' => $ADDITION_INFO,
'{$FORM_NOTICE_NOSCRIPT}' => _T('form_notice_noscript'));
$PMS->useModuleMethods('PostForm', array(&$pte_vals['{$FORM_EXTRA_COLUMN}'])); // "PostForm" Hook Point
if(RESIMG || !$resno){
$pte_vals += array('{$FORM_ATTECHMENT_TEXT}' => _T('form_attechment'),
'{$FORM_ATTECHMENT_FIELD}' => '<input type="file" name="upfile" id="fupfile" size="25" /><input class="hide" type="checkbox" name="reply" value="yes" />',
'{$FORM_NOATTECHMENT_TEXT}' => _T('form_noattechment'),
'{$FORM_NOATTECHMENT_FIELD}' => '<input type="checkbox" name="noimg" id="noimg" value="on" />');
if(USE_UPSERIES) { // 啟動連貼機能
$pte_vals['{$FORM_CONTPOST_FIELD}'] = '<input type="checkbox" name="up_series" id="up_series" value="on"'.((isset($_GET["upseries"]) && $resno)?' checked="checked"':'').' />';
$pte_vals['{$FORM_CONTPOST_TEXT}'] = _T('form_contpost');
}
}
if(USE_CATEGORY) {
$pte_vals += array('{$FORM_CATEGORY_FIELD}' => '<input type="text" name="category" size="28" />',
'{$FORM_CATEGORY_TEXT}' => _T('form_category'),
'{$FORM_CATEGORY_NOTICE}' => _T('form_category_notice'));
}
if(STORAGE_LIMIT) $pte_vals['{$FORM_NOTICE_STORAGE_LIMIT}'] = _T('form_notice_storage_limit',total_size(),STORAGE_MAX);
$PMS->useModuleMethods('PostInfo', array(&$pte_vals['{$HOOKPOSTINFO}'])); // "PostInfo" Hook Point
 
if(USE_FLOATFORM && !$resno) $pte_vals['{$FORMBOTTOM}'] = '<script type="text/javascript">hideform();</script>';
$dat .= $PTE->ParseBlock('POSTFORM',$pte_vals);
}
 
/* 輸出頁尾文字 */
function foot(&$dat){
global $PTE, $PMS, $language;
$pte_vals = array('{$FOOTER}'=>'<!-- GazouBBS v3.0 --><!-- ふたば改0.8 --><!-- Pixmicat! -->');
$PMS->useModuleMethods('Foot', array(&$pte_vals['{$FOOTER}'])); // "Foot" Hook Point
$pte_vals['{$FOOTER}'] .= '<small>- <a href="http://php.s3.to" rel="_top">GazouBBS</a> + <a href="http://www.2chan.net/" rel="_top">futaba</a> + <a href="http://pixmicat.openfoundry.org/" rel="_blank">Pixmicat!</a> -</small>';
$dat .= $PTE->ParseBlock('FOOTER',$pte_vals);
}
 
/* 網址自動連結 */
function auto_link($proto){
return preg_replace('/(https?|ftp|news)(:\/\/[\w\+\$\;\?\.\{\}%,!#~*\/:@&=_-]+)/u', '<a href="$1$2" rel="_blank">$1$2</a>', $proto);
}
 
/* 引用標註 */
function quoteLight($comment){
return preg_replace('/(^|<br \/>)((?:&gt;|>).*?)(?=<br \/>|$)/u', '$1<span class="resquote">$2</span>', $comment);
}
 
/* 取得完整的網址 */
function fullURL(){
return 'http://'.$_SERVER['HTTP_HOST'].preg_replace('/(.*)\/.+$/', '$1/', $_SERVER['PHP_SELF']);
}
 
/* 反櫻花字 */
function anti_sakura($str){
return preg_match('/[\x{E000}-\x{F848}]/u', $str);
}
 
/* 輸出錯誤畫面 */
function error($mes, $dest=''){
global $PTE;
if(is_file($dest)) unlink($dest);
$pte_vals = array('{$SELF2}'=>PHP_SELF2.'?'.time(), '{$MESG}'=>$mes, '{$RETURN_TEXT}'=>_T('return'), '{$BACK_TEXT}'=>_T('error_back'));
$dat = '';
head($dat);
$dat .= $PTE->ParseBlock('ERROR',$pte_vals);
foot($dat);
exit($dat);
}
 
/* 生成預覽圖:需要開啟GD模組 (GD 2.0.28以上) */
function thumb($path, $tim, $ext, $in_w, $in_h, $out_w, $out_h){
if(!function_exists('ImageCreateTrueColor')) return; // GD未開或版本太舊
$fname = $path.$tim.$ext;
$thumb_dir = THUMB_DIR; // 預覽圖儲存目錄位置
 
// 取得原附加圖檔之長寬及類型
switch($ext){
case '.gif': // GIF
$im_in = @ImageCreateFromGIF($fname);
break;
case '.jpg': // JPEG
$im_in = @ImageCreateFromJPEG($fname);
break;
case '.png': // PNG
$im_in = @ImageCreateFromPNG($fname);
break;
case '.bmp': // BMP
$im_in = @ImageCreateFromBMP($fname);
break;
default: return; // GD不支援的類型
}
if(!$im_in) return; // GD不支援的類型
// 生成預覽圖圖像
$im_out = ImageCreateTrueColor($out_w, $out_h);
ImageCopyResampled($im_out, $im_in, 0, 0, 0, 0, $out_w, $out_h, $in_w, $in_h); // 重取樣並縮小
// 儲存預覽圖
ImageJPEG($im_out, $thumb_dir.$tim.'s.jpg', THUMB_Q);
chmod($thumb_dir.$tim.'s.jpg', 0666);
// 刪除暫存之圖檔
ImageDestroy($im_in);
ImageDestroy($im_out);
}
 
/* ImageCreateFromBMP : 讓GD可處理BMP圖檔
此為修改後最適化版本。原出處:http://www.php.net/imagecreate#53879
原作宣告:
*****************************
Function: ImageCreateFromBMP
Author: DHKold
Contact: admin@dhkold.com
Date: The 15th of June 2005
Version: 2.0B
*****************************/
function ImageCreateFromBMP($filename){
// 序章:以二進位模式開啟檔案流
if(!$f1 = fopen($filename, 'rb')) return FALSE;
 
// 第一步:讀取BMP檔頭
$FILE = unpack('vfile_type/Vfile_size/Vreserved/Vbitmap_offset', fread($f1, 14));
if($FILE['file_type']!=19778) return FALSE; // BM
 
// 第二步:讀取BMP資訊
// 僅支援BITMAPINFOHEADER,不支援BITMAPV4HEADER及BITMAPV5HEADER
$BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel/Vcompression/Vsize_bitmap/Vhoriz_resolution/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1, 40));
$BMP['colors'] = pow(2, $BMP['bits_per_pixel']);
if($BMP['size_bitmap']==0) $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset'];
$BMP['bytes_per_pixel'] = $BMP['bits_per_pixel'] / 8;
$BMP['decal'] = ($BMP['width'] * $BMP['bytes_per_pixel'] / 4);
$BMP['decal'] -= floor($BMP['width'] * $BMP['bytes_per_pixel'] / 4);
$BMP['decal'] = 4 - (4 * $BMP['decal']);
if($BMP['decal']==4) $BMP['decal'] = 0;
 
// 第三步:讀取色盤資訊
$PALETTE = array();
if($BMP['colors'] < 16777216) $PALETTE = unpack('V'.$BMP['colors'], fread($f1, $BMP['colors'] * 4));
 
// 第四步:變換每一個畫素
// 尚不支援32bit, 32bit with BITFIELDS, 8bit with RLE8, 4bit with RLE4等格式
$IMG = fread($f1, $BMP['size_bitmap']);
$VIDE = chr(0);
 
$res = ImageCreateTrueColor($BMP['width'], $BMP['height']);
$P = 0;
$Y = $BMP['height'] - 1;
while($Y >= 0){
$X = 0;
while($X < $BMP['width']){
switch($BMP['bits_per_pixel']){
case 24: $COLOR = unpack('V', substr($IMG, $P, 3).$VIDE); break;
case 16: $COLOR = unpack('n', substr($IMG, $P, 2)); break;
case 8: $COLOR = unpack('n', $VIDE.substr($IMG, $P, 1)); break;
case 4:
$COLOR = unpack('n', $VIDE.substr($IMG, floor($P), 1));
if(($P*2)%2==0) $COLOR[1] = ($COLOR[1] >> 4);
else $COLOR[1] = ($COLOR[1] & 0x0F);
break;
case 1:
$COLOR = unpack('n', $VIDE.substr($IMG, floor($P), 1));
switch(($P * 8) % 8){
case 0: $COLOR[1] = $COLOR[1] >> 7; break;
case 1: $COLOR[1] = ($COLOR[1] & 0x40) >> 6; break;
case 2: $COLOR[1] = ($COLOR[1] & 0x20) >> 5; break;
case 3: $COLOR[1] = ($COLOR[1] & 0x10) >> 4; break;
case 4: $COLOR[1] = ($COLOR[1] & 0x8) >> 3; break;
case 5: $COLOR[1] = ($COLOR[1] & 0x4) >> 2; break;
case 6: $COLOR[1] = ($COLOR[1] & 0x2) >> 1; break;
case 7: $COLOR[1] = ($COLOR[1] & 0x1);
}
break;
default:
return FALSE;
}
if($BMP['bits_per_pixel']!=24) $COLOR[1] = $PALETTE[$COLOR[1]+1];
ImageSetPixel($res, $X, $Y, $COLOR[1]);
$X++;
$P += $BMP['bytes_per_pixel'];
}
$Y--;
$P += $BMP['decal'];
}
 
// 終章:關閉檔案,回傳新圖像
fclose($f1);
return $res;
}
 
/* 文字修整 */
function CleanStr($str, $IsAdmin=false){
$str = trim($str); // 去除前後多餘空白
if(get_magic_quotes_gpc()) $str = stripslashes($str); // "\"斜線符號去除
if(!($IsAdmin && CAP_ISHTML)) $str = preg_replace('/&(#[0-9]+|[a-z]+);/i', "&$1;", htmlspecialchars($str)); // 非管理員或管理員自己取消HTML使用:HTML標籤禁用
else{ // 管理員開啟HTML
$str = str_replace('>', '&gt;', $str); // 先將每個 > 都轉碼
$str = preg_replace('/(<.*?)&gt;/', '$1>', $str); // 如果有<...&gt;則轉回<...>成為正常標籤
}
return $str;
}
 
/* 適用UTF-8環境的擬substr,取出特定數目字元
原出處:Sea Otter @ 2005.05.10
http://www.meyu.net/star/viewthread.php?tid=267&fpage=10 */
function str_cut($str, $maxlen=20){
$i = $l = 0; $len = strlen($str); $f = true; $return_str = $str;
while($i < $len){
$chars = ord($str{$i});
if($chars < 0x80){ $l++; $i++; }
elseif($chars < 0xe0){ $l++; $i += 2; }
elseif($chars < 0xf0){ $l += 2; $i += 3; }
elseif($chars < 0xf8){ $l++; $i += 4; }
elseif($chars < 0xfc){ $l++; $i += 5; }
elseif($chars < 0xfe){ $l++; $i += 6; }
if(($l >= $maxlen) && $f){
$return_str = substr($str, 0, $i);
$f = false;
}
if(($l > $maxlen) && ($i <= $len)){
$return_str = $return_str.'…';
break;
}
}
return $return_str;
}
 
/* 檢查瀏覽器和伺服器是否支援gzip壓縮方式 */
function CheckSupportGZip(){
$HTTP_ACCEPT_ENCODING = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : '';
if(headers_sent() || connection_aborted()) return 0; // 已送出資料,取消
if(!(function_exists('gzencode') && function_exists('ob_start') && function_exists('ob_get_clean'))) return 0; // 伺服器相關的套件或函式無法使用,取消
if(strpos($HTTP_ACCEPT_ENCODING, 'gzip')!==false) return 'gzip';
return 0;
}
 
/* 封鎖 IP / Hostname / DNSBL 綜合性檢查 */
function BanIPHostDNSBLCheck($IP, $HOST, &$baninfo){
if(!BAN_CHECK) return false; // Disabled
global $BANPATTERN, $DNSBLservers, $DNSBLWHlist;
 
// IP/Hostname Check
$HOST = strtolower($HOST);
$checkTwice = ($IP != $HOST); // 是否需檢查第二次
$IsBanned = false;
foreach($BANPATTERN as $pattern){
if(substr_count($pattern, '/')==2){ // RegExp
$pattern .= 'i';
}elseif(strpos($pattern, '*')!==false || strpos($pattern, '?')!==false){ // Wildcard
$pattern = '/^'.str_replace(array('.', '*', '?'), array('\.', '.*', '.?'), $pattern).'$/i';
}else{ // Full-text
if($IP==$pattern || ($checkTwice && $HOST==strtolower($pattern))){ $IsBanned = true; break; }
continue;
}
if(preg_match($pattern, $HOST) || ($checkTwice && preg_match($pattern, $IP))){ $IsBanned = true; break; }
}
if($IsBanned){ $baninfo = _T('ip_banned'); return true; }
 
// DNS-based Blackhole List(DNSBL) 黑名單
if(!$DNSBLservers[0]) return false; // Skip check
if(array_search($IP, $DNSBLWHlist)!==false) return false; // IP位置在白名單內
$rev = implode('.', array_reverse(explode('.', $IP)));
$lastPoint = count($DNSBLservers) - 1; if($DNSBLservers[0] < $lastPoint) $lastPoint = $DNSBLservers[0];
$isListed = false;
for($i = 1; $i <= $lastPoint; $i++){
$query = $rev.'.'.$DNSBLservers[$i].'.'; // FQDN
$result = gethostbyname($query);
if($result && ($result != $query)){ $isListed = $DNSBLservers[$i]; break; }
}
if($isListed){ $baninfo = _T('ip_dnsbl_banned',$isListed); return true; }
return false;
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_pte.php
@@ -0,0 +1,73 @@
<?php
/*
Pixmicat! Template-Embedded Library v070606
by: scribe & RT
$Id$
*/
 
class PTELibrary{
var $tpl_block, $tpl;
 
/* 開啟樣板檔案並取出區塊 */
function PTELibrary($tplname){
$this->tpl_block = array();
$this->tpl = file_get_contents($tplname);
}
 
/* 回傳區塊樣板碼並快取 */
function _readBlock($blockName){
if(!isset($this->tpl_block[$blockName])){ // 是否找過
if(preg_match('/<!--&'.$blockName.'-->(.*)<!--\/&'.$blockName.'-->/smU', $this->tpl, $matches))
$this->tpl_block[$blockName] = $matches[1]; // 找到了存入陣列快取
else
$this->tpl_block[$blockName] = false; // 找過但沒找到
}
return $this->tpl_block[$blockName];
}
 
/* 將樣版的標籤取代為正確的字串並傳回 */
function ParseBlock($blockName, $ary_val){
if(($tmp_block = $this->_readBlock($blockName))===false) return ""; // 找無
$tmp_block = $this->EvalFOREACH($tmp_block, $ary_val); // 解析FOREACH敘述
$tmp_block = $this->EvalIF($tmp_block, $ary_val); // 解析IF敘述
$tmp_block = $this->EvalInclude($tmp_block, $ary_val); // 解析引用
return @str_replace(@array_keys($ary_val), @array_values($ary_val), $tmp_block);
}
 
/* 解析IF敘述 */
function EvalIF($tpl, $ary){
$tmp_tpl = $tpl;
if(preg_match_all('/<!--&IF\((\$.*),\'(.*)\',\'(.*)\'\)-->/smU', $tmp_tpl, $matches, PREG_SET_ORDER)){
foreach($matches as $submatches){
$vari = $submatches[1]; $iftrue = $submatches[2]; $iffalse = $submatches[3];
$tmp_tpl = @str_replace($submatches[0], ($ary['{'.$vari.'}'] ? $this->EvalInclude($iftrue, $ary) : $this->EvalInclude($iffalse, $ary)), $tmp_tpl);
}
}
return $tmp_tpl;
}
/* 解析FOREACH敘述 */
function EvalFOREACH($tpl, $ary){
$tmp_tpl = $tpl;
if(preg_match_all('/<!--&FOREACH\((\$.*),\'(.*)\'\)-->/smU', $tmp_tpl, $matches, PREG_SET_ORDER)){
foreach($matches as $submatches){
$vari = $submatches[1]; $block = $submatches[2];
$foreach_tmp = '';
if(isset($ary['{'.$vari.'}']) && is_array($ary['{'.$vari.'}']))
foreach($ary['{'.$vari.'}'] as $eachvar)
$foreach_tmp .= $this->ParseBlock($block, $eachvar);
$tmp_tpl = @str_replace($submatches[0], $foreach_tmp, $tmp_tpl);
}
}
return $tmp_tpl;
}
/* 解析區塊引用 */
function EvalInclude($tpl, $ary){
$tmp_tpl = $tpl;
if(preg_match_all('/<!--&(.*)\/-->/smU', $tmp_tpl, $matches, PREG_SET_ORDER))
foreach($matches as $submatches)
$tmp_tpl = str_replace($submatches[0], $this->ParseBlock($submatches[1], $ary), $tmp_tpl);
return $tmp_tpl;
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pms.php
@@ -0,0 +1,97 @@
<?php
/**
* Pixmicat! Module System
*
* 增加掛載點供函式掛上並在需要時依序呼叫以動態改變內容或達成各種效果
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PMS{
var $ENV;
var $moduleInstance, $moduleLists;
var $hookPoints;
 
/* Constructor */
function PMS($ENV){
$this->ENV = $ENV; // 環境變數
// 掛載點
$this->hookPoints = array(
'Head'=>array(), 'Toplink'=>array(), 'PostInfo'=>array(), 'PostForm'=>array(),
'ThreadFront'=>array(), 'ThreadRear'=>array(),
'ThreadPost'=>array(), 'ThreadReply'=>array(),
'Foot'=>array(), 'ModulePage'=>array(),
'AdminList'=>array(), 'RegistBegin'=>array(), 'RegistBeforeCommit'=>array(), 'UsageExceed'=>array()
);
$this->moduleInstance = array(); // 存放各模組實體
$this->moduleLists = array(); // 存放各模組類別名稱
}
 
// 模組載入相關
/* 進行初始化 */
function init($specificModule=false){
if($specificModule!==false){ // 有指定載入模組
// 搜尋載入模組列表有沒有,沒有就直接取消程式
if(array_search($specificModule, $this->ENV['MODULE.LOADLIST'])===false) return false;
}
$this->loadModules($specificModule); $this->autoHookMethods();
return true;
}
 
/* 載入擴充模組 */
function loadModules($specificModule=false){
$loadlist = $specificModule ? array($specificModule) : $this->ENV['MODULE.LOADLIST'];
foreach($loadlist as $f){
$mpath = $this->ENV['MODULE.PATH'].$f.'.php';
if(is_file($mpath)){
include($mpath);
$this->moduleInstance[$f] = new $f();
$this->moduleLists[] = $f;
}
}
}
 
/* 取得載入模組列表 */
function getLoadedModules(){
return $this->moduleLists;
}
 
/* 取得特定模組方法列表 */
function getModuleMethods($module){
return array_search($module, $this->moduleLists)!==false ? get_class_methods($module) : array();
}
 
// 提供給模組的取用資訊
/* 取得模組註冊獨立頁面之網址 */
function getModulePageURL($name){
return $this->ENV['MODULE.PAGE'].$name;
}
 
// 模組掛載與使用相關
/* 自動掛載相關模組方法於掛載點 */
function autoHookMethods(){
foreach(array_keys($this->hookPoints) as $h){
foreach($this->moduleLists as $m)
if(method_exists($this->moduleInstance[$m], 'autoHook'.$h)){
$this->hookModuleMethod($h, array(&$this->moduleInstance[$m], 'autoHook'.$h));
}
}
}
 
/* 將模組方法掛載於特定掛載點 */
function hookModuleMethod($hookPoint, $methodObject){
if(isset($this->hookPoints[$hookPoint])) $this->hookPoints[$hookPoint][] = $methodObject;
}
 
/* 使用模組方法 */
function useModuleMethods($hookPoint, $parameter){
$imax = count($this->hookPoints[$hookPoint]);
for($i = 0; $i < $imax; $i++){
call_user_func_array($this->hookPoints[$hookPoint][$i], $parameter);
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/fileio/fileio.normal.php
@@ -0,0 +1,51 @@
<?php
/**
* FileIO Normal 本機儲存 API
*
* 以本機硬碟空間作為圖檔儲存的方式,並提供一套方法供程式管理圖片
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class FileIO{
var $path, $imgPath, $thumbPath;
 
/* private 藉由檔名分辨圖檔存放位置 */
function _getImagePhysicalPath($imgname){
return (substr($imgname, -5)=='s.jpg' ? $this->thumbPath : $this->imgPath).$imgname;
}
 
function FileIO($parameter='', $ENV){
$this->path = $ENV['PATH'];
$this->imgPath = $this->path.$ENV['IMG'];
$this->thumbPath = $this->path.$ENV['THUMB'];
}
 
function init(){
return true;
}
 
function imageExists($imgname){
return file_exists($this->_getImagePhysicalPath($imgname));
}
 
function deleteImage($imgname){
if(is_array($imgname)){ foreach($imgname as $i){ if(!@unlink($this->_getImagePhysicalPath($i))) return false; } return true; }
else{ return @unlink($this->_getImagePhysicalPath($imgname)); }
}
 
function uploadImage($imgname='', $imgpath='', $imgsize=0){
return false;
}
 
function getImageFilesize($imgname){
return @filesize($this->_getImagePhysicalPath($imgname));
}
 
function getImageURL($imgname){
return $this->getImageLocalURL($imgname);
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/fileio/fileio.ftp.php
@@ -0,0 +1,95 @@
<?php
/**
* FileIO FTP 遠端儲存 API
*
* 以遠端硬碟空間作為圖檔儲存的方式 (以 FTP 存取),並提供一套方法供程式管理圖片
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class FileIO{
var $conn, $parameter;
var $IFS;
 
/* private 登入 FTP */
function _ftp_login(){
if($this->conn) return true;
$this->conn = ftp_connect($this->parameter[0], $this->parameter[1]);
if($result = @ftp_login($this->conn, $this->parameter[2], $this->parameter[3])){
if($this->parameter[4]=='PASV') ftp_pasv($this->conn, true); // 被動模式
ftp_set_option($this->conn, FTP_TIMEOUT_SEC, 120); // 延長 Timeout 至 120 秒
@ftp_chdir($this->conn, $this->parameter[5]);
}
return $result;
}
 
/* private 關閉 FTP 及儲存索引檔 */
function _ftp_close(){
if($this->conn) ftp_close($this->conn); // 有開啟 FTP 連線則關閉
$this->IFS->saveIndex(); // 索引表更新
}
 
function FileIO($parameter, $ENV){
require($ENV['IFS.PATH']);
$this->IFS = new IndexFS($ENV['IFS.LOG']); // IndexFS 物件
$this->IFS->openIndex();
register_shutdown_function(array($this, '_ftp_close')); // 設定解構元 (PHP 結束前執行)
set_time_limit(120); // 執行時間 120 秒 (FTP 傳輸過程可能很長)
$this->parameter = $parameter; // 將參數重新解析
/*
[0] : FTP 伺服器位置
[1] : FTP 伺服器埠號
[2] : FTP 使用者帳號
[3] : FTP 使用者密碼
[4] : 是否使用被動模式? (PASV: 使用, NOPASV: 不使用)
[5] : FTP 預設工作目錄
[6] : 工作目錄對應 URL
*/
}
 
function init(){
return true;
}
 
function imageExists($imgname){
return $this->IFS->beRecord($imgname);
}
 
function deleteImage($imgname){
if(!$this->_ftp_login()) return false;
if(is_array($imgname)){
foreach($imgname as $i){
if(!ftp_delete($this->conn, $i)) return false;
$this->IFS->delRecord($i); // 自索引中刪除
}
return true;
}
else{
if($result = ftp_delete($this->conn, $imgname)) $this->IFS->delRecord($imgname);
return $result;
}
}
 
function uploadImage($imgname='', $imgpath='', $imgsize=0){
if($imgname=='') return true; // 支援上傳方法
if(!$this->_ftp_login()) return false;
$result = ftp_put($this->conn, $imgname, $imgpath, FTP_BINARY);
if($result){
$this->IFS->addRecord($imgname, $imgsize, ''); // 加入索引之中
unlink($imgpath); // 確實上傳後刪除本機暫存
}
return $result;
}
 
function getImageFilesize($imgname){
if($rc = $this->IFS->getRecord($imgname)) return $rc['imgSize'];
return false;
}
 
function getImageURL($imgname){
return $this->IFS->beRecord($imgname) ? $this->parameter[6].$imgname : false;
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/fileio/ifs.php
@@ -0,0 +1,117 @@
<?php
/**
* FileIO Index File System
*
* 把遠端圖檔的各種屬性作本機快取及記錄,方便程式取用
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class IndexFS{
var $logfile, $backend, $index, $modified;
 
/* 建構元 */
function IndexFS($logfile){
$this->logfile = $logfile; // 索引記錄檔位置
}
 
/* 初始化 */
function init(){
switch($this->backend){
case 'log':
touch($this->logfile); chmod($this->logfile, 0666); // 建立索引檔
break;
case 'sqlite2':
$execText = 'CREATE TABLE IndexFS (
"imgName" VARCHAR(20) NOT NULL PRIMARY KEY,
"imgSize" INTEGER NOT NULL,
"imgURL" VARCHAR(255) NOT NULL
); CREATE INDEX IDX_IndexFS_imgName ON IndexFS(imgName);';
sqlite_exec($this->index, $execText);
break;
}
}
 
/* 開啟索引檔並讀入 */
function openIndex(){
if(extension_loaded('SQLite')){
$this->backend = 'sqlite2';
$this->index = sqlite_open($this->logfile, 0666);
if(sqlite_num_rows(sqlite_query($this->index, "SELECT name FROM sqlite_master WHERE name LIKE 'IndexFS'"))===0) $this->init();
}else{
$this->backend = 'log';
$this->modified = false;
if(!file_exists($this->logfile)){ $this->init(); return; }
if(filesize($this->logfile)==0) return;
$indexlog = file($this->logfile); $indexlog_count = count($indexlog); // 讀入索引檔並計算目前筆數
$this->index = array();
for($i = 0; $i < $indexlog_count; $i++){
if(!($trimline = rtrim($indexlog[$i]))) continue; // 本行無意義
$field = explode("\t\t", $trimline);
$this->index[$field[0]] = array('imgSize' => $field[1], 'imgURL' => $field[2]);
// 索引格式: 檔名 檔案大小 對應路徑
}
unset($indexlog);
}
}
 
/* 索引是否存在 */
function beRecord($id){
switch($this->backend){
case 'log':
return isset($this->index[$id]);
case 'sqlite2':
return (sqlite_fetch_array(sqlite_query($this->index, 'SELECT imgName FROM IndexFS WHERE imgName = "'.sqlite_escape_string($id).'"'), SQLITE_ASSOC) ? true : false);
}
}
 
/* 取得一筆索引 */
function getRecord($id){
switch($this->backend){
case 'log':
return isset($this->index[$id]) ? $this->index[$id] : false;
case 'sqlite2':
return sqlite_fetch_array(sqlite_query($this->index, 'SELECT * FROM IndexFS WHERE imgName = "'.sqlite_escape_string($id).'"'), SQLITE_ASSOC);
}
}
 
/* 新增一筆索引 */
function addRecord($id, $imgSize, $imgURL){
switch($this->backend){
case 'log':
$this->modified = true;
$this->index[$id] = array('imgSize' => $imgSize, 'imgURL' => $imgURL); // 加入索引之中
break;
case 'sqlite2':
sqlite_exec($this->index, 'INSERT INTO IndexFS (imgName, imgSize, imgURL) VALUES ("'.sqlite_escape_string($id).'", '.sqlite_escape_string($imgSize).', "'.sqlite_escape_string($imgURL).'");');
break;
}
}
 
/* 刪除一筆索引 */
function delRecord($id){
switch($this->backend){
case 'log':
if(isset($this->index[$id])){ unset($this->index[$id]); $this->modified = true; return true; }
return false;
case 'sqlite2':
return sqlite_exec($this->index, 'DELETE FROM IndexFS WHERE imgName = "'.sqlite_escape_string($id).'";');
}
}
 
/* 儲存索引變更 */
function saveIndex(){
if($this->backend=='log' && $this->modified){ // 如果有修改索引就回存
$indexlog = '';
if(count($this->index)) foreach($this->index as $ikey => $ival){ $indexlog .= $ikey."\t\t".$ival['imgSize']."\t\t".$ival['imgURL']."\n"; } // 有資料才跑迴圈
$fp = fopen($this->logfile, 'w');
fwrite($fp, $indexlog);
fclose($fp);
}elseif($this->backend=='sqlite2'){
sqlite_close($this->index);
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/fileio/fileio.imageshack.php
@@ -0,0 +1,151 @@
<?php
/**
* FileIO ImageShack 遠端儲存 API
*
* 以 ImageShack 存圖網站作為圖檔儲存的方式,並提供一套方法供程式管理圖片
*
* 使用此功能請遵守 ImageShack 網站的 Terms of Service,並注意以下條約:
* Terms specific to the XML API:
*
* - Website or software must already be developed or have a strategic plan to be developed in the near future.
* - Website or software users must be informed that ImageShack is providing free image hosting.
*
* Free implimentation support is offered to websites that have at least 500 unique visitors per day (users)
* or expect to achieve 500 users in the near future. Otherwise, the XML API is offered as is.
*
* 使用時請自律將 ImageShack 網站連結置於明顯處,並說明正使用其提供之免費圖檔存放功能。
* (http://reg.imageshack.us/content.php?page=linkto 可選擇喜歡方式使用)
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class FileIO{
var $userAgent, $parameter;
var $IFS;
 
/* private 傳檔案到 ImageShack 上面 (發送抓取請求) */
function _transloadImageShack($imgname){
if(!($fp = @fsockopen('www.imageshack.us', 80))) return false;
 
$argument = 'xml=yes&rembar=1&url='.$this->getImageLocalURL($imgname);
$out = "POST /transload.php HTTP/1.1\r\n";
$out .= 'Host: www.imageshack.us'."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
if($this->parameter[0]) $out .= 'Cookie: myimages='.$this->parameter[0]."\r\n"; // ImageShack Registration Key Cookie
$out .= 'Content-Length: '.strlen($argument)."\r\n\r\n";
$out .= $argument;
fwrite($fp, $out);
 
$result = '';
while(!feof($fp)){ $result .= fgets($fp, 128); }
fclose($fp);
 
if(strpos($result, '<'.'?xml version="1.0" encoding="iso-8859-1"?>')===false) return false;
else{
$returnValue = array();
$xmlData = explode("\n", $result);
foreach($xmlData as $xmlDatum){
$xmlDatum = trim($xmlDatum);
if($xmlDatum != '' && !eregi('links', $xmlDatum) && !eregi('xml', $xmlDatum)){
$xmlDatum = str_replace('>', '<', $xmlDatum);
$xmlDatum = explode('<', $xmlDatum);
if(count($xmlDatum) >= 3) $returnValue[$xmlDatum[1]] = $xmlDatum[2];
}
}
return $returnValue;
}
}
 
/* private 發出刪除圖片要求 (需填入 Registration Key) */
function _deleteImageShack($imgURL){
if(!$this->parameter[0]) return true; // 沒金鑰無法要求刪除故直接略過
$imgURL = parse_url($imgURL); // 分析 URL 結構準備重組
if(!($fp = @fsockopen($imgURL['host'], 80))) return false;
 
$out = 'GET /delete.php?l='.substr($imgURL['path'], 1).'&c='.$this->parameter[0].'&page=THIS_IS_A_FLAG HTTP/1.1'."\r\n";
$out .= 'Host: '.$imgURL['host']."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n\r\n";
fwrite($fp, $out);
 
$result = '';
while(!feof($fp)){ $result .= fgets($fp, 128); }
fclose($fp);
 
return (strpos($result, 'THIS_IS_A_FLAG')!==false ? true : false); // 偷吃步,偵測page是否為設定的特殊值
}
 
/* private 生成 ImageShack my.php 指向頁面位置 */
function _myphpImageShack($imgurl, $ishotlink){
if($ishotlink) return $imgurl; // 直連:直接傳回不需處理
$imgurl = parse_url($imgurl);
return 'http://'.$imgurl['host'].'/my.php?image='.basename($imgurl['path']);
}
 
/* private 儲存索引檔 */
function _setIndex(){
$this->IFS->saveIndex(); // 索引表更新
}
 
function FileIO($parameter, $ENV){
require($ENV['IFS.PATH']);
$this->IFS = new IndexFS($ENV['IFS.LOG']); // IndexFS 物件
$this->IFS->openIndex();
register_shutdown_function(array($this, '_setIndex')); // 設定解構元 (PHP 結束前執行)
set_time_limit(120); // 執行時間 120 秒 (傳輸過程可能很長)
$this->userAgent = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'; // Just for fun ;-)
$this->parameter = $parameter; // 將參數重新解析
/*
[0] : ImageShack 註冊金鑰 (即登入頁面 setlogin.php 網址後面附帶一串編碼) * 可不填,但功能會少
登入後亦可在 http://reg.imageshack.us/content.php?page=register 找到 Your Registration Code
*/
}
 
function init(){
return true;
}
 
function imageExists($imgname){
return $this->IFS->beRecord($imgname);
}
 
function deleteImage($imgname){
if(is_array($imgname)){
foreach($imgname as $i){
if(($rc = $this->IFS->getRecord($i)) && $this->_deleteImageShack($rc['imgURL'])) $this->IFS->delRecord($i); // 自索引中刪除
else return false; // 送出刪除要求失敗
}
return true;
}
else{
if(($rc = $this->IFS->getRecord($imgname)) && $this->_deleteImageShack($rc['imgURL'])){ $this->IFS->delRecord($imgname); return true; }
return false;
}
}
 
function uploadImage($imgname='', $imgpath='', $imgsize=0){
if($imgname=='') return true; // 支援上傳方法
if(substr($imgname, -5)=='s.jpg'){ unlink($imgpath); return true; } // 預覽圖不用上傳,直接刪除
$result = $this->_transloadImageShack($imgname);
if($result){
$this->IFS->addRecord($imgname, $imgsize, $result['image_link']); // 加入索引之中
list($w, $h) = explode('x', $result['resolution']); // 原圖解析度
// 判斷是否自動生成預覽圖 (長寬任一大於200像素) 並儲存預覽圖位置
$this->IFS->addRecord(substr($imgname, 0, 13).'s.jpg', ceil($imgsize / 4), (($w > 200 || $h > 200) ? $result['thumb_link'] : $result['image_link']));
unlink($imgpath); // 確實上傳後刪除本機暫存
}
return $result;
}
 
function getImageFilesize($imgname){
if($rc = $this->IFS->getRecord($imgname)) return $rc['imgSize'];
return false;
}
 
function getImageURL($imgname, $ishotlink = false){ // 是否使用熱連結直連圖檔位置 (極有可能被 Ban 網域!請慎用)
return ($rc = $this->IFS->getRecord($imgname)) ? (substr($imgname, -5)=='s.jpg' ? $rc['imgURL'] : $this->_myphpImageShack($rc['imgURL'], $ishotlink)) : false;
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/fileio/fileio.satellite.php
@@ -0,0 +1,170 @@
<?php
/**
* FileIO Satellite 衛星計畫後端
*
* 搭配 satellite.php/pl 利用遠端空間管理圖檔
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class FileIO{
var $userAgent, $parameter;
var $IFS;
 
/* private 測試連線並且初始化遠端衛星主機 */
function _initSatellite(){
if(!($fp = @fsockopen($this->parameter[0]['host'], 80))) return false;
 
$argument = 'mode=init&key='.$this->parameter[2];
$out = 'POST '.$this->parameter[0]['path']." HTTP/1.1\r\n";
$out .= 'Host: '.$this->parameter[0]['host']."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= 'Content-Length: '.strlen($argument)."\r\n\r\n";
$out .= $argument;
fwrite($fp, $out);
$result = fgets($fp, 128); // 取一次足以取到檔頭
fclose($fp);
 
return (strpos($result, '202 Accepted')!==false ? true : false); // 檢查狀態值偵測是否傳輸成功
}
 
/* private 傳送抓取要求到遠端衛星主機上面 */
function _transloadSatellite($imgname){
if(!($fp = @fsockopen($this->parameter[0]['host'], 80))) return false;
 
$argument = 'mode=transload&key='.$this->parameter[2].'&imgurl='.$this->getImageLocalURL($imgname).'&imgname='.$imgname;
$out = 'POST '.$this->parameter[0]['path']." HTTP/1.1\r\n";
$out .= 'Host: '.$this->parameter[0]['host']."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= 'Content-Length: '.strlen($argument)."\r\n\r\n";
$out .= $argument;
fwrite($fp, $out);
$result = fgets($fp, 128); // 取一次足以取到檔頭
fclose($fp);
 
return (strpos($result, '202 Accepted')!==false ? true : false); // 檢查狀態值偵測是否傳輸成功
}
 
/* private 直接傳送檔案到遠端衛星主機上面 */
function _uploadSatellite($imgname, $imgpath){
srand((double) microtime()*1000000);
$boundary = '---------------------'.substr(md5(rand(0,32000)), 0, 10); // 生成分隔線
 
$argument = ''; // 資料暫存
// 一般欄位資料轉換
$formField = array('mode' => 'upload', 'key' => $this->parameter[2], 'imgname' => $imgname);
foreach($formField as $ikey => $ival){
$argument .= "--$boundary\r\n";
$argument .= "Content-Disposition: form-data; name=\"".$ikey."\"\r\n\r\n";
$argument .= $ival."\r\n";
$argument .= "--$boundary\r\n";
}
// 上傳檔案欄位資料轉換
$imginfo = getimagesize($imgpath); // 取得圖檔資訊
$argument .= "--$boundary\r\n";
$argument .= 'Content-Disposition: form-data; name="imgfile"; filename="'.$imgname.'"'."\r\n";
$argument .= 'Content-Type: '.$imginfo['mime']."\r\n\r\n";
$argument .= join('', file($imgpath))."\r\n";
$argument .= "--$boundary--\r\n";
 
$out = 'POST '.$this->parameter[0]['path']." HTTP/1.1\r\n";
$out .= 'Host: '.$this->parameter[0]['host']."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n";
$out .= "Content-Type: multipart/form-data, boundary=$boundary\r\n";
$out .= 'Content-Length: '.strlen($argument)."\r\n\r\n";
$out .= $argument;
 
if(!($fp = @fsockopen($this->parameter[0]['host'], 80))) return false;
fwrite($fp, $out);
$result = fgets($fp, 128);
fclose($fp);
 
return (strpos($result, '202 Accepted')!==false ? true : false);
}
 
/* private 發出刪除圖片要求 */
function _deleteSatellite($imgname){
if(!($fp = @fsockopen($this->parameter[0]['host'], 80))) return false;
 
$argument = 'mode=delete&key='.$this->parameter[2].'&imgname='.$imgname;
$out = 'POST '.$this->parameter[0]['path']." HTTP/1.1\r\n";
$out .= 'Host: '.$this->parameter[0]['host']."\r\n";
$out .= 'User-Agent: '.$this->userAgent."\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= 'Content-Length: '.strlen($argument)."\r\n\r\n";
$out .= $argument;
fwrite($fp, $out);
$result = fgets($fp, 128);
fclose($fp);
 
return (strpos($result, '202 Accepted')!==false ? true : false);
}
 
/* private 儲存索引檔 */
function _setIndex(){
$this->IFS->saveIndex(); // 索引表更新
}
 
function FileIO($parameter, $ENV){
require($ENV['IFS.PATH']);
$this->IFS = new IndexFS($ENV['IFS.LOG']); // IndexFS 物件
$this->IFS->openIndex();
register_shutdown_function(array($this, '_setIndex')); // 設定解構元 (PHP 結束前執行)
set_time_limit(120); // 執行時間 120 秒 (傳輸過程可能很長)
$this->userAgent = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'; // Just for fun ;-)
$this->parameter = $parameter; // 將參數重新解析
$this->parameter[0] = parse_url($this->parameter[0]); // URL 位置拆解
/*
[0] : 衛星程式遠端 URL 位置
[1] : 是否使用 Transload 方式要求衛星程式抓取圖檔 (true:是 false:否,使用傳統 HTTP 上傳)
[2] : 傳輸認證金鑰
[3] : 遠端目錄對應 URL
*/
}
 
function init(){
return $this->_initSatellite();
}
 
function imageExists($imgname){
return $this->IFS->beRecord($imgname);
}
 
function deleteImage($imgname){
if(is_array($imgname)){
foreach($imgname as $i){
if(!$this->_deleteSatellite($i)) return false;
$this->IFS->delRecord($i); // 自索引中刪除
}
return true;
}
else{
if($result = $this->_deleteSatellite($imgname)) $this->IFS->delRecord($imgname);
return $result;
}
}
 
function uploadImage($imgname='', $imgpath='', $imgsize=0){
if($imgname=='') return true; // 支援上傳方法
$result = $this->parameter[1] ? $this->_transloadSatellite($imgname) : $this->_uploadSatellite($imgname, $imgpath); // 選擇傳輸方法
if($result){
$this->IFS->addRecord($imgname, $imgsize, ''); // 加入索引之中
unlink($imgpath); // 確實上傳後刪除本機暫存
}
return $result;
}
 
function getImageFilesize($imgname){
if($rc = $this->IFS->getRecord($imgname)) return $rc['imgSize'];
return false;
}
 
function getImageURL($imgname){
return $this->IFS->beRecord($imgname) ? $this->parameter[3].$imgname : false;
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pio/pio.log.php
@@ -0,0 +1,547 @@
<?php
/**
* PIO Log API
*
* 提供存取以 Log 檔案構成的資料結構後端的物件
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PIOlog{
var $ENV, $logfile, $treefile, $porderfile; // Local Constant
var $logs, $trees, $LUT, $porder, $torder, $prepared; // Local Global
//var $memcached, $mid;
 
function PIOlog($connstr='', $ENV){
$this->ENV = $ENV;
$this->logs = $this->trees = $this->LUT = $this->porder = $this->torder = array();
$this->prepared = 0;
//$this->mid = md5($_SERVER['SCRIPT_FILENAME']); // Unique ID
//$this->memcached = false; // memcached object (null: use, false: don't use)
 
if($connstr) $this->dbConnect($connstr);
}
 
/* private 設定 memcached 資料
function _memcacheSet($isAnalysis=true){
if(!$this->_memcachedEstablish()) return false;
$this->memcached->set('pmc'.$this->mid.'_isset', true);
// 是否需要將每行資料分析為陣列
$this->memcached->set('pmc'.$this->mid.'_logs', ($isAnalysis ? array_map(array($this, '_AnalysisLogs'), $this->logs) : $this->logs));
$this->memcached->set('pmc'.$this->mid.'_trees', $this->trees);
$this->memcached->set('pmc'.$this->mid.'_LUT', $this->LUT);
$this->memcached->set('pmc'.$this->mid.'_porder', $this->porder);
$this->memcached->set('pmc'.$this->mid.'_torder', $this->torder);
}
 
/* private 取得 memcached 資料
function _memcacheGet(){
if(!$this->_memcachedEstablish()) return false;
if($this->memcached->get('pmc'.$this->mid.'_isset')){ // 有資料
$this->logs = $this->memcached->get('pmc'.$this->mid.'_logs');
$this->trees = $this->memcached->get('pmc'.$this->mid.'_trees');
$this->LUT = $this->memcached->get('pmc'.$this->mid.'_LUT');
$this->porder = $this->memcached->get('pmc'.$this->mid.'_porder');
$this->torder = $this->memcached->get('pmc'.$this->mid.'_torder');
return true;
}else return false;
}*/
 
/* private 把每一行 Log 解析轉換成陣列資料 */
function _AnalysisLogs($line){
$tline = array();
list($tline['no'], $tline['resto'], $tline['md5chksum'], $tline['category'], $tline['tim'], $tline['ext'], $tline['imgw'], $tline['imgh'], $tline['imgsize'], $tline['tw'], $tline['th'], $tline['pwd'], $tline['now'], $tline['name'], $tline['email'], $tline['sub'], $tline['com'], $tline['host'], $tline['status']) = explode(',', $line);
return array_reverse($tline);
}
 
/* private 建立 memcached 實體
function _memcachedEstablish(){
if(!extension_loaded('memcache')) return ($this->memcached = false);
if(is_null($this->memcached)){
$this->memcached = new Memcache;
if(!$this->memcached->pconnect('localhost')) return ($this->memcached = false);
return true;
}
return ($this->memcached===false) ? false : true;
}*/
 
/* private 將回文放進陣列 */
function _includeReplies($posts){
$torder_flip = array_flip($this->torder);
foreach($posts as $post){
if(array_key_exists($post, $torder_flip)){ // 討論串首篇
$posts = array_merge($posts, $this->trees[$post]);
}
}
return array_merge(array(), array_unique($posts)); // 去除重複值
}
 
/* private 取代 , 成為 &#44; 避免衝突 */
function _replaceComma($txt){
return str_replace(',', '&#44;', $txt);
}
 
/* private 由編號取出資料分析成陣列 */
function _ArrangeArrayStructure($line){
$line = (array)$line; // 全部視為Arrays
$posts = array();
foreach($line as $i){
if(!is_array($this->logs[$this->LUT[$i]])){ // 進行分析轉換
$line = $this->logs[$this->LUT[$i]];
if($line=='') continue;
$tline = array();
list($tline['no'], $tline['resto'], $tline['md5chksum'], $tline['category'], $tline['tim'], $tline['ext'], $tline['imgw'], $tline['imgh'], $tline['imgsize'], $tline['tw'], $tline['th'], $tline['pwd'], $tline['now'], $tline['name'], $tline['email'], $tline['sub'], $tline['com'], $tline['host'], $tline['status']) = explode(',', $line);
$this->logs[$this->LUT[$i]] = array_reverse($tline); // list()是由右至左代入的
}
$posts[] = $this->logs[$this->LUT[$i]];
}
return $posts;
}
 
/* PIO模組版本 */
function pioVersion(){
return '0.4 (v20070605)';
}
 
/* 處理連線字串/連接 */
function dbConnect($connStr){
if(preg_match('/^log:\/\/(.*)\:(.*)\/$/i', $connStr, $linkinfos)){
$this->logfile = $this->ENV['BOARD'].'/'.$linkinfos[1]; // 投稿文字記錄檔檔名
$this->treefile = $this->ENV['BOARD'].'/'.$linkinfos[2]; // 樹狀結構記錄檔檔名
$this->porderfile = $this->ENV['LUTCACHE']; // LUT索引查找表暫存檔案
}
}
 
/* 初始化 */
function dbInit(){
$chkfile = array($this->logfile, $this->treefile, $this->porderfile);
// 自動建置
foreach($chkfile as $value){
if(!is_file($value)){ // 檔案不存在
$fp = fopen($value, 'w');
stream_set_write_buffer($fp, 0);
if($value==$this->logfile) fwrite($fp, '1,0,,,0,,0,0,,0,0,,05/01/01(六)00:00,'.$this->ENV['NONAME'].',,'.$this->ENV['NOTITLE'].','.$this->ENV['NOCOMMENT'].',,,'); // PIO Structure V2
if($value==$this->treefile) fwrite($fp, '1');
if($value==$this->porderfile) fwrite($fp, '1');
fclose($fp);
unset($fp);
@chmod($value, 0666);
}
}
return true;
}
 
/* 準備/讀入 */
function dbPrepare($reload=false, $transaction=true){
if($this->prepared && !$reload) return true;
if($reload && $this->prepared) $this->porder = $this->torder = $this->LUT = $this->logs = $this->trees = array();
//if($this->_memcacheGet()){ $this->prepared = 1; return true; } // 如果 memcache 有快取則直接使用
 
$this->logs = file($this->logfile); // Log每行原始資料
if(!file_exists($this->porderfile)){ // LUT不在,重生成
$lut = '';
foreach($this->logs as $line){
if(!isset($line)) continue;
$tmp = explode(',', $line); $lut .= $tmp[0]."\r\n";
}
$fp = fopen($this->porderfile, 'w'); // LUT
stream_set_write_buffer($fp, 0);
flock($fp, LOCK_EX); // 鎖定檔案
fwrite($fp, $lut);
flock($fp, LOCK_UN); // 解鎖
fclose($fp);
}
$this->porder = array_map('rtrim', file($this->porderfile)); // 文章編號陣列
$this->LUT = array_flip($this->porder); // LUT索引查找表
 
$tree = array_map('rtrim', file($this->treefile));
foreach($tree as $treeline){ // 解析樹狀結構製成索引
if($treeline=='') continue;
$tline = explode(',', $treeline);
$this->torder[] = $tline[0]; // 討論串首篇編號陣列
$this->trees[$tline[0]] = $tline; // 特定編號討論串完整結構陣列
}
//$this->_memcacheSet(); // 把目前資料設定到 memcached 內
$this->prepared = 1;
}
 
/* 提交/儲存 */
function dbCommit(){
if(!$this->prepared) return false;
 
$log = $tree = $lut = '';
$this->logs = array_merge(array(), $this->logs); // 更新logs鍵值
$this->torder = array_merge(array(), $this->torder); // 更新torder鍵值
$this->porder = $this->LUT = array(); // 重新生成索引
 
foreach($this->logs as $line){
if(!isset($line)) continue;
if(is_array($line)){ // 已被分析過
$log .= implode(',', $line).",\r\n";
$lut .= ($this->porder[] = $line['no'])."\r\n";
}else{ // 尚未分析過
$log .= $line;
$tmp = explode(',', $line); $lut .= ($this->porder[] = $tmp[0])."\r\n";
}
}
$this->LUT = array_flip($this->porder);
$tcount = count($this->trees);
for($tline = 0; $tline < $tcount; $tline++){
$tree .= $this->isThread($this->torder[$tline]) ? implode(',', $this->trees[$this->torder[$tline]])."\r\n" : '';
}
//$this->_memcacheSet(false); // 更新快取 (不需要再分析)
 
$fp = fopen($this->logfile, 'w'); // Log
stream_set_write_buffer($fp, 0);
flock($fp, LOCK_EX); // 鎖定檔案
fwrite($fp, $log);
flock($fp, LOCK_UN); // 解鎖
fclose($fp);
 
$fp = fopen($this->treefile, 'w'); // tree
stream_set_write_buffer($fp, 0);
flock($fp, LOCK_EX); // 鎖定檔案
fwrite($fp, $tree);
flock($fp, LOCK_UN); // 解鎖
fclose($fp);
 
$fp = fopen($this->porderfile, 'w'); // LUT
stream_set_write_buffer($fp, 0);
flock($fp, LOCK_EX); // 鎖定檔案
fwrite($fp, $lut);
flock($fp, LOCK_UN); // 解鎖
fclose($fp);
}
 
/* 優化資料表 */
function dbOptimize($doit=false){
return false; // 不支援
}
 
/* 匯入資料來源 */
function dbImport($data){
$arrData = explode("\r\n", $data);
$arrData_cnt = count($arrData) - 1; // 最後一個是空的
$arrTree = array();
$tree = $logs = $lut = '';
for($i = 0; $i < $arrData_cnt; $i++){
$line = explode(',', $arrData[$i], 4); // 切成四段
$logs .= $line[0].','.$line[1].','.$line[3]."\r\n"; // 重建討論結構
$lut .= $line[0]."\r\n"; // 重建 LUT 查找表結構
if($line[1]==0){ // 首篇
if(!isset($arrTree[$line[0]])) $arrTree[$line[0]] = array($line[0]); // 僅自身一篇
else array_unshift($arrTree[$line[0]], $line[0]);
continue;
}
if(!isset($arrTree[$line[1]])) $arrTree[$line[1]] = array();
array_unshift($arrTree[$line[1]], $line[0]);
}
foreach($arrTree as $t) $tree .= implode(',', $t)."\r\n"; // 重建樹狀結構
$chkfile = array($this->logfile, $this->treefile, $this->porderfile);
foreach($chkfile as $value){
$fp = fopen($value, 'w');
stream_set_write_buffer($fp, 0);
if($value==$this->logfile) fwrite($fp, $logs);
if($value==$this->treefile) fwrite($fp, $tree);
if($value==$this->porderfile) fwrite($fp, $lut);
fclose($fp);
unset($fp);
@chmod($value, 0666);
}
return true;
}
 
/* 匯出資料來源 */
function dbExport(){
if(!$this->prepared) $this->dbPrepare();
$f = file($this->logfile);
$data = '';
foreach($f as $line){
$line = explode(',', $line, 3); // 分成三段 (最後一段特別長)
if($line[1]==0 && isset($this->trees[$line[0]])){
$lastno = array_pop($this->trees[$line[0]]);
$line2 = $this->fetchPosts($lastno);
$root = gmdate('Y-m-d H:i:s', substr($line2[0]['tim'], 0, 10)); // UTC 時間
unset($this->trees[$line[0]]); // 刪除表示已取過
}else{
$root = '0';
}
$data .= $line[0].','.$line[1].','.$root.','.$line[2];
}
return $data;
}
 
/* 文章數目 */
function postCount($resno=0){
if(!$this->prepared) $this->dbPrepare();
 
return $resno ? ($this->isThread($resno) ? count(@$this->trees[$resno]) : 0) : count($this->porder);
}
 
/* 討論串數目 */
function threadCount(){
if(!$this->prepared) $this->dbPrepare();
 
return count($this->torder);
}
 
/* 取得最後的文章編號 */
function getLastPostNo($state){
if(!$this->prepared) $this->dbPrepare();
 
switch($state){
case 'beforeCommit':
case 'afterCommit':
return reset($this->porder);
}
}
 
/* 輸出文章清單 */
function fetchPostList($resno=0, $start=0, $amount=0){
if(!$this->prepared) $this->dbPrepare();
 
$plist = array();
if($resno){
if($this->isThread($resno)){
if($start && $amount){
$plist = array_slice($this->trees[$resno], $start, $amount);
array_unshift($plist, $resno);
}
if(!$start && $amount) $plist = array_slice($this->trees[$resno], 0, $amount);
if(!$start && !$amount) $plist = $this->trees[$resno];
}
}else{
$plist = $amount ? array_slice($this->porder, $start, $amount) : $this->porder;
}
return $plist;
}
 
/* 輸出討論串清單 */
function fetchThreadList($start=0, $amount=0, $isDESC=false){
if(!$this->prepared) $this->dbPrepare();
$tmp_array = $this->torder;
if($isDESC) rsort($tmp_array); // 按編號遞減排序 (預設為按最後更新時間排序)
return $amount ? array_slice($tmp_array, $start, $amount) : $tmp_array;
}
 
/* 輸出文章 */
function fetchPosts($postlist){
if(!$this->prepared) $this->dbPrepare();
 
return $this->_ArrangeArrayStructure($postlist); // 輸出陣列結構
}
 
/* 刪除舊文 */
function delOldPostes(){
if(!$this->prepared) $this->dbPrepare();
 
$delPosts = @array_slice($this->porder, $this->ENV['LOG_MAX'] - 1); // 截出舊文編號陣列
if(count($delPosts)) return $this->removePosts($delPosts);
else return false;
}
 
/* 刪除舊附件 (輸出附件清單) */
function delOldAttachments($total_size, $storage_max, $warnOnly=true){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$rpord = $this->porder; sort($rpord); // 由舊排到新 (小->大)
$arr_warn = $arr_kill = array();
foreach($rpord as $post){
$logsarray = $this->_ArrangeArrayStructure($post); // 分析資料為陣列
if($FileIO->imageExists($logsarray[0]['tim'].$logsarray[0]['ext'])){ $total_size -= $FileIO->getImageFilesize($logsarray[0]['tim'].$logsarray[0]['ext']) / 1024; $arr_kill[] = $post; $arr_warn[$post] = 1; } // 標記刪除
if($FileIO->imageExists($logsarray[0]['tim'].'s.jpg')) $total_size -= $FileIO->getImageFilesize($logsarray[0]['tim'].'s.jpg') / 1024;
if($total_size < $storage_max) break;
}
return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);
}
 
/* 刪除文章 */
function removePosts($posts){
if(!$this->prepared) $this->dbPrepare();
 
$posts = $this->_includeReplies($posts); // 包含所有回文
$filelist = $this->removeAttachments($posts); // 欲刪除附件
$torder_flip = array_flip($this->torder);
$pcount = count($posts);
$logsarray = $this->_ArrangeArrayStructure($posts); // 分析資料為陣列
for($p = 0; $p < $pcount; $p++){
if(!isset($logsarray[$p])) continue;
if($logsarray[$p]['resto']==0){ // 討論串頭
unset($this->trees[$logsarray[$p]['no']]); // 刪除樹狀記錄
if(array_key_exists($logsarray[$p]['no'], $torder_flip)) unset($this->torder[$torder_flip[$logsarray[$p]['no']]]); // 從討論串首篇陣列中移除
}else{
// 從樹狀檔刪除
if(array_key_exists($logsarray[$p]['resto'], $this->trees)){
$tr_flip = array_flip($this->trees[$logsarray[$p]['resto']]);
unset($this->trees[$logsarray[$p]['resto']][$tr_flip[$posts[$p]]]);
}
}
unset($this->logs[$this->LUT[$logsarray[$p]['no']]]);
if(array_key_exists($logsarray[$p]['no'], $this->LUT)) unset($this->porder[$this->LUT[$logsarray[$p]['no']]]); // 從討論串編號陣列中移除
}
$this->LUT = array_flip($this->porder);
return $filelist;
}
 
/* 刪除附件 (輸出附件清單) */
function removeAttachments($posts){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$files = array();
$logsarray = $this->_ArrangeArrayStructure($posts); // 分析資料為陣列
$lcount = count($logsarray);
for($i = 0; $i < $lcount; $i++){
if($logsarray[$i]['ext']){
if($FileIO->imageExists($logsarray[$i]['tim'].$logsarray[$i]['ext'])) $files[] = $logsarray[$i]['tim'].$logsarray[$i]['ext'];
if($FileIO->imageExists($logsarray[$i]['tim'].'s.jpg')) $files[] = $logsarray[$i]['tim'].'s.jpg';
}
}
return $files;
}
 
/* 新增文章/討論串 */
function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false) {
if(!$this->prepared) $this->dbPrepare();
 
$tline = array($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, '');
$tline = array_map(array($this, '_replaceComma'), $tline); // 將資料內的 , 轉換 (Only Log needed)
array_unshift($this->logs, implode(',', $tline).",\r\n"); // 更新logs
array_unshift($this->porder, $no); // 更新porder
$this->LUT = array_flip($this->porder); // 更新LUT
 
// 更新torder及trees
if($resto){
$this->trees[$resto][] = $no;
if($age){
$torder_flip = array_flip($this->torder);
unset($this->torder[$torder_flip[$resto]]); // 先刪除舊有位置
array_unshift($this->torder, $resto); // 再移到頂端
}
}else{
$this->trees[$no][0] = $no;
array_unshift($this->torder, $no);
}
}
 
/* 檢查是否連續投稿 */
function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){
if(!$this->prepared) $this->dbPrepare();
 
$pcount = $this->postCount();
$lcount = ($pcount > $lcount) ? $lcount : $pcount;
for($i = 0; $i < $lcount; $i++){
$logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列
list($lcom, $lhost, $lpwd, $ltime) = array($logsarray[0]['com'], $logsarray[0]['host'], $logsarray[0]['pwd'], substr($logsarray[0]['tim'],0,-3));
if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) $pchk = 1;
else $pchk = 0;
if($this->ENV['PERIOD.POST'] && $pchk){ // 密碼比對符合且開啟連續投稿時間限制
if($timestamp - $ltime < $this->ENV['PERIOD.POST']) return true; // 投稿時間相距太短
if($timestamp - $ltime < $this->ENV['PERIOD.IMAGEPOST'] && $isupload) return true; // 附加圖檔的投稿時間相距太短
if($com == $lcom && !$isupload) return true; // 內文一樣
}
}
return false;
}
 
/* 檢查是否重複貼圖 */
function isDuplicateAttechment($lcount, $md5hash){
global $FileIO;
 
$pcount = $this->postCount();
$lcount = ($pcount > $lcount) ? $lcount : $pcount;
for($i = 0; $i < $lcount; $i++){
$logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列
if(!$logsarray[0]['md5chksum']) continue; // 無附加圖檔
if($logsarray[0]['md5chksum']==$md5hash){
if($FileIO->imageExists($logsarray[0]['tim'].$logsarray[0]['ext'])) return true; // 存在MD5雜湊相同的檔案
}
}
return false;
}
 
/* 有此討論串? */
function isThread($no){
if(!$this->prepared) $this->dbPrepare();
 
return isset($this->trees[$no]);
}
 
/* 搜尋文章 */
function searchPost($keyword,$field,$method){
if(!$this->prepared) $this->dbPrepare();
 
$foundPosts = array();
$keyword_cnt = count($keyword);
$pcount = $this->postCount();
for($i = 0; $i < $pcount; $i++){
$logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列
$found = 0;
foreach($keyword as $k){
if(strpos($logsarray[0][$field], $k)!==FALSE) $found++;
if($method=="OR" && $found) break;
}
if($method=="AND" && $found==$keyword_cnt) array_push($foundPosts, $logsarray[0]); // 全部都有找到 (AND交集搜尋)
elseif($method=="OR" && $found) array_push($foundPosts, $logsarray[0]); // 有找到 (OR聯集搜尋)
}
return $foundPosts;
}
 
/* 搜尋類別標籤 */
function searchCategory($category){
if(!$this->prepared) $this->dbPrepare();
 
$category = strtolower($category);
$foundPosts = array();
$pcount = $this->postCount();
for($i = 0; $i < $pcount; $i++){
$logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列
if(!($ary_category = $logsarray[0]['category'])) continue;
if(strpos(strtolower($ary_category), '&#44;'.$category.'&#44;')!==false) array_push($foundPosts, $logsarray[0]['no']); // 找到標籤,加入名單
}
return $foundPosts;
}
 
/* 取得文章屬性 */
function getPostStatus($status, $statusType){
if(!$this->prepared) $this->dbPrepare();
$returnValue = 0; // 回傳值
 
switch($statusType){
case 'TS': // 討論串是否鎖定
$returnValue = (strpos($status, 'T')!==false) ? 1 : 0; // 討論串是否鎖定
break;
default:
}
return $returnValue;
}
 
/* 設定文章屬性 */
function setPostStatus($no, $status, $statusType, $newValue){
if(!$this->prepared) $this->dbPrepare();
 
$scount = count($no);
for($i = 0; $i < $scount; $i++){
$statusType[$i] = explode(',', $statusType[$i]);
$newValue[$i] = explode(',', $newValue[$i]);
$st_count = count($statusType[$i]);
for($j = 0; $j < $st_count; $j++){
switch($statusType[$i][$j]){
case 'TS': // 討論串鎖定
if(strpos($status[$i], 'T')!==false && $newValue[$i][$j]==0)
$status[$i] = str_replace('T', '', $status[$i]); // 討論串解除鎖定
elseif(strpos($status[$i], 'T')===false && $newValue[$i][$j]==1)
$status[$i] .= 'T'; // 討論串鎖定
break;
default:
}
}
$this->_ArrangeArrayStructure($no[$i]); // 將資料變成陣列
$this->logs[$this->LUT[$no[$i]]]['status'] = $status[$i]; // 修改狀態
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pio/pio.sqlite.php
@@ -0,0 +1,459 @@
<?php
/**
* PIO SQLite API
*
* 提供存取以 SQLite 資料庫構成的資料結構後端的物件
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PIOsqlite{
var $ENV, $dbname, $tablename; // Local Constant
var $con, $prepared; // Local Global
 
function PIOsqlite($connstr='', $ENV){
$this->ENV = $ENV;
$this->prepared = 0;
if($connstr) $this->dbConnect($connstr);
}
 
/* private 攔截SQL錯誤 */
function _error_handler($errtext, $errline){
$err = "Pixmicat! SQL Error: $errtext, debug info: at line $errline";
trigger_error($err, E_USER_ERROR);
}
 
/* private 使用SQL字串和SQLite要求 */
function _sqlite_call($query){
return sqlite_query($this->con, $query);
}
 
/* private 由資源輸出陣列 */
function _ArrangeArrayStructure($line){
$posts = array();
while($row=sqlite_fetch_array($line, SQLITE_ASSOC)) $posts[] = $row;
return $posts;
}
 
/* private SQLite的sqlite_result頂替函數 */
function _sqlite_result($rh, $row, $field){
$currrow = sqlite_fetch_all($rh, SQLITE_NUM);
return $currrow[$row][$field];
}
 
/* PIO模組版本 */
function pioVersion(){
return '0.4 (v20070605)';
}
 
/* 處理連線字串/連接 */
function dbConnect($connStr){
// 格式: sqlite://SQLite檔案之位置/資料表/
// 示例: sqlite://pixmicat.db/imglog/
if(preg_match('/^sqlite:\/\/(.*)\/(.*)\/$/i', $connStr, $linkinfos)){
$this->dbname = $linkinfos[1]; // SQLite檔案之位置
$this->tablename = $linkinfos[2]; // 資料表名稱
}
}
 
/* 初始化 */
function dbInit($isAddInitData=true){
$this->dbPrepare();
if(sqlite_num_rows(sqlite_query($this->con, "SELECT name FROM sqlite_master WHERE name LIKE '".$this->tablename."'"))===0){ // 資料表不存在
$result = 'CREATE TABLE '.$this->tablename.' (
"no" INTEGER NOT NULL PRIMARY KEY,
"resto" INTEGER NOT NULL,
"root" TIMESTAMP DEFAULT \'0\' NOT NULL,
"time" INTEGER NOT NULL,
"md5chksum" VARCHAR(32) NOT NULL,
"category" VARCHAR(255) NOT NULL,
"tim" INTEGER NOT NULL,
"ext" VARCHAR(4) NOT NULL,
"imgw" INTEGER NOT NULL,
"imgh" INTEGER NOT NULL,
"imgsize" VARCHAR(10) NOT NULL,
"tw" INTEGER NOT NULL,
"th" INTEGER NOT NULL,
"pwd" VARCHAR(8) NOT NULL,
"now" VARCHAR(255) NOT NULL,
"name" VARCHAR(255) NOT NULL,
"email" VARCHAR(255) NOT NULL,
"sub" VARCHAR(255) NOT NULL,
"com" TEXT NOT NULL,
"host" VARCHAR(255) NOT NULL,
"status" VARCHAR(4) NOT NULL
);'; // PIO Structure V2
$idx = array('resto', 'root', 'time');
foreach($idx as $x){
$result .= 'CREATE INDEX IDX_'.$this->tablename.'_'.$x.' ON '.$this->tablename.'('.$x.');';
}
$result .= 'CREATE INDEX IDX_'.$this->tablename.'_resto_no ON '.$this->tablename.'(resto,no);';
if($isAddInitData) $result .= 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES (0, datetime("now"), 1111111111, "", "", 1111111111111, "", 0, 0, "", 0, 0, "", "05/01/01(六)00:00", "'.$this->ENV['NONAME'].'", "", "'.$this->ENV['NOTITLE'].'", "'.$this->ENV['NOCOMMENT'].'", "", "");';
sqlite_exec($this->con, $result); // 正式新增資料表
$this->dbCommit();
}
}
 
/* 準備/讀入 */
function dbPrepare($transaction=true){
if($this->prepared) return true;
 
if(@!$this->con=sqlite_popen($this->dbname, 0666)) $this->_error_handler('Open database failed', __LINE__);
if($transaction) @sqlite_exec($this->con, 'BEGIN;'); // 啟動交易性能模式
 
$this->prepared = 1;
}
 
/* 提交/儲存 */
function dbCommit(){
if(!$this->prepared) return false;
 
@sqlite_exec($this->con, 'COMMIT;'); // 交易性能模式提交
}
 
/* 優化資料表 */
function dbOptimize($doit=false){
if($doit){
$this->dbPrepare(false);
if($this->_sqlite_call('VACUUM '.$this->tablename)) return true;
else return false;
}else return true; // 支援最佳化資料表
}
 
/* 匯入資料來源 */
function dbImport($data){
$this->dbInit(false); // 僅新增結構不新增資料
$data = explode("\r\n", $data);
$data_count = count($data) - 1;
$replaceComma = create_function('$txt', 'return str_replace("&#44;", ",", $txt);');
for($i = 0; $i < $data_count; $i++){
$line = array_map($replaceComma, explode(',', $data[$i])); // 取代 &#44; 為 ,
$SQL = 'INSERT INTO '.$this->tablename.' (no,resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
$line[0].','.
$line[1].',\''.
$line[2].'\','.
substr($line[5], 0, 10).',\''.
sqlite_escape_string($line[3]).'\',\''.
sqlite_escape_string($line[4]).'\','.
$line[5].',\''.sqlite_escape_string($line[6]).'\','.
$line[7].','.$line[8].',\''.sqlite_escape_string($line[9]).'\','.$line[10].','.$line[11].',\''.
sqlite_escape_string($line[12]).'\',\''.
sqlite_escape_string($line[13]).'\',\''.
sqlite_escape_string($line[14]).'\',\''.
sqlite_escape_string($line[15]).'\',\''.
sqlite_escape_string($line[16]).'\',\''.
sqlite_escape_string($line[17]).'\',\''.
sqlite_escape_string($line[18]).'\',\''.
$line[19].'\')';
//echo $SQL."<BR>\n";
if(!$this->_sqlite_call($SQL)){ echo $SQL."<BR>\n"; $this->_error_handler('Insert a new post failed', __LINE__); }
}
$this->dbCommit(); // 送交
return true;
}
 
/* 匯出資料來源 */
function dbExport(){
if(!$this->prepared) $this->dbPrepare();
$line = $this->_sqlite_call('SELECT no,resto,root,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status FROM '.$this->tablename.' ORDER BY no DESC');
$data = '';
$replaceComma = create_function('$txt', 'return str_replace(",", "&#44;", $txt);');
while($row=sqlite_fetch_array($line, SQLITE_ASSOC)){
$row = array_map($replaceComma, $row); // 取代 , 為 &#44;
$data .= implode(',', $row).",\r\n";
}
return $data;
}
 
/* 文章數目 */
function postCount($resno=0){
if(!$this->prepared) $this->dbPrepare();
 
if($resno){ // 回傳討論串總文章數目
$line = $this->_sqlite_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = '.$resno);
$countline = $this->_sqlite_result($line, 0, 0) + 1;
}else{ // 回傳總文章數目
$line = $this->_sqlite_call('SELECT COUNT(no) FROM '.$this->tablename);
$countline = $this->_sqlite_result($line, 0, 0);
}
return $countline;
}
 
/* 討論串數目 */
function threadCount(){
if(!$this->prepared) $this->dbPrepare();
 
$tree = $this->_sqlite_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = 0');
$counttree = $this->_sqlite_result($tree, 0, 0); // 計算討論串目前資料筆數
return $counttree;
}
 
/* 取得最後文章編號 */
function getLastPostNo($state){
if(!$this->prepared) $this->dbPrepare();
 
if($state=='afterCommit'){ // 送出後的最後文章編號
$tree = $this->_sqlite_call('SELECT MAX(no) FROM '.$this->tablename);
$lastno = $this->_sqlite_result($tree, 0, 0);
return $lastno;
}else return 0; // 其他狀態沒用
}
 
/* 輸出文章清單 */
function fetchPostList($resno=0, $start=0, $amount=0){
if(!$this->prepared) $this->dbPrepare();
 
$line = array();
if($resno){ // 輸出討論串的結構 (含自己, EX : 1,2,3,4,5,6)
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE no = '.$resno.' OR resto = '.$resno.' ORDER BY no';
}else{ // 輸出所有文章編號,新的在前
$tmpSQL = 'SELECT no FROM '.$this->tablename.' ORDER BY no DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 有指定數量才用 LIMIT
}
$tree = $this->_sqlite_call($tmpSQL);
while($rows=sqlite_fetch_array($tree)) $line[] = $rows[0]; // 迴圈
 
return $line;
}
 
/* 輸出討論串清單 */
function fetchThreadList($start=0, $amount=0, $isDESC=false) {
if(!$this->prepared) $this->dbPrepare();
 
$treeline = array();
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE resto = 0 ORDER BY '.($isDESC ? 'no' : 'root').' DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 有指定數量才用 LIMIT
$tree = $this->_sqlite_call($tmpSQL);
while($rows=sqlite_fetch_array($tree)) $treeline[] = $rows[0]; // 迴圈
 
return $treeline;
}
 
/* 輸出文章 */
function fetchPosts($postlist){
if(!$this->prepared) $this->dbPrepare();
 
if(is_array($postlist)){ // 取多串
$pno = implode(', ', $postlist); // ID字串
$tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no IN ('.$pno.') ORDER BY no';
if(count($postlist) > 1){ if($postlist[0] > $postlist[1]) $tmpSQL .= ' DESC'; } // 由大排到小
}else $tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no = '.$postlist; // 取單串
$line = $this->_sqlite_call($tmpSQL);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 刪除舊文 */
function delOldPostes(){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$oldAttachments = array(); // 舊文的附加檔案清單
$countline = $this->postCount(); // 文章數目
$cutIndex = $countline - $this->ENV['LOG_MAX'] + 1; // LIMIT用,取出最舊的幾篇
if(!$result=$this->_sqlite_call('SELECT no,ext,tim FROM '.$this->tablename." ORDER BY no LIMIT 0, ".$cutIndex)) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=sqlite_fetch_array($result)){ // 個別跑舊文迴圈
if($dext){
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $oldAttachments[] = $dfile;
if($FileIO->imageExists($dthumb)) $oldAttachments[] = $dthumb;
}
// 逐次搜尋舊文之回應
if(!$resultres=$this->_sqlite_call('SELECT ext,tim FROM '.$this->tablename." WHERE ext <> '' AND resto = $dno")) $this->_error_handler('Get replies of the old post failed', __LINE__);
while(list($rext, $rtim)=sqlite_fetch_array($resultres)){
$rfile = $rtim.$rext; // 附加檔案名稱
$rthumb = $rtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($rfile)) $oldAttachments[] = $rfile;
if($FileIO->imageExists($rthumb)) $oldAttachments[] = $rthumb;
}
if(!$this->_sqlite_call('DELETE FROM '.$this->tablename.' WHERE no = '.$dno.' OR resto = '.$dno)) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪除文章
}
}
return $oldAttachments; // 回傳需刪除檔案列表
}
 
/* 刪除舊附件 (輸出附件清單) */
function delOldAttachments($total_size, $storage_max, $warnOnly=true){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$arr_warn = $arr_kill = array(); // 警告 / 即將被刪除標記陣列
if(!$result=$this->_sqlite_call('SELECT no,ext,tim FROM '.$this->tablename." WHERE ext <> '' ORDER BY no")) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=sqlite_fetch_array($result)){ // 個別跑舊文迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)){ $total_size -= $FileIO->getImageFilesize($dfile) / 1024; $arr_kill[] = $dno; $arr_warn[$dno] = 1; } // 標記刪除
if($FileIO->imageExists($dthumb)) $total_size -= $FileIO->getImageFilesize($dthumb) / 1024;
if($total_size < $storage_max) break;
}
}
return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);
}
 
/* 刪除文章 */
function removePosts($posts){
if(!$this->prepared) $this->dbPrepare();
 
$files = $this->removeAttachments($posts, true); // 先遞迴取得刪除文章及其回應附件清單
$pno = implode(', ', $posts); // ID字串
if(!$result=$this->_sqlite_call('DELETE FROM '.$this->tablename.' WHERE no IN ('.$pno.') OR resto IN('.$pno.')')) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪掉文章
return $files;
}
 
/* 刪除附件 (輸出附件清單) */
function removeAttachments($posts, $recursion=false){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$files = array();
$pno = implode(', ', $posts); // ID字串
if($recursion) $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE (no IN ('.$pno.') OR resto IN('.$pno.")) AND ext <> ''"; // 遞迴取出 (含回應附件)
else $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE no IN ('.$pno.") AND ext <> ''"; // 只有指定的編號
 
if(!$result=$this->_sqlite_call($tmpSQL)) $this->_error_handler('Get attachments of the post failed', __LINE__);
else{
while(list($dext, $dtim)=sqlite_fetch_array($result)){ // 個別跑迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $files[] = $dfile;
if($FileIO->imageExists($dthumb)) $files[] = $dthumb;
}
}
return $files;
}
 
/* 新增文章/討論串 */
function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false){
if(!$this->prepared) $this->dbPrepare();
 
$time = (int)substr($tim, 0, -3); // 13位數的數字串是檔名,10位數的才是時間數值
$updatetime = gmdate('Y-m-d H:i:s'); // 更動時間 (UTC)
if($resto){ // 新增回應
$root = '0';
if($age){ // 推文
$query = 'UPDATE '.$this->tablename.' SET root = "'.$updatetime.'" WHERE no = '.$resto; // 將被回應的文章往上移動
if(!$result=$this->_sqlite_call($query)) $this->_error_handler('Push the post failed', __LINE__);
}
}else $root = $updatetime; // 新增討論串, 討論串最後被更新時間
 
$query = 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
(int)$resto.','. // 回應編號
"'$root',". // 最後更新時間
$time.','. // 發文時間數值
"'$md5chksum',". // 附加檔案md5
"'".sqlite_escape_string($category)."',". // 分類標籤
"$tim, '$ext',". // 附加檔名
$imgw.','.$imgh.",'".$imgsize."',".$tw.','.$th.','. // 圖檔長寬及檔案大小;預覽圖長寬
"'".sqlite_escape_string($pwd)."',".
"'$now',". // 時間(含ID)字串
"'".sqlite_escape_string($name)."',".
"'".sqlite_escape_string($email)."',".
"'".sqlite_escape_string($sub)."',".
"'".sqlite_escape_string($com)."',".
"'".sqlite_escape_string($host)."', '')";
if(!$result=$this->_sqlite_call($query)) $this->_error_handler('Insert a new post failed', __LINE__);
}
 
/* 檢查是否連續投稿 */
function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$this->ENV['PERIOD.POST']) return false; // 關閉連續投稿檢查
$tmpSQL = 'SELECT pwd,host FROM '.$this->tablename.' WHERE time > '.($timestamp - $this->ENV['PERIOD.POST']); // 一般投稿時間檢查
if($isupload) $tmpSQL .= ' OR time > '.($timestamp - $this->ENV['PERIOD.IMAGEPOST']); // 附加圖檔的投稿時間檢查 (與下者兩者擇一)
else $tmpSQL .= " OR php('md5', com) = '".md5($com)."'"; // 內文一樣的檢查 (與上者兩者擇一) * 此取巧採用了PHP登錄的函式php來叫用md5
if(!$result=$this->_sqlite_call($tmpSQL)) $this->_error_handler('Get the post to check the succession failed', __LINE__);
else{
while(list($lpwd, $lhost)=sqlite_fetch_array($result)){
// 判斷為同一人發文且符合連續投稿條件
if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) return true;
}
return false;
}
}
 
/* 檢查是否重複貼圖 */
function isDuplicateAttechment($lcount, $md5hash){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$result=$this->_sqlite_call('SELECT tim,ext FROM '.$this->tablename." WHERE ext <> '' AND md5chksum = '$md5hash' ORDER BY no DESC")) $this->_error_handler('Get the post to check the duplicate attachment failed', __LINE__);
else{
while(list($ltim, $lext)=sqlite_fetch_array($result)){
if($FileIO->imageExists($ltim.$lext)) return true; // 有相同檔案
}
return false;
}
}
 
/* 有此討論串? */
function isThread($no){
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->_sqlite_call('SELECT no FROM '.$this->tablename.' WHERE no = '.$no.' AND resto = 0');
return sqlite_fetch_array($result);
}
 
/* 搜尋文章 */
function searchPost($keyword, $field, $method){
if(!$this->prepared) $this->dbPrepare();
 
$keyword_cnt = count($keyword);
$SearchQuery = 'SELECT * FROM '.$this->tablename." WHERE {$field} LIKE '%".($keyword[0])."%'";
if($keyword_cnt > 1) for($i = 1; $i < $keyword_cnt; $i++) $SearchQuery .= " {$method} {$field} LIKE '%".($keyword[$i])."%'"; // 多重字串交集 / 聯集搜尋
$SearchQuery .= ' ORDER BY no DESC'; // 按照號碼大小排序
if(!$line=$this->_sqlite_call($SearchQuery)) $this->_error_handler('Search the post failed', __LINE__);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 搜尋類別標籤 */
function searchCategory($category){
if(!$this->prepared) $this->dbPrepare();
 
$foundPosts = array();
$SearchQuery = 'SELECT no FROM '.$this->tablename." WHERE lower(category) LIKE '%,".strtolower(sqlite_escape_string($category)).",%' ORDER BY no DESC";
$line = $this->_sqlite_call($SearchQuery);
while($rows=sqlite_fetch_array($line)) $foundPosts[] = $rows[0];
 
return $foundPosts;
}
 
/* 取出單一文章狀態 */
function getPostStatus($status, $statusType){
if(!$this->prepared) $this->dbPrepare();
$returnValue = 0; // 回傳值
 
switch($statusType){
case 'TS': // 討論串是否鎖定
$returnValue = (strpos($status, 'T')!==false) ? 1 : 0; // 討論串是否鎖定
break;
default:
}
return $returnValue;
}
 
/* 設定文章狀態 */
function setPostStatus($no, $status, $statusType, $newValue){
if(!$this->prepared) $this->dbPrepare();
 
$forcount = count($no);
for($i = 0; $i < $forcount; $i++){
$newStatus = ''; // 討論串狀態旗標字串
switch($statusType[$i]){
case 'TS': // 討論串是否停止
$newStatus = $newValue[$i] ? ($status[$i].'T') : str_replace('T', '', $status[$i]); // 更改狀態字串
if(!$this->_sqlite_call('UPDATE '.$this->tablename." SET status = '$newStatus' WHERE no = ".$no[$i])) $this->_error_handler('Update the status of the post failed', __LINE__); // 更新討論串屬性
break;
default:
}
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pio/pio.sqlite3.php
@@ -0,0 +1,421 @@
<?php
/**
* PIO SQLite3 (PDO) API
*
* 提供存取以 SQLite3 資料庫構成的資料結構後端的物件 (需要 PHP 5.1.0 以上並開啟 PDO 功能)
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PIOsqlite3{
private $ENV, $DSN, $tablename; // Local Constant
private $con, $prepared; // Local Global
 
public function __construct($connstr='', $ENV){
$this->ENV = $ENV;
$this->prepared = false;
if($connstr) $this->dbConnect($connstr);
}
 
/* private 攔截SQL錯誤 */
private function _error_handler($errtext, $errline){
$err = "Pixmicat! SQL Error: $errtext, debug info: at line $errline";
trigger_error($err, E_USER_ERROR);
}
 
/* PIO模組版本 */
public function pioVersion() {
return '0.4 (v20070605)';
}
 
/* 處理連線字串/連接 */
public function dbConnect($connStr){
// 格式: sqlite3://資料庫檔案之位置/資料表/
// 示例: sqlite3://pixmicat.db/imglog/
//     sqlite3://:memory:/imglog
if(preg_match('/^sqlite3:\/\/(.*)\/(.*)\/$/i', $connStr, $linkinfos)){
$this->DSN = 'sqlite:'.$linkinfos[1];
$this->tablename = $linkinfos[2];
}
}
 
/* 初始化 */
public function dbInit($isAddInitData=true){
$this->dbPrepare();
$nline = $this->con->query('SELECT COUNT(name) FROM sqlite_master WHERE name LIKE "'.$this->tablename.'"')->fetch();
if($nline[0]==='0'){ // 資料表不存在
$result = 'CREATE TABLE '.$this->tablename.' (
"no" INTEGER NOT NULL PRIMARY KEY,
"resto" INTEGER NOT NULL,
"root" TIMESTAMP DEFAULT \'0\' NOT NULL,
"time" INTEGER NOT NULL,
"md5chksum" VARCHAR(32) NOT NULL,
"category" VARCHAR(255) NOT NULL,
"tim" INTEGER NOT NULL,
"ext" VARCHAR(4) NOT NULL,
"imgw" INTEGER NOT NULL,
"imgh" INTEGER NOT NULL,
"imgsize" VARCHAR(10) NOT NULL,
"tw" INTEGER NOT NULL,
"th" INTEGER NOT NULL,
"pwd" VARCHAR(8) NOT NULL,
"now" VARCHAR(255) NOT NULL,
"name" VARCHAR(255) NOT NULL,
"email" VARCHAR(255) NOT NULL,
"sub" VARCHAR(255) NOT NULL,
"com" TEXT NOT NULL,
"host" VARCHAR(255) NOT NULL,
"status" VARCHAR(4) NOT NULL
);'; // PIO Structure V2
$idx = array('resto', 'root', 'time');
foreach($idx as $x) $result .= 'CREATE INDEX IDX_'.$this->tablename.'_'.$x.' ON '.$this->tablename.'('.$x.');';
$result .= 'CREATE INDEX IDX_'.$this->tablename.'_resto_no ON '.$this->tablename.'(resto,no);';
if($isAddInitData) $result .= 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES (0, datetime("now"), 1111111111, "", "", 1111111111111, "", 0, 0, "", 0, 0, "", "05/01/01(六)00:00", "'.$this->ENV['NONAME'].'", "", "'.$this->ENV['NOTITLE'].'", "'.$this->ENV['NOCOMMENT'].'", "", "");';
$this->con->exec($result);
$this->dbCommit();
}
}
 
/* 準備/讀入 */
public function dbPrepare($transaction=true){
if($this->prepared) return true;
 
($this->con = new PDO($this->DSN, '', '', array(PDO::ATTR_PERSISTENT => true))) or $this->_error_handler('Open database failed', __LINE__);
if($transaction) $this->con->beginTransaction(); // 啟動交易性能模式
 
$this->prepared = true;
}
 
/* 提交/儲存 */
public function dbCommit(){
if(!$this->prepared) return false;
 
$this->con->commit();
}
 
/* 優化資料表 */
public function dbOptimize($doit=false){
if($doit){
$this->dbPrepare(false);
if($this->con->exec('VACUUM '.$this->tablename)) return true;
else return false;
}else return true; // 支援最佳化資料表
}
 
/* 匯入資料來源 */
public function dbImport($data){
$this->dbInit(false); // 僅新增結構不新增資料
$data = explode("\r\n", $data);
$data_count = count($data) - 1;
$replaceComma = create_function('$txt', 'return str_replace("&#44;", ",", $txt);');
for($i = 0; $i < $data_count; $i++){
$line = array_map($replaceComma, explode(',', $data[$i])); // 取代 &#44; 為 ,
$SQL = 'INSERT INTO '.$this->tablename.' (no,resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
$line[0].','.
$line[1].',\''.
$line[2].'\','.
substr($line[5], 0, 10).','.
$this->con->quote($line[3]).','.
$this->con->quote($line[4]).','.
$line[5].','.$this->con->quote($line[6]).','.
$line[7].','.$line[8].','.$this->con->quote($line[9]).','.$line[10].','.$line[11].','.
$this->con->quote($line[12]).','.
$this->con->quote($line[13]).','.
$this->con->quote($line[14]).','.
$this->con->quote($line[15]).','.
$this->con->quote($line[16]).','.
$this->con->quote($line[17]).','.
$this->con->quote($line[18]).',\''.
$line[19].'\')';
//echo $SQL."<BR>\n";
if(!$this->con->exec($SQL)) $this->_error_handler('Insert a new post failed', __LINE__);
}
$this->dbCommit(); // 送交
return true;
}
 
/* 匯出資料來源 */
public function dbExport(){
if(!$this->prepared) $this->dbPrepare();
$line = $this->con->query('SELECT no,resto,root,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status FROM '.$this->tablename.' ORDER BY no DESC');
$data = '';
$replaceComma = create_function('$txt', 'return str_replace(",", "&#44;", $txt);');
while($row = $line->fetch(PDO::FETCH_ASSOC)){
$row = array_map($replaceComma, $row); // 取代 , 為 &#44;
$data .= implode(',', $row).",\r\n";
}
return $data;
}
 
/* 文章數目 */
public function postCount($resno=0){
if(!$this->prepared) $this->dbPrepare();
 
if($resno){ // 一討論串文章總數目
$line = $this->con->query('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = '.$resno)->fetch();
$countline = $line[0] + 1;
}else{ // 文章總數目
$line = $this->con->query('SELECT COUNT(no) FROM '.$this->tablename)->fetch();
$countline = $line[0];
}
return $countline;
}
 
/* 討論串數目 */
public function threadCount(){
if(!$this->prepared) $this->dbPrepare();
 
$tree = $this->con->query('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = 0')->fetch();
return $tree[0]; // 討論串目前數目
}
 
/* 取得最後文章編號 */
public function getLastPostNo($state){
if(!$this->prepared) $this->dbPrepare();
 
if($state=='afterCommit'){ // 送出後的最後文章編號
$lastno = $this->con->query('SELECT MAX(no) FROM '.$this->tablename)->fetch();
return $lastno[0];
}else return 0; // 其他狀態沒用
}
 
/* 輸出文章清單 */
public function fetchPostList($resno=0, $start=0, $amount=0){
if(!$this->prepared) $this->dbPrepare();
 
if($resno){ // 輸出討論串的結構 (含自己, EX : 1,2,3,4,5,6)
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE no = '.$resno.' OR resto = '.$resno.' ORDER BY no';
}else{ // 輸出所有文章編號,新的在前
$tmpSQL = 'SELECT no FROM '.$this->tablename.' ORDER BY no DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 指定數量
}
return $this->con->query($tmpSQL)->fetchAll(PDO::FETCH_COLUMN, 0);
}
 
/* 輸出討論串清單 */
public function fetchThreadList($start=0, $amount=0, $isDESC=false) {
if(!$this->prepared) $this->dbPrepare();
 
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE resto = 0 ORDER BY '.($isDESC ? 'no' : 'root').' DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 指定數量
return $this->con->query($tmpSQL)->fetchAll(PDO::FETCH_COLUMN, 0);
}
 
/* 輸出文章 */
public function fetchPosts($postlist){
if(!$this->prepared) $this->dbPrepare();
 
if(is_array($postlist)){ // 取多串
$pno = implode(', ', $postlist); // ID字串
$tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no IN ('.$pno.') ORDER BY no';
if(count($postlist) > 1){ if($postlist[0] > $postlist[1]) $tmpSQL .= ' DESC'; } // 由大排到小
}else $tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no = '.$postlist; // 取單串
$line = $this->con->query($tmpSQL)->fetchAll();
return $line;
}
 
/* 刪除舊文 */
public function delOldPostes(){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$oldAttachments = array();
$delStack = array(); // Records needed to be deleted
$countline = $this->postCount();
$cutIndex = $countline - $this->ENV['LOG_MAX'] + 1;
$result = $this->con->prepare('SELECT no,ext,tim FROM '.$this->tablename.' ORDER BY no LIMIT 0, :cutindex');
$result->execute(array(':cutindex' => $cutIndex)) or $this->_error_handler('Get the old post failed', __LINE__);
while(list($dno, $dext, $dtim) = $result->fetch(PDO::FETCH_NUM)){
if($dext){
$dfile = $dtim.$dext; $dthumb = $dtim.'s.jpg';
if($FileIO->imageExists($dfile)) $oldAttachments[] = $dfile;
if($FileIO->imageExists($dthumb)) $oldAttachments[] = $dthumb;
}
// 逐次搜尋舊文之回應
$resultres = $this->con->prepare('SELECT ext,tim FROM '.$this->tablename.' WHERE ext <> "" AND resto = :dno');
$resultres->execute(array(':dno' => $dno)) or $this->_error_handler('Get replies of the old post failed', __LINE__);
while(list($rext, $rtim) = $resultres->fetch(PDO::FETCH_NUM)){
$rfile = $rtim.$rext; $rthumb = $rtim.'s.jpg';
if($FileIO->imageExists($rfile)) $oldAttachments[] = $rfile;
if($FileIO->imageExists($rthumb)) $oldAttachments[] = $rthumb;
}
$delStack[] = $dno; // Add to stack
}
$delCount = count($delStack);
for($i = 0; $i < $delCount; $i++){
if(!$this->con->exec('DELETE FROM '.$this->tablename.' WHERE no = '.$delStack[$i].' OR resto = '.$delStack[$i])) $this->_error_handler('Delete old posts and replies failed', __LINE__);
}
return $oldAttachments; // 回傳需刪除檔案列表
}
 
/* 刪除舊附件 (輸出附件清單) */
public function delOldAttachments($total_size, $storage_max, $warnOnly=true){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$arr_warn = $arr_kill = array(); // 警告 / 即將被刪除標記
($result = $this->con->query('SELECT no,ext,tim FROM '.$this->tablename.' WHERE ext <> "" ORDER BY no')) or $this->_error_handler('Get the old post failed', __LINE__);
while(list($dno, $dext, $dtim) = $result->fetch(PDO::FETCH_NUM)){
$dfile = $dtim.$dext; $dthumb = $dtim.'s.jpg';
if($FileIO->imageExists($dfile)){ $total_size -= $FileIO->getImageFilesize($dfile) / 1024; $arr_kill[] = $dno; $arr_warn[$dno] = 1; } // 標記刪除
if($FileIO->imageExists($dthumb)) $total_size -= $FileIO->getImageFilesize($dthumb) / 1024;
if($total_size < $storage_max) break;
}
return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);
}
 
/* 刪除文章 */
public function removePosts($posts){
if(!$this->prepared) $this->dbPrepare();
 
$files = $this->removeAttachments($posts, true); // 先遞迴取得刪除文章及其回應附件清單
$pno = implode(', ', $posts); // ID字串
if(!$this->con->exec('DELETE FROM '.$this->tablename.' WHERE no IN ('.$pno.') OR resto IN('.$pno.')')) $this->_error_handler('Delete old posts and replies failed', __LINE__);
return $files;
}
 
/* 刪除附件 (輸出附件清單) */
public function removeAttachments($posts, $recursion=false){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$files = array();
$pno = implode(', ', $posts); // ID字串
if($recursion) $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE (no IN ('.$pno.') OR resto IN('.$pno.")) AND ext <> ''"; // 遞迴取出 (含回應附件)
else $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE no IN ('.$pno.") AND ext <> ''"; // 只有指定的編號
 
($result = $this->con->query($tmpSQL)) or $this->_error_handler('Get attachments of the post failed', __LINE__);
while(list($dext, $dtim) = $result->fetch(PDO::FETCH_NUM)){
$dfile = $dtim.$dext; $dthumb = $dtim.'s.jpg';
if($FileIO->imageExists($dfile)) $files[] = $dfile;
if($FileIO->imageExists($dthumb)) $files[] = $dthumb;
}
return $files;
}
 
/* 新增文章/討論串 */
public function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false){
if(!$this->prepared) $this->dbPrepare();
 
$time = (int)substr($tim, 0, -3); // 13位數的數字串是檔名,10位數的才是時間數值
$updatetime = gmdate('Y-m-d H:i:s'); // 更動時間 (UTC)
if($resto){ // 新增回應
$root = '0';
if($age){ // 推文
$result = $this->con->prepare('UPDATE '.$this->tablename.' SET root = :now WHERE no = :resto');
$result->execute(array(':now' => $updatetime, ':resto' => $resto)) or $this->_error_handler('Push the post failed', __LINE__);
}
}else $root = $updatetime; // 新增討論串, 討論串最後被更新時間
 
$query = 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
(int)$resto.','. // 回應編號
"'$root',". // 最後更新時間
$time.','. // 發文時間數值
"'$md5chksum',". // 附加檔案 MD5
$this->con->quote($category).",". // 分類標籤
"$tim, '$ext',". // 附檔檔名
$imgw.','.$imgh.",'".$imgsize."',".$tw.','.$th.','. // 圖檔長寬及檔案大小;預覽圖長寬
$this->con->quote($pwd).','.
"'$now',". // 時間(含ID)字串
$this->con->quote($name).','.
$this->con->quote($email).','.
$this->con->quote($sub).','.
$this->con->quote($com).','.
$this->con->quote($host).", '')";
if(!$this->con->exec($query)) $this->_error_handler('Insert a new post failed', __LINE__);
}
 
/* 檢查是否連續投稿 */
public function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$this->ENV['PERIOD.POST']) return false; // 關閉連續投稿檢查
$tmpSQL = 'SELECT pwd,host FROM '.$this->tablename.' WHERE time > '.($timestamp - $this->ENV['PERIOD.POST']); // 一般投稿時間檢查
if($isupload) $tmpSQL .= ' OR time > '.($timestamp - $this->ENV['PERIOD.IMAGEPOST']); // 附加圖檔的投稿時間檢查 (與下者兩者擇一)
else $tmpSQL .= " OR md5(com) = '".md5($com)."'"; // 內文一樣的檢查 (與上者兩者擇一)
$this->con->sqliteCreateFunction('md5', 'md5', 1); // Register MD5 function
($result = $this->con->query($tmpSQL)) or $this->_error_handler('Get the post to check the succession failed', __LINE__);
while(list($lpwd, $lhost) = $result->fetch(PDO::FETCH_NUM)){
// 判斷為同一人發文且符合連續投稿條件
if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) return true;
}
return false;
}
 
/* 檢查是否重複貼圖 */
public function isDuplicateAttechment($lcount, $md5hash){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->con->query('SELECT tim,ext FROM '.$this->tablename.' WHERE ext <> "" AND md5chksum = "'.$md5hash.'" ORDER BY no DESC') or $this->_error_handler('Get the post to check the duplicate attachment failed', __LINE__);
while(list($ltim, $lext) = $result->fetch(PDO::FETCH_NUM)){
if($FileIO->imageExists($ltim.$lext)) return true; // 有相同檔案
}
return false;
}
 
/* 有此討論串? */
public function isThread($no){
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->con->query('SELECT no FROM '.$this->tablename.' WHERE no = '.$no.' AND resto = 0');
return $result->fetch() ? true : false;
}
 
/* 搜尋文章 */
public function searchPost($keyword, $field, $method){
if(!$this->prepared) $this->dbPrepare();
 
$keyword_cnt = count($keyword);
$SearchQuery = 'SELECT * FROM '.$this->tablename." WHERE {$field} LIKE '%".($keyword[0])."%'";
if($keyword_cnt > 1) for($i = 1; $i < $keyword_cnt; $i++) $SearchQuery .= " {$method} {$field} LIKE '%".($keyword[$i])."%'"; // 多重字串交集 / 聯集搜尋
$SearchQuery .= ' ORDER BY no DESC'; // 按照號碼大小排序
($line = $this->con->query($SearchQuery)) or $this->_error_handler('Search the post failed', __LINE__);
 
return $line->fetchAll();
}
 
/* 搜尋類別標籤 */
public function searchCategory($category){
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->con->prepare('SELECT no FROM '.$this->tablename.' WHERE lower(category) LIKE :category');
$result->execute(array(':category' => '%,'.strtolower($category).',%'));
return $result->fetchAll(PDO::FETCH_COLUMN, 0);
}
 
/* 取出單一文章狀態 */
public function getPostStatus($status, $statusType){
if(!$this->prepared) $this->dbPrepare();
$returnValue = 0; // 回傳值
 
switch($statusType){
case 'TS': // 討論串是否鎖定
$returnValue = (strpos($status, 'T')!==false) ? 1 : 0; // 討論串是否鎖定
break;
default:
}
return $returnValue;
}
 
/* 設定文章狀態 */
public function setPostStatus($no, $status, $statusType, $newValue){
if(!$this->prepared) $this->dbPrepare();
 
$forcount = count($no);
for($i = 0; $i < $forcount; $i++){
$newStatus = ''; // 討論串狀態旗標字串
switch($statusType[$i]){
case 'TS': // 討論串是否停止
$newStatus = $newValue[$i] ? ($status[$i].'T') : str_replace('T', '', $status[$i]);
if(!$this->con->exec('UPDATE '.$this->tablename." SET status = '$newStatus' WHERE no = ".$no[$i])) $this->_error_handler('Update the status of the post failed', __LINE__);
break;
default:
}
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pio/pio.mysql.php
@@ -0,0 +1,471 @@
<?php
/**
* PIO MySQL API
*
* 提供存取以 MySQL 資料庫構成的資料結構後端的物件
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PIOmysql{
var $ENV, $username, $password, $server, $dbname, $tablename; // Local Constant
var $con, $prepared; // Local Global
 
function PIOmysql($connstr='', $ENV){
$this->ENV = $ENV;
$this->prepared = 0;
if($connstr) $this->dbConnect($connstr);
}
 
/* private 攔截SQL錯誤 */
function _error_handler($errtext, $errline){
$err = "Pixmicat! SQL Error: $errtext, debug info: at line $errline";
trigger_error($err, E_USER_ERROR);
}
 
/* private 使用SQL字串和MySQL伺服器要求 */
function _mysql_call($query){
return mysql_query($query);
}
 
/* private 由資源輸出陣列 */
function _ArrangeArrayStructure($line){
$posts = array();
while($row=mysql_fetch_array($line, MYSQL_ASSOC)) $posts[] = $row;
mysql_free_result($line);
return $posts;
}
 
/* PIO模組版本 */
function pioVersion(){
return '0.4 (v20070605)';
}
 
/* 處理連線字串/連接 */
function dbConnect($connStr){
// 格式: mysql://帳號:密碼@伺服器位置:埠號(可省略)/資料庫/資料表/
// 示例: mysql://pixmicat:1234@127.0.0.1/pixmicat_use/imglog/
if(preg_match('/^mysql:\/\/(.*)\:(.*)\@(.*(?:\:[0-9]+)?)\/(.*)\/(.*)\/$/i', $connStr, $linkinfos)){
$this->username = $linkinfos[1]; // 登入帳號
$this->password = $linkinfos[2]; // 登入密碼
$this->server = $linkinfos[3]; // 登入伺服器 (含埠號)
$this->dbname = $linkinfos[4]; // 資料庫名稱
$this->tablename = $linkinfos[5]; // 資料表名稱
}
}
 
/* 初始化 */
function dbInit($isAddInitData=true){
$this->dbPrepare();
if(mysql_num_rows(mysql_query("SHOW TABLES LIKE '".$this->tablename."'"))!=1){ // 資料表不存在
$result = "CREATE TABLE ".$this->tablename." (primary key(no),
index (resto),index (root),index (time),
no int(1) not null auto_increment,
resto int(1) not null,
root timestamp(14) null DEFAULT 0,
time int(1) not null,
md5chksum varchar(32) not null,
category varchar(255) not null,
tim bigint(1) not null,
ext varchar(4) not null,
imgw smallint(1) not null,
imgh smallint(1) not null,
imgsize varchar(10) not null,
tw smallint(1) not null,
th smallint(1) not null,
pwd varchar(8) not null,
now varchar(255) not null,
name varchar(255) not null,
email varchar(255) not null,
sub varchar(255) not null,
com text not null,
host varchar(255) not null,
status varchar(4) not null)
TYPE = MYISAM
COMMENT = 'PIO Structure V2'";
$result2 = @mysql_query("SHOW CHARACTER SET like 'utf8'"); // 是否支援UTF-8 (MySQL 4.1.1開始支援)
if($result2 && mysql_num_rows($result2)){
$result .= ' CHARACTER SET utf8 COLLATE utf8_general_ci'; // 資料表追加UTF-8編碼
mysql_free_result($result2);
}
mysql_query($result); // 正式新增資料表
if($isAddInitData) $this->addPost(1, 0, '', '', 0, '', 0, 0, '', 0, 0, '', '05/01/01(六)00:00', $this->ENV['NONAME'], '', $this->ENV['NOTITLE'], $this->ENV['NOCOMMENT'], ''); // 追加一筆新資料
$this->dbCommit();
}
}
 
/* 準備/讀入 */
function dbPrepare($transaction=false){
if($this->prepared) return true;
 
if(@!$this->con=mysql_pconnect($this->server, $this->username, $this->password)) $this->_error_handler('Open database failed', __LINE__);
@mysql_select_db($this->dbname, $this->con);
@mysql_query("SET NAMES 'utf8'"); // MySQL資料以UTF-8模式傳送
if($transaction) @mysql_query('START TRANSACTION'); // 啟動交易性能模式 (據說會降低效能,但可防止資料寫入不一致)
 
$this->prepared = 1;
}
 
/* 提交/儲存 */
function dbCommit(){
if(!$this->prepared) return false;
 
//@mysql_query('COMMIT'); // 交易性能模式提交
}
 
/* 優化資料表 */
function dbOptimize($doit=false){
if($doit){
$this->dbPrepare(false);
if($this->_mysql_call('OPTIMIZE TABLES '.$this->tablename)) return true;
else return false;
}else return true; // 支援最佳化資料表
}
 
/* 匯入資料來源 */
function dbImport($data){
$this->dbInit(false); // 僅新增結構不新增資料
$data = explode("\r\n", $data);
$data_count = count($data) - 1;
$replaceComma = create_function('$txt', 'return str_replace("&#44;", ",", $txt);');
for($i = 0; $i < $data_count; $i++){
$line = array_map($replaceComma, explode(',', $data[$i])); // 取代 &#44; 為 ,
$SQL = 'INSERT INTO '.$this->tablename.' (no,resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
$line[0].','.
$line[1].',\''.
$line[2].'\','.
substr($line[5], 0, 10).',\''.
mysql_real_escape_string($line[3], $this->con).'\',\''.
mysql_real_escape_string($line[4], $this->con).'\','.
$line[5].',\''.mysql_real_escape_string($line[6], $this->con).'\','.
$line[7].','.$line[8].',\''.mysql_real_escape_string($line[9], $this->con).'\','.$line[10].','.$line[11].',\''.
mysql_real_escape_string($line[12], $this->con).'\',\''.
mysql_real_escape_string($line[13], $this->con).'\',\''.
mysql_real_escape_string($line[14], $this->con).'\',\''.
mysql_real_escape_string($line[15], $this->con).'\',\''.
mysql_real_escape_string($line[16], $this->con).'\',\''.
mysql_real_escape_string($line[17], $this->con).'\',\''.
mysql_real_escape_string($line[18], $this->con).'\',\''.
$line[19].'\')';
//echo $SQL."<BR>\n";
if(!$this->_mysql_call($SQL)) $this->_error_handler('Insert a new post failed', __LINE__);
}
$this->dbCommit(); // 送交
return true;
}
 
/* 匯出資料來源 */
function dbExport(){
if(!$this->prepared) $this->dbPrepare();
$line = $this->_mysql_call('SELECT no,resto,root,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status FROM '.$this->tablename.' ORDER BY no DESC');
$data = '';
$replaceComma = create_function('$txt', 'return str_replace(",", "&#44;", $txt);');
while($row=mysql_fetch_array($line, MYSQL_ASSOC)){
$row = array_map($replaceComma, $row); // 取代 , 為 &#44;
if($row['root']=='0000-00-00 00:00:00') $row['root'] = '0'; // 初始值設為 0
$data .= rtrim(implode(',', $row)).",\r\n";
}
mysql_free_result($line);
return $data;
}
 
/* 文章數目 */
function postCount($resno=0){
if(!$this->prepared) $this->dbPrepare();
 
if($resno){ // 回傳討論串總文章數目
$line = $this->_mysql_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = '.$resno);
$countline = mysql_result($line, 0) + 1;
}else{ // 回傳總文章數目
$line = $this->_mysql_call('SELECT COUNT(no) FROM '.$this->tablename);
$countline = mysql_result($line, 0);
}
mysql_free_result($line);
return $countline;
}
 
/* 討論串數目 */
function threadCount(){
if(!$this->prepared) $this->dbPrepare();
 
$tree = $this->_mysql_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = 0');
$counttree = mysql_result($tree, 0); mysql_free_result($tree); // 計算討論串目前資料筆數
return $counttree;
}
 
/* 取得最後文章編號 */
function getLastPostNo($state){
if(!$this->prepared) $this->dbPrepare();
 
if($state=='afterCommit'){ // 送出後的最後文章編號
$tree = $this->_mysql_call('SELECT MAX(no) FROM '.$this->tablename);
$lastno = mysql_result($tree, 0); mysql_free_result($tree);
return $lastno;
}else return 0; // 其他狀態沒用
}
 
/* 輸出文章清單 */
function fetchPostList($resno=0, $start=0, $amount=0){
if(!$this->prepared) $this->dbPrepare();
 
$line = array();
if($resno){ // 輸出討論串的結構 (含自己, EX : 1,2,3,4,5,6)
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE no = '.$resno.' OR resto = '.$resno.' ORDER BY no';
}else{ // 輸出所有文章編號,新的在前
$tmpSQL = 'SELECT no FROM '.$this->tablename.' ORDER BY no DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 有指定數量才用 LIMIT
}
$tree = $this->_mysql_call($tmpSQL);
while($rows=mysql_fetch_row($tree)) $line[] = $rows[0]; // 迴圈
 
mysql_free_result($tree);
return $line;
}
 
/* 輸出討論串清單 */
function fetchThreadList($start=0, $amount=0, $isDESC=false){
if(!$this->prepared) $this->dbPrepare();
 
$treeline = array();
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE resto = 0 ORDER BY '.($isDESC ? 'no' : 'root').' DESC';
if($amount) $tmpSQL .= " LIMIT {$start}, {$amount}"; // 有指定數量才用 LIMIT
$tree = $this->_mysql_call($tmpSQL);
while($rows=mysql_fetch_row($tree)) $treeline[] = $rows[0]; // 迴圈
 
mysql_free_result($tree);
return $treeline;
}
 
/* 輸出文章 */
function fetchPosts($postlist){
if(!$this->prepared) $this->dbPrepare();
 
if(is_array($postlist)){ // 取多串
$pno = implode(', ', $postlist); // ID字串
$tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no IN ('.$pno.') ORDER BY no';
if(count($postlist) > 1){ if($postlist[0] > $postlist[1]) $tmpSQL .= ' DESC'; } // 由大排到小
}else $tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no = '.$postlist; // 取單串
$line = $this->_mysql_call($tmpSQL);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 刪除舊文 */
function delOldPostes(){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$oldAttachments = array(); // 舊文的附加檔案清單
$countline = $this->postCount(); // 文章數目
$cutIndex = $countline - $this->ENV['LOG_MAX'] + 1; // LIMIT用,取出最舊的幾篇
if(!$result=$this->_mysql_call('SELECT no,ext,tim FROM '.$this->tablename.' ORDER BY no LIMIT 0, '.$cutIndex)) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=mysql_fetch_row($result)){ // 個別跑舊文迴圈
if($dext){
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $oldAttachments[] = $dfile;
if($FileIO->imageExists($dthumb)) $oldAttachments[] = $dthumb;
}
// 逐次搜尋舊文之回應
if(!$resultres=$this->_mysql_call('SELECT ext,tim FROM '.$this->tablename." WHERE ext <> '' AND resto = $dno")) $this->_error_handler('Get replies of the old post failed', __LINE__);
while(list($rext, $rtim)=mysql_fetch_row($resultres)){
$rfile = $rtim.$rext; // 附加檔案名稱
$rthumb = $rtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($rfile)) $oldAttachments[] = $rfile;
if($FileIO->imageExists($rthumb)) $oldAttachments[] = $rthumb;
}
mysql_free_result($resultres);
if(!$this->_mysql_call('DELETE FROM '.$this->tablename.' WHERE no = '.$dno.' OR resto = '.$dno)) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪除文章
}
}
mysql_free_result($result);
return $oldAttachments; // 回傳需刪除檔案列表
}
 
/* 刪除舊附件 (輸出附件清單) */
function delOldAttachments($total_size, $storage_max, $warnOnly=true){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$arr_warn = $arr_kill = array(); // 警告 / 即將被刪除標記陣列
if(!$result=$this->_mysql_call('SELECT no,ext,tim FROM '.$this->tablename." WHERE ext <> '' ORDER BY no")) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=mysql_fetch_row($result)){ // 個別跑舊文迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)){ $total_size -= $FileIO->getImageFilesize($dfile) / 1024; $arr_kill[] = $dno; $arr_warn[$dno] = 1; } // 標記刪除
if($FileIO->imageExists($dthumb)) $total_size -= $FileIO->getImageFilesize($dthumb) / 1024;
if($total_size < $storage_max) break;
}
}
mysql_free_result($result);
return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);
}
 
/* 刪除文章 */
function removePosts($posts){
if(!$this->prepared) $this->dbPrepare();
 
$files = $this->removeAttachments($posts, true); // 先遞迴取得刪除文章及其回應附件清單
$pno = implode(', ', $posts); // ID字串
if(!$result=$this->_mysql_call('DELETE FROM '.$this->tablename.' WHERE no IN ('.$pno.') OR resto IN('.$pno.')')) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪掉文章
return $files;
}
 
/* 刪除附件 (輸出附件清單) */
function removeAttachments($posts, $recursion=false){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$files = array();
$pno = implode(', ', $posts); // ID字串
if($recursion) $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE (no IN ('.$pno.') OR resto IN('.$pno.")) AND ext <> ''"; // 遞迴取出 (含回應附件)
else $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE no IN ('.$pno.") AND ext <> ''"; // 只有指定的編號
 
if(!$result=$this->_mysql_call($tmpSQL)) $this->_error_handler('Get attachments of the post failed', __LINE__);
else{
while(list($dext, $dtim)=mysql_fetch_row($result)){ // 個別跑迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $files[] = $dfile;
if($FileIO->imageExists($dthumb)) $files[] = $dthumb;
}
}
mysql_free_result($result);
return $files;
}
 
/* 新增文章/討論串 */
function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false){
if(!$this->prepared) $this->dbPrepare();
 
$time = (int)substr($tim, 0, -3); // 13位數的數字串是檔名,10位數的才是時間數值
$updatetime = gmdate('Y-m-d H:i:s'); // 更動時間 (UTC)
if($resto){ // 新增回應
$root = '0';
if($age){ // 推文
$query = 'UPDATE '.$this->tablename.' SET root = "'.$updatetime.'" WHERE no = '.$resto; // 將被回應的文章往上移動
if(!$result=$this->_mysql_call($query)) $this->_error_handler('Push the post failed', __LINE__);
}
}else $root = $updatetime; // 新增討論串, 討論串最後被更新時間
 
$query = 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
(int)$resto.','. // 回應編號
"'$root',". // 最後更新時間
$time.','. // 發文時間數值
"'$md5chksum',". // 附加檔案md5
"'".mysql_real_escape_string($category, $this->con)."',". // 分類標籤
"'$tim', '$ext',". // 附加檔名
$imgw.','.$imgh.",'".$imgsize."',".$tw.','.$th.','. // 圖檔長寬及檔案大小;預覽圖長寬
"'".mysql_real_escape_string($pwd, $this->con)."',".
"'$now',". // 時間(含ID)字串
"'".mysql_real_escape_string($name, $this->con)."',".
"'".mysql_real_escape_string($email, $this->con)."',".
"'".mysql_real_escape_string($sub, $this->con)."',".
"'".mysql_real_escape_string($com, $this->con)."',".
"'".mysql_real_escape_string($host, $this->con)."', '')";
if(!$this->_mysql_call($query)) $this->_error_handler('Insert a new post failed', __LINE__);
}
 
/* 檢查是否連續投稿 */
function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$this->ENV['PERIOD.POST']) return false; // 關閉連續投稿檢查
$tmpSQL = 'SELECT pwd,host FROM '.$this->tablename.' WHERE time > '.($timestamp - $this->ENV['PERIOD.POST']); // 一般投稿時間檢查
if($isupload) $tmpSQL .= ' OR time > '.($timestamp - $this->ENV['PERIOD.IMAGEPOST']); // 附加圖檔的投稿時間檢查 (與下者兩者擇一)
else $tmpSQL .= ' OR md5(com) = "'.md5($com).'"'; // 內文一樣的檢查 (與上者兩者擇一)
if(!$result=$this->_mysql_call($tmpSQL)) $this->_error_handler('Get the post to check the succession failed', __LINE__);
else{
while(list($lpwd, $lhost)=mysql_fetch_row($result)){
// 判斷為同一人發文且符合連續投稿條件
if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) return true;
}
return false;
}
}
 
/* 檢查是否重複貼圖 */
function isDuplicateAttechment($lcount, $md5hash){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$result=$this->_mysql_call('SELECT tim,ext FROM '.$this->tablename." WHERE ext <> '' AND md5chksum = '$md5hash' ORDER BY no DESC")) $this->_error_handler('Get the post to check the duplicate attachment failed', __LINE__);
else{
while(list($ltim, $lext)=mysql_fetch_row($result)){
if($FileIO->imageExists($ltim.$lext)) return true; // 有相同檔案
}
return false;
}
}
 
/* 有此討論串? */
function isThread($no){
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->_mysql_call('SELECT no FROM '.$this->tablename.' WHERE no = '.$no.' AND resto = 0');
return mysql_fetch_array($result);
}
 
/* 搜尋文章 */
function searchPost($keyword, $field, $method){
if(!$this->prepared) $this->dbPrepare();
 
$keyword_cnt = count($keyword);
$SearchQuery = 'SELECT * FROM '.$this->tablename." WHERE {$field} LIKE '%".($keyword[0])."%'";
if($keyword_cnt > 1) for($i = 1; $i < $keyword_cnt; $i++) $SearchQuery .= " {$method} {$field} LIKE '%".($keyword[$i])."%'"; // 多重字串交集 / 聯集搜尋
$SearchQuery .= ' ORDER BY no DESC'; // 按照號碼大小排序
if(!$line=$this->_mysql_call($SearchQuery)) $this->_error_handler('Search the post failed', __LINE__);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 搜尋類別標籤 */
function searchCategory($category){
if(!$this->prepared) $this->dbPrepare();
 
$foundPosts = array();
$SearchQuery = 'SELECT no FROM '.$this->tablename." WHERE lower(category) LIKE '%,".strtolower(mysql_real_escape_string($category)).",%' ORDER BY no DESC";
$line = $this->_mysql_call($SearchQuery);
while($rows=mysql_fetch_row($line)) $foundPosts[] = $rows[0];
 
mysql_free_result($line);
return $foundPosts;
}
 
/* 取出單一文章狀態 */
function getPostStatus($status, $statusType){
if(!$this->prepared) $this->dbPrepare();
$returnValue = 0; // 回傳值
 
switch($statusType){
case 'TS': // 討論串是否鎖定
$returnValue = (strpos($status, 'T')!==false) ? 1 : 0; // 討論串是否鎖定
break;
default:
}
return $returnValue;
}
 
/* 設定文章狀態 */
function setPostStatus($no, $status, $statusType, $newValue){
if(!$this->prepared) $this->dbPrepare();
 
$forcount = count($no);
for($i = 0; $i < $forcount; $i++){
$newStatus = ''; // 討論串狀態旗標字串
switch($statusType[$i]){
case 'TS': // 討論串是否停止
$newStatus = $newValue[$i] ? ($status[$i].'T') : str_replace('T', '', $status[$i]); // 更改狀態字串
if(!$this->_mysql_call('UPDATE '.$this->tablename." SET status = '$newStatus' WHERE no = ".$no[$i])) $this->_error_handler('Update the status of the post failed', __LINE__); // 更新討論串屬性
break;
default:
}
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/pio/pio.pgsql.php
@@ -0,0 +1,469 @@
<?php
/**
* PIO PostgreSQL API
*
* 提供存取以 PostgreSQL 資料庫構成的資料結構後端的物件
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
class PIOpgsql{
var $ENV, $username, $password, $server, $port, $dbname, $tablename; // Local Constant
var $con, $prepared; // Local Global
 
function PIOpgsql($connstr='', $ENV){
$this->ENV = $ENV;
$this->prepared = 0;
if($connstr) $this->dbConnect($connstr);
}
 
/* private 攔截SQL錯誤 */
function _error_handler($errtext, $errline){
$err = "Pixmicat! SQL Error: $errtext, debug info: at line $errline";
trigger_error($err, E_USER_ERROR);
}
 
/* private 使用SQL字串和PostgreSQL伺服器要求 */
function _pgsql_call($query){
return pg_query($this->con, $query);
}
 
/* private 由資源輸出陣列 */
function _ArrangeArrayStructure($line){
$posts = array();
while($row=pg_fetch_array($line, null, PGSQL_ASSOC)) $posts[] = $row;
pg_free_result($line);
return $posts;
}
 
/* PIO模組版本 */
function pioVersion(){
return '0.4 (v20070605)';
}
 
/* 處理連線字串/連接 */
function dbConnect($connStr){
// 格式: pgsql://帳號:密碼@伺服器位置:埠號(可省略)/資料庫/資料表/
// 示例: pgsql://pixmicat:1234@127.0.0.1/pixmicat_use/imglog/
if(preg_match('/^pgsql:\/\/(.*)\:(.*)\@([^\:]*)((\:)([0-9]+)){0,1}\/(.*)\/(.*)\/$/i', $connStr, $linkinfos)){
$this->username = $linkinfos[1]; // 登入帳號
$this->password = $linkinfos[2]; // 登入密碼
$this->server = $linkinfos[3]; // 登入伺服器
$this->port = ($linkinfos[6] ? $linkinfos[6] : '5432'); // 登入埠號
$this->dbname = $linkinfos[7]; // 資料庫名稱
$this->tablename = $linkinfos[8]; // 資料表名稱
}
}
 
/* 初始化 */
function dbInit($isAddInitData=true){
$this->dbPrepare();
if(pg_num_rows(pg_query($this->con, "SELECT relname FROM pg_class WHERE relname = '".$this->tablename."'"))!=1){ // 資料表不存在
$result = "CREATE SEQUENCE ".$this->tablename."_no_seq;
CREATE TABLE ".$this->tablename." (
\"no\" int NOT NULL DEFAULT nextval('".$this->tablename."_no_seq'),
\"resto\" int NOT NULL,
\"root\" timestamp NULL DEFAULT '1970-00-00 00:00:00',
\"time\" int NOT NULL,
\"md5chksum\" varchar(32) NOT NULL,
\"category\" varchar(255) NOT NULL,
\"tim\" bigint NOT NULL,
\"ext\" varchar(4) NOT NULL,
\"imgw\" smallint NOT NULL,
\"imgh\" smallint NOT NULL,
\"imgsize\" varchar(10) NOT NULL,
\"tw\" smallint NOT NULL,
\"th\" smallint NOT NULL,
\"pwd\" varchar(8) NOT NULL,
\"now\" varchar(255) NOT NULL,
\"name\" varchar(255) NOT NULL,
\"email\" varchar(255) NOT NULL,
\"sub\" varchar(255) NOT NULL,
\"com\" text NOT NULL,
\"host\" varchar(255) NOT NULL,
\"status\" varchar(4) NOT NULL,
PRIMARY KEY (\"no\"));"; // PIO Structure V2
$idxs = array('resto', 'root', 'time');
foreach($idxs as $idx) $result .= 'CREATE INDEX '.$this->tablename.'_'.$idx.'_index ON '.$this->tablename.' ('.$idx.');';
pg_query($this->con, $result); // 正式新增資料表
if($isAddInitData) $this->addPost(1, 0, '', '', 0, '', 0, 0, '', 0, 0, '', '05/01/01(六)00:00', $this->ENV['NONAME'], '', $this->ENV['NOTITLE'], $this->ENV['NOCOMMENT'], ''); // 追加一筆新資料
$this->dbCommit();
}
}
 
/* 準備/讀入 */
function dbPrepare($transaction=true){
if($this->prepared) return true;
 
if(@!$this->con=pg_pconnect('host='.$this->server.' port='.$this->port.' dbname='.$this->dbname.' user='.$this->username.' password='.$this->password)) $this->_error_handler('Open database failed', __LINE__);
if($transaction) @pg_query($this->con, 'START TRANSACTION;'); // 啟動交易性能模式 (據說會降低效能,但可防止資料寫入不一致)
 
$this->prepared = 1;
}
 
/* 提交/儲存 */
function dbCommit(){
if(!$this->prepared) return false;
 
@pg_query($this->con, 'COMMIT;'); // 交易性能模式提交
}
 
/* 優化資料表 */
function dbOptimize($doit=false){
if($doit){
$this->dbPrepare(false);
if($this->_pgsql_call('VACUUM '.$this->tablename)) return true;
else return false;
}else return true; // 支援最佳化資料表
}
 
/* 匯入資料來源 */
function dbImport($data){
$this->dbInit(false); // 僅新增結構不新增資料
$data = explode("\r\n", $data);
$data_count = count($data) - 1;
$replaceComma = create_function('$txt', 'return str_replace("&#44;", ",", $txt);');
for($i = 0; $i < $data_count; $i++){
$line = array_map($replaceComma, explode(',', $data[$i])); // 取代 &#44; 為 ,
if($line[2]=='0') $line[2] = '1970-00-00 00:00:00'; // 零值
$SQL = 'INSERT INTO '.$this->tablename.' (no,resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
$line[0].','.
$line[1].',\''.
$line[2].'\','.
substr($line[5], 0, 10).',\''.
pg_escape_string($line[3]).'\',\''.
pg_escape_string($line[4]).'\','.
$line[5].',\''.pg_escape_string($line[6]).'\','.
$line[7].','.$line[8].',\''.pg_escape_string($line[9]).'\','.$line[10].','.$line[11].',\''.
pg_escape_string($line[12]).'\',\''.
pg_escape_string($line[13]).'\',\''.
pg_escape_string($line[14]).'\',\''.
pg_escape_string($line[15]).'\',\''.
pg_escape_string($line[16]).'\',\''.
pg_escape_string($line[17]).'\',\''.
pg_escape_string($line[18]).'\',\''.
$line[19].'\')';
//echo $SQL."<BR>\n";
if(!$this->_pgsql_call($SQL)) $this->_error_handler('Insert a new post failed', __LINE__);
}
$this->dbCommit(); // 送交
return true;
}
 
/* 匯出資料來源 */
function dbExport(){
if(!$this->prepared) $this->dbPrepare();
$line = $this->_pgsql_call('SELECT * FROM '.$this->tablename.' ORDER BY no DESC');
$data = '';
$replaceComma = create_function('$txt', 'return str_replace(",", "&#44;", $txt);');
while($row=pg_fetch_array($line, null, PGSQL_ASSOC)){
unset($row['time']); // 非必要欄位
$row = array_map($replaceComma, $row); // 取代 , 為 &#44;
if($row['root']=='1970-00-00 00:00:00') $row['root'] = '0'; // 初始值設為 0
$data .= implode(',', $row).",\r\n";
}
pg_free_result($line);
return $data;
}
 
/* 文章數目 */
function postCount($resno=0){
if(!$this->prepared) $this->dbPrepare();
 
if($resno){ // 回傳討論串總文章數目
$line = $this->_pgsql_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = '.$resno);
$countline = pg_fetch_result($line, 0, 0) + 1;
}else{ // 回傳總文章數目
$line = $this->_pgsql_call('SELECT COUNT(no) FROM '.$this->tablename);
$countline = pg_fetch_result($line, 0, 0);
}
pg_free_result($line);
return $countline;
}
 
/* 討論串數目 */
function threadCount(){
if(!$this->prepared) $this->dbPrepare();
 
$tree = $this->_pgsql_call('SELECT COUNT(no) FROM '.$this->tablename.' WHERE resto = 0');
$counttree = pg_fetch_result($tree, 0, 0); pg_free_result($tree); // 計算討論串目前資料筆數
return $counttree;
}
 
/* 取得最後文章編號 */
function getLastPostNo($state){
if(!$this->prepared) $this->dbPrepare();
 
if($state=='afterCommit'){ // 送出後的最後文章編號
$tree = $this->_pgsql_call('SELECT MAX(no) FROM '.$this->tablename);
$lastno = pg_fetch_result($tree, 0, 0); pg_free_result($tree);
return $lastno;
}else return 0; // 其他狀態沒用
}
 
/* 輸出文章清單 */
function fetchPostList($resno=0, $start=0, $amount=0){
if(!$this->prepared) $this->dbPrepare();
 
$line = array();
if($resno){ // 輸出討論串的結構 (含自己, EX : 1,2,3,4,5,6)
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE no = '.$resno.' OR resto = '.$resno.' ORDER BY no';
}else{ // 輸出所有文章編號,新的在前
$tmpSQL = 'SELECT no FROM '.$this->tablename.' ORDER BY no DESC';
if($amount) $tmpSQL .= " LIMIT {$amount} OFFSET {$start}"; // 有指定數量才用 LIMIT
}
$tree = $this->_pgsql_call($tmpSQL);
while($rows=pg_fetch_array($tree)) $line[] = $rows[0]; // 迴圈
 
pg_free_result($tree);
return $line;
}
 
/* 輸出討論串清單 */
function fetchThreadList($start=0, $amount=0, $isDESC=false){
if(!$this->prepared) $this->dbPrepare();
 
$treeline = array();
$tmpSQL = 'SELECT no FROM '.$this->tablename.' WHERE resto = 0 ORDER BY '.($isDESC ? 'no' : 'root').' DESC';
if($amount) $tmpSQL .= " LIMIT {$amount} OFFSET {$start}"; // 有指定數量才用 LIMIT
$tree = $this->_pgsql_call($tmpSQL);
while($rows=pg_fetch_array($tree)) $treeline[] = $rows[0]; // 迴圈
 
pg_free_result($tree);
return $treeline;
}
 
/* 輸出文章 */
function fetchPosts($postlist){
if(!$this->prepared) $this->dbPrepare();
 
if(is_array($postlist)){ // 取多串
if(!count($postlist)) return array();
$pno = implode(', ', $postlist); // ID字串
$tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no IN ('.$pno.') ORDER BY no';
if(count($postlist) > 1){ if($postlist[0] > $postlist[1]) $tmpSQL .= ' DESC'; } // 由大排到小
}else $tmpSQL = 'SELECT * FROM '.$this->tablename.' WHERE no = '.$postlist; // 取單串
$line = $this->_pgsql_call($tmpSQL);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 刪除舊文 */
function delOldPostes(){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$oldAttachments = array(); // 舊文的附加檔案清單
$countline = $this->postCount(); // 文章數目
$cutIndex = $countline - $this->ENV['LOG_MAX'] + 1; // LIMIT用,取出最舊的幾篇
if(!$result=$this->_pgsql_call('SELECT no,ext,tim FROM '.$this->tablename.' ORDER BY no LIMIT '.$cutIndex)) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=pg_fetch_array($result)){ // 個別跑舊文迴圈
if($dext){
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $oldAttachments[] = $dfile;
if($FileIO->imageExists($dthumb)) $oldAttachments[] = $dthumb;
}
// 逐次搜尋舊文之回應
if(!$resultres=$this->_pgsql_call('SELECT ext,tim FROM '.$this->tablename." WHERE ext <> '' AND resto = $dno")) $this->_error_handler('Get replies of the old post failed', __LINE__);
while(list($rext, $rtim)=pg_fetch_array($resultres)){
$rfile = $rtim.$rext; // 附加檔案名稱
$rthumb = $rtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($rfile)) $oldAttachments[] = $rfile;
if($FileIO->imageExists($rthumb)) $oldAttachments[] = $rthumb;
}
pg_free_result($resultres);
if(!$this->_pgsql_call('DELETE FROM '.$this->tablename.' WHERE no = '.$dno.' OR resto = '.$dno)) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪除文章
}
}
pg_free_result($result);
return $oldAttachments; // 回傳需刪除檔案列表
}
 
/* 刪除舊附件 (輸出附件清單) */
function delOldAttachments($total_size, $storage_max, $warnOnly=true){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$arr_warn = $arr_kill = array(); // 警告 / 即將被刪除標記陣列
if(!$result=$this->_pgsql_call('SELECT no,ext,tim FROM '.$this->tablename." WHERE ext <> '' ORDER BY no")) $this->_error_handler('Get the old post failed', __LINE__);
else{
while(list($dno, $dext, $dtim)=pg_fetch_array($result)){ // 個別跑舊文迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)){ $total_size -= $FileIO->getImageFilesize($dfile) / 1024; $arr_kill[] = $dno; $arr_warn[$dno] = 1; } // 標記刪除
if($FileIO->imageExists($dthumb)) $total_size -= $FileIO->getImageFilesize($dthumb) / 1024;
if($total_size < $storage_max) break;
}
}
pg_free_result($result);
return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);
}
 
/* 刪除文章 */
function removePosts($posts){
if(!$this->prepared) $this->dbPrepare();
 
$files = $this->removeAttachments($posts, true); // 先遞迴取得刪除文章及其回應附件清單
$pno = implode(', ', $posts); // ID字串
if(!$result=$this->_pgsql_call('DELETE FROM '.$this->tablename.' WHERE no IN ('.$pno.') OR resto IN('.$pno.')')) $this->_error_handler('Delete old posts and replies failed', __LINE__); // 刪掉文章
return $files;
}
 
/* 刪除附件 (輸出附件清單) */
function removeAttachments($posts, $recursion=false){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
$files = array();
$pno = implode(', ', $posts); // ID字串
if($recursion) $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE (no IN ('.$pno.') OR resto IN('.$pno.")) AND ext <> ''"; // 遞迴取出 (含回應附件)
else $tmpSQL = 'SELECT ext,tim FROM '.$this->tablename.' WHERE no IN ('.$pno.") AND ext <> ''"; // 只有指定的編號
 
if(!$result=$this->_pgsql_call($tmpSQL)) $this->_error_handler('Get attachments of the post failed', __LINE__);
else{
while(list($dext, $dtim)=pg_fetch_array($result)){ // 個別跑迴圈
$dfile = $dtim.$dext; // 附加檔案名稱
$dthumb = $dtim.'s.jpg'; // 預覽檔案名稱
if($FileIO->imageExists($dfile)) $files[] = $dfile;
if($FileIO->imageExists($dthumb)) $files[] = $dthumb;
}
}
pg_free_result($result);
return $files;
}
 
/* 新增文章/討論串 */
function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false){
if(!$this->prepared) $this->dbPrepare();
 
$time = (int)substr($tim, 0, -3); // 13位數的數字串是檔名,10位數的才是時間數值
$updatetime = gmdate('Y-m-d H:i:s'); // 更動時間 (UTC)
if($resto){ // 新增回應
$root = '1970-00-00 00:00:00';
if($age){ // 推文
$query = 'UPDATE '.$this->tablename.' SET root = "'.$updatetime.'" WHERE no = '.$resto; // 將被回應的文章往上移動
if(!$result=$this->_pgsql_call($query)) $this->_error_handler('Push the post failed', __LINE__);
}
}else $root = $updatetime; // 新增討論串, 討論串最後被更新時間
 
$query = 'INSERT INTO '.$this->tablename.' (resto,root,time,md5chksum,category,tim,ext,imgw,imgh,imgsize,tw,th,pwd,now,name,email,sub,com,host,status) VALUES ('.
(int)$resto.',"'. // 回應編號
$root.'",'. // 最後更新時間
$time.','. // 發文時間數值
"'$md5chksum',". // 附加檔案md5
"'".pg_escape_string($category)."',". // 分類標籤
"'$tim', '$ext',". // 附加檔名
$imgw.','.$imgh.",'".$imgsize."',".$tw.','.$th.','. // 圖檔長寬及檔案大小;預覽圖長寬
"'".pg_escape_string($pwd)."',".
"'$now',". // 時間(含ID)字串
"'".pg_escape_string($name)."',".
"'".pg_escape_string($email)."',".
"'".pg_escape_string($sub)."',".
"'".pg_escape_string($com)."',".
"'".pg_escape_string($host)."', '')";
if(!$result=$this->_pgsql_call($query)) $this->_error_handler('Insert a new post failed', __LINE__);
}
 
/* 檢查是否連續投稿 */
function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$this->ENV['PERIOD.POST']) return false; // 關閉連續投稿檢查
$tmpSQL = 'SELECT pwd,host FROM '.$this->tablename.' WHERE time > '.($timestamp - $this->ENV['PERIOD.POST']); // 一般投稿時間檢查
if($isupload) $tmpSQL .= ' OR time > '.($timestamp - $this->ENV['PERIOD.IMAGEPOST']); // 附加圖檔的投稿時間檢查 (與下者兩者擇一)
else $tmpSQL .= " OR md5(com) = '".md5($com)."'"; // 內文一樣的檢查 (與上者兩者擇一)
if(!$result=$this->_pgsql_call($tmpSQL)) $this->_error_handler('Get the post to check the succession failed', __LINE__);
else{
while(list($lpwd, $lhost)=pg_fetch_array($result)){
// 判斷為同一人發文且符合連續投稿條件
if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) return true;
}
return false;
}
}
 
/* 檢查是否重複貼圖 */
function isDuplicateAttechment($lcount, $md5hash){
global $FileIO;
if(!$this->prepared) $this->dbPrepare();
 
if(!$result=$this->_pgsql_call('SELECT tim,ext FROM '.$this->tablename." WHERE ext <> '' AND md5chksum = '$md5hash' ORDER BY no DESC")) $this->_error_handler('Get the post to check the duplicate attachment failed', __LINE__);
else{
while(list($ltim, $lext)=pg_fetch_array($result)){
if($FileIO->imageExists($ltim.$lext)) return true; // 有相同檔案
}
return false;
}
}
 
/* 有此討論串? */
function isThread($no){
if(!$this->prepared) $this->dbPrepare();
 
$result = $this->_pgsql_call('SELECT no FROM '.$this->tablename.' WHERE no = '.$no.' AND resto = 0');
return pg_fetch_array($result);
}
 
/* 搜尋文章 */
function searchPost($keyword, $field, $method){
if(!$this->prepared) $this->dbPrepare();
 
$keyword_cnt = count($keyword);
$SearchQuery = 'SELECT * FROM '.$this->tablename." WHERE {$field} LIKE '%".($keyword[0])."%'";
if($keyword_cnt > 1) for($i = 1; $i < $keyword_cnt; $i++) $SearchQuery .= " {$method} {$field} LIKE '%".($keyword[$i])."%'"; // 多重字串交集 / 聯集搜尋
$SearchQuery .= ' ORDER BY no DESC'; // 按照號碼大小排序
if(!$line=$this->_pgsql_call($SearchQuery)) $this->_error_handler('Search the post failed', __LINE__);
 
return $this->_ArrangeArrayStructure($line); // 輸出陣列結構
}
 
/* 搜尋類別標籤 */
function searchCategory($category){
if(!$this->prepared) $this->dbPrepare();
 
$foundPosts = array();
$SearchQuery = 'SELECT no FROM '.$this->tablename." WHERE category ~* ',".pg_escape_string($category).",' ORDER BY no DESC";
$line = $this->_pgsql_call($SearchQuery);
while($rows=pg_fetch_array($line)) $foundPosts[] = $rows[0];
 
pg_free_result($line);
return $foundPosts;
}
 
/* 取出單一文章狀態 */
function getPostStatus($status, $statusType){
if(!$this->prepared) $this->dbPrepare();
$returnValue = 0; // 回傳值
 
switch($statusType){
case 'TS': // 討論串是否鎖定
$returnValue = (strpos($status, 'T')!==false) ? 1 : 0; // 討論串是否鎖定
break;
default:
}
return $returnValue;
}
 
/* 設定文章狀態 */
function setPostStatus($no, $status, $statusType, $newValue){
if(!$this->prepared) $this->dbPrepare();
 
$forcount = count($no);
for($i = 0; $i < $forcount; $i++){
$newStatus = ''; // 討論串狀態旗標字串
switch($statusType[$i]){
case 'TS': // 討論串是否停止
$newStatus = $newValue[$i] ? ($status[$i].'T') : str_replace('T', '', $status[$i]); // 更改狀態字串
if(!$this->_pgsql_call('UPDATE '.$this->tablename." SET status = '$newStatus' WHERE no = ".$no[$i])) $this->_error_handler('Update the status of the post failed', __LINE__); // 更新討論串屬性
break;
default:
}
}
}
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_pms.php
@@ -0,0 +1,20 @@
<?php
/**
* Pixmicat! Module System Dispatcher
*
* 設定環境變數並初始化物件以供使用
*
* @package PMCLibrary
* @version $Id$
* @date $Date$
*/
 
$PMSEnv = array( // PMS 環境常數
'MODULE.PATH' => './module/',
'MODULE.PAGE' => PHP_SELF.'?mode=module&amp;load=',
'MODULE.LOADLIST' => $ModuleList
);
require('./lib/pms.php');
$PMS = new PMS($PMSEnv);
$PMS->init();
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_language.php
@@ -0,0 +1,26 @@
<?php
/*
Pixmicat! Language module loader
*/
 
function _T(/*$arg1, $arg2...$argN*/) {
global $language;
if (!isset($language)) // language file is not loaded
LoadLanguage(PIXMICAT_LANGUAGE);
if (!func_num_args()) // called with no arg
return '';
$arg_list = func_get_args();
$arg_list[0] = isset($language[$arg_list[0]]) ? $language[$arg_list[0]] : $arg_list[0];
return call_user_func_array('sprintf',$arg_list);
}
 
function LoadLanguage($locale = 'en_US') {
global $language;
if(!defined('PIXMICAT_LANGUAGE') || defined('PIXMICAT_LANGUAGE_OVERLOADING')) // language overloading
include_once("./lib/lang/en_US.php");
if (file_exists("./lib/lang/$locale.php"))
include_once("./lib/lang/$locale.php");
else
include_once("./lib/lang/en_US.php");
}
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lang/ja_JP.php
@@ -0,0 +1,191 @@
<?php
/*
Pixmicat! language file - Japanese [ja_JP]
*/
if (!isset($language)) $language = Array();
 
// pixmicat.php
$language['page_not_found'] = 'ページがありません';
$language['thread_not_found'] = '該当記事がみつかりません';
$language['del_head'] = '【記事削除】';
$language['del_img_only'] = '画像だけ消す';
$language['del_pass'] = '削除キー: ';
$language['del_btn'] = ' 削除 ';
$language['prev_page'] = '前のページ';
$language['first_page'] = '最初のページ';
$language['all_pages'] = 'すべて';
$language['next_page'] = '次のページ';
$language['last_page'] = '最後のページ';
$language['img_sample'] = '[サムネ表示]';
$language['img_filename'] = '画像ファイル名:';
$language['reply_btn'] = '返信';
$language['warn_sizelimit'] = 'この画像は容量リミットの原因なので、もうすぐ消えます。';
$language['warn_oldthread'] = 'このスレは古いので、もうすぐ消えます。';
$language['warn_locked'] = '管理者によるスレッドストップです。';
$language['notice_omitted'] = 'レス %1$s 件省略。全て読むには返信ボタンを押してください。';
$language['post_name'] = 'Name ';
$language['post_category'] = 'カテゴリ: ';
$language['regist_notpost'] = '不正な投稿をしないで下さい(post)';
$language['regist_nospam'] = '反 Spambot 機能スタート!';
$language['regist_ipfiltered'] = '拒絶されました(host) 原因:%1$s';
$language['regist_wordfiltered'] = '拒絶されました(str)';
$language['regist_upload_exceedphp'] = 'アップロードに失敗しました<br />PHPファイルサイズオーバー';
$language['regist_upload_exceedcustom'] = 'アップロードに失敗しました<br />ファイルサイズオーバー';
$language['regist_upload_incompelete'] = 'アップロードに失敗しました<br />ファイルが破損しています';
$language['regist_upload_direrror'] = 'アップロードに失敗しました<br />パーミッションエラー';
$language['regist_upload_noimg'] = '画像がありません';
$language['regist_upload_filenotfound'] = 'アップロードに失敗しました<br />サーバがサポートしていない可能性があります';
$language['regist_upload_killincomp'] = '[Notice] Your sending was canceled because of the incorrect file size.';
$language['regist_upload_notimage'] = 'アップロードに失敗しました<br />画像ファイル以外は受け付けません';
$language['regist_upload_notsupport'] = 'アップロードに失敗しました<br />GIF,JPG,PNG以外の画像ファイルは受け付けません';
$language['regist_upload_blocked'] = 'アップロードに失敗しました<br />禁止画像です';
$language['regist_uploaded'] = '画像 %1$s のアップロードが成功しました<br />';
$language['regist_sakuradetected'] = '櫻花仮名を検出した';
$language['regist_withoutname'] = '名前がありません';
$language['regist_withoutcomment'] = '何か書いて下さい';
$language['regist_nametoolong'] = '名前が長すぎます';
$language['regist_emailtoolong'] = 'メル欄が長すぎます';
$language['regist_topictoolong'] = '題名が長すぎます';
$language['regist_longthreadnum'] = '変なレス番号指定するんじゃないの';
$language['admin'] = '管理';
$language['deletor'] = '削除';
$language['trip_pre'] = '◆';
$language['trip_pre_fake'] = '◇';
$language['cap_char'] = '★';
$language['cap_char_fake'] = '☆';
$language['regist_commenttoolong'] = 'コメントが長すぎます';
$language['notice_incompletefile'] = '注意:ファイルが破損しています';
$language['sun'] = '日';
$language['mon'] = '月';
$language['tue'] = '火';
$language['wed'] = '水';
$language['thu'] = '木';
$language['fri'] = '金';
$language['sat'] = '土';
$language['regist_successivepost'] = '連続投稿はもうしばらく時間を置いてからお願い致します';
$language['regist_duplicatefile'] = 'アップロードに失敗しました<br />同じ画像があります';
$language['regist_threaddeleted'] = 'スレッドがありません';
$language['regist_threadlocked'] = 'もう書けませんです';
$language['regist_redirect'] = '%1$s 画面を切り替えます。<p>しばらく待っても変わらない場合は、<a href="%2$s">こちら</a>をクリックしてください。</p>';
$language['del_notchecked'] = '何でもない削除のために選択されなかった。';
$language['del_wrongpwornotfound'] = '該当記事が見つからないかパスワードが間違っています';
$language['admin_wrongpassword'] = '"パスワードが違います';
$language['return'] = '掲示板に戻る';
$language['admin_remake'] = 'ログを更新する';
$language['admin_top'] = '管理モード';
$language['admin_manageposts'] = '記事管理';
$language['admin_optimize'] = 'テーブルを最適化する';
$language['admin_verify_btn'] = ' 認証 ';
$language['admin_archive'] = '<th>保管</th>';
$language['admin_notices'] = '<ul><li>削除したい記事の削除チェックボックスにチェックを入れ、実行ボタンを押して下さい。</li><li>終了/再開したいスレのストップチェックボックスにチェックを入れ、実行ボタンを押して下さい。</li><li>他のページに行くとき、動作を実行するのに注意してください。</li><li>記事を管理した後に、[ログを更新する]を押してください。</li></ul>';
$language['admin_submit_btn'] = ' 実行する ';
$language['admin_reset_btn'] = ' リセット ';
$language['admin_list_header'] = '<th>機能</th><th>ストップ</th><th>削除</th><th>投稿日</th><th>題名</th><th>投稿者</th><th>コメント</th><th>ホスト名</th><th>添付(Bytes)<br />MD5</th>';
$language['admin_archive_btn'] = '保管';
$language['admin_stop_btn'] = 'ストップ';
$language['admin_totalsize'] = '【 画像データ合計 : <b>%1$s</b> KB 】';
$language['search_disabled'] = '検索は禁じられます。';
$language['search_top'] = '検索';
$language['search_notice'] = '<li>お探しの記事に関連する言葉をキーワード入力欄に入力して検索ボタンを押してください。</li><li>複数のキーワードの間にスペースを入れて入力します。</li><li>複数キーワード間の検索条件を細かく指定する場合に利用します。<p />キーワード:';
$language['search_target'] = '検索対象:';
$language['search_target_comment'] = 'コメント';
$language['search_target_name'] = 'お名前';
$language['search_target_topic'] = '題名';
$language['search_target_number'] = '記事No.';
$language['search_method'] = '検索条件:';
$language['search_method_and'] = 'AND';
$language['search_method_or'] = 'OR';
$language['search_submit_btn'] = ' 検索 ';
$language['search_notfound'] = 'キーワードにマッチする記事がありませんでした。';
$language['search_back'] = '[戻る]';
$language['category_nokeyword'] = '探すことの同様の記事のためにカテゴリに入ってください。'; /***/
$language['category_notfound'] = 'カテゴリにマッチする記事がありませんでした。';
$language['category_recache'] = 'キャッシュを更新する';
$language['module_info_top'] = 'モジュール情報';
$language['module_loaded'] = 'Module Loaded:';
$language['module_info'] = 'Module Infomation:';
$language['info_top'] = 'システム情報';
$language['info_disabled'] = '無効';
$language['info_enabled'] = '有効';
$language['info_functional'] = '働ける';
$language['info_nonfunctional'] = '働けない';
$language['info_basic'] = '基本設定';
$language['info_basic_ver'] = 'バージョン';
$language['info_basic_pio'] = 'PIO バックエンドとバージョン';
$language['info_basic_threadsperpage'] = '1ページに表示する記事';
$language['info_basic_threads'] = '件';
$language['info_basic_postsperpage'] = 'スレに表示するレス';
$language['info_basic_posts'] = '件';
$language['info_basic_postsinthread'] = '1ページに表示するレス';
$language['info_basic_posts_showall'] = '(すべて:0)';
$language['info_basic_bumpposts'] = '強制sageレス数';
$language['info_basic_bumphours'] = '強制sageまでの時間';
$language['info_basic_hours'] = '時間';
$language['info_basic_0disable'] = '(無効:0)';
$language['info_basic_urllinking'] = 'URLをリンク';
$language['info_0no1yes'] = '(する:1 しない:0)';
$language['info_basic_com_limit'] = 'コメント欄最大文字数';
$language['info_basic_com_after'] = '';
$language['info_basic_anonpost'] = '名前を必須に';
$language['info_basic_anonpost_opt'] = '(強制名無し化:2 いいえ:1 はい:0)';
$language['info_basic_del_incomplete'] = '破損したファイルを消す';
$language['info_basic_use_sample'] = 'サムネイルを作る (画質:%1$s)';
$language['info_0notuse1use'] = '(する:1 しない:0)';
$language['info_basic_use_sample_func'] = '└ サムネイルを作り関数';
$language['info_basic_useblock'] = '拒絶するホスト';
$language['info_0disable1enable'] = '(有効:1 無効:0)';
$language['info_basic_showid'] = 'IDを表示する?';
$language['info_basic_showid_after'] = '(強制:2 する:1 しない:0)';
$language['info_basic_cr_limit'] = '改行を抑制する行数';
$language['info_basic_cr_after'] = ' 行 (しない:0)';
$language['info_basic_timezone'] = '時間帯設定';
$language['info_basic_threadcount'] = '現在のスレ数';
$language['info_dsusage_top'] = 'データソース使用量';
$language['info_dsusage_max'] = '最大値';
$language['info_dsusage_usage'] = '使用率';
$language['info_dsusage_count'] = '現在の値';
$language['info_fileusage_top'] = '添付ファイル容量リミット:';
$language['info_fileusage_limit'] = '大きさの上限';
$language['info_fileusage_count'] = '現在の容量';
$language['info_fileusage_unlimited'] = '限界なし';
$language['info_server_top'] = 'サーバー支援情報';
$language['info_server_gd'] = 'GD モジュール';
$language['init_permerror'] = 'カレントディレクトリに書けません<br />';
$language['init_inited'] = '環境初始化成功した!<br />このファイルを編集して、init()を削除してください。<br />';
$language['action_opt_notsupport'] = 'バックエンドには、この動作がありません。';
$language['action_opt_optimize'] = 'テーブル最適化';
$language['action_opt_success'] = '成功した';
$language['action_opt_failed'] = '失敗した';
 
// lib_common.php
$language['head_home'] = 'ホーム';
$language['head_search'] = '検索';
$language['head_info'] = 'システム情報';
$language['head_admin'] = '管理用';
$language['head_refresh'] = 'リロード';
$language['form_top'] = 'レス送信モード';
$language['form_showpostform'] = '投稿';
$language['form_hidepostform'] = 'フォームを隠す';
$language['form_name'] = 'おなまえ';
$language['form_email'] = 'E-mail';
$language['form_topic'] = '題  名';
$language['form_submit_btn'] = '送信する';
$language['form_comment'] = 'コメント';
$language['form_attechment'] = '添付File';
$language['form_noattechment'] = '画像なし';
$language['form_contpost'] = '連貼り';
$language['form_category'] = 'カテゴリ';
$language['form_category_notice'] = '(複数のカテゴリの間に「,」を入れて入力します)';
$language['form_delete_password'] = '投稿キー';
$language['form_delete_password_notice']= '(記事の削除用。英数字で8文字以内)';
$language['form_notice'] = '<li>添付可能ファイル:GIF, JPG, PNG ブラウザによっては正常に添付できないことがあります。</li><li>最大投稿データ量は %1$s KBまで。sage機能付き。</li><li>画像は横 %2$s ピクセル、縦 %3$s ピクセルを超えると縮小表示されます。</li>';
$language['form_notice_storage_limit'] = '<li>現在の添付ファイル使用量: %1$s KB / %2$s KB</li>';
$language['form_notice_noscript'] = '*JavaScriptは無効にでもブラウジングと返信に影響しない。';
$language['error_back'] = '戻る';
$language['ip_banned'] = 'IP/ホスト名ブラックリストに記載されています。';
$language['ip_dnsbl_banned'] = 'DNSBL(%1$s) ブラックリストに記載されています。';
 
// mainscript.js // regist_withoutcomment,regist_upload_notsupport,js_convert_sakura
$language['js_convert_sakura'] = '櫻花仮名を検出した。変換します。';
 
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lang/en_US.php
@@ -0,0 +1,191 @@
<?php
/*
Pixmicat! language file - English (U.S.) [en_US]
*/
if (!isset($language)) $language = Array();
 
// pixmicat.php
$language['page_not_found'] = 'Sorry, the page you requested is not found.';
$language['thread_not_found'] = 'The thread you want to reply does not exist!';
$language['del_head'] = '[Delete Post]';
$language['del_img_only'] = 'Delete image only';
$language['del_pass'] = 'Password: ';
$language['del_btn'] = ' Delete ';
$language['prev_page'] = 'Previous';
$language['first_page'] = 'First';
$language['all_pages'] = 'ALL';
$language['next_page'] = 'Next';
$language['last_page'] = 'Last';
$language['img_sample'] = '[Thumbnail]';
$language['img_filename'] = 'Filename: ';
$language['reply_btn'] = 'Reply';
$language['warn_sizelimit'] = 'Image will be deleted soon because of the storage limit of image.';
$language['warn_oldthread'] = 'Thread will be deleted soon because it is old.';
$language['warn_locked'] = 'Thread is locked by administrator.';
$language['notice_omitted'] = '%1$s posts omitted. Click Reply to view.';
$language['post_name'] = 'Name: ';
$language['post_category'] = 'Category: ';
$language['regist_notpost'] = 'Please use the form on the board to submit.';
$language['regist_nospam'] = 'Anti Spambot system activated.';
$language['regist_ipfiltered'] = 'Your connection is refused. Reason: %1$s';
$language['regist_wordfiltered'] = 'Banned words is found, cannot submit.';
$language['regist_upload_exceedphp'] = 'Upload failed.<br />File size exceeds server limitation.';
$language['regist_upload_exceedcustom'] = 'Upload failed.<br />File size exceeds limitation.';
$language['regist_upload_incompelete'] = 'Upload failed.<br />Incomplete upload. Please retry.';
$language['regist_upload_direrror'] = 'Upload failed.<br />Wrong setting of upload temporary directory. Please contact the system administrator.';
$language['regist_upload_noimg'] = 'Please check No Image check box if you post without uploading an image!';
$language['regist_upload_filenotfound'] = 'Upload failed.<br />Server disabled file uploading, permission denied, or unsupported format.';
$language['regist_upload_killincomp'] = '[Notice] Your sending was canceled because of the incorrect file size.';
$language['regist_upload_notimage'] = 'Upload failed.<br />Files other than image is not accepted.';
$language['regist_upload_notsupport'] = 'Unsupported image.';
$language['regist_upload_blocked'] = 'Upload failed.<br />Uploading this image is blocked.';
$language['regist_uploaded'] = 'Image %1$s uploaded.<br />';
$language['regist_sakuradetected'] = 'Big5 sakura Japanese characters detected.';
$language['regist_withoutname'] = 'Please fill your name.';
$language['regist_withoutcomment'] = 'Please fill comment field if you don\'t upload an image.';
$language['regist_nametoolong'] = 'Name is too long.';
$language['regist_emailtoolong'] = 'E-mail is too long.';
$language['regist_topictoolong'] = 'Topic is too long.';
$language['regist_longthreadnum'] = 'The thread you reply may be wrong.';
$language['admin'] = 'Admin';
$language['deletor'] = 'Deletor';
$language['trip_pre'] = '!';
$language['trip_pre_fake'] = '|';
$language['cap_char'] = '¤';
$language['cap_char_fake'] = 'ø';
$language['regist_commenttoolong'] = 'Comment is too long.';
$language['notice_incompletefile'] = 'Notice: Incomplete image.';
$language['sun'] = 'Sun';
$language['mon'] = 'Mon';
$language['tue'] = 'Tue';
$language['wed'] = 'Wed';
$language['thu'] = 'Thu';
$language['fri'] = 'Fri';
$language['sat'] = 'Sat';
$language['regist_successivepost'] = 'Please wait for a while for continuous posting.';
$language['regist_duplicatefile'] = 'Upload failed.<br />Same file uploaded lately.';
$language['regist_threaddeleted'] = 'This thread is too old that it is deleted!';
$language['regist_threadlocked'] = 'Thread is locked by administrator!';
$language['regist_redirect'] = '%1$s Redirecting... <p>If your browser don\'t redirect for you, please click: <a href="%2$s">Return</a></p>';
$language['del_notchecked'] = 'Nothing selected for deletion. Please go back and select again.';
$language['del_wrongpwornotfound'] = 'No such post or wrong password.';
$language['admin_wrongpassword'] = 'Wrong password';
$language['return'] = 'Return';
$language['admin_remake'] = 'Update';
$language['admin_top'] = 'Administrator mode';
$language['admin_manageposts'] = 'Manage posts';
$language['admin_optimize'] = 'Optimize';
$language['admin_verify_btn'] = ' Sign in ';
$language['admin_archive'] = '<th>Archive</th>';
$language['admin_notices'] = '<ul><li>If you want to delete a post, check the "delete" checkbox before that post and click Submit button.</li><li>If you want to delete image only, please check "Delete image only" checkbox and follow normal deletion procedures.</li><li>If you want to lock/unlock a thread, please check "Stop" checkbox of that thread and click "Submit" button.</li><li>Actions will be submitted when you go to other page, please be careful.</li><li>Please press "Update" after managing posts.</li></ul>';
$language['admin_submit_btn'] = ' Submit ';
$language['admin_reset_btn'] = ' Reset ';
$language['admin_list_header'] = '<th>Func</th><th>Stop</th><th>Del</th><th>Date</th><th>Topic</th><th>Name</th><th>Comment</th><th>Host</th><th>Image (Bytes)<br />MD5 checksum</th>';
$language['admin_archive_btn'] = 'A';
$language['admin_stop_btn'] = 'Stop';
$language['admin_totalsize'] = '[Total size of images: <b>%1$s</b> KB ]';
$language['search_disabled'] = 'Search is disabled!';
$language['search_top'] = 'Search';
$language['search_notice'] = '<li>Please enter keyword and set search target, then press "Search" button.</li><li>Searching with multiple keywords can be done by seperating keywords with space.</li><li>You can set Search method("and searching" and "or searching") for multiple keywords searching.<p />Keyword:';
$language['search_target'] = 'Target:';
$language['search_target_comment'] = 'Comment';
$language['search_target_name'] = 'Name';
$language['search_target_topic'] = 'Topic';
$language['search_target_number'] = 'No.';
$language['search_method'] = 'Method:';
$language['search_method_and'] = 'AND';
$language['search_method_or'] = 'OR';
$language['search_submit_btn'] = ' Search ';
$language['search_notfound'] = 'no search result with specified keyword.';
$language['search_back'] = '[Back]';
$language['category_nokeyword'] = 'Please enter category for searching similar posts.';
$language['category_notfound'] = 'No matching posts for this category.';
$language['category_recache'] = 'Recache';
$language['module_info_top'] = 'Module Information';
$language['module_loaded'] = 'Module Loaded:';
$language['module_info'] = 'Module Infomation:';
$language['info_top'] = 'System Information';
$language['info_disabled'] = 'Disabled';
$language['info_enabled'] = 'Enabled';
$language['info_functional'] = 'Functional';
$language['info_nonfunctional'] = 'Non functional';
$language['info_basic'] = 'Basic Settings';
$language['info_basic_ver'] = 'Program version';
$language['info_basic_pio'] = 'PIO library backend and version';
$language['info_basic_threadsperpage'] = 'Threads per page';
$language['info_basic_threads'] = '';
$language['info_basic_postsperpage'] = 'Replies to show in index';
$language['info_basic_posts'] = '';
$language['info_basic_postsinthread'] = 'Posts per page in Reply mode';
$language['info_basic_posts_showall'] = '(Show all:0)';
$language['info_basic_bumpposts'] = 'Do not bump post if reply is more than';
$language['info_basic_bumphours'] = 'Thread bumping hours';
$language['info_basic_hours'] = 'hour(s)';
$language['info_basic_0disable'] = '(Disable:0)';
$language['info_basic_urllinking'] = 'URL Auto Linking';
$language['info_0no1yes'] = '(Yes:1 No:0)';
$language['info_basic_com_limit'] = 'Maximum size of comments';
$language['info_basic_com_after'] = ' Bytes';
$language['info_basic_anonpost'] = 'Anonymous posting';
$language['info_basic_anonpost_opt'] = '(Force anonymous:2 Yes:1 No:0)';
$language['info_basic_del_incomplete'] = 'Delete incomplete images';
$language['info_basic_use_sample'] = 'Use thumbnails (Quality: %1$s)';
$language['info_0notuse1use'] = '(Use:1 Not used:0)';
$language['info_basic_use_sample_func'] = '+ Thumbnails generation';
$language['info_basic_useblock'] = 'IP blocking';
$language['info_0disable1enable'] = '(Enable:1 Disable:0)';
$language['info_basic_showid'] = 'Show ID';
$language['info_basic_showid_after'] = '(force show:2 selective show:1 do not show:0)';
$language['info_basic_cr_limit'] = 'Comment row limit';
$language['info_basic_cr_after'] = ' Row(s) (unlimited:0)';
$language['info_basic_timezone'] = 'Time zone';
$language['info_basic_threadcount'] = 'Total threads count';
$language['info_dsusage_top'] = 'Data source usage';
$language['info_dsusage_max'] = 'Maximum size';
$language['info_dsusage_usage'] = 'Usage';
$language['info_dsusage_count'] = 'Current usage';
$language['info_fileusage_top'] = 'Storage limit of images:';
$language['info_fileusage_limit'] = 'Maximum size';
$language['info_fileusage_count'] = 'Current usage';
$language['info_fileusage_unlimited'] = 'Unlimited';
$language['info_server_top'] = 'Server infomation';
$language['info_server_gd'] = 'GD library ';
$language['init_permerror'] = 'No write permission in root directory. Please modify permission settings.<br />';
$language['init_inited'] = 'Environment initialized.<br />Please edit this file and remove init() statement.<br />';
$language['action_opt_notsupport'] = 'Backend does not support this operation.';
$language['action_opt_optimize'] = 'Optimization ';
$language['action_opt_success'] = 'success!';
$language['action_opt_failed'] = 'failed!';
 
// lib_common.php
$language['head_home'] = 'Home';
$language['head_search'] = 'Search';
$language['head_info'] = 'Info';
$language['head_admin'] = 'Admin';
$language['head_refresh'] = 'Refresh';
$language['form_top'] = 'Reply Mode';
$language['form_showpostform'] = 'Post';
$language['form_hidepostform'] = 'Hide form';
$language['form_name'] = 'Name';
$language['form_email'] = 'E-mail';
$language['form_topic'] = 'Topic';
$language['form_submit_btn'] = 'Submit';
$language['form_comment'] = 'Comment';
$language['form_attechment'] = 'Image';
$language['form_noattechment'] = 'No Image';
$language['form_contpost'] = 'Continuous';
$language['form_category'] = 'Category';
$language['form_category_notice'] = '(Use , to seperate)';
$language['form_delete_password'] = 'Password';
$language['form_delete_password_notice']= '(for deletion, 8 chars max)';
$language['form_notice'] = '<li>Supported file types are: GIF, JPG, PNG</li><li>Maximum file size allowed is %1$s KB. Fill E-mail with sage for not bumping post.</li><li>mages greater than %2$s * %3$s pixels will be thumbnailed.</li>';
$language['form_notice_storage_limit'] = '<li>Current storage usage: %1$s KB / %2$s KB</li>';
$language['form_notice_noscript'] = '*** You disabled JavaScript, but this won\'t affect you when browsing and posting.';
$language['error_back'] = 'Back';
$language['ip_banned'] = 'Listed in IP/Hostname Blacklist';
$language['ip_dnsbl_banned'] = 'Listed in DNSBL(%1$s) Blacklist';
 
// mainscript.js // regist_withoutcomment,regist_upload_notsupport,js_convert_sakura
$language['js_convert_sakura'] = 'Big5 sakura Japanese characters detected, please try to convert to standard one.';
 
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lang/zh_TW.php
@@ -0,0 +1,191 @@
<?php
/*
Pixmicat! language file - Chinese Traditional [zh_TW]
*/
if (!isset($language)) $language = Array();
 
// pixmicat.php
$language['page_not_found'] = '對不起,您所要求的頁數並不存在';
$language['thread_not_found'] = '欲回應之文章並不存在!';
$language['del_head'] = '【刪除文章】';
$language['del_img_only'] = '僅刪除附加圖檔';
$language['del_pass'] = '刪除用密碼: ';
$language['del_btn'] = ' 刪除 ';
$language['prev_page'] = '上一頁';
$language['first_page'] = '第一頁';
$language['all_pages'] = 'ALL';
$language['next_page'] = '下一頁';
$language['last_page'] = '最後一頁';
$language['img_sample'] = '[以預覽圖顯示]';
$language['img_filename'] = '檔名:';
$language['reply_btn'] = '回應';
$language['warn_sizelimit'] = '這篇因附加圖檔容量限制,附加圖檔不久後就會刪除。';
$language['warn_oldthread'] = '這篇已經很舊了,不久後就會刪除。';
$language['warn_locked'] = '這篇討論串已被管理員標記為禁止回應。';
$language['notice_omitted'] = '有回應 %1$s 篇被省略。要閱讀所有回應請按下回應連結。';
$language['post_name'] = '名稱: ';
$language['post_category'] = '類別: ';
$language['regist_notpost'] = '請使用此版提供的表單來上傳';
$language['regist_nospam'] = '防止 Spambot 機制啟動!';
$language['regist_ipfiltered'] = '您所使用的連線已被拒絕。原因:%1$s';
$language['regist_wordfiltered'] = '發出的文章中有被管理員列為限制的字句,送出失敗';
$language['regist_upload_exceedphp'] = '上傳失敗<br />上傳的附加圖檔容量超過PHP內定值';
$language['regist_upload_exceedcustom'] = '上傳失敗<br />上傳的附加圖檔容量超過上傳容量限制';
$language['regist_upload_incompelete'] = '上傳失敗<br />上傳的附加圖檔不完整,請回版面再重試';
$language['regist_upload_direrror'] = '上傳失敗<br />上傳的暫存資料夾設定錯誤,請通報系統管理員';
$language['regist_upload_noimg'] = '因應防止Spam對策,發文無附加圖檔請勾選[無貼圖]核選框!';
$language['regist_upload_filenotfound'] = '上傳失敗<br />伺服器有可能禁止上傳、沒有權限,或不支援此格式';
$language['regist_upload_killincomp'] = '[Notice] Your sending was canceled because of the incorrect file size.';
$language['regist_upload_notimage'] = '上傳失敗<br />不接受圖片以外的檔案';
$language['regist_upload_notsupport'] = '附加圖檔為系統不支援的格式';
$language['regist_upload_blocked'] = '上傳失敗<br />此附加圖檔被管理員列為禁止上傳';
$language['regist_uploaded'] = '附加圖檔 %1$s 上傳完畢<br />';
$language['regist_sakuradetected'] = '偵測到您有輸入櫻花日文假名';
$language['regist_withoutname'] = '您沒有填寫名稱';
$language['regist_withoutcomment'] = '在沒有附加圖檔的情況下,請寫入內文';
$language['regist_nametoolong'] = '名稱過長';
$language['regist_emailtoolong'] = 'E-mail過長';
$language['regist_topictoolong'] = '標題過長';
$language['regist_longthreadnum'] = '欲回應的文章編號可能有誤';
$language['admin'] = '管理';
$language['deletor'] = '刪除';
$language['trip_pre'] = '◆';
$language['trip_pre_fake'] = '◇';
$language['cap_char'] = '★';
$language['cap_char_fake'] = '☆';
$language['regist_commenttoolong'] = '內文過長';
$language['notice_incompletefile'] = '注意:附加圖檔上傳不完全';
$language['sun'] = '日';
$language['mon'] = '一';
$language['tue'] = '二';
$language['wed'] = '三';
$language['thu'] = '四';
$language['fri'] = '五';
$language['sat'] = '六';
$language['regist_successivepost'] = '連續投稿請稍候一段時間';
$language['regist_duplicatefile'] = '上傳失敗<br />近期已經有相同的附加圖檔';
$language['regist_threaddeleted'] = '此討論串因為過舊已被刪除!';
$language['regist_threadlocked'] = '這篇討論串已被管理員標記為禁止回應!';
$language['regist_redirect'] = '%1$s 畫面正在切換 <p>如果瀏覽器沒有自動切換,請手動按連結前往:<a href="%2$s">回到版面</a></p>';
$language['del_notchecked'] = '你真的有要刪除嗎?請回頁面重勾選';
$language['del_wrongpwornotfound'] = '無此文章或是密碼錯誤';
$language['admin_wrongpassword'] = '密碼錯誤';
$language['return'] = '回到版面';
$language['admin_remake'] = '更新文章';
$language['admin_top'] = '管理模式';
$language['admin_manageposts'] = '管理文章';
$language['admin_optimize'] = '資料表最佳化';
$language['admin_verify_btn'] = ' 認證 ';
$language['admin_archive'] = '<th>庫存</th>';
$language['admin_notices'] = '<ul><li>想刪除文章,請勾選該文章前之「刪除」核取框之後按下執行按鈕</li><li>只想刪除文章的附加圖檔,請先勾選「僅刪除附加圖檔」再按照一般刪文方式</li><li>想停止/繼續討論串,請勾選該文章前之「停止」核取框之後按下執行按鈕</li><li>勾選後換頁亦相當於執行,請慎用此功能</li><li>管理文章完畢,記得順手按下「更新文章」以更新靜態快取</li></ul>';
$language['admin_submit_btn'] = ' 執行 ';
$language['admin_reset_btn'] = ' 重置 ';
$language['admin_list_header'] = '<th>功能</th><th>停止</th><th>刪除</th><th>投稿日</th><th>標題</th><th>名稱</th><th>內文</th><th>主機位置名稱</th><th>附加圖檔 (Bytes)<br />MD5 檢查碼</th>';
$language['admin_archive_btn'] = '存';
$language['admin_stop_btn'] = '停';
$language['admin_totalsize'] = '【 附加圖檔使用容量總計 : <b>%1$s</b> KB 】';
$language['search_disabled'] = '管理員選擇不開放搜尋功能!';
$language['search_top'] = '搜尋';
$language['search_notice'] = '<li>請輸入要搜尋的關鍵字,設定好搜尋目標之後,按下「搜尋」按鈕。</li><li>關鍵字使用半形空白可以區隔多個搜尋關鍵字作搜尋。</li><li>如果有多個關鍵字,可以選擇搜尋方法,系統提供 AND(交集) 和 OR(聯集) 方式搜尋。<p />關鍵字:';
$language['search_target'] = '搜尋目標:';
$language['search_target_comment'] = '內文';
$language['search_target_name'] = '名稱';
$language['search_target_topic'] = '標題';
$language['search_target_number'] = '編號';
$language['search_method'] = '搜尋方法:';
$language['search_method_and'] = 'AND';
$language['search_method_or'] = 'OR';
$language['search_submit_btn'] = ' 搜尋 ';
$language['search_notfound'] = '找不到符合的關鍵字。';
$language['search_back'] = '[回上一頁]';
$language['category_nokeyword'] = '請輸入類別標籤以搜尋類似文章。';
$language['category_notfound'] = '沒有符合此類別標籤的文章';
$language['category_recache'] = '重新快取';
$language['module_info_top'] = '模組資訊';
$language['module_loaded'] = 'Module Loaded:';
$language['module_info'] = 'Module Infomation:';
$language['info_top'] = '系統資訊';
$language['info_disabled'] = '未開啟';
$language['info_enabled'] = '已開啟';
$language['info_functional'] = '功能正常';
$language['info_nonfunctional'] = '功能失常';
$language['info_basic'] = '基本設定';
$language['info_basic_ver'] = '程式版本';
$language['info_basic_pio'] = 'PIO 函式庫後端及版本';
$language['info_basic_threadsperpage'] = '一頁顯示幾篇討論串';
$language['info_basic_threads'] = '篇';
$language['info_basic_postsperpage'] = '一篇討論串最多顯示之回應筆數';
$language['info_basic_posts'] = '筆';
$language['info_basic_postsinthread'] = '回應模式一頁顯示幾筆回應內容';
$language['info_basic_posts_showall'] = '(全部顯示:0)';
$language['info_basic_bumpposts'] = '回應筆數超過多少則不自動推文';
$language['info_basic_bumphours'] = '討論串可接受推文的時間範圍';
$language['info_basic_hours'] = '小時';
$language['info_basic_0disable'] = '(關閉:0)';
$language['info_basic_urllinking'] = 'URL文字自動作成超連結';
$language['info_0no1yes'] = '(是:1 否:0)';
$language['info_basic_com_limit'] = '內文接受Bytes數';
$language['info_basic_com_after'] = ' Bytes (中文字為2Bytes)';
$language['info_basic_anonpost'] = '接受匿名發送';
$language['info_basic_anonpost_opt'] = '(強制砍名:2 是:1 否:0)';
$language['info_basic_del_incomplete'] = '自動刪除上傳不完整附加圖檔';
$language['info_basic_use_sample'] = '使用預覽圖機能 (品質:%1$s)';
$language['info_0notuse1use'] = '(使用:1 不使用:0)';
$language['info_basic_use_sample_func'] = '└ 預覽圖生成功能';
$language['info_basic_useblock'] = '封鎖檢查功能';
$language['info_0disable1enable'] = '(啟動:1 關閉:0)';
$language['info_basic_showid'] = '顯示ID';
$language['info_basic_showid_after'] = '(強制顯示:2 選擇性顯示:1 永遠不顯示:0)';
$language['info_basic_cr_limit'] = '文字換行行數上限';
$language['info_basic_cr_after'] = ' 行 (不限:0)';
$language['info_basic_timezone'] = '時區設定';
$language['info_basic_threadcount'] = '目前總討論串篇數';
$language['info_dsusage_top'] = '記錄檔使用量';
$language['info_dsusage_max'] = '最大筆數';
$language['info_dsusage_usage'] = '使用率';
$language['info_dsusage_count'] = '目前筆數';
$language['info_fileusage_top'] = '附加圖檔總容量限制功能:';
$language['info_fileusage_limit'] = '上限大小';
$language['info_fileusage_count'] = '目前容量';
$language['info_fileusage_unlimited'] = '無上限';
$language['info_server_top'] = '伺服器支援情報';
$language['info_server_gd'] = 'GD函式庫';
$language['init_permerror'] = '根目錄沒有寫入權限,請修改權限<br />';
$language['init_inited'] = '環境初始化成功!<br />請現在打開此程式刪除init()程式環境初始化區段<br />';
$language['action_opt_notsupport'] = '後端並不支援此動作';
$language['action_opt_optimize'] = '資料表最佳化';
$language['action_opt_success'] = '成功!';
$language['action_opt_failed'] = '失敗!';
 
// lib_common.php
$language['head_home'] = '回首頁';
$language['head_search'] = '搜尋';
$language['head_info'] = '系統資訊';
$language['head_admin'] = '管理區';
$language['head_refresh'] = '重新整理';
$language['form_top'] = '回應模式';
$language['form_showpostform'] = '投稿';
$language['form_hidepostform'] = '隱藏表單';
$language['form_name'] = '名 稱';
$language['form_email'] = 'E-mail';
$language['form_topic'] = '標 題';
$language['form_submit_btn'] = '送 出';
$language['form_comment'] = '內 文';
$language['form_attechment'] = '附加圖檔';
$language['form_noattechment'] = '無貼圖';
$language['form_contpost'] = '連貼機能';
$language['form_category'] = '類別標籤';
$language['form_category_notice'] = '(請以 , 逗號分隔多個標籤)';
$language['form_delete_password'] = '刪除用密碼';
$language['form_delete_password_notice']= '(刪除文章用。英數字8字元以內)';
$language['form_notice'] = '<li>可附加圖檔類型:GIF, JPG, PNG,瀏覽器才能正常附加圖檔</li><li>附加圖檔最大上傳資料量為 %1$s KB。當回文時E-mail填入sage為不推文功能</li><li>當檔案超過寬 %2$s 像素、高 %3$s 像素時會自動縮小尺寸顯示</li>';
$language['form_notice_storage_limit'] = '<li>目前附加圖檔使用量大小: %1$s KB / %2$s KB</li>';
$language['form_notice_noscript'] = '*您選擇關閉了JavaScript,但這對您的瀏覽及發文應無巨大影響';
$language['error_back'] = '回上頁';
$language['ip_banned'] = '被列在 IP/Hostname 封鎖名單之內';
$language['ip_dnsbl_banned'] = '被列在 DNSBL(%1$s) 封鎖名單之內';
 
// mainscript.js // regist_withoutcomment,regist_upload_notsupport,js_convert_sakura
$language['js_convert_sakura'] = '偵測到您有輸入櫻花日文假名的可能性,將自動為您轉換';
 
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_fileio.php
@@ -0,0 +1,33 @@
<?php
/*
FileIO - Pixmicat! File I/O
FileIO Kernel Switcher
*/
 
// 引入必要函式庫
$fileio_file = './lib/fileio/fileio.'.FILEIO_BACKEND.'.php'; // FileIO Backend
if(is_file($fileio_file)) include_once($fileio_file);
 
// 擴充物件
class FileIOWrapper extends FileIO{
var $absoluteURL; // 伺服器絕對位置
function _getAbsoluteURL(){
return 'http://'.$_SERVER['HTTP_HOST'].preg_replace('/(.*)\/.+$/', '$1/', $_SERVER['PHP_SELF']);
}
function getImageLocalURL($imgname){
if(!isset($this->absoluteURL)) $this->absoluteURL = $this->_getAbsoluteURL();
 
return $this->absoluteURL.(substr($imgname, -5)=='s.jpg' ? THUMB_DIR : IMG_DIR).$imgname;
}
}
 
$FileIOEnv = array( // FileIO 環境常數
'IFS.PATH' => './lib/fileio/ifs.php',
'IFS.LOG' => FILEIO_INDEXLOG,
'PATH' => realpath('.').DIRECTORY_SEPARATOR,
'IMG' => IMG_DIR,
'THUMB' => THUMB_DIR
);
 
$FileIO = new FileIOWrapper(unserialize(FILEIO_PARAMETER), $FileIOEnv); // FileIO 物件
?>
New file

Property changes:

Name: svn:keywords
+ Id Date Author

/release/PIO-v4/lib/lib_pio.php
@@ -0,0 +1,27 @@
<?php
/*
PIO - Pixmicat! data source I/O
PIO Kernel Switcher
*/
 
// 分析連線字串
if(preg_match('/^(.*):\/\//i', CONNECTION_STRING, $backend)) define('PIXMICAT_BACKEND', $backend[1]);
 
// 引入必要函式庫
$pio_file = './lib/pio/pio.'.PIXMICAT_BACKEND.'.php';
$PIOEnv = array( // PIO 環境常數
'BOARD' => '.',
'LUTCACHE' => './lutcache.dat',
'NONAME' => DEFAULT_NONAME,
'NOTITLE' => DEFAULT_NOTITLE,
'NOCOMMENT' => DEFAULT_NOCOMMENT,
'LOG_MAX' => LOG_MAX,
'PERIOD.POST' => RENZOKU,
'PERIOD.IMAGEPOST' => RENZOKU2
);
if(is_file($pio_file)) include_once($pio_file);
 
// PIO Kernel Switcher
$pioSwitch = 'PIO'.PIXMICAT_BACKEND;
$PIO = new $pioSwitch(CONNECTION_STRING, $PIOEnv);
?>
New file
/release/PIO-v4/ChangeLog
@@ -0,0 +1,215 @@
Pixmicat!-PIO 修改紀錄
----------------------
2007/06/17: 4th.Release (v070617)
-正式釋出版
 
2007/06/15: 4th.Release-dev RC2 (b070615)
-[PMS] 新增 PostForm, UsageExceed 掛載點
 
2007/06/07: 4th.Release-dev RC (b070607)
-[PIO] PIO 0.4 穩定版
 
2007/06/05
-[config] 刪除 CAPTCHA 預留設定 (不考慮內建)
 
2007/05/27
-[pixmicat][PMS] 新增 RegistBegin, RegistBeforeCommit 掛載點
-[pixmicat] 修正模組資訊頁面的排版問題
-[PTE] 強化並取代原內建輸出
 
2007/05/13
-[mainscript] 改寫增加效率,以 /packer/ 壓縮得到更小檔案
- 新增 Preset() Hook 機能,能在頁面載入完執行特定掛載函式
- replace_sakura() 改寫,增加效率
-[iedivfix] 改寫,使用 Hook 機能提前執行時機
-[lib_common] 把 mainscript.js 和 iedivfix.js 順序對調,防止執行錯誤
 
2007/05/04, 05, 06
-[pixmicat] 修正名稱輸入特殊字元轉成 Unicode 參照碼後被視為 Trip 轉換留下 & 造成 XML 解析錯誤的問題 (儲存/顯示雙重檢查)
-[pixmicat] 修正搜尋文章結果印出錯誤的問題 (Since rev.363)
-[pixmicat] 修正週五 (Friday) 字串顯示錯誤的問題
 
2007/04/30
-[PMS] 新增 AdminList 掛載點功能
 
2007/04/25
-將函式庫搬移至 /lib 目錄
 
2007/04/15
-[PMS] PMS 更新 (與 Pixmicat!2 共用相同的函式庫),並廢止PMS::getModulePageURL()方法
 
2007/03/31
-[pixmicat] 修改 Trip, Cap 生成函式 (ereg 在 PHP6 即將廢止,以 PCRE 函式取代)
-[pixmicat] 修正 "管理"、"刪除" 在使用 Admin Cap 後仍然不能作為名稱的問題
-[PIO] PIO 0.4gamma (與 Pixmicat!2 共用相同的函式庫)
-[FileIO] FileIO 更新 (與 Pixmicat!2 共用相同的函式庫)
 
2007/03/12
01.[pixmicat] 修正翻譯索引錯誤 useage => usage
 
2007/02/24
01.[pixmicat][lib_common] 修正部份變數未定義錯誤
 
2007/02/21
01.[PIO] 修正 SQLite3 isThread() 總是回傳 true 的錯誤
 
2007/02/14
01.[PIO] PIO 0.4 beta,修改 fetchThreadList 方法
 
2007/02/10, 11
01.[PIO] 有關時區部份改以 UTC 時間手動位移避免混亂
 
2007/02/07
01.[PTE] 預設樣板更新 (inc_pixmicat.tpl)
 
2007/02/01,02
01.[lib_common] str_cut() 補上來源
02.[upgradePIO] mysql2pio.php 修正,新增欄位次序修正 (舊版無影響)
03.[PIO] PIO 0.4 alpha,新增 dbImport / dbExport 方法
 
2007/01/31
01.[lib_common][config] 刪除 RE_COL,改以 CSS 樣式表設定
 
2007/01/30
01.[FileIO] ImageShack 後端因應 mod_archiver 修改 getImageURL 方法
02.[PMS] 新增 PMS::getModulePageURL(),可讓模組取得獨立頁面網址
03.[PIO] Log postCount() 修正為回傳本篇加回應的數目,非先前僅有回應數
04.[pixmicat] 修正 MAX_RES 不推文判斷,回應超過這個數字才不推 (先前是等於就不推了)
 
2007/01/28,29
01.[PMS] ThreadPost, ThreadReply 掛載點新增是否位於回應模式的判斷參數
02.[PMS][lib_common] 新增 PostInfo 掛載點
 
2007/01/27
01.[PIO] SQLite3 類別標籤搜尋 SQL 語法更正
02.[pixmicat][PMS] 新增 Foot / ThreadPost, ThreadReply 掛載點,可搭配 PTE 動態新增插入標籤
 
2007/01/26
01.[PMS] 增設 Toplink Hook Point, 並完成 mod_catalog
02.[config] 更改模組載入形式,較可以自由切換載入模組 (註解起來即可)
 
2007/01/25
01.[PMS][config] 試驗性模組系統雛型架上,並完成 mod_rss 改寫
02.[pixmicat][lib_common] fullURL(), anti_sakura() 移至 lib_common.php
 
2007/01/24
01.[lib_common] 廢除 PROXY_CHECK 檢查功能
02.[config][pixmicat] 強化 IP 封鎖為綜合性 IP/Hostname/DNSBL 封鎖機能,支援 RegExp/Wildcard 模式
 
2007/01/05:3rd.Release (v070106)
01.[lib_common] 減少 str_cut() 的 ord() 呼叫次數
02.[lib_common] CheckSupportGZip() 判斷式修改
03.[FileIO] 修改 ImageShack 後端當上傳圖檔小於 200x200 時無法顯示縮圖的問題
 
2006/12/28
01.[pixmicat] 新增回應模式分頁 [ALL] 全部顯示功能 (顯示條件:分頁數超過一頁)
02.[lib_common] 修正 2006/10/27 01. 項失效的問題 (Cookie 無法正確填入欄位)
03.[PIO] SQL 讀寫新增 _error_handler() 錯誤攔截機制
 
2006/12/23
01.[FileIO] 修改normal模式使用getImageURL()回傳相對位置造成mod_rss生成RSS不合語法的問題
02.[FileIO] 減少getImageLocalURL()每次都要取目前伺服器絕對位置造成的CPU浪費
 
2006/12/22
01.[lib_common][pixmicat] Publicβ 開始
02.[config] 修改部分預設設定
 
2006/12/19
01.[PIO] 修正 PIO PgSQL, SQLite 儲存密碼欄位時變數名稱打錯的問題
 
2006/12/16
01.[config] 新增 PIO 及 FileIO 參考設定,移除 *_URL_PREFIX 設定 (目前取消)
02.[PIO] 新增 searchCategory() 輸出順序依編號由大排到小
 
2006/12/12
01.[pixmicat] 修改 Category 印出時使用 urlencode 以確保各瀏覽器相容性
02.[FileIO] 修改 FileIO IndexFS 使其在需要時才讀取索引以節省 CPU
 
2006/12/11
01.[PIOlog] 修正當 LUT 快取不見時無法重新生成造成版面無法復原問題
02.[PTE] 預設樣版新增類別標籤欄位 (供修改製作參考)
 
2006/12/10
01.[PTE][lib_common] 改回原本樣式,原獨立樣式表方式廢棄 (見2006/09/22,23)
02.[pixmicat] 新增搜尋類別標籤的重新快取功能,以免 Session 快取過久無法顯示最新文章
 
2006/12/09
01.[FileIO] Satellite PHP & Perl CGI 完成
02.[FileIO] FileIO Index File System 獨立,FileIO 完成
 
2006/12/07
01.[FileIO] FileIO Satellite 後端完成,衛星 PHP 亦完成
02.[FileIO] FileIO ImageShack 大圖連結 URL 更改,預設連至顯示頁面
 
2006/12/06
01.[FileIO] FileIO ImageShack 實裝註冊金鑰認證、要求刪圖功能及取消預覽圖資訊列
 
2006/12/05
01.[FileIO] FileIO 改用建立物件式,為此所有操作到 FileIO 敘述全部更改
02.[FileIO] FileIO FTP 後端重寫完成
03.[config] FileIO 參數規格修改,改以傳入陣列方式供後端自行分析使用
04.字詞修正 CATALOG -> CATEGORY
05.[FileIO] FileIO ImageShack 後端完成
 
2006/12/04
01.[FileIO] FileIO 0.2 版,並完成 Normal 後端
02.[PIO][pixmicat] PIO, pixmicat.php 與 FileIO 相關函式修改
 
2006/12/03
01.[pixmicat] 修正顯示附加圖檔檔案大小時多一個B變成KBB的問題
02.[pixmicat] 類別標籤分頁功能
03.[PIO] PIO 0.3 更新
 
2006/11/13
01.[pixmicat] 修正E-mail填入 ◆XXX 的Trip後會造成版面亂掉的問題
02.[pixmicat][config] 新增強制砍名功能 (仿二次元壁紙@ふたば、攤開Customized)
 
2006/10/27
01.[mainscript][lib_common] 修正Firefox瀏覽器自動填入Cookies儲存密碼時底端欄位漏填的問題
02.[pixmicat] 用語更改:防止Spam對策機制啟動! -> 防止 Spambot 機制啟動!
03.[PIO] searchCatalog 完成
 
2006/10/23
01.[mainscript] 更新方法,並重新以JSLint, JSMin檢測壓縮
02.[lib_common][config][pixmicat] 實裝Spambot欄位陷阱
 
2006/09/22,23
01.[pixmicat] searchCatalog()初步完成,分頁功能暫時未加入 (目前採Session儲存,有可能改變)
02.[pixmicat][lib_common] 無貼圖勾選框加回來以防止Spam攻擊
03.[PTE][mainstyle] 將StyleSheet部分獨立出新樣板區塊,將mainstyle的討論串樣式搬過來
04.[PIOlog][PIOmysql] 完成searchCatalog(),SQL錯誤訊息預設不顯示避免被利用
 
2006/09/20
01.[pixmicat] 搜尋部分(searchCatalog)實作中
 
2006/09/15
01.[pixmicat] 新增類別標籤功能,目前可以儲存標籤、印出標籤,搜尋部分尚未完成
 
2006/08/26
01.[PIOlog] 修正索引鍵錯亂造成Log丟失問題及刪舊文不刪其圖問題
02.[PIOlog] 取消array_search改用array_flip + array_key_exists
03.[PIOlog] 修正porder[0]刪掉後造成編號歸零計算
04.[PIOlog] 修正torder混亂的問題
 
2006/08/25
01.[PIO] 廢除PIO Class Wrapper,以PIO Kernel Switcher代替
02.[PIOlog] 修改處理結構,增加LUT Cache (目前尚有Bug, 徵求除錯)
03.[pixmicat] 不具合小修正
04.[PIOlog] 修正資料筆數必須要到LOG_MAX+1才會開始刪除舊文的問題
 
2006/08/24
01.[PIO] 各資料來源的資料結構更新完成
02.[PIOlog] 修正在管理模式時若改變文章狀態會在dbCommit()刪除resto鍵值造成後續的輸出發生錯誤
03.[pixmicat] 修正發文後檢查是否討論串被設為禁止回應的tim鍵值使用錯誤的問題
 
2006/08/23
01.[PIO] 修改資料結構,增加catalog和imgw, imgh, imgsize等欄位
02.[pixmicat] 去除extract POST, GET以增強安全性和效率,需要時再解析取得
03.[pixmicat] 去除對URL「?mode=search」POST的不符標準
 
2006/08/11
01.[pixmicat] PIO版本決議將繼續開發直到成熟為止
02.[PIO] PIOlog, mysql, sqlite, pgsql物件化
03.[pixmicat] 加回Refresh標籤,發文完成即使關閉JavaScript也能跳轉頁面
04.[pixmicat] 配合PIO物件化修改,連續投稿 / 相同附加圖檔檢查給PIO處理
05.[PIO] 新增checkSuccessivePost及checkDuplicateAttechment函式

Property changes:

Name: svn:mergeinfo
+ /PIO:125-435
Name: svn:ignore
+ .project

/PIO-v4