<?php
/**
 * DokuWiki Plugin wikistats (Syntax Component)
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  Chris4x4 <4x4.chris@gmail.com>
 */

// must be run within Dokuwiki
if (!defined('DOKU_INC')) die();

if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');

/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_wikistats extends DokuWiki_Syntax_Plugin {
   /**
    * Get an associative array with plugin info.
    *
    * <p>
    * The returned array holds the following fields:
    * <dl>
    * <dt>author</dt><dd>Author of the plugin</dd>
    * <dt>email</dt><dd>Email address to contact the author</dd>
    * <dt>date</dt><dd>Last modified date of the plugin in
    * <tt>YYYY-MM-DD</tt> format</dd>
    * <dt>name</dt><dd>Name of the plugin</dd>
    * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd>
    * <dt>url</dt><dd>Website with more information on the plugin
    * (eg. syntax description)</dd>
    * </dl>
    * @param none
    * @return Array Information about this plugin class.
    * @public
    * @static
    */
    function getInfo(){
        return confToHash(dirname(__FILE__).'/plugin.info.txt');
    }

   /**
    * Get the type of syntax this plugin defines.
    *
    * The type of this plugin is "substition".
    *
    * @param none
    * @return String <tt>'substition'</tt>.
    * @public
    * @static
    */
    public function getType() {
        return 'substition';
    }

   /**
    * Define how this plugin is handled regarding paragraphs.
    *
    * <p>
    * This method is important for correct XHTML nesting. It returns
    * one of the following values:
    * </p>
    * <dl>
    * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
    * <dt>block</dt><dd>Open paragraphs need to be closed before
    * plugin output.</dd>
    * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
    * </dl>
    * @param none
    * @return String <tt>'normal'</tt>.
    * @public
    * @static
    */
    public function getPType() {
        return 'normal';
    }

   /**
    * Where to sort in?
    *
    * Sort the plugin in just behind the formating tokens
    * Low numbers go before high numbers
    *
    * @param none
    * @return Integer <tt>128</tt>.
    * @public
    * @static
    */
    public function getSort() {
        return 128;
    }

   /**
    * Connect lookup pattern to lexer.
    *
    * @param $aMode String The desired rendermode.
    * @return none
    * @public
    * @see render()
    */
    public function connectTo($mode) {
        $this->Lexer->addSpecialPattern('\{\{wikistats>[^}]*\}\}',$mode,'plugin_wikistats');
    }

    /**
     * Handle matches of the wikistats syntax
     *
     * @param string $match The match of the syntax
     * @param int    $state The state of the handler
     * @param int    $pos The position in the document
     * @param Doku_Handler    $handler The handler
     * @return array Data for the renderer
     */
    public function handle($match, $state, $pos, Doku_Handler &$handler){
        $match = substr($match, 12, -2);

        $data = array(
            'ns' => array(),
            'type' => array()
        );

        $match = explode('&', $match);
        foreach($match as $m) {
            if (preg_match('/(\w+)\s*=(.+)/', $m, $temp) == 1){
                $this->handleNamedParameter($temp[1], trim($temp[2]), $data);
            } else {
                $this->addNamespace($data, trim($m));
            }
        }

        return $data;
    }

    /**
     * Handle parameters that are specified using <name>=<value> syntax
     */
    function handleNamedParameter($name, $value, &$data) {
        static $types = array('pages', 'medias', 'stats');

        switch($name) {
            case 'ns':
                foreach(preg_split('/\s*,\s*/', $value) as $value) {
                    $this->addNamespace($data, $value);
                }
                break;
            case 'type':
                if (preg_match('/(\w+)/', $value, $match) == 1) {
                    if (in_array($match[1], $types)) {
                        $data[$name] = $match[1];
                    }
                }
                break;
        }
    }

    /**
     * Clean-up the namespace name and add it (if valid) into the $data array
     */
    function addNamespace(&$data, $namespace) {
        $action = ($namespace{0} == '-') ? 'exclude' : 'include';
        $namespace = cleanID(preg_replace('/^[+-]/', '', $namespace));
        if (!empty($namespace)) {
            $data['ns'][$action][] = $namespace;
        }
    }

    /**
     * Render xhtml output or metadata
     *
     * @param string         $mode      Renderer mode (supported modes: xhtml)
     * @param Doku_Renderer  $renderer  The renderer
     * @param array          $data      The data from the handler() function
     * @return bool If rendering was successful.
     */
    public function render($mode, Doku_Renderer &$renderer, $data) {
	    global $conf;
        global $ID;
        global $TOC;

        if ($mode != 'xhtml') return false;

        // prevent caching to ensure the included pages are always fresh
        $renderer->info['cache'] = false;

        $list = array();
        $content = '';
        
        switch ($data['type']) {
            case "pages":
                search($list, $conf['datadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']), '');
                $content = $list['pages_count'];
                break;
            case "medias":
                search($list, $conf['mediadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']));
                $content = $list['medias_count'];
                break;
            case "stats":
                search($list, $conf['datadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']), '');
                search($list, $conf['mediadir'], array($this, '_search_count'), array('type' => $data['type'], 'ns' => $data['ns']));

                if ($this->getConf('display_toc')) {
                    $TOC = p_get_metadata($ID,'description tableofcontents');
                } else {
                    $TOC = NULL;
                }
                $content .= $this->displayResourceStats($list);
                $content .= $this->displayTagStats($list);
                $content .= $this->displayNamespaceStats($list['dir_label']['ns']);

                break;
        }
        $renderer->doc .= $content;

        return true;
    }

    function _search_count(&$data, $base, $file, $type, $lvl, $opts){
        if ($type == 'd') {
            if ($data['dir_nest'] < $lvl) $data['dir_nest'] = $lvl;

            $data['dir_count']++;

            return true;
        }
        
        $file = str_replace("/", ":", $file);
        
        // filter included namespaces
        if (isset($opts['ns']['include'])) {
            if (!$this->isInNamespace($opts['ns']['include'], $file)) return false;
        }

        // filter excluded namespaces
        if (isset($opts['ns']['exclude'])) {
            if ($this->isInNamespace($opts['ns']['exclude'], $file)) return false;
        }

        switch ($opts['type']) {
            case "pages":
                $data['pages_count']++;
                break;
            case "medias":
                $data['medias_count']++;
                break;
            case "stats":
                $path = $file;
                $file = substr($file, 1);
                $file = substr($file, 0, strrpos($file, ':'));
                //if (pathinfo($path, PATHINFO_EXTENSION) != 'txt') $file = substr($file, 0, strrpos($file, ':'));
                
                switch (pathinfo($path, PATHINFO_EXTENSION)) {
                    case 'txt':
                        $data['pages_count']++;
                        $data['dir_label']['ns'][$file]['pages']++;
                        break;
                    case 'swf':
                        $data['flash_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'jpg':
                    case 'jpeg':
                    case 'png':
                    case 'gif':
                    case 'svg':
                        $data['images_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'mov':
                    case 'avi':
                    case 'flv':
                    case 'mp4':
                    case 'webm':
                    case 'ogv':
                        $data['movies_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'wav':
                    case 'mp3':
                    case 'ogg':
                        $data['audio_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'xls':
                    case 'xlsm':
                    case 'xlsx':
                    case 'ods':
                        $data['sheets_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'doc':
                    case 'docx':
                    case 'odt':
                        $data['writer_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'ppt':
                    case 'pptx':
                    case 'odp':
                        $data['presentation_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'djvu':
                    case 'epub':
                    case 'mobi':
                    case 'pdf':
                        $data['documents_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'tar':
                    case 'arj':
                    case 'zip':
                    case 'bzip':
                    case 'rar':
                    case 'tgz':
                    case 'gz':
                    case '7z':
                    case 'bz2':
                        $data['archives_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    case 'exe':
                    case 'com':
                        $data['binaries_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                    default:
                        //var_export($file);
                        $data['others_count']++;
                        $data['dir_label']['ns'][$file]['medias']++;
                        break;
                }
                break;
        }

        return false;
    }

    /**
     * Check if page belongs to one of namespaces in the list
     */
    function isInNamespace($namespaces, $id) {
        foreach($namespaces as $ns) {
            if ((strpos($id, ':' . $ns . ':') === 0)) {
                return true;
            }
        }
        return false;
    }
  
    /**
     * Display pages and medias stats
     */
    private function displayResourceStats($list) {
        global $TOC;

        $content = '';

        if ($this->getConf('display_ressources_stats')) {
            if ($this->getConf('display_toc')) {
                $TOC[] = Array('hid' => 'resources', 'title' => $this->getLang('resources_title'), 'type' => 'ul', 'level' => '2');
            }

            $class = $this->getConf('display_type');
            $col = "page";

            $content .= '<!-- Resources Stats -->'.DOKU_LF;
            $content .= '<h2 class="sectionedit2" id="resources">'.$this->getLang('resources_title').'</h2>'.DOKU_LF;
            $content .= '<div class="level2">'.DOKU_LF;
            $content .= '<table class="'.$class.'">'.DOKU_LF;
            $content .= DOKU_TAB.'<tr>'.DOKU_LF;
            $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('resources').'</th>'.DOKU_LF;
            $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">#</th>'.DOKU_LF;
            $content .= DOKU_TAB.'</tr>'.DOKU_LF;

            if(empty($list)) {
                // Skip output
                $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="2">'.$this->getLang('empty_output').'</td>'.DOKU_LF;
                $content .= DOKU_TAB.'</tr>'.DOKU_LF;
            } else {
                foreach($list as $resname => $count) {
                    if ($count <= 0) continue; // don't display resources with zero occurrences
                    if ($resname === "dir_nest") continue;
                    if ($resname === "dir_count") continue;
                    if ($resname === "dir_label") continue;
                    $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$this->getLang($resname).'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$count.'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.'</tr>'.DOKU_LF;
                }
            }

            $content .= '</table>'.DOKU_LF;
            $content .= '</div>'.DOKU_LF.DOKU_LF;
        }
        return $content;
    }
  
    /**
     * Display tags related stats
     */
    private function displayTagStats($list) {
        global $TOC;

        $content = '';

        if ($this->getConf('display_tags_stats')) {
            if (plugin_isdisabled('tag') || (!$tag = plugin_load('helper', 'tag'))) {
                msg('The Tag Plugin must be installed to display tag related stats.', -1);
            } else {
                if ($this->getConf('display_toc')) {
                    $TOC[] = Array('hid' => 'tags', 'title' => $this->getLang('tags_title'), 'type' => 'ul', 'level' => '2');
                }

                $occurrences = $tag->tagOccurrences(NULL, NULL, true, NULL);
                ksort($occurrences);
                
                $class = $this->getConf('display_type');
                $col = "page";

                $content .= '<!-- Tags Stats -->'.DOKU_LF;
                $content .= '<h2 class="sectionedit2" id="tags">'.$this->getLang('tags_title').'</h2>'.DOKU_LF;
                $content .= '<div class="level2">'.DOKU_LF;
                $content .= '<table class="'.$class.'">'.DOKU_LF;
                $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('tags').' ('.count($occurrences).')</th>'.DOKU_LF;
                $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">#</th>'.DOKU_LF;
                $content .= DOKU_TAB.'</tr>'.DOKU_LF;

                if (empty($occurrences)) {
                    // Skip output
                    $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="2">'.$this->getLang('empty_output').'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.'</tr>'.DOKU_LF;
                } else {
                    foreach($occurrences as $tagname => $count) {
                        if ($count <= 0) continue; // don't display tags with zero occurrences
                        $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                        $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$tagname.'</td>'.DOKU_LF;
                        $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$count.'</td>'.DOKU_LF;
                        $content .= DOKU_TAB.'</tr>'.DOKU_LF;
                    }
                }

                $content .= '</table>'.DOKU_LF;
                $content .= '</div>'.DOKU_LF.DOKU_LF;
            }
        }
        return $content;
    }

    /**
     * Display namespaces related stats
     */
    private function displayNamespaceStats($list) {
        global $TOC;

        $content = '';

        if ($this->getConf('display_namespaces_stats')) {
            if ($this->getConf('display_toc')) {
                $TOC[] = Array('hid' => 'namespaces', 'title' => $this->getLang('namespaces_title'), 'type' => 'ul', 'level' => '2');
            }

            $class = $this->getConf('display_type');
            $col = "page";

            $content .= '<!-- Namespaces Stats -->'.DOKU_LF;
            $content .= '<h2 class="sectionedit2" id="namespaces">'.$this->getLang('namespaces_title').'</h2>'.DOKU_LF;
            $content .= '<div class="level2">'.DOKU_LF;
            $content .= '<table class="'.$class.'">'.DOKU_LF;
            $content .= DOKU_TAB.'<tr>'.DOKU_LF;
            $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'">'.$this->getLang('namespaces').' ('.count($list).')</th>'.DOKU_LF;
            $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'"># '.$this->getLang('pages_count').'</th>'.DOKU_LF;
            $content .= DOKU_TAB.DOKU_TAB.'<th class="'.$col.'"># '.$this->getLang('medias_count').'</th>'.DOKU_LF;
            $content .= DOKU_TAB.'</tr>'.DOKU_LF;

            if(empty($list)) {
                // Skip output
                $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'" colspan="3">'.$this->getLang('empty_output').'</td>'.DOKU_LF;
                $content .= DOKU_TAB.'</tr>'.DOKU_LF;
            } else {
                ksort($list);

                foreach($list as $namespace => $arr) {
                    if ($namespace == '') $namespace = '[root]';
                    $content .= DOKU_TAB.'<tr>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.$namespace.'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.(isset($arr['pages']) ? $arr['pages'] : '0') .'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.DOKU_TAB.'<td class="'.$class.'">'.(isset($arr['medias']) ? $arr['medias'] : '0') .'</td>'.DOKU_LF;
                    $content .= DOKU_TAB.'</tr>'.DOKU_LF;
                }
            }

            $content .= '</table>'.DOKU_LF;
            $content .= '</div>'.DOKU_LF.DOKU_LF;
        }
        return $content;
    }

}

// vim:ts=4:sw=4:et:
