cosmocode/dokuwiki-plugin-issuelinks

View on GitHub
helper/data.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php
/**
 * DokuWiki Plugin issuelinks (Helper Component)
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  Andreas Gohr <dokuwiki@cosmocode.de>
 */

use dokuwiki\plugin\issuelinks\classes\Issue;
use dokuwiki\plugin\issuelinks\classes\ServiceProvider;

class helper_plugin_issuelinks_data extends DokuWiki_Plugin
{

    /** @var helper_plugin_issuelinks_db */
    private $db = null;

    /**
     * constructor. loads helpers
     */
    public function __construct()
    {
        $this->db = $this->loadHelper('issuelinks_db');
    }


    /**
     * Import all Jira issues starting at given paging offset
     *
     * @param string $serviceName The name of the project management service
     * @param string $projectKey  The short-key of the project to be imported
     *
     * @throws Exception
     */
    public function importAllIssues($serviceName, $projectKey)
    {
        $lockfileKey = $this->getImportLockID($serviceName, $projectKey);
        if ($this->isImportLocked($lockfileKey)) {
            throw new RuntimeException('Import of Issues is already locked!');
        }
        dbglog('start import. $lockfileKey: ' . $lockfileKey);
        $this->lockImport($lockfileKey, json_encode(['user' => $_SERVER['REMOTE_USER'], 'status' => 'started']));

        $serviceProvider = ServiceProvider::getInstance();
        $services = $serviceProvider->getServices();
        dbglog($services);
        dbglog($serviceName);
        $serviceClass = $services[$serviceName];
        dbglog($serviceClass);
        $service = $serviceClass::getInstance();

        $total = 0;
        $counter = 0;
        $startAt = 0;

        try {
            while ($issues = $service->retrieveAllIssues($projectKey, $startAt)) {
                if (!$total) {
                    $total = $service->getTotalIssuesBeingImported();
                }

                if ($counter > $total) {
                    break;
                }

                if (!$this->isImportLockedByMe($lockfileKey)) {
                    throw new RuntimeException('Import of Issues aborted because lock removed');
                }

                $counter += count($issues);
                $this->lockImport($lockfileKey, json_encode([
                    'user' => $_SERVER['REMOTE_USER'],
                    'total' => $total,
                    'count' => $counter,
                    'status' => 'running',
                ]));
            }
        } catch (\Throwable $e) {
            dbglog(
                "Downloading all issues from $serviceName fpr project $projectKey failed ",
                __FILE__ . ': ' . __LINE__
            );
            if (is_a($e, \dokuwiki\plugin\issuelinks\classes\HTTPRequestException::class)) {
                /** @var \dokuwiki\plugin\issuelinks\classes\HTTPRequestException $e */
                dbglog($e->getUrl());
                dbglog($e->getHttpError());
                dbglog($e->getMessage());
                dbglog($e->getCode());
                dbglog($e->getResponseBody());
            }
            $this->lockImport($lockfileKey, json_encode(['status' => 'failed']));
            throw $e;
        }
        $this->unlockImport($lockfileKey);
    }


    public function getImportLockID($serviceName, $projectKey)
    {
        return "_plugin__issuelinks_import_$serviceName-$projectKey";
    }

    /**
     * This checks the lock for the import process it behaves differently from the dokuwiki-core checklock() function!
     *
     * It returns false if the lock does not exist. It returns **boolean true** if the lock exists and is mine.
     * It returns the username/ip if the lock exists and is not mine.
     * It is therefore important to use strict (===) checking for true!
     *
     * @param $id
     *
     * @return bool|string
     */
    public function isImportLocked($id)
    {
        global $conf;
        $lockFN = $conf['lockdir'] . '/' . md5('_' . $id) . '.lock';
        if (!file_exists($lockFN)) {
            return false;
        }

        clearstatcache(true, $lockFN);
        if ((time() - filemtime($lockFN)) > 120) {
            unlink($lockFN);
            dbglog('issuelinks: stale lock timeout');
            return false;
        }

        $lockData = json_decode(io_readFile($lockFN), true);
        if (!empty($lockData['status']) && $lockData['status'] === 'done') {
            return false;
        }

        return true;
    }

    /**
     * Generate lock file for import of issues/commits
     *
     * This is mostly a reimplementation of @see lock()
     * However we do not clean the id and prepent a underscore to avoid conflicts with locks of existing pages.
     *
     * @param $id
     * @param $jsonData
     */
    public function lockImport($id, $jsonData)
    {
        global $conf;

        $lock = $conf['lockdir'] . '/' . md5('_' . $id) . '.lock';
        dbglog('lock import: ' . $jsonData, __FILE__ . ': ' . __LINE__);
        io_saveFile($lock, $jsonData);
    }

