├── .gitignore ├── public ├── media │ ├── sunset.jpg │ ├── city-at-night.jpg │ ├── w3c-socialwg.gif │ └── micropub-rocks.png ├── assets │ ├── micropub-rocks.png │ ├── micropub-rocks-icon.png │ ├── themes │ │ └── default │ │ │ └── assets │ │ │ └── fonts │ │ │ ├── icons.eot │ │ │ ├── icons.otf │ │ │ ├── icons.ttf │ │ │ ├── icons.woff │ │ │ └── icons.woff2 │ ├── entry.css │ ├── style.css │ └── common.js └── index.php ├── views ├── client-tests │ ├── success.php │ ├── errors.php │ ├── not-found.php │ ├── entry.php │ ├── basic.php │ └── update.php ├── partials │ ├── server-test-row.php │ ├── client-test-row.php │ ├── query-source-test.php │ ├── delete-test.php │ ├── update-test-basic.php │ ├── media-endpoint-test.php │ └── undelete-test.php ├── auth-email.php ├── server-tests │ ├── 700.php │ ├── 701.php │ ├── 702.php │ ├── 500.php │ ├── 501.php │ ├── 502.php │ ├── 503.php │ ├── 400.php │ ├── 602.php │ ├── 402.php │ ├── 401.php │ ├── 403.php │ ├── 404.php │ ├── 603.php │ ├── 600.php │ ├── 801.php │ ├── 800.php │ ├── 803.php │ ├── 100.php │ ├── 805.php │ ├── 200.php │ ├── 601.php │ ├── 804.php │ ├── 300.php │ ├── 301.php │ ├── 101.php │ ├── 107.php │ ├── 203.php │ ├── 202.php │ ├── 104.php │ ├── 205.php │ ├── 201.php │ ├── 206.php │ ├── 405.php │ ├── 204.php │ └── 802.php ├── auth-error.php ├── edit-endpoint.php ├── edit-client.php ├── client-info.php ├── client-auth.php ├── view-implementation-report.php ├── reports │ └── servers.php ├── layout.php ├── index.php ├── server-tests.php ├── dashboard.php ├── auth-start.php ├── implementation-report.php ├── client-tests.php └── implementation-report-client.php ├── app ├── ClientReports.php ├── Auth.php └── ServerTests.php ├── README.md ├── composer.json ├── lib ├── config.template.php ├── Rocks │ └── Redis.php └── helpers.php ├── database └── schema.sql └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib/config.php 3 | vendor/ 4 | issues-micropub.rocks.html 5 | -------------------------------------------------------------------------------- /public/media/sunset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/media/sunset.jpg -------------------------------------------------------------------------------- /public/media/city-at-night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/media/city-at-night.jpg -------------------------------------------------------------------------------- /public/media/w3c-socialwg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/media/w3c-socialwg.gif -------------------------------------------------------------------------------- /public/assets/micropub-rocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/micropub-rocks.png -------------------------------------------------------------------------------- /public/media/micropub-rocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/media/micropub-rocks.png -------------------------------------------------------------------------------- /public/assets/micropub-rocks-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/micropub-rocks-icon.png -------------------------------------------------------------------------------- /views/client-tests/success.php: -------------------------------------------------------------------------------- 1 |
2 |

Great! You passed test !

3 |
4 | -------------------------------------------------------------------------------- /public/assets/themes/default/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/themes/default/assets/fonts/icons.eot -------------------------------------------------------------------------------- /public/assets/themes/default/assets/fonts/icons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/themes/default/assets/fonts/icons.otf -------------------------------------------------------------------------------- /public/assets/themes/default/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/themes/default/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /public/assets/themes/default/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/themes/default/assets/fonts/icons.woff -------------------------------------------------------------------------------- /public/assets/themes/default/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/micropub.rocks/main/public/assets/themes/default/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /views/client-tests/errors.php: -------------------------------------------------------------------------------- 1 |
2 |

There were one or more problems with your request. Please see the details below.

3 | 8 | 9 | -------------------------------------------------------------------------------- /views/partials/server-test-row.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /views/partials/client-test-row.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /views/client-tests/not-found.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => 'Not Found', 3 | 'client' => $client, 4 | 'test' => false 5 | ]); ?> 6 |
7 |
8 | This test is not yet implemented 9 |
10 |
layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 | 8 |
9 |
Great!
10 |

Check your email for a login link!

11 |
12 |
13 | -------------------------------------------------------------------------------- /views/server-tests/700.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'config', 8 | ]); 9 | 10 | 11 | $this->insert('partials/media-endpoint-test', [ 12 | 'test' => $test, 13 | 'endpoint' => $endpoint, 14 | 'filename' => 'sunset.jpg', 15 | 'content_type' => 'image/jpeg', 16 | 'query_url' => $query_url 17 | ]); 18 | -------------------------------------------------------------------------------- /views/server-tests/701.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'config', 8 | ]); 9 | 10 | 11 | $this->insert('partials/media-endpoint-test', [ 12 | 'test' => $test, 13 | 'endpoint' => $endpoint, 14 | 'filename' => 'micropub-rocks.png', 15 | 'content_type' => 'image/png', 16 | 'query_url' => $query_url 17 | ]); 18 | -------------------------------------------------------------------------------- /views/server-tests/702.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'config', 8 | ]); 9 | 10 | 11 | $this->insert('partials/media-endpoint-test', [ 12 | 'test' => $test, 13 | 'endpoint' => $endpoint, 14 | 'filename' => 'w3c-socialwg.gif', 15 | 'content_type' => 'image/gif', 16 | 'query_url' => $query_url 17 | ]); 18 | -------------------------------------------------------------------------------- /app/ClientReports.php: -------------------------------------------------------------------------------- 1 | getBody()->write(view('reports/clients', [ 13 | 'title' => 'Client Reports - Micropub Rocks!', 14 | ])); 15 | return $response; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /views/server-tests/500.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/delete-test', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test creates a post, and then deletes it.', 10 | 'postbody' => 'h=entry&content=This+post+will+be+deleted+when+the+test+succeeds.', 11 | 'content_type' => 'form', 12 | 'deletebody' => 'action=delete&url=%%%', 13 | 'feature_num' => 23, 14 | ]); 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropub.rocks 2 | Micropub test suite and debugging utility 3 | 4 | ## Setup 5 | * Install Redis and MySQL 6 | * Install composer https://getcomposer.org/ 7 | * Run `composer install` from the project folder 8 | * Copy `lib/config.template.php` to `lib/config.php` and fill in the details for your Redis and MySQL instance 9 | * Run the SQL in `database/schema.sql` and `database/data.sql` to initialize the database 10 | * Run `php -S 127.0.0.1:8080 -t public` to start the built in web server 11 | * Visit http://127.0.0.1:8080 12 | 13 | -------------------------------------------------------------------------------- /views/server-tests/501.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/delete-test', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test creates a post, and then deletes it with a JSON payload.', 10 | 'postbody' => '{"type":["h-entry"],"properties":{"content":["This post will be deleted when the test succeeds."]}}', 11 | 'content_type' => 'json', 12 | 'deletebody' => '{"action":"delete","url":"%%%"}', 13 | 'feature_num' => 24, 14 | ]); 15 | -------------------------------------------------------------------------------- /views/server-tests/502.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/undelete-test', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test creates a post, deletes it, and then undeletes it.', 10 | 'postbody' => 'h=entry&content=This+post+will+be+deleted,+and+should+be+restored+after+undeleting+it.', 11 | 'content_type' => 'form', 12 | 'deletebody' => 'action=delete&url=%%%', 13 | 'undeletebody' => 'action=undelete&url=%%%', 14 | 'feature_num' => 25, 15 | ]); 16 | -------------------------------------------------------------------------------- /views/server-tests/503.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/undelete-test', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test creates a post, deletes it, and then undeletes it.', 10 | 'postbody' => '{"type":["h-entry"],"properties":{"content":["This post will be deleted, and should be restored after undeleting it."]}}', 11 | 'content_type' => 'json', 12 | 'deletebody' => '{"action":"delete","url":"%%%"}', 13 | 'undeletebody' => '{"action":"undelete","url":"%%%"}', 14 | 'feature_num' => 26, 15 | ]); 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "league/route": "^2.0", 4 | "league/plates": "^3.1", 5 | "predis/predis": "^1.1", 6 | "guzzlehttp/guzzle": "^6.3", 7 | "ezyang/htmlpurifier": "^4.8", 8 | "mf2/mf2": "^0.3.2", 9 | "indieauth/client": ">=0.4.1", 10 | "j4mie/idiorm": "^1.5", 11 | "zendframework/zend-diactoros": "^1.3", 12 | "mailgun/mailgun-php": "^2.1", 13 | "php-http/guzzle6-adapter": "^1.1", 14 | "firebase/php-jwt": "^4.0" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Rocks\\": "lib/Rocks/", 19 | "App\\": "app/" 20 | }, 21 | "files": [ 22 | "lib/helpers.php" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /views/auth-error.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 | 8 |
9 |
e($error) ?>
10 |

e($error_description) ?>

11 | 12 |
13 | 14 | Start Over 15 |
16 | 17 | insert($include); 20 | } 21 | ?> 22 |
23 | -------------------------------------------------------------------------------- /views/server-tests/400.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/update-test-basic', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test will create a post, then attempt to replace a property in the post.', 10 | 'feature_num' => 16, 11 | 'postbody' => '{ 12 | "type": ["h-entry"], 13 | "properties": { 14 | "content": ["Micropub update test. This text should be replaced if the test succeeds."] 15 | } 16 | }', 17 | 'updatebody' => '{ 18 | "action": "update", 19 | "url": "%%%", 20 | "replace": { 21 | "content": ["This is the updated text. If you can see this you passed the test!"] 22 | } 23 | }', 24 | ]); 25 | -------------------------------------------------------------------------------- /views/server-tests/602.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'source', 8 | 'url' => '%%%', 9 | ]); 10 | 11 | $description = 'This test will check if your endpoint supports the "source" query to retrieve the original content in a post. This test starts by creating a post at your endpoint, then when you click "Continue", it will query your endpoint to ask for the source content.'; 12 | 13 | $this->insert('partials/query-source-test', [ 14 | 'test' => $test, 15 | 'endpoint' => $endpoint, 16 | 'description' => $description, 17 | 'query_url' => $query_url, 18 | 'feature_num' => 30 19 | ]); 20 | -------------------------------------------------------------------------------- /views/server-tests/402.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/update-test-basic', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test will create a post, then attempt to add a category property to the post.', 10 | 'feature_num' => 33, 11 | 'postbody' => '{ 12 | "type": ["h-entry"], 13 | "properties": { 14 | "content": ["This test adds a category property to a post that previously had no category. After you run the update, this post should have the category test1."] 15 | } 16 | }', 17 | 'updatebody' => '{ 18 | "action": "update", 19 | "url": "%%%", 20 | "add": { 21 | "category": ["test1"] 22 | } 23 | }', 24 | ]); 25 | -------------------------------------------------------------------------------- /views/server-tests/401.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/update-test-basic', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test will create a post, then attempt to add a value to an existing property in the post.', 10 | 'feature_num' => 17, 11 | 'postbody' => '{ 12 | "type": ["h-entry"], 13 | "properties": { 14 | "content": ["Micropub update test for adding a category. After you run the update, this post should have two categories: test1 and test2."], 15 | "category": ["test1"] 16 | } 17 | }', 18 | 'updatebody' => '{ 19 | "action": "update", 20 | "url": "%%%", 21 | "add": { 22 | "category": ["test2"] 23 | } 24 | }', 25 | ]); 26 | -------------------------------------------------------------------------------- /views/server-tests/403.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/update-test-basic', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test will create a post with two categories, then attempt to delete one of the categories.', 10 | 'feature_num' => 18, 11 | 'postbody' => '{ 12 | "type": ["h-entry"], 13 | "properties": { 14 | "content": ["This test deletes a category from the post. After you run the update, this post should have only the category test1."], 15 | "category": ["test1", "test2"] 16 | } 17 | }', 18 | 'updatebody' => '{ 19 | "action": "update", 20 | "url": "%%%", 21 | "delete": { 22 | "category": ["test2"] 23 | } 24 | }', 25 | ]); 26 | -------------------------------------------------------------------------------- /views/server-tests/404.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $this->insert('partials/update-test-basic', [ 7 | 'test' => $test, 8 | 'endpoint' => $endpoint, 9 | 'description' => 'This test will create a post with two categories, then attempt to delete the category property completely.', 10 | 'feature_num' => 19, 11 | 'postbody' => '{ 12 | "type": ["h-entry"], 13 | "properties": { 14 | "content": ["This test deletes the category property from the post. After you run the update, this post should have no categories."], 15 | "category": ["test1", "test2"] 16 | } 17 | }', 18 | 'updatebody' => '{ 19 | "action": "update", 20 | "url": "%%%", 21 | "delete": [ 22 | "category" 23 | ] 24 | }', 25 | ]); 26 | -------------------------------------------------------------------------------- /views/server-tests/603.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'source', 8 | 'properties' => ['content','category'], 9 | 'url' => '%%%', 10 | ]); 11 | 12 | $description = 'This test will check if your endpoint supports the "source" query to retrieve the original content in a post. This test starts by creating a post at your endpoint, then when you click "Continue", it will query your endpoint to return the source properties specified.'; 13 | 14 | $this->insert('partials/query-source-test', [ 15 | 'test' => $test, 16 | 'endpoint' => $endpoint, 17 | 'description' => $description, 18 | 'query_url' => $query_url, 19 | 'feature_num' => 31 20 | ]); 21 | 22 | -------------------------------------------------------------------------------- /lib/config.template.php: -------------------------------------------------------------------------------- 1 | '', 22 | 'domain' => '', 23 | 'from' => '"micropub.rocks" ' 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /views/edit-endpoint.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 | 8 |
9 | 10 |

