Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 13
CRAP
n/a
0 / 0
wpcom_better_footer_links_buffer
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
wpcom_better_footer_links
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 1
56
better_wpcom_link_init
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
better_wpcom_link_workarounds_init
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
wpcom_better_footer_credit_regex_filter
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcom_footer_html_comment_flag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentyten_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentyeleven_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentytwelve_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentythirteen_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentyfourteen_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentyfifteen_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomthemes_twentysixteen_credits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php // phpcs:ignore Squiz.Commenting.FileComment.MissingPackageTag
2/**
3 * This file is taken from wp-content/blog-plugins/theme-optimizations.php on wpcom and is split into this and 'theme-optimizations.php'.
4 */
5
6/**
7 * Create an output buffer to capture the HTML content of the footer.php theme
8 * template. Used to change links, text, run a/b tests. Etc.
9 *
10 * @param string $page The HTML content from footer.php template file.
11 * @return string $page HTML content.
12 */
13function wpcom_better_footer_links_buffer( $page ) {
14    // Only add theme and colophon links for pub and premium themes, and VIP "partner" themes.
15    if ( ! apply_filters( 'wpcom_better_footer_credit_apply', true ) ) {
16        return $page;
17    }
18
19    // Would like to only see footer content before wp_footer output.
20    $output = explode( 'wpcom_wp_footer', $page, 2 );
21
22    // Run "better link" filters.
23    $footer = wpcom_better_footer_links( $output[0] );
24
25    $remaining_content = $output[1] ?? '';
26
27    // Piece back together again.
28    $page = $footer . 'wpcom_wp_footer' . $remaining_content;
29
30    // If nothing to join, return empty string.
31    if ( 'wpcom_wp_footer' === $page ) {
32        return '';
33    }
34
35    // Replace any dangling references of glue code.
36    $page = str_replace( 'wpcom_wp_footer', '', $page );
37
38    return $page;
39}
40
41/**
42 * Better WP.com footer links.
43 *
44 * 1. Replace default "Powered by WordPress" text and link with
45 * a link to WordPress.com and custom call-out text.
46 *
47 * 2. Replace theme name in footer with a link to relevant theme page on the Theme Showcase.
48 * URL structure: http://theme.wordpress.com/themes/{theme-slug}/
49 *
50 * 3. Link to the Vertical landing page for sites that are stickered with a vertical.
51 *
52 * @param string $footer Footer HTML content to filter.
53 * @return string $footer Filtered HTML content.
54 */
55function wpcom_better_footer_links( $footer ) {
56    // Only add theme and colophon links for pub and premium themes, and VIP "partner" themes.
57    if ( ! apply_filters( 'wpcom_better_footer_credit_apply', true ) ) {
58        return $footer;
59    }
60
61    // Get current theme data.
62    $theme = wp_get_theme();
63
64    // Replace separator from content, since we are replacing theme and designer credits.
65    // Any span separator with a .sep class will be matched and replaced by the regular expression.
66    $footer = preg_replace( '/\s\|\s(?=\<a)|\<span class="([^"]+\s)?sep(\s[^"]+)?">.*<\/span>/i', '', $footer );
67
68    // Handle WP.com footer text.
69    $lang = get_bloginfo( 'language' );
70
71    // Replace credit link in footer, and make sure it is replaced only once, to avoid duplicates.
72    $credit_link = apply_filters( 'wpcom_better_footer_credit_link', '', $lang );
73
74    // The regular expression to match the credit link replacement.
75    $credit_regex = implode(
76        '',
77        array(
78            '#',                // Open delimiter
79            '<a[^>]*href="http(s?)://(([a-z]{2}(-[A-Z]{2})?|www)\.)?(wordpress|wordpress-fr|wpfr)\.(com|org|net)/?"([^>]+)?>', // Opening link tag
80            '\s*',              // Optional whitespace
81            '(.+?)',            // Any word or sentence
82            '\s*',              // Optional whitespace
83            '</a>',             // Closing link tag
84            '\.?',              // Optional period
85            '(\s*&[^;]+;\s*)?', // Optional HTML Entity
86            '#i',               // Ending delimiter & modifier
87        )
88    );
89
90    // Add filter for specific themes that may need to tweak the regex a bit.
91    $credit_regex = apply_filters( 'wpcom_better_footer_credit_regex', $credit_regex, $theme );
92
93    // Get the full matches of the credit regular expression, proceed if match.
94    if ( preg_match_all( $credit_regex, $footer, $matches, PREG_OFFSET_CAPTURE ) ) {
95
96        // Get last match and offset.
97        $match  = array_pop( $matches[0] );
98        $offset = $match[1];
99
100        // Split the content into two parts, which we will join later on.
101        $before = substr( $footer, 0, $offset );
102        $after  = substr( $footer, $offset );
103
104        // Replace on the last part. Ensure we only do one replacement to avoid duplicates.
105        $after = preg_replace( $credit_regex, $credit_link, $after, 1 );
106
107        // Join the two parts.
108        $footer = $before . $after;
109
110    }
111
112    // Themes that have duplicate footer credit links (e.g. "Powered by WordPress.com" + another credit link).
113    $powered_by_themes = array(
114        'pub/toujours',
115    );
116
117    // Remove "Proudly powered by WordPress" on selected themes.
118    if ( in_array( $theme->stylesheet, $powered_by_themes, true ) ) {
119        $powered_string = preg_quote( __( 'Proudly powered by WordPress', 'wpcomsh' ), '#' );
120        // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
121        $powered_regex = sprintf( '#<a[^>]*href="http(s?)://(([a-z]{2}|www)\.)?wordpress\.(com|org)/?"([^>]+)?>%s</a>\.?#i', $powered_string );
122        $footer        = preg_replace( $powered_regex, '', $footer );
123    }
124
125    // Handle adding Theme Name and colophon link to footer text.
126    $theme_regex = array(
127        '(?:\s*\|\s*)?',       // Optional pipe with spaces (non-capturing)
128        '(?:<span\s[^>]+>)?',  // Optional opening span tag (non-capturing)
129        '(Theme|%s)',          // $1: "Theme" or the localized equivalent
130        '\s*(?:&nbsp;)?:?\s*', // Zero or more whitespace characters, an optional colon, zero or more whitespace characters
131        '(%s|<a[^>]+>%s</a>)', // $2: The theme name, or link
132        '\.?',                 // Optional period
133        '(?:</span>)?',        // Optional closing span tag (non-capturing)
134        '\.?',                 // Optional period
135    );
136    $theme_match = sprintf(
137        implode( $theme_regex ),
138        preg_quote( __( 'Theme', 'wpcomsh' ), '#' ),
139        preg_quote( $theme->name, '#' ),
140        preg_quote( $theme->name, '#' )
141    );
142
143    // Theme designer match.
144    $designer_regex = array(
145        '(',                  // Start $3
146        '\s*',                // Zero or more whitespace characters
147        '(?:<span\s[^>]+>)?', // Optional opening span tag (non-capturing)
148        '(?:by|%s)',          // "by" or the localized equivalent (non-capturing)
149        '(?:</span>)?',       // Optional closing span tag (non-capturing)
150        '\s*',                // Zero or more whitespace characters
151        '(<a[^>]+>.+?</a>)?', // $4: Maybe a full <a> element
152        ')',                  // End $3
153        '\.?',                // Optional period
154    );
155    $designer_match = $theme_match . sprintf(
156        implode( $designer_regex ),
157        preg_quote( __( 'by', 'wpcomsh' ), '#' ) // localized "by" preposition
158    );
159
160    // Match "Design by <shop>".
161    $design_by_regex = array(
162        '\.?',               // Optional period
163        '\s*',               // Optional whitespace
164        '(Design by|%s)',    // "Design by" or localized equivalent
165        '\s*',               // Optional whitespace
166        '(<a[^>]+>.+?</a>)', // Full link element
167        '\.?',               // Optional period
168    );
169    $design_by       = preg_quote( $credit_link, '#' ) . sprintf(
170        implode( $design_by_regex ),
171        preg_quote( __( 'Design by', 'wpcomsh' ), '#' )
172    );
173
174    if ( preg_match( "#$designer_match#i", $footer ) ) {
175        $footer = preg_replace( "#$designer_match#i", '', $footer, 1 );
176    }
177
178    if ( preg_match( "#$theme_match#i", $footer ) ) {
179        $footer = preg_replace( "#$theme_match#i", '', $footer, 1 );
180    }
181
182    if ( preg_match( "#$design_by#i", $footer ) ) {
183        $footer = preg_replace( "#$design_by#i", $credit_link, $footer, 1 );
184    }
185
186    return $footer;
187}
188
189/**
190 * Enable filters for footer content for all themes, except VIP sites.
191 */
192function better_wpcom_link_init() {
193    if ( apply_filters( 'wpcom_better_footer_credit_apply', true ) && ! wp_is_json_request() ) {
194        ob_start( 'wpcom_better_footer_links_buffer' );
195    }
196}
197add_action( 'get_footer', 'better_wpcom_link_init' );
198
199/**
200 * Enable filters on those themes that need special treatment.
201 */
202function better_wpcom_link_workarounds_init() {
203    if ( function_exists( 'blogly_widgets_init' ) && 'premium/blogly' === wp_get_theme()->stylesheet ) {
204        add_action( 'get_sidebar', 'better_wpcom_link_init' );
205    }
206    if ( function_exists( 'designer_widgets_init' ) && 'premium/designer' === wp_get_theme()->stylesheet ) {
207        add_action( 'get_header', 'better_wpcom_link_init' );
208    }
209}
210add_action( 'init', 'better_wpcom_link_workarounds_init' );
211
212// Enable filters Infinite Scroll footer conntent, except VIP sites.
213if ( ! apply_filters( 'wpcom_better_footer_credit_apply', true ) ) {
214    add_filter( 'infinite_scroll_credit', 'wpcom_better_footer_links' );
215}
216
217/**
218 * Filters the default footer credits regex.
219 *
220 * @param string $credit_regex The regular expression for the footer credit.
221 * @param object $theme The object returned by `wp_get_theme()`.
222 * @return string
223 */
224function wpcom_better_footer_credit_regex_filter( $credit_regex, $theme ) {
225    // Twotone renders the social menu after the credit links. If there is a WordPress.com link in the menu,
226    // it will break the footer credits. Adding a space before the actual link fixes this.
227    if ( 'premium/twotone' === $theme->stylesheet ) {
228        $credit_regex = str_replace( '#<a', '#\s<a', $credit_regex );
229    }
230    return $credit_regex;
231}
232add_filter( 'wpcom_better_footer_credit_regex', 'wpcom_better_footer_credit_regex_filter', 10, 2 );
233
234/**
235 * Add an HTML comment flag for wp_footer output so that our footer replacement
236 * script knows when to stop looking for more footer content.
237 */
238function wpcom_footer_html_comment_flag() {
239    echo "<!-- wpcom_wp_footer -->\n";
240}
241add_action( 'wp_footer', 'wpcom_footer_html_comment_flag', 9 );
242
243/**
244 * Add theme name to Twenty Ten footer
245 */
246function wpcomthemes_twentyten_credits() {
247    echo 'Theme: Twenty Ten'; // leave untranslated for regex match, will be translated in final output
248}
249add_action( 'twentyten_credits', 'wpcomthemes_twentyten_credits' );
250
251/**
252 * Add theme name to Twenty Eleven footer
253 */
254function wpcomthemes_twentyeleven_credits() {
255    echo 'Theme: Twenty Eleven <span class="sep"> | </span>'; // leave untranslated for regex match, will be translated in final output
256}
257add_action( 'twentyeleven_credits', 'wpcomthemes_twentyeleven_credits' );
258
259/**
260 * Add theme name to Twenty Twelve footer
261 */
262function wpcomthemes_twentytwelve_credits() {
263    echo 'Theme: Twenty Twelve.'; // leave untranslated for regex match, will be translated in final output
264}
265add_action( 'twentytwelve_credits', 'wpcomthemes_twentytwelve_credits' );
266
267/**
268 * Add theme name to Twenty Thirteen footer
269 */
270function wpcomthemes_twentythirteen_credits() {
271    echo 'Theme: Twenty Thirteen.'; // leave untranslated for regex match, will be translated in final output
272}
273add_action( 'twentythirteen_credits', 'wpcomthemes_twentythirteen_credits' );
274
275/**
276 * Add theme name to Twenty Fourteen footer
277 */
278function wpcomthemes_twentyfourteen_credits() {
279    echo 'Theme: Twenty Fourteen.'; // leave untranslated for regex match, will be translated in final output
280}
281add_action( 'twentyfourteen_credits', 'wpcomthemes_twentyfourteen_credits' );
282
283/**
284 * Add theme name to Twenty Fifteen footer
285 */
286function wpcomthemes_twentyfifteen_credits() {
287    echo 'Theme: Twenty Fifteen.'; // leave untranslated for regex match, will be translated in final output
288}
289add_action( 'twentyfifteen_credits', 'wpcomthemes_twentyfifteen_credits' );
290
291/**
292 * Add theme name to Twenty Sixteen footer
293 */
294function wpcomthemes_twentysixteen_credits() {
295    echo 'Theme: Twenty Sixteen.'; // leave untranslated for regex match, will be translated in final output
296}
297add_action( 'twentysixteen_credits', 'wpcomthemes_twentysixteen_credits' );