├── img └── mm.jpg ├── languages ├── semantic-linkbacks-de_DE.mo ├── semantic-linkbacks-de_DE.po └── semantic-linkbacks.pot ├── .codeclimate.yml ├── tests ├── templates │ ├── basic-like.html │ ├── basic-like.json │ ├── basic-multi.json │ ├── basic-reply.json │ ├── brid-gy.json │ ├── person-tag.json │ ├── adactio-com.json │ ├── basic-reply-2.json │ ├── sandeep-io.json │ ├── tantek-com.json │ ├── voxpelli-com.json │ ├── aaronparecki-com.json │ ├── brid-gy-emoji.json │ ├── brid-gy-invite.json │ ├── notiz-blog.json │ ├── notizblog-org.json │ ├── checkmention-xss.json │ ├── person-tag.html │ ├── basic-with-comments.json │ ├── checkmention-hcardxss.json │ ├── basic-with-comments.html │ ├── fed-brid-gy.json │ ├── basic-reply.html │ ├── basic-multi.html │ ├── checkmention-hcardxss.html │ ├── fed-brid-gy.html │ ├── brid-gy-emoji.html │ ├── brid-gy.html │ ├── basic-reply-2.html │ ├── brid-gy-invite.html │ ├── checkmention-xss.html │ ├── tantek-com.html │ ├── voxpelli-com.html │ ├── aaronparecki-com.html │ └── sandeep-io.html ├── test-microformats.php ├── bootstrap.php └── test-rendering.php ├── .editorconfig ├── .distignore ├── phpunit.xml.dist ├── docker-compose.yml ├── Gruntfile.js ├── package.json ├── phpcs.xml ├── vendor ├── p3k │ └── emoji-detector │ │ ├── LICENSE │ │ └── src │ │ └── Emoji.php └── mf2 │ └── mf2 │ └── LICENSE.md ├── templates ├── linkbacks-edit-comment-form.php └── linkbacks.php ├── LICENSE.md ├── css └── semantic-linkbacks.css ├── composer.json ├── js └── semantic-linkbacks.js ├── bin └── install-wp-tests.sh ├── includes ├── class-linkbacks-walker-comment.php ├── class-linkbacks-avatar-handler.php ├── functions.php ├── class-linkbacks-notifications.php └── class-linkbacks-mf2-handler.php ├── semantic-linkbacks.php ├── readme.txt └── README.md /img/mm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfefferle/wordpress-semantic-linkbacks/HEAD/img/mm.jpg -------------------------------------------------------------------------------- /languages/semantic-linkbacks-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfefferle/wordpress-semantic-linkbacks/HEAD/languages/semantic-linkbacks-de_DE.mo -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | phpcodesniffer: 3 | enabled: true 4 | config: 5 | ignore_warnings: true 6 | file_extensions: "php" 7 | standard: "phpcs.xml" 8 | -------------------------------------------------------------------------------- /tests/templates/basic-like.html: -------------------------------------------------------------------------------- 1 | 2 | A Cool Post 3 | 4 | -------------------------------------------------------------------------------- /tests/templates/basic-like.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "basic-like", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "like" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/basic-multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "basic-multi", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/basic-reply.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "basic-reply", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/brid-gy.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Markus Heurung", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "repost" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/person-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "person-tag", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "tag" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/adactio-com.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Jeremy Keith", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "mention" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/basic-reply-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "basic-reply", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/sandeep-io.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Sandeep Shetty", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "like" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/tantek-com.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Tantek Çelik", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "rsvp:yes" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/voxpelli-com.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Pelle Wessman", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/aaronparecki-com.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Aaron Parecki", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/brid-gy-emoji.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Matthias Pfefferle", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/brid-gy-invite.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Angelo Gladding", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "invite" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/notiz-blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Matthias Pfefferle", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "mention" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/notizblog-org.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Matthias Pfefferle", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/checkmention-xss.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Checkmention XSS test", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/person-tag.html: -------------------------------------------------------------------------------- 1 | 2 | Homepage 3 | Test Person 4 | 5 | -------------------------------------------------------------------------------- /tests/templates/basic-with-comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "basic-with-comments", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/checkmention-hcardxss.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Does clicking me alert?", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "reply" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/templates/basic-with-comments.html: -------------------------------------------------------------------------------- 1 |
2 | @post: This one even has a response! 3 | A comment 4 |
5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.php] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | [*.{js,json}] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | /.wordpress-org 2 | /.git 3 | /.github 4 | /node_modules 5 | /bin 6 | /sass 7 | /tests 8 | /config 9 | readme.md 10 | package.json 11 | composer.json 12 | composer.lock 13 | Gruntfile.js 14 | push.sh 15 | phpunit.xml 16 | phpunit.xml.dist 17 | phpcs.xml 18 | README.md 19 | readme.md 20 | .travis.yml 21 | .distignore 22 | .gitignore 23 | docker-compose.yml 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/templates/fed-brid-gy.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment_author": "Ryan Barrett", 3 | "comment_author_url": "http://example.com/webmention/target/placeholder", 4 | "comment_meta": { 5 | "semantic_linkbacks_type": "like", 6 | "semantic_linkbacks_author_url": "https://mastodon.technology/@snarfed", 7 | "avatar": "https://static.mastodon.technology/accounts/avatars/000/023/507/original/c183cf1e3a60f5c3.jpg" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/templates/basic-reply.html: -------------------------------------------------------------------------------- 1 |
2 | @post: That's a great idea! 3 |
4 | 5 |
6 | @post: That's a great idea! 7 |
8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: mysql:5.7 5 | restart: always 6 | environment: 7 | MYSQL_ROOT_PASSWORD: wordpress 8 | MYSQL_DATABASE: wordpress 9 | MYSQL_USER: wordpress 10 | MYSQL_PASSWORD: wordpress 11 | 12 | wordpress: 13 | depends_on: 14 | - db 15 | image: wordpress:latest 16 | links: 17 | - db 18 | ports: 19 | - "8088:80" 20 | volumes: 21 | - .:/var/www/html/wp-content/plugins/semantic-linkbacks 22 | restart: always 23 | environment: 24 | WORDPRESS_DB_HOST: db:3306 25 | WORDPRESS_DB_PASSWORD: wordpress 26 | WORDPRESS_DEBUG: 1 27 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | // Project configuration. 3 | grunt.initConfig({ 4 | wp_readme_to_markdown: { 5 | target: { 6 | files: { 7 | 'README.md': 'readme.txt' 8 | }, 9 | }, 10 | }, 11 | makepot: { 12 | target: { 13 | options: { 14 | mainFile: 'semantic-linkbacks.php', 15 | potFilename: 'languages/semantic-linkbacks.pot', 16 | type: 'wp-plugin', 17 | updateTimestamp: true 18 | } 19 | } 20 | } 21 | }); 22 | 23 | grunt.loadNpmTasks('grunt-wp-readme-to-markdown'); 24 | grunt.loadNpmTasks('grunt-wp-i18n'); 25 | 26 | // Default task(s). 27 | grunt.registerTask('default', ['wp_readme_to_markdown']); 28 | }; 29 | -------------------------------------------------------------------------------- /tests/templates/basic-multi.html: -------------------------------------------------------------------------------- 1 |
2 | Awesome links: 3 | First and 4 | Second 5 |
6 | 7 |
8 | Awesome links: 9 | First and 10 | Second 11 |
12 | 13 |
14 | Awesome links: 15 | First and 16 | Second 17 |
18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-semantic-linkbacks", 3 | "description": "Semantic Trackbacks/Pingbacks/WebMentions for WordPress!", 4 | "devDependencies": { 5 | "npm": "^8.0.0", 6 | "grunt": "^1.0.4", 7 | "grunt-wp-deploy": "^2.0.0", 8 | "grunt-wp-i18n": "^1.0.3", 9 | "grunt-wp-readme-to-markdown": "^2.0.1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/pfefferle/wordpress-semantic-linkbacks.git" 14 | }, 15 | "keywords": [ 16 | "wordpress", 17 | "webmention", 18 | "pingback", 19 | "trackback", 20 | "linkback" 21 | ], 22 | "author": "Matthias Pfefferle", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/pfefferle/wordpress-semantic-linkbacks/issues" 26 | }, 27 | "homepage": "https://github.com/pfefferle/wordpress-semantic-linkbacks" 28 | } 29 | -------------------------------------------------------------------------------- /tests/test-microformats.php: -------------------------------------------------------------------------------- 1 | file_get_contents( $path ), 10 | 'comment_author_url' => 'http://example.com/webmention/target/placeholder', 11 | 'target' => 'http://example.com/webmention/target/placeholder', 12 | 'comment_type' => 'webmention', 13 | 'comment_author' => basename( $path, '.html' ), 14 | ) 15 | ); 16 | 17 | $subset = json_decode( file_get_contents( substr( $path, 0, -4 ) . 'json' ), true ); 18 | $this->assertArraySubset( $subset, $comment ); 19 | } 20 | 21 | public function template_provider() { 22 | return array_map( 23 | function( $path ) { 24 | return array( $path ); }, 25 | glob( dirname( __FILE__ ) . '/templates/*.html' ) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/templates/checkmention-hcardxss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XSS in hcard test 6 | 7 | 8 | 9 |
10 |

This test mentions 11 | 13 | this page 14 | and is a 15 | 16 | 17 | Does clicking me alert? 18 |

19 | 20 |

21 | This test embeds XSS within the hcard name and time field. Clicking on 22 | the name or title should not raise an alert.

23 | 24 |

This note was created on 25 | 29 |

30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Semantic Linkbacks Standards 4 | 5 | ./semantic-linkbacks.php 6 | ./includes/ 7 | */includes/*\.(inc|css|js|svg) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /vendor/p3k/emoji-detector/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | ----------- 3 | 4 | Copyright 2017 by Aaron Parecki 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/templates/fed-brid-gy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | https://mastodon.technology/users/snarfed#likes/53998 7 | 8 | 9 | 10 | 11 | 12 | Ryan Barrett 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | likes this. 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/linkbacks-edit-comment-form.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 | comment_type, array( 'trackback', 'pingback', 'webmention' ), true ) ) { ?> 14 | 15 | 18 |
19 | 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2017` `` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /css/semantic-linkbacks.css: -------------------------------------------------------------------------------- 1 | .mention-list ul { 2 | border-style: solid; 3 | } 4 | 5 | .mention-list li { 6 | display: inline-block; 7 | list-style: none; 8 | vertical-align: top; 9 | margin-left: 3px; 10 | position: relative; 11 | } 12 | 13 | .mention-list .hide-name { 14 | display: none; 15 | } 16 | 17 | .mention-list.initialized .additional-facepile, 18 | .mention-list .additional-facepile-button-list-item { 19 | display: none; 20 | } 21 | 22 | .mention-list.initialized .additional-facepile-button-list-item:not(.is-hidden) { 23 | display: inline-block; 24 | text-align: center; 25 | height: 64px; 26 | width: 64px; 27 | line-height: 1.5; 28 | font-size: 32px; 29 | cursor: pointer; 30 | } 31 | 32 | .mention-list.initialized .additional-facepile-button-list-item.is-hidden ~ .additional-facepile { 33 | display: inline-block; 34 | } 35 | 36 | .mention-list .additional-facepile-button-list-item button { 37 | border: none; 38 | background-color: transparent; 39 | padding: 0; 40 | } 41 | 42 | .mention-list li img { 43 | height: 64px; 44 | width: 64px; 45 | display: block; 46 | object-fit: cover; 47 | } 48 | 49 | li.emoji-reaction { 50 | position: relative; 51 | } 52 | 53 | .emoji-overlay { 54 | position: absolute; 55 | bottom: 0; 56 | right: 0; 57 | } 58 | 59 | .reactions { 60 | clear: both; 61 | } 62 | -------------------------------------------------------------------------------- /tests/templates/brid-gy-emoji.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 😢 6 | 22 | 23 |
24 | tag:facebook.com,2013:10102667863137683_sad_by_10153762523688552 25 | 26 | 27 | 28 | 29 | Matthias Pfefferle 30 | 31 | 32 | 33 | https://www.facebook.com/212038/posts/10102667863137683#sad-by-10153762523688552 34 |
35 | 36 | 😢 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 |
10 | tag:twitter.com,2013:423756080376995840 11 | 12 | 13 | 14 |
15 | 16 | 17 | tag:twitter.com,2013:muhh 18 |
19 | 20 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pfefferle/wordpress-semantic-linkbacks", 3 | "description": "Semantic Linkbacks for WebMentions, Trackbacks and Pingbacks", 4 | "type": "wordpress-plugin", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Matthias Pfefferle", 9 | "email": "pfefferle@gmail.com" 10 | } 11 | ], 12 | "extra": { 13 | "installer-name": "semantic-linkbacks" 14 | }, 15 | "require": { 16 | "php": ">=5.6.0", 17 | "composer/installers": "~1.0", 18 | "mf2/mf2": "^0.4.6", 19 | "p3k/emoji-detector": "*" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^5.7.21 || ^6.5 || ^7.5 || ^8.0", 23 | "phpcompatibility/php-compatibility": "*", 24 | "phpcompatibility/phpcompatibility-wp": "*", 25 | "squizlabs/php_codesniffer": "3.*", 26 | "wp-coding-standards/wpcs": "*", 27 | "yoast/phpunit-polyfills": "^1.0", 28 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1" 29 | }, 30 | "scripts": { 31 | "test": [ 32 | "composer install", 33 | "bin/install-wp-tests.sh webmention-test root webmention-test test-db latest true", 34 | "vendor/bin/phpunit" 35 | ], 36 | "test-local": [ 37 | "composer update", 38 | "bin/install-wp-tests.sh wordpress wordpress wordpress 127.0.0.1 4.9.8 true", 39 | "vendor/bin/phpunit" 40 | ] 41 | }, 42 | "config": { 43 | "allow-plugins": { 44 | "dealerdirect/phpcodesniffer-composer-installer": true, 45 | "composer/installers": true 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/templates/basic-reply-2.html: -------------------------------------------------------------------------------- 1 |
2 | tag:twitter.com,2013:placeholder 3 | 4 | 5 | 6 | 7 | 8 | 9 | basic-reply 10 | 11 | basic-reply 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | Ryan Barrett 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
-------------------------------------------------------------------------------- /tests/templates/brid-gy-invite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bridgy Response 6 | 22 | 23 |
24 | tag:facebook.com,2013:169650146961455_rsvp_318643335139472 25 | 26 | 27 | 28 | 29 | 30 | 31 | Chris Aldrich 32 | 33 | 34 | 35 | 36 | invited 37 |
38 | 39 | Angelo Gladding 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/templates/checkmention-xss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | XSS test 5 | 6 | 7 | 8 |
9 |

This test mentions 10 | 12 | this page 13 | and is a 14 | 15 | XSS test 16 | Checkmention XSS test 17 |

18 |

19 | Clicking this 20 | should not cause an alert. 21 | This div 22 | should not alert. 23 | Try clicking this link 24 | <script>alert("encoded-xss")</script> 25 | and this too. 26 | Mouse over this 27 | should not cause an alert. This broken 28 | should not throw an alert. 29 | < 30 | 31 | Neither should .
32 | Please look at the Owasp XSS prevention cheat sheet for more information. 33 |

34 | 35 |

This note was created on 36 | 40 |

41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /js/semantic-linkbacks.js: -------------------------------------------------------------------------------- 1 | document.addEventListener( 'DOMContentLoaded', function() { 2 | var showAdditionalFacepileButtons = document.querySelectorAll( '.show-additional-facepiles' ), 3 | mentionLists = document.querySelectorAll( '.mention-list' ); 4 | 5 | if ( showAdditionalFacepileButtons.length === 0 || mentionLists.length === 0 ) { 6 | return; 7 | } 8 | 9 | // Add `initialized` class to mention-list container. When JS is disabled or not working, we want to show all items. 10 | for ( var i = 0; i < mentionLists.length; i++ ) { 11 | var mentionList = mentionLists[i]; 12 | mentionList.classList.add( 'initialized' ); 13 | } 14 | 15 | // Loop the buttons to show additional facepiles. 16 | for ( var i = 0; i < showAdditionalFacepileButtons.length; i++ ) { 17 | var showAdditionalFacepileButton = showAdditionalFacepileButtons[i], 18 | hideAdditionalFacepileButton = showAdditionalFacepileButton.parentNode.parentNode.querySelector( '.hide-additional-facepiles' ); 19 | 20 | showAdditionalFacepileButton.addEventListener( 'click', function() { 21 | toggleListItemClasses( this ); 22 | } ); 23 | 24 | hideAdditionalFacepileButton.addEventListener( 'click', function() { 25 | toggleListItemClasses( this ); 26 | } ); 27 | } 28 | 29 | /** 30 | * Toggle the classes of the list items that contain the buttons. 31 | * 32 | * @param {HTMLElement} clickedButton 33 | */ 34 | var toggleListItemClasses = function( clickedButton ) { 35 | var buttonListItem = clickedButton.parentNode, 36 | otherButtonListItem = buttonListItem.classList.contains( 'is-hidden' ) 37 | ? buttonListItem.parentNode.querySelector( '.additional-facepile-button-list-item:not(.is-hidden)' ) 38 | : buttonListItem.parentNode.querySelector( '.additional-facepile-button-list-item.is-hidden' ); 39 | 40 | buttonListItem.classList.toggle( 'is-hidden' ); 41 | otherButtonListItem.classList.toggle( 'is-hidden' ); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /vendor/p3k/emoji-detector/src/Emoji.php: -------------------------------------------------------------------------------- 1 | 'skin-tone-2', 39 | '1F3FC' => 'skin-tone-3', 40 | '1F3FD' => 'skin-tone-4', 41 | '1F3FE' => 'skin-tone-5', 42 | '1F3FF' => 'skin-tone-6', 43 | ); 44 | foreach($points as $pt) { 45 | if(array_key_exists($pt, $skin_tones)) 46 | $skin_tone = $skin_tones[$pt]; 47 | } 48 | 49 | $data[] = array( 50 | 'emoji' => $ch, 51 | 'short_name' => $short_name, 52 | 'num_points' => mb_strlen($ch), 53 | 'points_hex' => $points, 54 | 'hex_str' => $hexstr, 55 | 'skin_tone' => $skin_tone, 56 | ); 57 | } 58 | } 59 | 60 | if($prevencoding) 61 | mb_internal_encoding($prevencoding); 62 | 63 | return $data; 64 | } 65 | 66 | function is_single_emoji($string) { 67 | $prevencoding = mb_internal_encoding(); 68 | mb_internal_encoding('UTF-8'); 69 | 70 | // If the string is longer than the longest emoji, it's not a single emoji 71 | if(mb_strlen($string) >= LONGEST_EMOJI) return false; 72 | 73 | $all_emoji = detect_emoji($string); 74 | 75 | $emoji = false; 76 | 77 | // If there are more than one or none, return false immediately 78 | if(count($all_emoji) == 1) { 79 | $emoji = $all_emoji[0]; 80 | 81 | // Check if there are any other characters in the string 82 | 83 | // Remove the emoji found 84 | $string = str_replace($emoji['emoji'], '', $string); 85 | 86 | // If there are any characters left, then the string is not a single emoji 87 | if(strlen($string) > 0) 88 | $emoji = false; 89 | } 90 | 91 | if($prevencoding) 92 | mb_internal_encoding($prevencoding); 93 | 94 | return $emoji; 95 | } 96 | 97 | function _load_map() { 98 | return json_decode(file_get_contents(dirname(__FILE__).'/map.json'), true); 99 | } 100 | 101 | function _load_regexp() { 102 | return '/(?:' . json_decode(file_get_contents(dirname(__FILE__).'/regexp.json')) . ')/u'; 103 | } 104 | 105 | function uniord($c) { 106 | $ord0 = ord($c[0]); if ($ord0>=0 && $ord0<=127) return $ord0; 107 | $ord1 = ord($c[1]); if ($ord0>=192 && $ord0<=223) return ($ord0-192)*64 + ($ord1-128); 108 | $ord2 = ord($c[2]); if ($ord0>=224 && $ord0<=239) return ($ord0-224)*4096 + ($ord1-128)*64 + ($ord2-128); 109 | $ord3 = ord($c[3]); if ($ord0>=240 && $ord0<=247) return ($ord0-240)*262144 + ($ord1-128)*4096 + ($ord2-128)*64 + ($ord3-128); 110 | return false; 111 | } 112 | -------------------------------------------------------------------------------- /tests/test-rendering.php: -------------------------------------------------------------------------------- 1 | 'Some Post' ) ); 24 | for ( $i = 0; $i < $num; $i++ ) { 25 | $id = wp_new_comment( 26 | array( 27 | 'comment_author_url' => 'http://example.com/person' . $i, 28 | 'comment_author' => 'Person ' . $i, 29 | 'comment_type' => 'webmention', 30 | 'comment_post_ID' => $post_id, 31 | ) 32 | ); 33 | add_comment_meta( $id, 'semantic_linkbacks_type', $semantic_linkbacks_type ); 34 | add_comment_meta( $id, 'avatar', 'http://example.com/photo' ); 35 | $comments[] = get_comment( $id ); 36 | } 37 | return $comments; 38 | } 39 | 40 | public function test_facepile_markup() { 41 | $comments = $this->make_comments( 1 ); 42 | $this->assertStringMatchesFormat( 43 | '', 44 | list_linkbacks( array( 'echo' => false ), $comments ) 45 | ); 46 | } 47 | 48 | public function test_facepile_fold() { 49 | $comments = $this->make_comments( 3 ); 50 | $html = list_linkbacks( array( 'echo' => false ), $comments ); 51 | $person_0 = strpos( $html, '' ); 55 | $this->assertGreaterThan( 0, $person_0 ); 56 | $this->assertGreaterThan( $person_0, $person_1 ); 57 | $this->assertGreaterThan( $person_1, $person_2 ); 58 | $this->assertGreaterThan( $person_2, $ellipsis ); 59 | } 60 | 61 | public function test_facepile_no_fold() { 62 | $comments = $this->make_comments( 4 ); 63 | update_option( 'semantic_linkbacks_facepiles_fold_limit', 0 ); 64 | $html = list_linkbacks( array( 'echo' => false ), $comments ); 65 | $this->assertContains( '', $html ) ); 70 | } 71 | 72 | public function test_reactions() { 73 | $id = wp_new_comment( 74 | array( 75 | 'comment_author_url' => 'http://example.com/person', 76 | 'comment_author' => 'Person', 77 | 'comment_type' => 'comment', 78 | 'comment_content' => '😢', // 'crying face' emoji 79 | ) 80 | ); 81 | add_comment_meta( $id, 'avatar', 'http://example.com/photo' ); 82 | Semantic_Linkbacks_Walker_Comment::$reactions = array( get_comment( $id ) ); 83 | add_option( 'semantic_linkbacks_facepiles', array( 'reacji' ) ); 84 | 85 | ob_start(); 86 | load_template( dirname( __FILE__ ) . '/../templates/linkbacks.php', false ); 87 | $html = ob_get_contents(); 88 | ob_end_clean(); 89 | 90 | $this->assertStringMatchesFormat( 91 | '
92 |

Reacjis

93 |
', 94 | trim( $html ) 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /languages/semantic-linkbacks-de_DE.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 pfefferle & acegiak 2 | # This file is distributed under the same license as the Semantic-Linkbacks package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Semantic-Linkbacks 3.0.5\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/semantic-linkbacks\n" 7 | "POT-Creation-Date: 2015-09-14 22:30+0200\n" 8 | "PO-Revision-Date: 2016-11-08 20:04+0100\n" 9 | "Last-Translator: Matthias Pfefferle \n" 10 | "Language-Team: \n" 11 | "Language: de_DE\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "X-Generator: Poedit 1.8.8\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 17 | 18 | #: semantic-linkbacks.php:180 19 | msgid "%1$s mentioned %2$s on %4$s." 20 | msgstr "%1$s hat %2$s auf %4$s erwähnt." 21 | 22 | #: semantic-linkbacks.php:182 23 | msgid "%1$s replied to %2$s on %4$s." 24 | msgstr "%1$s hat %2$s auf %4$s geantwortet." 25 | 26 | #: semantic-linkbacks.php:183 27 | msgid "%1$s reposted %2$s on %4$s." 28 | msgstr "%1$s hat %2$s auf %4$s geteilt." 29 | 30 | #: semantic-linkbacks.php:184 31 | msgid "%1$s liked %2$s on %4$s." 32 | msgstr "%1$s hat %2$s auf %4$s geliked." 33 | 34 | #: semantic-linkbacks.php:185 35 | msgid "%1$s favorited %2$s on %4$s." 36 | msgstr "%1$s hat %2$s auf %4$s favorisiert." 37 | 38 | #: semantic-linkbacks.php:186 39 | msgid "%1$s tagged %2$s on %4$s." 40 | msgstr "%1$s hat %2$s auf %4$s markiert." 41 | 42 | #: semantic-linkbacks.php:187 43 | msgid "%1$s bookmarked %2$s on %4$s." 44 | msgstr "%1$s hat %2$s auf %4$s als Lesezeichen gespeichert." 45 | 46 | #: semantic-linkbacks.php:188 47 | msgid "%1$s is attending." 48 | msgstr "%1$s nimmt teil." 49 | 50 | #: semantic-linkbacks.php:189 51 | msgid "%1$s is not attending." 52 | msgstr "%1$s nimmt nicht teil." 53 | 54 | #: semantic-linkbacks.php:190 55 | msgid "Maybe %1$s will be attending." 56 | msgstr "%1$s nimmt vielleicht teil." 57 | 58 | #: semantic-linkbacks.php:191 59 | msgid "%1$s is invited." 60 | msgstr "%1$s ist eingeladen." 61 | 62 | #: semantic-linkbacks.php:192 63 | msgid "%1$s tracks this event." 64 | msgstr "%1$s verfolgt diese Veranstaltung." 65 | 66 | #: semantic-linkbacks.php:206 67 | msgid "Mention" 68 | msgstr "Erwähnung" 69 | 70 | #: semantic-linkbacks.php:208 71 | msgid "Reply" 72 | msgstr "Antwort" 73 | 74 | #: semantic-linkbacks.php:209 75 | msgid "Repost" 76 | msgstr "Repost" 77 | 78 | #: semantic-linkbacks.php:210 79 | msgid "Like" 80 | msgstr "Like" 81 | 82 | #: semantic-linkbacks.php:211 83 | msgid "Favorite" 84 | msgstr "Favorite" 85 | 86 | #: semantic-linkbacks.php:212 87 | msgid "Tag" 88 | msgstr "Tag" 89 | 90 | #: semantic-linkbacks.php:213 91 | msgid "Bookmark" 92 | msgstr "Lesezeichen" 93 | 94 | #: semantic-linkbacks.php:214 semantic-linkbacks.php:215 95 | #: semantic-linkbacks.php:216 semantic-linkbacks.php:217 96 | #: semantic-linkbacks.php:218 97 | msgid "RSVP" 98 | msgstr "RSVP" 99 | 100 | #: semantic-linkbacks.php:232 101 | msgid "this Article" 102 | msgstr "diesen Artikel" 103 | 104 | #: semantic-linkbacks.php:234 105 | msgid "this Aside" 106 | msgstr "diese Kurzmitteilung" 107 | 108 | #: semantic-linkbacks.php:235 109 | msgid "this Chat" 110 | msgstr "dieses Chatprotokoll" 111 | 112 | #: semantic-linkbacks.php:236 113 | msgid "this Gallery" 114 | msgstr "diese Galerie" 115 | 116 | #: semantic-linkbacks.php:237 117 | msgid "this Link" 118 | msgstr "diesen Link" 119 | 120 | #: semantic-linkbacks.php:238 121 | msgid "this Image" 122 | msgstr "dieses Bild" 123 | 124 | #: semantic-linkbacks.php:239 125 | msgid "this Quote" 126 | msgstr "dieses Zitat" 127 | 128 | #: semantic-linkbacks.php:240 129 | msgid "this Status" 130 | msgstr "diese Statusmitteilung" 131 | 132 | #: semantic-linkbacks.php:241 133 | msgid "this Video" 134 | msgstr "dieses Video" 135 | 136 | #: semantic-linkbacks.php:242 137 | msgid "this Audio" 138 | msgstr "dieses Audio-Dokument" 139 | 140 | #. Plugin Name of the plugin/theme 141 | msgid "Semantic-Linkbacks" 142 | msgstr "Semantic-Linkbacks" 143 | 144 | #. Plugin URI of the plugin/theme 145 | msgid "https://github.com/pfefferle/wordpress-semantic-linkbacks" 146 | msgstr "https://github.com/pfefferle/wordpress-semantic-linkbacks" 147 | 148 | #. Description of the plugin/theme 149 | msgid "Semantic Linkbacks for WebMentions, Trackbacks and Pingbacks" 150 | msgstr "Semantic Linkbacks für WebMentions, Trackbacks and Pingbacks" 151 | 152 | #. Author of the plugin/theme 153 | msgid "pfefferle" 154 | msgstr "pfeffere" 155 | 156 | #. Author URI of the plugin/theme 157 | msgid "http://notizblog.org/" 158 | msgstr "http://notizblog.org/" 159 | 160 | #~ msgid "pfefferle & acegiak" 161 | #~ msgstr "pfefferle & acegiak" 162 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | fi 26 | } 27 | 28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 29 | WP_TESTS_TAG="branches/$WP_VERSION" 30 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 31 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 32 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 33 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 34 | else 35 | WP_TESTS_TAG="tags/$WP_VERSION" 36 | fi 37 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 38 | WP_TESTS_TAG="trunk" 39 | else 40 | # http serves a single offer, whereas https serves multiple. we only want one 41 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 42 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 43 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 44 | if [[ -z "$LATEST_VERSION" ]]; then 45 | echo "Latest WordPress version could not be found" 46 | exit 1 47 | fi 48 | WP_TESTS_TAG="tags/$LATEST_VERSION" 49 | fi 50 | 51 | set -ex 52 | 53 | install_wp() { 54 | 55 | if [ -d $WP_CORE_DIR ]; then 56 | return; 57 | fi 58 | 59 | mkdir -p $WP_CORE_DIR 60 | 61 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 62 | mkdir -p $TMPDIR/wordpress-nightly 63 | download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip 64 | unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ 65 | mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR 66 | else 67 | if [ $WP_VERSION == 'latest' ]; then 68 | local ARCHIVE_NAME='latest' 69 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 70 | # https serves multiple offers, whereas http serves single. 71 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 72 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 73 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 74 | LATEST_VERSION=${WP_VERSION%??} 75 | else 76 | # otherwise, scan the releases and get the most up to date minor version of the major release 77 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 78 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 79 | fi 80 | if [[ -z "$LATEST_VERSION" ]]; then 81 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 82 | else 83 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 84 | fi 85 | else 86 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 87 | fi 88 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 89 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 90 | fi 91 | 92 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 93 | } 94 | 95 | install_test_suite() { 96 | # portable in-place argument for both GNU sed and Mac OSX sed 97 | if [[ $(uname -s) == 'Darwin' ]]; then 98 | local ioption='-i .bak' 99 | else 100 | local ioption='-i' 101 | fi 102 | 103 | # set up testing suite if it doesn't yet exist 104 | if [ ! -d $WP_TESTS_DIR ]; then 105 | # set up testing suite 106 | mkdir -p $WP_TESTS_DIR 107 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 108 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 109 | fi 110 | 111 | if [ ! -f wp-tests-config.php ]; then 112 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 113 | # remove all forward slashes in the end 114 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 115 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 116 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 117 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 118 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 119 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 120 | fi 121 | 122 | } 123 | 124 | install_db() { 125 | 126 | if [ ${SKIP_DB_CREATE} = "true" ]; then 127 | return 0 128 | fi 129 | 130 | # parse DB_HOST for port or socket references 131 | local PARTS=(${DB_HOST//\:/ }) 132 | local DB_HOSTNAME=${PARTS[0]}; 133 | local DB_SOCK_OR_PORT=${PARTS[1]}; 134 | local EXTRA="" 135 | 136 | if ! [ -z $DB_HOSTNAME ] ; then 137 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 138 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 139 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 140 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 141 | elif ! [ -z $DB_HOSTNAME ] ; then 142 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 143 | fi 144 | fi 145 | 146 | # create database 147 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 148 | } 149 | 150 | install_wp 151 | install_test_suite 152 | install_db 153 | -------------------------------------------------------------------------------- /templates/linkbacks.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

12 | 'reacji', 16 | ), 17 | Semantic_Linkbacks_Walker_Comment::$reactions 18 | ); 19 | ?> 20 |
21 | 22 | 23 | 24 | 35 | 36 | 37 | 38 |
39 |

