Skip to content

Commit

Permalink
Merge pull request #1 from skpy/v1
Browse files Browse the repository at this point in the history
release version 1
  • Loading branch information
skpy authored Apr 20, 2018
2 parents 205f224 + a522794 commit 3717e47
Show file tree
Hide file tree
Showing 9 changed files with 634 additions and 244 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
composer.phar
composer.lock
config.php
secret.txt
vendor/
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
# micropub
a minimal PHP micropub endpoint, with media support
a minimal PHP micropub endpoint for static sites, with media support

This is a micropub solution for static sites generated with [Hugo](https://gohugo.io/). It will accept most actions as defined in the [Micropub standard](https://www.w3.org/TR/micropub/), and upon completion will invoke Hugo and rebuild your site.

This is based heavily off of the following projects:
* [rhiaro's MVP micropub](https://rhiaro.co.uk/2015/04/minimum-viable-micropub)
* [dgold's Nanopub](https://github.com/dg01d/nanopub/)
* [aaronpk's MVP Media Endpoint](https://gist.github.com/aaronpk/4bee1753688ca9f3036d6f31377edf14)

My personal setup is a little convoluted. I run a variety of sites on my server, all with different document roots. I run PHP in a container, which mounts my host's `/var/www/html` into the container. On my host, `/var/www/html` holds a WordPress multi-site setup. My other sites are all static sites generated with [Hugo](https://gohugo.io/).

I use [Caddy](https://caddyserver.com/) as my web server. In my static sites, I have the following directive to make all PHP requests work with the PHP container:
```
fastcgi / 127.0.0.1:9000 php { root /var/www/html }
```
But because PHP is running in a container, it does not have access to anything outside of `/var/www/html`. In order to get my micropub-published files into my static sites, I use [incron](http://inotify.aiken.cz/?section=incron&page=about&lang=en). I create a new `incrontab` entry for each static site that should be micropub-enabled, to watch a directory that corresponds with the site's domain name. When a new file is written, the `micropub.sh` script in this repository will execute, copying and moving the files as necessary. If it's a Markdown file, `hugo` is invoked to rebuild the site.

The `micropub.sh` script copies AND moves images. This is so that micropub endpoints can see and access uploaded images without requiring a full site rebuild. The image is copied into the `/images/` directory of the site's docroot, and moved to the `/static/images` directory of the source of my Hugo site.

The `is.php` script in this repo is an example of how to use most of this functionality **without** a full micropub setup. I use it to power [https://skippy.is/](https://skippy.is/) for easy uploading from my phone. It builds the Markdown file in exactly the way I want with minimal input from me.
This works **for me**, following the principles of [self dog fooding](https://indieweb.org/selfdogfood). Rather then develop a universal widget that might work for all possible implementation, I built what I needed. Hopefully this serves as an inspiration for others, in the same way that those projects linked above heavily inspired me.
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"require": {
"symfony/yaml": "4.0.*",
"p3k/micropub": "*"
}
}
50 changes: 50 additions & 0 deletions config.php.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
this is the configuration file for my micropub implementation. Rather than
jump through a bunch of hoops trying to parse the host name, and figure out
the current working directory, just capture those values as needed.
**/
$config = array(
# the URL of our site, with trailing slash.
'base_url' => 'https://' . $_SERVER['HTTP_HOST'] .'/',

# the base path of the site's docroot, with trailing slash
'base_path' => '/var/www/html/',

# the name of the sub-directory for images, with trailing slash.
# we'll create sub-directories of the form 'year/month/'.
'upload_path' => 'images/',

# the max pixel width of uploaded images.
'max_image_width' => 800,

# the path to the Hugo site. DO NOT include "content/", we'll handle that.
# trailing slash required.
'source_path' => '/var/www/skippy/',

# different types of content may have different paths.
# by default, entries are in the root of the /content/ directory, so
# are not included here. Notes are in the /note/ directory.
'content_paths' => array(
'note' => 'note/' . date('Y/m/d/'),
),

# whether or not to copy uploaded files to the source /static/ directory.
'copy_uploads_to_source' => true,

# an array of syndication targets; each of which should contain the
# necessary credentials.
'syndication' => array(
'twitter' => 'smerrill',
),

# the timezone to use for all times
'tz' => 'America/New_York',

# the command used to build the site
'command' => '/var/www/bin/hugo --quiet --config /var/www/skippy/config.yaml -s /var/www/skippy/ -d /var/www/html/',
);

return $config;
?>
134 changes: 134 additions & 0 deletions inc/common.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php
function http_status ($code) {
$http_codes = array(
'200' => '200 OK',
'201' => '201 Created',
'202' => '202 Accepted',
'400' => '400 Bad Request',
'401' => '401 Unauthorized',
'403' => '403 Forbidden',
'409' => '409 Conflict',
'413' => '413 Payload Too Large',
'415' => '415 Unsupported Media Type',
'502' => '502 Bad Gateway',
);
return $http_codes[$code];
}

function quit ($code = 400, $error = '', $description = 'An error occurred.', $location = '') {
$code = (int) $code;
$status = 'success';
header("HTTP/1.1 " . http_status($code));
if ( $code >= 400 ) {
echo json_encode(['error' => $error, 'error_description' => $description]);
} elseif ($code == 200 || $code == 201 || $code == 202) {
if (!empty($location)) {
header('Location: ' . $location);
}
}
die();
}

function show_info() {
echo '<p>This is a <a href="https://www.w3.org/TR/micropub/">micropub</a> endpoint.</p>';
die();
}

function parse_request() {
if ( strtolower($_SERVER['CONTENT_TYPE']) == 'application/json' || strtolower($_SERVER['HTTP_CONTENT_TYPE']) == 'application/json' ) {
$request = \p3k\Micropub\Request::createFromJSONObject(json_decode(file_get_contents('php://input'), true));
} else {
$request = \p3k\Micropub\Request::createFromPostArray($_POST);
}
if($request->error) {
quit(400, $request->error_property, $request->error_description);
}
return $request;
}

/**
* getallheaders() replacement for nginx
*
* Replaces the getallheaders function which relies on Apache
*
* @return array incoming headers from _POST
*/
if (!function_exists('getallheaders')) {
function getallheaders() {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
}

/**
* Validate incoming requests, using IndieAuth
*
* This section largely adopted from rhiaro
*
* @param array $token the authorization token to check
* @param string $me the site to authorize
*
* @return boolean true if authorised
*/
function indieAuth($token, $me = '') {
/**
* Check token is valid
*/
if ( $me == '' ) { $me = $_SERVER['HTTP_HOST']; }
$ch = curl_init("https://tokens.indieauth.com/token");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Authorization: $token"));
$response = Array();
$curl_response = curl_exec($ch);
if (false === $curl_response) {
quit(502, 'connection_problem', 'Unable to connect to indieauth service');
}
parse_str($curl_response, $response);
curl_close($ch);
if (empty($response) || ! isset($response['me']) || ! isset($response['scope']) ) {
quit(401, 'insufficient_scope', 'The request lacks authentication credentials');
} elseif ($response['me'] != $me) {
quit(401, 'insufficient_scope', 'The request lacks valid authentication credentials');
} elseif (is_array($response['scope']) && !in_array('create', $response['scope']) && !in_array('post', $response['scope'])) {
quit(403, 'forbidden', 'Client does not have access to this resource');
} elseif (FALSE === stripos($response['scope'], 'create')) {
quit(403, 'Forbidden', 'Client does not have access to this resource');
}
// we got here, so all checks passed. return true.
return true;
}

# respond to queries about config and/or syndication
function show_config($show = 'all') {
global $config;
$syndicate_to = array();
if ( ! empty($config['syndication'])) {
foreach ($config['syndication'] as $k => $v) {
$syndicate_to[] = array('uid' => $k, 'name' => $k);
}
}

$conf = array("media-endpoint" => $config['base_url'] . 'micropub/index.php');
if ( ! empty($syndicate_to) ) {
$conf['syndicate_to'] = $syndicate_to;
}

header('Content-Type: application/json');
if ($show == "syndicate-to") {
echo json_encode(array('syndicate-to' => $syndicate_to), 32 | 64 | 128 | 256);
} else {
echo json_encode($conf, 32 | 64 | 128 | 256);
}
exit;
}

function build_site() {
global $config;
exec( $config['command']);
}
?>
Loading

0 comments on commit 3717e47

Please sign in to comment.