Editing Endpoint

11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /views/edit-client.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 | 8 |
9 | 10 |

Editing Client

11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 |

If your Micropub client expects to be able to discover an alternate profile URL for users, you can enter a URL here. This will be rendered on the simulated user's profile page in a rel=me link.

20 | 21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /views/client-info.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | 'link_tag' => ' 4 | 5 | 6 | 7 | ' 8 | ]); ?> 9 | 10 |
11 |
12 |

e($client->name) ?>

13 |

This a user profile page for testing the Micropub client e($client->name) ?>. Typically this would be the user's home page. This page advertises the user's Micropub endpoint and authorization endpoint that clients use when signing the user in.

14 | 15 | profile_url): ?> 16 | e($client->profile_url) ?> 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /views/client-auth.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 |

An application would like to connect to your account

8 | 9 | 10 |

An application is attempting to post to your micropub.rocks test website. However, there was a problem with the authorization request.

11 | 12 | 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 |

Do you want to authorize e($client_id) ?> to post to your micropub.rocks test website?

22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /views/client-tests/entry.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Micropub Rocks! 5 |
6 |
7 | 8 |

e(mf2_val($name)) ?>

9 | 10 | 11 |
e(mf2_val($content)) ?>
12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | e(implode(' ', array_map(function($el){ if($t=mf2_val($el)) { return '#'.$t; }; }, $category))) ?> 26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /views/client-tests/basic.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | 'client' => $client, 4 | 'test' => $test 5 | ]); ?> 6 |
7 | 8 |
9 |

number . ': ' . $test->name) ?>

10 | 11 | description ?> 12 | 13 |
14 | 15 | 18 | 22 | 23 |
24 | 46 | -------------------------------------------------------------------------------- /views/view-implementation-report.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 | id == $endpoint->user_id): ?> 9 |
10 |

Your report is published! Copy the URL below to share your report.

11 | 12 |
13 | edit 14 | 15 | 16 |

Implementation Report

17 | 18 |

19 | 20 | implementation_name) ?> 21 | 22 |

23 | 24 |

by 25 | developer_name) ?> 26 | 27 |

28 | 29 |

Programming Language: programming_language) ?>

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
number ?>implements) ?>description ?>
40 | 41 |
42 |
43 | -------------------------------------------------------------------------------- /views/client-tests/update.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | 'client' => $client, 4 | 'test' => $test 5 | ]); ?> 6 |
7 | 8 |
9 |

number . ': ' . $test->name) ?>

10 | 11 | description ?> 12 |

The post's URL to reference in the request is:
13 |

14 | 15 |
16 | 17 |
18 | 19 |
20 | 24 | 25 |
26 | 47 | -------------------------------------------------------------------------------- /views/reports/servers.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

Micropub Server Implementation Reports

9 | 10 | 11 | 12 | 13 | $endpoint): ?> 14 | 19 | 20 | 21 | $result): ?> 22 | 23 | 24 | 25 | 26 | 27 | $endpoint): ?> 28 | 31 | 32 | 33 | 34 |
15 | 16 | 17 | 18 |
29 | id]) ? $result[$endpoint->id] : 0 ) ?> 30 |
35 | 36 | 46 | 47 |

view server report summary 48 |

49 | 50 |
51 | -------------------------------------------------------------------------------- /lib/Rocks/Redis.php: -------------------------------------------------------------------------------- 1 | setex($redis_key, 60*60*24*7, $html); 11 | $redis_key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':raw'; 12 | redis()->setex($redis_key, 60*60*24*7, $raw); 13 | if($properties) { 14 | $redis_key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':properties'; 15 | redis()->setex($redis_key, 60*60*24*7, json_encode($properties)); 16 | } 17 | } 18 | 19 | public static function getPostHTML($client, $num, $key) { 20 | $redis_key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':html'; 21 | $html = redis()->get($redis_key); 22 | $redis_key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':raw'; 23 | $raw = redis()->get($redis_key); 24 | $redis_key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':properties'; 25 | $properties = json_decode(redis()->get($redis_key), true); 26 | return [$html, $raw, $properties]; 27 | } 28 | 29 | public static function storePostImage($client, $num, $key, $img) { 30 | $key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':img'; 31 | redis()->setex($key, 60*60*24*1, base64_encode($img)); 32 | } 33 | 34 | public static function getPostImage($client, $num, $key) { 35 | $key = Config::$base . ':' . $client . ':' . $num . ':' . $key . ':img'; 36 | $data = redis()->get($key); 37 | if($data) 38 | return base64_decode($data); 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /views/layout.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <?= $this->e($title) ?> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 39 | 40 | section('content') ?> 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/assets/entry.css: -------------------------------------------------------------------------------- 1 | .post-container .post-main { 2 | display: flex; 3 | flex-direction: row; 4 | 5 | border-radius: .28571429rem; 6 | border: 1px #abcce9 solid; 7 | 8 | background: #fff; 9 | padding: 12px; 10 | 11 | margin-bottom: 12px; 12 | } 13 | .post-container .post-main .meta { 14 | font-size: 10pt; 15 | } 16 | .post-container .post-main .meta a { 17 | color: #777; 18 | } 19 | .post-container .post-main .meta a:hover { 20 | color: #999; 21 | } 22 | .post-container .post-main .meta { 23 | color: #777; 24 | } 25 | 26 | @media(max-width: 616px) { 27 | .post-container { 28 | margin-left: 8px; 29 | margin-right: 8px; 30 | } 31 | } 32 | 33 | @media(max-width: 400px) { 34 | .post-container .post-main { 35 | display: block; 36 | } 37 | .post-container .left { 38 | float: left; 39 | } 40 | } 41 | 42 | .post-container .left { 43 | flex: 0 auto; /* this needs to be "0 auto" for Safari, but works as "0" for everything else */ 44 | padding-right: 12px; 45 | } 46 | .post-container .right { 47 | flex: 1; 48 | display: flex; 49 | flex-direction: column; 50 | } 51 | .post-container .right .content { 52 | flex: auto; 53 | } 54 | .post-container .right .meta { 55 | flex: 0 auto; 56 | } 57 | .post-container img { 58 | background: white; 59 | border: 1px #abcce9 solid; 60 | border-radius: .28571429rem; 61 | max-width: 100%; 62 | } 63 | 64 | 65 | 66 | /* name of the test */ 67 | .post-container h1 { 68 | margin: 0; 69 | font-size: 16pt; 70 | font-weight: bold; 71 | } 72 | /* name if a comment has the name, or if the comment text includes h tags */ 73 | .post-container .post-responses h1, 74 | .post-container .post-responses h2, 75 | .post-container .post-responses h3 { 76 | font-size: 15pt; 77 | font-weight: bold; 78 | } 79 | .post-container .post-responses h4, 80 | .post-container .post-responses h5, 81 | .post-container .post-responses h6 { 82 | font-size: 13pt; 83 | font-weight: bold; 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /views/server-tests/600.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'config', 8 | ]); 9 | ?> 10 |
11 | 12 |
13 |

number . ': ' . $test->name) ?>

14 | 15 |

This test will check if your endpoint supports the "config" query. To pass this test, your endpoint must return HTTP 200 and a JSON object in the response. The JSON object can be an empty object if the endpoint has nothing to report, or can include media-endpoint and/or syndicate-to properties.

16 |

Clicking "Run" will make the following request to your endpoint.

17 |
18 | 19 |
20 |
GET  HTTP/1.1
21 | Authorization: Bearer access_token."\n" ?>
22 |
23 | 24 |
25 | 26 |
    27 |
  •   Returned HTTP 200
  • 28 |
  •   Returned a JSON object
  • 29 |
30 |
31 | 32 | 36 | 37 |
38 | 60 | -------------------------------------------------------------------------------- /views/server-tests/801.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This test will send the access token in the body of the form-encoded request.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Content-type: application/x-www-form-urlencoded; charset=utf-8
17 | 
18 | h=entry&
19 | content=Testing+accepting+access+token+in+post+body&
20 | access_token=access_token ?>
21 |
22 | 23 |
24 | 25 |
    26 |
  •   Returned HTTP 201 or 202
  • 27 |
  •   Returned a Location header
  • 28 |
29 |
30 | 31 | 35 | 36 |
37 | 59 | -------------------------------------------------------------------------------- /views/server-tests/800.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This test will send the access token in the HTTP Authorization header.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Testing+accepting+access+token+in+HTTP+Authorization+header
21 |
22 | 23 |
24 | 25 |
    26 |
  •   Returned HTTP 201 or 202
  • 27 |
  •   Returned a Location header
  • 28 |
29 |
30 | 31 | 35 | 36 |
37 | 60 | -------------------------------------------------------------------------------- /views/server-tests/803.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This test ensures your endpoint does not allow unauthenticated requests. This request does not have an access token and your server must reply with an HTTP "Forbidden" response.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Content-type: application/x-www-form-urlencoded; charset=utf-8
17 | 
18 | h=entry&
19 | content=Testing+unauthenticated+request.+This+should+not+create+a+post.
20 |
21 | 22 |
23 | 24 |
    25 |
  •   Returned HTTP 401
  • 26 | 27 |
28 |
29 | 30 | 34 | 35 |
36 | 60 | -------------------------------------------------------------------------------- /views/server-tests/100.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a basic test of creating an h-entry post at your Micropub endpoint in form-encoded format.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Micropub+test+of+creating+a+basic+h-entry
21 |
22 | 23 |
24 | 25 |
    26 |
  •   Returned HTTP 201 or 202
  • 27 |
  •   Returned a Location header
  • 28 |
29 |
30 | 31 | 35 | 36 |
37 | 66 | -------------------------------------------------------------------------------- /views/server-tests/805.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This test will send the access token in the HTTP Authorization header, and the POST body, which is not valid according to RFC6750.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Testing+accepting+access+token+in+HTTP+Authorization+header+and+POST+body.+This+should+not+create+a+post&
21 | access_token=access_token ?>
22 | 
23 |
24 | 25 |
26 | 27 |
    28 |
  •   Returned HTTP 400
  • 29 | 30 |
31 |
32 | 33 | 37 | 38 |
39 | 63 | -------------------------------------------------------------------------------- /views/server-tests/200.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a basic test of creating an h-entry post at your Micropub endpoint in JSON format.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": ["Micropub test of creating an h-entry with a JSON request"]
23 |   }
24 | }
25 |
26 | 27 |
28 | 29 |
    30 |
  •   Returned HTTP 201 or 202
  • 31 |
  •   Returned a Location header
  • 32 |
33 |
34 | 35 | 39 | 40 |
41 | 69 | -------------------------------------------------------------------------------- /views/index.php: -------------------------------------------------------------------------------- 1 | layout('layout', ['title' => $title]); ?> 2 | 3 |
4 |
5 | 6 |
7 |

