PHPword支持导出富文本网络图片,支持SVG导出

1、服务器环境配置要求

php 复制代码
# 安装依赖
yum install -y ImageMagick ImageMagick-devel
# 安装PHP扩展
pecl install imagick
# 重启PHP服务
systemctl restart php-fpm

2、安装 SVG 依赖库

php 复制代码
# CentOS 7/8
yum install -y librsvg2 librsvg2-devel

# 如果是 Debian/Ubuntu
# apt-get install -y librsvg2-bin librsvg2-dev
php 复制代码
<?php
    // 临时图片目录(addHtml会自动下载图片到这里)
    $tempDir = $_SERVER['DOCUMENT_ROOT'] . '/temp/temp_images/';
    // 新增:临时文件路径清单,用于统一清理SVG转换的PNG文件
    $tempFiles = [];

    /**
     * 初始化临时目录
     */
    function initTempDir()
    {
        global $tempDir;
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }
        if (!is_writable($tempDir)) {
            throw new Exception("临时目录不可写:{$tempDir}");
        }
    }

    /**
     * 清理临时目录中的图片文件
     * addHtml会自动下载图片到临时目录,需要手动清理
     */
    function cleanTempDir()
    {
        global $tempDir;
        if (is_dir($tempDir)) {
            $files = glob($tempDir . '/*');
            foreach ($files as $file) {
                if (is_file($file)) {
                    @unlink($file);
                }
            }
        }
    }

    /**
     * 新增:清理SVG转换生成的临时PNG文件
     */
    function cleanSvgTempFiles()
    {
        global $tempFiles;
        foreach ($tempFiles as $key => $file) {
            if (is_file($file)) {
                @unlink($file); // @屏蔽删除不存在文件的警告
            }
            // 从清单移除已处理的路径,防止重复清理
            unset($tempFiles[$key]);
        }
    }

    /**
     * 新增:将SVG图片转换为PNG格式
     * @param string $svgUrl SVG图片URL
     * @param string $savePath 转换后的PNG保存路径
     * @return string|false 转换后的PNG本地路径,失败返回false
     */
    function svgToPng($svgUrl, $savePath)
    {
        global $tempDir, $tempFiles;
        $tempFiles[] = $savePath;
        // 新增:先清空目标文件(避免残留空文件)
        if (file_exists($savePath)) {
            unlink($savePath);
        }

        // 1. 下载 SVG 内容
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $svgUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
        $svgContent = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($curlError || $httpCode != 200 || empty($svgContent)) {
            error_log("SVG下载失败:URL={$svgUrl},HTTP状态码={$httpCode},curl错误={$curlError}");
            return false;
        }

        // ========== 核心修改:移除 SVG 命名空间前缀 ==========
        // 1. 移除所有 ns0: / ns1: 前缀
        $svgContent = preg_replace('/ns[0-9]+:/', '', $svgContent);
        // 2. 清理命名空间声明(保留核心 xmlns)
        $svgContent = preg_replace('/xmlns:ns[0-9]+="[^"]+"/', '', $svgContent);
        // 3. 确保根标签是 <svg> 而不是 <ns0:svg>
        $svgContent = preg_replace('/<\/?ns[0-9]+:svg/', '<$1svg', $svgContent);
        // 4. 修复 href 属性(ns1:href → xlink:href,兼容 SVG 标准)
        $svgContent = str_replace('href="#', 'xlink:href="#', $svgContent);
        // 5. 补充 xlink 命名空间(确保 href 生效)
        if (strpos($svgContent, 'xmlns:xlink') === false) {
            $svgContent = preg_replace('/<svg /', '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ', $svgContent, 1);
        }

        // 2. 使用Imagick转换SVG为PNG
        try {
            $imagick = new \Imagick();
            $imagick->setResolution(300, 300);
            $imagick->setBackgroundColor(new \ImagickPixel('white'));
            // 关键:设置 SVG 解析器(强制使用 RSVG 解析)
            $imagick->setOption('svg:use-rsvg', 'true');

            $imagick->readImageBlob($svgContent);
            $imagick->setImageFormat('png');
            $imagick->writeImage($savePath);

            // 增强校验:文件存在且大小>0
            if (!file_exists($savePath) || filesize($savePath) < 10) { // 至少10字节(避免空文件)
                error_log("PNG文件无效:{$savePath},大小:" . filesize($savePath));
                if (file_exists($savePath)) {
                    unlink($savePath); // 删除空文件
                }
                return false;
            }

            error_log("SVG 转 PNG 成功:生成文件={$savePath},大小=" . filesize($savePath) . "字节");
            $imagick->clear();
            $imagick->destroy();
            return $savePath;
        } catch (Exception $e) {
            error_log("SVG转PNG异常:" . $e->getMessage() . ",URL:" . $svgUrl);
            // 清理临时文件
            if (file_exists($savePath)) {
                unlink($savePath);
            }
            return false;
        }
    }

    /**
     * 修复图片URL中的空格和格式问题
     * @param string $url 原始URL
     * @return string 修复后的URL
     */
    function fixImageUrl($url)
    {
        // 移除URL中的所有空格(包括协议后的空格,如https: // -> https://)
        $url = preg_replace('/\s+/', '', $url);
        // 确保URL是合法的http/https链接
        if (strpos($url, 'http') !== 0) {
            $url = 'https://' . ltrim($url, '/');
        }
        // URL编码特殊字符
        $parsed = parse_url($url);
        if ($parsed && isset($parsed['path'])) {
            $pathParts = explode('/', $parsed['path']);
            $encodedPath = [];
            foreach ($pathParts as $part) {
                $encodedPath[] = rawurlencode($part);
            }
            $url = $parsed['scheme'] . '://' . $parsed['host'] . implode('/', $encodedPath);
            if (isset($parsed['query'])) {
                $url .= '?' . $parsed['query'];
            }
        }
        return $url;
    }

    /**
     * 规范化HTML,修复XML解析问题,确保符合DOMDocument解析要求
     * @param string $html 原始HTML
     * @return string 规范化后的HTML
     */
    /**
     * 规范化HTML,修复XML解析问题,确保符合DOMDocument解析要求
     * 核心优化:修复图片路径格式,确保PhpWord能正确加载图片
     * @param string $html 原始HTML
     * @return string 规范化后的HTML
     */
    function normalizeHtmlForPhpWord($html)
    {
        global $tempDir;
        // 1. 移除控制字符(避免DOM解析报错)
        $html = preg_replace('/[\x00-\x1F\x7F]/', '', $html);

        // 2. 移除换行/制表符(保留单个空格)
        $html = str_replace(["\r\n", "\r", "\n", "\t"], ' ', $html);

        // 3. 合并多余空格
        $html = preg_replace('/\s+/', ' ', $html);

        // 4. 彻底清理所有样式相关内容(关键:完全移除原有样式)
        $html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $html); // 移除style标签
        $html = preg_replace('/\s+(style|font-size|font-family|line-height|font-weight|color)="[^"]*"/i', '', $html); // 移除内联样式属性
        $html = preg_replace('/<font[^>]*>/i', '', $html); // 移除font开始标签
        $html = preg_replace('/<\/font>/i', '', $html); // 移除font结束标签
        $html = preg_replace('/<span[^>]*>/i', '', $html); // 移除span开始标签
        $html = preg_replace('/<\/span>/i', '', $html); // 移除span结束标签

        // 5. 核心修改:为整个HTML包裹统一的样式容器(强制字体/行高)
        $html = '<div style="font-family: 宋体; font-size: 12pt; line-height: 1.5;">' . $html . '</div>';

        // 6. 修复img标签的属性格式(核心:路径格式修复 + 定高不定宽 + 动态高度)
        $html = preg_replace_callback('/<img\s+[^>]*src\s*=\s*["\']?([^"\'>]+)["\']?[^>]*>/i', function ($matches) {
            global $tempDir;
            $imgTag = $matches[0];
            $src = $matches[1];
            // 修复图片URL(清理空格、补全协议)
            $fixedSrc = fixImageUrl($src);

            // ========== 核心:图片路径格式修复 ==========
            // 定义最大/最小高度(动态高度判断)
            $MAX_HEIGHT = 200; // 最大高度px
            $MIN_HEIGHT = 50;  // 最小高度px
            $targetHeight = $MAX_HEIGHT;

            // SVG转PNG逻辑 + 本地路径修复
            $fileExt = strtolower(pathinfo(parse_url($fixedSrc, PHP_URL_PATH), PATHINFO_EXTENSION));
            if ($fileExt === 'svg') {
                $pngFileName = uniqid('svg_') . '.png';
                $pngFilePath = $tempDir . $pngFileName;

                // 确保临时目录存在且可写
                if (!is_dir($tempDir)) {
                    mkdir($tempDir, 0755, true);
                }

                // 转换SVG为PNG
                if (svgToPng($fixedSrc, $pngFilePath)) {
                    // 修复1:获取真实绝对路径(兼容Windows/Linux)
                    $pngRealPath = realpath($pngFilePath);
                    if ($pngRealPath === false) {
                        error_log("SVG转换后的PNG路径无效:{$pngFilePath}");
                        return '';
                    }
                    // 修复2:添加标准file://协议(必须是3个/,兼容不同系统)
                    $fixedSrc = 'file:///' . str_replace(DIRECTORY_SEPARATOR, '/', $pngRealPath);
                    error_log("SVG转换后路径:{$fixedSrc}");
                } else {
                    error_log("SVG转PNG失败,原URL:{$fixedSrc}");
                    return ''; // 转换失败移除图片标签
                }
            } else {
                // 非SVG图片:区分远程URL和本地路径
                if (strpos($fixedSrc, 'http') === 0) {
                    // 远程图片:确保URL格式正确(已通过fixImageUrl修复)
                    error_log("远程图片路径:{$fixedSrc}");
                } else {
                    // 本地图片:修复路径格式
                    // 修复1:拼接网站根目录,获取真实绝对路径
                    $localPath = realpath($_SERVER['DOCUMENT_ROOT'] . '/' . ltrim($fixedSrc, '/'));
                    if ($localPath === false || !file_exists($localPath)) {
                        error_log("本地图片不存在:{$fixedSrc} → 真实路径:{$localPath}");
                        return ''; // 路径无效移除图片标签
                    }
                    // 修复2:添加标准file://协议
                    $fixedSrc = 'file:///' . str_replace(DIRECTORY_SEPARATOR, '/', $localPath);
                    error_log("本地图片修复后路径:{$fixedSrc}");
                }
            }

            // ========== 动态高度计算(定高不定宽) ==========
            $imageInfo = false;
            // 获取图片尺寸(区分file协议和远程URL)
            if (strpos($fixedSrc, 'file://') === 0) {
                // 本地文件:去掉file:///前缀
                $localFile = substr($fixedSrc, 8);
                $imageInfo = @getimagesize($localFile);
            } else if (strpos($fixedSrc, 'http') === 0) {
                // 远程文件:需开启allow_url_fopen
                $imageInfo = @getimagesize($fixedSrc);
            }

            // 计算目标高度
            if ($imageInfo !== false && is_array($imageInfo)) {
                $originalHeight = $imageInfo[1];
                // 原图高度 < 最大高度 → 保留原图高度;否则用最大高度
                $targetHeight = $originalHeight < $MAX_HEIGHT ? $originalHeight : $MAX_HEIGHT;
                // 确保不小于最小高度
                $targetHeight = max($targetHeight, $MIN_HEIGHT);
            } else {
                error_log("无法获取图片尺寸:{$fixedSrc}");
            }

            // ========== 图片标签属性最终调整 ==========
            // 1. 替换src为修复后的路径
            $imgTag = preg_replace('/src\s*=\s*["\']?[^"\'>]+["\']?/i', 'src="' . $fixedSrc . '"', $imgTag);
            // 2. 移除所有width属性(定高不定宽)
            $imgTag = preg_replace('/\s+width\s*=\s*["\']?[^"\'>]+["\']?/i', '', $imgTag);
            // 3. 移除原有height属性,设置动态高度
            $imgTag = preg_replace('/\s+height\s*=\s*["\']?[^"\'>]+["\']?/i', '', $imgTag);
            $imgTag = preg_replace('/(<img[^>]+)\/?>/i', '$1 height="' . $targetHeight . '" />', $imgTag);
            // 4. 确保属性有引号包裹,标签自闭合(XML规范)
            $imgTag = preg_replace('/(\w+)\s*=\s*([^\s>"]+)(?=\s|>)/i', '$1="$2"', $imgTag);
            if (substr(trim($imgTag), -1) != '/') {
                $imgTag = rtrim($imgTag, '>') . ' />';
            }

            return $imgTag;
        }, $html);

        // 7. 修复所有HTML属性的格式(确保属性名=值都有引号)
        $html = preg_replace('/(\w+)\s*=\s*([^\s>"]+)(?=\s|>)/i', '$1="$2"', $html);

        // 8. 清理首尾空格
        return trim($html);
    }

    // 数据校验
    if (!$rs = _get_one_tj('a_ai_fenxi', 'id="' . $detailId . '"')) {
        json('信息发生变化,请刷新页面再试!', 888);
    }
    $rszl = _get_one('a_ziliao', $rs["member_id"], 'member_id');

    // 获取/刷新知识点和统计数据
    $knowledgearr = $rs["knowledgearr"] ? json_decode($rs["knowledgearr"], true) : array();
    $statsarr = $rs["statsarr"] ? json_decode($rs["statsarr"], true) : array();
    if (count($knowledgearr) == 0) {
        $url = 'http://121.89.200.131:5678/get_recommendations?member_id=' . $userid;
        $result = curl_get_al_fenxi($url);
        $array = json_decode($result["data"], true);

        $knowledgearr = $array["knowledge_recommendations"];
        $statsarr = $array["user_stats"];
        // 存储更新后的数据
        $dataArr = array();
        $dataArr["knowledgearr"] = json_encode($knowledgearr, JSON_UNESCAPED_UNICODE);
        $dataArr["statsarr"] = json_encode($statsarr, JSON_UNESCAPED_UNICODE);
        _update('a_ai_fenxi', $dataArr, $rs["id"]);
    }

    if ($rs["download_url_word"]) {
        $infoArray["subjects"]["file"] = $swtBaseImg . $swtWjj . $rs["download_url_word"];
        echo json_encode($infoArray);
        exit;
    }

    // 配置内存限制和PhpWord
    ini_set("memory_limit", "2048M");
    require_once $_SERVER['DOCUMENT_ROOT'] . '/api/vendor/autoload.php';
    $phpWord = new \PhpOffice\PhpWord\PhpWord();
    $section = $phpWord->addSection();

    // ========== 样式定义 ==========
    /**
     * 基础字体样式(微软雅黑)
     * @param string|int $size 字号,默认20pt
     * @param string $color 颜色,默认#000
     * @param bool $bold 是否加粗,默认false
     * @return array
     */
    function addFontStyle($size = '20', $color = '#000', $bold = false)
    {
        return [
            'name' => '微软雅黑',
            'size' => $size,
            'color' => $color,
            'bold' => $bold
        ];
    }

    /**
     * 宋体字体样式
     * @param string|int $size 字号,默认12pt
     * @param string $color 颜色,默认#000
     * @param bool $bold 是否加粗,默认false
     * @return array
     */
    function addFontStylesong($size = '12', $color = '#000', $bold = false)
    {
        return [
            'name' => '宋体',
            'size' => $size,
            'color' => $color,
            'bold' => $bold
        ];
    }

    /**
     * 微软雅黑 + 四号字(14pt)
     * @param string $color 颜色,默认#000
     * @param bool $bold 是否加粗,默认false
     * @return array
     */
    function addFontStyleSimHeiFourth($color = '#000', $bold = false)
    {
        return addFontStyle(14, $color, $bold);
    }

    /**
     * 宋体 + 小四(12pt) + 加粗
     * @param string $color 颜色,默认#000
     * @return array
     */
    function addFontStyleSimSunSmallFourth($color = '#000')
    {
        return addFontStylesong(12, $color, true);
    }

    /**
     * 1.00倍行距段落样式
     * @param string $align 对齐方式,默认left
     * @param int $spaceAfter 段后间距,默认0
     * @return array
     */
    function addParagraphStyleLineHeight1($align = 'left', $spaceAfter = 0)
    {
        return [
            'align' => $align,
            'lineHeight' => 1.00,
            'lineHeightRule' => 'multiple',
            'spaceAfter' => $spaceAfter,
            'spaceBefore' => 0
        ];
    }

    /**
     * 1.5倍行距段落样式
     * @param string $align 对齐方式,默认left
     * @param int $spaceAfter 段后间距,默认0
     * @return array
     */
    function addParagraphStyleLineHeight1_5($align = 'left', $spaceAfter = 0)
    {
        return [
            'align' => $align,
            'lineHeight' => 1.5,
            'lineHeightRule' => 'multiple',
            'spaceAfter' => $spaceAfter,
            'spaceBefore' => 0
        ];
    }

    /**
     * 给HTML文本中第一个<p>标签内的文本添加序号前缀
     * @param string $html 原始HTML文本(需包含<p>标签)
     * @return string 处理后的HTML文本
     */
    function addNumberPrefixToFirstPtag($html, $prefix = '')
    {
        // 空值处理
        if (empty($html) || empty($prefix)) {
            return $html;
        }

        // ========== 优化后的匹配规则 ==========
        // 1. 先清理HTML中的多余空白符(避免干扰匹配)
        $html = preg_replace('/\s+/', ' ', $html);
        $html = trim($html);

        // 2. 优化正则:
        // - 放宽<p>标签匹配范围,兼容各种属性和空白符
        // - 使用更稳定的匹配方式,避免非贪婪匹配提前终止
        $pattern = '/(<p[^>]*>)([^<]*)/i';
        // 替换逻辑:在<p>标签后直接添加前缀(匹配<p>标签 + 标签后第一个非<字符段)
        $html = preg_replace('/<p>(.*?)<\/p>/', '<p>' . $prefix . '$1</p>', $html, 1);

        return $html;
    }

    // 替换所有Html::addHtml调用,添加异常捕获
    function safeAddHtml($section, $html, $htmlOptions)
    {
        try {
            // 先过滤空HTML
            if (trim($html) === '') {
                return;
            }
            \PhpOffice\PhpWord\Shared\Html::addHtml($section, $html, false, $htmlOptions);
        } catch (\Exception $e) {
            error_log("解析HTML失败:" . $e->getMessage() . ",HTML:" . $html);
            // 跳过该段HTML,不中断导出
            $section->addText('【图片加载失败】', addFontStylesong(12, '#ff0000'));
            echo "解析HTML失败:" . $e->getMessage() . ",HTML:" . $html;
            exit();
        }
    }

    // 基础段落样式
    $paragraphStyleCenter = ['align' => 'center', 'spaceAfter' => 20];

    // addHtml配置(仅保留图片样式和基础解析配置)
    $htmlOptions = [
        // 禁用默认样式自动生成,优先使用HTML内的样式
        'useDefaultStyles' => false
    ];

    // 大写字母数组(A-Z)
    $ABC = [];
    for ($i = 65; $i < 91; $i++) {
        $ABC[] = strtoupper(chr($i));
    }

    try {
        // 初始化临时目录
        initTempDir();

        // 禁用DOMDocument的错误提示(解决解析警告)
        libxml_use_internal_errors(true);

        // ========== 文档内容构建 ==========
        // 标题
        $section->addText('博电线下班学员学习监督报告', addFontStyle(20, '#000', true), $paragraphStyleCenter);
        $section->addTextBreak(1);

        // 用户名和报告日期
        $section->addText('用户名:' . $rszl["name"], addFontStyleSimHeiFourth(), addParagraphStyleLineHeight1('right'));
        $section->addText('报告日期:' . $rs["title"], addFontStyleSimHeiFourth(), addParagraphStyleLineHeight1('right'));
        $section->addTextBreak(1);

        // 一、推荐知识点
        $section->addText('一、推荐知识点', addFontStyleSimSunSmallFourth(), addParagraphStyleLineHeight1_5('left'));
        $section->addTextBreak(1);

        $knowledgearr_timu = array();
        foreach ($knowledgearr as $iai => $rsai) {
            $timu_title = '';
            if ($rst = _get_one('php_tiku_timu', $rsai["knowledge_id"])) {
                $xuhaoiai = ($iai + 1) . '、';
                $timu_title = addNumberPrefixToFirstPtag($rst["title"], $xuhaoiai);
                $timu_title = is_body_img_word_style($timu_title, $swtBaseImg);
            }

            // 规范化HTML
            $timu_title = preg_replace('/[\x00-\x1F\x7F]/', '', $timu_title);
            $timu_title = htmlspecialchars_decode($timu_title);
            $timu_title = normalizeHtmlForPhpWord($timu_title);

            // 添加知识点内容
            safeAddHtml($section, $timu_title, $htmlOptions);

            // 收集习题ID
            foreach ($rsai["timus"] as $it => $rst) {
                $knowledgearr_timu[] = $rst;
            }
        }

        // 二、推荐习题
        $section->addText('二、推荐习题', addFontStyleSimSunSmallFourth(), addParagraphStyleLineHeight1_5('left'));

        $knowledgearr_timu_xuan = array();
        $itmjie = 0;
        foreach ($knowledgearr_timu as $itm => $rstms) {
            if ($rstm = _get_one_tj('php_tiku_timu', 'id=' . $rstms["timu_id"] . ' and del=0 and types!=5')) {
                // 处理题干

                $titleitm = ($itm + 1) . '、';
                $timu_txt = addNumberPrefixToFirstPtag($rstm["title"], $titleitm);
                $timu_txt = is_body_img_word_style($timu_txt, $swtBaseImg);
                $timu_txt = preg_replace('/[\x00-\x1F\x7F]/', '', $timu_txt);
                $timu_txt = htmlspecialchars_decode($timu_txt);
                $timu_txt = normalizeHtmlForPhpWord($timu_txt);

                // 添加题干
                safeAddHtml($section, $timu_txt, $htmlOptions);

                // 处理选项/答案
                if ($rstm["types"] != 3) {
                    $daan_txt = array();
                    if ($rstm["types"] != 2 && $rstm["types"] != 4) {
                        $arrx = _getallCachedByTj3('php_tiku_xuanxiang', "timu_id='" . $rstm["id"] . "'");
                        // 初始化daan5数组(修复未定义警告)
                        $daan5 = [];
                        foreach ($arrx as $ix => $rsx) {
                            $daan5[] = $rsx["title"];
                            if ($rstm["types"] != 5) {
                                // 标记正确答案
                                if ($rsx["correct"] == 1) {
                                    $daan_txt[] = $ABC[$ix];
                                }

                                // 处理选项内容
                                $xuanfuhao = $ABC[$ix] . ':';
                                $xuantitle = addNumberPrefixToFirstPtag($rsx["title"], $xuanfuhao);
                                $xuantitle = is_body_img_word_style($xuantitle, $swtBaseImg);
                                $xuantitle = preg_replace('/[\x00-\x1F\x7F]/', '', $xuantitle);
                                $xuantitle = htmlspecialchars_decode($xuantitle);
                                $xuantitle = normalizeHtmlForPhpWord($xuantitle);

                                // 添加选项
                                safeAddHtml($section, $xuantitle, $htmlOptions);
                            }
                        }

                        // 存储答案
                        if ($rstm["types"] == 5) {
                            $title_5 = replaceContentsInBrackets($timu_txt, $daan5);
                            $knowledgearr_timu_xuan[$itmjie]["daan"] = $title_5;
                        } else {
                            $knowledgearr_timu_xuan[$itmjie]["daan"] = implode('', $daan_txt);
                        }
                    } elseif ($rstm["types"] == 2 || $rstm["types"] == 4) {
                        // 判断题/问答题选项
                        $section->addText(
                            '选项A:正确',
                            addFontStylesong(12), // 宋体 + 小四(12pt)
                            addParagraphStyleLineHeight1_5('left') // 1.5倍行高 + 左对齐
                        );
                        $section->addText(
                            '选项B:错误',
                            addFontStylesong(12), // 宋体 + 小四(12pt)
                            addParagraphStyleLineHeight1_5('left') // 1.5倍行高 + 左对齐
                        );

                        if ($rstm["types"] == 4) {
                            $knowledgearr_timu_xuan[$itmjie]["daan"] = 'A';
                            $xwenda_txt = is_body_img_word_style($rstm["wenda_txt"], $swtBaseImg);
                            $xwenda_txt = preg_replace('/[\x00-\x1F\x7F]/', '', $xwenda_txt);
                            $xwenda_txt = htmlspecialchars_decode($xwenda_txt);
                            $xwenda_txt = normalizeHtmlForPhpWord($xwenda_txt);

                            safeAddHtml($section, $xwenda_txt, $htmlOptions);

                        } elseif ($rstm["types"] == 2) {
                            $knowledgearr_timu_xuan[$itmjie]["daan"] = $rstm["tie_types3"] == 1 ? 'A' : 'B';
                        }
                    }
                }

                // 存储解析和视频信息
                $knowledgearr_timu_xuan[$itmjie]["jiexi_txt"] = $rstm["jiexi_txt"];
                $knowledgearr_timu_xuan[$itmjie]["media_id_txt"] = $rstm["media_id"] ? '有' : '无';

                $itmjie++;
            }
        }

        // 三、答案及解析情况
        $section->addText('三、答案及解析情况', addFontStyleSimSunSmallFourth(), addParagraphStyleLineHeight1_5('left'));

        foreach ($knowledgearr_timu_xuan as $itmx => $rstmsx) {
            // 解析标题
            $section->addText(
                $itmx + 1 . '、解析:',
                addFontStylesong(12), // 宋体 + 小四(12pt)
                addParagraphStyleLineHeight1_5('left') // 1.5倍行高 + 左对齐
            );
            // 答案
            $section->addText(
                '答案:' . $rstmsx["daan"],
                addFontStylesong(12), // 宋体 + 小四(12pt)
                addParagraphStyleLineHeight1_5('left') // 1.5倍行高 + 左对齐
            );

            // 解析内容
            $jiexifuhao = '文本解析:';
            $jiexi_txt = addNumberPrefixToFirstPtag($rstmsx["jiexi_txt"], $jiexifuhao);
            $jiexi_txt = is_body_img_word_style($jiexi_txt, $swtBaseImg);
            $jiexi_txt = preg_replace('/[\x00-\x1F\x7F]/', '', $jiexi_txt);
            $jiexi_txt = htmlspecialchars_decode($jiexi_txt);
            $jiexi_txt = normalizeHtmlForPhpWord($jiexi_txt);

            safeAddHtml($section, $jiexi_txt, $htmlOptions);

            // 视频信息
            $section->addText(
                '是否有解析视频:' . $rstmsx["media_id_txt"],
                addFontStylesong(12), // 宋体 + 小四(12pt)
                addParagraphStyleLineHeight1_5('left') // 1.5倍行高 + 左对齐
            );
        }

        // ========== 文件生成与上传 ==========
        // 生成文件名
        $datetime = date("Ymdhis", time());
        $savename = "{$datetime}.docx";
        $basPath = "/temp/up_word/" . date("Ymd") . "/";

        // 创建Writer
        $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');

        // 输出到内存
        ob_start();
        $objWriter->save('php://output');
        $file_content = ob_get_clean();

        // 上传到远程服务器
        $upload_url = $swtBaseAdmin . '/api/3.0.0/pcapi/word_url.php';
        $post_data = [
            'filename' => $savename,
            'path' => $basPath,
            'file_content' => $file_content,
            'api_key' => 'your_very_secret_api_key' // 建议替换为真实密钥
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $upload_url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // HTTPS兼容(如需)
        // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if (curl_errno($ch)) {
            json('cURL 错误: ' . curl_error($ch), 200, 'error');
        } else {
            if ($http_code == 200) {
                // 返回文件地址并更新数据库
                $infoArray["subjects"]["file"] = $swtBaseImg . $swtWjj . $basPath . $savename;
                $dataArrfenxi = ["download_url_word" => $basPath . $savename];
                _update('a_ai_fenxi', $dataArrfenxi, $rs["id"]);
            } else {
                json("上传失败!HTTP 状态码: $http_code, 服务器响应: " . $response, 200, 'error');
            }
        }
        curl_close($ch);

        // 恢复DOM错误提示
        libxml_use_internal_errors(false);

        // 输出结果
        echo json_encode($infoArray);
        // 延迟清理:先输出响应,再清理临时文件(给PhpWord足够时间加载图片)
        register_shutdown_function(function () {
            cleanTempDir();
            cleanSvgTempFiles();
        });
        exit;
    } catch (Exception $e) {
        // 异常处理
        echo "执行出错:" . $e->getMessage() . "<br>";
        echo "错误行号:" . $e->getLine() . "<br>";
        // 恢复DOM错误提示
        libxml_use_internal_errors(false);
        // 延迟清理:先输出响应,再清理临时文件(给PhpWord足够时间加载图片)
        register_shutdown_function(function () {
            cleanTempDir();
            cleanSvgTempFiles();
        });
    }
相关推荐
BingoGo18 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack18 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082854 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe4 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5