]> 4ch.mooo.com Git - test.git/blob - lib/pio/pio.logflockp.php
new file: ChangeLog
[test.git] / lib / pio / pio.logflockp.php
1 <?php\r
2 /**\r
3  * PIO Log API (flock+)\r
4  *\r
5  * 提供存取以 Log 檔案構成的資料結構後端的物件\r
6  *\r
7  * @package PMCLibrary\r
8  * @version $Id: pio.logflockp.php 671 2008-09-20 05:54:15Z scribe $\r
9  * @date $Date: 2008-09-20 13:54:15 +0800 (星期六, 20 九月 2008) $
10  *
11  * @package PMCLibrary\r
12  * @version $Id$\r
13  * @date $Date$\r
14  */\r
15 \r
16 class PIOlogflockp{\r
17         var $ENV, $logfile, $treefile, $porderfile; // Local Constant\r
18         var $logs, $trees, $LUT, $porder, $torder, $prepared; // Local Global\r
19         //var $memcached, $mid;\r
20 \r
21         /* private 設定 memcached 資料\r
22         function _memcacheSet($isAnalysis=true){\r
23                 if(!$this->_memcachedEstablish()) return false;\r
24                 $this->memcached->set('pmc'.$this->mid.'_isset', true);\r
25                 // 是否需要將每行資料分析為陣列\r
26                 $this->memcached->set('pmc'.$this->mid.'_logs', ($isAnalysis ? array_map(array($this, '_AnalysisLogs'), $this->logs) : $this->logs));\r
27                 $this->memcached->set('pmc'.$this->mid.'_trees', $this->trees);\r
28                 $this->memcached->set('pmc'.$this->mid.'_LUT', $this->LUT);\r
29                 $this->memcached->set('pmc'.$this->mid.'_porder', $this->porder);\r
30                 $this->memcached->set('pmc'.$this->mid.'_torder', $this->torder);\r
31         }\r
32 \r
33         /* private 取得 memcached 資料\r
34         function _memcacheGet(){\r
35                 if(!$this->_memcachedEstablish()) return false;\r
36                 if($this->memcached->get('pmc'.$this->mid.'_isset')){ // 有資料\r
37                         $this->logs = $this->memcached->get('pmc'.$this->mid.'_logs');\r
38                         $this->trees = $this->memcached->get('pmc'.$this->mid.'_trees');\r
39                         $this->LUT = $this->memcached->get('pmc'.$this->mid.'_LUT');\r
40                         $this->porder = $this->memcached->get('pmc'.$this->mid.'_porder');\r
41                         $this->torder = $this->memcached->get('pmc'.$this->mid.'_torder');\r
42                         return true;\r
43                 }else return false;\r
44         }\r
45 \r
46         /* private 建立 memcached 實體\r
47         function _memcachedEstablish(){\r
48                 if(!extension_loaded('memcache')) return ($this->memcached = false);\r
49                 if(is_null($this->memcached)){\r
50                         $this->memcached = new Memcache;\r
51                         if(!$this->memcached->pconnect('localhost')) return ($this->memcached = false);\r
52                         return true;\r
53                 }\r
54                 return ($this->memcached===false) ? false : true;\r
55         }*/\r
56 \r
57         function PIOlogflockp($connstr='', $ENV){\r
58                 $this->ENV = $ENV;\r
59                 $this->logs = $this->trees = $this->LUT = $this->porder = $this->torder = array();\r
60                 $this->prepared = 0;\r
61                 //$this->mid = md5($_SERVER['SCRIPT_FILENAME']); // Unique ID\r
62                 //$this->memcached = false; // memcached object (null: use, false: don't use)\r
63 \r
64                 if($connstr) $this->dbConnect($connstr);\r
65         }\r
66 \r
67         /* private 把每一行 Log 解析轉換成陣列資料 */\r
68         function _AnalysisLogs($line){\r
69                 $tline = array();\r
70                 list($tline['no'], $tline['resto'], $tline['md5chksum'], $tline['category'], $tline['tim'], $tline['ext'], $tline['imgw'], $tline['imgh'], $tline['imgsize'], $tline['filename'], $tline['tw'], $tline['th'], $tline['pwd'], $tline['now'], $tline['name'], $tline['email'], $tline['sub'], $tline['com'], $tline['host'], $tline['status']) = explode(',', $line);\r
71                 return array_reverse($tline); // list()是由右至左代入的\r
72         }\r
73 \r
74         /* private 將回文放進陣列 */\r
75         function _includeReplies($posts){\r
76                 $torder_flip = array_flip($this->torder);\r
77                 foreach($posts as $post){\r
78                         if(array_key_exists($post, $torder_flip)){ // 討論串首篇\r
79                                 $posts = array_merge($posts, $this->trees[$post]);\r
80                         }\r
81                 }\r
82                 return array_merge(array(), array_unique($posts)); // 去除重複值\r
83         }\r
84 \r
85         /* private 取代 , 成為 &#44; 避免衝突 */\r
86         function _replaceComma($txt){\r
87                 return str_replace(',', '&#44;', $txt);\r
88         }\r
89 \r
90         /* private 由編號取出資料分析成陣列 */\r
91         function _ArrangeArrayStructure($line){\r
92                 $line = (array)$line; // 全部視為Arrays\r
93                 $posts = array();\r
94                 foreach($line as $i){\r
95                         if(!isset($this->LUT[$i])) continue;\r
96                         if(!is_array($this->logs[$this->LUT[$i]])){ // 進行分析轉換\r
97                                 $line = $this->logs[$this->LUT[$i]]; if($line=='') continue;\r
98                                 $this->logs[$this->LUT[$i]] = $this->_AnalysisLogs($line);\r
99                         }\r
100                         $posts[] = $this->logs[$this->LUT[$i]];\r
101                 }\r
102                 return $posts;\r
103         }\r
104 \r
105         /* PIO模組版本 */\r
106         function pioVersion(){\r
107                 return '0.6 (v20100404)';\r
108         }\r
109 \r
110         /* 處理連線字串/連接 */\r
111         function dbConnect($connStr){\r
112                 if(preg_match('/^logflockp:\/\/(.*)\:(.*)\/$/i', $connStr, $linkinfos)){\r
113                         $this->logfile = $this->ENV['BOARD'].'/'.$linkinfos[1]; // 投稿文字記錄檔檔名\r
114                         $this->treefile = $this->ENV['BOARD'].'/'.$linkinfos[2]; // 樹狀結構記錄檔檔名\r
115                         $this->porderfile = $this->ENV['LUTCACHE']; // LUT索引查找表暫存檔案\r
116                 }\r
117         }\r
118 \r
119         /* 初始化 */\r
120         function dbInit(){\r
121                 $chkfile = array($this->logfile, $this->treefile, $this->porderfile);\r
122                 // 自動建置\r
123                 foreach($chkfile as $value){\r
124                         if(!is_file($value)){ // 檔案不存在\r
125                                 $fp = fopen($value, 'w');\r
126                                 stream_set_write_buffer($fp, 0);\r
127                                 if($value==$this->logfile) fwrite($fp, '0,0,,,0,,0,0,,,0,0,,08/11/08(土) 10:24:04,【スパーキー(④ ^ヮ^)】,,'.$this->ENV['NOTITLE'].','.$this->ENV['NOCOMMENT'].',,,'); // PIO Structure V4\r
128                                 if($value==$this->treefile) fwrite($fp, '1');\r
129                                 if($value==$this->porderfile) fwrite($fp, '1');\r
130                                 fclose($fp);\r
131                                 unset($fp);\r
132                                 @chmod($value, 0666);\r
133                         }\r
134                 }\r
135                 return true;\r
136         }\r
137 \r
138         /* 準備/讀入 */\r
139         function dbPrepare($reload=false, $transaction=true){\r
140                 if($this->prepared && !$reload) return true;\r
141                 if($reload && $this->prepared) $this->porder = $this->torder = $this->LUT = $this->logs = $this->trees = array();\r
142                 //if($this->_memcacheGet()){ $this->prepared = 1; return true; } // 如果 memcache 有快取則直接使用\r
143 \r
144                 $this->logs = file($this->logfile); // Log每行原始資料\r
145                 if(!file_exists($this->porderfile)){ // LUT不在,重生成\r
146                         $lut = '';\r
147                         foreach($this->logs as $line){\r
148                                 if(!isset($line)) continue;\r
149                                 $tmp = explode(',', $line); $lut .= $tmp[0]."\r\n";\r
150                         }\r
151                         $this->_lock($this->porderfile);\r
152                         $fp = fopen($this->porderfile, 'w'); // LUT\r
153                         stream_set_write_buffer($fp, 0);\r
154 //                      flock($fp, LOCK_EX); // 鎖定檔案\r
155                         fwrite($fp, $lut);\r
156 //                      flock($fp, LOCK_UN); // 解鎖\r
157                         fclose($fp);\r
158                         $this->_unlock($this->porderfile);\r
159                 }\r
160                 $this->porder = array_map('rtrim', file($this->porderfile)); // 文章編號陣列\r
161                 $this->LUT = array_flip($this->porder); // LUT索引查找表\r
162 \r
163                 $tree = array_map('rtrim', file($this->treefile));\r
164                 foreach($tree as $treeline){ // 解析樹狀結構製成索引\r
165                         if($treeline=='') continue;\r
166                         $tline = explode(',', $treeline);\r
167                         $this->torder[] = $tline[0]; // 討論串首篇編號陣列\r
168                         $this->trees[$tline[0]] = $tline; // 特定編號討論串完整結構陣列\r
169                 }\r
170                 //$this->_memcacheSet(); // 把目前資料設定到 memcached 內\r
171                 $this->prepared = 1;\r
172         }\r
173 \r
174         /* 提交/儲存 */\r
175         function dbCommit(){\r
176                 if(!$this->prepared) return false;\r
177 \r
178                 $log = $tree = $lut = '';\r
179                 $this->logs = array_merge(array(), $this->logs); // 更新logs鍵值\r
180                 $this->torder = array_merge(array(), $this->torder); // 更新torder鍵值\r
181                 $this->porder = $this->LUT = array(); // 重新生成索引\r
182 \r
183                 foreach($this->logs as $line){\r
184                         if(!isset($line)) continue;\r
185                         if(is_array($line)){ // 已被分析過\r
186                                 $log .= implode(',', $line).",\r\n";\r
187                                 $lut .= ($this->porder[] = $line['no'])."\r\n";\r
188                         }else{ // 尚未分析過\r
189                                 $log .= $line;\r
190                                 $tmp = explode(',', $line); $lut .= ($this->porder[] = $tmp[0])."\r\n";\r
191                         }\r
192                 }\r
193                 $this->LUT = array_flip($this->porder);\r
194                 $tcount = count($this->trees);\r
195                 for($tline = 0; $tline < $tcount; $tline++){\r
196                         $tree .= $this->isThread($this->torder[$tline]) ? implode(',', $this->trees[$this->torder[$tline]])."\r\n" : '';\r
197                 }\r
198                 //$this->_memcacheSet(false); // 更新快取 (不需要再分析)\r
199 \r
200                 $this->_lock($this->logfile);\r
201                 $fp = fopen($this->logfile, 'w'); // Log\r
202                 stream_set_write_buffer($fp, 0);\r
203 //              flock($fp, LOCK_EX); // 鎖定檔案\r
204                 fwrite($fp, $log);\r
205 //              flock($fp, LOCK_UN); // 解鎖\r
206                 fclose($fp);\r
207                 $this->_unlock($this->logfile);\r
208 \r
209                 $this->_lock($this->treefile);\r
210                 $fp = fopen($this->treefile, 'w'); // tree\r
211                 stream_set_write_buffer($fp, 0);\r
212 //              flock($fp, LOCK_EX); // 鎖定檔案\r
213                 fwrite($fp, $tree);\r
214 //              flock($fp, LOCK_UN); // 解鎖\r
215                 fclose($fp);\r
216                 $this->_unlock($this->treefile);\r
217 \r
218                 $this->_lock($this->porderfile);\r
219                 $fp = fopen($this->porderfile, 'w'); // LUT\r
220                 stream_set_write_buffer($fp, 0);\r
221 //              flock($fp, LOCK_EX); // 鎖定檔案\r
222                 fwrite($fp, $lut);\r
223 //              flock($fp, LOCK_UN); // 解鎖\r
224                 fclose($fp);\r
225                 $this->_unlock($this->porderfile);\r
226         }\r
227 \r
228         /* 資料表維護 */\r
229         function dbMaintanence($action,$doit=false){\r
230                 switch($action) {\r
231                         case 'export':\r
232                                 if($doit){\r
233                                         $this->dbPrepare(false);\r
234                                         $gp = gzopen('piodata.log.gz', 'w9');\r
235                                         gzwrite($gp, $this->dbExport());\r
236                                         gzclose($gp);\r
237                                         return '<a href="piodata.log.gz">下載 piodata.log.gz 中介檔案</a>';\r
238                                 }else return true; // 支援匯出資料\r
239                                 break;\r
240                         case 'optimize':\r
241                         case 'check':\r
242                         case 'repair':\r
243                         default: return false; // 不支援\r
244                 }\r
245         }\r
246 \r
247         /* 匯入資料來源 */\r
248         function dbImport($data){\r
249                 $arrData = explode("\r\n", $data);\r
250                 $arrData_cnt = count($arrData) - 1; // 最後一個是空的\r
251                 $arrTree = array();\r
252                 $tree = $logs = $lut = '';\r
253                 for($i = 0; $i < $arrData_cnt; $i++){\r
254                         $line = explode(',', $arrData[$i], 4); // 切成四段\r
255                         $logs .= $line[0].','.$line[1].','.$line[3]."\r\n"; // 重建討論結構\r
256                         $lut .= $line[0]."\r\n"; // 重建 LUT 查找表結構\r
257                         if($line[1]==0){ // 首篇\r
258                                 if(!isset($arrTree[$line[0]])) $arrTree[$line[0]] = array($line[0]); // 僅自身一篇\r
259                                 else array_unshift($arrTree[$line[0]], $line[0]);\r
260                                 continue;\r
261                         }\r
262                         if(!isset($arrTree[$line[1]])) $arrTree[$line[1]] = array();\r
263                         array_unshift($arrTree[$line[1]], $line[0]);\r
264                 }\r
265                 foreach($arrTree as $t) $tree .= implode(',', $t)."\r\n"; // 重建樹狀結構\r
266                 $chkfile = array($this->logfile, $this->treefile, $this->porderfile);\r
267                 foreach($chkfile as $value){\r
268                         $fp = fopen($value, 'w');\r
269                         stream_set_write_buffer($fp, 0);\r
270                         if($value==$this->logfile) fwrite($fp, $logs);\r
271                         if($value==$this->treefile) fwrite($fp, $tree);\r
272                         if($value==$this->porderfile) fwrite($fp, $lut);\r
273                         fclose($fp);\r
274                         unset($fp);\r
275                         @chmod($value, 0666);\r
276                 }\r
277                 return true;\r
278         }\r
279 \r
280         /* 匯出資料來源 */\r
281         function dbExport(){\r
282                 if(!$this->prepared) $this->dbPrepare();\r
283                 $f = file($this->logfile);\r
284                 $data = '';\r
285                 foreach($f as $line){\r
286                         $line = explode(',', $line, 3); // 分成三段 (最後一段特別長)\r
287                         if($line[1]==0 && isset($this->trees[$line[0]])){\r
288                                 $lastno = array_pop($this->trees[$line[0]]);\r
289                                 $line2 = $this->fetchPosts($lastno);\r
290                                 $root = gmdate('Y-m-d H:i:s', substr($line2[0]['tim'], 0, 10)); // UTC 時間\r
291                                 unset($this->trees[$line[0]]); // 刪除表示已取過\r
292                         }else{\r
293                                 $root = '0';\r
294                         }\r
295                         $data .= $line[0].','.$line[1].','.$root.','.$line[2];\r
296                 }\r
297                 return $data;\r
298         }\r
299 \r
300         /* 文章數目 */\r
301         function postCount($resno=0){\r
302                 if(!$this->prepared) $this->dbPrepare();\r
303 \r
304                 return $resno ? ($this->isThread($resno) ? count(@$this->trees[$resno]) : 0) : count($this->porder);\r
305         }\r
306 \r
307         /* 討論串數目 */\r
308         function threadCount(){\r
309                 if(!$this->prepared) $this->dbPrepare();\r
310 \r
311                 return count($this->torder);\r
312         }\r
313 \r
314         /* 取得最後的文章編號 */\r
315         function getLastPostNo($state){\r
316                 if(!$this->prepared) $this->dbPrepare();\r
317 \r
318                 switch($state){\r
319                         case 'beforeCommit':\r
320                         case 'afterCommit':\r
321                                 return reset($this->porder);\r
322                 }\r
323         }\r
324 \r
325         /* 輸出文章清單 */\r
326         function fetchPostList($resno=0, $start=0, $amount=0){\r
327                 if(!$this->prepared) $this->dbPrepare();\r
328 \r
329                 $plist = array();\r
330                 if($resno){\r
331                         if($this->isThread($resno)){\r
332                                 if($start && $amount){\r
333                                         $plist = array_slice($this->trees[$resno], $start, $amount);\r
334                                         array_unshift($plist, $resno);\r
335                                 }\r
336                                 if(!$start && $amount) $plist = array_slice($this->trees[$resno], 0, $amount);\r
337                                 if(!$start && !$amount) $plist = $this->trees[$resno];\r
338                         }\r
339                 }else{\r
340                         $plist = $amount ? array_slice($this->porder, $start, $amount) : $this->porder;\r
341                 }\r
342                 return $plist;\r
343         }\r
344 \r
345         /* 輸出討論串清單 */\r
346         function fetchThreadList($start=0, $amount=0, $isDESC=false){\r
347                 if(!$this->prepared) $this->dbPrepare();\r
348                 $tmp_array = $this->torder;\r
349                 if($isDESC) rsort($tmp_array); // 按編號遞減排序 (預設為按最後更新時間排序)\r
350                 return $amount ? array_slice($tmp_array, $start, $amount) : $tmp_array;\r
351         }\r
352 \r
353         /* 輸出文章 */\r
354         function fetchPosts($postlist){\r
355                 if(!$this->prepared) $this->dbPrepare();\r
356 \r
357                 return $this->_ArrangeArrayStructure($postlist); // 輸出陣列結構\r
358         }\r
359 \r
360         /* 刪除舊附件 (輸出附件清單) */\r
361         function delOldAttachments($total_size, $storage_max, $warnOnly=true){\r
362                 global $FileIO;\r
363                 if(!$this->prepared) $this->dbPrepare();\r
364 \r
365                 $rpord = $this->porder; sort($rpord); // 由舊排到新 (小->大)\r
366                 $arr_warn = $arr_kill = array();\r
367                 foreach($rpord as $post){\r
368                         $logsarray = $this->_ArrangeArrayStructure($post); // 分析資料為陣列\r
369                         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; } // 標記刪除\r
370                         if($FileIO->imageExists($logsarray[0]['tim'].'s.jpg')) $total_size -= $FileIO->getImageFilesize($logsarray[0]['tim'].'s.jpg') / 1024;\r
371                         if($total_size < $storage_max) break;\r
372                 }\r
373                 return $warnOnly ? $arr_warn : $this->removeAttachments($arr_kill);\r
374         }\r
375 \r
376         /* 刪除文章 */\r
377         function removePosts($posts){\r
378                 if(!$this->prepared) $this->dbPrepare();\r
379                 if(count($posts)==0) return array();\r
380 \r
381                 $posts = $this->_includeReplies($posts); // 包含所有回文\r
382                 $filelist = $this->removeAttachments($posts); // 欲刪除附件\r
383                 $torder_flip = array_flip($this->torder);\r
384                 $pcount = count($posts);\r
385                 $logsarray = $this->_ArrangeArrayStructure($posts); // 分析資料為陣列\r
386                 for($p = 0; $p < $pcount; $p++){\r
387                         if(!isset($logsarray[$p])) continue;\r
388                         if($logsarray[$p]['resto']==0){ // 討論串頭\r
389                                 unset($this->trees[$logsarray[$p]['no']]); // 刪除樹狀記錄\r
390                                 if(array_key_exists($logsarray[$p]['no'], $torder_flip)) unset($this->torder[$torder_flip[$logsarray[$p]['no']]]); // 從討論串首篇陣列中移除\r
391                         }else{\r
392                                 // 從樹狀檔刪除\r
393                                 if(array_key_exists($logsarray[$p]['resto'], $this->trees)){\r
394                                         $tr_flip = array_flip($this->trees[$logsarray[$p]['resto']]);\r
395                                         unset($this->trees[$logsarray[$p]['resto']][$tr_flip[$posts[$p]]]);\r
396                                 }\r
397                         }\r
398                         unset($this->logs[$this->LUT[$logsarray[$p]['no']]]);\r
399                         if(array_key_exists($logsarray[$p]['no'], $this->LUT)) unset($this->porder[$this->LUT[$logsarray[$p]['no']]]); // 從討論串編號陣列中移除\r
400                 }\r
401                 $this->LUT = array_flip($this->porder);\r
402                 return $filelist;\r
403         }\r
404 \r
405         /* 刪除附件 (輸出附件清單) */\r
406         function removeAttachments($posts){\r
407                 global $FileIO;\r
408                 if(!$this->prepared) $this->dbPrepare();\r
409                 if(count($posts)==0) return array();\r
410 \r
411                 $files = array();\r
412                 $logsarray = $this->_ArrangeArrayStructure($posts); // 分析資料為陣列\r
413                 $lcount = count($logsarray);\r
414                 for($i = 0; $i < $lcount; $i++){\r
415                         if($logsarray[$i]['ext']){\r
416                                 if($FileIO->imageExists($logsarray[$i]['tim'].$logsarray[$i]['ext'])) $files[] = $logsarray[$i]['tim'].$logsarray[$i]['ext'];\r
417                                 if($FileIO->imageExists($logsarray[$i]['tim'].'s.jpg')) $files[] = $logsarray[$i]['tim'].'s.jpg';\r
418                         }\r
419                 }\r
420                 return $files;\r
421         }\r
422 \r
423         /* 新增文章/討論串 */\r
424         function addPost($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $filename, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $age=false, $status='') {\r
425                 if(!$this->prepared) $this->dbPrepare();\r
426
427                 $tline = array($no, $resto, $md5chksum, $category, $tim, $ext, $imgw, $imgh, $imgsize, $filename, $tw, $th, $pwd, $now, $name, $email, $sub, $com, $host, $status);\r
428                 $tline = array_map(array($this, '_replaceComma'), $tline); // 將資料內的 , 轉換 (Only Log needed)\r
429                 array_unshift($this->logs, implode(',', $tline).",\r\n"); // 更新logs\r
430                 array_unshift($this->porder, $no); // 更新porder\r
431                 $this->LUT = array_flip($this->porder); // 更新LUT\r
432 \r
433                 // 更新torder及trees\r
434                 if($resto){\r
435                         $this->trees[$resto][] = $no;\r
436                         if($age){\r
437                                 $torder_flip = array_flip($this->torder);\r
438                                 unset($this->torder[$torder_flip[$resto]]); // 先刪除舊有位置\r
439                                 array_unshift($this->torder, $resto); // 再移到頂端\r
440                         }\r
441                 }else{\r
442                         $this->trees[$no][0] = $no;\r
443                         array_unshift($this->torder, $no);\r
444                 }\r
445         }\r
446 \r
447         /* 檢查是否連續投稿 */\r
448         function isSuccessivePost($lcount, $com, $timestamp, $pass, $passcookie, $host, $isupload){\r
449                 if(!$this->prepared) $this->dbPrepare();\r
450 \r
451                 $pcount = $this->postCount();\r
452                 $lcount = ($pcount > $lcount) ? $lcount : $pcount;\r
453                 for($i = 0; $i < $lcount; $i++){\r
454                         $logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列\r
455                         list($lcom, $lhost, $lpwd, $ltime) = array($logsarray[0]['com'], $logsarray[0]['host'], $logsarray[0]['pwd'], substr($logsarray[0]['tim'],0,-3));\r
456                         if($host==$lhost || $pass==$lpwd || $passcookie==$lpwd) $pchk = 1;\r
457                         else $pchk = 0;\r
458                         if($this->ENV['PERIOD.POST'] && $pchk){ // 密碼比對符合且開啟連續投稿時間限制\r
459                                 if($timestamp - $ltime < $this->ENV['PERIOD.POST']) return true; // 投稿時間相距太短\r
460                                 if($timestamp - $ltime < $this->ENV['PERIOD.IMAGEPOST'] && $isupload) return true; // 附加圖檔的投稿時間相距太短\r
461                                 if($com == $lcom && !$isupload) return true; // 內文一樣\r
462                         }\r
463                 }\r
464                 return false;\r
465         }\r
466 \r
467         /* 檢查是否重複貼圖 */\r
468         function isDuplicateAttachment($lcount, $md5hash){\r
469                 global $FileIO;\r
470 \r
471                 $pcount = $this->postCount();\r
472                 $lcount = ($pcount > $lcount) ? $lcount : $pcount;\r
473                 for($i = 0; $i < $lcount; $i++){\r
474                         $logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列\r
475                         if(!$logsarray[0]['md5chksum']) continue; // 無附加圖檔\r
476                         if($logsarray[0]['md5chksum']==$md5hash){\r
477                                 if($FileIO->imageExists($logsarray[0]['tim'].$logsarray[0]['ext'])) return true; // 存在MD5雜湊相同的檔案\r
478                         }\r
479                 }\r
480                 return false;\r
481         }\r
482 \r
483         /* 有此討論串? */\r
484         function isThread($no){\r
485                 if(!$this->prepared) $this->dbPrepare();\r
486 \r
487                 return isset($this->trees[$no]);\r
488         }\r
489 \r
490         /* 搜尋文章 */\r
491         function searchPost($keyword,$field,$method){\r
492                 if(!$this->prepared) $this->dbPrepare();\r
493 \r
494                 $foundPosts = array();\r
495                 $keyword_cnt = count($keyword);\r
496                 $pcount = $this->postCount();\r
497                 for($i = 0; $i < $pcount; $i++){\r
498                         $logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列\r
499                         $found = 0;\r
500                         foreach($keyword as $k){\r
501                                 if(strpos($logsarray[0][$field], $k)!==FALSE) $found++;\r
502                                 if($method=="OR" && $found) break;\r
503                         }\r
504                         if($method=="AND" && $found==$keyword_cnt) array_push($foundPosts, $logsarray[0]); // 全部都有找到 (AND交集搜尋)\r
505                         elseif($method=="OR" && $found) array_push($foundPosts, $logsarray[0]); // 有找到 (OR聯集搜尋)\r
506                 }\r
507                 return $foundPosts;\r
508         }\r
509 \r
510         /* 搜尋類別標籤 */\r
511         function searchCategory($category){\r
512                 if(!$this->prepared) $this->dbPrepare();\r
513 \r
514                 $category = strtolower($category);\r
515                 $foundPosts = array();\r
516                 $pcount = $this->postCount();\r
517                 for($i = 0; $i < $pcount; $i++){\r
518                         $logsarray = $this->_ArrangeArrayStructure($this->porder[$i]); // 分析資料為陣列\r
519                         if(!($ary_category = $logsarray[0]['category'])) continue;\r
520                         if(strpos(strtolower($ary_category), '&#44;'.$category.'&#44;')!==false) array_push($foundPosts, $logsarray[0]['no']); // 找到標籤,加入名單\r
521                 }\r
522                 return $foundPosts;\r
523         }\r
524 \r
525         /* 取得文章屬性 */\r
526         function getPostStatus($status){\r
527                 return new FlagHelper($status); // 回傳 FlagHelper 物件\r
528         }\r
529 \r
530         /* 更新文章 */\r
531         function updatePost($no, $newValues){\r
532                 if(!$this->prepared) $this->dbPrepare();\r
533 \r
534                 $chk = array('resto', 'md5chksum', 'category', 'tim', 'ext', 'imgw', 'imgh', 'imgsize', 'filename', 'tw', 'th', 'pwd', 'now', 'name', 'email', 'sub', 'com', 'host', 'status');\r
535 \r
536                 $this->_ArrangeArrayStructure($no); // 將資料變成陣列\r
537                 foreach($chk as $c)\r
538                         if(isset($newValues[$c]))\r
539                                 $this->logs[$this->LUT[$no]][$c] = $newValues[$c]; // 修改數值\r
540         }\r
541 \r
542         /* 設定文章屬性 */\r
543         function setPostStatus($no, $newStatus){\r
544                 $this->updatePost($no, array('status' => $newStatus));\r
545         }\r
546 \r
547         /* 檔案鎖定/解鎖處理 */\r
548         function _lock($lock, $tries=10) {\r
549                 ignore_user_abort(true);\r
550                 $lock0 = ".{$lock}0";\r
551                 $lock1 = ".{$lock}1";\r
552                 for ($i=0; $i<$tries; $i++) {\r
553                         if (!is_file($lock0)) {\r
554                                 touch($lock0);\r
555                                 if (!is_file($lock1)) {\r
556                                    touch($lock1);\r
557                                    return TRUE;\r
558                                 }\r
559                         }\r
560                         usleep(100);\r
561                 }\r
562                 return FALSE;\r
563         }\r
564 \r
565         // Unlock a file.\r
566         function _unlock($lock) {\r
567                 unlink(".{$lock}1");\r
568                 unlink(".{$lock}0");\r
569                 ignore_user_abort(false);\r
570         }\r
571 }\r
572 ?>