About this site

8 |

Micropub Rocks! is a validator to help you test your Micropub implementation. Several kinds of tests are available on the site.

9 |
10 | 11 |
12 |

Implementation Reports

13 | 14 |

15 | 16 | 13 Micropub Client Reports 17 | 18 | 19 | Last updated 2017-03-22 20 | 21 |

22 |

23 | 24 | Micropub Server Reports 25 | 26 | 27 | Last updated 28 | 29 |

30 |
31 | 32 |
33 | 34 |

Sign in to begin

35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 |

You will receive an email with a link to sign in.

48 | 49 |
50 | Why email sign-in? Many of the tests here require different levels of authorization against your Micropub endpoint. Rather than complicating the test flow with authenticating against this site as well, authenticating with your email address simplifies the way we are able to handle the various tests against your Micropub endpoint. 51 |
52 | 53 |

Welcome!

54 |

You are already signed in.

55 |

Continue

56 | 57 |
58 | 59 |
60 |

This code is open source. Feel free to file an issue if you notice any errors.

61 |
62 | 63 |
64 | -------------------------------------------------------------------------------- /views/server-tests/601.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'syndicate-to', 8 | ]); 9 | ?> 10 |
11 | 12 |
13 |

number . ': ' . $test->name) ?>

14 | 15 |

This test will check if your endpoint supports the "syndicate-to" query. To pass this test, your endpoint must return HTTP 200 and a JSON object in the response, with a syndicate-to property. If no syndication targets are specified, the value should be an empty array.

16 |

Clicking "Run" will make the following request to your endpoint.

17 |
18 | 19 |
20 |
GET  HTTP/1.1
21 | Authorization: Bearer access_token."\n" ?>
22 |
23 | 24 |
25 | 26 |
    27 |
  •   Returned HTTP 200
  • 28 |
  •   Returned a JSON object with a syndicate-to property
  • 29 |
30 |
31 | 32 | 36 | 37 |
38 | 73 | -------------------------------------------------------------------------------- /views/server-tests/804.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This test ensures your endpoint rejects requests from access tokens that are not allowed to create posts. You will first need to create an access token that should not be allowed to create posts, but is otherwise a valid access token for a user who would otherwise be allowed to post. This is testing your endpoint's ability to issue limited-scope access tokens to untrusted applications.

11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
POST micropub_endpoint ?> HTTP/1.1
21 | Authorization: Bearer 
22 | Content-type: application/x-www-form-urlencoded; charset=utf-8
23 | 
24 | h=entry&
25 | content=Testing+a+request+with+an+unauthorized+access+token.+This+should+not+create+a+post.
26 |
27 | 28 |
29 | 30 |
    31 |
  •   Returned HTTP 401
  • 32 | 33 |
34 |
35 | 36 | 40 | 41 |
42 | 74 | -------------------------------------------------------------------------------- /public/assets/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background: #cee0f0; 4 | font-size: 16px; 5 | } 6 | 7 | body.logged-in { 8 | padding-top: 60px; 9 | } 10 | 11 | .ui.top.menu .item img { 12 | border-radius: 4px; 13 | } 14 | 15 | .hidden { 16 | display: none; 17 | } 18 | 19 | .small { 20 | font-size: 14px; 21 | } 22 | 23 | pre.small { 24 | font-size: 14px; 25 | line-height: 16px; 26 | } 27 | 28 | #header-graphic { 29 | max-width: 600px; 30 | margin-top: -60px; 31 | margin-bottom: -80px; 32 | z-index: -1000; 33 | position: relative; 34 | } 35 | #header-graphic img { 36 | max-width: 600px; 37 | width: 100%; 38 | } 39 | 40 | .single-column { 41 | margin: 0 auto; 42 | max-width: 600px; 43 | } 44 | 45 | section.content { 46 | padding: 10px; 47 | border-radius: 4px; 48 | border: 1px #abcce9 solid; 49 | background: #fff; 50 | margin-bottom: 12px; 51 | } 52 | 53 | section.content.errors { 54 | background-color: #fdd; 55 | border-color: #daa; 56 | } 57 | 58 | section.content.success { 59 | background-color: #e1eccc; 60 | border-color: #a2cc56; 61 | } 62 | 63 | section.content.code { 64 | background: #eee; 65 | font-size: 14px; 66 | line-height: 16px; 67 | } 68 | 69 | section.content h4 { 70 | margin: 0; 71 | margin-top: 1em; 72 | } 73 | 74 | section.content#debug { 75 | font-size: 14px; 76 | } 77 | section.content#debug pre { 78 | margin: 0; 79 | } 80 | 81 | @media(max-width: 616px) { 82 | section.content { 83 | margin-left: 8px; 84 | margin-right: 8px; 85 | } 86 | } 87 | 88 | section.content textarea { 89 | border: 1px #abcce9 solid; 90 | border-radius: 4px; 91 | padding: 7px; 92 | -webkit-box-shadow: inset 0px 0px 6px 2px #e0edf9; 93 | -moz-box-shadow: inset 0px 0px 6px 2px #e0edf9; 94 | box-shadow: inset 0px 0px 6px 2px #e0edf9; 95 | } 96 | 97 | .result-list { 98 | list-style-type: none; 99 | margin: 0; 100 | margin-top: 1em; 101 | padding: 0; 102 | } 103 | 104 | .ui.circular.label.prompt { 105 | box-shadow: inset 0 0 10px #aaa; 106 | cursor: pointer; 107 | } 108 | 109 | .step_instructions { 110 | padding-left: 28px; 111 | font-size: 0.8em; 112 | } 113 | 114 | .endpoint-details a { 115 | display: none; 116 | } 117 | .endpoint-details:hover a { 118 | display: inline-block; 119 | } 120 | 121 | .implementation-features { 122 | margin: 1em 0; 123 | display: block; 124 | overflow: scroll; 125 | } 126 | .implementation-features td { 127 | padding: 2px 4px; 128 | } 129 | .implementation-features .num { 130 | text-align: right; 131 | font-size: 1.5em; 132 | font-weight: bold; 133 | color: #999; 134 | } 135 | 136 | .implementation-features .even-row { 137 | background-color: #eee; 138 | } 139 | .implementation-features .even-row .ui.label { 140 | background-color: #dbdbdb; 141 | } 142 | 143 | .implementation-features .small { 144 | font-size: 12px; 145 | line-height: 1.3em; 146 | } 147 | 148 | 149 | #report-details td { 150 | padding: 2px; 151 | } 152 | 153 | 154 | .last-updated { 155 | font-size: 12px; 156 | display: block; 157 | } 158 | 159 | -------------------------------------------------------------------------------- /views/server-tests/300.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a basic test of posting a photo to the Micropub endpoint.

11 |

Clicking "Run" will make a multipart request to your endpoint containing one photo.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | 
18 | ...multipart data...
19 | 
20 |
21 | 22 |
23 | 24 |
    25 |
  •   Returned HTTP 201 or 202
  • 26 |
  •   Returned a Location header
  • 27 |
  • 28 |
      Check that the photo appears in the post
    29 | 30 |
  • 31 |
32 |
33 | 34 | 38 | 39 |
40 | 82 | -------------------------------------------------------------------------------- /views/server-tests.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

Server Tests

9 | 10 |
11 | micropub_endpoint ?> 12 | 13 |
14 | 15 |
16 | Implementation Report 17 |
18 | 19 |

Creating Posts (Form-Encoded)

20 | 21 | insert('partials/server-test-row', ['num'=>100, 'tests'=>$tests, 'endpoint'=>$endpoint]); ?> 22 | insert('partials/server-test-row', ['num'=>101, 'tests'=>$tests, 'endpoint'=>$endpoint]); ?> 23 | insert('partials/server-test-row', ['num'=>104, 'tests'=>$tests, 'endpoint'=>$endpoint]); ?> 24 | insert('partials/server-test-row', ['num'=>107, 'tests'=>$tests, 'endpoint'=>$endpoint]); ?> 25 |
26 | 27 |

Creating Posts (JSON)

28 | 29 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 32 | } 33 | ?> 34 |
35 | 36 |

Creating Posts (Multipart)

37 | 38 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 41 | } 42 | ?> 43 |
44 | 45 |

Updates

46 | 47 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 50 | } 51 | ?> 52 |
53 | 54 |

Deletes

55 | 56 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 59 | } 60 | ?> 61 |
62 | 63 |

Query

64 | 65 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 68 | } 69 | ?> 70 |
71 | 72 |

Media Endpoint

73 | 74 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 77 | } 78 | ?> 79 |
80 | 81 |

Authentication

82 | 83 | insert('partials/server-test-row', ['num'=>$i, 'tests'=>$tests, 'endpoint'=>$endpoint]); 86 | } 87 | ?> 88 |
89 | 90 |
91 | 92 |
93 | -------------------------------------------------------------------------------- /views/server-tests/301.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a basic test of posting a photo to the Micropub endpoint.

11 |

Clicking "Run" will make a multipart request to your endpoint containing two photos. In this case, the name of the photo parts will be photo[].

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | 
18 | ...multipart data...
19 | 
20 |
21 | 22 |
23 | 24 |
    25 |
  •   Returned HTTP 201 or 202
  • 26 |
  •   Returned a Location header
  • 27 |
  • 28 |
      Check that both photos appear in the post
    29 | 30 |
  • 31 |
