封面图

前些日子在 破袜子@大致 提到 让WordPress4.9在php8.2下正常运行。 在不知不觉间,PHP已经支持到8.X了。

好快,当初刚接触PHP时还是5.6。

恰好周六,恰好服务器搬家。

索性,在AI的帮助下,尝试让梦幻辰风这个运行在 Emlog5.3.1 版本的部落格,体验到php8的快乐。

通过搜索,在 一年又一年的博客 中找到《Emlog 5.3.1 兼容 PHP 8.0 错误修复》这篇博文,甚至于我还看到今年5月我留下的留言:

感谢大佬分享,我目前还是5.3.1,停留在PHP7.4,想着要不要到8.X的版本。

不过博主没有回我,Ta的更新也停留在2022年2月。

无妨,现在开始,结合 一年又一年的博客 的文章内容和我的操作日志,简单记录一下升级历史。

首先:在2016年或2017年左右,梦幻辰风已经修改版本支持到PHP7.X。

所以,以下所有折腾,都是基于PHP7.0时的版本。而修改Emlog5.3.1支持PHP7.0,我是参考的 星知苑 的博文《PHP7下安装Emlog5.3.1》(注:在本文发布时,Ta的站点已经打不开了。)

(注:以下内容好像是CSDN采集的 星知苑 博文,大致方法如下,如果造成影响,后果自负。)

1.修改includeliboption.php,大概11行修改为mysqli

//即:默认MySQL连接方式,mysql或mysqli
const DEFAULT_MYSQLCONN = 'mysql';

改为

const DEFAULT_MYSQLCONN = 'mysqli';
//默认链接方式改为mysqli

2.修改includelibdatabase.php,大概16行删除default:

case 'mysql':
default ://这边需要删除default:

3.修改includelibcache.php,大概195行加大括号

$$row['option_name'] = $row['option_value'];

改为

${$row['option_name']} = $row['option_value'];

4.PHP版本最近更新的比较快,autoload自动加载函数会报错,function.base.php开头的__autoload函数修改如下

spl_autoload_register(function($class) {
$class = strtolower($class);
if (file_exists(EMLOG_ROOT . '/include/model/' . $class . '.php')) {
require_once(EMLOG_ROOT . '/include/model/' . $class . '.php');
} elseif (file_exists(EMLOG_ROOT . '/include/lib/' . $class . '.php')) {
require_once(EMLOG_ROOT . '/include/lib/' . $class . '.php');
} elseif (file_exists(EMLOG_ROOT . '/include/controller/' . $class . '.php')) {
require_once(EMLOG_ROOT . '/include/controller/' . $class . '.php');
} else {
emMsg($class . '加载失败。');
}
});

5.对于部分插件写死了数据库链接方式,需要将 $DB = MySql::getInstance(); 改为 $DB = Database::getInstance(); 等等。

通过上述的修改,Emlog5.3.1(也许)可以支持PHP7.X了。现在,我们将向PHP8进发。

首先,在 init.php 的最开头调用 error_reporting() 设置了错误级别,要排错的话,需要把级别开到 E_ALL

*以下方法来自 一年又一年的博客

1.修改 /include/lib/function.base.php 文件:

  • 修改 __autoload() 函数:
//把 __autoload() 换为匿名函数,然后传给 spl_autoload_register 函数注册
// function __autoload($class) {
spl_autoload_register(function ($class) {
    $class = strtolower($class);
    if (file_exists(EMLOG_ROOT . '/include/model/' . $class . '.php')) {
        require_once(EMLOG_ROOT . '/include/model/' . $class . '.php');
    } elseif (file_exists(EMLOG_ROOT . '/include/lib/' . $class . '.php')) {
        require_once(EMLOG_ROOT . '/include/lib/' . $class . '.php');
    } elseif (file_exists(EMLOG_ROOT . '/include/controller/' . $class . '.php')) {
        require_once(EMLOG_ROOT . '/include/controller/' . $class . '.php');
    } else {
        emMsg($class . '加载失败。');
    }
});
  • 修补 get_magic_quotes_gpc() 函数

