PukiwikiAPIExtension

Top > PluginDev > PukiwikiAPIExtension
Table of Contents

PukiWiki API Extension

make_inline

/**
 * Convert only inline plugins unlike make_link()
 *
 * This (Precisely, InlineConverter) does htmlspecialchars, too.
 *
 * @param $string string
 * @param $page pagename, default is $vars['page']
 * @see make_link
 * @uses InlineConverter
 */
function make_inline($string, $page = '')
{
    global $vars;
    static $converter;
  
    if (! isset($converter)) $converter = new InlineConverter(array('plugin'));
    
    $clone = $converter->get_clone($converter);
    return $clone->convert($string, ($page != '') ? $page : $vars['page']);
}

parse_options

簡単な割には効果の高いオプション解析

if (! function_exists('parse_options')) {
    function parse_options(&$args, &$options, $sep = '=')
    {
        foreach ($args as $arg) {
            list($key, $val) = array_pad(explode($sep, $arg, 2), 2, TRUE);
            if (array_key_exists($key, $options)) {
                $options[$key] = $val;
            }
        }
    }
}

show_iframe

XHTML1.1 Strict のためにできるだけ iframe タグは使用しない。が、IE では object タグではうまくならないので仕方なく iframe タグを使用し、XHTML1.0 Transitional にして逃げる。

// iframe.inc.php, amazonpd.inc.php
function show_iframe($url, $style = NULL)
{
    if (ereg("MSIE (3|4|5|6|7)", getenv("HTTP_USER_AGENT"))) {
        global $pkwk_dtd; //1.4.4 or above
        global $html_transitional; //1.4.3
        $pkwk_dtd = PKWK_DTD_XHTML_1_0_TRANSITIONAL;
        $html_transitional = 1;
        
        $ret  = '<iframe frameborder="0" class="iframe"';
        $ret .= isset($style) ? ' style="' . $style . '"' : '';
        $ret .= ' src="' . $url . '">';
        $ret .= '<p>Your borwser is not supporting iframe tag. ' . 
            'Please use one of the latest browsers.<br />' .
            'Go to <a href="' . $url . '">' . $url . '</a></p>';
        $ret .= '</iframe>';
        return $ret;
    } else {
        $ret  = '<object class="iframe" type="text/html"';
        $ret .= isset($style) ? ' style="' . $style . '"' : '';
        $ret .= ' data="' . $url . '">';
        $ret .= '<p>Your borwser is not supporting object tag. ' . 
            'Please use one of the latest browsers.<br />' .
            'Go to <a href="' . $url . '">' . $url . '</a></p>';
        $ret .= '</object>';
        return $ret;
    }
}

remove_multiline_plugin

/**
 * Remove inside of multiline plugin arguments. 
 * Keys are preserved. 
 *
 * @param array $lines 
 * @return void
 */
function remove_multiline_plugin(&$lines)
{
    if(! (defined('PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK') && PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK === 0)) {
        return $lines;
    }
    $multiline = 0;
    foreach ($lines as $i => $line) {
        $matches = array();
        if ($multiline < 2) {
            if(preg_match('/^#([^\(\{]+)(?:\(([^\r]*)\))?(\{*)/', $line, $matches)) {
                $multiline  = strlen($matches[3]);
            }
        } else {
            if (preg_match('/^\}{' . $multiline . '}$/', $line, $matches)) {
                $multiline = 0;
            }
            unset($lines[$i]);
            continue;
        }
    }
}

is_interwiki($wikiname)

オーバーライドしたい。

/**
 * Check if string is InterWiki syntax or not
 *
 * @param string $str
 * @return boolean
 */
function is_interwiki($str)
{
    return ! is_url($str) && is_interwiki($str);
}

get_interwiki_url($interwiki)

オーバーライドしたい。

/**
 * Resolve InterWiki name
 *
 * @param string $interwiki InterWiki name
 * @return string url
 */
