<?php
/**
 * Delete unnecessary languages -> administration function
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Taggic <taggic@t-online.de>
 */

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

/** Implicit data type:
 *
 * ^Lang is an array that looks like the following
 * { "core": [ $lang... ],
 *      "templates": [ $tpl_name: [ $lang... ], ... ],
 *      "plugins": [ $plugin_name: [ $lang... ], ... ]
 * }
 *     where $lang is a DokuWiki language code
 *           $tpl_name is the template name
 *           $plugin_name is the plugin name
 *  The $lang arrays are zero-indexed
 */

/** CSS Classes:
 *
 * ul.languages is an inline list of language codes
 *   if li.active is set, the text will be highlighted
 *   if li.enabled is set, the text is normal,
 *       otherwise it's red and striked-out
 * .module is set on text that represent module names: template names,
 *     plugin names and "dokuwiki"
 *
 * #langshortlist is the list of language with checkboxes
 * #langlonglist is the list of list of languages available for each module
 * .langdelete__text is the class set on the section wrapper around all the text
 */

/**
 * All DokuWiki plugins to extend the admin function
 * need to inherit from this class
 */
class admin_plugin_langdelete extends DokuWiki_Admin_Plugin {

    /** Fallback language */
    const DEFAULT_LANG = 'en';
    /** data stdObject  assigned by ->handle() and used in ->html() */
    private $d;

    /** return sort order for position in admin menu */
    function getMenuSort() { return 20; }

    /** Called when dispatching the DokuWiki action;
     * Puts the required data for ->html() in $->d */
    function handle() {
        $d =& $this->d;
        $d = new stdClass; // reset

        $d->submit = isset($_REQUEST['submit']);
        $submit =& $d->submit;

		/* Check security token */
        if ($submit) {
            $valid =& $d->valid;
            $valid = True;
            if (!checkSecurityToken()) {
                $valid = False;
                return;
            }
		}

		/* Set DokuWiki language info */
        $d->langs = $this->list_languages();
        $langs =& $d->langs;

        // $u_langs is in alphabetical (?) order because directory listing
        $d->u_langs = $this->lang_unique($langs);
        $u_langs =& $d->u_langs;

		/* Grab form data */
		if ($submit) {
			$d->dryrun = $_REQUEST['dryrun'];
            $lang_str = $_REQUEST['langdelete_w'];
		}

		/* What languages do we keep ? */
        $lang_keep[] = self::DEFAULT_LANG; // add 'en', the fallback
        $lang_keep[] = $conf['lang'];      // add current lang

        if ($submit) {
            /* Add form data to languages to keep */
            if (strlen ($lang_str) > 0) {
                $lang_keep = array_merge ($lang_keep, explode(',', $lang_str));
            }
        } else {
            // Keep every language on first run
            $lang_keep = $u_langs;
        }

        $lang_keep = array_values(array_filter(array_unique($lang_keep)));
        $d->lang_keep =& $lang_keep;

        /* Does the language we want to keep actually exist ? */
        $non_langs = array_diff ($lang_keep, $u_langs);
        if ($non_langs) {
            $d->nolang_s = implode (",", $non_langs);
        }

		/* Prepare data for deletion */
		if ($submit) {
            $d->langs_to_delete = $this->_filter_out_lang ($langs, $lang_keep);
		}

		/* What do the checkboxes say ? */
        if ($submit) {
            /* Grab checkboxes */
            $d->shortlang = array_keys ($_REQUEST['shortlist']);
            $shortlang =& $d->shortlang;

            /* Prevent discrepancy between shortlist and text form */
            if (array_diff ($lang_keep, $shortlang)
                || array_diff ($shortlang, $lang_keep))
            {
                $d->discrepancy = True;
            }
        } else {
            // Keep every language on first run
            $d->shortlang = $u_langs;
        }
    }