40 | 'favorite', 44 | ), 45 | get_linkbacks( 'favorite' ) 46 | ); 47 | ?> 48 |
49 | 50 | 51 | 52 |
53 |

54 | 'bookmark', 58 | ), 59 | get_linkbacks( 'bookmark' ) 60 | ); 61 | ?> 62 |
63 | 64 | 65 | 66 |
67 |

68 | 'repost', 72 | ), 73 | get_linkbacks( 'repost' ) 74 | ); 75 | ?> 76 |
77 | 78 | 79 | 80 |
81 |

82 | 'tag', 86 | ), 87 | get_linkbacks( 'tag' ) 88 | ); 89 | ?> 90 |
91 | 92 | 93 | 94 |
95 |

96 | 'listen', 100 | ), 101 | get_linkbacks( 'listen' ) 102 | ); 103 | ?> 104 |
105 | 106 | 107 | 108 |
109 |

110 | 'read', 114 | ), 115 | get_linkbacks( 'read' ) 116 | ); 117 | ?> 118 |
119 | 120 | 121 | 122 |
123 |

124 | 'follow', 128 | ), 129 | get_linkbacks( 'follow' ) 130 | ); 131 | ?> 132 |
133 | 134 | 135 | 136 |
137 |

138 | 'watch', 142 | ), 143 | get_linkbacks( 'watch' ) 144 | ); 145 | ?> 146 |
147 | 148 | 149 | 150 | 151 | 152 |
153 |

154 | 155 | 156 |

