├── Documentation ├── index.html ├── stylesheet.css └── undoc.html ├── Upload ├── admin │ ├── jscripts │ │ └── xtofedit.js │ └── modules │ │ └── config │ │ └── threadfields.php ├── inc │ ├── languages │ │ └── english │ │ │ ├── admin │ │ │ └── xthreads.lang.php │ │ │ └── xthreads.lang.php │ ├── plugins │ │ └── xthreads.php │ ├── tasks │ │ └── xtaorphan_cleanup.php │ └── xthreads │ │ ├── index.html │ │ ├── phptpl_allowed_funcs.txt │ │ ├── xt_admin.php │ │ ├── xt_attachfuncs.php │ │ ├── xt_forumdhooks.php │ │ ├── xt_image.php │ │ ├── xt_install.php │ │ ├── xt_mischooks.php │ │ ├── xt_modupdhooks.php │ │ ├── xt_phptpl_lib.php │ │ ├── xt_sthreadhooks.php │ │ ├── xt_updatehooks.php │ │ ├── xt_upgrader.php │ │ ├── xt_upload.php │ │ └── xt_urlfetcher.php ├── jscripts │ ├── xthreads_attach_input.js │ └── xthreads_jquery-ui.min.js ├── uploads │ └── xthreads_ul │ │ ├── admindrop │ │ └── index.html │ │ └── index.html └── xthreads_attach.php └── readme.html /Documentation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XThreads Documentation 6 | 7 | 8 | 9 | 10 |
11 |

XThreads Documentation

12 | 16 |
17 | 18 |
19 |

Thank you for downloading XThreads (and actually opening this file!). I hope you enjoy using this plugin, which I actually personally like (duh).

20 | 21 |

Info

22 |

XThreads is a plugin for MyBB

23 |
24 |
Version
1.68
25 |
MyBB Compatibility
1.4.x, 1.5.x (betas), 1.6.x, 1.7.x (betas), 1.8.x
26 |
Other Requirements
MySQL/i DBMS (PostgreSQL/SQLite not supported)
27 |
Links
Discussion Thread, GitHub page
28 |
29 |

Please use the above discussion thread for more information, updates and posting questions/feedback you may have.

30 | 31 |

About XThreads

32 |

It's really difficult to explain XThreads in a simple manner. You can somewhat think of it as a tool to allow admins to perform a higher level of customisation on their forum.

33 |

Perhaps some examples of what can be achieved with this plugin will give you an idea of what this can do. Note that this plugin doesn't actually do much itself - you use it more like a tool.

34 | 35 |

Using XThreads

36 |

Some example usages can be found here (and if you have any interesting usages, I kindly ask you to share them with everyone else). Those examples are probably the best way to start understanding how XThreads works in general.

37 |

I've tried to include some long-ish descriptions in many parts of the plugin, hopefully meaning that you won't need to refer to this document much. As for this documentation, I will only really be focusing on stuff not documented in the interface.

38 |

In other words, you probably shouldn't be using this documentation to read up on how to really use this plugin, rather, it's more a reference to undocumented features.

39 | 40 |

This Documentation

41 |

...currently only has one other page. Maybe I'll expand it a bit more in the future.