在文件开头 function doStripslashes() {} 函数声明前,补充定义纯用于兼容的桩函数 get_magic_quotes_gpc(),恒返回 FALSE。

if (!function_exists('get_magic_quotes_gpc')) {
    function get_magic_quotes_gpc() {
        return false;
    }
}
  • in_array() 前检查 NULL 值
//找到if (!@in_array($actionFunc, $emHooks[$hook])) ,将其修改为:
if (!@$emHooks[$hook] || !@in_array($actionFunc, $emHooks[$hook])) {

2.更新 passwordhash.php 密码函数库

前往 phpass 官网下载最新版本的 passwordhash.php ,替换原本的 /include/lib/passwordhash.php

现在,程序能跑了,但是会有一些小报错,如:

报错提示:Deprecated: Function get_magic_quotes_gpc() is deprecated in /www/include/lib/function.base.php on line 31

需要修改 include/lib/function.base.php 文件,找到 doStripslashes() 函数,修改如下:

function doStripslashes() {
    // 删除对 get_magic_quotes_gpc() 的调用
    // 直接进行 stripslashesDeep 处理
    $_GET = stripslashesDeep($_GET);
    $_POST = stripslashesDeep($_POST);
    $_COOKIE = stripslashesDeep($_COOKIE);
    $_REQUEST = stripslashesDeep($_REQUEST);
}

同时还需要修改 stripslashesDeep() 函数,修改如下:

function stripslashesDeep($value) {
    if (is_array($value)) {
        return array_map('stripslashesDeep', $value);
    } elseif (is_object($value)) {
        $vars = get_object_vars($value);
        foreach ($vars as $key => $data) {
            $value->{$key} = stripslashesDeep($data);
        }
        return $value;
    } else {
        // 添加对 null 值的检查
        return is_string($value) ? stripslashes($value) : $value;
    }
}

同时,元宝AI提示我还有一个完整替代方案:在 init.php最前面添加全局处理。

// 替换 magic_quotes_gpc 功能
if (version_compare(PHP_VERSION, '7.4', '>=')) {
    $process = [&$_GET, &$_POST, &$_COOKIE, &$_REQUEST];
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][stripslashes($k)] = $v;
                $process[] = &$process[$key][stripslashes($k)];
            } else {
                $process[$key][stripslashes($k)] = stripslashes($v);
            }
        }
    }
    unset($process);
}

不过我没试,因为已经解决这个报错了。

不过在网站头部出现了:Notice: Undefined variable 。我看了一下代码如下:

<?php if ($logid): ?>

修改为:

<?php if (isset($logid) && $logid): ?>

即可。

随后,新的报错:

Deprecated: Using ${var} in strings is deprecated, use {$var} instead in /www/content/plugins/tpl_options/tpl_options.php on line 987

这个是emlog5.3.1的插件:模板设置

打开 tpl_options.php第 987 行:

// 旧代码(已弃用):
$values[$tag['tagname']] = "${tag['tagname']} (${tag['usenum']})";

// 新代码(推荐):
$values[$tag['tagname']] = "{$tag['tagname']} ({$tag['usenum']})";

完整修改后的函数:

/**
     * @param array $option
     * @return void
     */
    private function renderTag($option) {
        $tags = Cache::getInstance()->readCache('tags');
        $values = array();
        foreach ($tags as $tag) {
            // 修复 PHP 8.2 弃用的字符串插值语法
            $values[$tag['tagname']] = "{$tag['tagname']} ({$tag['usenum']})";
        }
        $option['values'] = $values;
        $this->renderCheckbox($option);
    }

随后,新的报错:

Deprecated: Log_Model::getLogsForHome(): Optional parameter $condition declared before required parameter $perPageNum is implicitly treated as a required parameter in /www/include/model/log_model.php on line 184

修改 log_model.php第 184 行的函数声明:

/**
 * 前台获取文章列表
 *
 * @param int $perPageNum
 * @param string $condition
 * @param int $page
 * @return array
 */
