<?php

/**
 * This file contains all functions for displaying the publication sources page in admin menu
 * 
 * @package teachpress\admin\publications
 * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2 or later
 */

include_once(__DIR__ . '/../core/constants.php');

/**
 * Add help tab for sources page
 */
function tp_import_publication_sources_help() {
    $screen = get_current_screen();  
    $screen->add_help_tab( array(
        'id'        => 'tp_import_publication_sources_help',
        'title'     => esc_html__('Sources'),
        'content'   => '<p><strong>' . esc_html__('Publication sources') . '</strong></p>
                        <p>' . esc_html__("Additional publication sources to scan regularly.",'teachpress') . '</p>',
     ) );
}

/**
 * Auxiliary function to get source url from dict.
 */
function tp_get_source_url($source) {
    return trim($source['src_url']);
}
    
/**
 * The controller for the import page of teachPress
 * @since 9.0.0
*/ 
function tp_show_publication_sources_page() {
    if ( isset($_POST['tp_sources_save']) ) {
        TP_Publication_Sources_Page::sources_actions($_POST);
    }
    
    TP_Publication_Sources_Page::sources_tab();      
}
        
/**
 * This function is the REST call implementation for updating sources.
 * @since 9.0.0
 */
function tp_rest_update_sources() {
    sleep(2);  // insert small delay in case of repeated calls
    return new WP_REST_Response(TP_Publication_Sources_Page::update_sources());
}

/**
 * This class contains functions for generating the publication sources page.
 * @since 9.0.0
 */
class TP_Publication_Sources_Page {
    /**
     * Returns current sources.
     */
    public static function get_current_sources() {
        global $wpdb;
        $source_urls = $wpdb->get_results("SELECT * FROM " . TEACHPRESS_MONITORED_SOURCES);
        $result = array();
        
        foreach ($source_urls as $src_url) {
            $result[] = array("src_url" => $src_url->name,
                              "last_res" => $src_url->last_res,
                              "update_time" => $src_url->update_time);
        }
        
        return $result;
    }
    
    /**
     * Returns the table rows for sources rendering
     */
    public static function get_pages_rows($current_pages) {
        $result = "";
        
        $alternate = true;
        
        foreach ($current_pages as $src_url) {
            $last_res = $src_url['last_res'];
            if (strlen($last_res) == 0) {
                $last_res = esc_html__("URL not scanned yet.", "teachpress");
            }
            $result .= sprintf("<tr class='%s'><td class='tp_url'>%s</td><td>%s</td><td>%s</td></tr>",
                               $alternate ? "alternate" : "", $src_url['src_url'],
                               esc_html__($last_res, "teachpress"), $src_url['update_time']);
            $alternate = ! $alternate;
        }
        
        return $result;
    }
    
