Render Markdown/GFM Documents online using the GitHub v3 API

simple code snipped to convert markdown to html, public github api

Sometimes, you need to render parts of your Markdown documents – e.g. README.md or CHANGES.md – as html to embed it into your application, documentation or project website. There are a several markdown or especially GFM (GitHub Flavored Markdown) libraries are out there, but they require an additional setup and have to be maintained.

The simple Way#

Thanks to GitHub, there is a public API available which allows you to render your documents by the GitHub webservices.

PHP Client#

/**
 * Render Markdown content using the GitHub v3 Markdown API
 * @see https://developer.github.com/v3/markdown/
 * @source https://andidittrich.de/2016/05/render-markdown-gfm-documents-online-using-the-github-v3-api
 * @license: MIT
 * @return string(html)
 */
function renderGFM($text, $repositoryContext = null){

    // create the payload
    // @see https://developer.github.com/v3/markdown/
    $postdata = json_encode(
        array(
            'text' => $text,
            'mode' => ($repositoryContext != null ? 'gfm' : 'markdown'),
            'context' => $repositoryContext
        )
    );

    // prepare the HTTP 1.1 POST Request
    $opts = array('http' =>
        array(
            'method'  => 'POST',
            'protocol_version' => '1.1',
            'user_agent' => $repositoryContext,
            'header'  => array(
                'Content-type: application/x-www-form-urlencoded;charset=UTF-8',
                'Connection: close',
                'Accept: application/vnd.github.v3+json'
            ),
            'content' => $postdata
        )
    );

    // send request
    return file_get_contents('https://api.github.com/markdown', false, stream_context_create($opts));
}

Usage#

The optional $repositoryContext argument allows your to define the context which should be used for rendering to e.g. enable issue linking

// fetch the document (example)
$document = file_get_contents('https://raw.githubusercontent.com/AndiDittrich/WordPress.Enlighter/master/CHANGES.md');

// render html using the GitHub GFM API
$html = renderGFM($document, 'AndiDittrich/WordPress.Enlighter');

// show it!
echo $html;

 

 

You may have noticed, that normal users (especially Author’s and Contributor’s) are not allowed to use all kind of HTML Tags and related Attributes.

Those elements got removed by the WordPress buil-in KSES Filter – and it’s a very useful feature in matter of security to prevent html-code-injection.

But sometimes it is required to enable some additional html tags and/or attributes. You can modify the list of allowed html tags and attributes by appling a custom filter:

The Filter#

Example how to allow EnlighterJS related attributes for pre and code tags

function ksesAllowHtmlCodeAttributes($data, $context){
    // only apply filter on post-context
    if ($context === 'post'){

        // list of all available enlighterjs attributes
        $allowedAttributes = array(
            'data-enlighter-language' => true,
            'data-enlighter-theme' => true,
            'data-enlighter-group' => true,
            'data-enlighter-title' => true,
            'data-enlighter-linenumbers' => true,
            'data-enlighter-highlight' => true,
            'data-enlighter-lineoffset' => true
        );

        // apply to pre and code tags
        $data['pre'] = array_merge($data['pre'], $allowedAttributes);
        $data['code'] = array_merge($data['code'], $allowedAttributes);
    }

    return $data;
}

// add the filter function (2 arguments and priority 100)
add_filter('wp_kses_allowed_html', 'ksesAllowHtmlCodeAttributes', 100, 2);

 

 

 

Single File ReCaptcha 2 PHP Client

leading captcha system, curl, php, json-response

Today, a web-form without a proven captcha system generates a lot of spam entries and data-trash in your database. One of the best is ReCaptcha (even the latest v2).

Google provides an easy to use ReCaptcha PHP Client – but it’s a bit over engineered! You need a bunch of PHP files and a composer based environment to use it out of the box. This can cause some trouble in highly customized/optimized projects.

Therefore, here is a “one-file” solution which works without any configuration overhead:

Usage#

require('ReCaptcha.php');

// register your secret
ReCaptcha::setSecret('<your-secret>');

// some code ...