function get_interwiki_url($interwiki)
{
    if (is_url($interwiki) || ! is_interwiki($interwiki)) return FALSE;
    list($interwiki, $page) = explode(':', $interwiki, 2);
    $url = get_interwiki_url($interwiki, $page);
    return $url;
}

pkwk_noskin_output

/**
 * Output contents without skin
 *
 * @param string $body html
 * @param string $content_type e.g., 'text/html', 'text/css', 'text/javascript'
 * @return void exit
 */
function pkwk_output_noskin($body, $content_type = 'text/html') // text/css, text/javascript
{
    pkwk_common_headers();
    header('Content-Type: ' . $content_type);
    print $body;
    exit;
}

get_page_uri

/**
 * get uri of a page
 *
 * @param string $page
 * @param string $query query word if needs
 * @return string uri
 */
function get_page_uri($page, $query = '')
{
    if (function_exists('get_script_uri')) { // from pukiwiki 1.4
        $url = get_script_uri() . '?' . rawurlencode($page);
    } else {
        global $script;
        $url = $script . '?' . rawurlencode($page);
    }
    if ($query != '') {
        $url .= '&amp;' . $query;
    }
    return $url;
}

get_existpages($prefix = '')

オ-バーライドしたい

/**
 * get existing pages with prefix restriction (instead of directory name)
 *
 * @param string $prefix
 * @return array
 */
function get_existpages($prefix = '')
{
    $pages = get_existpages();
    if ($prefix === '') return $pages;
    foreach ($pages as $i => $page) {
        if (strpos($page, $prefix) !== 0) {
            unset($pages[$i]);
        }
    }
    return $pages;
}

is_edit_restrict

requires below is_edit_auth

function is_edit_restrict($page)
{
    return PKWK_READONLY > 0 or is_freeze($page) or self::is_edit_auth($page);
}

is_edit_auth

/**
 * Check if a page is configured to require authentication
 *
 * @param string $page
 * @param string $user if want to check whether this user possibly can get permission
 * @return boolean
 */
function is_edit_auth($page, $user = '')
{
    global $edit_auth, $edit_auth_pages, $auth_method_type;
    if (! $edit_auth) {
        return FALSE;
    }
    // Checked by:
    $target_str = '';
    if ($auth_method_type == 'pagename') {
        $target_str = $page; // Page name
    } else if ($auth_method_type == 'contents') {
        $target_str = join('', get_source($page)); // Its contents
    }

    foreach($edit_auth_pages as $regexp => $users) {
        if (preg_match($regexp, $target_str)) {
            if ($user == '' || in_array($user, explode(',', $users))) {
                return TRUE;
            }
        }
    }
    return FALSE;
}

exec_existpages

tag.inc.php など、ページが参照されて始めてログが作成されるプラグインのために、全ページ実行する。 正規表現で必要な行だけに絞り込むことが可能。

if (! function_exists('exec_existpages')) {
    /**
     * Execute (convert_html) all pages
     *
     * PukiWiki API Extension
     *
     * @param string $regexp execute only matched lines (preg_grep)
     * @return array executed pages
     */
    function exec_existpages($regexp = null)
    {
        global $vars, $get, $post;
        $pages = get_existpages();
        $exec_pages = array();
        $tmp_page = $vars['page'];
        $tmp_cmd  = $vars['cmd'];
        $vars['cmd'] = $get['cmd'] = $post['cmd'] = 'read';
        foreach ($pages as $page) {
            $vars['page'] = $get['page'] = $post['page'] = $page;
            $lines = get_source($page);
            if (isset($regexp)) {
                $lines = preg_grep($regexp, $lines);
            }
            if (empty($lines)) continue;
            convert_html($lines);
            $exec_pages[] = $page;
        }
        $vars['page'] = $get['page'] = $post['page'] = $tmp_page;
        $vars['cmd'] = $get['cmd'] = $post['cmd'] = $tmp_cmd;
        return $exec_pages;
    }
}

exec_page

