MediaWiki
コラボレーションツール
MediaWiki
概要
MediaWikiは、Wikipediaを動かしているパワフルで柔軟なオープンソースのWikiソフトウェアです。PHPで書かれ、大規模なコラボレーティブプロジェクトから小規模な内部ドキュメント管理まで、幅広い用途に対応します。強力な編集機能、バージョン管理、権限管理、多言語サポートを備え、世界中の組織で知識共有プラットフォームとして採用されています。
詳細
MediaWikiは、スケーラブルで高度にカスタマイズ可能なWikiプラットフォームです。Wikiテキストマークアップとビジュアルエディタの両方をサポートし、ページの履歴、差分表示、ウォッチリスト、カテゴリシステムなどの包括的な機能を提供します。拡張機能システムにより、Semantic MediaWiki、VisualEditor、Parsoidなどの高度な機能を追加できます。Action APIとREST APIの両方を提供し、プログラマティックなアクセスと統合を可能にします。
主な機能
- 柔軟な編集: Wikiマークアップとビジュアルエディタ
- バージョン管理: 完全な編集履歴と差分表示
- 権限システム: 細かい権限制御とユーザーグループ管理
- 多言語対応: 300以上の言語をサポート
- 検索機能: 高度な全文検索とカテゴリシステム
- 拡張性: 数千の拡張機能による機能追加
- API: Action APIとREST APIによるプログラマティックアクセス
- スケーラビリティ: 小規模から超大規模まで対応
API概要
Action API
POST https://example.com/w/api.php
Content-Type: application/x-www-form-urlencoded
action=query&meta=siteinfo&siprop=general&format=json
REST API
GET https://example.com/w/rest.php/v1/page/Main_Page
Accept: application/json
認証
# ログイン
POST /w/api.php
action=login&lgname=username&lgpassword=password&format=json
# トークン取得
POST /w/api.php
action=query&meta=tokens&type=csrf&format=json
メリット・デメリット
メリット
- 完全無料でオープンソース
- 実績のある堅牢性(Wikipediaで実証済み)
- 高度なスケーラビリティ
- 豊富な拡張機能エコシステム
- 強力なAPIサポート
- 包括的なバージョン管理
- 多言語対応が優秀
- 大規模コミュニティサポート
デメリット
- 初期設定が複雑
- 独自のWikiマークアップ言語
- UIが古く感じる場合がある
- パフォーマンスチューニングが必要
- 拡張機能の互換性管理が必要
- リアルタイム編集機能がデフォルトではない
実践的な例
1. MediaWikiのDockerセットアップ
version: '3'
services:
mediawiki:
image: mediawiki:latest
restart: always
ports:
- 8080:80
links:
- database
volumes:
- ./images:/var/www/html/images
- ./LocalSettings.php:/var/www/html/LocalSettings.php
environment:
- MEDIAWIKI_DB_HOST=database
- MEDIAWIKI_DB_NAME=mediawiki
- MEDIAWIKI_DB_USER=wikiuser
- MEDIAWIKI_DB_PASSWORD=wikipass
database:
image: mariadb:10.6
restart: always
environment:
- MYSQL_DATABASE=mediawiki
- MYSQL_USER=wikiuser
- MYSQL_PASSWORD=wikipass
- MYSQL_ROOT_PASSWORD=rootpass
volumes:
- ./mysql:/var/lib/mysql
2. APIを使用したページ操作(Python)
import requests
import json
class MediaWikiAPI:
def __init__(self, api_url, username=None, password=None):
self.api_url = api_url
self.session = requests.Session()
if username and password:
self.login(username, password)
def login(self, username, password):
"""MediaWikiにログイン"""
# ログイントークンを取得
login_token = self.get_token('login')
# ログイン
login_data = {
'action': 'login',
'lgname': username,
'lgpassword': password,
'lgtoken': login_token,
'format': 'json'
}
response = self.session.post(self.api_url, data=login_data)
return response.json()
def get_token(self, token_type='csrf'):
"""APIトークンを取得"""
params = {
'action': 'query',
'meta': 'tokens',
'type': token_type,
'format': 'json'
}
response = self.session.get(self.api_url, params=params)
data = response.json()
if token_type == 'login':
return data['query']['tokens']['logintoken']
else:
return data['query']['tokens']['csrftoken']
def create_page(self, title, content, summary=''):
"""新しいページを作成または更新"""
token = self.get_token()
edit_data = {
'action': 'edit',
'title': title,
'text': content,
'summary': summary,
'token': token,
'format': 'json'
}
response = self.session.post(self.api_url, data=edit_data)
return response.json()
def get_page_content(self, title):
"""ページの内容を取得"""
params = {
'action': 'query',
'prop': 'revisions',
'titles': title,
'rvprop': 'content',
'format': 'json'
}
response = self.session.get(self.api_url, params=params)
data = response.json()
pages = data['query']['pages']
for page_id, page_data in pages.items():
if 'revisions' in page_data:
return page_data['revisions'][0]['*']
return None
# 使用例
wiki = MediaWikiAPI('https://example.com/w/api.php', 'username', 'password')
# ページを作成
wiki.create_page(
'API_Test_Page',
'== テストページ ==\nこれはAPIで作成されたページです。',
'APIテストによるページ作成'
)
3. JavaScript APIクライアント
class MediaWikiClient {
constructor(apiUrl) {
this.apiUrl = apiUrl;
}
async request(params) {
const url = new URL(this.apiUrl);
Object.keys(params).forEach(key =>
url.searchParams.append(key, params[key])
);
const response = await fetch(url, {
method: params.action === 'edit' ? 'POST' : 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params.action === 'edit' ?
new URLSearchParams(params) : undefined
});
return response.json();
}
async searchPages(searchTerm, limit = 10) {
const params = {
action: 'query',
list: 'search',
srsearch: searchTerm,
srlimit: limit,
format: 'json',
origin: '*'
};
const data = await this.request(params);
return data.query.search;
}
async getRecentChanges(limit = 50) {
const params = {
action: 'query',
list: 'recentchanges',
rcprop: 'title|timestamp|user|comment',
rclimit: limit,
format: 'json',
origin: '*'
};
const data = await this.request(params);
return data.query.recentchanges;
}
async getCategoryMembers(category, limit = 100) {
const params = {
action: 'query',
list: 'categorymembers',
cmtitle: `Category:${category}`,
cmlimit: limit,
format: 'json',
origin: '*'
};
const data = await this.request(params);
return data.query.categorymembers;
}
}
// 使用例
const wiki = new MediaWikiClient('https://example.com/w/api.php');
// ページを検索
wiki.searchPages('JavaScript').then(results => {
results.forEach(page => {
console.log(`${page.title}: ${page.snippet}`);
});
});
4. カスタム拡張機能の作成(PHP)
<?php
/**
* CustomTools拡張機能
*/
class CustomToolsHooks {
/**
* パーサー関数を登録
*/
public static function onParserFirstCallInit(Parser $parser) {
// カスタムパーサー関数を追加
$parser->setFunctionHook('custombox', [self::class, 'renderCustomBox']);
return true;
}
/**
* カスタムボックスをレンダリング
*/
public static function renderCustomBox(Parser $parser, $content = '', $type = 'info') {
$validTypes = ['info', 'warning', 'success', 'error'];
if (!in_array($type, $validTypes)) {
$type = 'info';
}
$output = Html::rawElement(
'div',
['class' => "custom-box custom-box-$type"],
$parser->recursiveTagParse($content)
);
return [$output, 'noparse' => false];
}
/**
* カスタムAPIモジュールを追加
*/
public static function onApiMain_moduleManager($moduleManager) {
$moduleManager->addModule(
'customdata',
'action',
'ApiCustomData'
);
return true;
}
}
// extension.json
{
"name": "CustomTools",
"version": "1.0.0",
"author": "Your Name",
"url": "https://example.com/CustomTools",
"description": "カスタムツールとパーサー関数を追加",
"license-name": "GPL-2.0-or-later",
"type": "parserhook",
"Hooks": {
"ParserFirstCallInit": "CustomToolsHooks::onParserFirstCallInit",
"ApiMain::moduleManager": "CustomToolsHooks::onApiMain_moduleManager"
},
"ResourceModules": {
"ext.customtools.styles": {
"styles": "resources/ext.customtools.css"
}
},
"ResourceFileModulePaths": {
"localBasePath": "",
"remoteExtPath": "CustomTools"
},
"manifest_version": 2
}
5. バッチインポートスクリプト(Python)
#!/usr/bin/env python3
import os
import mwclient
from datetime import datetime
class MediaWikiBatchImporter:
def __init__(self, site_url, path='/w/', username=None, password=None):
self.site = mwclient.Site(site_url, path=path)
if username and password:
self.site.login(username, password)
def import_markdown_files(self, directory, namespace=0):
"""Markdownファイルを一括インポート"""
import markdown
md = markdown.Markdown()
imported = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.md'):
filepath = os.path.join(root, file)
page_title = file[:-3].replace('_', ' ')
with open(filepath, 'r', encoding='utf-8') as f:
md_content = f.read()
wiki_content = self.markdown_to_wiki(md_content)
try:
page = self.site.pages[page_title]
page.save(
wiki_content,
summary=f'Imported from {file}'
)
imported.append(page_title)
print(f"Imported: {page_title}")
except Exception as e:
print(f"Error importing {page_title}: {e}")
return imported
def markdown_to_wiki(self, md_text):
"""簡単なMarkdownからWikiテキストへの変換"""
# 見出し
wiki_text = md_text
for i in range(6, 0, -1):
wiki_text = wiki_text.replace(
'#' * i + ' ',
'=' * i + ' '
)
wiki_text = wiki_text.replace(
'\n' + '#' * i + ' ',
'\n' + '=' * i + ' '
)
# リスト
wiki_text = wiki_text.replace('\n- ', '\n* ')
wiki_text = wiki_text.replace('\n - ', '\n** ')
# リンク
import re
wiki_text = re.sub(
r'\[([^\]]+)\]\(([^\)]+)\)',
r'[\2 \1]',
wiki_text
)
return wiki_text
def create_category_structure(self, categories):
"""カテゴリ構造を作成"""
for cat_name, cat_info in categories.items():
cat_page = self.site.pages[f'Category:{cat_name}']
content = f"== {cat_info['description']} ==\n"
if 'parent' in cat_info:
content += f"\n[[Category:{cat_info['parent']}]]"
cat_page.save(content, summary='カテゴリ作成')
print(f"Created category: {cat_name}")
# 使用例
importer = MediaWikiBatchImporter(
'example.com',
username='ImportBot',
password='bot_password'
)
# Markdownファイルをインポート
importer.import_markdown_files('/path/to/markdown/docs')
# カテゴリ構造を作成
categories = {
'Documentation': {
'description': '技術文書のメインカテゴリ'
},
'API_Documentation': {
'description': 'API関連の文書',
'parent': 'Documentation'
}
}
importer.create_category_structure(categories)