function getLogsForHome($perPageNum, $condition = '', $page = 1) {
    $timezone = Option::get('timezone');
    $start_limit = !empty($page) ? ($page - 1) * $perPageNum : 0;
    $limit = $perPageNum ? "LIMIT $start_limit, $perPageNum" : '';
    $sql = "SELECT * FROM " . DB_PREFIX . "blog WHERE type='blog' and hide='n' and checked='y' $condition $limit";
    $res = $this->db->query($sql);
    $logs = array();
    while ($row = $this->db->fetch_array($res)) {
        $row['date'] += $timezone * 3600;
        $row['log_title'] = htmlspecialchars(trim($row['title']));
        $row['log_url'] = Url::log($row['gid']);
        $row['logid'] = $row['gid'];
        $cookiePassword = isset($_COOKIE['em_logpwd_' . $row['gid']]) ? addslashes(trim($_COOKIE['em_logpwd_' . $row['gid']])) : '';
        if (!empty($row['password']) && $cookiePassword != $row['password']) {
            $row['excerpt'] = '<p>[该文章已设置加密,请点击标题输入密码访问]</p>';
        } else {
            if (!empty($row['excerpt'])) {
                $row['excerpt'] .= '';
                //$row['excerpt'] .= '<p class="readmore"><a href="/jump.php?url=' . Url::log($row['logid']) . '">阅读全文>></a></p>';
            }
        }
        $row['log_description'] = empty($row['excerpt']) ? breakLog($row['content'], $row['gid']) : $row['excerpt'];
        $row['attachment'] = '';
        $row['tag'] = '';
        $row['tbcount'] = 0;//兼容未删除引用的模板
        $logs[] = $row;
    }
    return $logs;
}

然后需要搜索整个代码库,找到所有调用 getLogsForHome函数的地方,并调整参数顺序。

参照如下:

// 旧调用方式:
$logs = $Log_Model->getLogsForHome('', $page, Option::get('index_lognum'));

// 新调用方式:
$logs = $Log_Model->getLogsForHome(Option::get('index_lognum'), '', $page);

当然,我没这么干,因为太多了。

我选择保持向后兼容,可以修改函数签名但不改变参数顺序:

/**
 * 前台获取文章列表
 *
 * @param string $condition
 * @param int $page
 * @param int|null $perPageNum
 * @return array
 */
function getLogsForHome($condition = '', $page = 1, $perPageNum = null) {
    // 如果 $perPageNum 为 null,使用默认值
    if ($perPageNum === null) {
        $perPageNum = Option::get('index_lognum') ?: 10;
    }

    $timezone = Option::get('timezone');
    $start_limit = !empty($page) ? ($page - 1) * $perPageNum : 0;
    $limit = $perPageNum ? "LIMIT $start_limit, $perPageNum" : '';
    $sql = "SELECT * FROM " . DB_PREFIX . "blog WHERE type='blog' and hide='n' and checked='y' $condition $limit";
    $res = $this->db->query($sql);
    $logs = array();
    while ($row = $this->db->fetch_array($res)) {
        $row['date'] += $timezone * 3600;
        $row['log_title'] = htmlspecialchars(trim($row['title']));
        $row['log_url'] = Url::log($row['gid']);
        $row['logid'] = $row['gid'];
        $cookiePassword = isset($_COOKIE['em_logpwd_' . $row['gid']]) ? addslashes(trim($_COOKIE['em_logpwd_' . $row['gid']])) : '';
        if (!empty($row['password']) && $cookiePassword != $row['password']) {
            $row['excerpt'] = '<p>[该文章已设置加密,请点击标题输入密码访问]</p>';
        } else {
            if (!empty($row['excerpt'])) {
                $row['excerpt'] .= '';
            }
        }
        $row['log_description'] = empty($row['excerpt']) ? breakLog($row['content'], $row['gid']) : $row['excerpt'];
        $row['attachment'] = '';
        $row['tag'] = '';
        $row['tbcount'] = 0;
        $logs[] = $row;
    }
    return $logs;
}