// check user form
if (ReCaptcha::isValid()){ ...

One-File Solution#

// Developer Guide: https://developers.google.com/recaptcha/docs/verify
class ReCaptcha{

    // ReCaptcha API Endpoint
    const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';

    // the last result
    private static $_result = null;

    // client secret
    private static $_secret = null;

    // validate
    public static function isValid(){
        // token available ?
        if (!isset($_POST['g-recaptcha-response'])){
            return false;
        }

        // extract token
        $token = trim($_POST['g-recaptcha-response']);

        // generate url
        $params = http_build_query(array(
            'secret' => self::$_secret,
            'response' => $token,
            'remoteIp' => $_SERVER['REMOTE_ADDR']
        ), '', '&');

        // create curl based post request
        $handle = curl_init(self::SITE_VERIFY_URL);
        $options = array(
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $params,
            CURLOPT_HTTPHEADER => array(
                'Content-Type: application/x-www-form-urlencoded'
            ),
            CURLINFO_HEADER_OUT => false,
            CURLOPT_HEADER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true
        );
        curl_setopt_array($handle, $options);
        $response = curl_exec($handle);
        curl_close($handle);

        // decode response
        self::$_result = json_decode($response, true);

        // check
        return (self::$_result['success'] === true);
    }

    // error occurred ?
    public static function isError(){
        return (self::$_result['success'] === false);
    }

    // get error message from last request
    public static function getErrorMessages(){
        return self::$_result['error-codes'];
    }

    // set client secret
    public static function setSecret($s){
        self::$_secret = $s;
    }
}

 

Maybe your wondering about some HTTP Headers sent to your browser which are not set in your script by the header() function ? Especially these Cache-Control headers:

Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

First of all: everything is ok! These headers are automatically set by the PHP Session module to prevent browser/proxy based caching of your pages. Depending on your environment setup, it’s possible to control these headers by using the session_cache_limiter() function or use the php.ini

To disable these behaviour just pass an empty string to the session_cache_limiter() function as mentioned in the documentation:

Solution#

// add this line to the beginning of your php script to disable the cache limiter funktion:
session_cache_limiter('');

HowTo: Upgrade Notification for WordPress Plugins

add version based notices to the wordpress plugin page; wp 4.2

Notify your users!# Sometimes, your WordPress plugin will have a major release which may causes groundbreaking API changes. In the past, WordPress displays the “Upgrade Notice” section from readme.txt directly to the users but it seems currently broken – i’ve got no notification for any plugin in the last few month. Therefore i’ve carried out […]

PHP-FPM “Access Denied” on .phtml Files

a problem which took about 1h of research..

Some Weeks ago, i switched most of the webserver setups from custom spawn-fcgi init scritps to php-fpm and everything seems to work fine until today. The php-version of GitHubButtons won’t work anymore – just a text-message appears: “Access Denied.” First of all i thougt it was a problem with lighttpd and the fastcgi.map-extensions directive, but the error message doesn’t seem to be served by lighttpd…and well…it was a php-fpm related issue, beacause php-fpm only processes .php files by default!

You will not find these directive in the official FPM Documentation on php.net – it’s missing including tons of other directives. To get an overview about all possible php-fpm config keys, you should take a look into to default php-fpm.conf file included into the php-sources (sapi/fpm/php-fpm.conf) – also attached to this post!

Important: This directive can’t be used in global context, it’s a pool based config key!

Examle Pool: php-fpm.conf#

[pool-testwww]
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
; Recommended: .php .phtml
security.limit_extensions = .php .php3 .php4 .php5 .phtml

Add Links to WordPress Plugin Page (Metadata Row)

Additional News/Update or Settings Links

Sometime you want to add special links directly to the plugin-page. For example, you can enable an easy access to the plugin’s settings-page or to related docs/resources. All the magic is done within the plugin_row_meta hook.

It’s important to check which plugin is currently processed (there are no plugin/namespace specific hooks). Therefore you have to check which plugin-(main)file is selected: if ($file == 'enlighter/Enlighter.php'){

Additional links can be added to the $links array including I18n support.

Appearance#

additional_plugin_links

How it’s done#

// add links
add_filter('plugin_row_meta', 'addPluginPageLinks', 10, 2);

// links on the plugin page
function addPluginPageLinks($links, $file){
  // current plugin ?
  if ($file == 'enlighter/Enlighter.php'){
    $links[] = '<a href="'.admin_url('options-general.php?page='.plugin_basename(__FILE__)).'">'.__('Settings', 'enlighter').'</a>';
    $links[] = '<a href="https://twitter.com/andidittrich">'.__('News & Updates', 'enlighter').'</a>';
  }
  
  return $links;
}

Cryptex 4.0 with Retina/HighDPI support

behind the scene – high-resolution, css based images using media queries

The Story# No scrapers. No harvesters. No spambots. That’s our goal. A several years ago, i’ve released the first version of the Crypex Plugin to protect E-Mail-Addresses on WordPress based websites. It works great but currently many mobile devices like tablets, smartphones are using high-dpi displays which results in blurred E-Mail-Addresses. Therefore i’ve create a […]

Howto: Content Navigation/Menu with WordPress

automatically generated navigation including anchors

State of the Art# There are a several ways how to deal with large content. The most common used method is to split the content in multiple pages, but this completely destroys the usability on tablets, big screens or other mobile devices and frustate your users. Furthermore, each sub-page causes an additional http-request and extra […]

Enlighter WordPress Plugin v2.0 release

EnlighterJS Advanced Javascript based post syntax highlighting

Version 2.0 Released# I am proudly to announce the immediate availbility of Enlighter v2.0. Including EnlighterJS 2.1 with support for Inline-Syntax-Highlighting!  About the Plugin# Enlighter is a free, easy-to-use, syntax highlighting tool for WordPress. It’s build in PHP and uses the MooTools(Javascript) based EnlighterJS to provide a beautiful code-appearance. Using it can be as simple […]