#!/usr/bin/env php
<?php

use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
use dokuwiki\Utf8\PhpString;

if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');

/**
 * Checkout and commit pages from the command line while maintaining the history
 */
class PageCLI extends CLI
{
    protected $force = false;
    protected $username = '';

    /**
     * Register options and arguments on the given $options object
     *
     * @param Options $options
     * @return void
     */
    protected function setup(Options $options)
    {
        /* global */
        $options->registerOption(
            'force',
            'force obtaining a lock for the page (generally bad idea)',
            'f'
        );
        $options->registerOption(
            'user',
            'work as this user. defaults to current CLI user',
            'u',
            'username'
        );
        $options->setHelp(
            'Utility to help command line Dokuwiki page editing, allow ' .
            'pages to be checked out for editing then committed after changes'
        );

        /* checkout command */
        $options->registerCommand(
            'checkout',
            'Checks out a file from the repository, using the wiki id and obtaining ' .
            'a lock for the page. ' . "\n" .
            'If a working_file is specified, this is where the page is copied to. ' .
            'Otherwise defaults to the same as the wiki page in the current ' .
            'working directory.'
        );
        $options->registerArgument(
            'wikipage',
            'The wiki page to checkout',
            true,
            'checkout'
        );
        $options->registerArgument(
            'workingfile',
            'How to name the local checkout',
            false,
            'checkout'
        );

        /* commit command */
        $options->registerCommand(
            'commit',
            'Checks in the working_file into the repository using the specified ' .
            'wiki id, archiving the previous version.'
        );
        $options->registerArgument(
            'workingfile',
            'The local file to commit',
            true,
            'commit'
        );
        $options->registerArgument(
            'wikipage',
            'The wiki page to create or update',
            true,
            'commit'
        );
        $options->registerOption(
            'message',
            'Summary describing the change (required)',
            'm',
            'summary',
            'commit'
        );
        $options->registerOption(
            'trivial',
            'minor change',
            't',
            false,
            'commit'
        );

        /* lock command */
        $options->registerCommand(
            'lock',
            'Obtains or updates a lock for a wiki page'
        );
        $options->registerArgument(
            'wikipage',
            'The wiki page to lock',
            true,
            'lock'
        );

        /* unlock command */
        $options->registerCommand(
            'unlock',
            'Removes a lock for a wiki page.'
        );
        $options->registerArgument(
            'wikipage',
            'The wiki page to unlock',
            true,
            'unlock'
        );

        /* gmeta command */
        $options->registerCommand(
            'getmeta',
            'Prints metadata value for a page to stdout.'
        );
        $options->registerArgument(
            'wikipage',
            'The wiki page to get the metadata for',
            true,
            'getmeta'
        );
        $options->registerArgument(
            'key',
            'The name of the metadata item to be retrieved.' . "\n" .
            'If empty, an array of all the metadata items is returned.' . "\n" .
            'For retrieving items that are stored in sub-arrays, separate the ' .
            'keys of the different levels by spaces, in quotes, eg "date modified".',
            false,
            'getmeta'
        );
    }

