├── .gitmodules ├── admin ├── img │ ├── editor.png │ ├── icons.png │ ├── icons@2x.png │ ├── noscreen.png │ ├── ajax-loader.gif │ └── editor@2x.png ├── js │ └── Moxie.swf ├── src │ ├── img │ │ ├── editor │ │ │ ├── editor-hr.png │ │ │ ├── editor-bold.png │ │ │ ├── editor-code.png │ │ │ ├── editor-hr@2x.png │ │ │ ├── editor-image.png │ │ │ ├── editor-link.png │ │ │ ├── editor-more.png │ │ │ ├── editor-olist.png │ │ │ ├── editor-quote.png │ │ │ ├── editor-redo.png │ │ │ ├── editor-ulist.png │ │ │ ├── editor-undo.png │ │ │ ├── editor-bold@2x.png │ │ │ ├── editor-code@2x.png │ │ │ ├── editor-heading.png │ │ │ ├── editor-image@2x.png │ │ │ ├── editor-italic.png │ │ │ ├── editor-link@2x.png │ │ │ ├── editor-more@2x.png │ │ │ ├── editor-olist@2x.png │ │ │ ├── editor-quote@2x.png │ │ │ ├── editor-redo@2x.png │ │ │ ├── editor-ulist@2x.png │ │ │ ├── editor-undo@2x.png │ │ │ ├── editor-fullscreen.png │ │ │ ├── editor-heading@2x.png │ │ │ ├── editor-italic@2x.png │ │ │ ├── editor-fullscreen@2x.png │ │ │ ├── editor-exit-fullscreen.png │ │ │ └── editor-exit-fullscreen@2x.png │ │ └── icons │ │ │ ├── icon-delete.png │ │ │ ├── icon-edit.png │ │ │ ├── icon-exlink.png │ │ │ ├── icon-upload.png │ │ │ ├── mime-audio.png │ │ │ ├── mime-html.png │ │ │ ├── mime-image.png │ │ │ ├── mime-office.png │ │ │ ├── mime-script.png │ │ │ ├── mime-text.png │ │ │ ├── mime-unknow.png │ │ │ ├── mime-video.png │ │ │ ├── icon-edit@2x.png │ │ │ ├── mime-archive.png │ │ │ ├── mime-audio@2x.png │ │ │ ├── mime-html@2x.png │ │ │ ├── mime-image@2x.png │ │ │ ├── mime-text@2x.png │ │ │ ├── mime-video@2x.png │ │ │ ├── icon-delete@2x.png │ │ │ ├── icon-exlink@2x.png │ │ │ ├── icon-upload@2x.png │ │ │ ├── mime-application.png │ │ │ ├── mime-archive@2x.png │ │ │ ├── mime-office@2x.png │ │ │ ├── mime-script@2x.png │ │ │ ├── mime-unknow@2x.png │ │ │ ├── icon-upload-active.png │ │ │ ├── icon-upload-active@2x.png │ │ │ └── mime-application@2x.png │ └── scss │ │ ├── _components.scss │ │ ├── _vars.scss │ │ ├── _footer.scss │ │ ├── _pagenavi.scss │ │ ├── _messages.scss │ │ ├── _hidden.scss │ │ ├── install.scss │ │ ├── _forms.scss │ │ ├── _buttons.scss │ │ └── components │ │ ├── _tokeninput.scss │ │ └── _timepicker.scss ├── footer.php ├── page-title.php ├── extending.php ├── user.php ├── category.php ├── table-js.php ├── options-general.php ├── options-plugin.php ├── options-discussion.php ├── options-permalink.php ├── css │ └── install.css ├── preview.php ├── copyright.php ├── form-js.php ├── menu.php ├── header.php ├── options-reading.php ├── options-theme.php ├── file-upload.php ├── welcome.php ├── upgrade.php ├── common.php ├── register.php ├── custom-fields-js.php ├── login.php ├── profile.php └── theme-editor.php ├── tools ├── yuicompressor-2.4.2.jar ├── package.json ├── default_site ├── fastcgi_params ├── Makefile └── build.js ├── composer.json ├── usr ├── themes │ └── default │ │ ├── screenshot.png │ │ ├── img │ │ ├── icon-search.png │ │ └── icon-search@2x.png │ │ ├── footer.php │ │ ├── 404.php │ │ ├── page.php │ │ ├── functions.php │ │ ├── post.php │ │ ├── index.php │ │ ├── archive.php │ │ ├── comments.php │ │ └── header.php └── plugins │ └── HelloWorld │ └── Plugin.php ├── var ├── IXR │ ├── Exception.php │ ├── Hook.php │ ├── Base64.php │ ├── Request.php │ ├── Error.php │ └── Date.php ├── Widget │ ├── ActionInterface.php │ ├── ExceptionHandle.php │ ├── Users │ │ ├── Author.php │ │ └── Admin.php │ ├── Logout.php │ ├── Contents │ │ ├── Post │ │ │ ├── Recent.php │ │ │ └── Date.php │ │ ├── Page │ │ │ ├── Rows.php │ │ │ └── Admin.php │ │ ├── Attachment │ │ │ ├── Unattached.php │ │ │ └── Related.php │ │ ├── Related │ │ │ └── Author.php │ │ └── Related.php │ ├── Base │ │ ├── QueryInterface.php │ │ └── Options.php │ ├── Metas │ │ ├── Tag │ │ │ ├── Admin.php │ │ │ └── Cloud.php │ │ └── Category │ │ │ └── Admin.php │ ├── Comments │ │ └── Recent.php │ ├── Notice.php │ ├── Themes │ │ ├── Config.php │ │ └── Rows.php │ ├── Base.php │ ├── Action.php │ ├── Plugins │ │ └── Config.php │ ├── Login.php │ └── Upgrade.php ├── Typecho │ ├── Widget │ │ ├── Terminal.php │ │ ├── Exception.php │ │ └── Helper │ │ │ ├── EmptyClass.php │ │ │ ├── Form │ │ │ └── Element │ │ │ │ ├── Submit.php │ │ │ │ ├── Textarea.php │ │ │ │ ├── Text.php │ │ │ │ ├── Password.php │ │ │ │ ├── Hidden.php │ │ │ │ ├── Fake.php │ │ │ │ ├── Select.php │ │ │ │ ├── Radio.php │ │ │ │ └── Checkbox.php │ │ │ ├── PageNavigator │ │ │ └── Classic.php │ │ │ └── PageNavigator.php │ ├── Db │ │ ├── Exception.php │ │ ├── Query │ │ │ └── Exception.php │ │ ├── Adapter │ │ │ ├── SQLException.php │ │ │ ├── ConnectionException.php │ │ │ ├── MysqlTrait.php │ │ │ ├── QueryTrait.php │ │ │ ├── Pdo │ │ │ │ ├── Mysql.php │ │ │ │ ├── Pgsql.php │ │ │ │ └── SQLite.php │ │ │ └── SQLiteTrait.php │ │ └── Adapter.php │ ├── Router │ │ ├── Exception.php │ │ └── Parser.php │ ├── Http │ │ ├── Client │ │ │ └── Exception.php │ │ └── Client.php │ ├── Exception.php │ ├── Plugin │ │ ├── Exception.php │ │ └── PluginInterface.php │ ├── I18n │ │ └── GetTextMulti.php │ ├── Cookie.php │ └── Date.php └── Utils │ └── Markdown.php ├── .editorconfig ├── app.json ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── Typecho-release-Ci.yml │ └── Typecho-dev-Ci.yml ├── .gitattributes ├── install ├── SQLite.php ├── Pgsql.php └── Mysql.php ├── .gitignore ├── composer.lock ├── index.php ├── README.md ├── changelog.txt └── .phpstorm.meta.php /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/img/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/editor.png -------------------------------------------------------------------------------- /admin/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/icons.png -------------------------------------------------------------------------------- /admin/js/Moxie.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/js/Moxie.swf -------------------------------------------------------------------------------- /admin/img/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/icons@2x.png -------------------------------------------------------------------------------- /admin/img/noscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/noscreen.png -------------------------------------------------------------------------------- /admin/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/ajax-loader.gif -------------------------------------------------------------------------------- /admin/img/editor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/img/editor@2x.png -------------------------------------------------------------------------------- /tools/yuicompressor-2.4.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/tools/yuicompressor-2.4.2.jar -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": "7.*", 4 | "ext-gd": "*", 5 | "ext-mbstring": "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /admin/src/img/editor/editor-hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-hr.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-delete.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-edit.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-exlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-exlink.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-upload.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-audio.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-html.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-image.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-office.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-script.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-text.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-unknow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-unknow.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-video.png -------------------------------------------------------------------------------- /usr/themes/default/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/usr/themes/default/screenshot.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-bold.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-code.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-hr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-hr@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-image.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-link.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-more.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-olist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-olist.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-quote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-quote.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-redo.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-ulist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-ulist.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-undo.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-edit@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-edit@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-archive.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-audio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-audio@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-html@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-html@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-image@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-text@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-text@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-video@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-video@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-bold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-bold@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-code@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-code@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-heading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-heading.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-image@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-italic.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-link@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-link@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-more@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-olist@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-olist@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-quote@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-quote@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-redo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-redo@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-ulist@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-ulist@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-undo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-undo@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-delete@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-exlink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-exlink@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-upload@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-upload@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-application.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-archive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-archive@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-office@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-office@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-script@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-script@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-unknow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-unknow@2x.png -------------------------------------------------------------------------------- /admin/src/scss/_components.scss: -------------------------------------------------------------------------------- 1 | @import "components/editor"; 2 | @import "components/timepicker"; 3 | @import "components/tokeninput"; -------------------------------------------------------------------------------- /usr/themes/default/img/icon-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/usr/themes/default/img/icon-search.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-fullscreen.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-heading@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-heading@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-italic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-italic@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-upload-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-upload-active.png -------------------------------------------------------------------------------- /usr/themes/default/img/icon-search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/usr/themes/default/img/icon-search@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-fullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-fullscreen@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/icon-upload-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/icon-upload-active@2x.png -------------------------------------------------------------------------------- /admin/src/img/icons/mime-application@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/icons/mime-application@2x.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-exit-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-exit-fullscreen.png -------------------------------------------------------------------------------- /admin/src/img/editor/editor-exit-fullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PJIDC/TFH-Project/HEAD/admin/src/img/editor/editor-exit-fullscreen@2x.png -------------------------------------------------------------------------------- /var/IXR/Exception.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | end(); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | 7 | [*.yml] 8 | indent_size = 2 9 | 10 | [*.scss] 11 | indent_size = 2 12 | 13 | [*.php] 14 | insert_final_newline = true -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TFH", 3 | "description": "Typecho For Heroku", 4 | "website": "https://github.com/iam233/TFH-Project", 5 | "repository": "https://github.com/iam233/TFH-Project", 6 | "success_url": "/" 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 1. 该问题的重现步骤是什么? 2 | 3 | ### 2. 你期待的结果是什么?实际看到的又是什么? 4 | 5 | ### 3. 问题出现的环境 6 | 7 | - 操作系统版本: 8 | - Apache/NGINX 版本: 9 | - 数据库版本: 10 | - PHP 版本: 11 | - Typecho 版本: 12 | - 浏览器版本: 13 | 14 | [//]: # (如有图片请附上截图) -------------------------------------------------------------------------------- /var/Widget/ActionInterface.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

title; ?>addLink)) { 5 | echo "addLink}\">" . _t("新增") . ""; 6 | } 7 | ?>

8 |
9 | -------------------------------------------------------------------------------- /var/Typecho/Db/Exception.php: -------------------------------------------------------------------------------- 1 | message = $message; 17 | $this->code = $code; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.php text eol=lf 7 | *.js text eol=lf 8 | *.css text eol=lf 9 | *.html text eol=lf 10 | *.xml text eol=lf 11 | 12 | # Denote all files that are truly binary and should not be modified. 13 | *.png binary 14 | *.jpg binary 15 | 16 | -------------------------------------------------------------------------------- /admin/extending.php: -------------------------------------------------------------------------------- 1 | get('panel'); 6 | $panelTable = unserialize($options->panelTable); 7 | 8 | if (!isset($panelTable['file']) || !in_array(urlencode($panel), $panelTable['file'])) { 9 | throw new \Typecho\Plugin\Exception(_t('页面不存在'), 404); 10 | } 11 | 12 | [$pluginName, $file] = explode('/', trim($panel, '/'), 2); 13 | 14 | require_once $options->pluginDir($pluginName) . '/' . $file; 15 | -------------------------------------------------------------------------------- /install/SQLite.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .*.swo 3 | ._* 4 | .DS_Store 5 | /ImgCache/ 6 | /Backup_rar/ 7 | /Debug/ 8 | /debug/ 9 | /upload/ 10 | /avatar/ 11 | /.idea/ 12 | .svn/ 13 | *.orig 14 | *.aps 15 | *.APS 16 | *.chm 17 | *.exp 18 | *.pdb 19 | *.rar 20 | *.mo 21 | *.po 22 | *.pot 23 | .smbdelete* 24 | *.sublime* 25 | .sass-cache 26 | config.rb 27 | /config.inc.php 28 | /usr/uploads/ 29 | /usr/*.db 30 | /usr/plugins/ 31 | !/usr/plugins/HelloWorld 32 | /usr/themes/ 33 | !/usr/themes/default 34 | node_modules/ 35 | /tools/tmp/ 36 | -------------------------------------------------------------------------------- /usr/themes/default/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | footer(); ?> 13 | 14 | 15 | -------------------------------------------------------------------------------- /tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typecho-tools", 3 | "version": "1.0.0", 4 | "description": "Typecho build tools", 5 | "main": "build.js", 6 | "engines": { 7 | "node": "16.x" 8 | }, 9 | "scripts": { 10 | "build_js": "node build.js js", 11 | "build_css": "node build.js css" 12 | }, 13 | "keywords": [ 14 | "typecho" 15 | ], 16 | "author": "joyqi", 17 | "license": "GPL-2.0-only", 18 | "dependencies": { 19 | "chalk": "^4.0.0", 20 | "node-sass": "^6.0.1", 21 | "sprite-magic-importer": "^1.6.2", 22 | "uglify-js": "^3.11.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /var/Typecho/Plugin/Exception.php: -------------------------------------------------------------------------------- 1 | render(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /var/IXR/Hook.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 24 | -------------------------------------------------------------------------------- /admin/category.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 24 | -------------------------------------------------------------------------------- /admin/table-js.php: -------------------------------------------------------------------------------- 1 | 2 | 19 | -------------------------------------------------------------------------------- /admin/options-general.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 24 | -------------------------------------------------------------------------------- /admin/options-plugin.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | config()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 24 | -------------------------------------------------------------------------------- /admin/options-discussion.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 24 | -------------------------------------------------------------------------------- /admin/options-permalink.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /admin/src/scss/_pagenavi.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 后台分页 3 | */ 4 | 5 | .typecho-pager { 6 | list-style: none; 7 | float: right; 8 | margin: 0; 9 | padding: 0; 10 | line-height: 1; 11 | text-align: center; 12 | zoom: 1; 13 | } 14 | 15 | .typecho-pager li { 16 | display: inline-block; 17 | margin: 0 3px; 18 | height: 28px; 19 | line-height: 28px; 20 | } 21 | 22 | .typecho-pager a { 23 | display: block; 24 | padding: 0 10px; 25 | border-radius: 2px; 26 | } 27 | 28 | .typecho-pager a:hover { 29 | text-decoration: none; 30 | background: #E9E9E6; 31 | } 32 | 33 | .typecho-pager li.current a { 34 | background: #E9E9E6; 35 | color: #444; 36 | } 37 | -------------------------------------------------------------------------------- /var/IXR/Base64.php: -------------------------------------------------------------------------------- 1 | data = $data; 27 | } 28 | 29 | /** 30 | * 获取XML数据 31 | * 32 | * @return string 33 | */ 34 | public function getXml() 35 | { 36 | return '' . base64_encode($this->data) . ''; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /usr/themes/default/404.php: -------------------------------------------------------------------------------- 1 | 2 | need('header.php'); ?> 3 | 4 |
5 | 6 |
7 |