32 |
33 | 34 | 38 | 39 |
40 | 82 | -------------------------------------------------------------------------------- /database/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `users` ( 2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 | `email` varchar(255) DEFAULT NULL, 4 | `auth_code` varchar(64) DEFAULT NULL, 5 | `auth_code_exp` datetime DEFAULT NULL, 6 | `last_login` datetime DEFAULT NULL, 7 | PRIMARY KEY (`id`) 8 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 9 | 10 | CREATE TABLE `tests` ( 11 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 12 | `group` enum('server','client') DEFAULT NULL, 13 | `number` int(11) DEFAULT NULL, 14 | `name` varchar(255) DEFAULT NULL, 15 | `description` text, 16 | PRIMARY KEY (`id`), 17 | KEY `group_number` (`group`,`number`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 19 | 20 | CREATE TABLE `test_results` ( 21 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 22 | `endpoint_id` int(11) DEFAULT NULL, 23 | `client_id` int(11) DEFAULT NULL, 24 | `test_id` int(11) DEFAULT NULL, 25 | `passed` tinyint(4) DEFAULT NULL, 26 | `response` blob, 27 | `location` varchar(255) DEFAULT NULL, 28 | `created_at` datetime DEFAULT NULL, 29 | `last_result_at` datetime DEFAULT NULL, 30 | PRIMARY KEY (`id`), 31 | KEY `endpoint_id` (`endpoint_id`) 32 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 33 | 34 | CREATE TABLE `micropub_endpoints` ( 35 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 36 | `user_id` int(11) NOT NULL, 37 | `me` varchar(255) DEFAULT NULL, 38 | `micropub_endpoint` varchar(255) DEFAULT NULL, 39 | `access_token` text, 40 | `scope` varchar(255) DEFAULT '', 41 | `created_at` datetime DEFAULT NULL, 42 | `last_test_at` datetime DEFAULT NULL, 43 | `share_token` varchar(255) DEFAULT NULL, 44 | `implementation_name` varchar(255) DEFAULT NULL, 45 | `implementation_url` varchar(255) DEFAULT NULL, 46 | `programming_language` varchar(255) DEFAULT NULL, 47 | `developer_name` varchar(255) DEFAULT NULL, 48 | `developer_url` varchar(255) DEFAULT NULL, 49 | PRIMARY KEY (`id`), 50 | KEY `user_id` (`user_id`) 51 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 52 | 53 | CREATE TABLE `micropub_clients` ( 54 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 55 | `user_id` int(11) DEFAULT NULL, 56 | `name` varchar(100) DEFAULT NULL, 57 | `redirect_uri` varchar(255) DEFAULT NULL, 58 | `token` varchar(100) DEFAULT NULL, 59 | `last_viewed_test` int(11) NOT NULL DEFAULT '100', 60 | `profile_url` varchar(255) DEFAULT NULL, 61 | `created_at` datetime DEFAULT NULL, 62 | `share_token` varchar(255) DEFAULT NULL, 63 | PRIMARY KEY (`id`) 64 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 65 | 66 | CREATE TABLE `features` ( 67 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 68 | `group` enum('server','client') DEFAULT NULL, 69 | `number` int(11) unsigned NOT NULL, 70 | `description` varchar(255) DEFAULT NULL, 71 | `tests` varchar(255) DEFAULT NULL, 72 | PRIMARY KEY (`id`) 73 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 74 | 75 | CREATE TABLE `feature_results` ( 76 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 77 | `feature_num` int(11) NOT NULL, 78 | `endpoint_id` int(11) DEFAULT NULL, 79 | `client_id` int(11) DEFAULT NULL, 80 | `source_test_id` int(11) DEFAULT NULL, 81 | `implements` int(11) NOT NULL DEFAULT '0', 82 | `created_at` datetime DEFAULT NULL, 83 | `updated_at` datetime DEFAULT NULL, 84 | PRIMARY KEY (`id`) 85 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 86 | -------------------------------------------------------------------------------- /views/server-tests/101.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in form-encoded format including two categories. The category parameters are sent using the form-encoded array syntax category[].

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Micropub+test+of+creating+an+h-entry+with+categories.+This+post+should+have+two+categories,+test1+and+test2&
21 | category[]=test1&category[]=test2
22 |
23 | 24 |
25 | 26 |
    27 |
  • Returned HTTP 201 or 202
  • 28 |
  • Returned a Location header
  • 29 |
  • 30 |
      Check that the post has both categories
    31 | 32 |
  • 33 |
34 |
35 | 36 | 40 | 41 |
42 | 81 | -------------------------------------------------------------------------------- /views/server-tests/107.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in form-encoded format including one category. The category parameter is sent without the form-encoded array syntax, to test that your endpoint properly handles single values of a property that can support multiple values.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Micropub+test+of+creating+an+h-entry+with+one+category.+This+post+should+have+one+category,+test1&
21 | category=test1
22 |
23 | 24 |
25 | 26 |
    27 |
  • Returned HTTP 201 or 202
  • 28 |
  • Returned a Location header
  • 29 |
  • 30 |
      Check that the post has one category
    31 | 32 |
  • 33 |
34 |
35 | 36 | 40 | 41 |
42 | 81 | -------------------------------------------------------------------------------- /views/server-tests/203.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format including a photo property that references a URL. Your endpoint should recognize the photo property, and either download it to your own server, or render the post with the hotlinked URL.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": ["Micropub test of creating a photo referenced by URL. This post should include a photo of a sunset."],
23 |     "photo": ["media/sunset.jpg"]
24 |   }
25 | }
26 |
27 | 28 |
29 | 30 |
    31 |
  • Returned HTTP 201 or 202
  • 32 |
  • Returned a Location header
  • 33 |
  • 34 |
      Check that the photo appears in the post
    35 | 36 |
  • 37 |
38 |
39 | 40 | 44 | 45 |
46 | 85 | -------------------------------------------------------------------------------- /views/server-tests/202.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format with HTML content. In this case, the client will send an object {"html":...} instead of just a text string for content. Your endpoint should treat the value as HTML, rendering the HTML instead of escaping it.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": [{
23 |       "html": "This post has bold and italic text.

') ?>" 24 | }] 25 | } 26 | }
27 |
28 | 29 |
30 | 31 |
    32 |
  • Returned HTTP 201 or 202
  • 33 |
  • Returned a Location header
  • 34 |
  • 35 |
      Check that the HTML is rendered rather than escaped
    36 | 37 |
  • 38 |
39 |
40 | 41 | 45 | 46 |
47 | 86 | -------------------------------------------------------------------------------- /views/server-tests/104.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in form-encoded format including a photo property that references a URL. Your endpoint should recognize the photo property, and either download it to your own server, or render the post with the hotlinked URL.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/x-www-form-urlencoded; charset=utf-8
18 | 
19 | h=entry&
20 | content=Micropub+test+of+creating+a+photo+referenced+by+URL&
21 | photo=
22 |
23 | 24 |
25 | 26 |
    27 |
  • Returned HTTP 201 or 202
  • 28 |
  • Returned a Location header
  • 29 |
  • 30 |
      Check that the photo appears in the post
    31 | 32 |
  • 33 |
34 |
35 | 36 | 40 | 41 |
42 | 82 | -------------------------------------------------------------------------------- /views/server-tests/205.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format including a photo property with alt text.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": ["Micropub test of creating a photo referenced by URL with alt text. This post should include a photo of a sunset."],
23 |     "photo": [
24 |       {
25 |         "value": "media/sunset.jpg",
26 |         "alt": "Photo of a sunset"
27 |       }
28 |     ]
29 |   }
30 | }
31 |
32 | 33 |
34 | 35 |
    36 |
  • Returned HTTP 201 or 202
  • 37 |
  • Returned a Location header
  • 38 |
  • 39 |
      Check that the photo and alt text were stored
    40 | 41 |
  • 42 |
43 |
44 | 45 | 49 | 50 |
51 | 91 | -------------------------------------------------------------------------------- /views/server-tests/201.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format including two categories. The categories are sent in an array.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": ["Micropub test of creating an h-entry with a JSON request containing multiple categories. This post should have two categories, test1 and test2."],
23 |     "category": [
24 |       "test1",
25 |       "test2"
26 |     ]
27 |   }
28 | }
29 |
30 | 31 |
32 | 33 |
    34 |
  • Returned HTTP 201 or 202
  • 35 |
  • Returned a Location header
  • 36 |
  • 37 |
      Check that the post has both categories
    38 | 39 |
  • 40 |
41 |
42 | 43 | 47 | 48 |
49 | 89 | -------------------------------------------------------------------------------- /app/Auth.php: -------------------------------------------------------------------------------- 1 | getParsedBody(); 16 | 17 | if($params['galaxy'] != 42) { 18 | $response->getBody()->write(view('auth-email', [ 19 | 'title' => 'Error - Micropub Rocks!', 20 | ])); 21 | return $response; 22 | } 23 | 24 | if($params['confirm'] != $_SESSION['login_confirm']) { 25 | $response->getBody()->write(view('auth-error', [ 26 | 'title' => 'Error - Micropub Rocks!', 27 | 'error' => 'Login Error', 28 | 'error_description' => 'Please make sure you enter the number given.', 29 | ])); 30 | return $response; 31 | } 32 | 33 | $user = ORM::for_table('users')->where('email', $params['email'])->find_one(); 34 | 35 | if(!$user) { 36 | $user = ORM::for_table('users')->create(); 37 | $user->email = $params['email']; 38 | } 39 | 40 | $user->auth_code = $code = random_string(64); 41 | $user->auth_code_exp = date('Y-m-d H:i:s', time()+60*30); 42 | $user->save(); 43 | 44 | $login_url = Config::$base . 'auth/code?code=' . $code; 45 | 46 | if(Config::$skipauth) { 47 | return $response->withHeader('Location', $login_url)->withStatus(302); 48 | } 49 | 50 | // Email the login URL to the user 51 | $mg = new Mailgun(Config::$mailgun['key']); 52 | $mg->sendMessage(Config::$mailgun['domain'], [ 53 | 'from' => Config::$mailgun['from'], 54 | 'to' => $user->email, 55 | 'subject' => 'Your micropub.rocks Login URL', 56 | 'text' => "Click on the link below to sign in to micropub.rocks\n\n$login_url\n" 57 | ]); 58 | 59 | $response->getBody()->write(view('auth-email', [ 60 | 'title' => 'Sign In - Micropub Rocks!', 61 | ])); 62 | return $response; 63 | } 64 | 65 | public function code(ServerRequestInterface $request, ResponseInterface $response) { 66 | $params = $request->getQueryParams(); 67 | 68 | if(!array_key_exists('code', $params)) { 69 | return $response->withHeader('Location', '/')->withStatus(302); 70 | } 71 | 72 | $user = ORM::for_table('users') 73 | ->where('auth_code', $params['code']) 74 | ->where_gt('auth_code_exp', date('Y-m-d H:i:s')) 75 | ->find_one(); 76 | 77 | if(!$user) { 78 | $response->getBody()->write(view('auth-error', [ 79 | 'title' => 'Error - Micropub Rocks!', 80 | 'error' => 'Invalid Link', 81 | 'error_description' => 'The link you followed is invalid or has expired. Please try again.', 82 | ])); 83 | return $response; 84 | } 85 | 86 | $user->auth_code = ''; 87 | $user->auth_code_exp = null; 88 | $user->last_login = date('Y-m-d H:i:s'); 89 | $user->save(); 90 | 91 | session_setup(true); 92 | $_SESSION['user_id'] = $user->id; 93 | $_SESSION['email'] = $user->email; 94 | $_SESSION['login'] = 'success'; 95 | return $response->withHeader('Location', '/dashboard')->withStatus(302); 96 | } 97 | 98 | public function signout(ServerRequestInterface $request, ResponseInterface $response) { 99 | session_setup(true); 100 | unset($_SESSION['user_id']); 101 | unset($_SESSION['email']); 102 | $_SESSION = []; 103 | session_destroy(); 104 | return $response->withHeader('Location', '/')->withStatus(302); 105 | } 106 | 107 | } 108 | 109 | -------------------------------------------------------------------------------- /views/server-tests/206.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format including a photo property that references multiple URLs. Your endpoint should recognize the photo property, and either download them to your own server, or render the post with the hotlinked URLs.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
16 | Authorization: Bearer access_token."\n" ?>
17 | Content-type: application/json
18 | 
19 | {
20 |   "type": ["h-entry"],
21 |   "properties": {
22 |     "content": ["Micropub test of creating multiple photos referenced by URL. This post should include a photo of a city at night."],
23 |     "photo": [
24 |       "media/sunset.jpg",
25 |       "media/city-at-night.jpg"
26 |     ]
27 |   }
28 | }
29 |
30 | 31 |
32 | 33 |
    34 |
  • Returned HTTP 201 or 202
  • 35 |
  • Returned a Location header
  • 36 |
  • 37 |
      Check that the photos appears in the post
    38 | 39 |
  • 40 |
41 |
42 | 43 | 47 | 48 |
49 | 90 | -------------------------------------------------------------------------------- /views/server-tests/405.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | ?> 6 |
7 | 8 |
9 |

number . ': ' . $test->name) ?>

10 | 11 |

This test makes an invalid request to your endpoint. Your endpoint should reject this with an HTTP 400 response.

12 |

Clicking "Run" will first create a post, and after you've confirmed it's been created, you can click "Continue" to make the edit.

13 |
14 | 15 |
16 |
POST micropub_endpoint ?> HTTP/1.1
 17 | Authorization: Bearer access_token."\n" ?>
 18 | Content-type: application/json
 19 | 
 20 | {
 21 |   "type": ["h-entry"],
 22 |   "properties": {
 23 |     "content": ["Micropub update test #405."]
 24 |   }
 25 | }
26 |
27 | 28 |
29 | 30 |
    31 |
  •   Returned HTTP 201 or 202
  • 32 |
  •   Returned a Location header
  • 33 |
34 |
35 | 36 | 40 | 41 | 72 | 73 |
74 | 108 | -------------------------------------------------------------------------------- /views/dashboard.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 | 8 |
9 |
Welcome!
10 |

You are logged in as !

11 |
12 | 13 | 14 | 15 |
16 |

Your Micropub Endpoints

17 | 18 |

Select one of your Micropub endpoints to begin the tests

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
me ?: $endpoint->micropub_endpoint ?>
27 | Add New Endpoint 28 |
29 | 30 | 31 |
32 |

Add New Endpoint

33 | 34 | 38 |
39 |

Enter a profile URL below that advertises its Micropub endpoint. The Micropub endpoint will be discovered from that URL and you will be asked to authenticate against the authorization server specified.

40 |
41 |
42 | 43 | 44 |
45 |
46 |
47 |
48 |

Enter a Micropub endpoint and access token below to add an endpoint manually.

49 |
50 |
51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 | 59 | 60 |
61 |

Your Micropub Clients

62 | 63 |

Select one of your Micropub clients to begin the tests

64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
name ?>
72 | Add New Client 73 |
74 | 75 | 76 |
77 |

Add New Client

78 | 79 |
80 |
81 | 82 | 83 |
84 |
85 | 86 |
87 | 88 |
89 | -------------------------------------------------------------------------------- /views/partials/query-source-test.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

number . ': ' . $test->name) ?>