    /**
     * Your main program
     *
     * Arguments and options have been parsed when this is run
     *
     * @param Options $options
     * @return void
     */
    protected function main(Options $options)
    {
        $this->force = $options->getOpt('force', false);
        $this->username = $options->getOpt('user', $this->getUser());

        $command = $options->getCmd();
        $args = $options->getArgs();
        switch ($command) {
            case 'checkout':
                $wiki_id = array_shift($args);
                $localfile = array_shift($args);
                $this->commandCheckout($wiki_id, $localfile);
                break;
            case 'commit':
                $localfile = array_shift($args);
                $wiki_id = array_shift($args);
                $this->commandCommit(
                    $localfile,
                    $wiki_id,
                    $options->getOpt('message', ''),
                    $options->getOpt('trivial', false)
                );
                break;
            case 'lock':
                $wiki_id = array_shift($args);
                $this->obtainLock($wiki_id);
                $this->success("$wiki_id locked");
                break;
            case 'unlock':
                $wiki_id = array_shift($args);
                $this->clearLock($wiki_id);
                $this->success("$wiki_id unlocked");
                break;
            case 'getmeta':
                $wiki_id = array_shift($args);
                $key = trim(array_shift($args));
                $meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED);
                echo trim(json_encode($meta, JSON_PRETTY_PRINT));
                echo "\n";
                break;
            default:
                echo $options->help();
        }
    }

    /**
     * Check out a file
     *
     * @param string $wiki_id
     * @param string $localfile
     */
    protected function commandCheckout($wiki_id, $localfile)
    {
        global $conf;

        $wiki_id = cleanID($wiki_id);
        $wiki_fn = wikiFN($wiki_id);

        if (!file_exists($wiki_fn)) {
            $this->fatal("$wiki_id does not yet exist");
        }

        if (empty($localfile)) {
            $localfile = getcwd() . '/' . PhpString::basename($wiki_fn);
        }

        if (is_dir($localfile)) {
            $this->fatal("Working file $localfile cannot be a directory");
        }

        if (!file_exists(dirname($localfile))) {
            $this->fatal("Directory " . dirname($localfile) . " does not exist");
        }

        if (stristr(realpath(dirname($localfile)), (string) realpath($conf['datadir'])) !== false) {
            $this->fatal("Attempt to check out file into data directory - not allowed");
        }

        $this->obtainLock($wiki_id);

        if (!copy($wiki_fn, $localfile)) {
            $this->clearLock($wiki_id);
            $this->fatal("Unable to copy $wiki_fn to $localfile");
        }

        $this->success("$wiki_id > $localfile");
    }

    /**
     * Save a file as a new page revision
     *
     * @param string $localfile
     * @param string $wiki_id
     * @param string $message
     * @param bool $minor
     */
    protected function commandCommit($localfile, $wiki_id, $message, $minor)
    {
        $wiki_id = cleanID($wiki_id);
        $message = trim($message);

        if (!file_exists($localfile)) {
            $this->fatal("$localfile does not exist");
        }

        if (!is_readable($localfile)) {
            $this->fatal("Cannot read from $localfile");
        }

        if (!$message) {
            $this->fatal("Summary message required");
        }

        $this->obtainLock($wiki_id);

        saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor);

        $this->clearLock($wiki_id);

        $this->success("$localfile > $wiki_id");
    }

    /**
     * Lock the given page or exit
     *
     * @param string $wiki_id
     */
    protected function obtainLock($wiki_id)
    {
        if ($this->force) $this->deleteLock($wiki_id);

        $_SERVER['REMOTE_USER'] = $this->username;

        if (checklock($wiki_id)) {
            $this->error("Page $wiki_id is already locked by another user");
            exit(1);
        }

        lock($wiki_id);

        if (checklock($wiki_id)) {
            $this->error("Unable to obtain lock for $wiki_id ");
            var_dump(checklock($wiki_id));
            exit(1);
        }
    }

    /**
     * Clear the lock on the given page
     *
     * @param string $wiki_id
     */
    protected function clearLock($wiki_id)
    {
        if ($this->force) $this->deleteLock($wiki_id);

        $_SERVER['REMOTE_USER'] = $this->username;
        if (checklock($wiki_id)) {
            $this->error("Page $wiki_id is locked by another user");
            exit(1);
        }

        unlock($wiki_id);

        if (file_exists(wikiLockFN($wiki_id))) {
            $this->error("Unable to clear lock for $wiki_id");
            exit(1);
        }
    }

    /**
     * Forcefully remove a lock on the page given
     *
     * @param string $wiki_id
     */
    protected function deleteLock($wiki_id)
    {
        $wikiLockFN = wikiLockFN($wiki_id);

        if (file_exists($wikiLockFN)) {
            if (!unlink($wikiLockFN)) {
                $this->error("Unable to delete $wikiLockFN");
                exit(1);
            }
        }
    }

    /**
     * Get the current user's username from the environment
     *
     * @return string
     */
    protected function getUser()
    {
        $user = getenv('USER');
        if (empty($user)) {
            $user = getenv('USERNAME');
        } else {
            return $user;
        }
        if (empty($user)) {
            $user = 'admin';
        }
        return $user;
    }
}

// Main
$cli = new PageCLI();
$cli->run();