if (! function_exists('exec_page')) {
    /**
     * Execute (convert_html) this page
     *
     * PukiWiki API Extension
     *
     * @param string $page
     * @param string $regexp execute only matched lines (preg_grep)
     * @return boolean executed
     */
    function exec_page($page, $regexp = null)
    {
        global $vars, $get, $post;
        $lines = get_source($page);
        if (isset($regexp)) {
            $lines = preg_grep($regexp, $lines);
        }
        if (empty($lines)) return FALSE;
        $tmp_page = $vars['page'];
        $tmp_cmd  = $vars['cmd'];
        $vars['cmd'] = $get['cmd'] = $post['cmd'] = 'read';
        $vars['page'] = $get['page'] = $post['page'] = $page;
        convert_html($lines);
        $vars['page'] = $get['page'] = $post['page'] = $tmp_page;
        $vars['cmd'] = $get['cmd'] = $post['cmd'] = $tmp_cmd;
        return TRUE;
    }
}

is_human

requires below is_admin too

if (! function_exists('is_human')) {
    /**
     * Human recognition using PukiWiki Auth methods
     *
     * @param boolean $is_human Tell this is a human (Use TRUE to store into session)
     * @param boolean $use_session Use Session log
     * @param int $use_rolelevel accepts users whose role levels are stronger than this
     * @return boolean
     */
    if (! defined('ROLE_AUTH')) define('ROLE_AUTH', 5); // define for PukiWiki Official
    if (! defined('ROLE_ENROLLEE')) define('ROLE_ENROLLEE', 4);
    if (! defined('ROLE_ADM_CONTENTS')) define('ROLE_ADM_CONTENTS', 3);
    if (! defined('ROLE_ADM')) define('ROLE_ADM', 2);
    if (! defined('ROLE_GUEST')) define('ROLE_GUEST', 0);
    function is_human($is_human = FALSE, $use_session = FALSE, $use_rolelevel = 0)
    {
        if (! $is_human) {
            if ($use_session) {
                session_start();
                $is_human = isset($_SESSION['pkwk_is_human']) && $_SESSION['pkwk_is_human'];
            }
        }
        if (! $is_human) {
            if (ROLE_GUEST < $use_rolelevel && $use_rolelevel <= ROLE_ENROLLEE) {
                if (is_callable(array('auth', 'check_role'))) { // Plus!
                    $is_human = ! auth::check_role('role_enrollee');
                } else { // In PukiWiki Official, enrollees are all auth_users (ROLE_AUTH && BasicAuth in Plus!)
                    $is_human = isset($_SERVER['PHP_AUTH_USER']);
                }
            }
        }
        if (! $is_human) {
            if (ROLE_GUEST < $use_rolelevel && $use_rolelevel <= ROLE_ADM_CONTENTS) {
                $is_human = is_admin(NULL, $use_session, TRUE);
                // In PukiWiki Official, username 'admin' is the Admin
            }
        }
        if ($use_session) {
            session_start();
            $_SESSION['pkwk_is_human'] = $is_human;
        } else {
            global $vars;
            $vars['pkwk_is_human'] = $is_human;
        }
        return $is_human;
    }
}

is_admin

if (! function_exists('is_admin')) {
    /**
     * PukiWiki admin login with session
     *
     * @param string $pass Password. Use NULL when to get current session state. 
     * @param boolean $use_session Use Session log
     * @param boolean $use_authlog Use Auth log. 
     *  Username 'admin' is deemed to be Admin in PukiWiki Official. 
     *  PukiWiki Plus! has role management, roles ROLE_ADM and ROLE_ADM_CONTENTS are deemed to be Admin. 
     * @return boolean
     */
    function is_admin($pass = NULL, $use_session = FALSE, $use_authlog = FALSE)
    {
        $is_admin = FALSE;
        if (! $is_admin) {
            if ($use_session) {
                session_start();
                $is_admin = isset($_SESSION['pkwk_is_admin']) && $_SESSION['pkwk_is_admin'];
            }
        }
        // BasicAuth (etc) login
        if (! $is_admin) {
            if ($use_authlog) {
                if (is_callable(array('auth', 'check_role'))) { // Plus!
                    $is_admin = ! auth::check_role('role_adm_contents');
                } else {
                    $is_admin = (isset($_SERVER['PHP_AUTH_USER']) && $_SERVER['PHP_AUTH_USER'] === 'admin');
                }
            }
        }
        // PukiWiki Admin login
        if (! $is_admin) {
            if (isset($pass)) {
                $is_admin = function_exists('pkwk_login') ? pkwk_login($pass) : 
                    md5($pass) === $GLOBALS['adminpass']; // 1.4.3
            }
        }
        if ($use_session) {
            session_start();
            if ($is_admin) $_SESSION['pkwk_is_admin'] = TRUE;
        } else {
            global $vars;
            $vars['pkwk_is_admin'] = $is_admin;
        }
        return $is_admin;
    }
}

