Make WordPress Core

Changeset 53933


Ignore:
Timestamp:
08/23/2022 05:46:46 PM (2 years ago)
Author:
SergeyBiryukov
Message:

Themes: Add support for Update URI header.

This allows third-party themes to avoid accidentally being overwritten with an update of a theme of a similar name from the WordPress.org Theme Directory.

Additionally, introduce the update_themes_{$hostname} filter, which third-party themes can use to offer updates for a given hostname.

If set, the Update URI header field should be a URI and have a unique hostname.

Some examples include:

  • https://wordpress.org/themes/example-theme/
  • https://example.com/my-theme/
  • my-custom-theme-name

Update URI: false also works, and unless there is code handling the false hostname, the theme will never get an update notification.

If the header is present, the WordPress.org API will currently only return updates for the theme if it matches the following format:

  • https://wordpress.org/themes/{$slug}/
  • w.org/theme/{$slug}

If the header has any other value, the API will not return a result and will ignore the theme for update purposes.

Follow-up to [50921].

Props dd32, meloniq, costdev, audrasjb, DavidAnderson, markjaquith, DrewAPicture, mweichert, design_dolphin, filosofo, sean212, nhuja, JeroenReumkens, infolu, dingdang, joyously, earnjam, williampatton, grapplerulrich, markparnell, apedog, afragen, miqrogroove, rmccue, crazycoders, jdgrimes, damonganto, joostdevalk, jorbin, georgestephanis, khromov, GeekStreetWP, jb510, Rarst, juliobox, Ipstenu, mikejolley, Otto42, gMagicScott, TJNowell, GaryJ, knutsp, mordauk, nvartolomei, aspexi, chriscct7, benoitchantre, ryno267, lev0, gregorlove, dougwollison, leemon, SergeyBiryukov.
See #14179, #23318, #32101.