5 | 6 |

This test will check if your endpoint supports the "source" query to retrieve the original content in a post. This test starts by creating a post at your endpoint, then when you click "Continue", it will query your endpoint to ask for the source content.

7 |

Clicking "Run" will make the following request to your endpoint.

8 |
9 | 10 |
11 |
POST micropub_endpoint ?> HTTP/1.1
 12 | Authorization: Bearer access_token."\n" ?>
 13 | Content-type: application/json
 14 | 
 15 | {
 16 |   "type": ["h-entry"],
 17 |   "properties": {
 18 |     "content": ["Test of querying the endpoint for the source content"],
 19 |     "category": ["micropub", "test"]
 20 |   }
 21 | }
22 |
23 | 24 |
25 | 26 |
    27 |
  •   Returned HTTP 201 or 202
  • 28 |
  •   Returned a Location header
  • 29 |
30 |
31 | 32 | 36 | 37 | 58 | 59 |
60 | 106 | -------------------------------------------------------------------------------- /views/server-tests/204.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $test->name, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |

number . ': ' . $test->name) ?>

9 | 10 |

This is a test of creating an h-entry post in JSON format including a nested Microformats 2 object. While it is not expected that every endpoint will be able to render the checkin object, it should still store the checkin property and render the rest of the post.

11 |

Clicking "Run" will make the following request to your endpoint.

12 |
13 | 14 |
15 |
POST micropub_endpoint ?> HTTP/1.1
 16 | Authorization: Bearer access_token."\n" ?>
 17 | Content-type: application/json
 18 | 
 19 | {
 20 |     "type": [
 21 |         "h-entry"
 22 |     ],
 23 |     "properties": {
 24 |         "published": [
 25 |             "2017-05-31T12:03:36-07:00"
 26 |         ],
 27 |         "content": [
 28 |             "Lunch meeting"
 29 |         ],
 30 |         "checkin": [
 31 |             {
 32 |                 "type": [
 33 |                     "h-card"
 34 |                 ],
 35 |                 "properties": {
 36 |                     "name": ["Los Gorditos"],
 37 |                     "url": ["https://foursquare.com/v/502c4bbde4b06e61e06d1ebf"],
 38 |                     "latitude": [45.524330801154],
 39 |                     "longitude": [-122.68068808051],
 40 |                     "street-address": ["922 NW Davis St"],
 41 |                     "locality": ["Portland"],
 42 |                     "region": ["OR"],
 43 |                     "country-name": ["United States"],
 44 |                     "postal-code": ["97209"]
 45 |                 }
 46 |             }
 47 |         ]
 48 |     }
 49 | }
50 |
51 | 52 |
53 | 54 |
    55 |
  • Returned HTTP 201 or 202
  • 56 |
  • Returned a Location header
  • 57 |
  • 58 |
      Check that the nested object was stored
    59 | 60 |
  • 61 |
62 |
63 | 64 | 68 | 69 |
70 | 110 | -------------------------------------------------------------------------------- /views/server-tests/802.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 3 | 'title' => $test->name, 4 | ]); 5 | 6 | $query_url = build_micropub_query_url($endpoint->micropub_endpoint, [ 7 | 'q' => 'source', 8 | 'url' => '%%%', 9 | ]); 10 | ?> 11 | 12 |
13 | 14 |
15 |

number . ': ' . $test->name) ?>

16 | 17 |

This test will ensure your endpoint does not store the access token in the post. It does this by creating a post while providing the access token in the post body, then querying the endpoint for the source properties of the post to ensure the access token is not returned as part of the content. In order to pass this test, you will also have to support the Source Query test.

18 |

Clicking "Run" will make the following request to your endpoint.

19 |
20 | 21 |
22 |
POST micropub_endpoint ?> HTTP/1.1
 23 | Content-type: application/x-www-form-urlencoded; charset=utf-8
 24 | 
 25 | h=entry&
 26 | content=Testing+accepting+access+token+in+post+body&
 27 | access_token=access_token ?>
28 |
29 | 30 |
31 | 32 |
    33 |
  •   Returned HTTP 201 or 202
  • 34 |
  •   Returned a Location header
  • 35 |
36 |
37 | 38 | 42 | 43 | 64 | 65 |
66 | 108 | -------------------------------------------------------------------------------- /views/auth-start.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 |
7 | 8 | 9 |
10 |
Ready!
11 |

Clicking the button below will take you to your authorization server which is where you will allow this app to be able to post to your site.

12 | Authorize 13 |
14 | 15 | 16 |
17 |

Authorization Endpoint

18 | 19 |

The authorization endpoint tells this app where to direct your browser to sign you in.

20 | 21 | 22 |
Found your authorization endpoint:
23 | 24 |
Could not find your authorization endpoint!
25 |

You need to set your authorization endpoint in a <link> tag on your home page, or by including a Link header in the HTTP response.

26 |

You can create your own authorization endpoint, but it's easier to use an existing service such as IndieAuth.com. To delegate to IndieAuth.com, you can use the markup provided below.

27 |

<link rel="authorization_endpoint" href="https://indieauth.com/auth">

28 |

Alternately, you can return the following HTTP Link header.

29 |

Link: <https://indieauth.com/auth>; rel="authorization_endpoint"

30 | 31 |
32 | 33 |
34 |

Token Endpoint

35 | 36 |

The token endpoint is where this app will make a request to get an access token after obtaining an authorization code.

37 | 38 | 39 |
Found your token endpoint:
40 | 41 |
Could not find your token endpoint!
42 |

You need to set your token endpoint in a <link> tag on your home page, or by including a .

43 |

You can create your own token endpoint for 44 | your website which can issue access tokens when given an authorization code, but 45 | it's easier to get started by using an existing service such as tokens.indieauth.com. To use this service as your token endpoint, use the markup provided below.

46 |

<link rel="token_endpoint" href="https://tokens.indieauth.com/token">

47 |

Alternately, you can return the following HTTP Link header.

48 |

Link: <https://tokens.indieauth.com/auth>; rel="token_endpoint"

49 | 50 | 51 |
52 | 53 |
54 |

Micropub Endpoint

55 | 56 |

The Micropub endpoint is the URL this app will make requests to that include the access token.

57 | 58 | 59 |
60 | Found your Micropub endpoint: 61 |
62 | 63 |
Could not find your Micropub endpoint!
64 |

You need to set your Micropub endpoint in a <link> tag on your home page.

65 |

You will need to create a Micropub endpoint for your website which can create posts on your site. Once you've created the Micropub endpoint, you can indicate its location using the markup below.

66 |

<link rel="micropub" href="https:///micropub">

67 |

Alternately, you can return the following HTTP Link header.

68 |

Link: <https:///micropub>; rel="micropub"

69 | 70 | 71 | 72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /views/partials/delete-test.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

number . ': ' . $test->name) ?>

5 | 6 |

7 |

Clicking "Run" will first create a post, and after you've confirmed it's been created, you can click "Continue" to delete the post.

8 |
9 | 10 |
11 |
POST micropub_endpoint ?> HTTP/1.1
 12 | Authorization: Bearer access_token."\n" ?>
 13 | Content-type: application/x-www-form-urlencoded; charset=utf-8
 14 | 
 15 | 
16 |
17 | 18 |
19 | 20 |
    21 |
  •   Returned HTTP 201 or 202
  • 22 |
  •   Returned a Location header
  • 23 |
24 |
25 | 26 | 30 | 31 | 62 | 63 |
64 | 107 | -------------------------------------------------------------------------------- /views/partials/update-test-basic.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

number . ': ' . $test->name) ?>

5 | 6 |

7 |

Clicking "Run" will first create a post, and after you've confirmed it's been created, you can click "Continue" to make the edit.

8 |
9 | 10 |
11 |
POST micropub_endpoint ?> HTTP/1.1
 12 | Authorization: Bearer access_token."\n" ?>
 13 | Content-type: application/json
 14 | 
 15 | 
16 |
17 | 18 |
19 | 20 |
    21 |
  •   Returned HTTP 201 or 202
  • 22 |
  •   Returned a Location header
  • 23 |
