attogram/justrefs

View on GitHub
src/Topic.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * Just Refs - https://github.com/attogram/justrefs
 * Topic Class
 */
declare(strict_types = 1);

namespace Attogram\Justrefs;

use function array_keys;
use function count;
use function gmdate;
use function in_array;
use function is_array;
use function json_decode;
use function json_encode;
use function sort;
use function substr;

class Topic extends Base
{
    const ERROR_NOT_FOUND  = 'Topic Not Found';

    /** @var array $data - topic data */
    private $data = [];

    /** @var array $vars - template variables */
    private $vars = [];

    /**
     * Get a topic
     * @return void
     */
    public function get()
    {
        if (!$this->setTopicFromUrl()) { // build topic from URL
            $this->error404(self::ERROR_NOT_FOUND);
        }

        $this->initFilesystem();
        
        if ($this->setDataFromCache()) { // get topic data from Cache
            if (!empty($this->data['error'])) { // if cache contains error report (404)
                $this->error404(self::ERROR_NOT_FOUND, $this->topic);

                return;
            }
            $this->display(); // show cached results

            return;
        }

        // If can get topic data from API
        if ($this->setDataFromApi()) {
            // save results to cache for CACHE_TIME seconds
            $this->filesystem->set($this->topic, json_encode($this->data), self::CACHE_TIME);
            if (!empty($this->data['error'])) { // if API reported an error
                $this->error404(self::ERROR_NOT_FOUND, $this->topic);

                return;
            }
            $this->display(); // show api results

            return;
        }

        $this->error404(self::ERROR_NOT_FOUND);
    }

    /**
     * set $this->data to array from cached file, or empty array
     * @return bool
     */
    private function setDataFromCache()
    {
        $this->data = $this->filesystem->get($this->topic);
        if (!$this->data) {
            $this->data = [];

            return false;
        }

        $this->data = json_decode($this->data, true); // @TODO catch errors from decode

        return true;
    }

    /**
     * set $this->data to array from api response, or empty array
     * @return bool
     */
    private function setDataFromApi()
    {
        $this->initMediawiki();
        $this->data = $this->mediawiki->links($this->topic);
        if (!is_array($this->data)) {
            $this->data = [];

            return false;
        }

        return true;
    }

    private function display()
    {
        $this->setTemplateVars();
    
        // set Extraction source url
        $this->template->set('source', $this->source . $this->encodeLink($this->data[self::TITLE]));
        $this->template->set('now', gmdate(self::DATE_FORMAT));

        if (!isset($this->data['cached'])) {  // if no cache time is set...
            $this->data['cached'] = time();
        }
        $this->template->set('cached', gmdate(self::DATE_FORMAT, $this->data['cached']));

        $this->template->set(
            'refresh', // refresh link
            $this->template->get('home') . 'refresh/' . $this->encodeLink($this->data[self::TITLE])
        );
        $this->template->set('h1', $this->data[self::TITLE]);
        $this->template->set(self::TITLE, $this->data[self::TITLE] . ' - ' . $this->siteName);
        $this->template->include('topic');
    }

    private function setTemplateVars()
    {
        $this->initVars();
        $this->setNamespaces();
        $this->setRefs();
        $this->setTemplates();
        $this->setTemplateExists();
        $this->removeTemplateTopics();
        foreach (array_keys($this->vars) as $index) {
            $this->template->set($index . '_count', count($this->vars[$index])); // set counts
            sort($this->vars[$index]); // sort var lists alphabetically
            $this->template->set($index . '_list', $this->listify($index)); // set html list
        }
    }

    private function initVars()
    {
        $namespaces = [
            self::MAIN, self::TALK, self::MAIN_SECONDARY, self::TEMPLATE, self::TEMPLATE_TALK, self::TEMPLATE_SECONDARY,
            self::PORTAL, self::PORTAL_TALK, self::WIKIPEDIA, self::WIKIPEDIA_TALK, self::HELP, self::HELP_TALK,
            self::MODULE, self::MODULE_TALK, self::DRAFT, self::DRAFT_TALK, self::USER, self::USER_TALK,
            self::REFS, self::MISSING, self::EXISTS,
        ];
        foreach ($namespaces as $index) {
            $this->vars[$index] = [];
        }
    }