404 -

8 |

9 |
10 |

11 |

12 | 13 |

14 |
15 |
16 | 17 |
18 | need('footer.php'); ?> 19 | -------------------------------------------------------------------------------- /admin/css/install.css: -------------------------------------------------------------------------------- 1 | h1 { text-align: center; } 2 | 3 | details summary { cursor: pointer; } 4 | 5 | @keyframes fadein { from { opacity: 0; } 6 | to { opacity: 1; } } 7 | 8 | .fresh .keep-word { display: none; } 9 | 10 | .keep .fresh-word { display: none; } 11 | 12 | form > .message { display: none; padding: 20px; border-radius: 5px; } 13 | 14 | .message textarea { width: 100%; height: 200px; resize: none; margin: 10px 0; } 15 | 16 | .message.fade { display: block; animation: fadein .5s linear; } 17 | 18 | .message *:last-child { margin-bottom: 0; } 19 | 20 | .message p { margin-top: 10px; } 21 | 22 | .message p button { margin-left: 5px; } 23 | 24 | .message p button:first-child { margin-left: 0; } 25 | -------------------------------------------------------------------------------- /admin/preview.php: -------------------------------------------------------------------------------- 1 | to($content); 7 | 8 | /** 检测是否存在 */ 9 | if (!$content->have()) { 10 | $response->redirect($options->adminUrl); 11 | } 12 | 13 | /** 检测权限 */ 14 | if (!$user->pass('editor', true) && $content->authorId != $user->uid) { 15 | $response->redirect($options->adminUrl); 16 | } 17 | 18 | /** 输出内容 */ 19 | $content->render(); 20 | ?> 21 | 28 | -------------------------------------------------------------------------------- /admin/copyright.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter/MysqlTrait.php: -------------------------------------------------------------------------------- 1 | query('TRUNCATE TABLE ' . $this->quoteColumn($table), $handle); 19 | } 20 | 21 | /** 22 | * 合成查询语句 23 | * 24 | * @access public 25 | * @param array $sql 查询对象词法数组 26 | * @return string 27 | */ 28 | public function parseSelect(array $sql): string 29 | { 30 | return $this->buildQuery($sql); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "f6d55c83d6fa3e06a9e3569fc5bea39b", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "ext-gd": "*", 17 | "ext-mbstring": "*", 18 | "php": "7.*" 19 | }, 20 | "platform-dev": [], 21 | "plugin-api-version": "2.0.0" 22 | } 23 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | begin(); 21 | 22 | /** 开始路由分发 */ 23 | \Typecho\Router::dispatch(); 24 | 25 | /** 注册一个结束插件 */ 26 | \Typecho\Plugin::factory('index.php')->end(); 27 | -------------------------------------------------------------------------------- /usr/themes/default/page.php: -------------------------------------------------------------------------------- 1 | 2 | need('header.php'); ?> 3 | 4 |
5 |
6 |

7 | 9 |