24 |
25 | 26 | 30 | 31 | 62 | 63 |
64 | 122 | -------------------------------------------------------------------------------- /public/assets/common.js: -------------------------------------------------------------------------------- 1 | 2 | $(function(){ 3 | $("#galaxy").val(42); 4 | }); 5 | 6 | function set_up_form_test(test, endpoint, callback, skipauth=false) { 7 | $(function(){ 8 | $("#run").click(function(){ 9 | $("#run").removeClass('green').addClass('disabled'); 10 | $.post('/server-tests/micropub', { 11 | test: test, 12 | endpoint: endpoint, 13 | skipauth: skipauth ? 1 : 0, 14 | access_token: $("#access-token-input").val(), 15 | method: 'post', 16 | body: $('#postbody').text().replace(/\n/g,'') 17 | }, function(data) { 18 | $("#response").text(data.debug); 19 | $("#response-section").removeClass('hidden'); 20 | $("#run").addClass('green').removeClass('disabled'); 21 | callback(data); 22 | }) 23 | }); 24 | }); 25 | } 26 | 27 | function set_up_multipart_test(test, endpoint, media_endpoint, params, files, callback) { 28 | $(function(){ 29 | $("#run").click(function(){ 30 | $("#run").removeClass('green').addClass('disabled'); 31 | $.post('/server-tests/micropub', { 32 | test: test, 33 | endpoint: endpoint, 34 | url: media_endpoint, 35 | method: 'multipart', 36 | params: params, 37 | files: files 38 | }, function(data) { 39 | $("#response").text(data.debug); 40 | $("#response-section").removeClass('hidden'); 41 | $("#run").addClass('green').removeClass('disabled'); 42 | callback(data); 43 | }) 44 | }); 45 | }); 46 | } 47 | 48 | function set_up_json_test(test, endpoint, callback) { 49 | $(function(){ 50 | $("#run").click(function(){ 51 | $("#run").removeClass('green').addClass('disabled'); 52 | $.post('/server-tests/micropub', { 53 | test: test, 54 | endpoint: endpoint, 55 | method: 'postjson', 56 | body: $('#postbody').text().replace(/\n/g,'') 57 | }, function(data) { 58 | $("#response").text(data.debug); 59 | $("#response-section").removeClass('hidden'); 60 | $("#run").addClass('green').removeClass('disabled'); 61 | callback(data); 62 | }) 63 | }); 64 | }); 65 | } 66 | 67 | function set_up_update_test(test, endpoint, callback) { 68 | $("#run-update").click(function(){ 69 | $("#run-update").removeClass('green').addClass('disabled'); 70 | $.post('/server-tests/micropub', { 71 | test: test, 72 | endpoint: endpoint, 73 | method: 'postjson', 74 | body: $('#updatebody').text().replace(/\n/g,'') 75 | }, function(data) { 76 | $("#update-response").text(data.debug); 77 | $("#update-response-section").removeClass('hidden'); 78 | $("#run-update").addClass('green').removeClass('disabled'); 79 | callback(data); 80 | }); 81 | }); 82 | } 83 | 84 | function set_up_delete_test(test, method, endpoint, callback) { 85 | $("#run-delete").click(function(){ 86 | $("#run-delete").removeClass('green').addClass('disabled'); 87 | $.post('/server-tests/micropub', { 88 | test: test, 89 | endpoint: endpoint, 90 | method: method, 91 | body: $('#deletebody').text().replace(/\n/g,'') 92 | }, function(data) { 93 | $("#delete-response").text(data.debug); 94 | $("#delete-response-section").removeClass('hidden'); 95 | $("#run-delete").addClass('green').removeClass('disabled'); 96 | callback(data); 97 | }); 98 | }); 99 | } 100 | 101 | function set_up_undelete_test(test, method, endpoint, callback) { 102 | $("#run-undelete").click(function(){ 103 | $("#run-undelete").removeClass('green').addClass('disabled'); 104 | $.post('/server-tests/micropub', { 105 | test: test, 106 | endpoint: endpoint, 107 | method: method, 108 | body: $('#undeletebody').text().replace(/\n/g,'') 109 | }, function(data) { 110 | $("#undelete-response").text(data.debug); 111 | $("#undelete-response-section").removeClass('hidden'); 112 | $("#run-undelete").addClass('green').removeClass('disabled'); 113 | callback(data); 114 | }); 115 | }); 116 | } 117 | 118 | function set_up_query_test(test, endpoint, callback) { 119 | $(function(){ 120 | $("#run-query").click(function(){ 121 | $("#run-query").removeClass('green').addClass('disabled'); 122 | $.post('/server-tests/micropub', { 123 | test: test, 124 | endpoint: endpoint, 125 | method: 'get', 126 | url: $("#query_url").text() 127 | }, function(data) { 128 | $("#query-response").text(data.debug); 129 | $("#query-response-section").removeClass('hidden'); 130 | $("#run-query").addClass('green').removeClass('disabled'); 131 | callback(data); 132 | }) 133 | }); 134 | }); 135 | } 136 | 137 | function store_result(test, endpoint, passed) { 138 | $.post('/server-tests/store-result', { 139 | endpoint: endpoint, 140 | test: test, 141 | passed: passed 142 | }); 143 | } 144 | 145 | function store_server_feature(endpoint_id, feature_num, implements, test) { 146 | $.post('/implementation-report/store-result', { 147 | type: 'server', 148 | id: endpoint_id, 149 | feature_num: feature_num, 150 | implements: implements, 151 | source_test: test 152 | }); 153 | } 154 | 155 | function set_result_icon(sel, passed) { 156 | switch(passed) { 157 | case 1: 158 | $(sel).addClass('green').removeClass('red').html('✔'); 159 | break; 160 | case -1: 161 | $(sel).removeClass('green').addClass('red').html('✖'); 162 | break; 163 | case 0: 164 | $(sel).removeClass('green').removeClass('red').html(' '); 165 | break; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /views/partials/media-endpoint-test.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

number . ': ' . $test->name) ?>

5 | 6 |

This test will discover your Media Endpoint, and then upload an image to it, and check that the response from the Media Endpoint is correct.

7 |

Clicking "Run" will make the following request to your Micropub endpoint to discover the Media Endpoint.

8 |
9 | 10 |
11 |
GET  HTTP/1.1
 12 | Authorization: Bearer access_token."\n" ?>
13 |
14 | 15 |
16 | 17 |
    18 |
  •   Returned HTTP 200
  • 19 |
  •   Returned a JSON object with a media-endpoint property with the full URL of the endpoint
  • 20 |
21 |
22 | 23 | 27 | 28 | 59 | 60 |
61 | 135 | -------------------------------------------------------------------------------- /views/implementation-report.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 | 12 | 15 | 16 |

Implementation Report

17 | 18 |

19 |

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | 63 | 64 |
Endpoint URLmicropub_endpoint ?>
Software Name 28 | 29 | 30 |
Software Home Page 35 | 36 | 39 |
Developer Name 44 | 45 | 46 |
Developer Home Page 51 | 52 | 55 |
Programming Language 60 | 61 | 62 |
65 |
66 |

67 | 68 | 69 | 70 |
The results below are automatically compiled from the various test results for your implementation. You can re-run tests to update the results here.
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
number ?>implements) ?>description ?>
81 | 82 |
83 |
84 | 85 | 162 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | share('response', Zend\Diactoros\Response::class); 10 | $container->share('request', function () { 11 | return Zend\Diactoros\ServerRequestFactory::fromGlobals( 12 | $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES 13 | ); 14 | }); 15 | $container->share('emitter', Zend\Diactoros\Response\SapiEmitter::class); 16 | 17 | $route = new League\Route\RouteCollection($container); 18 | 19 | 20 | $route->map('GET', '/', 'App\\Controller::index'); 21 | 22 | $route->map('POST', '/auth/start', 'App\\Auth::start'); 23 | $route->map('GET', '/auth/code', 'App\\Auth::code'); 24 | $route->map('GET', '/auth/signout', 'App\\Auth::signout'); 25 | 26 | $route->map('GET', '/dashboard', 'App\\Controller::dashboard'); 27 | $route->map('GET', '/image', 'ImageProxy::image'); 28 | 29 | 30 | ////////////////////////////////////////////////////////////////////////// 31 | // Server Management 32 | $route->map('POST', '/endpoints/new', 'App\\Controller::new_endpoint'); 33 | $route->map('GET', '/endpoints/callback', 'App\\Controller::endpoint_callback'); 34 | $route->map('GET', '/endpoints/{id}', 'App\\Controller::edit_endpoint'); 35 | $route->map('POST', '/endpoints/save', 'App\\Controller::save_endpoint'); 36 | 37 | // Server Tests 38 | $route->map('GET', '/server-tests', 'App\\ServerTests::index'); 39 | $route->map('POST', '/server-tests/micropub', 'App\\ServerTests::micropub_request'); 40 | $route->map('POST', '/server-tests/media-check', 'App\\ServerTests::media_check'); 41 | $route->map('POST', '/server-tests/store-result', 'App\\ServerTests::store_result'); 42 | $route->map('GET', '/server-tests/{num}', 'App\\ServerTests::get_test'); 43 | ////////////////////////////////////////////////////////////////////////// 44 | 45 | 46 | ////////////////////////////////////////////////////////////////////////// 47 | // Client Management 48 | $route->map('POST', '/clients/new', 'App\\Controller::new_client'); 49 | $route->map('GET', '/clients/{id}', 'App\\Controller::edit_client'); 50 | $route->map('POST', '/clients/save', 'App\\Controller::save_client'); 51 | $route->map('POST', '/clients/{id}/new_access_token', 'App\\Controller::create_client_access_token'); 52 | 53 | // Client Tests 54 | $route->map('GET', '/client/{token}', 'App\\ClientTests::index'); 55 | $route->map('GET', '/client/{token}/auth', 'App\\ClientTests::auth'); 56 | $route->map('GET', '/client/{token}/micropub', 'App\\ClientTests::micropub_get'); 57 | $route->map('GET', '/client/{token}/{num}', 'App\\ClientTests::get_test'); 58 | $route->map('GET', '/client/{token}/{num}/{key}', 'App\\ClientTests::get_test'); 59 | $route->map('GET', '/client/{token}/{num}/{key}/photo.jpg', 'App\\ClientTests::get_image'); 60 | $route->map('GET', '/client/{token}/{num}/{key}/video.mp4', 'App\\ClientTests::get_video'); 61 | $route->map('GET', '/client/{token}/{num}/{key}/audio.mp3', 'App\\ClientTests::get_audio'); 62 | $route->map('GET', '/client/{token}/{num}/{key}/file', 'App\\ClientTests::get_image'); 63 | $route->map('POST', '/client/{token}/auth', 'App\\ClientTests::auth_confirm'); 64 | $route->map('POST', '/client/{token}/token', 'App\\ClientTests::token'); 65 | $route->map('POST', '/client/{token}/micropub', 'App\\ClientTests::micropub'); 66 | $route->map('POST', '/client/{token}/media', 'App\\ClientTests::media_endpoint'); 67 | $route->map('OPTIONS', '/client/{token}/micropub', 'App\\ClientTests::options'); 68 | $route->map('OPTIONS', '/client/{token}/media', 'App\\ClientTests::options'); 69 | ////////////////////////////////////////////////////////////////////////// 70 | 71 | 72 | ////////////////////////////////////////////////////////////////////////// 73 | // Reports 74 | $route->map('GET', '/implementation-reports/servers/', 'App\\ImplementationReport::show_reports'); 75 | $route->map('GET', '/implementation-reports/servers/summary/', 'App\\ImplementationReport::server_report_summary'); 76 | $route->map('GET', '/implementation-reports/servers/{id}', 'App\\ImplementationReport::get_server_report'); 77 | $route->map('GET', '/implementation-reports/servers/{id}/{token}', 78 | 'App\\ImplementationReport::view_server_report'); 79 | 80 | $route->map('GET', '/implementation-reports/clients/', 'App\\ClientReports::show_reports'); 81 | 82 | $route->map('GET', '/implementation-reports/', function(ServerRequestInterface $request, ResponseInterface $response){ 83 | return $response->withHeader('Location', '/')->withStatus(302); 84 | }); 85 | 86 | $route->map('GET', '/implementation-report/client/{id}', 'App\\ImplementationReport::get_client_report'); 87 | $route->map('POST', '/implementation-report/store-result', 'App\\ImplementationReport::store_result'); 88 | $route->map('POST', '/implementation-report/save', 'App\\ImplementationReport::save_report'); 89 | $route->map('POST', '/implementation-report/publish', 'App\\ImplementationReport::publish_report'); 90 | 91 | // Old redirects 92 | $route->map('GET', '/implementation-report/server/{id}', 'App\\ImplementationReport::redirect_server'); 93 | $route->map('GET', '/implementation-report/server/{id}/{token}', 'App\\ImplementationReport::redirect_server'); 94 | $route->map('GET', '/reports', function(ServerRequestInterface $request, ResponseInterface $response){ 95 | return $response->withHeader('Location', '/implementation-reports/servers/')->withStatus(301); 96 | }); 97 | ////////////////////////////////////////////////////////////////////////// 98 | 99 | 100 | $templates = new League\Plates\Engine(dirname(__FILE__).'/../views'); 101 | 102 | try { 103 | $response = $route->dispatch($container->get('request'), $container->get('response')); 104 | $container->get('emitter')->emit($response); 105 | } catch(League\Route\Http\Exception\NotFoundException $e) { 106 | $response = $container->get('response'); 107 | $response->getBody()->write("Not Found\n"); 108 | $container->get('emitter')->emit($response->withStatus(404)); 109 | } catch(League\Route\Http\Exception\MethodNotAllowedException $e) { 110 | $response = $container->get('response'); 111 | $response->getBody()->write("Method not allowed\n"); 112 | $container->get('emitter')->emit($response->withStatus(405)); 113 | } 114 | -------------------------------------------------------------------------------- /lib/helpers.php: -------------------------------------------------------------------------------- 1 | render($template, $data); 17 | } 18 | 19 | function redis() { 20 | static $client = false; 21 | if(!$client) 22 | $client = new Predis\Client(Config::$redis); 23 | return $client; 24 | } 25 | 26 | function flash($key) { 27 | if(isset($_SESSION) && isset($_SESSION[$key])) { 28 | $value = $_SESSION[$key]; 29 | unset($_SESSION[$key]); 30 | return $value; 31 | } 32 | } 33 | 34 | function e($text) { 35 | return htmlspecialchars($text); 36 | } 37 | 38 | // Always return a string 39 | function mf2_val($in) { 40 | if(is_string($in)) return $in; 41 | if(is_array($in)) { 42 | if(array_key_exists(0, $in) && is_string($in[0])) 43 | return $in[0]; 44 | if(array_key_exists(0, $in) && is_array($in[0])) { 45 | if(array_key_exists('value', $in[0])) 46 | return $in[0]['value']; 47 | } 48 | if(array_key_exists('value', $in)) 49 | return $in['value']; 50 | } 51 | return ''; 52 | } 53 | 54 | function random_string($len) { 55 | $charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 56 | $str = ''; 57 | $c = strlen($charset)-1; 58 | for($i=0; $i<$len; $i++) { 59 | $str .= $charset[mt_rand(0, $c)]; 60 | } 61 | return $str; 62 | } 63 | 64 | // Sets up the session. 65 | // If create is true, the session will be created even if there is no cookie yet. 66 | // If create is false, the session will only be set up in PHP if they already have a session cookie. 67 | function session_setup($create=false) { 68 | if($create || isset($_COOKIE[session_name()])) { 69 | session_set_cookie_params(86400*30); 70 | session_start(); 71 | } 72 | } 73 | 74 | function is_logged_in() { 75 | return isset($_SESSION) && array_key_exists('user_id', $_SESSION); 76 | } 77 | 78 | function login_required(&$response) { 79 | return $response->withHeader('Location', '/?login_required')->withStatus(302); 80 | } 81 | 82 | function logged_in_user() { 83 | return ORM::for_table('users')->where('id', $_SESSION['user_id'])->find_one(); 84 | } 85 | 86 | function domains_are_equal($a, $b) { 87 | return parse_url($a, PHP_URL_HOST) == parse_url($b, PHP_URL_HOST); 88 | } 89 | 90 | function display_url($url) { 91 | # remove scheme 92 | $url = preg_replace('/^https?:\/\//', '', $url); 93 | # if the remaining string has no path components but has a trailing slash, remove the trailing slash 94 | $url = preg_replace('/^([^\/]+)\/$/', '$1', $url); 95 | return $url; 96 | } 97 | 98 | function is_url($url) { 99 | return is_string($url) && preg_match('/^https?:\/\/[a-z0-9\.\-]\/?/', $url); 100 | } 101 | 102 | function is_url_any_scheme($url) { 103 | return is_string($url) && preg_match('/^[a-z0-9\+\-\.]+?:\/\/[a-z0-9\.\-]\/?/', $url); 104 | } 105 | 106 | function add_parameters_to_url($url, $add_params) { 107 | $parts = parse_url($url); 108 | if(array_key_exists('query', $parts) && $parts['query']) { 109 | parse_str($parts['query'], $params); 110 | } else { 111 | $params = []; 112 | } 113 | 114 | foreach($add_params as $k=>$v) { 115 | $params[$k] = $v; 116 | } 117 | 118 | $parts['query'] = http_build_query($params); 119 | 120 | return http_build_url($parts); 121 | } 122 | 123 | if(!function_exists('http_build_url')) { 124 | function http_build_url($parsed_url) { 125 | $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; 126 | $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; 127 | $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; 128 | $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; 129 | $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; 130 | $pass = ($user || $pass) ? "$pass@" : ''; 131 | $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; 132 | $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; 133 | $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; 134 | return "$scheme$user$pass$host$port$path$query$fragment"; 135 | } 136 | } 137 | 138 | function http_header_case($str) { 139 | $str = str_replace('-', ' ', $str); 140 | $str = ucwords($str); 141 | $str = str_replace(' ', '-', $str); 142 | return $str; 143 | } 144 | 145 | function result_icon($passed, $id=false) { 146 | if($passed == 1) { 147 | return ''; 148 | } elseif($passed == -1) { 149 | return ''; 150 | } elseif($passed == 0) { 151 | return ' '; 152 | } else { 153 | return ''; 154 | } 155 | } 156 | 157 | function result_checkbox($results, $num) { 158 | foreach($results as $result) { 159 | if($result->number == $num) { 160 | return $result->implements == 1 ? 'x' : ' '; 161 | } 162 | } 163 | return ' '; 164 | } 165 | 166 | function test_url($test_num, $endpoint_id) { 167 | return '/server-tests/' . $test_num . '?endpoint=' . $endpoint_id; 168 | } 169 | 170 | function client_test_url($test_num, $token) { 171 | return '/client/' . $token . '/' . $test_num; 172 | } 173 | 174 | function build_micropub_query_url($endpoint, $params) { 175 | $url = parse_url($endpoint); 176 | if(!array_key_exists('query', $url)) 177 | $url['query'] = http_build_query($params); 178 | else 179 | $url['query'] .= '&' . http_build_query($params); 180 | $url = http_build_url($url); 181 | return preg_replace('/%5B[0-9]+%5D=/simU', '%5B%5D=', $url); 182 | } 183 | 184 | function streaming_publish($channel, $data) { 185 | $ch = curl_init(Config::$base . 'streaming/pub?id='.$channel); 186 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 187 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); 188 | curl_exec($ch); 189 | } 190 | 191 | -------------------------------------------------------------------------------- /views/client-tests.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 |
9 |

