MediaWiki扩展中获取页面编辑前后内容的PHP教程
在MediaWiki扩展开发中,捕获页面编辑前后的内容差异是一项常见且重要的需求。无论是构建审计日志系统、自动生成变更摘要,还是实现内容差异分析功能,掌握这一技术都至关重要。本教程将深入讲解如何在MediaWiki扩展中使用PHP获取页面编辑前与编辑后的内容,并提供完整的代码示例。

理解MediaWiki的编辑流程与钩子机制
MediaWiki的内容修改流程涉及多个阶段,从用户打开编辑界面到最终保存页面,每个阶段都提供了相应的钩子(Hook)供扩展开发者介入。要获取编辑前后的内容,我们需要关注以下核心钩子:
EditPage::showEditForm - 在显示编辑表单时触发,此时可以获取页面的当前版本内容(编辑前内容)。
PageContentSave - 在页面内容保存到数据库之前触发,此时可以获取即将保存的新内容(编辑后内容)。
PageContentSaveComplete - 在页面内容成功保存到数据库之后触发,此时可以获取已保存的新内容以及旧内容。
扩展的基础结构与注册
首先,我们需要创建一个基本的MediaWiki扩展。假设我们的扩展名为ContentDiffTracker,用于追踪页面编辑前后的内容变化。
创建扩展目录和文件:
<?php
// ContentDiffTracker.php - 扩展主文件
if ( !defined( 'MEDIAWIKI' ) ) {
die( 'This file is a MediaWiki extension and cannot be accessed directly.' );
}
$wgExtensionCredits['other'][] = array(
'path' => __FILE__,
'name' => 'ContentDiffTracker',
'author' => 'Your Name',
'url' => 'https://www.ipipp.com',
'description' => 'Tracks content changes before and after page edits.',
'version' => '1.0.0',
);
// 注册钩子
$wgHooks['EditPage::showEditForm'][] = 'ContentDiffTracker::onShowEditForm';
$wgHooks['PageContentSave'][] = 'ContentDiffTracker::onPageContentSave';上述代码注册了两个核心钩子,接下来我们需要实现对应的处理方法。
获取编辑前的内容
当用户点击"编辑"标签打开编辑界面时,EditPage::showEditForm钩子被触发。此时我们可以获取页面的当前版本内容,即编辑前的内容。
<?php
class ContentDiffTracker {
/**
* 处理 EditPage::showEditForm 钩子
* 在显示编辑表单时获取当前页面内容(编辑前内容)
*
* @param EditPage $editPage 编辑页面对象
* @param OutputPage $output 输出页面对象
*/
public static function onShowEditForm( $editPage, $output ) {
// 获取页面对象
$title = $editPage->getTitle();
$wikiPage = WikiPage::factory( $title );
// 获取当前页面的最新版本内容
$content = $wikiPage->getContent();
if ( $content instanceof TextContent ) {
$currentText = $content->getText();
} else {
// 对于非文本内容(如JSON内容模型),根据需要处理
$currentText = $content ? $content->serialize() : '';
}
// 将编辑前内容存储在静态变量中,以便后续钩子使用
self::$preEditContent = $currentText;
// 可以根据需要记录日志或进行其他处理
wfDebugLog( 'ContentDiffTracker', 'Pre-edit content retrieved for page: ' . $title->getPrefixedText() );
return true;
}
// 存储编辑前内容的静态变量
public static $preEditContent = null;
}获取编辑后的内容
当用户点击"保存页面"时,PageContentSave钩子在内容实际写入数据库之前被触发。此时我们可以获取即将保存的新内容,并与之前存储的编辑前内容进行对比。
<?php
class ContentDiffTracker {
// 在 onShowEditForm 中已定义的静态变量
public static $preEditContent = null;
/**
* 处理 PageContentSave 钩子
* 在内容保存到数据库之前获取新内容(编辑后内容)
*
* @param WikiPage $wikiPage 页面对象
* @param User $user 执行编辑的用户
* @param Content $content 即将保存的新内容对象
* @param string $summary 编辑摘要
* @param bool $isMinor 是否为小编辑
* @param bool $isWatch 是否监视此页面
* @param int $section 编辑的章节编号
* @param int $flags 标志位
* @param Status $status 状态对象
*/
public static function onPageContentSave( $wikiPage, $user, $content, $summary,
$isMinor, $isWatch, $section, $flags, $status ) {
// 获取页面标题
$title = $wikiPage->getTitle();
// 获取编辑后的内容
if ( $content instanceof TextContent ) {
$newText = $content->getText();
} else {
$newText = $content ? $content->serialize() : '';
}
// 获取之前存储的编辑前内容
$oldText = self::$preEditContent;
// 如果编辑前内容为空,则尝试从页面当前版本获取
if ( $oldText === null ) {
$currentContent = $wikiPage->getContent();
if ( $currentContent instanceof TextContent ) {
$oldText = $currentContent->getText();
} else {
$oldText = $currentContent ? $currentContent->serialize() : '';
}
}
// 比较新旧内容
if ( $oldText !== $newText ) {
// 内容发生了变化
self::logContentChange( $title, $user, $oldText, $newText );
// 可以在这里执行差异分析、通知等操作
self::processContentDiff( $oldText, $newText, $title );
}
return true;
}
/**
* 记录内容变更
*/
private static function logContentChange( $title, $user, $oldText, $newText ) {
$logEntry = sprintf(
'Page: %s | User: %s | Old length: %d | New length: %d',
$title->getPrefixedText(),
$user->getName(),
strlen( $oldText ),
strlen( $newText )
);
wfDebugLog( 'ContentDiffTracker', $logEntry );
}
/**
* 处理内容差异(示例:计算简单的行数差异)
*/
private static function processContentDiff( $oldText, $newText, $title ) {
$oldLines = explode( "n", $oldText );
$newLines = explode( "n", $newText );
$addedLines = array_diff( $newLines, $oldLines );
$removedLines = array_diff( $oldLines, $newLines );
if ( !empty( $addedLines ) || !empty( $removedLines ) ) {
$diffSummary = sprintf(
'Lines added: %d | Lines removed: %d',
count( $addedLines ),
count( $removedLines )
);
wfDebugLog( 'ContentDiffTracker', $diffSummary . ' for page: ' . $title->getPrefixedText() );
}
}
}使用PageContentSaveComplete钩子获取完整上下文
有时候我们需要在内容保存完成后再获取旧内容和新内容,此时可以使用PageContentSaveComplete钩子。这个钩子提供了编辑前后的内容对象,更加方便。
<?php
class ContentDiffTracker {
/**
* 处理 PageContentSaveComplete 钩子
* 在内容成功保存到数据库后触发
*
* @param WikiPage $wikiPage 页面对象
* @param User $user 执行编辑的用户
* @param Content $content 保存后的新内容
* @param string $summary 编辑摘要
* @param bool $isMinor 是否为小编辑
* @param bool $isWatch 是否监视此页面
* @param int $section 编辑的章节编号
* @param int $flags 标志位
* @param Revision $revision 新版本对象
* @param Status $status 状态对象
* @param int $baseRevId 基础版本ID
* @param int $undidRevId 被撤销的版本ID(如果有)
*/
public static function onPageContentSaveComplete( $wikiPage, $user, $content, $summary,
$isMinor, $isWatch, $section, $flags,
$revision, $status, $baseRevId, $undidRevId ) {
// 获取新内容
if ( $content instanceof TextContent ) {
$newText = $content->getText();
} else {
$newText = $content ? $content->serialize() : '';
}
// 获取旧内容:通过基础版本ID获取
$oldContent = null;
if ( $baseRevId ) {
$oldRevision = Revision::newFromId( $baseRevId );
if ( $oldRevision ) {
$oldContent = $oldRevision->getContent();
}
}
// 如果无法通过基础版本获取,则尝试获取前一个版本
if ( !$oldContent ) {
$oldRevision = $wikiPage->getRevision()->getPrevious();
if ( $oldRevision ) {
$oldContent = $oldRevision->getContent();
}
}
$oldText = '';
if ( $oldContent instanceof TextContent ) {
$oldText = $oldContent->getText();
} elseif ( $oldContent ) {
$oldText = $oldContent->serialize();
}
// 新旧内容都已获取,可以进行差异分析
if ( $oldText !== $newText ) {
// 执行更详细的差异分析
self::performDetailedDiff( $oldText, $newText, $wikiPage, $user );
}
return true;
}
/**
* 执行详细的差异分析
*/
private static function performDetailedDiff( $oldText, $newText, $wikiPage, $user ) {
// 这里可以使用MediaWiki内置的Diff引擎
$diff = new Diff( explode( "n", $oldText ), explode( "n", $newText ) );
$diffFormatter = new TableDiffFormatter();
$diffHtml = $diffFormatter->format( $diff );
wfDebugLog( 'ContentDiffTracker', 'Detailed diff generated for page: ' .
$wikiPage->getTitle()->getPrefixedText() );
}
}完整的扩展实现
以下是将上述所有功能整合到一个完整扩展中的代码:
<?php
// ContentDiffTracker.php - 完整扩展实现
if ( !defined( 'MEDIAWIKI' ) ) {
die( 'This file is a MediaWiki extension and cannot be accessed directly.' );
}
$wgExtensionCredits['other'][] = array(
'path' => __FILE__,
'name' => 'ContentDiffTracker',
'author' => 'Your Name',
'url' => 'https://www.ipipp.com',
'description' => 'Tracks content changes before and after page edits.',
'version' => '1.0.0',
);
// 注册所有钩子
$wgHooks['EditPage::showEditForm'][] = 'ContentDiffTracker::onShowEditForm';
$wgHooks['PageContentSave'][] = 'ContentDiffTracker::onPageContentSave';
$wgHooks['PageContentSaveComplete'][] = 'ContentDiffTracker::onPageContentSaveComplete';
class ContentDiffTracker {
// 存储编辑前内容
private static $preEditContent = null;
private static $preEditTitle = null;
/**
* EditPage::showEditForm 钩子
*/
public static function onShowEditForm( $editPage, $output ) {
$title = $editPage->getTitle();
$wikiPage = WikiPage::factory( $title );
$content = $wikiPage->getContent();
if ( $content instanceof TextContent ) {
self::$preEditContent = $content->getText();
} elseif ( $content ) {
self::$preEditContent = $content->serialize();
} else {
self::$preEditContent = '';
}
self::$preEditTitle = $title;
wfDebugLog( 'ContentDiffTracker', 'Pre-edit content captured for ' . $title->getPrefixedText() );
return true;
}
/**
* PageContentSave 钩子
*/
public static function onPageContentSave( $wikiPage, $user, $content, $summary,
$isMinor, $isWatch, $section, $flags, $status ) {
$title = $wikiPage->getTitle();
// 获取新内容
if ( $content instanceof TextContent ) {
$newText = $content->getText();
} else {
$newText = $content ? $content->serialize() : '';
}
// 获取旧内容
$oldText = self::$preEditContent;
if ( $oldText === null ) {
$currentContent = $wikiPage->getContent();
if ( $currentContent instanceof TextContent ) {
$oldText = $currentContent->getText();
} else {
$oldText = $currentContent ? $currentContent->serialize() : '';
}
}
// 检测内容变化
if ( $oldText !== $newText ) {
$addedLength = strlen( $newText ) - strlen( $oldText );
wfDebugLog( 'ContentDiffTracker', sprintf(
'SAVE - Page: %s | User: %s | Change: %+d bytes',
$title->getPrefixedText(),
$user->getName(),
$addedLength
));
}
return true;
}
/**
* PageContentSaveComplete 钩子
*/
public static function onPageContentSaveComplete( $wikiPage, $user, $content, $summary,
$isMinor, $isWatch, $section, $flags,
$revision, $status, $baseRevId, $undidRevId ) {
$title = $wikiPage->getTitle();
// 获取新内容
if ( $content instanceof TextContent ) {
$newText = $content->getText();
} else {
$newText = $content ? $content->serialize() : '';
}
// 获取旧内容
$oldText = '';
if ( $baseRevId ) {
$oldRevision = Revision::newFromId( $baseRevId );
if ( $oldRevision ) {
$oldContent = $oldRevision->getContent();
if ( $oldContent instanceof TextContent ) {
$oldText = $oldContent->getText();
} elseif ( $oldContent ) {
$oldText = $oldContent->serialize();
}
}
}
// 如果基础版本获取失败,尝试前一个版本
if ( empty( $oldText ) ) {
$oldRevision = $wikiPage->getRevision()->getPrevious();
if ( $oldRevision ) {
$oldContent = $oldRevision->getContent();
if ( $oldContent instanceof TextContent ) {
$oldText = $oldContent->getText();
} elseif ( $oldContent ) {
$oldText = $oldContent->serialize();
}
}
}
// 最终的内容差异处理
if ( $oldText !== $newText ) {
wfDebugLog( 'ContentDiffTracker', sprintf(
'COMPLETE - Page: %s | User: %s | Old length: %d | New length: %d',
$title->getPrefixedText(),
$user->getName(),
strlen( $oldText ),
strlen( $newText )
));
// 在此处添加自定义处理逻辑,例如:
// - 将差异写入到外部数据库
// - 发送通知给关注者
// - 更新搜索结果索引
}
// 清理静态变量
self::$preEditContent = null;
self::$preEditTitle = null;
return true;
}
}配置与启用扩展
完成代码编写后,按照以下步骤启用扩展:
将
ContentDiffTracker.php文件放置在MediaWiki的extensions/ContentDiffTracker/目录下。在
LocalSettings.php中添加以下配置:
// 启用扩展 wfLoadExtension( 'ContentDiffTracker' ); // 可选:启用调试日志 $wgDebugLogGroups['ContentDiffTracker'] = '/path/to/your/logfile.log';
进阶应用场景
掌握了获取编辑前后内容的基础方法后,可以将其应用于更多实际场景:
内容审核工作流:在
PageContentSave钩子中检测敏感内容,阻止或标记可疑编辑。自动生成变更摘要:基于内容差异自动生成结构化的编辑摘要,提升协作效率。
页面变动通知:当页面内容发生变化时,通过邮件或即时消息通知相关用户。
内容版本统计:统计用户编辑的字符数变化,用于贡献度计算或游戏化机制。
差异可视化:生成类似于维基百科的差异视图,在自定义页面中展示版本间变化。
例如,以下代码展示了如何在PageContentSave钩子中阻止包含特定关键词的编辑:
public static function onPageContentSave( $wikiPage, $user, $content, $summary,
$isMinor, $isWatch, $section, $flags, $status ) {
$newText = $content instanceof TextContent ? $content->getText() : '';
// 黑名单关键词
$blockedWords = array( 'spam', 'malware', 'inappropriate' );
foreach ( $blockedWords as $word ) {
if ( stripos( $newText, $word ) !== false ) {
// 设置错误状态,阻止保存
$status->fatal( 'contentdifftracker-blocked-word', $word );
return false; // 返回 false 以终止保存操作
}
}
return true;
}总结
本教程详细讲解了在MediaWiki扩展开发中获取页面编辑前后内容的完整方法。通过合理利用EditPage::showEditForm、PageContentSave和PageContentSaveComplete这三个核心钩子,我们可以在编辑流程的不同阶段获取所需的内容数据,并在此基础上构建丰富的功能。
关键要点回顾:
使用
EditPage::showEditForm钩子获取编辑前的内容,通常存储在静态变量中供后续使用。使用
PageContentSave钩子在保存前获取新内容,并可选择中断保存操作。使用
PageContentSaveComplete钩子在保存完成后获取完整的上下文信息,包括新旧版本内容。对于非文本内容模型(如JSON、JavaScript等),需要使用
serialize()方法获取文本表示。在生产环境中,请确保处理各种边缘情况,如页面创建(旧内容为空)、内容模型变更等。
掌握这些技术后,您将能够构建出功能强大且灵活的MediaWiki扩展,满足各种内容管理需求。如有更复杂的场景需求,建议查阅MediaWiki官方文档或社区扩展的实现代码以获取更多灵感。