    public function isImportLockedByMe($id)
    {
        if (!$this->isImportLocked($id)) {
            return false;
        }

        global $conf, $INPUT;
        $lockFN = $conf['lockdir'] . '/' . md5('_' . $id) . '.lock';
        $lockData = json_decode(io_readFile($lockFN), true);
        if ($lockData['user'] !== $INPUT->server->str('REMOTE_USER')) {
            return false;
        }

        touch($lockFN);
        return true;
    }

    /**
     * Marks the import as unlocked / done
     *
     * @param $id
     */
    public function unlockImport($id)
    {
        global $conf;
        $lockFN = $conf['lockdir'] . '/' . md5('_' . $id) . '.lock';
        $lockData = json_decode(io_readFile($lockFN), true);
        $lockData['status'] = 'done';
        $lockData['total'] = $lockData['count'];
        io_saveFile($lockFN, json_encode($lockData));
    }

    public function getLockContent($id)
    {
        global $conf;
        $lockFN = $conf['lockdir'] . '/' . md5('_' . $id) . '.lock';
        if (!file_exists($lockFN)) {
            return false;
        }
        return json_decode(io_readFile($lockFN), true);
    }

    public function removeLock($lockID)
    {
        global $conf;
        $lockFN = $conf['lockdir'] . '/' . md5('_' . $lockID) . '.lock';
        unlink($lockFN);
    }

    /**
     * Get an issue either from local DB or attempt to import it
     *
     * @param string $pmServiceName The name of the project management service
     * @param string $project
     * @param int    $issueid
     * @param bool   $isMergeRequest
     *
     * @return bool|Issue
     */
    public function getIssue($pmServiceName, $project, $issueid, $isMergeRequest)
    {
        $issue = Issue::getInstance($pmServiceName, $project, $issueid, $isMergeRequest);
        if (!$issue->isValid()) {
            try {
                $issue->getFromService();
                $issue->saveToDB();
            } catch (Exception $e) {
                // that's fine
            }
        }
        return $issue;
    }

    public function getMergeRequestsForIssue($serviceName, $projectKey, $issueId, $isMergeRequest)
    {
        /** @var helper_plugin_issuelinks_db $db */
        $db = plugin_load('helper', 'issuelinks_db');
        $issues = $db->getMergeRequestsReferencingIssue($serviceName, $projectKey, $issueId, $isMergeRequest);
        foreach ($issues as &$issueData) {
            $issue = Issue::getInstance(
                $issueData['service'],
                $issueData['project_id'],
                $issueData['issue_id'],
                $issueData['is_mergerequest']
            );
            $issue->getFromDB();
            $issueData['summary'] = $issue->getSummary();
            $issueData['status'] = $issue->getStatus();
            $issueData['url'] = $issue->getIssueURL();
        }
        unset($issueData);

        return $issues;
    }

    /**
     * Get Pages with links to issues
     *
     * @param string $pmServiceName The name of the project management service
     * @param string $projectKey
     * @param int    $issueId       the issue id
     * @param bool   $isMergeRequest
     *
     * @return array
     */
    public function getLinkingPages($pmServiceName, $projectKey, $issueId, $isMergeRequest)
    {
        $pages = $this->db->getAllPageLinkingToIssue($pmServiceName, $projectKey, $issueId, $isMergeRequest);
        $pages = $this->db->removeOldLinks($pmServiceName, $projectKey, $issueId, $isMergeRequest, $pages);

        if (empty($pages)) {
            return [];
        }

        $pages = $this->keepNewest($pages);
        $pages = $this->filterPagesForACL($pages);
        $pages = $this->addUserToPages($pages);
        return $pages;
    }

    /**
     * remove duplicate revisions of a page and keep only the newest
     *
     * @param array $pages Array of pages sorted(!) from newest to oldest
     *
     * @return array
     */
    public function keepNewest($pages)
    {
        $uniquePages = [];
        foreach ($pages as $page) {
            if (!array_key_exists($page['page'], $uniquePages) || $uniquePages[$page['page']]['rev'] < $page['rev']) {
                $uniquePages[$page['page']] = $page;
            }
        }
        return array_values($uniquePages);
    }

    /**
     * Filter the given pages for at least AUTH_READ
     *
     * @param array $pages
     *
     * @return array
     */
    private function filterPagesForACL($pages)
    {
        $allowedPagegs = [];
        foreach ($pages as $page) {
            if (auth_quickaclcheck($page['page']) >= AUTH_READ) {
                $allowedPagegs[] = $page;
            }
        }
        return $allowedPagegs;
    }

    /**
     * add the corresponding user to each revision
     *
     * @param array $pages
     *
     * @return array
     */
    public function addUserToPages($pages)
    {
        foreach ($pages as &$page) {
            $changelog = new PageChangelog($page['page']);
            $revision = $changelog->getRevisionInfo($page['rev']);
            $page['user'] = $revision['user'];
        }
        return $pages;
    }
}

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