157 | 'rsvp-yes', 161 | ), 162 | get_linkbacks( 'rsvp:yes' ) 163 | ); 164 | ?> 165 | 166 | 167 | 168 |

169 | 'invited', 173 | ), 174 | get_linkbacks( 'invited' ) 175 | ); 176 | ?> 177 | 178 | 179 | 180 |

181 | 'rsvp-maybe', 185 | ), 186 | get_linkbacks( 'rsvp:maybe' ) 187 | ); 188 | ?> 189 | 190 | 191 | 192 |

193 | 'rsvp-no', 197 | ), 198 | get_linkbacks( 'rsvp:no' ) 199 | ); 200 | ?> 201 | 202 | 203 | 204 |

205 | 'rsvp-interested', 209 | ), 210 | get_linkbacks( 'rsvp:interested' ) 211 | ); 212 | ?> 213 | 214 |
215 | 216 | 217 | 218 |
219 |

220 | 'mention', 224 | ), 225 | get_linkbacks( 'mention' ) 226 | ); 227 | ?> 228 |
229 | 230 | -------------------------------------------------------------------------------- /tests/templates/tantek-com.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | going to Homebrew Website Club, 2014-05-21 18:30 @MozSF. indieweb: werd.io/2014/homebrew-website-club-5 silo: fb.com/events/1509367239285562/ - Tantek 4 | 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | 23 |
24 |

tantek.com

25 | 26 | 32 | 33 |
34 |
    35 | 36 |
  1. 37 |
    38 | RSVP yes to: 39 |

    http://werd.io/2014/homebrew-website-club-5

    40 |
    41 |
    42 | 43 |
    44 | 45 | 48 |
    49 | 86 |
  2. 87 |
88 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /includes/class-linkbacks-walker-comment.php: -------------------------------------------------------------------------------- 1 | %s', $author ); 40 | } else { 41 | $return = sprintf( '%s', $url, $author ); 42 | } 43 | 44 | /** 45 | * Filters the comment author's link for display. 46 | * 47 | * @since 1.5.0 48 | * @since 4.1.0 The `$author` and `$comment_ID` parameters were added 49 | * @param string $return The HTML-formatted comment author link. 50 | * Empty for an invalid URL. 51 | * @param string $author The comment author's username. 52 | * @param int $comment_ID The comment ID. 53 | */ 54 | return apply_filters( 'get_comment_author_link', $return, $author, $comment->comment_ID ); 55 | } 56 | 57 | protected static function is_reaction( $comment ) { 58 | // If this library is not installed then emoji detection will not work 59 | if ( ! function_exists( 'mb_internal_encoding' ) ) { 60 | return false; 61 | } 62 | return Emoji\is_single_emoji( trim( wp_strip_all_tags( $comment->comment_content ) ) ) && empty( $comment->comment_parent ); 63 | } 64 | 65 | public function start_el( &$output, $comment, $depth = 0, $args = array(), $id = 0 ) { 66 | if ( self::is_reaction( $comment ) ) { 67 | self::$reactions[] = $comment; 68 | } 69 | 70 | if ( ! self::should_facepile( $comment ) ) { 71 | return parent::start_el( $output, $comment, $depth, $args, $id ); 72 | } 73 | } 74 | 75 | public function end_el( &$output, $comment, $depth = 0, $args = array() ) { 76 | if ( ! self::should_facepile( $comment ) ) { 77 | return parent::end_el( $output, $comment, $depth, $args ); 78 | } 79 | } 80 | 81 | protected function html5_comment( $comment, $depth, $args ) { 82 | // To use the default html5_comment set this filter to false 83 | if ( ! Linkbacks_Handler::render_comments() ) { 84 | parent::html5_comment( $comment, $depth, $args ); 85 | return; 86 | } 87 | $tag = ( 'div' === $args['style'] ) ? 'div' : 'li'; 88 | $cite = apply_filters( 'semantic_linkbacks_cite', ' @ %2s' ); 89 | $type = Linkbacks_Handler::get_type( $comment ); 90 | $url = Linkbacks_Handler::get_url( $comment ); 91 | $coins = Linkbacks_Handler::get_prop( $comment, 'mf2_swarm-coins' ); 92 | $host = wp_parse_url( $url, PHP_URL_HOST ); 93 | // strip leading www, if any 94 | $host = preg_replace( '/^www\./', '', $host ); 95 | ?> 96 | < id="comment-" has_children ? 'parent' : '', $comment ); ?>> 97 |
98 |
99 |
100 | 104 | says:', 'semantic-linkbacks' ), 109 | sprintf( '%s', self::get_comment_author_link( $comment ) ) 110 | ); 111 | if ( $type && ! empty( $cite ) ) { 112 | printf( $cite, $url, $host ); 113 | } 114 | 115 | ?> 116 |
117 | 118 | 139 | 140 | comment_approved ) : ?> 141 |

142 | 143 |
144 | 145 |
146 | 147 |
148 | 149 | 'div-comment', 155 | 'depth' => $depth, 156 | 'max_depth' => $args['max_depth'], 157 | 'before' => '
', 158 | 'after' => '
', 159 | ) 160 | ) 161 | ); 162 | ?> 163 |
164 | 2 | 3 | 4 | 5 | 6 | New service: WebMentions for static pages 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

Pelle Wessman

21 |
Things about me and the world around us
22 |
23 | 24 |
25 |
26 |

New service: WebMentions for static pages

27 |
28 | 29 |
30 |

I'm now launching a service that can receive and embeds WebMentions for sites. Useful for eg. static blogs on GitHub Pages.

31 | 32 |

Earlier this year when I went to the IndieWebCamp in Brighton, a very nice event, the thing people were most excited about, at least when it came to the actually implementing it, was WebMentions.

33 | 34 |

So when it was time to get hands on with code I wanted to do so as well, my only trouble was: This blog is on Jekyll/GitHub Pages since two years and I like it there. How can I then accept something as dynamic as a comment from someone elses blog?

35 | 36 |

I know you’re already thinking it – a hosted solution like Disqus could surely solve my problem and yes indeed, therefore I went ahead and built one. Then came ordinary life and occupied me with other stuff, but now finally a very first rough version is ready for launch and can be found at webmention.herokuapp.com.

37 | 38 |

This post will prove that it works by appearing as the first WebMention on webmention.herokuapp.com itself and hopefully mentions from others will start appearing both there and on this blog eventually so that maybe I’ll become as cool as Aaron Parecki, Jeremy Keith and the others in the end.

39 | 40 |

I’m aware that this service isn’t totally inline with the spirit of the IndieWeb, that you should own and host your own stuff (I’m not even using IndieAuth yet!), but it’s my pragmatic solution for getting a step closer to such a world and a solution I’m happy to share with others.

41 | 42 |

I’m opening up just five accounts for others now, considering this is still an early alpha, and will keep the development open on GitHub – all feedback is welcome!

43 |
44 | 45 |
46 | by 49 | Pelle Wessman 50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 | Have you written a response to this? Let me know the URL: 59 |
60 | 61 | 62 | 63 |
64 |
65 | 66 | 76 | 77 |
78 | 79 | 89 | 98 | 105 | 106 | -------------------------------------------------------------------------------- /includes/class-linkbacks-avatar-handler.php: -------------------------------------------------------------------------------- 1 | comment_ID, 'avatar', true ); 41 | // Backward Compatibility for Semantic Linkbacks 42 | if ( ! $avatar ) { 43 | $avatar = get_comment_meta( $comment->comment_ID, 'semantic_linkbacks_avatar', true ); 44 | } 45 | 46 | return $avatar; 47 | } 48 | 49 | 50 | /** 51 | * Function to retrieve default avatar URL 52 | * 53 | * 54 | * @param string $type Default Avatar URL 55 | * 56 | * @return string|boolean $url 57 | */ 58 | public static function get_default_avatar( $type = null ) { 59 | if ( ! $type ) { 60 | $type = get_option( 'avatar_default', 'mystery' ); 61 | } 62 | switch ( $type ) { 63 | case 'mm': 64 | case 'mystery': 65 | case 'mysteryman': 66 | return plugin_dir_url( dirname( __FILE__ ) ) . 'img/mm.jpg'; 67 | } 68 | return apply_filters( 'semantic_linkbacks_default_avatar', $type ); 69 | } 70 | 71 | /** 72 | * Function to check if there is a gravatar 73 | * 74 | * 75 | * @param WP_Comment $comment 76 | * 77 | * @return boolean 78 | */ 79 | public static function check_gravatar( $comment ) { 80 | $hash = md5( strtolower( trim( $comment->comment_author_email ) ) ); 81 | $url = 'https://www.gravatar.com/avatar/' . $hash . '?d=404'; 82 | $response = wp_remote_head( $url ); 83 | if ( is_wp_error( $response ) || 404 === wp_remote_retrieve_response_code( $response ) ) { 84 | return false; 85 | } 86 | return true; 87 | } 88 | 89 | 90 | /** 91 | * Replaces the default avatar with a locally stored default 92 | * 93 | * @param array $args Arguments passed to get_avatar_data(), after processing. 94 | * @param int|string|object $id_or_email A user ID, email address, or comment object 95 | * 96 | * @return array $args 97 | */ 98 | public static function anonymous_avatar_data( $args, $id_or_email ) { 99 | $local = apply_filters( 'semantic_linkbacks_local_avatars', array( 'mm', 'mystery', 'mysteryman' ) ); 100 | if ( ! in_array( $args['default'], $local, true ) ) { 101 | return $args; 102 | } 103 | // Always override if default forced 104 | if ( $args['force_default'] ) { 105 | $args['url'] = self::get_default_avatar( $args['default'] ); 106 | return $args; 107 | } 108 | if ( ! strpos( $args['url'], 'gravatar.com' ) ) { 109 | return $args; 110 | } 111 | $args['url'] = str_replace( 'd=' . $args['default'], 'd=' . self::get_default_avatar( $args['default'] ), $args['url'] ); 112 | if ( $id_or_email instanceof WP_Comment ) { 113 | if ( ! empty( $id_or_email->comment_author_email ) ) { 114 | if ( self::check_gravatar( $id_or_email ) ) { 115 | update_comment_meta( $id_or_email->comment_ID, 'avatar', $args['url'] ); 116 | } else { 117 | update_comment_meta( $id_or_email->comment_ID, 'avatar', self::get_default_avatar() ); 118 | $args['url'] = self::get_default_avatar(); 119 | } 120 | return $args; 121 | } else { 122 | $args['url'] = self::get_default_avatar(); 123 | } 124 | } 125 | return $args; 126 | } 127 | 128 | /** 129 | * Replaces the default avatar with the WebMention uf2 photo 130 | * 131 | * @param array $args Arguments passed to get_avatar_data(), after processing. 132 | * @param int|string|object $id_or_email A user ID, email address, or comment object 133 | * 134 | * @return array $args 135 | */ 136 | public static function pre_get_avatar_data( $args, $id_or_email ) { 137 | if ( ! $id_or_email instanceof WP_Comment || 138 | ! isset( $id_or_email->comment_type ) || 139 | $id_or_email->user_id ) { 140 | return $args; 141 | } 142 | 143 | $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) ); 144 | if ( ! empty( $id_or_email->comment_type ) && ! in_array( $id_or_email->comment_type, (array) $allowed_comment_types, true ) ) { 145 | $args['url'] = false; 146 | /** This filter is documented in wp-includes/link-template.php */ 147 | return apply_filters( 'get_avatar_data', $args, $id_or_email ); 148 | } 149 | 150 | $type = Linkbacks_Handler::get_type( $id_or_email ); 151 | $type = explode( ':', $type ); 152 | 153 | if ( is_array( $type ) ) { 154 | $type = $type[0]; 155 | } 156 | 157 | $option = 'semantic_linkbacks_facepile_' . $type; 158 | 159 | if ( get_option( $option, false ) && 'blank' === $args['default'] ) { 160 | $args['default'] = 'anonymous'; 161 | } 162 | 163 | // check if comment has an avatar 164 | $avatar = self::get_avatar_url( $id_or_email->comment_ID ); 165 | 166 | if ( $avatar ) { 167 | if ( ! isset( $args['class'] ) || ! is_array( $args['class'] ) ) { 168 | $args['class'] = array( 'u-photo' ); 169 | } else { 170 | $args['class'][] = 'u-photo'; 171 | $args['class'] = array_unique( $args['class'] ); 172 | } 173 | $args['url'] = $avatar; 174 | $args['class'][] = 'avatar-semantic-linkbacks'; 175 | } 176 | 177 | return $args; 178 | } 179 | 180 | /** 181 | * Show avatars also on trackbacks and pingbacks 182 | * 183 | * @param array $types list of avatar enabled comment types 184 | * 185 | * @return array show avatars also on trackbacks and pingbacks 186 | */ 187 | public static function get_avatar_comment_types( $types ) { 188 | $types[] = 'pingback'; 189 | $types[] = 'trackback'; 190 | 191 | return array_unique( $types ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /vendor/mf2/mf2/LICENSE.md: -------------------------------------------------------------------------------- 1 | # Creative Commons Legal Code 2 | 3 | ## CC0 1.0 Universal 4 | 5 | http://creativecommons.org/publicdomain/zero/1.0 6 | 7 | Official translations of this legal tool are available> CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. 8 | 9 | ### _Statement of Purpose_ 10 | 11 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 12 | 13 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 14 | 15 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 16 | 17 | **1. Copyright and Related Rights.** A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 18 | 19 | 1. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 20 | 2. moral rights retained by the original author(s) and/or performer(s); 21 | 3. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 22 | 4. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 23 | 5. rights protecting the extraction, dissemination, use and reuse of data in a Work; 24 | 6. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 25 | 7. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 26 | 27 | **2. Waiver.** To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | **3. Public License Fallback.** Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 30 | 31 | **4. Limitations and Disclaimers.** 32 | 33 | 1. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 34 | 2. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 35 | 3. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 36 | 4. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 37 | -------------------------------------------------------------------------------- /semantic-linkbacks.php: -------------------------------------------------------------------------------- 1 | (may not work on all themes) for:', 'semantic-linkbacks' ), 76 | array( 'Semantic_Linkbacks_Plugin', 'facepile_checkboxes' ), 77 | $page, 78 | 'semantic-linkbacks' 79 | ); 80 | add_settings_field( 81 | 'semantic_linkbacks_facepile_fold_limit', 82 | __( 'Initial number of faces to show in facepiles (0 for all)', 'semantic-linkbacks' ), 83 | array( 'Semantic_Linkbacks_Plugin', 'facepile_fold_limit' ), 84 | $page, 85 | 'semantic-linkbacks' 86 | ); 87 | } 88 | 89 | public static function facepile_fold_limit() { 90 | printf( '', get_option( 'semantic_linkbacks_facepiles_fold_limit' ) ); 91 | } 92 | 93 | public static function facepile_checkboxes() { 94 | $strings = Linkbacks_Handler::get_comment_type_strings(); 95 | $facepile = get_option( 'semantic_linkbacks_facepiles' ); 96 | // If getting the facepiles hasn't worked, create an empty array to avoid generating errors 97 | if ( ! is_array( $facepile ) ) { 98 | $facepile = array(); 99 | } 100 | echo '
'; 101 | foreach ( $strings as $key => $value ) { 102 | printf( '
', $key, checked( in_array( $key, $facepile, true ), true, false ), $value ); 103 | } 104 | echo '
'; 105 | } 106 | 107 | public static function register_settings() { 108 | $option_group = function_exists( 'webmention_init' ) ? 'webmention' : 'discussion'; 109 | register_setting( 110 | $option_group, 111 | 'semantic_linkbacks_facepiles', 112 | array( 113 | 'type' => 'string', 114 | 'description' => __( 'Types to show in Facepiles', 'semantic-linkbacks' ), 115 | 'show_in_rest' => true, 116 | 'default' => array_keys( Linkbacks_Handler::get_comment_type_strings() ), 117 | ) 118 | ); 119 | register_setting( 120 | $option_group, 121 | 'semantic_linkbacks_facepiles_fold_limit', 122 | array( 123 | 'type' => 'integer', 124 | 'description' => __( 'Initial number of faces to show in facepiles', 'semantic-linkbacks' ), 125 | 'show_in_rest' => true, 126 | 'default' => 8, 127 | ) 128 | ); 129 | } 130 | 131 | /** 132 | * Load language files 133 | */ 134 | public static function plugin_textdomain() { 135 | // Note to self, the third argument must not be hardcoded, to account for relocated folders. 136 | load_plugin_textdomain( 'semantic-linkbacks', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 137 | } 138 | 139 | /** 140 | * Add Semantic Linkbacks options to the webmentions settings page. 141 | */ 142 | public static function settings() { 143 | _e( 'For webmentions that do not have avatars you can pick from several locally served default avatars in the Discussion Settings', 'semantic-linkbacks' ); 144 | 145 | if ( ! function_exists( 'mb_internal_encoding' ) ) { 146 | ?> 147 |