反正没报错了。

然后发现发邮件的插件 kl_sendmail 报错了,而且发送不了邮件。

于是,更新 PHPMailer 的版本,直接更新到7.0.1,将src中所有的文件上传到该插件的目录的class文件中,修改 kl_sendmail.php 文件代码如下:

<?php
/*
Plugin Name: Sendmail
Version: 3.8
Plugin URL: (链接已失效)
Description: 发送博客留言至E-mail。
Author: 作者:KLLER 
Author Email: kller@foxmail.com
Author URL: (链接已失效)
*/

!defined('EMLOG_ROOT') && exit('access deined!');

// 使用命名空间引入PHPMailer类
use PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerSMTP;
use PHPMailerPHPMailerException;

require_once(EMLOG_ROOT.'/content/plugins/kl_sendmail/class/PHPMailer.php');
require_once(EMLOG_ROOT.'/content/plugins/kl_sendmail/class/SMTP.php');
require_once(EMLOG_ROOT.'/content/plugins/kl_sendmail/class/Exception.php');

function UBB($content){
    $content=preg_replace('!【链接:(.*)】!uU',"<br /><a href="/jump.php?url=/jump.php?url=\1" target="_blank" rel="nofollow">\1</a><br />",$content);
    $content=preg_replace('!【图片链接:(.*)】!uU',"",$content);
    $content=preg_replace('!【图片地址:(.*)】!uU',"",$content);
    $content=preg_replace('!【图片:(.*)】!uU',"",$content);
    $content=preg_replace("!【隐藏评论】([sS]*?)【/隐藏评论】!uU","(小声说)<br /><i>\1</i>",$content);
    $content=preg_replace("!【隐藏内容】([sS]*?)【/隐藏内容】!uU","(小声说)<br /><i>\1</i>",$content);
    $content=preg_replace("!【隐藏内容:([sS]*?)】!uU","(小声说)<br /><i>\1</i>",$content);
    $content=preg_replace("!【隐藏信息:([sS]*?)】!uU","(小声说)<br /><i>\1</i>",$content);
    $content=preg_replace("!【隐藏:([sS]*?)】!uU","(小声说)<br /><i>\1</i>",$content);
    return $content;
}

function kl_sendmail_do($mailserver, $port, $mailuser, $mailpass, $mailto, $subject, $content, $fromname)
{
    try {
        $mail = new PHPMailer(true);

        // 字符设置
        $mail->CharSet = "UTF-8";
        $mail->Encoding = "base64";

        // 服务器设置
        if(KL_MAIL_SENDTYPE == 1) {
            $mail->isSMTP();
        } else {
            $mail->isMail();
        }

        $mail->Host = $mailserver;
        $mail->Port = $port;
        $mail->SMTPAuth = true;
        $mail->Username = $mailuser;
        $mail->Password = $mailpass;

        // SSL/TLS设置
        if($mailserver == 'smtp.gmail.com' || $mailserver == KL_MAIL_SMTP) {
            if($port == 465) {
                $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // SSL
            } else {
                $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // TLS
            }
        }

        // 发件人
        $mail->setFrom($mailuser, $fromname);

        // 收件人
        $mail->addAddress($mailto);

        // 内容
        $mail->isHTML(true);
        $mail->Subject = $subject;
        $mail->Body = $content;
        $mail->AltBody = strip_tags($content); // 纯文本版本

        // 发送邮件
        $mail->send();
        return true;

    } catch (Exception $e) {
        error_log("邮件发送失败: " . $mail->ErrorInfo);
        return false;
    }
}

