Files
Rusty Shackleford af24bd579f Initial commit
2014-05-24 05:27:14 +04:00

629 lines
22 KiB
PHP

<?php # KT_HANDLE # Transmitted information handler
use Katana\Seal as Seal;
import(Module::regex);
final class Handle extends Protector {
/**
* Escape name and generate tripcode if needed
*/
static function name($name){
if(preg_match(Regex::TRIPCODE, $name, $regs) && X::$mode&Seal::NAME){
$cap = y(mb_convert_encoding($regs[2],'SJIS','UTF-8'),$cap);
if(strpos($name,'#')===false) $cap_delimiter = '!';
elseif(strpos($name,'!')===false) $cap_delimiter = '#';
else $cap_delimiter = (strpos($name,'#') < strpos($name,'!'))?'#':'!';
$real = mb_substr($name,0,mb_strpos($name,$cap_delimiter));
if(preg_match("/(.*)(".$cap_delimiter.")(.*)/",$cap,$regs_secure)){
$cap = $regs_secure[1];
$cap_secure = $regs_secure[3];
$is_secure_trip = true;
} else $is_secure_trip = false;
$trip = "";
if(!empty($cap)){
$cap = strtr($cap, "&amp;", "&");
$cap = strtr($cap, "&#44;", ", ");
$salt = substr($cap . "H.", 1, 2);
$salt = preg_replace("/[^\.-z]/", ".", $salt);
$salt = strtr($salt, ":;<=>?@[\\]^_`", "ABCDEFGabcdef");
$trip = substr(crypt($cap, $salt), -10);
}
if($is_secure_trip){
if(!empty($cap)) $trip .= "!";
$trip .= "!".substr(md5($cap_secure.SEED),2,10);
}
return array($real,$trip);
}
return array($name,null);
}
/**
* Escape and format post text
*/
static function text($string,$literal){
if(mb_strlen($string) > X::$conf['kt']['textsize']){
$string = mb_substr($string, 0, X::$conf['kt']['textsize'] - 1).'&hellip;';
new Note('Text was too long, truncated.', Note::WARNING);
}
# FORMATTING
$layout = function($string) use ($literal){
$pat = array(
#Regex::GENERIC_DASH => '&nbsp;&mdash;&nbsp;',
Regex::GENERIC_REF_NUM => '<a class="ref" rel="$2" href="/'.$literal.'/post/$2">&gt;&gt;$2</a>',
Regex::GENERIC_REF_LIT => '<a class="ref" rev="$3" rel="$2" href="/$2/post/$3">&gt;&gt;&gt;$2/$3</a>',
Regex::GENERIC_QUOTE => '<blockquote class="quotes">$0</blockquote>',
#Regex::TRIFORCE => '<pre>&nbsp;&#9650;\n&#9650;&nbsp;&#9650;</pre><br/>',
Regex::T_WM_SPOILER => '<mark class="spoiler">$2</mark>',
Regex::T_WM_STRIKEOUT => '<strike>$2</strike>',
Regex::T_WM_BOLD => '<strong class="bold">$2</strong>',
Regex::T_WM_ITALIC => '<em class="italic">$2</em>',
Regex::T_WM_UNDERLINE => '<ins class="underline">$2</ins>',
Regex::T_WM_HEADER => '<h3 class="header">$2</h3>',
Regex::T_BB_FORMAT => '<$1>$2</$1>',
Regex::T_BB_SPOILER => '<mark class="spoiler">$2</mark>',
Regex::T_BB_TEXTWALL => '<details class="textwall"><summary>Cut</summary>$2</details>',
Regex::T_BB_RED => '<span style="color:crimson">$2</span>',
Regex::T_BB_GREEN => '<span style="color:lightgreen">$2</span>',
Regex::T_BB_BLUE => '<span style="color:cornflowerblue">$2</span>'
);
return preg_replace(array_keys($pat),array_values($pat),$string);
};
# RICH TEXT FEATURES
$string = preg_replace_callback_array([
Regex::GENERIC_USERAGENT => function($match){ import(System::CLIENT); return '<span class="special">'.Client::browser().' on '.Client::OS().'</span>'; },
Regex::GENERIC_POSTCOUNT => function($match){ import(System::CLIENT); return '<span class="special">'.Client::postcount().'</span>'; },
Regex::GENERIC_DICE => function($match){
$dices = abs(intval($match[1]));
$sides = abs(intval($match[2]));
$count = abs(intval(y($match[4],0)))+1;
$final = 0; for($j=0;$j<$count;$j++) for($i=0;$i<$dices;$i++) $final += mt_rand(1,$sides);
return "<span class='dice' title='{$dices} dices, {$sides} sides, {$count} attempts'><i class='icon-stop'></i>{$final}</span>";
},
Regex::T_RAW => function($matches){
return '<pre>'.Regex::escape(str_replace('<br/>',"\r\n",$matches[2])).'</pre>';
},
Regex::T_LATEX => function($matchee){
if(X::$conf['xt']['latex']){
#
}
return '<pre class="code latex">'.Regex::escape(str_replace('<br/>',"\r\n",$matches[2])).'</pre>';
},
Regex::T_WM_CODE => function($matches){
if(X::$conf['xt']['geshi']){
#
}
return '<br/><code class="code">'.Regex::escape(trim(str_replace('<br/>',"\r\n",$matches[2]))).'</code>';
},
Regex::T_BB_CODE => function($matches){
if(X::$conf['xt']['geshi']){
#
}
return '<br/><code class="code">'.Regex::escape(trim(str_replace('<br/>',"\r\n",$matches[2]))).'</code>';
},
Regex::GENERIC_LINK => function($matches){
return $matches[1].$matches[2].'<noindex><a href="'.$matches[3].'" target="_blank" rel="noreferrer">'.$matches[3].'</a></noindex>';
#return '<a href="'.$matches[0].'" target="_blank" rel="nofollow">'.$matches[0].'</a>';
},
Regex::FS_GOOGLE => function($matches){
return '<a href="https://www.google.com/search?q='
.urlencode($matches[1])
.'" rel="nofollow" target="_blank" class="goto-google">'.$matches[1].'</a>';
},
Regex::FS_DDG => function($matches){
return '<a href="https://duckduckgo.com/?q='
.urlencode($matches[1])
.'" rel="nofollow" target="_blank" class="goto-ddg">'.$matches[1].'</a>';
},
Regex::FS_WIKI => function($matches){
return '<a href="https://ru.wikipedia.org/wiki/'
.urlencode($matches[1])
.'" rel="nofollow" target="_blank" class="goto-wiki">'.$matches[1].'</a>';
},
Regex::FS_WOLFRAM => function($matches){
return '<a href="https://www.wolframalpha.com/input/?i='
.urlencode($matches[1])
.'" rel="nofollow" target="_blank" class="goto-wolfram">'.$matches[1].'</a>';
},
Regex::FS_GIT => function($matches){
return '<a href="https://github.com/search?utf8=✓&q='
.urlencode($matches[1])
.'" rel="nofollow" target="_blank" class="goto-github">'.$matches[1].'</a>';
}
], $string);
$string = $layout($string);
return $string;
}
/**
* Escape and format external service links
*/
static function link($url){
$result = null;
if(preg_match(Regex::INTERNAL, $url) || preg_match(Regex::EXTERNAL, $url)){
$preg = [
'img' => Regex::TYPE_IMAGE,
'aud' => Regex::TYPE_AUDIO,
'vid' => Regex::TYPE_VIDEO,
'fla' => Regex::TYPE_FLASH,
'txt' => Regex::TYPE_PASTE,
'arc' => Regex::TYPE_ARCHIVE,
'bit' => Regex::TYPE_BITTORRENT,
'key' => Regex::TYPE_CIPHER,
'vlt' => Regex::TYPE_CONTAINER
];
foreach($preg as $lst => $reg){
if(preg_match($reg,$url,$ff)){
return array(
'link'=>$url,
'name'=>"<i class='icon-{$lst}'></i>",
'info'=>"{$ff[1]}.{$ff[2]} (external)"
);
}
}
}
$type = 0;
if(preg_match(Regex::SERVICE_YT, $url)) $type = 1;
elseif(preg_match(Regex::SERVICE_VI, $url)) $type = 2;
elseif(preg_match(Regex::SERVICE_SC, $url)) $type = 3;
else deny(E::link__service_type());
$time = time().substr(microtime(),2,3);
$types = array(
1 => ["http://www.youtube.com/oembed?url=",Regex::SERVICE_YT_ID,"YouTube"],
2 => ["http://vimeo.com/api/oembed.json?url=",Regex::SERVICE_VI_ID,"Vimeo"],
3 => ["http://soundcloud.com/oembed?format=json&url=",Regex::SERVICE_SC_ID,"SoundCloud"]
);
$curl = function($svc){
$curl = curl_init($svc);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
$result = curl_exec($curl);
curl_close($curl);
$result = json_decode($result, true);
return $result;
};
$save = function($from,$to){
$path = ROOT."static/node/".X::$node."/pre/";
$prev = $path.$to;
if(file_exists($prev)) return $to;
$ch = curl_init($from);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0');
$exec = curl_exec($ch);
curl_close($ch);
$fp = fopen($prev,'w+');
fwrite($fp, $exec);
fclose($fp);
return $to;
};
$result = $curl($types[$type][0].$url);
if(empty($result)){
deny(E::link__request());
}
if(!preg_match($types[$type][1],$result['html'],$id)){
deny(E::link__uri());
}
switch($type){
case 1:
return array(
'link'=>"https://www.youtube-nocookie.com/embed/{$id[2]}",
'name'=>$save($result['thumbnail_url'],"yt-{$id[2]}.jpg"),
'info'=>$result['title']." (YouTube)"
);
break;
case 2:
return array(
'link'=>"https://player.vimeo.com/video/{$id[2]}?color=bbd531&title=0&byline=0&portrait=0&badge=0",
'name'=>$save($result['thumbnail_url'],"vi-{$id[2]}.jpg"),
'info'=>$result['title']." (Vimeo)"
);
break;
case 3:
return array(
'link'=>"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/{$id[2]}&auto_play=false&show_artwork=true",
'name'=>$save($result['thumbnail_url'],"sc-{$id[2]}.jpg"),
'info'=>$result['title']." (SoundCloud)"
);
break;
default: deny(E::link__response()); break;
}
return false;
}
/**
* Copy and format files as many-to-many structure
*/
static function files($literal, $tid, $pid, $data){
if(!empty($data)){
$iterator = 0;
$lid = X::$tree[$literal]['id'];
while(!empty($data["name"][$iterator])){
if($iterator > X::$conf['kt']['maxfiles']){ break; }
$hash = md5_file($data["tmp_name"][$iterator]);
$existing = Q::file__find_hash($literal, $lid, $hash);
if(empty($existing)){
$temp = Handle::file(
$data["name"][$iterator],
$data["tmp_name"][$iterator],
$data["size"][$iterator],
$data["type"][$iterator],
$data["error"][$iterator]
);
$unique_name = addslashes($temp['unique_name']);
$original_name = addslashes($temp['original_name']);
$finfo = addslashes($temp['finfo']);
$size = $data["size"][$iterator];
} else {
$unique_name = $existing['name'];
$original_name = $existing['orig'];
$finfo = $existing['info'];
$size = $existing['size'];
}
Q::file__push($literal, $lid, $tid, $pid, $unique_name, $original_name, $finfo, $size, $hash);
$iterator++;
}
}
}
/**
* Move and convert the file
*/
static function file($original_name, $tmp_name, $size, $type, $error){
$WINDOWS = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
if(!static::validate($tmp_name, $size, $error)){
return false;
}
$name = mb_substr(Regex::get_file_name($original_name), 0, 96);
$type = mb_substr(mb_strtolower(Regex::get_extension($original_name)), 0, 8);
$mark = time().substr(microtime(),2,3);
if(!isset($type[1])){
deny(E::file__proc_name());
}
$unique_name = $mark.".".$type;
$dir = ROOT."static/node/".X::$node;
if(!file_exists($d = "{$dir}/src")) mkdir($d, 0770);
if(!file_exists($d = "{$dir}/pre")) mkdir($d, 0770);
$src = $dir."/src/".$unique_name;
$pre = $dir."/pre/".$unique_name;
if(!move_uploaded_file($tmp_name, $src)){
deny(E::file__proc_copy());
}
if(!file_exists($src) || !file_get_contents($src,0,NULL,0,1)){
deny(E::file__proc_read_size($original_name));
}
$mime = $WINDOWS
? mb_strtolower(trim(mime_content_type($src)))
: mb_strtolower(trim(array_pop(explode(' ',`mimetype -M "{$src}"`))));
$information = ""; $dim = ""; $duration = "";
#$size = Chrona::bytes($size);
$attach = function($finfo) use ($original_name, $unique_name){
return array(
'unique_name' => $unique_name,
'original_name' => $original_name,
'finfo' => $finfo
);
};
# image
if(preg_match(Regex::MIME_IMAGE_STRICT, $mime)){
$x = X::$conf['kt']['prevsize'];
$y = $x;
switch(X::$conf['gl']){
case "im":
$img = new Imagick($src);
$dim = $img->getImageGeometry();
$x = $dim['width'];
$y = $dim['height'];
break;
case "gd":
$temp = getimagesize($src);
$x = $temp[0];
$y = $temp[1];
break;
default:
if($WINDOWS){
# well, fuck
} else {
$dim = shell_exec("identify -format '%wx%h' ".escapeshellarg($src));
if(preg_match('/(\d+)x(\d+)/', $dim, $dim_matches)){
$x = $dim_matches[1];
$y = $dim_matches[2];
}
}
}
self::unexif($src, $WINDOWS);
if($x > X::$conf['kt']['prevsize'] || $y > X::$conf['kt']['prevsize'] || $size > 8 * 1024) {
self::thumbnail($src, $pre, $mime);
} else {
copy($src, Regex::get_file_name($pre).".jpg");
}
$information = "{$x}x{$y}";
return $attach($information);
}
# audio/video/flash
elseif(preg_match(Regex::MIME_MULTIMEDIA, $mime)){
$stderr = ($WINDOWS ? "" : "2>&1");
$ffprobe = empty(X::$conf['ff']['probe'])
? "ffprobe"
: X::$conf['ff']['probe'];
$ffprobe_flags = implode(' ', [
"-v quiet",
"-hide_banner",
"-print_format json",
"-show_format",
"-show_streams",
#"-read_frames"
]);
$filepath = $src;
#dump(`{$ffprobe} -i {$filepath} {$ffprobe_flags} {$stderr}`);
$output = `{$ffprobe} {$ffprobe_flags} {$filepath} {$stderr}`;
$info = json_decode($output, true);
if(empty($info)){
unlink($src);
deny(E::file__proc_corrupt(basename($src)));
}
$d = Chrona::time(abs(intval($info['streams'][0]['duration'])));
$ct = $info['streams'][0]['codec_type'];
$xy = X::$conf['kt']['prevsize'];
if(preg_match(Regex::MIME_AUDIO_STRICT, $mime)){
$br = Chrona::bits($info['streams'][0]['bit_rate']);
return $attach("{$d}<br>{$br}");
}
if(preg_match(Regex::MIME_VIDEO_STRICT, $mime)){
$w = $info['streams'][0]['width'];
$h = $info['streams'][0]['height'];
$pre = $dir."/pre/".$mark.".jpg";
$output = $pre;
if($WINDOWS){
self::thumbnail($src, $pre, $mime);
} else {
$ffmpeg = empty(X::$conf['ff']['mpeg'])
? "ffmpeg"
: X::$conf['ff']['mpeg'];
$ffmpeg_flags = implode(' ', [
"-frames:v 1",
"-q:v 2",
"-vf 'scale={$xy}:{$xy}:force_original_aspect_ratio=increase,crop={$xy}:{$xy}'"
]);
$ss = "";#-ss 00:00:01
shell_exec("{$ffmpeg} {$ss} -i {$filepath} {$ffmpeg_flags} {$output}");
if(!file_exists($output)){
$output = $dir."/pre/nani.jpg";
}
}
return $attach("{$d}<br>{$w}x{$h}");
}
if(preg_match(Regex::MIME_FLASH_STRICT, $mime)){
$w = y($info['streams'][1]['width'],'?');
$h = y($info['streams'][1]['height'], '?');
return $attach("{$d}<br>{$w}x{$h}");
}
unlink($src);
deny(E::file__proc_unknown(basename($src)));
}
# source
elseif(preg_match(Regex::MIME_PASTE, $mime, $format)){
return $attach("Source");
}
# archive
elseif(preg_match(Regex::MIME_ARCHIVE, $mime, $format)){
return $attach("{$type} archive");
}
# torrent
elseif(preg_match(Regex::MIME_BITTORRENT, $mime)){
return $attach("BitTorrent");
}
# binary # ATAC
else deny(E::file__mime($mime));
return false;
}
static function validate($tmp_name, $size, $error){
if($size > X::$conf['kt']['filesize']){
deny(E::file__size_config(X::$conf['kt']['filesize']));
}
$hash = decode(Data::HASHLIST);
if(in_array(md5_file($tmp_name), $hash['banned'])){
deny(E::file__banned());
}
switch($error){
case UPLOAD_ERR_OK: return true; break;
case UPLOAD_ERR_FORM_SIZE: deny(E::file__size_config(X::$conf['kt']['filesize'])); break;
case UPLOAD_ERR_INI_SIZE: deny(E::file__size_server(ini_get('upload_max_filesize'))); break;
case UPLOAD_ERR_PARTIAL: deny(E::file__partial()); break;
case UPLOAD_ERR_NO_FILE: deny(E::file__lost()); break;
case UPLOAD_ERR_NO_TMP_DIR: deny(E::file__temp_dir()); break;
case UPLOAD_ERR_CANT_WRITE: deny(E::file__write()); break;
default: return deny(E::file__default()); break;
}
return false;
}
static function unexif($src, $WINDOWS){
if(X::$conf['gl'] == 'im'){
exec("convert ".escapeshellarg($source_image_path)." -strip ".escapeshellarg($source_image_path));
} elseif(X::$conf['gl'] == 'gd'){
}
}
static function thumbnail($source_image_path, $thumbnail_image_path, $mimetype){
if(X::$conf['gl'] == 'im'){ # easy way
$gif = (substr($source_image_path, -3) == 'gif');
$convert = 'convert '.escapeshellarg($source_image_path.($gif ? '[0]' : ''));
$convert .= ' -resize '.X::$conf['kt']['prevsize'].'x'.X::$conf['kt']['prevsize'].' -quality 50';
$convert .= ' '.escapeshellarg(Regex::get_file_name($thumbnail_image_path).".jpg");
exec($convert);
return is_file($thumbnail_image_path);
} elseif(X::$conf['gl'] == 'gd'){ # pierdole way
$e = function($msg = "unknown error"){ debug(E::file__proc_thumbnail($msg)); return false; };
$source_gd_image = false;
$preprocess = function() use (&$source_gd_image, &$thumbnail_gd_image){
if(false === $source_gd_image){
return $e("unable to create image");
} else try {
$source_image_width = imageSX($source_gd_image);
$source_image_height = imageSY($source_gd_image);
} catch (Exception $E) {
return $e("image sXY failed");
}
$aspect = 1; # aspect on percents
if($source_image_width > $source_image_height) $aspect = X::$conf['kt']['prevsize'] / $source_image_width;
else $aspect = X::$conf['kt']['prevsize'] / $source_image_height;
$thumbnail_image_width = round($source_image_width * $aspect);
$thumbnail_image_height = round($source_image_height * $aspect);
try {
$thumbnail_gd_image = imagecreatetruecolor($thumbnail_image_width, $thumbnail_image_height);
$white = imagecolorallocate($thumbnail_gd_image,0,0,0); # black background
imagefill($thumbnail_gd_image, 0, 0, $white);
self::fastimagecopyresampled($thumbnail_gd_image, $source_gd_image,
0, 0, 0, 0,
$thumbnail_image_width, $thumbnail_image_height,
$source_image_width, $source_image_height);
} catch (Exception $E) {
return $e("resampling failed");
}
};
try {
preg_match(Regex::MIME_IMAGE_STRICT,$mimetype,$type);
if(!isset($type[1])){
return $e("invalid mimetype");
}
$success = false;
switch($type[1]){
case 'jpg': case 'jpeg':
$source_gd_image = imagecreatefromjpeg($source_image_path);
$preprocess();
$success = imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 50);
break;
case 'png': case 'apng':
$source_gd_image = imagecreatefrompng($source_image_path);
$preprocess();
#$success = imagepng($thumbnail_gd_image, $thumbnail_image_path,0,PNG_ALL_FILTERS);
$success = imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 50);
break;
case 'gif':
$source_gd_image = imagecreatefromgif($source_image_path);
$preprocess();
#$success = imagegif($thumbnail_gd_image, $thumbnail_image_path);
$success = imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 50);
break;
case 'webp':
$source_gd_image = imagecreatefromwebp($source_image_path);
$preprocess();
#$success = imagewebp($thumbnail_gd_image, $thumbnail_image_path);
$success = imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 50);
break;
default: return $e("failed at failing and flailed"); break;
}
imagedestroy($source_gd_image);
imagedestroy($thumbnail_gd_image);
if(!$success){
return $e("postprocess");
}
} catch (Exception $E) { return $e('got an exception: '.$E->getMessage()); }
return true;
} else {
debug("Image library not specified, skipping thumbnail generation");
}
return false;
}
static function fastimagecopyresampled(&$dst_image,$src_image,$dst_x,$dst_y,$src_x,$src_y,$dst_w,$dst_h,$src_w,$src_h,$quality=2){
/**
* Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect.
* 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized.
* 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3.
* 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster.
* 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images.
* 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled.
*/
if(empty($src_image) || empty($dst_image) || $quality <= 0) { return false; }
if($quality < 5 && (($dst_w*$quality) < $src_w || ($dst_h*$quality) < $src_h)) {
$temp = imagecreatetruecolor($dst_w*$quality+1,$dst_h*$quality+1);
$white = imagecolorallocate($temp,255,255,255);
imagefill($temp,0,0,$white);
imagecopyresized($temp,$src_image,0,0,$src_x,$src_y,$dst_w*$quality+1,$dst_h*$quality+1,$src_w,$src_h);
imagecopyresampled($dst_image,$temp,$dst_x,$dst_y,0,0,$dst_w,$dst_h,$dst_w*$quality,$dst_h*$quality);
imagedestroy($temp);
} else imagecopyresampled($dst_image,$src_image,$dst_x,$dst_y,$src_x,$src_y,$dst_w,$dst_h,$src_w,$src_h);
return true;
}
static function compare($fn1, $fn2){ # compares 2 files by headers
$RLEN = 4096; $same = true;
if((filetype($fn1)!==filetype($fn2))||(filesize($fn1)!==filesize($fn2))) return false;
if(!$fp1 = fopen($fn1, 'rb')) return false;
if(!$fp2 = fopen($fn2, 'rb')){ fclose($fp1); return false; }
while (!feof($fp1) and !feof($fp2)) if(fread($fp1, $RLEN) !== fread($fp2, $RLEN)){ $same = false; break; }
if(feof($fp1)!==feof($fp2)) $same = false;
fclose($fp1); fclose($fp2);
return $same;
}
static function references($literal, $tid, $pid, $text){
if(!X::$conf['kt']['wiremaps']){
return;
}
preg_match_all(Regex::GENERIC_REF_LIT, $text, $reflink_lit);
preg_match_all(Regex::GENERIC_REF_NUM, $text, $reflink_num);
$update = function($N, $W, $P) use ($literal, $tid, $pid){
if(isset(X::$tree[$N])){
if($W > 0){
if(!file_exists($f = ROOT."static/node/{$N}/map/{$W}.json")){
touch($f);
encode("static/node/{$N}/map/{$W}", []);
}
$path = "static/node/{$N}/map/{$W}";
$map = decode($path);
if(empty($map)){ $map = []; }
if(!isset($map[$P])){
$map[$P] = [];
}
$map[$P][] = [$literal, $tid, $pid];
encode($path, $map);
}
}
};
$i = 0;
if(!empty($reflink_lit)){ # cross-node reflinks >>>x/123
$n = "";
foreach($reflink_lit[3] as $i => $p){
$n = $reflink_lit[2][$i];
if(isset(X::$tree[$n])){
Katana::cata($n);
$w = intval(Request::parent($n, $p));
if($w > 0){
$update($n, $w, $p);
}
}
if(++$i > 10) break;
}
Katana::cata($literal);
}
if(!empty($reflink_num)){ # local-node reflinks >>123
foreach($reflink_num[2] as $p){
$w = intval(Request::parent($literal, $p));
if($w > 0){
$update($literal, $w, $p);
}
if(++$i > 10) break;
}
}
}
}