├── _dir ├── data │ └── .gitkeep ├── static │ ├── images │ │ ├── logo.png │ │ └── music.png │ ├── css │ │ ├── editor.css │ │ └── style.css │ └── js │ │ ├── clipBoard.min.js │ │ ├── admin.js │ │ └── main.js ├── page │ ├── audio.php │ ├── video.php │ ├── footer.php │ ├── text.php │ ├── markdown.php │ ├── login.php │ ├── header.php │ ├── editor.php │ ├── editor2.php │ ├── upload.php │ ├── admin.php │ └── home.php ├── inc.php ├── Cache.class.php ├── functions.php ├── FileMgr.class.php ├── DirList.class.php └── Parsedown.class.php ├── README.md └── index.php /_dir/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_dir/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netcccyun/dirlist/HEAD/_dir/static/images/logo.png -------------------------------------------------------------------------------- /_dir/static/images/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netcccyun/dirlist/HEAD/_dir/static/images/music.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 彩虹目录列表 2 | 3 | 彩虹目录列表是一款用PHP开发的目录列表程序,无需数据库,安装简单,使用方便。 4 | 5 | ### 功能特点 6 | 7 | - 在线预览图片、视频、音频、文本文件、Markdown文件、Office文档等 8 | - 自动识别目录下的README.md文件并展示(类GitHub) 9 | - 后台管理可设置网站标题、公告、底部代码等信息 10 | - 文件搜索功能,支持缓存文件索引 11 | - 自带全新的文件管理功能 12 | - 支持中文文件名编码设置,解决乱码问题 13 | - 支持安装在子目录 14 | 15 | ### 演示地址 16 | - http://file.cccyun.cc/ 17 | 18 | ### 部署方法 19 | 20 | - 上传后直接访问即可使用,后台默认管理员账号密码:admin/123456 21 | 22 | ### 官方网站 23 | 24 | - https://blog.cccyun.cn/ 25 | -------------------------------------------------------------------------------- /_dir/static/css/editor.css: -------------------------------------------------------------------------------- 1 | #file_content { 2 | border-top: 1px solid #c9c6c6; 3 | border-bottom: 1px solid #c9c6c6; 4 | height:500px; 5 | width:100%; 6 | } 7 | .input_button { 8 | line-height: 16px; 9 | padding: 4px 10px; 10 | border-radius: 5px; 11 | background: #f3f3f3; 12 | background-image: linear-gradient(-189deg, #ffffff 49%,#eeeeee 81%); 13 | border: 1px solid #b6b6b6; 14 | cursor: pointer; 15 | margin-right: 2px; 16 | color: #202020; 17 | font-size: 12px; 18 | } 19 | .input_button:hover { 20 | background-image: linear-gradient(-217deg, #ffffff 49%,#eeeeee 81%); 21 | } 22 | .input_primary { 23 | background: #99AFDA; 24 | background-image: linear-gradient(154deg, #99AFDA 62%, #7592c9 107%); 25 | color: #f3f3f3; 26 | border: 1px solid #878D96; 27 | font-weight: bold; 28 | line-height: 23px; 29 | font-size: 14px; 30 | padding: 4px 26px; 31 | } 32 | .input_primary:hover { 33 | background-image: linear-gradient(35deg, #99AFDA -45%, #819ccf 100%); 34 | background-color: #a5b9df; 35 | } -------------------------------------------------------------------------------- /_dir/page/audio.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 音频播放器 10 | 11 | 12 | 13 |
14 | 15 | 16 | 28 | 29 | -------------------------------------------------------------------------------- /_dir/page/video.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 视频播放器 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 30 | 31 | -------------------------------------------------------------------------------- /_dir/page/footer.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /_dir/page/text.php: -------------------------------------------------------------------------------- 1 | get_view_type($ext); 5 | if($view_type!='text') sysmsg('不支持的文件格式'); 6 | 7 | if(filesize($path) > 1024 * 1024 * 10) sysmsg('文件超过10M无法查看'); 8 | 9 | $content = file_get_contents($path); 10 | if($content===false) sysmsg('文件读取失败'); 11 | 12 | $coding = mb_detect_encoding($content,"UTF-8,GBK,GB2312"); 13 | if($coding != 'UTF-8'){ 14 | $content = mb_convert_encoding($content, 'UTF-8', $coding); 15 | } 16 | 17 | $content = htmlspecialchars($content); 18 | 19 | header('Content-Type: text/html; charset=UTF-8'); 20 | ?> 21 | 22 | 23 | 24 | 文本查看器 25 | 26 | 35 | 36 | 37 |

38 | 
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /_dir/inc.php: -------------------------------------------------------------------------------- 1 | get('config'); 18 | if(!$conf){ 19 | if(!$CACHE->set('config', ['admin_username'=>'admin','admin_password'=>md5('123456'),'title'=>'彩虹目录列表', 'keywords'=>'彩虹目录列表,Directory Lister目录列表,目录索引','description'=>'彩虹目录列表程序','announce'=>'','footer'=>'', 'name_encode'=>'utf8', 'file_hash'=>'1', 'cache_indexes'=>'0', 'footer_bar'=>'1', 'readme_md'=>'1', 'auth'=>'0', 'nav'=>'音乐搜索*http://music.hi.cn/|图片压缩*https://tinypng.com/|今日热榜*https://tophub.today/'])){ 20 | sysmsg('配置项初始化失败,可能无文件写入权限'); 21 | } 22 | $conf = $CACHE->get('config'); 23 | } 24 | 25 | $scriptpath=str_replace('\\','/',$_SERVER['SCRIPT_NAME']); 26 | $sitepath = substr($scriptpath, 0, strrpos($scriptpath, '/')); 27 | $siteurl = (is_https() ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].$sitepath.'/'; 28 | 29 | $cdnpublic = 'https://s4.zstatic.net/ajax/libs/'; 30 | 31 | if(isset($_COOKIE["admin_session"])) 32 | { 33 | if($conf['admin_session']===$_COOKIE["admin_session"]) { 34 | $islogin=1; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /_dir/page/markdown.php: -------------------------------------------------------------------------------- 1 | get_view_type($ext); 5 | if($view_type!='markdown') sysmsg('不支持的文件格式'); 6 | 7 | if(filesize($path) > 1024 * 1024 * 10) sysmsg('文件超过10M无法查看'); 8 | 9 | $content = file_get_contents($path); 10 | if($content===false) sysmsg('文件读取失败'); 11 | 12 | require SYSTEM_ROOT.'Parsedown.class.php'; 13 | $Parsedown = new Parsedown(); 14 | $content = $Parsedown->text($content); 15 | $content = str_replace('[x]','',$content); 16 | $content = str_replace('[ ]','',$content); 17 | 18 | header('Content-Type: text/html; charset=UTF-8'); 19 | ?> 20 | 21 | 22 | 23 | 24 | MDtoHTML 25 | 26 | 27 | 43 | 44 | 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /_dir/static/js/clipBoard.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * clipboard.js 3 | * width clipboard.js, you can copy cut and paste clipboard data 4 | * the main methods ard execCommand for modern browser and clipboardData for ie 5 | * @author ganzw@gmail.com 6 | * @url https://github.com/baixuexiyang/clipBoard.js 7 | */ 8 | !function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define(e):this[t]=e()}("clipBoard",function(){"use strict";function t(t,e){this.options=e||{},this.tar=t[0]||t,this.options.copy&&this.copyd(),this.options.cut&&this.cut(),this.options.paste&&this.paste()}return t.prototype.copyd=function(t){if(this.options.beforeCopy&&this.options.beforeCopy(),t=t||this.tar.value||this.tar.innerText,this.options.copy&&(t=this.options.copy()),document.execCommand){var e=document.createElement("SPAN");if(e.textContent=t,document.body.appendChild(e),document.selection){var o=document.body.createTextRange();o.moveToElementText(e),o.select()}else if(window.getSelection){var o=document.createRange();o.selectNode(e),window.getSelection().removeAllRanges(),window.getSelection().addRange(o)}document.execCommand("copy"),e.remove?e.remove():e.removeNode(!0)}window.clipboardData&&window.clipboardData.setData("text",t),this.options.afterCopy&&this.options.afterCopy()},t.prototype.cut=function(){if("text"===this.tar.type||"textarea"===this.tar.type){if(this.options.beforeCut&&this.options.beforeCut(),document.execCommand){var t=this.tar;if(document.selection){var e=document.body.createTextRange();e.moveToElementText(t),e.select()}else window.getSelection&&t.select();document.execCommand("cut")}window.clipboardData&&(window.clipboardData.setData("text",this.tar.value),this.tar.value=""),this.options.afterCut&&this.options.afterCut()}},t.prototype.paste=function(){if("text"===this.tar.type||"textarea"===this.tar.type){if(this.options.beforePaste&&this.options.beforePaste(),document.execCommand){var t=this.tar;if(t.setSelectionRange)t.focus(),t.setSelectionRange(t.value.length,t.value.length);else if(t.createTextRange){var e=t.createTextRange();e.collapse(!0),e.moveEnd("character",t.value.length),e.moveStart("character",t.value.length),e.select()}document.execCommand("paste")}!document.execCommand&&window.clipboardData&&(this.tar.value+=window.clipboardData.getData("text")),this.options.afterPaste&&this.options.afterPaste()}},t}); 9 | -------------------------------------------------------------------------------- /_dir/Cache.class.php: -------------------------------------------------------------------------------- 1 | getCacheKey($name); 14 | 15 | if (!is_file($filename)) return; 16 | 17 | $content = @file_get_contents($filename); 18 | if (false === $content) return; 19 | 20 | $expire = (int) substr($content, 8, 12); 21 | if (0 != $expire && time() - $expire > filemtime($filename)) { 22 | //缓存过期删除缓存文件 23 | $this->unlink($filename); 24 | return; 25 | } 26 | 27 | $content = substr($content, 32); 28 | 29 | return is_string($content) ? $content : null; 30 | } 31 | 32 | public function has($name){ 33 | return $this->getRaw($name) !== null; 34 | } 35 | 36 | public function get($name, $default = null){ 37 | $raw = $this->getRaw($name); 38 | 39 | if (is_null($raw)){ 40 | return $default; 41 | } elseif (is_numeric($raw)) { 42 | return $raw; 43 | } else { 44 | return unserialize($raw); 45 | } 46 | } 47 | 48 | public function set($name, $value, $expire = null){ 49 | if (is_null($expire)) $expire = self::Expire; 50 | 51 | $filename = $this->getCacheKey($name); 52 | 53 | if (is_numeric($value)) { 54 | $data = (string) $value; 55 | } else { 56 | $data = serialize($value); 57 | } 58 | 59 | $data = "\n" . $data; 60 | $result = file_put_contents($filename, $data); 61 | 62 | if ($result) { 63 | clearstatcache(); 64 | return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | public function delete($name){ 71 | $filename = $this->getCacheKey($name); 72 | $this->unlink($filename); 73 | return true; 74 | } 75 | 76 | private function unlink($path) 77 | { 78 | try { 79 | return is_file($path) && unlink($path); 80 | } catch (\Exception $e) { 81 | return false; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /_dir/page/login.php: -------------------------------------------------------------------------------- 1 | -1, 'msg'=>'用户名或密码不能为空']); 11 | } 12 | if($username === $conf['admin_username'] && md5($password) === $conf['admin_password']){ 13 | $conf['admin_session'] = getSid(); 14 | $conf['admin_lastlogin'] = date('Y-m-d H:i:s'); 15 | if($CACHE->set('config', $conf)){ 16 | setcookie("admin_session", $conf['admin_session'], time() + 2592000); 17 | echo_json(['code'=>0]); 18 | }else{ 19 | echo_json(['code'=>-1, 'msg'=>'登录失败,可能无文件写入权限']); 20 | } 21 | }else{ 22 | echo_json(['code'=>-1, 'msg'=>'用户名或密码错误']); 23 | } 24 | } 25 | 26 | header('Content-Type: text/html; charset=UTF-8'); 27 | 28 | if($islogin) exit(""); 29 | 30 | include PAGE_ROOT.'header.php'; 31 | ?> 32 | 33 | 34 |
35 |
36 |
37 |
38 |
后台管理登录
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /_dir/page/header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <?php echo $conf['title']?> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /_dir/static/css/style.css: -------------------------------------------------------------------------------- 1 | .header{ 2 | background-color:#393D49; 3 | width:100%; 4 | } 5 | .footer{ 6 | position:fixed; 7 | width:100%; 8 | left:0; 9 | bottom:0; 10 | color:#777; 11 | font-size:14px; 12 | text-align: center!important; 13 | } 14 | .footer a{ 15 | color:#777; 16 | } 17 | .fileinfo{ 18 | text-align:right; 19 | } 20 | .fileinfo a{ 21 | margin-right:0.5em; 22 | color: #7d8d9f; 23 | } 24 | .logo{ 25 | float:left; 26 | color:#FFFFFF; 27 | vertical-align: middle; 28 | margin-top:1em; 29 | margin-bottom:1em; 30 | } 31 | .logo a{ 32 | color:#FFFFFF; 33 | } 34 | .menu{ 35 | float:left; 36 | } 37 | .dirlist tbody td,.dirlist thead th{ 38 | padding: .55rem 1rem; 39 | min-height: 20px; 40 | line-height: 20px; 41 | font-size: 14px; 42 | vertical-align: middle; 43 | border-width: 0; 44 | border-bottom: 1px solid #eee; 45 | } 46 | .dirlist thead th{ 47 | padding: .75rem 1rem; 48 | background-color: rgba(0,0,0,.03); 49 | border-bottom: 1px solid rgba(0,0,0,.125); 50 | } 51 | .dirlist { 52 | border: 1px solid rgba(0,0,0,.125); 53 | } 54 | .fname{ 55 | width:520px; 56 | overflow: hidden; 57 | text-overflow:ellipsis; 58 | white-space: nowrap; 59 | word-break: break-all; 60 | display:block; 61 | color: #24292f; 62 | } 63 | @media screen and (max-width: 992px) { 64 | .fname{ 65 | width:380px; 66 | } 67 | } 68 | @media screen and (max-width: 768px) { 69 | .fname{ 70 | width:unset; 71 | white-space: unset; 72 | } 73 | } 74 | .fname .fa { 75 | color: #57606a; 76 | } 77 | .fname .fa-folder-open{ 78 | color: #368edb; 79 | } 80 | .fname .fa-level-up{ 81 | color: #e1a50e; 82 | } 83 | 84 | #msg{ 85 | border:1px solid #FF5722; 86 | border-radius:2px; 87 | color:#505050; 88 | padding:0.7em; 89 | line-height:1.3em; 90 | margin-bottom:0.8em; 91 | background-color:#F5F5F5; 92 | } 93 | #msg i{ 94 | color:#FF5722; 95 | font-weight:bold; 96 | } 97 | 98 | .btn-sm{ 99 | padding: .12rem .24rem; 100 | font-size: .765rem; 101 | margin: 0 .18rem; 102 | } 103 | .center-block { 104 | margin: 0 auto; 105 | float: none; 106 | padding: 0; 107 | } 108 | textarea{ 109 | word-break: break-all; 110 | } 111 | @media screen and (min-width: 576px) { 112 | .col-form-label{ 113 | text-align: right; 114 | } 115 | } 116 | 117 | #gg-left{ 118 | position:fixed; 119 | left:10px; 120 | top:80px; 121 | width:100%; 122 | max-width: 120px; 123 | } 124 | #gg-left img{ 125 | width:100%; 126 | max-width: 120px; 127 | } 128 | #gg-right{ 129 | position:fixed; 130 | top:80px; 131 | right:10px; 132 | } 133 | #gg-right img{ 134 | width:100%; 135 | max-width: 120px; 136 | } 137 | @media screen and (max-width: 1366px) { 138 | #gg-left,#gg-right{ 139 | display: none; 140 | } 141 | } 142 | .ellipsis{ 143 | overflow: hidden; 144 | white-space: nowrap; 145 | text-overflow: ellipsis; 146 | } -------------------------------------------------------------------------------- /_dir/static/js/admin.js: -------------------------------------------------------------------------------- 1 | var items = $("select[default]"); 2 | for (i = 0; i < items.length; i++) { 3 | $(items[i]).val($(items[i]).attr("default")||0); 4 | } 5 | 6 | function submitlogin(){ 7 | var username = $("input[name='username']").val(); 8 | var password = $("input[name='password']").val(); 9 | if(username=='' || password==''){layer.alert('用户名或密码不能为空!');return false;} 10 | var ii = layer.load(2, {shade:[0.1,'#fff']}); 11 | $.ajax({ 12 | type : 'POST', 13 | url : './?c=login', 14 | data: {username:username, password:password}, 15 | dataType : 'json', 16 | success : function(data) { 17 | layer.close(ii); 18 | if(data.code == 0){ 19 | layer.msg('登录成功,正在跳转', {icon: 1,shade: 0.01,time: 15000}); 20 | window.location.href='./?c=admin'; 21 | }else{ 22 | layer.alert(data.msg, {icon: 2}); 23 | } 24 | }, 25 | error:function(){ 26 | layer.close(ii); 27 | layer.msg('服务器错误'); 28 | } 29 | }); 30 | return false; 31 | } 32 | 33 | function saveSetting(obj){ 34 | var ii = layer.load(2, {shade:[0.1,'#fff']}); 35 | $.ajax({ 36 | type : 'POST', 37 | url : './?c=admin&do=set', 38 | data : $(obj).serialize(), 39 | dataType : 'json', 40 | success : function(data) { 41 | layer.close(ii); 42 | if(data.code == 0){ 43 | layer.alert(data.msg, { 44 | icon: 1, 45 | closeBtn: false 46 | }, function(){ 47 | window.location.reload() 48 | }); 49 | }else{ 50 | layer.alert(data.msg, {icon: 2}) 51 | } 52 | }, 53 | error:function(){ 54 | layer.close(ii); 55 | layer.msg('服务器错误'); 56 | } 57 | }); 58 | return false; 59 | } 60 | 61 | function setAccount(obj){ 62 | var ii = layer.load(2, {shade:[0.1,'#fff']}); 63 | $.ajax({ 64 | type : 'POST', 65 | url : './?c=admin&do=account', 66 | data : $(obj).serialize(), 67 | dataType : 'json', 68 | success : function(data) { 69 | layer.close(ii); 70 | if(data.code == 0){ 71 | layer.alert(data.msg, { 72 | icon: 1, 73 | closeBtn: false 74 | }, function(){ 75 | window.location.reload() 76 | }); 77 | }else{ 78 | layer.alert(data.msg, {icon: 2}) 79 | } 80 | }, 81 | error:function(){ 82 | layer.close(ii); 83 | layer.msg('服务器错误'); 84 | } 85 | }); 86 | return false; 87 | } 88 | 89 | function clearIndexes(){ 90 | var ii = layer.load(2, {shade:[0.1,'#fff']}); 91 | $.ajax({ 92 | type : 'GET', 93 | url : './?c=admin&do=clearindexes', 94 | dataType : 'json', 95 | success : function(data) { 96 | layer.close(ii); 97 | if(data.code == 0){ 98 | layer.msg('清空文件索引成功!', {icon:1}); 99 | }else{ 100 | layer.msg('清空文件索引失败', {icon: 2}); 101 | } 102 | }, 103 | error:function(){ 104 | layer.close(ii); 105 | layer.msg('服务器错误'); 106 | } 107 | }); 108 | return false; 109 | } 110 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | -1,'msg'=>'未开启该功能']); 13 | $path = isset($_POST['path'])?trim($_POST['path']):''; 14 | try{ 15 | $r = $x->get_file_hash($path); 16 | }catch(Exception $e){ 17 | $r = ['code'=>-1, 'msg'=>$e->getMessage()]; 18 | } 19 | echo_json($r); 20 | break; 21 | case 'video': 22 | case 'audio': 23 | case 'markdown': 24 | case 'text': 25 | if(!checkRefererHost())exit('Access Denied'); 26 | $path = isset($_GET['path'])?trim($_GET['path']):''; 27 | try{ 28 | $path = './'.$x->set_dir_path($path, true); 29 | }catch(Exception $e){ 30 | $errmsg = $e->getMessage(); 31 | sysmsg($errmsg); 32 | } 33 | $name = basename($path); 34 | $ext = $x->get_file_ext($path); 35 | $url = implode('/', array_map('rawurlencode', explode('/', $path))); 36 | include PAGE_ROOT.$c.'.php'; 37 | break; 38 | case 'search': 39 | $s = isset($_GET['s'])?trim($_GET['s']):''; 40 | if($s == '') exit(""); 41 | try{ 42 | $list = $x->search_files($s); 43 | $r = ['list'=>$list]; 44 | }catch(Exception $e){ 45 | $errmsg = $e->getMessage(); 46 | } 47 | $s = htmlspecialchars($s); 48 | include PAGE_ROOT.'home.php'; 49 | break; 50 | case 'home': 51 | $dir = isset($_GET['dir'])?trim($_GET['dir']):''; 52 | try{ 53 | $r = $x->list_dir($dir); 54 | }catch(Exception $e){ 55 | $errmsg = $e->getMessage(); 56 | $r = $x->list_dir(); 57 | } 58 | if(!empty($r['passwd']) && isset($_POST['passwd'])){ 59 | if($errmsg) echo_json(['code'=>-1, 'msg'=>$errmsg]); 60 | if(password_verify($_POST['passwd'], $r['passwd'])){ 61 | setcookie('dir_passwd', md5($r['passwd'])); 62 | echo_json(['code'=>0]); 63 | }else{ 64 | echo_json(['code'=>-1, 'msg'=>'目录访问密码错误']); 65 | } 66 | } 67 | include PAGE_ROOT.'home.php'; 68 | break; 69 | case 'admin': 70 | if(!$islogin) exit(""); 71 | include PAGE_ROOT.'admin.php'; 72 | break; 73 | case 'upload': 74 | if(!$islogin) exit(""); 75 | $path = isset($_GET['path'])?trim($_GET['path']):''; 76 | try{ 77 | $path = $x->set_dir_path($path); 78 | }catch(Exception $e){ 79 | sysmsg($e->getMessage()); 80 | } 81 | include PAGE_ROOT.'upload.php'; 82 | break; 83 | case 'filemgr': 84 | if(!$islogin) exit(""); 85 | $path = isset($_POST['path'])?trim($_POST['path']):''; 86 | $do = isset($_POST['do'])?trim($_POST['do']):echo_json(['code'=>-1, 'msg'=>'param error']); 87 | try{ 88 | $path = $x->set_dir_path($path); 89 | }catch(Exception $e){ 90 | echo_json(['code'=>-1, 'msg'=>$e->getMessage()]); 91 | } 92 | $mgr = new FileMgr($path); 93 | if(!method_exists($mgr, $do))echo_json(['code'=>-1, 'msg'=>'action error']); 94 | try{ 95 | $result = $mgr->$do(); 96 | echo_json(['code'=>0, 'data'=>$result]); 97 | }catch(Exception $e){ 98 | echo_json(['code'=>-1, 'msg'=>$e->getMessage()]); 99 | } 100 | break; 101 | case 'editor': 102 | if(!$islogin) exit(""); 103 | $path = isset($_GET['path'])?trim($_GET['path']):''; 104 | try{ 105 | $path = $x->set_dir_path($path, true); 106 | }catch(Exception $e){ 107 | $errmsg = $e->getMessage(); 108 | sysmsg($errmsg); 109 | } 110 | if($conf['editor'] == '1'){ 111 | include PAGE_ROOT.'editor2.php'; 112 | }else{ 113 | include PAGE_ROOT.'editor.php'; 114 | } 115 | break; 116 | case 'login': 117 | include PAGE_ROOT.'login.php'; 118 | break; 119 | default: 120 | break; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /_dir/page/editor.php: -------------------------------------------------------------------------------- 1 | get_file_ext($path); 6 | if(isset($_POST['file_content']) && isset($_POST['character'])){ 7 | $file_content = stripslashes($_POST['file_content']); 8 | $character = $_POST['character']; 9 | if ($character != 'UTF-8') 10 | { 11 | $file_content = mb_convert_encoding($file_content, $character, 'UTF-8'); 12 | } 13 | file_put_contents($path, $file_content); 14 | echo_json(['code'=>0]); 15 | } 16 | 17 | if(filesize($path) > 1024 * 1024 * 10) sysmsg('文件超过10M无法在线编辑'); 18 | $file_content = file_get_contents($path); 19 | $character = mb_detect_encoding($file_content, array('UTF-8',"EUC-CN",'BIG-5',"CP936"), true); 20 | if(!$character) sysmsg('该文件因编码问题不支持在线编辑'); 21 | if($character != 'UTF-8') 22 | $file_content = mb_convert_encoding($file_content, 'UTF-8', $character); 23 | $file_content = htmlspecialchars($file_content); 24 | 25 | header('Content-Type: text/html; charset=UTF-8'); 26 | ?> 27 | 28 | 29 | 30 | 31 | 文本编辑器 32 | 33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 |   41 |   42 |   43 |   44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 128 | 129 | -------------------------------------------------------------------------------- /_dir/page/editor2.php: -------------------------------------------------------------------------------- 1 | get_file_ext($path); 6 | if(isset($_POST['file_content']) && isset($_POST['character'])){ 7 | $file_content = stripslashes($_POST['file_content']); 8 | $character = $_POST['character']; 9 | if ($character != 'UTF-8') 10 | { 11 | $file_content = mb_convert_encoding($file_content, $character, 'UTF-8'); 12 | } 13 | file_put_contents($path, $file_content); 14 | echo_json(['code'=>0]); 15 | } 16 | 17 | if(filesize($path) > 1024 * 1024 * 10) sysmsg('文件超过10M无法在线编辑'); 18 | $file_content = file_get_contents($path); 19 | $character = mb_detect_encoding($file_content, array('UTF-8',"EUC-CN",'BIG-5',"CP936"), true); 20 | if(!$character) sysmsg('该文件因编码问题不支持在线编辑'); 21 | if($character != 'UTF-8') 22 | $file_content = mb_convert_encoding($file_content, 'UTF-8', $character); 23 | $file_content = htmlspecialchars($file_content); 24 | 25 | header('Content-Type: text/html; charset=UTF-8'); 26 | ?> 27 | 28 | 29 | 30 | 31 | 文本编辑器 32 | 33 | 34 | 35 |
36 | 37 | 38 | 39 |
40 |
41 |   42 |   43 |   44 |   45 |   46 | 47 |
48 |
49 | 50 | 51 | 52 | 149 | 150 | -------------------------------------------------------------------------------- /_dir/page/upload.php: -------------------------------------------------------------------------------- 1 | -1, 'msg'=>'分块上传失败,可能无文件写入权限'])); 18 | } 19 | $in = fopen($tmp_name, "rb"); 20 | if(!$in){ 21 | exit(json_encode(['code'=>-1, 'msg'=>'分块上传失败,无法读取临时文件'])); 22 | } 23 | while ($buff = fread($in, 4096)) { fwrite($out, $buff); } 24 | fclose($in); 25 | fclose($out); 26 | unlink($tmp_name); 27 | 28 | if ($chunkIndex == $chunkTotal - 1) { 29 | file_exists($fullPath) && unlink($fullPath); 30 | rename($fullPath.'.part', $fullPath); 31 | exit(json_encode(['code'=>0])); 32 | }else{ 33 | exit(json_encode(['code'=>0, 'chunkindex'=>$chunkIndex])); 34 | } 35 | }else{ 36 | file_exists($fullPath) && unlink($fullPath); 37 | if(move_uploaded_file($tmp_name, $fullPath)){ 38 | exit(json_encode(['code'=>0])); 39 | }else{ 40 | exit(json_encode(['code'=>-1, 'msg'=>$filename.' 上传失败,可能无文件写入权限'])); 41 | } 42 | } 43 | }else if($_POST['type'] == 'urlupload' && !empty($_POST['url']) && !empty($_POST['filename'])){ 44 | header('Content-Type: application/json; charset=UTF-8'); 45 | $url = trim($_POST['url']); 46 | $filename = trim($_POST['filename']); 47 | if(!filter_var($url, FILTER_VALIDATE_URL)) exit(json_encode(['code'=>-1, 'msg'=>'URL格式不正确'])); 48 | if(!$_POST['token'] || $_POST['token']!=$_SESSION['csrf_token'])exit('{"code":-1,"msg":"CSRF TOKEN ERROR"}'); 49 | $fullPath = $path.'/'.$filename; 50 | try{ 51 | curl_download($url, $fullPath.'.part'); 52 | file_exists($fullPath) && unlink($fullPath); 53 | rename($fullPath.'.part', $fullPath); 54 | exit(json_encode(['code'=>0, 'filename'=>$filename])); 55 | }catch(Exception $e){ 56 | exit(json_encode(['code'=>-1, 'msg'=>$e->getMessage()])); 57 | } 58 | } 59 | $csrf_token = md5(mt_rand(0,999).time()); 60 | $_SESSION['csrf_token'] = $csrf_token; 61 | 62 | header('Content-Type: text/html; charset=UTF-8'); 63 | ?> 64 | 65 | 66 | 67 | 68 | 上传文件 69 | 70 | 71 | 72 | 73 | 74 | 87 | 88 |
89 |
90 | 98 |
99 |
100 |

