Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/prosolution-wp-client/admin/templates/admin-overview.php
+++ b/prosolution-wp-client/admin/templates/admin-overview.php
@@ -105,7 +105,7 @@
<label for="tablecell">' . esc_attr__($prosolwpclient_table_name, 'prosolution-wp-client') . '</label>
</td>
<td>
- <a href="' . add_query_arg($prosolwpclient_url_params, $prosolwpclient_overview_page_url) . '" class="">' . esc_attr__('View Data', 'prosolution-wp-client') . '</a>
+ <a href="' . esc_url( add_query_arg( $prosolwpclient_url_params, $prosolwpclient_overview_page_url ) ) . '" class="">' . esc_attr__('View Data', 'prosolution-wp-client') . '</a>
</td>
<td>
' . $prosolwpclient_sync_html . '
@@ -138,12 +138,12 @@
<h2>
<span><?php esc_attr_e('Logs and Activity', 'prosolution-wp-client'); ?></span>
<a title="<?php esc_html_e('Refresh/reload Log', 'prosolution-wp-client'); ?>"
- href="<?php echo add_query_arg('clear_log', 2, $_SERVER['REQUEST_URI']) ?>"
+ href="<?php echo esc_url( add_query_arg( 'clear_log', 2 ) ); ?>"
style="float: right;" class="button prosolrefreshlog_reset" data-cleartype="2">
<span class="dashicons dashicons-update"></span>
</a>
<a title="<?php esc_html_e('Delete Log and reload', 'prosolution-wp-client'); ?>"
- href="<?php echo add_query_arg('clear_log', 1, $_SERVER['REQUEST_URI']) ?>"
+ href="<?php echo esc_url( add_query_arg( 'clear_log', 1 ) ); ?>"
style="margin-left: 20px; float: right;" class="button prosolrefreshlog_reset"
data-cleartype="1" >
<span class="dashicons dashicons-trash"></span>
--- a/prosolution-wp-client/admin/templates/admin-view-single-table-list.php
+++ b/prosolution-wp-client/admin/templates/admin-view-single-table-list.php
@@ -82,7 +82,7 @@
<div class="inside">
<?php $prosolwpclient_pswp_table_list->views(); ?>
<form id="pswp_table_listing" method="post">
- <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>"/>
+ <input type="hidden" name="page" value="<?php echo isset( $_REQUEST['page'] ) ? esc_attr( wp_unslash( $_REQUEST['page'] ) ) : ''; ?>"/>
<?php $prosolwpclient_pswp_table_list->search_box('Search List', 'prosolution-wp-client'); ?>
<?php $prosolwpclient_pswp_table_list->display() ?>
</form>
--- a/prosolution-wp-client/includes/class-prosolwpclient-helper.php
+++ b/prosolution-wp-client/includes/class-prosolwpclient-helper.php
@@ -399,6 +399,267 @@
}
/**
+ * WOEX-4665 (PS-015): job data is synced from ProSolution and stored locally without
+ * any encoding, so every value has to be escaped where it is rendered.
+ *
+ * Custom job fields 1-10 and 21-25 are authored in the WOEX CKEditor and legitimately
+ * contain markup, so they are filtered against the WordPress post allow-list instead of
+ * being escaped. That keeps the formatting while dropping scripts and event handlers.
+ *
+ * @param string $prosolwpclient_value
+ * @return string
+ */
+ public static function proSol_escRichText( $prosolwpclient_value ) {
+ if ( ! is_scalar( $prosolwpclient_value ) ) {
+ return '';
+ }
+
+ add_filter( 'safe_style_css', array( __CLASS__, 'proSol_richTextSafeStyles' ) );
+ add_filter( 'safecss_filter_attr_allow_css', array( __CLASS__, 'proSol_richTextAllowColorFunctions' ), 10, 2 );
+
+ try {
+ return wp_kses_post( (string) $prosolwpclient_value );
+ } finally {
+ // Never leave the widened allow-list in place for the rest of the request.
+ remove_filter( 'safecss_filter_attr_allow_css', array( __CLASS__, 'proSol_richTextAllowColorFunctions' ), 10 );
+ remove_filter( 'safe_style_css', array( __CLASS__, 'proSol_richTextSafeStyles' ) );
+ }
+ }
+
+ /**
+ * Additional inert CSS properties the WOEX editor emits, kept so filtered job text keeps
+ * the layout the recruiter authored.
+ *
+ * @param array $prosolwpclient_styles
+ * @return array
+ */
+ public static function proSol_richTextSafeStyles( $prosolwpclient_styles ) {
+ // Presentational only: none of these can reference an external resource or execute.
+ return array_merge(
+ $prosolwpclient_styles,
+ array(
+ 'box-sizing',
+ 'word-spacing',
+ 'font-variant-caps',
+ 'font-variant-ligatures',
+ 'text-decoration-color',
+ 'text-decoration-style',
+ 'text-decoration-thickness',
+ )
+ );
+ }
+
+ /**
+ * WordPress rejects any declaration containing brackets, which drops the rgb() colours
+ * CKEditor writes. Colour functions cannot reference external resources or execute code,
+ * so allow them for colour properties with a purely numeric argument list.
+ *
+ * @param bool $prosolwpclient_allow_css
+ * @param string $prosolwpclient_declaration
+ * @return bool
+ */
+ public static function proSol_richTextAllowColorFunctions( $prosolwpclient_allow_css, $prosolwpclient_declaration ) {
+ if ( $prosolwpclient_allow_css ) {
+ return true;
+ }
+
+ return (bool) preg_match(
+ '#^s*(?:color|background-color|border-color|border-top-color|border-right-color|border-bottom-color|border-left-color|outline-color)s*:s*(?:rgb|rgba|hsl|hsla)(s*[0-9.,%s/]+)s*$#i',
+ $prosolwpclient_declaration
+ );
+ }
+
+ /**
+ * Escape a value used as an <img> src. WhatsApp QR codes arrive from the ProSolution API
+ * either as a URL or as an inline base64 image, and esc_url() would discard the latter.
+ *
+ * Only valid for the <img> src context: browsers do not run scripts in an SVG loaded that
+ * way. Do not reuse the result in <object>, <embed>, <iframe> or CSS url().
+ *
+ * @param string $prosolwpclient_value
+ * @return string
+ */
+ public static function proSol_escImageSrc( $prosolwpclient_value ) {
+ if ( ! is_scalar( $prosolwpclient_value ) ) {
+ return '';
+ }
+
+ $prosolwpclient_value = trim( (string) $prosolwpclient_value );
+ if ( $prosolwpclient_value === '' ) {
+ return '';
+ }
+
+ if ( preg_match( '#^data:image/(?:png|jpeg|jpg|gif|webp|svg+xml);base64,[A-Za-z0-9+/=s]+$#i', $prosolwpclient_value ) ) {
+ return esc_attr( preg_replace( '/s+/', '', $prosolwpclient_value ) );
+ }
+
+ return esc_url( $prosolwpclient_value );
+ }
+
+ /**
+ * WOEX-4668 (PS-013): allowlist ORDER BY column + direction for admin list tables.
+ *
+ * `$wpdb->prepare()` cannot bind identifiers, so sort inputs from `$_REQUEST`
+ * must never be interpolated unless they match a fixed column allowlist and
+ * ASC/DESC only. Unknown values fall back to the provided defaults.
+ *
+ * @param mixed $prosolwpclient_orderby Requested column (e.g. $_REQUEST['orderby']).
+ * @param mixed $prosolwpclient_order Requested direction (e.g. $_REQUEST['order']).
+ * @param array $prosolwpclient_allowed Map of allowed request keys => SQL column names,
+ * or a flat list of identical key/column names.
+ * @param string $prosolwpclient_default_orderby Default column when request is missing/unsafe.
+ * @param string $prosolwpclient_default_order Default direction when request is missing/unsafe.
+ * @return array{0:string,1:string} Safe [ column, ASC|DESC ].
+ */
+ public static function proSol_sanitizeSqlOrderBy( $prosolwpclient_orderby, $prosolwpclient_order, $prosolwpclient_allowed, $prosolwpclient_default_orderby = 'name', $prosolwpclient_default_order = 'asc' ) {
+ $allowed_map = array();
+ if ( is_array( $prosolwpclient_allowed ) ) {
+ foreach ( $prosolwpclient_allowed as $prosolwpclient_key => $prosolwpclient_column ) {
+ if ( is_int( $prosolwpclient_key ) ) {
+ $prosolwpclient_key = $prosolwpclient_column;
+ }
+ $prosolwpclient_key = (string) $prosolwpclient_key;
+ $prosolwpclient_column = (string) $prosolwpclient_column;
+ // Identifiers only: letters, digits, underscore (optional table.column).
+ if ( $prosolwpclient_key === '' || $prosolwpclient_column === '' ) {
+ continue;
+ }
+ if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9_]*(.[A-Za-z_][A-Za-z0-9_]*)?$/', $prosolwpclient_column ) ) {
+ continue;
+ }
+ $allowed_map[ $prosolwpclient_key ] = $prosolwpclient_column;
+ }
+ }
+
+ $orderby = is_scalar( $prosolwpclient_orderby ) ? (string) $prosolwpclient_orderby : '';
+ if ( isset( $allowed_map[ $orderby ] ) ) {
+ $safe_orderby = $allowed_map[ $orderby ];
+ } elseif ( isset( $allowed_map[ $prosolwpclient_default_orderby ] ) ) {
+ $safe_orderby = $allowed_map[ $prosolwpclient_default_orderby ];
+ } elseif ( ! empty( $allowed_map ) ) {
+ $safe_orderby = reset( $allowed_map );
+ } else {
+ $safe_orderby = preg_match( '/^[A-Za-z_][A-Za-z0-9_]*(.[A-Za-z_][A-Za-z0-9_]*)?$/', (string) $prosolwpclient_default_orderby )
+ ? (string) $prosolwpclient_default_orderby
+ : 'name';
+ }
+
+ $order = is_scalar( $prosolwpclient_order ) ? strtoupper( trim( (string) $prosolwpclient_order ) ) : '';
+ if ( $order !== 'ASC' && $order !== 'DESC' ) {
+ $order = strtoupper( trim( (string) $prosolwpclient_default_order ) );
+ if ( $order !== 'ASC' && $order !== 'DESC' ) {
+ $order = 'ASC';
+ }
+ }
+
+ return array( $safe_orderby, $order );
+ }
+
+ /**
+ * WOEX-4666 (PS-017 / PS-049): sanitize a single WorkExpert API path segment
+ * (crawler `uuid`, `woexPreview`, `jobid`, client-list ids, sync jobdetail ids).
+ *
+ * Only alphanumeric + hyphen values are accepted so path traversal (`../`),
+ * slashes, query fragments, and other URL-control characters cannot be
+ * concatenated into outbound `wp_remote_*` URLs. Returns '' when unsafe.
+ *
+ * @param mixed $prosolwpclient_value
+ * @return string
+ */
+ public static function proSol_sanitizeApiPathSegment( $prosolwpclient_value ) {
+ if ( ! is_scalar( $prosolwpclient_value ) ) {
+ return '';
+ }
+
+ $prosolwpclient_value = trim( (string) $prosolwpclient_value );
+ if ( $prosolwpclient_value === '' || strlen( $prosolwpclient_value ) > 64 ) {
+ return '';
+ }
+
+ // WorkExpert ids are alphanumeric with optional hyphens (jobid VARCHAR(11),
+ // preview/agent uuids up to ~50). Exclude `.` `/` `` `?` `#` `%` and spaces.
+ if ( ! preg_match( '/^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/', $prosolwpclient_value ) ) {
+ return '';
+ }
+
+ return $prosolwpclient_value;
+ }
+
+ /**
+ * Alias for crawler/preview UUID tokens (same allowlist as path segments).
+ *
+ * @param mixed $prosolwpclient_value
+ * @return string
+ */
+ public static function proSol_sanitizeApiUuid( $prosolwpclient_value ) {
+ return self::proSol_sanitizeApiPathSegment( $prosolwpclient_value );
+ }
+
+ /**
+ * WOEX-4670 (PS-039): whether a jobs-table row is treated as published for the
+ * public portal. Synced WorkExpert joblist rows carry a non-empty publishdate;
+ * bare inserts (and unpublished/internal rows without a publish marker) must
+ * not be readable via ?type=details&jobid=.
+ *
+ * @param mixed $prosolwpclient_publishdate
+ * @return bool
+ */
+ public static function proSol_isPublishedJobPublishdate( $prosolwpclient_publishdate ) {
+ if ( ! is_scalar( $prosolwpclient_publishdate ) ) {
+ return false;
+ }
+
+ return trim( (string) $prosolwpclient_publishdate ) !== '';
+ }
+
+ /**
+ * Sanitize a list of API path segments (e.g. skill-group ids joined for rating lookup).
+ * Invalid items are dropped; returns '' if none remain.
+ *
+ * @param mixed $prosolwpclient_values Array or comma-separated string.
+ * @return string Comma-joined safe segments, or ''.
+ */
+ public static function proSol_sanitizeApiPathSegmentList( $prosolwpclient_values ) {
+ if ( is_string( $prosolwpclient_values ) ) {
+ $prosolwpclient_values = explode( ',', $prosolwpclient_values );
+ }
+ if ( ! is_array( $prosolwpclient_values ) ) {
+ return '';
+ }
+
+ $safe = array();
+ foreach ( $prosolwpclient_values as $prosolwpclient_value ) {
+ $segment = self::proSol_sanitizeApiPathSegment( $prosolwpclient_value );
+ if ( $segment !== '' ) {
+ $safe[] = $segment;
+ }
+ }
+
+ return empty( $safe ) ? '' : implode( ',', $safe );
+ }
+
+ /**
+ * Escape a design-template colour before it is printed inside a <style> block, where
+ * esc_attr() would not prevent the value from closing the element.
+ *
+ * @param string $prosolwpclient_value
+ * @return string
+ */
+ public static function proSol_escCssColor( $prosolwpclient_value ) {
+ if ( ! is_scalar( $prosolwpclient_value ) ) {
+ return '';
+ }
+
+ $prosolwpclient_value = trim( (string) $prosolwpclient_value );
+
+ $prosolwpclient_is_color = preg_match( '/^#[0-9A-Fa-f]{3,8}$/', $prosolwpclient_value )
+ || preg_match( '/^[A-Za-z]+$/', $prosolwpclient_value )
+ || preg_match( '#^(?:rgb|rgba|hsl|hsla)(s*[0-9.,%s/]+)$#i', $prosolwpclient_value );
+
+ return $prosolwpclient_is_color ? $prosolwpclient_value : '';
+ }
+
+ /**
* handle color shade
* @param $color
* @param $percent
@@ -414,4 +675,275 @@
return '#'.substr(base_convert(0x1000000 + ($r<255?$r<1?0:$r:255)*0x10000 + ($b<255?$b<1?0:$b:255)*0x100 + ($g<255?$g<1?0:$g:255), 10, 16), 1);
}
+ /**
+ * WOEX-4667 (PS-019): drop the bundled blueimp demo upload server.
+ *
+ * `public/js/jQuery-File-Upload-master/server/` shipped the vendor sample
+ * `index.php` + `UploadHandler.php`, which are reachable directly over HTTP and
+ * run outside WordPress: no nonce, no capability check, stock blueimp defaults
+ * (write to `server/php/files/`, GET listing, DELETE unlink). The plugin never
+ * used them — only the client-side library under `js/` is registered, and real
+ * uploads go through `includes/UploadHandler.php` behind
+ * `proSol_fileUploadProcess`. The directory is gone from the package as of
+ * 2.0.11; this removes copies left behind by in-place/FTP upgrades that only
+ * overwrite files instead of replacing the plugin folder.
+ *
+ * Settles once per plugin version (option-gated) on admin page loads and on
+ * activation. Takes no request input: the target path is fixed and is verified
+ * to sit inside the plugin directory before anything is deleted.
+ *
+ * When the directory cannot be deleted (read-only plugin folder, locked file) the
+ * attempt is retried on later admin requests, throttled to once an hour so a host
+ * that never becomes writable does not re-walk the tree on every page load.
+ *
+ * @return void
+ */
+ public static function proSol_removeLegacyUploadServer() {
+ // Not a request-driven action: skip AJAX/cron/REST so the portal is untouched.
+ if ( ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() )
+ || ( function_exists( 'wp_doing_cron' ) && wp_doing_cron() )
+ || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
+ return;
+ }
+
+ if ( ! defined( 'PROSOLWPCLIENT_ROOT_PATH' ) || ! defined( 'PROSOLWPCLIENT_PLUGIN_VERSION' ) ) {
+ return;
+ }
+
+ if ( get_option( 'prosolwpclient_legacy_upload_server_removed' ) === PROSOLWPCLIENT_PLUGIN_VERSION ) {
+ return;
+ }
+
+ $prosolwpclient_plugin_dir = trailingslashit( wp_normalize_path( PROSOLWPCLIENT_ROOT_PATH ) );
+ $prosolwpclient_legacy_dir = $prosolwpclient_plugin_dir . 'public/js/jQuery-File-Upload-master/server';
+
+ // Nothing to clean: settle the version so later requests stop at the option.
+ if ( ! self::proSol_pathExists( $prosolwpclient_legacy_dir ) ) {
+ update_option( 'prosolwpclient_legacy_upload_server_removed', PROSOLWPCLIENT_PLUGIN_VERSION );
+
+ return;
+ }
+
+ $prosolwpclient_retry_after = defined( 'HOUR_IN_SECONDS' ) ? HOUR_IN_SECONDS : 3600;
+ $prosolwpclient_last_try = (int) get_option( 'prosolwpclient_legacy_upload_server_try', 0 );
+
+ if ( $prosolwpclient_last_try > 0 && ( time() - $prosolwpclient_last_try ) < $prosolwpclient_retry_after ) {
+ return;
+ }
+
+ // Recorded before the work so two concurrent admin requests do not both walk
+ // the tree, and so a fatal mid-delete cannot turn into a per-request retry.
+ update_option( 'prosolwpclient_legacy_upload_server_try', time() );
+
+ if ( self::proSol_pathIsLinked( $prosolwpclient_legacy_dir ) ) {
+ // A link stands where the vendor folder used to be: drop the link itself and
+ // never its target, which may point at files the plugin still needs.
+ // unlink() handles file symlinks, rmdir() the directory symlinks/junctions.
+ if ( ! @unlink( $prosolwpclient_legacy_dir ) ) {
+ @rmdir( $prosolwpclient_legacy_dir );
+ }
+ } elseif ( is_dir( $prosolwpclient_legacy_dir ) ) {
+ self::proSol_deleteDirWithin( $prosolwpclient_legacy_dir, $prosolwpclient_plugin_dir );
+ }
+
+ clearstatcache( true, $prosolwpclient_legacy_dir );
+
+ if ( self::proSol_pathExists( $prosolwpclient_legacy_dir ) ) {
+ // Read-only plugin directory: at least stop Apache from serving the endpoint.
+ // The version stays unsettled so the removal is retried once the directory
+ // becomes writable, which also covers servers that ignore `.htaccess`.
+ self::proSol_denyDirAccess( $prosolwpclient_legacy_dir, $prosolwpclient_plugin_dir );
+
+ return;
+ }
+
+ update_option( 'prosolwpclient_legacy_upload_server_removed', PROSOLWPCLIENT_PLUGIN_VERSION );
+ delete_option( 'prosolwpclient_legacy_upload_server_try' );
+ }
+
+ /**
+ * Whether anything at all occupies a path, including a broken symlink that
+ * file_exists() reports as absent.
+ *
+ * @param string $prosolwpclient_path Absolute path.
+ * @return bool
+ */
+ private static function proSol_pathExists( $prosolwpclient_path ) {
+ return file_exists( $prosolwpclient_path ) || is_link( $prosolwpclient_path );
+ }
+
+ /**
+ * Whether a path is a symlink, a Windows junction, or any other reparse point,
+ * i.e. whether it resolves somewhere other than where it sits.
+ *
+ * is_link() returns false for junctions (and is_dir() false for a stale one), so
+ * the resolved path is additionally compared against the literal location. Both
+ * sides are taken from realpath() so short-name and casing differences cannot
+ * make a genuine directory look redirected.
+ *
+ * @param string $prosolwpclient_path Absolute path.
+ * @return bool True for links and reparse points, and for anything unresolvable.
+ */
+ private static function proSol_pathIsLinked( $prosolwpclient_path ) {
+ if ( is_link( $prosolwpclient_path ) ) {
+ return true;
+ }
+
+ if ( ! self::proSol_pathExists( $prosolwpclient_path ) ) {
+ return false;
+ }
+
+ $prosolwpclient_resolved = realpath( $prosolwpclient_path );
+ $prosolwpclient_parent = realpath( dirname( $prosolwpclient_path ) );
+
+ if ( $prosolwpclient_resolved === false || $prosolwpclient_parent === false ) {
+ return true; // Occupied but unresolvable: treat as suspect and leave the target alone.
+ }
+
+ $prosolwpclient_resolved = wp_normalize_path( $prosolwpclient_resolved );
+ $prosolwpclient_expected = trailingslashit( wp_normalize_path( $prosolwpclient_parent ) ) . basename( $prosolwpclient_path );
+
+ if ( DIRECTORY_SEPARATOR === '\' ) {
+ return strcasecmp( $prosolwpclient_resolved, $prosolwpclient_expected ) !== 0;
+ }
+
+ return $prosolwpclient_resolved !== $prosolwpclient_expected;
+ }
+
+ /**
+ * Recursively delete a directory, but only when it resolves inside $prosolwpclient_boundary.
+ * Links are never followed: a linked target is refused, linked entries are unlinked.
+ *
+ * @param string $prosolwpclient_target Absolute directory path to delete.
+ * @param string $prosolwpclient_boundary Absolute directory the target must live under.
+ * @return bool True when the target no longer exists.
+ */
+ private static function proSol_deleteDirWithin( $prosolwpclient_target, $prosolwpclient_boundary ) {
+ // realpath() below would resolve a link and delete whatever it points at.
+ if ( self::proSol_pathIsLinked( $prosolwpclient_target ) ) {
+ return false;
+ }
+
+ $prosolwpclient_real_target = realpath( $prosolwpclient_target );
+ $prosolwpclient_real_boundary = realpath( $prosolwpclient_boundary );
+
+ if ( $prosolwpclient_real_target === false || $prosolwpclient_real_boundary === false ) {
+ return false;
+ }
+
+ $prosolwpclient_real_target = wp_normalize_path( $prosolwpclient_real_target );
+ $prosolwpclient_real_boundary = trailingslashit( wp_normalize_path( $prosolwpclient_real_boundary ) );
+
+ // Containment check: never delete anything outside the plugin directory.
+ if ( strpos( $prosolwpclient_real_target . '/', $prosolwpclient_real_boundary ) !== 0 ) {
+ return false;
+ }
+
+ return self::proSol_deleteTree( $prosolwpclient_real_target );
+ }
+
+ /**
+ * Depth-first delete that detaches links instead of descending into them.
+ *
+ * RecursiveDirectoryIterator does follow links, and SplFileInfo::isLink() reports
+ * false for a Windows junction, so an iterator-based walk can delete files in a
+ * link target. Recursing by hand keeps every entry behind proSol_pathIsLinked().
+ *
+ * The caller is responsible for having confirmed that $prosolwpclient_dir sits
+ * inside the plugin directory.
+ *
+ * @param string $prosolwpclient_dir Absolute directory path, already resolved.
+ * @return bool True when the directory no longer exists.
+ */
+ private static function proSol_deleteTree( $prosolwpclient_dir ) {
+ $prosolwpclient_entries = @scandir( $prosolwpclient_dir );
+
+ if ( $prosolwpclient_entries === false ) {
+ return false;
+ }
+
+ foreach ( $prosolwpclient_entries as $prosolwpclient_entry ) {
+ if ( $prosolwpclient_entry === '.' || $prosolwpclient_entry === '..' ) {
+ continue;
+ }
+
+ $prosolwpclient_path = $prosolwpclient_dir . '/' . $prosolwpclient_entry;
+
+ if ( self::proSol_pathIsLinked( $prosolwpclient_path ) ) {
+ // Detach the link only: its target may hold files the plugin still
+ // needs, or files outside the plugin directory altogether.
+ if ( ! @unlink( $prosolwpclient_path ) ) {
+ @rmdir( $prosolwpclient_path );
+ }
+
+ continue;
+ }
+
+ if ( is_dir( $prosolwpclient_path ) ) {
+ self::proSol_deleteTree( $prosolwpclient_path );
+
+ continue;
+ }
+
+ @unlink( $prosolwpclient_path );
+ }
+
+ @rmdir( $prosolwpclient_dir );
+ clearstatcache( true, $prosolwpclient_dir );
+
+ return ! self::proSol_pathExists( $prosolwpclient_dir );
+ }
+
+ /**
+ * Last-resort Apache block for a directory that could not be deleted.
+ *
+ * Written only inside the leftover vendor directory, never into the asset tree:
+ * these directives need `AllowOverride AuthConfig` (or `Limit`), and a host that
+ * grants neither answers 500 for the directory. That is acceptable here, because
+ * the endpoint is meant to be unreachable and nothing else lives under it.
+ *
+ * @param string $prosolwpclient_dir Absolute directory path.
+ * @param string $prosolwpclient_boundary Absolute directory the target must live under.
+ * @return void
+ */
+ private static function proSol_denyDirAccess( $prosolwpclient_dir, $prosolwpclient_boundary ) {
+ // A link resolves elsewhere, so the deny file would land in a live directory
+ // (for instance the registered js/ library) and break asset delivery.
+ if ( self::proSol_pathIsLinked( $prosolwpclient_dir ) ) {
+ return;
+ }
+
+ $prosolwpclient_real_dir = realpath( $prosolwpclient_dir );
+ $prosolwpclient_real_boundary = realpath( $prosolwpclient_boundary );
+
+ if ( $prosolwpclient_real_dir === false || $prosolwpclient_real_boundary === false ) {
+ return;
+ }
+
+ $prosolwpclient_real_dir = trailingslashit( wp_normalize_path( $prosolwpclient_real_dir ) );
+ $prosolwpclient_real_boundary = trailingslashit( wp_normalize_path( $prosolwpclient_real_boundary ) );
+
+ // A link pointing outside the plugin must not receive a written file.
+ if ( strpos( $prosolwpclient_real_dir, $prosolwpclient_real_boundary ) !== 0 ) {
+ return;
+ }
+
+ $prosolwpclient_htaccess = $prosolwpclient_real_dir . '.htaccess';
+
+ if ( file_exists( $prosolwpclient_htaccess ) && strpos( (string) @file_get_contents( $prosolwpclient_htaccess ), 'WOEX-4667' ) !== false ) {
+ return;
+ }
+
+ $prosolwpclient_rules = "# ProSolution WP Client - legacy blueimp demo server, access denied (WOEX-4667 / PS-019)n"
+ . "<IfModule mod_authz_core.c>n"
+ . " Require all deniedn"
+ . "</IfModule>n"
+ . "<IfModule !mod_authz_core.c>n"
+ . " Order allow,denyn"
+ . " Deny from alln"
+ . "</IfModule>n";
+
+ @file_put_contents( $prosolwpclient_htaccess, $prosolwpclient_rules );
+ }
+
}
No newline at end of file
--- a/prosolution-wp-client/includes/class-prosolwpclient-table-helper.php
+++ b/prosolution-wp-client/includes/class-prosolwpclient-table-helper.php
@@ -182,9 +182,21 @@
}
rmdir($img_dir_path);
}
- foreach ( $prosolwpclient_jobid_arr as $prosolwpclient_index => $prosolwpclient_jobid ) {
- $prosolwpclient_response = wp_remote_get( $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . $prosolwpclient_jobid, array( 'headers' => $prosolwpclient_header_info, 'timeout' => 300 ) );
-
+ foreach ( $prosolwpclient_jobid_arr as $prosolwpclient_index => $prosolwpclient_jobid ) {
+ // WOEX-4666: sync jobdetail path — allowlist API-sourced ids before URL concat.
+ $safe_jobid = CBXProSolWpClient_Helper::proSol_sanitizeApiPathSegment( $prosolwpclient_jobid );
+ if ( $safe_jobid === '' ) {
+ continue;
+ }
+ $prosolwpclient_response = wp_remote_get(
+ $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . rawurlencode( $safe_jobid ),
+ array(
+ 'headers' => $prosolwpclient_header_info,
+ 'timeout' => 300,
+ 'redirection' => 0,
+ )
+ );
+
if ( ! is_wp_error( $prosolwpclient_response ) ) {
$prosolwpclient_response_data = json_decode( $prosolwpclient_response['body'] )->data;
// for table jobs call proSol_allTablesInsertion here, not like other table, different flow
@@ -293,7 +305,15 @@
// project 1440, dailytask table jobs
public function proSol_dailytask_tableJobs($siteidfordb = "0"){
-
+ // Cron fires this same hook name; interactive admin-ajax must be privileged.
+ // Without this gate any logged-in role (e.g. Subscriber) can trigger the sync.
+ if ( ! wp_doing_cron() ) {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( esc_html__( 'You are not allowed to run this action.', 'prosolution-wp-client' ), 403 );
+ }
+ check_ajax_referer( 'prosolwpclient_admin', 'security' );
+ }
+
$prosolwpclient_selsite=$siteidfordb=="0" ? '' : 'site'.$siteidfordb.'_';
$frontend_settingPage = get_option( 'prosolwpclient_frontend' );
$chkclientlist = $frontend_settingPage[$prosolwpclient_selsite.'client_list'];
@@ -362,9 +382,20 @@
rmdir($img_dir_path);
}
$chkerror=0;
- foreach ( $prosolwpclient_jobid_arr as $prosolwpclient_index => $prosolwpclient_jobid ) {
- $prosolwpclient_response = wp_remote_get( $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . $prosolwpclient_jobid, array( 'headers' => $prosolwpclient_header_info ) );
-
+ foreach ( $prosolwpclient_jobid_arr as $prosolwpclient_index => $prosolwpclient_jobid ) {
+ // WOEX-4666: sync jobdetail path — allowlist API-sourced ids before URL concat.
+ $safe_jobid = CBXProSolWpClient_Helper::proSol_sanitizeApiPathSegment( $prosolwpclient_jobid );
+ if ( $safe_jobid === '' ) {
+ continue;
+ }
+ $prosolwpclient_response = wp_remote_get(
+ $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . rawurlencode( $safe_jobid ),
+ array(
+ 'headers' => $prosolwpclient_header_info,
+ 'redirection' => 0,
+ )
+ );
+
if ( ! is_wp_error( $prosolwpclient_response ) ) {
$prosolwpclient_response_data = json_decode( $prosolwpclient_response['body'] )->data;
--- a/prosolution-wp-client/includes/class-prosolwpclient.php
+++ b/prosolution-wp-client/includes/class-prosolwpclient.php
@@ -209,6 +209,10 @@
//setting init and add setting sub menu in setting menu
$this->loader->proSol_add_action( 'admin_init', $plugin_admin, 'proSol_adminInitCallback' );
+
+ // WOEX-4667 (PS-019): clear the legacy blueimp demo upload server left by in-place upgrades.
+ $this->loader->proSol_add_action( 'admin_init', 'CBXProSolWpClient_Helper', 'proSol_removeLegacyUploadServer' );
+
$this->loader->proSol_add_action( 'admin_notices', $plugin_admin, 'proSol_adminNotices' );
//$this->loader->proSol_add_action( 'admin_menu', $plugin_admin, 'myRenamedPlugin' );
--- a/prosolution-wp-client/includes/class-setting.php
+++ b/prosolution-wp-client/includes/class-setting.php
@@ -1203,8 +1203,15 @@
*/
function proSol_callback_file_with_thumbnail( $args ) {
$array_value = $this->proSol_get_option( $args['id'], $args['section'], $args['default'] );
- $value_file = $array_value['file'];
- $value_name = $array_value['name'];
+ $value_file = isset( $array_value['file'] ) ? $array_value['file'] : '';
+ $value_name = isset( $array_value['name'] ) ? $array_value['name'] : '';
+ // WOEX-4669 / PS-044: escape logo URL/name in admin HTML.
+ // img src uses display escaping; hidden inputs use esc_attr on the raw URL/name
+ // so a settings re-save does not entity-encode a valid media library URL.
+ $safe_src = CBXProSolWpClient_Helper::proSol_escImageSrc( $value_file );
+ $safe_file_attr = esc_attr( esc_url_raw( is_scalar( $value_file ) ? (string) $value_file : '' ) );
+ $safe_name = esc_html( is_scalar( $value_name ) ? (string) $value_name : '' );
+ $safe_name_attr = esc_attr( is_scalar( $value_name ) ? (string) $value_name : '' );
$size = isset( $args['size'] ) && ! is_null( $args['size'] ) ? $args['size'] : 'regular';
$id = $args['section'] . '[' . $args['id'] . ']';
@@ -1213,10 +1220,10 @@
esc_html__( 'Choose File', 'prosolution-wp-client' );
$prosolwpclient_html = sprintf( '<input type="button" id="%1$s[%2$s]" class="button wpul-browse" value="%3$s" />', $args['section'], $args['id'], $label );
- $prosolwpclient_html .= sprintf( '<span style="color:gray; padding-left:15px;vertical-align:inherit;" class="%1$s-text wpul-url" id="%2$s[%3$s]" name="%2$s[%3$s]">%4$s</span>', $size, $args['section'], $args['id'], $value_name );
- $prosolwpclient_html .= sprintf( '<br><img style="margin:32px 0"; class="wpul-img" src="%1$s" draggable="false" alt="" value="">', $value_file );
- $prosolwpclient_html .= sprintf( '<input type="hidden" class="%1$s-text wpul-value" id="%2$s[%3$sfile]" name="%2$s[%3$sfile]" value="%4$s"/>', $size, $args['section'], $args['id'], $value_file );
- $prosolwpclient_html .= sprintf( '<input type="hidden" class="%1$s-text wpul-name" id="%2$s[%3$sname]" name="%2$s[%3$sname]" value="%4$s"/>', $size, $args['section'], $args['id'], $value_name );
+ $prosolwpclient_html .= sprintf( '<span style="color:gray; padding-left:15px;vertical-align:inherit;" class="%1$s-text wpul-url" id="%2$s[%3$s]" name="%2$s[%3$s]">%4$s</span>', $size, $args['section'], $args['id'], $safe_name );
+ $prosolwpclient_html .= sprintf( '<br><img style="margin:32px 0"; class="wpul-img" src="%1$s" draggable="false" alt="" value="">', $safe_src );
+ $prosolwpclient_html .= sprintf( '<input type="hidden" class="%1$s-text wpul-value" id="%2$s[%3$sfile]" name="%2$s[%3$sfile]" value="%4$s"/>', $size, $args['section'], $args['id'], $safe_file_attr );
+ $prosolwpclient_html .= sprintf( '<input type="hidden" class="%1$s-text wpul-name" id="%2$s[%3$sname]" name="%2$s[%3$sname]" value="%4$s"/>', $size, $args['section'], $args['id'], $safe_name_attr );
$prosolwpclient_html .= $this->proSol_get_field_description( $args );
echo $prosolwpclient_html;
@@ -1321,6 +1328,10 @@
* Sanitize callback for Settings API
*/
function proSol_sanitize_options( $options ) {
+ if ( ! is_array( $options ) ) {
+ return $options;
+ }
+
foreach ( $options as $option_slug => $option_value ) {
$sanitize_callback = $this->proSol_get_sanitize_callback( $option_slug );
@@ -1329,6 +1340,17 @@
$options[ $option_slug ] = call_user_func( $sanitize_callback, $option_value );
continue;
}
+
+ // WOEX-4669 / PS-044: logo URL/name keys are saved as *deslogofile / *deslogoname
+ // (including siteN_ prefixes). Keep valid media URLs; strip attribute breakouts.
+ if ( is_string( $option_slug ) && substr( $option_slug, -11 ) === 'deslogofile' ) {
+ $options[ $option_slug ] = esc_url_raw( is_scalar( $option_value ) ? (string) $option_value : '' );
+ continue;
+ }
+ if ( is_string( $option_slug ) && substr( $option_slug, -11 ) === 'deslogoname' ) {
+ $options[ $option_slug ] = sanitize_text_field( is_scalar( $option_value ) ? (string) $option_value : '' );
+ continue;
+ }
}
return $options;
--- a/prosolution-wp-client/includes/single-table-list/class-prosolwpclient-federal-list.php
+++ b/prosolution-wp-client/includes/single-table-list/class-prosolwpclient-federal-list.php
@@ -164,13 +164,31 @@
$sortable_columns = array(
'federalId' => array('federalId', false), //true means it's already sorted
'name' => array('name', false),
- 'countryCode' => array('Country Code', false), //true means it's already sorted
- 'site_id' => array('site_id', false),
+ // Must match the SQL column name (and ORDER BY allowlist), not the display label.
+ 'countryCode' => array('countryCode', false),
+ 'site_id' => array('site_id', false),
);
return $sortable_columns;
}
+ /**
+ * WOEX-4668 (PS-013): columns allowed in ORDER BY for this admin list.
+ *
+ * @return array
+ */
+ function proSol_get_allowed_orderby_columns()
+ {
+ return array(
+ 'federalId' => 'federalId',
+ 'name' => 'name',
+ 'countryCode' => 'countryCode',
+ // Legacy sortable value from older builds used the display label.
+ 'Country Code'=> 'countryCode',
+ 'site_id' => 'site_id',
+ );
+ }
+
function prepare_items()
{
//global $wpdb;global $prosolwpclient_prefix; //This is used only if making any database queries
@@ -239,8 +257,14 @@
*/
- $prosolwpclient_order = (isset($_REQUEST['order']) && $_REQUEST['order'] != '') ? $_REQUEST['order'] : 'asc';
- $orderby = (isset($_REQUEST['orderby']) && $_REQUEST['orderby'] != '') ? $_REQUEST['orderby'] : 'name';
+ // WOEX-4668 (PS-013): never pass raw $_REQUEST orderby/order into SQL.
+ list( $orderby, $prosolwpclient_order ) = CBXProSolWpClient_Helper::proSol_sanitizeSqlOrderBy(
+ isset( $_REQUEST['orderby'] ) ? $_REQUEST['orderby'] : 'name',
+ isset( $_REQUEST['order'] ) ? $_REQUEST['order'] : 'asc',
+ $this->proSol_get_allowed_orderby_columns(),
+ 'name',
+ 'asc'
+ );
$search = (isset($_REQUEST['s']) && $_REQUEST['s'] != '') ? sanitize_text_field($_REQUEST['s']) : '';
@@ -311,11 +335,18 @@
$prosolwpclient_start_point = ($page * $prosolwpclient_perpage) - $prosolwpclient_perpage;
$prosolwpclient_limit_sql = "LIMIT";
- $prosolwpclient_limit_sql .= ' ' . $prosolwpclient_start_point . ',';
- $prosolwpclient_limit_sql .= ' ' . $prosolwpclient_perpage;
-
+ $prosolwpclient_limit_sql .= ' ' . intval( $prosolwpclient_start_point ) . ',';
+ $prosolwpclient_limit_sql .= ' ' . intval( $prosolwpclient_perpage );
- $prosolwpclient_sortingOrder = " ORDER BY $orderby $prosolwpclient_order ";
+ // Defense in depth: re-allowlist even if callers pass unsanitized values.
+ list( $orderby, $prosolwpclient_order ) = CBXProSolWpClient_Helper::proSol_sanitizeSqlOrderBy(
+ $orderby,
+ $prosolwpclient_order,
+ $this->proSol_get_allowed_orderby_columns(),
+ 'name',
+ 'asc'
+ );
+ $prosolwpclient_sortingOrder = " ORDER BY {$orderby} {$prosolwpclient_order} ";
$data = $wpdb->get_results("$prosolwpclient_sql_select WHERE $prosolwpclient_where_sql $prosolwpclient_sortingOrder $prosolwpclient_limit_sql", 'ARRAY_A');
@@ -356,11 +387,8 @@
$prosolwpclient_where_sql = '1';
}
-
- $prosolwpclient_sortingOrder = " ORDER BY $orderby $prosolwpclient_order ";
-
-
- $count = $wpdb->get_var("$prosolwpclient_sql_select WHERE $prosolwpclient_where_sql $prosolwpclient_sortingOrder");
+ // COUNT does not need ORDER BY; omit it so injected sort input cannot delay this path.
+ $count = $wpdb->get_var("$prosolwpclient_sql_select WHERE $prosolwpclient_where_sql");
return $count;
}
--- a/prosolution-wp-client/prosolwpclient.php
+++ b/prosolution-wp-client/prosolwpclient.php
@@ -16,7 +16,7 @@
* Plugin Name: ProSolution WP Client
* Plugin URI: https://prosolution.com/produkte-und-services/workexpert.html
* Description: WordPress client for ProSolution
- * Version: 2.0.10
+ * Version: 2.0.11
* Author: ProSolution
* Author URI: https://www.prosolution.com
* License: GPL-2.0+
@@ -44,7 +44,7 @@
defined('PROSOLWPCLIENT_PLUGIN_NAME') or define('PROSOLWPCLIENT_PLUGIN_NAME', 'prosolwpclient');
- defined('PROSOLWPCLIENT_PLUGIN_VERSION') or define('PROSOLWPCLIENT_PLUGIN_VERSION', '2.0.10');
+ defined('PROSOLWPCLIENT_PLUGIN_VERSION') or define('PROSOLWPCLIENT_PLUGIN_VERSION', '2.0.11');
defined('PROSOLWPCLIENT_BASE_NAME') or define('PROSOLWPCLIENT_BASE_NAME', plugin_basename(__FILE__));
defined('PROSOLWPCLIENT_ROOT_PATH') or define('PROSOLWPCLIENT_ROOT_PATH', plugin_dir_path(__FILE__));
defined('PROSOLWPCLIENT_ROOT_URL') or define('PROSOLWPCLIENT_ROOT_URL', plugin_dir_url(__FILE__));
@@ -338,6 +338,11 @@
CBXProSolWpClient_Activator::proSol_activate();
CBXProSolWpClient_Activator::proSol_createPages(); //create the shortcode page
+
+ // WOEX-4667 (PS-019): remove the legacy blueimp demo upload server on install/upgrade.
+ if ( class_exists( 'CBXProSolWpClient_Helper' ) ) {
+ CBXProSolWpClient_Helper::proSol_removeLegacyUploadServer();
+ }
}
/**
--- a/prosolution-wp-client/public/class-prosolwpclient-public.php
+++ b/prosolution-wp-client/public/class-prosolwpclient-public.php
@@ -487,9 +487,15 @@
$template_name = 'templates/prosolwpclientjobsearchresult.php';
}
- $prosolwpclient_jobid = isset( $_REQUEST['jobid'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['jobid'] ) ) : '';
+ // WOEX-4666: allowlist path segments used in outbound WorkExpert URLs / SQL job lookups.
+ $prosolwpclient_jobid = isset( $_REQUEST['jobid'] )
+ ? CBXProSolWpClient_Helper::proSol_sanitizeApiPathSegment( wp_unslash( $_REQUEST['jobid'] ) )
+ : '';
//$uuid='EAC87A80-9262-4C49-851F7C5FD02FBE64';
- $woexPreviewUUID = isset($_REQUEST['woexPreview']) ? $_REQUEST['woexPreview'] : '';
+ // WOEX-4666 (PS-017 / PS-049): never feed raw request tokens into API path/body.
+ $woexPreviewUUID = isset( $_REQUEST['woexPreview'] )
+ ? CBXProSolWpClient_Helper::proSol_sanitizeApiUuid( wp_unslash( $_REQUEST['woexPreview'] ) )
+ : '';
if ( $param_type == 'details' ) {
// project 1440,kev
/* $prosolwpclient_header_info = CBXProSolWpClient_TableHelper::proSol_apiConfig($prosolwpclient_issite);
@@ -511,16 +517,24 @@
global $wpdb;global $prosolwpclient_prefix;
$table_ps_jobs = $prosolwpclient_prefix . 'jobs';
- $qjobs = $wpdb->get_results(
- $wpdb->prepare(
- "SELECT * FROM $table_ps_jobs WHERE jobid = %s AND site_id = %d",
- $prosolwpclient_jobid,
- $prosolwpclient_siteid
- ),
- "OBJECT"
- );
+ // WOEX-4670 / PS-039: only return portal-published jobs by jobid.
+ // Synced joblist rows always carry publishdate; unpublished / synthetic
+ // rows (and guessable IDs without a publish marker) must not leak.
+ // TRIM matches proSol_isPublishedJobPublishdate() so whitespace-only
+ // values are not treated as published.
+ $qjobs = array();
+ if ( $prosolwpclient_jobid !== '' ) {
+ $qjobs = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT * FROM $table_ps_jobs WHERE jobid = %s AND site_id = %d AND publishdate IS NOT NULL AND TRIM(publishdate) <> ''",
+ $prosolwpclient_jobid,
+ $prosolwpclient_siteid
+ ),
+ "OBJECT"
+ );
+ }
- $prosolwpclient_recordcount = count($qjobs);
+ $prosolwpclient_recordcount = is_array( $qjobs ) ? count( $qjobs ) : 0;
if ( $prosolwpclient_recordcount != 0 ) {
// transform into API's output structure
$obj_response= (object)[];
@@ -558,6 +572,14 @@
$job_details_result = $obj_response;
// var_dump($profession_decode);
+ } else if ( $prosolwpclient_jobid === '' ) {
+ // WOEX-4666: invalid/missing jobid — do not call jobdetail with a crafted path segment.
+ $job_details_result = sprintf( __( 'Database returns empty', 'prosolution-wp-client' ) );
+ } else if ( $woexPreviewUUID === '' ) {
+ // WOEX-4670 / PS-039: unpublished jobs are not in the published local
+ // set. Do not call WorkExpert jobdetail by guessable jobid alone —
+ // preview links must supply a valid woexPreview UUID.
+ $job_details_result = sprintf( __( 'Database returns empty', 'prosolution-wp-client' ) );
} else {
$prosolwpclient_header_info = CBXProSolWpClient_TableHelper::proSol_apiConfig($prosolwpclient_issite);
$safe_data = array(
@@ -567,14 +589,21 @@
$api_location = 'recruitment/';
$api_body = array( "param" => json_encode( $safe_data ) );
$prosolwpclient_response = wp_remote_post( $prosolwpclient_api_config['api_url'] .''. $api_location .'previewisvalid', array(
- 'headers' => $prosolwpclient_header_info,
- 'body' => $api_body
+ 'headers' => $prosolwpclient_header_info,
+ 'body' => $api_body,
+ 'redirection' => 0,
) );
if ( ! is_wp_error( $prosolwpclient_response ) ) {
$prosolwpclient_response_data = json_decode( $prosolwpclient_response['body'] )->data;
$result = $prosolwpclient_response_data->previewstatus;
if($result){
- $prosolwpclient_response = wp_remote_get( $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . $prosolwpclient_jobid, array( 'headers' => $prosolwpclient_header_info ) );
+ $prosolwpclient_response = wp_remote_get(
+ $prosolwpclient_api_config['api_url'] . $api_location . 'jobdetail/' . rawurlencode( $prosolwpclient_jobid ),
+ array(
+ 'headers' => $prosolwpclient_header_info,
+ 'redirection' => 0,
+ )
+ );
$prosolwpclient_response_data = json_decode( $prosolwpclient_response['body'] )->data;
$job_details_result = $prosolwpclient_response_data;
@@ -599,26 +628,43 @@
}
if ( $param_type == 'crawler' ) {
- $uuid = $_REQUEST['uuid'];
- $prosolwpclient_header_info = CBXProSolWpClient_TableHelper::proSol_apiConfig($prosolwpclient_issite);
- $prosolwpclient_response = wp_remote_get( $prosolwpclient_api_config['api_url'] . 'application/jobdetails/' . $uuid, array( 'headers' => $prosolwpclient_header_info ) );
- if ( ! is_wp_error( $prosolwpclient_response ) ) {
-
- $job_details_result = json_decode( $prosolwpclient_response['body'] )->data;
+ // WOEX-4666 (PS-017): allowlist uuid before concatenating into the outbound API path.
+ // Evidence: uuid=../../evil → .../application/jobdetails/../../evil (path injection).
+ $uuid = isset( $_REQUEST['uuid'] )
+ ? CBXProSolWpClient_Helper::proSol_sanitizeApiUuid( wp_unslash( $_REQUEST['uuid'] ) )
+ : '';
+ if ( $uuid === '' ) {
+ $job_details_result = __( 'Not found any jobs', 'prosolution-wp-client' );
} else {
- $job_details_result = sprintf( __( 'Api response failed. Message: %s', 'prosolution-wp-client' ), $prosolwpclient_response->get_error_message() );
+ $prosolwpclient_header_info = CBXProSolWpClient_TableHelper::proSol_apiConfig( $prosolwpclient_issite );
+ $prosolwpclient_response = wp_remote_get(
+ $prosolwpclient_api_config['api_url'] . 'application/jobdetails/' . rawurlencode( $uuid ),
+ array(
+ 'headers' => $prosolwpclient_header_info,
+ 'redirection' => 0,
+ )
+ );
+ if ( ! is_wp_error( $prosolwpclient_response ) ) {
+ $job_details_result = json_decode( $prosolwpclient_response['body'] )->data;
+ } else {
+ $job_details_result = sprintf( __( 'Api response failed. Message: %s', 'prosolution-wp-client' ), $prosolwpclient_response->get_error_message() );
+ }
}
-
+
$template_name = 'templates/prosolwpclientcrawler.php';
}
- $prosolwpclient_opt = get_option('prosolwpclient_designtemplate');
- $wplogo= $prosolwpclient_opt[$prosolwpclient_issite.'deslogofile'];
+ $prosolwpclient_opt = get_option( 'prosolwpclient_designtemplate' );
+ // WOEX-4669 / PS-044: deslogofile is stored in options and echoed into <img src>;
+ // escape as an image URL so attribute breakout payloads cannot execute.
+ $wplogo = ( is_array( $prosolwpclient_opt ) && isset( $prosolwpclient_opt[ $prosolwpclient_issite . 'deslogofile' ] ) )
+ ? CBXProSolWpClient_Helper::proSol_escImageSrc( $prosolwpclient_opt[ $prosolwpclient_issite . 'deslogofile' ] )
+ : '';
ob_start();
echo '<p id="anchorwp"></p>';
- echo '<img style="margin: 32px 15px;" class="prosolwpclientlogo" src="'. $wplogo .'"></img>';
+ echo '<img style="margin: 32px 15px;" class="prosolwpclientlogo" src="' . $wplogo . '"></img>';
echo '<div class="prosolwpclientcustombootstrap">';
include( $template_name );
echo '</div>';
--- a/prosolution-wp-client/public/js/jQuery-File-Upload-master/server/php/UploadHandler.php
+++ b/prosolution-wp-client/public/js/jQuery-File-Upload-master/server/php/UploadHandler.php
@@ -1,1480 +0,0 @@
-<?php
-/*
- * jQuery File Upload Plugin PHP Class
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2010, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * https://opensource.org/licenses/MIT
- */
-
-class UploadHandler
-{
-
- protected $options;
-
- // PHP File Upload error message codes:
- // https://php.net/manual/en/features.file-upload.errors.php
- protected $error_messages = array(
- 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
- 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
- 3 => 'The uploaded file was only partially uploaded',
- 4 => 'No file was uploaded',
- 6 => 'Missing a temporary folder',
- 7 => 'Failed to write file to disk',
- 8 => 'A PHP extension stopped the file upload',
- 'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini',
- 'max_file_size' => 'File is too big',
- 'min_file_size' => 'File is too small',
- 'accept_file_types' => 'Filetype not allowed',
- 'max_number_of_files' => 'Maximum number of files exceeded',
- 'invalid_file_type' => 'Invalid file type',
- 'max_width' => 'Image exceeds maximum width',
- 'min_width' => 'Image requires a minimum width',
- 'max_height' => 'Image exceeds maximum height',
- 'min_height' => 'Image requires a minimum height',
- 'abort' => 'File upload aborted',
- 'image_resize' => 'Failed to resize image'
- );
-
- const IMAGETYPE_GIF = 'image/gif';
- const IMAGETYPE_JPEG = 'image/jpeg';
- const IMAGETYPE_PNG = 'image/png';
-
- protected $image_objects = array();
- protected $response = array();
-
- public function __construct($options = null, $initialize = true, $error_messages = null) {
- $this->options = array(
- 'script_url' => $this->get_full_url().'/'.$this->basename($this->get_server_var('SCRIPT_NAME')),
- 'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
- 'upload_url' => $this->get_full_url().'/files/',
- 'input_stream' => 'php://input',
- 'user_dirs' => false,
- 'mkdir_mode' => 0755,
- 'param_name' => 'files',
- // Set the following option to 'POST', if your server does not support
- // DELETE requests. This is a parameter sent to the client:
- 'delete_type' => 'DELETE',
- 'access_control_allow_origin' => '*',
- 'access_control_allow_credentials' => false,
- 'access_control_allow_methods' => array(
- 'OPTIONS',
- 'HEAD',
- 'GET',
- 'POST',
- 'PUT',
- 'PATCH',
- 'DELETE'
- ),
- 'access_control_allow_headers' => array(
- 'Content-Type',
- 'Content-Range',
- 'Content-Disposition'
- ),
- // By default, allow redirects to the referer protocol+host:
- 'redirect_allow_target' => '/^'.preg_quote(
- parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_SCHEME)
- .'://'
- .parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_HOST)
- .'/', // Trailing slash to not match subdomains by mistake
- '/' // preg_quote delimiter param
- ).'/',
- // Enable to provide file downloads via GET requests to the PHP script:
- // 1. Set to 1 to download files via readfile method through PHP
- // 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache
- // 3. Set to 3 to send a X-Accel-Redirect header for nginx
- // If set to 2 or 3, adjust the upload_url option to the base path of
- // the redirect parameter, e.g. '/files/'.
- 'download_via_php' => false,
- // Read files in chunks to avoid memory limits when download_via_php
- // is enabled, set to 0 to disable chunked reading of files:
- 'readfile_chunk_size' => 10 * 1024 * 1024, // 10 MiB
- // Defines which files can be displayed inline when downloaded:
- 'inline_file_types' => '/.(gif|jpe?g|png)$/i',
- // Defines which files (based on their names) are accepted for upload.
- // By default, only allows file uploads with image file extensions.
- // Only change this setting after making sure that any allowed file
- // types cannot be executed by the webserver in the files directory,
- // e.g. PHP scripts, nor executed by the browser when downloaded,
- // e.g. HTML files with embedded JavaScript code.
- // Please also read the SECURITY.md document in this repository.
- 'accept_file_types' => '/.(gif|jpe?g|png)$/i',
- // Replaces dots in filenames with the given string.
- // Can be disabled by setting it to false or an empty string.
- // Note that this is a security feature for servers that support
- // multiple file extensions, e.g. the Apache AddHandler Directive:
- // https://httpd.apache.org/docs/current/mod/mod_mime.html#addhandler
- // Before disabling it, make sure that files uploaded with multiple
- // extensions cannot be executed by the webserver, e.g.
- // "example.php.png" with embedded PHP code, nor executed by the
- // browser when downloaded, e.g. "example.html.gif" with embedded
- // JavaScript code.
- 'replace_dots_in_filenames' => '-',
- // The php.ini settings upload_max_filesize and post_max_size
- // take precedence over the following max_file_size setting:
- 'max_file_size' => null,
- 'min_file_size' => 1,
- // The maximum number of files for the upload directory:
- 'max_number_of_files' => null,
- // Reads first file bytes to identify and correct file extensions:
- 'correct_image_extensions' => false,
- // Image resolution restrictions:
- 'max_width' => null,
- 'max_height' => null,
- 'min_width' => 1,
- 'min_height' => 1,
- // Set the following option to false to enable resumable uploads:
- 'discard_aborted_uploads' => true,
- // Set to 0 to use the GD library to scale and orient images,
- // set to 1 to use imagick (if installed, falls back to GD),
- // set to 2 to use the ImageMagick convert binary directly:
- 'image_library' => 1,
- // Uncomment the following to define an array of resource limits
- // for imagick:
- /*
- 'imagick_resource_limits' => array(
- imagick::RESOURCETYPE_MAP => 32,
- imagick::RESOURCETYPE_MEMORY => 32
- ),
- */
- // Command or path for to the ImageMagick convert binary:
- 'convert_bin' => 'convert',
- // Uncomment the following to add parameters in front of each
- // ImageMagick convert call (the limit constraints seem only
- // to have an effect if put in front):
- /*
- 'convert_params' => '-limit memory 32MiB -limit map 32MiB',
- */
- // Command or path for to the ImageMagick identify binary:
- 'identify_bin' => 'identify',
- 'image_versions' => array(
- // The empty image version key defines options for the original image.
- // Keep in mind: these image manipulations are inherited by all other image versions from this point onwards.
- // Also note that the property 'no_cache' is not inherited, since it's not a manipulation.
- '' => array(
- // Automatically rotate images based on EXIF meta data:
- 'auto_orient' => true
- ),
- // You can add arrays to generate different versions.
- // The name of the key is the name of the version (example: 'medium').
- // the array contains the options to apply.
- /*
- 'medium' => array(
- 'max_width' => 800,
- 'max_height' => 600
- ),
- */
- 'thumbnail' => array(
- // Uncomment the following to use a defined directory for the thumbnails
- // instead of a subdirectory based on the version identifier.
- // Make sure that this directory doesn't allow execution of files if you
- // don't pose any restrictions on the type of uploaded files, e.g. by
- // copying the .htaccess file from the files directory for Apache:
- //'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/',
- //'upload_url' => $this->get_full_url().'/thumb/',
- // Uncomment the following to force the max
- // dimensions and e.g. create square thumbnails:
- // 'auto_orient' => true,
- // 'crop' => true,
- // 'jpeg_quality' => 70,
- // 'no_cache' => true, (there's a caching option, but this remembers thumbnail sizes from a previous action!)
- // 'strip' => true, (this strips EXIF tags, such as geolocation)
- 'max_width' => 80, // either specify width, or set to 0. Then width is automatically adjusted - keeping aspect ratio to a specified max_height.
- 'max_height' => 80 // either specify height, or set to 0. Then height is automatically adjusted - keeping aspect ratio to a specified max_width.
- )
- ),
- 'print_response' => true
- );
- if ($options) {
- $this->options = $options + $this->options;
- }
- if ($error_messages) {
- $this->error_messages = $error_messages + $this->error_messages;
- }
- if ($initialize) {
- $this->initialize();
- }
- }
-
- protected function initialize() {
- switch ($this->get_server_var('REQUEST_METHOD')) {
- case 'OPTIONS':
- case 'HEAD':
- $this->head();
- break;
- case 'GET':
- $this->get($this->options['print_response']);
- break;
- case 'PATCH':
- case 'PUT':
- case 'POST':
- $this->post($this->options['print_response']);
- break;
- case 'DELETE':
- $this->delete($this->options['print_response']);
- break;
- default:
- $this->header('HTTP/1.1 405 Method Not Allowed');
- }
- }
-
- protected function get_full_url() {
- $https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0 ||
- !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
- strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
- return
- ($https ? 'https://' : 'http://').
- (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
- (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
- ($https && $_SERVER['SERVER_PORT'] === 443 ||
- $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
- substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
- }
-
- protected function get_user_id() {
- @session_start();
- return session_id();