    /**
     * langdelete Output function
     *
     * Prints a table with all found language folders.
     * HTML and data processing are done here at the same time
     *
     * @author  Taggic <taggic@t-online.de>
     */
    function html() {
        global $conf; // access DW configuration array
        $d =& $this->d; // from ->handle()

        // In case we want to fetch the files from gh
        #$version = getVersionData();

        // langdelete__intro
        echo $this->locale_xhtml('intro');

        // input anchor
        echo '<a name="langdelete_inputbox"></a>'.NL;
        echo $this->locale_xhtml('guide');
        // input form
        $this->_html_form($d);


        $langs = $this->list_languages();
        $u_langs = $this->lang_unique($langs);


        /* Switch on form submission state */
        if (!$d->submit) {
            /* Show available languages */
            echo '<section class="langdelete__text">';
            echo $this->getLang('available_langs');
            $this->print_shortlist ($d);
            $this->html_print_langs($d->langs);
            echo '</section>';

        } else {
            /* Process form */

            /* Check token */
            if (!$d->valid) {
                echo "<p>Invalid security token</p>";
                return;
            }

            if ($d->discrepancy) {
                msg($this->getLang('discrepancy_warn'), 2);
            }
            if ($d->nolang_s) {
                msg($this->getLang('nolang') . $d->nolang_s , 2);
            }

            echo '<h2>'.$this->getLang('h2_output').'</h2>'.NL;

            if ($d->dryrun) {
                /* Display what will be deleted */
                msg($this->getLang('langdelete_willmsg'), 2);
                echo '<section class="langdelete__text">';
                echo $this->getLang('available_langs');
                $this->print_shortlist ($d);
                $this->html_print_langs($d->langs, $d->lang_keep);
                echo '</section>';

                msg($this->getLang('langdelete_attention'), 2);
                echo '<a href="#langdelete_inputbox">'.$this->getLang('backto_inputbox').'</a>'.NL;

            } else {
                /* Delete and report what was deleted */
                msg($this->getLang('langdelete_delmsg'), 0);

                echo '<section class="langdelete__text">';
                $this->html_print_langs($d->langs_to_delete);
                echo '</section>';

                echo '<pre>';
                $this->remove_langs($d->langs_to_delete);
                echo '</pre>';
            }
        }
    }

    /**
     * Display the form with input control to let the user specify,
     * which languages to be kept beside en
     *
     * @author  Taggic <taggic@t-online.de>
     */
    private function _html_form (&$d) {
        global $ID, $conf;

        echo '<form id="langdelete__form" action="'.wl($ID).'" method="post">';
        echo     '<input type="hidden" name="do" value="admin" />'.NL;
        echo     '<input type="hidden" name="page" value="'.$this->getPluginName().'" />'.NL;
        formSecurityToken();

        echo     '<fieldset class="langdelete__fieldset"><legend>'.$this->getLang('i_legend').'</legend>'.NL;

        echo         '<label class="formTitle">'.$this->getLang('i_using').':</label>';
        echo         '<div class="box">'.$conf['lang'].'</div>'.NL;

        echo         '<label class="formTitle" for="langdelete_w">'.$this->getLang('i_shouldkeep').':</label>';
        echo         '<input type="text" name="langdelete_w" class="edit" value="'.hsc(implode(',', $d->lang_keep)).'" />'.NL;

        echo         '<label class="formTitle" for="option">'.$this->getLang('i_runoption').':</label>';
        echo         '<div class="box">'.NL;
        echo             '<input type="checkbox" name="dryrun" checked="checked" /> ';
        echo             '<label for="dryrun">'.$this->getLang('i_dryrun').'</label>'.NL;
        echo         '</div>'.NL;

        echo         '<button name="submit">'.$this->getLang('btn_start').'</button>'.NL;

        echo     '</fieldset>'.NL;
        echo '</form>'.NL;
    }

    /** Print the language shortlist and cross-out those not in $keep */
    function print_shortlist (&$d) {
        $shortlang =& $d->shortlang;

        echo '<ul id="langshortlist" class="languages">';

        # As the disabled input won't POST
        echo '<input type="hidden" name="shortlist['.self::DEFAULT_LANG.']"'
            .' form="langdelete__form" />';

        foreach ($d->u_langs as $l) {
            $checked = in_array($l, $shortlang) || $l == self::DEFAULT_LANG;

            echo '<li'.($checked ? ' class="enabled"' : '').'>';

            echo '<input type="checkbox" id="shortlang-'.$l.'"'
                .' name="shortlist['.$l.']"'
                .' form="langdelete__form"'
                .($checked ? ' checked' : '')
                .($l == self::DEFAULT_LANG ? ' disabled' : '')
                .' />';

            echo '<label for="shortlang-'.$l.'">';
            if ($checked) {
                echo $l;
            } else {
                echo '<del>'.$l.'</del>';
            }
            echo '</label>';

            echo '</li>';
        }
        echo '</ul>';
    }


    /** Display the languages in $langs for each module as a HTML list;
     * Cross-out those not in $keep
     *
     * Signature: ^Lang, Array => () */
    private function html_print_langs ($langs, $keep = null) {
        /* Print language list, $langs being an array;
         * Cross out those not in $keep */
        $print_lang_li = function ($langs) use ($keep) {
            echo '<ul class="languages">';
            foreach ($langs as $val) {
                // If $keep is null, we keep everything
                $enabled = is_null($keep) || in_array ($val, $keep);

                echo '<li val="'.$val.'"'
                    .($enabled ? ' class="enabled"' : '')
                    .'>';
                if ($enabled) {
                    echo $val;
                } else {
                    echo '<del>'.$val.'</del>';
                }
                echo '</li>';
            }
            echo '</ul>';
        };


        echo '<ul id="langlonglist">';

        // Core
        echo '<li><span class="module">'.$this->getLang('dokuwiki_core').'</span>';
        $print_lang_li ($langs['core']);
        echo '</li>';

        // Templates
        echo '<li>'.$this->getLang('templates');
        echo     '<ul>';
        foreach ($langs['templates'] as $name => $l) {
            echo '<li><span class="module">'.$name.':</span>';
            $print_lang_li ($l);
            echo '</li>';
        }
        echo     '</ul>';
        echo '</li>';

        // Plugins
        echo '<li>'.$this->getLang('plugins');
        echo     '<ul>';
        foreach ($langs['plugins'] as $name => $l) {
            echo '<li><span class="module">'.$name.':</span>';
            $print_lang_li ($l);
            echo '</li>';
        }
        echo     '</ul>';
        echo '</li>';

        echo '</ul>';
    }

