<?php
/**
 * PyCode plugin: it embeds a Python script hosted in a remote repository.
 *
 * action.php: it defines all the methods used by PyCode plugin
 *      who interact with DokuWiki's events.
 *
 * @author Torpedo <dgtorpedo@gmail.com>
 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @package action
 */

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

require_once "method.php";  // common methods used by PyCode plugin

/**
 * This class defines all the methods used by the PyCode plugin to interact
 * with the DokuWiki's events.
 *
 * It extends DokuWiki's basic action defined in lib/plugins/action.php.
 *
 * @package action_pycode
 */
class action_plugin_pycode extends DokuWiki_Action_Plugin {

    /**
     * Constructor method for class suitable for any initialization.
     */
    public function __construct() {
        $this->mpp = new method_pycode_plugin;
    }

    /**
     * Here are registered the event handlers
     */
    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook("ACTION_ACT_PREPROCESS", "BEFORE", $this, "update_code", array ());
        $controller->register_hook("INDEXER_PAGE_ADD", "BEFORE",  $this, "add_code", array());
        $controller->register_hook("DOKUWIKI_DONE", "BEFORE", $this, "update_log", array ());
        $controller->register_hook("TOOLBAR_DEFINE", "AFTER", $this, "insert_button", array ());
    }

    /**
     * It substitutes, in the local copy of <file>, only the pice of code which
     * the user wants to update.
     *
     * After that it's necessary to refresh the page, but, since it's used
     * the function header() to reload the page it's important to call this
     * method before any actual output is sent.
     * So it's necessary to use ACTION_ACT_PREPROCESS.
     *
     * In DokuWiki, instead of use $_POST, is strongly recommended to access
     * to it using its input classes:
     *      $_POST["foo"]; becomes $INPUT->post->str("foo");
     *
     * @param (obj) $event the event object
     * @param (arr) $param data passed when this handler was registered
     */
    public function update_code(Doku_Event $event, $param) {
        global $INPUT;

        if ($INPUT->post->str("submit") == "Ok") {
            $loc_url = $INPUT->post->str("url");
            $code_new = unserialize(base64_decode($INPUT->post->str("new")));
            $ln_s = $INPUT->post->str("start");
            $ln_e = $INPUT->post->str("end");
            list($code_all_old, $flag, $name, $subname) = $this->mpp->_get_code($loc_url);

            $ls = $ln_s - 1;
            $le = $ln_e - 1;
            $ln = $le - $ls + 1;
            array_splice($code_all_old, $ls, $ln, $code_new);
            $this->mpp->_save_code($loc_url, $code_all_old);

            $uri = rtrim(DOKU_URL, "/") . $_SERVER['REQUEST_URI'];
            header("Location: " . $uri);
            exit;
        }
    }

    /**
     * It adds the code to the page which has to be indexed.
     *
     * Since the code is embedded and not write whitin the page, normally,
     * it is not parsed by the search engine.
     * So it's necessary to append the code to the page.
     *
     * @param (obj) $event the event object
     * @param (arr) $param data passed when this handler was registered
     */
    public function add_code(Doku_Event $event, $param) {
        $log = DOKU_PLUGIN . "pycode/tmp/logfile";
        $log = json_decode(file_get_contents($log), true);

        foreach ($log as $page => $files) {
            $page = substr($page, 0, -4);  // remove extension .txt
            $page = str_replace("/", ":", $page);
            if ($event->data["page"] == $page) {
                $event->data["body"] = p_cached_output(wikiFN($page));
            }
        }
    }

    /**
     * It records in a logfile all <file>(s) embedded in each wiki page.
     *
     * For each wiki page are listed all the <file>(s) embedded (same <file> is
     * listed only one time) so that one of them is no longer used, the relative
     * local copy can be safely deleted.
     *
     * During the creation of a wiki page, the list of <file>(s) is saved in a
     * temporary logfile.tmp.
     * When the page is created, logfile.tmp is used to update a permanent
     * logfile.
     * Same <file> in the same wiki page are listed only once.
     *
     * This function have to be called after the wiki page is created,
     * so it's necessary to use an event like DOKUWIKI_DONE.
     *
     * @param (obj) $event the event object
     * @param (arr) $param data passed when this handler was registered
     */
    public function update_log(Doku_Event $event, $param) {
        global $ACT;
        global $INFO;
        $wiki = str_replace(DOKU_INC . "data/pages/", "", $INFO["filepath"]);
        $tmp = DOKU_PLUGIN . "pycode/tmp/";
        $log_old = DOKU_PLUGIN . "pycode/tmp/logfile";
        $log_new = DOKU_PLUGIN . "pycode/tmp/logfile.tmp";

        if ($ACT == "show") {
            if (file_exists($log_old) == true) {
                // recover data stored in the logfile so we have sth. like this:
                // array {
                // ["<wiki-pg>1"] => array {
                //                   [0] => "<file>1",
                //                   [1] => "<file>2"
                //                   }
                // }
                $log_old = json_decode(file_get_contents($log_old), true);
                if (file_exists($log_new) == true) {
                    // recover data stored in the temporary logfile
                    $log_new = json_decode(file_get_contents($log_new), true);
                    if (array_key_exists($wiki, $log_old) == false) {
                        // all <file>(s) added to this wiki page are new
                        $log_old[$wiki] = array();
                        foreach ($log_new[$wiki] as $file) {
                            array_push($log_old[$wiki], $file);
                        }
                    }
                    else {
                        // look for new <file>(s) not present among old <file>(s)
                        $add = array_diff($log_new[$wiki], $log_old[$wiki]);

                        // add new entry <file>(s) in the logfile
                        foreach ($add as $file_add) {
                            array_push($log_old[$wiki], $file_add);
                        }

                        // look for old <file>(s) not present among new <file>(s)
                        $del = array_diff($log_old[$wiki], $log_new[$wiki]);

                        // check among all wiki pages if these <file>(s) deleted were
                        // the last ones and, if so, remove them from the logfile
                        // and the relatives <file>(s)
                        foreach ($del as $key => $file_del) {
                            $i = 0;
                            foreach ($log_old as $page => $files) {
                                if ($wiki == $page) {
                                    continue;
                                }
                                if (array_search($file_del, $files) !== false) {
                                    $i = 1;  // <file> is still embedded in another wiki page
                                }
                            }
                            if ($i == 0) {
                                unlink(DOKU_PLUGIN . "pycode/tmp/" . $file_del);
                                unset($log_old[$wiki][$key]);
                                $log_old[$wiki] = array_values($log_old[$wiki]);  // reindex
                            }
                        }
                    }
                }
                else {
                    // all <file>(s) deleted from this wiki page are old
                    $del = $log_old[$wiki];
                    if (empty($del) == false) {
                        foreach ($del as $key => $file_del) {
                            unlink(DOKU_PLUGIN . "pycode/tmp/" . $file_del);
                        }
                        unset($log_old[$wiki]);
                    }
                }

                if (count($log_old) == 0) {
                    unlink(DOKU_PLUGIN . "pycode/tmp/logfile");
                }
                else {
                    // now the array for logfile is update and ready to be written
                    $str = json_encode($log_old);
                    file_put_contents(DOKU_PLUGIN . "pycode/tmp/logfile", $str);
                }
            }
            elseif (file_exists($log_old) == false and file_exists($log_new) == true) {
                rename($log_new, $log_old);
            }

            if (file_exists($tmp) == true) {
                // remove empty directories
                $tree_dir = $this->mpp->_get_tree_dir(DOKU_PLUGIN . "pycode/tmp/");
                $this->mpp->_remove_empty_dir($tree_dir);

                // destroy temporary logfile
                if (file_exists(DOKU_PLUGIN . "pycode/tmp/logfile.tmp") == true) {
                    unlink(DOKU_PLUGIN . "pycode/tmp/logfile.tmp");
                }
            }
        }
    }

    /**
     * It inserts the PyCode's buttons in the toolbar.
     *
     * @param (obj) $event the event object
     * @param (arr) $param data passed when this handler was registered
     */
    public function insert_button(Doku_Event $event, $param) {
        $btns = $this->getConf("btns");

        if ($btns == 1){
            $event->data[] = array (
                'type' => "PyCode",
                'title' => "PyCode Plugin",
                'icon' => "../../plugins/pycode/images/picker.png"
            );
        }
    }
}