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();
        });
    }
相关推荐
小李独爱秋4 小时前
计算机网络经典问题透视:TLS协议工作过程全景解析
运维·服务器·开发语言·网络协议·计算机网络·php
易营宝6 小时前
高效的跨境电商广告优化系统:易营宝广告投放实操指南
大数据·开发语言·人工智能·php
运维行者_8 小时前
远程办公场景 NFA:从网络嗅探与局域网流量监控软件排查团队网络卡顿问题
运维·服务器·开发语言·网络·自动化·php
掘根9 小时前
【仿Muduo库项目】HTTP模块4——HttpServer子模块
网络协议·http·php
郑州光合科技余经理9 小时前
私有化B2B订货系统实战:核心模块设计与代码实现
java·大数据·开发语言·后端·架构·前端框架·php
万岳软件开发小城11 小时前
直播电商系统源码搭建直播带货APP/小程序的完整流程
小程序·php·软件开发·直播带货系统源码·直播电商app开发
Sammyyyyy12 小时前
PHP 8.6 新特性预览,更简洁的语法与更严谨的类型控制
android·php·android studio
万岳软件开发小城12 小时前
如何用直播电商系统源码低成本打造自己的直播带货APP/小程序?
开源·php·源码·直播带货系统源码·直播带货软件开发·直播带货app开发·电商直播小程序
一颗青果12 小时前
常见的网络命令
网络·智能路由器·php