Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/all-in-one-wp-security-and-firewall/admin/general/wp-security-ajax-data-table.php
+++ b/all-in-one-wp-security-and-firewall/admin/general/wp-security-ajax-data-table.php
@@ -127,6 +127,41 @@
'extra_tablenav',
'single_row_columns',
);
+
+ /**
+ * Allowed HTML tags and attributes used when rendering pagination & header markup.
+ *
+ * @var array
+ */
+ protected $allowed_html = array(
+ 'a' => array(
+ 'class' => true,
+ 'id' => true,
+ 'href' => true,
+ ),
+ 'div' => array(
+ 'class' => true,
+ 'id' => true,
+ ),
+ 'span' => array(
+ 'class' => true,
+ 'id' => true,
+ 'aria-hidden' => true,
+ ),
+ 'label' => array(
+ 'class' => true,
+ 'for' => true,
+ ),
+ 'input' => array(
+ 'class' => true,
+ 'id' => true,
+ 'name' => true,
+ 'type' => true,
+ 'value' => true,
+ 'size' => true,
+ 'aria-describedby' => true,
+ ),
+ );
/**
* Constructor.
@@ -164,6 +199,11 @@
)
);
+ // Workaround for WordPress bug that was fixed in 6.1: https://core.trac.wordpress.org/ticket/49089
+ if (empty($GLOBALS['hook_suffix'])) {
+ $GLOBALS['hook_suffix'] = '';
+ }
+
$this->screen = convert_to_screen($args['screen']);
add_filter("manage_{$this->screen->id}_columns", array($this, 'get_columns'), 0);
@@ -184,8 +224,8 @@
if (empty($this->modes)) {
$this->modes = array(
- 'list' => __('List view', 'all-in-one-wp-security-and-firewall'),
- 'excerpt' => __('Excerpt view', 'all-in-one-wp-security-and-firewall'),
+ 'list' => esc_html__('List view', 'all-in-one-wp-security-and-firewall'),
+ 'excerpt' => esc_html__('Excerpt view', 'all-in-one-wp-security-and-firewall'),
);
}
}
@@ -484,10 +524,8 @@
echo '<option value="-1">' . esc_html__('Bulk actions', 'all-in-one-wp-security-and-firewall') . "</option>n";
foreach ($this->_actions as $name => $title) {
- $class = 'edit' === $name ? ' class="hide-if-no-js"' : '';
-
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP error. HTML in $class doesn't need escaping.
- echo "t" . '<option value="' . esc_attr($name) . '"' . $class . '>' . esc_html($title) . "</option>n";
+ $class = 'edit' === $name ? 'hide-if-no-js' : '';
+ echo "t" . '<option value="' . esc_attr($name) . '" class="' . esc_attr($class) . '">' . esc_html($title) . "</option>n";
}
echo "</select>n";
@@ -498,7 +536,7 @@
$submit_attributes['onclick'] = "return confirm('".esc_js(__('Are you sure you want to perform this bulk action?', 'all-in-one-wp-security-and-firewall'))."')";
}
- submit_button(__('Apply', 'all-in-one-wp-security-and-firewall'), 'action', '', false, $submit_attributes);
+ submit_button(esc_html__('Apply', 'all-in-one-wp-security-and-firewall'), 'action', '', false, $submit_attributes);
echo "n";
}
@@ -530,29 +568,41 @@
*
* @since 3.1.0
*
- * @param string[] $actions - An array of action links.
- * @param bool $always_visible - Whether the actions should be always visible.
- * @return string
+ * @param array[] $actions - An array of action properties to make into a link.
*/
- protected function row_actions($actions, $always_visible = false) {
+ protected function row_actions($actions) {
$action_count = count($actions);
$i = 0;
-
- if (!$action_count) {
- return '';
+
+ if (empty($action_count)) {
+ return;
}
-
- $out = '<div class="' . ($always_visible ? 'row-actions visible' : 'row-actions') . '">';
- foreach ($actions as $action => $link) {
+
+ echo '<div class="row-actions">';
+
+ foreach ($actions as $action => $data) {
++$i;
- ($i == $action_count) ? $sep = '' : $sep = ' | ';
- $out .= "<span class='$action'>$link$sep</span>";
- }
- $out .= '</div>';
-
- $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __('Show more details', 'all-in-one-wp-security-and-firewall') . '</span></button>';
-
- return $out;
+
+ if (!isset($data['attributes']['href'])) {
+ $data['attributes']['href'] = '#';
+ }
+
+ echo '<span class="'.esc_attr($action).'">';
+ echo '<a';
+ foreach ($data['attributes'] as $attribute => $value) {
+ if ('href' === $attribute) {
+ echo ' href="'.esc_url($value).'"';
+ } else {
+ echo ' '.esc_attr($attribute).'="'.esc_attr($value).'"';
+ }
+ }
+ $sep = ($i === $action_count) ? '' : ' | ';
+ echo '>'.esc_html($data['text']).'</a>'.esc_html($sep);
+ echo '</span>';
+ }
+
+ echo '</div>';
+ echo '<button type="button" class="toggle-row"><span class="screen-reader-text">'.esc_html__('Show more details', 'all-in-one-wp-security-and-firewall').'</span></button>';
}
/**
@@ -863,11 +913,11 @@
if ('bottom' === $which) {
$html_current_page = $current;
- $total_pages_before = '<span class="screen-reader-text">' . __('Current page', 'all-in-one-wp-security-and-firewall') . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
+ $total_pages_before = '<span class="screen-reader-text">' . esc_html__('Current page', 'all-in-one-wp-security-and-firewall') . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
} else {
$html_current_page = sprintf(
"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
- '<label for="current-page-selector" class="screen-reader-text">' . __('Current page', 'all-in-one-wp-security-and-firewall') . '</label>',
+ '<label for="current-page-selector" class="screen-reader-text">' . esc_html__('Current page', 'all-in-one-wp-security-and-firewall') . '</label>',
$current,
strlen($total_pages)
);
@@ -911,8 +961,7 @@
}
$this->_pagination = "<div class='tablenav-pages" . $page_class . "'>" . $output . "</div>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Necessary escaping done above.
- echo $this->_pagination;
+ echo wp_kses($this->_pagination, $this->allowed_html);
}
/**
@@ -1120,7 +1169,7 @@
if (!empty($columns['cb'])) {
static $cb_counter = 1;
- $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __('Select all', 'all-in-one-wp-security-and-firewall') . '</label>'
+ $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . esc_html__('Select all', 'all-in-one-wp-security-and-firewall') . '</label>'
. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
$cb_counter++;
}
@@ -1169,17 +1218,17 @@
. $sorting_indicators_html
. '</a>';
}
-
- $tag = ('cb' === $column_key) ? 'td' : 'th';
- $scope = ('th' === $tag) ? 'scope="col"' : '';
- $id = $with_id ? "id='$column_key'" : '';
-
- if (!empty($class)) {
- $class = "class='" . join(' ', $class) . "'";
- }
-
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped earlier in other functions.
- echo "<$tag $scope $id $class>$column_display_name</$tag>";
+
+ $tag = ('cb' === $column_key) ? 'td' : 'th';
+
+ printf(
+ '<%1$s %2$s %3$s class="%4$s">%5$s</%1$s>',
+ tag_escape($tag),
+ ('th' === $tag) ? 'scope="col"' : '',
+ $with_id ? 'id="'.esc_attr($column_key).'"' : '',
+ esc_attr(join(' ', $class)),
+ wp_kses($column_display_name, $this->allowed_html)
+ );
}
}
@@ -1316,15 +1365,22 @@
* @param array $item - Item object
* @param string $column_name - Column name to be rendered from item object
*/
- protected function column_default($item, $column_name) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore, PEAR.WhiteSpace.ScopeClosingBrace.Line -- this is a protected function
-
+ protected function column_default($item, $column_name) {
+ echo esc_html($item[$column_name]);
+ }
+
/**
* This function renders the checkbox column
*
* @param array $item - item object
*/
- protected function column_cb($item) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore, PEAR.WhiteSpace.ScopeClosingBrace.Line -- this is a protected function
-
+ protected function column_cb($item) {
+ printf(
+ '<input type="checkbox" name="%1$s[]" value="%2$s" />',
+ esc_attr($this->_args['singular']), //Let's simply repurpose the table's singular label
+ esc_attr($item['id']) //The value of the checkbox should be the record's id
+ );
+ }
/**
* Generates the columns for a single row of the table
@@ -1350,57 +1406,24 @@
// Comments column uses HTML in the display name with screen reader text.
// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
- $data = 'data-colname="' . wp_strip_all_tags($column_display_name) . '"';
+ $data_colname = wp_strip_all_tags($column_display_name);
- $attributes = "class='$classes' $data";
if ('cb' === $column_name) {
echo '<th scope="row" class="check-column">';
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->column_cb($item);
+ $this->column_cb($item);
echo '</th>';
- } elseif (method_exists($this, '_column_' . $column_name)) {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo call_user_func(
- array($this, '_column_' . $column_name),
- $item,
- $classes,
- $data,
- $primary
- );
} elseif (method_exists($this, 'column_' . $column_name)) {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo "<td $attributes>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo call_user_func(array($this, 'column_' . $column_name), $item);
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->handle_row_actions($item, $column_name, $primary);
+ echo '<td class="'.esc_attr($classes).'" data-colname="'.esc_attr($data_colname).'">';
+ call_user_func(array($this, 'column_' . $column_name), $item);
echo '</td>';
} else {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo "<td $attributes>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->column_default($item, $column_name);
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->handle_row_actions($item, $column_name, $primary);
+ echo '<td class="'.esc_attr($classes).'" data-colname="'.esc_attr($data_colname).'">';
+ $this->column_default($item, $column_name);
echo '</td>';
}
}
}
- /**
- * Generates and display row actions links for the list table.
- *
- * @since 4.3.0
- *
- * @param object $item - The item being acted upon.
- * @param string $column_name - Current column name.
- * @param string $primary - Primary column name.
- * @return string The row actions HTML, or an empty string if the current column is the primary column.
- */
- protected function handle_row_actions($item, $column_name, $primary) {
- return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __('Show more details', 'all-in-one-wp-security-and-firewall') . '</span></button>' : '';
- }
-
/**
* Handle an incoming ajax request (called from admin-ajax.php)
--- a/all-in-one-wp-security-and-firewall/admin/general/wp-security-list-table.php
+++ b/all-in-one-wp-security-and-firewall/admin/general/wp-security-list-table.php
@@ -119,6 +119,41 @@
);
/**
+ * Allowed HTML tags and attributes used when rendering pagination & header markup.
+ *
+ * @var array
+ */
+ protected $allowed_html = array(
+ 'a' => array(
+ 'class' => true,
+ 'id' => true,
+ 'href' => true,
+ ),
+ 'div' => array(
+ 'class' => true,
+ 'id' => true,
+ ),
+ 'span' => array(
+ 'class' => true,
+ 'id' => true,
+ 'aria-hidden' => true,
+ ),
+ 'label' => array(
+ 'class' => true,
+ 'for' => true,
+ ),
+ 'input' => array(
+ 'class' => true,
+ 'id' => true,
+ 'name' => true,
+ 'type' => true,
+ 'value' => true,
+ 'size' => true,
+ 'aria-describedby' => true,
+ ),
+ );
+
+ /**
* Constructor.
*
* The child class should call this constructor from its own constructor to override
@@ -153,6 +188,11 @@
)
);
+ // Workaround for WordPress bug that was fixed in 6.1: https://core.trac.wordpress.org/ticket/49089
+ if (empty($GLOBALS['hook_suffix'])) {
+ $GLOBALS['hook_suffix'] = '';
+ }
+
$this->screen = convert_to_screen($args['screen']);
add_filter("manage_{$this->screen->id}_columns", array($this, 'get_columns'), 0);
@@ -173,8 +213,8 @@
if (empty($this->modes)) {
$this->modes = array(
- 'list' => __('List view', 'all-in-one-wp-security-and-firewall'),
- 'excerpt' => __('Excerpt view', 'all-in-one-wp-security-and-firewall'),
+ 'list' => esc_html__('List view', 'all-in-one-wp-security-and-firewall'),
+ 'excerpt' => esc_html__('Excerpt view', 'all-in-one-wp-security-and-firewall'),
);
}
}
@@ -475,9 +515,8 @@
echo '<option value="-1">' . esc_html__('Bulk actions', 'all-in-one-wp-security-and-firewall') . "</option>n";
foreach ($this->_actions as $name => $title) {
- $class = 'edit' === $name ? ' class="hide-if-no-js"' : '';
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP error. Cannot escape $class with HTML inside.
- echo "t" . '<option value="' . esc_attr($name) . '"' . $class . '>' . esc_html($title) . "</option>n";
+ $class = 'edit' === $name ? 'hide-if-no-js' : '';
+ echo "t" . '<option value="' . esc_attr($name) . '" class="' . esc_attr($class) . '">' . esc_html($title) . "</option>n";
}
echo "</select>n";
@@ -488,7 +527,7 @@
$submit_attributes['onclick'] = "return confirm('".esc_js(__('Are you sure you want to perform this bulk action?', 'all-in-one-wp-security-and-firewall'))."')";
}
- submit_button(__('Apply', 'all-in-one-wp-security-and-firewall'), 'action', '', false, $submit_attributes);
+ submit_button(esc_html__('Apply', 'all-in-one-wp-security-and-firewall'), 'action', '', false, $submit_attributes);
echo "n";
}
@@ -522,29 +561,41 @@
*
* @since 3.1.0
*
- * @param string[] $actions An array of action links.
- * @param bool $always_visible Whether the actions should be always visible.
- * @return string
+ * @param array[] $actions - An array of action properties to make into a link.
*/
- protected function row_actions($actions, $always_visible = false) {
+ protected function row_actions($actions) {
$action_count = count($actions);
$i = 0;
-
- if (!$action_count) {
- return '';
+
+ if (empty($action_count)) {
+ return;
}
-
- $out = '<div class="' . ($always_visible ? 'row-actions visible' : 'row-actions') . '">';
- foreach ($actions as $action => $link) {
+
+ echo '<div class="row-actions">';
+
+ foreach ($actions as $action => $data) {
++$i;
- ($i == $action_count) ? $sep = '' : $sep = ' | ';
- $out .= "<span class='$action'>$link$sep</span>";
- }
- $out .= '</div>';
-
- $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __('Show more details', 'all-in-one-wp-security-and-firewall') . '</span></button>';
-
- return $out;
+
+ if (!isset($data['attributes']['href'])) {
+ $data['attributes']['href'] = '#';
+ }
+
+ echo '<span class="'.esc_attr($action).'">';
+ echo '<a';
+ foreach ($data['attributes'] as $attribute => $value) {
+ if ('href' === $attribute) {
+ echo ' href="'.esc_url($value).'"';
+ } else {
+ echo ' '.esc_attr($attribute).'="'.esc_attr($value).'"';
+ }
+ }
+ $sep = ($i === $action_count) ? '' : ' | ';
+ echo '>'.esc_html($data['text']).'</a>'.esc_html($sep);
+ echo '</span>';
+ }
+
+ echo '</div>';
+ echo '<button type="button" class="toggle-row"><span class="screen-reader-text">'.esc_html__('Show more details', 'all-in-one-wp-security-and-firewall').'</span></button>';
}
/**
@@ -873,11 +924,11 @@
if ('bottom' === $which) {
$html_current_page = $current;
- $total_pages_before = '<span class="screen-reader-text">' . __('Current page', 'all-in-one-wp-security-and-firewall') . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
+ $total_pages_before = '<span class="screen-reader-text">' . esc_html__('Current page', 'all-in-one-wp-security-and-firewall') . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
} else {
$html_current_page = sprintf(
"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
- '<label for="current-page-selector" class="screen-reader-text">' . __('Current page', 'all-in-one-wp-security-and-firewall') . '</label>',
+ '<label for="current-page-selector" class="screen-reader-text">' . esc_html__('Current page', 'all-in-one-wp-security-and-firewall') . '</label>',
$current,
strlen($total_pages)
);
@@ -923,8 +974,7 @@
}
$this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Necessary escaping done above.
- echo $this->_pagination;
+ echo wp_kses($this->_pagination, $this->allowed_html);
}
/**
@@ -1133,7 +1183,7 @@
if (! empty($columns['cb'])) {
static $cb_counter = 1;
- $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __('Select all', 'all-in-one-wp-security-and-firewall') . '</label>'
+ $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . esc_html__('Select all', 'all-in-one-wp-security-and-firewall') . '</label>'
. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
$cb_counter++;
}
@@ -1183,16 +1233,16 @@
. '</a>';
}
- $tag = ('cb' === $column_key) ? 'td' : 'th';
- $scope = ('th' === $tag) ? 'scope="col"' : '';
- $id = $with_id ? "id='$column_key'" : '';
-
- if (! empty($class)) {
- $class = "class='" . join(' ', $class) . "'";
- }
-
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped earlier in other functions.
- echo "<$tag $scope $id $class>$column_display_name</$tag>";
+ $tag = ('cb' === $column_key) ? 'td' : 'th';
+
+ printf(
+ '<%1$s %2$s %3$s class="%4$s">%5$s</%1$s>',
+ tag_escape($tag),
+ ('th' === $tag) ? 'scope="col"' : '',
+ $with_id ? 'id="'.esc_attr($column_key).'"' : '',
+ esc_attr(join(' ', $class)),
+ wp_kses($column_display_name, $this->allowed_html)
+ );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended -- No nonce.
}
@@ -1330,15 +1380,23 @@
* @param array $item - Item object
* @param string $column_name - Column name to be rendered from item object
*/
- protected function column_default($item, $column_name) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore, PEAR.WhiteSpace.ScopeClosingBrace.Line -- this is a protected function
-
+ protected function column_default($item, $column_name) {
+ echo esc_html($item[$column_name]);
+ }
+
/**
* This function renders the checkbox column
*
* @param array $item - item object
*/
- protected function column_cb($item) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore, PEAR.WhiteSpace.ScopeClosingBrace.Line -- this is a protected function
-
+ protected function column_cb($item) {
+ printf(
+ '<input type="checkbox" name="%1$s[]" value="%2$s" />',
+ esc_attr($this->_args['singular']), //Let's simply repurpose the table's singular label
+ esc_attr($item['id']) //The value of the checkbox should be the record's id
+ );
+ }
+
/**
* Generates the columns for a single row of the table
*
@@ -1361,57 +1419,24 @@
// Comments column uses HTML in the display name with screen reader text.
// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
- $data = 'data-colname="' . wp_strip_all_tags($column_display_name) . '"';
-
- $attributes = "class='$classes' $data";
+ $data_colname = wp_strip_all_tags($column_display_name);
if ('cb' === $column_name) {
echo '<th scope="row" class="check-column">';
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->column_cb($item);
+ $this->column_cb($item);
echo '</th>';
- } elseif (method_exists($this, '_column_' . $column_name)) {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo call_user_func(
- array($this, '_column_' . $column_name),
- $item,
- $classes,
- $data,
- $primary
- );
} elseif (method_exists($this, 'column_' . $column_name)) {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo "<td $attributes>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo call_user_func(array($this, 'column_' . $column_name), $item);
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->handle_row_actions($item, $column_name, $primary);
+ echo '<td class="'.esc_attr($classes).'" data-colname="'.esc_attr($data_colname).'">';
+ call_user_func(array($this, 'column_' . $column_name), $item);
echo '</td>';
} else {
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo "<td $attributes>";
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->column_default($item, $column_name);
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PCP Error. Escaped earlier in other functions.
- echo $this->handle_row_actions($item, $column_name, $primary);
+ echo '<td class="'.esc_attr($classes).'" data-colname="'.esc_attr($data_colname).'">';
+ $this->column_default($item, $column_name);
echo '</td>';
}
}
}
- /**
- * Generates and display row actions links for the list table.
- *
- * @since 4.3.0
- *
- * @param object $item The item being acted upon.
- * @param string $column_name Current column name.
- * @param string $primary Primary column name.
- * @return string The row actions HTML, or an empty string if the current column is the primary column.
- */
- protected function handle_row_actions($item, $column_name, $primary) {
- return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __('Show more details', 'all-in-one-wp-security-and-firewall') . '</span></button>' : '';
- }
/**
* Handle an incoming ajax request (called from admin-ajax.php)
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-admin-init.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-admin-init.php
@@ -340,7 +340,18 @@
wp_enqueue_script('chartjs-gauge', AIO_WP_SECURITY_URL . '/includes/chartjs/chartjs-gauge.min.js', array(), AIO_WP_SECURITY_VERSION, true);
wp_register_script('jquery-blockui', AIO_WP_SECURITY_URL.'/includes/blockui/jquery.blockUI.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
wp_enqueue_script('jquery-blockui');
- wp_register_script('aiowpsec-admin-js', AIO_WP_SECURITY_URL. '/js/wp-security-admin-script.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
+ wp_register_script('aiowpsec-heartbeat-js', AIO_WP_SECURITY_URL. '/js/heartbeat.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
+ wp_enqueue_script('aiowpsec-heartbeat-js');
+ wp_localize_script('aiowpsec-heartbeat-js',
+ 'aios_heartbeat_ajax',
+ array(
+ 'ajaxurl' => admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('heartbeat-nonce'),
+ 'aios_nonce' => wp_create_nonce(AIOWPSecurity_Heartbeat::NONCE_ACTION),
+ 'interval' => AIOWPSecurity_Ajax::HEARTBEAT_INTERVAL
+ )
+ );
+ wp_register_script('aiowpsec-admin-js', AIO_WP_SECURITY_URL. '/js/wp-security-admin-script.js', array('jquery', 'aiowpsec-heartbeat-js'), AIO_WP_SECURITY_VERSION, true);
wp_enqueue_script('aiowpsec-admin-js');
wp_localize_script('aiowpsec-admin-js',
'aios_data',
@@ -378,6 +389,12 @@
'downgrading_firewall' => __('Downgrading firewall...', 'all-in-one-wp-security-and-firewall'),
'maintenance_mode_enabled' => __('Maintenance mode is currently enabled.', 'all-in-one-wp-security-and-firewall') . ' ' . __('Remember to disable it when you are done.', 'all-in-one-wp-security-and-firewall'),
'maintenance_mode_disabled' => __('Maintenance mode is currently disabled.', 'all-in-one-wp-security-and-firewall'),
+ 'scan_now' => __('Scan Now', 'all-in-one-wp-security-and-firewall'),
+ 'scan_result_initial' => __('This is your first file change detection scan.', 'all-in-one-wp-security-and-firewall') . ' ' . __('The details from this scan will be used for future scans.', 'all-in-one-wp-security-and-firewall'),
+ 'scan_result_no_changes' => __('The scan is complete - There were no file changes detected.', 'all-in-one-wp-security-and-firewall'),
+ 'scan_result_changes' => __('The scan has detected that there was a change in your website's files.', 'all-in-one-wp-security-and-firewall'),
+ 'scan_result_view_link' => __('View the file scan results', 'all-in-one-wp-security-and-firewall'),
+ 'scan_result_view_last_link' => __('View last file scan results', 'all-in-one-wp-security-and-firewall'),
)
);
wp_register_script('aiowpsec-pw-tool-js', AIO_WP_SECURITY_URL. '/js/password-strength-tool.js', array('jquery', 'zxcvbn-async'), AIO_WP_SECURITY_VERSION, true); // We will enqueue this in the user acct menu class
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-admin-menu.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-admin-menu.php
@@ -43,7 +43,7 @@
protected function render_page($title) {
$current_tab = $this->get_current_tab();
?>
- <div class="wrap">
+ <div class="wrap" id="aios-wrap">
<h2><?php echo esc_html($title); ?></h2>
<?php $this->render_tabs($current_tab); ?>
<div id="poststuff">
@@ -183,21 +183,39 @@
/**
* Renders record(s) successfully deleted message at top of page.
*
- * @param bool $return_instead_of_echo - This is used for when the function needs to return the message
+ * @param bool $return_instead_of_echo - This is used for when the function needs to return the message.
+ * @param bool $only_text - Whether to only echo/return the text without div.
+ *
* @return mixed
*/
- public static function show_msg_record_deleted_st($return_instead_of_echo = false) {
- return AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected record(s) has been deleted successfully.', 'all-in-one-wp-security-and-firewall'), $return_instead_of_echo);
+ public static function show_msg_record_deleted_st($return_instead_of_echo = false, $only_text = false) {
+ $message = esc_html__('The selected record(s) has been deleted successfully.', 'all-in-one-wp-security-and-firewall');
+
+ if ($only_text) {
+ if ($return_instead_of_echo) return $message;
+ echo $message;
+ } else {
+ return AIOWPSecurity_Admin_Menu::show_msg_updated_st($message, $return_instead_of_echo);
+ }
}
/**
* Renders record(s) unsuccessfully deleted message at top of page.
*
- * @param bool $return_instead_of_echo - This is used for when the function needs to return the message
+ * @param bool $return_instead_of_echo - This is used for when the function needs to return the message.
+ * @param bool $only_text - Whether to only echo/return the text without div.
+ *
* @return mixed
*/
- public static function show_msg_record_not_deleted_st($return_instead_of_echo = false) {
- return AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall'), $return_instead_of_echo);
+ public static function show_msg_record_not_deleted_st($return_instead_of_echo = false, $only_text = false) {
+ $message = esc_html__('The selected record(s) have failed to delete.', 'all-in-one-wp-security-and-firewall');
+
+ if ($only_text) {
+ if ($return_instead_of_echo) return $message;
+ echo $message;
+ } else {
+ return AIOWPSecurity_Admin_Menu::show_msg_error_st($message, $return_instead_of_echo);
+ }
}
/**
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-brute-force-menu.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-brute-force-menu.php
@@ -51,7 +51,6 @@
'404-detection' => array(
'title' => __('404 detection', 'all-in-one-wp-security-and-firewall'),
'render_callback' => array($this, 'render_404_detection'),
- 'display_condition_callback' => array('AIOWPSecurity_Utility_Permissions', 'is_main_site_and_super_admin'),
),
'honeypot' => array(
'title' => __('Honeypot', 'all-in-one-wp-security-and-firewall'),
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-dashboard-menu.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-dashboard-menu.php
@@ -481,25 +481,56 @@
}
/**
+ * Gets last x days login details
+ *
+ * @global $wpdb
+ * @param int $days Number of days
+ *
+ * @return array
+ */
+ public static function get_login_data_lastx_days($days) {
+ global $wpdb;
+ $days_before_time = strtotime('-'.$days.' days', time());
+ $audit_log_table = AIOWPSEC_TBL_AUDIT_LOG;
+ if (is_super_admin()) {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $audit_log_table is a plugin-defined constant
+ $login_data_lastx_days = $wpdb->get_results($wpdb->prepare("SELECT id,created FROM $audit_log_table WHERE event_type = %s and created > %d", array('successful_login', $days_before_time)), ARRAY_A);
+ } else {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $audit_log_table is a plugin-defined constant
+ $login_data_lastx_days = $wpdb->get_results($wpdb->prepare("SELECT id,created FROM $audit_log_table WHERE event_type = %s and created > %d and site_id = %d", array('successful_login', $days_before_time, get_current_blog_id())), ARRAY_A);
+ }
+ return $login_data_lastx_days;
+ }
+
+ /**
+ * Gets last 5 login record details
+ *
+ * @global $wpdb
+ * @param int $records Number of records
+ *
+ * @return array
+ */
+ public static function get_login_lastx_records($records) {
+ global $wpdb;
+ $audit_log_table = AIOWPSEC_TBL_AUDIT_LOG;
+ if (is_super_admin()) {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $audit_log_table is a plugin-defined constant
+ $login_lastx_records = $wpdb->get_results($wpdb->prepare("SELECT * FROM $audit_log_table WHERE event_type = %s ORDER BY created DESC LIMIT %d", array('successful_login', $records)), ARRAY_A);
+ } else {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $audit_log_table is a plugin-defined constant
+ $login_lastx_records = $wpdb->get_results($wpdb->prepare("SELECT * FROM $audit_log_table WHERE event_type = %s and site_id = %d ORDER BY created DESC LIMIT %d", array('successful_login', get_current_blog_id(), $records)), ARRAY_A);
+ }
+ return $login_lastx_records;
+ }
+
+ /**
* This outputs the latest logins dashboard widget
*
* @return void
*/
public function widget_last_5_logins() {
- global $wpdb;
- $audit_log_table = AIOWPSEC_TBL_AUDIT_LOG;
- $where_sql = (is_super_admin()) ? '' : ' and site_id = '.get_current_blog_id().' ';
-
$last_days = 7;
- $days_before_time = strtotime('-'.$last_days.' days', time());
-
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query necessary.
- $login_data_lastx_days = $wpdb->get_results(
- // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- pcp Warning. Ignore.
- $wpdb->prepare("SELECT id,created FROM $audit_log_table WHERE event_type = %s $where_sql and created > %s", 'successful_login', $days_before_time),
- ARRAY_A
- ); // Get the last x days records
-
+ $login_data_lastx_days = self::get_login_data_lastx_days($last_days); // Get the last x days login records
if (!empty($login_data_lastx_days)) {
$chart_data = array();
$chart_data['columns'] = array(__('Date', 'all-in-one-wp-security-and-firewall'), __('Logins', 'all-in-one-wp-security-and-firewall'));
@@ -508,24 +539,18 @@
$chart_data['id'] = 'logins_last_'.$last_days.'days';
$this->dashboard_widget_chart($chart_data, 'bar');
}
-
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP Error. Ignore.
- $data = $wpdb->get_results(
- // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Ignore.
- $wpdb->prepare("SELECT * FROM $audit_log_table WHERE event_type = %s ORDER BY created DESC LIMIT %d", 'successful_login', 5),
- ARRAY_A
- ); //Get the last 5 records
-
- if (null == $data) {
+
+ $records = 5;
+ $login_lastx_records = self::get_login_lastx_records($records); //Get the last 5 login records
+ if (null == $login_lastx_records) {
echo '<p>' . esc_html__('No data found.', 'all-in-one-wp-security-and-firewall') . '</p>';
} else {
$login_summary_table_data = array();
- //$login_summary_table_data['title'] = __('Last 5 login summary:', 'all-in-one-wp-security-and-firewall');
$login_summary_table_data['columns'] = array(__('User', 'all-in-one-wp-security-and-firewall'), __('Date', 'all-in-one-wp-security-and-firewall'), 'IP');
- foreach ($data as $entry) {
+ foreach ($login_lastx_records as $entry) {
$login_summary_table_data['data'][] = array($entry['username'], gmdate('Y-m-d H:i:s', $entry['created']), $entry['ip']);
}
- $login_summary_table_data = apply_filters('aios_last5_logins_summary', $login_summary_table_data, $data);
+ $login_summary_table_data = apply_filters('aios_last5_logins_summary', $login_summary_table_data, $login_lastx_records);
$this->dashboard_widget($login_summary_table_data);
// View all login logs
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php
@@ -108,10 +108,10 @@
$perform_db_change = false;
if (isset($_POST['aiowps_db_prefix_change'])) { // Do form submission tasks
- $nonce = $_REQUEST['_wpnonce'];
+ $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])) : '';
if (!wp_verify_nonce($nonce, 'aiowpsec-db-prefix-change-nonce')) {
$aio_wp_security->debug_logger->log_debug("Nonce check failed for DB prefix change operation.", 4);
- die(__('Nonce check failed for DB prefix change operation.', 'all-in-one-wp-security-and-firewall'));
+ die(esc_html__('Nonce check failed for DB prefix change operation.', 'all-in-one-wp-security-and-firewall'));
}
// Let's first check if user's system allows writing to wp-config.php file. If plugin cannot write to wp-config we will not do the prefix change.
@@ -129,16 +129,16 @@
$this->show_msg_error(__('Please enter a value for the DB prefix.', 'all-in-one-wp-security-and-firewall'));
} else {
// User has chosen their own DB prefix value
- $new_db_prefix = wp_strip_all_tags(trim($_POST['aiowps_new_manual_db_prefix']));
+ $new_db_prefix = trim(sanitize_text_field(wp_unslash($_POST['aiowps_new_manual_db_prefix'])));
if ($new_db_prefix !== $_POST['aiowps_new_manual_db_prefix']) {
- wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".__('prefix contains HTML tags', 'all-in-one-wp-security-and-firewall'));
+ wp_die("<strong>".esc_html__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".esc_html__('prefix contains HTML tags', 'all-in-one-wp-security-and-firewall'));
}
if (preg_match('|[^a-z0-9_]|i', $new_db_prefix)) {
- wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".__('prefix contains invalid characters, the prefix should only contain alphanumeric and underscore characters.', 'all-in-one-wp-security-and-firewall'));
+ wp_die("<strong>".esc_html__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> ".esc_html__('prefix contains invalid characters, the prefix should only contain alphanumeric and underscore characters.', 'all-in-one-wp-security-and-firewall'));
}
$error = $wpdb->set_prefix($new_db_prefix); // validate the user chosen prefix
if (is_wp_error($error)) {
- wp_die("<strong>".__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> (".$error->get_error_code()."): ".$error->get_error_message());
+ wp_die("<strong>".esc_html__('Error:', 'all-in-one-wp-security-and-firewall')."</strong> (" . esc_html($error->get_error_code()) . "): " . esc_html($error->get_error_message()));
}
$wpdb->set_prefix($old_db_prefix);
$perform_db_change = true;
@@ -194,28 +194,29 @@
$config_file = AIOWPSecurity_Utility_File::get_wp_config_file_path();
// Get the table resource
- // $result = mysql_list_tables(DB_NAME);
- $result = $this->get_mysql_tables(DB_NAME); //Fix for deprecated php mysql_list_tables function
+ $result = $this->get_mysql_tables(DB_NAME);
// Count the number of tables
if (is_array($result) && count($result) > 0) {
$num_rows = count($result);
} else {
- echo '<div class="aio_red_box"><p>'.__('Error - Could not get tables or no tables found!', 'all-in-one-wp-security-and-firewall').'</p></div>';
+ echo '<div class="aio_red_box"><p>' . esc_html__('Error - Could not get tables or no tables found!', 'all-in-one-wp-security-and-firewall').'</p></div>';
return;
}
$table_count = 0;
- $info_msg_string = '<p class="aio_info_with_icon">'.__('Starting DB prefix change operations.....', 'all-in-one-wp-security-and-firewall').'</p>';
-
- $info_msg_string .= '<p class="aio_info_with_icon">'.sprintf(__('Your WordPress system has a total of %s tables and your new DB prefix will be: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$num_rows.'</strong>', '<strong>'.$table_new_prefix.'</strong>').'</p>';
+ $info_msg_string = '<p class="aio_info_with_icon">' . esc_html__('Starting DB prefix change operations.....', 'all-in-one-wp-security-and-firewall').'</p>';
+
+ /* translators: 1. Number of tables 2. New DB table prefix. */
+ $info_msg_string .= '<p class="aio_info_with_icon">' . sprintf(esc_html__('Your WordPress system has a total of %1$s tables and your new DB prefix will be: %2$s', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($num_rows) . '</strong>', '<strong>' . esc_html($table_new_prefix) . '</strong>') . '</p>';
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Variable escaped earlier.
echo $info_msg_string;
// Do a back of the config file
if (!AIOWPSecurity_Utility_File::backup_and_rename_wp_config($config_file)) {
- echo '<div class="aio_red_box"><p>'.__('Failed to make a backup of the wp-config.php file.', 'all-in-one-wp-security-and-firewall') . ' ' .__('This operation will not go ahead.', 'all-in-one-wp-security-and-firewall').'</p></div>';
+ echo '<div class="aio_red_box"><p>' . esc_html__('Failed to make a backup of the wp-config.php file.', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('This operation will not go ahead.', 'all-in-one-wp-security-and-firewall').'</p></div>';
return;
} else {
- echo '<p class="aio_success_with_icon">'.__('A backup copy of your wp-config.php file was created successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
+ echo '<p class="aio_success_with_icon">' . esc_html__('A backup copy of your wp-config.php file was created successfully!', 'all-in-one-wp-security-and-firewall') . '</p>';
}
// Get multisite blog_ids if applicable
@@ -230,17 +231,16 @@
if (strpos($table_old_name, $table_old_prefix) === 0) {
// Get table name with new prefix
- $table_new_name = AIOWPSecurity_Utility::backquote($table_new_prefix . substr($table_old_name, $old_prefix_length));
- $table_old_name = AIOWPSecurity_Utility::backquote($table_old_name);
-
- // Write query to rename tables name
- $sql = "RENAME TABLE ".$table_old_name." TO ".$table_new_name;
- // $sql = "RENAME TABLE %s TO %s";
-
- // Execute the query
- if (false === $wpdb->query($sql)) {
+ $raw_table_new_name = $table_new_prefix . substr($table_old_name, $old_prefix_length);
+ $table_new_name = AIOWPSecurity_Utility::quote_identifier($raw_table_new_name);
+ $table_old_name = AIOWPSecurity_Utility::quote_identifier($table_old_name);
+
+ // Write query to rename tables name and execute the query.
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- We can't use $wpdb->prepare with %i because our plugin supports WordPress < 6.2.
+ if (false === $wpdb->query("RENAME TABLE $table_old_name TO $table_new_name")) {
$error = 1;
- echo '<p class="aio_error_with_icon">'.sprintf(__('%s table name update failed', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_old_name.'</strong>').'</p>';
+ /* translators: %s: Old DB table name. */
+ echo '<p class="aio_error_with_icon">' . sprintf(esc_html__('%s table name update failed', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($table_old_name) . '</strong>') . '</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Unable to change prefix of table ".$table_old_name, 4);
} else {
$table_count++;
@@ -250,9 +250,11 @@
}
}
if (1 == $error) {
- echo '<p class="aio_error_with_icon">'.sprintf(__('Please change the prefix manually for the above tables to: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_new_prefix.'</strong>').'</p>';
+ /* translators: %s: New DB table name. */
+ echo '<p class="aio_error_with_icon">' . sprintf(esc_html__('Please change the prefix manually for the above tables to: %s', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($table_new_prefix) . '</strong>') . '</p>';
} else {
- echo '<p class="aio_success_with_icon">'.sprintf(__('%s tables had their prefix updated successfully!', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_count.'</strong>').'</p>';
+ /* translators: %s: DB table count. */
+ echo '<p class="aio_success_with_icon">' . sprintf(esc_html__('%s tables had their prefix updated successfully!', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($table_count) . '</strong>') . '</p>';
}
// Let's check for mysql tables of type "view"
@@ -272,20 +274,29 @@
}
// Now let's modify the wp-config.php file
if (AIOWPSecurity_Utility_File::write_content_to_file($config_file, $config_contents)) {
- echo '<p class="aio_success_with_icon">'. __('wp-config.php file was updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
+ echo '<p class="aio_success_with_icon">'. esc_html__('wp-config.php file was updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
} else {
- echo '<p class="aio_error_with_icon">'.sprintf(__('The "wp-config.php" file was not able to be modified.', 'all-in-one-wp-security-and-firewall').' '.__('Please modify this file manually using your favourite editor and search for variable "$table_prefix" and assign the following value to that variable: %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.$table_new_prefix.'</strong>').'</p>';
+ /* translators: %s: New DB table prefix. */
+ echo '<p class="aio_error_with_icon">'.sprintf(esc_html__('The "wp-config.php" file was not able to be modified.', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('Please modify this file manually using your favourite editor and search for variable "$table_prefix" and assign the following value to that variable: %s', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($table_new_prefix) . '</strong>') . '</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Unable to modify wp-config.php", 4);
}
-
- // Now let's update the options table
- $update_option_table_query = $wpdb->prepare("UPDATE " . $table_new_prefix . "options SET option_name = '".$table_new_prefix ."user_roles' WHERE option_name = %s LIMIT 1", $table_old_prefix."user_roles");
- if (false === $wpdb->query($update_option_table_query)) {
- echo '<p class="aio_error_with_icon">'.sprintf(__('Update of table %s failed: unable to change %s to %s', 'all-in-one-wp-security-and-firewall'), $table_new_prefix.'options', $table_old_prefix.'user_roles', $table_new_prefix.'user_roles').'</p>';
+ // Now let's update the options table
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be an alternative.
+ $update_option_table_result = $wpdb->update(
+ $table_new_prefix . 'options',
+ array('option_name' => $table_new_prefix . 'user_roles'),
+ array('option_name' => $table_old_prefix . 'user_roles'),
+ array('%s'),
+ array('%s')
+ );
+
+ if (false === $update_option_table_result) {
+ /* translators: 1. Options new prefix, 2. User roles old prefix, 3. User roles new prefix. */
+ echo '<p class="aio_error_with_icon">' . esc_html(sprintf(__('Update of table %1$s failed: unable to change %2$s to %3$s', 'all-in-one-wp-security-and-firewall'), $table_new_prefix . 'options', $table_old_prefix . 'user_roles', $table_new_prefix . 'user_roles')) . '</p>';
$aio_wp_security->debug_logger->log_debug("DB Security Feature - Error when updating the options table", 4);//Log the highly unlikely event of DB error
} else {
- echo '<p class="aio_success_with_icon">'. __('The options table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall') .'</p>';
+ echo '<p class="aio_success_with_icon">'. esc_html__('The options table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall') .'</p>';
}
// Now let's update the options tables for the multisite subsites if applicable
@@ -296,72 +307,78 @@
if ($blog_id == $main_site_id) continue;
$new_pref_and_site_id = $table_new_prefix.$blog_id.'_';
$old_pref_and_site_id = $table_old_prefix.$blog_id.'_';
- $update_ms_option_table_query = $wpdb->prepare("UPDATE " . $new_pref_and_site_id . "options SET option_name = '".$new_pref_and_site_id."user_roles' WHERE option_name = %s LIMIT 1", $old_pref_and_site_id."user_roles");
- if (false === $wpdb->query($update_ms_option_table_query)) {
- echo '<p class="aio_error_with_icon">'.sprintf(__('Update of table %s failed: unable to change %s to %s', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id.'options', $old_pref_and_site_id.'user_roles', $new_pref_and_site_id.'user_roles').'</p>';
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be an alternative.
+ $update_ms_option_table_result = $wpdb->update(
+ $new_pref_and_site_id . 'options',
+ array('option_name' => $new_pref_and_site_id . 'user_roles'),
+ array('option_name' => $old_pref_and_site_id . 'user_roles'),
+ array('%s'),
+ array('%s')
+ );
+
+ if (false === $update_ms_option_table_result) {
+ /* translators: 1. New options table name. 2. Old user roles table name, 3. New user roles table name. */
+ echo '<p class="aio_error_with_icon">' . esc_html(sprintf(__('Update of table %1$s failed: unable to change %2$s to %3$s', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id . 'options', $old_pref_and_site_id . 'user_roles', $new_pref_and_site_id . 'user_roles')) . '</p>';
$aio_wp_security->debug_logger->log_debug("DB change prefix feature - Error when updating the subsite options table: ".$new_pref_and_site_id.'options', 4);//Log the highly unlikely event of DB error
} else {
- echo '<p class="aio_success_with_icon">'.sprintf(__('The %s table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id.'options').'</p>';
+ /* translators: %s: New options table name. */
+ echo '<p class="aio_success_with_icon">' . esc_html(sprintf(__('The %s table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall'), $new_pref_and_site_id . 'options')) . '</p>';
}
}
}
}
-
- //Now let's update the user meta table
- $custom_sql = "SELECT user_id, meta_key FROM " . $table_new_prefix . "usermeta WHERE meta_key LIKE '" . $table_old_prefix . "%'";
-
- $meta_keys = $wpdb->get_results($custom_sql);
-
- $error_update_usermeta = '';
-
- // Update all meta_key field values which have the old table prefix in user_meta table
- foreach ($meta_keys as $meta_key) {
- // Create new meta key
- $new_meta_key = $table_new_prefix . substr($meta_key->meta_key, $old_prefix_length);
-
- $update_user_meta_sql = $wpdb->prepare("UPDATE " . $table_new_prefix . "usermeta SET meta_key='" . $new_meta_key . "' WHERE meta_key=%s AND user_id=%s", $meta_key->meta_key, $meta_key->user_id);
- if (false === $wpdb->query($update_user_meta_sql)) {
- $error_update_usermeta .= '<p class="aio_error_with_icon">'.sprintf(__('Error updating user_meta table where new meta_key = %s, old meta_key = %s and user_id = %s.', 'all-in-one-wp-security-and-firewall'), $new_meta_key, $meta_key->meta_key, $meta_key->user_id).'</p>';
- echo $error_update_usermeta;
- $aio_wp_security->debug_logger->log_debug("DB Security Feature - Error updating user_meta table where new meta_key = ".$new_meta_key." old meta_key = ".$meta_key->meta_key." and user_id = ".$meta_key->user_id, 4);//Log the highly unlikely event of DB error
- }
+ // Update all meta_key field values which have the old table prefix in the usermeta table.
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- There doesn't seem to be an alternative.
+ $update_usermeta_result = $wpdb->query(
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We can't use %i because our plugin supports WordPress < 6.2.
+ $wpdb->prepare("UPDATE `{$table_new_prefix}usermeta` SET meta_key = CONCAT(%s, SUBSTRING(meta_key, %d)) WHERE meta_key LIKE %s",
+ $table_new_prefix,
+ $old_prefix_length + 1,
+ $wpdb->esc_like($table_old_prefix) . '%'
+ )
+ );
+
+ if (false === $update_usermeta_result) {
+ echo '<p class="aio_error_with_icon">' . esc_html__('Error updating usermeta table.', 'all-in-one-wp-security-and-firewall') . '</p>';
+ $aio_wp_security->debug_logger->log_debug('DB Security Feature - Error updating usermeta table.', 4);
+ } else {
+ echo '<p class="aio_success_with_icon">' . esc_html__('The usermeta table records which had references to the old DB prefix were updated successfully.', 'all-in-one-wp-security-and-firewall') . '</p>';
}
- echo '<p class="aio_success_with_icon">'.__('The usermeta table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
+
// Display tasks finished message
- $tasks_finished_msg_string = '<p class="aio_info_with_icon">'. __('The database prefix change tasks have been completed.', 'all-in-one-wp-security-and-firewall').'</p>';
- echo $tasks_finished_msg_string;
+ echo '<p class="aio_info_with_icon">' . esc_html__('The database prefix change tasks have been completed.', 'all-in-one-wp-security-and-firewall') . '</p>';
}
/**
- * This is an alternative to the deprecated "mysql_list_tables
+ * Gets all of the tables of a given database.
*
- * @param string $database - database name
+ * @param string $database
*
- * @returns array - an array of table names
+ * @return array|bool
*/
- public function get_mysql_tables($database = '') {
- global $aio_wp_security;
- $tables = array();
- $list_tables_sql = "SHOW TABLES FROM `{$database}`;";
- $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
-
- if ($mysqli->connect_errno) {
- $aio_wp_security->debug_logger->log_debug("AIOWPSecurity_Database_Menu->get_mysql_tables() - DB connection error.", 4);
- return false;
+ private function get_mysql_tables($database = '') {
+ global $wpdb, $aio_wp_security;
+
+ if (empty($database)) {
+ $list_tables_sql = "SHOW TABLES";
+ } else {
+ $safe_database = AIOWPSecurity_Utility::quote_identifier($database);
+ $list_tables_sql = "SHOW TABLES FROM $safe_database";
}
- $result = $mysqli->query($list_tables_sql, MYSQLI_USE_RESULT);
- if ($result) {
- //Alternative way to get the tables
- while ($row = $result->fetch_assoc()) {
- foreach ($row as $value) {
- $tables[] = $value;
- }
- }
- $result->close();
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- We can't use $wpdb->prepare with %i because our plugin supports WordPress < 6.2.
+ $tables = $wpdb->get_col($list_tables_sql);
+
+ if (empty($tables)) {
+ $aio_wp_security->debug_logger->log_debug(
+ "AIOWPSecurity_Database_Menu::get_mysql_tables() - Failed to retrieve tables for database: " . ($database ?: 'current'),
+ 4
+ );
+ return false;
}
- $mysqli->close();
+
return $tables;
}
@@ -376,31 +393,38 @@
private function alter_table_views($old_db_prefix, $new_db_prefix) {
global $wpdb, $aio_wp_security;
$db_name = $wpdb->dbname;
- $info_msg_string = '<p class="aio_info_with_icon">'.__('Checking for MySQL tables of type "view".....', 'all-in-one-wp-security-and-firewall').'</p>';
- echo $info_msg_string;
-
+ echo '<p class="aio_info_with_icon">' . esc_html__('Checking for MySQL tables of type "view".....', 'all-in-one-wp-security-and-firewall') . '</p>';
+
// get tables which are views
- $query = "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA LIKE '".$db_name."'";
- $res = $wpdb->get_results($query);
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be a better alternative than $wpdb->get_results.
+ $res = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = %s",
+ $db_name
+ )
+ );
if (empty($res)) return;
$view_count = 0;
foreach ($res as $item) {
$old_def = $item->VIEW_DEFINITION;
$new_def = AIOWPSecurity_Utility::str_replace_once($old_db_prefix, $new_db_prefix, $old_def);
- $new_def = AIOWPSecurity_Utility::backquote($new_def);
+ $new_def = AIOWPSecurity_Utility::quote_identifier($new_def);
+
+ $view_name = AIOWPSecurity_Utility::quote_identifier($item->TABLE_NAME);
- $view_name = AIOWPSecurity_Utility::backquote($item->TABLE_NAME);
- $chg_view_sql = "ALTER VIEW $view_name AS $new_def";
- $view_res = $wpdb->query($chg_view_sql);
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.SchemaChange -- ALTER VIEW requires a raw SQL fragment for the view definition which cannot be parameterized with $wpdb->prepare. We have to do a schema change for this feature.
+ $view_res = $wpdb->query("ALTER VIEW $view_name AS $new_def");
if (false === $view_res) {
- echo '<p class="aio_error_with_icon">'.sprintf(__('Update of the following MySQL view definition failed: %s', 'all-in-one-wp-security-and-firewall'), $old_def).'</p>';
+ /* translators: %s: Old definition. */
+ echo '<p class="aio_error_with_icon">' . esc_html(sprintf(__('Update of the following MySQL view definition failed: %s', 'all-in-one-wp-security-and-firewall'), $old_def)) . '</p>';
$aio_wp_security->debug_logger->log_debug("Update of the following MySQL view definition failed: ".$old_def, 4);//Log the highly unlikely event of DB error
} else {
$view_count++;
}
}
if ($view_count > 0) {
- echo '<p class="aio_success_with_icon">'.sprintf(__('%s view definitions were updated successfully.', 'all-in-one-wp-security-and-firewall'), '<strong>'.$view_count.'</strong>').'</p>';
+ /* translators: %s: View count. */
+ echo '<p class="aio_success_with_icon">' . sprintf(esc_html__('%s view definitions were updated successfully.', 'all-in-one-wp-security-and-firewall'), '<strong>' . esc_html($view_count) . '</strong>') . '</p>';
}
return;
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-filescan-menu.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-filescan-menu.php
@@ -55,9 +55,7 @@
protected function render_file_change_detect() {
global $aio_wp_security;
- $aios_commands = new AIOWPSecurity_Commands();
-
- $scanner_data = $aios_commands->get_scanner_data();
+ $scanner_data = AIOWPSecurity_Utility_File::get_scanner_data();
$aio_wp_security->include_template('wp-admin/scanner/file-change-detect.php', false, $scanner_data);
}
--- a/all-in-one-wp-security-and-firewall/admin/wp-security-list-404.php
+++ b/all-in-one-wp-security-and-firewall/admin/wp-security-list-404.php
@@ -4,108 +4,134 @@
exit; // Exit if accessed directly
}
-class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
+class AIOWPSecurity_List_404 extends AIOWPSecurity_Ajax_Data_Table {
- public function __construct() {
+ public function __construct($data = array()) {
//Set parent defaults
parent::__construct(array(
'singular' => 'item', //singular name of the listed records
- 'plural' => 'items', //plural name of the listed records
- 'ajax' => false //does this table support ajax?
+ 'plural' => 'items', //plural name of the listed records
+ 'ajax' => true, //does this table support ajax?
+ 'data' => $data // Request data
));
}
/**
- * Returns created column in datetime format as per user setting time zone.
+ * Renders created column in datetime format as per user setting time zone.
*
* @param array $item - data for the columns on the current row
*
- * @return string - the datetime
+ * @return void
*/
public function column_created($item) {
- return AIOWPSecurity_Utility::convert_timestamp($item['created']);
+ echo esc_html(AIOWPSecurity_Utility::convert_timestamp($item['created']));
}
- public function column_default($item, $column_name) {