Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions assets/css/admin-pull-table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,74 @@
list-style: disc;
padding-left: 20px;
}

.searchable-select {
position: relative;
display: inline-block;
width: 300px;
vertical-align: top;
}

.searchable-select__input-container {
display: flex;
align-items: center;
box-shadow: 0 0 0 transparent;
border-radius: 4px;
border: 1px solid #8c8f94;
background-color: #fff;
color: #2c3338;
cursor: text;
overflow: hidden;
}

.searchable-select__input {
flex-grow: 1;
border: none;
padding: 8px;
outline: none;
border: none !important;
width: 100%;

&:focus {
outline: none;
border: none !important;
box-shadow: none !important;
}
}

.searchable-select__icon {
padding: 8px;
background-color: #f0f0f0;
cursor: pointer;
}

.searchable-select__input-container:focus-within {
border-color: #007bff;
}

.searchable-select__dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
border: 1px solid #ccc;
border-top: none;
max-height: 200px;
overflow-y: auto;
background-color: white;
display: none;
font-size: small;
}

.searchable-select__item {
padding: 8px;
cursor: pointer;
}

.searchable-select__item:hover {
background-color: #f0f0f0;
}

.searchable-select__item.selected {
background-color: #e0e0e0;
}
130 changes: 116 additions & 14 deletions assets/js/admin-pull.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { __ } from '@wordpress/i18n';

const { document } = window;

const chooseConnection = document.getElementById( 'pull_connections' );
const chooseConnection = document.getElementsByClassName(
'searchable-select__input'
)[ 0 ];
const choosePostType = document.getElementById( 'pull_post_type' );
const choosePostTypeBtn = document.getElementById( 'pull_post_type_submit' );
const searchField = document.getElementById( 'post-search-input' );
Expand All @@ -15,15 +17,6 @@ const form = document.getElementById( 'posts-filter' );
const asDraftCheckboxes = document.querySelectorAll( '[name=dt_as_draft]' );
const pullLinks = document.querySelectorAll( '.distributor_page_pull .pull a' );

jQuery( chooseConnection ).on( 'change', ( event ) => {
document.location =
event.currentTarget.options[
event.currentTarget.selectedIndex
].getAttribute( 'data-pull-url' );

document.body.className += ' ' + 'dt-loading';
} );

