Plugin Directory

Changeset 3754860


Ignore:
Timestamp:
10/19/2026 01:44:34 PM (4 weeks ago)
Author:
robert.peake
Message:

Soapbox for Blygger 0.1.3: Blygger 0.4

Location:
soapbox-for-blygger
Files:
20 edited
1 copied

Legend:

Unmodified
Added
Removed
  • soapbox-for-blygger/tags/0.1.3/readme.txt

    r3737840 r3754860  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.1.2
     7Stable tag: 0.1.3
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1515An independent implementation of the Blygger protocol; not affiliated with the Blygger project.
    1616
    17 Soapbox for Blygger makes your WordPress site a publisher under the [Blygger protocol](https://blygger.org/spec/0.3/), an open protocol for publishing on your own domain in a way other people's software can subscribe to, archive completely, and quote exactly.
     17Soapbox for Blygger makes your WordPress site a publisher under the [Blygger protocol](https://blygger.org/spec/0.4/), an open protocol for publishing on your own domain in a way other people's software can subscribe to, archive completely, and quote exactly.
    1818
    1919Once it is switched on, every post you publish also becomes an item of your **blyg**, at an address such as `https://example.com/blyg/`. For each item the plugin keeps:
    … …  
    2828= Before you publish =
    2929
    30 **The protocol is a draft.** This plugin implements Blygger 0.3, which is before 1.0. Its wire format may change, and the plugin will follow it. What will not change is what publishing means: every address your blyg serves is a promise to keep answering there, and a pin cannot be undone. Read "Can I undo this?" below first.
     30**The protocol is a draft.** This plugin implements Blygger 0.4. Like every version before 1.0 it is a draft: its wire format may change, and the plugin will follow it. What will not change is what publishing means: every address your blyg serves is a promise to keep answering there, and a pin cannot be undone. Read "Can I undo this?" below first.
    3131
    3232**Nothing is published until you say so.** Activating the plugin publishes nothing. You choose where the blyg lives and how it is laid out, save that, and then switch publishing on. Before you run the backfill, which is the step that puts your existing posts out, do these four things:
    … …  
    37374. **Be sure of the mount path and the layout.** Both are permanent from the first item published.
    3838
    39 **This is an independent implementation.** Soapbox for Blygger is not written by, endorsed by or affiliated with the authors of the Blygger protocol. The protocol's specification is at [blygger.org](https://blygger.org/spec/0.3/).
     39**This is an independent implementation.** Soapbox for Blygger is not written by, endorsed by or affiliated with the authors of the Blygger protocol. The protocol's specification is at [blygger.org](https://blygger.org/spec/0.4/).
    4040
    4141= What it does not do =
    … …  
    8383= Which surface layout should I choose? =
    8484
    85 **Fixed paths** (selected to begin with) serves the protocol's own file names under the mount: `feed.xml`, `items/index.json`, `items/{id}.json`. This is Blygger 0.3 as published, and every reader understands it. Many hosts cache addresses ending in `.json` and `.xml` aggressively, so check Site Health after you choose it; see the next question.
    86 
    87 **Templated** serves the manifest at `/blyg/blyg.json` and everything else through the WordPress REST API, at addresses the manifest names. It avoids the paths hosts cache, but it follows a construct the protocol has ruled on for version 0.4 and not yet published as part of 0.3 (the manifest locates the surface). A reader has to support that, and most do not yet: one that does not will find the manifest and then get 404s. Choose it only if you know your readers can use it.
     85**Fixed paths** (selected to begin with) serves the protocol's own file names under the mount: `feed.xml`, `items/index.json`, `items/{id}.json`. These are the protocol's default paths, unchanged since 0.3, so readers of any version find them. Many hosts cache addresses ending in `.json` and `.xml` aggressively, so check Site Health after you choose it; see the next question.
     86
     87**Templated** serves the manifest at `/blyg/blyg.json` and everything else through the WordPress REST API, at addresses the manifest names. It avoids the paths hosts cache. It uses the manifest-located surface, which is part of Blygger 0.4: readers built to 0.4 must follow it, and the reference client does. A reader built only to 0.3 will find the manifest and then get 404s.
    8888
    8989The choice is permanent once the blyg has an item, so it is made by you, on the settings screen, before publishing can be switched on.
    … …  
    148148= What are Webmentions, and what happens if I switch them on? =
    149149
    150 When another blyg quotes one of your items, answers it or forks it, that site can tell yours, by sending a Webmention. It is off by default.
     150When another blyg quotes one of your items, answers it, forks it or has machine-generated text that drew on it, that site can tell yours, by sending a Webmention. It is off by default.
    151151
    152152If you switch it on (Settings > Soapbox for Blygger > Mentions), then for each mention your site receives it fetches the sender's page and its item document, and sometimes that blyg's manifest, to check that the document really refers to your item. See "External requests" below for exactly what that involves. Verified mentions are listed under Tools > Soapbox for Blygger, for administrators. Nothing is shown to visitors, and nothing from the other site is stored except its address, its item's id and its author's published name.
    … …  
    161161
    162162Test it before you publish anything, and above all before you run the backfill: what is published cannot be unpublished, only withdrawn. The plugin renders each post as a logged-out visitor would see it, by running the post through `the_content` outside the main loop. A plugin that hides content only inside the loop, or only on the post's own page, may not hide it there, and the full text would then be published in the blyg. Publish a test post, open its item document from the Blyg panel in the editor, and look at what it contains. If it shows more than a visitor should see, leave that post type unticked, or exclude posts with the `soapbox_blyg_is_public` filter.
     163
     164= Can I help translate it? =
     165
     166Yes, at [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/soapbox-for-blygger/). The plugin ships in English; each language reaches sites as a language pack once its translations are approved there.
    163167
    164168= Does it work on multisite? =
    … …  
    180184How a mention is handled. Your site answers the sender at once and looks nothing up while it does: the sender's address is resolved, checked and fetched afterwards, by a background job. That job stops after twenty seconds in all, checked between each step; one step it cannot cut short is a DNS lookup, which can take as long as your server's own resolver allows. Each sender is limited by the number of requests it makes, whether or not they are accepted.
    181185
    182 Known limits. Each request is pinned to the address that was checked, which closes DNS rebinding. That needs PHP's curl extension, which nearly every host has. A site without it, or one that sends its outbound requests through a proxy (`WP_PROXY_HOST`), cannot pin a request, so it verifies no Webmentions: they are accepted and then marked as failed. The templated layout's manifest declares Blygger "0.3", because the specification does not say what a manifest that uses the 0.4 construct should declare. IPv6-only senders are refused: a sender's host must have an IPv4 address, and no address it has, in either family, may be private. WordPress only allows such requests on ports 80, 443 and 8080; a sender on another port is refused unless you add it with WordPress's `http_allowed_safe_ports` filter. A mention is believed only when its item document is at exactly the address its own blyg gives for it, written plainly: an address with `..` in it, or with characters percent-encoded that need not be, is refused. A document kept on another host than its blyg (a CDN, say) cannot be sent as the source itself; send the page that links to it, which must be on the blyg's own host. In the templated layout, your own items are at addresses only your manifest names, so a receiver that implements Blygger 0.3 alone cannot verify a mention that names one of your items as its source; a receiver that reads the manifest's templates can. That is one more reason the fixed layout is the one selected to begin with.
     186Known limits. Each request is pinned to the address that was checked, which closes DNS rebinding. That needs PHP's curl extension, which nearly every host has. A site without it, or one that sends its outbound requests through a proxy (`WP_PROXY_HOST`), cannot pin a request, so it verifies no Webmentions: they are accepted and then marked as failed. IPv6-only senders are refused: a sender's host must have an IPv4 address, and no address it has, in either family, may be private. WordPress only allows such requests on ports 80, 443 and 8080; a sender on another port is refused unless you add it with WordPress's `http_allowed_safe_ports` filter. A mention is believed only when its item document is at exactly the address its own blyg gives for it, written plainly: an address with `..` in it, or with characters percent-encoded that need not be, is refused. A document kept on another host than its blyg (a CDN, say) cannot be sent as the source itself; send the page that links to it, which must be on the blyg's own host. In the templated layout, your own items are at addresses only your manifest names, so a receiver that implements Blygger 0.3 alone cannot verify a mention that names one of your items as its source; a receiver that reads the manifest's templates can. That is one more reason the fixed layout is the one selected to begin with.
    183187
    184188Caching. The plugin states its lifetime first, and relies on caches using the first of a repeated instruction, as RFC 9111 says and Varnish does. A cache that does not follow that rule could still use a lifetime your host appends. nginx's `proxy_cache` and `fastcgi_cache` are one such case: when the host's instruction arrives as a separate `Cache-Control` header line after the plugin's, nginx uses the last line. This has not been tested with nginx in front. Site Health asks for the documents as a reader would and compares what it is given with the documents as they are now; if it is given an old copy, purge that cache and exclude the blyg's paths from it as described above.
    … …  
    261265== Changelog ==
    262266
     267= 0.1.3 =
     268* Publishes under Blygger 0.4, the protocol's published version: every document says `"blyg": "0.4"`, in both layouts. Nothing else in them changes. Every 0.3 document is a valid 0.4 document, and readers accept any 0.x.
     269* Pinned versions say "0.4" too, so their files change by those three characters. A pin promises its content, which the content hash covers, and that is unchanged.
     270* The manifest names where the plugin's source is (`generator_url`), as 0.4 recommends.
     271* Webmentions: a mention from an item whose machine-generated text drew on one of yours now verifies, as 0.4's fourth relation ("Drew on" under Tools).
     272* With Soapbox Blyg still installed, the settings now say that "Delete all data" has been switched off and does not come back on by itself.
     273
    263274= 0.1.2 =
    264275* The plugin is now called Soapbox for Blygger, and its folder is `soapbox-for-blygger`. It was Soapbox Blyg. Nothing a site has published moves: the blyg's addresses, its ids, its settings and its data are exactly where they were, and so are the plugin's hooks, functions and `wp blyg` commands.
    … …  
    280291== Upgrade Notice ==
    281292
     293= 0.1.3 =
     294Moves your blyg to Blygger 0.4. No address, id or version changes.
     295
    282296= 0.1.2 =
    283297Renamed from Soapbox Blyg. Your blyg, its addresses and its data do not move. Activate this plugin before you deactivate Soapbox Blyg, then delete Soapbox Blyg.
  • soapbox-for-blygger/tags/0.1.3/soapbox-for-blygger.php

    r3737840 r3754860  
    44 * Plugin URI:        https://github.com/cyberscribe/soapbox-blyg
    55 * Description:       Publishes your posts as a blyg: stable ids, versions, pins and a feed that other clients can subscribe to and quote. An independent implementation of the Blygger protocol; not affiliated with the Blygger project.
    6  * Version:           0.1.2
     6 * Version:           0.1.3
    77 * Requires at least: 6.6
    88 * Requires PHP:      7.4
    … …  
    5252const SOAPBOX_BLYG_TEXT_DOMAIN = 'soapbox-for-blygger';
    5353const SOAPBOX_BLYG_GENERATOR   = 'soapbox-for-blygger';
    54 const SOAPBOX_BLYG_VERSION     = '0.1.2';
     54const SOAPBOX_BLYG_VERSION     = '0.1.3';
    5555const SOAPBOX_BLYG_FILE        = __FILE__;
    5656
    5757/*
    58  * Where this client's source lives, for the manifest's generator_url (spec §6.1). Empty until
    59  * there is a public address to name; the key is then left out.
     58 * Where this client's source lives, for the manifest's generator_url (spec §3.2, §6.1). Empty
     59 * leaves the key out.
    6060 */
    61 const SOAPBOX_BLYG_GENERATOR_URL = '';
     61const SOAPBOX_BLYG_GENERATOR_URL = 'https://github.com/cyberscribe/soapbox-blyg';
    6262
    6363/*
  • soapbox-for-blygger/tags/0.1.3/src/Admin/SettingsPage.php

    r3737840 r3754860  
    464464                                        'fixed'     => array(
    465465                                                __( 'Fixed paths', 'soapbox-for-blygger' ),
    466                                                 __( 'Blygger 0.3, understood by every reader. The protocol\'s own file names under the mount (feed.xml, items/index.json, items/{id}.json). Check Site Health afterwards: if your host caches them at the edge, exclude the mount path from that cache.', 'soapbox-for-blygger' ),
     466                                                __( 'The protocol\'s default paths under the mount (feed.xml, items/index.json, items/{id}.json), unchanged since 0.3: readers of any version find them. Check Site Health afterwards: if your host caches them at the edge, exclude the mount path from that cache.', 'soapbox-for-blygger' ),
    467467                                        ),
    468468                                        'templated' => array(
    469469                                                __( 'Templated', 'soapbox-for-blygger' ),
    470                                                 __( 'Blygger 0.4 draft (decision #51). Needs a reader that supports manifest-located surfaces; most don\'t yet. The manifest says where everything else is, and the documents are served through the REST API. Managed hosts often cache paths ending in .json and .xml for weeks, which would keep serving an item after you withdrew it; this layout avoids those paths.', 'soapbox-for-blygger' ),
     470                                                __( 'Blygger 0.4\'s manifest-located surface: the manifest says where everything else is, and the documents are served through the REST API. Readers built to 0.4 must follow it, and the reference client does; a reader built only to 0.3 finds the manifest and then gets 404s. Managed hosts often cache paths ending in .json and .xml for weeks, which would keep serving an item after you withdrew it; this layout avoids those paths.', 'soapbox-for-blygger' ),
    471471                                        ),
    472472                                );
    … …  
    554554                                                esc_html__( 'Delete all Soapbox for Blygger data when the plugin is deleted', 'soapbox-for-blygger' )
    555555                                        );
    556                                         $note( __( 'Kept off while Soapbox Blyg, this plugin\'s earlier name, is still installed: the two share one record of what has been published, and deleting Soapbox Blyg with this on would delete it. Delete Soapbox Blyg from the Plugins screen first.', 'soapbox-for-blygger' ) );
     556                                        $note( __( 'Switched off while Soapbox Blyg, this plugin\'s earlier name, is still installed: the two share one record of what has been published, and deleting Soapbox Blyg with this on would delete it. It does not come back on by itself: delete Soapbox Blyg from the Plugins screen, then tick this again if you want it.', 'soapbox-for-blygger' ) );
    557557                                        break;
    558558                                }
    … …  
    581581                                                sprintf(
    582582                                                        /* translators: 1: protocol version, 2: URL of the protocol specification */
    583                                                         __( 'This plugin publishes under the <a href="%2$s">Blygger protocol</a>, version %1$s, which is a draft: its wire format may change before 1.0, and this plugin will follow it. What does not change is the promise you make by publishing. Every address the blyg serves is meant to keep answering, and a pinned version is served forever. Soapbox for Blygger is an independent implementation and is not affiliated with the protocol\'s authors.', 'soapbox-for-blygger' ),
     583                                                        __( 'This plugin publishes under the <a href="%2$s">Blygger protocol</a>, version %1$s. Like every version before 1.0 it is a draft: its wire format may change, and this plugin will follow it. What does not change is the promise you make by publishing. Every address the blyg serves is meant to keep answering, and a pinned version is served forever. Soapbox for Blygger is an independent implementation and is not affiliated with the protocol\'s authors.', 'soapbox-for-blygger' ),
    584584                                                        esc_html( \SoapboxBlyg\Surface\Documents::PROTOCOL ),
    585                                                         esc_url( 'https://blygger.org/spec/0.3/' )
     585                                                        esc_url( 'https://blygger.org/spec/' . \SoapboxBlyg\Surface\Documents::PROTOCOL . '/' )
    586586                                                ),
    587587                                                array( 'a' => array( 'href' => true ) )
  • soapbox-for-blygger/tags/0.1.3/src/Admin/ToolsPage.php

    r3737840 r3754860  
    2828
    2929        const PAGE = 'soapbox-blyg-tools';
     30
     31        /**
     32         * The user meta that records which schema problem a person has dismissed.
     33         */
     34        const DISMISSED_META = 'soapbox_blyg_dismissed_problem';
    3035
    3136        /**
    … …  
    110115                $stamp   = Schema::problem_key();
    111116                if ( '' !== $problem && isset( $_GET['soapbox_blyg_dismiss'] ) && check_admin_referer( 'soapbox_blyg_dismiss' ) ) {
    112                         update_user_meta( get_current_user_id(), 'soapbox_blyg_dismissed_problem', $stamp );
    113                 }
    114                 if ( '' !== $problem && ( $ours || get_user_meta( get_current_user_id(), 'soapbox_blyg_dismissed_problem', true ) !== $stamp ) ) {
     117                        update_user_meta( get_current_user_id(), self::DISMISSED_META, $stamp );
     118                }
     119                if ( '' !== $problem && ( $ours || get_user_meta( get_current_user_id(), self::DISMISSED_META, true ) !== $stamp ) ) {
    115120                        echo '<div class="notice notice-error"><p>';
    116121                        echo esc_html(
    … …  
    391396                                                /* translators: %s: the address of one of this site's items, as a link. Another publication's item is a fork of it. */
    392397                                                'fork'         => __( 'Forked from %s', 'soapbox-for-blygger' ),
     398                                                /* translators: %s: the address of one of this site's items, as a link. Another publication's item has machine-generated text that drew on it. */
     399                                                'source'       => __( 'Drew on %s', 'soapbox-for-blygger' ),
    393400                                        );
    394401                                        ?>
  • soapbox-for-blygger/tags/0.1.3/src/Mentions/Verifier.php

    r3737840 r3754860  
    456456                $fetched_from = self::address( $document_url );
    457457                if ( $speaker . 'items/' . $id . '.json' !== $fetched_from ) {
    458                         // Not at the fixed address, so the origin may publish its items by template (§16.6e).
     458                        // Not at the fixed address, so the origin may publish its items by template (spec §6.1, §15.4).
    459459                        // Its manifest says where, and is the third and last document fetched: the page, the
    460460                        // item document, and this. It is asked for at the claimed origin and nowhere else, so
    … …  
    477477                        $template = isset( $manifest['body'] ) && self::is_plain( $manifest['url'] ) && self::address( $manifest['url'] ) === $speaker . 'blyg.json' ? json_decode( $manifest['body'], true ) : null;
    478478                        $template = is_array( $template ) && isset( $template['item'] ) && is_string( $template['item'] ) ? $template['item'] : '';
    479                         // {id} is the only expression a template may hold (URI Template level 1, spec §16.6e).
     479                        // {id} is the only expression a template may hold (URI Template level 1, spec §6.1).
    480480                        $expanded = str_replace( '{id}', rawurlencode( $id ), $template );
    481481                        if ( false === strpos( $template, '{id}' ) || false !== strpbrk( $expanded, '{}' ) || self::address( $expanded ) !== $fetched_from ) {
    … …  
    535535        /**
    536536         * How a document refers to one of our items, if it does (spec §15.4), in the protocol's order:
    537          * a stub of it, a transclusion of it, or a fork of a pinned version of it. Only the three
    538          * identity members of a reference are read; `cited` and `selector` are never consulted.
     537         * a stub of it, a transclusion of it, a fork of a pinned version of it, or a generation source
     538         * drawn from it. Only the identity members of a reference are read; `cited` and `selector`
     539         * are never consulted.
    539540         *
    540541         * @param array  $document  The source's item document.
    541542         * @param string $our_url   This origin's URL: its identity.
    542543         * @param string $target_id Our item's id.
    543          * @return string|null stub, transclusion, fork or null.
     544         * @return string|null stub, transclusion, fork, source or null.
    544545         */
    545546        private function relation( array $document, string $our_url, string $target_id ) {
    … …  
    570571                        if ( $row && (int) $row['pinned'] ) {
    571572                                return 'fork';
     573                        }
     574                }
     575                // A generation source (spec 0.4 §5.7): one of our items that machine-generated text in the
     576                // document drew on. Each span of `generated` lists its sources as references; one without
     577                // an origin is the sender's own item. Nothing is promised of the version, so no pin check.
     578                if ( isset( $document['generated'] ) && is_array( $document['generated'] ) ) {
     579                        foreach ( $document['generated'] as $span ) {
     580                                if ( ! is_array( $span ) || ! isset( $span['sources'] ) || ! is_array( $span['sources'] ) ) {
     581                                        continue;
     582                                }
     583                                foreach ( $span['sources'] as $entry ) {
     584                                        if ( $names( $entry ) ) {
     585                                                return 'source';
     586                                        }
     587                                }
    572588                        }
    573589                }
  • soapbox-for-blygger/tags/0.1.3/src/Plugin.php

    r3737840 r3754860  
    3636
    3737        /**
     38         * The WP-CLI command: `wp blyg`.
     39         */
     40        const CLI_COMMAND = 'blyg';
     41
     42        /**
    3843         * The one instance.
    3944         *
    … …  
    203208
    204209                if ( defined( 'WP_CLI' ) && WP_CLI ) {
    205                         \WP_CLI::add_command( 'blyg', Cli\Command::class );
     210                        \WP_CLI::add_command( self::CLI_COMMAND, Cli\Command::class );
    206211                }
    207212        }
    … …  
    239244         * this plugin is serving from: every id and every address the site has published. Nothing in
    240245         * this plugin runs at that moment, so the setting is switched off beforehand, here, and the
    241          * settings screen will not switch it on again until the earlier plugin has gone.
     246         * settings screen will not switch it on again until the earlier plugin has gone. Nor does
     247         * anything else: the owner ticks it again.
    242248         *
    243249         * The file is looked for only when the setting is on, so an ordinary request reads nothing extra.
    … …  
    263269                        return;
    264270                }
    265                 echo '<div class="notice notice-info"><p>' . esc_html__( 'Soapbox Blyg, the earlier name of Soapbox for Blygger, is still installed. It can be deleted: this plugin has taken over the same blyg, at the same addresses. Until it has gone, "Delete all data when the plugin is deleted" is kept switched off, because deleting Soapbox Blyg with that setting on would delete the record of everything this site has published.', 'soapbox-for-blygger' ) . '</p></div>';
     271                echo '<div class="notice notice-info"><p>' . esc_html__( 'Soapbox Blyg, the earlier name of Soapbox for Blygger, is still installed. It can be deleted: this plugin has taken over the same blyg, at the same addresses. Until it has gone, "Delete all data when the plugin is deleted" stays switched off, because deleting Soapbox Blyg with that setting on would delete the record of everything this site has published. It does not come back on by itself: after deleting Soapbox Blyg, tick it again if you want it.', 'soapbox-for-blygger' ) . '</p></div>';
    266272        }
    267273
  • soapbox-for-blygger/tags/0.1.3/src/Surface/Documents.php

    r3737840 r3754860  
    2828
    2929        /**
    30          * The protocol version these documents are written to (spec §3.1).
    31          */
    32         const PROTOCOL = '0.3';
     30         * The protocol version these documents are written to (spec §3.1). One value for every
     31         * document in both layouts, pinned versions included: a pin promises its content, which
     32         * content_hash covers, not its bytes (§8).
     33         */
     34        const PROTOCOL = '0.4';
    3335
    3436        /**
  • soapbox-for-blygger/tags/0.1.3/src/Surface/Locations.php

    r3737840 r3754860  
    2020 * The manifest is always at {origin}blyg.json: that filename is what lets a reader handed a bare
    2121 * origin find the blyg, and it is what makes the origin, and so the blyg's identity, a stable
    22  * path of the site's own choosing (spec §12.2, §16.6e). Everything past the manifest is located
     22 * path of the site's own choosing (spec §4, §12.2). Everything past the manifest is located
    2323 * by the manifest:
    2424 *
    … …  
    177177         * The item a URL names, within one origin: by its page, in any spelling the item has ever had,
    178178         * or by its item document URL in the origin's current layout. This is the check a Webmention's
    179          * target has to pass (spec §15.3, §16.6e: receivers check targets against their own templates).
     179         * target has to pass (spec §15.3: receivers check targets against their own templates).
    180180         *
    181181         * @param Origin $origin The origin.
  • soapbox-for-blygger/tags/0.1.3/src/Surface/RestDocuments.php

    r3737840 r3754860  
    1818/**
    1919 * Serves the feed, the archive index, item documents and pinned versions of a templated origin
    20  * from REST routes. The manifest names these URLs and readers follow it (spec §16.6e), so the
     20 * from REST routes. The manifest names these URLs and readers follow it (spec §6.1), so the
    2121 * documents can sit where WordPress routes natively, away from the .json and .xml paths that
    2222 * managed hosts treat as static files and cache at the edge.
  • soapbox-for-blygger/tags/0.1.3/src/Surface/Router.php

    r3737840 r3754860  
    2525 *
    2626 * The REST API is not used for the manifest: the protocol fixes that one filename at the origin
    27  * (spec §4, §16.6e), and {origin}blyg.json is what makes the origin's identity a path the site
     27 * (spec §4, §6.1), and {origin}blyg.json is what makes the origin's identity a path the site
    2828 * owns. add_feed() is not used either: it serves /feed/{name}/, and the protocol's default feed
    2929 * path is {origin}feed.xml.
  • soapbox-for-blygger/trunk/readme.txt

    r3737840 r3754860  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.1.2
     7Stable tag: 0.1.3
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1515An independent implementation of the Blygger protocol; not affiliated with the Blygger project.
    1616
    17 Soapbox for Blygger makes your WordPress site a publisher under the [Blygger protocol](https://blygger.org/spec/0.3/), an open protocol for publishing on your own domain in a way other people's software can subscribe to, archive completely, and quote exactly.
     17Soapbox for Blygger makes your WordPress site a publisher under the [Blygger protocol](https://blygger.org/spec/0.4/), an open protocol for publishing on your own domain in a way other people's software can subscribe to, archive completely, and quote exactly.
    1818
    1919Once it is switched on, every post you publish also becomes an item of your **blyg**, at an address such as `https://example.com/blyg/`. For each item the plugin keeps:
    … …  
    2828= Before you publish =
    2929
    30 **The protocol is a draft.** This plugin implements Blygger 0.3, which is before 1.0. Its wire format may change, and the plugin will follow it. What will not change is what publishing means: every address your blyg serves is a promise to keep answering there, and a pin cannot be undone. Read "Can I undo this?" below first.
     30**The protocol is a draft.** This plugin implements Blygger 0.4. Like every version before 1.0 it is a draft: its wire format may change, and the plugin will follow it. What will not change is what publishing means: every address your blyg serves is a promise to keep answering there, and a pin cannot be undone. Read "Can I undo this?" below first.
    3131
    3232**Nothing is published until you say so.** Activating the plugin publishes nothing. You choose where the blyg lives and how it is laid out, save that, and then switch publishing on. Before you run the backfill, which is the step that puts your existing posts out, do these four things:
    … …  
    37374. **Be sure of the mount path and the layout.** Both are permanent from the first item published.
    3838
    39 **This is an independent implementation.** Soapbox for Blygger is not written by, endorsed by or affiliated with the authors of the Blygger protocol. The protocol's specification is at [blygger.org](https://blygger.org/spec/0.3/).
     39**This is an independent implementation.** Soapbox for Blygger is not written by, endorsed by or affiliated with the authors of the Blygger protocol. The protocol's specification is at [blygger.org](https://blygger.org/spec/0.4/).
    4040
    4141= What it does not do =
    … …  
    8383= Which surface layout should I choose? =
    8484
    85 **Fixed paths** (selected to begin with) serves the protocol's own file names under the mount: `feed.xml`, `items/index.json`, `items/{id}.json`. This is Blygger 0.3 as published, and every reader understands it. Many hosts cache addresses ending in `.json` and `.xml` aggressively, so check Site Health after you choose it; see the next question.
    86 
    87 **Templated** serves the manifest at `/blyg/blyg.json` and everything else through the WordPress REST API, at addresses the manifest names. It avoids the paths hosts cache, but it follows a construct the protocol has ruled on for version 0.4 and not yet published as part of 0.3 (the manifest locates the surface). A reader has to support that, and most do not yet: one that does not will find the manifest and then get 404s. Choose it only if you know your readers can use it.
     85**Fixed paths** (selected to begin with) serves the protocol's own file names under the mount: `feed.xml`, `items/index.json`, `items/{id}.json`. These are the protocol's default paths, unchanged since 0.3, so readers of any version find them. Many hosts cache addresses ending in `.json` and `.xml` aggressively, so check Site Health after you choose it; see the next question.
     86
     87**Templated** serves the manifest at `/blyg/blyg.json` and everything else through the WordPress REST API, at addresses the manifest names. It avoids the paths hosts cache. It uses the manifest-located surface, which is part of Blygger 0.4: readers built to 0.4 must follow it, and the reference client does. A reader built only to 0.3 will find the manifest and then get 404s.
    8888
    8989The choice is permanent once the blyg has an item, so it is made by you, on the settings screen, before publishing can be switched on.
    … …  
    148148= What are Webmentions, and what happens if I switch them on? =
    149149
    150 When another blyg quotes one of your items, answers it or forks it, that site can tell yours, by sending a Webmention. It is off by default.
     150When another blyg quotes one of your items, answers it, forks it or has machine-generated text that drew on it, that site can tell yours, by sending a Webmention. It is off by default.
    151151
    152152If you switch it on (Settings > Soapbox for Blygger > Mentions), then for each mention your site receives it fetches the sender's page and its item document, and sometimes that blyg's manifest, to check that the document really refers to your item. See "External requests" below for exactly what that involves. Verified mentions are listed under Tools > Soapbox for Blygger, for administrators. Nothing is shown to visitors, and nothing from the other site is stored except its address, its item's id and its author's published name.
    … …  
    161161
    162162Test it before you publish anything, and above all before you run the backfill: what is published cannot be unpublished, only withdrawn. The plugin renders each post as a logged-out visitor would see it, by running the post through `the_content` outside the main loop. A plugin that hides content only inside the loop, or only on the post's own page, may not hide it there, and the full text would then be published in the blyg. Publish a test post, open its item document from the Blyg panel in the editor, and look at what it contains. If it shows more than a visitor should see, leave that post type unticked, or exclude posts with the `soapbox_blyg_is_public` filter.
     163
     164= Can I help translate it? =
     165
     166Yes, at [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/soapbox-for-blygger/). The plugin ships in English; each language reaches sites as a language pack once its translations are approved there.
    163167
    164168= Does it work on multisite? =
    … …  
    180184How a mention is handled. Your site answers the sender at once and looks nothing up while it does: the sender's address is resolved, checked and fetched afterwards, by a background job. That job stops after twenty seconds in all, checked between each step; one step it cannot cut short is a DNS lookup, which can take as long as your server's own resolver allows. Each sender is limited by the number of requests it makes, whether or not they are accepted.
    181185
    182 Known limits. Each request is pinned to the address that was checked, which closes DNS rebinding. That needs PHP's curl extension, which nearly every host has. A site without it, or one that sends its outbound requests through a proxy (`WP_PROXY_HOST`), cannot pin a request, so it verifies no Webmentions: they are accepted and then marked as failed. The templated layout's manifest declares Blygger "0.3", because the specification does not say what a manifest that uses the 0.4 construct should declare. IPv6-only senders are refused: a sender's host must have an IPv4 address, and no address it has, in either family, may be private. WordPress only allows such requests on ports 80, 443 and 8080; a sender on another port is refused unless you add it with WordPress's `http_allowed_safe_ports` filter. A mention is believed only when its item document is at exactly the address its own blyg gives for it, written plainly: an address with `..` in it, or with characters percent-encoded that need not be, is refused. A document kept on another host than its blyg (a CDN, say) cannot be sent as the source itself; send the page that links to it, which must be on the blyg's own host. In the templated layout, your own items are at addresses only your manifest names, so a receiver that implements Blygger 0.3 alone cannot verify a mention that names one of your items as its source; a receiver that reads the manifest's templates can. That is one more reason the fixed layout is the one selected to begin with.
     186Known limits. Each request is pinned to the address that was checked, which closes DNS rebinding. That needs PHP's curl extension, which nearly every host has. A site without it, or one that sends its outbound requests through a proxy (`WP_PROXY_HOST`), cannot pin a request, so it verifies no Webmentions: they are accepted and then marked as failed. IPv6-only senders are refused: a sender's host must have an IPv4 address, and no address it has, in either family, may be private. WordPress only allows such requests on ports 80, 443 and 8080; a sender on another port is refused unless you add it with WordPress's `http_allowed_safe_ports` filter. A mention is believed only when its item document is at exactly the address its own blyg gives for it, written plainly: an address with `..` in it, or with characters percent-encoded that need not be, is refused. A document kept on another host than its blyg (a CDN, say) cannot be sent as the source itself; send the page that links to it, which must be on the blyg's own host. In the templated layout, your own items are at addresses only your manifest names, so a receiver that implements Blygger 0.3 alone cannot verify a mention that names one of your items as its source; a receiver that reads the manifest's templates can. That is one more reason the fixed layout is the one selected to begin with.
    183187
    184188Caching. The plugin states its lifetime first, and relies on caches using the first of a repeated instruction, as RFC 9111 says and Varnish does. A cache that does not follow that rule could still use a lifetime your host appends. nginx's `proxy_cache` and `fastcgi_cache` are one such case: when the host's instruction arrives as a separate `Cache-Control` header line after the plugin's, nginx uses the last line. This has not been tested with nginx in front. Site Health asks for the documents as a reader would and compares what it is given with the documents as they are now; if it is given an old copy, purge that cache and exclude the blyg's paths from it as described above.
    … …  
    261265== Changelog ==
    262266
     267= 0.1.3 =
     268* Publishes under Blygger 0.4, the protocol's published version: every document says `"blyg": "0.4"`, in both layouts. Nothing else in them changes. Every 0.3 document is a valid 0.4 document, and readers accept any 0.x.
     269* Pinned versions say "0.4" too, so their files change by those three characters. A pin promises its content, which the content hash covers, and that is unchanged.
     270* The manifest names where the plugin's source is (`generator_url`), as 0.4 recommends.
     271* Webmentions: a mention from an item whose machine-generated text drew on one of yours now verifies, as 0.4's fourth relation ("Drew on" under Tools).
     272* With Soapbox Blyg still installed, the settings now say that "Delete all data" has been switched off and does not come back on by itself.
     273
    263274= 0.1.2 =
    264275* The plugin is now called Soapbox for Blygger, and its folder is `soapbox-for-blygger`. It was Soapbox Blyg. Nothing a site has published moves: the blyg's addresses, its ids, its settings and its data are exactly where they were, and so are the plugin's hooks, functions and `wp blyg` commands.
    … …  
    280291== Upgrade Notice ==
    281292
     293= 0.1.3 =
     294Moves your blyg to Blygger 0.4. No address, id or version changes.
     295
    282296= 0.1.2 =
    283297Renamed from Soapbox Blyg. Your blyg, its addresses and its data do not move. Activate this plugin before you deactivate Soapbox Blyg, then delete Soapbox Blyg.
  • soapbox-for-blygger/trunk/soapbox-for-blygger.php

    r3737840 r3754860  
    44 * Plugin URI:        https://github.com/cyberscribe/soapbox-blyg
    55 * Description:       Publishes your posts as a blyg: stable ids, versions, pins and a feed that other clients can subscribe to and quote. An independent implementation of the Blygger protocol; not affiliated with the Blygger project.
    6  * Version:           0.1.2
     6 * Version:           0.1.3
    77 * Requires at least: 6.6
    88 * Requires PHP:      7.4
    … …  
    5252const SOAPBOX_BLYG_TEXT_DOMAIN = 'soapbox-for-blygger';
    5353const SOAPBOX_BLYG_GENERATOR   = 'soapbox-for-blygger';
    54 const SOAPBOX_BLYG_VERSION     = '0.1.2';
     54const SOAPBOX_BLYG_VERSION     = '0.1.3';
    5555const SOAPBOX_BLYG_FILE        = __FILE__;
    5656
    5757/*
    58  * Where this client's source lives, for the manifest's generator_url (spec §6.1). Empty until
    59  * there is a public address to name; the key is then left out.
     58 * Where this client's source lives, for the manifest's generator_url (spec §3.2, §6.1). Empty
     59 * leaves the key out.
    6060 */
    61 const SOAPBOX_BLYG_GENERATOR_URL = '';
     61const SOAPBOX_BLYG_GENERATOR_URL = 'https://github.com/cyberscribe/soapbox-blyg';
    6262
    6363/*
  • soapbox-for-blygger/trunk/src/Admin/SettingsPage.php

    r3737840 r3754860  
    464464                                        'fixed'     => array(
    465465                                                __( 'Fixed paths', 'soapbox-for-blygger' ),
    466                                                 __( 'Blygger 0.3, understood by every reader. The protocol\'s own file names under the mount (feed.xml, items/index.json, items/{id}.json). Check Site Health afterwards: if your host caches them at the edge, exclude the mount path from that cache.', 'soapbox-for-blygger' ),
     466                                                __( 'The protocol\'s default paths under the mount (feed.xml, items/index.json, items/{id}.json), unchanged since 0.3: readers of any version find them. Check Site Health afterwards: if your host caches them at the edge, exclude the mount path from that cache.', 'soapbox-for-blygger' ),
    467467                                        ),
    468468                                        'templated' => array(
    469469                                                __( 'Templated', 'soapbox-for-blygger' ),
    470                                                 __( 'Blygger 0.4 draft (decision #51). Needs a reader that supports manifest-located surfaces; most don\'t yet. The manifest says where everything else is, and the documents are served through the REST API. Managed hosts often cache paths ending in .json and .xml for weeks, which would keep serving an item after you withdrew it; this layout avoids those paths.', 'soapbox-for-blygger' ),
     470                                                __( 'Blygger 0.4\'s manifest-located surface: the manifest says where everything else is, and the documents are served through the REST API. Readers built to 0.4 must follow it, and the reference client does; a reader built only to 0.3 finds the manifest and then gets 404s. Managed hosts often cache paths ending in .json and .xml for weeks, which would keep serving an item after you withdrew it; this layout avoids those paths.', 'soapbox-for-blygger' ),
    471471                                        ),
    472472                                );
    … …  
    554554                                                esc_html__( 'Delete all Soapbox for Blygger data when the plugin is deleted', 'soapbox-for-blygger' )
    555555                                        );
    556                                         $note( __( 'Kept off while Soapbox Blyg, this plugin\'s earlier name, is still installed: the two share one record of what has been published, and deleting Soapbox Blyg with this on would delete it. Delete Soapbox Blyg from the Plugins screen first.', 'soapbox-for-blygger' ) );
     556                                        $note( __( 'Switched off while Soapbox Blyg, this plugin\'s earlier name, is still installed: the two share one record of what has been published, and deleting Soapbox Blyg with this on would delete it. It does not come back on by itself: delete Soapbox Blyg from the Plugins screen, then tick this again if you want it.', 'soapbox-for-blygger' ) );
    557557                                        break;
    558558                                }
    … …  
    581581                                                sprintf(
    582582                                                        /* translators: 1: protocol version, 2: URL of the protocol specification */
    583                                                         __( 'This plugin publishes under the <a href="%2$s">Blygger protocol</a>, version %1$s, which is a draft: its wire format may change before 1.0, and this plugin will follow it. What does not change is the promise you make by publishing. Every address the blyg serves is meant to keep answering, and a pinned version is served forever. Soapbox for Blygger is an independent implementation and is not affiliated with the protocol\'s authors.', 'soapbox-for-blygger' ),
     583                                                        __( 'This plugin publishes under the <a href="%2$s">Blygger protocol</a>, version %1$s. Like every version before 1.0 it is a draft: its wire format may change, and this plugin will follow it. What does not change is the promise you make by publishing. Every address the blyg serves is meant to keep answering, and a pinned version is served forever. Soapbox for Blygger is an independent implementation and is not affiliated with the protocol\'s authors.', 'soapbox-for-blygger' ),
    584584                                                        esc_html( \SoapboxBlyg\Surface\Documents::PROTOCOL ),
    585                                                         esc_url( 'https://blygger.org/spec/0.3/' )
     585                                                        esc_url( 'https://blygger.org/spec/' . \SoapboxBlyg\Surface\Documents::PROTOCOL . '/' )
    586586                                                ),
    587587                                                array( 'a' => array( 'href' => true ) )
  • soapbox-for-blygger/trunk/src/Admin/ToolsPage.php

    r3737840 r3754860  
    2828
    2929        const PAGE = 'soapbox-blyg-tools';
     30
     31        /**
     32         * The user meta that records which schema problem a person has dismissed.
     33         */
     34        const DISMISSED_META = 'soapbox_blyg_dismissed_problem';
    3035
    3136        /**
    … …  
    110115                $stamp   = Schema::problem_key();
    111116                if ( '' !== $problem && isset( $_GET['soapbox_blyg_dismiss'] ) && check_admin_referer( 'soapbox_blyg_dismiss' ) ) {
    112                         update_user_meta( get_current_user_id(), 'soapbox_blyg_dismissed_problem', $stamp );
    113                 }
    114                 if ( '' !== $problem && ( $ours || get_user_meta( get_current_user_id(), 'soapbox_blyg_dismissed_problem', true ) !== $stamp ) ) {
     117                        update_user_meta( get_current_user_id(), self::DISMISSED_META, $stamp );
     118                }
     119                if ( '' !== $problem && ( $ours || get_user_meta( get_current_user_id(), self::DISMISSED_META, true ) !== $stamp ) ) {
    115120                        echo '<div class="notice notice-error"><p>';
    116121                        echo esc_html(
    … …  
    391396                                                /* translators: %s: the address of one of this site's items, as a link. Another publication's item is a fork of it. */
    392397                                                'fork'         => __( 'Forked from %s', 'soapbox-for-blygger' ),
     398                                                /* translators: %s: the address of one of this site's items, as a link. Another publication's item has machine-generated text that drew on it. */
     399                                                'source'       => __( 'Drew on %s', 'soapbox-for-blygger' ),
    393400                                        );
    394401                                        ?>
  • soapbox-for-blygger/trunk/src/Mentions/Verifier.php

    r3737840 r3754860  
    456456                $fetched_from = self::address( $document_url );
    457457                if ( $speaker . 'items/' . $id . '.json' !== $fetched_from ) {
    458                         // Not at the fixed address, so the origin may publish its items by template (§16.6e).
     458                        // Not at the fixed address, so the origin may publish its items by template (spec §6.1, §15.4).
    459459                        // Its manifest says where, and is the third and last document fetched: the page, the
    460460                        // item document, and this. It is asked for at the claimed origin and nowhere else, so
    … …  
    477477                        $template = isset( $manifest['body'] ) && self::is_plain( $manifest['url'] ) && self::address( $manifest['url'] ) === $speaker . 'blyg.json' ? json_decode( $manifest['body'], true ) : null;
    478478                        $template = is_array( $template ) && isset( $template['item'] ) && is_string( $template['item'] ) ? $template['item'] : '';
    479                         // {id} is the only expression a template may hold (URI Template level 1, spec §16.6e).
     479                        // {id} is the only expression a template may hold (URI Template level 1, spec §6.1).
    480480                        $expanded = str_replace( '{id}', rawurlencode( $id ), $template );
    481481                        if ( false === strpos( $template, '{id}' ) || false !== strpbrk( $expanded, '{}' ) || self::address( $expanded ) !== $fetched_from ) {
    … …  
    535535        /**
    536536         * How a document refers to one of our items, if it does (spec §15.4), in the protocol's order:
    537          * a stub of it, a transclusion of it, or a fork of a pinned version of it. Only the three
    538          * identity members of a reference are read; `cited` and `selector` are never consulted.
     537         * a stub of it, a transclusion of it, a fork of a pinned version of it, or a generation source
     538         * drawn from it. Only the identity members of a reference are read; `cited` and `selector`
     539         * are never consulted.
    539540         *
    540541         * @param array  $document  The source's item document.
    541542         * @param string $our_url   This origin's URL: its identity.
    542543         * @param string $target_id Our item's id.
    543          * @return string|null stub, transclusion, fork or null.
     544         * @return string|null stub, transclusion, fork, source or null.
    544545         */
    545546        private function relation( array $document, string $our_url, string $target_id ) {
    … …  
    570571                        if ( $row && (int) $row['pinned'] ) {
    571572                                return 'fork';
     573                        }
     574                }
     575                // A generation source (spec 0.4 §5.7): one of our items that machine-generated text in the
     576                // document drew on. Each span of `generated` lists its sources as references; one without
     577                // an origin is the sender's own item. Nothing is promised of the version, so no pin check.
     578                if ( isset( $document['generated'] ) && is_array( $document['generated'] ) ) {
     579                        foreach ( $document['generated'] as $span ) {
     580                                if ( ! is_array( $span ) || ! isset( $span['sources'] ) || ! is_array( $span['sources'] ) ) {
     581                                        continue;
     582                                }
     583                                foreach ( $span['sources'] as $entry ) {
     584                                        if ( $names( $entry ) ) {
     585                                                return 'source';
     586                                        }
     587                                }
    572588                        }
    573589                }
  • soapbox-for-blygger/trunk/src/Plugin.php

    r3737840 r3754860  
    3636
    3737        /**
     38         * The WP-CLI command: `wp blyg`.
     39         */
     40        const CLI_COMMAND = 'blyg';
     41
     42        /**
    3843         * The one instance.
    3944         *
    … …  
    203208
    204209                if ( defined( 'WP_CLI' ) && WP_CLI ) {
    205                         \WP_CLI::add_command( 'blyg', Cli\Command::class );
     210                        \WP_CLI::add_command( self::CLI_COMMAND, Cli\Command::class );
    206211                }
    207212        }
    … …  
    239244         * this plugin is serving from: every id and every address the site has published. Nothing in
    240245         * this plugin runs at that moment, so the setting is switched off beforehand, here, and the
    241          * settings screen will not switch it on again until the earlier plugin has gone.
     246         * settings screen will not switch it on again until the earlier plugin has gone. Nor does
     247         * anything else: the owner ticks it again.
    242248         *
    243249         * The file is looked for only when the setting is on, so an ordinary request reads nothing extra.
    … …  
    263269                        return;
    264270                }
    265                 echo '<div class="notice notice-info"><p>' . esc_html__( 'Soapbox Blyg, the earlier name of Soapbox for Blygger, is still installed. It can be deleted: this plugin has taken over the same blyg, at the same addresses. Until it has gone, "Delete all data when the plugin is deleted" is kept switched off, because deleting Soapbox Blyg with that setting on would delete the record of everything this site has published.', 'soapbox-for-blygger' ) . '</p></div>';
     271                echo '<div class="notice notice-info"><p>' . esc_html__( 'Soapbox Blyg, the earlier name of Soapbox for Blygger, is still installed. It can be deleted: this plugin has taken over the same blyg, at the same addresses. Until it has gone, "Delete all data when the plugin is deleted" stays switched off, because deleting Soapbox Blyg with that setting on would delete the record of everything this site has published. It does not come back on by itself: after deleting Soapbox Blyg, tick it again if you want it.', 'soapbox-for-blygger' ) . '</p></div>';
    266272        }
    267273
  • soapbox-for-blygger/trunk/src/Surface/Documents.php

    r3737840 r3754860  
    2828
    2929        /**
    30          * The protocol version these documents are written to (spec §3.1).
    31          */
    32         const PROTOCOL = '0.3';
     30         * The protocol version these documents are written to (spec §3.1). One value for every
     31         * document in both layouts, pinned versions included: a pin promises its content, which
     32         * content_hash covers, not its bytes (§8).
     33         */
     34        const PROTOCOL = '0.4';
    3335
    3436        /**
  • soapbox-for-blygger/trunk/src/Surface/Locations.php

    r3737840 r3754860  
    2020 * The manifest is always at {origin}blyg.json: that filename is what lets a reader handed a bare
    2121 * origin find the blyg, and it is what makes the origin, and so the blyg's identity, a stable
    22  * path of the site's own choosing (spec §12.2, §16.6e). Everything past the manifest is located
     22 * path of the site's own choosing (spec §4, §12.2). Everything past the manifest is located
    2323 * by the manifest:
    2424 *
    … …  
    177177         * The item a URL names, within one origin: by its page, in any spelling the item has ever had,
    178178         * or by its item document URL in the origin's current layout. This is the check a Webmention's
    179          * target has to pass (spec §15.3, §16.6e: receivers check targets against their own templates).
     179         * target has to pass (spec §15.3: receivers check targets against their own templates).
    180180         *
    181181         * @param Origin $origin The origin.
  • soapbox-for-blygger/trunk/src/Surface/RestDocuments.php

    r3737840 r3754860  
    1818/**
    1919 * Serves the feed, the archive index, item documents and pinned versions of a templated origin
    20  * from REST routes. The manifest names these URLs and readers follow it (spec §16.6e), so the
     20 * from REST routes. The manifest names these URLs and readers follow it (spec §6.1), so the
    2121 * documents can sit where WordPress routes natively, away from the .json and .xml paths that
    2222 * managed hosts treat as static files and cache at the edge.
  • soapbox-for-blygger/trunk/src/Surface/Router.php

    r3737840 r3754860  
    2525 *
    2626 * The REST API is not used for the manifest: the protocol fixes that one filename at the origin
    27  * (spec §4, §16.6e), and {origin}blyg.json is what makes the origin's identity a path the site
     27 * (spec §4, §6.1), and {origin}blyg.json is what makes the origin's identity a path the site
    2828 * owns. add_feed() is not used either: it serves /feed/{name}/, and the protocol's default feed
    2929 * path is {origin}feed.xml.
Note: See TracChangeset for help on using the changeset viewer.