Plus! の check_role は auth.ini.php にてユーザ作成、パスワード登録時に

'hoge' => array('password', 3);

のようにすると、role_adm_contents になれる。2だと admin(最高権限), 4だと enrollee などなど

get_pkwk_baseuri

if (! function_exists('get_pkwk_baseuri')) {
    /**
     * Get PukiWiki Base(Top) URI (without index.php)
     *
     * @return string baseuri
     */
    function get_pkwk_baseuri()
    {
        static $baseuri = '';
        if ($baseuri !== '') return $baseuri;
        $baseuri = get_script_uri();
        if (($pos = strrpos($baseuri, '/')) !== FALSE) {
            $baseuri = substr($baseuri, 0, $pos + 1);
        }
        return $baseuri;
    }
}

is_read_auth

if (! function_exists('is_read_auth')) {

   /**
    * Check if the page requires the read-authentication
    *
    * @param $page pagename
    * @param $user check if it is possible for this page to be read by the user
    * @return boolean
    */
   function is_read_auth($page, $user = '')
   {
       global $read_auth, $read_auth_pages, $auth_method_type;
       if (! $read_auth) {
           return FALSE;
       }
       // Checked by:
       $target_str = '';
       if ($auth_method_type == 'pagename') {
           $target_str = $page; // Page name
       } else if ($auth_method_type == 'contents') {
           $target_str = join('', get_source($page)); // Its contents
       }
       
       foreach($read_auth_pages as $regexp => $users) {
           if (preg_match($regexp, $target_str)) {
               if ($user == '' || in_array($user, explode(',', $users))) {
                   return TRUE;
               }
           }
       }
       return FALSE;
   }

}

is_page_newer

if (! function_exists('is_page_newer')) {
    /**
     * Check if the page timestamp is newer than the file timestamp
     *
     * PukiWiki API Extension
     *
     * @param string $page pagename
     * @param string $file filename
     * @param bool $ignore_notimestamp Ignore notimestamp edit and see the real time editted
     * @return boolean
     */
    function is_page_newer($page, $file, $ignore_notimestamp = TRUE)
    {
        $filestamp = file_exists($file) ? filemtime($file) : 0;
        if ($ignore_notimestamp) { // See the diff file. PukiWiki Trick. 
            $pagestamp  = is_page($page) ? filemtime(DIFF_DIR . encode($page) . '.txt') : 0;
        } else {
            $pagestamp  = is_page($page) ? filemtime(get_filename($page)) : 0;
        }    
        return $pagestamp > $filestamp;
    }
}

get_filecreatetime

if (! function_exists('get_filecreatetime')) {
    /**
     * Get page created time
     *
     * PukiWiki API Extension
     *
     * @param string $page pagename
     * @return int timestamp
     * @see get_filetime($page)
     */
    function get_filecreatetime($page)
    {
        if (_backup_file_exists($page)) { // PukiWiki Trick
            // This is not a created time exactly, but the closest time
            $backup = get_backup($page, 1); // 1st age
            return $backup['time'];
        } else {
            return get_filetime($page);
        }
    }
}

is_newpage

function is_newpage($page)
{
    // pukiwiki trick
    return ! _backup_file_exists($page);
}

