Skip to content

Commit fe84056

Browse files
authored
[codex] Fix Push MD toolkit loading and clone blockers (#49)
## Summary Fixes the Push MD activation and clone blockers found while testing on WordPress.com staging. - Makes PHP Toolkit standalone function files safe to load when another plugin has already loaded the same toolkit functions. - Updates Push MD's toolkit bootstrap paths to avoid Composer autoload-file redeclaration crashes. - Returns Git protocol error responses for `/info/refs` failures instead of opaque REST 500s. - Exports WordPress posts/pages with empty slugs using stable ID-based fallback paths, e.g. `post/post-4937.md`, to avoid `post/.md` collisions. - Keeps those fallback paths stable while content remains slugless, without writing fallback names back into WordPress slugs; stale fallback pushes are rejected once WordPress assigns a real slug. - Makes the Push MD bootstrap compatibility tests avoid Windows `cmd.exe` quoting failures. ## Root Cause WordPress.com SH (`wpcomsh`) already bundles `wp-php-toolkit/filesystem`, so Push MD's bundled toolkit could redeclare namespaced helper functions during activation. After activation was fixed, authenticated clone exposed two runtime issues: `/info/refs` errors were surfaced as REST 500s, and draft posts with empty slugs collided on the same export path. The follow-up fallback-path fix avoids treating generated filenames such as `post/post-4937.md` as desired WordPress slugs. If WordPress later assigns a canonical slug, Push MD now asks the user to pull the slug-based path before editing rather than silently moving content back to the generated fallback path. The Windows CI failure came from passing inline PHP snippets through `php -r` inside a shell command. The tests now write a temporary PHP script and invoke PHP with shell bypassing enabled on Windows. ## Validation - `vendor/bin/phpunit -c phpunit.xml` - `vendor/bin/phpunit -c phpunit.xml components/Filesystem/Tests/FunctionsTest.php` - `vendor/bin/phpunit -c phpunit.xml --filter PMD_Bootstrap_Compatibility_Test plugins/push-md/Tests/` - `vendor/bin/phpunit -c phpunit.xml plugins/push-md/Tests/` - `vendor/bin/phpunit -c phpunit.xml components/Filesystem/Tests/ components/Git/Tests/ components/Zip/Tests/ components/DataLiberation/Tests/ components/Encoding/Tests/` - `composer test` - `php -l plugins/push-md/class-pmd-plugin.php && php -l plugins/push-md/Tests/ExportPathTest.php` - `php -l plugins/push-md/Tests/BootstrapCompatibilityTest.php && php -l components/Filesystem/Tests/FunctionsTest.php` - `vendor/bin/phpcs -d memory_limit=1G plugins/push-md/class-pmd-plugin.php plugins/push-md/Tests/ExportPathTest.php` - `vendor/bin/phpcs -d memory_limit=1G plugins/push-md/Tests/BootstrapCompatibilityTest.php components/Filesystem/Tests/FunctionsTest.php` - `git diff --check` - rebuilt `dist/plugins/push-md.zip` and `/Users/artpi/Desktop/push-md.zip` - installed/tested on staging and confirmed `git clone` succeeds into `~/Desktop/site-title` Note: repo-wide `composer lint` still exits on pre-existing warning-level findings outside this change; scoped PHPCS for touched files passes.
1 parent b8b0072 commit fe84056

16 files changed

Lines changed: 1280 additions & 799 deletions

components/DataLiberation/URL/functions.php

Lines changed: 90 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -36,33 +36,35 @@
3636
* then it would be nice to re-encode that block markup also without the space character. This is similar
3737
* to how the tag processor avoids changing parts of the tag it doesn't need to change.
3838
*/
39-
function wp_rewrite_urls( $options ) {
40-
if ( empty( $options['base_url'] ) ) {
41-
// Use first from-url as base_url if not specified.
42-
$from_urls = array_keys( $options['url-mapping'] );
43-
$options['base_url'] = $from_urls[0];
44-
}
39+
if ( ! function_exists( __NAMESPACE__ . '\\wp_rewrite_urls' ) ) {
40+
function wp_rewrite_urls( $options ) {
41+
if ( empty( $options['base_url'] ) ) {
42+
// Use first from-url as base_url if not specified.
43+
$from_urls = array_keys( $options['url-mapping'] );
44+
$options['base_url'] = $from_urls[0];
45+
}
4546

46-
$url_mapping = array();
47-
foreach ( $options['url-mapping'] as $from_url_string => $to_url_string ) {
48-
$url_mapping[] = array(
49-
'from_url' => WPURL::parse( $from_url_string ),
50-
'to_url' => WPURL::parse( $to_url_string ),
51-
);
52-
}
47+
$url_mapping = array();
48+
foreach ( $options['url-mapping'] as $from_url_string => $to_url_string ) {
49+
$url_mapping[] = array(
50+
'from_url' => WPURL::parse( $from_url_string ),
51+
'to_url' => WPURL::parse( $to_url_string ),
52+
);
53+
}
5354

54-
$p = new BlockMarkupUrlProcessor( $options['block_markup'], $options['base_url'] );
55-
while ( $p->next_url() ) {
56-
$parsed_url = $p->get_parsed_url();
57-
foreach ( $url_mapping as $mapping ) {
58-
if ( is_child_url_of( $parsed_url, $mapping['from_url'] ) ) {
59-
$p->replace_base_url( $mapping['to_url'] );
60-
break;
55+
$p = new BlockMarkupUrlProcessor( $options['block_markup'], $options['base_url'] );
56+
while ( $p->next_url() ) {
57+
$parsed_url = $p->get_parsed_url();
58+
foreach ( $url_mapping as $mapping ) {
59+
if ( is_child_url_of( $parsed_url, $mapping['from_url'] ) ) {
60+
$p->replace_base_url( $mapping['to_url'] );
61+
break;
62+
}
6163
}
6264
}
63-
}
6465

65-
return $p->get_updated_html();
66+
return $p->get_updated_html();
67+
}
6668
}
6769

6870
/**
@@ -73,32 +75,34 @@ function wp_rewrite_urls( $options ) {
7375
*
7476
* @return bool Whether the URL matches the current site URL.
7577
*/
76-
function is_child_url_of( $child, $parent_url ) {
77-
$parent_url = is_string( $parent_url ) ? WPURL::parse( $parent_url ) : $parent_url;
78-
$child = is_string( $child ) ? WPURL::parse( $child ) : $child;
79-
$child_pathname_no_trailing_slash = rtrim( urldecode( $child->pathname ), '/' );
80-
81-
if ( false === $child || false === $parent_url ) {
82-
return false;
83-
}
78+
if ( ! function_exists( __NAMESPACE__ . '\\is_child_url_of' ) ) {
79+
function is_child_url_of( $child, $parent_url ) {
80+
$parent_url = is_string( $parent_url ) ? WPURL::parse( $parent_url ) : $parent_url;
81+
$child = is_string( $child ) ? WPURL::parse( $child ) : $child;
82+
$child_pathname_no_trailing_slash = rtrim( urldecode( $child->pathname ), '/' );
83+
84+
if ( false === $child || false === $parent_url ) {
85+
return false;
86+
}
8487

85-
if ( $parent_url->hostname !== $child->hostname ) {
86-
return false;
87-
}
88+
if ( $parent_url->hostname !== $child->hostname ) {
89+
return false;
90+
}
8891

89-
if ( $parent_url->protocol !== $child->protocol ) {
90-
return false;
91-
}
92+
if ( $parent_url->protocol !== $child->protocol ) {
93+
return false;
94+
}
9295

93-
$parent_pathname = urldecode( $parent_url->pathname );
96+
$parent_pathname = urldecode( $parent_url->pathname );
9497

95-
return (
96-
// Direct match.
97-
$parent_pathname === $child_pathname_no_trailing_slash ||
98-
$parent_pathname === $child_pathname_no_trailing_slash . '/' ||
99-
// Path prefix.
100-
0 === strncmp( $child_pathname_no_trailing_slash . '/', $parent_pathname, strlen( $parent_pathname ) )
101-
);
98+
return (
99+
// Direct match.
100+
$parent_pathname === $child_pathname_no_trailing_slash ||
101+
$parent_pathname === $child_pathname_no_trailing_slash . '/' ||
102+
// Path prefix.
103+
0 === strncmp( $child_pathname_no_trailing_slash . '/', $parent_pathname, strlen( $parent_pathname ) )
104+
);
105+
}
102106
}
103107

104108
/**
@@ -112,53 +116,55 @@ function is_child_url_of( $child, $parent_url ) {
112116
*
113117
* @return string The decoded string.
114118
*/
115-
function urldecode_n( $input, $decode_n ) {
116-
// Fast paths: nothing to do.
117-
if ( $decode_n <= 0 || false === strpos( $input, '%' ) ) {
119+
if ( ! function_exists( __NAMESPACE__ . '\\urldecode_n' ) ) {
120+
function urldecode_n( $input, $decode_n ) {
121+
// Fast paths: nothing to do.
122+
if ( $decode_n <= 0 || false === strpos( $input, '%' ) ) {
118123
return $input;
119-
}
120-
121-
$result = '';
122-
$at = 0;
123-
while ( true ) {
124-
if ( $at + 3 > strlen( $input ) ) {
125-
break;
126124
}
127125

128-
$last_at = $at;
129-
$at += strcspn( $input, '%', $at );
130-
// Consume bytes except for the percent sign.
131-
$result .= substr( $input, $last_at, $at - $last_at );
126+
$result = '';
127+
$at = 0;
128+
while ( true ) {
129+
if ( $at + 3 > strlen( $input ) ) {
130+
break;
131+
}
132132

133-
// If we've already decoded the requested number of bytes, stop.
134-
if ( strlen( $result ) >= $decode_n ) {
135-
break;
136-
}
133+
$last_at = $at;
134+
$at += strcspn( $input, '%', $at );
135+
// Consume bytes except for the percent sign.
136+
$result .= substr( $input, $last_at, $at - $last_at );
137137

138-
++$at;
139-
if ( $at > strlen( $input ) ) {
140-
break;
141-
}
138+
// If we've already decoded the requested number of bytes, stop.
139+
if ( strlen( $result ) >= $decode_n ) {
140+
break;
141+
}
142142

143-
$decodable_length = strspn(
144-
$input,
145-
'0123456789ABCDEFabcdef',
146-
$at,
147-
2
148-
);
143+
++$at;
144+
if ( $at > strlen( $input ) ) {
145+
break;
146+
}
149147

150-
if ( 2 === $decodable_length ) {
151-
// Decodes the urlencoded hex sequence from URL.
152-
// Note: This decodes bytes, not characters. It will recover the original byte sequence,
153-
// not necessarily any valid UTF-8 characters.
154-
$result .= chr( hexdec( $input[ $at ] . $input[ $at + 1 ] ) );
155-
$at += 2;
156-
} else {
157-
// Consume the next byte and move on.
158-
$result .= '%';
148+
$decodable_length = strspn(
149+
$input,
150+
'0123456789ABCDEFabcdef',
151+
$at,
152+
2
153+
);
154+
155+
if ( 2 === $decodable_length ) {
156+
// Decodes the urlencoded hex sequence from URL.
157+
// Note: This decodes bytes, not characters. It will recover the original byte sequence,
158+
// not necessarily any valid UTF-8 characters.
159+
$result .= chr( hexdec( $input[ $at ] . $input[ $at + 1 ] ) );
160+
$at += 2;
161+
} else {
162+
// Consume the next byte and move on.
163+
$result .= '%';
164+
}
159165
}
160-
}
161-
$result .= substr( $input, $at );
166+
$result .= substr( $input, $at );
162167

163-
return $result;
168+
return $result;
169+
}
164170
}

0 commit comments

Comments
 (0)