if ( chooseConnection && choosePostType && form ) {
if ( choosePostTypeBtn ) {
jQuery( choosePostTypeBtn ).on( 'click', ( event ) => {
Expand Down Expand Up @@ -84,10 +77,7 @@ if ( chooseConnection && choosePostType && form ) {
const getURL = () => {
const postType =
choosePostType.options[ choosePostType.selectedIndex ].value;
const baseURL =
chooseConnection.options[ chooseConnection.selectedIndex ].getAttribute(
'data-pull-url'
);
const baseURL = chooseConnection.getAttribute( 'data-pull-url' );
let status = 'new';

if ( -1 < ` ${ form.className } `.indexOf( ' status-skipped ' ) ) {
Expand All @@ -98,3 +88,115 @@ const getURL = () => {

return `${ baseURL }&pull_post_type=${ postType }&status=${ status }`;
};

document.addEventListener( 'DOMContentLoaded', async function () {
const container = document.querySelector( '.searchable-select' );
const inputContainer = container.querySelector(
'.searchable-select__input-container'
);
const input = container.querySelector( '.searchable-select__input' );
const icon = container.querySelector(
'.searchable-select__input-container > .dashicons-arrow-down'
);
const dropdown = container.querySelector( '.searchable-select__dropdown' );

const itemss = await fetch( '/wp-admin/admin-ajax.php', {
Comment thread
jeffpaul marked this conversation as resolved.
Outdated
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'action=dt_load_connections_pull',
} )
.then( ( response ) => response.json() )
.then( ( data ) => {
return data.data;
} );

function htmlDecode( inputText ) {
const doc = new DOMParser().parseFromString( inputText, 'text/html' ); // eslint-disable-line no-undef
return doc.documentElement.textContent;
}

function setInputDefault() {
const params = new URL( document.location.toString() ).searchParams;
const connection_id = params.get( 'connection_id' );

if ( connection_id ) {
const connection = itemss.find(
( item ) => item.id === connection_id
);

if ( connection ) {
input.value = connection.name;
input.setAttribute(
'data-pull-url',
htmlDecode( connection.pull_url )
);
}
}
}

setInputDefault();

function createDropdownItems( items ) {
dropdown.innerHTML = '';
items.forEach( ( item ) => {
const div = document.createElement( 'div' );
div.classList.add( 'searchable-select__item' );
div.textContent = item.name;
div.setAttribute( 'data-url', item.url );

div.addEventListener( 'click', () => {
input.value = item.name;
input.setAttribute(
'data-pull-url',
htmlDecode( item.pull_url )
// item.pull_url
);
document.location = getURL();

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML

[DOM text](1) is reinterpreted as HTML without escaping meta-characters. [DOM text](2) is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI about 1 year ago

To fix the problem, we need to ensure that any data extracted from the DOM and used in constructing URLs or other HTML content is properly sanitized and escaped. This can be achieved by using a library like DOMPurify to sanitize the input before using it.

  1. Import the DOMPurify library.
  2. Use DOMPurify.sanitize to sanitize the values extracted from the DOM before using them in the getURL function and other places where they are used.
Suggested changeset 2
assets/js/admin-pull.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/assets/js/admin-pull.js b/assets/js/admin-pull.js
--- a/assets/js/admin-pull.js
+++ b/assets/js/admin-pull.js
@@ -3,2 +3,3 @@
 import jQuery from 'jquery';
+import DOMPurify from 'dompurify';
 import { addQueryArgs } from '@wordpress/url';
@@ -78,4 +79,4 @@
 	const postType =
-		choosePostType.options[ choosePostType.selectedIndex ].value;
-	const baseURL = chooseConnection.getAttribute( 'data-pull-url' );
+		DOMPurify.sanitize(choosePostType.options[ choosePostType.selectedIndex ].value);
+	const baseURL = DOMPurify.sanitize(chooseConnection.getAttribute( 'data-pull-url' ));
 	let status = 'new';
@@ -132,3 +133,3 @@
 					'data-pull-url',
-					htmlDecode( connection.pull_url )
+					DOMPurify.sanitize(htmlDecode( connection.pull_url ))
 				);
@@ -156,3 +157,3 @@
 					'data-pull-url',
-					htmlDecode( item.pull_url )
+					DOMPurify.sanitize(htmlDecode( item.pull_url ))
 				);
EOF
@@ -3,2 +3,3 @@
import jQuery from 'jquery';
import DOMPurify from 'dompurify';
import { addQueryArgs } from '@wordpress/url';
@@ -78,4 +79,4 @@
const postType =
choosePostType.options[ choosePostType.selectedIndex ].value;
const baseURL = chooseConnection.getAttribute( 'data-pull-url' );
DOMPurify.sanitize(choosePostType.options[ choosePostType.selectedIndex ].value);
const baseURL = DOMPurify.sanitize(chooseConnection.getAttribute( 'data-pull-url' ));
let status = 'new';
@@ -132,3 +133,3 @@
'data-pull-url',
htmlDecode( connection.pull_url )
DOMPurify.sanitize(htmlDecode( connection.pull_url ))
);
@@ -156,3 +157,3 @@
'data-pull-url',
htmlDecode( item.pull_url )
DOMPurify.sanitize(htmlDecode( item.pull_url ))
);
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -24,3 +24,4 @@
 	"dependencies": {
-		"mustache": "^4.2.0"
+		"mustache": "^4.2.0",
+		"dompurify": "^3.2.4"
 	},
EOF
@@ -24,3 +24,4 @@
"dependencies": {
"mustache": "^4.2.0"
"mustache": "^4.2.0",
"dompurify": "^3.2.4"
},
This fix introduces these dependencies
Package Version Security advisories
dompurify (npm) 3.2.4 None
Copilot is powered by AI and may make mistakes. Always verify output.

hideDropdown();
} );
dropdown.appendChild( div );
} );
}

function showDropdown() {
createDropdownItems( itemss );
dropdown.style.display = 'block';
}

function hideDropdown() {
dropdown.style.display = 'none';
}

function filterItems( searchTerm ) {
const filteredItems = itemss.filter( ( item ) =>
item.toLowerCase().includes( searchTerm.toLowerCase() )
Comment thread
jeffpaul marked this conversation as resolved.
Outdated
);
createDropdownItems( filteredItems );
}

inputContainer.addEventListener( 'click', function () {
input.focus();
showDropdown();
} );

icon.addEventListener( 'click', function ( event ) {
event.stopPropagation();
input.focus();
showDropdown();
} );

input.addEventListener( 'input', function () {
filterItems( this.value );
} );

input.addEventListener( 'focus', showDropdown );

document.addEventListener( 'click', function ( event ) {
if ( ! container.contains( event.target ) ) {
hideDropdown();
}
} );
} );
2 changes: 1 addition & 1 deletion assets/js/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ jQuery( window ).on( 'load', () => {
distributorPushWrapper.classList.add( 'loaded' );

const data = {
action: 'dt_load_connections',
action: 'dt_load_connections_push',
loadConnectionsNonce: dt.loadConnectionsNonce,
postId: dt.postId,
};
Expand Down
114 changes: 69 additions & 45 deletions includes/pull-ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function setup() {
function() {
add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\admin_enqueue_scripts' );
add_action( 'wp_ajax_dt_load_connections_pull', __NAMESPACE__ . '\get_connections' );
add_action( 'load-distributor_page_pull', __NAMESPACE__ . '\setup_list_table' );
add_filter( 'set-screen-option', __NAMESPACE__ . '\set_screen_option', 10, 3 );
}
Expand Down Expand Up @@ -425,51 +426,14 @@ function dashboard() {
?>
<?php else : ?>
<?php esc_html_e( 'Pull Content from', 'distributor' ); ?>
<select id="pull_connections" name="connection" method="get">
<?php if ( ! empty( $internal_connection_group ) ) : ?>
<?php if ( ! empty( $external_connection_group ) ) : ?>
<optgroup label="<?php esc_attr_e( 'Network Connections', 'distributor' ); ?>">
<?php endif; ?>
<?php
foreach ( $internal_connection_group as $connection ) :
$selected = false;
$type = 'internal';
$name = untrailingslashit( $connection->site->domain . $connection->site->path );
$id = $connection->site->blog_id;

if ( is_a( $connection_now, '\Distributor\InternalConnections\NetworkSiteConnection' ) && (int) $connection_now->site->blog_id === (int) $id ) {
$selected = true;
}
?>
<option <?php selected( true, $selected ); ?> data-pull-url="<?php echo esc_url( admin_url( 'admin.php?page=pull&connection_type=' . $type . '&connection_id=' . $id ) ); ?>"><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
<?php if ( ! empty( $external_connection_group ) ) : ?>
</optgroup>
<?php endif; ?>
<?php endif; ?>

<?php if ( ! empty( $external_connection_group ) ) : ?>
<?php if ( ! empty( $internal_connection_group ) ) : ?>
<optgroup label="<?php esc_attr_e( 'External Connections', 'distributor' ); ?>">
<?php endif; ?>
<?php
foreach ( $external_connection_group as $connection ) :
$type = 'external';
$selected = false;
$name = $connection->name;
$id = $connection->id;

if ( is_a( $connection_now, '\Distributor\ExternalConnection' ) && (int) $connection_now->id === (int) $id ) {
$selected = true;
}
?>
<option <?php selected( true, $selected ); ?> data-pull-url="<?php echo esc_url( admin_url( 'admin.php?page=pull&connection_type=' . $type . '&connection_id=' . $id ) ); ?>"><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
<?php if ( ! empty( $internal_connection_group ) ) : ?>
</optgroup>
<?php endif; ?>
<?php endif; ?>
</select>
<div class="searchable-select">
<div class="searchable-select__input-container">
<input class="searchable-select__input" type="text" placeholder="Search...">
<span class="dashicons dashicons-arrow-down"></span>
</div>
<div class="searchable-select__dropdown"></div>
</div>


<?php
$connection_now->pull_post_type = '';
Expand Down Expand Up @@ -624,3 +588,63 @@ function output_pull_errors() {

<?php
}

/**
* Get connections for pull
*/
function get_connections() {
$connections = array();

if ( ! empty( \Distributor\Connections::factory()->get_registered()['networkblog'] ) ) {
$sites = \Distributor\InternalConnections\NetworkSiteConnection::get_available_authorized_sites( 'pull' );

foreach ( $sites as $site_array ) {
$internal_connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site_array['site'] );

$connections[] = [
'id' => $internal_connection->site->blog_id,
'name' => untrailingslashit( $internal_connection->site->domain . $internal_connection->site->path ),
'url' => untrailingslashit( preg_replace( '#(https?:\/\/|www\.)#i', '', get_site_url( $internal_connection->site->blog_id ) ) ),
Comment thread
jeffpaul marked this conversation as resolved.
Outdated
'pull_url' => esc_url( admin_url( 'admin.php?page=pull&connection_type=internal&connection_id=' . $internal_connection->site->blog_id ) ),
'type' => 'internal',
];
}
}

$external_connections = new \WP_Query(
array(
'post_type' => 'dt_ext_connection',
'fields' => 'ids',
'no_found_rows' => true,
'posts_per_page' => -1,
)
);

foreach ( $external_connections->posts as $external_connection_id ) {
$external_connection_type = get_post_meta( $external_connection_id, 'dt_external_connection_type', true );

if ( empty( \Distributor\Connections::factory()->get_registered()[ $external_connection_type ] ) ) {
continue;
}

$external_connection_status = get_post_meta( $external_connection_id, 'dt_external_connections', true );

if ( empty( $external_connection_status ) || empty( $external_connection_status['can_get'] ) ) {
continue;
}

$external_connection = \Distributor\ExternalConnection::instantiate( $external_connection_id );

if ( ! is_wp_error( $external_connection ) ) {
$connections[] = [
'id' => $external_connection->id,
'name' => $external_connection->name,
'url' => $external_connection->base_url,
'pull_url' => esc_url( admin_url( 'admin.php?page=pull&connection_type=external&connection_id=' . $external_connection->id ) ),
'type' => 'external',
];
}
}

wp_send_json_success( $connections );
}
2 changes: 1 addition & 1 deletion includes/push-ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function() {
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );
add_filter( 'amp_dev_mode_element_xpaths', __NAMESPACE__ . '\add_element_xpaths' );
add_filter( 'script_loader_tag', __NAMESPACE__ . '\add_dev_mode_to_assets', 10, 2 );
add_action( 'wp_ajax_dt_load_connections', __NAMESPACE__ . '\get_connections' );
add_action( 'wp_ajax_dt_load_connections_push', __NAMESPACE__ . '\get_connections' );
Comment on lines 26 to +28

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is for clarity from wp_ajax_dt_load_connections_pull?

We're probably stuck with the old name to ensure backward compatibility with the third party extensions and between different versions of the plugin. Otherwise we could go through a soft deprecation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing it out. I've added the old method back with soft deprecation. LMK if I did it correctly(this is my first time doing it)

add_action( 'wp_ajax_dt_push', __NAMESPACE__ . '\ajax_push' );
add_action( 'admin_bar_menu', __NAMESPACE__ . '\menu_button', 999 );
add_action( 'wp_footer', __NAMESPACE__ . '\menu_content', 10, 1 );
Expand Down