Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@
add_action( 'do_feed_rss', 'do_feed_rss', 10, 0 );
add_action( 'do_feed_rss2', 'do_feed_rss2', 10, 1 );
add_action( 'do_feed_atom', 'do_feed_atom', 10, 1 );
add_action( 'do_feed_markdown', 'do_feed_markdown', 10, 1 );
add_action( 'init', 'wp_register_markdown_feed' );
add_action( 'do_pings', 'do_all_pings', 10, 0 );
add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 );
add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 );
Expand Down
71 changes: 71 additions & 0 deletions src/wp-includes/feed-markdown.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
/**
* Markdown Feed Template for displaying posts as Markdown text.
*
* Accessible via /?feed=markup or /feed/markup/ depending on permalink settings.
*
* @package WordPress
* @since 6.7.0
*/

// Output Markdown so clients can render appropriately.
header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ), true );

// Ensure full content is used.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Used intentionally to ensure full content in feed.
$more = 1;

// Register a simple autoloader for the bundled League HTMLToMarkdown library.
spl_autoload_register( function ( $class ) {
if ( 0 === strpos( $class, 'League\\HTMLToMarkdown\\' ) ) {
$relative = str_replace( 'League\\HTMLToMarkdown\\', '', $class );
$relative = str_replace( '\\', DIRECTORY_SEPARATOR, $relative );
$file = ABSPATH . WPINC . '/html-to-markdown/' . $relative . '.php';
if ( file_exists( $file ) ) {
require_once $file;
}
}
} );

// Create a converter instance; tune options if desired.
$__wp_md_converter = new \League\HTMLToMarkdown\HtmlConverter( array(
'header_style' => 'atx',
'suppress_errors' => true,
) );

// Feed header as Markdown.
echo '# ' . wp_strip_all_tags( get_bloginfo( 'name' ) ) . ' — ' . __( 'Markdown Feed', 'default' ) . "\n\n";
$desc = get_bloginfo( 'description' );
if ( $desc ) {
echo wp_strip_all_tags( $desc ) . "\n\n";
}

echo __( 'Feed URL:', 'default' ) . ' <' . esc_url_raw( get_self_link() ) . ">\n\n";

if ( have_posts() ) :
while ( have_posts() ) :
the_post();

$title = wp_strip_all_tags( get_the_title() );
$permalink = get_permalink();
$date_r = get_post_time( 'r', true );
$content = get_post_field( 'post_content', get_the_ID() );

// Post block in Markdown.
echo "## [" . $title . "]("
. $permalink . ")\n";
echo '*' . __( 'Published:', 'default' ) . '* ' . $date_r . "\n\n";

$html = apply_filters( 'the_content', $content );
$md = $__wp_md_converter->convert( (string) $html );
$md = trim( $md );
if ( $md !== '' ) {
echo $md . "\n\n";
}

// Separator.
echo "---\n\n";
endwhile;
else :
echo __( 'No posts found.', 'default' );
endif;
25 changes: 25 additions & 0 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,31 @@ function do_feed_atom( $for_comments ) {
}
}

/**
* Loads the MarkDown Feed Template.
*
* A simple HTML feed that outputs post content with MarkDown preserved.
*
* @since 6.7.0
*
* @see load_template()
*
* @param bool $for_comments Unused. Present for parity with other feed handlers.
*/
function do_feed_markdown( $for_comments ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable

load_template( ABSPATH . WPINC . '/feed-markdown.php' );
}

/**
* Registers the Markup feed rewrite and hook.
*
* @since 6.7.0
*/
function wp_register_markdown_feed() {
add_feed( 'markdown', 'do_feed_markdown' );
}

/**
* Displays the default robots.txt file content.
*
Expand Down
35 changes: 35 additions & 0 deletions src/wp-includes/html-to-markdown/Coerce.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace League\HTMLToMarkdown;

/**
* @internal
*/
final class Coerce
{
private function __construct()
{
}

/**
* @param mixed $val
*/
public static function toString($val): string
{
switch (true) {
case \is_string($val):
return $val;
case \is_bool($val):
case \is_float($val):
case \is_int($val):
case $val === null:
return \strval($val);
case \is_object($val) && \method_exists($val, '__toString'):
return $val->__toString();
default:
throw new \InvalidArgumentException('Cannot coerce this value to string');
}
}
}
80 changes: 80 additions & 0 deletions src/wp-includes/html-to-markdown/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace League\HTMLToMarkdown;

