davidcarlisle / latexcgi

LaTeX server via perl cgi script, developed for learnlatex.org
https://davidcarlisle.github.io/latexcgi/
MIT License
32 stars 3 forks source link

call by php #3

Open Tesla-Renting opened 3 years ago

Tesla-Renting commented 3 years ago

I created a php script that calls the cgi with multiple files, because I couldn't find any php example. I think maybe other people find this helpful, so maybe it could be included somewhere in the docs.

<?php

class TeXLive
{
    private $files;
    private $boundary;
    private $eol = "\r\n";
    /**
     * The path & filename to save to.
     */
    private $output = 'document.pdf';

    private static function generateBoundary(): string
    {
        return 'TITO-'.md5(time());
    }

    public function __construct(array $files, string $output = 'document.pdf')
    {
        $this->files = $files;
        $this->boundary = self::generateBoundary();
        $this->output = $output;
    }

    private function getPart($name, $value)
    {
        $part = '--'.$this->boundary.$this->eol;
        $part .= 'Content-Disposition: form-data; name="'.$name.'"'.$this->eol;
        // $part .= 'Content-Length: '.strlen($value).$this->eol;
        $part .= $this->eol;
        $part .= $value.$this->eol;

        return $part;
    }

    private function getBody()
    {
        $body = '';
        $body .= $this->getPart('return', 'pdf');
        $body .= $this->getPart('engine', 'pdflatex');
        $body .= $this->getPart('filename[]', 'document.tex');
        $body .= $this->getPart('filecontents[]', file_get_contents($this->files[0]));
        for ($i = 1; $i < count($this->files); ++$i) {
            $filename = $this->files[$i];
            $body .= $this->getPart('filename[]', $filename);
            $body .= $this->getPart('filecontents[]', file_get_contents($filename));
        }

        $body .= '--'.$this->boundary.'--'.$this->eol;

        return $body;
    }

    public function generateDocument()
    {
        $ch = curl_init();
        // curl_setopt($ch, CURLOPT_URL, 'https://postman-echo.com/post');
        curl_setopt($ch, CURLOPT_URL, 'https://texlive.net/cgi-bin/latexcgi');
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_HTTPHEADER,
          ['Content-Type: multipart/form-data; boundary='.$this->boundary]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getBody($this->boundary));
        // header('Content-Type: application/json');
        curl_exec($ch);

        $info = curl_getinfo($ch);
        // echo json_encode($info);

        curl_close($ch);
        $this->saveDocument($info['redirect_url']);
    }

    /**
     * fileUrl: The resource that we want to download.
     */
    private function saveDocument(string $fileUrl)
    {
        $saveTo = $this->output;
        if (endsWith($fileUrl, 'log')) {
            $saveTo = 'TeXLiveError.log';
        }

        //Open file handler.
        $fp = fopen($saveTo, 'w+');

        //If $fp is FALSE, something went wrong.
        if ($fp === false) {
            throw new Exception('Could not open: '.$saveTo);
        }

        //Create a cURL handle.
        $ch = curl_init($fileUrl);

        //Pass our file handle to cURL.
        curl_setopt($ch, CURLOPT_FILE, $fp);

        //Timeout if the file doesn't download after 20 seconds.
        curl_setopt($ch, CURLOPT_TIMEOUT, 20);

        //Execute the request.
        curl_exec($ch);

        //If there was an error, throw an Exception
        if (curl_errno($ch)) {
            throw new Exception(curl_error($ch));
        }

        //Get the HTTP status code.
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        //Close the cURL handler.
        curl_close($ch);

        //Close the file handler.
        fclose($fp);

        if ($statusCode != 200) {
            throw new Exception('Error downloading pdf. Status Code: '.$statusCode);
        }
    }
}

// (new TeXLive(['document.tex', 'file1.tex']))->generateDocument();

function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if (!$length) {
        return true;
    }

    return substr($haystack, -$length) === $needle;
}

How to call

Only the constructor and generateDocument are public, so just look at the interface of these methods. The files handed over to the constructor should all be in the current working directory, so maybe you have to call chdir before calling generateDocument.

davidcarlisle commented 3 years ago

Thanks for this, I may add to the currently undocumented curl directory that already has a shell script for a commandline wrapper around curl. I'm wary about advertising these things too much though. Using the javascript interface with a manual submit button (as at https://learnlatex.org or https://latex.org/forum/ adds a natural hinderence to too many document requests being made in a short time.

Currently the service runs free to the user with no login or registration required, I'd like to keep it that way but if scripted interfaces are being used (php as here or the bash script that I provided) it is easy to generate multiple requests and swamp the server, even if that is not intended to be malicious.