backup はとめることもできるので、そうなると無理。 ちなみに、diff ファイルは最初の編集でも作られるので、それも無理。

ctime と mtime が同じかどうか、も無理。 pukiwiki は「タイムスタンプを更新しない」編集で touch してしまうので、そのときに ctime が更新されてしまう。

備忘録:backup ファイルは page_write のタイミングで作成される。ただし lib/backup.php で UTIME - $lastmod > 60 * 60 * $cycle のようなチェックがされており、デフォルトでは 1 時間以降の更新タイミングで作成される。

get_heading

if (! function_exists('get_heading')) {
    /**
     * Get heading strings from a wiki source line
     *
     * *** Heading Strings ((footnotes)) [id]
     *   -> array("Heading Strings", "id")
     *
     * @param string $line a wiki source line
     * @param bool   $strip cut footnotes
     * @return array [0] heading string [1] a fixed-heading anchor
     * @uses lib/html.php#make_heading
     */
    function get_heading($line, $strip = TRUE)
    {
        global $NotePattern;
        $id = make_heading($line, FALSE); // $line is modified inside
        if ($strip) {
            $line = preg_replace($NotePattern, '', $line); // cut footnotes
        }
        return array($line, $id);
    }
}

make_pagelink_nopg

no passage。経過時間加工、timestamp 取得をしなくなるので格段に早くなる(timestamp の取得などファイルシステムにアクセスするものはかなりのろい)。

if (! function_exists('make_pagelink_nopg')) {
    /**
     * Make a hyperlink to the page without passage
     *
     * @param string $page pagename
     * @param string $alias string to be displayed on the link
     * @param string $anchor anchor
     * @param string $refer reference pagename. query '&amp;refer=' is added. 
     * @param bool $isautolink flag if this link is created via autolink or not
     * @return string link html
     * @uses make_pagelink
     */
    function make_pagelink_nopg($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE)
    {
        // no passage
        global $show_passage;
        $tmp = $show_passage; $show_passage = 0;
        $link = make_pagelink($page, $alias, $anchor, $refer, $isautolink);
        $show_passage = $tmp;
        return $link;
    }
}

PukiWiki Syntax Definitions

$def_freeze   = '/^(?:#freeze(?!\w)\s*)+/im';
// define('PLUGIN_EDIT_FREEZE_REGEX', '/^(?:#freeze(?!\w)\s*)+/im'); // edit.inc.php
$def_headline = '/^(\*{1,3})/';
$def_include  = '/^#include.*\((.+)\)/';

PukiWiki Version Adapter

update_recent

if (! function_exists('update_recent')) {
    /**
     * Update recent
     * 
     * PukiWiki Version Adapter
     *
     * @param string $page
     */
    function update_recent($page)
    {
        if (is_page($page) && function_exists('lastmodified_add')) {
            lastmodified_add($page); // 1.4.7 or higher
        } elseif (function_exists('put_lastmodified')) {
            put_lastmodified();
        }
    }
}

PukiWiki Plus Adapter

get_plugin_dir

if (! function_exists('get_plugin_dir')) {
    /**
     * Get plugin directory name where a given plugin exists
     *
     * PukiWiki Plus! <-> PukiWiki Adapter
     *
     * @param string $plugin_name plugin name
     * @return string plugin directory name
     */
    function get_plugin_dir($plugin_name)
    {
        static $plugin_dir = array();
        if (isset($plugin_dir[$plugin_name])) return $plugin_dir[$plugin_name];

        $p_dirs = defined('EXT_PLUGIN_DIR') ? 
            array(EXT_PLUGIN_DIR, PLUGIN_DIR) : array(PLUGIN_DIR);
        foreach ($p_dirs as $p_dir) {
            if (file_exists($p_dir . $plugin_name . '.inc.php')) {
                $plugin_dir[$plugin_name] = $p_dir;
                break;
            }
        }
        return $plugin_dir[$plugin_name];
    }
}

auth::get_existpages