    /** Returns the available languages for each module
     * (core, template or plugin)
     *
     * Signature: () => ^Lang
     */
    private function list_languages () {
        // See https://www.dokuwiki.org/devel:localization

        /* Returns the subfolders of $dir as an array */
        $dir_subfolders = function ($dir) {
            $sub = scandir($dir);
            $sub = array_filter ($sub, function ($e) use ($dir) {
                return is_dir ("$dir/$e")
                       && !in_array ($e, array('.', '..'));
            } );
            return $sub;
        };

        /* Return an array of template names */
        $list_templates = function () use ($dir_subfolders) {
            return $dir_subfolders (DOKU_INC."lib/tpl");
        };

        /* Return an array of languages available for the module
         * (core, template or plugin) given its $root directory */
        $list_langs = function ($root) use ($dir_subfolders) {
            $dir = "$root/lang";
            if (!is_dir ($dir)) return;

            return $dir_subfolders ($dir);
        };

        /* Get templates and plugins names */
        global $plugin_controller;
        $plugins = $plugin_controller->getList();
        $templates = $list_templates();

        return array(
            "core" => $list_langs (DOKU_INC."inc"),
            "templates" => array_combine ($templates,
                array_map ($list_langs,
                    array_prefix ($templates, DOKU_INC."lib/tpl/"))),
            "plugins" => array_combine ($plugins,
                array_map ($list_langs,
                    array_prefix ($plugins, DOKU_PLUGIN)))
        );
    }

    /** Remove $lang_keep from the module languages $e
     *
     * Signature: ^Lang, Array => ^Lang */
    private function _filter_out_lang ($e, $lang_keep) {
        // Recursive function with cases being an array of arrays, or an array
        if (count ($e) > 0 && is_array (array_values($e)[0])) {
            foreach ($e as $k => $elt) {
                $out[$k] = $this->_filter_out_lang ($elt, $lang_keep);
            }
            return $out;

        } else {
            return array_filter ($e, function ($v) use ($lang_keep) {
                return !in_array ($v, $lang_keep);
            });
        }
    }

    /** Return an array of the languages in $l
     *
     * Signature: ^Lang => Array */
    private function lang_unique ($l) {
        foreach ($l['core'] as $lang) {
            $count[$lang]++;
        }
        foreach ($l['templates'] as $tpl => $arr) {
            foreach ($arr as $lang) {
                $count[$lang]++;
            }
        }
        foreach ($l['plugins'] as $plug => $arr) {
            foreach ($arr as $lang) {
                $count[$lang]++;
            }
        }

        return array_keys ($count);
    }

    /** Delete the languages from the modules as specified by $langs
     *
     * Signature: ^Lang => () */
    private function remove_langs($langs) {
        foreach ($langs['core'] as $l) {
            $this->rrm(DOKU_INC."inc/lang/$l");
        }

        foreach ($langs['templates'] as $tpl => $arr) {
            foreach ($arr as $l) {
                $this->rrm(DOKU_INC."lib/tpl/$tpl/lang/$l");
            }
        }

        foreach ($langs['plugins'] as $plug => $arr) {
            foreach ($arr as $l) {
                $this->rrm(DOKU_INC."lib/plugins/$plug/lang/$l");
            }
        }
    }

    /** Recursive file removal of $path with reporting */
    private function rrm ($path) {
        if (is_dir ($path)) {
            $objects = scandir ($path);
            foreach ($objects as $object) {
                if (!in_array ($object, array('.', '..'))) {
                    $this->rrm("$path/$object");
                }
            }
            $sucess = @rmdir ($path);
            if (!$sucess) { echo "Failed to delete $path/\n"; }
            else echo "Delete $path\n";
        } else {
            $sucess = @unlink ($path);
            if (!$sucess) { echo "Failed to delete $path\n"; }
            else echo "Delete $path\n";
        }
    }
}

/** Returns an array with each element of $arr prefixed with $prefix */
function array_prefix ($arr, $prefix) {
    return array_map (
        function ($p) use ($prefix) { return $prefix.$p; },
        $arr);
}