Improve confluence export
Migrate from Parsedown to Commonmark
This commit is contained in:
parent
fa798ff1e4
commit
1fe3e62df3
@ -13,9 +13,9 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"erusev/parsedown": "~1.0",
|
||||
"league/plates": "~3.1",
|
||||
"guzzlehttp/guzzle": "~5.3"
|
||||
"guzzlehttp/guzzle": "~5.3",
|
||||
"league/commonmark": "0.8.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {"Todaymade\\Daux\\": "libs/"}
|
||||
|
102
composer.lock
generated
102
composer.lock
generated
@ -4,47 +4,8 @@
|
||||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "82c1c29dbd870c4ee2ea79c0561ea5d8",
|
||||
"hash": "a7e29fdc079b4e2a588148bff949237a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
"version": "1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/erusev/parsedown.git",
|
||||
"reference": "2da10d277b086372f17b96df6cdc903829e1dde0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown/zipball/2da10d277b086372f17b96df6cdc903829e1dde0",
|
||||
"reference": "2da10d277b086372f17b96df6cdc903829e1dde0",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Parsedown": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Emanuil Rusev",
|
||||
"email": "hello@erusev.com",
|
||||
"homepage": "http://erusev.com"
|
||||
}
|
||||
],
|
||||
"description": "Parser for Markdown.",
|
||||
"homepage": "http://parsedown.org",
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2014-05-14 10:14:49"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "5.3.0",
|
||||
@ -204,6 +165,67 @@
|
||||
],
|
||||
"time": "2014-10-12 19:18:40"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "0.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "91696c88df298f75fdd2075e4bb19c6dbd7338ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91696c88df298f75fdd2075e4bb19c6dbd7338ca",
|
||||
"reference": "91696c88df298f75fdd2075e4bb19c6dbd7338ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.4.8"
|
||||
},
|
||||
"replace": {
|
||||
"colinodell/commonmark-php": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"erusev/parsedown": "~1.0",
|
||||
"jgm/commonmark": "0.19",
|
||||
"michelf/php-markdown": "~1.4",
|
||||
"phpunit/phpunit": "~4.3",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0",
|
||||
"symfony/finder": "~2.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "http://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "Markdown parser for PHP based on the CommonMark spec",
|
||||
"homepage": "https://github.com/thephpleague/commonmark",
|
||||
"keywords": [
|
||||
"commonmark",
|
||||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2015-04-29 18:01:46"
|
||||
},
|
||||
{
|
||||
"name": "league/plates",
|
||||
"version": "3.1.0",
|
||||
|
@ -84,7 +84,7 @@ class Daux
|
||||
|
||||
// Read command line overrides
|
||||
$config_file = $this->local_base . DS . $override_file;
|
||||
if (file_exists($config_file)) {
|
||||
if (!is_null($override_file) && file_exists($config_file)) {
|
||||
$config = json_decode(file_get_contents($config_file), true);
|
||||
if (!isset($config)) {
|
||||
throw new Exception('The local config file is missing. Check path : ' . $config_file);
|
||||
|
@ -50,12 +50,15 @@ class DauxHelper
|
||||
return $theme;
|
||||
}
|
||||
|
||||
public static function getCleanPath($path) {
|
||||
public static function getCleanPath($path)
|
||||
{
|
||||
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
|
||||
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
|
||||
$absolutes = array();
|
||||
foreach ($parts as $part) {
|
||||
if ('.' == $part) continue;
|
||||
if ('.' == $part) {
|
||||
continue;
|
||||
}
|
||||
if ('..' == $part) {
|
||||
array_pop($absolutes);
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace Todaymade\Daux\Format\Base;
|
||||
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use Todaymade\Daux\Tree\Content;
|
||||
|
||||
abstract class MarkdownPage extends SimplePage
|
||||
@ -29,9 +30,19 @@ abstract class MarkdownPage extends SimplePage
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
protected function getMarkdownConverter()
|
||||
{
|
||||
return new CommonMarkConverter();
|
||||
}
|
||||
|
||||
protected function convertPage($content)
|
||||
{
|
||||
return $this->getMarkdownConverter()->convertToHtml($content);
|
||||
}
|
||||
|
||||
protected function generatePage()
|
||||
{
|
||||
return (new \Parsedown())->text($this->content);
|
||||
return $this->convertPage($this->content);
|
||||
}
|
||||
|
||||
public static function fromFile(Content $file, $params)
|
||||
|
@ -1,8 +1,12 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\ParseException;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
|
||||
class Api {
|
||||
class Api
|
||||
{
|
||||
|
||||
protected $base_url;
|
||||
protected $user;
|
||||
@ -10,28 +14,82 @@ class Api {
|
||||
|
||||
protected $space;
|
||||
|
||||
public function __construct($base_url, $user, $pass, $space_id) {
|
||||
public function __construct($base_url, $user, $pass)
|
||||
{
|
||||
$this->base_url = $base_url;
|
||||
$this->user = $user;
|
||||
$this->pass = $pass;
|
||||
$this->setSpace($space_id);
|
||||
}
|
||||
|
||||
public function setSpace($space_id) {
|
||||
public function setSpace($space_id)
|
||||
{
|
||||
$this->space = $space_id;
|
||||
}
|
||||
|
||||
protected function getClient()
|
||||
{
|
||||
$options = [
|
||||
'base_url' => $this->base_url . 'rest/api/',
|
||||
'defaults' => [
|
||||
'auth' => [$this->user, $this->pass]
|
||||
]
|
||||
];
|
||||
|
||||
return new Client($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard error message from guzzle is quite poor in informations,
|
||||
* this will give little bit more sense to it and return it
|
||||
*
|
||||
* @param BadResponseException $e
|
||||
* @return BadResponseException
|
||||
*/
|
||||
protected function handleError(BadResponseException $e)
|
||||
{
|
||||
$request = $e->getRequest();
|
||||
$response = $e->getResponse();
|
||||
|
||||
$level = floor($response->getStatusCode() / 100);
|
||||
|
||||
if ($level == '4') {
|
||||
$label = 'Client error response';
|
||||
} elseif ($level == '5') {
|
||||
$label = 'Server error response';
|
||||
} else {
|
||||
$label = 'Unsuccessful response';
|
||||
}
|
||||
|
||||
$message = $label .
|
||||
' [url] ' . $request->getUrl() .
|
||||
' [status code] ' . $response->getStatusCode() .
|
||||
' [message] ';
|
||||
|
||||
try {
|
||||
$message .= $response->json()['message'];
|
||||
} catch (ParseException $e) {
|
||||
$message .= (string) $response->getBody();
|
||||
}
|
||||
|
||||
return new BadResponseException($message, $request, $response, $e->getPrevious());
|
||||
}
|
||||
|
||||
/**
|
||||
* /rest/api/content/{id}/child/{type}
|
||||
*
|
||||
* @param $rootPage
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHierarchy($rootPage) {
|
||||
$hiera = $this->getClient()->get("content/$rootPage/child/page?expand=version,body.storage")->json();
|
||||
public function getHierarchy($rootPage)
|
||||
{
|
||||
try {
|
||||
$hierarchy = $this->getClient()->get("content/$rootPage/child/page?expand=version,body.storage")->json();
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
|
||||
$children = [];
|
||||
foreach($hiera['results'] as $result) {
|
||||
foreach ($hierarchy['results'] as $result) {
|
||||
$children[$result['title']] = [
|
||||
"id" => $result['id'],
|
||||
"title" => $result['title'],
|
||||
@ -44,8 +102,8 @@ class Api {
|
||||
return $children;
|
||||
}
|
||||
|
||||
public function createPage($parent_id, $title, $content) {
|
||||
|
||||
public function createPage($parent_id, $title, $content)
|
||||
{
|
||||
$body = [
|
||||
'type' => 'page',
|
||||
'space' => ['key' => $this->space],
|
||||
@ -54,12 +112,17 @@ class Api {
|
||||
'body' => ['storage' => ['value' => $content, 'representation' => 'storage']]
|
||||
];
|
||||
|
||||
$response = $this->getClient()->post('content', [ 'json' => $body ])->json();
|
||||
try {
|
||||
$response = $this->getClient()->post('content', [ 'json' => $body ])->json();
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
|
||||
return $response['id'];
|
||||
}
|
||||
|
||||
public function updatePage($parent_id, $page_id, $newVersion, $title, $content) {
|
||||
public function updatePage($parent_id, $page_id, $newVersion, $title, $content)
|
||||
{
|
||||
$body = [
|
||||
'type' => 'page',
|
||||
'space' => ['key' => $this->space],
|
||||
@ -69,22 +132,45 @@ class Api {
|
||||
'body' => ['storage' => ['value' => $content, 'representation' => 'storage']]
|
||||
];
|
||||
|
||||
$this->getClient()->put("content/$page_id", [ 'json' => $body ])->json();
|
||||
try {
|
||||
$this->getClient()->put("content/$page_id", ['json' => $body])->json();
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function deletePage($page_id) {
|
||||
return $this->getClient()->delete('content/' . $page_id)->json();
|
||||
public function deletePage($page_id)
|
||||
{
|
||||
try {
|
||||
return $this->getClient()->delete('content/' . $page_id)->json();
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getClient() {
|
||||
public function uploadAttachment($id, $attachment)
|
||||
{
|
||||
//get if attachment is uploaded
|
||||
try {
|
||||
$result = $this->getClient()->get("content/$id/child/attachment?filename=$attachment[filename]")->json();
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
|
||||
$options = [
|
||||
'base_url' => $this->base_url . 'rest/api/',
|
||||
'defaults' => [
|
||||
'auth' => [$this->user, $this->pass]
|
||||
]
|
||||
];
|
||||
$url = "content/$id/child/attachment" . (count($result['results'])? "/{$result['results'][0]['id']}/data" : "");
|
||||
|
||||
return new Client($options);
|
||||
try {
|
||||
$this->getClient()->post(
|
||||
$url,
|
||||
[
|
||||
'body' => ['file' => fopen($attachment['file']->getPath(), 'r')] ,
|
||||
'headers' => ['X-Atlassian-Token' => 'nocheck'],
|
||||
]
|
||||
);
|
||||
} catch (BadResponseException $e) {
|
||||
throw $this->handleError($e);
|
||||
}
|
||||
|
||||
//FIXME :: When doing an update, Confluence does a null pointer exception
|
||||
}
|
||||
}
|
||||
|
26
libs/Format/Confluence/CommonMarkConverter.php
Normal file
26
libs/Format/Confluence/CommonMarkConverter.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use League\CommonMark\DocParser;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\HtmlRenderer;
|
||||
|
||||
class CommonMarkConverter extends \League\CommonMark\CommonMarkConverter
|
||||
{
|
||||
/**
|
||||
* Create a new commonmark converter instance.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = array())
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->mergeConfig($config);
|
||||
|
||||
//Add code renderer
|
||||
$environment->addBlockRenderer('FencedCode', new FencedCodeRenderer());
|
||||
$environment->addBlockRenderer('IndentedCode', new IndentedCodeRenderer());
|
||||
|
||||
$this->docParser = new DocParser($environment);
|
||||
$this->htmlRenderer = new HtmlRenderer($environment);
|
||||
}
|
||||
}
|
85
libs/Format/Confluence/FencedCodeRenderer.php
Normal file
85
libs/Format/Confluence/FencedCodeRenderer.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use League\CommonMark\Block\Element\AbstractBlock;
|
||||
use League\CommonMark\Block\Element\FencedCode;
|
||||
use League\CommonMark\Block\Renderer\BlockRendererInterface;
|
||||
use League\CommonMark\HtmlElement;
|
||||
use League\CommonMark\HtmlRendererInterface;
|
||||
|
||||
class FencedCodeRenderer implements BlockRendererInterface
|
||||
{
|
||||
|
||||
protected $supported_languages = [
|
||||
'actionscript3',
|
||||
'bash',
|
||||
'csharp',
|
||||
'coldfusion',
|
||||
'cpp',
|
||||
'css',
|
||||
'delphi',
|
||||
'diff',
|
||||
'erlang',
|
||||
'groovy',
|
||||
'html/xml',
|
||||
'java',
|
||||
'javafx',
|
||||
'javascript',
|
||||
'none',
|
||||
'perl',
|
||||
'php',
|
||||
'powershell',
|
||||
'python',
|
||||
'ruby',
|
||||
'scala',
|
||||
'sql',
|
||||
'vb'
|
||||
];
|
||||
protected $known_conversions = ['html' => 'html/xml', 'xml' => 'html/xml', 'js' => 'javascript'];
|
||||
|
||||
/**
|
||||
* @param AbstractBlock $block
|
||||
* @param HtmlRendererInterface $htmlRenderer
|
||||
* @param bool $inTightList
|
||||
*
|
||||
* @return HtmlElement|string
|
||||
*/
|
||||
public function render(AbstractBlock $block, HtmlRendererInterface $htmlRenderer, $inTightList = false)
|
||||
{
|
||||
if (!($block instanceof FencedCode)) {
|
||||
throw new \InvalidArgumentException('Incompatible block type: ' . get_class($block));
|
||||
}
|
||||
|
||||
$content = [];
|
||||
|
||||
if ($language = $this->getLanguage($block->getInfoWords(), $htmlRenderer)) {
|
||||
$content[] = new HtmlElement('ac:parameter', ['ac:name' => 'language'], $language);
|
||||
}
|
||||
|
||||
$content[] = new HtmlElement('ac:plain-text-body', [], '<![CDATA[' . $block->getStringContent() . ']]>');
|
||||
|
||||
return new HtmlElement(
|
||||
'ac:structured-macro',
|
||||
['ac:name' => 'code'],
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
public function getLanguage($infoWords, HtmlRendererInterface $htmlRenderer)
|
||||
{
|
||||
if (count($infoWords) === 0 || strlen($infoWords[0]) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$language = $htmlRenderer->escape($infoWords[0], true);
|
||||
|
||||
if (array_key_exists($language, $this->known_conversions)) {
|
||||
$language = $this->known_conversions[$language];
|
||||
}
|
||||
|
||||
if (in_array($language, $this->supported_languages)) {
|
||||
return $language;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Todaymade\Daux\Daux;
|
||||
use Todaymade\Daux\Tree\Content;
|
||||
use Todaymade\Daux\Tree\Directory;
|
||||
@ -8,112 +7,30 @@ use Todaymade\Daux\Tree\Entry;
|
||||
|
||||
class Generator
|
||||
{
|
||||
/**
|
||||
* @var Api
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
|
||||
public function generate(Daux $daux)
|
||||
{
|
||||
$confluence = $daux->getParams()['confluence'];
|
||||
|
||||
$this->prefix = trim($confluence['prefix']) . " ";
|
||||
|
||||
$main_title = $this->prefix . $daux->getParams()['title'];
|
||||
|
||||
$params = $daux->getParams();
|
||||
|
||||
echo "Generating Tree...\n";
|
||||
$tree = $this->generateRecursive($daux->tree, $params);
|
||||
$tree['title'] = $main_title;
|
||||
$tree['title'] = $this->prefix . $daux->getParams()['title'];
|
||||
|
||||
echo "Getting already published pages...\n";
|
||||
$this->client = new Api($confluence['base_url'], $confluence['user'], $confluence['pass'], $confluence['space_id']);
|
||||
$all_published = $this->client->getHierarchy($confluence['ancestor_id']);
|
||||
|
||||
echo "Finding Root Page...\n";
|
||||
$published = [];
|
||||
foreach ($all_published as $page) {
|
||||
if ($page['title'] == $main_title) {
|
||||
$published = $page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Create placeholder pages...\n";
|
||||
$published = $this->createRecursive($confluence['ancestor_id'], $tree, $published);
|
||||
|
||||
echo "Publishing updates...\n";
|
||||
$this->updateRecursive($confluence['ancestor_id'], $tree, $published);
|
||||
echo "Start Publishing...\n";
|
||||
$publisher = new Publisher($confluence);
|
||||
$publisher->publish($tree);
|
||||
|
||||
echo "Done !\n";
|
||||
}
|
||||
|
||||
private function createRecursive($parent_id, $entry, $published)
|
||||
{
|
||||
//TODO :: remove deleted pages
|
||||
|
||||
if (!array_key_exists('id', $published)) {
|
||||
if (array_key_exists('page', $entry)) {
|
||||
echo "Creating: " . $entry['file']->getUrl() . "\n";
|
||||
$published['version'] = 1;
|
||||
$published['id'] = $this->client->createPage($parent_id, $entry['title'], "The content will come very soon !");
|
||||
} else {
|
||||
echo "Creating Placeholder page: " . $entry['title'] . "\n";
|
||||
$published['version'] = 1;
|
||||
$published['id'] = $this->client->createPage($parent_id, $entry['title'], "");
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('children', $entry)) {
|
||||
foreach($entry['children'] as $child) {
|
||||
$pub = [];
|
||||
if (array_key_exists('children', $published) && array_key_exists($child['title'], $published['children'])) {
|
||||
$pub = $published['children'][$child['title']];
|
||||
}
|
||||
|
||||
$published['children'][$child['title']] = $this->createRecursive($published['id'], $child, $pub);
|
||||
}
|
||||
}
|
||||
|
||||
return $published;
|
||||
}
|
||||
|
||||
private function updateRecursive($parent_id, $entry, $published)
|
||||
{
|
||||
if (array_key_exists('id', $published) && array_key_exists('page', $entry)) {
|
||||
echo "Updating: " . $entry['file']->getUrl() . "\n";
|
||||
try {
|
||||
$this->client->updatePage(
|
||||
$parent_id,
|
||||
$published['id'],
|
||||
$published['version'] + 1,
|
||||
$entry['title'],
|
||||
$entry['page']->getContent()
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
echo "-> Failed with message: " . $e->getResponse()->json()['message'] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('children', $entry)) {
|
||||
foreach($entry['children'] as $child) {
|
||||
$pub = [];
|
||||
if (array_key_exists('children', $published) && array_key_exists($child['title'], $published['children'])) {
|
||||
$pub = $published['children'][$child['title']];
|
||||
}
|
||||
|
||||
$this->updateRecursive($published['id'], $child, $pub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateRecursive(Entry $tree, array $params, $base_url = '')
|
||||
{
|
||||
$final = ['title' => $this->prefix . $tree->getTitle()];
|
||||
@ -125,9 +42,12 @@ class Generator
|
||||
}
|
||||
foreach ($tree->value as $key => $node) {
|
||||
if ($node instanceof Directory) {
|
||||
$final['children'][$this->prefix . $node->getTitle()] = $this->generateRecursive($node, $params, '../' . $base_url);
|
||||
} else if ($node instanceof Content) {
|
||||
|
||||
$final['children'][$this->prefix . $node->getTitle()] = $this->generateRecursive(
|
||||
$node,
|
||||
$params,
|
||||
'../' . $base_url
|
||||
);
|
||||
} elseif ($node instanceof Content) {
|
||||
$params['request'] = $node->getUrl();
|
||||
$params['file_uri'] = $node->getName();
|
||||
|
||||
@ -137,6 +57,10 @@ class Generator
|
||||
'page' => MarkdownPage::fromFile($node, $params),
|
||||
];
|
||||
|
||||
// As the page is lazily generated
|
||||
// We do it now to fail fast in case of problem
|
||||
$data['page']->getContent();
|
||||
|
||||
if ($key == 'index.html') {
|
||||
$final['title'] = $this->prefix . $tree->getTitle();
|
||||
$final['file'] = $node;
|
||||
|
30
libs/Format/Confluence/IndentedCodeRenderer.php
Normal file
30
libs/Format/Confluence/IndentedCodeRenderer.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use League\CommonMark\Block\Element\AbstractBlock;
|
||||
use League\CommonMark\Block\Element\IndentedCode;
|
||||
use League\CommonMark\Block\Renderer\BlockRendererInterface;
|
||||
use League\CommonMark\HtmlElement;
|
||||
use League\CommonMark\HtmlRendererInterface;
|
||||
|
||||
class IndentedCodeRenderer implements BlockRendererInterface
|
||||
{
|
||||
/**
|
||||
* @param AbstractBlock $block
|
||||
* @param HtmlRendererInterface $htmlRenderer
|
||||
* @param bool $inTightList
|
||||
*
|
||||
* @return HtmlElement
|
||||
*/
|
||||
public function render(AbstractBlock $block, HtmlRendererInterface $htmlRenderer, $inTightList = false)
|
||||
{
|
||||
if (!($block instanceof IndentedCode)) {
|
||||
throw new \InvalidArgumentException('Incompatible block type: ' . get_class($block));
|
||||
}
|
||||
|
||||
return new HtmlElement(
|
||||
'ac:structured-macro',
|
||||
['ac:name' => 'code'],
|
||||
new HtmlElement('ac:plain-text-body', [], '<![CDATA['.$block->getStringContent().']]>')
|
||||
);
|
||||
}
|
||||
}
|
@ -1,18 +1,32 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use DOMDocument;
|
||||
use Todaymade\Daux\DauxHelper;
|
||||
|
||||
class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage
|
||||
{
|
||||
public $attachments = [];
|
||||
|
||||
protected function getMarkdownConverter()
|
||||
{
|
||||
return new CommonMarkConverter();
|
||||
}
|
||||
|
||||
protected function generatePage()
|
||||
{
|
||||
$page = parent::generatePage();
|
||||
|
||||
//Embed images
|
||||
// We do it after generation so we can catch the images that were in html already
|
||||
$page = preg_replace_callback(
|
||||
"/<img\\s+[^>]*src=['\"]([^\"]*)['\"][^>]*>/",
|
||||
function ($matches) {
|
||||
return str_replace($matches[1], $this->findImage($matches[1]), $matches[0]);
|
||||
|
||||
if ($result = $this->findImage($matches[1], $matches[0])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
},
|
||||
$page
|
||||
);
|
||||
@ -20,7 +34,7 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage
|
||||
return $page;
|
||||
}
|
||||
|
||||
private function findImage($src)
|
||||
private function findImage($src, $tag)
|
||||
{
|
||||
//for protocol relative or http requests : keep the original one
|
||||
if (substr($src, 0, strlen("http")) === "http" || substr($src, 0, strlen("//")) === "//") {
|
||||
@ -35,16 +49,46 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage
|
||||
|
||||
|
||||
if ($file === false) {
|
||||
return $src;
|
||||
return false;
|
||||
}
|
||||
|
||||
$encoded = base64_encode(file_get_contents($file->getPath()));
|
||||
$extension = pathinfo($file->getPath(), PATHINFO_EXTENSION);
|
||||
$filename = basename($file->getPath());
|
||||
|
||||
if (!in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||
return $src;
|
||||
//Add the attachment for later upload
|
||||
$this->attachments[] = ['filename' => $filename, 'file' => $file];
|
||||
|
||||
return $this->createImageTag($filename, $this->getAttributes($tag));
|
||||
}
|
||||
|
||||
private function getAttributes($tag)
|
||||
{
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadHTML($tag);
|
||||
|
||||
$img = $dom->getElementsByTagName('img')[0];
|
||||
|
||||
$attributes = ['align', 'class', 'title', 'style', 'alt', 'height', 'width'];
|
||||
$used = [];
|
||||
foreach ($attributes as $attr) {
|
||||
if ($img->attributes->getNamedItem($attr)) {
|
||||
$used[$attr] = $img->attributes->getNamedItem($attr)->value;
|
||||
}
|
||||
}
|
||||
|
||||
return "data:image/$extension;base64,$encoded";
|
||||
return $used;
|
||||
}
|
||||
|
||||
private function createImageTag($filename, $attributes)
|
||||
{
|
||||
|
||||
$img = "<ac:image";
|
||||
|
||||
foreach ($attributes as $name => $value) {
|
||||
$img .= ' ac:' . $name.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8', false).'"';
|
||||
}
|
||||
|
||||
$img .= "><ri:attachment ri:filename=\"$filename\" /></ac:image>";
|
||||
|
||||
return $img;
|
||||
}
|
||||
}
|
||||
|
212
libs/Format/Confluence/Publisher.php
Normal file
212
libs/Format/Confluence/Publisher.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php namespace Todaymade\Daux\Format\Confluence;
|
||||
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\ParseException;
|
||||
|
||||
class Publisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Api
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $confluence;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $previous_title;
|
||||
|
||||
public function __construct($confluence)
|
||||
{
|
||||
$this->confluence = $confluence;
|
||||
|
||||
$this->client = new Api($confluence['base_url'], $confluence['user'], $confluence['pass']);
|
||||
$this->client->setSpace($confluence['space_id']);
|
||||
}
|
||||
|
||||
public function publish(array $tree)
|
||||
{
|
||||
echo "Getting already published pages...\n";
|
||||
$all_published = $this->client->getHierarchy($this->confluence['ancestor_id']);
|
||||
|
||||
echo "Finding Root Page...\n";
|
||||
$published = [];
|
||||
foreach ($all_published as $page) {
|
||||
if ($page['title'] == $tree['title']) {
|
||||
$published = $page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Create placeholder pages...\n";
|
||||
$published = $this->createRecursive($this->confluence['ancestor_id'], $tree, $published);
|
||||
|
||||
echo "Publishing updates...\n";
|
||||
$this->updateRecursive($this->confluence['ancestor_id'], $tree, $published);
|
||||
}
|
||||
|
||||
protected function niceTitle($title)
|
||||
{
|
||||
if ($title == "index.html") {
|
||||
return "Homepage";
|
||||
}
|
||||
|
||||
return rtrim(strtr($title, ['index.html' => '', '.html' => '']), "/");
|
||||
}
|
||||
|
||||
protected function createPage($parent_id, $entry, $published)
|
||||
{
|
||||
if ($this->previous_title != "Creating") {
|
||||
$this->previous_title = "Creating";
|
||||
echo "Creating Pages...\n";
|
||||
}
|
||||
|
||||
echo "- " . $this->niceTitle($entry['file']->getUrl());
|
||||
$published['version'] = 1;
|
||||
$published['id'] = $this->client->createPage($parent_id, $entry['title'], "The content will come very soon !");
|
||||
echo " √ \n";
|
||||
|
||||
return $published;
|
||||
}
|
||||
|
||||
protected function createPlaceholderPage($parent_id, $entry, $published)
|
||||
{
|
||||
if ($this->previous_title != "Creating Placeholder") {
|
||||
$this->previous_title = "Creating Placeholder";
|
||||
echo "Creating Placeholder Pages...\n";
|
||||
}
|
||||
|
||||
echo "- " . $entry['title'];
|
||||
$published['version'] = 1;
|
||||
$published['id'] = $this->client->createPage($parent_id, $entry['title'], "");
|
||||
echo " √ \n";
|
||||
|
||||
return $published;
|
||||
}
|
||||
|
||||
protected function recursiveWithCallback($parent_id, $entry, $published, $callback)
|
||||
{
|
||||
$published = $callback($parent_id, $entry, $published);
|
||||
|
||||
if (array_key_exists('children', $entry)) {
|
||||
foreach ($entry['children'] as $child) {
|
||||
$pub = [];
|
||||
if (isset($published['children']) && array_key_exists($child['title'], $published['children'])) {
|
||||
$pub = $published['children'][$child['title']];
|
||||
}
|
||||
|
||||
$published['children'][$child['title']] = $this->recursiveWithCallback(
|
||||
$published['id'],
|
||||
$child,
|
||||
$pub,
|
||||
$callback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $published;
|
||||
}
|
||||
|
||||
protected function createRecursive($parent_id, $entry, $published)
|
||||
{
|
||||
$callback = function ($parent_id, $entry, $published) {
|
||||
|
||||
//TODO :: remove deleted pages
|
||||
|
||||
// nothing to do if the ID already exists
|
||||
if (array_key_exists('id', $published)) {
|
||||
return $published;
|
||||
}
|
||||
|
||||
if (array_key_exists('page', $entry)) {
|
||||
return $this->createPage($parent_id, $entry, $published);
|
||||
}
|
||||
|
||||
// If we have no $entry['page'] it means the page
|
||||
// doesn't exist, but to respect the hierarchy,
|
||||
// we need a blank page there
|
||||
return $this->createPlaceholderPage($parent_id, $entry, $published);
|
||||
};
|
||||
|
||||
return $this->recursiveWithCallback($parent_id, $entry, $published, $callback);
|
||||
}
|
||||
|
||||
protected function updateRecursive($parent_id, $entry, $published)
|
||||
{
|
||||
$callback = function ($parent_id, $entry, $published) {
|
||||
if (array_key_exists('id', $published) && array_key_exists('page', $entry)) {
|
||||
$this->updatePage($parent_id, $entry, $published);
|
||||
}
|
||||
|
||||
return $published;
|
||||
};
|
||||
|
||||
return $this->recursiveWithCallback($parent_id, $entry, $published, $callback);
|
||||
}
|
||||
|
||||
protected function shouldUpdate($local, $published)
|
||||
{
|
||||
if (!array_key_exists('content', $published)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$trimmed_local = trim($local);
|
||||
$trimmed_distant = trim($published['content']);
|
||||
|
||||
if ($trimmed_local == $trimmed_distant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
similar_text($trimmed_local, $trimmed_distant, $percent);
|
||||
|
||||
// I consider that if the files are 98% identical you
|
||||
// don't need to update. This will work for false positives.
|
||||
// But sadly will miss if it's just a typo update
|
||||
if ($percent >= 98) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function updatePage($parent_id, $entry, $published)
|
||||
{
|
||||
if ($this->previous_title != "Updating") {
|
||||
$this->previous_title = "Updating";
|
||||
echo "Updating Pages...\n";
|
||||
}
|
||||
|
||||
echo "- " . $this->niceTitle($entry['file']->getUrl());
|
||||
|
||||
try {
|
||||
if ($this->shouldUpdate($entry['page']->getContent(), $published)) {
|
||||
$this->client->updatePage(
|
||||
$parent_id,
|
||||
$published['id'],
|
||||
$published['version'] + 1,
|
||||
$entry['title'],
|
||||
$entry['page']->getContent()
|
||||
);
|
||||
echo " √\n";
|
||||
} else {
|
||||
echo " √ (No update needed)\n";
|
||||
}
|
||||
|
||||
if (count($entry['page']->attachments)) {
|
||||
foreach ($entry['page']->attachments as $attachment) {
|
||||
echo " With attachment: $attachment[filename]";
|
||||
$this->client->uploadAttachment($published['id'], $attachment);
|
||||
echo " √\n";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (BadResponseException $e) {
|
||||
echo " X Failed with message: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ class MarkdownPage extends \Todaymade\Daux\Format\Base\MarkdownPage
|
||||
'modified_time' => filemtime($this->file->getPath()),
|
||||
'markdown' => $this->content,
|
||||
'request' => $params['request'],
|
||||
'content' => (new \Parsedown())->text($this->content),
|
||||
'content' => $this->convertPage($this->content),
|
||||
'breadcrumbs' => $params['breadcrumbs']
|
||||
];
|
||||
|
||||
|
@ -254,8 +254,7 @@ class Getopt
|
||||
$long = [];
|
||||
$short = '';
|
||||
foreach ($this->rules as $rule) {
|
||||
foreach($rule['alias'] as $alias) {
|
||||
|
||||
foreach ($rule['alias'] as $alias) {
|
||||
$prepared = $alias;
|
||||
if ($rule['param'] == 'optional') {
|
||||
$prepared .= '::';
|
||||
|
Loading…
x
Reference in New Issue
Block a user