10 | name ?> 11 | 12 |

13 |
14 | 15 | 18 | 19 |
20 | 24 |
25 |

Copy the URL below to use when signing in to your client.

26 | 27 |
28 | 29 |
30 |
31 | 32 |

See Obtaining an Access Token for documentation on how to discover the endpoints from this URL and build the request for authorization.

33 |
34 |
35 |

Generate an access token below and copy and paste it into your client.

36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 |

Note: Your client does not need to pass every test. It's okay to pass only the tests you're interested in if your client is intentionally supporting a subset of features.

51 | 52 |
53 | Implementation Report 54 |
55 | 56 |

Creating Posts (Form-Encoded)

57 | 58 | insert('partials/client-test-row', ['num'=>100, 'tests'=>$tests, 'client'=>$client]); ?> 59 | insert('partials/client-test-row', ['num'=>101, 'tests'=>$tests, 'client'=>$client]); ?> 60 | insert('partials/client-test-row', ['num'=>104, 'tests'=>$tests, 'client'=>$client]); ?> 61 | insert('partials/client-test-row', ['num'=>105, 'tests'=>$tests, 'client'=>$client]); ?> 62 | insert('partials/client-test-row', ['num'=>106, 'tests'=>$tests, 'client'=>$client]); ?> 63 |
64 | 65 |

Creating Posts (JSON)

66 | 67 | insert('partials/client-test-row', ['num'=>200, 'tests'=>$tests, 'client'=>$client]); ?> 68 | insert('partials/client-test-row', ['num'=>201, 'tests'=>$tests, 'client'=>$client]); ?> 69 | insert('partials/client-test-row', ['num'=>202, 'tests'=>$tests, 'client'=>$client]); ?> 70 | insert('partials/client-test-row', ['num'=>203, 'tests'=>$tests, 'client'=>$client]); ?> 71 | insert('partials/client-test-row', ['num'=>204, 'tests'=>$tests, 'client'=>$client]); ?> 72 | insert('partials/client-test-row', ['num'=>205, 'tests'=>$tests, 'client'=>$client]); ?> 73 |
74 | 75 |

Creating Posts (Multipart)

76 | 77 | insert('partials/client-test-row', ['num'=>300, 'tests'=>$tests, 'client'=>$client]); ?> 78 | insert('partials/client-test-row', ['num'=>301, 'tests'=>$tests, 'client'=>$client]); ?> 79 |
80 | 81 |

Updates

82 | 83 | insert('partials/client-test-row', ['num'=>400, 'tests'=>$tests, 'client'=>$client]); ?> 84 | insert('partials/client-test-row', ['num'=>401, 'tests'=>$tests, 'client'=>$client]); ?> 85 | insert('partials/client-test-row', ['num'=>402, 'tests'=>$tests, 'client'=>$client]); ?> 86 | insert('partials/client-test-row', ['num'=>403, 'tests'=>$tests, 'client'=>$client]); ?> 87 |
88 | 89 |

Deletes

90 | 91 | insert('partials/client-test-row', ['num'=>500, 'tests'=>$tests, 'client'=>$client]); ?> 92 | insert('partials/client-test-row', ['num'=>502, 'tests'=>$tests, 'client'=>$client]); ?> 93 |
94 | 95 |

Query

96 | 97 | insert('partials/client-test-row', ['num'=>600, 'tests'=>$tests, 'client'=>$client]); ?> 98 | insert('partials/client-test-row', ['num'=>601, 'tests'=>$tests, 'client'=>$client]); ?> 99 | insert('partials/client-test-row', ['num'=>602, 'tests'=>$tests, 'client'=>$client]); ?> 100 | insert('partials/client-test-row', ['num'=>603, 'tests'=>$tests, 'client'=>$client]); ?> 101 |
102 | 103 |

Media Endpoint

104 | 105 | insert('partials/client-test-row', ['num'=>700, 'tests'=>$tests, 'client'=>$client]); ?> 106 |
107 | 108 |
109 |
110 | 139 | -------------------------------------------------------------------------------- /views/partials/undelete-test.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

number . ': ' . $test->name) ?>

5 | 6 |

7 |

Clicking "Run" will first create a post, and after you've confirmed it's been created, you can click "Continue" to delete the post, then "Continue" to undelete it.

8 |
9 | 10 |
11 |
POST micropub_endpoint ?> HTTP/1.1
 12 | Authorization: Bearer access_token."\n" ?>
 13 | Content-type: application/x-www-form-urlencoded; charset=utf-8
 14 | 
 15 | 
16 |
17 | 18 |
19 | 20 |
    21 |
  •   Returned HTTP 201 or 202
  • 22 |
  •   Returned a Location header
  • 23 |