148 | $post_id, 16 | 'count' => true, 17 | 'status' => 'approve', 18 | ); 19 | 20 | if ( $type ) { // use type if set 21 | if ( 'mention' === $type ) { 22 | $args['type__not_in'] = 'comment'; 23 | $args['meta_query'] = array( 24 | 'relation' => 'OR', 25 | array( 26 | 'key' => 'semantic_linkbacks_type', 27 | 'value' => '', 28 | ), 29 | array( 30 | 'key' => 'semantic_linkbacks_type', 31 | 'compare' => 'NOT EXISTS', 32 | ), 33 | array( 34 | 'key' => 'semantic_linkbacks_type', 35 | 'value' => 'mention', 36 | ), 37 | ); 38 | } elseif ( 'rsvp' === $type ) { 39 | $args['meta_query'] = array( 40 | array( 41 | 'key' => 'semantic_linkbacks_type', 42 | 'value' => 'rsvp', 43 | 'compare' => 'LIKE', 44 | ), 45 | ); 46 | } else { 47 | $args['meta_query'] = array( 48 | array( 49 | 'key' => 'semantic_linkbacks_type', 50 | 'value' => $type, 51 | ), 52 | ); 53 | } 54 | } else { // check only if type exists 55 | $args['meta_query'] = array( 56 | array( 57 | 'key' => 'semantic_linkbacks_type', 58 | 'compare' => 'EXISTS', 59 | ), 60 | ); 61 | } 62 | 63 | $comments = get_comments( $args ); 64 | if ( $comments ) { 65 | return $comments; 66 | } else { 67 | return 0; 68 | } 69 | } 70 | 71 | /** 72 | * Returns comments of linkback type 73 | * 74 | * @param string $type the comment type 75 | * @param int $post_id the id of the post 76 | * @param string $order the order of the retrieved comments, ASC or DESC (default) 77 | * 78 | * @return the matching linkback "comments" 79 | */ 80 | function get_linkbacks( $type = null, $post_id = null, $order = 'DESC' ) { 81 | if ( null === $post_id ) { 82 | $post_id = get_the_ID(); 83 | } 84 | $args = array( 85 | 'post_id' => $post_id, 86 | 'status' => 'approve', 87 | 'order' => $order, 88 | ); 89 | 90 | if ( $type ) { // use type if set 91 | if ( 'mention' === $type ) { 92 | $args['type__not_in'] = 'comment'; 93 | $args['meta_query'] = array( 94 | 'relation' => 'OR', 95 | array( 96 | 'key' => 'semantic_linkbacks_type', 97 | 'value' => '', 98 | ), 99 | array( 100 | 'key' => 'semantic_linkbacks_type', 101 | 'compare' => 'NOT EXISTS', 102 | ), 103 | array( 104 | 'key' => 'semantic_linkbacks_type', 105 | 'value' => 'mention', 106 | ), 107 | ); 108 | } elseif ( 'rsvp' === $type ) { 109 | $args['meta_query'] = array( 110 | array( 111 | 'key' => 'semantic_linkbacks_type', 112 | 'value' => 'rsvp', 113 | 'compare' => 'LIKE', 114 | ), 115 | ); 116 | } else { 117 | $args['meta_query'] = array( 118 | array( 119 | 'key' => 'semantic_linkbacks_type', 120 | 'value' => $type, 121 | ), 122 | ); 123 | } 124 | } else { // check only if type exists 125 | $args['meta_query'] = array( 126 | array( 127 | 'key' => 'semantic_linkbacks_type', 128 | 'compare' => 'EXISTS', 129 | ), 130 | ); 131 | } 132 | 133 | return get_comments( $args ); 134 | } 135 | 136 | 137 | function has_linkbacks( $type = null, $post_ID = null ) { 138 | if ( get_linkbacks( $type, $post_ID ) ) { 139 | return true; 140 | } 141 | return false; 142 | } 143 | 144 | /** 145 | * Return marked up linkbacks 146 | * Based on wp_list_comments() 147 | */ 148 | function list_linkbacks( $args, $comments ) { 149 | $defaults = array( 150 | 'avatar_size' => 64, 151 | 'style' => 'ul', // What HTML type to wrap it in. Accepts 'ul', 'ol'. 152 | 'style-class' => 'mention-list', // What class to assign to the wrapper 153 | 'li-class' => null, // What class to assign to the list elements 154 | 'echo' => true, // Whether to echo the output or return 155 | 'type' => 'mention', // Type is the semantic linkbacks type and is here only to automatically add to the classes if present 156 | ); 157 | 158 | $r = wp_parse_args( $args, $defaults ); 159 | /** 160 | * Filters the arguments used in retrieving the linkbacks list 161 | * 162 | * 163 | * @param array $r An array of arguments for displaying linkbacks 164 | */ 165 | $r = apply_filters( 'list_linkbacks_args', $r ); 166 | if ( ! is_array( $comments ) ) { 167 | return; 168 | } 169 | if ( is_string( $r['li-class'] ) ) { 170 | $classes = explode( ' ', $r['li-class'] ); 171 | } else { 172 | $classes = $r['li-class']; 173 | } 174 | if ( is_string( $r['style-class'] ) ) { 175 | $r['style-class'] = explode( ' ', $r['style-class'] ); 176 | } 177 | 178 | $classes[] = 'linkback-' . $r['type'] . '-single'; 179 | $r['style-class'][] = 'linkback-' . $r['type']; 180 | 181 | $return = sprintf( '<%1$s class="%2$s">', $r['style'], join( ' ', $r['style-class'] ) ); 182 | $fold_at = function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ? 0 : (int) get_option( 'semantic_linkbacks_facepiles_fold_limit', 8 ); 183 | 184 | $type_labels = array( 185 | 'reacji' => __( 'Reacjis', 'semantic-linkbacks' ), 186 | 'like' => __( 'Likes', 'semantic-linkbacks' ), 187 | 'favorite' => __( 'Favourites', 'semantic-linkbacks' ), 188 | 'bookmark' => __( 'Bookmarks', 'semantic-linkbacks' ), 189 | 'repost' => __( 'Reposts', 'semantic-linkbacks' ), 190 | 'tag' => __( 'Tags', 'semantic-linkbacks' ), 191 | 'listen' => __( 'Listening', 'semantic-linkbacks' ), 192 | 'read' => __( 'Reading', 'semantic-linkbacks' ), 193 | 'follow' => __( 'Following', 'semantic-linkbacks' ), 194 | 'watch' => __( 'Watching', 'semantic-linkbacks' ), 195 | 'rsvp-yes' => __( 'RSVPs', 'semantic-linkbacks' ), 196 | 'invited' => __( 'Invited', 'semantic-linkbacks' ), 197 | 'rsvp-maybe' => __( 'Maybe', 'semantic-linkbacks' ), 198 | 'rsvp-no' => __( 'No', 'semantic-linkbacks' ), 199 | 'rsvp-interested' => __( 'Interested', 'semantic-linkbacks' ), 200 | 'mention' => __( 'Mentions', 'semantic-linkbacks' ), 201 | ); 202 | 203 | foreach ( $comments as $i => $comment ) { 204 | if ( $fold_at && $i === $fold_at ) { 205 | $classes[] = 'additional-facepile'; 206 | 207 | // Add show button. 208 | $return .= sprintf( 209 | '
  • ', 210 | sprintf( /* translators: s=Linback type */ 211 | __( 'Show more %s', 'semantic-linkbacks' ), 212 | $type_labels[ $r['type'] ] 213 | ) 214 | ); 215 | } 216 | 217 | // If it's an emoji reaction, overlay the emoji. 218 | $overlay = ''; 219 | $content = trim( wp_strip_all_tags( $comment->comment_content ) ); 220 | $title = Linkbacks_Handler::comment_text_excerpt( '', $comment ); 221 | if ( Emoji\is_single_emoji( $content ) ) { 222 | $overlay = ' ' . $content . ''; 223 | $url = wp_parse_url( Linkbacks_Handler::get_url( $comment ), PHP_URL_HOST ); 224 | $title = sprintf( 225 | '%1$s %2$s on %3$s.', 226 | $comment->comment_author, 227 | $content, 228 | preg_replace( '/^www\./', '', $url ) 229 | ); 230 | } 231 | $class = get_comment_class( $classes, $comment ); 232 | $class = join( ' ', $class ); 233 | $avatar = get_avatar( $comment, $r['avatar_size'] ); 234 | // If the avatar comes back empty show the name 235 | if ( ! $avatar ) { 236 | $avatar = get_comment_author( $comment ); 237 | } 238 | $return .= sprintf( 239 | '
  • %2$s%8$s%4$s
  • ', 240 | $class, 241 | $avatar, 242 | get_comment_author_url( $comment ), 243 | get_comment_author( $comment ), 244 | esc_attr( 'comment-' . $comment->comment_ID ), 245 | esc_attr( wp_strip_all_tags( $title ) ), 246 | esc_url_raw( Linkbacks_Handler::get_canonical_url( $comment ) ), 247 | $overlay 248 | ); 249 | } 250 | 251 | if ( $fold_at && count( $comments ) > $fold_at ) { 252 | // Add hide button at end. 253 | $return .= sprintf( 254 | '', 255 | sprintf( /* translators: s=Linback type */ 256 | __( 'Show fewer %s', 'semantic-linkbacks' ), 257 | $type_labels[ $r['type'] ] 258 | ) 259 | ); 260 | } 261 | 262 | $return .= sprintf( '', $r['style'] ); 263 | if ( $r['echo'] ) { 264 | echo $return; 265 | } 266 | return $return; 267 | } 268 | -------------------------------------------------------------------------------- /includes/class-linkbacks-notifications.php: -------------------------------------------------------------------------------- 1 | comment_post_ID ); 81 | if ( ! Linkbacks_Handler::get_type( $comment ) ) { 82 | return $notify_message; 83 | } 84 | $notify_message = self::notification( $comment ); 85 | $notify_message .= self::notification_body( $comment ); 86 | if ( user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) { 87 | $notify_message .= self::moderate_text( $comment ) . "\r\n"; 88 | } 89 | return $notify_message; 90 | 91 | } 92 | 93 | /** 94 | * Generate the moderation text 95 | * 96 | * @param int|WP_Comment $comment comment 97 | * @return string $message Appropriate text. 98 | */ 99 | public static function moderate_text( $comment ) { 100 | $comment = get_comment( $comment ); 101 | /* translators: Comment moderation. 1: Comment action URL */ 102 | $message = sprintf( __( 'Approve it: %s', 'semantic-linkbacks' ), admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) ) . "\r\n"; 103 | if ( EMPTY_TRASH_DAYS ) { 104 | /* translators: Trash it URL */ 105 | $message .= sprintf( __( 'Trash it: %s', 'semantic-linkbacks' ), admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; 106 | } else { 107 | /* translators: Delete it URL */ 108 | $message .= sprintf( __( 'Delete it: %s', 'semantic-linkbacks' ), admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; 109 | } 110 | /* translators: Spam it URL */ 111 | $message .= sprintf( __( 'Spam it: %s', 'semantic-linkbacks' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n"; 112 | return $message; 113 | } 114 | 115 | /** 116 | * Filter the comment notification subject 117 | * 118 | * @param string $subject 119 | * @param int $comment_id comment 120 | * @return string $subject 121 | */ 122 | public static function comment_notification_subject( $subject, $comment_id ) { 123 | $comment = get_comment( $comment_id ); 124 | $type = Linkbacks_Handler::get_type( $comment ); 125 | if ( ! $type ) { 126 | return $subject; 127 | } 128 | $title = get_the_title( $comment->comment_post_ID ); 129 | $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 130 | /* translators: 1: blog name, 2: Semantic Linkbacks type, 3: post title */ 131 | return sprintf( __( '[%1$s] %2$s: %3$s', 'semantic-linkbacks' ), $blogname, $type, $title ); 132 | } 133 | 134 | /** 135 | * Filter the comment moderation text 136 | * 137 | * @param string $notify_message 138 | * @param int $comment_id comment 139 | * @return string $notify_message 140 | */ 141 | public static function comment_moderation_text( $notify_message, $comment_id ) { 142 | $comment = get_comment( $comment_id ); 143 | $notify_message = self::moderation( $comment ) . "\r\n\r\n"; 144 | $notify_message .= self::notification_body( $comment ) . "\r\n\r\n"; 145 | $notify_message .= self::moderate_text( $comment ); 146 | return $notify_message; 147 | } 148 | 149 | /** 150 | * Filter a notification text 151 | * @param string $notify_messsage 152 | * @param int|WP_Comment $comment 153 | * @return string $notify_basic 154 | */ 155 | public static function basic_notification_text( $notify_message, $comment_id ) { 156 | $comment = get_comment( $comment_id ); 157 | $type = Linkbacks_Handler::get_type( $comment ); 158 | if ( ! $type ) { 159 | return ''; 160 | } 161 | $type = Linkbacks_Handler::get_comment_type_strings( $type ); 162 | $comment_link = get_comment_link( $comment ); 163 | $notify_basic = wp_strip_all_tags( get_comment_text( $comment ) ); 164 | return $notify_basic; 165 | } 166 | 167 | 168 | /** 169 | * Filter the comment moderation subject 170 | * 171 | * @param string $subject 172 | * @param int $comment_id comment 173 | * @return string $subject 174 | */ 175 | public static function comment_moderation_subject( $subject, $comment_id ) { 176 | $comment = get_comment( $comment_id ); 177 | $type = Linkbacks_Handler::get_type( $comment ); 178 | if ( ! $type ) { 179 | return $subject; 180 | } 181 | $type = Linkbacks_Handler::get_comment_type_strings( $type ); 182 | $title = get_the_title( $comment->comment_post_ID ); 183 | $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 184 | /* translators: Comment moderation notification email subject. 1: Site name, 2: Semantic Linkbacks type, 3: Post title */ 185 | return sprintf( __( '[%1$s] Please moderate a %2$s: %3$s', 'semantic-linkbacks' ), $blogname, $type, $title ); 186 | } 187 | 188 | 189 | /** 190 | * Generate the body of the notification 191 | * 192 | * @param int|WP_Comment $comment comment 193 | * @return string $message Appropriate text. 194 | */ 195 | public static function notification_body( $comment ) { 196 | $comment = get_comment( $comment ); 197 | $type = Linkbacks_Handler::get_type( $comment ); 198 | if ( ! $type ) { 199 | return ''; 200 | } 201 | $type = Linkbacks_Handler::get_comment_type_strings( $type ); 202 | /* translators: 1: website name, 2: website URL */ 203 | $notify_text[] = sprintf( __( 'Author: %1$1s(%2$2s) ', 'semantic-linkbacks' ), get_comment_author( $comment ), Linkbacks_Handler::get_author_url( $comment ) ); 204 | /* translators: Semantic Linkback comment type string */ 205 | $notify_text[] = sprintf( __( 'Semantic Type: %s', 'semantic-linkbacks' ), $type ); 206 | /* translators: URL */ 207 | $notify_text[] = sprintf( __( 'URL: %s ', 'semantic-linkbacks' ), Linkbacks_Handler::get_url( $comment ) ); 208 | /* translators: Comment Text */ 209 | $notify_text[] = sprintf( __( 'Text: %s', 'semantic-linkbacks' ), "\r\n" . wp_strip_all_tags( get_comment_text( $comment ) ) ); 210 | /* translators: 1. Post Permalink */ 211 | $notify_text[] = "\r\n" . sprintf( __( 'You can see all responses to this post here: %1s#comments', 'semantic-linkbacks' ), get_permalink( $comment->comment_post_ID ) ); 212 | /* translators: Comment Permalink */ 213 | $notify_text[] = sprintf( __( 'Permalink: %s', 'semantic-linkbacks' ), get_comment_link( $comment ) ); 214 | return implode( "\r\n", $notify_text ); 215 | } 216 | 217 | /** 218 | * Generate the notification which can be placed at the top of the email or as a text notification 219 | * 220 | * @param int|WP_Comment $comment comment 221 | * @return string $message Appropriate text. 222 | */ 223 | public static function notification( $comment ) { 224 | $comment = get_comment( $comment ); 225 | $type = Linkbacks_Handler::get_type( $comment ); 226 | if ( ! $type ) { 227 | return ''; 228 | } 229 | $type = Linkbacks_Handler::get_comment_type_strings( $type ); 230 | /* translators: 1. Semantic Linkbacks type, 2. Post Title */ 231 | return sprintf( __( 'New %1$1s on your post "%2$2s"', 'semantic-linkbacks' ), $type, get_the_title( $comment->comment_post_ID ) ); 232 | } 233 | 234 | /** 235 | * Generate the moderation notification which can be placed at the top of the email or as a text notification 236 | * 237 | * @param int|WP_Comment $comment comment 238 | * @return string $message Appropriate text. 239 | */ 240 | public static function moderation( $comment ) { 241 | $type = Linkbacks_Handler::get_type( $comment ); 242 | if ( ! $type ) { 243 | return ''; 244 | } 245 | $type = Linkbacks_Handler::get_comment_type_strings( $type ); 246 | /* translators: 1. Semantic Linkbacks type, 2. Post Title */ 247 | return sprintf( __( 'A new %1$1s on the post "%2$2s" is waiting for your approval', 'semantic-linkbacks' ), $type, get_the_title( $comment->comment_post_ID ) ); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /languages/semantic-linkbacks.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Matthias Pfefferle 2 | # This file is distributed under the MIT. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Semantic-Linkbacks 3.10.1\n" 6 | "Report-Msgid-Bugs-To: " 7 | "https://wordpress.org/support/plugin/wordpress-semantic-linkbacks\n" 8 | "POT-Creation-Date: 2020-04-07 12:37:56+00:00\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=utf-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "PO-Revision-Date: 2020-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "X-Generator: grunt-wp-i18n 1.0.3\n" 16 | 17 | #: includes/class-linkbacks-avatar-handler.php:23 18 | msgid "Mystery Person (hosted locally)" 19 | msgstr "" 20 | 21 | #: includes/class-linkbacks-handler.php:57 22 | msgid "Semantic Linkbacks Data" 23 | msgstr "" 24 | 25 | #: includes/class-linkbacks-handler.php:274 26 | #. translators: Name verb on domain 27 | msgid "%1$s mentioned %2$s on %4$s." 28 | msgstr "" 29 | 30 | #: includes/class-linkbacks-handler.php:276 31 | #. translators: Name verb on domain 32 | msgid "%1$s replied to %2$s on %4$s." 33 | msgstr "" 34 | 35 | #: includes/class-linkbacks-handler.php:278 36 | #. translators: Name verb on domain 37 | msgid "%1$s reposted %2$s on %4$s." 38 | msgstr "" 39 | 40 | #: includes/class-linkbacks-handler.php:280 41 | #. translators: Name verb on domain 42 | msgid "%1$s liked %2$s on %4$s." 43 | msgstr "" 44 | 45 | #: includes/class-linkbacks-handler.php:282 46 | #. translators: Name verb on domain 47 | msgid "%1$s favorited %2$s on %4$s." 48 | msgstr "" 49 | 50 | #: includes/class-linkbacks-handler.php:284 51 | #. translators: Name verb on domain 52 | msgid "%1$s tagged %2$s on %4$s." 53 | msgstr "" 54 | 55 | #: includes/class-linkbacks-handler.php:286 56 | #. translators: Name verb on domain 57 | msgid "%1$s bookmarked %2$s on %4$s." 58 | msgstr "" 59 | 60 | #: includes/class-linkbacks-handler.php:288 61 | #. translators: Name verb on domain 62 | msgid "%1$s is attending." 63 | msgstr "" 64 | 65 | #: includes/class-linkbacks-handler.php:290 66 | #. translators: Name verb on domain 67 | msgid "%1$s is not attending." 68 | msgstr "" 69 | 70 | #: includes/class-linkbacks-handler.php:292 71 | #. translators: Name verb on domain 72 | msgid "Maybe %1$s will be attending." 73 | msgstr "" 74 | 75 | #: includes/class-linkbacks-handler.php:294 76 | #. translators: Name verb on domain 77 | msgid "%1$s is interested in this event." 78 | msgstr "" 79 | 80 | #: includes/class-linkbacks-handler.php:296 81 | #. translators: Name verb on domain 82 | msgid "%1$s is invited." 83 | msgstr "" 84 | 85 | #: includes/class-linkbacks-handler.php:298 86 | #. translators: Name verb on domain 87 | msgid "%1$s listened to %2$s (via %4$s)." 88 | msgstr "" 89 | 90 | #: includes/class-linkbacks-handler.php:300 91 | #. translators: Name verb on domain 92 | msgid "%1$s watched %2$s (via %4$s)." 93 | msgstr "" 94 | 95 | #: includes/class-linkbacks-handler.php:302 96 | #. translators: Name verb on domain 97 | msgid "%1$s read %2$s (via %4$s)." 98 | msgstr "" 99 | 100 | #: includes/class-linkbacks-handler.php:304 101 | #. translators: Name verb on domain 102 | msgid "%1$s followed %2$s (via %4$s)." 103 | msgstr "" 104 | 105 | #: includes/class-linkbacks-handler.php:320 106 | msgid "Mention" 107 | msgstr "" 108 | 109 | #: includes/class-linkbacks-handler.php:322 110 | msgid "Reply" 111 | msgstr "" 112 | 113 | #: includes/class-linkbacks-handler.php:323 114 | msgid "Repost" 115 | msgstr "" 116 | 117 | #: includes/class-linkbacks-handler.php:324 118 | msgid "Like" 119 | msgstr "" 120 | 121 | #: includes/class-linkbacks-handler.php:325 122 | msgid "Favorite" 123 | msgstr "" 124 | 125 | #: includes/class-linkbacks-handler.php:326 126 | msgid "Tag" 127 | msgstr "" 128 | 129 | #: includes/class-linkbacks-handler.php:327 130 | msgid "Bookmark" 131 | msgstr "" 132 | 133 | #: includes/class-linkbacks-handler.php:328 134 | msgid "RSVP Yes" 135 | msgstr "" 136 | 137 | #: includes/class-linkbacks-handler.php:329 138 | msgid "RSVP No" 139 | msgstr "" 140 | 141 | #: includes/class-linkbacks-handler.php:330 142 | msgid "RSVP Maybe" 143 | msgstr "" 144 | 145 | #: includes/class-linkbacks-handler.php:331 146 | msgid "RSVP Interested" 147 | msgstr "" 148 | 149 | #: includes/class-linkbacks-handler.php:332 templates/linkbacks.php:161 150 | msgid "Invited" 151 | msgstr "" 152 | 153 | #: includes/class-linkbacks-handler.php:333 154 | msgid "Listen" 155 | msgstr "" 156 | 157 | #: includes/class-linkbacks-handler.php:334 158 | msgid "Watch" 159 | msgstr "" 160 | 161 | #: includes/class-linkbacks-handler.php:335 162 | msgid "Read" 163 | msgstr "" 164 | 165 | #: includes/class-linkbacks-handler.php:336 166 | msgid "Follow" 167 | msgstr "" 168 | 169 | #: includes/class-linkbacks-handler.php:337 170 | msgid "Reacji" 171 | msgstr "" 172 | 173 | #: includes/class-linkbacks-handler.php:365 174 | msgid "this Post" 175 | msgstr "" 176 | 177 | #: includes/class-linkbacks-handler.php:366 178 | msgid "this Page" 179 | msgstr "" 180 | 181 | #: includes/class-linkbacks-handler.php:368 182 | msgid "this Article" 183 | msgstr "" 184 | 185 | #: includes/class-linkbacks-handler.php:369 186 | msgid "this Aside" 187 | msgstr "" 188 | 189 | #: includes/class-linkbacks-handler.php:370 190 | msgid "this Chat" 191 | msgstr "" 192 | 193 | #: includes/class-linkbacks-handler.php:371 194 | msgid "this Gallery" 195 | msgstr "" 196 | 197 | #: includes/class-linkbacks-handler.php:372 198 | msgid "this Link" 199 | msgstr "" 200 | 201 | #: includes/class-linkbacks-handler.php:373 202 | msgid "this Image" 203 | msgstr "" 204 | 205 | #: includes/class-linkbacks-handler.php:374 206 | msgid "this Quote" 207 | msgstr "" 208 | 209 | #: includes/class-linkbacks-handler.php:375 210 | msgid "this Status" 211 | msgstr "" 212 | 213 | #: includes/class-linkbacks-handler.php:376 214 | msgid "this Video" 215 | msgstr "" 216 | 217 | #: includes/class-linkbacks-handler.php:377 218 | msgid "this Audio" 219 | msgstr "" 220 | 221 | #: includes/class-linkbacks-notifications.php:34 222 | msgid "Resend Comment Email" 223 | msgstr "" 224 | 225 | #: includes/class-linkbacks-notifications.php:35 226 | msgid "Resend Moderation Email" 227 | msgstr "" 228 | 229 | #: includes/class-linkbacks-notifications.php:37 230 | msgid "Send Pushover Note" 231 | msgstr "" 232 | 233 | #: includes/class-linkbacks-notifications.php:40 234 | msgid "Send Pushbullet Note" 235 | msgstr "" 236 | 237 | #: includes/class-linkbacks-notifications.php:102 238 | #. translators: Comment moderation. 1: Comment action URL 239 | msgid "Approve it: %s" 240 | msgstr "" 241 | 242 | #: includes/class-linkbacks-notifications.php:105 243 | #. translators: Trash it URL 244 | msgid "Trash it: %s" 245 | msgstr "" 246 | 247 | #: includes/class-linkbacks-notifications.php:108 248 | #. translators: Delete it URL 249 | msgid "Delete it: %s" 250 | msgstr "" 251 | 252 | #: includes/class-linkbacks-notifications.php:111 253 | #. translators: Spam it URL 254 | msgid "Spam it: %s" 255 | msgstr "" 256 | 257 | #: includes/class-linkbacks-notifications.php:131 258 | #. translators: 1: blog name, 2: Semantic Linkbacks type, 3: post title 259 | msgid "[%1$s] %2$s: %3$s" 260 | msgstr "" 261 | 262 | #: includes/class-linkbacks-notifications.php:185 263 | #. translators: Comment moderation notification email subject. 1: Site name, 2: 264 | #. Semantic Linkbacks type, 3: Post title 265 | msgid "[%1$s] Please moderate a %2$s: %3$s" 266 | msgstr "" 267 | 268 | #: includes/class-linkbacks-notifications.php:203 269 | #. translators: 1: website name, 2: website URL 270 | msgid "Author: %1$1s(%2$2s) " 271 | msgstr "" 272 | 273 | #: includes/class-linkbacks-notifications.php:205 274 | #. translators: Semantic Linkback comment type string 275 | msgid "Semantic Type: %s" 276 | msgstr "" 277 | 278 | #: includes/class-linkbacks-notifications.php:207 279 | #. translators: URL 280 | msgid "URL: %s " 281 | msgstr "" 282 | 283 | #: includes/class-linkbacks-notifications.php:209 284 | #. translators: Comment Text 285 | msgid "Text: %s" 286 | msgstr "" 287 | 288 | #: includes/class-linkbacks-notifications.php:211 289 | #. translators: 1. Post Permalink 290 | msgid "You can see all responses to this post here: %1s#comments" 291 | msgstr "" 292 | 293 | #: includes/class-linkbacks-notifications.php:213 294 | #. translators: Comment Permalink 295 | msgid "Permalink: %s" 296 | msgstr "" 297 | 298 | #: includes/class-linkbacks-notifications.php:231 299 | #. translators: 1. Semantic Linkbacks type, 2. Post Title 300 | msgid "New %1$1s on your post \"%2$2s\"" 301 | msgstr "" 302 | 303 | #: includes/class-linkbacks-notifications.php:247 304 | #. translators: 1. Semantic Linkbacks type, 2. Post Title 305 | msgid "A new %1$1s on the post \"%2$2s\" is waiting for your approval" 306 | msgstr "" 307 | 308 | #: includes/class-linkbacks-walker-comment.php:104 309 | #. translators: %s: comment author link 310 | msgid "%s says:" 311 | msgstr "" 312 | 313 | #: includes/class-linkbacks-walker-comment.php:118 314 | #. translators: Number of Swarm Coins 315 | msgid "+%d coin" 316 | msgid_plural "+%d coins" 317 | msgstr[0] "" 318 | msgstr[1] "" 319 | 320 | #: includes/class-linkbacks-walker-comment.php:129 321 | #. translators: 1: comment date, 2: comment time 322 | msgid "%1$s at %2$s" 323 | msgstr "" 324 | 325 | #: includes/class-linkbacks-walker-comment.php:133 326 | msgid "Edit" 327 | msgstr "" 328 | 329 | #: includes/class-linkbacks-walker-comment.php:137 330 | msgid "Your response is awaiting moderation." 331 | msgstr "" 332 | 333 | #: semantic-linkbacks.php:67 334 | msgid "Semantic Linkbacks Settings" 335 | msgstr "" 336 | 337 | #: semantic-linkbacks.php:73 338 | msgid "" 339 | "Automatically embed facepiles (may not work on all themes) " 340 | "for:" 341 | msgstr "" 342 | 343 | #: semantic-linkbacks.php:80 344 | msgid "Initial number of faces to show in facepiles (0 for all)" 345 | msgstr "" 346 | 347 | #: semantic-linkbacks.php:108 348 | msgid "Types to show in Facepiles" 349 | msgstr "" 350 | 351 | #: semantic-linkbacks.php:118 352 | msgid "Initial number of faces to show in facepiles" 353 | msgstr "" 354 | 355 | #: semantic-linkbacks.php:137 356 | msgid "" 357 | "For webmentions that do not have avatars you can pick from several locally " 358 | "served default avatars in the Discussion Settings" 359 | msgstr "" 360 | 361 | #: semantic-linkbacks.php:141 362 | msgid "" 363 | "This server does not have the php-mbstring package installed and Emoji " 364 | "reactions have been disabled." 365 | msgstr "" 366 | 367 | #: semantic-linkbacks.php:159 368 | msgid "" 369 | "For received webmentions, pingbacks and trackbacks, such as responding to a " 370 | "post or article, this site stores information retrieved from the source\n" 371 | "\t\t\t\tin order to provide a richer comment. Items such as author name and " 372 | "image, summary of the text, etc may be stored if present in the source and " 373 | "are\n" 374 | "\t\t\t\tsolely to provide richer comments. We will remove any of this on " 375 | "request." 376 | msgstr "" 377 | 378 | #: templates/linkbacks-edit-comment-form.php:2 379 | msgid "Source" 380 | msgstr "" 381 | 382 | #: templates/linkbacks-edit-comment-form.php:6 383 | msgid "Canonical" 384 | msgstr "" 385 | 386 | #: templates/linkbacks-edit-comment-form.php:10 387 | msgid "Avatar" 388 | msgstr "" 389 | 390 | #: templates/linkbacks-edit-comment-form.php:14 391 | msgid "Type" 392 | msgstr "" 393 | 394 | #: templates/linkbacks.php:4 395 | msgid "Reacjis" 396 | msgstr "" 397 | 398 | #: templates/linkbacks.php:18 399 | msgid "Likes" 400 | msgstr "" 401 | 402 | #: templates/linkbacks.php:32 403 | msgid "Favourites" 404 | msgstr "" 405 | 406 | #: templates/linkbacks.php:46 407 | msgid "Bookmarks" 408 | msgstr "" 409 | 410 | #: templates/linkbacks.php:60 411 | msgid "Reposts" 412 | msgstr "" 413 | 414 | #: templates/linkbacks.php:74 415 | msgid "Tags" 416 | msgstr "" 417 | 418 | #: templates/linkbacks.php:88 419 | msgid "Listening" 420 | msgstr "" 421 | 422 | #: templates/linkbacks.php:102 423 | msgid "Reading" 424 | msgstr "" 425 | 426 | #: templates/linkbacks.php:116 427 | msgid "Following" 428 | msgstr "" 429 | 430 | #: templates/linkbacks.php:130 431 | msgid "Watching" 432 | msgstr "" 433 | 434 | #: templates/linkbacks.php:146 435 | msgid "RSVPs" 436 | msgstr "" 437 | 438 | #: templates/linkbacks.php:149 439 | msgid "Yes" 440 | msgstr "" 441 | 442 | #: templates/linkbacks.php:173 443 | msgid "Maybe" 444 | msgstr "" 445 | 446 | #: templates/linkbacks.php:185 447 | msgid "No" 448 | msgstr "" 449 | 450 | #: templates/linkbacks.php:197 451 | msgid "Interested" 452 | msgstr "" 453 | 454 | #: templates/linkbacks.php:212 455 | msgid "Mentions" 456 | msgstr "" 457 | 458 | #. Plugin Name of the plugin/theme 459 | msgid "Semantic-Linkbacks" 460 | msgstr "" 461 | 462 | #. Plugin URI of the plugin/theme 463 | msgid "https://github.com/pfefferle/wordpress-semantic-linkbacks" 464 | msgstr "" 465 | 466 | #. Description of the plugin/theme 467 | msgid "Semantic Linkbacks for WebMentions, Trackbacks and Pingbacks" 468 | msgstr "" 469 | 470 | #. Author of the plugin/theme 471 | msgid "Matthias Pfefferle" 472 | msgstr "" 473 | 474 | #. Author URI of the plugin/theme 475 | msgid "https://notiz.blog/" 476 | msgstr "" -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Semantic-Linkbacks === 2 | Contributors: pfefferle, dshanske, edent 3 | Donate link: https://notiz.blog/donate/ 4 | Tags: webmention, pingback, trackback, linkback, microformats, comments, indieweb 5 | Requires at least: 4.9 6 | Requires PHP: 5.6 7 | Tested up to: 6.1 8 | Stable tag: 3.11.3 9 | License: MIT 10 | License URI: http://opensource.org/licenses/MIT 11 | 12 | Richer Comments and Linkbacks for WordPress! 13 | 14 | == Description == 15 | 16 | Generates richer WordPress comments from linkbacks such as [Webmention](https://wordpress.org/plugins/webmention) or classic linkback protocols like Trackback or Pingback. 17 | 18 | The limited display for trackbacks and linkbacks is replaced by a clean full sentence, such as "Bob mentioned this article on bob.com." If Bob's site uses markup that the plugin can interpret, it may add his profile picture or other parts of his page to display as a full comment. It will optionally show collections of profile pictures, known as Facepiles, instead of the full setences. 19 | 20 | Semantic Linkbacks uses [Microformats 2](http://microformats.org/wiki/microformats2) to get information about the linked post and it is highly extensible to also add support for other forms of markup. 21 | 22 | == Privacy and Data Collection == 23 | 24 | This plugin collects data from sites that send webmentions for the purpose of displaying richer comments on a site. This data is under the control of the site owner. It is the personal responsibility of that individual or individuals to remove any information at the request of the original content creator. Over time, we will add additional tools to assist in doing so. 25 | 26 | == Frequently Asked Questions == 27 | 28 | = Do I need to mark up my site? = 29 | 30 | Most modern WordPress themes support the older Microformats standard, which means the plugin should be able to get basic information from to enhance linkbacks. The plugin is most useful with the [WordPress Webmention plugin](https://wordpress.org/plugins/webmention/) and sites/themes that support Microformats 2. 31 | 32 | = Why Webmentions? = 33 | 34 | [Webmention](http://indiewebcamp.com/webmention) is a modern reimplementation of Pingback and is now a W3C Recommendation. 35 | 36 | = What about the semantic "comment" types? = 37 | 38 | The IndieWeb community defines several types of feedback: 39 | 40 | * Replies: 41 | * Reposts: 42 | * Likes: 43 | * Favorites: 44 | * RSVPs: 45 | * Tagging: 46 | * Listen: 47 | * Watch: 48 | * Read: 49 | * Follow: 50 | * Classic "Mentions": 51 | 52 | = How do I extend this plugin? = 53 | 54 | See [Extensions](https://indieweb.org/Semantic_Linkbacks#Extensions) 55 | 56 | = How do I add this into my plugin? = 57 | 58 | The plugin will automatically enhance webmentions, trackbacks, and pingbacks with an avatar and additional context. It will also automatically add a facepile instead of individual comments, but this feature can either be turned off by an aware theme or under Discussion in your Settings. 59 | 60 | = Why do some [emoji reactions](https://indieweb.org/reacji) not show up? = 61 | 62 | Some emoji characters in webmentions you might receive, e.g. Facebook reactions from [Bridgy](https://brid.gy/), take more than two bytes to encode. (In technical terms, these Unicode characters are [above the Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)).) To handle them, you need MySQL 5.5.3 or higher, and your database and tables need to use the [`utf8mb4` charset](https://dev.mysql.com/doc/refman/5.7/en/charset-mysql.html). [Usually WordPress does this automatically](https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/), but not always. 63 | 64 | First, [follow these instructions](https://wordpress.stackexchange.com/questions/195046/relaunch-4-2-utf8mb4-databse-upgrade/244992#244992) to switch your MySQL database to `utf8mb4`. Then, make sure `DB_CHARSET` and `DB_COLLATE` in your `wp-config.php` are either unset, set to the blank string, or set to these values: 65 | 66 | define('DB_CHARSET', 'utf8mb4'); 67 | define('DB_COLLATE', 'utf8mb4_general_ci'); 68 | 69 | = Who made the logos? = 70 | 71 | The Webmention and Pingback logos are made by [Aaron Parecki](http://aaronparecki.com) and the Microformats logo is made by [Dan Cederholm](http://simplebits.com/work/microformats/). 72 | 73 | = Why are you providing avatars? = 74 | 75 | The plugin attempts to store the URL to an actual profile image on the source site. The default avatar set by WordPress is only used if there is no such image found. 76 | 77 | Even the WordPress default avatars are served by querying Gravatar.com which serves the file. Gravatar works by you providing an email address which it uses to match the image. 78 | The majority of linkbacks enhanced by this plugin do not have email addresses therefore we know that gravatar will not have anything on file. 79 | 80 | If there is no email address it will serve the local avatar. If there is an email, it will go out to gravatar.com and see if they 81 | have a gravatar on file. If there is it will store the gravatar URL, otherwise it will store the URL for the local avatar if set. 82 | 83 | The plugin uses a locally cached version of the mystery icon normally provided by WordPress and Gravatar. 84 | 85 | == Changelog == 86 | 87 | Project actively developed on Github at [pfefferle/wordpress-semantic-linkbacks](https://github.com/pfefferle/wordpress-semantic-linkbacks). Please file support issues there. 88 | 89 | = 3.11.3 = 90 | 91 | * Deactivate itself if Webmention Version is 5.0.0 or above 92 | 93 | = 3.11.2 = 94 | 95 | * Fix array access issue 96 | * Fix coding standards 97 | 98 | = 3.11.1 = 99 | 100 | * Fix HTML issue 101 | 102 | = 3.11.0 = 103 | 104 | * Fix AMP compatibility. Props to [Milind More](https://github.com/milindmore22). 105 | 106 | = 3.10.4 = 107 | 108 | * Whitelist `vendor` folder 109 | 110 | = 3.10.3 = 111 | 112 | * Remove jQuery dependency. Props to [Florian Brinkmann](https://github.com/florianbrinkmann). 113 | * Fix Facepile errors. Props to [Terence Eden](https://github.com/edent). 114 | * Remove refbacks as a default. Add a filter to decide on what comment types would be used. 115 | * Use `comment` as default comment-type: https://core.trac.wordpress.org/ticket/49236 116 | 117 | = 3.10.2 = 118 | 119 | * Fix default value of `semantic_linkbacks_facepiles` (props @prtksxna) 120 | 121 | = 3.10.1 = 122 | 123 | * Fix load issues with Webmention vs Semantic Linkbacks 124 | 125 | = 3.10.0 = 126 | 127 | * Use Webmentions avatar-handler if available 128 | 129 | = 3.9.3 = 130 | 131 | * Fixes https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/41 132 | 133 | = 3.9.2 = 134 | 135 | * Only load MF2 parser inside parsing function to ensure bundled version is loaded 136 | * Update development dependencies 137 | 138 | = 3.9.1 = 139 | 140 | * Quick fix facepile problem id comments are closed 141 | 142 | = 3.9.0 = 143 | 144 | * Support rel-alternate (mf2-json) 145 | * New php-mf2 version 146 | 147 | = 3.8.1 = 148 | 149 | * Add follow post as type 150 | * Add warning to settings page if php-mbstring not installed 151 | * Return false in Emoji function if php-mbstring not installed 152 | * Add approve link to emails sent 153 | 154 | = 3.8.0 = 155 | 156 | * Add locally hosted copy of the mystery man icon and serve it if there is no gravatar 157 | * Redo settings and settings page 158 | * Settings page to merge with Webmentions page if webmentions loaded 159 | 160 | = 3.7.7 = 161 | 162 | * Add read type 163 | * Capture read-status if available 164 | * Change string as previous tense was off 165 | 166 | = 3.7.6 = 167 | 168 | * Update Parser to version 0.4.3 169 | * Introduce watch and listen properties 170 | 171 | = 3.7.5 = 172 | 173 | * fixed Reacji UI ((#154)[https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/154]) 174 | 175 | = 3.7.4 = 176 | 177 | * Replace `rsvp-invite` property which is not in use with `invite` property and add unit tests 178 | * Enhance post type returns to include post, page, and sitename 179 | * Add basic person tagging support 180 | 181 | = 3.7.3 = 182 | 183 | * Replace tracking with interested property as noted on https://indieweb.org/rsvp 184 | * Remove `h-as` properties 185 | * Remove hard-coded microformats2 properties from facepile and move them to being generated from comment_class 186 | * Remove unused properties 187 | * Introduce type argument in list_linkbacks to generate unique ideas for each list of linkbacks without having to specify them using style and li-class 188 | * Whitelist property swarm-coins, used by [OwnYourSwarm](https://ownyourswarm.p3k.io/docs#coins) and display it if using built-in comment handler. 189 | 190 | = 3.7.2 = 191 | 192 | * Bugfix: "Normal comments" hidden in comment-section (https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/140) 193 | 194 | = 3.7.1 = 195 | 196 | * Fixed reacjis and facepiles 197 | 198 | = 3.7.0 = 199 | 200 | * Add settings to enable each type independently in the Facepile 201 | * Optionally render mentions as normal comments again 202 | * Support Reacji...aka single-emoji reactions 203 | * Bump minimum PHP to 5.4 due emoji detector library dependency issues 204 | * Overlay emoji on individual avatars in reactions facepile 205 | * Offer mf2 compatible template for comments 206 | * Fix semantic_linkbacks_cite filter as was previously filtering the entire comment text 207 | * Switch semantic_links_cite filter to filtering the format for the citation instead of the prepared citation 208 | * Count correct text length for unicode characters 209 | * Facepile Template improvements 210 | * Allow new comment template to be overridden by filter or theme declaring microformats2 support 211 | * Code standards compliance changes 212 | * Improved testing for PHP versions 5.4 and up to ensure compatibility 213 | * Remove direct calls to comment meta in favor of helper functions to ensure future proofing 214 | 215 | = 3.6.0 = 216 | 217 | * Only show the first 8 avatars in a facepile by default. If there are more, include a clickable ellipsis to show the rest. Customizable via the `FACEPILE_FOLD_LIMIT` constant. 218 | * Link facepile avatars to user profile/home page, not response post 219 | * Always show avatar images with correct aspect ratio 220 | 221 | = 3.5.1 = 222 | 223 | * Bugfix release 224 | 225 | = 3.5.0 = 226 | 227 | * Add Facepile code 228 | * Add setting to disable automatic facepile include 229 | * Add filter to allow themes to disable the setting and the feature if they facepile themselves 230 | * Add PHP requirement to readme file 231 | 232 | = 3.4.1 = 233 | 234 | * Abstract out linkback retrieval functions to allow for easier changes in future 235 | * Fix retrieval issue 236 | * Remove merge and compatibility function creating double slashing due update in 4.7.1 237 | * Replace blacklist for properties with whitelist for select properties 238 | * Update avatar function to not override if user_id is set on assumption local overrides remote 239 | 240 | = 3.4.0 = 241 | 242 | * Fix Tests and Error in Authorship 243 | * Update Parser 244 | * Switch to looser restrictions if WP_DEBUG is enabled and stricter ones otherwise 245 | * Enhance Author Properties to allow for retrieving remote h-card 246 | * Store mf2 properties 247 | * Store location in WordPress Geodata 248 | * Use rel-syndication if not u-syndication 249 | * Support new webmention source meta key 250 | 251 | = 3.3.1 = 252 | 253 | * fixed https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/68 254 | 255 | = 3.3.0 = 256 | 257 | * Due to changes in WordPress 4.4 through 4.7 and version 3.0.0 of the Webmentions plugin this plugin can act on the retrieved remote source 258 | rather than rerequesting this information. 259 | * Major enhancement work is done in preprocessing now rather than post-processing 260 | * Refactoring 261 | * Render full mention content if short enough. Introduce MAX_INLINE_MENTION_LENGTH which defaults to 300 characters to implement same. 262 | * Fix text domain 263 | 264 | = 3.2.1 = 265 | 266 | * updated hooks/filters 267 | 268 | = 3.2.0 = 269 | 270 | * changed hook from `_post` to `comment_post` (thanks to @dshanske) 271 | * used the WordPress Coding Standard 272 | * small code improvements 273 | 274 | = 3.1.0 = 275 | * I18n support 276 | * German translation 277 | * some small changes and bugfixes 278 | 279 | = 3.0.5 = 280 | 281 | * quick fix to prevent crash if Mf2 lib is used by a second plugin 282 | 283 | = 3.0.4 = 284 | 285 | * added counter functions for comments by type (props to David Shanske) 286 | * some bugfixes 287 | 288 | = 3.0.3 = 289 | 290 | * some small tweaks 291 | * added custom comment classes based on the linkback-type (props to David Shanske for the idea) 292 | 293 | = 3.0.2 = 294 | 295 | * added support for threaded comments 296 | 297 | = 3.0.1 = 298 | 299 | * fixed bug in comments section 300 | 301 | = 3.0.0 = 302 | 303 | * nicer integration with trackbacks, linkbacks and webmentions 304 | * cleanup 305 | 306 | = 2.0.1 = 307 | 308 | * "via" links for indieweb "reply"s (thanks to @snarfed for the idea) 309 | * simplified output for all other indieweb "comment" types 310 | * better parser (thanks to voxpelly for his test-pinger) 311 | * now ready to use in a bundle 312 | 313 | = 2.0.0 = 314 | 315 | * initial release 316 | 317 | == Thanks to == 318 | 319 | * Pelle Wessman ([@voxpelli](https://github.com/voxpelli)) for his awesome [WebMention test-pinger](https://github.com/voxpelli/node-webmention-testpinger) 320 | * Ryan Barrett ([@snarfed](https://github.com/snarfed)) for his feedback and pull requests 321 | * Barnaby Walters ([@barnabywalters](https://github.com/barnabywalters)) for his awesome [mf2 parser](https://github.com/indieweb/php-mf2) 322 | * David Shanske ([@dshanske](https://github.com/dshanske)) for his feedback and a lot of pull requests 323 | * ([@acegiak](https://github.com/acegiak)) for the initial plugin 324 | 325 | == Installation == 326 | 327 | 1. Upload the `semantic-linkbacks`-folder to the `/wp-content/plugins/` directory 328 | 2. Activate the plugin through the *Plugins* menu in WordPress 329 | 3. ...and that's it :) 330 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![project status: inactive](https://img.shields.io/badge/project_status-inactive-red.svg?style=flat) 2 | 3 | The plugin was merged into the [Webmention plugin](https://github.com/pfefferle/wordpress-webmention). 4 | 5 | ---- 6 | 7 | # Semantic-Linkbacks # 8 | **Contributors:** [pfefferle](https://profiles.wordpress.org/pfefferle/), [dshanske](https://profiles.wordpress.org/dshanske/), [edent](https://profiles.wordpress.org/edent/) 9 | **Donate link:** https://notiz.blog/donate/ 10 | **Tags:** webmention, pingback, trackback, linkback, microformats, comments, indieweb 11 | **Requires at least:** 4.9 12 | **Requires PHP:** 5.6 13 | **Tested up to:** 6.1 14 | **Stable tag:** 3.11.3 15 | **License:** MIT 16 | **License URI:** http://opensource.org/licenses/MIT 17 | 18 | Richer Comments and Linkbacks for WordPress! 19 | 20 | ## Description ## 21 | 22 | Generates richer WordPress comments from linkbacks such as [Webmention](https://wordpress.org/plugins/webmention) or classic linkback protocols like Trackback or Pingback. 23 | 24 | The limited display for trackbacks and linkbacks is replaced by a clean full sentence, such as "Bob mentioned this article on bob.com." If Bob's site uses markup that the plugin can interpret, it may add his profile picture or other parts of his page to display as a full comment. It will optionally show collections of profile pictures, known as Facepiles, instead of the full setences. 25 | 26 | Semantic Linkbacks uses [Microformats 2](http://microformats.org/wiki/microformats2) to get information about the linked post and it is highly extensible to also add support for other forms of markup. 27 | 28 | ## Privacy and Data Collection ## 29 | 30 | This plugin collects data from sites that send webmentions for the purpose of displaying richer comments on a site. This data is under the control of the site owner. It is the personal responsibility of that individual or individuals to remove any information at the request of the original content creator. Over time, we will add additional tools to assist in doing so. 31 | 32 | ## Frequently Asked Questions ## 33 | 34 | ### Do I need to mark up my site? ### 35 | 36 | Most modern WordPress themes support the older Microformats standard, which means the plugin should be able to get basic information from to enhance linkbacks. The plugin is most useful with the [WordPress Webmention plugin](https://wordpress.org/plugins/webmention/) and sites/themes that support Microformats 2. 37 | 38 | ### Why Webmentions? ### 39 | 40 | [Webmention](http://indiewebcamp.com/webmention) is a modern reimplementation of Pingback and is now a W3C Recommendation. 41 | 42 | ### What about the semantic "comment" types? ### 43 | 44 | The IndieWeb community defines several types of feedback: 45 | 46 | * Replies: 47 | * Reposts: 48 | * Likes: 49 | * Favorites: 50 | * RSVPs: 51 | * Tagging: 52 | * Listen: 53 | * Watch: 54 | * Read: 55 | * Follow: 56 | * Classic "Mentions": 57 | 58 | ### How do I extend this plugin? ### 59 | 60 | See [Extensions](https://indieweb.org/Semantic_Linkbacks#Extensions) 61 | 62 | ### How do I add this into my plugin? ### 63 | 64 | The plugin will automatically enhance webmentions, trackbacks, and pingbacks with an avatar and additional context. It will also automatically add a facepile instead of individual comments, but this feature can either be turned off by an aware theme or under Discussion in your Settings. 65 | 66 | ### Why do some [emoji reactions](https://indieweb.org/reacji) not show up? ### 67 | 68 | Some emoji characters in webmentions you might receive, e.g. Facebook reactions from [Bridgy](https://brid.gy/), take more than two bytes to encode. (In technical terms, these Unicode characters are [above the Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode)).) To handle them, you need MySQL 5.5.3 or higher, and your database and tables need to use the [`utf8mb4` charset](https://dev.mysql.com/doc/refman/5.7/en/charset-mysql.html). [Usually WordPress does this automatically](https://make.wordpress.org/core/2015/04/02/the-utf8mb4-upgrade/), but not always. 69 | 70 | First, [follow these instructions](https://wordpress.stackexchange.com/questions/195046/relaunch-4-2-utf8mb4-databse-upgrade/244992#244992) to switch your MySQL database to `utf8mb4`. Then, make sure `DB_CHARSET` and `DB_COLLATE` in your `wp-config.php` are either unset, set to the blank string, or set to these values: 71 | 72 | define('DB_CHARSET', 'utf8mb4'); 73 | define('DB_COLLATE', 'utf8mb4_general_ci'); 74 | 75 | ### Who made the logos? ### 76 | 77 | The Webmention and Pingback logos are made by [Aaron Parecki](http://aaronparecki.com) and the Microformats logo is made by [Dan Cederholm](http://simplebits.com/work/microformats/). 78 | 79 | ### Why are you providing avatars? ### 80 | 81 | The plugin attempts to store the URL to an actual profile image on the source site. The default avatar set by WordPress is only used if there is no such image found. 82 | 83 | Even the WordPress default avatars are served by querying Gravatar.com which serves the file. Gravatar works by you providing an email address which it uses to match the image. 84 | The majority of linkbacks enhanced by this plugin do not have email addresses therefore we know that gravatar will not have anything on file. 85 | 86 | If there is no email address it will serve the local avatar. If there is an email, it will go out to gravatar.com and see if they 87 | have a gravatar on file. If there is it will store the gravatar URL, otherwise it will store the URL for the local avatar if set. 88 | 89 | The plugin uses a locally cached version of the mystery icon normally provided by WordPress and Gravatar. 90 | 91 | ## Changelog ## 92 | 93 | Project actively developed on Github at [pfefferle/wordpress-semantic-linkbacks](https://github.com/pfefferle/wordpress-semantic-linkbacks). Please file support issues there. 94 | 95 | ### 3.11.3 ### 96 | 97 | * Deactivate itself if Webmention Version is 5.0.0 or above 98 | 99 | ### 3.11.2 ### 100 | 101 | * Fix array access issue 102 | * Fix coding standards 103 | 104 | ### 3.11.1 ### 105 | 106 | * Fix HTML issue 107 | 108 | ### 3.11.0 ### 109 | 110 | * Fix AMP compatibility. Props to [Milind More](https://github.com/milindmore22). 111 | 112 | ### 3.10.4 ### 113 | 114 | * Whitelist `vendor` folder 115 | 116 | ### 3.10.3 ### 117 | 118 | * Remove jQuery dependency. Props to [Florian Brinkmann](https://github.com/florianbrinkmann). 119 | * Fix Facepile errors. Props to [Terence Eden](https://github.com/edent). 120 | * Remove refbacks as a default. Add a filter to decide on what comment types would be used. 121 | * Use `comment` as default comment-type: https://core.trac.wordpress.org/ticket/49236 122 | 123 | ### 3.10.2 ### 124 | 125 | * Fix default value of `semantic_linkbacks_facepiles` (props @prtksxna) 126 | 127 | ### 3.10.1 ### 128 | 129 | * Fix load issues with Webmention vs Semantic Linkbacks 130 | 131 | ### 3.10.0 ### 132 | 133 | * Use Webmentions avatar-handler if available 134 | 135 | ### 3.9.3 ### 136 | 137 | * Fixes https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/41 138 | 139 | ### 3.9.2 ### 140 | 141 | * Only load MF2 parser inside parsing function to ensure bundled version is loaded 142 | * Update development dependencies 143 | 144 | ### 3.9.1 ### 145 | 146 | * Quick fix facepile problem id comments are closed 147 | 148 | ### 3.9.0 ### 149 | 150 | * Support rel-alternate (mf2-json) 151 | * New php-mf2 version 152 | 153 | ### 3.8.1 ### 154 | 155 | * Add follow post as type 156 | * Add warning to settings page if php-mbstring not installed 157 | * Return false in Emoji function if php-mbstring not installed 158 | * Add approve link to emails sent 159 | 160 | ### 3.8.0 ### 161 | 162 | * Add locally hosted copy of the mystery man icon and serve it if there is no gravatar 163 | * Redo settings and settings page 164 | * Settings page to merge with Webmentions page if webmentions loaded 165 | 166 | ### 3.7.7 ### 167 | 168 | * Add read type 169 | * Capture read-status if available 170 | * Change string as previous tense was off 171 | 172 | ### 3.7.6 ### 173 | 174 | * Update Parser to version 0.4.3 175 | * Introduce watch and listen properties 176 | 177 | ### 3.7.5 ### 178 | 179 | * fixed Reacji UI ((#154)[https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/154]) 180 | 181 | ### 3.7.4 ### 182 | 183 | * Replace `rsvp-invite` property which is not in use with `invite` property and add unit tests 184 | * Enhance post type returns to include post, page, and sitename 185 | * Add basic person tagging support 186 | 187 | ### 3.7.3 ### 188 | 189 | * Replace tracking with interested property as noted on https://indieweb.org/rsvp 190 | * Remove `h-as` properties 191 | * Remove hard-coded microformats2 properties from facepile and move them to being generated from comment_class 192 | * Remove unused properties 193 | * Introduce type argument in list_linkbacks to generate unique ideas for each list of linkbacks without having to specify them using style and li-class 194 | * Whitelist property swarm-coins, used by [OwnYourSwarm](https://ownyourswarm.p3k.io/docs#coins) and display it if using built-in comment handler. 195 | 196 | ### 3.7.2 ### 197 | 198 | * Bugfix: "Normal comments" hidden in comment-section (https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/140) 199 | 200 | ### 3.7.1 ### 201 | 202 | * Fixed reacjis and facepiles 203 | 204 | ### 3.7.0 ### 205 | 206 | * Add settings to enable each type independently in the Facepile 207 | * Optionally render mentions as normal comments again 208 | * Support Reacji...aka single-emoji reactions 209 | * Bump minimum PHP to 5.4 due emoji detector library dependency issues 210 | * Overlay emoji on individual avatars in reactions facepile 211 | * Offer mf2 compatible template for comments 212 | * Fix semantic_linkbacks_cite filter as was previously filtering the entire comment text 213 | * Switch semantic_links_cite filter to filtering the format for the citation instead of the prepared citation 214 | * Count correct text length for unicode characters 215 | * Facepile Template improvements 216 | * Allow new comment template to be overridden by filter or theme declaring microformats2 support 217 | * Code standards compliance changes 218 | * Improved testing for PHP versions 5.4 and up to ensure compatibility 219 | * Remove direct calls to comment meta in favor of helper functions to ensure future proofing 220 | 221 | ### 3.6.0 ### 222 | 223 | * Only show the first 8 avatars in a facepile by default. If there are more, include a clickable ellipsis to show the rest. Customizable via the `FACEPILE_FOLD_LIMIT` constant. 224 | * Link facepile avatars to user profile/home page, not response post 225 | * Always show avatar images with correct aspect ratio 226 | 227 | ### 3.5.1 ### 228 | 229 | * Bugfix release 230 | 231 | ### 3.5.0 ### 232 | 233 | * Add Facepile code 234 | * Add setting to disable automatic facepile include 235 | * Add filter to allow themes to disable the setting and the feature if they facepile themselves 236 | * Add PHP requirement to readme file 237 | 238 | ### 3.4.1 ### 239 | 240 | * Abstract out linkback retrieval functions to allow for easier changes in future 241 | * Fix retrieval issue 242 | * Remove merge and compatibility function creating double slashing due update in 4.7.1 243 | * Replace blacklist for properties with whitelist for select properties 244 | * Update avatar function to not override if user_id is set on assumption local overrides remote 245 | 246 | ### 3.4.0 ### 247 | 248 | * Fix Tests and Error in Authorship 249 | * Update Parser 250 | * Switch to looser restrictions if WP_DEBUG is enabled and stricter ones otherwise 251 | * Enhance Author Properties to allow for retrieving remote h-card 252 | * Store mf2 properties 253 | * Store location in WordPress Geodata 254 | * Use rel-syndication if not u-syndication 255 | * Support new webmention source meta key 256 | 257 | ### 3.3.1 ### 258 | 259 | * fixed https://github.com/pfefferle/wordpress-semantic-linkbacks/issues/68 260 | 261 | ### 3.3.0 ### 262 | 263 | * Due to changes in WordPress 4.4 through 4.7 and version 3.0.0 of the Webmentions plugin this plugin can act on the retrieved remote source 264 | rather than rerequesting this information. 265 | * Major enhancement work is done in preprocessing now rather than post-processing 266 | * Refactoring 267 | * Render full mention content if short enough. Introduce MAX_INLINE_MENTION_LENGTH which defaults to 300 characters to implement same. 268 | * Fix text domain 269 | 270 | ### 3.2.1 ### 271 | 272 | * updated hooks/filters 273 | 274 | ### 3.2.0 ### 275 | 276 | * changed hook from `_post` to `comment_post` (thanks to @dshanske) 277 | * used the WordPress Coding Standard 278 | * small code improvements 279 | 280 | ### 3.1.0 ### 281 | * I18n support 282 | * German translation 283 | * some small changes and bugfixes 284 | 285 | ### 3.0.5 ### 286 | 287 | * quick fix to prevent crash if Mf2 lib is used by a second plugin 288 | 289 | ### 3.0.4 ### 290 | 291 | * added counter functions for comments by type (props to David Shanske) 292 | * some bugfixes 293 | 294 | ### 3.0.3 ### 295 | 296 | * some small tweaks 297 | * added custom comment classes based on the linkback-type (props to David Shanske for the idea) 298 | 299 | ### 3.0.2 ### 300 | 301 | * added support for threaded comments 302 | 303 | ### 3.0.1 ### 304 | 305 | * fixed bug in comments section 306 | 307 | ### 3.0.0 ### 308 | 309 | * nicer integration with trackbacks, linkbacks and webmentions 310 | * cleanup 311 | 312 | ### 2.0.1 ### 313 | 314 | * "via" links for indieweb "reply"s (thanks to @snarfed for the idea) 315 | * simplified output for all other indieweb "comment" types 316 | * better parser (thanks to voxpelly for his test-pinger) 317 | * now ready to use in a bundle 318 | 319 | ### 2.0.0 ### 320 | 321 | * initial release 322 | 323 | ## Thanks to ## 324 | 325 | * Pelle Wessman ([@voxpelli](https://github.com/voxpelli)) for his awesome [WebMention test-pinger](https://github.com/voxpelli/node-webmention-testpinger) 326 | * Ryan Barrett ([@snarfed](https://github.com/snarfed)) for his feedback and pull requests 327 | * Barnaby Walters ([@barnabywalters](https://github.com/barnabywalters)) for his awesome [mf2 parser](https://github.com/indieweb/php-mf2) 328 | * David Shanske ([@dshanske](https://github.com/dshanske)) for his feedback and a lot of pull requests 329 | * ([@acegiak](https://github.com/acegiak)) for the initial plugin 330 | 331 | ## Installation ## 332 | 333 | 1. Upload the `semantic-linkbacks`-folder to the `/wp-content/plugins/` directory 334 | 2. Activate the plugin through the *Plugins* menu in WordPress 335 | 3. ...and that's it :) 336 | -------------------------------------------------------------------------------- /tests/templates/aaronparecki-com.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @adactio Crossing my fingers that this post makes it! #indiewebcampuk #webmention - Aaron Parecki 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 63 | 64 | 65 | 66 | 67 |
    68 | 75 | 88 | 100 | 101 |
    102 |
    103 |
    104 |
    105 | 106 |
    107 | 114 |
    115 | 116 | 132 | 133 | 148 | 149 |
    150 |
    151 | 152 | 185 | 186 |
    187 |
    188 |
    189 | 190 | 191 | 224 |
    225 | 226 | 227 | 228 | 283 | 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /tests/templates/sandeep-io.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | sandeep.io 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 68 | 69 |
    70 |
    71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
    82 |
    83 |
    84 |
    Liked a post by Ben Werdmuller.
    85 |

    86 |

    What idno is

    87 | 88 |

    This site runs on idno: an open source social publishing platform that I've been working on for the past few months in my own time...

    89 |

    90 | 91 |

    # #

    92 |
    93 | 95 | 105 |
    106 |
    107 | 108 | 109 | 110 |
    111 |
    112 |
    113 |
    114 |

    Comments

    115 |
    116 |
    117 |
    118 | 119 |
    120 |
    121 |
    122 |

    Likes

    123 |
    124 |
    125 |
    126 | 127 |
    128 |
    129 |
    130 |

    Reposts

    131 |
    132 |
    133 |
    134 | 135 |
    136 |
    137 |
    138 |

    Mentions

    139 |
    140 |
    141 |
    142 |
    143 | 144 |
    145 |
    146 | 147 |
    148 |
    149 | 150 | 151 |
    152 |
    153 | 154 | 155 | 306 | 307 | 308 | 310 | 311 | 312 | 313 | 314 | 356 | 357 | 358 | 359 | -------------------------------------------------------------------------------- /includes/class-linkbacks-mf2-handler.php: -------------------------------------------------------------------------------- 1 | parse( true ); 149 | 150 | // check for rel-alternate links 151 | $alternate_source = self::get_alternate_source( $mf_array ); 152 | if ( $alternate_source ) { 153 | $mf_array = $alternate_source; 154 | } 155 | 156 | // get all 'relevant' entries 157 | $entries = self::get_entries( $mf_array ); 158 | 159 | if ( empty( $entries ) ) { 160 | return $commentdata; 161 | } 162 | 163 | // get the entry of interest 164 | $entry = self::get_representative_entry( $entries, $commentdata['target'] ); 165 | 166 | if ( empty( $entry ) ) { 167 | return $commentdata; 168 | } 169 | 170 | $commentdata['remote_source_mf2'] = $entry; 171 | $properties = array_filter( self::flatten_microformats( $entry ) ); 172 | $commentdata['remote_source_properties'] = $properties; 173 | $rels = $mf_array['rels']; 174 | $commentdata['remote_source_rels'] = $rels; 175 | 176 | // try to find some content 177 | // @link http://indieweb.org/comments-presentation 178 | if ( isset( $properties['summary'] ) ) { 179 | $commentdata['comment_content'] = wp_slash( $properties['summary'] ); 180 | } elseif ( isset( $properties['content'] ) ) { 181 | $commentdata['comment_content'] = wp_filter_kses( $properties['content']['html'] ); 182 | } elseif ( isset( $properties['name'] ) ) { 183 | $commentdata['comment_content'] = wp_slash( $properties['name'] ); 184 | } 185 | $commentdata['comment_content'] = trim( $commentdata['comment_content'] ); 186 | 187 | // set the right date 188 | if ( isset( $properties['published'] ) ) { 189 | $commentdata['comment_date'] = self::convert_time( $properties['published'] ); 190 | } elseif ( isset( $properties['updated'] ) ) { 191 | $commentdata['comment_date'] = self::convert_time( $properties['updated'] ); 192 | } 193 | $commentdata['comment_date_gmt'] = get_gmt_from_date( $commentdata['comment_date'] ); 194 | 195 | $author = null; 196 | 197 | // check if h-card has an author 198 | if ( isset( $properties['author'] ) ) { 199 | $author = $properties['author']; 200 | } else { 201 | $author = self::get_representative_author( $mf_array, $source ); 202 | $author = self::flatten_microformats( $author ); 203 | } 204 | // If this is an invite than the invite should be displayed instead of the author 205 | if ( isset( $properties['invitee'] ) ) { 206 | $author = $properties['invitee']; 207 | } 208 | 209 | // if author is present use the informations for the comment 210 | if ( $author ) { 211 | if ( ! is_array( $author ) ) { 212 | if ( self::is_url( $author ) ) { 213 | $response = Linkbacks_Handler::retrieve( $author ); 214 | if ( ! is_wp_error( $response ) ) { 215 | $parser = new Parser( wp_remote_retrieve_body( $response ), $author ); 216 | $author_array = $parser->parse( true ); 217 | $author = self::flatten_microformats( self::get_representative_author( $author_array, $author ) ); 218 | $properties['author'] = $author; 219 | } 220 | } else { 221 | $commentdata['comment_author'] = wp_slash( $author ); 222 | } 223 | } 224 | 225 | if ( is_array( $author ) ) { 226 | if ( ! isset( $author['me'] ) ) { 227 | if ( isset( $mf_array['rels']['me'] ) ) { 228 | $author['me'] = $mf_array['rels']['me']; 229 | $properties['author']['me'] = $author['me']; 230 | } 231 | } 232 | if ( isset( $author['name'] ) ) { 233 | $commentdata['comment_author'] = wp_slash( self::first( $author['name'] ) ); 234 | } 235 | 236 | if ( isset( $author['email'] ) ) { 237 | $commentdata['comment_author_email'] = wp_slash( self::first( $author['email'] ) ); 238 | } 239 | 240 | if ( isset( $author['url'] ) ) { 241 | $commentdata['comment_meta']['semantic_linkbacks_author_url'] = self::first( $author['url'] ); 242 | } 243 | 244 | if ( isset( $author['photo'] ) ) { 245 | $commentdata['comment_meta']['avatar'] = self::first( $author['photo'] ); 246 | } 247 | } 248 | } 249 | 250 | // set canonical url (u-url) 251 | if ( isset( $properties['url'] ) ) { 252 | $commentdata['comment_meta']['semantic_linkbacks_canonical'] = self::first( $properties['url'] ); 253 | // If the source URL and the canonical URL are not on the same domain, set this flag in the comment data to trigger actions in the Linkbacks_Handler class 254 | if ( wp_parse_url( $properties['url'], PHP_URL_HOST ) !== wp_parse_url( $source, PHP_URL_HOST ) ) { 255 | $commentdata['proxy_mention'] = 1; 256 | } 257 | } else { 258 | $commentdata['comment_meta']['semantic_linkbacks_canonical'] = esc_url_raw( $source ); 259 | } 260 | 261 | // If u-syndication is not set use rel syndication 262 | if ( array_key_exists( 'syndication', $rels ) && ! array_key_exists( 'syndication', $properties ) ) { 263 | $properties['syndication'] = $rels['syndication']; 264 | } 265 | 266 | // Check and parse for location property 267 | if ( array_key_exists( 'location', $properties ) ) { 268 | $location = $properties['location']; 269 | if ( is_array( $location ) ) { 270 | if ( array_key_exists( 'latitude', $location ) ) { 271 | $commentdata['comment_meta']['geo_latitude'] = self::first( $location['latitude'] ); 272 | } 273 | if ( array_key_exists( 'longitude', $location ) ) { 274 | $commentdata['comment_meta']['geo_longitude'] = self::first( $location['longitude'] ); 275 | } 276 | if ( array_key_exists( 'name', $location ) ) { 277 | $commentdata['comment_meta']['geo_address'] = self::first( $location['name'] ); 278 | } 279 | } else { 280 | if ( substr( $location, 0, 4 ) === 'geo:' ) { 281 | $geo = explode( ':', substr( urldecode( $location ), 4 ) ); 282 | $geo = explode( ';', $geo[0] ); 283 | $coords = explode( ',', $geo[0] ); 284 | $commentdata['comment_meta']['geo_latitude'] = trim( $coords[0] ); 285 | $commentdata['comment_meta']['geo_longitude'] = trim( $coords[1] ); 286 | } else { 287 | $commentdata['comment_meta']['geo_address'] = $location; 288 | } 289 | } 290 | } 291 | 292 | // check rsvp property 293 | if ( isset( $properties['rsvp'] ) ) { 294 | $commentdata['comment_meta']['semantic_linkbacks_type'] = wp_slash( 'rsvp:' . self::first( $properties['rsvp'] ) ); 295 | } else { 296 | // get post type 297 | $commentdata['comment_meta']['semantic_linkbacks_type'] = wp_slash( self::get_entry_type( $commentdata['target'], $entry, $mf_array ) ); 298 | } 299 | if ( isset( $properties['invitee'] ) ) { 300 | $commentdata['comment_meta']['semantic_linkbacks_type'] = wp_slash( 'invite' ); 301 | } 302 | 303 | // Check for person tagging 304 | if ( isset( $properties['category'] ) ) { 305 | if ( in_array( $commentdata['target'], $properties['category'], true ) ) { 306 | $commentdata['category'] = array( $commentdata['target'] ); 307 | } 308 | } 309 | 310 | $whitelist = array( 311 | 'author', 312 | 'location', 313 | 'syndication', 314 | 'uid', 315 | 'video', 316 | 'audio', 317 | 'photo', 318 | 'featured', 319 | 'swarm-coins', // https://ownyourswarm.p3k.io/docs#coins 320 | 'read-status', // https://indieweb.org/read 321 | ); 322 | 323 | // Add in supported properties 324 | $whitelist = array_merge( $whitelist, array_keys( self::get_class_mapper() ) ); 325 | $whitelist = apply_filters( 'semantic_linkbacks_mf2_props_whitelist', $whitelist ); 326 | foreach ( $properties as $key => $value ) { 327 | if ( in_array( $key, $whitelist, true ) ) { 328 | if ( self::is_url( $value ) ) { 329 | $value = esc_url_raw( $value ); 330 | } 331 | if ( is_string( $value ) ) { 332 | $value = wp_kses_post( $value ); 333 | } 334 | $commentdata['comment_meta'][ 'mf2_' . $key ] = $value; 335 | } 336 | } 337 | $commentdata['comment_meta'] = array_filter( $commentdata['comment_meta'] ); 338 | 339 | return $commentdata; 340 | } 341 | 342 | public static function convert_time( $time ) { 343 | $time = strtotime( $time ); 344 | // If it can't read the time it will return null which will mean the comment time will be set to now. 345 | if ( $time ) { 346 | return get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $time ), 'Y-m-d H:i:s' ); 347 | } 348 | return null; 349 | } 350 | 351 | public static function get_property( $key, $properties ) { 352 | if ( isset( $properties[ $key ] ) && isset( $properties[ $key ][0] ) ) { 353 | if ( is_array( $properties[ $key ] ) ) { 354 | $properties[ $key ] = array_unique( $properties[ $key ] ); 355 | } 356 | if ( 1 === count( $properties[ $key ] ) ) { 357 | return $properties[ $key ][0]; 358 | } 359 | return $properties[ $key ]; 360 | } 361 | return null; 362 | } 363 | 364 | /** 365 | * Returns the first item in $val if it's a non-empty array, otherwise $val itself. 366 | */ 367 | public static function first( $val ) { 368 | if ( $val && is_array( $val ) ) { 369 | return $val[0]; 370 | } 371 | return $val; 372 | } 373 | 374 | /** 375 | * Is string a URL. 376 | * 377 | * @param array $string 378 | * @return bool 379 | */ 380 | public static function is_url( $string ) { 381 | if ( ! is_string( $string ) ) { 382 | return false; 383 | } 384 | // If debugging is on just validate that URL is validly formatted 385 | if ( WP_DEBUG ) { 386 | return filter_var( $string, FILTER_VALIDATE_URL ) !== false; 387 | } 388 | // If debugging is off limit based on WordPress parameters 389 | return wp_http_validate_url( $string ); 390 | } 391 | 392 | // Accepted h types 393 | public static function is_h( $string ) { 394 | return in_array( $string, array( 'h-cite', 'h-entry', 'h-feed', 'h-product', 'h-event', 'h-review', 'h-recipe' ), true ); 395 | } 396 | 397 | public static function flatten_microformats( $item ) { 398 | $flat = array(); 399 | if ( 1 === count( $item ) ) { 400 | $item = $item[0]; 401 | } 402 | if ( array_key_exists( 'type', $item ) ) { 403 | // If there are multiple types strip out everything but the standard one. 404 | if ( 1 < count( $item['type'] ) ) { 405 | $item['type'] = array_filter( $item['type'], array( 'Linkbacks_MF2_Handler', 'is_h' ) ); 406 | } 407 | $flat['type'] = $item['type'][0]; 408 | } 409 | if ( array_key_exists( 'properties', $item ) ) { 410 | $properties = $item['properties']; 411 | foreach ( $properties as $key => $value ) { 412 | $flat[ $key ] = self::get_property( $key, $properties ); 413 | 414 | if ( ! is_string( $flat[ $key ] ) && 1 < count( $flat[ $key ] ) ) { 415 | $flat[ $key ] = self::flatten_microformats( $flat[ $key ] ); 416 | } 417 | } 418 | } else { 419 | $flat = $item; 420 | } 421 | foreach ( $flat as $key => $value ) { 422 | // Sanitize all URL properties 423 | if ( self::is_url( $value ) ) { 424 | $flat[ $key ] = esc_url_raw( $value ); 425 | } 426 | } 427 | 428 | // If name and URL are the same, remove name. 429 | if ( array_key_exists( 'name', $flat ) && array_key_exists( 'url', $flat ) ) { 430 | if ( $flat['name'] === $flat['url'] ) { 431 | unset( $flat['name'] ); 432 | } 433 | } 434 | 435 | // Duplicate url values for a property may be caused by implied urls https://github.com/indieweb/php-mf2/issues/110 436 | if ( array_key_exists( 'url', $flat ) && is_array( $flat['url'] ) ) { 437 | $flat['url'] = $flat['url'][0]; 438 | } 439 | $flat = array_filter( $flat ); 440 | return $flat; 441 | } 442 | 443 | public static function has_alternate_url( $mf_array ) { 444 | return (bool) self::get_alternate_url( $mf_array ); 445 | } 446 | 447 | public static function get_alternate_url( $mf_array ) { 448 | if ( ! array_key_exists( 'rel-urls', $mf_array ) ) { 449 | return false; 450 | } 451 | 452 | foreach ( $mf_array['rel-urls'] as $url => $meta ) { 453 | if ( 454 | isset( $meta['type'] ) && 455 | 'application/mf2+json' === trim( $meta['type'] ) && 456 | isset( $meta['rels'] ) && 457 | in_array( 'alternate', $meta['rels'], true ) && 458 | filter_var( $url, FILTER_VALIDATE_URL ) !== false 459 | ) { 460 | return $url; 461 | } 462 | } 463 | 464 | return false; 465 | } 466 | 467 | public static function get_alternate_source( $mf_array ) { 468 | $url = self::get_alternate_url( $mf_array ); 469 | 470 | if ( ! $url ) { 471 | return false; 472 | } 473 | 474 | $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ); 475 | $args = array( 476 | 'timeout' => 100, 477 | 'limit_response_size' => 153600, 478 | 'redirection' => 20, 479 | 'user-agent' => "$user_agent; Semantic-Linkbacks/Webmention read rel-alternate source", 480 | ); 481 | 482 | $response = wp_safe_remote_get( $url, $args ); 483 | // check if source is accessible 484 | if ( is_wp_error( $response ) ) { 485 | return false; 486 | } 487 | 488 | $body = wp_remote_retrieve_body( $response ); 489 | 490 | return json_decode( $body, true ); 491 | } 492 | 493 | /** 494 | * get all h-entry items 495 | * 496 | * @param array $mf_array the microformats array 497 | * @param array the h-entry array 498 | * 499 | * @return array 500 | */ 501 | public static function get_entries( $mf_array ) { 502 | $entries = array(); 503 | 504 | // some basic checks 505 | if ( ! is_array( $mf_array ) ) { 506 | return $entries; 507 | } 508 | if ( ! isset( $mf_array['items'] ) ) { 509 | return $entries; 510 | } 511 | if ( 0 === count( $mf_array['items'] ) ) { 512 | return $entries; 513 | } 514 | 515 | // get first item 516 | $first_item = $mf_array['items'][0]; 517 | 518 | // check if it is an h-feed 519 | if ( isset( $first_item['type'] ) && 520 | in_array( 'h-feed', $first_item['type'], true ) && 521 | isset( $first_item['children'] ) ) { 522 | $mf_array['items'] = $first_item['children']; 523 | } 524 | 525 | // iterate array 526 | foreach ( $mf_array['items'] as $mf ) { 527 | if ( isset( $mf['type'] ) && in_array( 'h-entry', $mf['type'], true ) ) { 528 | $entries[] = $mf; 529 | } 530 | } 531 | 532 | // return entries 533 | return $entries; 534 | } 535 | 536 | /** 537 | * helper to find the correct author node 538 | * 539 | * @param array $mf_array the parsed microformats array 540 | * @param string $source the source url 541 | * 542 | * @return array|null the h-card node or null 543 | */ 544 | public static function get_representative_author( $mf_array, $source ) { 545 | foreach ( $mf_array['items'] as $mf ) { 546 | if ( isset( $mf['type'] ) ) { 547 | if ( in_array( 'h-card', $mf['type'], true ) ) { 548 | // check domain 549 | if ( isset( $mf['properties'] ) && isset( $mf['properties']['url'] ) ) { 550 | foreach ( $mf['properties']['url'] as $url ) { 551 | if ( wp_parse_url( $url, PHP_URL_HOST ) === wp_parse_url( $source, PHP_URL_HOST ) ) { 552 | if ( isset( $mf_array['rels']['me'] ) ) { 553 | $mf['properties']['me'] = $mf_array['rels']['me']; 554 | } 555 | return $mf; 556 | } 557 | } 558 | } 559 | } 560 | } 561 | } 562 | 563 | // If there is no h-card then return rel=author and see what can be done with it 564 | if ( isset( $mf_array['rels']['author'] ) ) { 565 | return $mf_array['rels']['author']; 566 | } 567 | 568 | return null; 569 | } 570 | 571 | /** 572 | * helper to find the correct h-entry node 573 | * 574 | * @param array $mf_array the parsed microformats array 575 | * @param string $target the target url 576 | * 577 | * @return array the h-entry node or false 578 | */ 579 | public static function get_representative_entry( $entries, $target ) { 580 | // iterate array 581 | foreach ( $entries as $entry ) { 582 | // check properties 583 | if ( isset( $entry['properties'] ) ) { 584 | // check properties if target urls was mentioned 585 | foreach ( $entry['properties'] as $key => $values ) { 586 | // check "normal" links 587 | if ( self::compare_urls( $target, $values ) ) { 588 | return $entry; 589 | } 590 | 591 | // check included h-* formats and their links 592 | foreach ( $values as $obj ) { 593 | // check if reply is a "cite" 594 | if ( isset( $obj['type'] ) && array_intersect( array( 'h-cite', 'h-entry' ), $obj['type'] ) ) { 595 | // check url 596 | if ( isset( $obj['properties'] ) && isset( $obj['properties']['url'] ) ) { 597 | // check target 598 | if ( self::compare_urls( $target, $obj['properties']['url'] ) ) { 599 | return $entry; 600 | } 601 | } 602 | } 603 | } 604 | } 605 | 606 | // check properties if target urls was mentioned 607 | foreach ( $entry['properties'] as $key => $values ) { 608 | // check content for the link 609 | if ( 'content' === $key && 610 | preg_match_all( '/]+?' . preg_quote( $target, '/' ) . '[^>]*>([^>]+?)<\/a>/i', $values[0]['html'], $context ) ) { 611 | return $entry; 612 | } elseif ( 'summary' === $key && 613 | preg_match_all( '/]+?' . preg_quote( $target, '/' ) . '[^>]*>([^>]+?)<\/a>/i', $values[0], $context ) ) { 614 | return $entry; 615 | } 616 | } 617 | } 618 | } 619 | 620 | // return first h-entry 621 | return $entries[0]; 622 | } 623 | 624 | /** 625 | * check entry classes or document rels for post-type 626 | * 627 | * @param string $target the target url 628 | * @param array $entry the represantative entry 629 | * @param array $mf_array the document 630 | * 631 | * @return string the post-type 632 | */ 633 | public static function get_entry_type( $target, $entry, $mf_array = array() ) { 634 | $classes = self::get_class_mapper(); 635 | 636 | // check properties for target-url 637 | foreach ( $entry['properties'] as $key => $values ) { 638 | // check u-* params 639 | if ( in_array( $key, array_keys( $classes ), true ) ) { 640 | // check "normal" links 641 | if ( self::compare_urls( $target, $values ) ) { 642 | return $classes[ $key ]; 643 | } 644 | 645 | // iterate in-reply-tos 646 | foreach ( $values as $obj ) { 647 | // check if reply is a "cite" or "entry" 648 | if ( isset( $obj['type'] ) && array_intersect( array( 'h-cite', 'h-entry' ), $obj['type'] ) ) { 649 | // check url 650 | if ( isset( $obj['properties'] ) && isset( $obj['properties']['url'] ) ) { 651 | // check target 652 | if ( self::compare_urls( $target, $obj['properties']['url'] ) ) { 653 | return $classes[ $key ]; 654 | } 655 | } 656 | } 657 | } 658 | } 659 | } 660 | 661 | // check if site has any rels 662 | if ( ! isset( $mf_array['rels'] ) ) { 663 | return 'mention'; 664 | } 665 | 666 | $rels = self::get_rel_mapper(); 667 | 668 | // check rels for target-url 669 | foreach ( $mf_array['rels'] as $key => $values ) { 670 | // check rel params 671 | if ( in_array( $key, array_keys( $rels ), true ) ) { 672 | foreach ( $values as $value ) { 673 | if ( $value === $target ) { 674 | return $rels[ $key ]; 675 | } 676 | } 677 | } 678 | } 679 | 680 | return 'mention'; 681 | } 682 | 683 | /** 684 | * compare an url with a list of urls 685 | * 686 | * @param string $needle the target url 687 | * @param array $haystack a list of urls 688 | * @param boolean $schemelesse define if the target url should be checked with http:// and https:// 689 | * 690 | * @return boolean 691 | */ 692 | public static function compare_urls( $needle, $haystack, $schemeless = true ) { 693 | if ( ! self::is_url( $needle ) ) { 694 | return false; 695 | } 696 | if ( is_array( reset( $haystack ) ) ) { 697 | return false; 698 | } 699 | if ( true === $schemeless ) { 700 | // remove url-scheme 701 | $schemeless_target = preg_replace( '/^https?:\/\//i', '', $needle ); 702 | 703 | // add both urls to the needle 704 | $needle = array( 'http://' . $schemeless_target, 'https://' . $schemeless_target ); 705 | } else { 706 | // make $needle an array 707 | $needle = array( $needle ); 708 | } 709 | 710 | // compare both arrays 711 | return array_intersect( $needle, $haystack ); 712 | } 713 | } 714 | --------------------------------------------------------------------------------