class Configuration
{
/** @var array<string, mixed> */
protected $config;

/**
* @param array<string, mixed> $config
*/
public function __construct(array $config = [])
{
$this->config = $config;

$this->checkForDeprecatedOptions($config);
}

/**
* @param array<string, mixed> $config
*/
public function merge(array $config = []): void
{
$this->checkForDeprecatedOptions($config);
$this->config = \array_replace_recursive($this->config, $config);
}

/**
* @param array<string, mixed> $config
*/
public function replace(array $config = []): void
{
$this->checkForDeprecatedOptions($config);
$this->config = $config;
}

/**
* @param mixed $value
*/
public function setOption(string $key, $value): void
{
$this->checkForDeprecatedOptions([$key => $value]);
$this->config[$key] = $value;
}

/**
* @param mixed|null $default
*
* @return mixed|null
*/
public function getOption(?string $key = null, $default = null)
{
if ($key === null) {
return $this->config;
}

if (! isset($this->config[$key])) {
return $default;
}

return $this->config[$key];
}

/**
* @param array<string, mixed> $config
*/
private function checkForDeprecatedOptions(array $config): void
{
foreach ($config as $key => $value) {
if ($key === 'bold_style' && $value !== '**') {
@\trigger_error('Customizing the bold_style option is deprecated and may be removed in the next major version', E_USER_DEPRECATED);
} elseif ($key === 'italic_style' && $value !== '*') {
@\trigger_error('Customizing the italic_style option is deprecated and may be removed in the next major version', E_USER_DEPRECATED);
}
}
}
}
10 changes: 10 additions & 0 deletions src/wp-includes/html-to-markdown/ConfigurationAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace League\HTMLToMarkdown;

interface ConfigurationAwareInterface
{
public function setConfig(Configuration $config): void;
}
42 changes: 42 additions & 0 deletions src/wp-includes/html-to-markdown/Converter/BlockquoteConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace League\HTMLToMarkdown\Converter;

use League\HTMLToMarkdown\ElementInterface;

class BlockquoteConverter implements ConverterInterface
{
public function convert(ElementInterface $element): string
{
// Contents should have already been converted to Markdown by this point,
// so we just need to add '>' symbols to each line.

$markdown = '';

$quoteContent = \trim($element->getValue());

$lines = \preg_split('/\r\n|\r|\n/', $quoteContent);
\assert(\is_array($lines));

$totalLines = \count($lines);

foreach ($lines as $i => $line) {
$markdown .= '> ' . $line . "\n";
if ($i + 1 === $totalLines) {
$markdown .= "\n";
}
}

return $markdown;
}

/**
* @return string[]
*/
public function getSupportedTags(): array
{
return ['blockquote'];
}
}
68 changes: 68 additions & 0 deletions src/wp-includes/html-to-markdown/Converter/CodeConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace League\HTMLToMarkdown\Converter;

use League\HTMLToMarkdown\ElementInterface;

class CodeConverter implements ConverterInterface
{
public function convert(ElementInterface $element): string
{
$language = '';

// Checking for language class on the code block
$classes = $element->getAttribute('class');

if ($classes) {
// Since tags can have more than one class, we need to find the one that starts with 'language-'
$classes = \explode(' ', $classes);
foreach ($classes as $class) {
if (\strpos($class, 'language-') !== false) {
// Found one, save it as the selected language and stop looping over the classes.
$language = \str_replace('language-', '', $class);
break;
}
}
}

$markdown = '';
$code = \html_entity_decode($element->getChildrenAsString());

// In order to remove the code tags we need to search for them and, in the case of the opening tag
// use a regular expression to find the tag and the other attributes it might have
$code = \preg_replace('/<code\b[^>]*>/', '', $code);
\assert($code !== null);
$code = \str_replace('</code>', '', $code);

// Checking if it's a code block or span
if ($this->shouldBeBlock($element, $code)) {
// Code block detected, newlines will be added in parent
$markdown .= '```' . $language . "\n" . $code . "\n" . '```';
} else {
// One line of code, wrapping it on one backtick, removing new lines
$markdown .= '`' . \preg_replace('/\r\n|\r|\n/', '', $code) . '`';
}

return $markdown;
}

/**
* @return string[]
*/
public function getSupportedTags(): array
{
return ['code'];
}

private function shouldBeBlock(ElementInterface $element, string $code): bool
{
$parent = $element->getParent();
if ($parent !== null && $parent->getTagName() === 'pre') {
return true;
}

return \preg_match('/[^\s]` `/', $code) === 1;
}
}
Loading
Loading