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)

参考リンク