if (! class_exists('auth')) {
    class auth
    {
        function get_existpages($dir = DATA_DIR, $ext = '.txt')
        {
            return get_existpages($dir, $ext);
        }
    }
}

メモ

auth::get_existpages は権限がない人にはそもそも存在自体を隠して、リストに出さないようにしてしまえというもの。

一覧表示時、すでにログインをしていることが必須となる。ので、lsx には使用していない。

制限ページアクセス時に認証を求めるのが pukiwiki 的なので、一覧でとりあえず出してもらわないとそのページに飛べない。

悩ましいところではある。

Pukiwiki API Tips

exist_plugin

exist_plugin は関数名からは、 そのプラグインが存在しているかのチェックだけと見えるが、 実は require_once もしている。

require_once(PLUGIN_DIR . $plugin_name . '.inc.php);

とするよりは、

exist_plugin($plugin_name);

のほうが楽なので require 目的にも使える。

exist_plugin_[inline|convert|action] は対応する型の関数があるかどうかのチェックもしている。

do_plugin

do_plugin_convert, do_plugin_inline, do_plugin_action は function plugin_[plugin_name]_init の部分も実行してくれる。

プラグイン multilang を l という名前で実行するラッパープラグインの作成を例に使い方を説明する。

function plugin_l_inline()
{
    if (! exist_plugin_inline('multilang'))
        return '<span>l(): multilang plugin does not exist.</span>';
    $args = func_get_args();
    $body = array_pop($args);
    return do_plugin_inline('multilang', csv_implode(',', $args), $body);
}
function plugin_l_convert()
{
    if (! exist_plugin_convert('multilang'))
        return '<p>l(): multilang plugin does not exist.</p>';
    $args = func_get_args();
    return do_plugin_convert('multilang', csv_implode(',', $args));
}
function plugin_l_action()
{
    if (! exist_plugin_inline('action'))
        return array('msg'=>'l', 'body'=>'<p>l(): multilang plugin does not exist.</p');
    return do_plugin_action('multilang');
}

意味の理解を深めるためにこれとほぼ同等のものを標準 PHP 関数を用いて記述してみる。

function plugin_l_inline()
{
    if (! file_exists(PLUGIND_DIR . 'multilang' . '.inc.php'))
        return '<span>l(): multilang plugin does not exist.</span>';
    require_once(PLUGIN_DIR . 'multilang' . '.inc.php');
    if (! function_exists('plugin_multilang_inline'))
        return '<span>l(): multilang plugin does not exist.</span>';

    $args = func_get_args();
    if (function_exists('plugin_multilang_init')) call_user_func('plugin_multilang_init');
    return call_user_func_array('plugin_multilang_inline', $args);
}
function plugin_l_convert()
{
    if (! file_exists(PLUGIND_DIR . 'multilang' . '.inc.php'))
        return '<p>l(): multilang plugin does not exist.</p>';
    require_once(PLUGIN_DIR . 'multilang' . '.inc.php');
    if (! function_exists('plugin_multilang_convert'))
        return '<p>l(): multilang plugin does not exist.</p>';

    $args = func_get_args();
    if (function_exists('plugin_multilang_init')) call_user_func('plugin_multilang_init');
    return call_user_func_array('plugin_multilang_convert', $args);
}
function plugin_l_action()
{
    if (! file_exists(PLUGIND_DIR . 'multilang' . '.inc.php'))
        return array('msg'=>'l', 'body'=>'<p>l(): multilang plugin does not exist.</p');
    require_once(PLUGIN_DIR . 'multilang' . '.inc.php');
    if (! function_exists('plugin_multilang_action'))
        return array('msg'=>'l', 'body'=>'<p>l(): multilang plugin does not exist.</p');

    if (function_exists('plugin_multilang_init')) call_user_func('plugin_multilang_init');
    return call_user_func('plugin_multilang_action');
}

プラグインの関数そのものは、call_user_func_array のほうが呼びやすい・・・(csv_implode($args) しなくてよい)