24 |
25 | 26 | 30 | 31 | 62 | 63 | 64 | 95 | 96 |
97 | 157 | -------------------------------------------------------------------------------- /app/ServerTests.php: -------------------------------------------------------------------------------- 1 | getQueryParams(); 24 | 25 | $this->user = logged_in_user(); 26 | 27 | // Verify an endpoint is specified and the user has permission to access it 28 | if(!isset($params['endpoint'])) 29 | return $response->withHeader('Location', '/dashboard')->withStatus(302); 30 | 31 | $this->endpoint = ORM::for_table('micropub_endpoints') 32 | ->where('user_id', $this->user->id) 33 | ->where('id', $params['endpoint']) 34 | ->find_one(); 35 | 36 | if(!$this->endpoint) 37 | return $response->withHeader('Location', '/dashboard')->withStatus(302); 38 | 39 | return null; 40 | } 41 | 42 | public function index(ServerRequestInterface $request, ResponseInterface $response) { 43 | if($check = $this->_check_permissions($request, $response)) 44 | return $check; 45 | 46 | $data = ORM::for_table('tests') 47 | ->raw_query('SELECT tests.*, test_results.passed FROM tests 48 | LEFT JOIN test_results ON tests.id = test_results.test_id AND test_results.endpoint_id = :endpoint_id 49 | WHERE tests.group = :group 50 | ORDER BY tests.number', ['endpoint_id'=>$this->endpoint->id, 'group'=>'server']) 51 | ->find_many(); 52 | 53 | $tests = []; 54 | foreach($data as $test) { 55 | $tests[$test->number] = [ 56 | 'name' => $test->name, 57 | 'passed' => $test->passed 58 | ]; 59 | } 60 | 61 | $response->getBody()->write(view('server-tests', [ 62 | 'title' => 'Micropub Rocks!', 63 | 'endpoint' => $this->endpoint, 64 | 'tests' => $tests, 65 | ])); 66 | return $response; 67 | } 68 | 69 | public function get_test(ServerRequestInterface $request, ResponseInterface $response, $args) { 70 | if($check = $this->_check_permissions($request, $response)) 71 | return $check; 72 | 73 | $test = ORM::for_table('tests')->where('group','server')->where('number',$args['num'])->find_one(); 74 | 75 | if(!$test) 76 | return $response->withHeader('Location', '/server-tests?endpoint='.$this->endpoint->id)->withStatus(302); 77 | 78 | $response->getBody()->write(view('server-tests/'.$args['num'], [ 79 | 'title' => 'Micropub Rocks!', 80 | 'endpoint' => $this->endpoint, 81 | 'test' => $test, 82 | ])); 83 | return $response; 84 | } 85 | 86 | public function micropub_request(ServerRequestInterface $request, ResponseInterface $response) { 87 | session_setup(); 88 | 89 | if(!is_logged_in()) { 90 | return new JsonResponse(['error'=>'unauthorized'], 401); 91 | } 92 | 93 | $params = $request->getParsedBody(); 94 | 95 | $user = logged_in_user(); 96 | 97 | $endpoint = ORM::for_table('micropub_endpoints') 98 | ->where('user_id', $user->id) 99 | ->where('id', $params['endpoint']) 100 | ->find_one(); 101 | 102 | if(!$endpoint) { 103 | return new JsonResponse(['error'=>'invalid_endpoint'], 400); 104 | } 105 | 106 | $client = new GuzzleHttp\Client(); 107 | 108 | if(!(array_key_exists('skipauth', $params) && $params['skipauth'] == 1)) { 109 | $options = [ 110 | 'headers' => [ 111 | 'Authorization' => 'Bearer ' . (isset($params['access_token']) ? $params['access_token'] : $endpoint->access_token) 112 | ] 113 | ]; 114 | } 115 | 116 | $options['allow_redirects'] = false; 117 | 118 | $endpoint_url = $endpoint->micropub_endpoint; 119 | switch($params['method']) { 120 | case 'get': 121 | $method = 'GET'; 122 | $endpoint_url = $params['url']; 123 | $options['headers']['Accept'] = 'application/json'; 124 | break; 125 | case 'post': 126 | $method = 'POST'; 127 | $options['headers']['Content-type'] = 'application/x-www-form-urlencoded'; 128 | $options['body'] = $params['body']; 129 | break; 130 | case 'postjson': 131 | $method = 'POST'; 132 | $options['body'] = $params['body']; 133 | $options['headers']['Content-type'] = 'application/json'; 134 | $options['headers']['Accept'] = 'application/json'; 135 | break; 136 | case 'multipart': 137 | $method = 'POST'; 138 | $options['multipart'] = []; 139 | if(isset($params['params'])) { 140 | foreach($params['params'] as $prop=>$val) { 141 | $options['multipart'][] = [ 142 | 'name' => $prop, 143 | 'contents' => $val 144 | ]; 145 | } 146 | } 147 | foreach($params['files'] as $prop=>$files) { 148 | if(!is_array($files)) $files = [$files]; 149 | foreach($files as $file) { 150 | if(strpos($file,'/') === false) { 151 | $options['multipart'][] = [ 152 | 'name' => (count($files) == 1 ? $prop : $prop.'[]'), 153 | 'contents' => fopen('public/media/'.$file, 'r'), 154 | 'filename' => $file 155 | ]; 156 | } 157 | } 158 | } 159 | if(array_key_exists('url', $params) && $params['url']) 160 | $endpoint_url = $params['url']; 161 | break; 162 | } 163 | 164 | if(!preg_match('/^https?:\/\//', $endpoint_url)) { 165 | return new JsonResponse([ 166 | 'code' => '', 167 | 'location' => null, 168 | 'content_type' => null, 169 | 'headers' => [], 170 | 'body' => '', 171 | 'json' => [], 172 | 'debug' => 'Invalid endpoint URL: '.$endpoint_url 173 | ], 200); 174 | } 175 | 176 | try { 177 | $res = $client->request($method, $endpoint_url, $options); 178 | } catch(RequestException $e) { 179 | $res = $e->getResponse(); 180 | $debug = $e->getMessage(); 181 | } catch(\Exception $e) { 182 | $res = null; 183 | $debug = $e->getMessage(); 184 | } 185 | 186 | if(!$res) { 187 | return new JsonResponse([ 188 | 'code' => '', 189 | 'location' => null, 190 | 'content_type' => null, 191 | 'headers' => [], 192 | 'body' => '', 193 | 'json' => [], 194 | 'debug' => (isset($debug) ? $debug : 'Unknown error making request to the Micropub endpoint') 195 | ], 200); 196 | } 197 | 198 | $code = $res->getStatusCode(); 199 | $location = $res->getHeader('Location'); 200 | $content_type = $res->getHeader('Content-Type'); 201 | $headers = $res->getHeaders(); 202 | $body = ''.$res->getBody(); 203 | 204 | if($location && array_key_exists(0, $location)) 205 | $location = $location[0]; 206 | else 207 | $location = false; 208 | 209 | if($content_type && array_key_exists(0, $content_type)) { 210 | $content_type = $content_type[0]; 211 | if(preg_match('/application\/json/', $content_type)) { 212 | $content_type = 'application/json'; 213 | } 214 | } else { 215 | $content_type = false; 216 | } 217 | 218 | $debug = 'HTTP/1.1 '.$code.' '.$res->getReasonPhrase()."\n"; 219 | foreach($headers as $k=>$vs) { 220 | foreach($vs as $v) { 221 | $debug .= $k.': '.$v."\n"; 222 | } 223 | } 224 | $debug .= "\n"; 225 | 226 | $json = null; 227 | if($body && $body[0] == '{' && $content_type == 'application/json') { 228 | if($json = @json_decode($body)) 229 | $debug .= json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); 230 | else 231 | $debug .= $body; 232 | } else { 233 | $debug .= $body; 234 | } 235 | 236 | // Store the last response in the test_results table 237 | $last = ORM::for_table('test_results') 238 | ->where('endpoint_id', $endpoint->id) 239 | ->where('test_id', $params['test']) 240 | ->find_one(); 241 | if(!$last) { 242 | $last = ORM::for_table('test_results')->create(); 243 | $last->endpoint_id = $endpoint->id; 244 | $last->test_id = $params['test']; 245 | $last->created_at = date('Y-m-d H:i:s'); 246 | } 247 | $last->passed = 0; 248 | $last->response = $debug; 249 | $last->location = $location; 250 | $last->last_result_at = date('Y-m-d H:i:s'); 251 | $last->save(); 252 | 253 | $endpoint->last_test_at = date('Y-m-d H:i:s'); 254 | $endpoint->save(); 255 | 256 | return new JsonResponse([ 257 | 'code' => $code, 258 | 'location' => $location, 259 | 'content_type' => $content_type, 260 | 'headers' => $headers, 261 | 'body' => $body, 262 | 'json' => $json, 263 | 'debug' => $debug 264 | ], 200); 265 | } 266 | 267 | public function media_check(ServerRequestInterface $request, ResponseInterface $response) { 268 | session_setup(); 269 | 270 | if(!is_logged_in()) { 271 | return new JsonResponse(['error'=>'unauthorized'], 401); 272 | } 273 | 274 | $params = $request->getParsedBody(); 275 | 276 | $user = logged_in_user(); 277 | 278 | $client = new GuzzleHttp\Client(); 279 | 280 | try { 281 | $res = $client->request('GET', $params['url'], []); 282 | } catch(RequestException $e) { 283 | $res = $e->getResponse(); 284 | } 285 | 286 | $code = $res->getStatusCode(); 287 | $content_type = $res->getHeader('Content-Type'); 288 | if($content_type) { 289 | $content_type = $content_type[0]; 290 | } 291 | 292 | return new JsonResponse([ 293 | 'code' => $code, 294 | 'http' => 'HTTP/1.1 '.$code.' '.$res->getReasonPhrase(), 295 | 'content_type' => $content_type, 296 | ], 200); 297 | } 298 | 299 | public function store_result(ServerRequestInterface $request, ResponseInterface $response) { 300 | session_setup(); 301 | 302 | if(!is_logged_in()) { 303 | return new JsonResponse(['error'=>'unauthorized'], 401); 304 | } 305 | 306 | $params = $request->getParsedBody(); 307 | 308 | $user = logged_in_user(); 309 | 310 | $endpoint = ORM::for_table('micropub_endpoints') 311 | ->where('user_id', $user->id) 312 | ->where('id', $params['endpoint']) 313 | ->find_one(); 314 | 315 | if(!$endpoint) { 316 | return new JsonResponse(['error'=>'invalid_endpoint'], 400); 317 | } 318 | 319 | $last = ORM::for_table('test_results') 320 | ->where('endpoint_id', $endpoint->id) 321 | ->where('test_id', $params['test']) 322 | ->find_one(); 323 | 324 | if($last) { 325 | $last->passed = $params['passed']; 326 | $last->save(); 327 | } 328 | 329 | return new JsonResponse([ 330 | 'result' => 'ok' 331 | ], 200); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /views/implementation-report-client.php: -------------------------------------------------------------------------------- 1 | layout('layout', [ 2 | 'title' => $title, 3 | ]); ?> 4 | 5 |
6 | 7 |
8 | 17 | 18 |

Implementation Report

19 | 20 |

21 |

22 | 23 | 24 | 25 | 26 | 27 | 68 |
Client Namename ?>
69 |
70 |

71 | 72 | 73 | 74 |
The results below are automatically compiled from the various test results for your implementation. You can re-run tests to update the results here.
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
number ?>implements) ?>description ?>
85 | 86 |

Implementation Report Template

87 | 88 |

Below is an implementation report template you can use to submit an implementation report. The answers have been pre-filled based on the checkboxes above, but there are some questions that do not have a corresponding automatic test. Please review the answers in this report and fill out any missing information based on your implementation. When you are complete, you can submit this as a new file on GitHub.

89 | 90 |

Any items in the list below with double square brackets, (e.g. [[ ]]) will need to be self-reported, as there is no automated test that will check those off. Any items with single square brackets will be checked off for you as you progress through the tests. When a specific test corresponds with a checkbox, the test number is noted in the list. Some items such as authentication will be checked off from multiple tests.

91 | 92 |
# Implementation Name (Replace this header)
 93 | 
 94 | Implementation Home Page URL: 
 95 | 
 96 | Source code repo URL(s) (optional):
 97 | * [[ ]] 100% open source implementation
 98 | 
 99 | Programming Language(s): 
100 | 
101 | Developer(s): [Name](https://you.example.com)
102 | 
103 | Answers are:
104 | * [x] Confirmed via micropub.rocks
105 | 
106 | ## Discovery
107 | * [] The client discovers the Micropub endpoint given the profile URL of a user (e.g. the sign-in form asks the user to enter their URL, which is used to find the Micropub endpoint)
108 | 
109 | ## Authentication
110 | * [] The client sends the access token in the HTTP `Authorization` header for `x-www-form-urlencoded` requests.
111 | * [] The client sends the access token in the post body for `x-www-form-urlencoded` requests.
112 | * [] The client sends the access token in the HTTP `Authorization` header for `multipart/form-data` requests.
113 | * [] The client sends the access token in the post body for `multipart/form-data` requests.
114 | * [] The client requests one or more `scope` values when obtaining user authorization.
115 |  * (list scopes requested here)
116 | 
117 | ## Syntax
118 | * [] 100: Creates posts using `x-www-form-urlencoded` syntax.
119 | * [] 200: Creates posts using JSON syntax.
120 | * [] 101: Creates posts using `x-www-form-urlencoded` syntax with multiple values of the same property name (e.g. tags).
121 | * [] 201: Creates posts using JSON syntax with multiple values of the same property name (e.g. tags).
122 | * [] 202: Creates posts with HTML content. (JSON)
123 | * [] 204: Creates posts using JSON syntax including a nested Microformats2 object.
124 | * [] 300: Creates posts including a file by sending the request as `multipart/form-data` to the Micropub endpoint.
125 | 
126 | ## Creating Posts
127 | * [] 104: Allows creating posts with a photo referenced by URL rather than uploading the photo as a Multipart request. (form-encoded)
128 | * [] 203: Allows creating posts with a photo referenced by URL rather than uploading the photo as a Multipart request. (JSON)
129 | * [] 205: Allows creating posts with a photo including image alt text.
130 | * [] Recognizes HTTP 201 and 202 with a `Location` header as a successful response from the Micropub endpoint.
131 | * [] 105: Allows the user to specify one or more syndication endpoints from their list of endpoints discovered in the `q=config` or `q=syndicate-to` query.
132 | 
133 | ## Media Endpoint
134 | * [] 700: Checks to see if the Micropub endpoint specifies a Media Endpoint, and uploads photos there instead.
135 | * [[ ]] Uses multipart requests only as a fallback when there is no Media Endpoint specified.
136 | 
137 | ## Updates
138 | * [] 400: Supports replacing all values of a property (e.g. replacing the post content).
139 | * [] 401: Supports adding a value to a property (e.g. adding a tag).
140 | * [] 402: Supports removing a value from a property (e.g. removing a specific tag).
141 | * [] 403: Supports removing a property.
142 | * [[ ]] Recognizes HTTP 200, 201 and 204 as a successful response from the Micropub endpoint.
143 | 
144 | ## Deletes
145 | * [] 500: Sends deletion requests using `x-www-form-urlencoded` syntax.
146 | * [] 500: Sends deletion requests using JSON syntax.
147 | * [] 502: Sends undeletion requests using `x-www-form-urlencoded` syntax.
148 | * [] 502: Sends undeletion requests using JSON syntax.
149 | 
150 | ## Querying
151 | * [] 600: Queries the Micropub endpoint with `q=config`
152 |  * [[]] Looks in the response for the Media Endpoint
153 |  * [[ ]] Looks in the response for syndication targets
154 | * [] 601: Queries the Micropub endpoint with `q=syndicate-to`
155 | * [] 602: Queries the Micropub endpoint for a post's source content without specifying a list of properties
156 | * [] 603: Queries the Micropub endpoint for a post's source content looking only for specific properties
157 | 
158 | ## Extensions
159 | 
160 | Please list any [Micropub extensions](https://indieweb.org/Micropub-extensions) that the client supports.
161 | 
162 | ## Vocabularies
163 | 
164 | Please list all vocabularies and properties the client supports, if applicable.
165 | 
166 | ## Other Notes
167 | 
168 | Please use this space to document anything else significant about your implementation.
169 | 
170 | 171 |
172 |
173 | 174 | 250 | --------------------------------------------------------------------------------