101 | 目标文件夹: / 102 |

103 |
104 |
105 |
106 | 107 | 108 |
109 | 110 |
111 |
112 |
113 |
114 |
115 |
116 | 117 | 118 |
119 | 120 |
121 |
122 |
123 | 124 |
125 | 126 |
127 |
128 |
129 | 132 |
133 |
134 |
135 |
136 |
137 |
138 | 139 | 140 | 141 | 142 | 207 | 208 | -------------------------------------------------------------------------------- /_dir/page/admin.php: -------------------------------------------------------------------------------- 1 | $value){ 8 | $conf[$key] = $value; 9 | } 10 | if($CACHE->set('config', $conf)){ 11 | echo_json(['code'=>0, 'msg'=>'设置保存成功!']); 12 | }else{ 13 | echo_json(['code'=>-1, 'msg'=>'保存失败,可能无文件写入权限']); 14 | } 15 | } 16 | elseif($_GET['do'] == 'account'){ 17 | $username=trim($_POST['username']); 18 | $oldpwd=trim($_POST['oldpwd']); 19 | $newpwd=trim($_POST['newpwd']); 20 | $newpwd2=trim($_POST['newpwd2']); 21 | if(empty($username))echo_json(['code'=>-1, 'msg'=>'用户名不能为空!']); 22 | $conf['admin_username'] = $username; 23 | $msg = '修改成功!'; 24 | if(!empty($newpwd) && !empty($newpwd2)){ 25 | if(md5($oldpwd)!=$conf['admin_password'])echo_json(['code'=>-1, 'msg'=>'旧密码不正确!']); 26 | if($newpwd!=$newpwd2)echo_json(['code'=>-1, 'msg'=>'两次输入的密码不一致!']); 27 | $conf['admin_password'] = md5($newpwd); 28 | $conf['admin_session'] = null; 29 | $msg = '修改成功!请重新登录'; 30 | } 31 | if($CACHE->set('config', $conf)){ 32 | echo_json(['code'=>0, 'msg'=>$msg]); 33 | }else{ 34 | echo_json(['code'=>-1, 'msg'=>'修改失败,可能无文件写入权限']); 35 | } 36 | } 37 | elseif($_GET['do'] == 'clearindexes'){ 38 | $CACHE->delete('indexes'); 39 | echo_json(['code'=>0]); 40 | } 41 | elseif($_GET['do'] == 'logout'){ 42 | setcookie("admin_session", "", time() - 2592000); 43 | header('Content-Type: text/html; charset=UTF-8'); 44 | exit(""); 45 | } 46 | } 47 | 48 | header('Content-Type: text/html; charset=UTF-8'); 49 | include PAGE_ROOT.'header.php'; 50 | ?> 51 | 52 | 78 | 79 |
80 |
81 |
82 | 93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 | 108 |
109 |
110 |
111 | 112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 | 120 |
填写格式:链接文字*链接地址|链接文字*链接地址
121 |
122 |
123 | 124 |
125 |
126 |
127 | 128 |
当出现中文文件名乱码的情况下可以修改此项
129 |
130 |
131 | 132 |
133 |
134 |
135 | 136 |
137 |
138 |
139 | 140 |
141 |
142 |
143 | 144 |
在这里填写扩展名后,可解决图片、视频等文件,点击下载会直接在浏览器中预览的问题。需要先配置伪静态
145 |
146 |
147 | 148 |
149 |
150 |
151 | 152 |
文件和目录数量多的情况下建议开启,可提升搜索速度。开启后如文件有变动需手动清除缓存
153 |
154 |
155 | 156 |
157 |
158 |
159 |
160 | 0){?> 161 |
162 | 163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | 171 |
172 |
173 |
174 | 175 |
176 |
177 |
178 | 179 |
180 |
181 |
182 | 183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 197 |
198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /_dir/functions.php: -------------------------------------------------------------------------------- 1 | 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) { 134 | return substr($result, 26); 135 | } else { 136 | return ''; 137 | } 138 | } else { 139 | return $keyc.str_replace('=', '', base64_encode($result)); 140 | } 141 | } 142 | 143 | function random($length, $numeric = 0) { 144 | $seed = base_convert(md5(microtime().$_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35); 145 | $seed = $numeric ? (str_replace('0', '', $seed).'012340567890') : ($seed.'zZ'.strtoupper($seed)); 146 | $hash = ''; 147 | $max = strlen($seed) - 1; 148 | for($i = 0; $i < $length; $i++) { 149 | $hash .= $seed[mt_rand(0, $max)]; 150 | } 151 | return $hash; 152 | } 153 | 154 | function sysmsg($msg = '未知的异常',$title = '站点提示信息') { 155 | ?> 156 | 157 | 158 | 159 | 160 | 161 | <?php echo $title?> 162 | 165 | 166 | 167 | '.$title.''; 168 | echo $msg; ?> 169 | 170 | 171 | 172 | 299){ 213 | curl_close($ch); 214 | fclose($fp); 215 | throw new Exception('下载文件失败:HTTPCODE-'.$httpcode); 216 | } 217 | curl_close($ch); 218 | fclose($fp); 219 | return true; 220 | } 221 | 222 | function generatePagination($currentPage, $totalPages) { 223 | $html = ''; 284 | 285 | return $html; 286 | } -------------------------------------------------------------------------------- /_dir/FileMgr.class.php: -------------------------------------------------------------------------------- 1 | path = $path; 11 | } 12 | 13 | //创建文件/文件夹 14 | public function create() 15 | { 16 | $type = trim($_POST['type']); 17 | $name = trim($_POST['name']); 18 | if (empty($name)) throw new Exception('创建名称不能为空'); 19 | if (!$this->check_filename($name)) throw new Exception('名称不能包含系统禁止的特殊符号'); 20 | 21 | $filepath = $this->encoding($this->path . '/' . $name); 22 | if ($type == 'file') { 23 | if (!touch($filepath)) throw new Exception('文件创建失败'); 24 | } else { 25 | if (!mkdir($filepath)) throw new Exception('文件夹创建失败'); 26 | } 27 | return true; 28 | } 29 | 30 | //重命名 31 | public function rename() 32 | { 33 | $oldname = trim($_POST['oldname']); 34 | $newname = trim($_POST['newname']); 35 | if (empty($oldname)) throw new Exception('原名称不能为空'); 36 | if (empty($newname)) throw new Exception('名称不能为空'); 37 | if (!$this->check_filename($newname)) throw new Exception('名称不能包含系统禁止的特殊符号'); 38 | 39 | $oldpath = $this->encoding($this->path . '/' . $oldname); 40 | $newpath = $this->encoding($this->path . '/' . $newname); 41 | if (!rename($oldpath, $newpath)) throw new Exception('重命名失败'); 42 | return true; 43 | } 44 | 45 | //批量删除文件/文件夹 46 | public function delete() 47 | { 48 | $files = $_POST['files']; 49 | if (!$files || count($files) == 0) throw new Exception('请选择要删除的文件'); 50 | 51 | $count = 0; 52 | foreach ($files as $file) { 53 | $filepath = $this->encoding($this->path . '/' . $file); 54 | if (is_dir($filepath)) { 55 | $this->rm_dir($filepath, $count); 56 | } else { 57 | if (unlink($filepath)) $count++; 58 | } 59 | } 60 | return $count; 61 | } 62 | 63 | //添加到剪贴板 64 | public function addclip() 65 | { 66 | $files = $_POST['files']; 67 | $op = $_POST['op']; 68 | $opname = $op == 'cut' ? '剪切' : '复制'; 69 | if (!$files || count($files) == 0) throw new Exception('请选择要' . $opname . '的文件'); 70 | $_SESSION['filemgr_clip'] = ['dir' => $this->path, 'op' => $op, 'files' => $files]; 71 | return true; 72 | } 73 | 74 | //粘贴 75 | public function paste() 76 | { 77 | $clip = $_SESSION['filemgr_clip']; 78 | if (!$clip || !$clip['op'] || count($clip['files']) == 0) throw new Exception('没有进行复制或剪切'); 79 | if ($clip['dir'] == $this->path) throw new Exception('粘贴目录不能为当前目录'); 80 | 81 | unset($_SESSION['filemgr_clip']); 82 | $op = $clip['op']; 83 | $count = 0; 84 | foreach ($clip['files'] as $file) { 85 | if ($file == '') continue; 86 | 87 | $oldpath = $this->encoding($clip['dir'] . '/' . $file); 88 | $newpath = $this->encoding($this->path . '/' . $file); 89 | 90 | switch ($op) { 91 | case 'cut': 92 | if (rename($oldpath, $newpath)) $count++; 93 | break; 94 | case 'copy': 95 | if (is_dir($oldpath)) { 96 | @mkdir($newpath); 97 | $this->copy_dir($oldpath, $newpath, $count); 98 | } else { 99 | if (copy($oldpath, $newpath)) $count++; 100 | } 101 | break; 102 | } 103 | } 104 | return ['op' => $op, 'count' => $count]; 105 | } 106 | 107 | //查询文件夹访问密码 108 | public function query_secret() 109 | { 110 | $file = $this->encoding($this->path . '/.passwd'); 111 | if (file_exists($file)) { 112 | $passwd = file_get_contents($file); 113 | if ($passwd) { 114 | return ['issecret' => true]; 115 | } 116 | } 117 | return ['issecret' => false]; 118 | } 119 | 120 | //设置文件夹访问密码 121 | public function set_secret() 122 | { 123 | $file = $this->encoding($this->path . '/.passwd'); 124 | $issecret = intval($_POST['issecret']); 125 | if ($issecret == 1) { 126 | $passwd = trim($_POST['passwd']); 127 | if (empty($passwd)) throw new Exception('密码不能为空'); 128 | $passwd = password_hash($passwd, PASSWORD_DEFAULT); 129 | if (!file_put_contents($file, $passwd)) throw new Exception('设置访问密码失败'); 130 | } else { 131 | is_file($file) && unlink($file); 132 | } 133 | return true; 134 | } 135 | 136 | //创建压缩包文件 137 | public function compress() 138 | { 139 | if (!class_exists('ZipArchive')) throw new Exception('php未开启ZipArchive,不支持该功能'); 140 | $name = trim($_POST['name']); 141 | if (empty($name)) throw new Exception('压缩包文件名不能为空'); 142 | if (!$this->check_filename($name)) throw new Exception('名称不能包含系统禁止的特殊符号'); 143 | $files = $_POST['files']; 144 | if (!$files || count($files) == 0) throw new Exception('请选择要压缩的文件'); 145 | 146 | $zipfilepath = $this->encoding($this->path . '/' . $name); 147 | if (file_exists($zipfilepath)) throw new Exception('压缩包文件名已存在'); 148 | 149 | $zip = new ZipArchive(); 150 | if ($zip->open($zipfilepath, ZipArchive::CREATE) === false) throw new Exception('压缩包文件创建失败'); 151 | foreach ($files as $file) { 152 | $filepath = $this->encoding($this->path . '/' . $file); 153 | if (is_dir($filepath)) { 154 | $this->zip_dir($filepath, $zip, strlen($this->path . '/')); 155 | } else { 156 | $zip->addFile($filepath, basename($filepath)); 157 | } 158 | } 159 | $zip->close(); 160 | return true; 161 | } 162 | 163 | //解压缩 164 | public function uncompress() 165 | { 166 | $name = trim($_POST['name']); 167 | $targetdir = trim($_POST['targetdir']); 168 | if (empty($name)) throw new Exception('压缩包文件名不能为空'); 169 | if (empty($targetdir)) throw new Exception('解压目录不能为空'); 170 | 171 | $zipfilepath = $this->encoding($this->path . '/' . $name); 172 | if (!is_file($zipfilepath)) throw new Exception('压缩包文件不存在'); 173 | 174 | $ext = $this->get_file_ext($zipfilepath); 175 | if ($ext == 'zip') { 176 | if (!class_exists('ZipArchive')) throw new Exception('php未开启ZipArchive,不支持解压该文件'); 177 | 178 | if (!is_dir($targetdir)) @mkdir($targetdir, 0777, true); 179 | $zip = new ZipArchive(); 180 | if ($zip->open($zipfilepath) === false) throw new Exception('压缩包文件打开失败'); 181 | $zip->extractTo('.' . $targetdir); 182 | $zip->close(); 183 | } elseif ($ext == 'gz') { 184 | if (!function_exists('gzopen')) throw new Exception('php未开启zlib,不支持解压该文件'); 185 | 186 | if (!is_dir($targetdir)) @mkdir($targetdir, 0777, true); 187 | $fp = gzopen($zipfilepath, 'rb'); 188 | if (!$fp) throw new Exception('压缩包文件打开失败'); 189 | $targetfile = '.' . $targetdir . '/' . substr($name, 0, -3); 190 | $out = fopen($targetfile, 'wb'); 191 | if (!$out) throw new Exception('解压目录打开失败'); 192 | while (!feof($fp)) { 193 | fwrite($out, gzread($fp, 4096)); 194 | } 195 | fclose($out); 196 | gzclose($fp); 197 | } elseif ($ext == 'bz2') { 198 | if (!function_exists('bzopen')) throw new Exception('php未开启bz2扩展,不支持解压该文件'); 199 | 200 | if (!is_dir($targetdir)) @mkdir($targetdir, 0777, true); 201 | $fp = bzopen($zipfilepath, 'r'); 202 | if (!$fp) throw new Exception('压缩包文件打开失败'); 203 | $targetfile = '.' . $targetdir . '/' . substr($name, 0, -4); 204 | $out = fopen($targetfile, 'wb'); 205 | if (!$out) throw new Exception('解压目录打开失败'); 206 | while (!feof($fp)) { 207 | fwrite($out, bzread($fp, 4096)); 208 | } 209 | fclose($out); 210 | bzclose($fp); 211 | } elseif ($ext == 'tar') { 212 | if (!class_exists('PharData')) throw new Exception('php未开启phar,不支持解压该文件'); 213 | 214 | if (!is_dir($targetdir)) @mkdir($targetdir, 0777, true); 215 | $phar = new PharData($zipfilepath); 216 | $phar->extractTo('.' . $targetdir, null, true); 217 | } elseif ($ext == 'tgz') { 218 | if (!function_exists('gzopen')) throw new Exception('php未开启zlib,不支持解压该文件'); 219 | if (!class_exists('PharData')) throw new Exception('php未开启phar,不支持解压该文件'); 220 | 221 | if (!is_dir($targetdir)) @mkdir($targetdir, 0777, true); 222 | $fp = gzopen($zipfilepath, 'rb'); 223 | if (!$fp) throw new Exception('压缩包文件打开失败'); 224 | $targetfile = '.' . $targetdir . '/' . substr($name, 0, -3) . 'tar'; 225 | $out = fopen($targetfile, 'wb'); 226 | if (!$out) throw new Exception('解压目录打开失败'); 227 | while (!feof($fp)) { 228 | fwrite($out, gzread($fp, 4096)); 229 | } 230 | fclose($out); 231 | gzclose($fp); 232 | if (filesize($targetfile) == 0) throw new Exception('解压失败,文件损坏或格式不支持'); 233 | 234 | $phar = new PharData($targetfile); 235 | $phar->extractTo('.' . $targetdir, null, true); 236 | unlink($targetfile); 237 | } else { 238 | throw new Exception('不支持的压缩包格式'); 239 | } 240 | 241 | return true; 242 | } 243 | 244 | //检查文件名 245 | private function check_filename($name) 246 | { 247 | $forbidden = [':', '/', '\\', '?', '<', '>', '|', '*', '"']; 248 | foreach ($forbidden as $a) { 249 | if (strpos($name, $a) !== false) return false; 250 | } 251 | return true; 252 | } 253 | 254 | // 复制文件夹 255 | private function copy_dir($src, $dst, &$count) 256 | { 257 | $rd = opendir($src); 258 | if (!$rd) { 259 | return false; 260 | } 261 | 262 | while (($file = readdir($rd)) !== false) { 263 | if ($file == '.' || $file == '..') { 264 | continue; 265 | } 266 | 267 | $src_file = $src . '/' . $file; 268 | 269 | if (is_dir($src_file)) { 270 | @mkdir($dst . '/' . $file); 271 | $this->copy_dir($src_file, $dst . '/' . $file, $count); 272 | } else { 273 | ++$count; 274 | copy($src_file, $dst . '/' . $file); 275 | } 276 | } 277 | 278 | closedir($rd); 279 | ++$count; 280 | return true; 281 | } 282 | 283 | // 删除文件夹 284 | private function rm_dir($dir, &$count) 285 | { 286 | $rd = opendir($dir); 287 | if (!$rd) { 288 | return false; 289 | } 290 | 291 | while (($file = readdir($rd)) !== false) { 292 | if ($file == '.' || $file == '..') { 293 | continue; 294 | } 295 | 296 | $file = $dir . '/' . $file; 297 | 298 | if (is_dir($file)) { 299 | $this->rm_dir($file, $count); 300 | } else { 301 | ++$count; 302 | unlink($file); 303 | } 304 | } 305 | 306 | closedir($rd); 307 | ++$count; 308 | rmdir($dir); 309 | return true; 310 | } 311 | 312 | // 压缩文件夹 313 | private function zip_dir($folder, &$zipFile, $exclusiveLength) 314 | { 315 | $handle = opendir($folder); 316 | if (!$handle) { 317 | return false; 318 | } 319 | while (($file = readdir($handle)) !== false) { 320 | if ($file == '.' || $file == '..' || $file == '.passwd') { 321 | continue; 322 | } 323 | $filePath = $folder . '/' . $file; 324 | 325 | // 在添加到zip之前从文件路径中删除前缀 326 | $localPath = substr($filePath, $exclusiveLength); 327 | if (is_dir($filePath)) { 328 | $zipFile->addEmptyDir($localPath); 329 | self::zip_dir($filePath, $zipFile, $exclusiveLength); 330 | } else { 331 | $zipFile->addFile($filePath, $localPath); 332 | } 333 | } 334 | closedir($handle); 335 | } 336 | 337 | 338 | // 解决中文文件名编码问题 339 | private function encoding($str, $type = false) 340 | { 341 | global $conf; 342 | if ($conf['name_encode'] == 'gbk' && preg_match("/[\x7f-\xff]/", $str)) { 343 | if ($type) { 344 | return mb_convert_encoding($str, 'UTF-8', 'GBK'); 345 | } else { 346 | return mb_convert_encoding($str, 'GBK', 'UTF-8'); 347 | } 348 | } 349 | return $str; 350 | } 351 | 352 | // 获取文件后缀名 353 | private function get_file_ext($filepath) 354 | { 355 | $suffix = explode(".", $filepath); 356 | $suffix = end($suffix); 357 | $suffix = strtolower($suffix); 358 | return $suffix; 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /_dir/page/home.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 |
19 |
20 |
21 | 22 |
23 |
提示信息
24 |
25 |
26 | 返回首页 27 |
28 |
29 |
30 | 33 |
34 | 35 |
36 | 38 |
39 | 40 |
41 | 42 |
43 | 44 |
45 |
46 |
47 | 49 |

50 | 的搜索结果 () 51 |

52 | 53 | 54 |

55 | 当前位置:'.$item['name'].' / '; 58 | } 59 | ?> 60 |

61 | 62 | 63 | 64 | 66 |
67 |
68 |
69 |
当前目录已加密
70 |
71 |
72 |
73 |
74 | 75 |
76 | 77 |
78 |
79 |
80 |
81 | <<返回上级 82 |
83 |
84 |
85 |
86 |
87 |
88 | 89 |
90 | 91 | 101 | 102 | 103 | 104 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 124 | 126 | 127 | 128 | 130 | 131 | 138 | 139 | 145 | 148 | 155 | 161 | 162 | 163 | 185 | 209 | 210 | 213 | 214 |
105 |
106 | 107 | 108 |
109 | 110 |
文件名修改时间大小操作
122 | .. 123 | 125 | -- 129 |
140 |
141 | 142 | 143 |
144 |
146 | 147 | 149 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 184 |
215 | 0 && $r['pagenum'] > 0) echo generatePagination($r['page'], $r['pagenum']);?> 216 |
217 | text($content); 224 | $content = str_replace('[x]','',$content); 225 | $content = str_replace('[ ]','',$content); 226 | ?> 227 |
228 |
229 | README.md 230 |
231 |
232 |
233 | 234 |
235 |
236 |
237 | 241 | 242 |
243 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /_dir/DirList.class.php: -------------------------------------------------------------------------------- 1 | true, 7 | // 隐藏文件 8 | 'hidden_files' => [ 9 | 'index.php', 10 | '.htaccess', 11 | '*/.htaccess', 12 | '_dir', 13 | '_dir/*', 14 | 'robots.txt', 15 | ], 16 | ]; 17 | 18 | // 获取文件后缀名 19 | public function get_file_ext($filepath) 20 | { 21 | $suffix = explode(".", $filepath); 22 | $suffix = end($suffix); 23 | $suffix = strtolower($suffix); 24 | return $suffix; 25 | } 26 | 27 | // 获取文件预览类型 28 | public function get_view_type($type) 29 | { 30 | $type_image = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'ico', 'svg', 'svgz', 'tif', 'tiff', 'heic', 'exif']; 31 | $type_audio = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac']; 32 | $type_video = ['mp4', 'webm', 'flv', 'f4v', 'mov', '3gp', '3gpp', 'avi', 'wmv', 'mkv', 'ts', 'dat', 'asf', 'mts', 'm2ts', 'm3u8', 'm4v']; 33 | $type_office = ['doc', 'docx', 'xps', 'rtf', 'wps', 'xls', 'xlsx', 'ppt', 'pptx']; 34 | $type_markdown = ['md']; 35 | $type_text = ['txt', 'text', 'log', 'yaml', 'yml', 'conf', 'config', 'ini', 'c', 'cpp', 'cxx', 'rc', 'php', 'py', 'cs', 'h', 'htm', 'html', 'css', 'less', 'sass', 'scss', 'js', 'hdml', 'dtd', 'wml', 'xml', 'xsl', 'vbs', 'vb', 'rtx', 'xsd', 'dpr', 'sql', 'java', 'go', 'jsp', 'asp', 'aspx', 'asa', 'asax', 'pl', 'bat', 'cmd', 'rb', 'reg', 'sh', 'json', 'lua', 'r', 'm', 'mm', 'mak', 'swift', 'tpl', 'vue', 'rs']; 36 | if (in_array($type, $type_image)) { 37 | return 'image'; 38 | } elseif (in_array($type, $type_audio)) { 39 | return 'audio'; 40 | } elseif (in_array($type, $type_video)) { 41 | return 'video'; 42 | } elseif (in_array($type, $type_office)) { 43 | return 'office'; 44 | } elseif (in_array($type, $type_markdown)) { 45 | return 'markdown'; 46 | } elseif (in_array($type, $type_text)) { 47 | return 'text'; 48 | } else { 49 | return ''; 50 | } 51 | } 52 | 53 | // 获取文件小图标 54 | public function get_file_icon($type) 55 | { 56 | $type_image = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'ico', 'svg', 'svgz', 'tif', 'tiff', 'heic', 'psd', 'exif', 'pcx', 'tga', 'fpx', 'cdr', 'pcd', 'eps', 'ai', 'wmf', 'raw', 'ufo', 'jpc', 'jp2', 'jpx', 'xbm', 'wbmp', 'avif', 'heif']; 57 | $type_audio = ['mp3', 'wav', 'wma', 'ogg', 'm4a', 'flac', 'ape', 'aac', 'ra', 'cda', 'midi', 'mid', 'aif', 'au', 'voc']; 58 | $type_video = ['mp4', 'webm', 'flv', 'f4v', 'mov', '3gp', '3gpp', 'avi', 'mpg', 'mpeg', 'wmv', 'mkv', 'ts', 'dat', 'asf', 'rm', 'rmvb', 'ram', 'divx', 'vob', 'qt', 'fli', 'flc', 'mod', 'm2t', 'swf', 'mts', 'm2ts', 'mpe', 'div', 'lavf', 'm3u8', 'm4v', 'ogm', 'ogv', 'ogx', 'drc', 'dts', 'eac3', 'mka', 'mks', 'mpv', 'mp4v', 'mp2v', 'mp2t', 'mxf']; 59 | $type_text = ['txt', 'text', 'log', 'md', 'yaml', 'yml', 'conf', 'config', 'ini']; 60 | $type_code = ['c', 'cpp', 'cxx', 'rc', 'php', 'py', 'cs', 'h', 'htm', 'html', 'css', 'less', 'sass', 'scss', 'js', 'hdml', 'dtd', 'wml', 'xml', 'xsl', 'vbs', 'vb', 'rtx', 'xsd', 'dpr', 'sql', 'java', 'go', 'jsp', 'asp', 'aspx', 'asa', 'asax', 'pl', 'bat', 'cmd', 'rb', 'reg', 'sh', 'json', 'lua', 'r', 'm', 'mm', 'mak', 'swift', 'tpl', 'vue', 'rs']; 61 | $type_archive = ['zip', '7z', 'rar', 'tgz', 'gz', 'xz', 'tar', 'jar', 'iso', 'z', 'zipx', 'cab', 'bz2', 'arj', 'lz', 'lzh']; 62 | $type_word = ['doc', 'docx', 'xps', 'rtf', 'wps', 'odt', 'dot', 'dotx', 'dotm', 'docm', 'docb']; 63 | $type_excel = ['xls', 'xlsx', 'ods', 'csv', 'xlsm', 'xlsb', 'xltx', 'xltm', 'xlt']; 64 | $type_pdf = ['pdf', 'epub']; 65 | $type_powerpoint = ['ppt', 'pptx', 'pps', 'ppsx', 'pot', 'potx', 'potm', 'pptm', 'ppsm']; 66 | $type_android = ['apk', 'aab', 'obb', 'xapk']; 67 | $type_apple = ['ipa', 'dmg']; 68 | $type_windows = ['exe', 'appx', 'msi']; 69 | $type_linux = ['deb', 'rpm']; 70 | if (in_array($type, $type_image)) { 71 | return 'fa-file-image-o'; 72 | } elseif (in_array($type, $type_audio)) { 73 | return 'fa-file-audio-o'; 74 | } elseif (in_array($type, $type_video)) { 75 | return 'fa-file-video-o'; 76 | } elseif (in_array($type, $type_text)) { 77 | return 'fa-file-text-o'; 78 | } elseif (in_array($type, $type_code)) { 79 | return 'fa-file-code-o'; 80 | } elseif (in_array($type, $type_archive)) { 81 | return 'fa-file-archive-o'; 82 | } elseif (in_array($type, $type_word)) { 83 | return 'fa-file-word-o'; 84 | } elseif (in_array($type, $type_excel)) { 85 | return 'fa-file-excel-o'; 86 | } elseif (in_array($type, $type_pdf)) { 87 | return 'fa-file-pdf-o'; 88 | } elseif (in_array($type, $type_powerpoint)) { 89 | return 'fa-file-powerpoint-o'; 90 | } elseif (in_array($type, $type_android)) { 91 | return 'fa-android'; 92 | } elseif (in_array($type, $type_apple)) { 93 | return 'fa-apple'; 94 | } elseif (in_array($type, $type_windows)) { 95 | return 'fa-windows'; 96 | } elseif (in_array($type, $type_linux)) { 97 | return 'fa-linux'; 98 | } else { 99 | return 'fa-file-o'; 100 | } 101 | } 102 | 103 | // 文件大小格式化 104 | private function size_format($size) 105 | { 106 | if ($size < 1024) { 107 | $size .= ' B'; 108 | } else { 109 | $size /= 1024; 110 | if ($size < 1024) { 111 | $size = round($size, 2) . ' KB'; 112 | } else { 113 | $size /= 1024; 114 | if ($size < 1024) { 115 | $size = round($size, 2) . ' MB'; 116 | } else { 117 | $size /= 1024; 118 | if ($size < 1024) { 119 | $size = round($size, 2) . ' GB'; 120 | } 121 | } 122 | } 123 | } 124 | return $size; 125 | } 126 | 127 | // 检测路径 128 | public function set_dir_path($dir, $is_file = false) 129 | { 130 | $dir = str_replace("\\", "/", $dir); 131 | 132 | while (strpos($dir, '//')) { 133 | $dir = str_replace('//', '/', $dir); 134 | } 135 | 136 | if (substr($dir, -1, 1) == '/') { 137 | $dir = substr($dir, 0, -1); 138 | } 139 | 140 | if (substr($dir, 0, 1) == '/') { 141 | $dir = substr($dir, 1); 142 | } 143 | 144 | if (empty($dir) || $dir == '.') { 145 | return '.'; 146 | } 147 | 148 | if ( 149 | strpos($dir, '<') !== false || strpos($dir, '>') !== false 150 | || strpos($dir, '..') !== false || strpos($dir, '/./') !== false 151 | ) { 152 | throw new Exception('检测到无效的路径字符串'); 153 | } 154 | 155 | if ($this->is_hide($dir)) { 156 | throw new Exception('拒绝访问'); 157 | } 158 | 159 | $dir = $this->encoding($dir, false); 160 | 161 | if ($is_file) { 162 | if (!file_exists($dir) || !is_file($dir)) { 163 | throw new Exception('文件路径不存在'); 164 | } 165 | } else { 166 | if (!file_exists($dir) || !is_dir($dir)) { 167 | throw new Exception('文件路径不存在'); 168 | } 169 | } 170 | 171 | return $dir; 172 | } 173 | 174 | // 扫描所有文件 175 | private function scan_files($dir = '.') 176 | { 177 | global $conf, $islogin; 178 | $list = []; 179 | if (is_file($dir . '/.passwd') && filesize($dir . '/.passwd') > 0) { 180 | if (!($conf['cache_indexes'] == 0 && ($islogin || isset($_COOKIE['dir_passwd']) && $_COOKIE['dir_passwd'] === md5(file_get_contents($dir . '/.passwd'))))) { 181 | return $list; 182 | } 183 | } 184 | $files = scandir($dir); 185 | foreach ($files as $file) { 186 | if ($file == '.' || $file == '..') continue; 187 | 188 | $relativePath = $dir . '/' . $file; 189 | if (substr($relativePath, 0, 2) == './') { 190 | $relativePath = substr($relativePath, 2); 191 | } 192 | 193 | if ($this->is_hide($relativePath)) continue; 194 | 195 | $name = $this->encoding($file, true); 196 | $relativePathEncode = $this->encoding($relativePath, true); 197 | 198 | $ctime = filemtime($relativePath); 199 | $ctime = date("Y-m-d H:i", $ctime); 200 | 201 | if (is_dir($relativePath)) { 202 | $list = array_merge($list, $this->scan_files($relativePath)); 203 | } else { 204 | $list[] = [ 205 | 'name' => $name, 206 | 'path' => $relativePathEncode, 207 | ]; 208 | } 209 | } 210 | return $list; 211 | } 212 | 213 | // 获取所有文件 214 | public function get_all_files() 215 | { 216 | global $conf, $CACHE; 217 | switch ($conf['cache_indexes']) { 218 | case '1': 219 | $cache_time = 3600; 220 | break; 221 | case '2': 222 | $cache_time = 3600 * 6; 223 | break; 224 | case '3': 225 | $cache_time = 3600 * 24; 226 | break; 227 | default: 228 | $cache_time = 0; 229 | break; 230 | } 231 | if ($cache_time > 0) { 232 | $all_files = $CACHE->get('indexes'); 233 | if ($all_files && count($all_files) > 0) { 234 | return $all_files; 235 | } 236 | } 237 | $all_files = $this->scan_files(); 238 | if ($cache_time > 0) { 239 | $CACHE->set('indexes', $all_files, $cache_time); 240 | } 241 | return $all_files; 242 | } 243 | 244 | // 文件搜索 245 | public function search_files($s) 246 | { 247 | if (empty($s)) return []; 248 | $list = []; 249 | $all_files = $this->get_all_files(); 250 | foreach ($all_files as $file) { 251 | if (stripos($file['name'], $s) === false) continue; 252 | $relativePathEncode = $file['path']; 253 | $relativePath = $this->encoding($file['path'], false); 254 | $ctime = filemtime($relativePath); 255 | $ctime = date("Y-m-d H:i", $ctime); 256 | $src = './' . implode('/', array_map('rawurlencode', explode('/', $relativePath))); 257 | $ext = $this->get_file_ext($relativePath); 258 | $icon = $this->get_file_icon($ext); 259 | $view_type = $this->get_view_type($ext); 260 | $size = filesize($relativePath); 261 | $list[] = [ 262 | 'type' => 'file', 263 | 'name' => $file['name'], 264 | 'path' => $relativePathEncode, 265 | 'src' => $src, 266 | 'icon' => $icon, 267 | 'mtime' => $ctime, 268 | 'size' => $size, 269 | 'size_format' => $this->size_format($size), 270 | 'view_type' => $view_type, 271 | ]; 272 | if (count($list) >= 100) break; 273 | } 274 | return $list; 275 | } 276 | 277 | // 目录列表 278 | public function list_dir($dir = '.') 279 | { 280 | global $conf; 281 | $dir = $this->set_dir_path($dir); 282 | 283 | $navi = $this->get_navigation($dir); 284 | 285 | $newdir = []; 286 | $newfile = []; 287 | $audio_list = []; 288 | 289 | $readme_md = null; 290 | 291 | $files = scandir($dir); 292 | foreach ($files as $file) { 293 | if ($file == '.' || $file == '..') continue; 294 | 295 | $relativePath = $dir . '/' . $file; 296 | if (substr($relativePath, 0, 2) == './') { 297 | $relativePath = substr($relativePath, 2); 298 | } 299 | 300 | if ($this->is_hide($relativePath)) continue; 301 | 302 | $name = $this->encoding($file, true); 303 | $relativePathEncode = $this->encoding($relativePath, true); 304 | 305 | $ctime = filemtime($relativePath); 306 | $ctime = date("Y-m-d H:i", $ctime); 307 | 308 | if (is_dir($relativePath)) { 309 | $src = './?dir=' . rawurlencode('/' . $relativePathEncode); 310 | $newdir[] = [ 311 | 'type' => 'dir', 312 | 'name' => $name, 313 | 'path' => $relativePathEncode, 314 | 'src' => $src, 315 | 'icon' => 'fa-folder-open', 316 | 'mtime' => $ctime, 317 | 'size' => false, 318 | 'size_format' => '-', 319 | 'view_type' => false, 320 | 'ext' => false, 321 | ]; 322 | } else { 323 | $src = './' . implode('/', array_map('rawurlencode', explode('/', $relativePath))); 324 | $ext = $this->get_file_ext($relativePath); 325 | $icon = $this->get_file_icon($ext); 326 | $view_type = $this->get_view_type($ext); 327 | $size = filesize($relativePath); 328 | $newfile[] = [ 329 | 'type' => 'file', 330 | 'name' => $name, 331 | 'path' => $relativePathEncode, 332 | 'src' => $src, 333 | 'icon' => $icon, 334 | 'mtime' => $ctime, 335 | 'size' => $size, 336 | 'size_format' => $this->size_format($size), 337 | 'view_type' => $view_type, 338 | 'ext' => $ext, 339 | ]; 340 | if ($view_type == 'audio') { 341 | $audio_list[] = [ 342 | 'name' => $name, 343 | 'url' => $src, 344 | ]; 345 | } 346 | if (($name == 'readme.md' || $name == 'README.md') && $size < 1024 * 1024 * 5) $readme_md = $relativePath; 347 | } 348 | } 349 | $listdir = array_merge($newdir, $newfile); 350 | 351 | if ($conf['page_size'] > 0) { 352 | $pagesize = intval($conf['page_size']); 353 | $pagenum = ceil(count($listdir) / $pagesize); 354 | if ($pagenum > 1) { 355 | $page = isset($_GET['page']) ? intval($_GET['page']) : 1; 356 | $listdir = array_slice($listdir, ($page - 1) * $pagesize, $pagesize); 357 | } 358 | } 359 | 360 | $parent = null; 361 | if ($dir != '.') { 362 | $pathArray = explode('/', '/' . $dir); 363 | unset($pathArray[count($pathArray) - 1]); 364 | $parentPath = implode('/', $pathArray); 365 | if ($parentPath == '') $parent = './'; 366 | else $parent = './?dir=' . rawurlencode($this->encoding($parentPath, true)); 367 | } 368 | $passwd = $this->get_passwd($dir); 369 | 370 | $result = ['dir' => $dir, 'list' => $listdir, 'navi' => $navi, 'parent' => $parent, 'readme_md' => $readme_md, 'passwd' => $passwd, 'audio_list' => $audio_list, 'page' => $page, 'pagenum' => $pagenum]; 371 | return $result; 372 | } 373 | 374 | // 获取目录访问密码 375 | private function get_passwd($dir) 376 | { 377 | if ($dir != '.') $dir = './' . $dir; 378 | 379 | $level = substr_count($dir, '/') + 1; 380 | while ($level > 0) { 381 | $file = $dir . '/.passwd'; 382 | if (is_file($file)) { 383 | $passwd = file_get_contents($file); 384 | if ($passwd) { 385 | return $passwd; 386 | } 387 | } 388 | $level--; 389 | $dir = dirname($dir); 390 | } 391 | return null; 392 | } 393 | 394 | // 获取导航栏 395 | private function get_navigation($dir) 396 | { 397 | $navi = [ 398 | ['name' => '首页', 'src' => './'] 399 | ]; 400 | $navi_arr = explode('/', $dir); 401 | $navi_src = ''; 402 | foreach ($navi_arr as $name) { 403 | if ($name == '.') continue; 404 | $name = $this->encoding($name, true); 405 | $navi_src .= '/' . rawurlencode($name); 406 | $navi[] = ['name' => $name, 'src' => './?dir=' . $navi_src]; 407 | } 408 | return $navi; 409 | } 410 | 411 | // 文件是否隐藏 412 | private function is_hide($filePath) 413 | { 414 | $hidden_files = $this->config['hidden_files']; 415 | if ($this->config['hide_dot_files']) { 416 | $hidden_files = array_merge( 417 | $hidden_files, 418 | array('.*', '*/.*') 419 | ); 420 | } 421 | 422 | foreach ($hidden_files as $hiddenPath) { 423 | if (fnmatch($hiddenPath, $filePath)) { 424 | return true; 425 | } 426 | } 427 | return false; 428 | } 429 | 430 | // 解决中文文件名编码问题 431 | private function encoding($str, $type = false) 432 | { 433 | global $conf; 434 | if ($conf['name_encode'] == 'gbk' && preg_match("/[\x7f-\xff]/", $str)) { 435 | if ($type) { 436 | return mb_convert_encoding($str, 'UTF-8', 'GBK'); 437 | } else { 438 | return mb_convert_encoding($str, 'GBK', 'UTF-8'); 439 | } 440 | } 441 | return $str; 442 | } 443 | 444 | // 文件hash 445 | public function get_file_hash($path) 446 | { 447 | global $conf; 448 | if (!$conf['file_hash']) throw new Exception('未开启该功能'); 449 | $path = $this->set_dir_path($path, true); 450 | $name = basename($path); 451 | $md5 = md5_file($path); 452 | $sha1 = sha1_file($path); 453 | return ['code' => 0, 'name' => $name, 'md5' => $md5, 'sha1' => $sha1]; 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /_dir/static/js/main.js: -------------------------------------------------------------------------------- 1 | var pageurl = window.location.protocol + '//' + window.location.host + window.location.pathname; 2 | var page_reload = false; 3 | 4 | function copy(src){ 5 | var url = pageurl + src.slice(2); 6 | new clipBoard(document.getElementById('list'), { 7 | beforeCopy: function() { 8 | 9 | }, 10 | copy: function() { 11 | return url; 12 | }, 13 | afterCopy: function() { 14 | layer.msg('链接已复制!', {icon:1, time:1000}); 15 | } 16 | }); 17 | } 18 | 19 | function qrcode(src){ 20 | var url = pageurl + src.slice(2); 21 | var content = '
'; 22 | layer.open({ 23 | type: 1, 24 | area: '220px', 25 | title: false, 26 | content: content, 27 | closeBtn: 2, 28 | shadeClose: true, 29 | success: function(layero, index){ 30 | $('#qrcode').qrcode({ 31 | text: url, 32 | width: 200, 33 | height: 200, 34 | foreground: "#000000", 35 | background: "#ffffff", 36 | typeNumber: -1 37 | }); 38 | } 39 | }); 40 | } 41 | 42 | function filehash(path){ 43 | var ii = layer.load(); 44 | $.post("./?c=hash", {path:path}, function(data){ 45 | layer.close(ii); 46 | if(data.code == 0){ 47 | layer.open({ 48 | title: data.name, 49 | shadeClose: true, 50 | area: ['400px', 'auto'], 51 | content: 'md5: ' + data.md5 + '
sha1: ' + data.sha1 52 | }); 53 | }else{ 54 | layer.alert(data.msg, {icon:7}); 55 | } 56 | }, 'json'); 57 | } 58 | 59 | function view_image(src){ 60 | var resourcesUrl = pageurl + src.slice(2); 61 | 62 | var img = new Image(); 63 | img.onload = function () {//避免图片还未加载完成无法获取到图片的大小。 64 | //避免图片太大,导致弹出展示超出了网页显示访问,所以图片大于浏览器时下窗口可视区域时,进行等比例缩小。 65 | var max_height = $(window).height() - 100; 66 | var max_width = $(window).width(); 67 | 68 | //rate1,rate2,rate3 三个比例中取最小的。 69 | var rate1 = max_height / img.height; 70 | var rate2 = max_width / img.width; 71 | var rate3 = 1; 72 | var rate = Math.min(rate1, rate2, rate3); 73 | //等比例缩放 74 | var imgHeight = img.height * rate; //获取图片高度 75 | var imgWidth = img.width * rate; //获取图片宽度 76 | 77 | var imgHtml = ""; 78 | //弹出层 79 | layer.open({ 80 | type:1, 81 | shade: 0.6, 82 | anim: 1, 83 | title: false, 84 | area: ['auto', 'auto'], 85 | shadeClose: true, 86 | content: imgHtml 87 | }); 88 | } 89 | img.src = resourcesUrl; 90 | } 91 | 92 | function view_audio(src){ 93 | if(audio_list == null || audio_list.length == 0){ 94 | layer.msg('当前列表没有音频文件', {icon:2, time:1000}); 95 | return; 96 | } 97 | $.each(audio_list, function(key, item){ 98 | item.artist = 'artist'; 99 | item.cover = './_dir/static/images/music.png'; 100 | }); 101 | var index = audio_list.findIndex(item => item.url == src); 102 | if(index == -1){ 103 | layer.msg('音频文件不存在', {icon:2, time:1000}); 104 | return; 105 | } 106 | layer.closeAll(); 107 | if(!aplayer){ 108 | aplayer = new APlayer({ 109 | container: document.getElementById('aplayer'), 110 | fixed: true, 111 | loop: 'none', 112 | audio: audio_list 113 | }); 114 | } 115 | aplayer.setMode('normal'); 116 | aplayer.list.switch(index); 117 | aplayer.play(); 118 | /*var apiurl = './?c=audio&path=' + encodeURIComponent(path); 119 | layer.open({ 120 | type: 2, 121 | shade: 0.6, 122 | title:false, 123 | area: [$(window).width() > 768 ? '60%' : '95%', '88px'], 124 | shadeClose: true, 125 | content: apiurl 126 | });*/ 127 | } 128 | 129 | function view_video(name, path){ 130 | if(aplayer && aplayer.audio && !aplayer.audio.paused){ 131 | aplayer.pause(); 132 | } 133 | var apiurl = './?c=video&path=' + encodeURIComponent(path); 134 | layer.open({ 135 | type: 2, 136 | shade: 0.6, 137 | title: '视频播放器 - ' + name, 138 | area: [$(window).width() > 768 ? '68%' : '95%', $(window).width() > 768 ? '78%' : '280px'], 139 | shadeClose: true, 140 | content: apiurl 141 | }); 142 | } 143 | 144 | function view_markdown(name, path){ 145 | var apiurl = './?c=markdown&path=' + encodeURIComponent(path); 146 | layer.open({ 147 | title:'MarkDown查看器 - ' + name, 148 | type: 2, 149 | area: ['100%', '100%'], 150 | content: apiurl 151 | }); 152 | } 153 | 154 | function view_text(name, path){ 155 | var apiurl = './?c=text&path=' + encodeURIComponent(path); 156 | layer.open({ 157 | title:'文本查看器 - ' + name, 158 | type: 2, 159 | area: ['100%', '100%'], 160 | content: apiurl 161 | }); 162 | } 163 | 164 | function view_office(name, src){ 165 | var url = pageurl + src.slice(2); 166 | var apiurl = 'https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(url); 167 | layer.open({ 168 | title: name, 169 | type: 2, 170 | area: ['100%', '100%'], 171 | content: apiurl 172 | }); 173 | } 174 | 175 | function submitpasswd(){ 176 | var dir = $("#dir").val(); 177 | var passwd = $("input[name='passwd']").val(); 178 | var ii = layer.load(); 179 | $.ajax({ 180 | type : 'POST', 181 | url : './?dir=' + encodeURIComponent(dir), 182 | data : {passwd:passwd}, 183 | dataType : 'json', 184 | success : function(data) { 185 | layer.close(ii); 186 | if(data.code == 0){ 187 | window.location.reload(); 188 | }else{ 189 | layer.msg(data.msg, {icon:2, time:1000}); 190 | } 191 | } 192 | }); 193 | return false; 194 | } 195 | 196 | function tinyview(obj){ 197 | var info = $(obj).parent().parent().find('template').html(); 198 | var html = '
' + info + '
'; 199 | layer.open({ 200 | type: 1, 201 | shade: 0.6, 202 | title:false, 203 | area: ['340px', '180px'], 204 | shadeClose: true, 205 | content: html 206 | }); 207 | } 208 | 209 | function change_checkboxes(e, t) { for (var n = e.length - 1; n >= 0; n--) e[n].checked = "boolean" == typeof t ? t : !e[n].checked } 210 | function get_checkboxes() { for (var e = document.getElementsByName("file[]"), t = [], n = e.length - 1; n >= 0; n--) (e[n].type = "checkbox") && t.push(e[n]); return t } 211 | function checkbox_toggle(obj) { change_checkboxes(get_checkboxes(), obj.checked) } 212 | function get_checked_values() { 213 | var chk_value = new Array(); 214 | $("input[name='file[]']:checked").each(function(){ 215 | chk_value.push($(this).val()); 216 | }) 217 | return chk_value; 218 | } 219 | 220 | function admin_upload(){ 221 | var dir = $("#dir").val(); 222 | if($(window).width() > 768){ 223 | layer.open({ 224 | type:2, 225 | title: false, 226 | area: ["720px",";max-height:100%;min-height:490px"], 227 | content: './?c=upload&path=' + encodeURIComponent(dir), 228 | end: function(){ 229 | if(page_reload){ 230 | window.location.reload() 231 | } 232 | } 233 | }); 234 | }else{ 235 | layer.open({ 236 | type:2, 237 | title: '上传', 238 | shadeClose: true, 239 | skin: 'layui-layer-molv', 240 | area: ["100%",";max-height:100%;min-height:490px"], 241 | content: './?c=upload&path=' + encodeURIComponent(dir), 242 | end: function(){ 243 | if(page_reload){ 244 | window.location.reload() 245 | } 246 | } 247 | }); 248 | } 249 | } 250 | 251 | function admin_create(){ 252 | var dir = $("#dir").val(); 253 | layer.open({ 254 | area: ['360px'], 255 | title: '创建文件/文件夹', 256 | content: '


', 257 | btn: ['创建', '取消'], 258 | yes: function(){ 259 | var newfile = $("input[name='newfile']:checked").val(); 260 | var newfilename = $("input[name='newfilename']").val(); 261 | if(newfilename == ''){ 262 | $("input[name='newfilename']").focus(); 263 | return; 264 | } 265 | var ii = layer.load(); 266 | $.ajax({ 267 | type : 'POST', 268 | url : './?c=filemgr', 269 | data : {do:'create', path:dir, type:newfile, name:newfilename}, 270 | dataType : 'json', 271 | success : function(data) { 272 | layer.close(ii); 273 | if(data.code == 0){ 274 | window.location.reload() 275 | }else{ 276 | layer.alert(data.msg, {icon:2}); 277 | } 278 | } 279 | }); 280 | } 281 | }); 282 | } 283 | 284 | function admin_secret(){ 285 | var dir = $("#dir").val(); 286 | var files = get_checked_values() 287 | if(files.length > 0){ 288 | layer.alert('只支持设置当前目录的密码,请勿选中任何文件或文件夹', {icon:7}); 289 | return; 290 | } 291 | var ii = layer.load(); 292 | $.ajax({ 293 | type : 'POST', 294 | url : './?c=filemgr', 295 | data : {do:'query_secret', path:dir}, 296 | dataType : 'json', 297 | success : function(data) { 298 | layer.close(ii); 299 | if(data.code == 0){ 300 | layer.open({ 301 | area: ['360px','240px'], 302 | title: '设置目录密码访问', 303 | content: '
', 304 | btn: ['确定', '取消'], 305 | success: function(){ 306 | var issecret = data.data.issecret; 307 | if(issecret){ 308 | $("#customRadioInline2").prop('checked', true); 309 | $("#passwd_frame").show(); 310 | $("#passwd").attr('placeholder','填写后可重置密码'); 311 | }else{ 312 | $("#customRadioInline1").prop('checked', true); 313 | } 314 | $("input[name='issecret']").click(function(){ 315 | var issecret = $("input[name='issecret']:checked").val(); 316 | if(issecret=='1'){ 317 | $("#passwd_frame").show(); 318 | }else{ 319 | $("#passwd_frame").hide(); 320 | } 321 | }) 322 | }, 323 | yes: function(){ 324 | var issecret = $("input[name='issecret']:checked").val(); 325 | var passwd = $("input[name='passwd']").val(); 326 | if(issecret == '1' && passwd == ''){ 327 | $("input[name='passwd']").focus(); 328 | return; 329 | } 330 | var ii = layer.load(); 331 | $.ajax({ 332 | type : 'POST', 333 | url : './?c=filemgr', 334 | data : {do:'set_secret', path:dir, issecret:issecret, passwd:passwd}, 335 | dataType : 'json', 336 | success : function(data) { 337 | layer.close(ii); 338 | if(data.code == 0){ 339 | layer.alert('修改成功', {icon:1}, function(){window.location.reload()}); 340 | }else{ 341 | layer.alert(data.msg, {icon:2}); 342 | } 343 | } 344 | }); 345 | } 346 | }); 347 | }else{ 348 | layer.alert(data.msg, {icon:2}); 349 | } 350 | } 351 | }); 352 | } 353 | 354 | function admin_rename(name){ 355 | var dir = $("#dir").val(); 356 | layer.open({ 357 | area: ['360px'], 358 | title: '重命名', 359 | content: '
', 360 | btn: ['确定', '取消'], 361 | yes: function(){ 362 | var newname = $("input[name='newname']").val(); 363 | if(newname == ''){ 364 | $("input[name='newname']").focus(); 365 | return; 366 | } 367 | var ii = layer.load(); 368 | $.ajax({ 369 | type : 'POST', 370 | url : './?c=filemgr', 371 | data : {do:'rename', path:dir, oldname:name, newname:newname}, 372 | dataType : 'json', 373 | success : function(data) { 374 | layer.close(ii); 375 | if(data.code == 0){ 376 | window.location.reload() 377 | }else{ 378 | layer.alert(data.msg, {icon:2}); 379 | } 380 | } 381 | }); 382 | } 383 | }); 384 | } 385 | 386 | function admin_delete(name, type){ 387 | var dir = $("#dir").val(); 388 | var typename = type == 'file'?'文件':'文件夹'; 389 | var confirmobj = layer.confirm('确实要删除此'+typename+'吗?删除后无法恢复', { 390 | btn: ['确定','取消'], icon:0, title: '删除'+typename 391 | }, function(){ 392 | var files = new Array(); 393 | files.push(name); 394 | var ii = layer.load(); 395 | $.ajax({ 396 | type : 'POST', 397 | url : './?c=filemgr', 398 | data : {do:'delete', path:dir, files:files}, 399 | dataType : 'json', 400 | success : function(data) { 401 | layer.close(ii); 402 | if(data.code == 0){ 403 | layer.alert('成功删除了'+data.data+'个文件或目录', {icon:1}, function(){window.location.reload()}); 404 | }else{ 405 | layer.alert(data.msg, {icon: 2}); 406 | } 407 | } 408 | }); 409 | }, function(){ 410 | layer.close(confirmobj); 411 | }); 412 | } 413 | 414 | function admin_delete_batch(){ 415 | var dir = $("#dir").val(); 416 | var files = get_checked_values() 417 | if(files.length == 0){ 418 | layer.msg('未选中任何文件或文件夹', {time:1000}); 419 | return; 420 | } 421 | var confirmobj = layer.confirm('确实要删除所选的'+files.length+'个文件或目录吗?删除后无法恢复', { 422 | btn: ['确定','取消'], icon:0, title: '批量删除' 423 | }, function(){ 424 | var ii = layer.load(); 425 | $.ajax({ 426 | type : 'POST', 427 | url : './?c=filemgr', 428 | data : {do:'delete', path:dir, files:files}, 429 | dataType : 'json', 430 | success : function(data) { 431 | layer.close(ii); 432 | if(data.code == 0){ 433 | layer.alert('成功删除了'+data.data+'个文件或目录', {icon:1}, function(){window.location.reload()}); 434 | }else{ 435 | layer.alert(data.msg, {icon: 2}); 436 | } 437 | } 438 | }); 439 | }, function(){ 440 | layer.close(confirmobj); 441 | }); 442 | } 443 | 444 | function admin_addclip(op, name){ 445 | var dir = $("#dir").val(); 446 | var opname = op == 'cut'?'剪切':'复制'; 447 | var files = new Array(); 448 | files.push(name); 449 | var ii = layer.load(); 450 | $.ajax({ 451 | type : 'POST', 452 | url : './?c=filemgr', 453 | data : {do:'addclip', path:dir, op:op, files:files}, 454 | dataType : 'json', 455 | success : function(data) { 456 | layer.close(ii); 457 | if(data.code == 0){ 458 | layer.msg(opname+'成功,请点击粘贴按钮', {time:1000}); 459 | }else{ 460 | layer.alert(data.msg, {icon:2}); 461 | } 462 | } 463 | }); 464 | } 465 | 466 | function admin_addclip_batch(op){ 467 | var dir = $("#dir").val(); 468 | var opname = op == 'cut'?'剪切':'复制'; 469 | var files = get_checked_values() 470 | if(files.length == 0){ 471 | layer.msg('未选中任何文件或文件夹', {time:1000}); 472 | return; 473 | } 474 | var ii = layer.load(); 475 | $.ajax({ 476 | type : 'POST', 477 | url : './?c=filemgr', 478 | data : {do:'addclip', path:dir, op:op, files:files}, 479 | dataType : 'json', 480 | success : function(data) { 481 | layer.close(ii); 482 | if(data.code == 0){ 483 | layer.msg(opname+'成功,请点击粘贴按钮', {time:1000}); 484 | }else{ 485 | layer.alert(data.msg, {icon:2}); 486 | } 487 | } 488 | }); 489 | } 490 | 491 | function admin_paste(){ 492 | var dir = $("#dir").val(); 493 | var ii = layer.load(); 494 | $.ajax({ 495 | type : 'POST', 496 | url : './?c=filemgr', 497 | data : {do:'paste', path:dir}, 498 | dataType : 'json', 499 | success : function(data) { 500 | layer.close(ii); 501 | if(data.code == 0){ 502 | var opname = data.data.op == 'cut'?'剪切':'复制'; 503 | var count = data.data.count; 504 | layer.alert('成功'+opname+'了'+count+'个文件或目录', {icon:1}, function(){window.location.reload()}); 505 | }else{ 506 | layer.msg(data.msg, {icon:2, time:1500}); 507 | } 508 | } 509 | }); 510 | } 511 | 512 | function admin_compress(){ 513 | var dir = $("#dir").val(); 514 | var files = get_checked_values() 515 | if(files.length == 0){ 516 | layer.msg('未选中任何文件或文件夹', {time:1000}); 517 | return; 518 | } 519 | var name = 'archive.zip'; 520 | if(files.length == 1) name = files[0] + '.zip'; 521 | layer.open({ 522 | area: ['360px'], 523 | title: '创建压缩包文件', 524 | content: '

', 525 | btn: ['确定', '取消'], 526 | yes: function(){ 527 | var zipname = $("input[name='zipname']").val(); 528 | if(zipname == ''){ 529 | $("input[name='zipname']").focus(); 530 | return; 531 | } 532 | var ii = layer.load(); 533 | $.ajax({ 534 | type : 'POST', 535 | url : './?c=filemgr', 536 | data : {do:'compress', path:dir, name:zipname, files:files}, 537 | dataType : 'json', 538 | success : function(data) { 539 | layer.close(ii); 540 | if(data.code == 0){ 541 | layer.alert('压缩成功', {icon:1}, function(){window.location.reload()}); 542 | }else{ 543 | layer.alert(data.msg, {icon:2}); 544 | } 545 | } 546 | }); 547 | } 548 | }); 549 | } 550 | 551 | function admin_uncompress(name){ 552 | var dir = $("#dir").val(); 553 | if(dir == '.') dir = '/'; 554 | else dir = '/'+dir; 555 | layer.open({ 556 | area: ['360px'], 557 | title: '解压缩', 558 | content: '

', 559 | btn: ['确定', '取消'], 560 | yes: function(){ 561 | var targetdir = $("input[name='targetdir']").val(); 562 | if(targetdir == ''){ 563 | $("input[name='targetdir']").focus(); 564 | return; 565 | } 566 | var ii = layer.load(); 567 | $.ajax({ 568 | type : 'POST', 569 | url : './?c=filemgr', 570 | data : {do:'uncompress', path:dir, targetdir:targetdir, name:name}, 571 | dataType : 'json', 572 | success : function(data) { 573 | layer.close(ii); 574 | if(data.code == 0){ 575 | layer.alert('解压缩成功', {icon:1}, function(){window.location.reload()}); 576 | }else{ 577 | layer.alert(data.msg, {icon:2}); 578 | } 579 | } 580 | }); 581 | } 582 | }); 583 | } 584 | 585 | function admin_edit(name, path){ 586 | var apiurl = './?c=editor&path=' + encodeURIComponent(path); 587 | layer.open({ 588 | title:'文本编辑器 - ' + name, 589 | type: 2, 590 | area: ['100%', '100%'], 591 | content: apiurl 592 | }); 593 | } 594 | 595 | window.addEventListener('message', function(e){ 596 | if(e.data == 'reload'){ 597 | page_reload = true 598 | } 599 | }); -------------------------------------------------------------------------------- /_dir/Parsedown.class.php: -------------------------------------------------------------------------------- 1 | textElements($text); 27 | 28 | # convert to markup 29 | $markup = $this->elements($Elements); 30 | 31 | # trim line breaks 32 | $markup = trim($markup, "\n"); 33 | 34 | return $markup; 35 | } 36 | 37 | protected function textElements($text) 38 | { 39 | # make sure no definitions are set 40 | $this->DefinitionData = array(); 41 | 42 | # standardize line breaks 43 | $text = str_replace(array("\r\n", "\r"), "\n", $text); 44 | 45 | # remove surrounding line breaks 46 | $text = trim($text, "\n"); 47 | 48 | # split text into lines 49 | $lines = explode("\n", $text); 50 | 51 | # iterate through lines to identify blocks 52 | return $this->linesElements($lines); 53 | } 54 | 55 | # 56 | # Setters 57 | # 58 | 59 | function setBreaksEnabled($breaksEnabled) 60 | { 61 | $this->breaksEnabled = $breaksEnabled; 62 | 63 | return $this; 64 | } 65 | 66 | protected $breaksEnabled; 67 | 68 | function setMarkupEscaped($markupEscaped) 69 | { 70 | $this->markupEscaped = $markupEscaped; 71 | 72 | return $this; 73 | } 74 | 75 | protected $markupEscaped; 76 | 77 | function setUrlsLinked($urlsLinked) 78 | { 79 | $this->urlsLinked = $urlsLinked; 80 | 81 | return $this; 82 | } 83 | 84 | protected $urlsLinked = true; 85 | 86 | function setSafeMode($safeMode) 87 | { 88 | $this->safeMode = (bool) $safeMode; 89 | 90 | return $this; 91 | } 92 | 93 | protected $safeMode; 94 | 95 | function setStrictMode($strictMode) 96 | { 97 | $this->strictMode = (bool) $strictMode; 98 | 99 | return $this; 100 | } 101 | 102 | protected $strictMode; 103 | 104 | protected $safeLinksWhitelist = array( 105 | 'http://', 106 | 'https://', 107 | 'ftp://', 108 | 'ftps://', 109 | 'mailto:', 110 | 'tel:', 111 | 'data:image/png;base64,', 112 | 'data:image/gif;base64,', 113 | 'data:image/jpeg;base64,', 114 | 'irc:', 115 | 'ircs:', 116 | 'git:', 117 | 'ssh:', 118 | 'news:', 119 | 'steam:', 120 | ); 121 | 122 | # 123 | # Lines 124 | # 125 | 126 | protected $BlockTypes = array( 127 | '#' => array('Header'), 128 | '*' => array('Rule', 'List'), 129 | '+' => array('List'), 130 | '-' => array('SetextHeader', 'Table', 'Rule', 'List'), 131 | '0' => array('List'), 132 | '1' => array('List'), 133 | '2' => array('List'), 134 | '3' => array('List'), 135 | '4' => array('List'), 136 | '5' => array('List'), 137 | '6' => array('List'), 138 | '7' => array('List'), 139 | '8' => array('List'), 140 | '9' => array('List'), 141 | ':' => array('Table'), 142 | '<' => array('Comment', 'Markup'), 143 | '=' => array('SetextHeader'), 144 | '>' => array('Quote'), 145 | '[' => array('Reference'), 146 | '_' => array('Rule'), 147 | '`' => array('FencedCode'), 148 | '|' => array('Table'), 149 | '~' => array('FencedCode'), 150 | ); 151 | 152 | # ~ 153 | 154 | protected $unmarkedBlockTypes = array( 155 | 'Code', 156 | ); 157 | 158 | # 159 | # Blocks 160 | # 161 | 162 | protected function lines(array $lines) 163 | { 164 | return $this->elements($this->linesElements($lines)); 165 | } 166 | 167 | protected function linesElements(array $lines) 168 | { 169 | $Elements = array(); 170 | $CurrentBlock = null; 171 | 172 | foreach ($lines as $line) 173 | { 174 | if (chop($line) === '') 175 | { 176 | if (isset($CurrentBlock)) 177 | { 178 | $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) 179 | ? $CurrentBlock['interrupted'] + 1 : 1 180 | ); 181 | } 182 | 183 | continue; 184 | } 185 | 186 | while (($beforeTab = strstr($line, "\t", true)) !== false) 187 | { 188 | $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; 189 | 190 | $line = $beforeTab 191 | . str_repeat(' ', $shortage) 192 | . substr($line, strlen($beforeTab) + 1) 193 | ; 194 | } 195 | 196 | $indent = strspn($line, ' '); 197 | 198 | $text = $indent > 0 ? substr($line, $indent) : $line; 199 | 200 | # ~ 201 | 202 | $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); 203 | 204 | # ~ 205 | 206 | if (isset($CurrentBlock['continuable'])) 207 | { 208 | $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; 209 | $Block = $this->$methodName($Line, $CurrentBlock); 210 | 211 | if (isset($Block)) 212 | { 213 | $CurrentBlock = $Block; 214 | 215 | continue; 216 | } 217 | else 218 | { 219 | if ($this->isBlockCompletable($CurrentBlock['type'])) 220 | { 221 | $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; 222 | $CurrentBlock = $this->$methodName($CurrentBlock); 223 | } 224 | } 225 | } 226 | 227 | # ~ 228 | 229 | $marker = $text[0]; 230 | 231 | # ~ 232 | 233 | $blockTypes = $this->unmarkedBlockTypes; 234 | 235 | if (isset($this->BlockTypes[$marker])) 236 | { 237 | foreach ($this->BlockTypes[$marker] as $blockType) 238 | { 239 | $blockTypes []= $blockType; 240 | } 241 | } 242 | 243 | # 244 | # ~ 245 | 246 | foreach ($blockTypes as $blockType) 247 | { 248 | $Block = $this->{"block$blockType"}($Line, $CurrentBlock); 249 | 250 | if (isset($Block)) 251 | { 252 | $Block['type'] = $blockType; 253 | 254 | if ( ! isset($Block['identified'])) 255 | { 256 | if (isset($CurrentBlock)) 257 | { 258 | $Elements[] = $this->extractElement($CurrentBlock); 259 | } 260 | 261 | $Block['identified'] = true; 262 | } 263 | 264 | if ($this->isBlockContinuable($blockType)) 265 | { 266 | $Block['continuable'] = true; 267 | } 268 | 269 | $CurrentBlock = $Block; 270 | 271 | continue 2; 272 | } 273 | } 274 | 275 | # ~ 276 | 277 | if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') 278 | { 279 | $Block = $this->paragraphContinue($Line, $CurrentBlock); 280 | } 281 | 282 | if (isset($Block)) 283 | { 284 | $CurrentBlock = $Block; 285 | } 286 | else 287 | { 288 | if (isset($CurrentBlock)) 289 | { 290 | $Elements[] = $this->extractElement($CurrentBlock); 291 | } 292 | 293 | $CurrentBlock = $this->paragraph($Line); 294 | 295 | $CurrentBlock['identified'] = true; 296 | } 297 | } 298 | 299 | # ~ 300 | 301 | if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) 302 | { 303 | $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; 304 | $CurrentBlock = $this->$methodName($CurrentBlock); 305 | } 306 | 307 | # ~ 308 | 309 | if (isset($CurrentBlock)) 310 | { 311 | $Elements[] = $this->extractElement($CurrentBlock); 312 | } 313 | 314 | # ~ 315 | 316 | return $Elements; 317 | } 318 | 319 | protected function extractElement(array $Component) 320 | { 321 | if ( ! isset($Component['element'])) 322 | { 323 | if (isset($Component['markup'])) 324 | { 325 | $Component['element'] = array('rawHtml' => $Component['markup']); 326 | } 327 | elseif (isset($Component['hidden'])) 328 | { 329 | $Component['element'] = array(); 330 | } 331 | } 332 | 333 | return $Component['element']; 334 | } 335 | 336 | protected function isBlockContinuable($Type) 337 | { 338 | return method_exists($this, 'block' . $Type . 'Continue'); 339 | } 340 | 341 | protected function isBlockCompletable($Type) 342 | { 343 | return method_exists($this, 'block' . $Type . 'Complete'); 344 | } 345 | 346 | # 347 | # Code 348 | 349 | protected function blockCode($Line, $Block = null) 350 | { 351 | if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) 352 | { 353 | return; 354 | } 355 | 356 | if ($Line['indent'] >= 4) 357 | { 358 | $text = substr($Line['body'], 4); 359 | 360 | $Block = array( 361 | 'element' => array( 362 | 'name' => 'pre', 363 | 'element' => array( 364 | 'name' => 'code', 365 | 'text' => $text, 366 | ), 367 | ), 368 | ); 369 | 370 | return $Block; 371 | } 372 | } 373 | 374 | protected function blockCodeContinue($Line, $Block) 375 | { 376 | if ($Line['indent'] >= 4) 377 | { 378 | if (isset($Block['interrupted'])) 379 | { 380 | $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); 381 | 382 | unset($Block['interrupted']); 383 | } 384 | 385 | $Block['element']['element']['text'] .= "\n"; 386 | 387 | $text = substr($Line['body'], 4); 388 | 389 | $Block['element']['element']['text'] .= $text; 390 | 391 | return $Block; 392 | } 393 | } 394 | 395 | protected function blockCodeComplete($Block) 396 | { 397 | return $Block; 398 | } 399 | 400 | # 401 | # Comment 402 | 403 | protected function blockComment($Line) 404 | { 405 | if ($this->markupEscaped or $this->safeMode) 406 | { 407 | return; 408 | } 409 | 410 | if (strpos($Line['text'], '') !== false) 420 | { 421 | $Block['closed'] = true; 422 | } 423 | 424 | return $Block; 425 | } 426 | } 427 | 428 | protected function blockCommentContinue($Line, array $Block) 429 | { 430 | if (isset($Block['closed'])) 431 | { 432 | return; 433 | } 434 | 435 | $Block['element']['rawHtml'] .= "\n" . $Line['body']; 436 | 437 | if (strpos($Line['text'], '-->') !== false) 438 | { 439 | $Block['closed'] = true; 440 | } 441 | 442 | return $Block; 443 | } 444 | 445 | # 446 | # Fenced Code 447 | 448 | protected function blockFencedCode($Line) 449 | { 450 | $marker = $Line['text'][0]; 451 | 452 | $openerLength = strspn($Line['text'], $marker); 453 | 454 | if ($openerLength < 3) 455 | { 456 | return; 457 | } 458 | 459 | $infostring = trim(substr($Line['text'], $openerLength), "\t "); 460 | 461 | if (strpos($infostring, '`') !== false) 462 | { 463 | return; 464 | } 465 | 466 | $Element = array( 467 | 'name' => 'code', 468 | 'text' => '', 469 | ); 470 | 471 | if ($infostring !== '') 472 | { 473 | /** 474 | * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes 475 | * Every HTML element may have a class attribute specified. 476 | * The attribute, if specified, must have a value that is a set 477 | * of space-separated tokens representing the various classes 478 | * that the element belongs to. 479 | * [...] 480 | * The space characters, for the purposes of this specification, 481 | * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), 482 | * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and 483 | * U+000D CARRIAGE RETURN (CR). 484 | */ 485 | $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); 486 | 487 | $Element['attributes'] = array('class' => "language-$language"); 488 | } 489 | 490 | $Block = array( 491 | 'char' => $marker, 492 | 'openerLength' => $openerLength, 493 | 'element' => array( 494 | 'name' => 'pre', 495 | 'element' => $Element, 496 | ), 497 | ); 498 | 499 | return $Block; 500 | } 501 | 502 | protected function blockFencedCodeContinue($Line, $Block) 503 | { 504 | if (isset($Block['complete'])) 505 | { 506 | return; 507 | } 508 | 509 | if (isset($Block['interrupted'])) 510 | { 511 | $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); 512 | 513 | unset($Block['interrupted']); 514 | } 515 | 516 | if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] 517 | and chop(substr($Line['text'], $len), ' ') === '' 518 | ) { 519 | $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); 520 | 521 | $Block['complete'] = true; 522 | 523 | return $Block; 524 | } 525 | 526 | $Block['element']['element']['text'] .= "\n" . $Line['body']; 527 | 528 | return $Block; 529 | } 530 | 531 | protected function blockFencedCodeComplete($Block) 532 | { 533 | return $Block; 534 | } 535 | 536 | # 537 | # Header 538 | 539 | protected function blockHeader($Line) 540 | { 541 | $level = strspn($Line['text'], '#'); 542 | 543 | if ($level > 6) 544 | { 545 | return; 546 | } 547 | 548 | $text = trim($Line['text'], '#'); 549 | 550 | if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') 551 | { 552 | return; 553 | } 554 | 555 | $text = trim($text, ' '); 556 | 557 | $Block = array( 558 | 'element' => array( 559 | 'name' => 'h' . $level, 560 | 'handler' => array( 561 | 'function' => 'lineElements', 562 | 'argument' => $text, 563 | 'destination' => 'elements', 564 | ) 565 | ), 566 | ); 567 | 568 | return $Block; 569 | } 570 | 571 | # 572 | # List 573 | 574 | protected function blockList($Line, ?array $CurrentBlock = null) 575 | { 576 | list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); 577 | 578 | if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) 579 | { 580 | $contentIndent = strlen($matches[2]); 581 | 582 | if ($contentIndent >= 5) 583 | { 584 | $contentIndent -= 1; 585 | $matches[1] = substr($matches[1], 0, -$contentIndent); 586 | $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; 587 | } 588 | elseif ($contentIndent === 0) 589 | { 590 | $matches[1] .= ' '; 591 | } 592 | 593 | $markerWithoutWhitespace = strstr($matches[1], ' ', true); 594 | 595 | $Block = array( 596 | 'indent' => $Line['indent'], 597 | 'pattern' => $pattern, 598 | 'data' => array( 599 | 'type' => $name, 600 | 'marker' => $matches[1], 601 | 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), 602 | ), 603 | 'element' => array( 604 | 'name' => $name, 605 | 'elements' => array(), 606 | ), 607 | ); 608 | $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); 609 | 610 | if ($name === 'ol') 611 | { 612 | $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; 613 | 614 | if ($listStart !== '1') 615 | { 616 | if ( 617 | isset($CurrentBlock) 618 | and $CurrentBlock['type'] === 'Paragraph' 619 | and ! isset($CurrentBlock['interrupted']) 620 | ) { 621 | return; 622 | } 623 | 624 | $Block['element']['attributes'] = array('start' => $listStart); 625 | } 626 | } 627 | 628 | $Block['li'] = array( 629 | 'name' => 'li', 630 | 'handler' => array( 631 | 'function' => 'li', 632 | 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), 633 | 'destination' => 'elements' 634 | ) 635 | ); 636 | 637 | $Block['element']['elements'] []= & $Block['li']; 638 | 639 | return $Block; 640 | } 641 | } 642 | 643 | protected function blockListContinue($Line, array $Block) 644 | { 645 | if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) 646 | { 647 | return null; 648 | } 649 | 650 | $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); 651 | 652 | if ($Line['indent'] < $requiredIndent 653 | and ( 654 | ( 655 | $Block['data']['type'] === 'ol' 656 | and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) 657 | ) or ( 658 | $Block['data']['type'] === 'ul' 659 | and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) 660 | ) 661 | ) 662 | ) { 663 | if (isset($Block['interrupted'])) 664 | { 665 | $Block['li']['handler']['argument'] []= ''; 666 | 667 | $Block['loose'] = true; 668 | 669 | unset($Block['interrupted']); 670 | } 671 | 672 | unset($Block['li']); 673 | 674 | $text = isset($matches[1]) ? $matches[1] : ''; 675 | 676 | $Block['indent'] = $Line['indent']; 677 | 678 | $Block['li'] = array( 679 | 'name' => 'li', 680 | 'handler' => array( 681 | 'function' => 'li', 682 | 'argument' => array($text), 683 | 'destination' => 'elements' 684 | ) 685 | ); 686 | 687 | $Block['element']['elements'] []= & $Block['li']; 688 | 689 | return $Block; 690 | } 691 | elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) 692 | { 693 | return null; 694 | } 695 | 696 | if ($Line['text'][0] === '[' and $this->blockReference($Line)) 697 | { 698 | return $Block; 699 | } 700 | 701 | if ($Line['indent'] >= $requiredIndent) 702 | { 703 | if (isset($Block['interrupted'])) 704 | { 705 | $Block['li']['handler']['argument'] []= ''; 706 | 707 | $Block['loose'] = true; 708 | 709 | unset($Block['interrupted']); 710 | } 711 | 712 | $text = substr($Line['body'], $requiredIndent); 713 | 714 | $Block['li']['handler']['argument'] []= $text; 715 | 716 | return $Block; 717 | } 718 | 719 | if ( ! isset($Block['interrupted'])) 720 | { 721 | $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); 722 | 723 | $Block['li']['handler']['argument'] []= $text; 724 | 725 | return $Block; 726 | } 727 | } 728 | 729 | protected function blockListComplete(array $Block) 730 | { 731 | if (isset($Block['loose'])) 732 | { 733 | foreach ($Block['element']['elements'] as &$li) 734 | { 735 | if (end($li['handler']['argument']) !== '') 736 | { 737 | $li['handler']['argument'] []= ''; 738 | } 739 | } 740 | } 741 | 742 | return $Block; 743 | } 744 | 745 | # 746 | # Quote 747 | 748 | protected function blockQuote($Line) 749 | { 750 | if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) 751 | { 752 | $Block = array( 753 | 'element' => array( 754 | 'name' => 'blockquote', 755 | 'handler' => array( 756 | 'function' => 'linesElements', 757 | 'argument' => (array) $matches[1], 758 | 'destination' => 'elements', 759 | ) 760 | ), 761 | ); 762 | 763 | return $Block; 764 | } 765 | } 766 | 767 | protected function blockQuoteContinue($Line, array $Block) 768 | { 769 | if (isset($Block['interrupted'])) 770 | { 771 | return; 772 | } 773 | 774 | if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) 775 | { 776 | $Block['element']['handler']['argument'] []= $matches[1]; 777 | 778 | return $Block; 779 | } 780 | 781 | if ( ! isset($Block['interrupted'])) 782 | { 783 | $Block['element']['handler']['argument'] []= $Line['text']; 784 | 785 | return $Block; 786 | } 787 | } 788 | 789 | # 790 | # Rule 791 | 792 | protected function blockRule($Line) 793 | { 794 | $marker = $Line['text'][0]; 795 | 796 | if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') 797 | { 798 | $Block = array( 799 | 'element' => array( 800 | 'name' => 'hr', 801 | ), 802 | ); 803 | 804 | return $Block; 805 | } 806 | } 807 | 808 | # 809 | # Setext 810 | 811 | protected function blockSetextHeader($Line, ?array $Block = null) 812 | { 813 | if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) 814 | { 815 | return; 816 | } 817 | 818 | if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') 819 | { 820 | $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; 821 | 822 | return $Block; 823 | } 824 | } 825 | 826 | # 827 | # Markup 828 | 829 | protected function blockMarkup($Line) 830 | { 831 | if ($this->markupEscaped or $this->safeMode) 832 | { 833 | return; 834 | } 835 | 836 | if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) 837 | { 838 | $element = strtolower($matches[1]); 839 | 840 | if (in_array($element, $this->textLevelElements)) 841 | { 842 | return; 843 | } 844 | 845 | $Block = array( 846 | 'name' => $matches[1], 847 | 'element' => array( 848 | 'rawHtml' => $Line['text'], 849 | 'autobreak' => true, 850 | ), 851 | ); 852 | 853 | return $Block; 854 | } 855 | } 856 | 857 | protected function blockMarkupContinue($Line, array $Block) 858 | { 859 | if (isset($Block['closed']) or isset($Block['interrupted'])) 860 | { 861 | return; 862 | } 863 | 864 | $Block['element']['rawHtml'] .= "\n" . $Line['body']; 865 | 866 | return $Block; 867 | } 868 | 869 | # 870 | # Reference 871 | 872 | protected function blockReference($Line) 873 | { 874 | if (strpos($Line['text'], ']') !== false 875 | and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) 876 | ) { 877 | $id = strtolower($matches[1]); 878 | 879 | $Data = array( 880 | 'url' => $matches[2], 881 | 'title' => isset($matches[3]) ? $matches[3] : null, 882 | ); 883 | 884 | $this->DefinitionData['Reference'][$id] = $Data; 885 | 886 | $Block = array( 887 | 'element' => array(), 888 | ); 889 | 890 | return $Block; 891 | } 892 | } 893 | 894 | # 895 | # Table 896 | 897 | protected function blockTable($Line, ?array $Block = null) 898 | { 899 | if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) 900 | { 901 | return; 902 | } 903 | 904 | if ( 905 | strpos($Block['element']['handler']['argument'], '|') === false 906 | and strpos($Line['text'], '|') === false 907 | and strpos($Line['text'], ':') === false 908 | or strpos($Block['element']['handler']['argument'], "\n") !== false 909 | ) { 910 | return; 911 | } 912 | 913 | if (chop($Line['text'], ' -:|') !== '') 914 | { 915 | return; 916 | } 917 | 918 | $alignments = array(); 919 | 920 | $divider = $Line['text']; 921 | 922 | $divider = trim($divider); 923 | $divider = trim($divider, '|'); 924 | 925 | $dividerCells = explode('|', $divider); 926 | 927 | foreach ($dividerCells as $dividerCell) 928 | { 929 | $dividerCell = trim($dividerCell); 930 | 931 | if ($dividerCell === '') 932 | { 933 | return; 934 | } 935 | 936 | $alignment = null; 937 | 938 | if ($dividerCell[0] === ':') 939 | { 940 | $alignment = 'left'; 941 | } 942 | 943 | if (substr($dividerCell, - 1) === ':') 944 | { 945 | $alignment = $alignment === 'left' ? 'center' : 'right'; 946 | } 947 | 948 | $alignments []= $alignment; 949 | } 950 | 951 | # ~ 952 | 953 | $HeaderElements = array(); 954 | 955 | $header = $Block['element']['handler']['argument']; 956 | 957 | $header = trim($header); 958 | $header = trim($header, '|'); 959 | 960 | $headerCells = explode('|', $header); 961 | 962 | if (count($headerCells) !== count($alignments)) 963 | { 964 | return; 965 | } 966 | 967 | foreach ($headerCells as $index => $headerCell) 968 | { 969 | $headerCell = trim($headerCell); 970 | 971 | $HeaderElement = array( 972 | 'name' => 'th', 973 | 'handler' => array( 974 | 'function' => 'lineElements', 975 | 'argument' => $headerCell, 976 | 'destination' => 'elements', 977 | ) 978 | ); 979 | 980 | if (isset($alignments[$index])) 981 | { 982 | $alignment = $alignments[$index]; 983 | 984 | $HeaderElement['attributes'] = array( 985 | 'style' => "text-align: $alignment;", 986 | ); 987 | } 988 | 989 | $HeaderElements []= $HeaderElement; 990 | } 991 | 992 | # ~ 993 | 994 | $Block = array( 995 | 'alignments' => $alignments, 996 | 'identified' => true, 997 | 'element' => array( 998 | 'name' => 'table', 999 | 'elements' => array(), 1000 | ), 1001 | ); 1002 | 1003 | $Block['element']['elements'] []= array( 1004 | 'name' => 'thead', 1005 | ); 1006 | 1007 | $Block['element']['elements'] []= array( 1008 | 'name' => 'tbody', 1009 | 'elements' => array(), 1010 | ); 1011 | 1012 | $Block['element']['elements'][0]['elements'] []= array( 1013 | 'name' => 'tr', 1014 | 'elements' => $HeaderElements, 1015 | ); 1016 | 1017 | return $Block; 1018 | } 1019 | 1020 | protected function blockTableContinue($Line, array $Block) 1021 | { 1022 | if (isset($Block['interrupted'])) 1023 | { 1024 | return; 1025 | } 1026 | 1027 | if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) 1028 | { 1029 | $Elements = array(); 1030 | 1031 | $row = $Line['text']; 1032 | 1033 | $row = trim($row); 1034 | $row = trim($row, '|'); 1035 | 1036 | preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); 1037 | 1038 | $cells = array_slice($matches[0], 0, count($Block['alignments'])); 1039 | 1040 | foreach ($cells as $index => $cell) 1041 | { 1042 | $cell = trim($cell); 1043 | 1044 | $Element = array( 1045 | 'name' => 'td', 1046 | 'handler' => array( 1047 | 'function' => 'lineElements', 1048 | 'argument' => $cell, 1049 | 'destination' => 'elements', 1050 | ) 1051 | ); 1052 | 1053 | if (isset($Block['alignments'][$index])) 1054 | { 1055 | $Element['attributes'] = array( 1056 | 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', 1057 | ); 1058 | } 1059 | 1060 | $Elements []= $Element; 1061 | } 1062 | 1063 | $Element = array( 1064 | 'name' => 'tr', 1065 | 'elements' => $Elements, 1066 | ); 1067 | 1068 | $Block['element']['elements'][1]['elements'] []= $Element; 1069 | 1070 | return $Block; 1071 | } 1072 | } 1073 | 1074 | # 1075 | # ~ 1076 | # 1077 | 1078 | protected function paragraph($Line) 1079 | { 1080 | return array( 1081 | 'type' => 'Paragraph', 1082 | 'element' => array( 1083 | 'name' => 'p', 1084 | 'handler' => array( 1085 | 'function' => 'lineElements', 1086 | 'argument' => $Line['text'], 1087 | 'destination' => 'elements', 1088 | ), 1089 | ), 1090 | ); 1091 | } 1092 | 1093 | protected function paragraphContinue($Line, array $Block) 1094 | { 1095 | if (isset($Block['interrupted'])) 1096 | { 1097 | return; 1098 | } 1099 | 1100 | $Block['element']['handler']['argument'] .= "\n".$Line['text']; 1101 | 1102 | return $Block; 1103 | } 1104 | 1105 | # 1106 | # Inline Elements 1107 | # 1108 | 1109 | protected $InlineTypes = array( 1110 | '!' => array('Image'), 1111 | '&' => array('SpecialCharacter'), 1112 | '*' => array('Emphasis'), 1113 | ':' => array('Url'), 1114 | '<' => array('UrlTag', 'EmailTag', 'Markup'), 1115 | '[' => array('Link'), 1116 | '_' => array('Emphasis'), 1117 | '`' => array('Code'), 1118 | '~' => array('Strikethrough'), 1119 | '\\' => array('EscapeSequence'), 1120 | ); 1121 | 1122 | # ~ 1123 | 1124 | protected $inlineMarkerList = '!*_&[:<`~\\'; 1125 | 1126 | # 1127 | # ~ 1128 | # 1129 | 1130 | public function line($text, $nonNestables = array()) 1131 | { 1132 | return $this->elements($this->lineElements($text, $nonNestables)); 1133 | } 1134 | 1135 | protected function lineElements($text, $nonNestables = array()) 1136 | { 1137 | # standardize line breaks 1138 | $text = str_replace(array("\r\n", "\r"), "\n", $text); 1139 | 1140 | $Elements = array(); 1141 | 1142 | $nonNestables = (empty($nonNestables) 1143 | ? array() 1144 | : array_combine($nonNestables, $nonNestables) 1145 | ); 1146 | 1147 | # $excerpt is based on the first occurrence of a marker 1148 | 1149 | while ($excerpt = strpbrk($text, $this->inlineMarkerList)) 1150 | { 1151 | $marker = $excerpt[0]; 1152 | 1153 | $markerPosition = strlen($text) - strlen($excerpt); 1154 | 1155 | $Excerpt = array('text' => $excerpt, 'context' => $text); 1156 | 1157 | foreach ($this->InlineTypes[$marker] as $inlineType) 1158 | { 1159 | # check to see if the current inline type is nestable in the current context 1160 | 1161 | if (isset($nonNestables[$inlineType])) 1162 | { 1163 | continue; 1164 | } 1165 | 1166 | $Inline = $this->{"inline$inlineType"}($Excerpt); 1167 | 1168 | if ( ! isset($Inline)) 1169 | { 1170 | continue; 1171 | } 1172 | 1173 | # makes sure that the inline belongs to "our" marker 1174 | 1175 | if (isset($Inline['position']) and $Inline['position'] > $markerPosition) 1176 | { 1177 | continue; 1178 | } 1179 | 1180 | # sets a default inline position 1181 | 1182 | if ( ! isset($Inline['position'])) 1183 | { 1184 | $Inline['position'] = $markerPosition; 1185 | } 1186 | 1187 | # cause the new element to 'inherit' our non nestables 1188 | 1189 | 1190 | $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) 1191 | ? array_merge($Inline['element']['nonNestables'], $nonNestables) 1192 | : $nonNestables 1193 | ; 1194 | 1195 | # the text that comes before the inline 1196 | $unmarkedText = substr($text, 0, $Inline['position']); 1197 | 1198 | # compile the unmarked text 1199 | $InlineText = $this->inlineText($unmarkedText); 1200 | $Elements[] = $InlineText['element']; 1201 | 1202 | # compile the inline 1203 | $Elements[] = $this->extractElement($Inline); 1204 | 1205 | # remove the examined text 1206 | $text = substr($text, $Inline['position'] + $Inline['extent']); 1207 | 1208 | continue 2; 1209 | } 1210 | 1211 | # the marker does not belong to an inline 1212 | 1213 | $unmarkedText = substr($text, 0, $markerPosition + 1); 1214 | 1215 | $InlineText = $this->inlineText($unmarkedText); 1216 | $Elements[] = $InlineText['element']; 1217 | 1218 | $text = substr($text, $markerPosition + 1); 1219 | } 1220 | 1221 | $InlineText = $this->inlineText($text); 1222 | $Elements[] = $InlineText['element']; 1223 | 1224 | foreach ($Elements as &$Element) 1225 | { 1226 | if ( ! isset($Element['autobreak'])) 1227 | { 1228 | $Element['autobreak'] = false; 1229 | } 1230 | } 1231 | 1232 | return $Elements; 1233 | } 1234 | 1235 | # 1236 | # ~ 1237 | # 1238 | 1239 | protected function inlineText($text) 1240 | { 1241 | $Inline = array( 1242 | 'extent' => strlen($text), 1243 | 'element' => array(), 1244 | ); 1245 | 1246 | $Inline['element']['elements'] = self::pregReplaceElements( 1247 | $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', 1248 | array( 1249 | array('name' => 'br'), 1250 | array('text' => "\n"), 1251 | ), 1252 | $text 1253 | ); 1254 | 1255 | return $Inline; 1256 | } 1257 | 1258 | protected function inlineCode($Excerpt) 1259 | { 1260 | $marker = $Excerpt['text'][0]; 1261 | 1262 | if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]), 1269 | 'element' => array( 1270 | 'name' => 'code', 1271 | 'text' => $text, 1272 | ), 1273 | ); 1274 | } 1275 | } 1276 | 1277 | protected function inlineEmailTag($Excerpt) 1278 | { 1279 | $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; 1280 | 1281 | $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' 1282 | . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; 1283 | 1284 | if (strpos($Excerpt['text'], '>') !== false 1285 | and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) 1286 | ){ 1287 | $url = $matches[1]; 1288 | 1289 | if ( ! isset($matches[2])) 1290 | { 1291 | $url = "mailto:$url"; 1292 | } 1293 | 1294 | return array( 1295 | 'extent' => strlen($matches[0]), 1296 | 'element' => array( 1297 | 'name' => 'a', 1298 | 'text' => $matches[1], 1299 | 'attributes' => array( 1300 | 'href' => $url, 1301 | ), 1302 | ), 1303 | ); 1304 | } 1305 | } 1306 | 1307 | protected function inlineEmphasis($Excerpt) 1308 | { 1309 | if ( ! isset($Excerpt['text'][1])) 1310 | { 1311 | return; 1312 | } 1313 | 1314 | $marker = $Excerpt['text'][0]; 1315 | 1316 | if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) 1317 | { 1318 | $emphasis = 'strong'; 1319 | } 1320 | elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) 1321 | { 1322 | $emphasis = 'em'; 1323 | } 1324 | else 1325 | { 1326 | return; 1327 | } 1328 | 1329 | return array( 1330 | 'extent' => strlen($matches[0]), 1331 | 'element' => array( 1332 | 'name' => $emphasis, 1333 | 'handler' => array( 1334 | 'function' => 'lineElements', 1335 | 'argument' => $matches[1], 1336 | 'destination' => 'elements', 1337 | ) 1338 | ), 1339 | ); 1340 | } 1341 | 1342 | protected function inlineEscapeSequence($Excerpt) 1343 | { 1344 | if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) 1345 | { 1346 | return array( 1347 | 'element' => array('rawHtml' => $Excerpt['text'][1]), 1348 | 'extent' => 2, 1349 | ); 1350 | } 1351 | } 1352 | 1353 | protected function inlineImage($Excerpt) 1354 | { 1355 | if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') 1356 | { 1357 | return; 1358 | } 1359 | 1360 | $Excerpt['text']= substr($Excerpt['text'], 1); 1361 | 1362 | $Link = $this->inlineLink($Excerpt); 1363 | 1364 | if ($Link === null) 1365 | { 1366 | return; 1367 | } 1368 | 1369 | $Inline = array( 1370 | 'extent' => $Link['extent'] + 1, 1371 | 'element' => array( 1372 | 'name' => 'img', 1373 | 'attributes' => array( 1374 | 'src' => $Link['element']['attributes']['href'], 1375 | 'alt' => $Link['element']['handler']['argument'], 1376 | ), 1377 | 'autobreak' => true, 1378 | ), 1379 | ); 1380 | 1381 | $Inline['element']['attributes'] += $Link['element']['attributes']; 1382 | 1383 | unset($Inline['element']['attributes']['href']); 1384 | 1385 | return $Inline; 1386 | } 1387 | 1388 | protected function inlineLink($Excerpt) 1389 | { 1390 | $Element = array( 1391 | 'name' => 'a', 1392 | 'handler' => array( 1393 | 'function' => 'lineElements', 1394 | 'argument' => null, 1395 | 'destination' => 'elements', 1396 | ), 1397 | 'nonNestables' => array('Url', 'Link'), 1398 | 'attributes' => array( 1399 | 'href' => null, 1400 | 'title' => null, 1401 | ), 1402 | ); 1403 | 1404 | $extent = 0; 1405 | 1406 | $remainder = $Excerpt['text']; 1407 | 1408 | if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) 1409 | { 1410 | $Element['handler']['argument'] = $matches[1]; 1411 | 1412 | $extent += strlen($matches[0]); 1413 | 1414 | $remainder = substr($remainder, $extent); 1415 | } 1416 | else 1417 | { 1418 | return; 1419 | } 1420 | 1421 | if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) 1422 | { 1423 | $Element['attributes']['href'] = $matches[1]; 1424 | 1425 | if (isset($matches[2])) 1426 | { 1427 | $Element['attributes']['title'] = substr($matches[2], 1, - 1); 1428 | } 1429 | 1430 | $extent += strlen($matches[0]); 1431 | } 1432 | else 1433 | { 1434 | if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) 1435 | { 1436 | $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; 1437 | $definition = strtolower($definition); 1438 | 1439 | $extent += strlen($matches[0]); 1440 | } 1441 | else 1442 | { 1443 | $definition = strtolower($Element['handler']['argument']); 1444 | } 1445 | 1446 | if ( ! isset($this->DefinitionData['Reference'][$definition])) 1447 | { 1448 | return; 1449 | } 1450 | 1451 | $Definition = $this->DefinitionData['Reference'][$definition]; 1452 | 1453 | $Element['attributes']['href'] = $Definition['url']; 1454 | $Element['attributes']['title'] = $Definition['title']; 1455 | } 1456 | 1457 | return array( 1458 | 'extent' => $extent, 1459 | 'element' => $Element, 1460 | ); 1461 | } 1462 | 1463 | protected function inlineMarkup($Excerpt) 1464 | { 1465 | if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) 1466 | { 1467 | return; 1468 | } 1469 | 1470 | if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) 1471 | { 1472 | return array( 1473 | 'element' => array('rawHtml' => $matches[0]), 1474 | 'extent' => strlen($matches[0]), 1475 | ); 1476 | } 1477 | 1478 | if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) 1479 | { 1480 | return array( 1481 | 'element' => array('rawHtml' => $matches[0]), 1482 | 'extent' => strlen($matches[0]), 1483 | ); 1484 | } 1485 | 1486 | if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) 1487 | { 1488 | return array( 1489 | 'element' => array('rawHtml' => $matches[0]), 1490 | 'extent' => strlen($matches[0]), 1491 | ); 1492 | } 1493 | } 1494 | 1495 | protected function inlineSpecialCharacter($Excerpt) 1496 | { 1497 | if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false 1498 | and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) 1499 | ) { 1500 | return array( 1501 | 'element' => array('rawHtml' => '&' . $matches[1] . ';'), 1502 | 'extent' => strlen($matches[0]), 1503 | ); 1504 | } 1505 | 1506 | return; 1507 | } 1508 | 1509 | protected function inlineStrikethrough($Excerpt) 1510 | { 1511 | if ( ! isset($Excerpt['text'][1])) 1512 | { 1513 | return; 1514 | } 1515 | 1516 | if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) 1517 | { 1518 | return array( 1519 | 'extent' => strlen($matches[0]), 1520 | 'element' => array( 1521 | 'name' => 'del', 1522 | 'handler' => array( 1523 | 'function' => 'lineElements', 1524 | 'argument' => $matches[1], 1525 | 'destination' => 'elements', 1526 | ) 1527 | ), 1528 | ); 1529 | } 1530 | } 1531 | 1532 | protected function inlineUrl($Excerpt) 1533 | { 1534 | if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') 1535 | { 1536 | return; 1537 | } 1538 | 1539 | if (strpos($Excerpt['context'], 'http') !== false 1540 | and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) 1541 | ) { 1542 | $url = $matches[0][0]; 1543 | 1544 | $Inline = array( 1545 | 'extent' => strlen($matches[0][0]), 1546 | 'position' => $matches[0][1], 1547 | 'element' => array( 1548 | 'name' => 'a', 1549 | 'text' => $url, 1550 | 'attributes' => array( 1551 | 'href' => $url, 1552 | ), 1553 | ), 1554 | ); 1555 | 1556 | return $Inline; 1557 | } 1558 | } 1559 | 1560 | protected function inlineUrlTag($Excerpt) 1561 | { 1562 | if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) 1563 | { 1564 | $url = $matches[1]; 1565 | 1566 | return array( 1567 | 'extent' => strlen($matches[0]), 1568 | 'element' => array( 1569 | 'name' => 'a', 1570 | 'text' => $url, 1571 | 'attributes' => array( 1572 | 'href' => $url, 1573 | ), 1574 | ), 1575 | ); 1576 | } 1577 | } 1578 | 1579 | # ~ 1580 | 1581 | protected function unmarkedText($text) 1582 | { 1583 | $Inline = $this->inlineText($text); 1584 | return $this->element($Inline['element']); 1585 | } 1586 | 1587 | # 1588 | # Handlers 1589 | # 1590 | 1591 | protected function handle(array $Element) 1592 | { 1593 | if (isset($Element['handler'])) 1594 | { 1595 | if (!isset($Element['nonNestables'])) 1596 | { 1597 | $Element['nonNestables'] = array(); 1598 | } 1599 | 1600 | if (is_string($Element['handler'])) 1601 | { 1602 | $function = $Element['handler']; 1603 | $argument = $Element['text']; 1604 | unset($Element['text']); 1605 | $destination = 'rawHtml'; 1606 | } 1607 | else 1608 | { 1609 | $function = $Element['handler']['function']; 1610 | $argument = $Element['handler']['argument']; 1611 | $destination = $Element['handler']['destination']; 1612 | } 1613 | 1614 | $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); 1615 | 1616 | if ($destination === 'handler') 1617 | { 1618 | $Element = $this->handle($Element); 1619 | } 1620 | 1621 | unset($Element['handler']); 1622 | } 1623 | 1624 | return $Element; 1625 | } 1626 | 1627 | protected function handleElementRecursive(array $Element) 1628 | { 1629 | return $this->elementApplyRecursive(array($this, 'handle'), $Element); 1630 | } 1631 | 1632 | protected function handleElementsRecursive(array $Elements) 1633 | { 1634 | return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); 1635 | } 1636 | 1637 | protected function elementApplyRecursive($closure, array $Element) 1638 | { 1639 | $Element = call_user_func($closure, $Element); 1640 | 1641 | if (isset($Element['elements'])) 1642 | { 1643 | $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); 1644 | } 1645 | elseif (isset($Element['element'])) 1646 | { 1647 | $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); 1648 | } 1649 | 1650 | return $Element; 1651 | } 1652 | 1653 | protected function elementApplyRecursiveDepthFirst($closure, array $Element) 1654 | { 1655 | if (isset($Element['elements'])) 1656 | { 1657 | $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); 1658 | } 1659 | elseif (isset($Element['element'])) 1660 | { 1661 | $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); 1662 | } 1663 | 1664 | $Element = call_user_func($closure, $Element); 1665 | 1666 | return $Element; 1667 | } 1668 | 1669 | protected function elementsApplyRecursive($closure, array $Elements) 1670 | { 1671 | foreach ($Elements as &$Element) 1672 | { 1673 | $Element = $this->elementApplyRecursive($closure, $Element); 1674 | } 1675 | 1676 | return $Elements; 1677 | } 1678 | 1679 | protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) 1680 | { 1681 | foreach ($Elements as &$Element) 1682 | { 1683 | $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); 1684 | } 1685 | 1686 | return $Elements; 1687 | } 1688 | 1689 | protected function element(array $Element) 1690 | { 1691 | if ($this->safeMode) 1692 | { 1693 | $Element = $this->sanitiseElement($Element); 1694 | } 1695 | 1696 | # identity map if element has no handler 1697 | $Element = $this->handle($Element); 1698 | 1699 | $hasName = isset($Element['name']); 1700 | 1701 | $markup = ''; 1702 | 1703 | if ($hasName) 1704 | { 1705 | $markup .= '<' . $Element['name']; 1706 | 1707 | if (isset($Element['attributes'])) 1708 | { 1709 | foreach ($Element['attributes'] as $name => $value) 1710 | { 1711 | if ($value === null) 1712 | { 1713 | continue; 1714 | } 1715 | 1716 | $markup .= " $name=\"".self::escape($value).'"'; 1717 | } 1718 | } 1719 | } 1720 | 1721 | $permitRawHtml = false; 1722 | 1723 | if (isset($Element['text'])) 1724 | { 1725 | $text = $Element['text']; 1726 | } 1727 | // very strongly consider an alternative if you're writing an 1728 | // extension 1729 | elseif (isset($Element['rawHtml'])) 1730 | { 1731 | $text = $Element['rawHtml']; 1732 | 1733 | $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; 1734 | $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; 1735 | } 1736 | 1737 | $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); 1738 | 1739 | if ($hasContent) 1740 | { 1741 | $markup .= $hasName ? '>' : ''; 1742 | 1743 | if (isset($Element['elements'])) 1744 | { 1745 | $markup .= $this->elements($Element['elements']); 1746 | } 1747 | elseif (isset($Element['element'])) 1748 | { 1749 | $markup .= $this->element($Element['element']); 1750 | } 1751 | else 1752 | { 1753 | if (!$permitRawHtml) 1754 | { 1755 | $markup .= self::escape($text, true); 1756 | } 1757 | else 1758 | { 1759 | $markup .= $text; 1760 | } 1761 | } 1762 | 1763 | $markup .= $hasName ? '' : ''; 1764 | } 1765 | elseif ($hasName) 1766 | { 1767 | $markup .= ' />'; 1768 | } 1769 | 1770 | return $markup; 1771 | } 1772 | 1773 | protected function elements(array $Elements) 1774 | { 1775 | $markup = ''; 1776 | 1777 | $autoBreak = true; 1778 | 1779 | foreach ($Elements as $Element) 1780 | { 1781 | if (empty($Element)) 1782 | { 1783 | continue; 1784 | } 1785 | 1786 | $autoBreakNext = (isset($Element['autobreak']) 1787 | ? $Element['autobreak'] : isset($Element['name']) 1788 | ); 1789 | // (autobreak === false) covers both sides of an element 1790 | $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; 1791 | 1792 | $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); 1793 | $autoBreak = $autoBreakNext; 1794 | } 1795 | 1796 | $markup .= $autoBreak ? "\n" : ''; 1797 | 1798 | return $markup; 1799 | } 1800 | 1801 | # ~ 1802 | 1803 | protected function li($lines) 1804 | { 1805 | $Elements = $this->linesElements($lines); 1806 | 1807 | if ( ! in_array('', $lines) 1808 | and isset($Elements[0]) and isset($Elements[0]['name']) 1809 | and $Elements[0]['name'] === 'p' 1810 | ) { 1811 | unset($Elements[0]['name']); 1812 | } 1813 | 1814 | return $Elements; 1815 | } 1816 | 1817 | # 1818 | # AST Convenience 1819 | # 1820 | 1821 | /** 1822 | * Replace occurrences $regexp with $Elements in $text. Return an array of 1823 | * elements representing the replacement. 1824 | */ 1825 | protected static function pregReplaceElements($regexp, $Elements, $text) 1826 | { 1827 | $newElements = array(); 1828 | 1829 | while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) 1830 | { 1831 | $offset = $matches[0][1]; 1832 | $before = substr($text, 0, $offset); 1833 | $after = substr($text, $offset + strlen($matches[0][0])); 1834 | 1835 | $newElements[] = array('text' => $before); 1836 | 1837 | foreach ($Elements as $Element) 1838 | { 1839 | $newElements[] = $Element; 1840 | } 1841 | 1842 | $text = $after; 1843 | } 1844 | 1845 | $newElements[] = array('text' => $text); 1846 | 1847 | return $newElements; 1848 | } 1849 | 1850 | # 1851 | # Deprecated Methods 1852 | # 1853 | 1854 | function parse($text) 1855 | { 1856 | $markup = $this->text($text); 1857 | 1858 | return $markup; 1859 | } 1860 | 1861 | protected function sanitiseElement(array $Element) 1862 | { 1863 | static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; 1864 | static $safeUrlNameToAtt = array( 1865 | 'a' => 'href', 1866 | 'img' => 'src', 1867 | ); 1868 | 1869 | if ( ! isset($Element['name'])) 1870 | { 1871 | unset($Element['attributes']); 1872 | return $Element; 1873 | } 1874 | 1875 | if (isset($safeUrlNameToAtt[$Element['name']])) 1876 | { 1877 | $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); 1878 | } 1879 | 1880 | if ( ! empty($Element['attributes'])) 1881 | { 1882 | foreach ($Element['attributes'] as $att => $val) 1883 | { 1884 | # filter out badly parsed attribute 1885 | if ( ! preg_match($goodAttribute, $att)) 1886 | { 1887 | unset($Element['attributes'][$att]); 1888 | } 1889 | # dump onevent attribute 1890 | elseif (self::striAtStart($att, 'on')) 1891 | { 1892 | unset($Element['attributes'][$att]); 1893 | } 1894 | } 1895 | } 1896 | 1897 | return $Element; 1898 | } 1899 | 1900 | protected function filterUnsafeUrlInAttribute(array $Element, $attribute) 1901 | { 1902 | foreach ($this->safeLinksWhitelist as $scheme) 1903 | { 1904 | if (self::striAtStart($Element['attributes'][$attribute], $scheme)) 1905 | { 1906 | return $Element; 1907 | } 1908 | } 1909 | 1910 | $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); 1911 | 1912 | return $Element; 1913 | } 1914 | 1915 | # 1916 | # Static Methods 1917 | # 1918 | 1919 | protected static function escape($text, $allowQuotes = false) 1920 | { 1921 | return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); 1922 | } 1923 | 1924 | protected static function striAtStart($string, $needle) 1925 | { 1926 | $len = strlen($needle); 1927 | 1928 | if ($len > strlen($string)) 1929 | { 1930 | return false; 1931 | } 1932 | else 1933 | { 1934 | return strtolower(substr($string, 0, $len)) === strtolower($needle); 1935 | } 1936 | } 1937 | 1938 | static function instance($name = 'default') 1939 | { 1940 | if (isset(self::$instances[$name])) 1941 | { 1942 | return self::$instances[$name]; 1943 | } 1944 | 1945 | $instance = new static(); 1946 | 1947 | self::$instances[$name] = $instance; 1948 | 1949 | return $instance; 1950 | } 1951 | 1952 | private static $instances = array(); 1953 | 1954 | # 1955 | # Fields 1956 | # 1957 | 1958 | protected $DefinitionData; 1959 | 1960 | # 1961 | # Read-Only 1962 | 1963 | protected $specialCharacters = array( 1964 | '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' 1965 | ); 1966 | 1967 | protected $StrongRegex = array( 1968 | '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', 1969 | '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', 1970 | ); 1971 | 1972 | protected $EmRegex = array( 1973 | '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', 1974 | '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', 1975 | ); 1976 | 1977 | protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; 1978 | 1979 | protected $voidElements = array( 1980 | 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 1981 | ); 1982 | 1983 | protected $textLevelElements = array( 1984 | 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', 1985 | 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 1986 | 'i', 'rp', 'del', 'code', 'strike', 'marquee', 1987 | 'q', 'rt', 'ins', 'font', 'strong', 1988 | 's', 'tt', 'kbd', 'mark', 1989 | 'u', 'xm', 'sub', 'nobr', 1990 | 'sup', 'ruby', 1991 | 'var', 'span', 1992 | 'wbr', 'time', 1993 | ); 1994 | } 1995 | --------------------------------------------------------------------------------