    /**
     * Shows the sources
     * @since 9.0.0
     * @access public
    */
    public static function sources_tab () {
        ?>

        <div class="wrap">
            <h2><?php echo esc_html__('Auto-publish','teachpress'); ?></h2>
            <div class="teachpress_message teachpress_message_blue">
            <?php echo esc_html__("The following URLs can be scanned regularly and their bibtex entries
               automatically imported if they have changed. The publication log can
               be consulted on the Import/Export page.", "teachpress");?><br/><br/>
            <?php echo esc_html__("Zotero group bibliographies can be downloaded in BibTeX format by using special URLs such as <code>zotero://group/&lt;group_id&gt;/</code>,
                where <code>group_id</code> is the group id (numerical) found on zotero.org.", "teachpress");?>
            </div>
            <form id="tp_sources" name="tp_sources"
                  action="<?php echo esc_url($_SERVER['REQUEST_URI']); ?>" enctype="multipart/form-data" method="post">
                <p>
                    <label for="tp_source_freq"><? echo esc_html__("Update frequency:", "teachpress");?></label>

                    <select name="tp_source_freq" id="tp_source_freq" onchange="tp_source_freq_changed()" >
                        <?php
                            $cur_freq = TP_Publication_Sources_Page::get_update_freq();
                            $all_freqs = array("never" => esc_html__("Never (disable updates)", "teachpress"),
                                               "hourly" => esc_html__("Hourly", "teachpress"),
                                               "twicedaily" => esc_html__("Twice a day", "teachpress"),
                                               "daily" => esc_html__("Daily (recommended)", "teachpress"));
                            foreach ($all_freqs as $val => $render) {
                                print(sprintf("<option value='%s' %s>%s</option>", $val, $val == $cur_freq ? "selected='selected'" : "", $render));
                            }
                        ?>
                    </select>
                </p>
                
                <p id="tp_sources_holder">
                    <table id="tp_sources_table" class="widefat" cellspacing="0" cellpadding="0" border="0">
                        <thead>
                            <tr>
                                <td>URL</td>
                                <td><?php echo esc_html__("Previous update result", "teachpress");?></td>
                                <td><?php echo esc_html__("Date", "teachpress") . " (UTC)";?></td>
                            </tr>
                        </thead>
                        <tbody>
                            <?php $cur_sources = TP_Publication_Sources_Page::get_current_sources();
                                   print(TP_Publication_Sources_Page::get_pages_rows($cur_sources)); ?>
                        <tbody>
                    </table>
                    <label style="display:none;" id="tp_sources_area_lbl" for="tp_sources_area">
                        <?php echo esc_html__("One URL per line. Start each URL with http://, https:// or zotero://.", "teachpress"); ?></label>
                <textarea id="tp_sources_area" name="tp_sources_area" style="width: 100%; display: none;"><?php
                              $cur_sources = TP_Publication_Sources_Page::get_current_sources();
                              print(implode("\n", array_map('tp_get_source_url', $cur_sources)));
                    ?></textarea>
                </p>

                <p><button class="button-secondary" name="tp_edit_sources" id="tp_edit_sources"
                           type="button" onclick="teachpress_edit_sources()">
                                <?php echo esc_html__("Edit URL list", "teachpress");?></button>
                    <button class="button-secondary" name="tp_sources_cancel" id="tp_sources_cancel"
                    type="button" onclick="teachpress_edit_sources()" style="display: none;">
                        <?php echo esc_html__("Cancel", "teachpress");?></button></p>

                <p style="margin-top: 60px;"><button class="button-primary disabled"
                   name="tp_sources_save" id="tp_sources_save" type="submit" >
                    <?php echo esc_html__("Save configuration", "teachpress");?></button></p>
                <?php wp_nonce_field( 'verify_teachpress_auto_publish', 'tp_nonce', false, true ); ?>
            </form>
        </div>

        <?php
    }
    
    /**
     * This function executes all source publication action calls
     * @global object $current_user
     * @param array $post                   The $_POST array
     * @since 9.0.0
     * @access public
     */
    public static function sources_actions ($post) {
        // Check nonce field
        TP_Publication_Sources_Page::check_nonce_field();
        
        $sources_area = isset($post['tp_sources_area']) ? trim($post['tp_sources_area']) : '';
        $sources_to_monitor = array_filter(preg_split("/\r\n|\n|\r/", $sources_area),
                                           function($k) { return strlen(trim($k)) > 0; });
        $sources_to_monitor = array_map(function ($k) { return trim($k); }, $sources_to_monitor);
        $new_freq = isset($post['tp_source_freq']) ? trim($post['tp_source_freq']) : 'hourly';
        
        $installed = TP_Publication_Sources_Page::install_sources($sources_to_monitor);
                
        // manage cron hook
        if (count($sources_to_monitor) == 0 || $new_freq == 'never') {
            TP_Publication_Sources_Page::uninstall_cron(); // not needed anymore
        } else {
            TP_Publication_Sources_Page::install_cron($new_freq); // no problem if cron already installed
        }
        
        $new_freq = TP_Publication_Sources_Page::get_update_freq();
        get_tp_message( sprintf(esc_html__('Configuration updated with %d URL(s) at frequency "%s".', "teachpress"),
                                 count($sources_to_monitor), $new_freq) );
    }

    /**
     * Finds the current frequency of schedule.
     * @return Current frequency, or 'never' if none scheduled.
     * @since 9.0.0
     * @access public
     */
    public static function get_update_freq() {
        $result = wp_get_schedule(TEACHPRESS_CRON_SOURCES_HOOK);
        if ($result === false) {
            $result = 'never';
        }
        return $result;
    }
            
    /**
     * This function installs monitored bibtex sources. Sources present in the db but not
     * in the sources specified as a parameter are removed from the db.
     * @global object $wpdb
     * @param array $sources    An array of source URL strings.
     * @return Only the newly added URLs to monitor - can be the empty array.
     * @since 9.0.0
     * @access public
     */
    public static function install_sources($sources) {
        // find current sources already installed so as not to install them uselessly
        $cur_sources = TP_Publication_Sources_Page::get_current_sources();
        $cur_source_names = array();
        foreach ($cur_sources as $cur_src) {
            $cur_source_names[] = $cur_src['src_url'];
        }
        
        // start installing sources not present in database
        global $wpdb;
        $toremove = array();
        
        foreach ( $cur_source_names as $existing_source ) {
            if (!in_array($existing_source, $sources)) {
                $toremove[] = $existing_source;
            }
        }
        
        // create the filter set for the delete instruction
        $filter_set = "''"; // empty set
        if (count($toremove) > 0) {
            $filter_set = implode(",", array_map(function ($k) { return "'" . esc_sql($k) . "'"; }, $toremove));
        }

        // remove useless entries
        $wpdb->query( "DELETE FROM " . TEACHPRESS_MONITORED_SOURCES . " WHERE name IN ( " . $filter_set . " )");
        
        // write new entries -- could be done in a single statement
        $result = array();
        foreach( $sources as $element ) {
            if (! in_array($element, $cur_source_names) ) {
                $wpdb->insert(TEACHPRESS_MONITORED_SOURCES, array('name' => $element, 'md5' => 0),
                              array('%s', '%d'));
                $result[] = $element;
            }
        }
        
        return $result;
    }
            
    /**
     * This function installs the cron hook.
     * @param string $freq    Frequency of cron.
     * @since 9.0.0
     * @access public
     */
    public static function install_cron($freq) {
        // install action if not alreay installed
        if ( ! has_action( TEACHPRESS_CRON_SOURCES_HOOK, 'TP_Publication_Sources_Page::tp_cron_exec' ) ) {
            add_action( TEACHPRESS_CRON_SOURCES_HOOK, 'TP_Publication_Sources_Page::tp_cron_exec' );
        }
        
        // schedule hook if freq has changed and freq is not never
        if ( TP_Publication_Sources_Page::get_update_freq() != $freq && $freq != 'never' ) {
            TP_Publication_Sources_Page::uninstall_cron();
            wp_schedule_event( time(), $freq, TEACHPRESS_CRON_SOURCES_HOOK );
        }
    }

    /**
     * This function uninstalls the cron hook.
     * @since 9.0.0
     * @access public
     */
    public static function uninstall_cron() {
        $timestamp = wp_next_scheduled( TEACHPRESS_CRON_SOURCES_HOOK );
        wp_unschedule_event( $timestamp, TEACHPRESS_CRON_SOURCES_HOOK );
    }
            
    /**
     * Execute the scheduled sources update.
     * @since 9.0.0
     * @access public
     */
    public static function tp_cron_exec() {
        TP_Publication_Sources_Page::update_sources();
    }
        
    /**
     * Performs update for all sources registered in the db.
     * @since 9.0.0
     */
    public static function update_sources() {
        $result = array();
        
        // list all sources
        global $wpdb;
        $source_urls = $wpdb->get_results("SELECT * FROM " . TEACHPRESS_MONITORED_SOURCES);
        
        foreach ($source_urls as $src_url) {
            $result[] = array_merge(TP_Publication_Sources_Page::update_source($src_url->name, $src_url->md5),
                                    array('src_id' => $src_url->src_id, 'src_name' => $src_url->name));
        }
        
        foreach ($result as $cur_res) {
            $r = $wpdb->update(TEACHPRESS_MONITORED_SOURCES,
                array('md5' => $cur_res[0], 'last_res' => $cur_res[2], 'update_time' => current_time('mysql', 1)),
                array('src_id' => $cur_res['src_id']));
        }
        
        return $result;
    }

    /**
     * Performs update for a single source.
     * @param $url   The URL of the source. URL protocols supported: http://, https://.
     * @param previous_sig   Digest the last time the file was polled, 0 if this is the first time.
     * @param this_req   Http Request (callee assigned), by reference.
     * @return new_signature, nb_updates, status_message, success
     * @since 9.0.0
     */
    public static function update_source_http($url, $previous_sig, &$this_req) {
        $new_signature = '';
        $nb_updates = 0;
        $status_message = 'Unknown error.';
        $success = false;
        
        $req = wp_remote_get($url, array('sslverify' => false));
        $this_req = $req;
        if (is_wp_error($req)) {
            $status_message = 'Error while retrieving URL.';
        } else {
            $code = $req["response"]["code"];
            if (!preg_match("#^2\d+$#", $code)) {
                $status_message = sprintf('Error code %s while connecting to URL %s.', $code, $url);
            } else {
                $body = wp_remote_retrieve_body($req);
                if ($body) {
                    $new_signature = md5($body);
                    if ($new_signature != $previous_sig) {
                        if ( TP_Bibtex::is_utf8($body) === false ) {
                            $body = utf8_encode($body);
                        }
                        
                        if ( !TP_Bibtex::looks_like_bibtex($body) ) {
                            $status_message = "Content does not look like BibTeX.";
                        } else {
                            $settings = array(
                                'keyword_separator' => ',',
                                'author_format'     => 'dynamic',
                                'overwrite'         => true,
                                'ignore_tags'       => false,
                            );

                            $entries = TP_Bibtex_Import::init($body, $settings);
                            $status_message = 'Successfully read and imported.';
                            $nb_updates = count($entries);
                            $success = true;
                        }
                    } else {
                        $status_message = 'File unchanged.';
                        $new_signature = $previous_sig;
                        $success = true;
                    }
                } else {
                    $status_message = 'Invalid body in server response.';
                }
            }
        }
        
        return array($new_signature, $nb_updates, $status_message, $success);
    }

    /**
     * Performs update for a single source.
     * @param $url   The URL of the source. URL protocols supported:
                     zotero://group/<group_id> is special and downloads all group items in group <group_id>
     * @param previous_sig   String signature of the last file polled, 0 if this is the first time.
     * @return new_signature, nb_updates, status_message, success
     * @since 9.0.0
     * @see Zotero api https://www.zotero.org/support/dev/web_api/v3/basics
     */
    public static function update_source_zotero($url, $previous_sig) {
        $result = array('', 0, 'Zotero group download failed.', false);
        
        // be robust
        if (is_int($previous_sig)) {
            $previous_sig = strval($previous_sig);
        }
        
        // find group id
        $parts = explode("/", $url);
        
        if (count($parts) >= 3 && $parts[0] == "zotero:" && $parts[2] == "group") {
            $group_id = $parts[3];
            
            // has the group changed since the last poll?
            $previous_version = 0;
            if (is_numeric($previous_sig)) {
                $previous_version = (int) $previous_sig;
            }

            $current_version = 0;
            $req = wp_remote_get('https://api.zotero.org/groups/' . $group_id . '/items?since=0&limit=1', array('sslverify' => false));
            $headers = wp_remote_retrieve_headers($req);
            if (isset($headers['Last-Modified-Version'])) {
                $current_version = (int) $headers['Last-Modified-Version'];
            }
            
            $group_has_changed = $current_version > $previous_version || $current_version == 0;

            // main loop
            if (!$group_has_changed) {
                $result = array(strval($current_version), 0, 'Publications already synchronized with Zotero.', true);
            } else {
                // prepare pagination loop
                $has_more_results = true;
                $error_encountered = false;
                $current_offset = 0;
                $page_size = 30;
                $total_results = -1;
                
                while ($has_more_results && !$error_encountered) {
                    // download a single page
                    $page_url = sprintf("https://api.zotero.org/groups/%s/items?since=%d&format=bibtex&limit=%d&start=%d",
                                        $group_id, $previous_version, $page_size, $current_offset);
                    $page_result = TP_Publication_Sources_Page::update_source_http($page_url, '', $req);
                    if ($total_results == -1) {  // set on first loop
                        $total_results = intval($req["headers"]["total-results"]);
                    }
                    
                    $result[3] = $page_result[3];
                    $error_encountered = !$result[3];
                    
                    if ($error_encountered) {
                        $result[2] = 'Zotero group download failed. Error was: ' . $page_result[2];
                        $result[0] = $previous_version;
                    } else {
                        $result[1] += $page_result[1];
                        $result[2] = $page_result[2];

                        usleep(100000); // stay awhile and listen
                        $current_offset += $page_size;
                        $has_more_results = $current_offset < $total_results;
                    }
                }
                
                if (!$error_encountered) {
                    $result[0] = strval($current_version);
                }
            }
        } else {
            $result = array('', 0, 'Zotero URL format is incorrect.', false);
        }
        
        return $result;
    }
            
    /**
     * Performs update for a single source.
     * @param $url   The URL of the source. URL protocols supported: http://, https://, zotero://
                     zotero://group/<group_id> is special and downloads all group items in group <group_id>
     * @param previous_sig   Digest the last time the file was polled, 0 if this is the first time.
     * @return new_signature, nb_updates, status_message, success
     * @since 9.0.0
     */
    public static function update_source($url, $previous_sig) {
        // what is the protocol?
        $url_parts = explode("://", strtolower(trim($url)));
        $result = false;
        if (count($url_parts) > 1) {
            switch ($url_parts[0]) {
                case "http":
                case "https":
                    $http_req = NULL;
                    $result = TP_Publication_Sources_Page::update_source_http($url, $previous_sig, $http_req);
                    break;
                case "zotero":
                    $result = TP_Publication_Sources_Page::update_source_zotero($url, $previous_sig);
                    break;
                default:
                    $result = array('', 0, 'Invalid protocol.', false);
                    break;
            }
        }
        
        return $result;
    }
    
    /**
     * Checks the nonce field of the form. If the check fails wp_die() will be executed
     * @since 9.0.5
     */
    private static function check_nonce_field () {
        if ( ! isset( $_POST['tp_nonce'] ) 
            || ! wp_verify_nonce( $_POST['tp_nonce'], 'verify_teachpress_auto_publish' ) 
        ) {
           wp_die('teachPress error: This request could not be verified!');
           exit;
        }
    }

}
