--- author: email: mail@petermolnar.net image: https://petermolnar.net/favicon.jpg name: Peter Molnar url: https://petermolnar.net copies: - http://web.archive.org/web/20160709135310/https://petermolnar.eu/press-this-indieweb/ lang: en published: '2016-03-10T16:00:51+00:00' summary: I've been able to reply/like/repost with my site for a long while, but is wasn't elegant, fast or slick at all; it was time to fix it. tags: - WordPress title: Extending Press This in WordPress to support indieweb reply, like and repost --- The lazier I want to get the more code I need to write, but in the end, it always worth it. Pfefferle had pre-baked a solution already[^1] but my theme is special[^2] so I had to come up with my own solution. ## Me vs sanity For a long while I manually copy pasted URLs into an additional post meta field in my WordPress, which I called `webmention_url`. When I wanted to reply I: 1. copied the URL 2. went over to my admin area 3. opened a new post 4. copied the URL to a post meta field 5. selected webmention type from the radiobutton 6. posted the reply This was tedious, smelled redundant, and introduced a lot more work. To show the URLs I had to add it in my theme - it wasn't part of `the_content`, so it wasn't showing in the RSS or in the mail I'm sending out to some. I had to hook that post meta field into the webmention plugin and do other magic on it, which made all the things even more complicated. ## Bookmarklets To solve the copy-paste part I turned to Press This[^3] - which I used a long, long time ago, and when I wanted to use it now, it didn't work. Because Firefox[^4]. To solve it: about:config security.csp.enable => false This sacrificed some security, which I'm not proud of, but I'm tired of all the fresh things that break things we're actively using. After this I realized, I need to pass the type of the webmention - so for reply, I added `&type=reply` at the end of the bookmarklet url, which now looks like this: ``` {.javascript} javascript: (function(a, b, c, d) { function e(a, c) { if ("undefined" != typeof c) { var d = b.createElement("input"); d.name = a, d.value = c, d.type = "hidden", p.appendChild(d) } } var f, g, h, i, j, k, l, m, n, o = a.encodeURIComponent, p = b.createElement("form"), q = b.getElementsByTagName("head")[0], r = "_press_this_app", s = !0; if (d) { if (!c.match(/^https?:/)) return void(top.location.href = d); if (d += "&u=" + o(c), c.match(/^https:/) && d.match(/^http:/) && (s = !1), a.getSelection ? h = a.getSelection() + "" : b.getSelection ? h = b.getSelection() + "" : b.selection && (h = b.selection.createRange().text || ""), d += "&buster=" + (new Date).getTime(), s || (b.title && (d += "&t=" + o(b.title.substr(0, 256))), h && (d += "&s=" + o(h.substr(0, 512)))), f = a.outerWidth || b.documentElement.clientWidth || 600, g = a.outerHeight || b.documentElement.clientHeight || 700, f = 800 > f || f > 5e3 ? 600 : .7 * f, g = 800 > g || g > 3e3 ? 700 : .9 * g, !s) return void a.open(d, r, "location,resizable,scrollbars,width=" + f + ",height=" + g); i = q.getElementsByTagName("meta") || []; for (var t = 0; t < i.length && !(t > 200); t++) { var u = i[t], v = u.getAttribute("name"), w = u.getAttribute("property"), x = u.getAttribute("content"); x && (v ? e("_meta[" + v + "]", x) : w && e("_meta[" + w + "]", x)) } j = q.getElementsByTagName("link") || []; for (var y = 0; y < j.length && !(y >= 50); y++) { var z = j[y], A = z.getAttribute("rel"); ("canonical" === A || "icon" === A || "shortlink" === A) && e("_links[" + A + "]", z.getAttribute("href")) } b.body.getElementsByClassName && (k = b.body.getElementsByClassName("hfeed")[0]), k = b.getElementById("content") || k || b.body, l = k.getElementsByTagName("img") || []; for (var B = 0; B < l.length && !(B >= 100); B++) n = l[B], n.src.indexOf("avatar") > -1 || n.className.indexOf("avatar") > -1 || n.width && n.width < 256 || n.height && n.height < 128 || e("_images[]", n.src); m = b.body.getElementsByTagName("iframe") || []; for (var C = 0; C < m.length && !(C >= 50); C++) e("_embeds[]", m[C].src); b.title && e("t", b.title), h && e("s", h), p.setAttribute("method", "POST"), p.setAttribute("action", d), p.setAttribute("target", r), p.setAttribute("style", "display: none;"), a.open("about:blank", r, "location,resizable,scrollbars,width=" + f + ",height=" + g), b.body.appendChild(p), p.submit() } })(window, document, top.location.href, "https:\/\/example.com\/wp-admin\/press-this.php?v=8&type=reply"); ``` The harder part was to catch this in PHP. The initial approach of filling the post meta required the post to be saved first, which made me trying to introduce 3 additional filters to Press This[^5] - 2 of them turned out to be redundant. I used this setup for a little while, but it wasn't as good as I wanted it to. Then I came across how voxpelli[^6] is doing replies on Android: <https://www.youtube.com/watch?v=CBPmSpD2jN4> And it hit me: this is how I should be doing it! Inline, nicely integrated with my Markdown format and since I already extend the webmention/pingback URL list with the matches from the content, I wouldn't need to deal with that at all. No more special post meta which can't be accessed with the Android WordPress app either. ## The technical gore I have to give credit to <http://regex101.com/> again for helping creating that monster regex. ``` {.php} <?php add_filter( 'press_this_data', 'cleanup_press_this_data', 9, 1 ); add_filter( 'press_this_suggested_html', 'cleanup_press_this_suggested', 2, 2 ); add_filter('enable_press_this_media_discovery', '__return_false' ); function cleanup_press_this_data ( $data ) { if ( isset( $data['s'] ) && ! empty( $data['s'] )) // do magic here; I try to make Markdown from HTML, for example $data['s'] = some_parser_function( $data['s'] ); return $data; } function cleanup_press_this_suggested ( $default_html, $data ) { $ref = array(); $relation = ''; parse_str ( parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY ), $ref ); if ( is_array( $ref ) && isset ( $ref['u'] ) && ! empty( $ref['u'] ) ) { $url = $ref['u']; $t = ''; if ( isset( $ref['type'] ) ) $t = $ref['type']; switch ( $t ) { case 'fav': case 'like': case 'u-like-of': $type = 'like: '; break; case 'repost': $type = 'from: '; break; case 'reply': $type = 're: '; break; default: $type = ''; break; } $relation = "---\n{$type}{$url}\n---\n\n"; } $default_html = array ( 'quote' => '> %1$s', 'link' => '', 'embed' => $relation, ); return $default_html; } ``` Presenting this is a little trickier; this is what I use: ``` {.php} <?php add_filter( 'the_content', 'convert_reaction', 1 ); function convert_reaction ( $content ) { $pattern = "/---[\n\r]+(?:(.*?):\s+)?+\b((?:http|https)\:\/\/?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.[a-zA-Z0-9\.\/\?\:@\-_=#&]*)(?:[\n\r]+((?!---).*))?[\n\r]+---/mi"; $matches = array(); preg_match_all( $pattern, $content, $matches); if ( empty( $matches ) || ! isset( $matches[0] ) || empty( $matches[0] ) ) return $content; $replace = false; $r = false; $type = false; $rsvp = ''; $rsvps = array ( 'no' => __("Sorry, can't make it."), 'yes' => __("I'll be there."), 'maybe' => __("I'll do my best, but don't count on me for sure."), ); $replace = $matches[0][0]; $type = trim($matches[1][0]); $url = trim($matches[2][0]); $data = trim($matches[3][0]); if ( $type == 're' && !empty( $data ) ) $rsvp = '<data class="p-rsvp" value="' . $rsvp .'">'. $rsvps[ $rsvp ] .'</data>'; switch ($type) { case 'like': case 'fav': $cl = 'u-like-of'; $prefix = ''; break; case 'from': case 'repost': $cl = 'u-repost-of'; $prefix = '*reposted from:* '; break; case 're': $cl = 'u-in-reply-to'; $prefix = '**RE:** '; break; default: $cl = 'u-url'; $prefix = '**URL:** '; break; } $title = str_replace ( parse_url( $url, PHP_URL_SCHEME) .'://', '', $url); $r = "\n{$prefix}[{$title}]({$url}){.{$cl}}\n{$rsvp}"; return str_replace ( $replace, $r, $content ); } ``` ## Adding Android Thanks to the long and detailed description of Chris Aldrich[^7] I added this functionality to Android as well, with URL Forward[^8] as: `https://example.com/wp-admin/press-this.php?v=8&type=reply&u=@url` ## Footnotes Automating thins is good for you. [^1]: <https://github.com/pfefferle/wordpress-indieweb-press-this> [^2]: <http://satwcomic.com/coat-of-arms> [^3]: <http://codex.wordpress.org/Press_This> [^4]: <https://github.com/blog/1477-content-security-policy#bookmarklets> [^5]: <https://core.trac.wordpress.org/ticket/34455> [^6]: <http://voxpelli.com/> [^7]: <http://stream.boffosocko.com/2016/sharing-from-the-indieweb-on-mobile-android-with-apps-and> [^8]: <https://play.google.com/store/apps/details?id=net.daverix.urlforward>