10 |
11 | content(); ?> 12 |
13 |
14 | need('comments.php'); ?> 15 |
16 | 17 | need('sidebar.php'); ?> 18 | need('footer.php'); ?> 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TFH Project 2 | ========================= 3 | ![](https://phyllisjohnson.herokuapp.com/TFH-README/TFH-t.png) 4 | # 介绍 5 | TFH Project是一个让Typecho在免费容器Heroku上运行的项目
6 | Demo:https://tfhproject.herokuapp.com/ 7 | # 如何工作? 8 | 使用app.json设置Typecho,composer.json和composer.lock配置运行环境 9 | # 如何安装? 10 | 首先导入到heroku
11 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
12 | 安装PostgerSQL插件到自己的Heroku应用
13 | https://elements.heroku.com/addons/heroku-postgresql
14 | 打开管理面板,然后打开导航栏中的 Settings,下面的Database Credentials,点击View Credentials就会显示了
15 | ![](https://phyllisjohnson.herokuapp.com/TFH-README/database.png)
16 | Typecho数据库设置
17 | ![](https://phyllisjohnson.herokuapp.com/TFH-README/typecho-shezhi.png)
18 | 然后跟着Typecho的安装步骤走即可 19 | -------------------------------------------------------------------------------- /var/Widget/Users/Author.php: -------------------------------------------------------------------------------- 1 | parameter->uid) { 31 | $this->db->fetchRow($this->select() 32 | ->where('uid = ?', $this->parameter->uid), [$this, 'push']); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /admin/form-js.php: -------------------------------------------------------------------------------- 1 | 2 | 27 | -------------------------------------------------------------------------------- /var/Widget/Logout.php: -------------------------------------------------------------------------------- 1 | security->protect(); 31 | 32 | $this->user->logout(); 33 | self::pluginHandle()->logout(); 34 | @session_destroy(); 35 | $this->response->goBack(null, $this->options->index); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /admin/menu.php: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | -------------------------------------------------------------------------------- /admin/src/scss/_messages.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 提示信息框 4 | */ 5 | .message { 6 | padding: 8px 10px; 7 | border-radius: 2px; 8 | } 9 | 10 | .message a { 11 | font-weight: bold; 12 | text-decoration: underline; 13 | } 14 | 15 | .error { 16 | background: #FBE3E4; 17 | color: #8A1F11; 18 | } 19 | .error a { color: #8A1F11; } 20 | 21 | .notice { 22 | background: #FFF6BF; 23 | color: #8A6D3B; 24 | } 25 | .notice a { color: #8A6D3B; } 26 | 27 | .success { 28 | background: #E6EFC2; 29 | color: #264409; 30 | } 31 | .success a { color: #264409; } 32 | 33 | 34 | // 气泡 35 | .balloon { 36 | display: inline-block; 37 | padding: 0 4px; 38 | min-width: 10px; 39 | height: 14px; 40 | line-height: 14px; 41 | background: #B9B9B6; 42 | vertical-align: text-top; 43 | text-align: center; 44 | font-size: 12px; 45 | color: #FFF; 46 | 47 | border-radius: 20px; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /tools/default_site: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes 2; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile off; 13 | tcp_nopush on; 14 | tcp_nodelay on; 15 | 16 | keepalive_timeout 65; 17 | 18 | server { 19 | listen 80 default_server; 20 | server_name _; 21 | 22 | root /www; 23 | index index.html index.php; 24 | 25 | if (!-e $request_filename) { 26 | rewrite ^(.*)$ /index.php$1 last; 27 | } 28 | 29 | location ~ [^/]\.php(/|$) { 30 | fastcgi_split_path_info ^(.+?\.php)(/.*)$; 31 | fastcgi_pass unix:/php/var/run/php-fpm.sock; 32 | fastcgi_index index.php; 33 | include fastcgi_params.modified; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /var/Typecho/Http/Client.php: -------------------------------------------------------------------------------- 1 | hook('afterParseCode', function ($html) { 28 | return preg_replace("//i", "", $html); 29 | }); 30 | 31 | $parser->enableHtml(true); 32 | } 33 | 34 | return str_replace('

', '', $parser->makeHtml($text)); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /admin/src/scss/install.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center; 3 | } 4 | 5 | details { 6 | summary { 7 | cursor: pointer; 8 | } 9 | } 10 | 11 | @keyframes fadein { 12 | from { opacity: 0; } 13 | to { opacity: 1; } 14 | } 15 | 16 | .fresh { 17 | .keep-word { 18 | display: none; 19 | } 20 | } 21 | 22 | .keep { 23 | .fresh-word { 24 | display: none; 25 | } 26 | } 27 | 28 | .message { 29 | 30 | form > & { 31 | display: none; 32 | padding: 20px; 33 | border-radius: 5px; 34 | } 35 | 36 | textarea { 37 | width: 100%; 38 | height: 200px; 39 | resize: none; 40 | margin: 10px 0; 41 | } 42 | 43 | &.fade { 44 | display: block; 45 | animation: fadein .5s linear; 46 | } 47 | 48 | *:last-child { 49 | margin-bottom: 0; 50 | } 51 | 52 | p { 53 | margin-top: 10px; 54 | 55 | button { 56 | margin-left: 5px; 57 | } 58 | 59 | button:first-child { 60 | margin-left: 0; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /admin/header.php: -------------------------------------------------------------------------------- 1 | adminStaticUrl('css', 'normalize.css', true) . '"> 7 | 8 | '; 9 | 10 | /** 注册一个初始化插件 */ 11 | $header = \Typecho\Plugin::factory('admin/header.php')->header($header); 12 | 13 | ?> 14 | 15 | 16 | 17 | 18 | 19 | <?php _e('%s - %s - Powered by Typecho', $menu->title, $options->title); ?> 20 | 21 | 22 | 23 | > 24 | -------------------------------------------------------------------------------- /admin/options-reading.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | form()->render(); ?> 13 |
14 |
15 |
16 |
17 | 18 | 23 | 35 | 38 | -------------------------------------------------------------------------------- /var/Widget/Contents/Post/Recent.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault(['pageSize' => $this->options->postsListSize]); 30 | 31 | $this->db->fetchAll($this->select() 32 | ->where('table.contents.status = ?', 'publish') 33 | ->where('table.contents.created < ?', $this->options->time) 34 | ->where('table.contents.type = ?', 'post') 35 | ->order('table.contents.created', Db::SORT_DESC) 36 | ->limit($this->parameter->pageSize), [$this, 'push']); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/EmptyClass.php: -------------------------------------------------------------------------------- 1 | xml = << 25 | 26 | {$method} 27 | 28 | 29 | EOD; 30 | foreach ($args as $arg) { 31 | $this->xml .= ''; 32 | $v = new Value($arg); 33 | $this->xml .= $v->getXml(); 34 | $this->xml .= "\n"; 35 | } 36 | 37 | $this->xml .= ''; 38 | } 39 | 40 | /** 41 | * @return int 42 | */ 43 | public function getLength(): int 44 | { 45 | return strlen($this->xml); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getXml(): string 52 | { 53 | return $this->xml; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /admin/src/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Forms 3 | */ 4 | 5 | input[type=text], input[type=password], input[type=email], 6 | textarea { 7 | background: #FFF; 8 | border: 1px solid #D9D9D6; 9 | padding: 7px; 10 | 11 | border-radius: 2px; 12 | 13 | box-sizing: border-box; 14 | 15 | &:disabled, &:read-only { 16 | background: #F3F3F3; 17 | } 18 | } 19 | 20 | textarea { 21 | resize: vertical; 22 | line-height: 1.5; 23 | } 24 | 25 | input[type="radio"], input[type="checkbox"] { 26 | margin-right: 3px; 27 | } 28 | 29 | input[type="radio"], input[type="checkbox"], 30 | input[type="radio"] + label, input[type="checkbox"] + label { 31 | vertical-align: middle; 32 | } 33 | 34 | input, textarea { 35 | &.text-s { padding: 5px; } 36 | &.text-l { 37 | padding: 10px; 38 | font-size: 1.14286em; 39 | } 40 | } 41 | 42 | .w-10 { width: 10%; } 43 | .w-20 { width: 20%; } 44 | .w-30 { width: 30%; } 45 | .w-40 { width: 40%;} 46 | .w-50 { width: 50%;} 47 | .w-60 { width: 60%;} 48 | .w-70 { width: 70%;} 49 | .w-80 { width: 80%;} 50 | .w-90 { width: 90%;} 51 | .w-100 { width: 100%;} 52 | 53 | select { 54 | border: 1px solid #CCC; 55 | height: 28px; 56 | } 57 | -------------------------------------------------------------------------------- /var/Widget/Base/QueryInterface.php: -------------------------------------------------------------------------------- 1 | 权限控制增加“允许游客访问”选项,用于私密浏览 43 | fix Issue 545 44 | 实现文章公开度:公开、密码保护、私有、未审核 45 | little change 46 | 实现功能:1,当用户之前有审核通过的评论,再次发评论会直接为通过审核(可关闭);2,未通过审核的评论,评论作者可以在前台看到(他人不可见);3,隐藏功能; 47 | 修正后台修改或添加页面状态错误的问题;修正保存私密、审核日志时会新增一篇同样日志的问题 48 | 简单增强搜索功能 Issue 480 49 | 添加有密码和未发布图标 50 | fix Issue 551 51 | 将文章管理页的公开度草稿等信息改为文字 52 | 接受插件返回的header 53 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Submit.php: -------------------------------------------------------------------------------- 1 | setAttribute('class', 'typecho-option typecho-option-submit'); 32 | $input = new Layout('button', ['type' => 'submit']); 33 | $this->container($input); 34 | $this->inputs[] = $input; 35 | 36 | return $input; 37 | } 38 | 39 | /** 40 | * 设置表单元素值 41 | * 42 | * @param mixed $value 表单元素值 43 | */ 44 | protected function inputValue($value) 45 | { 46 | $this->input->html($value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /var/Widget/Contents/Page/Rows.php: -------------------------------------------------------------------------------- 1 | select()->where('table.contents.type = ?', 'page') 33 | ->where('table.contents.status = ?', 'publish') 34 | ->where('table.contents.created < ?', $this->options->time) 35 | ->order('table.contents.order', Db::SORT_ASC); 36 | 37 | //去掉自定义首页 38 | $frontPage = explode(':', $this->options->frontPage); 39 | if (2 == count($frontPage) && 'page' == $frontPage[0]) { 40 | $select->where('table.contents.cid <> ?', $frontPage[1]); 41 | } 42 | 43 | $this->db->fetchAll($select, [$this, 'push']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /usr/themes/default/functions.php: -------------------------------------------------------------------------------- 1 | addInput($logoUrl); 15 | 16 | $sidebarBlock = new \Typecho\Widget\Helper\Form\Element\Checkbox( 17 | 'sidebarBlock', 18 | [ 19 | 'ShowRecentPosts' => _t('显示最新文章'), 20 | 'ShowRecentComments' => _t('显示最近回复'), 21 | 'ShowCategory' => _t('显示分类'), 22 | 'ShowArchive' => _t('显示归档'), 23 | 'ShowOther' => _t('显示其它杂项') 24 | ], 25 | ['ShowRecentPosts', 'ShowRecentComments', 'ShowCategory', 'ShowArchive', 'ShowOther'], 26 | _t('侧边栏显示') 27 | ); 28 | 29 | $form->addInput($sidebarBlock->multiMode()); 30 | } 31 | 32 | /* 33 | function themeFields($layout) 34 | { 35 | $logoUrl = new \Typecho\Widget\Helper\Form\Element\Text( 36 | 'logoUrl', 37 | null, 38 | null, 39 | _t('站点LOGO地址'), 40 | _t('在这里填入一个图片URL地址, 以在网站标题前加上一个LOGO') 41 | ); 42 | $layout->addItem($logoUrl); 43 | } 44 | */ 45 | -------------------------------------------------------------------------------- /var/IXR/Error.php: -------------------------------------------------------------------------------- 1 | code = $code; 37 | $this->message = $message; 38 | } 39 | 40 | /** 41 | * 获取xml 42 | * 43 | * @return string 44 | */ 45 | public function getXml(): string 46 | { 47 | return << 49 | 50 | 51 | 52 | 53 | faultCode 54 | {$this->code} 55 | 56 | 57 | faultString 58 | {$this->message} 59 | 60 | 61 | 62 | 63 | 64 | 65 | EOD; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/Typecho-release-Ci.yml: -------------------------------------------------------------------------------- 1 | name: Typecho Build Release Ci 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | - name: Build 13 | run: | 14 | mkdir build 15 | cp -r LICENSE.txt index.php install.php admin install usr var build/ 16 | mkdir build/usr/uploads/ 17 | chmod 755 build/usr/uploads/ 18 | rm -rf build/admin/src 19 | cd build && zip -q -r typecho.zip * && mv typecho.zip ../ && cd - 20 | - name: Create Release 21 | id: create_release 22 | uses: actions/create-release@v1 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | tag_name: ${{ github.ref }} 27 | release_name: ${{ github.ref }} 28 | draft: true 29 | prerelease: false 30 | - name: Upload Release Asset 31 | id: upload-release-asset 32 | uses: actions/upload-release-asset@v1 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | with: 36 | upload_url: ${{ steps.create_release.outputs.upload_url }} 37 | asset_path: ./typecho.zip 38 | asset_name: typecho.zip 39 | asset_content_type: application/zip 40 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Textarea.php: -------------------------------------------------------------------------------- 1 | $name . '-0-' . self::$uniqueId, 'name' => $name]); 32 | $this->label->setAttribute('for', $name . '-0-' . self::$uniqueId); 33 | $this->container($input->setClose(false)); 34 | $this->inputs[] = $input; 35 | 36 | return $input; 37 | } 38 | 39 | /** 40 | * 设置表单项默认值 41 | * 42 | * @param mixed $value 表单项默认值 43 | */ 44 | protected function inputValue($value) 45 | { 46 | $this->input->html(htmlspecialchars($value)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /admin/options-theme.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 |
    13 |
  • 14 | 15 |
  • 16 | 17 |
  • 19 |
20 |
21 |
22 | config()->render(); ?> 23 |
24 |
25 |
26 |
27 | 28 | 34 | -------------------------------------------------------------------------------- /var/Widget/Contents/Attachment/Unattached.php: -------------------------------------------------------------------------------- 1 | select()->where('table.contents.type = ? AND 42 | (table.contents.parent = 0 OR table.contents.parent IS NULL)', 'attachment'); 43 | 44 | /** 加上对用户的判断 */ 45 | $this->where('table.contents.authorId = ?', $this->user->uid); 46 | 47 | /** 提交查询 */ 48 | $select->order('table.contents.created', Db::SORT_DESC); 49 | 50 | $this->db->fetchAll($select, [$this, 'push']); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /var/Widget/Metas/Tag/Admin.php: -------------------------------------------------------------------------------- 1 | select()->where('type = ?', 'tag')->order('mid', Db::SORT_DESC); 30 | $this->db->fetchAll($select, [$this, 'push']); 31 | } 32 | 33 | /** 34 | * 获取菜单标题 35 | * 36 | * @return string|null 37 | * @throws Exception|Db\Exception 38 | */ 39 | public function getMenuTitle(): ?string 40 | { 41 | if (isset($this->request->mid)) { 42 | $tag = $this->db->fetchRow($this->select() 43 | ->where('type = ? AND mid = ?', 'tag', $this->request->mid)); 44 | 45 | if (!empty($tag)) { 46 | return _t('编辑标签 %s', $tag['name']); 47 | } 48 | } else { 49 | return null; 50 | } 51 | 52 | throw new Exception(_t('标签不存在'), 404); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Text.php: -------------------------------------------------------------------------------- 1 | $name . '-0-' . self::$uniqueId, 32 | 'name' => $name, 'type' => 'text', 'class' => 'text']); 33 | $this->container($input); 34 | $this->label->setAttribute('for', $name . '-0-' . self::$uniqueId); 35 | $this->inputs[] = $input; 36 | 37 | return $input; 38 | } 39 | 40 | /** 41 | * 设置表单项默认值 42 | * 43 | * @param mixed $value 表单项默认值 44 | */ 45 | protected function inputValue($value) 46 | { 47 | $this->input->setAttribute('value', htmlspecialchars($value)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Password.php: -------------------------------------------------------------------------------- 1 | $name . '-0-' . self::$uniqueId, 32 | 'name' => $name, 'type' => 'password', 'class' => 'password']); 33 | $this->label->setAttribute('for', $name . '-0-' . self::$uniqueId); 34 | $this->container($input); 35 | $this->inputs[] = $input; 36 | return $input; 37 | } 38 | 39 | /** 40 | * 设置表单项默认值 41 | * 42 | * @param mixed $value 表单项默认值 43 | */ 44 | protected function inputValue($value) 45 | { 46 | $this->input->setAttribute('value', htmlspecialchars($value)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /var/Widget/Contents/Related/Author.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault('limit=5'); 32 | 33 | if ($this->parameter->author) { 34 | $this->db->fetchAll($this->select() 35 | ->where('table.contents.authorId = ?', $this->parameter->author) 36 | ->where('table.contents.cid <> ?', $this->parameter->cid) 37 | ->where('table.contents.status = ?', 'publish') 38 | ->where('table.contents.password IS NULL') 39 | ->where('table.contents.created < ?', $this->options->time) 40 | ->where('table.contents.type = ?', $this->parameter->type) 41 | ->order('table.contents.created', Db::SORT_DESC) 42 | ->limit($this->parameter->limit), [$this, 'push']); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Hidden.php: -------------------------------------------------------------------------------- 1 | setAttribute('style', 'display:none'); 31 | } 32 | 33 | /** 34 | * 初始化当前输入项 35 | * 36 | * @access public 37 | * @param string|null $name 表单元素名称 38 | * @param array|null $options 选择项 39 | * @return Layout|null 40 | */ 41 | public function input(?string $name = null, ?array $options = null): ?Layout 42 | { 43 | $input = new Layout('input', ['name' => $name, 'type' => 'hidden']); 44 | $this->container($input); 45 | $this->inputs[] = $input; 46 | return $input; 47 | } 48 | 49 | /** 50 | * 设置表单项默认值 51 | * 52 | * @param mixed $value 表单项默认值 53 | */ 54 | protected function inputValue($value) 55 | { 56 | $this->input->setAttribute('value', htmlspecialchars($value)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /var/Widget/Contents/Attachment/Related.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault('parentId=0&limit=0'); 32 | 33 | //如果没有cid值 34 | if (!$this->parameter->parentId) { 35 | return; 36 | } 37 | 38 | /** 构建基础查询 */ 39 | $select = $this->select()->where('table.contents.type = ?', 'attachment'); 40 | 41 | //order字段在文件里代表所属文章 42 | $select->where('table.contents.parent = ?', $this->parameter->parentId); 43 | 44 | /** 提交查询 */ 45 | $select->order('table.contents.created', Db::SORT_ASC); 46 | 47 | if ($this->parameter->limit > 0) { 48 | $select->limit($this->parameter->limit); 49 | } 50 | 51 | if ($this->parameter->offset > 0) { 52 | $select->offset($this->parameter->offset); 53 | } 54 | 55 | $this->db->fetchAll($select, [$this, 'push']); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /admin/file-upload.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | cid : $page->cid; 6 | 7 | if ($cid) { 8 | \Widget\Contents\Attachment\Related::alloc(['parentId' => $cid])->to($attachment); 9 | } else { 10 | \Widget\Contents\Attachment\Unattached::alloc()->to($attachment); 11 | } 12 | } 13 | ?> 14 | 15 |
16 |
或者 %s选择文件上传%s', '', ''); ?>
17 |
    18 | next()): ?> 19 |
  • 20 | title(); ?> 21 |
    22 | attachment->size / 1024)); ?> Kb 23 | 24 | 25 |
    26 |
  • 27 | 28 |
29 |
30 | 31 | -------------------------------------------------------------------------------- /var/Widget/Metas/Tag/Cloud.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault(['sort' => 'count', 'ignoreZeroCount' => false, 'desc' => true, 'limit' => 0]); 31 | $select = $this->select()->where('type = ?', 'tag') 32 | ->order($this->parameter->sort, $this->parameter->desc ? Db::SORT_DESC : Db::SORT_ASC); 33 | 34 | /** 忽略零数量 */ 35 | if ($this->parameter->ignoreZeroCount) { 36 | $select->where('count > 0'); 37 | } 38 | 39 | /** 总数限制 */ 40 | if ($this->parameter->limit) { 41 | $select->limit($this->parameter->limit); 42 | } 43 | 44 | $this->db->fetchAll($select, [$this, 'push']); 45 | } 46 | 47 | /** 48 | * 按分割数输出字符串 49 | * 50 | * @param mixed ...$args 需要输出的值 51 | */ 52 | public function split(...$args) 53 | { 54 | array_unshift($args, $this->count); 55 | echo call_user_func_array([Common::class, 'splitByCount'], $args); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /admin/welcome.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 |

title); ?>

15 |
    16 |
  1. 17 | pass('contributor', true)): ?> 18 |
  2. 19 |
  3. 20 | 21 |
  4. 22 | 23 |
24 |

25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 37 | -------------------------------------------------------------------------------- /install/Pgsql.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
  • 4 | 5 | 6 |

    7 |
  • 8 |
9 |
    10 |
  • 11 | 12 | 13 |

    14 |
  • 15 |
16 |
    17 |
  • 18 | 19 | 20 |

    21 |
  • 22 |
23 |
    24 |
  • 25 | 26 | 27 |
  • 29 |
      30 |
    • 31 | 32 | 33 |

      34 |
    • 36 | 37 | 38 | -------------------------------------------------------------------------------- /var/Widget/Comments/Recent.php: -------------------------------------------------------------------------------- 1 | setDefault( 30 | ['pageSize' => $this->options->commentsListSize, 'parentId' => 0, 'ignoreAuthor' => false] 31 | ); 32 | } 33 | 34 | /** 35 | * 执行函数 36 | * 37 | * @throws Exception 38 | */ 39 | public function execute() 40 | { 41 | $select = $this->select()->limit($this->parameter->pageSize) 42 | ->where('table.comments.status = ?', 'approved') 43 | ->order('table.comments.coid', Db::SORT_DESC); 44 | 45 | if ($this->parameter->parentId) { 46 | $select->where('cid = ?', $this->parameter->parentId); 47 | } 48 | 49 | if ($this->options->commentsShowCommentOnly) { 50 | $select->where('type = ?', 'comment'); 51 | } 52 | 53 | /** 忽略作者评论 */ 54 | if ($this->parameter->ignoreAuthor) { 55 | $select->where('ownerId <> authorId'); 56 | } 57 | 58 | $this->db->fetchAll($select, [$this, 'push']); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /usr/plugins/HelloWorld/Plugin.php: -------------------------------------------------------------------------------- 1 | navBar = __CLASS__ . '::render'; 30 | } 31 | 32 | /** 33 | * 禁用插件方法,如果禁用失败,直接抛出异常 34 | */ 35 | public static function deactivate() 36 | { 37 | } 38 | 39 | /** 40 | * 获取插件配置面板 41 | * 42 | * @param Form $form 配置面板 43 | */ 44 | public static function config(Form $form) 45 | { 46 | /** 分类名称 */ 47 | $name = new Text('word', null, 'Hello World', _t('说点什么')); 48 | $form->addInput($name); 49 | } 50 | 51 | /** 52 | * 个人用户的配置面板 53 | * 54 | * @param Form $form 55 | */ 56 | public static function personalConfig(Form $form) 57 | { 58 | } 59 | 60 | /** 61 | * 插件实现方法 62 | * 63 | * @access public 64 | * @return void 65 | */ 66 | public static function render() 67 | { 68 | echo '' 69 | . htmlspecialchars(Options::alloc()->plugin('HelloWorld')->word) 70 | . ''; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /var/Widget/Contents/Page/Admin.php: -------------------------------------------------------------------------------- 1 | select()->where( 34 | 'table.contents.type = ? OR (table.contents.type = ? AND table.contents.parent = ?)', 35 | 'page', 36 | 'page_draft', 37 | 0 38 | ); 39 | 40 | /** 过滤标题 */ 41 | if (null != ($keywords = $this->request->keywords)) { 42 | $args = []; 43 | $keywordsList = explode(' ', $keywords); 44 | $args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?')); 45 | 46 | foreach ($keywordsList as $keyword) { 47 | $args[] = '%' . Common::filterSearchQuery($keyword) . '%'; 48 | } 49 | 50 | call_user_func_array([$select, 'where'], $args); 51 | } 52 | 53 | /** 提交查询 */ 54 | $select->order('table.contents.order', Db::SORT_ASC); 55 | 56 | $this->db->fetchAll($select, [$this, 'push']); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Fake.php: -------------------------------------------------------------------------------- 1 | name = $name; 31 | self::$uniqueId++; 32 | 33 | /** 运行自定义初始函数 */ 34 | $this->init(); 35 | 36 | /** 初始化表单项 */ 37 | $this->input = $this->input($name); 38 | 39 | /** 初始化表单值 */ 40 | if (null !== $value) { 41 | $this->value($value); 42 | } 43 | } 44 | 45 | /** 46 | * 初始化当前输入项 47 | * 48 | * @param string|null $name 表单元素名称 49 | * @param array|null $options 选择项 50 | * @return Layout|null 51 | */ 52 | public function input(?string $name = null, ?array $options = null): ?Layout 53 | { 54 | $input = new Layout('input'); 55 | $this->inputs[] = $input; 56 | return $input; 57 | } 58 | 59 | /** 60 | * 设置表单项默认值 61 | * 62 | * @param mixed $value 表单项默认值 63 | */ 64 | protected function inputValue($value) 65 | { 66 | $this->input->setAttribute('value', $value); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /usr/themes/default/post.php: -------------------------------------------------------------------------------- 1 | 2 | need('header.php'); ?> 3 | 4 |
      5 |
      6 |

      7 | 9 |

      10 | 21 |
      22 | content(); ?> 23 |
      24 |

      tags(', ', true, 'none'); ?>

      25 |
      26 | 27 | need('comments.php'); ?> 28 | 29 |
        30 |
      • 上一篇: thePrev('%s', '没有了'); ?>
      • 31 |
      • 下一篇: theNext('%s', '没有了'); ?>
      • 32 |
      33 |
      34 | 35 | need('sidebar.php'); ?> 36 | need('footer.php'); ?> 37 | -------------------------------------------------------------------------------- /admin/upgrade.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
      8 |
      9 | 10 |
      11 |
      12 |
      13 |
      16 |

      17 |
        18 |
      • 19 |
      • %s 升级到 %s', $options->version, \Typecho\Common::VERSION); ?>
      • 20 |
      • 备份您的数据', \Typecho\Common::url('backup.php', $options->adminUrl)); ?> 22 |
      • 23 |
      24 |

      25 | 26 |

      27 |
      28 |
      29 |
      30 |
      31 |
      32 |
      33 | 34 | 38 | 45 | 46 | -------------------------------------------------------------------------------- /var/Widget/Notice.php: -------------------------------------------------------------------------------- 1 | highlight = $theId; 34 | Cookie::set( 35 | '__typecho_notice_highlight', 36 | $theId, 37 | Options::alloc()->time + Options::alloc()->timezone + 86400 38 | ); 39 | } 40 | 41 | /** 42 | * 获取高亮的id 43 | * 44 | * @return integer 45 | */ 46 | public function getHighlightId(): int 47 | { 48 | return preg_match("/[0-9]+/", $this->highlight, $matches) ? $matches[0] : 0; 49 | } 50 | 51 | /** 52 | * 设定堆栈每一行的值 53 | * 54 | * @param string|array $value 值对应的键值 55 | * @param string|null $type 提示类型 56 | * @param string $typeFix 兼容老插件 57 | */ 58 | public function set($value, ?string $type = 'notice', string $typeFix = 'notice') 59 | { 60 | $notice = is_array($value) ? array_values($value) : [$value]; 61 | if (empty($type) && $typeFix) { 62 | $type = $typeFix; 63 | } 64 | 65 | Cookie::set( 66 | '__typecho_notice', 67 | json_encode($notice), 68 | Options::alloc()->time + Options::alloc()->timezone + 86400 69 | ); 70 | Cookie::set( 71 | '__typecho_notice_type', 72 | $type, 73 | Options::alloc()->time + Options::alloc()->timezone + 86400 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Select.php: -------------------------------------------------------------------------------- 1 | container($input->setAttribute('name', $name) 40 | ->setAttribute('id', $name . '-0-' . self::$uniqueId)); 41 | $this->label->setAttribute('for', $name . '-0-' . self::$uniqueId); 42 | $this->inputs[] = $input; 43 | 44 | foreach ($options as $value => $label) { 45 | $this->options[$value] = new Layout('option'); 46 | $input->addItem($this->options[$value]->setAttribute('value', $value)->html($label)); 47 | } 48 | 49 | return $input; 50 | } 51 | 52 | /** 53 | * 设置表单元素值 54 | * 55 | * @param mixed $value 表单元素值 56 | */ 57 | protected function inputValue($value) 58 | { 59 | foreach ($this->options as $option) { 60 | $option->removeAttribute('selected'); 61 | } 62 | 63 | if (isset($this->options[$value])) { 64 | $this->options[$value]->setAttribute('selected', 'true'); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /usr/themes/default/index.php: -------------------------------------------------------------------------------- 1 | need('header.php'); 13 | ?> 14 | 15 |
      16 | next()): ?> 17 | 39 | 40 | 41 | pageNav('« 前一页', '后一页 »'); ?> 42 |
      43 | 44 | need('sidebar.php'); ?> 45 | need('footer.php'); ?> 46 | -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter/Pdo/Mysql.php: -------------------------------------------------------------------------------- 1 | dsn) 56 | ? $config->dsn : "mysql:dbname={$config->database};host={$config->host};port={$config->port}", 57 | $config->user, 58 | $config->password 59 | ); 60 | $pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); 61 | 62 | if ($config->charset) { 63 | $pdo->exec("SET NAMES '{$config->charset}'"); 64 | } 65 | 66 | return $pdo; 67 | } 68 | 69 | /** 70 | * 引号转义函数 71 | * 72 | * @param mixed $string 需要转义的字符串 73 | * @return string 74 | */ 75 | public function quoteValue($string): string 76 | { 77 | return '\'' . str_replace(['\'', '\\'], ['\'\'', '\\\\'], $string) . '\''; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | '@' 6 | ])); 7 | 8 | exitPoint(\Typecho\Widget\Response::redirect()); 9 | exitPoint(\Typecho\Widget\Response::throwContent()); 10 | exitPoint(\Typecho\Widget\Response::throwFile()); 11 | exitPoint(\Typecho\Widget\Response::throwJson()); 12 | exitPoint(\Typecho\Widget\Response::throwXml()); 13 | exitPoint(\Typecho\Widget\Response::goBack()); 14 | 15 | override(\Widget\Options::__get(0), map([ 16 | 'feedUrl' => string, 17 | 'feedRssUrl' => string, 18 | 'feedAtomUrl' => string, 19 | 'commentsFeedUrl' => string, 20 | 'commentsFeedRssUrl' => string, 21 | 'commentsFeedAtomUrl' => string, 22 | 'xmlRpcUrl' => string, 23 | 'index' => string, 24 | 'siteUrl' => string, 25 | 'routingTable' => \ArrayObject::class, 26 | 'rootUrl' => string, 27 | 'themeUrl' => string, 28 | 'pluginUrl' => string, 29 | 'adminUrl' => string, 30 | 'loginUrl' => string, 31 | 'loginAction' => string, 32 | 'registerUrl' => string, 33 | 'registerAction' => string, 34 | 'profileUrl' => string, 35 | 'logoutUrl' => string, 36 | 'serverTimezone' => int, 37 | 'contentType' => string, 38 | 'software' => string, 39 | 'version' => string, 40 | 'markdown' => int, 41 | 'allowedAttachmentTypes'=> \ArrayObject::class 42 | ])); 43 | 44 | override(\Typecho\Widget::__get(0), map([ 45 | 'sequence' => int, 46 | 'length' => int 47 | ])); 48 | } -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter/Pdo/Pgsql.php: -------------------------------------------------------------------------------- 1 | prepareQuery($query, $handle, $action, $table); 54 | return parent::query($query, $handle, $op, $action, $table); 55 | } 56 | 57 | /** 58 | * 初始化数据库 59 | * 60 | * @param Config $config 数据库配置 61 | * @return \PDO 62 | */ 63 | public function init(Config $config): \PDO 64 | { 65 | $pdo = new \PDO( 66 | "pgsql:dbname={$config->database};host={$config->host};port={$config->port}", 67 | $config->user, 68 | $config->password 69 | ); 70 | 71 | if ($config->charset) { 72 | $pdo->exec("SET NAMES '{$config->charset}'"); 73 | } 74 | 75 | return $pdo; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /admin/common.php: -------------------------------------------------------------------------------- 1 | begin(); 19 | 20 | \Widget\Options::alloc()->to($options); 21 | \Widget\User::alloc()->to($user); 22 | \Widget\Security::alloc()->to($security); 23 | \Widget\Menu::alloc()->to($menu); 24 | 25 | /** 初始化上下文 */ 26 | $request = $options->request; 27 | $response = $options->response; 28 | 29 | /** 检测是否是第一次登录 */ 30 | $currentMenu = $menu->getCurrentMenu(); 31 | 32 | if (!empty($currentMenu)) { 33 | $params = parse_url($currentMenu[2]); 34 | $adminFile = basename($params['path']); 35 | 36 | if (!$user->logged && !\Typecho\Cookie::get('__typecho_first_run')) { 37 | if ('welcome.php' != $adminFile) { 38 | $response->redirect(\Typecho\Common::url('welcome.php', $options->adminUrl)); 39 | } else { 40 | \Typecho\Cookie::set('__typecho_first_run', 1); 41 | } 42 | } elseif ($user->pass('administrator', true)) { 43 | /** 检测版本是否升级 */ 44 | $mustUpgrade = version_compare(\Typecho\Common::VERSION, $options->version, '>'); 45 | 46 | if ($mustUpgrade && 'upgrade.php' != $adminFile && 'backup.php' != $adminFile) { 47 | $response->redirect(\Typecho\Common::url('upgrade.php', $options->adminUrl)); 48 | } elseif (!$mustUpgrade && 'upgrade.php' == $adminFile) { 49 | $response->redirect($options->adminUrl); 50 | } elseif (!$mustUpgrade && 'welcome.php' == $adminFile && $user->logged) { 51 | $response->redirect($options->adminUrl); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /admin/register.php: -------------------------------------------------------------------------------- 1 | hasLogin() || !$options->allowRegister) { 5 | $response->redirect($options->siteUrl); 6 | } 7 | $rememberName = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_name')); 8 | $rememberMail = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_mail')); 9 | \Typecho\Cookie::delete('__typecho_remember_name'); 10 | \Typecho\Cookie::delete('__typecho_remember_mail'); 11 | 12 | $bodyClass = 'body-100'; 13 | 14 | include 'header.php'; 15 | ?> 16 | 40 | 43 | 48 | 51 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/PageNavigator/Classic.php: -------------------------------------------------------------------------------- 1 | prev($prevWord); 33 | $this->next($nextWord); 34 | } 35 | 36 | /** 37 | * 输出上一页 38 | * 39 | * @access public 40 | * @param string $prevWord 上一页文字 41 | * @return void 42 | */ 43 | public function prev(string $prevWord = 'PREV') 44 | { 45 | //输出上一页 46 | if ($this->total > 0 && $this->currentPage > 1) { 47 | echo ''; 51 | } 52 | } 53 | 54 | /** 55 | * 输出下一页 56 | * 57 | * @access public 58 | * @param string $nextWord 下一页文字 59 | * @return void 60 | */ 61 | public function next(string $nextWord = 'NEXT') 62 | { 63 | //输出下一页 64 | if ($this->total > 0 && $this->currentPage < $this->totalPage) { 65 | echo ''; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter/Pdo/SQLite.php: -------------------------------------------------------------------------------- 1 | file}"); 43 | $this->isSQLite2 = version_compare($pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '3.0.0', '<'); 44 | return $pdo; 45 | } 46 | 47 | /** 48 | * 将数据查询的其中一行作为对象取出,其中字段名对应对象属性 49 | * 50 | * @param \PDOStatement $resource 查询的资源数据 51 | * @return object|null 52 | */ 53 | public function fetchObject($resource): ?object 54 | { 55 | $result = $this->fetch($resource); 56 | return $result ? (object) $result : null; 57 | } 58 | 59 | /** 60 | * 将数据查询的其中一行作为数组取出,其中字段名对应数组键值 61 | * 62 | * @param \PDOStatement $resource 查询返回资源标识 63 | * @return array|null 64 | */ 65 | public function fetch($resource): ?array 66 | { 67 | $result = parent::fetch($resource); 68 | return $result ? $this->filterColumnName($result) : null; 69 | } 70 | 71 | /** 72 | * 将数据查询的结果作为数组全部取出,其中字段名对应数组键值 73 | * 74 | * @param \PDOStatement $resource 查询的资源数据 75 | * @return array 76 | */ 77 | public function fetchAll($resource): array 78 | { 79 | return array_map([$this, 'filterColumnName'], parent::fetchAll($resource)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /admin/custom-fields-js.php: -------------------------------------------------------------------------------- 1 | 2 | 51 | -------------------------------------------------------------------------------- /var/IXR/Date.php: -------------------------------------------------------------------------------- 1 | parseTimestamp(intval($time)); 32 | } else { 33 | $this->parseIso($time); 34 | } 35 | } 36 | 37 | /** 38 | * @param int $timestamp 39 | */ 40 | private function parseTimestamp(int $timestamp) 41 | { 42 | $this->year = date('Y', $timestamp); 43 | $this->month = date('m', $timestamp); 44 | $this->day = date('d', $timestamp); 45 | $this->hour = date('H', $timestamp); 46 | $this->minute = date('i', $timestamp); 47 | $this->second = date('s', $timestamp); 48 | } 49 | 50 | /** 51 | * @param string $iso 52 | */ 53 | private function parseIso(string $iso) 54 | { 55 | $this->year = substr($iso, 0, 4); 56 | $this->month = substr($iso, 4, 2); 57 | $this->day = substr($iso, 6, 2); 58 | $this->hour = substr($iso, 9, 2); 59 | $this->minute = substr($iso, 12, 2); 60 | $this->second = substr($iso, 15, 2); 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getIso(): string 67 | { 68 | return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getXml(): string 75 | { 76 | return '' . $this->getIso() . ''; 77 | } 78 | 79 | /** 80 | * @return false|int 81 | */ 82 | public function getTimestamp() 83 | { 84 | return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /var/Widget/Themes/Config.php: -------------------------------------------------------------------------------- 1 | user->pass('administrator'); 34 | 35 | if (!self::isExists()) { 36 | throw new Exception(_t('外观配置功能不存在'), 404); 37 | } 38 | } 39 | 40 | /** 41 | * 配置功能是否存在 42 | * 43 | * @return boolean 44 | */ 45 | public static function isExists(): bool 46 | { 47 | $options = Options::alloc(); 48 | $configFile = $options->themeFile($options->theme, 'functions.php'); 49 | 50 | if (file_exists($configFile)) { 51 | require_once $configFile; 52 | 53 | if (function_exists('themeConfig')) { 54 | return true; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | 61 | /** 62 | * 配置外观 63 | * 64 | * @return Form 65 | */ 66 | public function config(): Form 67 | { 68 | $form = new Form($this->security->getIndex('/action/themes-edit?config'), Form::POST_METHOD); 69 | themeConfig($form); 70 | $inputs = $form->getInputs(); 71 | 72 | if (!empty($inputs)) { 73 | foreach ($inputs as $key => $val) { 74 | $form->getInput($key)->value($this->options->{$key}); 75 | } 76 | } 77 | 78 | $submit = new Submit(null, null, _t('保存设置')); 79 | $submit->input->setAttribute('class', 'btn primary'); 80 | $form->addItem($submit); 81 | return $form; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /admin/login.php: -------------------------------------------------------------------------------- 1 | hasLogin()) { 5 | $response->redirect($options->adminUrl); 6 | } 7 | $rememberName = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_name')); 8 | \Typecho\Cookie::delete('__typecho_remember_name'); 9 | 10 | $bodyClass = 'body-100'; 11 | 12 | include 'header.php'; 13 | ?> 14 | 44 | 47 | 52 | 55 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Radio.php: -------------------------------------------------------------------------------- 1 | $label) { 39 | $this->options[$value] = new Layout('input'); 40 | $item = $this->multiline(); 41 | $id = $this->name . '-' . $this->filterValue($value); 42 | $this->inputs[] = $this->options[$value]; 43 | 44 | $item->addItem($this->options[$value]->setAttribute('name', $this->name) 45 | ->setAttribute('type', 'radio') 46 | ->setAttribute('value', $value) 47 | ->setAttribute('id', $id)); 48 | 49 | $labelItem = new Layout('label'); 50 | $item->addItem($labelItem->setAttribute('for', $id)->html($label)); 51 | $this->container($item); 52 | } 53 | 54 | return current($this->options) ?: null; 55 | } 56 | 57 | /** 58 | * 设置表单元素值 59 | * 60 | * @param mixed $value 表单元素值 61 | */ 62 | protected function inputValue($value) 63 | { 64 | foreach ($this->options as $option) { 65 | $option->removeAttribute('checked'); 66 | } 67 | 68 | if (isset($this->options[$value])) { 69 | $this->value = $value; 70 | $this->options[$value]->setAttribute('checked', 'true'); 71 | $this->input = $this->options[$value]; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /admin/src/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Buttons 4 | */ 5 | 6 | @mixin button($color) { 7 | border: none; 8 | background-color: $color; 9 | cursor: pointer; 10 | 11 | border-radius: 2px; 12 | // @include transition-property(background-color); 13 | 14 | &:hover { 15 | transition-duration: .4s; 16 | background-color: darken($color, 6%); 17 | } 18 | &:active, &.active { 19 | background-color: darken($color, 8%); 20 | } 21 | &:disabled { 22 | background-color: lighten($color, 6%); 23 | cursor: default; 24 | } 25 | } 26 | 27 | .btn { 28 | @include button(#E9E9E6); 29 | 30 | display: inline-block; 31 | padding: 0 12px; 32 | height: 32px; 33 | color: #666; 34 | vertical-align: middle; 35 | zoom: 1; 36 | 37 | &:disabled { 38 | color: #999; 39 | } 40 | } 41 | 42 | .btn-xs { 43 | padding: 0 10px; 44 | height: 25px; 45 | font-size: 13px; 46 | } 47 | .btn-s { height: 28px; } 48 | .btn-l { 49 | height: 40px; 50 | font-size: 1.14286em; 51 | font-weight: bold; 52 | } 53 | 54 | .primary { 55 | @include button(#467B96); 56 | color: #FFF; 57 | } 58 | 59 | .btn-group { 60 | display: inline-block; 61 | } 62 | 63 | .btn-warn { 64 | @include button(#B94A48); 65 | color: #FFF; 66 | } 67 | 68 | .btn-link, 69 | .btn-link:hover, 70 | .btn-link:focus, 71 | .btn-link:active, 72 | .btn-link.active { 73 | background-color: transparent; 74 | } 75 | 76 | /* 下拉菜单 */ 77 | .btn-drop { 78 | position: relative; 79 | } 80 | .dropdown-toggle { 81 | padding-right: 8px; 82 | } 83 | .dropdown-menu { 84 | list-style: none; 85 | position: absolute; 86 | z-index: 2; 87 | left: 0; 88 | margin: 0; 89 | padding: 0; 90 | border: 1px solid #D9D9D6; 91 | background: #FFF; 92 | text-align: left; 93 | min-width: 108px; 94 | display: none; 95 | 96 | li { 97 | white-space: nowrap; 98 | &.multiline { 99 | padding: 5px 12px 12px; 100 | } 101 | } 102 | 103 | a { 104 | display: block; 105 | padding: 5px 12px; 106 | color: #666; 107 | &:hover { 108 | background: #F6F6F3; 109 | text-decoration: none !important; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/Form/Element/Checkbox.php: -------------------------------------------------------------------------------- 1 | $label) { 39 | $this->options[$value] = new Layout('input'); 40 | $item = $this->multiline(); 41 | $id = $this->name . '-' . $this->filterValue($value); 42 | $this->inputs[] = $this->options[$value]; 43 | 44 | $item->addItem($this->options[$value]->setAttribute('name', $this->name . '[]') 45 | ->setAttribute('type', 'checkbox') 46 | ->setAttribute('value', $value) 47 | ->setAttribute('id', $id)); 48 | 49 | $labelItem = new Layout('label'); 50 | $item->addItem($labelItem->setAttribute('for', $id)->html($label)); 51 | $this->container($item); 52 | } 53 | 54 | return current($this->options) ?: null; 55 | } 56 | 57 | /** 58 | * 设置表单元素值 59 | * 60 | * @param mixed $value 表单元素值 61 | */ 62 | protected function inputValue($value) 63 | { 64 | $values = is_array($value) ? $value : [$value]; 65 | 66 | foreach ($this->options as $option) { 67 | $option->removeAttribute('checked'); 68 | } 69 | 70 | foreach ($values as $value) { 71 | if (isset($this->options[$value])) { 72 | $this->options[$value]->setAttribute('checked', 'true'); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /admin/src/scss/components/_tokeninput.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Jquery Tokeninput 3 | */ 4 | 5 | ul.token-input-list { 6 | list-style: none; 7 | margin: 0; 8 | padding: 0 4px; 9 | min-height: 32px; 10 | border: 1px solid #D9D9D6; 11 | cursor: text; 12 | z-index: 999; 13 | background-color: #FFF; 14 | clear: left; 15 | 16 | border-radius: 2px; 17 | 18 | -webkit-box-sizing: border-box; 19 | -moz-box-sizing: border-box; 20 | box-sizing: border-box; 21 | 22 | li { 23 | margin: 4px 0; 24 | } 25 | } 26 | 27 | 28 | ul.token-input-list li input { 29 | padding: 0; 30 | border: 0; 31 | width: 100%; 32 | -webkit-appearance: caret; 33 | } 34 | 35 | li.token-input-token { 36 | padding: 0 6px; 37 | height: 27px; 38 | line-height: 27px; 39 | background-color: #F3F3F0; 40 | cursor: default; 41 | font-size: .92857em; 42 | text-align: right; 43 | white-space: nowrap; 44 | p { 45 | float: left; 46 | display: inline; 47 | margin: 0; 48 | } 49 | span { 50 | color: #BBB; 51 | font-weight: bold; 52 | cursor: pointer; 53 | } 54 | } 55 | 56 | 57 | 58 | li.token-input-selected-token { 59 | background-color: #E9E9E6; 60 | } 61 | 62 | li.token-input-input-token { 63 | padding: 0 4px; 64 | } 65 | 66 | div.token-input-dropdown { 67 | position: absolute; 68 | background-color: #FFF; 69 | overflow: hidden; 70 | border: 1px solid #D9D9D6; 71 | border-top-width: 0; 72 | cursor: default; 73 | z-index: 1; 74 | font-size: .92857em; 75 | } 76 | 77 | div.token-input-dropdown p { 78 | margin: 0; 79 | padding: 5px 10px; 80 | color: #777; 81 | font-weight: bold; 82 | } 83 | 84 | div.token-input-dropdown ul { 85 | list-style: none; 86 | margin: 0; 87 | padding: 0; 88 | } 89 | 90 | div.token-input-dropdown ul li { 91 | padding: 4px 10px; 92 | background-color: #FFF; 93 | } 94 | 95 | div.token-input-dropdown ul li.token-input-dropdown-item { 96 | background-color: #FFF; 97 | } 98 | 99 | div.token-input-dropdown ul li em { 100 | font-style: normal; 101 | } 102 | 103 | div.token-input-dropdown ul li.token-input-selected-dropdown-item { 104 | background-color: #467B96; 105 | color: #FFF; 106 | } -------------------------------------------------------------------------------- /.github/workflows/Typecho-dev-Ci.yml: -------------------------------------------------------------------------------- 1 | name: Typecho Dev Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | php: 13 | name: PHP ${{ matrix.php }} Tests 14 | runs-on: ubuntu-latest 15 | if: "!contains(github.event.head_commit.message, 'skip ci')" 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | php: ['7.2', '7.3', '7.4', '8.0'] 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | - name: Setup PHP only 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php }} 27 | - name: Test 28 | run: | 29 | find . -type f -name '*.php' -print0 | xargs -0 -n1 -P4 php -l -n | (! grep -v "No syntax errors detected" ) 30 | build: 31 | name: Typecho Build 32 | runs-on: ubuntu-latest 33 | if: "!contains(github.event.head_commit.message, 'skip ci') && github.event_name != 'pull_request'" 34 | needs: 35 | - php 36 | steps: 37 | - name: Checkout code 38 | uses: actions/checkout@v2 39 | - name: Build 40 | run: | 41 | mkdir build 42 | cp -r LICENSE.txt index.php install.php admin install usr var build/ 43 | mkdir build/usr/uploads/ 44 | chmod 755 build/usr/uploads/ 45 | rm -rf build/admin/src 46 | cd build && zip -q -r typecho.zip * && mv typecho.zip ../ && cd - 47 | - name: Upload a Build Artifact 48 | uses: WebFreak001/deploy-nightly@v1.1.0 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | upload_url: https://uploads.github.com/repos/typecho/typecho/releases/49532662/assets{?name,label} 53 | release_id: 49532662 54 | asset_path: ./typecho.zip 55 | asset_name: typecho.zip 56 | asset_content_type: application/zip 57 | max_releases: 1 58 | - name: Trigger build 59 | run: | 60 | curl -XPOST -H "Authorization: token ${{ secrets.WORKFLOW_TOKEN }}" \ 61 | -H "Accept: application/vnd.github.everest-preview+json" \ 62 | -H "Content-Type: application/json" \ 63 | https://api.github.com/repos/typecho/languages/actions/workflows/update.yml/dispatches --data '{"ref": "master"}' 64 | 65 | -------------------------------------------------------------------------------- /usr/themes/default/archive.php: -------------------------------------------------------------------------------- 1 | 2 | need('header.php'); ?> 3 | 4 |
      5 |

      archiveTitle([ 6 | 'category' => _t('分类 %s 下的文章'), 7 | 'search' => _t('包含关键字 %s 的文章'), 8 | 'tag' => _t('标签 %s 下的文章'), 9 | 'author' => _t('%s 发布的文章') 10 | ], '', ''); ?>

      11 | have()): ?> 12 | next()): ?> 13 | 34 | 35 | 36 |
      37 |

      38 |
      39 | 40 | 41 | pageNav('« 前一页', '后一页 »'); ?> 42 |
      43 | 44 | need('sidebar.php'); ?> 45 | need('footer.php'); ?> 46 | -------------------------------------------------------------------------------- /var/Widget/Contents/Post/Date.php: -------------------------------------------------------------------------------- 1 | setDefault('format=Y-m&type=month&limit=0'); 30 | } 31 | 32 | /** 33 | * 初始化函数 34 | * 35 | * @return void 36 | */ 37 | public function execute() 38 | { 39 | /** 设置参数默认值 */ 40 | $this->parameter->setDefault('format=Y-m&type=month&limit=0'); 41 | 42 | $resource = $this->db->query($this->db->select('created')->from('table.contents') 43 | ->where('type = ?', 'post') 44 | ->where('table.contents.status = ?', 'publish') 45 | ->where('table.contents.created < ?', $this->options->time) 46 | ->order('table.contents.created', Db::SORT_DESC)); 47 | 48 | $offset = $this->options->timezone - $this->options->serverTimezone; 49 | $result = []; 50 | while ($post = $this->db->fetchRow($resource)) { 51 | $timeStamp = $post['created'] + $offset; 52 | $date = date($this->parameter->format, $timeStamp); 53 | 54 | if (isset($result[$date])) { 55 | $result[$date]['count'] ++; 56 | } else { 57 | $result[$date]['year'] = date('Y', $timeStamp); 58 | $result[$date]['month'] = date('m', $timeStamp); 59 | $result[$date]['day'] = date('d', $timeStamp); 60 | $result[$date]['date'] = $date; 61 | $result[$date]['count'] = 1; 62 | } 63 | } 64 | 65 | if ($this->parameter->limit > 0) { 66 | $result = array_slice($result, 0, $this->parameter->limit); 67 | } 68 | 69 | foreach ($result as $row) { 70 | $row['permalink'] = Router::url( 71 | 'archive_' . $this->parameter->type, 72 | $row, 73 | $this->options->index 74 | ); 75 | $this->push($row); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /var/Widget/Base/Options.php: -------------------------------------------------------------------------------- 1 | db->select()->from('table.options'); 33 | } 34 | 35 | /** 36 | * 插入一条记录 37 | * 38 | * @param array $rows 记录插入值 39 | * @return integer 40 | * @throws Exception 41 | */ 42 | public function insert(array $rows): int 43 | { 44 | return $this->db->query($this->db->insert('table.options')->rows($rows)); 45 | } 46 | 47 | /** 48 | * 更新记录 49 | * 50 | * @param array $rows 记录更新值 51 | * @param Query $condition 更新条件 52 | * @return integer 53 | * @throws Exception 54 | */ 55 | public function update(array $rows, Query $condition): int 56 | { 57 | return $this->db->query($condition->update('table.options')->rows($rows)); 58 | } 59 | 60 | /** 61 | * 删除记录 62 | * 63 | * @param Query $condition 删除条件 64 | * @return integer 65 | * @throws Exception 66 | */ 67 | public function delete(Query $condition): int 68 | { 69 | return $this->db->query($condition->delete('table.options')); 70 | } 71 | 72 | /** 73 | * 获取记录总数 74 | * 75 | * @param Query $condition 计算条件 76 | * @return integer 77 | * @throws Exception 78 | */ 79 | public function size(Query $condition): int 80 | { 81 | return $this->db->fetchObject($condition->select(['COUNT(name)' => 'num'])->from('table.options'))->num; 82 | } 83 | 84 | /** 85 | * 以checkbox选项判断是否某个值被启用 86 | * 87 | * @param mixed $settings 选项集合 88 | * @param string $name 选项名称 89 | * @return integer 90 | */ 91 | protected function isEnableByCheckbox($settings, string $name): int 92 | { 93 | return is_array($settings) && in_array($name, $settings) ? 1 : 0; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define root directory 4 | DIR=../ 5 | 6 | #update subversion 7 | update: 8 | @echo 'git update' 9 | rm -Rf build/ 10 | git clone --depth=1 https://github.com/typecho/typecho.git build 11 | rm -Rf build/.git 12 | rm -Rf build/.github 13 | rm -f build/.gitignore 14 | rm -f build/.gitattributes 15 | for i in `find build/ -name '*.css'`; do echo $$i && java -Xmx32m -jar yuicompressor-2.4.2.jar $$i --charset UTF-8 -o $$i; done; 16 | for i in `find build/admin/js/ -name '*.js'`; do echo $$i && java -Xmx32m -jar yuicompressor-2.4.2.jar $$i --charset UTF-8 -o $$i; done; 17 | for i in `find build/ -name '*.php'`; do php -l $$i; done; 18 | 19 | 20 | rm: 21 | rm -Rf build/tools/ 22 | rm -Rf build/.git* 23 | rm -f build/changelog.txt 24 | rm -f build/.editorconfig 25 | rm -f build/.travis.yml 26 | rm -f build/README.md 27 | rm -Rf build/admin/scss 28 | rm -Rf build/admin/img/editor 29 | rm -Rf build/admin/img/icons 30 | rm -Rf build/admin/img/icons-2x 31 | 32 | 33 | 34 | package: 35 | @echo 'package' 36 | make rm 37 | mkdir build/usr/uploads/ 38 | chmod 777 build/usr/uploads/ 39 | tar -cvvzf build.tar.gz build/ 40 | 41 | 42 | clear: 43 | rm -Rf build/ 44 | 45 | 46 | upgrade: 47 | make update 48 | rm -Rf ${DIR}/admin/ 49 | cp -Rf build/admin/ ${DIR} 50 | rm -Rf ${DIR}/var/ 51 | cp -Rf build/var/ ${DIR} 52 | rm -Rf ${DIR}/index.php 53 | cp build/index.php ${DIR} 54 | cp build/install.php ${DIR} 55 | make clear 56 | 57 | 58 | langs: 59 | rm -rf ../usr/langs 60 | mkdir ../usr/langs 61 | git clone https://github.com/typecho/languages.git 62 | cd languages/ && for i in `find . -name '*.po'`; do echo `basename $$i` && msgfmt -o `basename $$i .po`.mo `basename $$i`; done; 63 | cp languages/*.mo ../usr/langs/ 64 | rm -rf languages 65 | 66 | 67 | theme: 68 | make update 69 | rm -Rf ${DIR}/usr/themes/default/ 70 | cp -Rf build/usr/themes/default/ ${DIR}/usr/themes/ 71 | make clear 72 | 73 | 74 | install: 75 | make update 76 | make rm 77 | mkdir build/usr/uploads/ 78 | chmod 777 build/usr/uploads/ 79 | cp -Rf build/* ${DIR} 80 | make clear 81 | 82 | 83 | pot: 84 | php build_pot.php > messages.pot 85 | 86 | 87 | test: 88 | for i in `find ../var/ -name '*.php'`; do php -l $$i || exit 1; done; 89 | for i in `find ../usr/ -name '*.php'`; do php -l $$i || exit 1; done; 90 | for i in `find ../admin/ -name '*.php'`; do php -l $$i || exit 1; done; 91 | 92 | 93 | all: 94 | make update 95 | make package 96 | make clear 97 | -------------------------------------------------------------------------------- /admin/src/scss/components/_timepicker.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Jquery Timepicker 3 | */ 4 | 5 | #ui-datepicker-div { 6 | display: none; 7 | margin-top: -1px; 8 | padding: 10px; 9 | border: 1px solid #D9D9D6; 10 | background: #FFF; 11 | } 12 | .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } 13 | .ui-timepicker-div dl { text-align: left; } 14 | .ui-timepicker-div dl dt { float: left; clear:left; } 15 | .ui-timepicker-div dl dd { margin: 0 0 10px 40%; } 16 | .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } 17 | 18 | #ui-datepicker-div .ui-datepicker-header { 19 | margin-bottom: 10px; 20 | padding-bottom: 10px; 21 | border-bottom: 1px solid #EEE; 22 | } 23 | #ui-datepicker-div .ui-datepicker-prev { float: left; cursor: pointer; } 24 | #ui-datepicker-div .ui-datepicker-next { float: right; cursor: pointer; } 25 | #ui-datepicker-div .ui-datepicker-title { 26 | font-weight: bold; 27 | text-align: center; 28 | } 29 | #ui-datepicker-div .ui-datepicker-calendar th { line-height: 24px; } 30 | #ui-datepicker-div .ui-datepicker-calendar a { 31 | display: block; 32 | width: 30px; 33 | background-color: #F3F3F0; 34 | line-height: 24px; 35 | text-align: center; 36 | } 37 | #ui-datepicker-div .ui-datepicker-calendar a:hover { 38 | background-color: #E9E9E6; 39 | text-decoration: none; 40 | } 41 | #ui-datepicker-div .ui-datepicker-today a { 42 | background-color: #E9E9E6; 43 | color: #444; 44 | } 45 | #ui-datepicker-div .ui-datepicker-current-day a { 46 | background-color: #467B96 !important; 47 | color: #FFF; 48 | } 49 | #ui-datepicker-div .ui-timepicker-div { 50 | margin-top: 20px; 51 | border-top: 1px solid #EEE; 52 | } 53 | #ui-datepicker-div .ui-slider { 54 | position: relative; 55 | margin-top: 18px; 56 | border: 1px solid #E9E9E6; 57 | background-color: #F6F6F3; 58 | height: 4px; 59 | } 60 | #ui-datepicker-div .ui-slider .ui-slider-handle { 61 | position: absolute; 62 | top: -7px; 63 | margin-left: -5px; 64 | z-index: 2; 65 | width: 10px; 66 | height: 16px; 67 | background-color: #467B96; 68 | } 69 | 70 | #ui-datepicker-div .ui-datepicker-buttonpane { 71 | padding-top: 10px; 72 | border-top: 1px solid #EEE; 73 | } 74 | #ui-datepicker-div .ui-datepicker-current, 75 | #ui-datepicker-div .ui-datepicker-close { 76 | float: left; 77 | @extend .btn; 78 | @extend .btn-xs; 79 | } 80 | #ui-datepicker-div .ui-datepicker-close { 81 | float: right; 82 | } 83 | 84 | .ui-effects-transfer { border: 2px dotted #ccc; } -------------------------------------------------------------------------------- /var/Typecho/I18n/GetTextMulti.php: -------------------------------------------------------------------------------- 1 | addFile($fileName); 33 | } 34 | 35 | /** 36 | * 增加一个语言文件 37 | * 38 | * @access public 39 | * @param string $fileName 语言文件名 40 | * @return void 41 | */ 42 | public function addFile(string $fileName) 43 | { 44 | $this->handlers[] = new GetText($fileName, true); 45 | } 46 | 47 | /** 48 | * Translates a string 49 | * 50 | * @access public 51 | * @param string string to be translated 52 | * @return string translated string (or original, if not found) 53 | */ 54 | public function translate(string $string): string 55 | { 56 | foreach ($this->handlers as $handle) { 57 | $string = $handle->translate($string, $count); 58 | if (- 1 != $count) { 59 | break; 60 | } 61 | } 62 | 63 | return $string; 64 | } 65 | 66 | /** 67 | * Plural version of gettext 68 | * 69 | * @access public 70 | * @param string single 71 | * @param string plural 72 | * @param string number 73 | * @return string translated plural form 74 | */ 75 | public function ngettext($single, $plural, $number): string 76 | { 77 | $count = - 1; 78 | 79 | foreach ($this->handlers as $handler) { 80 | $string = $handler->ngettext($single, $plural, $number, $count); 81 | if (- 1 != $count) { 82 | break; 83 | } 84 | } 85 | 86 | return $string; 87 | } 88 | 89 | /** 90 | * 关闭所有句柄 91 | * 92 | * @access public 93 | * @return void 94 | */ 95 | public function __destruct() 96 | { 97 | foreach ($this->handlers as $handler) { 98 | /** 显示的释放内存 */ 99 | unset($handler); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /var/Widget/Contents/Related.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault('limit=5'); 33 | 34 | if ($this->parameter->tags) { 35 | $tagsGroup = implode(',', array_column($this->parameter->tags, 'mid')); 36 | $this->db->fetchAll($this->select() 37 | ->join('table.relationships', 'table.contents.cid = table.relationships.cid') 38 | ->where('table.relationships.mid IN (' . $tagsGroup . ')') 39 | ->where('table.contents.cid <> ?', $this->parameter->cid) 40 | ->where('table.contents.status = ?', 'publish') 41 | ->where('table.contents.password IS NULL') 42 | ->where('table.contents.created < ?', $this->options->time) 43 | ->where('table.contents.type = ?', $this->parameter->type) 44 | ->order('table.contents.created', Db::SORT_DESC) 45 | ->limit($this->parameter->limit), [$this, 'push']); 46 | } 47 | } 48 | 49 | /** 50 | * 获取查询对象 51 | * 52 | * @return Query 53 | * @throws Exception 54 | */ 55 | public function select(): Query 56 | { 57 | return $this->db->select( 58 | 'DISTINCT table.contents.cid', 59 | 'table.contents.title', 60 | 'table.contents.slug', 61 | 'table.contents.created', 62 | 'table.contents.authorId', 63 | 'table.contents.modified', 64 | 'table.contents.type', 65 | 'table.contents.status', 66 | 'table.contents.text', 67 | 'table.contents.commentsNum', 68 | 'table.contents.order', 69 | 'table.contents.template', 70 | 'table.contents.password', 71 | 'table.contents.allowComment', 72 | 'table.contents.allowPing', 73 | 'table.contents.allowFeed' 74 | ) 75 | ->from('table.contents'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /admin/profile.php: -------------------------------------------------------------------------------- 1 | 8 | 9 |
      10 |
      11 | 12 |
      13 |
      14 |

      mail, 220, 'X', 'mm', $request->isSecure()) . '" alt="' . $user->screenName . '" />'; ?> 16 |

      17 |

      screenName(); ?>

      18 |

      name(); ?>

      19 |

      %s 篇日志, 并有 %s 条关于你的评论在 %s 个分类中.', 20 | $stat->myPublishedPostsNum, $stat->myPublishedCommentsNum, $stat->categoriesNum); ?>

      21 |

      logged > 0) { 23 | $logged = new \Typecho\Date($user->logged); 24 | _e('最后登录: %s', $logged->word()); 25 | } 26 | ?>

      27 |
      28 | 29 |
      30 |
      31 |

      32 | profileForm()->render(); ?> 33 |
      34 | 35 | pass('contributor', true)): ?> 36 |
      37 |
      38 |

      39 | optionsForm()->render(); ?> 40 |
      41 | 42 | 43 |
      44 | 45 |
      46 |

      47 | passwordForm()->render(); ?> 48 |
      49 | 50 | personalFormList(); ?> 51 |
      52 |
      53 |
      54 |
      55 | 56 | bottom(); 61 | include 'footer.php'; 62 | ?> 63 | -------------------------------------------------------------------------------- /install/Mysql.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
        4 |
      • 5 | 6 | 7 |

        8 |
      • 9 |
      10 | 11 |
        12 |
      • 13 | 14 | 15 |

        16 |
      • 17 |
      18 | 19 |
        20 |
      • 21 | 22 | 23 |
      • 24 |
      25 |
        26 |
      • 27 | 28 | 29 |

        30 |
      • 31 | 32 |
      33 | 34 |
      35 | 36 | 37 | 38 |
        39 |
      • 40 | 41 | 42 |

        43 |
      • 44 |
      45 | 46 |
        47 |
      • 48 | 49 | 53 |

        54 |
      • 55 |
      56 | 57 |
        58 |
      • 59 | 60 | 64 |
      • 65 |
      66 |
      -------------------------------------------------------------------------------- /var/Widget/Themes/Rows.php: -------------------------------------------------------------------------------- 1 | getThemes(); 31 | 32 | if ($themes) { 33 | $options = Options::alloc(); 34 | $activated = 0; 35 | $result = []; 36 | 37 | foreach ($themes as $key => $theme) { 38 | $themeFile = $theme . '/index.php'; 39 | if (file_exists($themeFile)) { 40 | $info = Plugin::parseInfo($themeFile); 41 | $info['name'] = $this->getTheme($theme, $key); 42 | 43 | if ($info['activated'] = ($options->theme == $info['name'])) { 44 | $activated = $key; 45 | } 46 | 47 | $screen = array_filter(glob($theme . '/*'), function ($path) { 48 | return preg_match("/screenshot\.(jpg|png|gif|bmp|jpeg)$/i", $path); 49 | }); 50 | 51 | if ($screen) { 52 | $info['screen'] = $options->themeUrl(basename(current($screen)), $info['name']); 53 | } else { 54 | $info['screen'] = Common::url('noscreen.png', $options->adminStaticUrl('img')); 55 | } 56 | 57 | $result[$key] = $info; 58 | } 59 | } 60 | 61 | $clone = $result[$activated]; 62 | unset($result[$activated]); 63 | array_unshift($result, $clone); 64 | array_filter($result, [$this, 'push']); 65 | } 66 | } 67 | 68 | /** 69 | * @return array 70 | */ 71 | protected function getThemes(): array 72 | { 73 | return glob(__TYPECHO_ROOT_DIR__ . __TYPECHO_THEME_DIR__ . '/*', GLOB_ONLYDIR); 74 | } 75 | 76 | /** 77 | * get theme 78 | * 79 | * @param string $theme 80 | * @param mixed $index 81 | * @return string 82 | */ 83 | protected function getTheme(string $theme, $index): string 84 | { 85 | return basename($theme); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter/SQLiteTrait.php: -------------------------------------------------------------------------------- 1 | query('DELETE FROM ' . $this->quoteColumn($table), $handle); 24 | } 25 | 26 | /** 27 | * 对象引号过滤 28 | * 29 | * @param string $string 30 | * @return string 31 | */ 32 | public function quoteColumn(string $string): string 33 | { 34 | return '"' . $string . '"'; 35 | } 36 | 37 | /** 38 | * 过滤字段名 39 | * 40 | * @access private 41 | * 42 | * @param array $result 43 | * 44 | * @return array 45 | */ 46 | private function filterColumnName(array $result): array 47 | { 48 | /** 如果结果为空,直接返回 */ 49 | if (empty($result)) { 50 | return $result; 51 | } 52 | 53 | $tResult = []; 54 | 55 | /** 遍历数组 */ 56 | foreach ($result as $key => $val) { 57 | /** 按点分隔 */ 58 | if (false !== ($pos = strpos($key, '.'))) { 59 | $key = substr($key, $pos + 1); 60 | } 61 | 62 | $tResult[trim($key, '"')] = $val; 63 | } 64 | 65 | return $tResult; 66 | } 67 | 68 | /** 69 | * 处理sqlite2的distinct count 70 | * 71 | * @param string $sql 72 | * 73 | * @return string 74 | */ 75 | private function filterCountQuery(string $sql): string 76 | { 77 | if (preg_match("/SELECT\s+COUNT\(DISTINCT\s+([^\)]+)\)\s+(AS\s+[^\s]+)?\s*FROM\s+(.+)/is", $sql, $matches)) { 78 | return 'SELECT COUNT(' . $matches[1] . ') ' . $matches[2] . ' FROM SELECT DISTINCT ' 79 | . $matches[1] . ' FROM ' . $matches[3]; 80 | } 81 | 82 | return $sql; 83 | } 84 | 85 | /** 86 | * 合成查询语句 87 | * 88 | * @access public 89 | * @param array $sql 查询对象词法数组 90 | * @return string 91 | */ 92 | public function parseSelect(array $sql): string 93 | { 94 | $query = $this->buildQuery($sql); 95 | 96 | if ($this->isSQLite2) { 97 | $query = $this->filterCountQuery($query); 98 | } 99 | 100 | return $query; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /var/Typecho/Widget/Helper/PageNavigator.php: -------------------------------------------------------------------------------- 1 | total = $total; 75 | $this->totalPage = ceil($total / $pageSize); 76 | $this->currentPage = $currentPage; 77 | $this->pageSize = $pageSize; 78 | $this->pageTemplate = $pageTemplate; 79 | 80 | if (($currentPage > $this->totalPage || $currentPage < 1) && $total > 0) { 81 | throw new Exception('Page Not Exists', 404); 82 | } 83 | } 84 | 85 | /** 86 | * 设置页面占位符 87 | * 88 | * @param string $holder 页面占位符 89 | */ 90 | public function setPageHolder(string $holder) 91 | { 92 | $this->pageHolder = ['{' . $holder . '}', 93 | str_replace(['{', '}'], ['%7B', '%7D'], $holder)]; 94 | } 95 | 96 | /** 97 | * 设置锚点 98 | * 99 | * @param string $anchor 锚点 100 | */ 101 | public function setAnchor(string $anchor) 102 | { 103 | $this->anchor = '#' . $anchor; 104 | } 105 | 106 | /** 107 | * 输出方法 108 | * 109 | * @throws Exception 110 | */ 111 | public function render() 112 | { 113 | throw new Exception(get_class($this) . ':' . __METHOD__, 500); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /var/Widget/Base.php: -------------------------------------------------------------------------------- 1 | initComponents($components); 90 | 91 | if ($components != self::INIT_NONE) { 92 | $this->db = Db::get(); 93 | } 94 | 95 | if ($components & self::INIT_USER) { 96 | $this->user = User::alloc(); 97 | } 98 | 99 | if ($components & self::INIT_OPTIONS) { 100 | $this->options = Options::alloc(); 101 | } 102 | 103 | if ($components & self::INIT_SECURITY) { 104 | $this->security = Security::alloc(); 105 | } 106 | 107 | $this->initParameter($this->parameter); 108 | } 109 | 110 | /** 111 | * @param int $components 112 | */ 113 | protected function initComponents(int &$components) 114 | { 115 | } 116 | 117 | /** 118 | * @param Config $parameter 119 | */ 120 | protected function initParameter(Config $parameter) 121 | { 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /var/Widget/Action.php: -------------------------------------------------------------------------------- 1 | '\Widget\Ajax', 26 | 'login' => '\Widget\Login', 27 | 'logout' => '\Widget\Logout', 28 | 'register' => '\Widget\Register', 29 | 'upgrade' => '\Widget\Upgrade', 30 | 'upload' => '\Widget\Upload', 31 | 'service' => '\Widget\Service', 32 | 'xmlrpc' => '\Widget\XmlRpc', 33 | 'comments-edit' => '\Widget\Comments\Edit', 34 | 'contents-page-edit' => '\Widget\Contents\Page\Edit', 35 | 'contents-post-edit' => '\Widget\Contents\Post\Edit', 36 | 'contents-attachment-edit' => '\Widget\Contents\Attachment\Edit', 37 | 'metas-category-edit' => '\Widget\Metas\Category\Edit', 38 | 'metas-tag-edit' => '\Widget\Metas\Tag\Edit', 39 | 'options-discussion' => '\Widget\Options\Discussion', 40 | 'options-general' => '\Widget\Options\General', 41 | 'options-permalink' => '\Widget\Options\Permalink', 42 | 'options-reading' => '\Widget\Options\Reading', 43 | 'plugins-edit' => '\Widget\Plugins\Edit', 44 | 'themes-edit' => '\Widget\Themes\Edit', 45 | 'users-edit' => '\Widget\Users\Edit', 46 | 'users-profile' => '\Widget\Users\Profile', 47 | 'backup' => '\Widget\Backup' 48 | ]; 49 | 50 | /** 51 | * 入口函数,初始化路由器 52 | * 53 | * @throws Widget\Exception 54 | */ 55 | public function execute() 56 | { 57 | /** 验证路由地址 **/ 58 | $action = $this->request->action; 59 | 60 | /** 判断是否为plugin */ 61 | $actionTable = array_merge($this->map, unserialize(Options::alloc()->actionTable)); 62 | 63 | if (isset($actionTable[$action])) { 64 | $widgetName = $actionTable[$action]; 65 | } 66 | 67 | if (isset($widgetName) && class_exists($widgetName)) { 68 | $widget = self::widget($widgetName); 69 | 70 | if ($widget instanceof ActionInterface) { 71 | $widget->action(); 72 | return; 73 | } 74 | } 75 | 76 | throw new Widget\Exception(_t('请求的地址不存在'), 404); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /var/Typecho/Cookie.php: -------------------------------------------------------------------------------- 1 | setCookie($key, $value, $expire, self::$path); 95 | } 96 | 97 | /** 98 | * 删除指定的COOKIE值 99 | * 100 | * @param string $key 指定的参数 101 | */ 102 | public static function delete(string $key) 103 | { 104 | $key = self::$prefix . $key; 105 | if (!isset($_COOKIE[$key])) { 106 | return; 107 | } 108 | 109 | Response::getInstance()->setCookie($key, '', -1, self::$path); 110 | unset($_COOKIE[$key]); 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | const sass = require('node-sass'), 2 | color = require('chalk'), 3 | fs = require('fs'), 4 | SpriteMagicImporter = require('sprite-magic-importer'), 5 | UglifyJS = require("uglify-js"), 6 | srcDir = __dirname + '/../admin/src', 7 | distDir = __dirname + '/../admin', 8 | action = process.argv.pop(); 9 | 10 | let spriteImporter = SpriteMagicImporter({ 11 | sass_dir: srcDir, 12 | images_dir: srcDir + '/img', 13 | generated_images_dir: distDir + '/img', 14 | http_stylesheets_path: '.', 15 | http_generated_images_path: 'img', 16 | use_cache: false, 17 | cache_dir: __dirname + '/tmp', 18 | 19 | // spritesmith options 20 | spritesmith: { 21 | algorithm: 'top-down', 22 | padding: 0 23 | }, 24 | 25 | // imagemin-pngquant options 26 | pngquant: { 27 | quality: 75, 28 | speed: 10 29 | } 30 | }); 31 | 32 | function buildSass(file) { 33 | let outFile = distDir + '/css/' + file.split('.')[0] + '.css', 34 | sassDir = srcDir + '/scss'; 35 | console.log(color.green('processing ' + file)); 36 | 37 | sass.render({ 38 | file: sassDir + '/' + file, 39 | outFile: outFile, 40 | includePaths: [sassDir], 41 | outputStyle: 'compact', 42 | importer: spriteImporter 43 | }, function (error, result) { 44 | if (error) { 45 | console.error(color.red('Error: ' + error.message)); 46 | console.error(color.red('File: ' + error.file + ' [Line:' + error.line + ']' 47 | + '[Col:' + error.column + ']')); 48 | } else { 49 | fs.writeFileSync(outFile, result.css.toString()); 50 | } 51 | }); 52 | } 53 | 54 | function minifyJs(file) { 55 | console.log(color.green('minify ' + file)); 56 | let code = {}; 57 | code[file] = fs.readFileSync(srcDir + '/js/' + file).toString('utf8'); 58 | 59 | fs.writeFileSync(distDir + '/js/' + file, 60 | UglifyJS.minify(code).code); 61 | } 62 | 63 | function listFiles(dir, regExp) { 64 | let files = fs.readdirSync(srcDir + dir), result = []; 65 | 66 | files.map(function (file) { 67 | if (file.match(regExp)) { 68 | result.push(file); 69 | } 70 | }); 71 | 72 | return result; 73 | } 74 | 75 | if (action === 'css') { 76 | console.log(color.blue('build css')); 77 | 78 | listFiles('/scss', /^[a-z0-9-]+\.scss$/).forEach(function (file) { 79 | buildSass(file); 80 | }); 81 | } else if (action === 'js') { 82 | console.log(color.blue('build js')); 83 | 84 | listFiles('/js', /^[-\w]+\.js$/).forEach(function (file) { 85 | minifyJs(file); 86 | }); 87 | } else { 88 | console.log(color.red('Please choose correct action.')); 89 | } 90 | -------------------------------------------------------------------------------- /var/Typecho/Date.php: -------------------------------------------------------------------------------- 1 | timeStamp = (null === $time ? self::time() : $time) 69 | + (self::$timezoneOffset - self::$serverTimezoneOffset); 70 | 71 | $this->year = date('Y', $this->timeStamp); 72 | $this->month = date('m', $this->timeStamp); 73 | $this->day = date('d', $this->timeStamp); 74 | } 75 | 76 | /** 77 | * 设置当前期望的时区偏移 78 | * 79 | * @param integer $offset 80 | */ 81 | public static function setTimezoneOffset(int $offset) 82 | { 83 | self::$timezoneOffset = $offset; 84 | self::$serverTimezoneOffset = idate('Z'); 85 | } 86 | 87 | /** 88 | * 获取格式化时间 89 | * 90 | * @param string $format 时间格式 91 | * @return string 92 | */ 93 | public function format(string $format): string 94 | { 95 | return date($format, $this->timeStamp); 96 | } 97 | 98 | /** 99 | * 获取国际化偏移时间 100 | * 101 | * @return string 102 | */ 103 | public function word(): string 104 | { 105 | return I18n::dateWord($this->timeStamp, self::time() + (self::$timezoneOffset - self::$serverTimezoneOffset)); 106 | } 107 | 108 | /** 109 | * 获取GMT时间 110 | * 111 | * @deprecated 112 | * @return int 113 | */ 114 | public static function gmtTime(): int 115 | { 116 | return self::time(); 117 | } 118 | 119 | /** 120 | * 获取服务器时间 121 | * 122 | * @return int 123 | */ 124 | public static function time(): int 125 | { 126 | return self::$serverTimeStamp ?: (self::$serverTimeStamp = time()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /var/Widget/Metas/Category/Admin.php: -------------------------------------------------------------------------------- 1 | db->select('mid')->from('table.metas')->where('type = ?', 'category'); 26 | $select->where('parent = ?', $this->request->parent ? $this->request->parent : 0); 27 | 28 | $this->stack = $this->getCategories(array_column( 29 | $this->db->fetchAll($select->order('table.metas.order', Db::SORT_ASC)), 30 | 'mid' 31 | )); 32 | } 33 | 34 | /** 35 | * 向上的返回链接 36 | * 37 | * @throws Db\Exception 38 | */ 39 | public function backLink() 40 | { 41 | if (isset($this->request->parent)) { 42 | $category = $this->db->fetchRow($this->select() 43 | ->where('type = ? AND mid = ?', 'category', $this->request->parent)); 44 | 45 | if (!empty($category)) { 46 | $parent = $this->db->fetchRow($this->select() 47 | ->where('type = ? AND mid = ?', 'category', $category['parent'])); 48 | 49 | if ($parent) { 50 | echo ''; 53 | } else { 54 | echo ''; 55 | } 56 | 57 | echo '« '; 58 | _e('返回父级分类'); 59 | echo ''; 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * 获取菜单标题 66 | * 67 | * @return string|null 68 | * @throws Db\Exception|Exception 69 | */ 70 | public function getMenuTitle(): ?string 71 | { 72 | if (isset($this->request->parent)) { 73 | $category = $this->db->fetchRow($this->select() 74 | ->where('type = ? AND mid = ?', 'category', $this->request->parent)); 75 | 76 | if (!empty($category)) { 77 | return _t('管理 %s 的子分类', $category['name']); 78 | } 79 | } else { 80 | return null; 81 | } 82 | 83 | throw new Exception(_t('分类不存在'), 404); 84 | } 85 | 86 | /** 87 | * 获取菜单标题 88 | * 89 | * @return string 90 | */ 91 | public function getAddLink(): string 92 | { 93 | if (isset($this->request->parent)) { 94 | return 'category.php?parent=' . $this->request->filter('int')->parent; 95 | } else { 96 | return 'category.php'; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /var/Widget/Plugins/Config.php: -------------------------------------------------------------------------------- 1 | user->pass('administrator'); 55 | $config = $this->request->filter('slug')->config; 56 | if (empty($config)) { 57 | throw new Exception(_t('插件不存在'), 404); 58 | } 59 | 60 | /** 获取插件入口 */ 61 | [$this->pluginFileName, $this->className] = Plugin::portal($config, $this->options->pluginDir); 62 | $this->info = Plugin::parseInfo($this->pluginFileName); 63 | } 64 | 65 | /** 66 | * 获取菜单标题 67 | * 68 | * @return string 69 | */ 70 | public function getMenuTitle(): string 71 | { 72 | return _t('设置插件 %s', $this->info['title']); 73 | } 74 | 75 | /** 76 | * 配置插件 77 | * 78 | * @return Form 79 | * @throws Exception|Plugin\Exception 80 | */ 81 | public function config() 82 | { 83 | /** 获取插件名称 */ 84 | $pluginName = $this->request->filter('slug')->config; 85 | 86 | /** 获取已启用插件 */ 87 | $plugins = Plugin::export(); 88 | $activatedPlugins = $plugins['activated']; 89 | 90 | /** 判断实例化是否成功 */ 91 | if (!$this->info['config'] || !isset($activatedPlugins[$pluginName])) { 92 | throw new Exception(_t('无法配置插件'), 500); 93 | } 94 | 95 | /** 载入插件 */ 96 | require_once $this->pluginFileName; 97 | $form = new Form($this->security->getIndex('/action/plugins-edit?config=' . $pluginName), Form::POST_METHOD); 98 | call_user_func([$this->className, 'config'], $form); 99 | 100 | $options = $this->options->plugin($pluginName); 101 | 102 | if (!empty($options)) { 103 | foreach ($options as $key => $val) { 104 | $form->getInput($key)->value($val); 105 | } 106 | } 107 | 108 | $submit = new Form\Element\Submit(null, null, _t('保存设置')); 109 | $submit->input->setAttribute('class', 'btn primary'); 110 | $form->addItem($submit); 111 | return $form; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /var/Typecho/Db/Adapter.php: -------------------------------------------------------------------------------- 1 | 2 |
      3 | comments()->to($comments); ?> 4 | have()): ?> 5 |

      commentsNum(_t('暂无评论'), _t('仅有一条评论'), _t('已有 %d 条评论')); ?>

      6 | 7 | listComments(); ?> 8 | 9 | pageNav('« 前一页', '后一页 »'); ?> 10 | 11 | 12 | 13 | allow('comment')): ?> 14 |
      15 |
      16 | cancelReply(); ?> 17 |
      18 | 19 |

      20 |
      21 | user->hasLogin()): ?> 22 |

      user->screenName(); ?>. » 25 |

      26 | 27 |

      28 | 29 | 31 |

      32 |

      33 | 35 | options->commentsRequireMail): ?> required /> 37 |

      38 |

      39 | 41 | options->commentsRequireURL): ?> required /> 43 |

      44 | 45 |

      46 | 47 | 49 |

      50 |

      51 | 52 |

      53 |
      54 |
      55 | 56 |

      57 | 58 |
      59 | -------------------------------------------------------------------------------- /usr/themes/default/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <?php $this->archiveTitle([ 9 | 'category' => _t('分类 %s 下的文章'), 10 | 'search' => _t('包含关键字 %s 的文章'), 11 | 'tag' => _t('标签 %s 下的文章'), 12 | 'author' => _t('%s 发布的文章') 13 | ], '', ' - '); ?><?php $this->options->title(); ?> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | header(); ?> 22 | 23 | 24 | 25 | 60 |
      61 |
      62 |
      63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /var/Typecho/Router/Parser.php: -------------------------------------------------------------------------------- 1 | routingTable = $routingTable; 48 | 49 | $this->defaultRegex = [ 50 | 'string' => '(.%s)', 51 | 'char' => '([^/]%s)', 52 | 'digital' => '([0-9]%s)', 53 | 'alpha' => '([_0-9a-zA-Z-]%s)', 54 | 'alphaslash' => '([_0-9a-zA-Z-/]%s)', 55 | 'split' => '((?:[^/]+/)%s[^/]+)', 56 | ]; 57 | } 58 | 59 | /** 60 | * 局部匹配并替换正则字符串 61 | * 62 | * @access public 63 | * @param array $matches 匹配部分 64 | * @return string 65 | */ 66 | public function match(array $matches): string 67 | { 68 | $params = explode(' ', $matches[1]); 69 | $paramsNum = count($params); 70 | $this->params[] = $params[0]; 71 | 72 | if (1 == $paramsNum) { 73 | return sprintf($this->defaultRegex['char'], '+'); 74 | } elseif (2 == $paramsNum) { 75 | return sprintf($this->defaultRegex[$params[1]], '+'); 76 | } elseif (3 == $paramsNum) { 77 | return sprintf($this->defaultRegex[$params[1]], $params[2] > 0 ? '{' . $params[2] . '}' : '*'); 78 | } elseif (4 == $paramsNum) { 79 | return sprintf($this->defaultRegex[$params[1]], '{' . $params[2] . ',' . $params[3] . '}'); 80 | } 81 | 82 | return $matches[0]; 83 | } 84 | 85 | /** 86 | * 解析路由表 87 | * 88 | * @access public 89 | * @return array 90 | */ 91 | public function parse(): array 92 | { 93 | $result = []; 94 | 95 | foreach ($this->routingTable as $key => $route) { 96 | $this->params = []; 97 | $route['regx'] = preg_replace_callback( 98 | "/%([^%]+)%/", 99 | [$this, 'match'], 100 | preg_quote(str_replace(['[', ']', ':'], ['%', '%', ' '], $route['url'])) 101 | ); 102 | 103 | /** 处理斜线 */ 104 | $route['regx'] = rtrim($route['regx'], '/'); 105 | $route['regx'] = '|^' . $route['regx'] . '[/]?$|'; 106 | 107 | $route['format'] = preg_replace("/\[([^\]]+)\]/", "%s", $route['url']); 108 | $route['params'] = $this->params; 109 | 110 | $result[$key] = $route; 111 | } 112 | 113 | return $result; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /var/Widget/Users/Admin.php: -------------------------------------------------------------------------------- 1 | parameter->setDefault('pageSize=20'); 56 | $select = $this->select(); 57 | $this->currentPage = $this->request->get('page', 1); 58 | 59 | /** 过滤标题 */ 60 | if (null != ($keywords = $this->request->keywords)) { 61 | $select->where( 62 | 'name LIKE ? OR screenName LIKE ?', 63 | '%' . Common::filterSearchQuery($keywords) . '%', 64 | '%' . Common::filterSearchQuery($keywords) . '%' 65 | ); 66 | } 67 | 68 | $this->countSql = clone $select; 69 | 70 | $select->order('table.users.uid', Db::SORT_ASC) 71 | ->page($this->currentPage, $this->parameter->pageSize); 72 | 73 | $this->db->fetchAll($select, [$this, 'push']); 74 | } 75 | 76 | /** 77 | * 输出分页 78 | * 79 | * @throws Exception|Db\Exception 80 | */ 81 | public function pageNav() 82 | { 83 | $query = $this->request->makeUriByRequest('page={page}'); 84 | 85 | /** 使用盒状分页 */ 86 | $nav = new Box( 87 | !isset($this->total) ? $this->total = $this->size($this->countSql) : $this->total, 88 | $this->currentPage, 89 | $this->parameter->pageSize, 90 | $query 91 | ); 92 | $nav->render('«', '»'); 93 | } 94 | 95 | /** 96 | * 仅仅输出域名和路径 97 | * 98 | * @return string 99 | */ 100 | protected function ___domainPath(): string 101 | { 102 | $parts = parse_url($this->url); 103 | return $parts['host'] . ($parts['path'] ?? null); 104 | } 105 | 106 | /** 107 | * 发布文章数 108 | * 109 | * @return integer 110 | * @throws Db\Exception 111 | */ 112 | protected function ___postsNum(): int 113 | { 114 | return $this->db->fetchObject($this->db->select(['COUNT(cid)' => 'num']) 115 | ->from('table.contents') 116 | ->where('table.contents.type = ?', 'post') 117 | ->where('table.contents.status = ?', 'publish') 118 | ->where('table.contents.authorId = ?', $this->uid))->num; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /var/Widget/Login.php: -------------------------------------------------------------------------------- 1 | security->protect(); 33 | 34 | /** 如果已经登录 */ 35 | if ($this->user->hasLogin()) { 36 | /** 直接返回 */ 37 | $this->response->redirect($this->options->index); 38 | } 39 | 40 | /** 初始化验证类 */ 41 | $validator = new Validate(); 42 | $validator->addRule('name', 'required', _t('请输入用户名')); 43 | $validator->addRule('password', 'required', _t('请输入密码')); 44 | 45 | /** 截获验证异常 */ 46 | if ($error = $validator->run($this->request->from('name', 'password'))) { 47 | Cookie::set('__typecho_remember_name', $this->request->name); 48 | 49 | /** 设置提示信息 */ 50 | Notice::alloc()->set($error); 51 | $this->response->goBack(); 52 | } 53 | 54 | /** 开始验证用户 **/ 55 | $valid = $this->user->login( 56 | $this->request->name, 57 | $this->request->password, 58 | false, 59 | 1 == $this->request->remember ? $this->options->time + $this->options->timezone + 30 * 24 * 3600 : 0 60 | ); 61 | 62 | /** 比对密码 */ 63 | if (!$valid) { 64 | /** 防止穷举,休眠3秒 */ 65 | sleep(3); 66 | 67 | self::pluginHandle()->loginFail( 68 | $this->user, 69 | $this->request->name, 70 | $this->request->password, 71 | 1 == $this->request->remember 72 | ); 73 | 74 | Cookie::set('__typecho_remember_name', $this->request->name); 75 | Notice::alloc()->set(_t('用户名或密码无效'), 'error'); 76 | $this->response->goBack('?referer=' . urlencode($this->request->referer)); 77 | } 78 | 79 | self::pluginHandle()->loginSucceed( 80 | $this->user, 81 | $this->request->name, 82 | $this->request->password, 83 | 1 == $this->request->remember 84 | ); 85 | 86 | /** 跳转验证后地址 */ 87 | if (!empty($this->request->referer)) { 88 | /** fix #952 & validate redirect url */ 89 | if ( 90 | 0 === strpos($this->request->referer, $this->options->adminUrl) 91 | || 0 === strpos($this->request->referer, $this->options->siteUrl) 92 | ) { 93 | $this->response->redirect($this->request->referer); 94 | } 95 | } elseif (!$this->user->pass('contributor', true)) { 96 | /** 不允许普通用户直接跳转后台 */ 97 | $this->response->redirect($this->options->profileUrl); 98 | } 99 | 100 | $this->response->redirect($this->options->adminUrl); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /var/Widget/Upgrade.php: -------------------------------------------------------------------------------- 1 | options->generator, $matches); 32 | $currentVersion = $matches[1]; 33 | $currentMinor = '0'; 34 | if (isset($matches[2])) { 35 | $currentMinor = substr($matches[2], 1); 36 | } 37 | 38 | $message = []; 39 | 40 | foreach ($packages as $package) { 41 | preg_match("/^v([_0-9]+)(r[_0-9]+)?$/", $package, $matches); 42 | 43 | $version = str_replace('_', '.', $matches[1]); 44 | 45 | if (version_compare($currentVersion, $version, '>')) { 46 | break; 47 | } 48 | 49 | if (isset($matches[2])) { 50 | $minor = substr(str_replace('_', '.', $matches[2]), 1); 51 | 52 | if ( 53 | version_compare($currentVersion, $version, '=') 54 | && version_compare($currentMinor, $minor, '>=') 55 | ) { 56 | break; 57 | } 58 | 59 | $version .= '/' . $minor; 60 | } 61 | 62 | $options = Options::allocWithAlias($package); 63 | 64 | /** 执行升级脚本 */ 65 | try { 66 | $result = call_user_func([\Utils\Upgrade::class, $package], $this->db, $options); 67 | if (!empty($result)) { 68 | $message[] = $result; 69 | } 70 | } catch (Exception $e) { 71 | Notice::alloc()->set($e->getMessage(), 'error'); 72 | $this->response->goBack(); 73 | } 74 | 75 | /** 更新版本号 */ 76 | $this->update( 77 | ['value' => 'Typecho ' . $version], 78 | $this->db->sql()->where('name = ?', 'generator') 79 | ); 80 | 81 | Options::destroy($package); 82 | } 83 | 84 | /** 更新版本号 */ 85 | $this->update( 86 | ['value' => 'Typecho ' . Common::VERSION], 87 | $this->db->sql()->where('name = ?', 'generator') 88 | ); 89 | 90 | Notice::alloc()->set( 91 | empty($message) ? _t("升级已经完成") : $message, 92 | empty($message) ? 'success' : 'notice' 93 | ); 94 | } 95 | 96 | /** 97 | * 初始化函数 98 | * 99 | * @throws \Typecho\Db\Exception 100 | * @throws \Typecho\Widget\Exception 101 | */ 102 | public function action() 103 | { 104 | $this->user->pass('administrator'); 105 | $this->security->protect(); 106 | $this->on($this->request->isPost())->upgrade(); 107 | $this->response->redirect($this->options->adminUrl); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /admin/theme-editor.php: -------------------------------------------------------------------------------- 1 | to($files); 7 | ?> 8 | 9 |
      10 |
      11 | 12 |
      13 | 28 | 29 |
      30 |
      31 |
      33 | 34 | 36 |

      37 | currentIsWriteable()): ?> 38 | 39 | 40 | 41 | 42 | 43 | 44 |

      45 |
      46 |
      47 |
        48 |
      • 模板文件
      • 49 | next()): ?> 50 | current): ?> class="current"> 51 | file(); ?> 52 | 53 | 54 |
      55 |
      56 |
      57 |
      58 |
      59 | 60 | bottom($files); 64 | include 'footer.php'; 65 | ?> 66 | --------------------------------------------------------------------------------