56 Commits

Author SHA1 Message Date
沈唁
4be93f2741 Fix missing mysqli_connect_errno (#1346) 2022-03-28 12:11:24 +08:00
joyqi
6100695d87 fix db exception 2022-03-25 15:31:57 +08:00
fen
0bdf8721e1 add ignore 2022-03-25 12:08:22 +08:00
沈唁
ce7af58367 Optimize code (#1342) 2022-03-24 17:46:03 +08:00
joyqi
1b673e06ff fix typecho/Dockerfile#9 2022-03-22 14:24:14 +08:00
joyqi
0edb48fae0 fix #1335, close #1337 2022-03-16 21:05:22 +08:00
沈唁
17fcb2f08b Fix undefined Typecho\Db\Adapter_Exception (#1334) 2022-03-15 16:09:32 +08:00
joyqi
5f943d48b5 fix #1329 2022-02-25 21:16:12 +08:00
joyqi
b23277267a fix #1328 2022-02-24 23:21:35 +08:00
沈唁
b0d78a81dc Fix #1326 (#1327) 2022-02-21 21:51:13 +08:00
沈唁
f34d14280d Fix compatibility with PHP 8.1 (#1324)
* Fix compatibility with PHP 8.1

PHP Deprecated:  htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in typecho/var/Widget/Archive.php on line 1292

* Fix login

* Fix write

* Fix manage

* Fix PHP Deprecated
2022-02-15 11:10:47 +08:00
沈唁
13dc5e87dd Fix #1322 (#1323)
Great work!
2022-02-15 11:10:15 +08:00
沈唁
0b021e5e7d Fix broken extends.typecho.org link (#1315)
close #857
2022-02-09 11:41:35 +08:00
joyqi
00c75d2f75 fix version compare 2022-01-30 00:30:28 +08:00
joyqi
1eedc481ad fix #1295, ref #1297 2022-01-29 17:46:48 +08:00
joyqi
75899e287d fix #1268 2022-01-28 23:58:31 +08:00
jiayx
047bd17f19 Fixes for PHP 8.1 compatibility (#1293)
* Fixes for PHP 8.1 compatibility

* Fixes for PHP 8.1 compatibility

* 优化写法

* remove mixed type

* fix nullable column

* fix query filter

* recover

* recover fileds

* recover

* Update Text.php

* Update Text.php

Co-authored-by: joyqi <joyqi@users.noreply.github.com>
2022-01-28 23:51:24 +08:00
joyqi
9c075dcdf0 fix #1279 2022-01-13 14:04:58 +08:00
joyqi
157fc05265 fix #1268 2021-12-13 11:58:35 +08:00
joyqi
c047669900 fix #1257 2021-12-06 22:09:17 +08:00
joyqi
eee0228fed Refactor HttpClient, fix #1246 2021-11-18 00:20:24 +08:00
joyqi
389b46635e fix client 2021-11-17 18:34:01 +08:00
joyqi
919911e288 fix redirect 2021-11-17 00:33:05 +08:00
joyqi
dd4bf889de fix #1222 2021-11-04 16:37:29 +08:00
joyqi
0dcf45a152 fix #1220 2021-10-30 00:02:41 +08:00
joyqi
ceaa545c7d fix #1212 2021-10-28 19:53:18 +08:00
joyqi
01d9d0c3f5 fix #1207 2021-10-28 12:04:00 +08:00
joyqi
6b46dd9c50 Merge branch 'master' of github.com:typecho/typecho 2021-10-21 22:09:05 +08:00
joyqi
cb4457ab52 fix #1201 2021-10-21 22:08:53 +08:00
joyqi
8a57b91343 fix #1203 2021-10-21 18:49:05 +08:00
joyqi
c66b6e20ec fix #1187 2021-10-21 11:10:34 +08:00
joyqi
b33a9c4d02 ref https://github.com/typecho/Dockerfile/issues/1 2021-10-13 18:34:46 +08:00
joyqi
cac1c650a1 fix #1196 2021-10-13 16:31:15 +08:00
joyqi
42e192340d fix #1194 2021-10-13 15:50:40 +08:00
joyqi
b677f7db92 Merge branch 'master' of github.com:typecho/typecho 2021-10-13 14:33:43 +08:00
joyqi
2fbc56dead ref https://github.com/typecho/Dockerfile/issues/1 2021-10-13 14:33:28 +08:00
joyqi
d174cc5732 update hyperdown 2021-09-30 01:06:14 +08:00
joyqi
5557f6dd91 fix clear 2021-09-29 17:10:34 +08:00
joyqi
42fe6f7bf5 fix #1191 2021-09-26 16:15:17 +08:00
joyqi
1f1019ba5b fix #1189 2021-09-23 11:29:10 +08:00
joyqi
13d18e02cc Create CODE_OF_CONDUCT.md 2021-09-17 21:59:56 +08:00
joyqi
e46acd815d improve cli install 2021-09-17 15:15:54 +08:00
joyqi
432b69f70d fix install detect & error display 2021-09-17 15:06:05 +08:00
joyqi
0e750771ae fix service 2021-09-16 21:54:49 +08:00
joyqi
fc9aaf66f3 Use TypechoPlugin\ for plugin namespace 2021-09-16 17:53:59 +08:00
joyqi
ec495d7e24 Add magic method to native request object 2021-09-16 17:05:43 +08:00
joyqi
f6b02ebe24 fix port number 2021-09-16 15:42:17 +08:00
joyqi
64b8e68688 fix #1178, change directory permission from 0777 to 0755 2021-09-15 14:59:08 +08:00
joyqi
4e5b88821d fix #1179 2021-09-15 14:33:23 +08:00
joyqi
a003cffbbb fix radio & checkbox vertical alignment. 2021-09-15 14:15:06 +08:00
joyqi
7ef5fcabd5 fix pingback & request 2021-09-15 01:25:36 +08:00
joyqi
3c4a0022c3 fix pingback 2021-09-15 00:31:13 +08:00
joyqi
780c3b61f4 fix ci build 2021-09-14 22:21:51 +08:00
joyqi
1d21ecac16 fix zip 2021-09-14 21:48:43 +08:00
joyqi
2d777ec061 fix tar 2021-09-14 21:39:06 +08:00
joyqi
5d20d57be9 fix #1173 2021-09-14 00:24:56 +08:00
79 changed files with 1024 additions and 1058 deletions

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4', '8.0']
php: ['7.2', '7.3', '7.4', '8.0', '8.1']
steps:
- name: Checkout code
uses: actions/checkout@v2
@@ -36,21 +36,25 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install tree
run: sudo apt-get -y install tree
- name: Build
run: |
mkdir build
cp -r LICENSE.txt index.php install.php admin install usr var build/
mkdir build/usr/uploads/
chmod 777 build/usr/uploads/
chmod 755 build/usr/uploads/
rm -rf build/admin/src
tree -d build
cd build && zip -q -r typecho.zip * && mv typecho.zip ../ && cd -
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2
uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: typecho
path: ./build/
upload_url: https://uploads.github.com/repos/typecho/typecho/releases/49532662/assets{?name,label}
release_id: 49532662
asset_path: ./typecho.zip
asset_name: typecho.zip
asset_content_type: application/zip
max_releases: 1
- name: Trigger build
run: |
curl -XPOST -H "Authorization: token ${{ secrets.WORKFLOW_TOKEN }}" \

View File

@@ -14,7 +14,7 @@ jobs:
mkdir build
cp -r LICENSE.txt index.php install.php admin install usr var build/
mkdir build/usr/uploads/
chmod 777 build/usr/uploads/
chmod 755 build/usr/uploads/
rm -rf build/admin/src
cd build && zip -q -r typecho.zip * && mv typecho.zip ../ && cd -
- name: Create Release

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@
*.sublime*
.sass-cache
config.rb
prepros.config
/config.inc.php
/usr/uploads/
/usr/*.db

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
admin@typecho.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -8,6 +8,6 @@
<a href="http://docs.typecho.org"><?php _e('帮助文档'); ?></a> &bull;
<a href="http://forum.typecho.org"><?php _e('支持论坛'); ?></a> &bull;
<a href="https://github.com/typecho/typecho/issues"><?php _e('报告错误'); ?></a> &bull;
<a href="http://extends.typecho.org"><?php _e('资源下载'); ?></a>
<a href="http://typecho.org/download"><?php _e('资源下载'); ?></a>
</nav>
</div>

View File

@@ -29,6 +29,8 @@ textarea { resize: vertical; line-height: 1.5; }
input[type="radio"], input[type="checkbox"] { margin-right: 3px; }
input[type="radio"], input[type="checkbox"], input[type="radio"] + label, input[type="checkbox"] + label { vertical-align: middle; }
input.text-s, textarea.text-s { padding: 5px; }
input.text-l, textarea.text-l { padding: 10px; font-size: 1.14286em; }

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ include 'common.php';
if ($user->hasLogin()) {
$response->redirect($options->adminUrl);
}
$rememberName = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_name'));
$rememberName = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_name', ''));
\Typecho\Cookie::delete('__typecho_remember_name');
$bodyClass = 'body-100';
@@ -25,10 +25,12 @@ include 'header.php';
</p>
<p class="submit">
<button type="submit" class="btn btn-l w-100 primary"><?php _e('登录'); ?></button>
<input type="hidden" name="referer" value="<?php echo htmlspecialchars($request->get('referer')); ?>" />
<input type="hidden" name="referer" value="<?php echo htmlspecialchars($request->get('referer') ?? ''); ?>" />
</p>
<p>
<label for="remember"><input type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> <?php _e('下次自动登录'); ?></label>
<label for="remember">
<input<?php if(\Typecho\Cookie::get('__typecho_remember_remember')): ?> checked<?php endif; ?> type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> <?php _e('下次自动登录'); ?>
</label>
</p>
</form>

View File

@@ -69,7 +69,7 @@ $isAllComments = ('on' == $request->get('__typecho_all_comments') || 'on' == \Ty
(isset($request->status) ? 'status=' . htmlspecialchars($request->get('status')) : '') .
(isset($request->cid) ? (isset($request->status) ? '&' : '') . 'cid=' . htmlspecialchars($request->get('cid')) : '') : '')); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>" value="<?php echo htmlspecialchars($request->keywords); ?>"<?php if ('' == $request->keywords): ?> onclick="value='';name='keywords';" <?php else: ?> name="keywords"<?php endif; ?>/>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>" value="<?php echo htmlspecialchars($request->keywords ?? ''); ?>"<?php if ('' == $request->keywords): ?> onclick="value='';name='keywords';" <?php else: ?> name="keywords"<?php endif; ?>/>
<?php if(isset($request->status)): ?>
<input type="hidden" value="<?php echo htmlspecialchars($request->get('status')); ?>" name="status" />
<?php endif; ?>

View File

@@ -36,7 +36,7 @@ $attachments = \Widget\Contents\Attachment\Admin::alloc();
<a href="<?php $options->adminUrl('manage-medias.php'); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>"
value="<?php echo htmlspecialchars($request->keywords); ?>"<?php if ('' == $request->keywords): ?> onclick="value='';name='keywords';" <?php else: ?> name="keywords"<?php endif; ?>/>
value="<?php echo htmlspecialchars($request->keywords ?? ''); ?>"<?php if ('' == $request->keywords): ?> onclick="value='';name='keywords';" <?php else: ?> name="keywords"<?php endif; ?>/>
<button type="submit" class="btn btn-s"><?php _e('筛选'); ?></button>
</div>
</form>

View File

@@ -39,7 +39,7 @@ $pages = \Widget\Contents\Page\Admin::alloc();
<a href="<?php $options->adminUrl('manage-pages.php'); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>"
value="<?php echo htmlspecialchars($request->keywords); ?>" name="keywords"/>
value="<?php echo htmlspecialchars($request->keywords ?? ''); ?>" name="keywords"/>
<button type="submit" class="btn btn-s"><?php _e('筛选'); ?></button>
</div>
</form>

View File

@@ -16,10 +16,10 @@ $isAllPosts = ('on' == $request->get('__typecho_all_posts') || 'on' == \Typecho\
<ul class="typecho-option-tabs right">
<?php if ($user->pass('editor', true) && !isset($request->uid)): ?>
<li class="<?php if ($isAllPosts): ?> current<?php endif; ?>"><a
href="<?php echo $request->makeUriByRequest('__typecho_all_posts=on'); ?>"><?php _e('所有'); ?></a>
href="<?php echo $request->makeUriByRequest('__typecho_all_posts=on&page=1'); ?>"><?php _e('所有'); ?></a>
</li>
<li class="<?php if (!$isAllPosts): ?> current<?php endif; ?>"><a
href="<?php echo $request->makeUriByRequest('__typecho_all_posts=off'); ?>"><?php _e('我的'); ?></a>
href="<?php echo $request->makeUriByRequest('__typecho_all_posts=off&page=1'); ?>"><?php _e('我的'); ?></a>
</li>
<?php endif; ?>
</ul>
@@ -91,7 +91,7 @@ $isAllPosts = ('on' == $request->get('__typecho_all_posts') || 'on' == \Typecho\
(isset($request->uid) ? '?uid=' . htmlspecialchars($request->get('uid')) : '') : '')); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>"
value="<?php echo htmlspecialchars($request->keywords); ?>" name="keywords"/>
value="<?php echo htmlspecialchars($request->keywords ?? ''); ?>" name="keywords"/>
<select name="category">
<option value=""><?php _e('所有分类'); ?></option>
<?php \Widget\Metas\Category\Rows::alloc()->to($category); ?>

View File

@@ -31,7 +31,7 @@ $users = \Widget\Users\Admin::alloc();
<a href="<?php $options->adminUrl('manage-users.php'); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
<input type="text" class="text-s" placeholder="<?php _e('请输入关键字'); ?>"
value="<?php echo htmlspecialchars($request->keywords); ?>" name="keywords"/>
value="<?php echo htmlspecialchars($request->keywords ?? ''); ?>" name="keywords"/>
<button type="submit" class="btn btn-s"><?php _e('筛选'); ?></button>
</div>
</form>

View File

@@ -11,7 +11,7 @@ $stat = \Widget\Stat::alloc();
<?php include 'page-title.php'; ?>
<div class="row typecho-page-main">
<div class="col-mb-12 col-tb-3">
<p><a href="http://gravatar.com/emails/"
<p><a href="https://gravatar.com/emails/"
title="<?php _e('在 Gravatar 上修改头像'); ?>"><?php echo '<img class="profile-avatar" src="' . \Typecho\Common::gravatarUrl($user->mail, 220, 'X', 'mm', $request->isSecure()) . '" alt="' . $user->screenName . '" />'; ?></a>
</p>
<h2><?php $user->screenName(); ?></h2>

View File

@@ -819,7 +819,7 @@
}
parseList(lines, value, start) {
var html, j, key, l, last, len, len1, line, matches, row, rows, space, suffix, tab, type;
var html, j, key, l, last, len, len1, line, matches, olStart, row, rows, space, suffix, tab, type;
html = '';
[space, type, tab] = value;
rows = [];
@@ -829,9 +829,9 @@
line = lines[key];
if (matches = line.match(new RegExp(`^(\\s{${space}})((?:[0-9]+\\.?)|\\-|\\+|\\*)(\\s+)(.*)$`))) {
if (type === 'ol' && key === 0) {
start = parseInt(matches[2]);
if (start !== 1) {
suffix = ' start="' + start + '"';
olStart = parseInt(matches[2]);
if (olStart !== 1) {
suffix = ' start="' + olStart + '"';
}
}
rows.push([matches[4]]);

View File

@@ -22,7 +22,14 @@ textarea {
line-height: 1.5;
}
input[type="radio"], input[type="checkbox"] { margin-right: 3px; }
input[type="radio"], input[type="checkbox"] {
margin-right: 3px;
}
input[type="radio"], input[type="checkbox"],
input[type="radio"] + label, input[type="checkbox"] + label {
vertical-align: middle;
}
input, textarea {
&.text-s { padding: 5px; }

View File

@@ -128,36 +128,38 @@ a.button:hover, a.balloon-button:hover {
list-style: none;
margin: 1em 0;
padding: 0;
}
.typecho-option li {
}
&-submit li {
border-bottom: none;
}
.typecho-option-submit li {
border-bottom: none;
}
label {
&.typecho-label {
display: block;
margin-bottom: .5em;
font-weight: bold;
}
.typecho-option label.typecho-label {
display: block;
margin-bottom: .5em;
font-weight: bold;
}
.typecho-option label.required:after {
content: " *";
color: #B94A48;
}
.typecho-option label.typecho-label+input {
}
.typecho-option span { margin-right: 15px; }
.typecho-option .description {
margin: .5em 0 0;
color: #999;
font-size: .92857em;
}
&.required:after {
content: " *";
color: #B94A48;
}
}
.typecho-option input.file {
width: 100%;
margin: .7em 0;
span {
margin-right: 15px;
}
.description {
margin: .5em 0 0;
color: #999;
font-size: .92857em;
}
input.file {
width: 100%;
margin: .7em 0;
}
}
.front-archive {

View File

@@ -34,7 +34,7 @@ include 'menu.php';
if ($page->have()) {
$permalink = str_replace('{cid}', $page->cid, $permalink);
}
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($page->slug) . '" class="mono" />';
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($page->slug ?? '') . '" class="mono" />';
?>
<p class="mono url-slug">
<label for="slug" class="sr-only"><?php _e('网址缩略名'); ?></label>
@@ -43,7 +43,7 @@ include 'menu.php';
<p>
<label for="text" class="sr-only"><?php _e('页面内容'); ?></label>
<textarea style="height: <?php $options->editorSize(); ?>px" autocomplete="off" id="text"
name="text" class="w-100 mono"><?php echo htmlspecialchars($page->text); ?></textarea>
name="text" class="w-100 mono"><?php echo htmlspecialchars($page->text ?? ''); ?></textarea>
</p>
<?php include 'custom-fields.php'; ?>

View File

@@ -38,7 +38,7 @@ include 'menu.php';
$post->cid, $post->category, $post->year, $post->month, $post->day
], $permalink);
}
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($post->slug) . '" class="mono" />';
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($post->slug ?? '') . '" class="mono" />';
?>
<p class="mono url-slug">
<label for="slug" class="sr-only"><?php _e('网址缩略名'); ?></label>
@@ -47,7 +47,7 @@ include 'menu.php';
<p>
<label for="text" class="sr-only"><?php _e('文章内容'); ?></label>
<textarea style="height: <?php $options->editorSize(); ?>px" autocomplete="off" id="text"
name="text" class="w-100 mono"><?php echo htmlspecialchars($post->text); ?></textarea>
name="text" class="w-100 mono"><?php echo htmlspecialchars($post->text ?? ''); ?></textarea>
</p>
<?php include 'custom-fields.php'; ?>
@@ -134,7 +134,7 @@ include 'menu.php';
<option
value="hidden"<?php if ($post->status == 'hidden'): ?> selected<?php endif; ?>><?php _e('隐藏'); ?></option>
<option
value="password"<?php if (strlen($post->password) > 0): ?> selected<?php endif; ?>><?php _e('密码保护'); ?></option>
value="password"<?php if (strlen($post->password ?? '') > 0): ?> selected<?php endif; ?>><?php _e('密码保护'); ?></option>
<option
value="private"<?php if ($post->status == 'private'): ?> selected<?php endif; ?>><?php _e('私密'); ?></option>
<?php endif; ?>
@@ -142,7 +142,7 @@ include 'menu.php';
value="waiting"<?php if (!$user->pass('editor', true) || $post->status == 'waiting'): ?> selected<?php endif; ?>><?php _e('待审核'); ?></option>
</select>
</p>
<p id="post-password"<?php if (strlen($post->password) == 0): ?> class="hidden"<?php endif; ?>>
<p id="post-password"<?php if (strlen($post->password ?? '') == 0): ?> class="hidden"<?php endif; ?>>
<label for="protect-pwd" class="sr-only">内容密码</label>
<input type="text" name="password" id="protect-pwd" class="text-s"
value="<?php $post->password(); ?>" size="16"

View File

@@ -54,17 +54,8 @@ function install_get_lang(): string
*/
function install_get_site_url(): string
{
$serverSiteUrl = \Typecho\Request::getInstance()->getServer(
'TYPECHO_SITE_URL',
install_is_cli() ? 'http://localhost' : null
);
if (!empty($serverSiteUrl)) {
return $serverSiteUrl;
} else {
$request = \Typecho\Request::getInstance();
return $request->get('userUrl', $request->getRequestRoot());
}
$request = \Typecho\Request::getInstance();
return install_is_cli() ? $request->getServer('TYPECHO_SITE_URL', 'http://localhost') : $request->getRequestRoot();
}
/**
@@ -467,10 +458,10 @@ function install_check(string $type): bool
try {
// check if table exists
$values = $installDb->fetchAll($installDb->select()->from('table.options')
->where('user = 0'));
$installed = $installDb->fetchRow($installDb->select()->from('table.options')
->where('user = 0 AND name = ?', 'installed'));
if ($type == 'db_data' && empty($values)) {
if ($type == 'db_data' && empty($installed['value'])) {
return false;
}
} catch (\Typecho\Db\Adapter\ConnectionException $e) {
@@ -518,7 +509,13 @@ function install_raise_error($error, $config = null)
*/
function install_success($step, ?array $config = null)
{
global $installDb;
if (install_is_cli()) {
if ($step == 3) {
\Typecho\Db::set($installDb);
}
if ($step > 0) {
$method = 'install_step_' . $step . '_perform';
$method();
@@ -762,13 +759,13 @@ function install_step_1_perform()
$realUploadDir = \Typecho\Common::url($uploadDir, __TYPECHO_ROOT_DIR__);
$writeable = true;
if (is_dir($realUploadDir)) {
if (!is_writeable($realUploadDir)) {
if (!@chmod($realUploadDir, 0644)) {
if (!is_writeable($realUploadDir) || !is_readable($realUploadDir)) {
if (!@chmod($realUploadDir, 0755)) {
$writeable = false;
}
}
} else {
if (!@mkdir($realUploadDir, 0644)) {
if (!@mkdir($realUploadDir, 0755)) {
$writeable = false;
}
}
@@ -1024,6 +1021,9 @@ function install_step_2_perform()
case 'SQLite':
$error = (new \Typecho\Validate())
->addRule('dbFile', 'required', _t('确认您的配置'))
->addRule('dbFile', function (string $path) {
return !!preg_match("/^(\/[_a-z0-9-]+)*[a-z0-9]+\.[a-z0-9]{2,}$/i", $path);
}, _t('确认您的配置'))
->run($config);
break;
default:
@@ -1039,6 +1039,15 @@ function install_step_2_perform()
$dbConfig[strtolower(substr($key, 2))] = $config[$key];
}
// intval port number
if (isset($dbConfig['port'])) {
$dbConfig['port'] = intval($dbConfig['port']);
}
if (isset($dbConfig['file']) && preg_match("/^[a-z0-9]+\.[a-z0-9]{2,}$/i", $dbConfig['file'])) {
$dbConfig['file'] = __DIR__ . '/usr/' . $dbConfig['file'];
}
// check config file
if ($config['dbNext'] == 'config' && !install_check('config')) {
$code = install_config_file($config['dbAdapter'], $config['dbPrefix'], $dbConfig, true);
@@ -1049,10 +1058,10 @@ function install_step_2_perform()
$installDb = new \Typecho\Db($config['dbAdapter'], $config['dbPrefix']);
$installDb->addServer($dbConfig, \Typecho\Db::READ | \Typecho\Db::WRITE);
$installDb->query('SELECT 1=1');
} catch (\Typecho\Db\Adapter_Exception $e) {
} catch (\Typecho\Db\Adapter\ConnectionException $e) {
install_raise_error(_t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装'));
} catch (\Typecho\Db\Exception $e) {
install_raise_error(_t('安装程序捕捉到以下错误: " %s ". 程序被终止, 请检查您的配置信息.', $e->getMessage()));
install_raise_error(_t('安装程序捕捉到以下错误: "%s". 程序被终止, 请检查您的配置信息.', $e->getMessage()));
}
$code = install_config_file($config['dbAdapter'], $config['dbPrefix'], $dbConfig);
@@ -1082,12 +1091,14 @@ function install_step_2_perform()
try {
foreach ($tables as $table) {
if ($type == 'Mysql') {
$installDb->query("DROP TABLE IF EXISTS `{$table}`");
} elseif ($type == 'Pgsql') {
$installDb->query("DROP TABLE {$table}");
} elseif ($type == 'SQLite') {
$installDb->query("DROP TABLE {$table}");
switch ($type) {
case 'Mysql':
$installDb->query("DROP TABLE IF EXISTS `{$table}`");
break;
case 'Pgsql':
case 'SQLite':
$installDb->query("DROP TABLE {$table}");
break;
}
}
} catch (\Typecho\Db\Exception $e) {
@@ -1163,7 +1174,7 @@ function install_step_3()
<ul class="typecho-option">
<li>
<label class="typecho-label" for="userUrl"><?php _e('网站地址'); ?></label>
<input autocomplete="new-password" type="text" name="userUrl" id="userUrl" class="text" value="<?php $options->siteUrl(); ?>" />
<input autocomplete="new-password" type="text" name="userUrl" id="userUrl" class="text" value="<?php $options->rootUrl(); ?>" />
<p class="description"><?php _e('这是程序自动匹配的网站路径, 如果不正确请修改它'); ?></p>
</li>
</ul>
@@ -1248,13 +1259,6 @@ function install_step_3_perform()
}
try {
// write options
foreach (install_get_default_options() as $key => $value) {
$installDb->query(
$installDb->insert('table.options')->rows(['name' => $key, 'user' => 0, 'value' => $value])
);
}
// write user
$hasher = new \Utils\PasswordHash(8, true);
$installDb->query(
@@ -1262,7 +1266,7 @@ function install_step_3_perform()
'name' => $config['userName'],
'password' => $hasher->hashPassword($config['userPassword']),
'mail' => $config['userMail'],
'url' => $options->siteUrl,
'url' => $config['userUrl'],
'screenName' => $config['userName'],
'group' => 'administrator',
'created' => \Typecho\Date::time()
@@ -1326,7 +1330,7 @@ function install_step_3_perform()
'cid' => 1, 'created' => \Typecho\Date::time(),
'author' => 'Typecho',
'ownerId' => 1,
'url' => 'http://typecho.org',
'url' => 'https://typecho.org',
'ip' => '127.0.0.1',
'agent' => $options->generator,
'text' => '欢迎加入 Typecho 大家族',
@@ -1335,6 +1339,18 @@ function install_step_3_perform()
'parent' => 0
])
);
// write options
foreach (install_get_default_options() as $key => $value) {
// mark installing finished
if ($key == 'installed') {
$value = 1;
}
$installDb->query(
$installDb->insert('table.options')->rows(['name' => $key, 'user' => 0, 'value' => $value])
);
}
} catch (\Typecho\Db\Exception $e) {
install_raise_error($e->getMessage());
}
@@ -1351,7 +1367,7 @@ function install_step_3_perform()
$config['userName'],
$config['userPassword'],
\Widget\Security::alloc()->getTokenUrl($loginUrl, $request->getReferer()),
$options->siteUrl
$config['userUrl']
]);
}
@@ -1361,8 +1377,6 @@ function install_step_3_perform()
*/
function install_dispatch()
{
define('__TYPECHO_INSTALL__', true);
// disable root url on cli mode
if (install_is_cli()) {
define('__TYPECHO_ROOT_URL__', 'http://localhost');
@@ -1372,6 +1386,12 @@ function install_dispatch()
$options = \Widget\Options::alloc(install_get_default_options());
\Widget\Init::alloc();
// display version
if (install_is_cli()) {
echo $options->generator . "\n";
echo 'PHP ' . PHP_VERSION . "\n";
}
// install finished yet
if (
install_check('config')

View File

@@ -1,6 +1,6 @@
<?php
namespace HelloWorld;
namespace TypechoPlugin\HelloWorld;
use Typecho\Plugin\PluginInterface;
use Typecho\Widget\Helper\Form;
@@ -26,7 +26,7 @@ class Plugin implements PluginInterface
*/
public static function activate()
{
\Typecho\Plugin::factory('admin/menu.php')->navBar = ['HelloWorld_Plugin', 'render'];
\Typecho\Plugin::factory('admin/menu.php')->navBar = __CLASS__ . '::render';
}
/**

View File

@@ -40,7 +40,6 @@ class Server
$this->setCapabilities();
$this->callbacks = $callbacks;
$this->setCallbacks();
$this->serve();
}
/**
@@ -298,7 +297,7 @@ class Server
/**
* 服务入口
*/
private function serve()
public function serve()
{
$message = new Message(file_get_contents('php://input') ?: '');

View File

@@ -74,25 +74,42 @@ namespace {
}
namespace Typecho {
const PLUGIN_NAMESPACE = 'TypechoPlugin';
spl_autoload_register(function (string $className) {
$isDefinedAlias = defined('__TYPECHO_CLASS_ALIASES__');
$isNamespace = strpos($className, '\\') !== false;
$isAlias = $isDefinedAlias && isset(__TYPECHO_CLASS_ALIASES__[$className]);
$isPlugin = false;
// detect if class is predefined
if (strpos($className, '\\') !== false) {
if ($isDefinedAlias) {
$alias = array_search('\\' . ltrim($className, '\\'), __TYPECHO_CLASS_ALIASES__);
$isPlugin = strpos(ltrim($className, '\\'), PLUGIN_NAMESPACE . '\\') !== false;
if ($isPlugin) {
$realClassName = substr($className, strlen(PLUGIN_NAMESPACE) + 1);
$alias = Common::nativeClassName($realClassName);
$path = str_replace('\\', '/', $realClassName);
} else {
if ($isDefinedAlias) {
$alias = array_search('\\' . ltrim($className, '\\'), __TYPECHO_CLASS_ALIASES__);
}
$alias = empty($alias) ? Common::nativeClassName($className) : $alias;
$path = str_replace('\\', '/', $className);
}
$alias = empty($alias) ? Common::nativeClassName($className) : $alias;
$path = str_replace('\\', '/', $className);
} elseif (strpos($className, '_') !== false || $isAlias) {
$alias = $isAlias ? __TYPECHO_CLASS_ALIASES__[$className]
: '\\' . str_replace('_', '\\', $className);
$isPlugin = !$isAlias && !preg_match("/^(Typecho|Widget|IXR)_/", $className);
$path = str_replace('\\', '/', $alias);
if ($isPlugin) {
$alias = '\\TypechoPlugin\\' . str_replace('_', '\\', $className);
$path = str_replace('_', '/', $className);
} else {
$alias = $isAlias ? __TYPECHO_CLASS_ALIASES__[$className]
: '\\' . str_replace('_', '\\', $className);
$path = str_replace('\\', '/', $alias);
}
} else {
$path = $className;
}
@@ -104,13 +121,14 @@ namespace Typecho {
|| trait_exists($alias, false))
) {
class_alias($alias, $className, false);
return;
}
// load class file
$path .= '.php';
$defaultFile = __TYPECHO_ROOT_DIR__ . '/var/' . $path;
if (file_exists($defaultFile)) {
if (file_exists($defaultFile) && !$isPlugin) {
include_once $defaultFile;
} else {
$pluginFile = __TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__ . '/' . $path;
@@ -164,8 +182,10 @@ namespace Typecho {
*/
public static function url(?string $path, ?string $prefix): string
{
$path = $path ?? '';
$path = (0 === strpos($path, './')) ? substr($path, 2) : $path;
return rtrim($prefix, '/') . '/' . str_replace('//', '/', ltrim($path, '/'));
return rtrim($prefix ?? '', '/') . '/'
. str_replace('//', '/', ltrim($path, '/'));
}
/**
@@ -210,10 +230,10 @@ namespace Typecho {
//覆盖原始错误信息
$message = 'Database Server Error';
if ($exception instanceof \Typecho\Db\Adapter\SQLException) {
if ($exception instanceof \Typecho\Db\Adapter\ConnectionException) {
$code = 503;
$message = 'Error establishing a database connection';
} elseif ($exception instanceof \Typecho\Db\Query\Exception) {
} elseif ($exception instanceof \Typecho\Db\Adapter\SQLException) {
$message = 'Database Query Error';
}
}
@@ -462,7 +482,7 @@ EOF;
*/
public static function filterSearchQuery(?string $query): string
{
return str_replace('-', ' ', self::slugName($query));
return isset($query) ? str_replace('-', ' ', self::slugName($query)) : '';
}
/**
@@ -478,7 +498,7 @@ EOF;
*/
public static function slugName(?string $str, ?string $default = null, int $maxLength = 128): ?string
{
$str = trim($str);
$str = trim($str ?? '');
if (!strlen($str)) {
return $default;

View File

@@ -91,7 +91,7 @@ class Config implements \Iterator, \ArrayAccess
* @access public
* @return void
*/
public function rewind()
public function rewind(): void
{
reset($this->currentConfig);
}
@@ -102,6 +102,7 @@ class Config implements \Iterator, \ArrayAccess
* @access public
* @return mixed
*/
#[\ReturnTypeWillChange]
public function current()
{
return current($this->currentConfig);
@@ -113,7 +114,7 @@ class Config implements \Iterator, \ArrayAccess
* @access public
* @return void
*/
public function next()
public function next(): void
{
next($this->currentConfig);
}
@@ -124,6 +125,7 @@ class Config implements \Iterator, \ArrayAccess
* @access public
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key()
{
return key($this->currentConfig);
@@ -222,6 +224,7 @@ class Config implements \Iterator, \ArrayAccess
* @param mixed $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->currentConfig[$offset] ?? null;
@@ -231,7 +234,7 @@ class Config implements \Iterator, \ArrayAccess
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
$this->currentConfig[$offset] = $value;
}
@@ -239,7 +242,7 @@ class Config implements \Iterator, \ArrayAccess
/**
* @param mixed $offset
*/
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
unset($this->currentConfig[$offset]);
}

View File

@@ -129,6 +129,14 @@ class Db
$this->adapter = new $adapterName();
}
/**
* @return Adapter
*/
public function getAdapter(): Adapter
{
return $this->adapter;
}
/**
* 获取适配器名称
*

View File

@@ -37,6 +37,13 @@ interface Adapter
*/
public function getVersion($handle): string;
/**
* 获取数据库类型
*
* @return string
*/
public function getDriver(): string;
/**
* 清空数据表
*

View File

@@ -29,4 +29,12 @@ trait MysqlTrait
{
return $this->buildQuery($sql);
}
/**
* @return string
*/
public function getDriver(): string
{
return 'mysql';
}
}

View File

@@ -64,7 +64,7 @@ class Mysqli implements Adapter
}
/** 数据库异常 */
throw new ConnectionException(@$this->dbLink->error, @$this->dbLink->errno);
throw new ConnectionException("Couldn't connect to database.", mysqli_connect_errno());
}
/**
@@ -75,7 +75,7 @@ class Mysqli implements Adapter
*/
public function getVersion($handle): string
{
return 'mysqli:mysql ' . $this->dbLink->server_version;
return $this->dbLink->server_version;
}
/**
@@ -86,7 +86,6 @@ class Mysqli implements Adapter
* @param integer $op 数据库读写状态
* @param string|null $action 数据库动作
* @param string|null $table 数据表
* @return \mysqli_result
* @throws SQLException
*/
public function query(
@@ -95,7 +94,7 @@ class Mysqli implements Adapter
int $op = Db::READ,
?string $action = null,
?string $table = null
): \mysqli_result {
) {
if ($resource = @$this->dbLink->query($query)) {
return $resource;
}
@@ -113,7 +112,7 @@ class Mysqli implements Adapter
*/
public function quoteColumn(string $string): string
{
return $this->dbLink->real_escape_string($string);
return '`' . $string . '`';
}
/**
@@ -157,13 +156,13 @@ class Mysqli implements Adapter
*/
public function quoteValue($string): string
{
return '\'' . str_replace(['\'', '\\'], ['\'\'', '\\\\'], $string) . '\'';
return "'" . $this->dbLink->real_escape_string($string) . "'";
}
/**
* 取出最后一次查询影响的行数
*
* @param \mysqli_result $resource 查询的资源数据
* @param mixed $resource 查询的资源数据
* @param \mysqli $handle 连接对象
* @return integer
*/
@@ -175,7 +174,7 @@ class Mysqli implements Adapter
/**
* 取出最后一次插入返回的主键值
*
* @param \mysqli_result $resource 查询的资源数据
* @param mixed $resource 查询的资源数据
* @param \mysqli $handle 连接对象
* @return integer
*/

View File

@@ -81,8 +81,7 @@ abstract class Pdo implements Adapter
*/
public function getVersion($handle): string
{
return 'pdo:' . $handle->getAttribute(\PDO::ATTR_DRIVER_NAME)
. ' ' . $handle->getAttribute(\PDO::ATTR_SERVER_VERSION);
return $handle->getAttribute(\PDO::ATTR_SERVER_VERSION);
}
/**

View File

@@ -63,7 +63,7 @@ class Pgsql implements Adapter
public function getVersion($handle): string
{
$version = pg_version($handle);
return 'pgsql:pgsql ' . $version['server'];
return $version['server'];
}
/**

View File

@@ -153,6 +153,14 @@ WHERE
return 0;
}
/**
* @return string
*/
public function getDriver(): string
{
return 'pgsql';
}
abstract public function query(
string $query,
$handle,

View File

@@ -20,10 +20,10 @@ trait QueryTrait
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
$sql['limit'] = isset($sql['limit']) ? ' LIMIT ' . $sql['limit'] : '';
$sql['offset'] = isset($sql['offset']) ? ' OFFSET ' . $sql['offset'] : '';
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
}
}
}

View File

@@ -57,7 +57,7 @@ class SQLite implements Adapter
*/
public function getVersion($handle): string
{
return 'sqlite:sqlite ' . \SQLite3::version()['versionString'];
return \SQLite3::version()['versionString'];
}
/**

View File

@@ -99,4 +99,12 @@ trait SQLiteTrait
return $query;
}
/**
* @return string
*/
public function getDriver(): string
{
return 'sqlite';
}
}

View File

@@ -21,7 +21,7 @@ use Typecho\Db;
class Query
{
/** 数据库关键字 */
private const KEYWORDS = '*PRIMARY|AND|OR|LIKE|BINARY|BY|DISTINCT|AS|IN|IS|NULL';
private const KEYWORDS = '*PRIMARY|AND|OR|LIKE|ILIKE|BINARY|BY|DISTINCT|AS|IN|IS|NULL';
/**
* 默认字段
@@ -512,7 +512,7 @@ class Query
return preg_replace_callback("/#param:([0-9]+)#/", function ($matches) use ($params, $adapter) {
if (array_key_exists($matches[1], $params)) {
return $adapter->quoteValue($params[$matches[1]]);
return is_null($params[$matches[1]]) ? 'NULL' : $adapter->quoteValue($params[$matches[1]]);
} else {
return $matches[0];
}

View File

@@ -2,12 +2,12 @@
namespace Typecho\Http;
use Typecho\Http\Client\Adapter;
use Typecho\Common;
use Typecho\Http\Client\Exception;
/**
* Http客户端
*
* @author qining
* @category typecho
* @package Http
*/
@@ -19,24 +19,359 @@ class Client
/** GET方法 */
public const METHOD_GET = 'GET';
/** 定义行结束符 */
public const EOL = "\r\n";
/** PUT方法 */
public const METHOD_PUT = 'PUT';
private const ADAPTERS = [Adapter\Curl::class, Adapter\Socket::class];
/** DELETE方法 */
public const METHOD_DELETE = 'DELETE';
/**
* 方法名
*
* @var string
*/
private $method = self::METHOD_GET;
/**
* 传递参数
*
* @var string
*/
private $query;
/**
* User Agent
*
* @var string
*/
private $agent;
/**
* 设置超时
*
* @var string
*/
private $timeout = 3;
/**
* @var bool
*/
private $multipart = true;
/**
* 需要在body中传递的值
*
* @var array|string
*/
private $data = [];
/**
* 头信息参数
*
* @access private
* @var array
*/
private $headers = [];
/**
* cookies
*
* @var array
*/
private $cookies = [];
/**
* @var array
*/
private $options = [];
/**
* 回执头部信息
*
* @var array
*/
private $responseHeader = [];
/**
* 回执代码
*
* @var integer
*/
private $responseStatus;
/**
* 回执身体
*
* @var string
*/
private $responseBody;
/**
* 设置指定的COOKIE值
*
* @param string $key 指定的参数
* @param mixed $value 设置的值
* @return $this
*/
public function setCookie(string $key, $value): Client
{
$this->cookies[$key] = $value;
return $this;
}
/**
* 设置传递参数
*
* @param mixed $query 传递参数
* @return $this
*/
public function setQuery($query): Client
{
$query = is_array($query) ? http_build_query($query) : $query;
$this->query = empty($this->query) ? $query : $this->query . '&' . $query;
return $this;
}
/**
* 设置需要POST的数据
*
* @param array|string $data 需要POST的数据
* @param string $method
* @return $this
*/
public function setData($data, string $method = self::METHOD_POST): Client
{
if (is_array($data) && is_array($this->data)) {
$this->data = array_merge($this->data, $data);
} else {
$this->data = $data;
}
$this->setMethod($method);
return $this;
}
/**
* 设置方法名
*
* @param string $method
* @return $this
*/
public function setMethod(string $method): Client
{
$this->method = $method;
return $this;
}
/**
* 设置需要POST的文件
*
* @param array $files 需要POST的文件
* @param string $method
* @return $this
*/
public function setFiles(array $files, string $method = self::METHOD_POST): Client
{
if (is_array($this->data)) {
foreach ($files as $name => $file) {
$this->data[$name] = new \CURLFile($file);
}
}
$this->setMethod($method);
return $this;
}
/**
* 设置超时时间
*
* @param integer $timeout 超时时间
* @return $this
*/
public function setTimeout(int $timeout): Client
{
$this->timeout = $timeout;
return $this;
}
/**
* setAgent
*
* @param string $agent
* @return $this
*/
public function setAgent(string $agent): Client
{
$this->agent = $agent;
return $this;
}
/**
* @param bool $multipart
* @return $this
*/
public function setMultipart(bool $multipart): Client
{
$this->multipart = $multipart;
return $this;
}
/**
* @param int $key
* @param mixed $value
* @return $this
*/
public function setOption(int $key, $value): Client
{
$this->options[$key] = $value;
return $this;
}
/**
* 设置头信息参数
*
* @param string $key 参数名称
* @param string $value 参数值
* @return $this
*/
public function setHeader(string $key, string $value): Client
{
$key = str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)));
if ($key == 'User-Agent') {
$this->setAgent($value);
} else {
$this->headers[$key] = $value;
}
return $this;
}
/**
* 发送请求
*
* @param string $url 请求地址
* @throws Exception
*/
public function send(string $url)
{
$params = parse_url($url);
$query = empty($params['query']) ? '' : $params['query'];
if (!empty($this->query)) {
$query = empty($query) ? $this->query : '&' . $this->query;
}
if (!empty($query)) {
$params['query'] = $query;
}
$url = Common::buildUrl($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
if (isset($this->agent)) {
curl_setopt($ch, CURLOPT_USERAGENT, $this->agent);
}
/** 设置header信息 */
if (!empty($this->headers)) {
$headers = [];
foreach ($this->headers as $key => $val) {
$headers[] = $key . ': ' . $val;
}
if (!empty($this->cookies)) {
$headers[] = 'Cookie: ' . str_replace('&', '; ', http_build_query($this->cookies));
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
if (!empty($this->data)) {
$data = $this->data;
if (!$this->multipart) {
curl_setopt($ch, CURLOPT_POST, true);
$data = is_array($data) ? http_build_query($data) : $data;
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) {
$parts = explode(':', $header, 2);
if (count($parts) == 2) {
[$key, $value] = $parts;
$this->responseHeader[strtolower(trim($key))] = trim($value);
}
return strlen($header);
});
foreach ($this->options as $key => $val) {
curl_setopt($ch, $key, $val);
}
$response = curl_exec($ch);
if (false === $response) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception($error, 500);
}
$this->responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->responseBody = $response;
curl_close($ch);
}
/**
* 获取回执的头部信息
*
* @param string $key 头信息名称
* @return string
*/
public function getResponseHeader(string $key): ?string
{
$key = strtolower($key);
return $this->responseHeader[$key] ?? null;
}
/**
* 获取回执代码
*
* @return integer
*/
public function getResponseStatus(): int
{
return $this->responseStatus;
}
/**
* 获取回执身体
*
* @return string
*/
public function getResponseBody(): string
{
return $this->responseBody;
}
/**
* 获取可用的连接
*
* @return ?Adapter
* @return ?Client
*/
public static function get(): ?Adapter
public static function get(): ?Client
{
foreach (self::ADAPTERS as $adapter) {
if (call_user_func([$adapter, 'isAvailable'])) {
return new $adapter();
}
}
return null;
return extension_loaded('curl') ? new static() : null;
}
}

View File

@@ -1,421 +0,0 @@
<?php
namespace Typecho\Http\Client;
use Typecho\Common;
use Typecho\Http\Client;
/**
* 客户端适配器
*
* @author qining
* @category typecho
* @package Http
*/
abstract class Adapter
{
/**
* 方法名
*
* @access protected
* @var string
*/
protected $method = Client::METHOD_GET;
/**
* 传递参数
*
* @access protected
* @var string
*/
protected $query;
/**
* 设置超时
*
* @access protected
* @var string
*/
protected $timeout = 3;
/**
* 需要在body中传递的值
*
* @access protected
* @var array|string
*/
protected $data = [];
/**
* 文件列表
*
* @access protected
* @var array
*/
protected $files = [];
/**
* 头信息参数
*
* @access protected
* @var array
*/
protected $headers = [];
/**
* cookies
*
* @access protected
* @var array
*/
protected $cookies = [];
/**
* 协议名称及版本
*
* @access protected
* @var string
*/
protected $rfc = 'HTTP/1.1';
/**
* 请求地址
*
* @access protected
* @var string
*/
protected $url;
/**
* 主机名
*
* @access protected
* @var string
*/
protected $host;
/**
* 前缀
*
* @access protected
* @var string
*/
protected $scheme = 'http';
/**
* 路径
*
* @access protected
* @var string
*/
protected $path = '/';
/**
* 设置ip
*
* @access protected
* @var string
*/
protected $ip;
/**
* 端口
*
* @access protected
* @var integer
*/
protected $port = 80;
/**
* 回执头部信息
*
* @access protected
* @var array
*/
protected $responseHeader = [];
/**
* 回执代码
*
* @access protected
* @var integer
*/
protected $responseStatus;
/**
* 回执身体
*
* @access protected
* @var string
*/
protected $responseBody;
/**
* 判断适配器是否可用
*
* @access public
* @return boolean
*/
abstract public static function isAvailable(): bool;
/**
* 设置指定的COOKIE值
*
* @access public
* @param string $key 指定的参数
* @param mixed $value 设置的值
* @return $this
*/
public function setCookie(string $key, $value): Adapter
{
$this->cookies[$key] = $value;
return $this;
}
/**
* 设置传递参数
*
* @access public
* @param mixed $query 传递参数
* @return $this
*/
public function setQuery($query): Adapter
{
$query = is_array($query) ? http_build_query($query) : $query;
$this->query = empty($this->query) ? $query : $this->query . '&' . $query;
return $this;
}
/**
* 设置需要POST的数据
*
* @access public
* @param array|string $data 需要POST的数据
* @return $this
*/
public function setData($data): Adapter
{
$this->data = $data;
$this->setMethod(Client::METHOD_POST);
return $this;
}
/**
* 设置方法名
*
* @access public
* @param string $method
* @return $this
*/
public function setMethod(string $method): Adapter
{
$this->method = $method;
return $this;
}
/**
* 设置需要POST的文件
*
* @access public
* @param array $files 需要POST的文件
* @return $this
*/
public function setFiles(array $files): Adapter
{
$this->files = empty($this->files) ? $files : array_merge($this->files, $files);
$this->setMethod(Client::METHOD_POST);
return $this;
}
/**
* 设置超时时间
*
* @access public
* @param integer $timeout 超时时间
* @return $this
*/
public function setTimeout(int $timeout): Adapter
{
$this->timeout = $timeout;
return $this;
}
/**
* 设置http协议
*
* @access public
* @param string $rfc http协议
* @return $this
*/
public function setRfc(string $rfc): Adapter
{
$this->rfc = $rfc;
return $this;
}
/**
* 设置ip地址
*
* @access public
* @param string $ip ip地址
* @return $this
*/
public function setIp(string $ip): Adapter
{
$this->ip = $ip;
return $this;
}
/**
* 发送请求
*
* @access public
* @param string $url 请求地址
* @return string|null
* @throws Exception
*/
public function send(string $url): ?string
{
$params = parse_url($url);
if (!empty($params['host'])) {
$this->host = $params['host'];
} else {
throw new Exception('Unknown Host', 500);
}
if (!in_array($params['scheme'], ['http', 'https'])) {
throw new Exception('Unknown Scheme', 500);
}
if (!empty($params['path'])) {
$this->path = $params['path'];
}
$query = empty($params['query']) ? '' : $params['query'];
if (!empty($this->query)) {
$query = empty($query) ? $this->query : '&' . $this->query;
}
if (!empty($query)) {
$this->path .= '?' . $query;
$params['query'] = $query;
}
$this->scheme = $params['scheme'];
$this->port = ('https' == $params['scheme']) ? 443 : 80;
$url = Common::buildUrl($params);
if (!empty($params['port'])) {
$this->port = $params['port'];
}
/** 整理cookie */
if (!empty($this->cookies)) {
$this->setHeader('Cookie', str_replace('&', '; ', http_build_query($this->cookies)));
}
$response = $this->httpSend($url);
if (!$response) {
return null;
}
str_replace("\r", '', $response);
$rows = explode("\n", $response);
$foundStatus = false;
$foundInfo = false;
$lines = [];
foreach ($rows as $key => $line) {
if (!$foundStatus) {
if (0 === strpos($line, "HTTP/")) {
if ('' == trim($rows[$key + 1])) {
continue;
} else {
$status = explode(' ', str_replace(' ', ' ', $line));
$this->responseStatus = intval($status[1]);
$foundStatus = true;
}
}
} else {
if (!$foundInfo) {
if ('' != trim($line)) {
$status = explode(':', $line);
$name = strtolower(array_shift($status));
$data = implode(':', $status);
$this->responseHeader[trim($name)] = trim($data);
} else {
$foundInfo = true;
}
} else {
$lines[] = $line;
}
}
}
$this->responseBody = implode("\n", $lines);
return $this->responseBody;
}
/**
* 设置头信息参数
*
* @access public
* @param string $key 参数名称
* @param string $value 参数值
* @return $this
*/
public function setHeader(string $key, string $value): Adapter
{
$key = str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)));
$this->headers[$key] = $value;
return $this;
}
/**
* 需要实现的请求方法
*
* @access public
* @param string $url 请求地址
* @return string
*/
abstract protected function httpSend(string $url): string;
/**
* 获取回执的头部信息
*
* @access public
* @param string $key 头信息名称
* @return string
*/
public function getResponseHeader(string $key): ?string
{
$key = strtolower($key);
return $this->responseHeader[$key] ?? null;
}
/**
* 获取回执代码
*
* @access public
* @return integer
*/
public function getResponseStatus(): int
{
return $this->responseStatus;
}
/**
* 获取回执身体
*
* @access public
* @return string
*/
public function getResponseBody(): string
{
return $this->responseBody;
}
}

View File

@@ -1,124 +0,0 @@
<?php
namespace Typecho\Http\Client\Adapter;
use Typecho\Http\Client;
use Typecho\Http\Client\Adapter;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* CURL适配器
*
* @author qining
* @category typecho
* @package Http
*/
class Curl extends Adapter
{
/**
* 判断适配器是否可用
*
* @access public
* @return boolean
*/
public static function isAvailable(): bool
{
return function_exists('curl_version');
}
/**
* 发送请求
*
* @access public
* @param string $url 请求地址
* @return string
* @throws Client\Exception
*/
protected function httpSend(string $url): string
{
$ch = curl_init();
if ($this->ip) {
$url = $this->scheme . '://' . $this->ip . $this->path;
$this->headers['Rfc'] = $this->method . ' ' . $this->path . ' ' . $this->rfc;
$this->headers['Host'] = $this->host;
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_PORT, $this->port);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
/** 设置HTTP版本 */
switch ($this->rfc) {
case 'HTTP/1.0':
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
break;
case 'HTTP/1.1':
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
break;
default:
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
break;
}
/** 设置header信息 */
if (!empty($this->headers)) {
if (isset($this->headers['User-Agent'])) {
curl_setopt($ch, CURLOPT_USERAGENT, $this->headers['User-Agent']);
unset($this->headers['User-Agent']);
}
$headers = [];
if (isset($this->headers['Rfc'])) {
$headers[] = $this->headers['Rfc'];
unset($this->headers['Rfc']);
}
foreach ($this->headers as $key => $val) {
$headers[] = $key . ': ' . $val;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
/** POST模式 */
if (Client::METHOD_POST == $this->method) {
if (!isset($this->headers['content-type'])) {
curl_setopt($ch, CURLOPT_POST, true);
}
if (!empty($this->data)) {
curl_setopt(
$ch,
CURLOPT_POSTFIELDS,
is_array($this->data) ? http_build_query($this->data) : $this->data
);
}
if (!empty($this->files)) {
foreach ($this->files as &$file) {
$file = '@' . $file;
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->files);
}
}
$response = curl_exec($ch);
if (false === $response) {
throw new Client\Exception(curl_error($ch), 500);
}
curl_close($ch);
return $response;
}
}

View File

@@ -1,161 +0,0 @@
<?php
namespace Typecho\Http\Client\Adapter;
use Typecho\Http\Client;
use Typecho\Http\Client\Adapter;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* Socket适配器
*
* @author qining
* @category typecho
* @package Http
*/
class Socket extends Adapter
{
/**
* 判断适配器是否可用
*
* @access public
* @return boolean
*/
public static function isAvailable(): bool
{
return function_exists("fsockopen");
}
/**
* 获取回执身体
*
* @access public
* @return string
*/
public function getResponseBody(): string
{
/** 支持chunked编码 */
if ('chunked' == $this->getResponseHeader('Transfer-Encoding')) {
$parts = explode("\r\n", $this->responseBody, 2);
$counter = hexdec($parts[0]);
$this->responseBody = substr($parts[1], 0, $counter);
}
return $this->responseBody;
}
/**
* 发送请求
*
* @access public
* @param string $url 请求地址
* @return string
* @throws Client\Exception
*/
protected function httpSend(string $url): string
{
$eol = Client::EOL;
$request = $this->method . ' ' . $this->path . ' ' . $this->rfc . $eol;
$request .= 'Host: ' . $this->host . $eol;
$request .= 'Accept: */*' . $eol;
$request .= 'Cache-Control: no-cache' . $eol;
$request .= 'Connection: Close' . $eol;
/** 设置header信息 */
if (!empty($this->headers)) {
foreach ($this->headers as $key => $val) {
$request .= $key . ': ' . $val . $eol;
}
}
/** 发送POST信息 */
if (Client::METHOD_POST == $this->method) {
if (empty($this->files)) {
$content = is_array($this->data) ? http_build_query($this->data) : $this->data;
$request .= 'Content-Length: ' . strlen($content) . $eol;
if (!isset($this->headers['content-type'])) {
$request .= 'Content-Type: application/x-www-form-urlencoded' . $eol;
}
} else {
$boundary = '---------------------------' . substr(md5(uniqid()), 0, 16);
$content = $eol . $boundary;
if (!empty($this->data)) {
foreach ($this->data as $key => $val) {
$content .= $eol . 'Content-Disposition: form-data; name="' . $key . '"' . $eol . $eol;
$content .= $val . $eol;
$content .= $boundary;
}
}
foreach ($this->files as $key => $file) {
$content .= $eol . 'Content-Disposition: form-data; name="' . $key
. '"; filename="' . $file . '"' . $eol;
$content .= 'Content-Type: ' . mime_content_type($file) . $eol . $eol;
$content .= file_get_contents($file) . $eol;
$content .= $boundary;
}
$content .= '--' . $eol;
$request .= 'Content-Length: ' . strlen($content) . $eol;
$request .= 'Content-Type: multipart/form-data; boundary=' . $boundary;
}
$request .= $eol;
$request .= $content;
} else {
$request .= $eol;
}
/** 打开连接 */
$socket = @fsockopen($this->ip ?: $this->host, $this->port, $errno, $errstr, $this->timeout);
if (false === $socket) {
throw new Client\Exception($errno . ':' . $errstr, 500);
}
/** 发送数据 */
fwrite($socket, $request);
stream_set_timeout($socket, $this->timeout);
$response = '';
//facebook code
while (!feof($socket)) {
$buf = fgets($socket, 4096);
if (false === $buf || '' === $buf) {
$info = stream_get_meta_data($socket);
//超时判断
if ($info['timed_out']) {
throw new Client\Exception(
__CLASS__ . ': timeout reading from ' . $this->host . ':' . $this->port,
500
);
} else {
throw new Client\Exception(
__CLASS__ . ': could not read from ' . $this->host . ':' . $this->port,
500
);
}
} elseif (strlen($buf) < 4096) {
$info = stream_get_meta_data($socket);
if ($info['timed_out']) {
throw new Client\Exception(
__CLASS__ . ': timeout reading from ' . $this->host . ':' . $this->port,
500
);
}
}
$response .= $buf;
}
fclose($socket);
return $response;
}
}

View File

@@ -61,7 +61,6 @@ class I18n
* @param string $single 单数形式的翻译
* @param string $plural 复数形式的翻译
* @param integer $number 数字
*
* @return string
*/
public static function ngettext(string $single, string $plural, int $number): string

View File

@@ -167,7 +167,7 @@ class GetText
} else {
$result = $this->cache_translations[$key];
$list = explode(chr(0), $result);
return $list[$select];
return $list[$select] ?? '';
}
} else {
$num = $this->findString($key);
@@ -176,7 +176,7 @@ class GetText
} else {
$result = $this->getTranslationString($num);
$list = explode(chr(0), $result);
return $list[$select];
return $list[$select] ?? '';
}
}
}

View File

@@ -320,10 +320,10 @@ class Plugin
{
switch (true) {
case file_exists($pluginFileName = $path . '/' . $pluginName . '/Plugin.php'):
$className = "{$pluginName}_Plugin";
$className = "\\" . PLUGIN_NAMESPACE . "\\{$pluginName}\\Plugin";
break;
case file_exists($pluginFileName = $path . '/' . $pluginName . '.php'):
$className = $pluginName;
$className = "\\" . PLUGIN_NAMESPACE . "\\" . $pluginName;
break;
default:
throw new PluginException('Missing Plugin ' . $pluginName, 404);
@@ -388,9 +388,9 @@ class Plugin
* 设置回调函数
*
* @param string $component 当前组件
* @param mixed $value 回调函数
* @param callable $value 回调函数
*/
public function __set(string $component, $value)
public function __set(string $component, callable $value)
{
$weight = 0;

View File

@@ -136,6 +136,7 @@ class Request
public function get(string $key, $default = null, ?bool &$exists = true)
{
$exists = true;
$value = null;
switch (true) {
case isset($this->params) && isset($this->params[$key]):
@@ -145,7 +146,6 @@ class Request
if (isset($this->sandbox[$key])) {
$value = $this->sandbox[$key];
} else {
$value = $default;
$exists = false;
}
break;
@@ -156,7 +156,6 @@ class Request
$value = $_POST[$key];
break;
default:
$value = $default;
$exists = false;
break;
}
@@ -166,7 +165,34 @@ class Request
$this->params = null;
}
return ((!is_array($value) && strlen($value) > 0) || is_array($default)) ? $value : $default;
if (isset($value)) {
return is_array($default) == is_array($value) ? $value : $default;
} else {
return $default;
}
}
/**
* 获取实际传递参数(magic)
*
* @param string $key 指定参数
* @return mixed
*/
public function __get(string $key)
{
return $this->get($key);
}
/**
* 判断参数是否存在
*
* @param string $key 指定参数
* @return boolean
*/
public function __isset(string $key)
{
$this->get($key, null, $exists);
return $exists;
}
/**
@@ -177,9 +203,13 @@ class Request
*/
public function getArray($key): array
{
$result = $this->get($key, []);
$result = $this->get($key, [], $exists);
return is_array($result) ? $result : [$result];
if (!empty($result) || !$exists) {
return $result;
}
return [$this->get($key)];
}
/**
@@ -275,7 +305,7 @@ class Request
return $this->pathInfo;
}
//参考Zend Framework对pahtinfo的处理, 更好的兼容性
//参考Zend Framework对pathinfo的处理, 更好的兼容性
$pathInfo = null;
//处理requestUri
@@ -439,7 +469,7 @@ class Request
*/
public function isAjax(): bool
{
return 'XMLHttpRequest' == $this->getServer('HTTP_X_REQUESTED_WITH');
return 'XMLHttpRequest' == $this->getHeader('X-Requested-With');
}
/**

View File

@@ -203,12 +203,13 @@ class Response
[$key, $value, $timeout, $path, $domain] = $cookie;
if ($timeout > 0) {
$timeout += time();
$now = time();
$timeout += $timeout > $now - 86400 ? 0 : $now;
} elseif ($timeout < 0) {
$timeout = 1;
}
setrawcookie($key, rawurlencode($value), $timeout, $path, $domain);
setrawcookie($key, rawurlencode($value), $timeout, $path, $domain ?? '');
}
}

View File

@@ -221,7 +221,7 @@ class Validate
* @access public
*
* @param string $key 数值键值
* @param string|array $rule 规则名称
* @param string|callable $rule 规则名称
* @param string $message 错误字符串
*
* @return $this
@@ -271,12 +271,12 @@ class Validate
foreach ($rules as $key => $rule) {
$this->key = $key;
$data[$key] = (is_array($data[$key]) ? 0 == count($data[$key])
: 0 == strlen($data[$key])) ? null : $data[$key];
: 0 == strlen($data[$key] ?? '')) ? null : $data[$key];
foreach ($rule as $params) {
$method = $params[0];
if ('required' != $method && 'confirm' != $method && 0 == strlen($data[$key])) {
if ('required' != $method && 'confirm' != $method && 0 == strlen($data[$key] ?? '')) {
continue;
}
@@ -284,7 +284,7 @@ class Validate
$params[1] = $data[$key];
$params = array_slice($params, 1);
if (!call_user_func_array(is_array($method) ? $method : [$this, $method], $params)) {
if (!call_user_func_array(is_callable($method) ? $method : [$this, $method], $params)) {
$result[$key] = $message;
break;
}
@@ -319,11 +319,9 @@ class Validate
*
* @access public
*
* @param string|null $str 待处理的字符串
*
* @return boolean
*/
public function required(?string $str): bool
public function required(): bool
{
return !empty($this->data[$this->key]);
}

View File

@@ -190,19 +190,24 @@ abstract class Widget
/**
* alloc widget instance with alias
*
* @param string $alias
* @param string|null $alias
* @param mixed $params
* @param mixed $request
* @param bool|callable $disableSandboxOrCallback
* @return $this
*/
public static function allocWithAlias(
string $alias,
?string $alias,
$params = null,
$request = null,
$disableSandboxOrCallback = true
): Widget {
return self::widget(static::class . '@' . $alias, $params, $request, $disableSandboxOrCallback);
return self::widget(
static::class . (isset($alias) ? '@' . $alias : ''),
$params,
$request,
$disableSandboxOrCallback
);
}
/**

View File

@@ -204,9 +204,9 @@ abstract class Element extends Layout
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
abstract public function input(?string $name = null, ?array $options = null): Layout;
abstract public function input(?string $name = null, ?array $options = null): ?Layout;
/**
* 设置表单元素值

View File

@@ -31,9 +31,9 @@ class Checkbox extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
foreach ($options as $value => $label) {
$this->options[$value] = new Layout('input');
@@ -51,7 +51,7 @@ class Checkbox extends Element
$this->container($item);
}
return current($this->options);
return current($this->options) ?: null;
}
/**

View File

@@ -47,9 +47,9 @@ class Fake extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('input');
$this->inputs[] = $input;

View File

@@ -36,9 +36,9 @@ class Hidden extends Element
* @access public
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('input', ['name' => $name, 'type' => 'hidden']);
$this->container($input);

View File

@@ -24,9 +24,9 @@ class Password extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('input', ['id' => $name . '-0-' . self::$uniqueId,
'name' => $name, 'type' => 'password', 'class' => 'password']);

View File

@@ -31,9 +31,9 @@ class Radio extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
foreach ($options as $value => $label) {
$this->options[$value] = new Layout('input');
@@ -51,7 +51,7 @@ class Radio extends Element
$this->container($item);
}
return current($this->options);
return current($this->options) ?: null;
}
/**

View File

@@ -31,9 +31,9 @@ class Select extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('select');
$this->container($input->setAttribute('name', $name)

View File

@@ -24,9 +24,9 @@ class Submit extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$this->setAttribute('class', 'typecho-option typecho-option-submit');
$input = new Layout('button', ['type' => 'submit']);

View File

@@ -24,9 +24,9 @@ class Text extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('input', ['id' => $name . '-0-' . self::$uniqueId,
'name' => $name, 'type' => 'text', 'class' => 'text']);
@@ -44,6 +44,10 @@ class Text extends Element
*/
protected function inputValue($value)
{
$this->input->setAttribute('value', htmlspecialchars($value));
if (isset($value)) {
$this->input->setAttribute('value', htmlspecialchars($value));
} else {
$this->input->removeAttribute('value');
}
}
}

View File

@@ -24,9 +24,9 @@ class Textarea extends Element
*
* @param string|null $name 表单元素名称
* @param array|null $options 选择项
* @return Layout
* @return Layout|null
*/
public function input(?string $name = null, ?array $options = null): Layout
public function input(?string $name = null, ?array $options = null): ?Layout
{
$input = new Layout('textarea', ['id' => $name . '-0-' . self::$uniqueId, 'name' => $name]);
$this->label->setAttribute('for', $name . '-0-' . self::$uniqueId);

View File

@@ -186,8 +186,8 @@ class Response
}
$this->redirect($referer);
} elseif (!empty($default)) {
$this->redirect($default);
} else {
$this->redirect($default ?: '/');
}
}

View File

@@ -1453,10 +1453,10 @@ class HyperDown
foreach ($lines as $key => $line) {
if (preg_match("/^(\s{" . $space . "})((?:[0-9]+\.?)|\-|\+|\*)(\s+)(.*)$/i", $line, $matches)) {
if ($type == 'ol' && $key == 0) {
$start = intval($matches[2]);
$olStart = intval($matches[2]);
if ($start != 1) {
$suffix = ' start="' . $start . '"';
if ($olStart != 1) {
$suffix = ' start="' . $olStart . '"';
}
}

View File

@@ -352,12 +352,12 @@ Typecho_Date::setTimezoneOffset($options->timezone);
$uploadDir = Common::url(Upload::UPLOAD_DIR, __TYPECHO_ROOT_DIR__);
if (is_dir($uploadDir)) {
if (!is_writeable($uploadDir)) {
if (!@chmod($uploadDir, 0644)) {
if (!@chmod($uploadDir, 0755)) {
throw new \Typecho\Widget\Exception(_t('上传目录无法写入, 请手动将安装目录下的 %s 目录的权限设置为可写然后继续升级', Upload::UPLOAD_DIR));
}
}
} else {
if (!@mkdir($uploadDir, 0644)) {
if (!@mkdir($uploadDir, 0755)) {
throw new \Typecho\Widget\Exception(_t('上传目录无法创建, 请手动创建安装目录下的 %s 目录, 并将它的权限设置为可写然后继续升级', Upload::UPLOAD_DIR));
}
}

View File

@@ -54,20 +54,17 @@ class Ajax extends BaseOptions implements ActionInterface
$json = json_decode($response, true);
if (!empty($json)) {
[$soft, $version] = explode(' ', $this->options->generator);
$current = explode('/', $version);
$version = $this->options->version;
if (
isset($json['release']) && isset($json['version'])
isset($json['release'])
&& preg_match("/^[0-9\.]+$/", $json['release'])
&& preg_match("/^[0-9\.]+$/", $json['version'])
&& version_compare($json['release'], $current[0], '>=')
&& version_compare($json['version'], $current[1], '>')
&& version_compare($json['release'], $version, '>=')
) {
$result = [
'available' => 1,
'latest' => $json['release'] . '-' . $json['version'],
'current' => $current[0] . '-' . $current[1],
'latest' => $json['release'],
'current' => $version,
'link' => 'http://typecho.org/download'
];
}

View File

@@ -37,7 +37,6 @@ class Archive extends Contents
/**
* 调用的风格文件
*
* @access private
* @var string
*/
private $themeFile;
@@ -45,7 +44,6 @@ class Archive extends Contents
/**
* 风格目录
*
* @access private
* @var string
*/
private $themeDir;
@@ -53,7 +51,6 @@ class Archive extends Contents
/**
* 分页计算对象
*
* @access private
* @var Query
*/
private $countSql;
@@ -61,7 +58,6 @@ class Archive extends Contents
/**
* 所有文章个数
*
* @access private
* @var integer
*/
private $total = false;
@@ -69,7 +65,6 @@ class Archive extends Contents
/**
* 标记是否为从外部调用
*
* @access private
* @var boolean
*/
private $invokeFromOutside = false;
@@ -77,7 +72,6 @@ class Archive extends Contents
/**
* 是否由聚合调用
*
* @access private
* @var boolean
*/
private $invokeByFeed = false;
@@ -85,7 +79,6 @@ class Archive extends Contents
/**
* 当前页
*
* @access private
* @var integer
*/
private $currentPage;
@@ -93,7 +86,6 @@ class Archive extends Contents
/**
* 生成分页的内容
*
* @access private
* @var array
*/
private $pageRow = [];
@@ -101,7 +93,6 @@ class Archive extends Contents
/**
* 聚合器对象
*
* @access private
* @var Feed
*/
private $feed;
@@ -109,7 +100,6 @@ class Archive extends Contents
/**
* RSS 2.0聚合地址
*
* @access private
* @var string
*/
private $feedUrl;
@@ -117,7 +107,6 @@ class Archive extends Contents
/**
* RSS 1.0聚合地址
*
* @access private
* @var string
*/
private $feedRssUrl;
@@ -125,7 +114,6 @@ class Archive extends Contents
/**
* ATOM 聚合地址
*
* @access private
* @var string
*/
private $feedAtomUrl;
@@ -133,7 +121,6 @@ class Archive extends Contents
/**
* 本页关键字
*
* @access private
* @var string
*/
private $keywords;
@@ -141,7 +128,6 @@ class Archive extends Contents
/**
* 本页描述
*
* @access private
* @var string
*/
private $description;
@@ -149,7 +135,6 @@ class Archive extends Contents
/**
* 聚合类型
*
* @access private
* @var string
*/
private $feedType;
@@ -157,7 +142,6 @@ class Archive extends Contents
/**
* 聚合类型
*
* @access private
* @var string
*/
private $feedContentType;
@@ -165,7 +149,6 @@ class Archive extends Contents
/**
* 当前feed地址
*
* @access private
* @var string
*/
private $currentFeedUrl;
@@ -173,15 +156,20 @@ class Archive extends Contents
/**
* 归档标题
*
* @access private
* @var string
*/
private $archiveTitle = null;
/**
* 归档地址
*
* @var string|null
*/
private $archiveUrl = null;
/**
* 归档类型
*
* @access private
* @var string
*/
private $archiveType = 'index';
@@ -189,7 +177,6 @@ class Archive extends Contents
/**
* 是否为单一归档
*
* @access private
* @var string
*/
private $archiveSingle = false;
@@ -392,6 +379,22 @@ class Archive extends Contents
$this->archiveType = $archiveType;
}
/**
* @return string|null
*/
public function getArchiveUrl(): ?string
{
return $this->archiveUrl;
}
/**
* @param string|null $archiveUrl
*/
public function setArchiveUrl(?string $archiveUrl): void
{
$this->archiveUrl = $archiveUrl;
}
/**
* @return string|null
*/
@@ -528,6 +531,16 @@ class Archive extends Contents
return $this->currentPage;
}
/**
* _currentPage
*
* @return int
*/
public function ____currentPage(): int
{
return $this->getCurrentPage();
}
/**
* 获取页数
*
@@ -663,7 +676,7 @@ class Archive extends Contents
}
/** 初始化分页变量 */
$this->currentPage = $this->request->page ?? 1;
$this->currentPage = $this->request->filter('int')->page ?? 1;
$hasPushed = false;
/** select初始化 */
@@ -716,6 +729,7 @@ class Archive extends Contents
$this->feedAtomUrl = $this->options->feedAtomUrl;
$this->keywords = $this->options->keywords;
$this->description = $this->options->description;
$this->archiveUrl = $this->options->siteUrl;
if (isset($handles[$this->parameter->type])) {
$handle = $handles[$this->parameter->type];
@@ -747,6 +761,11 @@ class Archive extends Contents
$select->order('table.contents.created', Db::SORT_DESC)
->page($this->currentPage, $this->parameter->pageSize);
$this->query($select);
/** 处理超出分页的情况 */
if ($this->currentPage > 1 && !$this->have()) {
throw new WidgetException(_t('请求的地址不存在'), 404);
}
}
/**
@@ -1270,7 +1289,7 @@ class Archive extends Contents
if ($return) {
return $value;
} else {
echo htmlspecialchars($value);
echo htmlspecialchars($value ?? '');
}
}
@@ -1406,13 +1425,19 @@ class Archive extends Contents
*/
public function feed()
{
$this->feed->setSubTitle($this->description);
$this->feed->setFeedUrl($this->currentFeedUrl);
if ($this->feedType == Feed::RSS1) {
$feedUrl = $this->feedRssUrl;
} elseif ($this->feedType == Feed::ATOM1) {
$feedUrl = $this->feedAtomUrl;
} else {
$feedUrl = $this->feedUrl;
}
$this->feed->setBaseUrl(('/' == $this->request->feed || 0 == strlen($this->request->feed)
|| '/comments' == $this->request->feed || '/comments/' == $this->request->feed) ?
$this->options->siteUrl : Common::url($this->request->feed, $this->options->index));
$this->feed->setFeedUrl($this->request->makeUriByRequest());
$this->checkPermalink($feedUrl);
$this->feed->setSubTitle($this->description);
$this->feed->setFeedUrl($feedUrl);
$this->feed->setBaseUrl($this->archiveUrl);
if ($this->is('single') || 'comments' == $this->parameter->type) {
$this->feed->setTitle(_t(
@@ -1473,7 +1498,7 @@ class Archive extends Contents
'date' => $this->created,
'link' => $this->permalink,
'author' => $this->author,
'excerpt' => $this->description,
'excerpt' => $this->___description(),
'comments' => $this->commentsNum,
'commentsFeedUrl' => $feedUrl,
'suffix' => $suffix
@@ -1572,28 +1597,32 @@ class Archive extends Contents
/**
* 检查链接是否正确
*
* @param string|null $permalink
*/
private function checkPermalink()
private function checkPermalink(?string $permalink = null)
{
$type = $this->parameter->type;
if (!isset($permalink)) {
$type = $this->parameter->type;
if (
in_array($type, ['index', 'comment_page', 404])
|| $this->makeSinglePageAsFrontPage // 自定义首页不处理
|| !$this->parameter->checkPermalink
) { // 强制关闭
return;
}
if (
in_array($type, ['index', 'comment_page', 404])
|| $this->makeSinglePageAsFrontPage // 自定义首页不处理
|| !$this->parameter->checkPermalink
) { // 强制关闭
return;
}
if ($this->archiveSingle) {
$permalink = $this->permalink;
} else {
$value = array_merge($this->pageRow, [
'page' => $this->currentPage
]);
if ($this->archiveSingle) {
$permalink = $this->permalink;
} else {
$value = array_merge($this->pageRow, [
'page' => $this->currentPage
]);
$path = Router::url($type, $value);
$permalink = Common::url($path, $this->options->index);
$path = Router::url($type, $value);
$permalink = Common::url($path, $this->options->index);
}
}
$requestUrl = $this->request->getRequestUrl();
@@ -1749,8 +1778,7 @@ class Archive extends Contents
$this->security->protect();
Cookie::set(
'protectPassword_' . $this->request->filter('int')->protectCID,
$this->request->protectPassword,
0
$this->request->protectPassword
);
$isPasswordPosted = true;
@@ -1816,6 +1844,9 @@ class Archive extends Contents
/** 设置归档缩略名 */
$this->archiveSlug = ('post' == $this->type || 'attachment' == $this->type) ? $this->cid : $this->slug;
/** 设置归档地址 */
$this->archiveUrl = $this->permalink;
/** 设置403头 */
if ($this->hidden) {
$this->response->setStatus(403);
@@ -1907,6 +1938,9 @@ class Archive extends Contents
/** 设置归档缩略名 */
$this->archiveSlug = $category['slug'];
/** 设置归档地址 */
$this->archiveUrl = $category['permalink'];
/** 插件接口 */
self::pluginHandle()->categoryHandle($this, $select);
}
@@ -1976,6 +2010,9 @@ class Archive extends Contents
/** 设置归档缩略名 */
$this->archiveSlug = $tag['slug'];
/** 设置归档地址 */
$this->archiveUrl = $tag['permalink'];
/** 插件接口 */
self::pluginHandle()->tagHandle($this, $select);
}
@@ -2032,6 +2069,9 @@ class Archive extends Contents
/** 设置归档缩略名 */
$this->archiveSlug = $author['uid'];
/** 设置归档地址 */
$this->archiveUrl = $author['permalink'];
/** 插件接口 */
self::pluginHandle()->authorHandle($this, $select);
}
@@ -2115,6 +2155,9 @@ class Archive extends Contents
/** ATOM 1.0 */
$this->feedAtomUrl = Router::url($currentRoute, $value, $this->options->feedAtomUrl);
/** 设置归档地址 */
$this->archiveUrl = Router::url($currentRoute, $value, $this->options->index);
/** 插件接口 */
self::pluginHandle()->dateHandle($this, $select);
}
@@ -2146,7 +2189,9 @@ class Archive extends Contents
$select->where("table.contents.password IS NULL OR table.contents.password = ''");
}
$select->where('table.contents.title LIKE ? OR table.contents.text LIKE ?', $searchQuery, $searchQuery)
$op = $this->db->getAdapter()->getDriver() == 'pgsql' ? 'ILIKE' : 'LIKE';
$select->where("table.contents.title {$op} ? OR table.contents.text {$op} ?", $searchQuery, $searchQuery)
->where('table.contents.type = ?', 'post');
}
@@ -2175,6 +2220,9 @@ class Archive extends Contents
/** 设置归档缩略名 */
$this->archiveSlug = $keywords;
/** 设置归档地址 */
$this->archiveUrl = Router::url('search', ['keywords' => $keywords], $this->options->index);
/** 插件接口 */
self::pluginHandle()->searchHandle($this, $select);
}

View File

@@ -4,7 +4,6 @@ namespace Widget;
use Typecho\Config;
use Typecho\Db;
use Typecho\Plugin;
use Typecho\Widget;
if (!defined('__TYPECHO_ROOT_DIR__')) {

View File

@@ -234,6 +234,14 @@ class Comments extends Base implements QueryInterface
*/
public function filter(array $value): array
{
/** 处理默认空值 */
$value['author'] = $value['author'] ?? '';
$value['mail'] = $value['mail'] ?? '';
$value['url'] = $value['url'] ?? '';
$value['ip'] = $value['ip'] ?? '';
$value['agent'] = $value['agent'] ?? '';
$value['text'] = $value['text'] ?? '';
$value['date'] = new Date($value['created']);
return Comments::pluginHandle()->filter($value, $this);
}

View File

@@ -487,6 +487,11 @@ class Contents extends Base implements QueryInterface
*/
public function filter(array $value): array
{
/** 处理默认空值 */
$value['title'] = $value['title'] ?? '';
$value['text'] = $value['text'] ?? '';
$value['slug'] = $value['slug'] ?? '';
/** 取出所有分类 */
$value['categories'] = $this->db->fetchAll($this->db
->select()->from('table.metas')
@@ -494,7 +499,7 @@ class Contents extends Base implements QueryInterface
->where('table.relationships.cid = ?', $value['cid'])
->where('table.metas.type = ?', 'category'), [Rows::alloc(), 'filter']);
$value['category'] = null;
$value['category'] = '';
$value['directory'] = [];
/** 取出第一个分类作为slug条件 */
@@ -585,7 +590,7 @@ class Contents extends Base implements QueryInterface
/** 处理密码保护流程 */
if (
strlen($value['password']) > 0 &&
strlen($value['password'] ?? '') > 0 &&
$value['password'] !== Cookie::get('protectPassword_' . $value['cid']) &&
$value['authorId'] != $this->user->uid &&
!$this->user->pass('editor', true)
@@ -792,7 +797,9 @@ class Contents extends Base implements QueryInterface
*/
public function author(string $item = 'screenName')
{
echo $this->author->{$item};
if ($this->have()) {
echo $this->author->{$item};
}
}
/**
@@ -813,9 +820,9 @@ class Contents extends Base implements QueryInterface
/**
* 文章作者
*
* @return Widget
* @return Users
*/
protected function ___author(): Widget
protected function ___author(): Users
{
return Author::allocWithAlias($this->cid, ['uid' => $this->authorId]);
}

View File

@@ -91,16 +91,16 @@ class Metas extends Base implements QueryInterface
/**
* 获取最大排序
*
* @param mixed $type
* @param string $type
* @param int $parent
* @return integer
* @throws Exception
*/
public function getMaxOrder($type, int $parent = 0): int
public function getMaxOrder(string $type, int $parent = 0): int
{
return $this->db->fetchObject($this->db->select(['MAX(order)' => 'maxOrder'])
->from('table.metas')
->where('type = ? AND parent = ?', 'category', $parent))->maxOrder;
->where('type = ? AND parent = ?', $type, $parent))->maxOrder ?? 0;
}
/**

View File

@@ -275,7 +275,7 @@ class Edit extends Comments implements ActionInterface
}
/** 评论插件接口 */
self::pluginHandle()->edit($comment, $this);
$comment = self::pluginHandle()->edit($comment, $this);
/** 更新评论 */
$this->update($comment, $this->db->sql()->where('coid = ?', $coid));

View File

@@ -37,10 +37,7 @@ class Edit extends PostEdit implements ActionInterface
$this->user->pass('contributor');
/** 获取文章内容 */
if (
(isset($this->request->cid) && 'delete' != $this->request->do
&& 'insert' != $this->request->do) || 'update' == $this->request->do
) {
if (!empty($this->request->cid)) {
$this->db->fetchRow($this->select()
->where('table.contents.type = ?', 'attachment')
->where('table.contents.cid = ?', $this->request->filter('int')->cid)
@@ -344,7 +341,7 @@ class Edit extends PostEdit implements ActionInterface
{
$this->security->protect();
$this->on($this->request->is('do=delete'))->deleteAttachment();
$this->on($this->request->is('do=update'))->updateAttachment();
$this->on($this->have() && $this->request->is('do=update'))->updateAttachment();
$this->on($this->request->is('do=clear'))->clearAttachment();
$this->response->redirect($this->options->adminUrl);
}

View File

@@ -47,7 +47,7 @@ class Edit extends PostEdit implements ActionInterface
$this->user->pass('editor');
/** 获取文章内容 */
if (!empty($this->request->cid) && 'delete' != $this->request->do && 'sort' != $this->request->do) {
if (!empty($this->request->cid)) {
$this->db->fetchRow($this->select()
->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft')
->where('table.contents.cid = ?', $this->request->filter('int')->cid)
@@ -100,7 +100,7 @@ class Edit extends PostEdit implements ActionInterface
self::pluginHandle()->finishPublish($contents, $this);
/** 发送ping */
Service::alloc()->sendPing($this->cid);
Service::alloc()->sendPing($this);
/** 设置提示信息 */
Notice::alloc()->set(

View File

@@ -45,7 +45,7 @@ class Edit extends Contents implements ActionInterface
$this->user->pass('contributor');
/** 获取文章内容 */
if (!empty($this->request->cid) && 'delete' != $this->request->do) {
if (!empty($this->request->cid)) {
$this->db->fetchRow($this->select()
->where('table.contents.type = ? OR table.contents.type = ?', 'post', 'post_draft')
->where('table.contents.cid = ?', $this->request->filter('int')->cid)
@@ -289,7 +289,7 @@ class Edit extends Contents implements ActionInterface
/** 发送ping */
$trackback = array_unique(preg_split("/(\r|\n|\r\n)/", trim($this->request->trackback)));
Service::alloc()->sendPing($this->cid, $trackback);
Service::alloc()->sendPing($this, $trackback);
/** 设置提示信息 */
Notice::alloc()->set('post' == $this->type ?

View File

@@ -216,7 +216,7 @@ class Feedback extends Comments implements ActionInterface
}
}
$expire = $this->options->time + $this->options->timezone + 30 * 24 * 3600;
$expire = 30 * 24 * 3600;
Cookie::set('__typecho_remember_author', $comment['author'], $expire);
Cookie::set('__typecho_remember_mail', $comment['mail'], $expire);
Cookie::set('__typecho_remember_url', $comment['url'], $expire);

View File

@@ -32,6 +32,27 @@ class Init extends Widget
*/
public function execute()
{
/** 初始化exception */
if (!defined('__TYPECHO_DEBUG__') || !__TYPECHO_DEBUG__) {
set_exception_handler(function (\Throwable $exception) {
Response::getInstance()->clean();
ob_end_clean();
ob_start(function ($content) {
Response::getInstance()->sendHeaders();
return $content;
});
if (404 == $exception->getCode()) {
ExceptionHandle::alloc();
} else {
Common::error($exception);
}
exit;
});
}
// init class
define('__TYPECHO_CLASS_ALIASES__', [
'Typecho_Plugin_Interface' => '\Typecho\Plugin\PluginInterface',
@@ -60,11 +81,6 @@ class Init extends Widget
/** 对变量赋值 */
$options = Options::alloc();
/** 检查安装状态 */
if (!defined('__TYPECHO_INSTALL__') && !$options->installed) {
$options->update(['value' => 1], Db::get()->sql()->where('name = ?', 'installed'));
}
/** 语言包初始化 */
if ($options->lang && $options->lang != 'zh_CN') {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
@@ -77,28 +93,7 @@ class Init extends Widget
}
/** cookie初始化 */
Cookie::setPrefix($options->rootUrl);
/** 初始化exception */
if (!defined('__TYPECHO_DEBUG__') || !__TYPECHO_DEBUG__) {
set_exception_handler(function (\Throwable $exception) {
Response::getInstance()->clean();
ob_end_clean();
ob_start(function ($content) {
Response::getInstance()->sendHeaders();
return $content;
});
if (404 == $exception->getCode()) {
ExceptionHandle::alloc();
} else {
Common::error($exception);
}
exit;
});
}
Cookie::setPrefix($options->rootUrl);
/** 初始化路由器 */
Router::setRoutes($options->routingTable);
@@ -114,7 +109,7 @@ class Init extends Widget
Date::setTimezoneOffset($options->timezone);
/** 开始会话, 减小负载只针对后台打开session支持 */
if (!defined('__TYPECHO_INSTALL__') && User::alloc()->hasLogin()) {
if ($options->installed && User::alloc()->hasLogin()) {
@session_start();
}
}

View File

@@ -41,6 +41,14 @@ class Login extends Users implements ActionInterface
$validator = new Validate();
$validator->addRule('name', 'required', _t('请输入用户名'));
$validator->addRule('password', 'required', _t('请输入密码'));
$expire = 30 * 24 * 3600;
/** 记住密码状态 */
if ($this->request->remember) {
Cookie::set('__typecho_remember_remember', 1, $expire);
} elseif (Cookie::get('__typecho_remember_remember')) {
Cookie::delete('__typecho_remember_remember');
}
/** 截获验证异常 */
if ($error = $validator->run($this->request->from('name', 'password'))) {
@@ -56,7 +64,7 @@ class Login extends Users implements ActionInterface
$this->request->name,
$this->request->password,
false,
1 == $this->request->remember ? $this->options->time + $this->options->timezone + 30 * 24 * 3600 : 0
1 == $this->request->remember ? $expire : 0
);
/** 比对密码 */

View File

@@ -33,8 +33,7 @@ class Notice extends Widget
$this->highlight = $theId;
Cookie::set(
'__typecho_notice_highlight',
$theId,
Options::alloc()->time + Options::alloc()->timezone + 86400
$theId
);
}
@@ -52,10 +51,10 @@ class Notice extends Widget
* 设定堆栈每一行的值
*
* @param string|array $value 值对应的键值
* @param string $type 提示类型
* @param string|null $type 提示类型
* @param string $typeFix 兼容老插件
*/
public function set($value, string $type = 'notice', string $typeFix = 'notice')
public function set($value, ?string $type = 'notice', string $typeFix = 'notice')
{
$notice = is_array($value) ? array_values($value) : [$value];
if (empty($type) && $typeFix) {
@@ -64,13 +63,11 @@ class Notice extends Widget
Cookie::set(
'__typecho_notice',
json_encode($notice),
Options::alloc()->time + Options::alloc()->timezone + 86400
json_encode($notice)
);
Cookie::set(
'__typecho_notice_type',
$type,
Options::alloc()->time + Options::alloc()->timezone + 86400
$type
);
}
}

View File

@@ -94,6 +94,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property int $defaultCategory
* @property bool $frontArchive
* @property array $plugins
* @property string $secret
* @property bool $installed
*/
class Options extends Base
{

View File

@@ -87,6 +87,7 @@ RewriteRule ^(.*)$ {$basePath}index.php/$1 [L]
/** 发送一个rewrite地址请求 */
$client->setData(['do' => 'remoteCallback'])
->setHeader('User-Agent', $this->options->generator)
->setHeader('X-Requested-With', 'XMLHttpRequest')
->send(Common::url('/action/ajax', $this->options->siteUrl));
if (200 == $client->getResponseStatus() && 'OK' == $client->getResponseBody()) {
@@ -113,6 +114,7 @@ RewriteRule . {$basePath}index.php [L]
/** 发送一个rewrite地址请求 */
$client->setData(['do' => 'remoteCallback'])
->setHeader('User-Agent', $this->options->generator)
->setHeader('X-Requested-With', 'XMLHttpRequest')
->send(Common::url('/action/ajax', $this->options->siteUrl));
if (200 == $client->getResponseStatus() && 'OK' == $client->getResponseBody()) {

View File

@@ -6,6 +6,7 @@ use Typecho\Common;
use Typecho\Http\Client;
use Typecho\Response;
use Typecho\Widget\Exception;
use Widget\Base\Contents;
use Widget\Base\Options as BaseOptions;
if (!defined('__TYPECHO_ROOT_DIR__')) {
@@ -36,9 +37,14 @@ class Service extends BaseOptions implements ActionInterface
public function sendPingHandle()
{
/** 验证权限 */
$token = $this->request->token;
$token = $this->request->get('token');
$permalink = $this->request->get('permalink');
$title = $this->request->get('title');
$excerpt = $this->request->get('excerpt');
if (!Common::timeTokenValidate($token, $this->options->secret, 3)) {
$response = ['trackback' => [], 'pingback' => []];
if (!Common::timeTokenValidate($token, $this->options->secret, 3) || empty($permalink)) {
throw new Exception(_t('禁止访问'), 403);
}
@@ -51,12 +57,9 @@ class Service extends BaseOptions implements ActionInterface
set_time_limit(30);
}
/** 获取post */
$post = Archive::alloc('type=post', "cid={$this->request->cid}");
if ($post->have() && preg_match_all("|<a[^>]*href=[\"'](.*?)[\"'][^>]*>(.*?)</a>|", $post->text, $matches)) {
$links = array_unique($matches[1]);
$permalinkPart = parse_url($post->permalink);
if (!empty($this->request->pingback)) {
$links = $this->request->getArray('pingback');
$permalinkPart = parse_url($permalink);
/** 发送pingback */
foreach ($links as $url) {
@@ -94,11 +97,13 @@ class Service extends BaseOptions implements ActionInterface
}
if (!empty($xmlrpcUrl)) {
$response['pingback'][] = $url;
try {
$xmlrpc = new \IXR\Client($xmlrpcUrl);
$xmlrpc->pingback->ping($post->permalink, $url);
$xmlrpc->pingback->ping($permalink, $url);
unset($xmlrpc);
} catch (Exception $e) {
} catch (\IXR\Exception $e) {
continue;
}
}
@@ -109,19 +114,20 @@ class Service extends BaseOptions implements ActionInterface
}
/** 发送trackback */
if ($post->have() && !empty($this->request->trackback)) {
$links = array_filter(array_map('trim', explode("\n", $this->request->trackback)));
if (!empty($this->request->trackback)) {
$links = $this->request->getArray('trackback');
foreach ($links as $url) {
$client = Client::get();
$response['trackback'][] = $url;
if ($client) {
try {
$client->setTimeout(5)
->setData([
'blog_name' => $this->options->title . ' &raquo ' . $post->title,
'url' => $post->permalink,
'excerpt' => $post->excerpt
'blog_name' => $this->options->title . ' &raquo ' . $title,
'url' => $permalink,
'excerpt' => $excerpt
])
->send($url);
@@ -130,21 +136,22 @@ class Service extends BaseOptions implements ActionInterface
continue;
}
}
}
}
$this->response->throwJson($response);
}
/**
* 发送pingback
* <code>
* $this->sendPingbacks(365);
* $this->sendPing($post);
* </code>
*
* @param integer $cid 内容id
* @param array|null $trackback trackback的url
* @param Contents $content 内容url
* @param array|null $trackback
*/
public function sendPing($cid, ?array $trackback = null)
public function sendPing(Contents $content, ?array $trackback = null)
{
$this->user->pass('contributor');
@@ -152,10 +159,20 @@ class Service extends BaseOptions implements ActionInterface
try {
$input = [
'do' => 'ping',
'cid' => $cid,
'permalink' => $content->permalink,
'excerpt' => $content->excerpt,
'title' => $content->title,
'token' => Common::timeToken($this->options->secret)
];
if (preg_match_all("|<a[^>]*href=[\"'](.*?)[\"'][^>]*>(.*?)</a>|", $content->content, $matches)) {
$pingback = array_unique($matches[1]);
if (!empty($pingback)) {
$input['pingback'] = $pingback;
}
}
if (!empty($trackback)) {
$input['trackback'] = $trackback;
}
@@ -212,13 +229,13 @@ class Service extends BaseOptions implements ActionInterface
if (!$called) {
Response::getInstance()->addResponder(function () {
if (!empty($self->asyncRequests) && $client = Client::get()) {
if (!empty($this->asyncRequests) && $client = Client::get()) {
try {
$client->setHeader('User-Agent', $this->options->generator)
->setTimeout(2)
->setData([
'do' => 'async',
'requests' => json_encode($self->asyncRequests),
'requests' => json_encode($this->asyncRequests),
'token' => Common::timeToken($this->options->secret)
])
->setMethod(Client::METHOD_POST)

View File

@@ -273,14 +273,10 @@ class Upload extends Contents implements ActionInterface
return true;
}
if (!@mkdir($last)) {
if (!@mkdir($last, 0755)) {
return false;
}
$stat = @stat($last);
$perms = $stat['mode'] & 0007777;
@chmod($last, $perms);
return self::makeUploadDir($path);
}

View File

@@ -268,7 +268,7 @@ class User extends Users
} else {
//防止循环重定向
$this->response->redirect(defined('__TYPECHO_ADMIN__') ? $this->options->loginUrl .
(0 === strpos($this->request->getReferer(), $this->options->loginUrl) ? '' :
(0 === strpos($this->request->getReferer() ?? '', $this->options->loginUrl) ? '' :
'?referer=' . urlencode($this->request->makeUriByRequest())) : $this->options->siteUrl, false);
}
}

View File

@@ -1532,7 +1532,7 @@ class XmlRpc extends Contents implements ActionInterface, Hook
[
'isAdmin' => $this->user->pass('administrator', true),
'url' => $this->options->siteUrl,
'blogid' => '1',
'blogid' => 1,
'blogName' => $this->options->title,
'xmlrpc' => $this->options->xmlRpcUrl
]
@@ -1897,7 +1897,9 @@ EOF;
}
/** 直接把初始化放到这里 */
new Server($api);
$server = new Server($api);
$server->setHook($this);
$server->serve();
}
}