function kl_sendmail_get_comment_mail()
{
    include(EMLOG_ROOT.'/content/plugins/kl_sendmail/kl_sendmail_config.php');
    if(KL_IS_SEND_MAIL == 'Y' || KL_IS_REPLY_MAIL == 'Y')
    {
        $comname = isset($_POST['comname']) ? addslashes(trim($_POST['comname'])) : '';
        $comment = isset($_POST['comment']) ? addslashes(trim($_POST['comment'])) : '';
        $commail = isset($_POST['commail']) ? addslashes(trim($_POST['commail'])) : '';
        $comurl = isset($_POST['comurl']) ? addslashes(trim($_POST['comurl'])) : '';
        $gid = isset($_POST['gid']) ? intval($_POST['gid']) : (isset($_GET['gid']) ? intval($_GET['gid']) : -1);
        $pid = isset($_POST['pid']) ? intval($_POST['pid']) : 0;
        $http_referer = empty($_SERVER['HTTP_REFERER']) ? BLOG_URL : $_SERVER['HTTP_REFERER'];

        $blogname = Option::get('blogname');
        $Log_Model = new Log_Model();
        $logData = $Log_Model->getOneLogForHome($gid);
        $log_title = $logData['log_title'];
        $subject = "文章《{$log_title}》收到了新的评论";

        if(!empty($commail)){$commail = $commail;}else{$commail = '未填写';};
        if(!empty($comurl)){$comurl = $comurl;}else{$comurl = '未填写';};

        if(strpos(KL_MAIL_TOEMAIL, '@139.com') === false)
        {
            $content = '这里懒得弄了~';
        }else{
            $content = $comment;
        }

        if(KL_IS_SEND_MAIL == 'Y')
        {
            if(ROLE == 'visitor') kl_sendmail_do(KL_MAIL_SMTP, KL_MAIL_PORT, KL_MAIL_SENDEMAIL, KL_MAIL_PASSWORD, KL_MAIL_TOEMAIL, $subject, $content, $blogname);
        }

        if(KL_IS_REPLY_MAIL == 'Y')
        {
            if($pid > 0)
            {
                $DB = Option::EMLOG_VERSION >= '5.3.0' ? Database::getInstance() : MySql::getInstance();
                $Comment_Model = new Comment_Model();
                $pinfo = $Comment_Model->getOneComment($pid);
                if(!empty($pinfo['mail']))
                {
                    $subject = '您在梦幻辰风发表的评论收到了Ta的回复';
                    $content = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><div style="width:99%;">
    <div style="padding:0 15px;color:#111;background-color:#F5FFFA;border:1px solid #d8e3e8;border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-khtml-border-radius:0 0 6px 6px;">
        <p><strong>'.$pinfo['poster'].'</strong>阁下,您之前在《'.$log_title.'》发表的评论:</p>
        <p style="padding:10px;background-color:#F0FFF0;">'.$pinfo['comment'].'</p>
        <p><strong>'.$comname.'</strong> 给您的回复:</p>
        <p style="padding:10px;background-color:#F0F8FF;">'.$comment.'</p>
        <p>您可以直接<a href="/jump.php?url='.Url::log($gid).'#'.$pid.'" target="_blank">点击这里查看原文</a>,与'.$comname.'继续交流。</p>
        <p>感谢您关注 梦幻辰风,本通知由自动信箱发出,请勿直接回复本邮件。</p>
    </div>
</div>
<!-- 邮件版面格式来自挨踢路 -->';
                    $content=UBB($content);
                    kl_sendmail_do(KL_MAIL_SMTP, KL_MAIL_PORT, KL_MAIL_SENDEMAIL, KL_MAIL_PASSWORD, $pinfo['mail'], $subject, $content, $blogname);
                }
            }
        }
    }else{
        return;
    }
}
addAction('comment_saved', 'kl_sendmail_get_comment_mail');