42 | 43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /Documentation/stylesheet.css: -------------------------------------------------------------------------------- 1 | body {font-family: tahoma, sans-serif; font-size: 9pt; margin: 20px;} 2 | table {font-family: tahoma, sans-serif; font-size: 0.9em; border: 0; width: 95%; margin-top: 2} 3 | td.header {color: #FFFFFF; background: #000000} 4 | td {padding-left: 5; padding-right: 5; padding-top:2; padding-bottom: 2} 5 | h2 {font-family: tahoma, sans-serif; font-size: 1.3em; font-weight: bold; color: #4499EE; background: #DDEEFF; 6 | padding: 2px 3px; margin: 15 -10 5 -10} 7 | h1 {font-family: tahoma, sans-serif; font-size: 16pt; font-weight: bold; margin-bottom: 5px} 8 | h3 {font-family: tahoma, sans-serif; font-weight: bold; font-style: italic; font-size: 1em; margin-bottom: 0px; margin-top: 10px; } 9 | a {text-decoration: none} 10 | a:link {color: #3388FF} 11 | a:hover {color: #FF8833; } 12 | a:active {color: #FF0000} 13 | /*a:visited {color: 6633AA}*/ 14 | ol {margin-top: 5px; margin-bottom: 5px} 15 | li {margin-top: 1px; margin-bottom: 1px} 16 | li ul {padding-left: 2em} 17 | div.CITE {border-style: ridge; border-width: 2px; border-color: #880000; background: #EEF8EE; margin: 5 5 5 5; padding: 10 10 10 10; font-size: 0.9em} 18 | table.codecontainer { border: 1px solid #CCE5FE; max-height: 100px; overflow: auto; } 19 | td.codeline { font-family: courier new, monospaced; width: 50px; font-size: 10pt; white-space: no-wrap; background: #FFFFF0; text-align: right; line-height: 1.2em; } 20 | td.code { background: #ECF2F8; font-family: courier new, monospaced; font-size: 10pt; cursor: text; white-space: no-wrap; overflow: auto; width: 100%; padding-left: 5px; line-height: 1.2em; } 21 | pre { margin: 0px;} 22 | div.code { background: #ECF2F8; font-family: courier new, monospaced; font-size: 10pt; cursor: text; white-space: no-wrap; overflow: auto; width: 100%; border: 1px solid #CCE5FE; margin: 3px; padding: 2px; } 23 | 24 | code { font-size: 0.95em; } 25 | 26 | span.key { font-weight: bold; font-size: 1.5em; color: green; font-family: georgia; } 27 | 28 | div.navbar { width: 100%; background: #88AAFF; border: 1px solid #446688; text-align: center; margin-top: 2px; margin-bottom: 5px; padding-top: 3px; padding-bottom: 3px; } 29 | a.navlink { width: 19%; color: #446688; padding: 2px 5px 2px 5px; margin: 0px 2px 0px 2px; font-weight: bold; white-space: no-wrap; } 30 | a.navlink:link { background: #88AAFF; color: #446688; } 31 | a.navlink:hover { background: #DDEEFF; color: #FF8833; text-decoration: underline; } 32 | 33 | .indexlink { font-size: 1.1em; font-weight: bold; display: block; } 34 | ul.indexlinks li { margin-top: 5px; } 35 | 36 | span.token1 { color: green; font-style: italic; } 37 | span.token2 { color: blue; font-style: italic; } 38 | span.token3 { color: red; font-style: italic; } 39 | 40 | span.php_keyword { font-weight: bold; color: blue; } 41 | span.php_op { font-weight: bold; color: maroon; } 42 | span.php_string { color: green; } 43 | span.php_num { color: #60A060; } 44 | span.php_var { font-weight: bold; } 45 | span.php_comment { color: gray; font-style: italic; } 46 | -------------------------------------------------------------------------------- /Upload/admin/jscripts/xtofedit.js: -------------------------------------------------------------------------------- 1 | 2 | var appendNewChild = function(e,t) { 3 | var o; 4 | if(e.ownerDocument) 5 | o=e.ownerDocument.createElement(t); 6 | else // IE 7 | o=e.document.createElement(t); 8 | e.appendChild(o); 9 | return o; 10 | }; 11 | if(jQuery) { 12 | var listen = function(obj, event, f) { 13 | jQuery(obj).on(event, f); 14 | }; 15 | } else { 16 | // prototype 17 | var listen = Event.observe.bind(Event); 18 | } 19 | var xtOFEditor = function() {}; 20 | 21 | var xtOFEditorLang = {}; 22 | 23 | xtOFEditor.prototype = { 24 | src: null, 25 | loadFunc: null, 26 | saveFunc: null, 27 | fields: [], 28 | copyStyles: false, 29 | 30 | // internal vars 31 | winOpen: false, 32 | _testWinOpenTimer: null, 33 | unsaved: false, 34 | oldFormSubmit: null, 35 | selectFirstBox: false, 36 | 37 | initialize: function(){}, 38 | 39 | init: function() { 40 | // this is broken in Firefox 4 - if we have it, bail 41 | // see bug: https://bugzilla.mozilla.org/show_bug.cgi?id=637644 42 | // fixed in Firefox 5 though 43 | if(/Firefox\/4\./.test(navigator.userAgent)) return; 44 | 45 | this.src.onclick = this.open.bind(this); 46 | this.src.onkeypress = function(e) { 47 | if(!e) return true; // weird IE bug 48 | if(e.ctrlKey || e.altKey) return true; 49 | var key = 0; 50 | if(window.event) // IE 51 | key = e.keyCode; 52 | else 53 | key = e.which; 54 | 55 | if(key) 56 | this.open(); 57 | return true; 58 | }.bind(this); 59 | this.src.readOnly = true; 60 | this.src.style.borderColor = "#FF8040"; 61 | this.src.style.backgroundColor = "#FFFFF0"; 62 | this.src.style.color = "#504030"; 63 | this.src.style.cursor = "pointer"; 64 | 65 | if(this.src.form && !window.opera) { 66 | if(this.src.form.onsubmit) 67 | this.oldFormSubmit = this.src.form.onsubmit; 68 | this.src.form.onsubmit = this.formSubmit.bind(this); 69 | //listen(this.src.form, "submit", this.formSubmit.bind(this)); 70 | } 71 | listen(window, "focus", this.focusWin.bind(this)); 72 | listen(window, "beforeunload", this.parentLeave.bind(this)); 73 | }, 74 | 75 | isOpen: function() { 76 | try { // stop IE throwing error if window closed 77 | return this.winOpen && this.window && this.window.document && !this.window.closed; 78 | } catch(e) {} 79 | return false; 80 | }, 81 | closeWindow: function() { 82 | if(this.isOpen()) { 83 | //this.window.onbeforeunload = null; 84 | this.winOpen = false; 85 | this.window.close(); 86 | this.window = null; 87 | } 88 | }, 89 | 90 | parentLeave: function() { 91 | this.closeWindow(); 92 | }, 93 | 94 | formSubmit: function() { 95 | if(this.isOpen()) { 96 | if(!confirm(xtOFEditorLang.confirmFormSubmit)) 97 | return false; 98 | this.closeWindow(); 99 | } 100 | if(this.oldFormSubmit) 101 | return this.oldFormSubmit(); 102 | else 103 | return true; 104 | }, 105 | 106 | focusWin: function() { 107 | if(this.isOpen()) { 108 | setTimeout(function() { 109 | try { 110 | this.window.focus(); 111 | } catch(e) {} 112 | }.bind(this), 10); 113 | } 114 | }, 115 | 116 | open: function() { 117 | if(window.opera) { // cannot detect window opened/closed in Opera 118 | // so just close any old window 119 | this.closeWindow(); 120 | } 121 | if(this.winOpen) { 122 | if(this.isOpen()) { 123 | try { 124 | this.window.focus(); 125 | } catch(e) {} 126 | } else if(!this._testWinOpenTimer) { 127 | // window may still be loading, so try waiting for it 128 | this._testWinOpenTimer = setTimeout(function() { 129 | this._testWinOpenTimer = null; 130 | if(this.isOpen()) { 131 | try { 132 | this.window.focus(); 133 | } catch(e) {} 134 | } else { 135 | // maybe something got stuck, allow user to retry opening 136 | this.winOpen = false; 137 | } 138 | }.bind(this), 200); 139 | } 140 | return; 141 | } 142 | this.winOpen = true; // prevent our "race" condition :P 143 | var data = this.loadFunc(this.src.value); 144 | var i; 145 | 146 | this.window = window.open("", "", "status=0,toolbar=0,location=0,menubar=0,directories=0,resizable=1,scrollbars=1,width=400,height=400"); 147 | if(this.copyStyles) { 148 | var headTag; 149 | if(headTag = this.window.document.getElementsByTagName("head")) { 150 | if(headTag.length == 0) { 151 | headTag = this.window.document.createElement("head"); 152 | this.window.document.appendChild(headTag); 153 | } else 154 | headTag = headTag[0]; 155 | for(i=0; i 0)) { 260 | return false; 261 | } 262 | } 263 | return true; 264 | }, 265 | inputOnChange: function(input) { 266 | var row = input.parentNode.parentNode; 267 | // check if all boxes are empty 268 | 269 | if(this.rowIsBlank(row)) { 270 | // if 2nd last, remove last row 271 | if(row.nextSibling && !row.nextSibling.nextSibling) { 272 | do { 273 | var prevRow = row.previousSibling; 274 | row.parentNode.removeChild(row.nextSibling); 275 | // remove any preceeding blank rows too 276 | row = prevRow; 277 | } while(row && this.rowIsBlank(row)); 278 | } 279 | // TODO: remove blank line in the middle of things? 280 | } else { 281 | if(!row.nextSibling) 282 | this.addEditLine([]); 283 | } 284 | this.unsaved = true; 285 | }, 286 | 287 | save: function() { 288 | var data = []; 289 | // loop through and grab data 290 | var editctls = this.window.document.getElementById("editctls"); 291 | for(var iRow=0; iRow 0)) isBlank = false; 303 | } 304 | if(!isBlank) 305 | data.push(datum); 306 | } 307 | 308 | this.src.value = this.saveFunc(data); 309 | this.closeWindow(); 310 | 311 | return false; 312 | }, 313 | 314 | _beforeCloseConfirm: function() { 315 | try { // for browsers which disallow confirm before close 316 | return this.window.confirm(xtOFEditorLang.closeSaveChanges); 317 | } catch(e) {} 318 | return false; 319 | }, 320 | 321 | beforeClose: function() { 322 | if(!this.isOpen()) return; 323 | // check modification status and ask to save 324 | this.window.document.activeElement.blur(); // run update routine 325 | if(this.unsaved && this._beforeCloseConfirm()) 326 | this.save(); 327 | else { 328 | this.winOpen = false; 329 | } 330 | }, 331 | 332 | getValue: function(o) { 333 | if(o.multiple && o.options) { 334 | var ret = []; 335 | var i; 336 | for(i=0; i-1) { 342 | return o.options[o.selectedIndex].value; 343 | } else 344 | return o.value; 345 | }, 346 | 347 | setValue: function(o, v) { 348 | if(o.multiple && o.options) { 349 | var i; 350 | for(i=0; irun_hooks('xthreads_task_xtacleanup', $task); 7 | 8 | // clean out orphaned xtattachments more than 1 day old 9 | require_once MYBB_ROOT.'inc/xthreads/xt_modupdhooks.php'; 10 | $count = xthreads_rm_attach_query('tid=0 AND uploadtime<'.(TIME_NOW-86400)); 11 | 12 | // setting "isdatahandler" to true is destructive!!! 13 | if(!isset($lang->task_xtaorphan_run_done)) $lang->load('xthreads', true); 14 | if($count) 15 | add_task_log($task, $lang->sprintf($lang->task_xtaorphan_run_cleaned, $count)); 16 | else 17 | add_task_log($task, $lang->task_xtaorphan_run_done); 18 | 19 | 20 | // also perform deferred MD5 hashing 21 | $query = $db->simple_select('xtattachments', 'aid,indir,attachname,updatetime', 'md5hash IS NULL'); 22 | if(isset($db->db_encoding)) { // hack for MyBB >= 1.6.12 to force it to not screw up our binary field 23 | $old_db_encoding = $db->db_encoding; 24 | $db->db_encoding = 'binary'; 25 | } 26 | while($xta = $db->fetch_array($query)) { 27 | $file = xthreads_get_attach_path($xta); 28 | $file_md5 = @md5_file($file, true); 29 | if(strlen($file_md5) == 32) { 30 | // perhaps not PHP5 31 | $file_md5 = pack('H*', $file_md5); 32 | } 33 | // we ensure that the attachment hasn't been updated during the hashing process by double-checking the updatetime field 34 | 35 | $db->update_query('xtattachments', array('md5hash' => $db->escape_string($file_md5)), 'aid='.$xta['aid'].' AND updatetime='.$xta['updatetime']); 36 | } 37 | if(isset($old_db_encoding)) $db->db_encoding = $old_db_encoding; 38 | } 39 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |   7 | 8 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/phptpl_allowed_funcs.txt: -------------------------------------------------------------------------------- 1 | ; List of allowed functions for Template Conditionals plugin 2 | ; Note that although comments start with a ";" character, they aren't exactly ignored by the plugin 3 | ; rather, no function can start with a ";", so it'll never match 4 | 5 | ; Logical operators (dodgy fix for parser which could think these are functions) 6 | and 7 | or 8 | xor 9 | 10 | array_change_key_case 11 | array_chunk 12 | array_combine 13 | array_count_values 14 | array_diff_assoc 15 | array_diff_key 16 | array_diff 17 | array_fill_keys 18 | array_fill 19 | array_flip 20 | array_intersect_assoc 21 | array_intersect_key 22 | array_intersect 23 | array_key_exists 24 | array_keys 25 | array_merge_recursive 26 | array_merge 27 | array_multisort 28 | array_pad 29 | array_product 30 | array_rand 31 | array_replace 32 | array_replace_recursive 33 | array_reverse 34 | array_search 35 | array_slice 36 | array_sum 37 | array_unique 38 | array_values 39 | array 40 | compact 41 | count 42 | current 43 | each 44 | end 45 | in_array 46 | key 47 | next 48 | pos 49 | prev 50 | range 51 | reset 52 | sizeof 53 | get_class 54 | get_class_vars 55 | get_object_vars 56 | get_parent_class 57 | is_a 58 | is_subclass_of 59 | class_exists 60 | class_implements 61 | class_parents 62 | class_uses 63 | method_exists 64 | property_exists 65 | function_exists 66 | abs 67 | acos 68 | acosh 69 | asin 70 | asinh 71 | atan2 72 | atan 73 | atanh 74 | base_convert 75 | bindec 76 | ceil 77 | cos 78 | cosh 79 | decbin 80 | dechex 81 | decoct 82 | defined 83 | deg2rad 84 | exp 85 | expm1 86 | floor 87 | fmod 88 | getrandmax 89 | hex2bin 90 | hexdec 91 | hypot 92 | is_finite 93 | is_infinite 94 | is_nan 95 | lcg_value 96 | log10 97 | log1p 98 | log 99 | max 100 | min 101 | mt_getrandmax 102 | mt_rand 103 | octdec 104 | pi 105 | pow 106 | rad2deg 107 | rand 108 | round 109 | sin 110 | sinh 111 | sqrt 112 | tan 113 | tanh 114 | get_browser 115 | highlight_string 116 | pack 117 | uniqid 118 | unpack 119 | version_compare 120 | addcslashes 121 | addslashes 122 | bin2hex 123 | chop 124 | chr 125 | chunk_split 126 | convert_cyr_string 127 | convert_uudecode 128 | convert_uuencode 129 | count_chars 130 | crc32 131 | crypt 132 | explode 133 | get_html_translation_table 134 | hebrev 135 | hebrevc 136 | html_entity_decode 137 | htmlentities 138 | htmlspecialchars_decode 139 | htmlspecialchars 140 | implode 141 | join 142 | levenshtein 143 | localeconv 144 | ltrim 145 | md5 146 | metaphone 147 | money_format 148 | nl_langinfo 149 | nl2br 150 | number_format 151 | ord 152 | quoted_printable_decode 153 | quotemeta 154 | rtrim 155 | sha1 156 | soundex 157 | sprintf 158 | vsprintf 159 | str_getcsv 160 | str_ireplace 161 | str_pad 162 | str_repeat 163 | str_replace 164 | str_rot13 165 | str_shuffle 166 | str_split 167 | str_word_count 168 | strcasecmp 169 | strchr 170 | strcmp 171 | strcoll 172 | strcspn 173 | strip_tags 174 | stripcslashes 175 | stripos 176 | stripslashes 177 | stristr 178 | strlen 179 | strnatcasecmp 180 | strnatcmp 181 | strncasecmp 182 | strncmp 183 | strpbrk 184 | strpos 185 | strrchr 186 | strrev 187 | strripos 188 | strrpos 189 | strspn 190 | strstr 191 | strtok 192 | strtolower 193 | strtoupper 194 | strtr 195 | substr_compare 196 | substr_count 197 | substr_replace 198 | substr 199 | trim 200 | lcfirst 201 | ucfirst 202 | ucwords 203 | wordwrap 204 | unicode_decode 205 | unicode_encode 206 | base64_decode 207 | base64_encode 208 | get_headers 209 | http_build_cookie 210 | http_build_query 211 | http_build_str 212 | http_build_url 213 | http_chunked_decode 214 | http_date 215 | http_deflate 216 | http_inflate 217 | http_parse_cookie 218 | http_parse_headers 219 | http_parse_message 220 | http_parse_params 221 | parse_url 222 | rawurldecode 223 | rawurlencode 224 | urldecode 225 | urlencode 226 | doubleval 227 | empty 228 | floatval 229 | gettype 230 | intval 231 | is_array 232 | is_binary 233 | is_bool 234 | is_buffer 235 | is_double 236 | is_float 237 | is_int 238 | is_integer 239 | is_long 240 | is_null 241 | is_numeric 242 | is_object 243 | is_real 244 | is_resource 245 | is_scalar 246 | is_string 247 | is_unicode 248 | isset 249 | strval 250 | utf8_decode 251 | utf8_encode 252 | basename 253 | escapeshellarg 254 | escapeshellcmd 255 | fnmatch 256 | hash 257 | image_type_to_extension 258 | image_type_to_mime_type 259 | json_decode 260 | json_encode 261 | json_last_error 262 | parse_ini_string 263 | pathinfo 264 | recode_string 265 | recode 266 | simplexml_load_string 267 | token_name 268 | token_get_all 269 | 270 | ;GZip/BZip 271 | gzcompress 272 | gzdecode 273 | gzdeflate 274 | gzencode 275 | gzinflate 276 | gzuncompress 277 | bzcompress 278 | bzdecompress 279 | 280 | ;BCMath 281 | bcadd 282 | bccomp 283 | bcdiv 284 | bcmod 285 | bcmul 286 | bcpow 287 | bcpowmod 288 | bcsqrt 289 | bcsub 290 | 291 | ;Calendar extension 292 | cal_days_in_month 293 | cal_from_jd 294 | cal_info 295 | cal_to_jd 296 | easter_date 297 | easter_days 298 | frenchtojd 299 | gregoriantojd 300 | jddayofweek 301 | jdmonthname 302 | jdtofrench 303 | jdtogregorian 304 | jdtojewish 305 | jdtojulian 306 | jdtounix 307 | jewishtojd 308 | juliantojd 309 | unixtojd 310 | 311 | ;Date/Time 312 | checkdate 313 | date 314 | date_parse 315 | date_sun_info 316 | date_sunrise 317 | date_sunset 318 | getdate 319 | gettimeofday 320 | gmdate 321 | gmmktime 322 | gmstrftime 323 | idate 324 | localtime 325 | microtime 326 | mktime 327 | strftime 328 | strptime 329 | strtotime 330 | time 331 | 332 | ;CType 333 | ctype_alnum 334 | ctype_alpha 335 | ctype_cntrl 336 | ctype_digit 337 | ctype_graph 338 | ctype_lower 339 | ctype_print 340 | ctype_punct 341 | ctype_space 342 | ctype_upper 343 | ctype_xdigit 344 | 345 | ;Networking 346 | dns_check_record 347 | checkdnsrr 348 | dns_get_mx 349 | getmxrr 350 | dns_get_record 351 | gethostbyaddr 352 | gethostbyname 353 | gethostbynamel 354 | gethostname 355 | headers_list 356 | inet_ntop 357 | inet_pton 358 | ip2long 359 | long2ip 360 | 361 | ;iconv 362 | iconv 363 | iconv_get_encoding 364 | iconv_mime_decode 365 | iconv_mime_decode_headers 366 | iconv_mime_encode 367 | iconv_strlen 368 | iconv_strpos 369 | iconv_strrpos 370 | iconv_substr 371 | 372 | ;mbstring 373 | mb_check_encoding 374 | mb_convert_case 375 | mb_convert_encoding 376 | mb_convert_kana 377 | mb_decode_mimeheader 378 | mb_decode_numericentity 379 | mb_detect_encoding 380 | mb_detect_order 381 | mb_encode_mimeheader 382 | mb_encode_numericentity 383 | mb_encoding_aliases 384 | mb_ereg_match 385 | mb_get_info 386 | mb_http_input 387 | mb_list_encodings 388 | mb_output_handler 389 | mb_preferred_mime_name 390 | mb_regex_encoding 391 | mb_split 392 | mb_strcut 393 | mb_strimwidth 394 | mb_stripos 395 | mb_stristr 396 | mb_strlen 397 | mb_strpos 398 | mb_strrchr 399 | mb_strrichr 400 | mb_strripos 401 | mb_strrpos 402 | mb_strstr 403 | mb_strtolower 404 | mb_strtoupper 405 | mb_strwidth 406 | mb_substr_count 407 | mb_substr 408 | 409 | ;mcrypt 410 | mcrypt_cbc 411 | mcrypt_cfb 412 | mcrypt_ecb 413 | mcrypt_ofb 414 | mcrypt_create_iv 415 | mcrypt_decrypt 416 | mcrypt_encrypt 417 | mcrypt_get_block_size 418 | mcrypt_get_cipher_name 419 | mcrypt_get_iv_size 420 | mcrypt_get_key_size 421 | mcrypt_list_algorithms 422 | mcrypt_list_modes 423 | 424 | ;mhash 425 | mhash 426 | mhash_count 427 | mhash_get_block_size 428 | mhash_get_hash_name 429 | mhash_keygen_s2k 430 | 431 | ;OpenSSL 432 | openssl_cipher_iv_length 433 | openssl_decrypt 434 | openssl_digest 435 | openssl_encrypt 436 | openssl_error_string 437 | openssl_get_cipher_methods 438 | openssl_get_md_methods 439 | openssl_verify 440 | openssl_x509_check_private_key 441 | openssl_x509_checkpurpose 442 | openssl_x509_parse 443 | 444 | ;ssdeep 445 | ssdeep_fuzzy_hash 446 | ssdeep_fuzzy_compare 447 | 448 | ;stats 449 | stats_absolute_deviation 450 | stats_cdf_beta 451 | stats_cdf_binomial 452 | stats_cdf_cauchy 453 | stats_cdf_chisquare 454 | stats_cdf_exponential 455 | stats_cdf_f 456 | stats_cdf_gamma 457 | stats_cdf_laplace 458 | stats_cdf_logistic 459 | stats_cdf_negative_binomial 460 | stats_cdf_noncentral_chisquare 461 | stats_cdf_noncentral_f 462 | stats_cdf_poisson 463 | stats_cdf_t 464 | stats_cdf_uniform 465 | stats_cdf_weibull 466 | stats_covariance 467 | stats_den_uniform 468 | stats_dens_beta 469 | stats_dens_cauchy 470 | stats_dens_chisquare 471 | stats_dens_exponential 472 | stats_dens_f 473 | stats_dens_gamma 474 | stats_dens_laplace 475 | stats_dens_logistic 476 | stats_dens_negative_binomial 477 | stats_dens_normal 478 | stats_dens_pmf_binomial 479 | stats_dens_pmf_hypergeometric 480 | stats_dens_pmf_poisson 481 | stats_dens_t 482 | stats_dens_weibull 483 | stats_harmonic_mean 484 | stats_kurtosis 485 | stats_rand_gen_beta 486 | stats_rand_gen_chisquare 487 | stats_rand_gen_exponential 488 | stats_rand_gen_f 489 | stats_rand_gen_funiform 490 | stats_rand_gen_gamma 491 | stats_rand_gen_ibinomial_negative 492 | stats_rand_gen_ibinomial 493 | stats_rand_gen_int 494 | stats_rand_gen_ipoisson 495 | stats_rand_gen_iuniform 496 | stats_rand_gen_noncenral_chisquare 497 | stats_rand_gen_noncentral_f 498 | stats_rand_gen_noncentral_t 499 | stats_rand_gen_normal 500 | stats_rand_gen_t 501 | stats_rand_ranf 502 | stats_rand_setall 503 | stats_skew 504 | stats_standard_deviation 505 | stats_stat_binomial_coef 506 | stats_stat_correlation 507 | stats_stat_gennch 508 | stats_stat_independent_t 509 | stats_stat_innerproduct 510 | stats_stat_noncentral_t 511 | stats_stat_paired_t 512 | stats_stat_percentile 513 | stats_stat_powersum 514 | stats_variance 515 | 516 | ;PCRE 517 | preg_grep 518 | preg_quote 519 | preg_split 520 | 521 | ;xdiff 522 | xdiff_string_diff 523 | xdiff_string_diff_binary 524 | xdiff_string_bdiff 525 | xdiff_string_bdiff_size 526 | xdiff_string_bpatch 527 | xdiff_string_patch_binary 528 | xdiff_string_bdiff 529 | 530 | ;================ 531 | ;MyBB functions 532 | add_breadcrumb 533 | alt_trow 534 | ban_date2timestamp 535 | build_archive_link 536 | build_profile_link 537 | convert_through_utf8 538 | dec_to_utf8 539 | ;escaped_explode 540 | fetch_ban_times 541 | fetch_longipv4_range 542 | fetch_page_url 543 | fix_mktime 544 | format_bdays 545 | format_name 546 | generate_post_check 547 | get_age 548 | get_announcement_link 549 | get_attachment_icon 550 | get_bdays 551 | get_calendar_link 552 | get_calendar_week_link 553 | get_colored_warning_level 554 | get_current_location 555 | get_event_date 556 | get_event_link 557 | get_event_poster 558 | get_extension 559 | get_forum 560 | get_forum_link 561 | get_friendly_size 562 | get_inactive_forums 563 | get_ip 564 | get_parent_list 565 | get_post 566 | get_post_link 567 | get_profile_link 568 | get_reputation 569 | get_thread 570 | get_thread_link 571 | get_unviewable_forums 572 | get_user 573 | get_weekday 574 | htmlspecialchars_uni 575 | is_banned_email 576 | is_banned_ip 577 | is_banned_username 578 | is_moderator 579 | is_super_admin 580 | match_sequence 581 | multipage 582 | my_date 583 | my_get_array_cookie 584 | my_ip2long 585 | my_long2ip 586 | my_number_format 587 | my_rand 588 | my_strlen 589 | my_strpos 590 | my_strtolower 591 | my_strtoupper 592 | my_substr 593 | my_wordwrap 594 | nice_time 595 | random_str 596 | reset_breadcrumb 597 | signed 598 | subforums_count 599 | trim_blank_chrs 600 | unhtmlentities 601 | unichr 602 | unicode_chr 603 | user_permissions 604 | usergroup_permissions 605 | validate_email_format 606 | 607 | ;PHPTPL functions 608 | xthreads_phptpl_eval_expr 609 | xthreads_phptpl_eval_text 610 | 611 | ;ob_* ? 612 | ; yaml? -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_attachfuncs.php: -------------------------------------------------------------------------------- 1 | subnet mask; from http://en.wikipedia.org/wiki/Reserved_IP_addresses 23 | ip2long('0.0.0.0') => 8, 24 | ip2long('10.0.0.0') => 8, 25 | ip2long('127.0.0.0') => 8, 26 | ip2long('169.254.0.0') => 16, 27 | ip2long('172.16.0.0') => 12, 28 | ip2long('192.168.0.0') => 16, 29 | ); 30 | foreach($internal_masks as $ipmask => $subnet) { 31 | if(($ip & (0xFFFFFFFF << (32-$subnet))) == $ipmask) 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | // generate secret hash to mask random file hash with 38 | // @param: time period offset, we store whether the number was odd or even, and adapt accordingly 39 | function xthreads_attach_hash(&$odd=false) { 40 | static $secret=null; 41 | if(!isset($secret)) { 42 | if(isset($GLOBALS['mybb']->config['database']['password'])) 43 | $config =& $GLOBALS['mybb']->config; 44 | else { 45 | @include MYBB_ROOT.'inc/config.php'; 46 | } 47 | $secret = md5(substr(md5($config['database']['database'].','.$config['database']['password']), 0, 12).__FILE__); 48 | unset($config); 49 | } 50 | $key = $secret; 51 | if(defined('XTHREADS_EXPIRE_ATTACH_LINK') && XTHREADS_EXPIRE_ATTACH_LINK) { 52 | $time = floor(time() / XTHREADS_EXPIRE_ATTACH_LINK); 53 | if($odd !== false) { 54 | if($time % 2 != $odd) --$time; 55 | } else 56 | $odd = $time % 2; 57 | $key .= '|'.$time; 58 | } 59 | if(defined('XTHREADS_ATTACH_LINK_IPMASK') && XTHREADS_ATTACH_LINK_IPMASK) 60 | $key .= '|'.(xthreads_get_ip() & (0xFFFFFFFF << (32-XTHREADS_ATTACH_LINK_IPMASK))); // because PHP doesn't like ~(0xffffffff >> x) 61 | return crc32(md5(gzdeflate($key))); 62 | } 63 | 64 | // these two functions assume input is hex encoded 65 | function xthreads_attach_encode_hash($hash) { 66 | $hash = hexdec($hash); 67 | $odd = false; 68 | $hash ^= xthreads_attach_hash($odd); 69 | $hash = str_pad(dechex($hash), 8, '0', STR_PAD_LEFT); 70 | if(defined('XTHREADS_EXPIRE_ATTACH_LINK') && XTHREADS_EXPIRE_ATTACH_LINK) 71 | if($odd) $hash .= '0'; 72 | return $hash; 73 | } 74 | function xthreads_attach_decode_hash($hash) { 75 | $odd = strlen($hash) - 8; 76 | $hash = hexdec(substr($hash, 0, 8)); 77 | $hash ^= xthreads_attach_hash($odd); 78 | return str_pad(dechex($hash), 8, '0', STR_PAD_LEFT); 79 | } 80 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_image.php: -------------------------------------------------------------------------------- 1 | load()' call 20 | // force path to be within the forum root 21 | if(strpos($img, '../')) return false; 22 | return $this->_load(MYBB_ROOT.'/'.$img); 23 | } 24 | function _load($img) { 25 | if(!function_exists('imagecreate')) 26 | return false; 27 | 28 | $dims = getimagesize($img); 29 | if(empty($dims)) return false; 30 | $this->OWIDTH = $this->WIDTH = $dims[0]; 31 | $this->OHEIGHT = $this->HEIGHT = $dims[1]; 32 | 33 | $this->FILENAME = $img; 34 | 35 | $types = array(IMAGETYPE_GIF=>'GIF',IMAGETYPE_JPEG=>'JPEG',IMAGETYPE_PNG=>'PNG', 36 | IMAGETYPE_WBMP=>'WBMP',IMAGETYPE_XBM=>'XBM', 37 | ); 38 | if(!isset($types[$dims[2]])) return false; // unsupported type? TODO: maybe change this 39 | $this->TYPE = $types[$dims[2]]; 40 | $this->typeGD = $dims[2]; 41 | 42 | if($this->_img) imagedestroy($this->_img); 43 | 44 | switch($dims[2]) { 45 | case IMAGETYPE_PNG: 46 | $this->_img = imagecreatefrompng($img); 47 | break; 48 | case IMAGETYPE_JPEG: 49 | $this->_img = imagecreatefromjpeg($img); 50 | break; 51 | case IMAGETYPE_GIF: 52 | $this->_img = imagecreatefromgif($img); 53 | break; 54 | case IMAGETYPE_WBMP: 55 | $this->_img = imagecreatefromwbmp($img); 56 | break; 57 | case IMAGETYPE_XBM: 58 | $this->_img = imagecreatefromxbm($img); 59 | break; 60 | } 61 | if(!$this->_img) return false; 62 | return $this; 63 | } 64 | 65 | function blank($w, $h, $bg=0) { 66 | $this->OWIDTH = $this->WIDTH = $w; 67 | $this->OHEIGHT = $this->HEIGHT = $h; 68 | $this->FILENAME = ''; 69 | $this->TYPE = 'PNG'; 70 | $this->typeGD = IMAGETYPE_PNG; 71 | if($this->_img) imagedestroy($this->_img); 72 | $this->_img = $this->_surface($w, $h, $this->_color($bg)); 73 | if(!$this->_img) return false; 74 | return $this; 75 | } 76 | 77 | /*private*/ function _surface($w, $h, $col=null) { 78 | ($im = @imagecreatetruecolor($w, $h)) or ($im = @imagecreate($w, $h)); 79 | // set a transparent background 80 | imagealphablending($im, false); // don't blend alpha (copy it) 81 | imagesavealpha($im, true); // save alpha 82 | if(!isset($col)) 83 | $col = array($this->_transColor[0], $this->_transColor[1], $this->_transColor[2], 255); 84 | imagefill($im, 0, 0, imagecolorallocatealpha($im, $col[0], $col[1], $col[2], (int)($col[3]/2))); // fill background 85 | return $im; 86 | } 87 | 88 | function downscale($w, $h) { 89 | if($this->WIDTH > $w || $this->HEIGHT > $h) 90 | return $this->scale_max($w, $h); 91 | return $this; 92 | } 93 | function upscale($w, $h) { 94 | if($this->WIDTH < $w || $this->HEIGHT < $h) 95 | return $this->scale_min($w, $h); 96 | return $this; 97 | } 98 | 99 | function scale($w, $h) { 100 | return $this->scale_max($w, $h); 101 | } 102 | function scale_max($w, $h) { 103 | return $this->_scale($w, $h, true); 104 | } 105 | function scale_min($w, $h) { 106 | return $this->_scale($w, $h, false); 107 | } 108 | 109 | /*private*/ function _scale($w, $h, $max) { 110 | $r = max($this->WIDTH, 1) / max($this->HEIGHT, 1); 111 | $w = max($w, 1); $h = max($h, 1); 112 | if(($r > ($w/$h)) == $max) { 113 | $nw = $w; 114 | $nh = max(round($w/$r), 1); 115 | } else { 116 | $nw = max(round($h*$r), 1); 117 | $nh = $h; 118 | } 119 | return $this->resize($nw, $nh); 120 | } 121 | 122 | function resize($w, $h) { 123 | if(!isset($this->_img)) return; 124 | 125 | // actual resizing 126 | $im = $this->_surface($w, $h); 127 | @imagecopyresampled($im, $this->_img, 0,0,0,0, $w,$h, $this->WIDTH, $this->HEIGHT); 128 | imagedestroy($this->_img); 129 | $this->_img = $im; 130 | 131 | $this->WIDTH = $w; 132 | $this->HEIGHT = $h; 133 | return $this; 134 | } 135 | 136 | function crop($w, $h) { 137 | return $this->crop_cm($w, $h); 138 | } 139 | function crop_lt($w, $h, $x=0, $y=0) { 140 | if($w < 0) $w += $this->WIDTH; 141 | if($h < 0) $h += $this->HEIGHT; 142 | if($x === 'm') 143 | $x = max(($this->WIDTH - $w)/2, 0); 144 | elseif($x === 'e') 145 | $x = max($this->WIDTH - $w, 0); 146 | elseif($x < 0) 147 | $x += $this->WIDTH; 148 | if($y === 'm') 149 | $y = max(($this->HEIGHT - $h)/2, 0); 150 | elseif($y === 'e') 151 | $y = max($this->HEIGHT - $h, 0); 152 | elseif($y < 0) 153 | $y += $this->HEIGHT; 154 | 155 | // sanity check 156 | if($w < 0 || $h < 0 || $x < 0 || $y < 0 || $x+$w > $this->WIDTH || $y+$h > $this->HEIGHT) 157 | return $this; 158 | 159 | if(!isset($this->_img)) return; 160 | 161 | // actual cropping 162 | $im = $this->_surface($w, $h); 163 | @imagecopy($im, $this->_img, 0,0,$x,$y, $w,$h); 164 | imagedestroy($this->_img); 165 | $this->_img = $im; 166 | 167 | $this->WIDTH = $w; 168 | $this->HEIGHT = $h; 169 | return $this; 170 | } 171 | function crop_ct($w, $h, $y=0) { 172 | return $this->crop_lt($w, $h, 'm', $y); 173 | } 174 | function crop_rt($w, $h, $x=0, $y=0) { 175 | return $this->crop_lt($w, $h, ($x?-$x:'e'), $y); 176 | } 177 | function crop_lm($w, $h, $x=0) { 178 | return $this->crop_lt($w, $h, $x, 'm'); 179 | } 180 | function crop_cm($w, $h) { 181 | return $this->crop_lt($w, $h, 'm', 'm'); 182 | } 183 | function crop_rm($w, $h, $x=0) { 184 | return $this->crop_lt($w, $h, ($x?-$x:'e'), 'm'); 185 | } 186 | function crop_lb($w, $h, $x=0, $y=0) { 187 | return $this->crop_lt($w, $h, $x, ($y?-$y:'e')); 188 | } 189 | function crop_cb($w, $h, $y=0) { 190 | return $this->crop_lt($w, $h, 'm', ($y?-$y:'e')); 191 | } 192 | function crop_rb($w, $h, $x=0, $y=0) { 193 | return $this->crop_lt($w, $h, ($x?-$x:'e'), ($y?-$y:'e')); 194 | } 195 | 196 | /** misc filters **/ 197 | /*private*/ function _filter($filt) { 198 | if(!isset($this->_img)) return; 199 | $args = func_get_args(); 200 | array_unshift($args, $this->_img); 201 | call_user_func_array('imagefilter', $args); 202 | return $this; 203 | } 204 | function negate() { 205 | return $this->_filter(IMG_FILTER_NEGATE); 206 | } 207 | function grayscale() { 208 | return $this->_filter(IMG_FILTER_GRAYSCALE); 209 | } 210 | function brightness($n) { 211 | return $this->_filter(IMG_FILTER_BRIGHTNESS, $n); 212 | } 213 | function contrast($n) { 214 | return $this->_filter(IMG_FILTER_CONTRAST, $n); 215 | } 216 | function colorize($r,$g,$b,$a) { 217 | return $this->_filter(IMG_FILTER_COLORIZE, $r,$g,$b,$a); 218 | } 219 | function edgedetect() { 220 | return $this->_filter(IMG_FILTER_EDGEDETECT); 221 | } 222 | function emboss() { 223 | return $this->_filter(IMG_FILTER_EMBOSS); 224 | } 225 | function gaussian_blur() { 226 | return $this->_filter(IMG_FILTER_GAUSSIAN_BLUR); 227 | } 228 | function selective_blur() { 229 | return $this->_filter(IMG_FILTER_SELECTIVE_BLUR); 230 | } 231 | function mean_removal() { 232 | return $this->_filter(IMG_FILTER_MEAN_REMOVAL); 233 | } 234 | function smooth($n) { 235 | return $this->_filter(IMG_FILTER_SMOOTH, $n); 236 | } 237 | // requires PHP >= 5.3 238 | function pixelate($s, $adv=false) { 239 | return $this->_filter(IMG_FILTER_PIXELATE, $s, $adv); 240 | } 241 | 242 | function copy($from, $dest_x=0, $dest_y=0) { 243 | if(!isset($this->_img) || !is_a($from, get_class($this)) || !isset($from->_img)) return; 244 | if($dest_x < 0) $dest_x = $this->WIDTH - $from->WIDTH + $dest_x; 245 | if($dest_y < 0) $dest_y = $this->HEIGHT - $from->HEIGHT + $dest_y; 246 | imagealphablending($this->_img, true); 247 | @imagecopy($this->_img, $from->_img, $dest_x, $dest_y, 0, 0, $from->WIDTH, $from->HEIGHT); 248 | imagealphablending($this->_img, false); 249 | return $this; 250 | } 251 | function copy_onto($to, $dest_x=0, $dest_y=0) { 252 | if(!isset($this->_img) || !is_a($to, get_class($this)) || !isset($to->_img)) return; 253 | // we need to make a copy because we don't want to overwrite the $to image given 254 | $im = $this->_surface($to->WIDTH, $to->HEIGHT); 255 | @imagecopy($im, $to->_img, 0,0,0,0, $to->WIDTH,$to->HEIGHT); 256 | 257 | if($dest_x < 0) $dest_x = $to->WIDTH - $this->WIDTH + $dest_x; 258 | if($dest_y < 0) $dest_y = $to->HEIGHT - $this->HEIGHT + $dest_y; 259 | imagealphablending($im, true); 260 | @imagecopy($im, $this->_img, $dest_x, $dest_y, 0, 0, $this->WIDTH, $this->HEIGHT); 261 | imagealphablending($im, false); 262 | imagedestroy($this->_img); 263 | $this->_img = $im; 264 | $this->WIDTH = $to->WIDTH; 265 | $this->HEIGHT = $to->HEIGHT; 266 | return $this; 267 | } 268 | 269 | /*private static*/ function _color($v) { 270 | if(is_string($v)) { 271 | if(isset($v[0]) && $v[0] == '#') $v=substr($v,1); 272 | // split into halves to avoid precision/overflow problems 273 | $colA = (int)base_convert(str_pad(substr($v, 0, 4), 4, '0'), 16, 10); 274 | $colB = (int)base_convert(str_pad(substr($v, 4, 4), 4, '0'), 16, 10); 275 | return array( 276 | ($colA >> 8) & 0xFF, 277 | ($colA >> 0) & 0xFF, 278 | ($colB >> 8) & 0xFF, 279 | ($colB >> 0) & 0xFF, 280 | ); 281 | } elseif(is_int($v)) { 282 | return array( 283 | ($v >> 0) & 0xFF, 284 | ($v >> 8) & 0xFF, 285 | ($v >> 16) & 0xFF, 286 | ($v >> 24) & 0xFF, 287 | ); 288 | } elseif(is_array($v)) { 289 | return array( 290 | @$v[0] & 0xFF, 291 | @$v[1] & 0xFF, 292 | @$v[2] & 0xFF, 293 | @$v[3] & 0xFF 294 | ); 295 | } 296 | return array(0,0,0,0); 297 | } 298 | 299 | function jpeg($transCol=0, $q=null) { 300 | $this->TYPE = 'JPEG'; 301 | $this->typeGD = IMAGETYPE_JPEG; 302 | !isset($q) or $this->_jpeg_quality = max(min((int)$q, 100), 0); 303 | 304 | $transCol = array_slice($this->_color($transCol), 0,3); 305 | if($transCol != $this->_transColor) { 306 | $this->_transColor = $transCol; 307 | $im = $this->_surface($this->WIDTH, $this->HEIGHT); 308 | @imagecopy($im, $this->_img, 0,0,0,0, $this->WIDTH,$this->HEIGHT); 309 | imagedestroy($this->_img); 310 | $this->_img = $im; 311 | } 312 | return $this; 313 | } 314 | function png($l=null) { 315 | $this->TYPE = 'PNG'; 316 | $this->typeGD = IMAGETYPE_PNG; 317 | !isset($l) or $this->_png_level = max(min((int)$l, 9), 0); 318 | return $this; 319 | } 320 | 321 | function write($fn) { 322 | if(!isset($this->_img)) return; 323 | if(!$this->_enableWrite) return; 324 | if($this->TYPE == 'JPEG') { 325 | // for some reason, GD always turns a transparent background to black regardless of the actual colour there (:O) 326 | // fix by copying onto non transparent background 327 | $im = $this->_surface($this->WIDTH, $this->HEIGHT, array($this->_transColor[0], $this->_transColor[1], $this->_transColor[2], 0)); 328 | imagealphablending($im, true); // blend into background 329 | imagesavealpha($im, false); 330 | @imagecopy($im, $this->_img, 0,0,0,0, $this->WIDTH,$this->HEIGHT); 331 | @imageinterlace($im, false); 332 | imagejpeg($im, $fn, $this->_jpeg_quality); 333 | imagedestroy($im); 334 | } else 335 | imagepng($this->_img, $fn, $this->_png_level); // PNG_ALL_FILTERS 336 | } 337 | 338 | // PHP 4.x-ers? heh... 339 | function __destruct() { 340 | if(isset($this->_img)) imagedestroy($this->_img); 341 | } 342 | } 343 | 344 | // functions to allow conditionals to create image objects 345 | function newXTImg() { 346 | return new XTImageTransform; 347 | } 348 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_install.php: -------------------------------------------------------------------------------- 1 | add_hook('admin_config_plugins_activate_commit', 'xthreads_plugins_phptpl_activate'); 10 | $plugins->add_hook('admin_config_plugins_deactivate_commit', 'xthreads_plugins_phptpl_deactivate'); 11 | } 12 | $plugins->add_hook('admin_config_plugins_activate_commit', 'xthreads_plugins_quickthread_install'); 13 | } 14 | // if you don't wish to have XThreads modify any templates, set this value to true (the Quick Thread mod will still be done regardless) 15 | // note that once you have XThreads installed, this will be stored in cache/xthreads.php instead 16 | if(!defined('XTHREADS_MODIFY_TEMPLATES')) 17 | define('XTHREADS_MODIFY_TEMPLATES', true); 18 | 19 | function xthreads_install() { 20 | global $db, $cache, $plugins; 21 | $plugins->run_hooks('xthreads_install_start'); 22 | $create_table_suffix = $db->build_create_table_collation(); 23 | 24 | $dbtype = xthreads_db_type(); 25 | 26 | switch($dbtype) { 27 | case 'mysql': 28 | $engine = 'MyISAM'; 29 | // try to see if a custom table engine is being used 30 | $query = $db->query('SHOW TABLE STATUS LIKE "'.$db->table_prefix.'threads"', true); 31 | if($query) { 32 | $eng = $db->fetch_field($query, 'Engine'); 33 | if(in_array(strtolower($eng), array('innodb','aria','xtradb'))) // only stick to common possibilities to avoid issues with exquisite setups 34 | $engine = $eng; 35 | } 36 | $create_table_suffix = ' ENGINE='.$engine.$create_table_suffix; 37 | $auto_increment = ' auto_increment'; 38 | break; 39 | case 'sqlite': 40 | $auto_increment = ' PRIMARY KEY'; 41 | break; 42 | case 'pgsql': 43 | $auto_increment = ''; 44 | } 45 | 46 | if($dbtype != 'mysql') die('XThreads currently does not support database systems other than MySQL/i.'); 47 | 48 | if(!$db->table_exists('threadfields_data')) { 49 | $db->write_query('CREATE TABLE '.$db->table_prefix.'threadfields_data ( 50 | tid '.xthreads_db_fielddef('int').' not null 51 | '.($dbtype != 'sqlite' ? ', PRIMARY KEY (tid)':'').' 52 | )'.$create_table_suffix); 53 | } 54 | 55 | if(!$db->table_exists('xtattachments')) { 56 | $db->write_query('CREATE TABLE '.$db->table_prefix.'xtattachments ( 57 | aid '.xthreads_db_fielddef('int').' not null'.$auto_increment.', 58 | downloads '.xthreads_db_fielddef('bigint').' not null default 0, 59 | 60 | tid '.xthreads_db_fielddef('int').' not null, 61 | uid '.xthreads_db_fielddef('int').' not null default 0, 62 | field varchar(50) not null default \'\', 63 | posthash varchar(50) not null default \'\', 64 | filename varchar(255) not null default \'\', 65 | uploadmime varchar(120) not null default \'\', 66 | filesize '.xthreads_db_fielddef('bigint').' not null default 0, 67 | attachname varchar(120) not null default \'\', 68 | indir varchar(40) not null default \'\', 69 | md5hash '.xthreads_db_fielddef('binary', 16).' default null, 70 | uploadtime '.xthreads_db_fielddef('bigint').' not null default 0, 71 | updatetime '.xthreads_db_fielddef('bigint').' not null default 0, 72 | 73 | thumbs text not null 74 | 75 | '.($dbtype != 'sqlite' ? ', 76 | PRIMARY KEY (aid) 77 | '.($dbtype != 'pg' ? ', 78 | KEY (tid), 79 | KEY (tid,uid), 80 | KEY (posthash), 81 | KEY (field) 82 | ':'').' 83 | ':'').' 84 | )'.$create_table_suffix); 85 | } 86 | if(!$db->table_exists('threadfields')) { 87 | $fieldprops = xthreads_threadfields_props(); 88 | $query = ''; 89 | foreach($fieldprops as $field => &$prop) { 90 | $query .= ($query?',':'').'`'.$field.'` '.xthreads_db_fielddef($prop['db_type'], $prop['db_size'], $prop['db_unsigned']).' not null'; 91 | if(isset($prop['default']) && ($prop['db_type'] != 'text')) { 92 | if($prop['datatype'] == 'string') 93 | $query .= ' default \''.$db->escape_string($prop['default']).'\''; 94 | elseif($prop['datatype'] == 'double') 95 | $query .= ' default '.(float)$prop['default']; 96 | else 97 | $query .= ' default '.(int)$prop['default']; 98 | } 99 | if($field == 'field' && $dbtype == 'sqlite') 100 | $query .= ' PRIMARY KEY'; 101 | } 102 | $db->write_query('CREATE TABLE '.$db->table_prefix.'threadfields ( 103 | '.$query.' 104 | '.($dbtype != 'sqlite' ? ', 105 | PRIMARY KEY (field) 106 | '.($dbtype != 'pg' ? ', 107 | KEY (disporder) 108 | ':'').' 109 | ':'').' 110 | )'.$create_table_suffix); 111 | // `allowsort` '.xthreads_db_numdef('tinyint').' not null default 0, 112 | } 113 | 114 | foreach(array( 115 | 'grouping' => xthreads_db_fielddef('int').' not null default 0', 116 | 'firstpostattop' => xthreads_db_fielddef('tinyint').' not null default 0', 117 | 'inlinesearch' => xthreads_db_fielddef('tinyint').' not null default 0', 118 | 'tplprefix' => 'text not null', 119 | 'langprefix' => 'text not null', 120 | 'allow_blankmsg' => xthreads_db_fielddef('tinyint').' not null default 0', 121 | 'nostatcount' => xthreads_db_fielddef('tinyint').' not null default 0', 122 | 'fdcolspan_offset' => xthreads_db_fielddef('smallint', null, false).' not null default 0', 123 | 'settingoverrides' => 'text not null', 124 | 'postsperpage' => xthreads_db_fielddef('smallint').' not null default 0', 125 | 'hideforum' => xthreads_db_fielddef('tinyint').' not null default 0', 126 | 'hidebreadcrumb' => xthreads_db_fielddef('tinyint').' not null default 0', 127 | 'defaultfilter' => 'text not null', 128 | 'wol_announcements' => 'varchar(255) not null default \'\'', 129 | 'wol_forumdisplay' => 'varchar(255) not null default \'\'', 130 | 'wol_newthread' => 'varchar(255) not null default \'\'', 131 | 'wol_attachment' => 'varchar(255) not null default \'\'', 132 | 'wol_newreply' => 'varchar(255) not null default \'\'', 133 | 'wol_showthread' => 'varchar(255) not null default \'\'', 134 | ) as $field => $fdef) { 135 | if(!$db->field_exists($field, 'forums')) { 136 | $db->write_query('ALTER TABLE '.$db->table_prefix.'forums ADD COLUMN xthreads_'.$field.' '.$fdef); 137 | } 138 | } 139 | // add indexes 140 | foreach(array( 141 | 'uid', 142 | 'lastposteruid', 143 | 'prefix', 144 | 'icon', 145 | ) as $afe) { 146 | if($afe == 'uid') continue; // we won't remove this from the above array 147 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threads` ADD KEY `xthreads_'.$afe.'` (`'.$afe.'`)', true); 148 | } 149 | // increase size of sorting column 150 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` MODIFY `defaultsortby` varchar(255) NOT NULL default \'\''); 151 | $cache->update_forums(); 152 | 153 | // check for xthreads_attachment.php supported URL type 154 | if(file_exists(MYBB_ROOT.'xthreads_attach.php')) { // if not, our admin is a dufus 155 | $rand = 'aA0._|'.mt_rand(); 156 | $rand_md5 = md5($rand); 157 | $baseurl = $GLOBALS['mybb']->settings['bburl'].'/xthreads_attach.php'; 158 | if(fetch_remote_file($baseurl.'/test/'.$rand) == $rand_md5) 159 | define('XTHREADS_ATTACH_USE_QUERY', -1); // our default works 160 | elseif(fetch_remote_file($baseurl.'?file=test/'.$rand) == $rand_md5) 161 | define('XTHREADS_ATTACH_USE_QUERY', 1); 162 | elseif(fetch_remote_file($baseurl.'?file=test|'.$rand) == $rand_md5) 163 | define('XTHREADS_ATTACH_USE_QUERY', 2); 164 | // else, well, sucks for the user... 165 | } 166 | 167 | xthreads_buildtfcache(); 168 | xthreads_write_xtcachefile(); 169 | 170 | 171 | xthreads_insert_templates(xthreads_new_templates(), -2); 172 | xthreads_plugins_quickthread_tplmod(); 173 | 174 | // admin permissions - default to all allow 175 | $query = $db->simple_select('adminoptions', 'uid,permissions'); 176 | while($adminopt = $db->fetch_array($query)) { 177 | $perms = @unserialize($adminopt['permissions']); 178 | if(empty($perms)) continue; // inherited or just messed up 179 | $perms['config']['threadfields'] = 1; 180 | $db->update_query('adminoptions', array('permissions' => $db->escape_string(serialize($perms))), 'uid='.$adminopt['uid']); 181 | } 182 | $db->free_result($query); 183 | $plugins->run_hooks('xthreads_install_end'); 184 | } 185 | 186 | function xthreads_insert_templates($new_templates, $set=-1) { 187 | global $mybb, $db; 188 | if($mybb->version_code >= 1700) // MyBB 1.8 beta or final 189 | $tpl_ver = 1800; 190 | elseif($mybb->version_code >= 1500) // MyBB 1.6 beta or final 191 | $tpl_ver = 1600; 192 | elseif($mybb->version_code >= 1400) { 193 | //$tpl_ver = min($mybb->version_code, 1411); 194 | $tpl_ver = 1413; 195 | } 196 | foreach($new_templates as $name => &$tpl) { 197 | $db->insert_query('templates', array( 198 | 'title' => $name, 199 | 'template' => $db->escape_string($tpl), 200 | 'sid' => $set, 201 | 'version' => $tpl_ver 202 | )); 203 | } 204 | } 205 | function xthreads_new_templates() { 206 | return array( 207 | 'editpost_first' => ''."\n".'{$editpost}', 208 | 'forumdisplay_group_sep' => '', 209 | 'forumdisplay_thread_null' => '', 210 | 'showthread_noreplies' => ' 211 | ', 216 | 'forumdisplay_searchforum_inline' => '
217 | {$lang->search_forum} 218 | {$gobutton} 219 | 220 | 221 | 222 | 223 | {$xthreads_forum_filter_form} 224 |

', 225 | 'post_threadfields_inputrow' => ' 226 | {$tf[\'title\']} 227 | {$inputfield}{$tf[\'desc\']} 228 | ' 229 | ) + ($GLOBALS['mybb']->version_code >= 1700 ? array( 230 | 'showthread_threadfield_row' => '{$title}{$value}', 231 | 'showthread_threadfields' => '{$threadfields_display_rows}
', 232 | ) : array( 233 | 'showthread_threadfield_row' => '{$title}{$value}', 234 | 'showthread_threadfields' => '{$threadfields_display_rows}', 235 | )); 236 | } 237 | 238 | function xthreads_undo_template_edits() { 239 | require_once MYBB_ROOT.'inc/adminfunctions_templates.php'; 240 | find_replace_templatesets('editpost', '#\\{\\$extra_threadfields\\}#', '', 0); 241 | find_replace_templatesets('newthread', '#\\{\\$extra_threadfields\\}#', '', 0); 242 | find_replace_templatesets('showthread', '#\\{\\$first_post\\}#', '', 0); 243 | find_replace_templatesets('showthread', '#\\{\\$threadfields_display\\}#', '', 0); 244 | find_replace_templatesets('forumdisplay_threadlist', '#\\{\\$nullthreads\\}#', '', 0); 245 | $forumd_sorttpl = ($GLOBALS['mybb']->version_code >= 1827) ? 'forumdisplay_forumsort' : 'forumdisplay_threadlist'; 246 | find_replace_templatesets($forumd_sorttpl, '#\\{\\$sort_by_prefix\\}#', '', 0); 247 | find_replace_templatesets($forumd_sorttpl, "#\n?".preg_replace("#[\t\r\n]+#", '\\s*', preg_quote(XTHREADS_INSTALL_TPLADD_EXTRASORT))."\\s*\{\$xthreads_extra_sorting\}#", '', 0); 248 | find_replace_templatesets('forumdisplay_threadlist_sortrating', '#\\ 253 | 254 | ' 255 | )); 256 | function xthreads_activate() { 257 | global $db, $cache, $lang, $plugins; 258 | $plugins->run_hooks('xthreads_activate_start'); 259 | $db->insert_query('tasks', array( 260 | 'title' => $db->escape_string($lang->xthreads_orphancleanup_name), 261 | 'description' => $db->escape_string($lang->xthreads_orphancleanup_desc), 262 | 'file' => 'xtaorphan_cleanup', 263 | 'minute' => '35', 264 | 'hour' => '10', 265 | 'day' => '*', 266 | 'month' => '*', 267 | 'weekday' => '*', 268 | 'nextrun' => TIME_NOW + 86400*3, // low priority - we'll assume you don't accumulate many orphans in the first few days :P 269 | 'lastrun' => 0, 270 | 'enabled' => 1, 271 | 'logging' => 1, 272 | 'locked' => 0, 273 | )); 274 | $cache->update_tasks(); 275 | 276 | if(XTHREADS_MODIFY_TEMPLATES) { 277 | // prevent doubling of template edits 278 | xthreads_undo_template_edits(); 279 | // following original in the _install() function, as these variables aren't evaluated when deactivated 280 | // but putting them here has the advantage of allowing users to redo template edits with new themes 281 | require_once MYBB_ROOT.'inc/adminfunctions_templates.php'; 282 | find_replace_templatesets('editpost', '#\\{\\$posticons\\}#', '{$extra_threadfields}{$posticons}'); 283 | find_replace_templatesets('newthread', '#\\{\\$posticons\\}#', '{$extra_threadfields}{$posticons}'); 284 | find_replace_templatesets('showthread', '#\\{\\$posts\\}#', '{$first_post}{$posts}'); 285 | if($GLOBALS['mybb']->version_code >= 1700) 286 | find_replace_templatesets('showthread', '#\\s*\#', '{$threadfields_display}$0'); 287 | else 288 | find_replace_templatesets('showthread', '#\\{\\$classic_header\\}#', '{$threadfields_display}{$classic_header}'); 289 | find_replace_templatesets('forumdisplay_threadlist', '#\\{\\$threads\\}#', '{$threads}{$nullthreads}'); 290 | $forumd_sorttpl = ($GLOBALS['mybb']->version_code >= 1827) ? 'forumdisplay_forumsort' : 'forumdisplay_threadlist'; 291 | find_replace_templatesets($forumd_sorttpl, '#\\'); 292 | find_replace_templatesets($forumd_sorttpl, '#\\'."\n".XTHREADS_INSTALL_TPLADD_EXTRASORT); 293 | find_replace_templatesets('forumdisplay_threadlist_sortrating', '#$#', ''); 294 | } 295 | $plugins->run_hooks('xthreads_activate_end'); 296 | } 297 | function xthreads_deactivate() { 298 | global $db, $cache, $plugins; 299 | $plugins->run_hooks('xthreads_deactivate_start'); 300 | $db->delete_query('tasks', 'file="xtaorphan_cleanup"'); 301 | $cache->update_tasks(); 302 | 303 | if(XTHREADS_MODIFY_TEMPLATES) 304 | xthreads_undo_template_edits(); 305 | $plugins->run_hooks('xthreads_deactivate_end'); 306 | } 307 | 308 | function xthreads_uninstall() { 309 | global $db, $cache, $mybb, $plugins; 310 | 311 | if(!empty($mybb->input['no'])) { 312 | admin_redirect(xthreads_admin_url('config', 'plugins')); 313 | exit; 314 | } 315 | if(empty($mybb->input['confirm_uninstall'])) { 316 | $link = 'index.php?confirm_uninstall=1&'.htmlspecialchars($_SERVER['QUERY_STRING']); 317 | 318 | $GLOBALS['page']->output_confirm_action($link, $GLOBALS['lang']->xthreads_confirm_uninstall); 319 | exit; 320 | } else 321 | unset($mybb->input['confirm_uninstall']); 322 | 323 | $plugins->run_hooks('xthreads_uninstall_start'); 324 | 325 | $query = $db->simple_select('adminoptions', 'uid,permissions'); 326 | while($adminopt = $db->fetch_array($query)) { 327 | $perms = @unserialize($adminopt['permissions']); 328 | if(empty($perms)) continue; // inherited or just messed up 329 | unset($perms['config']['threadfields']); 330 | $db->update_query('adminoptions', array('permissions' => $db->escape_string(serialize($perms))), 'uid='.$adminopt['uid']); 331 | } 332 | $db->free_result($query); 333 | 334 | if($db->table_exists('threadfields_data')) 335 | $db->write_query('DROP TABLE '.$db->table_prefix.'threadfields_data'); 336 | if($db->table_exists('threadfields')) 337 | $db->write_query('DROP TABLE '.$db->table_prefix.'threadfields'); 338 | if($db->table_exists('xtattachments')) { 339 | // remove attachments first 340 | require_once MYBB_ROOT.'inc/xthreads/xt_updatehooks.php'; 341 | $query = $db->simple_select('xtattachments', 'aid,indir,attachname'); 342 | while($xta = $db->fetch_array($query)) { 343 | xthreads_rm_attach_fs($xta); 344 | } 345 | $db->free_result($query); 346 | $db->write_query('DROP TABLE '.$db->table_prefix.'xtattachments'); 347 | } 348 | // remove any indexes added on the threads table 349 | foreach(array( 350 | 'uid', 351 | 'lastposteruid', 352 | 'prefix', 353 | 'icon', 354 | ) as $afe) { 355 | if($afe == 'uid') continue; // we won't remove this from the above array 356 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threads` DROP KEY `xthreads_'.$afe.'`', true); 357 | } 358 | 359 | $fields = array( 360 | 'xthreads_grouping', 361 | 'xthreads_firstpostattop', 362 | 'xthreads_inlinesearch', 363 | 'xthreads_tplprefix', 364 | 'xthreads_langprefix', 365 | 'xthreads_allow_blankmsg', 366 | 'xthreads_nostatcount', 367 | 'xthreads_fdcolspan_offset', 368 | 'xthreads_settingoverrides', 369 | 'xthreads_postsperpage', 370 | 'xthreads_hideforum', 371 | 'xthreads_hidebreadcrumb', 372 | 'xthreads_defaultfilter', 373 | 'xthreads_addfiltenable', // legacy (uninstall w/o upgrade) 374 | //'xthreads_pull_firstpost', 375 | 'xthreads_wol_announcements', 376 | 'xthreads_wol_forumdisplay', 377 | 'xthreads_wol_newthread', 378 | 'xthreads_wol_attachment', 379 | 'xthreads_wol_newreply', 380 | 'xthreads_wol_showthread', 381 | ); 382 | foreach($fields as $k => &$f) 383 | if(!$db->field_exists($f, 'forums')) 384 | unset($fields[$k]); 385 | 386 | if(!empty($fields)) { 387 | switch($db->type) { 388 | case 'sqlite3': case 'sqlite2': case 'sqlite': 389 | $db->alter_table_parse($db->table_prefix.'forums', 'DROP '.implode(', DROP COLUMN ', $fields).''); 390 | break; 391 | case 'pgsql': 392 | foreach($fields as &$f) 393 | $db->write_query('ALTER TABLE '.$db->table_prefix.'forums 394 | DROP COLUMN '.$f); 395 | break; 396 | default: 397 | $db->write_query('ALTER TABLE '.$db->table_prefix.'forums 398 | DROP COLUMN '.implode(', DROP COLUMN ', $fields)); 399 | } 400 | } 401 | 402 | // remove any custom default sorts and reduce size of sorting column back to original 403 | $db->update_query('forums', array('defaultsortby' => ''), 'defaultsortby LIKE "tf_%" OR defaultsortby LIKE "tfa_%"'); 404 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` MODIFY `defaultsortby` varchar(10) NOT NULL default \'\''); 405 | $cache->update_forums(); 406 | 407 | xthreads_delete_datacache('threadfields'); 408 | 409 | if(file_exists(MYBB_ROOT.'cache/xthreads.php')) 410 | @unlink(MYBB_ROOT.'cache/xthreads.php'); 411 | if(file_exists(MYBB_ROOT.'cache/xthreads_evalcache.php')) 412 | @unlink(MYBB_ROOT.'cache/xthreads_evalcache.php'); 413 | 414 | $db->delete_query('templates', 'title IN ("'.implode('","', array_keys(xthreads_new_templates())).'") AND sid=-2'); 415 | 416 | // revert QuickThread modification 417 | if(function_exists('quickthread_uninstall')) { 418 | $tpl = $db->fetch_array($db->simple_select('templates', 'tid,template', 'title="forumdisplay_quick_thread" AND sid=-1', array('limit' => 1))); 419 | if($tpl && strpos($tpl['template'], '{$GLOBALS[\'extra_threadfields\']}') !== false) { 420 | $newtpl = preg_replace('~\\{\\$GLOBALS\\[\'extra_threadfields\'\\]\\}'."\r?(\n\t{0,3})?".'~is', '', $tpl['template'], 1); 421 | if($newtpl != $tpl['template']) 422 | $db->update_query('templates', array('template' => $db->escape_string($newtpl)), 'tid='.$tpl['tid']); 423 | } 424 | } 425 | 426 | // try to determine and remove stuff added to the custom moderation table 427 | $query = $db->simple_select('modtools', 'tid,threadoptions'); 428 | while($tool = $db->fetch_array($query)) { 429 | $opts = unserialize($tool['threadoptions']); 430 | if(isset($opts['edit_threadfields'])) { 431 | unset($opts['edit_threadfields']); 432 | $db->update_query('modtools', array('threadoptions' => $db->escape_string(serialize($opts))), 'tid='.$tool['tid']); 433 | } 434 | } 435 | 436 | $plugins->run_hooks('xthreads_uninstall_end'); 437 | } 438 | 439 | function xthreads_delete_datacache($key) { 440 | global $cache, $db; 441 | $cache->update($key, null); 442 | if(is_object($cache->handler) && method_exists($cache->handler, 'delete')) { 443 | $cache->handler->delete($key); 444 | } 445 | $db->delete_query('datacache', 'title="'.$db->escape_string($key).'"'); 446 | } 447 | 448 | // rebuild threadfields cache on phptpl activation/deactivation 449 | function xthreads_plugins_phptpl_activate() { xthreads_plugins_phptpl_reparse(true); } 450 | function xthreads_plugins_phptpl_deactivate() { xthreads_plugins_phptpl_reparse(false); } 451 | function xthreads_plugins_phptpl_reparse($active) { 452 | if($GLOBALS['codename'] != 'phptpl' || !function_exists('phptpl_evalphp')) return; 453 | 454 | define('XTHREADS_ALLOW_PHP_THREADFIELDS_ACTIVATION', $active); // define is maybe safer? 455 | xthreads_buildtfcache(); 456 | } 457 | 458 | function xthreads_plugins_quickthread_install() { 459 | if($GLOBALS['codename'] != 'quickthread' || !$GLOBALS['install_uninstall']) return; 460 | xthreads_plugins_quickthread_tplmod(); 461 | } 462 | function xthreads_plugins_quickthread_tplmod() { 463 | if(!function_exists('quickthread_install')) return; 464 | global $db; 465 | $tpl = $db->fetch_array($db->simple_select('templates', 'tid,template', 'title="forumdisplay_quick_thread" AND sid=-1', array('limit' => 1))); 466 | if($tpl && strpos($tpl['template'], '{$GLOBALS[\'extra_threadfields\']}') === false) { 467 | $newtpl = preg_replace('~(\.*?)(\)~is', '$1{\\$GLOBALS[\'extra_threadfields\']} 468 | $2', $tpl['template'], 1); 469 | if($newtpl != $tpl['template']) 470 | $db->update_query('templates', array('template' => $db->escape_string($newtpl)), 'tid='.$tpl['tid']); 471 | } 472 | } 473 | 474 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_mischooks.php: -------------------------------------------------------------------------------- 1 | $fields.' FROM '.TABLE_PREFIX, 18 | 'LEFT JOIN '.TABLE_PREFIX.'users u' => 'LEFT JOIN `'.$db->table_prefix.'threadfields_data` tfd ON t.tid=tfd.tid LEFT JOIN '.TABLE_PREFIX.'users u', 19 | )); 20 | } 21 | control_db(' 22 | function query($string, $hide_errors=0, $write_query=0) { 23 | static $done=false; 24 | if(!$done && !$write_query && strpos($string, \'SELECT \') && strpos($string, \'u.username AS userusername\') && strpos($string, \'LEFT JOIN '.TABLE_PREFIX.'users u ON \')) { 25 | $done = true; 26 | xthreads_search_dbhook($string, $this); 27 | } 28 | return parent::query($string, $hide_errors, $write_query); 29 | } 30 | '); 31 | } 32 | 33 | global $cache, $plugins, $forum_tpl_prefixes; 34 | // cache templates - we've got not much choice but to cache all forums with custom template prefixes 35 | $cachelist = ''; 36 | $forum_tpl_prefixes = xthreads_get_tplprefixes(true); 37 | foreach($forum_tpl_prefixes as $pref) { 38 | $pref = $db->escape_string($pref); 39 | $cachelist .= ($cachelist?',':'').$pref.'search_results_posts_post,'.$pref.'search_results_threads_thread'; 40 | } 41 | if($cachelist !== '') $GLOBALS['templates']->cache($cachelist); 42 | 43 | $plugins->add_hook('search_results_post', 'xthreads_search_result_post'); 44 | $plugins->add_hook('search_results_thread', 'xthreads_search_result_thread'); 45 | } 46 | 47 | function xthreads_search_result(&$data, $tplname) { 48 | global $threadfields, $threadfield_cache, $forumcache, $mybb; 49 | 50 | // need to set these variables before doing threadfields stuff! 51 | $data['threaddate'] = my_date($mybb->settings['dateformat'], $data['dateline']); 52 | $data['threadtime'] = my_date($mybb->settings['timeformat'], $data['dateline']); 53 | xthreads_set_threadforum_urlvars('thread', $data['tid']); 54 | xthreads_set_threadforum_urlvars('forum', $data['fid']); 55 | 56 | if(!empty($threadfield_cache)) { 57 | // make threadfields array 58 | $threadfields = array(); // clear previous threadfields 59 | 60 | if(isset($GLOBALS['thread_ids'])) $tidlist =& $GLOBALS['thread_ids']; 61 | elseif(isset($GLOBALS['tids'])) $tidlist =& $GLOBALS['tids']; 62 | else $tidlist = ''; 63 | 64 | foreach($threadfield_cache as $k => &$v) { 65 | if(!empty($v['forums']) && strpos(','.$v['forums'].',', ','.$data['fid'].',') === false) 66 | continue; 67 | 68 | xthreads_get_xta_cache($v, $tidlist); 69 | 70 | $threadfields[$k] =& $data['xthreads_'.$k]; 71 | xthreads_sanitize_disp($threadfields[$k], $v, ($data['username'] !== '' ? $data['username'] : $data['userusername'])); 72 | } 73 | } 74 | // template hack 75 | xthreads_portalsearch_cache_hack($GLOBALS['forum_tpl_prefixes'][$data['fid']], $tplname); 76 | } 77 | function xthreads_search_result_post() { 78 | xthreads_search_result($GLOBALS['post'], 'search_results_posts_post'); 79 | } 80 | function xthreads_search_result_thread() { 81 | global $thread; 82 | xthreads_search_result($thread, 'search_results_threads_thread'); 83 | 84 | // fix for posts per page override 85 | $forum =& $GLOBALS['forumcache'][$thread['fid']]; 86 | if($forum['xthreads_postsperpage'] && $forum['xthreads_postsperpage'] != $GLOBALS['mybb']->settings['postsperpage']) { 87 | // urgh, we have to reproduce MyBB code, how yuck 88 | global $threadpages, $morelink, $highlight, $templates, $lang; 89 | if($thread['posts'] > $forum['xthreads_postsperpage']) { 90 | $pagesstop = ceil($thread['posts'] / $forum['xthreads_postsperpage']); 91 | if($pagesstop != $thread['pages']) { // small optimisation 92 | $thread['pages'] = $pagesstop; 93 | $threadpages = $morelink = ''; 94 | if($pagesstop > 4) { 95 | $pagesstop = 4; 96 | $page_link = get_thread_link($thread['tid'], $thread['pages']).$highlight; 97 | eval('$morelink = "'.$templates->get('forumdisplay_thread_multipage_more').'";'); 98 | } 99 | for($i = 1; $i <= $pagesstop; ++$i) { 100 | $page_link = get_thread_link($thread['tid'], $i).$highlight; 101 | eval('$threadpages .= "'.$templates->get('forumdisplay_thread_multipage_page').'";'); 102 | } 103 | eval('$thread[\'multipage\'] = "'.$templates->get('forumdisplay_thread_multipage').'";'); 104 | } 105 | } 106 | else 107 | $thread['multipage'] = $threadpages = $morelink = ''; 108 | } 109 | } 110 | 111 | 112 | function xthreads_portal() { 113 | global $threadfield_cache, $mybb; 114 | $threadfield_cache = xthreads_gettfcache(); 115 | 116 | $fids = array_flip(array_map('intval', explode(',', $mybb->settings['portal_announcementsfid']))); 117 | $all_fids = ($mybb->settings['portal_announcementsfid'] == '-1'); 118 | $fields = ''; 119 | foreach($threadfield_cache as $k => &$v) { 120 | $available = empty($v['forums']) || $all_fids; 121 | if(!$available) 122 | foreach(explode(',', $v['forums']) as $fid) { 123 | if(isset($fids[$fid])) { 124 | $available = true; 125 | break; 126 | } 127 | } 128 | if($available) 129 | $fields .= ', tfd.`'.$v['field'].'` AS `xthreads_'.$v['field'].'`'; 130 | else 131 | unset($threadfield_cache[$k]); 132 | } 133 | 134 | if($fields) { 135 | // do DB hack 136 | control_db(' 137 | function query($string, $hide_errors=0, $write_query=0) { 138 | static $done=false; 139 | if(!$done && !$write_query && strpos($string, \'SELECT t.*, t.username AS threadusername, u.username, u.avatar\')) { 140 | $done = true; 141 | $string = strtr($string, array( 142 | \'SELECT t.*, t.username AS threadusername, u.username, u.avatar\' => \'SELECT t.*, t.username AS threadusername, u.username, u.avatar'.$fields.'\', 143 | \'FROM '.TABLE_PREFIX.'threads t\' => \'FROM '.TABLE_PREFIX.'threads t LEFT JOIN '.TABLE_PREFIX.'threadfields_data tfd ON t.tid=tfd.tid\' 144 | )); 145 | } 146 | return parent::query($string, $hide_errors, $write_query); 147 | } 148 | '); 149 | } 150 | } 151 | 152 | 153 | function xthreads_portal_announcement() { 154 | static $doneinit = false; 155 | 156 | global $threadfield_cache, $announcement, $threadfields, $forum_tpl_prefixes; 157 | 158 | if(!$doneinit) { 159 | $doneinit = true; 160 | 161 | // cache templates 162 | $cachelist = ''; 163 | $forum_tpl_prefixes = xthreads_get_tplprefixes(true, $GLOBALS['forum']); 164 | foreach($forum_tpl_prefixes as $pref) { 165 | $pref = $GLOBALS['db']->escape_string($pref); 166 | $cachelist .= ($cachelist?',':'').$pref.'portal_announcement,'.$pref.'portal_announcement_numcomments,'.$pref.'portal_announcement_numcomments_no'; 167 | } 168 | if($cachelist !== '') $GLOBALS['templates']->cache($cachelist); 169 | } 170 | 171 | 172 | // following two lines not needed as we have $anndate and $anntime 173 | //$announcement['threaddate'] = my_date($mybb->settings['dateformat'], $announcement['dateline']); 174 | //$announcement['threadtime'] = my_date($mybb->settings['timeformat'], $announcement['dateline']); 175 | xthreads_set_threadforum_urlvars('thread', $announcement['tid']); 176 | xthreads_set_threadforum_urlvars('forum', $announcement['fid']); 177 | 178 | if(!empty($threadfield_cache)) { 179 | // make threadfields array 180 | $threadfields = array(); // clear previous threadfields 181 | 182 | foreach($threadfield_cache as $k => &$v) { 183 | if(!empty($v['forums']) && strpos(','.$v['forums'].',', ','.$announcement['fid'].',') === false) 184 | continue; 185 | 186 | $tids = '0'.$GLOBALS['tids']; 187 | xthreads_get_xta_cache($v, $tids); 188 | 189 | $threadfields[$k] =& $announcement['xthreads_'.$k]; 190 | xthreads_sanitize_disp($threadfields[$k], $v, ($announcement['username'] !== '' ? $announcement['username'] : $announcement['threadusername'])); 191 | } 192 | } 193 | // template hack 194 | $tplprefix =& $forum_tpl_prefixes[$announcement['fid']]; 195 | xthreads_portalsearch_cache_hack($tplprefix, 'portal_announcement'); 196 | if(!xthreads_empty($tplprefix)) { 197 | $tplname = $tplprefix.'portal_announcement_numcomments'.($announcement['replies']?'':'_no'); 198 | if(!xthreads_empty($GLOBALS['templates']->cache[$tplname])) { 199 | global $lang, $mybb; 200 | // re-evaluate comments template 201 | eval('$GLOBALS[\'numcomments\'] = "'.$GLOBALS['templates']->get($tplname).'";'); 202 | } 203 | } 204 | } 205 | 206 | function xthreads_portalsearch_cache_hack($tplpref, $tplname) { 207 | $tplcache =& $GLOBALS['templates']->cache; 208 | if(!xthreads_empty($tplpref) && isset($tplcache[$tplpref.$tplname])) { 209 | if(!isset($tplcache['backup_'.$tplname.'_backup__'])) 210 | $tplcache['backup_'.$tplname.'_backup__'] = $tplcache[$tplname]; 211 | $tplcache[$tplname] =& $tplcache[$tplpref.$tplname]; 212 | } 213 | elseif(isset($tplcache['backup_'.$tplname.'_backup__'])) 214 | $tplcache[$tplname] =& $tplcache['backup_'.$tplname.'_backup__']; 215 | } 216 | 217 | function xthreads_wol_patch(&$a) { 218 | global $lang, $thread_fid_map; 219 | global $forums, $threads, $posts, $attachments; 220 | $langargs = array(); 221 | $user_activity =& $a['user_activity']; 222 | $activity = $user_activity['activity']; 223 | switch($user_activity['activity']) { 224 | case 'announcements': 225 | case 'forumdisplay': 226 | case 'newthread': 227 | $fid = $user_activity['fid']; 228 | $langargs = array(get_forum_link($fid), $forums[$fid]); 229 | // TODO: special forumdisplay linkto string? 230 | break; 231 | case 'attachment': 232 | $tid = $posts[$attachments[$user_activity['aid']]]; 233 | $fid = $thread_fid_map[$tid]; 234 | $langargs = array($user_activity['aid'], $threads[$tid], get_thread_link($tid)); 235 | break; 236 | case 'showpost': 237 | $activity = 'showthread'; 238 | case 'newreply': 239 | if(!empty($user_activity['pid'])) 240 | $user_activity['tid'] = $posts[$user_activity['pid']]; 241 | // fall through 242 | case 'showthread': 243 | $fid = $thread_fid_map[$user_activity['tid']]; 244 | $langargs = array(get_thread_link($user_activity['tid']), $threads[$user_activity['tid']], ''); 245 | break; 246 | 247 | //case 'editpost': - 248 | //case 'newpoll': 249 | //case 'editpoll': 250 | //case 'showresults': 251 | //case 'vote': 252 | //case 'ratethread': - 253 | //case 'report': 254 | //case 'sendthread': 255 | 256 | /* 257 | case 'xtattachment': 258 | $a['location_name'] = $lang->sprintf($lang->xthreads_downloading_attachment, htmlspecialchars_uni($user_activity['location']), htmlspecialchars_uni($user_activity['filenamename'])); 259 | // TODO: allow custom for this too 260 | return; 261 | */ 262 | } 263 | 264 | if(!$fid) return; 265 | global $forumcache; 266 | if(!is_array($forumcache)) $forumcache = $GLOBALS['cache']->read('forums'); 267 | $wolstr =& $forumcache[$fid]['xthreads_wol_'.$activity]; 268 | if(!xthreads_empty($wolstr)) { 269 | if(empty($langargs)) 270 | $a['location_name'] = $wolstr; 271 | else { 272 | array_unshift($langargs, $wolstr); 273 | $a['location_name'] = call_user_func_array(array($lang, 'sprintf'), $langargs); 274 | } 275 | } 276 | 277 | } 278 | 279 | function xthreads_wol_patch_init(&$ua) { 280 | switch($ua['activity']) { 281 | case 'attachment': 282 | case 'newreply': 283 | case 'showthread': 284 | case 'showpost': 285 | static $done_hook = false; 286 | if(!$done_hook) { 287 | $done_hook = true; 288 | // hook in to get thread_fid_map 289 | global $db; 290 | $GLOBALS['thread_fid_map'] = array(); 291 | if($GLOBALS['mybb']->version_code >= 1500 && $GLOBALS['mybb']->version_code < 1700) 292 | $hook = ' 293 | function query($string, $hide_errors=0, $write_query=0) { 294 | static $done=false; 295 | if(!$done && !$write_query && (substr(trim($string), 0, 73) == "SELECT t.fid, t.tid, t.subject, t.visible, p.displaystyle AS threadprefix" || substr(trim($string), 0, 80) == "SELECT t.uid, t.fid, t.tid, t.subject, t.visible, p.displaystyle AS threadprefix")) { 296 | $done = true; 297 | $this->xthreads_db_wol_hook = true; 298 | } 299 | return parent::query($string, $hide_errors, $write_query); 300 | } 301 | function simple_select($table, $fields="*", $conditions="", $options=array()) { 302 | if($this->xthreads_db_wol_hook) { 303 | $this->xthreads_db_wol_hook = false; 304 | } 305 | return parent::simple_select($table, $fields, $conditions, $options); 306 | } 307 | '; 308 | else 309 | $hook = ' 310 | function simple_select($table, $fields="*", $conditions="", $options=array()) { 311 | static $done=false; 312 | if($done && $this->xthreads_db_wol_hook) { 313 | $this->xthreads_db_wol_hook = false; 314 | } 315 | if(!$done && $table == "threads" && ($fields == "fid,tid,subject,visible" || $fields == "uid, fid, tid, subject, visible, prefix") && substr($conditions, 0, 7) == "tid IN(" && empty($options)) { 316 | $done = true; 317 | $this->xthreads_db_wol_hook = true; 318 | } 319 | return parent::simple_select($table, $fields, $conditions, $options); 320 | } 321 | '; 322 | 323 | control_db($hook.' 324 | function fetch_array($query, $resulttype=1) { // 1 == MYSQL_ASSOC == MYSQLI_ASSOC == PGSQL_ASSOC 325 | if($this->xthreads_db_wol_hook) { 326 | $r = parent::fetch_array($query, $resulttype); 327 | $GLOBALS[\'thread_fid_map\'][$r[\'tid\']] = $r[\'fid\']; 328 | return $r; 329 | } 330 | return parent::fetch_array($query, $resulttype); 331 | } 332 | '); 333 | $db->xthreads_db_wol_hook = false; 334 | } 335 | break; 336 | /* case 'unknown': 337 | // TODO: the following URL isn't guaranteed as query strings may be used 338 | if(($p = strpos($ua['location'], '/xthreads_attach.php/')) !== false) { 339 | // check if really is xtattach page 340 | if(strpos($ua['location'], '.php') > $p) { 341 | // yes, user isn't sticking this as an argument 342 | // TODO: parse URL for stuff 343 | } 344 | } 345 | break; 346 | */ 347 | } 348 | } 349 | 350 | 351 | function xthreads_fix_stats() { 352 | global $cache; 353 | function &xthreads_fix_stats_read($stats, $hard) { 354 | static $fix = null; 355 | if(!isset($fix) || $hard) { 356 | $fix = array('posts' => 0, 'threads' => 0); 357 | $forums = $GLOBALS['cache']->read('forums', $hard); 358 | $q = $comma = ''; 359 | foreach($forums as &$f) 360 | if($f['xthreads_nostatcount']) { 361 | $q .= $comma.$f['fid']; 362 | $comma = ','; 363 | } 364 | 365 | // since MyBB doesn't cache forum counters, we have to query for it 366 | if($q) { 367 | global $db; 368 | $fix = $db->fetch_array($db->simple_select('forums', 'SUM(threads)+SUM(unapprovedthreads) AS threads, SUM(posts)+SUM(unapprovedposts) AS posts', 'fid IN ('.$q.')')); 369 | } 370 | } 371 | $stats['numposts'] -= $fix['posts']; 372 | $stats['numthreads'] -= $fix['threads']; 373 | return $stats; 374 | } 375 | control_object($cache, ' 376 | function read($name, $hard=false) { 377 | if($name != "stats") 378 | return parent::read($name, $hard); 379 | else 380 | return xthreads_fix_stats_read(parent::read($name, $hard), $hard); 381 | } 382 | '); 383 | } 384 | 385 | function xthreads_fix_stats_index() { 386 | if($GLOBALS['mybb']->settings['showindexstats']) 387 | xthreads_fix_stats(); 388 | } 389 | function xthreads_fix_stats_portal() { 390 | if($GLOBALS['mybb']->settings['portal_showstats']) 391 | xthreads_fix_stats(); 392 | } 393 | function xthreads_fix_stats_stats() { 394 | xthreads_fix_stats(); 395 | // re-read the stats 396 | $GLOBALS['stats'] = $GLOBALS['cache']->read('stats'); 397 | } 398 | function xthreads_fix_stats_usercp() { 399 | if(empty($GLOBALS['mybb']->input['action'])) 400 | xthreads_fix_stats(); 401 | } 402 | 403 | 404 | 405 | // modified version of xthreads_breadcrumb_hack() 406 | // because printthread.php just has to do things differently... 407 | function xthreads_breadcrumb_hack_printthread() { 408 | global $pforumcache; 409 | //if(!is_array($pforumcache)) { 410 | // need to override because we want the 'xthreads_hidebreadcrumb' field 411 | global $forum, $db, $fid; 412 | $parlist = build_parent_list($fid, 'fid', 'OR', $forum['parentlist']); 413 | $query = $db->simple_select('forums', 'name, fid, pid, xthreads_hidebreadcrumb', $parlist, array('order_by' => 'pid, disporder')); 414 | while($forumnav = $db->fetch_array($query)) { 415 | $pforumcache[$forumnav['pid']][$forumnav['fid']] = $forumnav; 416 | } 417 | unset($forumnav, $forum, $fid); // unsetting the global references 418 | //} 419 | if(!is_array($pforumcache[0])) return; 420 | 421 | // do the same as xthreads_breadcrumb_hack() but in reverse 422 | foreach($pforumcache[0] as &$pforum) { // will only ever loop once 423 | if(!empty($pforum['pid'])) continue; // paranoia 424 | 425 | // firstly, skip any hidden top-level parents 426 | $prevforum =& $pforum; 427 | while($prevforum && $prevforum['xthreads_hidebreadcrumb'] && !empty($pforumcache[$prevforum['fid']])) 428 | $prevforum =& xthreads_get_array_first($pforumcache[$prevforum['fid']]); 429 | 430 | if($prevforum) { 431 | if(!empty($prevforum['pid'])) { 432 | $prevforum['pid'] = 0; 433 | $pforum = $prevforum; 434 | } 435 | 436 | $forum = null; 437 | if(!empty($pforumcache[$prevforum['fid']])) 438 | $forum =& xthreads_get_array_first($pforumcache[$prevforum['fid']]); 439 | while($forum) { 440 | if(!$forum['xthreads_hidebreadcrumb']) { 441 | // rewrite parent fid (won't actually change if there's no hidden breadcrumbs in-between) 442 | $forum['pid'] = $prevforum['fid']; 443 | $pforumcache[$forum['pid']] = array($forum['fid'] => $forum); 444 | $prevforum =& $forum; 445 | } 446 | if(!$pforumcache[$forum['fid']]) { 447 | // we always display the active breadcrumb, so set this if hidden 448 | if($forum['xthreads_hidebreadcrumb']) { 449 | $forum['pid'] = $prevforum['fid']; 450 | $pforumcache[$forum['pid']] = array($forum['fid'] => $forum); 451 | } 452 | break; 453 | } 454 | $forum =& xthreads_get_array_first($pforumcache[$forum['fid']]); 455 | } 456 | } 457 | } 458 | } 459 | 460 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_modupdhooks.php: -------------------------------------------------------------------------------- 1 | input['deletedraft']) || !is_array($mybb->input['deletedraft'])) return; 16 | // unfortunately, we need to grab a list of all the valid tids 17 | $tidin = ''; 18 | foreach($mybb->input['deletedraft'] as $id => &$val) { 19 | if($val == 'thread') 20 | $tidin .= ($tidin===''?'':',') . (int)$id; 21 | } 22 | 23 | if(!$tidin) return; 24 | $query = $db->simple_select('threads', 'tid', 'tid IN ('.$tidin.') AND visible=-2 AND uid='.$mybb->user['uid']); 25 | $tidin = ''; 26 | while($tid = $db->fetch_field($query, 'tid')) 27 | $tidin .= ($tidin==''?'':',') . $tid; 28 | $db->free_result($query); 29 | 30 | if(!$tidin) return; 31 | $db->delete_query('threadfields_data', 'tid IN ('.$tidin.')'); 32 | require_once MYBB_ROOT.'inc/xthreads/xt_modupdhooks.php'; 33 | xthreads_rm_attach_query('tid IN ('.$tidin.')'); 34 | } 35 | 36 | function xthreads_delete_thread($tid) { 37 | global $db; 38 | // awesome thing about this is that it will delete threadfields even if the thread was moved to a different forum 39 | $db->delete_query('threadfields_data', 'tid='.$tid); 40 | 41 | xthreads_rm_attach_query('tid='.$tid); 42 | } 43 | 44 | function xthreads_copy_thread(&$a) { 45 | control_db(' 46 | function insert_query($table, $array) { 47 | static $done = false; 48 | $ret = parent::insert_query($table, $array); 49 | if(!$done) { 50 | $done = true; 51 | xthreads_duplicate_threadfield_data($this->xthreads_copy_thread_tid, $ret); 52 | } 53 | return $ret; 54 | } 55 | '); 56 | $GLOBALS['db']->xthreads_copy_thread_tid = $a['tid']; 57 | } 58 | /* function xthreads_split_posts(&$a) { 59 | // impossible to get the new tid!! 60 | // or maybe there's a way... 61 | } */ 62 | 63 | function xthreads_duplicate_threadfield_data($tid_old, $tid_new) { 64 | global $db, $mybb; 65 | @ignore_user_abort(true); // not really that good, since cancelling elsewhere will break transaction, but, well, copies might be slow, so... 66 | $tf = $db->fetch_array($db->simple_select('threadfields_data', '*', 'tid='.$tid_old)); 67 | if(empty($tf)) { // no threadfields set for this thread -> nothing to duplicate 68 | @ignore_user_abort(false); 69 | return; 70 | } 71 | $tf['tid'] = $tid_new; 72 | 73 | // copy xtattachments over 74 | $query = $db->simple_select('xtattachments', '*', 'tid='.$tid_old); 75 | while($xta = $db->fetch_array($query)) { 76 | // we have a file we need to duplicate 77 | $xta['tid'] = $tid_new; 78 | $oldname = xthreads_get_attach_path($xta); 79 | $oldpath = dirname($oldname).'/'; 80 | $xta['attachname'] = substr(md5(uniqid(mt_rand(), true).substr($mybb->post_code, 16)), 12, 8).substr($xta['attachname'], 8); 81 | unset($xta['aid']); 82 | $tf[$xta['field']] = $xta['aid'] = xthreads_db_insert('xtattachments', $xta); 83 | 84 | $newname = xthreads_get_attach_path($xta); 85 | $newpath = dirname($newname).'/'; 86 | 87 | $oldfpref = basename(substr($oldname, 0, -6)); 88 | $newfpref = basename(substr($newname, 0, -6)); 89 | if($thumbs = @glob($oldpath.$oldfpref.'*.thumb')) { 90 | foreach($thumbs as &$thumb) { 91 | $thumb = basename($thumb); 92 | xthreads_hardlink_file($oldpath.$thumb, $newpath.str_replace($oldfpref, $newfpref, $thumb)); 93 | } 94 | } 95 | xthreads_hardlink_file($oldname, $newname); 96 | } 97 | 98 | xthreads_db_insert('threadfields_data', $tf); 99 | @ignore_user_abort(false); 100 | } 101 | 102 | 103 | function xthreads_rm_attach_query($where) { 104 | global $db; 105 | $has_attach = $successes = 0; 106 | $query = $db->simple_select('xtattachments', 'aid,indir,attachname', $where); 107 | $rmaid = ''; 108 | while($xta = $db->fetch_array($query)) { 109 | if(xthreads_rm_attach_fs($xta)) { 110 | if($successes) $rmaid .= ','; 111 | $rmaid .= $xta['aid']; 112 | ++$successes; 113 | } 114 | ++$has_attach; 115 | } 116 | $db->free_result($query); 117 | if($has_attach) { 118 | if($has_attach == $successes) 119 | $db->delete_query('xtattachments', $where); 120 | elseif($successes) 121 | $db->delete_query('xtattachments', 'aid IN ('.$rmaid.')'); 122 | } 123 | return $successes; 124 | } 125 | 126 | // will try to create a hardlink/copy of a file 127 | function xthreads_hardlink_file($src, $dest) { 128 | if($src == $dest) return false; 129 | if(@link($src, $dest)) return true; 130 | if(DIRECTORY_SEPARATOR == '\\' && @ini_get('safe_mode') != 'On') { 131 | $allow_exec = true; 132 | // check if exec() is allowed 133 | if(($func_blacklist = @ini_get('suhosin.executor.func.blacklist')) && strpos(','.$func_blacklist.',', ',exec,') !== false) 134 | $allow_exec = false; 135 | if(($func_blacklist = @ini_get('disable_functions')) && strpos(','.$func_blacklist.',', ',exec,') !== false) 136 | $allow_exec = false; 137 | 138 | if($allow_exec) { 139 | // try mklink (Windows Vista / Server 2008 and later only) 140 | // assuming mklink refers to the correct executable is a little dangerous perhaps, but it should work 141 | @unlink($dest); // mklink won't overwrite 142 | @exec('mklink /H '.escapeshellarg(str_replace('/', '\\', $src)).' '.escapeshellarg(str_replace('/', '\\', $dest)).' >NUL 2>NUL', $null, $ret); 143 | if($ret==0 && @file_exists($dest)) return true; 144 | } 145 | } 146 | // fail, resort to copy 147 | return @copy($src, $dest); 148 | } 149 | 150 | function xthreads_moderation() { 151 | // try to hook into custom moderation 152 | // lovely MyBB provides no custom moderation hook, what gives? 153 | $modactions = array( 154 | 'openclosethread', 155 | 'stick', 156 | 'removeredirects', 157 | 'deletethread', 158 | 'do_deletethread', 159 | 'deletepoll', 160 | 'do_deletepoll', 161 | 'approvethread', 162 | 'unapprovethread', 163 | 'deleteposts', 164 | 'do_deleteposts', 165 | 'mergeposts', 166 | 'do_mergeposts', 167 | 'move', 168 | 'do_move', 169 | 'threadnotes', 170 | 'do_threadnotes', 171 | 'getip', 172 | 'merge', 173 | 'do_merge', 174 | 'split', 175 | 'do_split', 176 | 'removesubscriptions', 177 | 'multideletethreads', 178 | 'do_multideletethreads', 179 | 'multiopenthreads', 180 | 'multiclosethreads', 181 | 'multiapprovethreads', 182 | 'multiunapprovethreads', 183 | 'multistickthreads', 184 | 'multiunstickthreads', 185 | 'multimovethreads', 186 | 'do_multimovethreads', 187 | 'multideleteposts', 188 | 'do_multideleteposts', 189 | 'multimergeposts', 190 | 'do_multimergeposts', 191 | 'multisplitposts', 192 | 'do_multisplitposts', 193 | 'multiapproveposts', 194 | 'multiunapproveposts', 195 | ); 196 | if($GLOBALS['mybb']->version_code >= 1500) { 197 | $modactions[] = 'cancel_delayedmoderation'; 198 | $modactions[] = 'do_delayedmoderation'; 199 | $modactions[] = 'delayedmoderation'; 200 | } 201 | if(in_array($GLOBALS['mybb']->input['action'], $modactions)) return; 202 | 203 | // we are probably now looking at custom moderation - let's get ourselves a hook into the system 204 | control_db(' 205 | function simple_select($table, $fields="*", $conditions="", $options=array()) { 206 | static $done=false; 207 | if(!$done && $table == "modtools" && substr($conditions, 0, 4) == "tid=" && empty($options)) { 208 | $done = true; 209 | xthreads_moderation_custom(); 210 | } 211 | return parent::simple_select($table, $fields, $conditions, $options); 212 | } 213 | '); 214 | } 215 | 216 | function xthreads_moderation_custom() { 217 | //if($tool['type'] != 't') return; 218 | if(!isset($GLOBALS['custommod']) || !is_object($GLOBALS['custommod'])) return; 219 | 220 | control_object($GLOBALS['custommod'], ' 221 | function execute_thread_moderation($thread_options=array(), $tids=array()) { 222 | if($thread_options[\'deletethread\'] != 1) 223 | xthreads_moderation_custom_do($tids, $thread_options[\'edit_threadfields\']); 224 | return parent::execute_thread_moderation($thread_options, $tids); 225 | } 226 | '); 227 | 228 | // this function is executed before copy thread (yay!) 229 | function xthreads_moderation_custom_do(&$tids, $editstr) { 230 | if(!$editstr) return; 231 | $edits = array(); 232 | 233 | // caching stuff 234 | static $threadfields = null; 235 | if(!isset($threadfields)) 236 | $threadfields = xthreads_gettfcache(); // grab all threadfields 237 | 238 | require_once MYBB_ROOT.'inc/xthreads/xt_phptpl_lib.php'; 239 | foreach(explode("\n", str_replace("{\n}", "\r", str_replace("\r",'',$editstr))) as $editline) { 240 | $editline = trim(str_replace("\r", "\n", $editline)); 241 | $kv = explode('=', $editline, 2); 242 | if(!isset($kv[1])) continue; 243 | 244 | // don't allow editing of file fields 245 | if(!isset($threadfields[$kv[0]]) || $threadfields[$kv[0]]['inputtype'] == XTHREADS_INPUT_FILE) continue; 246 | // we don't do much validation here as we trust admins, right? 247 | 248 | // this is just a prelim check (speed optimisation) - we'll need to check this again after evaluating conditionals 249 | $upperv = strtoupper($kv[1]); 250 | if(($upperv === '' || $upperv == 'NULL' || $upperv == 'NUL') && $threadfields[$kv[0]]['datatype'] != XTHREADS_DATATYPE_TEXT) 251 | $edits[$kv[0]] = null; 252 | else { 253 | $edits[$kv[0]] = $kv[1]; 254 | xthreads_sanitize_eval($edits[$kv[0]], array('VALUE'=>null, 'TID'=>null)); 255 | } 256 | } 257 | if(empty($edits)) return; 258 | $modfields = array_keys($edits); 259 | 260 | global $db; 261 | $query = $db->query(' 262 | SELECT t.tid, tfd.`'.implode('`, tfd.`', $modfields).'` 263 | FROM '.TABLE_PREFIX.'threads t 264 | LEFT JOIN '.TABLE_PREFIX.'threadfields_data tfd ON t.tid=tfd.tid 265 | WHERE t.tid IN ('.implode(',', $tids).') 266 | '); 267 | //$query = $db->simple_select('threadfields_data', 'tid,`'.implode('`,`', $modfields).'`', 'tid IN ('.implode(',', $tids).')'); 268 | while($thread = $db->fetch_array($query)) { 269 | $updates = array(); 270 | foreach($edits as $n => $v) { 271 | if($v !== null) { 272 | // TODO: allowing conditionals direct access to multivals? 273 | $v = trim(eval_str($v, array('VALUE' => $thread[$n], 'TID' => $thread['tid']))); 274 | if($threadfields[$n]['datatype'] != XTHREADS_DATATYPE_TEXT) { 275 | $upperv = strtoupper($v); 276 | if($upperv == '' || $upperv == 'NULL' || $upperv == 'NUL') 277 | $v = null; 278 | // TODO: intval/floatval here? 279 | } 280 | } 281 | if($v !== $thread[$n]) { 282 | // we'll do some basic validation for multival fields 283 | if(!xthreads_empty($threadfields[$n]['multival'])) { 284 | $d = "\n"; 285 | if($threadfields[$n]['inputtype'] == XTHREADS_INPUT_TEXT) 286 | $d = ','; 287 | $v = array_unique(array_map('trim', explode($d, str_replace("\r", '', $v)))); 288 | foreach($v as $key => &$val) 289 | if(xthreads_empty($val)) 290 | unset($v[$key]); 291 | $v = implode($d, $v); 292 | } 293 | $updates[$n] = $v; 294 | } 295 | } 296 | if(!empty($updates)) { 297 | xthreads_db_update_replace('threadfields_data', $updates, 'tid', $thread['tid']); 298 | } 299 | } 300 | $db->free_result($query); 301 | } 302 | } 303 | 304 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_phptpl_lib.php: -------------------------------------------------------------------------------- 1 | = 7 10 | // note, this will break parsers from PHP < 5.3 11 | function xthreads_phptpl_parsetpl(&$ourtpl, $fields=array(), $evalvarname=null) 12 | { 13 | $GLOBALS['__phptpl_if'] = array(); 14 | $repl = array( 15 | '#\<((?:else)?if\s+(.*?)\s+then|else\s*/?|/if)\>#si' => function($m) use($fields) { 16 | return xthreads_phptpl_if($m[1], isset($m[2]) ? _xthreads_phptpl_expr_parse2($m[2], $fields) : ''); 17 | }, 18 | '#\#i' => function($m) { 19 | return '".'.$m[1].'("'; 20 | }, 21 | '#\#i' => function() { 22 | return '")."'; 23 | }, 24 | //'#\#i' => function($m) {return $GLOBALS['templates']->get($m[1]);}, 25 | '#\<\?=(.*?)\?\>#s' => function($m) use($fields) { 26 | return '".strval('._xthreads_phptpl_expr_parse2($m[1], $fields).')."'; 27 | }, 28 | '#\(.*?)\#i' => function($m) use($fields) { 29 | return '".(($GLOBALS["tplvars"][\''.$m[1].'\'] = ('._xthreads_phptpl_expr_parse2($m[2], $fields).'))?"":"")."'; 30 | }, 31 | ); 32 | 33 | if($evalvarname) { 34 | $repl['#\#si'] = function($m) use($fields, $evalvarname) { 35 | return '"; while('._xthreads_phptpl_expr_parse2($m[1], $fields).') { $'.$evalvarname.'.="'; 36 | }; 37 | $repl['#\#si'] = function($m) use($fields, $evalvarname) { 38 | return '"; foreach('._xthreads_phptpl_expr_parse2($m[1], $fields).' as $__key => $__value) { $'.$evalvarname.'.="'; 39 | }; 40 | $repl['#\#si'] = function($m) use($fields, $evalvarname) { 41 | return '"; for($__iter=0; $__iter < '._xthreads_phptpl_expr_parse2($m[1], $fields).'; ++$__iter) { $'.$evalvarname.'.="'; 42 | }; 43 | $repl['#\#i'] = function($m) use($evalvarname) { 44 | return '"; } $'.$evalvarname.'.="'; 45 | }; 46 | } 47 | 48 | if(xthreads_allow_php()) { 49 | $repl['#\<\?(?:php|\s).+?(\?\>)#s'] = function($m) use($fields) { 50 | return xthreads_phptpl_evalphp(_xthreads_phptpl_expr_parse2($m[0], $fields), $m[1]); 51 | }; 52 | } 53 | $ourtpl = preg_replace_callback_array($repl, $ourtpl); 54 | } 55 | } else { 56 | function xthreads_phptpl_parsetpl(&$ourtpl, $fields=array(), $evalvarname=null) 57 | { 58 | $GLOBALS['__phptpl_if'] = array(); 59 | if(defined('HHVM_VERSION')) 60 | $fields_var = var_export($fields, true); 61 | else 62 | $fields_var = '$fields'; 63 | $find = array( 64 | '#\<((?:else)?if\s+(.*?)\s+then|else\s*/?|/if)\>#sie', // note that this relies on preg_replace working in a forward order 65 | '#\#i', 66 | '#\#i', 67 | //'#\#i', 68 | '#\<\?=(.*?)\?\>#se', 69 | '#\(.*?)\#ie', 70 | ); 71 | $repl = array( 72 | 'xthreads_phptpl_if(\'$1\', _xthreads_phptpl_expr_parse(\'$2\', '.$fields_var.'))', 73 | '".$1("', 74 | '")."', 75 | //'".eval("return \"".$GLOBALS[\'templates\']->get(\'$1\')."\";")."', 76 | '\'".strval(\'._xthreads_phptpl_expr_parse(\'$1\', '.$fields_var.').\')."\'', 77 | '\'".(($GLOBALS["tplvars"]["$1"] = (\'._xthreads_phptpl_expr_parse(\'$2\', '.$fields_var.').\'))?"":"")."\'', 78 | ); 79 | 80 | if($evalvarname) { 81 | $find[] = '#\#sie'; 82 | $repl[] = '\'"; while(\'._xthreads_phptpl_expr_parse(\'$1\', '.$fields_var.').\') { $'.$evalvarname.'.="\''; 83 | 84 | $find[] = '#\#sie'; 85 | $repl[] = '\'"; foreach(\'._xthreads_phptpl_expr_parse(\'$1\', '.$fields_var.').\' as $__key => $__value) { $'.$evalvarname.'.="\''; 86 | 87 | $find[] = '#\#sie'; 88 | $repl[] = '\'"; for($__iter=0; $__iter < \'._xthreads_phptpl_expr_parse(\'$1\', '.$fields_var.').\'; ++$__iter) { $'.$evalvarname.'.="\''; 89 | 90 | $find[] = '#\#i'; 91 | $repl[] = '"; } $'.$evalvarname.'.="'; 92 | } 93 | 94 | if(xthreads_allow_php()) { 95 | $find[] = '#\<\?(?:php|\s).+?(\?\>)#se'; 96 | $repl[] = 'xthreads_phptpl_evalphp(_xthreads_phptpl_expr_parse(\'$0\', '.$fields_var.'), \'$1\')'; 97 | } 98 | $ourtpl = preg_replace($find, $repl, $ourtpl); 99 | } 100 | 101 | } 102 | 103 | 104 | function xthreads_phptpl_if($s, $e) 105 | { 106 | if($s[0] == '/') { 107 | // end if tag 108 | $last = array_pop($GLOBALS['__phptpl_if']); 109 | $suf = str_repeat(')', (int)substr($last, 1)); 110 | if($last[0] == 'i') 111 | $suf = ':""'.$suf; 112 | return '"'.$suf.')."'; 113 | } else { 114 | $s = strtolower(substr($s, 0, strpos($s, ' '))); 115 | if($s == 'if') { 116 | $GLOBALS['__phptpl_if'][] = 'i0'; 117 | return '".(('.$e.')?"'; 118 | } elseif($s == 'elseif') { 119 | $last = array_pop($GLOBALS['__phptpl_if']); 120 | $last = 'i'.((int)substr($last, 1) + 1); 121 | $GLOBALS['__phptpl_if'][] = $last; 122 | return '":(('.$e.')?"'; 123 | } else { 124 | $last = array_pop($GLOBALS['__phptpl_if']); 125 | $last[0] = 'e'; 126 | $GLOBALS['__phptpl_if'][] = $last; 127 | return '":"'; 128 | } 129 | } 130 | } 131 | 132 | function _xthreads_phptpl_expr_parse($str, $fields=array()) { 133 | if(!$str && $str !== '0') return ''; 134 | 135 | // unescapes the slashes added by xthreads_sanitize_eval, plus addslashes() (double quote only) during preg_replace() 136 | $str = strtr($str, array('\\$' => '$', '\\\\"' => '"', '\\\\' => '\\')); 137 | 138 | return xthreads_phptpl_expr_parse($str, $fields); 139 | } 140 | // for non-eval escaped stuff 141 | function _xthreads_phptpl_expr_parse2($str, $fields=array()) { 142 | if(!$str && $str !== '0') return ''; 143 | 144 | // unescapes the slashes added by xthreads_sanitize_eval 145 | $str = strtr($str, array('\\$' => '$', '\\"' => '"', '\\\\' => '\\')); 146 | 147 | return xthreads_phptpl_expr_parse($str, $fields); 148 | } 149 | 150 | function xthreads_phptpl_expr_parse($str, $fields=array()) 151 | { 152 | // remove all single quote strings - they mess up all our plans... 153 | $strpreg = '~\'(|\\\\\\\\|.*?([^\\\\]|[^\\\\](\\\\\\\\)+))\'~s'; 154 | if(preg_match_all($strpreg, $str, $squotstr)) { 155 | $token = '\'__PHPTPL_PLACEHOLDER_'.md5(mt_rand()).'__\''; 156 | $str = preg_replace($strpreg, $token, $str); 157 | $squotstr = $squotstr[0]; 158 | } else 159 | $squotstr = null; 160 | 161 | // globalise all variables; conveniently will filter out stuff like {VALUE$1} 162 | $str = preg_replace('~\$([a-zA-Z_][a-zA-Z_0-9]*)~', '$GLOBALS[\'$1\']', $str); 163 | // won't pick up double variable syntax, eg $$var, or complex variable syntax, eg ${$var} 164 | 165 | // fix variables in double-quote and heredoc strings 166 | $strpreg = '~(")(|\\\\\\\\|.*?([^\\\\]|[^\\\\](\\\\\\\\)+))\\1~s'; 167 | if(xthreads_allow_php()) { 168 | $str = preg_replace_callback(array($strpreg, "~\<\<\<([a-zA-Z_][a-zA-Z_0-9]*)\r?\n.*?\r?\n\\1;?\r?\n~s"), function($match) { 169 | return preg_replace('~(?|\\:\\:)[a-zA-Z_][a-zA-Z_0-9]*|\\[\s*([\'"])?[ a-zA-Z_ 0-9]+\\4\s*\\])*)~', '{$0}', $match[0]); 170 | }, $str); 171 | } else { 172 | $str = preg_replace_callback($strpreg, function($match) { 173 | return preg_replace('~\$GLOBALS\\[\\s*\'([a-zA-Z_][a-zA-Z_0-9]*)\'\\s*\\]~', '$GLOBALS[$1]', $match[0]); 174 | }, $str); 175 | } 176 | 177 | if(!empty($fields)) 178 | // we need to parse {VALUE} tokens here, as they need to be parsed a bit differently, and so that they're checked for safe expressions 179 | $str = xthreads_phptpl_parse_fields($str, $fields, false); 180 | 181 | if(!empty($squotstr)) 182 | $str = xthreads_our_str_replace($token, $squotstr, $str); 183 | 184 | if(xthreads_allow_php() || xthreads_phptpl_is_safe_expression($str)) 185 | return $str; 186 | else 187 | return 'false'; 188 | } 189 | 190 | // also disables heredoc + array/object typecasting + braces in double-quoted strings 191 | function xthreads_phptpl_is_safe_expression($s) 192 | { 193 | 194 | // remove all strings 195 | $string_preg = '([\'"])(|\\\\\\\\|.*?([^\\\\]|[^\\\\](\\\\\\\\)+))\\1'; 196 | preg_match_all('~'.$string_preg.'~s', $s, $strings, PREG_SET_ORDER); 197 | 198 | // check double-quote strings 199 | foreach($strings as &$strdef) { 200 | if($strdef[1] == '"') { 201 | // check $strdef[2] 202 | // we'll only do a simple check 203 | if(strpos($strdef[2], '{') !== false) return false; 204 | } 205 | } 206 | 207 | // block string function calls, e.g. 'exec'() 208 | if(preg_match('~'.$string_preg.'\s*\(~', $s)) return false; 209 | 210 | // remove safe "equal" expressions and closed comments 211 | // use '^' character as substitution to try to prevent possible 'some==badfunc()' type exploits 212 | $check = strtr(preg_replace(array('~'.$string_preg.'~s', '~/\\*.*?\\*/~s'), ' ', $s), array('>=' => '^', '<=' => '^', '=>' => '^', '===' => '^', '!==' => '^', '==' => '^', '!=' => '^')); 213 | 214 | // block certain characters + operators 215 | if(preg_match('~([+\-/]{2}|[`#="\']|/\*|\<{3}|\?\>|\(array\)|\(object\))~i', $check)) return false; 216 | // blocking hanging quotes will actually also block an exploit 217 | // eg $a = "".strval(1).").".whatever.(".strval(1).").""; 218 | 219 | // block new, exit/die, include/require + constants 220 | if(preg_match('~(?\s*|[\\\\a-zA-Z0-9_]+\s*\:\:\s*)?[\\\\a-zA-Z0-9_]+)\s*\(~', $check, $matches); 228 | $allowed_funcs = xthreads_phptpl_get_allowed_funcs(); 229 | foreach($matches[1] as &$func) { 230 | if(!isset($allowed_funcs[strtr($func, array(' '=>'',"\n"=>'',"\r"=>'',"\t"=>''))])) return false; 231 | } 232 | 233 | return true; 234 | } 235 | 236 | function &xthreads_phptpl_get_allowed_funcs() 237 | { 238 | static $allowed_funcs = null; 239 | if(!isset($allowed_funcs)) { 240 | $allowed_funcs = array_flip(explode("\n", str_replace("\r", '', @file_get_contents(MYBB_ROOT.'inc/xthreads/phptpl_allowed_funcs.txt')))); 241 | } 242 | // hack to allow us to dynamically add more allowable functions (for image thumbnail processing) 243 | if(!empty($GLOBALS['phptpl_additional_functions'])) 244 | return array_merge($allowed_funcs, array_flip($GLOBALS['phptpl_additional_functions'])); 245 | else 246 | return $allowed_funcs; 247 | } 248 | 249 | function xthreads_phptpl_evalphp($str, $end) 250 | { 251 | return '".eval(\'ob_start(); ?>' 252 | .strtr($str, array('\'' => '\\\'', '\\' => '\\\\')) 253 | .($end?'':'?>').' $r) { 287 | if(isset($r)) { 288 | $tr['{'.$f.'}'] = ($in_string ? $r : '("'.$r.'")'); 289 | } else { 290 | $ptr[] = '~\\{('.preg_quote($f, '~').')((?:-\>|\[)[^}]+?)?\\}~'; 291 | } 292 | if($f == 'RAWVALUE') $do_value_repl = true; 293 | } 294 | $str_start = ($in_string?'{':' '); 295 | $str_end = ($in_string?'}':' '); 296 | if($do_value_repl) $s = preg_replace('~\{((?:RAW)?VALUE)\\\\?\$(\d+)\}~', $str_start.'$vars[\'$1$\'][$2]'.$str_end, $s); 297 | if(!empty($tr)) $s = strtr($s, $tr); 298 | if(!empty($ptr)) { 299 | $s = preg_replace_callback($ptr, function($match) use($in_string) { 300 | if(!isset($match[2])) $match[2] = ''; 301 | if($in_string) 302 | return '{$vars[\''.$match[1].'\']'._xthreads_phptpl_expr_parse2($match[2]).'}'; 303 | else 304 | return ' $vars[\''.$match[1].'\']'._xthreads_phptpl_expr_parse2($match[2]).' '; 305 | }, $s); 306 | } 307 | // careful with _xthreads_phptpl_expr_parse() call above - we avoid infinite looping by not supplying $fields 308 | // although _xthreads_phptpl_expr_parse should always be called outside string context, the above is safe because the user cannot put in a '}' character at all - that is, achieving something like {$vars['VAL'][0]}".whatever."{$var} should be impossible 309 | // an issue with the above, is that it's impossible to do something like {VAR[{VALUE}]} because variables are all auto-global'd... (but even if not, the above isn't guaranteed to work anyway, since the end token '}' might match early, eg {VAR[) 310 | } 311 | return $s; 312 | } 313 | 314 | 315 | // sanitises string $s so that we can directly eval it during "run-time" rather than performing sanitisation there 316 | function xthreads_sanitize_eval(&$s, $fields=array(), $evalvarname=null) { 317 | if(xthreads_empty($s)) { 318 | $s = ''; 319 | return; 320 | } 321 | // the following won't work properly with array indexes which have non-alphanumeric and underscore chars; also, it won't do ${var} syntax 322 | // also, damn PHP's magic quotes for preg_replace - but it does assist with backslash fun!!! 323 | $s = preg_replace_callback('~\\{\\\\\\$([a-zA-Z_][a-zA-Z_0-9]*)(|(?:-\>|\[)[^}]+?)\\}~', function($m) { 324 | return '{$GLOBALS[\''.$m[1].'\']'._xthreads_phptpl_expr_parse2($m[2]).'}'; 325 | }, preg_replace( 326 | array( 327 | '~\{\\\\\$forumurl\\\\\$\}~i', 328 | '~\{\\\\\$forumurl\?\}~i', 329 | '~\{\\\\\$threadurl\\\\\$\}~i', 330 | '~\{\\\\\$threadurl\?\}~i' 331 | ), array( 332 | '{$GLOBALS[\'forumurl\']}', 333 | '{$GLOBALS[\'forumurl_q\']}', 334 | '{$GLOBALS[\'threadurl\']}', 335 | '{$GLOBALS[\'threadurl_q\']}', 336 | ), strtr($s, array('\\' => '\\\\', '$' => '\\$', '"' => '\\"')) 337 | )); 338 | 339 | // replace conditionals 340 | xthreads_phptpl_parsetpl($s, $fields, $evalvarname); 341 | 342 | // replace value tokens at the end 343 | if(!empty($fields)) 344 | $s = xthreads_phptpl_parse_fields($s, $fields, true); 345 | } 346 | 347 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_sthreadhooks.php: -------------------------------------------------------------------------------- 1 | &$val) { 15 | $tf =& $threadfield_cache[$k]; 16 | if(isset($tf['hidefield']) && ($tf['hidefield'] & XTHREADS_HIDE_THREAD)) continue; 17 | if($tf['inputtype'] == XTHREADS_INPUT_FILE) 18 | $value =& $val['value']; 19 | else 20 | $value =& $val; 21 | $title = htmlspecialchars_uni($tf['title']); 22 | $bgcolor = alt_trow(); 23 | eval('$threadfields_display_rows .= "'.$templates->get('showthread_threadfield_row').'";'); 24 | } unset($value); 25 | if($threadfields_display_rows) 26 | eval('$threadfields_display = "'.$templates->get('showthread_threadfields').'";'); 27 | 28 | global $mybb; 29 | /* 30 | if($mybb->input['action'] == 'xtnext' || $mybb->input['action'] == 'xtprev') { 31 | global $db; 32 | $add_join = false; 33 | 34 | $nf = 'lastpost'; 35 | switch($mybb->input['order']) { 36 | case 'subject': 37 | case 'replies': 38 | case 'views': 39 | $nf = $mybb->input['order']; 40 | break; 41 | 42 | case 'starter': $nf = 'username'; break; 43 | case 'started': $nf = 'dateline'; break; 44 | case 'rating': // this is f***ing slow, but then, that's the best MyBB can do 45 | unset($nf); 46 | $nextfield = 'IF(t.numratings=0, 0, t.totalratings / t.numratings)'; 47 | $curval = ($thread['numratings'] ? $thread['totalratings'] / $thread['numratings'] : 0); 48 | break; 49 | 50 | // more XThreads sort options 51 | // TODO: prefix, icon 52 | 53 | case 'lastposter': 54 | case 'numratings': 55 | case 'attachmentcount': 56 | $nf = $mybb->input['order']; 57 | break; 58 | 59 | 60 | default: 61 | // TODO: threadfields sorting 62 | if(substr($mybb->input['order'], 0, 3) == 'tf_') { 63 | $add_join = true; 64 | 65 | } elseif(substr($mybb->input['order'], 0, 4) == 'tfa_') { 66 | $add_join = true; 67 | 68 | } 69 | break; 70 | 71 | } 72 | if(isset($nf)) { 73 | if($add_join) { 74 | $nextfield = 'tfd.`'.$nf.'`'; 75 | $curval = $threadfields[$nf]; 76 | } else { 77 | $nextfield = 't.'.$nf; 78 | $curval = $thread[$nf]; 79 | } 80 | } 81 | if(is_string($curval)) 82 | $curval = '"'.$db->escape_string($curval).'"'; 83 | $cond = $nextfield.($mybb->input['action']=='xtprev' ? '<':'>').$curval; 84 | 85 | // TODO: additional filtering 86 | 87 | $cond .= ' AND t.fid='.$thread['fid'].' AND t.visible=1 AND t.closed NOT LIKE "moved|%"'; 88 | $order_dir = ($mybb->input['action'] == 'xtprev' ? 'desc':'asc'); 89 | 90 | $join = ''; 91 | if($add_join) 92 | $join = 'LEFT JOIN '.$db->table_prefix.'threadfields_data tfd ON t.tid=tfd.tid'; 93 | $query = $db->query(' 94 | SELECT t.tid FROM '.$db->table_prefix.'threads t 95 | '.$join.' 96 | WHERE '.$cond.' 97 | ORDER BY '.$nextfield.' '.$order_dir.', t.tid '.$order_dir.' 98 | LIMIT 1 99 | '); 100 | $nexttid = $db->fetch_field($query, 'tid'); 101 | if(!$nexttid) 102 | error($GLOBALS['lang']->error_nonextoldest); 103 | 104 | header('Location: '.htmlspecialchars_decode(get_thread_link($nexttid))); 105 | exit; 106 | } 107 | */ 108 | 109 | // fix screwy jeditable default 110 | if($mybb->version_code >= 1800) { 111 | $GLOBALS['header'] .= ''; 115 | } 116 | } 117 | 118 | function xthreads_showthread_firstpost() { 119 | global $mybb, $templatelist; 120 | // don't do this if using threaded mode 121 | if(isset($mybb->input['mode'])) 122 | $threaded = ($mybb->input['mode'] == 'threaded'); 123 | elseif(!empty($mybb->user['threadmode'])) 124 | $threaded = ($mybb->user['threadmode'] == 'threaded'); 125 | else 126 | $threaded = ($mybb->settings['threadusenetstyle'] == 1); 127 | if($threaded) return; 128 | 129 | global $db; 130 | xthreads_firstpost_tpl_preload(); 131 | $templatelist .= ',showthread_noreplies'; 132 | $GLOBALS['first_post'] = ''; 133 | 134 | function xthreads_tpl_firstpost_moveout() { 135 | global $posts; 136 | static $done = false; 137 | if($done) return; 138 | $done = true; 139 | $GLOBALS['first_post'] = $posts; 140 | $posts = ''; 141 | // uh... what's this next line here for again? 142 | //$GLOBALS['plugins']->remove_hook('showthread_start', 'xthreads_showthread_firstpost_hack'); 143 | } 144 | function xthreads_tpl_firstpost_noreplies() { 145 | global $posts; 146 | // execute this in case there's only one post in the thread 147 | xthreads_tpl_firstpost_moveout(); 148 | if(!$posts) { 149 | eval('$posts = "'.$GLOBALS['templates']->get('showthread_noreplies').'";'); 150 | } 151 | } 152 | function xthreads_tpl_postbitrestore() { 153 | global $templates, $xthreads_postbit_templates, $page; 154 | foreach($xthreads_postbit_templates as &$t) { 155 | $pbname = substr($t, 7); 156 | if(!$pbname) $pbname = ''; 157 | if(isset($templates->cache['postbit_first'.$pbname]) && !isset($templates->non_existant_templates['postbit_first'.$pbname])) { 158 | $templates->cache[$t] = $templates->cache['backup_postbit'.$pbname.'_backup__']; 159 | } 160 | } 161 | // whilst we're here, add the necessary plugin hook 162 | $GLOBALS['plugins']->add_hook('postbit', 'xthreads_tpl_firstpost_moveout'); 163 | $GLOBALS['plugins']->add_hook('showthread_linear', 'xthreads_tpl_firstpost_noreplies'); 164 | 165 | // don't forget to fix the postcounter too! 166 | if($page > 1) { 167 | global $mybb; 168 | if(!$mybb->settings['postsperpage']) 169 | $mybb->settings['postsperpage'] = 20; 170 | $GLOBALS['postcounter'] = $mybb->settings['postsperpage']*($page-1); 171 | } 172 | } 173 | 174 | function xthreads_showthread_firstpost_hack() { 175 | if(xthreads_tpl_postbithack()) { 176 | // *sigh* no other way to do this other than to hack the templates object again... >_> 177 | control_object($GLOBALS['templates'], ' 178 | function get($title, $eslashes=1, $htmlcomments=1) { 179 | static $done=false; 180 | if(!$done && ($title == \'postbit\' || $title == \'postbit_classic\')) { 181 | $done = true; 182 | $r = parent::get($title, $eslashes, $htmlcomments); 183 | xthreads_tpl_postbitrestore(); 184 | //return str_replace(\'{$post_extra_style}\', \'border-top-width: 0;\', $r); 185 | return \'".($post_extra_style="border-top-width: 0;"?"":"")."\'.$r; 186 | } else 187 | return parent::get($title, $eslashes, $htmlcomments); 188 | } 189 | '); 190 | } 191 | } 192 | //$GLOBALS['plugins']->add_hook('showthread_start', 'xthreads_showthread_firstpost_hack'); 193 | 194 | 195 | // and now actually do the hack to display the first post on each page 196 | if($GLOBALS['forum']['xthreads_firstpostattop']) { // would be great if we had a reliable way to determine if we're on the first page here 197 | $db->xthreads_firstpost_hack = false; 198 | 199 | // this is a dirty hack we probably shouldn't be relying on (but eh, it works) 200 | // basically '-0' evaluates to true, effectively skipping the check in build_postbit() 201 | // but when incremented, becomes 1 202 | $GLOBALS['postcounter'] = '-0'; 203 | 204 | $extra_code = ' 205 | function fetch_array($query, $resulttype=1) { // 1 == MYSQL_ASSOC == MYSQLI_ASSOC == PGSQL_ASSOC 206 | if($this->xthreads_firstpost_hack) { 207 | $this->xthreads_firstpost_hack = false; 208 | return array(\'pid\' => $GLOBALS[\'thread\'][\'firstpost\']); 209 | } 210 | return parent::fetch_array($query, $resulttype); 211 | } 212 | '; 213 | $firstpost_hack_code = 'if(!empty($options[\'limit_start\'])) $this->xthreads_firstpost_hack = true;'; 214 | } else { 215 | $extra_code = ''; 216 | $firstpost_hack_code = 'if(empty($options[\'limit_start\']))'; 217 | } 218 | control_db(' 219 | function simple_select($table, $fields=\'*\', $conditions=\'\', $options=array()) { 220 | static $done=false; 221 | if(!$done && $table == \'posts p\' && $fields == \'p.pid\' && ($options[\'order_by\'] == \'p.dateline\' || $options[\'order_by\'] == \'p.dateline, p.pid\')) { 222 | $done = true; 223 | '.$firstpost_hack_code.' 224 | xthreads_showthread_firstpost_hack(); 225 | } 226 | return parent::simple_select($table, $fields, $conditions, $options); 227 | } 228 | '.$extra_code.' 229 | '); 230 | } 231 | 232 | 233 | function xthreads_firstpost_tpl_preload() { 234 | global $xthreads_postbit_templates, $templatelist; 235 | $xthreads_postbit_templates = array('postbit','postbit_attachments','postbit_attachments_attachment','postbit_attachments_attachment_unapproved','postbit_attachments_images','postbit_attachments_images_image','postbit_attachments_thumbnails','postbit_attachments_thumbnails_thumbnail','postbit_author_guest','postbit_author_user','postbit_avatar','postbit_away','postbit_classic','postbit_delete_pm','postbit_edit','postbit_editedby','postbit_editedby_editreason','postbit_email','postbit_find','postbit_forward_pm','postbit_gotopost','postbit_groupimage','postbit_icon','postbit_ignored','postbit_inlinecheck','postbit_iplogged_hiden','postbit_iplogged_show','postbit_multiquote','postbit_offline','postbit_online','postbit_pm','postbit_posturl','postbit_profilefield','postbit_profilefield_multiselect','postbit_profilefield_multiselect_value','postbit_purgespammer','postbit_quickdelete','postbit_quickrestore','postbit_quote','postbit_rep_button','postbit_reply_pm','postbit_replyall_pm','postbit_report','postbit_reputation','postbit_reputation_formatted','postbit_reputation_formatted_link','postbit_seperator','postbit_signature','postbit_userstar','postbit_warn','postbit_warninglevel','postbit_warninglevel_formatted','postbit_www'); 236 | foreach($xthreads_postbit_templates as &$t) { 237 | $templatelist .= ',postbit_first'.substr($t, 7); 238 | } 239 | } 240 | // returns true if postbit_first used at all 241 | function xthreads_tpl_postbithack() { 242 | global $templates, $xthreads_postbit_templates; 243 | $modified = false; 244 | if(!isset($templates->cache['postbit'])) 245 | $templates->cache(implode(',', $xthreads_postbit_templates)); 246 | foreach($xthreads_postbit_templates as &$t) { 247 | $pbname = substr($t, 7); 248 | if(!$pbname) $pbname = ''; 249 | if(isset($templates->cache['postbit_first'.$pbname]) && !isset($templates->non_existant_templates['postbit_first'.$pbname])) { 250 | $templates->cache['backup_postbit'.$pbname.'_backup__'] = $templates->cache[$t]; 251 | $templates->cache[$t] =& $templates->cache['postbit_first'.$pbname]; 252 | $modified = true; 253 | } 254 | } 255 | return $modified; 256 | } 257 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_upgrader.php: -------------------------------------------------------------------------------- 1 | write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 22 | `viewable_gids` varchar(255) not null default "", 23 | `unviewableval` text not null 24 | )'); 25 | } 26 | 27 | require_once MYBB_ROOT.'inc/adminfunctions_templates.php'; 28 | if(XTHREADS_INSTALLED_VERSION < 1.2) { 29 | if(XTHREADS_MODIFY_TEMPLATES) 30 | find_replace_templatesets('forumdisplay_searchforum_inline', '#\\#', '{$xthreads_forum_filter_form}'); 31 | 32 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'xtattachments` MODIFY COLUMN `md5hash` binary(16) default null'); 33 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 34 | `hideedit` tinyint(1) not null default 0 35 | )'); 36 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN ( 37 | `xthreads_hideforum` tinyint(3) not null default 0 38 | )'); 39 | 40 | /* 41 | // try to find orphaned xtattachments 42 | $orphaned = ''; 43 | $query = $db->simple_select('xtattachments a INNER JOIN '.$db->table_prefix.'threadfields_data tfd ON a.tid=t.tid', 'a.aid AS `a-aid`, a.field AS `a-field`, tfd.*', 'a.tid!=0'); // use a "-" in the name to guarantee no conflict with threadfields 44 | while($f = $db->fetch_array($query)) { 45 | if(!$f[$f['a-field']]) 46 | $orphaned = ($orphaned?',':'') . $f['a-aid']; 47 | } 48 | $db->free_result($query); 49 | if($orphaned) // mark as orphaned 50 | $db->update_query('xtattachments', array('tid' => 0), 'aid IN ('.$orphaned.')'); 51 | 52 | // also find xtattachment references which are invalid 53 | */ 54 | } 55 | 56 | if(XTHREADS_INSTALLED_VERSION < 1.3) { 57 | // we won't bother to fix potential issues with multiple values with textboxes 58 | } 59 | 60 | /* 61 | if(XTHREADS_INSTALLED_VERSION < 1.31) { 62 | // make table alterations for longer varchars + removal of default value 63 | $query = $db->simple_select('threadfields', 'field', 'inputtype IN ('.implode(',', array(XTHREADS_INPUT_TEXT, XTHREADS_INPUT_SELECT, XTHREADS_INPUT_RADIO, XTHREADS_INPUT_CHECKBOX)).')'); 64 | $qry_base = 'ALTER TABLE `'.$db->table_prefix.'threadfields_data` MODIFY '; 65 | $qry_suf = ' not null default ""'; 66 | while($field = $db->fetch_array($query)) { 67 | $alterfield_base = '`'.$field['field'].'` '; 68 | if(!$db->write_query($qry_base.$alterfield_base.'varchar(1024)'.$qry_suf, true)) { 69 | $db->write_query($qry_base.$alterfield_base.'varchar(255)'.$qry_suf); 70 | } 71 | } 72 | } 73 | */ 74 | 75 | if(XTHREADS_INSTALLED_VERSION < 1.32) { 76 | // fix DB collations 77 | $collation = $db->build_create_table_collation(); 78 | if($collation && ($db->type == 'mysql' || $db->type == 'mysqli')) { 79 | foreach(array('threadfields_data','xtattachments','threadfields') as $table) { 80 | $db->write_query('ALTER TABLE `'.$db->table_prefix.$table.'` CONVERT TO '.$collation); 81 | } 82 | } 83 | 84 | // make table alterations for longer varchars + removal of default value 85 | $query = $db->simple_select('threadfields', 'field,allowfilter,inputtype,multival', 'inputtype IN ('.implode(',', array(XTHREADS_INPUT_TEXT, XTHREADS_INPUT_SELECT, XTHREADS_INPUT_RADIO, XTHREADS_INPUT_CHECKBOX)).')'); 86 | $qry_base = 'ALTER TABLE `'.$db->table_prefix.'threadfields_data` MODIFY '; 87 | $qry_suf_vc = ' not null default ""'; 88 | while($field = $db->fetch_array($query)) { 89 | $alterfield_base = '`'.$field['field'].'` '; 90 | if($field['allowfilter']) { 91 | if($field['inputtype'] == XTHREADS_INPUT_TEXT || $field['inputtype'] == XTHREADS_INPUT_CHECKBOX || ($field['inputtype'] == XTHREADS_INPUT_SELECT && $field['multival'] !== '')) { 92 | if(!$db->write_query($qry_base.$alterfield_base.'varchar(1024)'.$qry_suf_vc, true)) { 93 | $db->write_query($qry_base.$alterfield_base.'varchar(255)'.$qry_suf_vc); 94 | } 95 | } else { 96 | $db->write_query($qry_base.$alterfield_base.'varchar(255)'.$qry_suf_vc); 97 | } 98 | } else { 99 | $db->write_query($qry_base.$alterfield_base.'text not null'); 100 | } 101 | } 102 | } 103 | 104 | if(XTHREADS_INSTALLED_VERSION < 1.33) { 105 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 106 | `tabstop` tinyint(1) not null default 1 107 | )'); 108 | 109 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` MODIFY `xthreads_tplprefix` varchar(255) not null default ""'); 110 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_hidebreadcrumb` tinyint(3) not null default 0'); 111 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_addfiltenable` varchar(200) not null default ""'); 112 | 113 | // replace default comment in showthread_noreplies template 114 | $query = $db->simple_select('templates', 'tid,template', 'title="showthread_noreplies"'); 115 | while($template = $db->fetch_array($query)) { 116 | $newtemplate = str_replace('', '', $template['template']); 117 | if($newtemplate != $template['template']) { 118 | $db->update_query('templates', array('template' => $db->escape_string($newtemplate)), 'tid='.$template['tid']); 119 | } 120 | } 121 | $db->free_result($query); 122 | } 123 | 124 | if(XTHREADS_INSTALLED_VERSION < 1.40) { 125 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_langprefix` text not null'); 126 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_defaultfilter` text not null'); 127 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` DROP COLUMN `xthreads_addfiltenable`'); 128 | // add indexes 129 | foreach(array('lastposteruid','prefix','icon') as $afe) { 130 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threads` ADD KEY `xthreads_'.$afe.'` (`'.$afe.'`)', true); 131 | } 132 | $cache->update_forums(); 133 | 134 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 135 | `datatype` tinyint(3) not null default '.XTHREADS_DATATYPE_TEXT.' 136 | )'); 137 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` MODIFY `xthreads_tplprefix` text not null'); 138 | 139 | // not _really_ necessary for XThreads, but we'll do it anyway for any 140 | // plugin which decides to rely on the 'uid' column of xtattachments table 141 | // and so that we don't end up stabbing ourselves in the foot later on 142 | $db->write_query('UPDATE `'.$db->table_prefix.'xtattachments` a INNER JOIN `'.$db->table_prefix.'threads` t ON a.tid=t.tid SET a.uid=t.uid WHERE a.uid=0 AND a.tid!=0'); 143 | // obviously not entirely accurate (thread starter may not be uploader of file) but better than leaving it as a '0' 144 | 145 | if(XTHREADS_MODIFY_TEMPLATES) { 146 | require_once MYBB_ROOT.'inc/xthreads/xt_install.php'; // grab XTHREADS_INSTALL_TPLADD_EXTRASORT define 147 | find_replace_templatesets('forumdisplay_threadlist', '#\\'); 148 | find_replace_templatesets('forumdisplay_threadlist', '#\\'."\n".XTHREADS_INSTALL_TPLADD_EXTRASORT); 149 | find_replace_templatesets('forumdisplay_threadlist_sortrating', '#$#', ''); 150 | } 151 | } 152 | 153 | if(XTHREADS_INSTALLED_VERSION < 1.41) { 154 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_fdcolspan_offset` smallint(6) not null default 0'); 155 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 156 | `editable_values` text not null 157 | )'); 158 | 159 | } 160 | if(XTHREADS_INSTALLED_VERSION < 1.42) { 161 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` MODIFY `defaultsortby` varchar(255) NOT NULL default \'\''); 162 | 163 | } 164 | if(XTHREADS_INSTALLED_VERSION < 1.43) { 165 | // remove hanging threadfields_data entries 166 | $query = $db->query('SELECT tfd.tid 167 | FROM `'.$db->table_prefix.'threadfields_data` tfd 168 | LEFT JOIN `'.$db->table_prefix.'threads` t ON tfd.tid=t.tid 169 | WHERE t.tid IS NULL'); 170 | /* other queries, which seem to be slower 171 | * select tid from mybb_threadfields_data where tid not in (select tid from mybb_threads) 172 | * ^ about same speed 173 | * select tid from mybb_threadfields_data tfd where not exists (select tid from mybb_threads t where t.tid=tfd.tid) 174 | * ^ a fair bit slower 175 | */ 176 | $tids = ''; 177 | while($tid = $db->fetch_field($query, 'tid')) { 178 | $tids .= ($tids?',':'') . $tid; 179 | } 180 | $db->free_result($query); 181 | 182 | if($tids) { 183 | $db->delete_query('threadfields_data', 'tid IN ('.$tids.')'); 184 | require_once MYBB_ROOT.'inc/xthreads/xt_modupdhooks.php'; 185 | xthreads_rm_attach_query('tid IN ('.$tids.')'); 186 | } 187 | 188 | require_once MYBB_ROOT.'inc/xthreads/xt_install.php'; 189 | xthreads_plugins_quickthread_tplmod(); 190 | 191 | $xt_forums_cache = $cache->read('xt_forums'); 192 | if(!empty($xt_forums_cache)) { // remove old xt_forums cache if present 193 | xthreads_delete_datacache('xt_forums'); 194 | } 195 | } 196 | 197 | if(XTHREADS_INSTALLED_VERSION < 1.45 && XTHREADS_INSTALLED_VERSION > 1.32) { 198 | // we'll fix up broken regexes in these versions if they exist 199 | // note that this does assume people haven't deliberately used bad regexes 200 | $db->update_query('threadfields', array( 201 | 'textmask' => $db->escape_string('^(https?)\://([a-z0-9.\-_]+)(/[^\r\n"<>&]*)?$') 202 | ), 'textmask="'.$db->escape_string('^(https?)\://([a-z.\-_]+)(/[^\r\n"<>&]*)?$').'"'); 203 | 204 | $db->update_query('threadfields', array( 205 | 'textmask' => $db->escape_string('^([a-z0-9]+)\://([a-z0-9.\-_]+)(/[^\r\n"<>&]*)?$') 206 | ), 'textmask="'.$db->escape_string('^([a-z0-9]+)\://([a-z.\-_]+)(/[^\r\n"<>&]*)?$').'"'); 207 | } 208 | 209 | if(XTHREADS_INSTALLED_VERSION < 1.50) { 210 | // template modification 211 | $tpl_threadfields_inputrow = $db->fetch_field($db->simple_select('templates', 'template', 'title="threadfields_inputrow" AND sid=-1'), 'template'); 212 | if($tpl_threadfields_inputrow) { 213 | $newtpl = preg_replace('~^(\s*\~i', '$1 class="xthreads_inputrow">', $tpl_threadfields_inputrow); 214 | if($newtpl != $tpl_threadfields_inputrow) 215 | $db->update_query('templates', array('template' => $db->escape_string($newtpl)), 'title="threadfields_inputrow" AND sid=-1'); 216 | } 217 | 218 | // settings overrides 219 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` ADD COLUMN `xthreads_settingoverrides` text not null'); 220 | // migrate old settings across 221 | $query = $db->simple_select('forums', 'fid,xthreads_force_postlayout,xthreads_threadsperpage'); 222 | while($forum = $db->fetch_array($query)) { 223 | $override = ''; 224 | if($forum['xthreads_force_postlayout']) 225 | $override .= 'postlayout='.$forum['xthreads_force_postlayout']."\n"; 226 | if($forum['xthreads_threadsperpage']) 227 | $override .= 'threadsperpage='.$forum['xthreads_threadsperpage']."\n"; 228 | if($override) 229 | $db->update_query('forums', array('xthreads_settingoverrides' => $db->escape_string($override)), 'fid='.$forum['fid']); 230 | } 231 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` DROP COLUMN `xthreads_force_postlayout`, DROP COLUMN `xthreads_threadsperpage`'); 232 | 233 | $cache->update_forums(); // the forum cache rebuild reads from here 234 | } 235 | 236 | if(XTHREADS_INSTALLED_VERSION < 1.53) { 237 | // fix up non-text select/radio inputs 238 | $query = $db->simple_select('threadfields', 'field, datatype', 'datatype!='.XTHREADS_DATATYPE_TEXT.' AND inputtype IN('.XTHREADS_INPUT_RADIO.','.XTHREADS_INPUT_SELECT.')'); 239 | $fixsql = ''; 240 | while($tf = $db->fetch_array($query)) { 241 | switch($tf['datatype']) { 242 | case XTHREADS_DATATYPE_INT: 243 | case XTHREADS_DATATYPE_UINT: 244 | $fieldtype = xthreads_db_fielddef('int', null, $tf['datatype']==XTHREADS_DATATYPE_UINT).' default null'; 245 | break; 246 | case XTHREADS_DATATYPE_BIGINT: 247 | case XTHREADS_DATATYPE_BIGUINT: 248 | $fieldtype = xthreads_db_fielddef('bigint', null, $tf['datatype']==XTHREADS_DATATYPE_BIGUINT).' default null'; 249 | break; 250 | case XTHREADS_DATATYPE_FLOAT: 251 | $fieldtype = 'double default null'; 252 | break; 253 | default: // paranoia 254 | $fieldtype = ''; 255 | } 256 | if($fieldtype) 257 | $fixsql .= ($fixsql?', ':'').'MODIFY `'.$db->escape_string($tf['field']).'` '.$fieldtype; 258 | } 259 | $db->free_result($query); 260 | if($fixsql) 261 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields_data` '.$fixsql); 262 | } 263 | 264 | if(XTHREADS_INSTALLED_VERSION < 1.60) { 265 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` ADD COLUMN ( 266 | `multival_limit` '.xthreads_db_fielddef('int', null, true).' not null default 0, 267 | `inputformat` text not null, 268 | `inputvalidate` text not null, 269 | `hidefield` '.xthreads_db_fielddef('int', null, false).' not null default 0 270 | )'); 271 | $db->update_query('threadfields', array('inputformat' => '{VALUE}')); 272 | // hideedit -> hidefield transition 273 | $db->update_query('threadfields', array('hidefield' => XTHREADS_HIDE_THREAD)); 274 | $db->update_query('threadfields', array('hidefield' => XTHREADS_HIDE_INPUT|XTHREADS_HIDE_THREAD), 'hideedit != 0'); 275 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'threadfields` DROP COLUMN `hideedit`'); 276 | // fix email masks 277 | $db->update_query('threadfields', array('textmask' => $db->escape_string('^([^ "(),:;<>@\\[\\\\\\]]+)@([a-z0-9_.\\-]+)$')), 'textmask="'.$db->escape_string('^([a-z0-9_.\\-]+)@([a-z0-9_.\\-]+)$').'"'); 278 | // we never used this, so may as well get rid of it 279 | $db->write_query('ALTER TABLE `'.$db->table_prefix.'forums` DROP COLUMN `xthreads_wol_xtattachment`'); 280 | 281 | if(XTHREADS_MODIFY_TEMPLATES) 282 | find_replace_templatesets('showthread', '#\\{\\$classic_header\\}#', '{$threadfields_display}{$classic_header}'); 283 | 284 | require_once MYBB_ROOT.'inc/xthreads/xt_install.php'; 285 | // migrate templates - surely no-one else is ending their template names with "threadfields_inputrow", right? 286 | $db->write_query('UPDATE `'.$db->table_prefix.'templates` SET title=CONCAT(SUBSTRING(title, 0, -21), "post_threadfields_inputrow") WHERE title LIKE "%threadfields_inputrow"'); 287 | // global -> master template conversion 288 | $newtpl = xthreads_new_templates(); // WARNING: if templates change, this could get funky 289 | function xtu_normalize_template($s) { 290 | return str_replace(' />', '/>', strtr( // remove spaces around tags 291 | preg_replace('~\s+~', ' ', trim($s)) // remove multiple spaces 292 | , array('> '=>'>', ' <'=>'<'))); 293 | } 294 | $query = $db->simple_select('templates', 'title,template', 'title IN ("editpost_first","forumdisplay_group_sep","forumdisplay_thread_null","showthread_noreplies","forumdisplay_searchforum_inline","post_threadfields_inputrow") AND sid=-1'); 295 | $rmtpl = array(); 296 | while($tpl = $db->fetch_array($query)) { 297 | if(xtu_normalize_template($tpl['template']) == xtu_normalize_template($newtpl[$tpl['title']])) 298 | // templates seem to be the same, remove 299 | $rmtpl[] = $tpl['title']; 300 | } 301 | if(!empty($rmtpl)) 302 | $db->delete_query('templates', 'title IN ("'.implode('","', $rmtpl).'") AND sid=-1'); 303 | xthreads_insert_templates($newtpl, -2); 304 | } 305 | 306 | if(XTHREADS_INSTALLED_VERSION < 1.62) { 307 | xthreads_buildtfcache(); // will also update XThreads forum cache 308 | } 309 | 310 | if(XTHREADS_INSTALLED_VERSION < 1.64 && XTHREADS_INSTALLED_VERSION > 1.32) { 311 | // we'll fix up broken regexes in these versions if they exist 312 | // note that this does assume people haven't deliberately used bad regexes 313 | $db->update_query('threadfields', array( 314 | 'textmask' => $db->escape_string('^(https?)\\://([^/?#]+)(/([^\\r\\n"<>#?]*)(\\?([^\\r\\n"<>#]*))?(#([^\\r\\n"<>]*))?)?$') 315 | ), 'textmask="'.$db->escape_string('^(https?)\\://([a-z0-9.\\-_]+)(/[^\\r\\n"<>&]*)?$').'"'); 316 | $db->update_query('threadfields', array( 317 | 'textmask' => $db->escape_string('^([a-z0-9]+)\\://([^/?#]+)(/([^\\r\\n"<>#?]*)(\\?([^\\r\\n"<>#]*))?(#([^\\r\\n"<>]*))?)?$') 318 | ), 'textmask="'.$db->escape_string('^([a-z0-9]+)\\://([a-z0-9.\\-_]+)(/[^\\r\\n"<>&]*)?$').'"'); 319 | $db->update_query('threadfields', array( 320 | 'textmask' => $db->escape_string('^([^:/?#]+)\\:((//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?)$') 321 | ), 'textmask="'.$db->escape_string('^([a-z0-9]+)\\:(.+)$').'"'); 322 | 323 | // if anyone's using custom file input HTML, try to fix the scriptaculous reference 324 | $db->write_query('UPDATE '.$db->table_prefix.'threadfields 325 | SET formhtml = REPLACE(formhtml, "'.$db->escape_string('src="{$mybb->settings[\'bburl\']}/jscripts/scriptaculous.js?load=effects,dragdrop"').'", "'.$db->escape_string('src="{$mybb->settings[\'bburl\']}/jscripts/version_code>=1700 then>xthreads_jquery-ui.min.jsscriptaculous.js?load=effects,dragdrop"').'") 326 | WHERE inputtype = '.XTHREADS_INPUT_FILE.' AND formhtml != "" 327 | '); 328 | } 329 | 330 | 331 | return true; 332 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_upload.php: -------------------------------------------------------------------------------- 1 | fetch_field($db->simple_select('xtattachments', 'uploadtime', 'tid=0 AND uid='.(int)$uid.' AND aid != '.$attacharray['aid'].' AND uploadtime > '.(TIME_NOW-XTHREADS_UPLOAD_FLOOD_TIME), array('order_by' => 'uploadtime', 'order_dir' => 'desc', 'limit' => 1, 'limit_start' => XTHREADS_UPLOAD_FLOOD_NUMBER)), 'uploadtime'); 22 | if($cutoff) 23 | xthreads_rm_attach_query('tid=0 AND uid='.(int)$uid.' AND aid != '.$attacharray['aid'].' AND uploadtime > '.(TIME_NOW-XTHREADS_UPLOAD_FLOOD_TIME).' AND uploadtime <= '.$cutoff); 24 | } 25 | 26 | return $attacharray; 27 | } 28 | 29 | function do_upload_xtattachment($attachment, &$tf, $update_attachment=0, $tid=0, $timestamp=TIME_NOW) 30 | { 31 | global $db, $mybb, $lang; 32 | 33 | $posthash = isset($mybb->input['posthash']) ? $db->escape_string($mybb->input['posthash']) : ''; 34 | 35 | $tid = (int)$tid; // may be possible for this to be null, if so, change to 0 36 | $path = $mybb->settings['uploadspath'].'/xthreads_ul/'; 37 | 38 | if(!isset($lang->xthreads_threadfield_attacherror)) $lang->load('xthreads'); 39 | 40 | if(is_array($attachment)) { 41 | if(isset($attachment['error']) && $attachment['error']) { 42 | if($attachment['error'] == 2) { 43 | return array('error' => $lang->sprintf($lang->xthreads_xtaerr_error_attachsize, get_friendly_size($tf['filemaxsize']))); 44 | } 45 | elseif($attachment['error'] >= 1 && $attachment['error'] <= 7) { 46 | $langvar = 'error_uploadfailed_php'.$attachment['error']; 47 | $langstr = $lang->$langvar; 48 | } 49 | else 50 | $langstr = $lang->sprintf($lang->error_uploadfailed_phpx, $attachment['error']); 51 | return array('error' => $lang->error_uploadfailed.$lang->error_uploadfailed_detail.$langstr); 52 | } 53 | 54 | if(!is_uploaded_file($attachment['tmp_name']) || empty($attachment['tmp_name'])) { 55 | return array('error' => $lang->error_uploadfailed.$lang->error_uploadfailed_php4); 56 | } 57 | 58 | 59 | $file_size = $attachment['size']; // @filesize($attachment['tmp_name']) 60 | 61 | $attachment['name'] = strtr($attachment['name'], array('/' => '', "\x0" => '')); 62 | 63 | if($error = xthreads_validate_attachment($attachment, $tf)) { 64 | @unlink($attachment['tmp_name']); 65 | return array('error' => $error); 66 | } 67 | 68 | $movefunc = 'move_uploaded_file'; 69 | } elseif($mybb->usergroup['cancp'] == 1 && substr($attachment, 0, 7) == 'file://') { 70 | // admin file move 71 | $filename = strtr(substr($attachment, 7), array('/' => '', DIRECTORY_SEPARATOR => '', "\0" => '')); 72 | $file = $path.'admindrop/'.$filename; 73 | if(xthreads_empty($filename) || !file_exists($file)) { 74 | return array('error' => $lang->sprintf($lang->xthreads_xtaerr_admindrop_not_found, htmlspecialchars_uni($filename), htmlspecialchars_uni($file))); 75 | } 76 | if(!is_writable($file)) { 77 | return array('error' => $lang->sprintf($lang->xthreads_xtaerr_admindrop_file_unwritable, htmlspecialchars_uni($filename))); 78 | } 79 | if(strtolower($file) == 'index.html') { 80 | return array('error' => $lang->xthreads_xtaerr_admindrop_index_error); 81 | } 82 | 83 | $attachment = array( 84 | 'name' => $filename, 85 | 'tmp_name' => $file, 86 | 'size' => @filesize($file), 87 | ); 88 | unset($file, $filename); 89 | if($error = xthreads_validate_attachment($attachment, $tf)) { 90 | return array('error' => $error); 91 | } 92 | 93 | $file_size = $attachment['size']; 94 | $movefunc = 'rename'; 95 | } else { 96 | // fetch URL 97 | if(!empty($tf['filemagic'])) 98 | $magic =& $tf['filemagic']; 99 | else 100 | $magic = array(); 101 | $attachment = xthreads_fetch_url($attachment, isset($tf['filemaxsize']) ? $tf['filemaxsize'] : 0, isset($tf['fileexts']) ? $tf['fileexts'] : '', $magic); 102 | db_ping($db); 103 | if(!empty($attachment['error'])) { 104 | return array('error' => $attachment['error']); 105 | } 106 | $file_size = $attachment['size']; 107 | 108 | if(xthreads_empty($attachment['name']) || $file_size < 1) 109 | return array('error' => $lang->error_uploadfailed); 110 | 111 | $attachment['name'] = strtr($attachment['name'], array('/' => '', "\x0" => '')); 112 | 113 | $movefunc = 'rename'; 114 | } 115 | 116 | 117 | if(!empty($tf['fileimage'])) { 118 | $img_dimensions = @getimagesize($attachment['tmp_name']); 119 | if(empty($img_dimensions) || !in_array($img_dimensions[2], array(IMAGETYPE_GIF,IMAGETYPE_JPEG,IMAGETYPE_PNG))) { 120 | @unlink($attachment['tmp_name']); 121 | return array('error' => $lang->error_attachtype); 122 | } 123 | if(preg_match('~^([0-9]+)x([0-9]+)(\\|([0-9]+)x([0-9]+))?$~', $tf['fileimage'], $match)) { 124 | // check if image exceeds max/min dimensions 125 | if(($img_dimensions[0] < $match[1] || $img_dimensions[1] < $match[2]) || ( 126 | $match[3] && ( 127 | $img_dimensions[0] > $match[4] || $img_dimensions[1] > $match[5] 128 | ) 129 | )) { 130 | @unlink($attachment['tmp_name']); 131 | return array('error' => $lang->sprintf($lang->xthreads_xtaerr_error_imgdims, $img_dimensions[0], $img_dimensions[1])); 132 | } 133 | } 134 | /* 135 | // convert WBMP -> PNG (saves space, bandwidth and works with MyBB's thumbnail generator) 136 | // unfortunately, although this is nice, we have a problem of filetype checking etc... 137 | if($img_dimensions[2] == IMAGETYPE_WBMP) { 138 | if(function_exists('imagecreatefromwbmp') && $img = @imagecreatefromwbmp($attachment['tmp_name'])) { 139 | @unlink($attachment['tmp_name']); 140 | @imagepng($img, $attachment['tmp_name'], 6); // use zlib's recommended compression level 141 | imgdestroy($img); 142 | unset($img); 143 | // double check that we have a file 144 | if(!file_exists($attachment['tmp_name'])) 145 | return array('error' => $lang->error_attachtype); // get user to upload a non-WBMP file, lol 146 | // change extension + update filesize, do MIME as well 147 | if(strtolower(substr($attachment['name'], -5)) == '.wbmp') 148 | $attachment['name'] = substr($attachment['name'], 0, -5).'.png'; 149 | $file_size = @filesize($attachment['tmp_name']); 150 | if(strtolower($attachment['type']) == 'image/wbmp') 151 | $attachment['type'] = 'image/png'; 152 | // update type too 153 | $img_dimensions[2] = IMAGETYPE_PNG; 154 | } 155 | else { 156 | // can't do much, error out 157 | @unlink($attachment['tmp_name']); 158 | return array('error' => $lang->error_attachtype); 159 | } 160 | } 161 | */ 162 | // we won't actually bother checking MIME types - not a big issue anyway 163 | } 164 | 165 | if(!XTHREADS_UPLOAD_LARGEFILE_SIZE || $file_size < XTHREADS_UPLOAD_LARGEFILE_SIZE) { 166 | @set_time_limit(30); // as md5_file may take a while 167 | $md5_start = time(); 168 | $file_md5 = @md5_file($attachment['tmp_name'], true); 169 | if(strlen($file_md5) == 32) { 170 | // perhaps not PHP5 171 | $file_md5 = pack('H*', $file_md5); 172 | } 173 | if(time() - $md5_start > 2) // ping DB if process took longer than 2 secs 174 | db_ping($db); 175 | unset($md5_start); 176 | } 177 | 178 | if($update_attachment) { 179 | $prevattach = $db->fetch_array($db->simple_select('xtattachments', 'aid,attachname,indir,md5hash', 'aid='.(int)$update_attachment)); 180 | if(empty($prevattach['aid'])) $update_attachment = false; 181 | } /* else { 182 | // Check if attachment already uploaded 183 | // TODO: this is actually a little problematic - perhaps verify that this is attached to this field (or maybe rely on checks in xt_updatehooks file) 184 | if(isset($file_md5)) 185 | $md5check = ' OR md5hash='.xthreads_db_escape_binary($file_md5); 186 | else 187 | $md5check = ''; 188 | $prevattach = $db->fetch_array($db->simple_select('xtattachments', 'aid', 'filename="'.$db->escape_string($attachment['name']).'" AND (md5hash IS NULL'.$md5check.') AND filesize='.$file_size.' AND (posthash="'.$posthash.'" OR (tid='.$tid.' AND tid!=0))')); 189 | if(!empty($prevattach['aid'])) { 190 | @unlink($attachment['tmp_name']); 191 | // TODO: maybe return aid instead? 192 | return array('error' => $lang->error_alreadyuploaded); 193 | } 194 | } */ 195 | 196 | 197 | // We won't use MyBB's nice monthly directories, instead, we'll use a more confusing system based on the timestamps 198 | // note, one month = 2592000 seconds, so if we split up by 1mil, it'll be approx 11.5 days 199 | // If safe_mode is enabled, don't attempt to use the monthly directories as it won't work 200 | if(ini_get('safe_mode') == 1 || strtolower(ini_get('safe_mode')) == 'on') { 201 | $month_dir = ''; 202 | } else { 203 | $month_dir = 'ts_'.floor(TIME_NOW / 1000000).'/'; 204 | if(!@is_dir($path.$month_dir)) { 205 | @mkdir($path.$month_dir); 206 | // Still doesn't exist - oh well, throw it in the main directory 207 | if(@is_dir($path.$month_dir)) { 208 | // write index file 209 | if($index = fopen($path.$month_dir.'index.html', 'w')) { 210 | fwrite($index, ''); 211 | fclose($index); 212 | @my_chmod($path.$month_dir.'index.html', 0644); 213 | } 214 | @my_chmod($path.$month_dir, 0755); 215 | } 216 | else 217 | $month_dir = ''; 218 | } 219 | } 220 | 221 | // All seems to be good, lets move the attachment! 222 | $basename = substr(md5(uniqid(mt_rand(), true).substr($mybb->post_code, 16)), 12, 8).'_'.preg_replace('~[^a-zA-Z0-9_\-%]~', '', str_replace(array(' ', '.', '+'), '_', $attachment['name'])).'.upload'; 223 | $filename = 'file_'.(!empty($prevattach['aid']) ? $prevattach['aid'] : 't'.TIME_NOW).'_'.$basename; 224 | 225 | @ignore_user_abort(true); // don't let the user break this integrity between file system and DB 226 | if(isset($GLOBALS['xtfurl_tmpfiles'])) { // if using url fetch, remove this from list of temp files 227 | unset($GLOBALS['xtfurl_tmpfiles'][$attachment['tmp_name']]); 228 | } 229 | while(!(@$movefunc($attachment['tmp_name'], $path.$month_dir.$filename))) { 230 | if($month_dir) { // try doing it again without the month_dir 231 | $month_dir = ''; 232 | } else { 233 | // failed 234 | @ignore_user_abort(false); 235 | return array('error' => $lang->error_uploadfailed.$lang->error_uploadfailed_detail.$lang->error_uploadfailed_movefailed); 236 | } 237 | } 238 | 239 | // Lets just double check that it exists 240 | if(!file_exists($path.$month_dir.$filename)) { 241 | @ignore_user_abort(false); 242 | return array('error' => $lang->error_uploadfailed.$lang->error_uploadfailed_detail.$lang->error_uploadfailed_lost); 243 | } 244 | 245 | // Generate the array for the insert_query 246 | $attacharray = array( 247 | 'posthash' => $posthash, 248 | 'tid' => $tid, 249 | 'uid' => (int)$mybb->user['uid'], 250 | 'field' => $tf['field'], 251 | 'filename' => strval($attachment['name']), 252 | 'uploadmime' => strval($attachment['type']), 253 | 'filesize' => $file_size, 254 | 'attachname' => $basename, 255 | 'indir' => $month_dir, 256 | 'downloads' => 0, 257 | 'uploadtime' => $timestamp, 258 | 'updatetime' => $timestamp, 259 | ); 260 | if(isset($file_md5)) 261 | $attacharray['md5hash'] = new xthreads_db_binary_value($file_md5); 262 | else 263 | $attacharray['md5hash'] = null; 264 | if(!empty($img_dimensions)) { 265 | $origdimarray = array('w' => $img_dimensions[0], 'h' => $img_dimensions[1], 'type' => $img_dimensions[2]); 266 | $attacharray['thumbs'] = serialize(array('orig' => $origdimarray)); 267 | } 268 | 269 | if($update_attachment) { 270 | unset($attacharray['downloads'], $attacharray['uploadtime']); 271 | //$attacharray['updatetime'] = TIME_NOW; 272 | xthreads_db_update('xtattachments', $attacharray, 'aid='.$prevattach['aid']); 273 | $attacharray['aid'] = $prevattach['aid']; 274 | 275 | // and finally, delete old attachment 276 | xthreads_rm_attach_fs($prevattach); 277 | $new_file = $path.$month_dir.$filename; 278 | } 279 | else { 280 | $attacharray['aid'] = xthreads_db_insert('xtattachments', $attacharray); 281 | // now that we have the aid, move the file 282 | $new_file = $path.$month_dir.'file_'.$attacharray['aid'].'_'.$basename; 283 | @rename($path.$month_dir.$filename, $new_file); 284 | if(!file_exists($new_file)) { 285 | // oh dear, all our work for nothing... 286 | @unlink($path.$month_dir.$filename); 287 | $db->delete_query('xtattachments', 'aid='.$attacharray['aid']); 288 | @ignore_user_abort(false); 289 | return array('error' => $lang->error_uploadfailed.$lang->error_uploadfailed_detail.$lang->error_uploadfailed_lost); 290 | } 291 | } 292 | @my_chmod($new_file, '0644'); 293 | @ignore_user_abort(false); 294 | 295 | if(!empty($img_dimensions) && !empty($tf['fileimgthumbs'])) { 296 | // generate thumbnails 297 | $attacharray['thumbs'] = xthreads_build_thumbnail($tf['fileimgthumbs'], $attacharray['aid'], $tf['field'], $new_file, $path, $month_dir, $img_dimensions); 298 | $attacharray['thumbs']['orig'] = $origdimarray; 299 | $attacharray['thumbs'] = serialize($attacharray['thumbs']); 300 | } 301 | 302 | return $attacharray; 303 | } 304 | 305 | function xthreads_validate_attachment(&$attachment, &$tf) { 306 | global $lang; 307 | if(empty($attachment['name']) || $attachment['size'] < 1) { 308 | return $lang->error_uploadfailed; 309 | } 310 | if(!empty($tf['filemaxsize']) && $attachment['size'] > $tf['filemaxsize']) { 311 | return $lang->sprintf($lang->xthreads_xtaerr_error_attachsize, get_friendly_size($tf['filemaxsize'])); 312 | } 313 | if(!xthreads_fetch_url_validext($attachment['name'], $tf['fileexts'])) 314 | return $lang->error_attachtype; 315 | if(!empty($tf['filemagic'])) { 316 | $validmagic = false; 317 | if($fp = @fopen($attachment['tmp_name'], 'rb')) { 318 | $startbuf = fread($fp, 255); // since it's impossible to exceed this amount in the field (yes, it's dirty, lol) 319 | fclose($fp); 320 | foreach($tf['filemagic'] as &$magic) { 321 | if(xthreads_empty($magic)) continue; 322 | if(substr($startbuf, 0, strlen($magic)) == $magic) { 323 | $validmagic = true; 324 | break; 325 | } 326 | } 327 | } else 328 | return $lang->error_uploadfailed; 329 | 330 | if(!$validmagic) { 331 | return $lang->error_attachtype; 332 | } 333 | } 334 | return false; // no error 335 | } 336 | 337 | function &xthreads_build_thumbnail($thumbdims, $aid, $fieldname, $filename, $path, $month_dir, $img_dimensions=null) { 338 | if(empty($img_dimensions)) { 339 | //$img_dimensions = @getimagesize($path.$month_dir.$filename); 340 | $img_dimensions = @getimagesize($filename); 341 | } 342 | $update_thumbs = array('orig' => array('w' => $img_dimensions[0], 'h' => $img_dimensions[1], 'type' => $img_dimensions[2])); 343 | if(is_array($img_dimensions)) { 344 | $filterfunc = 'xthreads_imgthumb_'.$fieldname; 345 | foreach($thumbdims as $dims => $complex) { 346 | $destname = basename(substr($filename, 0, -6).$dims.'.thumb'); 347 | if($complex) { 348 | require_once MYBB_ROOT.'inc/xthreads/xt_image.php'; 349 | $img = new XTImageTransform; 350 | if($img->_load($filename)) { 351 | // run filter chain 352 | $filterfunc($dims, $img); 353 | // write out file & save 354 | $img->_enableWrite = true; 355 | $img->write($path.$month_dir.'/'.$destname); 356 | $update_thumbs[$dims] = array('w' => $img->WIDTH, 'h' => $img->HEIGHT, 'type' => $img->typeGD, 'file' => $month_dir.$destname); 357 | } else { 358 | // failed 359 | $update_thumbs[$dims] = array('w' => 0, 'h' => 0, 'type' => 0, 'file' => ''); 360 | } 361 | } else { 362 | $p = strpos($dims, 'x'); 363 | if(!$p) continue; 364 | $w = (int)substr($dims, 0, $p); 365 | $h = (int)substr($dims, $p+1); 366 | 367 | if($img_dimensions[0] > $w || $img_dimensions[1] > $h) { 368 | // TODO: think about using own function to apply image convolution 369 | require_once MYBB_ROOT.'inc/functions_image.php'; 370 | $thumbnail = generate_thumbnail($filename, $path.$month_dir, $destname, $h, $w); 371 | // if it fails, there's nothing much we can do... so twiddle thumbs is the solution 372 | if($thumbnail['code'] == 1) { 373 | $newdims = scale_image($img_dimensions[0], $img_dimensions[1], $w, $h); 374 | $update_thumbs[$dims] = array('w' => $newdims['width'], 'h' => $newdims['height'], 'type' => $img_dimensions[2], 'file' => $month_dir.$destname); 375 | } 376 | else { 377 | $update_thumbs[$dims] = array('w' => 0, 'h' => 0, 'type' => 0, 'file' => ''); 378 | } 379 | } 380 | else { // image is small (hopefully), just copy it over 381 | // TODO: maybe use hardlink instead? 382 | @copy($filename, $path.$month_dir.$destname); 383 | $update_thumbs[$dims] = array('w' => $img_dimensions[0], 'h' => $img_dimensions[1], 'type' => $img_dimensions[2], 'file' => $month_dir.$destname); 384 | } 385 | } 386 | } 387 | } 388 | 389 | global $db; 390 | $db->update_query('xtattachments', array( 391 | 'thumbs' => $db->escape_string(serialize($update_thumbs)) 392 | ), 'aid='.$aid); 393 | return $update_thumbs; 394 | } 395 | 396 | 397 | // copied from MyBB's fetch_remote_file function, but modified for our needs 398 | // this will attempt to "smartly" terminate the transfer early if it's going to end up rejected anyway 399 | function xthreads_fetch_url($url, $max_size=0, $valid_ext='', $valid_magic=array()) { 400 | global $lang; 401 | if(!isset($lang->xthreads_xtfurlerr_invalidurl)) $lang->load('xthreads'); 402 | $url = str_replace("\x0", '', $url); 403 | $purl = @parse_url($url); 404 | if(xthreads_empty($purl['host'])) return array('error' => $lang->xthreads_xtfurlerr_invalidurl); 405 | 406 | // attempt to decode special IP tricks, eg 0x7F.0.0.0 or even 127.000.0.0 407 | if(substr_count($purl['host'], '.') == 3 && preg_match('~^[0-9a-fA-FxX.]+$~', $purl['host'])) { 408 | $parts = explode('.', $purl['host']); 409 | $modify = true; 410 | foreach($parts as &$part) { 411 | if($part === '') return array('error' => $lang->xthreads_xtfurlerr_invalidurl); 412 | if($part[0] === '0' && isset($part[1])) { 413 | if($part[1] == 'x' || $part[1] == 'X') { 414 | // check hex digit 415 | $hexpart = substr($part, 2); 416 | if($hexpart === '' || !ctype_xdigit($hexpart)) { 417 | $modify = false; 418 | break; 419 | } else { 420 | $part = hexdec($hexpart); 421 | } 422 | } elseif(!is_numeric($part)) { 423 | $modify = false; 424 | break; 425 | } elseif(preg_match('~^[0-7]+$~', $part)) { 426 | $part = octdec($part); 427 | } else { 428 | $part = (int)$part; 429 | } 430 | } 431 | elseif(!is_numeric($part)) { 432 | $modify = false; 433 | break; 434 | } else 435 | $part = (int)$part; // converts stuff like 000 into 0, although above should do that 436 | } 437 | if($modify) $purl['host'] = implode('.', $parts); 438 | } 439 | // IPv6 version - normalize IPv6 addresses 440 | // (regex won't match [::], but we don't need to process that anyway) 441 | elseif(substr_count($purl['host'], ':') > 1 && substr_count($purl['host'], ':') < 8 && preg_match('~^\\[(?:[0-9a-f]{1,4}\\:){0,7}(?:\\:\\:?(?:[0-9a-f]{1,4}\\:){0,6})?(?:[0-9a-f]{1,4})\\]$~i', $purl['host']) && strpos($purl['host'], ':::')===false) { 442 | $parts = explode(':', strtolower(substr($purl['host'], 1, -1))); 443 | // expand double-colon 444 | $expand = 8 - count($parts); 445 | if($expand) { 446 | if(($i = array_search('', $parts, true)) !== false) { 447 | array_splice($parts, $i, 1, array_fill(0, $expand+1, '0')); 448 | } 449 | // TODO: check if IP is still valid 450 | } 451 | // strip leading zeros 452 | foreach($parts as &$part) { 453 | $part = ltrim($part, '0'); 454 | if($part === '') $part = '0'; 455 | } 456 | // compress stream of zeros 457 | $parts = implode(':', $parts); 458 | preg_match_all('~(?<=\\:)(0\\:){2,}~', ':'.$parts.':', $ipzeros, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE); 459 | if(!empty($ipzeros) && !empty($ipzeros[0])) { 460 | $longest = 0; 461 | $longest_start = 0; 462 | foreach($ipzeros[0] as $ipzero) { 463 | $l = strlen($ipzero[0]); 464 | if($l > $longest) { 465 | $longest = $l; 466 | $longest_start = $ipzero[1]; 467 | } 468 | } 469 | if($longest && $longest_start) { // this should _always_ be true here 470 | $parts = ' '.$parts; 471 | $parts = substr($parts, 0, $longest_start-1).'::'.substr($parts, $longest_start+$longest); 472 | $parts = ltrim($parts); 473 | } 474 | } 475 | $purl['host'] = '['.$parts.']'; 476 | } 477 | 478 | if(XTHREADS_URL_FETCH_DISALLOW_HOSTS && in_array($purl['host'], array_map('trim', explode(',', XTHREADS_URL_FETCH_DISALLOW_HOSTS)))) 479 | return array('error' => $lang->xthreads_xtfurlerr_badhost); 480 | 481 | $portmap = array( 482 | 'http' => 80, 483 | 'https' => 443, 484 | 'ftp' => 21, 485 | 'ftps' => 990, 486 | ); 487 | $scheme = strtolower($purl['scheme']); 488 | 489 | if(!isset($portmap[$scheme])) return array('error' => $lang->xthreads_xtfurlerr_invalidscheme); 490 | if(!$purl['port']) 491 | $purl['port'] = $portmap[$scheme]; 492 | elseif(XTHREADS_URL_FETCH_DISALLOW_PORT && $purl['port'] != $portmap[$scheme]) 493 | return array('error' => $lang->xthreads_xtfurlerr_badport); 494 | 495 | $ret = array( 496 | 'tmp_name' => tempnam(xthreads_get_temp_dir(), mt_rand()), 497 | 'name' => basename($purl['path']), 498 | 'name_disposition' => false, 499 | 'size' => 0, 500 | ); 501 | @unlink($ret['tmp_name']); 502 | if(substr($purl['path'], -1) == '/' || xthreads_empty($ret['name'])) $ret['name'] = 'index.html'; 503 | 504 | require_once MYBB_ROOT.'inc/xthreads/xt_urlfetcher.php'; 505 | $fetcher = getXTUrlFetcher($purl['scheme']); 506 | if(!isset($fetcher)) { 507 | return array('error' => $lang->xthreads_xtfurlerr_nofetcher); 508 | } 509 | 510 | $fp = @fopen($ret['tmp_name'], 'wb'); 511 | if(!$fp) return array('error' => $lang->xthreads_xtfurlerr_cantwrite); 512 | 513 | xthreads_fetch_url_register_tmp($ret['tmp_name']); 514 | @set_time_limit(0); 515 | 516 | 517 | $fetcher->url = $url; 518 | $fetcher->setRefererFromUrl(); 519 | 520 | $fetcher->charset = $lang->settings['charset']; 521 | $fetcher->lang = $lang->settings['htmllang']; 522 | 523 | $GLOBALS['xtfurl_ret'] =& $ret; 524 | $GLOBALS['xtfurl_max_size'] = $max_size; 525 | $fetcher->meta_function = 'xthreads_fetch_url_meta'; 526 | $GLOBALS['xtfurl_datalen'] = 0; 527 | $GLOBALS['xtfurl_magicchecked'] = false; 528 | $GLOBALS['xtfurl_validmagic'] =& $valid_magic; 529 | $GLOBALS['xtfurl_databuf'] = ''; 530 | $GLOBALS['xtfurl_exts'] =& $valid_ext; 531 | $GLOBALS['xtfurl_fp'] =& $fp; 532 | $fetcher->body_function = 'xthreads_fetch_url_write'; 533 | 534 | $result = $fetcher->fetch(); 535 | // TODO: fix the following 536 | if($result === false) { 537 | $error = $fetcher->getError($errcode); 538 | $langvar = 'xthreads_xtfurlerr_'.$error; 539 | if(isset($lang->$langvar)) 540 | $ret['error'] = $lang->$langvar; 541 | else 542 | $ret['error'] = $lang->sprintf($lang->xthreads_xtfurlerr_errcode, $fetcher->name, $errcode, htmlspecialchars_uni($error)); 543 | } 544 | 545 | $fetcher->close(); 546 | 547 | if(empty($ret['error'])) { 548 | // check magic if not done 549 | if($result && empty($GLOBALS['xtfurl_magicchecked']) && !empty($valid_magic)) { 550 | if(!xthreads_fetch_url_validmagic($GLOBALS['xtfurl_databuf'], $valid_magic)) { 551 | $GLOBALS['xtfurl_magicchecked'] = 'invalid'; 552 | $result = null; 553 | } 554 | } 555 | if($result === null) { 556 | // aborted - most likely from early termination 557 | if(!empty($ret['size']) && $max_size && $ret['size'] > $max_size) { 558 | $ret['error'] = $lang->sprintf($lang->xthreads_xtaerr_error_attachsize, get_friendly_size($max_size)); 559 | } 560 | elseif($GLOBALS['xtfurl_magicchecked'] == 'invalid') { // this also covers extension check 561 | $ret['error'] = $lang->error_attachtype; 562 | } 563 | } 564 | } 565 | 566 | fclose($fp); 567 | if(!empty($ret['error'])) 568 | @unlink($ret['tmp_name']); 569 | else { 570 | $ret['size'] = @filesize($ret['tmp_name']); 571 | if($ret['size'] < 1 || empty($ret['name'])) // weird... 572 | @unlink($ret['tmp_name']); 573 | } 574 | 575 | @set_time_limit(30); 576 | return $ret; 577 | } 578 | function xthreads_fetch_url_validext(&$name, &$exts) { 579 | if(!xthreads_empty($exts)) { 580 | $fn = strtolower($name); 581 | foreach(explode('|', strtolower($exts)) as $ext) { 582 | if($ext !== '' && substr($fn, -strlen($ext) -1) == '.'.$ext) 583 | return true; 584 | } 585 | return false; 586 | } 587 | return true; 588 | } 589 | function xthreads_fetch_url_validmagic(&$data, &$magic) { 590 | if(empty($magic)) return true; 591 | foreach($magic as &$m) { 592 | if($m && substr($data, 0, strlen($m)) == $m) { 593 | return true; 594 | } 595 | } 596 | return false; 597 | } 598 | 599 | function xthreads_fetch_url_meta(&$fetcher, &$name, &$val) { 600 | global $xtfurl_ret; 601 | switch($name) { 602 | case 'retcode': 603 | if(!isset($val[0]) || $val[0] != 200) { 604 | global $lang; 605 | $GLOBALS['xtfurl_ret']['error'] = $lang->sprintf($lang->xthreads_xtfurlerr_badresponse, $val[0], $val[1]); 606 | return false; 607 | } 608 | return true; 609 | 610 | case 'name': 611 | $xtfurl_ret['name_disposition'] = true; 612 | // fall through 613 | case 'size': 614 | case 'type': 615 | if(!xthreads_empty($val)) 616 | $xtfurl_ret[$name] = $val; 617 | if($name == 'size' && !empty($GLOBALS['xtfurl_max_size']) && $val > $GLOBALS['xtfurl_max_size']) 618 | return false; 619 | } 620 | return true; 621 | } 622 | function xthreads_fetch_url_write(&$fetcher, &$data) { 623 | $len = strlen($data); 624 | global $xtfurl_datalen, $xtfurl_magicchecked, $xtfurl_ret; 625 | 626 | // check extension 627 | if(!$xtfurl_datalen) { 628 | // firstly, do we have an extension? if not, maybe try guess one from the content-type 629 | if(!xthreads_empty($xtfurl_ret['type']) && !$xtfurl_ret['name_disposition'] && strpos($xtfurl_ret['name'], '.') === false) { 630 | // we'll only try a few common ones 631 | switch(strtolower($xtfurl_ret['type'])) { 632 | case 'text/html': case 'text/xhtml+xml': 633 | $xtfurl_ret['name'] .= '.html'; break; 634 | case 'image/jpeg': case 'image/jpg': 635 | $xtfurl_ret['name'] .= '.jpg'; break; 636 | case 'image/gif': 637 | $xtfurl_ret['name'] .= '.gif'; break; 638 | case 'image/png': 639 | $xtfurl_ret['name'] .= '.png'; break; 640 | case 'image/bmp': 641 | $xtfurl_ret['name'] .= '.bmp'; break; 642 | case 'image/svg+xml': 643 | $xtfurl_ret['name'] .= '.svg'; break; 644 | case 'image/tiff': 645 | $xtfurl_ret['name'] .= '.tiff'; break; 646 | case 'image/x-icon': 647 | $xtfurl_ret['name'] .= '.ico'; break; 648 | case 'text/xml': 649 | $xtfurl_ret['name'] .= '.xml'; break; 650 | case 'text/plain': 651 | $xtfurl_ret['name'] .= '.txt'; break; 652 | case 'text/css': 653 | $xtfurl_ret['name'] .= '.css'; break; 654 | case 'text/javascript': case 'application/javascript': case 'application/x-javascript': 655 | $xtfurl_ret['name'] .= '.js'; break; 656 | } 657 | } 658 | if(!xthreads_fetch_url_validext($xtfurl_ret['name'], $GLOBALS['xtfurl_exts'])) { 659 | $xtfurl_magicchecked = 'invalid'; // dirty, but works... 660 | return false; 661 | } 662 | } 663 | 664 | $xtfurl_datalen += $len; 665 | if(!empty($GLOBALS['xtfurl_max_size']) && $xtfurl_datalen > $GLOBALS['xtfurl_max_size']) { 666 | $xtfurl_ret['size'] = $xtfurl_datalen; 667 | return false; 668 | } 669 | if(empty($xtfurl_magicchecked) && !empty($GLOBALS['xtfurl_validmagic'])) { 670 | global $xtfurl_databuf; 671 | if($xtfurl_datalen >= 255) { 672 | // check magic 673 | $xtfurl_databuf .= substr($data, 0, 255-$xtfurl_datalen+$len); 674 | if(!xthreads_fetch_url_validmagic($xtfurl_databuf, $GLOBALS['xtfurl_validmagic'])) { 675 | $xtfurl_magicchecked = 'invalid'; 676 | return false; 677 | } 678 | $xtfurl_magicchecked = true; 679 | } else { 680 | $xtfurl_databuf .= $data; 681 | } 682 | } 683 | fwrite($GLOBALS['xtfurl_fp'], $data); 684 | return true; 685 | } 686 | 687 | 688 | // these functions ensure that temp files are cleaned up if the user aborts the connection 689 | function xthreads_fetch_url_register_tmp($name) { 690 | global $xtfurl_tmpfiles; 691 | if(!is_array($xtfurl_tmpfiles)) { 692 | $xtfurl_tmpfiles = array(); 693 | register_shutdown_function('xthreads_fetch_url_tmp_shutdown'); 694 | } 695 | $xtfurl_tmpfiles[$name] = 1; 696 | } 697 | function xthreads_fetch_url_tmp_shutdown() { 698 | if(!connection_aborted()) return; 699 | global $xtfurl_tmpfiles; 700 | foreach($xtfurl_tmpfiles as $name => $foo) { 701 | @unlink($name); // should always succeed (hopefully)... 702 | } 703 | } 704 | 705 | if(!function_exists('ctype_xdigit')) { 706 | function ctype_xdigit($s) { 707 | return (bool)preg_match('~^[0-9a-fA-F]+$~', $s); 708 | } 709 | } 710 | function xthreads_get_temp_dir() { 711 | if(ini_get('safe_mode') == 1 || strtolower(ini_get('safe_mode')) == 'on') 712 | // safemode - fallback to cache dir 713 | return realpath(MYBB_ROOT.'cache/'); 714 | elseif(function_exists('sys_get_temp_dir') && ($tmpdir = sys_get_temp_dir()) && @is_dir($tmpdir) && is_writable($tmpdir)) 715 | return realpath($tmpdir); 716 | elseif(!function_exists('sys_get_temp_dir')) { 717 | // PHP < 5.2.1, try to find a temp dir 718 | $dirs = array(); 719 | foreach(array('TMP', 'TMPDIR', 'TEMP') as $e) { 720 | if($env = getenv($e)) 721 | $dirs[] = $env; 722 | } 723 | if(DIRECTORY_SEPARATOR == '\\') { // Windows 724 | // all this probably unnecessary, but oh well, enjoy it whilst we can 725 | if($env = getenv('LOCALAPPDATA')) 726 | $dirs[] = $env.'\\Temp\\'; 727 | if($env = getenv('USERPROFILE')) 728 | $dirs[] = $env.'\\Local Settings\\Temp\\'; 729 | if($env = getenv('SYSTEMROOT')) 730 | $dirs[] = $env.'\\Temp\\'; 731 | if($env = getenv('WINDIR')) 732 | $dirs[] = $env.'\\Temp\\'; 733 | if($env = getenv('SYSTEMDRIVE')) 734 | $dirs[] = $env.'\\Temp\\'; 735 | 736 | $dirs[] = 'C:\\Windows\\Temp\\'; 737 | $dirs[] = 'C:\\Temp\\'; 738 | } else { 739 | $dirs[] = '/tmp/'; 740 | } 741 | foreach($dirs as &$dir) { 742 | if(@is_dir($dir) && is_writable($dir)) 743 | return realpath($dir); 744 | } 745 | } 746 | // fallback on cache dir (guaranteed to be writable) 747 | return realpath(MYBB_ROOT.'cache/'); 748 | } 749 | 750 | function db_ping(&$dbobj) { 751 | if($dbobj->type == 'mysqli') 752 | $func = 'mysqli_ping'; 753 | else 754 | $func = xthreads_db_type($dbobj->type).'_ping'; 755 | if(!function_exists($func)) return true; // fallback 756 | if(isset($dbobj->db) && is_object($dbobj->db)) return true; // sqlite 757 | 758 | $ret = @$func($dbobj->read_link); 759 | if($dbobj->write_link !== $dbobj->read_link) 760 | $ret = @$func($dbobj->write_link) && $ret; 761 | return $ret; 762 | } 763 | -------------------------------------------------------------------------------- /Upload/inc/xthreads/xt_urlfetcher.php: -------------------------------------------------------------------------------- 1 | Note that although the variables are passed as references (for speed purposes), they should NOT be modified 47 | * Should return true if everything is well, false to abort 48 | */ 49 | var $body_function=null; 50 | 51 | /** 52 | * Error number and string 53 | */ 54 | /*protected*/ var $_errno=null; 55 | /*protected*/ var $_errstr=null; 56 | 57 | /** 58 | * Whether or not connection was aborted by calling app 59 | * Should not be written to externally 60 | */ 61 | var $aborted=false; 62 | 63 | /** 64 | * Whether this fetcher can be used 65 | * @return boolean true if fetcher can be used 66 | */ 67 | //abstract static function available(); 68 | 69 | /** 70 | * Free allocated resources 71 | */ 72 | function close() {} 73 | function __destruct() { 74 | $this->close(); 75 | } 76 | 77 | /** 78 | * Fetch $url 79 | * @return true if successful, false if not, and null if aborted 80 | * if body_function not supplied, will return fetched data 81 | */ 82 | //abstract function fetch(); 83 | 84 | /** 85 | * Set $referer based on $url; uses the host of $url as the referer 86 | */ 87 | function setRefererFromUrl() { 88 | $purl = parse_url($this->url); 89 | $this->referer = $purl['scheme'].'://'.$purl['host'].'/'; 90 | } 91 | 92 | /** 93 | * Generate an array of headers; does not include Host or GET header 94 | */ 95 | /*protected*/ function &_generateHeaders() { 96 | $headers = array( 97 | 'Connection: close', 98 | 'Accept: */*', 99 | ); 100 | // TODO: follow_redir, encoding 101 | 102 | if(isset($this->user_agent)) 103 | $headers[] = 'User-Agent: '.$this->user_agent; 104 | if(isset($this->charset)) 105 | $headers[] = 'Accept-Charset: '.$this->charset.';q=0.5, *;q=0.2'; 106 | if(isset($this->lang)) 107 | $headers[] = 'Accept-Language: '.$this->lang.';q=0.5, *;q=0.3'; 108 | if(isset($this->referer)) 109 | $headers[] = 'Referrer: '.$this->referer; 110 | 111 | return $headers; 112 | } 113 | 114 | /** 115 | * Processes a HTTP header, and calls the meta function if necessary 116 | * @return false to abort, true otherwise 117 | */ 118 | /*protected*/ function _processHttpHeader($header) { 119 | if(!isset($this->meta_function)) return true; 120 | 121 | $result = self::_processHttpHeader_parse($header); 122 | if(empty($result)) return true; 123 | foreach($result as $mname => &$mdata) { 124 | if(!call_user_func_array($this->meta_function, array(&$this, &$mname, &$mdata))) { 125 | $this->aborted = true; 126 | return false; 127 | } 128 | } 129 | 130 | return true; 131 | } 132 | /** 133 | * Parse info from HTTP header 134 | * @return array of info retrieved, or null if nothing retrieved 135 | */ 136 | /*protected*/ static function _processHttpHeader_parse(&$header) { 137 | $header = trim($header); 138 | $p = strpos($header, ':'); 139 | if(!$p) { 140 | // look for HTTP/1.1 type header 141 | if(strtoupper(substr($header, 0, 5)) == 'HTTP/') { 142 | if(preg_match('~^HTTP/[0-9.]+ (\d+) (.*)$~i', $header, $match)) { 143 | return array('retcode' => array((int)$match[1], trim($match[2]))); 144 | } 145 | } 146 | return null; 147 | } 148 | $hdata = trim(substr($header, $p+1)); 149 | switch(strtolower(substr($header, 0, $p))) { 150 | case 'content-length': 151 | $size = (int)$hdata; 152 | if($size || $hdata === '0') { 153 | return array('size' => $size); 154 | } 155 | break; 156 | case 'content-disposition': 157 | foreach(explode(';', $hdata) as $disp) { 158 | $disp = trim($disp); 159 | if(strtolower(substr($disp, 0, 9)) == 'filename=') { 160 | $tmp = substr($disp, 9); 161 | if(!xthreads_empty($tmp)) { 162 | if($tmp[0] == '"' && $tmp[strlen($tmp)-1] == '"') 163 | $tmp = substr($tmp, 1, -1); 164 | return array('name' => trim(str_replace("\x0", '', $tmp))); 165 | } 166 | } 167 | } 168 | break; 169 | case 'content-type': 170 | return array('type' => $hdata); 171 | break; 172 | } 173 | return null; 174 | } 175 | 176 | // since fread'ing won't necessarily fill the requested buffer size... 177 | /*protected*/ static function &fill_fread(&$fp, $len) { 178 | //$fill = 0; 179 | $ret = ''; 180 | while(!feof($fp) && $len > 0) { 181 | $data = fread($fp, $len); 182 | $len -= strlen($data); 183 | $ret .= $data; 184 | } 185 | return $ret; 186 | } 187 | 188 | /** 189 | * Get error code/message 190 | * @param [out] variable to receive error code 191 | * @return error message 192 | * special messages are 'cantwritesocket', 'headernotfound' and 'urlopenfailed' 193 | */ 194 | function getError(&$code=null) { 195 | $code = $this->errno; 196 | return $this->errstr; 197 | } 198 | } 199 | 200 | /** 201 | * Fetch URL through cURL 202 | */ 203 | class XTUrlFetcher_Curl extends XTUrlFetcher { 204 | /** 205 | * Internal cURL resource handle 206 | */ 207 | /*private*/ var $_ch; 208 | /*private*/ var $_redirectUrl; 209 | /*private*/ var $_headers; 210 | 211 | /*const*/ var $name = 'cURL'; 212 | 213 | static function available($scheme='') { 214 | return (!$scheme || $scheme != 'data') && function_exists('curl_init'); 215 | } 216 | 217 | function XTUrlFetcher_Curl() { 218 | $this->_ch = curl_init(); 219 | } 220 | function close() { 221 | if(isset($this->_ch)) 222 | @curl_close($this->_ch); // curl_close may not succeed if called within callback 223 | } 224 | 225 | function fetch() { 226 | curl_setopt($this->_ch, CURLOPT_URL, $this->url); 227 | curl_setopt($this->_ch, CURLOPT_HEADER, false); 228 | curl_setopt($this->_ch, CURLOPT_TIMEOUT, $this->timeout); 229 | if(isset($this->user_agent)) 230 | curl_setopt($this->_ch, CURLOPT_USERAGENT, $this->user_agent); 231 | if(isset($this->referer)) 232 | curl_setopt($this->_ch, CURLOPT_REFERER, $this->referrer); 233 | curl_setopt($this->_ch, CURLOPT_ENCODING, ''); 234 | 235 | if(isset($this->meta_function)) { 236 | // can only use this if http/s request 237 | if(strtolower(substr($this->url, 0, 4)) == 'http') 238 | curl_setopt($this->_ch, CURLOPT_HEADERFUNCTION, array($this, 'curl_header_func')); 239 | } 240 | if(isset($this->body_function)) { 241 | curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, false); 242 | curl_setopt($this->_ch, CURLOPT_WRITEFUNCTION, array($this, 'curl_body_func')); 243 | } else 244 | curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, true); 245 | 246 | // because cURL's auto redirection won't work for us (seems to be incompatible with a header function), we have to emulate it 247 | for($i=0; $i<$this->follow_redir; ++$i) { 248 | $this->_redirectUrl = false; 249 | $this->_headers = array(); 250 | $success = curl_exec($this->_ch); 251 | if(!$this->_redirectUrl) break; 252 | 253 | // do a redirect 254 | curl_setopt($this->_ch, CURLOPT_REFERER, $this->url); 255 | $this->url = $this->_redirectUrl; 256 | curl_setopt($this->_ch, CURLOPT_URL, $this->url); 257 | } 258 | // will execute if body function not set (or no body returned?) 259 | if(!empty($this->_headers)) { 260 | foreach($this->_headers as $h) 261 | if(!$this->_processHttpHeader($h)) { 262 | $this->aborted = true; 263 | break; 264 | } 265 | } 266 | if($this->aborted) 267 | return null; 268 | else 269 | return $success; 270 | } 271 | 272 | function getError(&$code=null) { 273 | $this->errno = curl_errno($this->_ch); 274 | $this->errstr = curl_error($this->_ch); 275 | return parent::getError($code); 276 | } 277 | 278 | function curl_header_func(&$ch, $header) { 279 | $theader = trim($header); 280 | // intercept redirect header for our redirect emulation 281 | if($this->follow_redir && strtolower(substr($theader, 0, 9)) == 'location:') { 282 | $this->_redirectUrl = trim(substr($theader, 9)); 283 | $this->close(); 284 | return 0; 285 | } 286 | // gather headers - we can't process them because we may do so prematurely (we need to check all headers for a redirect first) 287 | $this->_headers[] = $header; 288 | return strlen($header); 289 | /* 290 | elseif($this->_processHttpHeader($theader)) 291 | return strlen($header); 292 | else { 293 | $this->close(); 294 | return 0; 295 | }*/ 296 | } 297 | function curl_body_func(&$ch, $data) { 298 | if(!empty($this->_headers)) { 299 | foreach($this->_headers as $h) 300 | if(!$this->_processHttpHeader($h)) { 301 | $this->aborted = true; 302 | $this->close(); 303 | return 0; 304 | } 305 | $this->_headers = null; 306 | } 307 | if(call_user_func_array($this->body_function, array(&$this, &$data))) 308 | return strlen($data); 309 | else { 310 | $this->aborted = true; 311 | $this->close(); 312 | return 0; 313 | } 314 | } 315 | } 316 | 317 | 318 | /** 319 | * Fetch URL through Sockets 320 | */ 321 | class XTUrlFetcher_Socket extends XTUrlFetcher { 322 | /** 323 | * Optional preferred character set to send via Accept header 324 | */ 325 | var $charset; 326 | /** 327 | * Optional preferred language set to send via Accept header 328 | */ 329 | var $lang; 330 | 331 | /*const*/ var $name = 'Sockets'; 332 | 333 | static function available($scheme='') { 334 | return (!$scheme || $scheme == 'http' || $scheme == 'https') && function_exists('fsockopen'); 335 | } 336 | 337 | function fetch() { 338 | $redirs = $this->follow_redir; 339 | $url = $this->url; 340 | do { 341 | $redirect = false; 342 | $purl = @parse_url($url); 343 | if(empty($purl) || !isset($purl['host'])) { 344 | $this->errno = 0; 345 | $this->errstr = 'invalidurl'; 346 | return false; 347 | } 348 | if(!isset($purl['path']) || $purl['path'] === '') 349 | $purl['path'] = '/'; 350 | if(!empty($purl['query'])) 351 | $purl['path'] .= '?'.$purl['query']; 352 | if(!$purl['port']) $purl['port'] = ($purl['scheme']=='https' ? 443:80); 353 | if(!($fr = @fsockopen(($purl['scheme']=='https'?'ssl://':'').$purl['host'], $purl['port'], $errno, $errstr, $this->timeout))) { 354 | $this->errno = $errno; 355 | $this->errstr = $errstr; 356 | return false; 357 | } 358 | @stream_set_timeout($fr, $this->timeout); 359 | $headers = array_merge(array( 360 | 'GET '.$purl['path'].' HTTP/1.1', 361 | 'Host: '.$purl['host'], 362 | ), $this->_generateHeaders()); 363 | 364 | $headers[] = "\r\n"; 365 | 366 | if(!@fwrite($fr, implode("\r\n", $headers))) { 367 | $this->errno = 0; 368 | $this->errstr = 'cantwritesocket'; 369 | fclose($fr); 370 | return false; 371 | } 372 | 373 | $databuf = ''; // returned string if no body_function defined 374 | $doneheaders = $chunked = false; 375 | while(!feof($fr)) { 376 | if(!$doneheaders) { 377 | $data = self::fill_fread($fr, 16384); 378 | $len = strlen($data); 379 | $p = strpos($data, "\r\n\r\n"); 380 | if(!$p || $p > 12288 || substr($data, 0, 4) != 'HTTP') { // should be no reason to have >12KB headers 381 | $this->errno = 0; 382 | $this->errstr = 'headernotfound'; 383 | break; 384 | } 385 | $headerdata = substr($data, 0, $p); 386 | // check redirect 387 | if($redirs && preg_match("~\r\nlocation\:([^\r\n]*?)\r\n~i", $headerdata, $match)) { 388 | $url = trim($match[1]); 389 | if($url) { 390 | $redirect = true; 391 | --$redirs; 392 | break; 393 | } 394 | } 395 | // parse headers 396 | if(isset($this->meta_function)) { 397 | foreach(explode("\r\n", $headerdata) as $header) { 398 | if(!$this->_processHttpHeader(trim($header))) { 399 | break; 400 | } 401 | } 402 | if($this->aborted) break; 403 | } 404 | // check for chunked encoding; we won't bother handling the full spec for this - for simplicity, just do the common case 405 | $chunked = preg_match("~\r\ntransfer-encoding\:\s*chunked\s*\r\n~i", $headerdata); 406 | 407 | $p += 4; 408 | $data = substr($data, $p); 409 | $len -= $p; 410 | $doneheaders = true; 411 | } else { 412 | $len = 0; 413 | while(!feof($fr) && !$len) { 414 | $data = fread($fr, 16384); 415 | $len = strlen($data); 416 | } 417 | } 418 | if($len) { 419 | if($chunked) { 420 | // TODO: simple decode chunked encoding 421 | // this is potentially tricky because the fread could potentially stop inbetween a length value 422 | } 423 | if(isset($this->body_function)) { 424 | if(!call_user_func_array($this->body_function, array(&$this, &$data))) { 425 | $this->aborted = true; 426 | break; 427 | } 428 | } else { 429 | $databuf .= $data; 430 | } 431 | } 432 | } 433 | fclose($fr); 434 | if($this->aborted) return null; 435 | if(isset($this->errstr)) return false; 436 | } while($redirect); 437 | 438 | if(isset($this->body_function)) return true; 439 | return $databuf; 440 | } 441 | } 442 | 443 | /** 444 | * Fetch URL through PHP fopen 445 | */ 446 | class XTUrlFetcher_Fopen extends XTUrlFetcher { 447 | var $name = 'fopen'; 448 | 449 | static function available($scheme='') { 450 | return ($scheme == 'data') || @ini_get('allow_url_fopen'); 451 | // data:// streams don't require allow_url_fopen 452 | } 453 | 454 | function fetch() { 455 | $httpopts = array( 456 | 'header' => $this->_generateHeaders(), 457 | 'max_redirects' => $this->follow_redir 458 | ); 459 | $context = stream_context_create(array( 460 | 'http' => $httpopts, 461 | 'https' => $httpopts 462 | )); 463 | //if(isset($this->user_agent)) 464 | // @ini_set('user_agent', $this->user_agent); 465 | if(!($fr = @fopen($this->url, 'rb', false, $context))) { 466 | $this->errno = 0; 467 | $this->errstr = 'urlopenfailed'; 468 | return false; 469 | } 470 | @stream_set_timeout($fr, $this->timeout); 471 | 472 | // send headers if possible 473 | $meta = @stream_get_meta_data($fr); 474 | if(isset($meta['wrapper_data'])) { 475 | // headers are appended, even onto redirects, so first try to find the last redirected-to header 476 | $firstHeader = 0; 477 | for($i=0, $c=count($meta['wrapper_data']); $i<$c; ++$i) 478 | if(substr($meta['wrapper_data'][$i], 0, 7) == 'HTTP/1.') 479 | $firstHeader = $i; 480 | foreach(array_slice($meta['wrapper_data'], $firstHeader) as $header) { 481 | if(!$this->_processHttpHeader($header)) { 482 | fclose($fr); 483 | return null; 484 | } 485 | } 486 | } 487 | 488 | 489 | $databuf = ''; // returned string if no body_function defined 490 | while(!feof($fr)) { 491 | $len = 0; 492 | while(!feof($fr) && !$len) { 493 | $data = fread($fr, 16384); 494 | $len = strlen($data); 495 | } 496 | 497 | if($len) { 498 | if(isset($this->body_function)) { 499 | if(!call_user_func_array($this->body_function, array(&$this, &$data))) { 500 | $this->aborted = true; 501 | break; 502 | } 503 | } else { 504 | $databuf .= $data; 505 | } 506 | } 507 | } 508 | fclose($fr); 509 | if($this->aborted) return null; 510 | //if(isset($this->errstr)) return false; 511 | 512 | if(isset($this->body_function)) return true; 513 | return $databuf; 514 | } 515 | } 516 | 517 | /** 518 | * URL fetcher factory method 519 | * @return a new XTUrlFetcher object, depending on what is available 520 | */ 521 | function getXTUrlFetcher($scheme='') { 522 | $scheme = strtolower($scheme); 523 | if($p = strpos($scheme, ':')) 524 | $scheme = substr($scheme, 0, $p); 525 | if(XTUrlFetcher_Curl::available($scheme)) 526 | return new XTUrlFetcher_Curl; 527 | if(XTUrlFetcher_Socket::available($scheme)) 528 | return new XTUrlFetcher_Socket; 529 | if(XTUrlFetcher_Fopen::available($scheme)) 530 | return new XTUrlFetcher_Fopen; 531 | 532 | return null; // nothing can fetch it for us... >_> 533 | } 534 | 535 | 536 | // TODO add: support for data:// stream, FTP in cURL/fsockopen? 537 | -------------------------------------------------------------------------------- /Upload/jscripts/xthreads_attach_input.js: -------------------------------------------------------------------------------- 1 | function xta_load() { 2 | var s, each, child, parnt; 3 | if(typeof jQuery != 'undefined') { 4 | s = jQuery; 5 | each = function(c, f) { 6 | c.each(function(k,v){ 7 | f(v); 8 | }); 9 | }; 10 | child = function(e,s) { 11 | return jQuery(e).find(s); 12 | }; 13 | parnt = function(e,s) { 14 | return jQuery(e).parents(s); 15 | }; 16 | } else { 17 | s = $$; 18 | each = function(c, f) { 19 | c.each(function(v,k){ 20 | f(v); 21 | }); 22 | }; 23 | child = function(e,s) { 24 | if(typeof Prototype.Selector != 'undefined') // MyBB 1.6.x 25 | return Prototype.Selector.select(s, e); 26 | else // MyBB 1.4.x 27 | return Selector.findChildElements(e, [s]); 28 | }; 29 | parnt = function(e,s) { 30 | var r = $(e).up(s); 31 | return r ? [r]:r; 32 | }; 33 | } 34 | 35 | // hack to clear the contents of a file input 36 | clear_file = function(e) { 37 | var n = document.createElement(e.tagName); 38 | var a = e.attributes; 39 | for(var i=0; i 2 | 3 | 4 | 5 | 6 |   7 | 8 | -------------------------------------------------------------------------------- /Upload/uploads/xthreads_ul/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |   7 | 8 | -------------------------------------------------------------------------------- /Upload/xthreads_attach.php: -------------------------------------------------------------------------------- 1 | Download Error'.$msg.''); 37 | } 38 | 39 | if(LOAD_SESSION) { 40 | // we'll be lazy and just load the full MyBB core 41 | define('IN_MYBB', 1); 42 | define('THIS_SCRIPT', 'xthreads_attach.php'); 43 | define('NO_ONLINE', 1); // TODO: check 44 | 45 | require './global.php'; 46 | 47 | // TODO: disable calling send_page_headers() 48 | 49 | // TODO: maybe do online for WOL 50 | } 51 | else { 52 | 53 | // do some basic initialisation 54 | error_reporting(E_ALL ^ E_NOTICE); // this script works fine with E_ALL, however, we'll be compatible with MyBB 55 | // remove unnecessary stuff 56 | unset($HTTP_SERVER_VARS, $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, $HTTP_POST_FILES, $HTTP_ENV_VARS, $HTTP_SESSION_VARS); 57 | unset($_GET, $_POST, $_FILES, $_ENV); 58 | foreach(array('GLOBALS', '_COOKIE', '_REQUEST', '_SERVER') as $p) 59 | if(isset($_REQUEST[$p]) || isset($_FILES[$p]) || isset($_COOKIE[$p])) { 60 | fatal_error('400 Bad Request', 'Bad request'); 61 | } 62 | // script will work if magic quotes is on, unless filenames happen to have quotes or something 63 | if(function_exists('set_magic_quotes_runtime')) 64 | @set_magic_quotes_runtime(0); 65 | @ini_set('magic_quotes_runtime', 0); 66 | // will also work with register globals, so we won't bother with these 67 | 68 | if(function_exists('date_default_timezone_set') && !ini_get('date.timezone')) 69 | date_default_timezone_set('GMT'); // what MyBB does 70 | 71 | define('MYBB_ROOT', dirname(__FILE__).'/'); 72 | if(!file_exists(MYBB_ROOT.'cache/xthreads.php')) 73 | fatal_error('500 Internal Server Error', 'XThreads is not installed.'); 74 | @include_once(MYBB_ROOT.'cache/xthreads.php'); // include defines 75 | } 76 | 77 | 78 | // put everything in function to limit scope (and memory usage by relying on PHP to garbage collect all the unreferenced variables) 79 | function do_processing() { 80 | 81 | if(isset($GLOBALS['mybb']) && is_object($GLOBALS['mybb'])) { 82 | $basedir = $GLOBALS['mybb']->settings['uploadspath'].'/xthreads_ul/'; 83 | $bburl = $GLOBALS['mybb']->settings['bburl']; 84 | } else { 85 | if(file_exists(MYBB_ROOT.'inc/settings.php')) { 86 | require MYBB_ROOT.'inc/settings.php'; 87 | // TODO: perhaps have a dedicated setting for this one 88 | $basedir = $settings['uploadspath'].'/xthreads_ul/'; 89 | $bburl = $settings['bburl']; 90 | unset($settings); 91 | } 92 | else // use default 93 | $basedir = './uploads/xthreads_ul/'; 94 | $bburl = 'htp://example.com/'; // dummy 95 | } 96 | 97 | if(!@is_dir($basedir)) 98 | fatal_error('500 Internal Server Error', 'Can\'t find XThreads base directory.'); 99 | 100 | // parse input filename 101 | if(isset($_REQUEST['file']) && $_REQUEST['file'] !== '') { // using query string 102 | $pathInfoEncoded = false; 103 | $_SERVER['PATH_INFO'] = '/'.$_REQUEST['file']; 104 | //if(get_magic_quotes_gpc()) 105 | // $_SERVER['PATH_INFO'] = stripslashes($_SERVER['PATH_INFO']); 106 | } else { 107 | $pathInfoEncoded = true; 108 | if(!isset($_SERVER['PATH_INFO']) && isset($_SERVER['SCRIPT_NAME'])) { 109 | $snlen = strlen($_SERVER['SCRIPT_NAME']); 110 | foreach(array('PHP_SELF', 'REQUEST_URI') as $key) { 111 | if(isset($_SERVER[$key]) && substr($_SERVER[$key], 0, $snlen+1) == $_SERVER['SCRIPT_NAME'].'/') { 112 | $_SERVER['PATH_INFO'] = substr($_SERVER[$key], $snlen); 113 | break; 114 | } 115 | } 116 | } 117 | 118 | if(!isset($_SERVER['PATH_INFO']) || !$_SERVER['PATH_INFO']) 119 | fatal_error('400 Bad Request', 'No parameters specified.'); 120 | } 121 | 122 | // xtattachment URL test during installation 123 | if(substr($_SERVER['PATH_INFO'], 0, 5) == '/test') { 124 | die(md5(substr($_SERVER['PATH_INFO'], 6))); 125 | } 126 | 127 | // maybe disallow \:*?"<>| in filenames, but then, they're valid *nix names... 128 | if(!preg_match('~^[/|]([0-9]+)_([0-9]+)_([0-9a-fA-F]{8}0?)[/|]([0-9a-fA-F]{32}[/|])?([^/]*)([/|]thumb([a-zA-Z0-9_]+))?$~', $_SERVER['PATH_INFO'], $match)) 129 | fatal_error('400 Bad Request', 'Received malformed request string.'); 130 | 131 | $thumb = null; 132 | if(isset($match[6])) $thumb =& $match[7]; 133 | //if(isset($_REQUEST['thumb']) && preg_match('~^[0-9]+x[0-9]+$~', $_REQUEST['thumb'])) 134 | if($thumb) 135 | $fext = $thumb.'.thumb'; 136 | else 137 | $fext = 'upload'; 138 | 139 | if($pathInfoEncoded) 140 | $match[5] = rawurldecode($match[5]); 141 | $match[5] = str_replace("\0", '', $match[5]); 142 | $month_dir = 'ts_'.floor($match[2] / 1000000).'/'; 143 | if(XTHREADS_EXPIRE_ATTACH_LINK || XTHREADS_ATTACH_LINK_IPMASK) { 144 | // decode special thing 145 | require MYBB_ROOT.'inc/xthreads/xt_attachfuncs.php'; 146 | $match[3] = xthreads_attach_decode_hash($match[3]); 147 | // note that $match[3] isn't secret (eg sent as an ETag) 148 | } 149 | $fn = 'file_'.$match[1].'_'.$match[3].'_'.preg_replace('~[^a-zA-Z0-9_\-%]~', '', str_replace(array(' ', '.', '+'), '_', $match[5])).'.'.$fext; 150 | if(file_exists($basedir.$month_dir.$fn)) 151 | $fn_rel = $month_dir.$fn; 152 | elseif(file_exists($basedir.$fn)) 153 | $fn_rel = $fn; 154 | else 155 | fatal_error('404 Not Found', 'Specified attachment not found.'.(XTHREADS_EXPIRE_ATTACH_LINK ? ' It\'s possible that the link has expired - try going back, refresh the page, and access the attachment again.':'')); 156 | $fn = $basedir.$fn_rel; 157 | 158 | // check to see if unmodified/cached 159 | $cached = false; 160 | $modtime = filemtime($fn); 161 | if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && ($cachetime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) && $cachetime > 0) { 162 | $cached = ($cachetime >= $modtime); 163 | } 164 | $etag = '"xthreads_attach_'.substr(md5($bburl), 0, 8).'_'.$match[1].'_'.$match[2].'_'.$match[3].'"'; 165 | if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && ($etag_match = trim($_SERVER['HTTP_IF_NONE_MATCH']))) { 166 | if($etag_match == $etag) $cached = true; 167 | elseif(strpos($etag_match, ',') && in_array($etag, array_map('trim', explode(',', $etag_match)))) 168 | $cached = true; 169 | else 170 | $cached = false; 171 | } 172 | 173 | if($cached) { 174 | header('HTTP/1.1 304 Not Modified'); 175 | header('ETag: '.$etag); 176 | header('Vary: Range'); 177 | exit; 178 | } 179 | 180 | global $fp, $fsize, $range_start, $range_end, $plugins; 181 | if(is_object($plugins)) { 182 | $evalcode = ''; 183 | $evalcode = $plugins->run_hooks('xthreads_attachment_before_headers', $evalcode); 184 | if($evalcode) 185 | eval($evalcode); 186 | unset($evalcode); 187 | } 188 | 189 | if(!XTHREADS_PROXY_REDIR_HEADER_PREFIX) { 190 | $fp = fopen($fn, 'rb'); 191 | if(!$fp) 192 | fatal_error('500 Internal Server Error', 'Failed to open file.'); 193 | 194 | $range_start = 0; 195 | $fsize = filesize($fn); 196 | $range_end = $fsize-1; 197 | 198 | if(isset($_SERVER['HTTP_RANGE']) && ($p = strpos($_SERVER['HTTP_RANGE'], '='))) { 199 | $rangestr = substr($_SERVER['HTTP_RANGE'], $p+1); 200 | $p = strpos($rangestr, '-'); 201 | $ostart = (int)substr($rangestr, 0, $p); 202 | $oend = (int)substr($rangestr, $p+1); 203 | 204 | if($oend && $oend < $range_end && $oend > $range_start) 205 | $range_end = $oend; 206 | if($ostart && $ostart > $range_start && $ostart < $range_end) 207 | $range_start = $ostart; 208 | } 209 | 210 | if($range_start || $range_end != $fsize-1) { 211 | // check If-Range header 212 | $cached = true; // reuse this variable 213 | if(isset($_SERVER['HTTP_IF_RANGE']) && ($etag_match = trim($_SERVER['HTTP_IF_RANGE']))) { 214 | if($etag_match != $etag && (!strpos($etag_match, ',') || !in_array($etag, array_map('trim', explode(',', $etag_match))))) { 215 | $cached = false; 216 | // re-send whole file 217 | $range_start = 0; 218 | $range_end = $fsize -1; 219 | } 220 | } 221 | if($cached) 222 | header('HTTP/1.1 206 Partial Content'); 223 | } 224 | 225 | if(XTHREADS_COUNT_DOWNLOADS == 1 && !$thumb) increment_downloads($match[1]); 226 | header('Accept-Ranges: bytes'); 227 | } else { 228 | if(XTHREADS_COUNT_DOWNLOADS && !$thumb) increment_downloads($match[1]); 229 | } 230 | header('Allow: GET, HEAD'); 231 | header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modtime).' GMT'); 232 | if(XTHREADS_CACHE_TIME) { 233 | header('Expires: '.gmdate('D, d M Y H:i:s', time() + XTHREADS_CACHE_TIME).' GMT'); 234 | header('Cache-Control: max-age='.XTHREADS_CACHE_TIME); 235 | } else { 236 | header('Expires: Sat, 1 Jan 2000 01:00:00 GMT'); 237 | header('Cache-Control: no-cache, must-revalidate'); 238 | header('Pragma: no-cache'); 239 | } 240 | header('ETag: '.$etag); 241 | header('Vary: Range'); 242 | 243 | // check referrer? 244 | 245 | // TODO: perhaps think of a way to store thumbs w/ proper extension 246 | // try to determine Content-Type 247 | $content_type = ''; 248 | $p = strrpos($match[5], '.'); 249 | if($p) { 250 | $ext = strtolower(substr($match[5], $p+1)); 251 | $exts = array( 252 | 'txt' => 'text/plain', 253 | 'jpg' => 'image/jpeg', 254 | 'jpe' => 'image/jpeg', 255 | 'jpeg' => 'image/jpeg', 256 | 'gif' => 'image/gif', 257 | 'png' => 'image/png', 258 | 'bmp' => 'image/bmp', 259 | 'svg' => 'image/svg+xml', 260 | 'tif' => 'image/tiff', 261 | 'tiff' => 'image/tiff', 262 | 'ico' => 'image/x-icon', 263 | 'wmf' => 'application/x-msmetafile', 264 | 'zip' => 'application/zip', 265 | 'rar' => 'application/x-rar-compressed', 266 | '7z' => 'application/x-7z-compressed', 267 | 'doc' => 'application/msword', 268 | 'docx' => 'application/msword', 269 | 'xls' => 'application/msexcel', 270 | 'xlsx' => 'application/msexcel', 271 | 'ppt' => 'application/mspowerpoint', 272 | 'pptx' => 'application/mspowerpoint', 273 | 'mdb' => 'application/x-msaccess', 274 | 'pub' => 'application/x-mspublisher', 275 | 'pdf' => 'application/pdf', 276 | 'gz' => 'application/x-gzip', 277 | 'tar' => 'application/x-tar', 278 | 'htm' => 'text/html', 279 | 'html' => 'text/html', 280 | 'css' => 'text/css', 281 | 'js' => 'text/javascript', 282 | 'mid' => 'audio/mid', 283 | 'mp3' => 'audio/mpeg', 284 | 'flac' => 'audio/flac', 285 | 'ogg' => 'audio/ogg', 286 | 'wav' => 'audio/x-wav', 287 | 'mpeg' => 'video/mpeg', 288 | 'mpg' => 'video/mpeg', 289 | 'mov' => 'video/quicktime', 290 | 'avi' => 'video/x-msvideo', 291 | 'mp4' => 'video/mp4', 292 | 'm4a' => 'audio/mp4', 293 | 'mkv' => 'video/x-matroska', 294 | 'mka' => 'audio/x-matroska', 295 | 'ogv' => 'video/ogg', 296 | 'wmv' => 'audio/x-ms-wmv', 297 | ); 298 | if(XTHREADS_MIME_OVERRIDE) { 299 | foreach(explode(',', strtolower(XTHREADS_MIME_OVERRIDE)) as $mime_entry) { 300 | $mime_info = explode(' ', trim($mime_entry), 2); 301 | if(isset($mime_info[1]) && $mime_info[1] !== '') { 302 | $mime_info[0] = trim($mime_info[0]); 303 | foreach(explode(' ', $mime_info[1]) as $mime_ext) { 304 | if(($mime_ext = trim($mime_ext)) !== '') 305 | $exts[$mime_ext] = $mime_info[0]; 306 | } 307 | } 308 | } 309 | } 310 | if(isset($exts[$ext])) 311 | $content_type = $exts[$ext]; 312 | unset($exts); 313 | 314 | if(!$content_type) { 315 | // try MyBB's attachment cache if cached to files 316 | if(file_exists(MYBB_ROOT.'cache/attachtypes.php')) { 317 | @include MYBB_ROOT.'cache/attachtypes.php'; 318 | if(isset($attachtypes) && is_array($attachtypes) && isset($attachtypes[$ext])) { 319 | $content_type = $attachtypes[$ext]['mimetype']; 320 | } 321 | unset($attachtypes); 322 | } 323 | } 324 | } 325 | if(!$content_type) { 326 | // try system MIME file 327 | if(function_exists('mime_content_type')) 328 | //$content_type = @mime_content_type($match[5]); 329 | $content_type = @mime_content_type($fn); 330 | elseif(function_exists('finfo_open') && ($fi = @finfo_open(FILEINFO_MIME))) { 331 | $content_type = @finfo_file($fi, $fn); 332 | finfo_close($fi); 333 | } 334 | } 335 | if(!$content_type) // fallback 336 | $content_type = 'application/octet-stream'; 337 | header('Content-Type: '.$content_type); 338 | 339 | if(!$thumb) { // don't send disposition for thumbnails 340 | $disposition = 'attachment'; 341 | if(!isset($_REQUEST['download']) || !$_REQUEST['download']) 342 | if(!isset($_SERVER['HTTP_USER_AGENT']) || strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'msie') === false) { 343 | switch(strtolower($content_type)) { 344 | case 'text/plain': case 'text/css': case 'text/javascript': 345 | case 'application/pdf': 346 | $disposition = 'inline'; 347 | break; 348 | default: 349 | if(in_array(substr($content_type, 0, 6), array('image/', 'audio/', 'video/'))) 350 | $disposition = 'inline'; 351 | // Does this work well with IE's type sniffing? 352 | } 353 | } 354 | // TODO: does this work properly with UTF-8 encoded names? 355 | header('Content-Disposition: '.$disposition.'; filename="'.strtr($match[5], array('"'=>'\\"', "\r"=>'', "\n"=>'')).'"'); 356 | } 357 | 358 | if(XTHREADS_PROXY_REDIR_HEADER_PREFIX) { 359 | // we terminate here and let the webserver do the rest of the work 360 | header(XTHREADS_PROXY_REDIR_HEADER_PREFIX.$fn_rel); 361 | exit; 362 | } 363 | 364 | if($range_end < 0) { 365 | // this is a 0 byte file 366 | header('Content-Length: 0'); 367 | fclose($fp); 368 | exit; 369 | } 370 | 371 | header('Content-Length: '.($range_end - $range_start + 1)); 372 | header('Content-Range: bytes '.$range_start.'-'.$range_end.'/'.$fsize); 373 | if(!$range_start && $range_end == $fsize-1 && strlen($match[4]) == 33) 374 | header('Content-MD5: '.base64_encode(pack('H*', substr($match[4], 0, 32)))); 375 | 376 | if(isset($_SERVER['REQUEST_METHOD'])) 377 | $reqmeth = strtoupper($_SERVER['REQUEST_METHOD']); 378 | else 379 | $reqmeth = 'GET'; 380 | 381 | if($reqmeth == 'HEAD') { 382 | fclose($fp); 383 | exit; 384 | } 385 | 386 | if(XTHREADS_COUNT_DOWNLOADS == 2 && !$thumb) 387 | $GLOBALS['aid'] = $match[1]; // increment download below 388 | 389 | } do_processing(); 390 | 391 | // kill unneeded variables - save memory as this PHP thread may last a while on the server especially for larger downloads 392 | unset($_REQUEST, $_COOKIE, $_SERVER); 393 | /* $keepvars = array('keepvars'=>1, 'k'=>1, 'v'=>1, 'GLOBALS'=>1, 'fp'=>1, 'fsize'=>1, 'range_start'=>1, 'range_end'=>1, 'thumb'=>1, 'match'=>1); 394 | // note, thumb may be a reference to match 395 | foreach($GLOBALS as $k => &$v) { 396 | if(!isset($keepvars[$k])) 397 | unset($GLOBALS[$k]); 398 | } 399 | unset($keepvars, $k, $v); */ 400 | 401 | if(LOAD_SESSION) unset($mybb, $db); // TODO: maybe also unload other vars 402 | 403 | if(!function_exists('stream_copy_to_stream')) { 404 | function stream_copy_to_stream($source, $dest, $maxlength=-1, $offset=0) { 405 | if($offset) 406 | fseek($source, $offset, SEEK_CUR); 407 | $copied = 0; 408 | while(!feof($source) && ($maxlength == -1 || $copied < $maxlength)) { 409 | $len = 16384; 410 | if($maxlength > -1) $len = min($maxlength-$copied, $len); 411 | $data = fread($source, $len); 412 | $copied += strlen($data); 413 | fwrite($dest, $data); 414 | } 415 | return $copied; 416 | } 417 | } 418 | 419 | if(is_object($plugins)) $plugins->run_hooks('xthreads_attachment_before_download'); 420 | 421 | $fout = fopen('php://output', 'wb'); // this call shouldn't fail, right? 422 | 423 | if($range_start) 424 | fseek($fp, $range_start); 425 | 426 | if($range_end == $fsize-1) { 427 | unset($range_start, $range_end, $fsize); 428 | stream_copy_to_stream($fp, $fout); 429 | //while(!feof($fp)) echo fread($fp, 16384); 430 | if(isset($aid)) increment_downloads($aid); 431 | } else { 432 | $bytes = $range_end - $range_start + 1; 433 | unset($aid, $range_start, $range_end, $fsize); 434 | stream_copy_to_stream($fp, $fout, $bytes); 435 | /* unset($aid, $range_start, $range_end, $fsize); 436 | while(!feof($fp) && $bytes > 0) { 437 | $bufsize = min($bytes, 16384); 438 | echo fread($fp, $bufsize); 439 | $bytes -= $bufsize; 440 | } */ 441 | } 442 | 443 | fclose($fp); 444 | fclose($fout); 445 | 446 | 447 | function increment_downloads($aid) { 448 | // if DB is loaded, use it 449 | if(isset($GLOBALS['db']) && is_object($GLOBALS['db'])) { 450 | $GLOBALS['db']->write_query('UPDATE '.$db->table_prefix.'xtattachments SET downloads=downloads+1 WHERE aid='.(int)$aid, 1); 451 | return; 452 | } 453 | 454 | // otherwise, load config + MyBB's DB engine 455 | if(!file_exists(MYBB_ROOT.'inc/config.php')) return; 456 | require MYBB_ROOT.'inc/config.php'; 457 | if(!isset($config['database']) || !is_array($config['database'])) return; 458 | 459 | // create dummy classes before loading DB 460 | class dummy_mybb { 461 | var $debug_mode = false; 462 | } 463 | $GLOBALS['mybb'] = new dummy_mybb; 464 | 465 | // functions required by MyBB >= 1.7 466 | if(!function_exists('get_execution_time')) { 467 | function get_execution_time() { 468 | static $time_start; 469 | if($time_start) { 470 | $total = microtime(true) - $time_start; 471 | $time_start = 0; 472 | return max(0, $total); 473 | } 474 | $time_start = microtime(true); 475 | } 476 | } 477 | if(!function_exists('format_time_duration')) { 478 | function format_time_duration($time) { 479 | if(!is_numeric($time)) return '-'; 480 | // drop microseconds case - no-one really cares 481 | if($time < 1) 482 | return number_format(round(1000 * $time, 2)).' ms'; 483 | else 484 | return round($time, 3).' seconds'; 485 | } 486 | } 487 | 488 | // for some reason, MyBB doesn't load these from the DB classes themselves, so we have to manually load them ourselves 489 | if(file_exists(MYBB_ROOT.'inc/db_base.php')) { // MyBB >= 1.8.4 490 | include_once MYBB_ROOT.'inc/db_base.php'; 491 | } 492 | if(file_exists(MYBB_ROOT.'inc/AbstractPdoDbDriver.php')) { // MyBB >= 1.8.27 493 | include_once MYBB_ROOT.'inc/AbstractPdoDbDriver.php'; 494 | } 495 | $dbclass = 'db_'.$config['database']['type']; 496 | require_once MYBB_ROOT.'inc/'.$dbclass.'.php'; 497 | if(!class_exists($dbclass)) return; 498 | $db = new $dbclass; 499 | if(!extension_loaded($db->engine)) { 500 | if(!function_exists('dl')) return; 501 | if(DIRECTORY_SEPARATOR == '\\') 502 | @dl('php_'.$db->engine.'.dll'); 503 | else 504 | @dl($db->engine.'.so'); 505 | if(!extension_loaded($db->engine)) return; 506 | } 507 | 508 | // connect to DB 509 | define('TABLE_PREFIX', $config['database']['table_prefix']); 510 | $db->connect($config['database']); 511 | $db->set_table_prefix(TABLE_PREFIX); 512 | $db->type = $config['database']['type']; 513 | 514 | if(isset($GLOBALS['plugins']) && is_object($GLOBALS['plugins'])) { 515 | $GLOBALS['plugins']->run_hooks('xthreads_attachment_increment_dlcount', $aid); 516 | } 517 | 518 | // so we do all the above just to run an update query :P 519 | $db->write_query('UPDATE '.$db->table_prefix.'xtattachments SET downloads=downloads+1 WHERE aid='.(int)$aid, 1); 520 | $db->close(); 521 | unset($db, $GLOBALS['mybb']); 522 | } 523 | -------------------------------------------------------------------------------- /readme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redirecting... 4 | 5 | 6 | 7 | Redirecting... 8 | 9 | --------------------------------------------------------------------------------