    private function setNamespaces()
    {
        foreach ($this->data[self::TOPICS] as $topic) {
            if (!isset($topic[self::EXISTS])) {
                $this->vars[self::MISSING][] = $topic[self::ASTERISK]; // page does not exist
            }
            switch ($topic[self::NS]) { // @see https://en.wikipedia.org/wiki/Wikipedia:Namespace
                case '0':  // Mainspace
                    $this->vars[self::MAIN][] = $topic[self::ASTERISK];
                    break;
                case '1':  // Talk
                    $this->vars[self::TALK][] = $topic[self::ASTERISK];
                    break;
                case '2':  // User
                    $this->vars[self::USER][] = $topic[self::ASTERISK];
                    break;
                case '3':  // User_talk
                    $this->vars[self::USER_TALK][] = $topic[self::ASTERISK];
                    break;
                case '4':  // Wikipedia
                    $this->vars[self::WIKIPEDIA][] = $topic[self::ASTERISK];
                    break;
                case '5':  // Wikipedia_talk
                    $this->vars[self::WIKIPEDIA_TALK][] = $topic[self::ASTERISK];
                    break;
                case '10': // Template
                    $this->vars[self::TEMPLATE][] = $topic[self::ASTERISK];
                    break;
                case '11': // Template_talk
                    $this->vars[self::TEMPLATE_TALK][] = $topic[self::ASTERISK];
                    break;
                case '12': // Help
                    $this->vars[self::HELP][] = $topic[self::ASTERISK];
                    break;
                case '13': // Help_talk
                    $this->vars[self::HELP_TALK][] = $topic[self::ASTERISK];
                    break;
                case '100': // Portal
                    $this->vars[self::PORTAL][] = $topic[self::ASTERISK];
                    break;
                case '101': // Portal_talk
                    $this->vars[self::PORTAL_TALK][] = $topic[self::ASTERISK];
                    break;
                case '118': // Draft
                    $this->vars[self::DRAFT][] = $topic[self::ASTERISK];
                    break;
                case '119': // Draft_talk
                    $this->vars[self::DRAFT_TALK][] = $topic[self::ASTERISK];
                    break;
                case '828': // Module
                    $this->vars[self::MODULE][] = $topic[self::ASTERISK];
                    break;
                case '829': // Module_talk
                    $this->vars[self::MODULE_TALK][] = $topic[self::ASTERISK];
                    break;
                default:
                    break;
            }
        }
    }

    private function setTemplateExists()
    {
        foreach ($this->vars[self::TEMPLATE] as $item) {
            if ($this->filesystem->has($item)) {
                $this->vars[self::EXISTS][] = $item;
            }
        }
    }

    private function setRefs()
    {
        foreach ($this->data[self::REFS] as $ref) {
            if (substr($ref, 0, 2) == '//') {
                $ref = 'https:' . $ref;
            }
            $this->vars[self::REFS][] = $ref;
        }
    }

    private function setTemplates()
    {
        foreach ($this->data[self::TEMPLATES] as $item) {
            switch ($item[self::NS]) {
                case '0': // Main
                    if ($item[self::ASTERISK] != $this->topic) {
                        $this->vars[self::MAIN][] = $item[self::ASTERISK];
                    }
                    break;
                case '10': // Template:
                    if (!in_array($item[self::ASTERISK], $this->vars[self::TEMPLATE])) {
                        $this->vars[self::TEMPLATE_SECONDARY][] = $item[self::ASTERISK];
                    }
                    break;
                case '828': // Module:
                    $this->vars[self::MODULE][] = $item[self::ASTERISK];
                    break;
                default:
                    break;
            }
        }
    }

    private function removeTemplateTopics()
    {
        if (empty($this->vars[self::MAIN])
            || (empty($this->vars[self::TEMPLATE]) && $this->vars[self::TEMPLATE_SECONDARY])
        ) {
            return;
        }
        foreach ($this->vars[self::TEMPLATE] as $template) {
            if ($template == $this->topic || (!in_array($template, $this->vars[self::EXISTS]))) {
                continue; // error: is self, or template not cached
            }
            $templateData = json_decode($this->filesystem->get($template), true);
            if (empty($templateData[self::TOPICS]) || !is_array($templateData[self::TOPICS])) {
                continue; // error: malformed data
            }
            $this->unsetDupeTopics($templateData[self::TOPICS]);
        }
    }

    /**
     * @param array $topics
     */
    private function unsetDupeTopics(array $topics)
    {
        foreach ($topics as $exTopic) {
            if ($exTopic[self::NS] == '0' && in_array($exTopic[self::ASTERISK], $this->vars[self::MAIN])) {
                // main namespace only - remove this template topic from master topic list
                unset($this->vars[self::MAIN][array_search($exTopic[self::ASTERISK], $this->vars[self::MAIN])]);
                $this->vars[self::MAIN_SECONDARY][] = $exTopic[self::ASTERISK];
            }
        }
    }

    /**
     * @param string $index - this->vars index
     * @return string - html fragment
     */
    private function listify($index)
    {
        if (in_array($index, [self::EXISTS, self::MISSING]) // skip internal-usage vars
            || empty($this->vars[$index]) // Error - index not found, or index empty
        ) {
            return '&nbsp;';
        }
        $html = '<ol>';
        foreach ($this->vars[$index] as $item) {
            if ($index == self::REFS) { // Link to external reference
                $html .= '<li><a href="' . $item . '" target="_blank">' . $item . '</a></li>';
                continue;
            }
            if (in_array($item, $this->vars[self::MISSING])) { // non-existing page
                $html .= '<li><span class="red">' . $item . '</span></li>';
                continue;
            }
            // Link to internal page
            $class = '';
            if ($index == self::TEMPLATE && !in_array($item, $this->vars[self::EXISTS])) {
                // template is not loaded, thus possible that secondary-topics not all set
                $class = ' class="missing"';
            }
            $html .= '<li><a href="'
                . $this->template->get('home') . $this->getLink($item) . '"' . $class . '>'
                . $item . '</a></li>';
        }

        return $html . '</ol>';
    }
}