/*
* 微语模块 *
*/
function kl_sendmail_get_twitter_mail($r, $name, $date, $tid)
{
    include(EMLOG_ROOT.'/content/plugins/kl_sendmail/kl_sendmail_config.php');
    if(KL_IS_TWITTER_MAIL == 'Y')
    {
        $DB = Option::EMLOG_VERSION >= '5.3.0' ? Database::getInstance() : MySql::getInstance();
        $blogname = Option::get('blogname');
        $sql = "select a.content, b.username from ".DB_PREFIX."twitter a left join ".DB_PREFIX."user b on b.uid=a.author where a.id={$tid}";
        $res = $DB->query($sql);
        $row = $DB->fetch_array($res);
        $author = $row['username'];
        $twitter = $row['content'];
        $subject = "{$author}发布的碎语收到了新的回复";
        if(strpos(KL_MAIL_TOEMAIL, '@139.com') === false)
        {
            $content = "{$author}发布的碎语:{$twitter}<br /><br />{$name}对碎语的回复:{$r}<br /><br /><strong>=> 现在就前往<a href="/jump.php?url={$_SERVER['HTTP_REFERER']}" target="_blank">碎语页面</a>进行查看</strong><br />";
            $content=UBB($content);
        }else{
            $content = $r;
        }
        if(ROLE == 'visitor') kl_sendmail_do(KL_MAIL_SMTP, KL_MAIL_PORT, KL_MAIL_SENDEMAIL, KL_MAIL_PASSWORD, KL_MAIL_TOEMAIL, $subject, $content, $blogname);
    }
}
addAction('reply_twitter', 'kl_sendmail_get_twitter_mail');

/*
* 回复评论 *
*/
function kl_sendmail_put_reply_mail($commentId, $reply)
{
    global $userData;
    include(EMLOG_ROOT.'/content/plugins/kl_sendmail/kl_sendmail_config.php');
    if(KL_IS_REPLY_MAIL == 'Y')
    {
        $DB = Option::EMLOG_VERSION >= '5.3.0' ? Database::getInstance() : MySql::getInstance();
        $blogname = Option::get('blogname');
        $Comment_Model = new Comment_Model();
        $commentArray = $Comment_Model->getOneComment($commentId);
        extract($commentArray);
        $subject='您在梦幻辰风发表的评论收到了Ta的回复';
        if(strpos($mail, '@139.com') === false)
        {
            $emBlog = new Log_Model();
            $logData = $emBlog->getOneLogForHome($gid);
            $log_title = $logData['log_title'];
            $content =  '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><div style="width:99%;">
    <div style="padding:0 15px;color:#111;background-color:#F5FFFA;border:1px solid #d8e3e8;border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;-webkit-border-radius:0 0 6px 6px;-khtml-border-radius:0 0 6px 6px;">
        <p><strong>'.$poster.'</strong>阁下,您之前在《'.$log_title.'》发表的评论:</p>
        <p style="padding:10px;background-color:#F0FFF0;">'.$comment.'</p>
        <p><strong>'.$userData['username'].'</strong> 给您的回复:</p>
        <p style="padding:10px;background-color:#F0F8FF;">'.$reply.'</p>
        <p>您可以直接<a href="/jump.php?url='.Url::log($gid).'#'.$pid.'" target="_blank">点击这里查看原文</a>,与'.$userData['username'].'继续交流。</p>
        <p>感谢您关注 梦幻辰风,本通知由自动信箱发出,请勿直接回复本邮件。</p>
    </div>
</div>
<!-- 邮件版面格式来自挨踢路 -->';
            $content=UBB($content);
        }else{
            $content = $reply;
            $content=UBB($content);
        }
        if($mail != '')    kl_sendmail_do(KL_MAIL_SMTP, KL_MAIL_PORT, KL_MAIL_SENDEMAIL, KL_MAIL_PASSWORD, $mail, $subject, $content, $blogname);
    }else{
        return;
    }
}
addAction('comment_reply', 'kl_sendmail_put_reply_mail');

function kl_sendmail_menu()
{
    echo '<div class="sidebarsubmenu" id="kl_sendmail"><a href="/jump.php?url=./plugin.php?plugin=kl_sendmail">邮件设置</a></div>';
}
addAction('adm_sidebar_ext', 'kl_sendmail_menu');
?>

完事。

但是梦幻辰风邮件回复一直失败。

结果发现是今年3月腾讯企业邮箱看我长时间没使用smtp功能,自己给我关了!

……

搞定。

赞助作者

全文完 [ 喜欢本文,打赏作者! ]