GithubHelp home page GithubHelp logo

thephpleague / commonmark Goto Github PK

View Code? Open in Web Editor NEW
2.7K 35.0 187.0 5.81 MB

Highly-extensible PHP Markdown parser which fully supports the CommonMark and GFM specs.

Home Page: https://commonmark.thephpleague.com

License: BSD 3-Clause "New" or "Revised" License

PHP 94.31% HTML 5.48% Dockerfile 0.21%
php commonmark markdown github-flavored-markdown gfm hacktoberfest

commonmark's Introduction

league/commonmark

Latest Version Total Downloads Software License Build Status Coverage Status Quality Score Psalm Type Coverage CII Best Practices Sponsor development of this project

league/commonmark

league/commonmark is a highly-extensible PHP Markdown parser created by Colin O'Dell which supports the full CommonMark spec and GitHub-Flavored Markdown. It is based on the CommonMark JS reference implementation by John MacFarlane (@jgm).

📦 Installation & Basic Usage

This project requires PHP 7.4 or higher with the mbstring extension. To install it via Composer simply run:

$ composer require league/commonmark

The CommonMarkConverter class provides a simple wrapper for converting CommonMark to HTML:

use League\CommonMark\CommonMarkConverter;

$converter = new CommonMarkConverter([
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

echo $converter->convert('# Hello World!');

// <h1>Hello World!</h1>

Or if you want GitHub-Flavored Markdown, use the GithubFlavoredMarkdownConverter class instead:

use League\CommonMark\GithubFlavoredMarkdownConverter;

$converter = new GithubFlavoredMarkdownConverter([
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

echo $converter->convert('# Hello World!');

// <h1>Hello World!</h1>

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

🔒 If you will be parsing untrusted input from users, please consider setting the html_input and allow_unsafe_links options per the example above. See https://commonmark.thephpleague.com/security/ for more details. If you also do choose to allow raw HTML input from untrusted users, consider using a library (like HTML Purifier) to provide additional HTML filtering.

📓 Documentation

Full documentation on advanced usage, configuration, and customization can be found at commonmark.thephpleague.com.

⏫ Upgrading

Information on how to upgrade to newer versions of this library can be found at https://commonmark.thephpleague.com/releases.

💻 GitHub-Flavored Markdown

The GithubFlavoredMarkdownConverter shown earlier is a drop-in replacement for the CommonMarkConverter which adds additional features found in the GFM spec:

  • Autolinks
  • Disallowed raw HTML
  • Strikethrough
  • Tables
  • Task Lists

See the Extensions documentation for more details on how to include only certain GFM features if you don't want them all.

🗃️ Related Packages

Integrations

Included Extensions

See our extension documentation for a full list of extensions bundled with this library.

Community Extensions

Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting:

Others can be found on Packagist under the commonmark-extension package type.

If you build your own, feel free to submit a PR to add it to this list!

Others

Check out the other cool things people are doing with league/commonmark: https://packagist.org/packages/league/commonmark/dependents

🏷️ Versioning

SemVer is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.

Any classes or methods marked @internal are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them.

🛠️ Maintenance & Support

When a new minor version (e.g. 2.0 -> 2.1) is released, the previous one (2.0) will continue to receive security and critical bug fixes for at least 3 months.

When a new major version is released (e.g. 1.6 -> 2.0), the previous one (1.6) will receive critical bug fixes for at least 3 months and security updates for 6 months after that new release comes out.

(This policy may change in the future and exceptions may be made on a case-by-case basis.)

Professional support, including notification of new releases and security updates, is available through a Tidelift Subscription.

👷‍♀️ Contributing

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure with us.

If you encounter a bug in the spec, please report it to the CommonMark project. Any resulting fix will eventually be implemented in this project as well.

Contributions to this library are welcome, especially ones that:

Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to the reference implementation. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations.

Please see CONTRIBUTING for additional details.

🧪 Testing

$ composer test

This will also test league/commonmark against the latest supported spec.

🚀 Performance Benchmarks

You can compare the performance of league/commonmark to other popular parsers by running the included benchmark tool:

$ ./tests/benchmark/benchmark.php

👥 Credits & Acknowledgements

This code is partially based on the CommonMark JS reference implementation which is written, maintained and copyrighted by John MacFarlane. This project simply wouldn't exist without his work.

Sponsors

We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project:

Are you interested in sponsoring development of this project? See https://www.colinodell.com/sponsor for a list of ways to contribute.

📄 License

league/commonmark is licensed under the BSD-3 license. See the LICENSE file for more details.

🏛️ Governance

This project is primarily maintained by Colin O'Dell. Members of the PHP League Leadership Team may occasionally assist with some of these duties.

🗺️ Who Uses It?

This project is used by Drupal, Laravel Framework, Cachet, Firefly III, Neos, Daux.io, and more!


Get professional support for league/commonmark with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.

commonmark's People

Contributors

0b10011 avatar ayesh avatar baijunyao avatar bcremer avatar colinodell avatar dakota avatar dependabot[bot] avatar elgigi avatar fesor avatar github-actions[bot] avatar glensc avatar grahamcampbell avatar hason avatar johanmeiring avatar krakjoe avatar marijnvdwerf avatar markhalliwell avatar martijnengler avatar michalbundyra avatar miken32 avatar mnapoli avatar nanaya avatar ntzm avatar onigoetz avatar pdelre avatar philsturgeon avatar pjeby avatar renovate[bot] avatar szepeviktor avatar vkbansal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

commonmark's Issues

Custom Block Elements (AbstractInlineContainers) in custom parser?

EDIT: On second thought, it looks like this is achieved by creating a custom Element and a custom Renderer to accompany the custom Parser. Point still stands about adding some custom Block parser examples though, which would be very helpful.

Since there's understandably a shortage of examples so far, especially none for custom Block parsers, I'm puzzled at how to add classes (or other HTML attributes) to existing Block Elements like Paragraph, BlockQuote, etc. or even make new classes such as Divider (div).

Convert to Markdown

Hi,

I am not sure if I am looking for something specific to what I need, but we currently use Markdown for our article text and so on. What I would like to do is parse the Markdown to HTML, save it in the database and then show the HTML within the template instead of parsing content on load.

With this, is there (I have had a quick look but cannot find anything) or will there be an ability to convert HTML back to Markdown?

Again, not sure if I thinking about this the right way but it can add a small load to converting different context on the same page.

Cheers

Compatybility with Symfony Framework 2.6+

Is it possible for you to mark symfony components to be 2.5+ versions and not only restricted to 2.5 - this makes it impossible to use wuth Symfony Framework 2.6 at the moment and since Symfony is always backwrds compatibile it shoule be no problem.

Markdown Extra

Hi there!

I want to support Markdown Extra to be able to replace completely Parsedown with this package in Couscous. I'm guessing you don't want to add support for Markdown Extra in there right? (which would make sense, Markdown Extra ≠ CommonMark). Is there any plan on your side or anybody else for this?

And if not and I have to get to it myself, do you think it can easily be done (hopefully the new architecture with the model will help). Do you have pointers/tips to get me started?

Fix Scrutinizer

It's likely erroring out because PHP 7 doesn't support code coverage. Make Travis avoid the code coverage and Scrutinzer upload stuff and it should work again.

Parsing error

Add a PHP model for manipulating Markdown documents?

Both in:

I'd like to be able to:

  • add new features to Markdown

    For example auto-add HTML ids to headers (see #39 and TitleIdPreprocessor)

  • extract information from the documents.

    For example, to build up a summary of an article, I'd like to get a list of all the <h2> (or rather ##) (see ExtractSectionsPostprocessor). Or another example: I'd like to extract the main title of the article…

As you can see in the links, I manage to do all that by preprocessing/postprocessing the Markdown and generated HTML. But this is very hacky and inefficient.

What if we had a Markdown parser that would parse Markdown into a PHP model? That model could then be serialized to HTML (or even back to Markdown… or could even be parsed from HTML and thus generate Markdown from HTML whooo…).

So instead of rewriting yet another Markdown parser, I'm turning to you! You have the cleanest code of all Markdown parsers, and you already have some sort of PHP model (AST). However your AST representation doesn't seem to be 100% mapped to Markdown entities. So what about having a model containing classes for headers, paragraphs, links, etc... A sort of DOM for Markdown…

Would you be interest in adding that to that library? I could help of course. I just want to know if you consider it in the scope of that package or not?

Question About convertToHtml

Why are creating new classes over and over in the convertToHtml method? I don't understand why you don't do that bit in the constructor?

LaTeX output

I am in a bit of a predicament and I believe I can code my way out of it.

Currently I am trying to get my API book done as print, but LeanPub's PDF generation is... too simple. It has a lot of bugs and problems and I can't fix them, so I am trying to find a solution I can work with.

Markua, LeanPub's Markdown flavor, is a CommonMark superset. A potential league/markua package was started by @dshafik a while ago.

Luckily league/commonmark already outputs to an AST, so all we really need to do is output from that AST to LaTeX. At that point the LaTeX can be smashed through pandoc and I have a bazillion output formats to work with.

I have been talking to @cebe about this, and he already has some code which is doing kinda that with his own markdown package.

@colinodell how tough do you think it would be, to get some convertToLatex() functionality all up in here, alongside the HTML output we already have.

Would it be best as another library, or baked into this, and would it support Markua stuff (and any other added flavor) nicely?

Question: ordered lists starting with 0

If I'm not mistaken, ordered lists starting with any positive number will have the start='N' attribute, but this is not the case when starting a list with 0, it will simply start it at the default 1.

Is this expected behavior based on the spec (I did not see any rules prohibiting it, but I could be wrong), and would you consider adding it as a feature? I think it would be beneficial seeing how especially in areas related to computing often indexes begin with a 0.

Request: Convert to plain text

It would be great to have a convertToText() function. This would allow one to take markdown formatted input and obtain it as plain text. Scenario for this could be for example previews (showing first X words) where formatting is not always wanted.

Great work on this commonmark project! Much appreciated.

Extensions in repo

Hey,

since it is really easy to add useful additions to this awesome parser and they should not be added to the core (since the core should always just stick to the commonmark standards), how about adding a folder or link doc to extensions?

I would think if anybody who wrote a good inlineparser sends a pullrequest in which the file is added to a new extensions folder, this would mean many people could just use those instead of rewriting everything.

Maybe if you feel this is to much work or puts you in the spot of having to maintain those extensions, you could just add a list to the docs, where people can send PRs to have their extension added.

Or maybe they could be added using the suggest tag in composer.

I hope you get what I mean, I think this could add a great deal of features to the parser without bloating it, because every feature would be opt-in as non come pre-enabled.

Something analogous to HtmlRenderer's renderInlines() for parsing?

Say you're doing some custom inline parsing, and want a certain character to put the rest of the line into a span.

This is :some text here\n
Such :wonderful text

would become

This is <span>some text here</span>
Such <span>wonderful text</span>

This could (presumably) be done with the Processor interface, but that seems like a pretty complex beast and would require a separate class to match every single newline or end of line.

Instead, one could in theory just simply use something similar to what htmlRenderer->renderInlines() during rendering, and just advance over the rest of the line while saving it to string and doing some sort of parse() over it separately. However, there is no way to access the mother Environment object to see what the rules are.

I've "solved" this by doing something like this:

public function parse(ContextInterface $context, InlineParserContext $inlineContext)
{
        . . .

        $remainder = $cursor->match('/.*(?=$|\n)/'); // Match everything before newline, or end of line

        $newInlineCursor = new Cursor($remainder);
        require 'CommonMarkEnvironment.php'; // Contains $environment declaration and rules
        $newInlineParserEngine = new InlineParserEngine($environment);
        $newInlines = $newInlineParserEngine->parse($context, $newInlineCursor)->toArray();

        $inlineContext->getInlines()->add(new Indent($newInlines)); // Indent extends AbstractInlineContainer

        return true;
}

But this is obviously very dirty since it involves calling the code that established the current Environment again and creating a whole new Environment instance. Is there already a way to just simply have a certain string parsed as inlines according to the existing mother Environment rules?

One way that would work pretty great is if the Context class had a getEnvironment() method in the same way that it has a getDocument() method, since its constructor accepts both a Document and and an Environment object, but only has a getter method to retrieve the Document and not the Environment.

Configuration

Parsers and renderers will need to be configurable at some point. For example, some users may want to whitelist certain HTML elements as "safe", ensuring that others get escaped. If we were to allow such configurations now, the user would need to manually build their Environment and set the options on each individual parser/renderer:

$environment = new Environment();
$linkParser = new LinkParser(['defaultScheme' => 'https']);
$htmlParser = new HtmlParser(['allowedTags' => ['a', 'strong', 'p', 'dl', 'dt', 'dd']]);
// ... Repeat for every single parser/renderer

// Also repeat this for every new parser/renderer you add
$environment->addInlineParser($linkParser);
// ... etc

It would be much easier if the user could obtain the pre-built Environment and simply pass all their configurations in at once. Perhaps it would look like this:

$environment = Environment::createCommonMarkEnvironment([
    'links' => [
        'defaultScheme' => 'https',
    ],
    'html' => [
        'allowedTags' => ['a', 'strong', 'dl', /*...*/ ]
    ],
]);
$docParser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

So the questions I have are:

  1. Is this a good idea?
  2. How do the parsers/renderers define which options they support?
  3. How do the config values get set on the parsers/renderers?
  4. Do we use something like symfony/options-resolver, simple arrays, or roll our own solution?
  5. Should users be able to change the configuration on-the-fly, or is it an immutable private/protected variable than cannot be changed once the Environment is created?

Blockquotes aren't being parsed

As the title suggests, blockquotes aren't being parsed.

I am using Laravel 5.0.31 with GrahamCampbell''s Laravel Markdown wrapper for this package. As far as I can tell, all other markdown syntax is being parsed.

There is a big bug

In javascript:

'0' == true
0 == false

In php:

`0` == false
0 == false

so, if there is a 0 in markdown text, your code will crash, for example

![google_007.jpg][1]


  [1]: https://www.google.com/images/srpr/logo11w.png

but it will be correct after delete 0 character

![google_117.jpg][1]


  [1]: https://www.google.com/images/srpr/logo11w.png

Consider using `S` modifier for regex matching

From the docs:

When a pattern is going to be used several times, it is worth spending more time analyzing it in order to speed up the time taken for matching. If this modifier is set, then this extra analysis is performed. At present, studying a pattern is useful only for non-anchored patterns that do not have a single fixed starting character.

See here for more details. From anecdotal experience, I was able to bring our sample listingfeed api - gets tickets from external sources, parses them, groups them - from 2 seconds to 0.5 seconds, in large part because of this modifier.

Feature Request: Image URLs

I would like to request a feature where I can set a base directory for all images, so say for example I write:

![my image](thisimage.jpg)

CommonMark PHP would be able to turn it into

<img src="//images.mywebsite.com/thisimage.jpg" title="my image" />

I'm not sure if this is the scope of the project, but it would be a nice addition

Consider using "Keep A Changelog"

For other projects on the league we've been trying to use keepachangelog.com, and it's been working pretty well. It splits up changes into logical groups, so you can see whats added, fixed, security issues, etc.

Maybe have a go at that for the CHANGELOG.md?

I also then shove the markdown contents of that list into the release notes for GitHub tags. Looks kinda cool then. :)

Support for multiple renderers

My use case is that I need custom renderers for images and depending on the image URL, these renders will render the image as an iframe for YouTube, Vimeo, etc. At present I use the Sundown renderer and I have stuffed all these renderers into one big file but would like to move over to this lib and cleanup the mess while I'm at it.

I also imagine that a lot of contributors can then share their own YoutubeRenderer (or generic VideoRenderer). This would make extensibility a lot more fun and build an ecosystem around it. Maybe even give way to an extensions repo.

Based on your suggestion in #32, I currently render my custom Renderer as

$environment->addInlineRenderer('League\CommonMark\Inline\Element\Image', new MediaRenderer());

However, this only allows attaching a single renderer. It would be nice if the renderer can work sort of like conventional event handlers with the possibility to either chain the output as input to other renderers or stop propagation. The easiest approach would be to have symmetric input/output for renderers that would make chaining a breeze. Currently the input/output is (AbstractInline, HtmlRenderer)/HtmlElement.

Loose the requirement of mbstring

This library requires the mbstring extension. Unless I'm mistaken, this extension is not always installed on every system, and I'm afraid it will lead to problems with end users not being to install this project or projects requiring this one.

Why is that extension required? Simply for mb_* functions? In that case would it be acceptable to do something like "if mbstring then use mb_strlen else use strlen"?

Refactor AST to doubly-linked list

It seems the reference implementation had good results after making this change, so I'd like to try it out too. It involves some heavy refactoring across virtually all classes, so this could take a while, and would result in a major version bump.

One nice benefit is that it eliminates the possibility of stack overflows while handling deeply-nested elements. So I guess we'll try it out and see how it goes.

Markbench

It would probably be a good move to add yourself into Markbench, to show how things stack up against the other parsers.

https://github.com/kzykhys/Markbench#readme

Of course commonmark is going to be slower initially, as it is fully compliant with a larger spec, but it would be good to get the numbers in there then improve them over time.

Russian in markdown

Russian text with <code> renders like:

  • Из: твоя оя featu ветка
  • But must render like:

  • Из: твоя feature ветка
  • It repeats some symbols.

    When in line are only english sybmols - is OK

    Bug in emphasis parsing

    There is a bug in the emphasis parser for every pattern terminated with a 0 char.

    Examples : **1980** or *whatever0* are not parsed.

    How do I add custom directives?

    Given that @philsturgeon nearly wet his pants in excitement about this repo, I figured I'd check it out. I've been using cebe/markdown as a base parser - with extensions for leanpub's garbage - and while I'd like to switch to this, I haven't been able to figure out where I should hook into to add custom directives.

    Any thoughts on how I might go about this?

    Smartypants

    This is probably another package, but in the same vein as the potential markua package, it would be good to get Smartypants involved.

    This would help CommonMark projects be usable for printed books and whatnot, turning standard quotes into curly quotes and other similar bits.

    Unable to add most attributes to images

    I am writing a custom image processor that needs to add custom classes to an image:

    $inline->attributes['class'] = 'foo';
    

    However, the ImageRenderer explicitly sets the alt, src and title attributes only. Is this by design or can this be allow to pass through any other attributes?

    Alternatively, I tried adding a custom renderer to add more attributes but am probably missing some pieces of the puzzle to get this working:

    class MediaRenderer implements InlineRendererInterface
    {
        public function render(AbstractBaseInline $inline, HtmlRenderer $htmlRenderer)
        {
            return $htmlRenderer->inTags('img', ['class'=>'foo'], '', true);
        }
    }
    
    $environment->addInlineRenderer('MediaRenderer', new MediaRenderer());
    

    I then noticed that the ImageRender is added by default and there is no removeInlineRenderer option to unset it. Even so, creating my own image renderer for attribute support would require me to keep copy/paste the code from ImageRenderer and ensure it stays in sync in future releases.

    Allowing other attributes besides alt, src and title would be the easiest so hoping that can come through :)

    Extensions vs ad-hoc component registration

    The Environment currently requires that each parser/renderer/processor be registered individually using one of these methods:

    • addBlockParser()
    • addBlockRenderer()
    • addInlineParser()
    • addInlineProcessor()
    • addInlineRenderer()

    Should we continue doing this, or should we adopt something like Twig's Twig_Environment where you register "extension" objects which define the parsers/renderers that they require? For example, we'd create an interface like this:

    interface ExtensionInterface
    {
        /**
         * @return BlockParserInterface[]
         */
        public function getBlockParsers();
    
        /**
         * @return BlockRendererInterface[]
         */
        public function getBlockRenderers();
    
        /**
         * @return InlineParserInterface[]
         */
        public function getInlineParsers();
    
        /**
         * @return InlineProcessorInterface[]
         */
        public function getInlineProcessors();
    
        /**
         * @return InlineRendererInterface[]
         */
        public function getInlineRenderers();
    }

    We'd then replace the 5 Environment methods (top) with a single method like:

    public function registerExtension(ExtensionInterface $extension);

    Pros:

    • Consolidates 5 semi-related methods into 1
    • In most cases, each parser has a corresponding renderer anyway
    • Moves most of the Environment::createCommonMarkEnvironment logic into a separate CommonMarkExtension class
    • Perhaps the Extension becomes responsible for providing the required configuration elements? (See #42)

    Cons:

    • Lose the ability for quick one-of changes, like replacing a single renderer only
    • Too much abstraction?

    Call to undefined method League\CommonMark\HtmlRenderer::render()

    Since 0.5, I have been getting this error. It happens when using the example code:

      $document = $parser->parse($markdown);
      echo $htmlRenderer->render($document);
    

    Leading to the error in the title.

    This is fixed by instead using the renderBlock method instead of render, which I assume is removed in favor of the former:

      $document = $parser->parse($markdown);
      echo $htmlRenderer->renderBlock($document);
    

    Memory Exhaustion with basic custom parse

    I receive the following error:

    Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes) in /var/www/public/commonmark/index.php on line 21
    

    when trying this code:

    <?php
    
    ini_set('display_errors', 1);
    ini_set('error_reporting', E_ALL);
    
    require_once __DIR__ . '/vendor/autoload.php';
    
    use League\CommonMark\ContextInterface;
    use League\CommonMark\InlineParserContext;
    use League\CommonMark\Inline\Parser\AbstractInlineParser;
    use League\CommonMark\Inline\Element\Link;
    class TwitterHandleParser extends AbstractInlineParser
    {
        public function getCharacters() {
            return array('@');
        }
    
        public function parse(ContextInterface $context, InlineParserContext $inlineContext)
        {
            // // TODO: Use $inlineContext->getCursor() to parse through the current line
            $inlineContext->getInlines()->add(new Link("mailto:[email protected]", "[email protected]"));
            return true;
        }
    }
    
    
    use League\CommonMark\DocParser;
    use League\CommonMark\Environment;
    use League\CommonMark\HtmlRenderer;
    
    $environment = Environment::createCommonMarkEnvironment();
    $environment->addInlineParser(new TwitterHandleParser());
    $parser = new DocParser($environment);
    $renderer = new HtmlRenderer($environment);
    
    $markdown = '# Hello World! *foo* @foo';
    
    $documentAST = $parser->parse($markdown);
    $html = $renderer->renderBlock($documentAST);
    
    echo $html;
    

    Changing line 22 to return false fixes the issue. Am I missing something?

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.