PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / behavior / scan-item / theme-integrity.php

theme-integrity.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/behavior/scan-item/theme-integrity.php

226 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Behavior\Scan_Item;
4
5 use Calotes\Base\File;
6 use Calotes\Component\Behavior;
7 use WP_Defender\Component\Error_Code;
8 use WP_Defender\Model\Scan;
9 use WP_Defender\Model\Scan_Item;
10 use WP_Defender\Traits\Formats;
11 use WP_Defender\Traits\IO;
12 use WP_Error;
13
14 /**
15 * @since 2.5.0 Leave for migration from old versions to 2.5.0
16 */
17 class Theme_Integrity extends Behavior {
18 use Formats, IO;
19
20 public const URL_THEME_VCS = 'https://themes.svn.wordpress.org/';
21
22 /**
23 * Return general data so we can output on frontend.
24 *
25 * @return array
26 */
27 public function to_array() {
28 $data = $this->owner->raw_data;
29 $file = $data['file'];
30 $file_created_at = @filemtime( $file );
31 if ( $file_created_at ) {
32 $file_created_at = $this->format_date_time( $file_created_at );
33 } else {
34 $file_created_at = 'n/a';
35 }
36 $file_size = @filesize( $file );
37 if ( ! $file_size ) {
38 $file_size = 'n/a';
39 } else {
40 $file_size = $this->format_bytes_into_readable( $file_size );
41 }
42
43 return array(
44 'id' => $this->owner->id,
45 'type' => Scan_Item::TYPE_THEME_CHECK,
46 'file_name' => pathinfo( $file, PATHINFO_BASENAME ),
47 'full_path' => $file,
48 'date_added' => $file_created_at,
49 'size' => $file_size,
50 'scenario' => $data['type'],
51 'short_desc' => $this->get_short_description(),
52 );
53 }
54
55 /**
56 * We will get the origin code by looking into svn repo.
57 *
58 * @return false|string|\WP_Error
59 */
60 private function get_origin_code() {
61 $data = $this->owner->raw_data;
62 $file = wp_normalize_path( $data['file'] );
63 $ds = DIRECTORY_SEPARATOR;
64 $theme_dir = WP_CONTENT_DIR . $ds . 'themes';
65 $relative_path = str_replace( wp_normalize_path( $theme_dir ) . $ds, '', $file );
66 $path_data = explode( $ds, $relative_path, 2 );
67 if ( ! empty( $path_data ) ) {
68 $theme_slug = $path_data[0];
69 $file_path = $path_data[1];
70 } else {
71 return new WP_Error( 'defender_broken_file_path', __( 'Broken file path.', 'wpdef' ) );
72 }
73 if ( ! function_exists( 'wp_get_themes' ) ) {
74 require_once( ABSPATH . '/wp-includes/theme.php' );
75 }
76 $theme = wp_get_theme( $theme_slug );
77
78 // Get original from wp.org e.g. https://themes.svn.wordpress.org/twentytwenty/1.6/functions.php.
79 $source_file_url = self::URL_THEME_VCS . $theme_slug . $ds . $theme->get( 'Version' ) . $ds . $file_path;
80 if ( ! function_exists( 'download_url' ) ) {
81 require_once ABSPATH . 'wp-admin' . $ds . 'includes' . $ds . 'file.php';
82 }
83 $tmp = download_url( $source_file_url );
84 if ( is_wp_error( $tmp ) ) {
85 return $tmp;
86 }
87 $content = file_get_contents( $tmp );
88 @unlink( $tmp );
89
90 return $content;
91 }
92
93 /**
94 * Restore the file with its origin content.
95 *
96 * @return void|array
97 */
98 public function resolve() {
99 $data = $this->owner->raw_data;
100 if ( 'modified' !== $data['type'] ) {
101 // Should not be here unless case changed.
102 return;
103 }
104
105 $origin = $this->get_origin_code();
106 if ( false === $origin || is_wp_error( $origin ) ) {
107 return;
108 }
109
110 $path = $data['file'];
111 $ret = @file_put_contents( $path, $origin );// phpcs:ignore
112 if ( $ret ) {
113 $scan = Scan::get_last();
114 $scan->remove_issue( $this->owner->id );
115 $this->log( sprintf( '%s is deleted', $path ), 'scan.log' );
116
117 return array(
118 'message' => __( 'This item has been resolved.', 'wpdef' ),
119 );
120 } else {
121 return new WP_Error(
122 'defender_permissions_denied',
123 __( 'Permissions Denied. Defender does not have the needed permissions to edit the file. Please change file permissions to 640 or contact your hosting provider so they could change them for you.', 'wpdef' )
124 );
125 }
126 }
127
128 /**
129 * @return array
130 */
131 public function ignore() {
132 $scan = Scan::get_last();
133 $scan->ignore_issue( $this->owner->id );
134
135 return array(
136 'message' => __( 'The suspicious file has been successfully ignored.', 'wpdef' ),
137 );
138 }
139
140 /**
141 * @return array
142 */
143 public function unignore() {
144 $scan = Scan::get_last();
145 $scan->unignore_issue( $this->owner->id );
146
147 return array(
148 'message' => __( 'The suspicious file has been successfully restored.', 'wpdef' ),
149 );
150 }
151
152 /**
153 * Delete the file or whole folder.
154 *
155 * @return array|\WP_Error
156 */
157 public function delete() {
158 $data = $this->owner->raw_data;
159 $scan = Scan::get_last();
160 if ( 'unversion' === $data['type'] && @unlink( $data['file'] ) ) {
161 $scan->remove_issue( $this->owner->id );
162 $this->log( sprintf( '%s is deleted', $data['file'] ), 'scan.log' );
163
164 return array(
165 'message' => __( 'This item has been permanently removed', 'wpdef' ),
166 );
167 } elseif ( 'dir' === $data['type'] && $this->delete_dir( $data['file'] ) ) {
168 $scan->remove_issue( $this->owner->id );
169 $this->log( sprintf( '%s is deleted', $data['file'] ), 'scan.log' );
170
171 return array(
172 'message' => __( 'This item has been permanently removed', 'wpdef' ),
173 );
174 }
175
176 return new \WP_Error( Error_Code::NOT_WRITEABLE, __( 'Defender doesn\'t have enough permission to remove this file', 'wpdef' ) );
177 }
178
179 /**
180 * Return the source code depending on the type of the issue.
181 *
182 * @return array
183 */
184 public function pull_src() {
185 $data = $this->owner->raw_data;
186 if ( ! file_exists( $data['file'] ) && ! is_dir( $data['file'] ) ) {
187 return array(
188 'code' => '',
189 'origin' => '',
190 );
191 }
192 switch ( $data['type'] ) {
193 case 'unversion':
194 return array(
195 'code' => file_get_contents( $data['file'] ),
196 );
197 case 'dir':
198 $dir_tree = new File( $data['file'], true, true, array(), array(), false );
199
200 return array(
201 'code' => implode( PHP_EOL, $dir_tree->get_dir_tree() ),
202 );
203 case 'modified':
204 default:
205 return array(
206 'code' => file_get_contents( $data['file'] ),
207 'origin' => $this->get_origin_code(),
208 );
209 }
210 }
211
212 /**
213 * @return string
214 */
215 private function get_short_description() {
216 $data = $this->owner->raw_data;
217 if ( 'unversion' === $data['type'] ) {
218 return esc_html__( 'Unknown file in the WordPress theme', 'wpdef' );
219 } elseif ( 'dir' === $data['type'] ) {
220 return esc_html__( 'This directory does not belong to the WordPress theme', 'wpdef' );
221 } elseif ( 'modified' === $data['type'] ) {
222 return esc_html__( 'This theme file appears modified', 'wpdef' );
223 }
224 }
225 }
226