Location:
trunk
Files:
3 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-theme.php

    r53700 r53933  
    2424     * @since 3.4.0
    2525     * @since 5.4.0 Added `Requires at least` and `Requires PHP` headers.
     26     * @since 6.1.0 Added `Update URI` header.
    2627     * @var string[]
    2728     */
     
    4041        'RequiresWP'  => 'Requires at least',
    4142        'RequiresPHP' => 'Requires PHP',
     43        'UpdateURI'   => 'Update URI',
    4244    );
    4345
     
    845847     * @since 3.4.0
    846848     * @since 5.4.0 Added support for `Requires at least` and `Requires PHP` headers.
     849     * @since 6.1.0 Added support for `Update URI` header.
    847850     *
    848851     * @param string $header Theme header. Accepts 'Name', 'Description', 'Author', 'Version',
    849      *                       'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP'.
     852     *                       'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP',
     853     *                       'UpdateURI'.
    850854     * @param string $value  Value to sanitize.
    851855     * @return string|array An array for Tags header, string otherwise.
     
    897901            case 'RequiresWP':
    898902            case 'RequiresPHP':
     903            case 'UpdateURI':
    899904                $value = strip_tags( $value );
    900905                break;
  • trunk/src/wp-includes/update.php

    r53753 r53933  
    506506         * @param array       $plugin_data      Plugin headers.
    507507         * @param string      $plugin_file      Plugin filename.
    508          * @param array       $locales          Installed locales to look translations for.
     508         * @param array       $locales          Installed locales to look up translations for.
    509509         */
    510510        $update = apply_filters( "update_plugins_{$hostname}", false, $plugin_data, $plugin_file, $locales );
     
    614614            'Author'     => $theme->get( 'Author' ),
    615615            'Author URI' => $theme->get( 'AuthorURI' ),
     616            'UpdateURI'  => $theme->get( 'UpdateURI' ),
    616617            'Template'   => $theme->get_template(),
    617618            'Stylesheet' => $theme->get_stylesheet(),
     
    745746    }
    746747
     748    // Support updates for any themes using the `Update URI` header field.
     749    foreach ( $themes as $theme_stylesheet => $theme_data ) {
     750        if ( ! $theme_data['UpdateURI'] || isset( $new_update->response[ $theme_stylesheet ] ) ) {
     751            continue;
     752        }
     753
     754        $hostname = wp_parse_url( esc_url_raw( $theme_data['UpdateURI'] ), PHP_URL_HOST );
     755
     756        /**
     757         * Filters the update response for a given theme hostname.
     758         *
     759         * The dynamic portion of the hook name, `$hostname`, refers to the hostname
     760         * of the URI specified in the `Update URI` header field.
     761         *
     762         * @since 6.1.0
     763         *
     764         * @param array|false $update {
     765         *     The theme update data with the latest details. Default false.
     766         *
     767         *     @type string $id           Optional. ID of the theme for update purposes, should be a URI
     768         *                                specified in the `Update URI` header field.
     769         *     @type string $theme        Directory name of the theme.
     770         *     @type string $version      The version of the theme.
     771         *     @type string $url          The URL for details of the theme.
     772         *     @type string $package      Optional. The update ZIP for the theme.
     773         *     @type string $tested       Optional. The version of WordPress the theme is tested against.
     774         *     @type string $requires_php Optional. The version of PHP which the theme requires.
     775         *     @type bool   $autoupdate   Optional. Whether the theme should automatically update.
     776         *     @type array  $translations {
     777         *         Optional. List of translation updates for the theme.
     778         *
     779         *         @type string $language   The language the translation update is for.
     780         *         @type string $version    The version of the theme this translation is for.
     781         *                                  This is not the version of the language file.
     782         *         @type string $updated    The update timestamp of the translation file.
     783         *                                  Should be a date in the `YYYY-MM-DD HH:MM:SS` format.
     784         *         @type string $package    The ZIP location containing the translation update.
     785         *         @type string $autoupdate Whether the translation should be automatically installed.
     786         *     }
     787         * }
     788         * @param array       $theme_data       Theme headers.
     789         * @param string      $theme_stylesheet Theme stylesheet.
     790         * @param array       $locales          Installed locales to look up translations for.
     791         */
     792        $update = apply_filters( "update_themes_{$hostname}", false, $theme_data, $theme_stylesheet, $locales );
     793
     794        if ( ! $update ) {
     795            continue;
     796        }
     797
     798        $update = (object) $update;
     799
     800        // Is it valid? We require at least a version.
     801        if ( ! isset( $update->version ) ) {
     802            continue;
     803        }
     804
     805        // This should remain constant.
     806        $update->id = $theme_data['UpdateURI'];
     807
     808        // WordPress needs the version field specified as 'new_version'.
     809        if ( ! isset( $update->new_version ) ) {
     810            $update->new_version = $update->version;
     811        }
     812
     813        // Handle any translation updates.
     814        if ( ! empty( $update->translations ) ) {
     815            foreach ( $update->translations as $translation ) {
     816                if ( isset( $translation['language'], $translation['package'] ) ) {
     817                    $translation['type'] = 'theme';
     818                    $translation['slug'] = isset( $update->theme ) ? $update->theme : $update->id;
     819
     820                    $new_update->translations[] = $translation;
     821                }
     822            }
     823        }
     824
     825        unset( $new_update->no_update[ $theme_stylesheet ], $new_update->response[ $theme_stylesheet ] );
     826
     827        if ( version_compare( $update->new_version, $theme_data['Version'], '>' ) ) {
     828            $new_update->response[ $theme_stylesheet ] = (array) $update;
     829        } else {
     830            $new_update->no_update[ $theme_stylesheet ] = (array) $update;
     831        }
     832    }
     833
    747834    set_site_transient( 'update_themes', $new_update );
    748835}
  • trunk/tests/phpunit/tests/theme/themeDir.php

    r53916 r53933  
    183183            'Webfonts theme',
    184184            'Empty `fontFace` in theme.json - no webfonts defined',
     185            'A theme with the Update URI header',
    185186        );
    186187
  • trunk/tests/phpunit/tests/theme/wpTheme.php

    r53916 r53933  
    406406        );
    407407    }
     408
     409    /**
     410     * Tests that the UpdateURI header is retrieved.
     411     *
     412     * @ticket 14179
     413     *
     414     * @covers WP_Theme::get
     415     */
     416    public function test_theme_get_update_uri_header() {
     417        $theme = new WP_Theme( 'update-uri-theme', $this->theme_root );
     418
     419        $this->assertTrue(
     420            $theme->exists(),
     421            'The update-uri-theme does not exist.'
     422        );
     423
     424        $update_uri = $theme->get( 'UpdateURI' );
     425
     426        $this->assertIsString(
     427            $update_uri,
     428            'The UpdateURI header was not returned as a string.'
     429        );
     430
     431        $this->assertSame(
     432            'http://example.org/update-uri-theme/',
     433            $update_uri,
     434            'The UpdateURI header did not match the expected value.'
     435        );
     436    }
     437
     438    /**
     439     * Tests that WP_Theme::sanitize_header() strips tags from the UpdateURI header.
     440     *
     441     * @ticket 14179
     442     *
     443     * @covers WP_Theme::sanitize_header
     444     */
     445    public function test_should_strip_tags_from_update_uri_header() {
     446        $theme           = new WP_Theme( 'twentytwentytwo', $this->theme_root );
     447        $sanitize_header = new ReflectionMethod( $theme, 'sanitize_header' );
     448        $sanitize_header->setAccessible( true );
     449
     450        $actual = $sanitize_header->invoke( $theme, 'UpdateURI', '<?php?><a href="http://example.org">http://example.org</a>' );
     451
     452        $this->assertSame( 'http://example.org', $actual );
     453    }
    408454}
Note: See TracChangeset for help on using the changeset viewer.