PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
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 3.1.1 All 140 releases
defender-security / framework / base / class-component.php

class-component.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at framework/base/class-component.php

310 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base component class.
4 *
5 * @package Calotes\Base
6 */
7
8 namespace Calotes\Base;
9
10 use Exception;
11 use ReflectionClass;
12 use ReflectionMethod;
13 use ReflectionProperty;
14 use Calotes\Component\Behavior;
15 use WP_Filesystem_Base;
16
17 /**
18 * Base class for all components.
19 */
20 class Component {
21 /**
22 * Contains array of behaviors.
23 *
24 * @var array
25 */
26 protected $behaviors = array();
27
28 /**
29 * Cache the annotations of properties.
30 *
31 * @var array
32 */
33 public $annotations = array();
34
35 /**
36 * Store internal logging, mostly for debug.
37 *
38 * @var array
39 */
40 protected $internal_logging = array();
41
42 /**
43 * Attach a behavior to current class, a behavior is a mixins, which useful in case of pro/free version.
44 *
45 * @param string $name The name of the behavior.
46 * @param Behavior|string $behavior The behavior to attach.
47 */
48 public function attach_behavior( string $name, $behavior ): void {
49 // Make a fast init.
50 if ( ! $behavior instanceof Behavior ) {
51 $behavior = new $behavior();
52 }
53 $behavior->owner = $this;
54 $this->behaviors[ $name ] = $behavior;
55 }
56
57 /**
58 * Check if the object has a specific property.
59 *
60 * @param mixed $property The name of the property to check.
61 *
62 * @return bool
63 */
64 public function has_property( $property ): bool {
65 $ref = new ReflectionClass( $this );
66
67 return $ref->hasProperty( $property );
68 }
69
70 /**
71 * Check if the object has a specific method.
72 *
73 * @param mixed $method The name of the method to check.
74 *
75 * @return bool
76 */
77 public function has_method( $method ): bool {
78 $ref_class = new ReflectionClass( $this );
79 if ( $ref_class->hasMethod( $method ) ) {
80 return true;
81 }
82
83 foreach ( $this->behaviors as $behavior ) {
84 $ref_class = new ReflectionClass( $behavior );
85 if ( $ref_class->hasMethod( $method ) ) {
86 return true;
87 }
88 }
89
90 return false;
91 }
92
93 /**
94 * Get the value of a property.
95 *
96 * @param mixed $name The name of the property to get.
97 *
98 * @return mixed The value of the property.
99 * @throws Exception If the property is not found.
100 */
101 public function __get( $name ) {
102 // Priority to current class properties.
103 if ( $this->has_property( $name ) ) {
104 return $this->$name;
105 }
106 // Check if behaviors already have.
107 foreach ( $this->behaviors as $behavior ) {
108 $ref_class = new ReflectionClass( $behavior );
109
110 if ( $ref_class->hasProperty( $name ) ) {
111 return $ref_class->getProperty( $name )->getValue( $behavior );
112 }
113 }
114
115 throw new Exception( sprintf( 'Getting unknown property: %s::%s', esc_attr( get_class( $this ) ), esc_attr( $name ) ) );
116 }
117
118 /**
119 * Handles dynamic method calls on the object.
120 *
121 * @param mixed $name The name of the method to call.
122 * @param mixed $arguments The arguments to pass to the method.
123 *
124 * @return mixed The result of the method call.
125 * @throws Exception If the method is not found.
126 */
127 public function __call( $name, $arguments ) {
128 $ref_class = new ReflectionClass( $this );
129 if ( $ref_class->hasMethod( $name ) ) {
130 $ref_method = new ReflectionMethod( $this, $name );
131
132 return $ref_method->invokeArgs( $this, $arguments );
133 }
134 foreach ( $this->behaviors as $behavior ) {
135 $ref_class = new ReflectionClass( $behavior );
136 if ( $ref_class->hasMethod( $name ) ) {
137 $ref_method = new ReflectionMethod( $behavior, $name );
138
139 return $ref_method->invokeArgs( $behavior, $arguments );
140 }
141 }
142
143 throw new Exception( sprintf( 'Getting unknown property: %s::%s', esc_attr( get_class( $this ) ), esc_attr( $name ) ) );
144 }
145
146 /**
147 * Sets the value of a property.
148 *
149 * Do not call this directly, magic method for assign value to property.
150 * If property is not exist for this component, we will check its behavior.
151 *
152 * @param mixed $name The name of the property to set.
153 * @param mixed $value The value to set.
154 *
155 * @throws Exception If the property is not found.
156 */
157 public function __set( $name, $value ) {
158 $ref_class = new ReflectionClass( $this );
159 if ( $ref_class->hasProperty( $name ) ) {
160 $ref_class->getProperty( $name )->setValue( $value );
161
162 return;
163 }
164
165 foreach ( $this->behaviors as $behavior ) {
166 $ref_class = new ReflectionClass( $behavior );
167 if ( $ref_class->hasProperty( $name ) ) {
168 $ref_class->getProperty( $name )->setValue( $behavior, $value );
169
170 return;
171 }
172 }
173
174 throw new Exception( sprintf( 'Setting unknown property: %s::%s', esc_attr( get_class( $this ) ), esc_attr( $name ) ) );
175 }
176
177 /**
178 * It iterates over the properties of the class and checks if the property has a docblock containing the
179 * "@defender_property" tag. If the property has the tag, it extracts the type, sanitize, and rule information from
180 * the docblock and stores it in the "annotations" array.
181 *
182 * The list should be
183 * - type: for casting,
184 * - sanitize_*: the list of sanitize_ functions, which should be run on this property,
185 * - rule: the rule that we use for validation.
186 */
187 protected function parse_annotations() {
188 $class = new ReflectionClass( static::class );
189 $properties = $class->getProperties( ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED );
190 foreach ( $properties as $property ) {
191 $doc_block = $property->getDocComment();
192 if ( ! stristr( $doc_block, '@defender_property' ) ) {
193 continue;
194 }
195 $this->annotations[ $property->getName() ] = array(
196 'type' => $this->parse_annotations_var( $doc_block ),
197 'sanitize' => $this->parse_annotation_sanitize( $doc_block ),
198 'rule' => $this->parse_annotation_rule( $doc_block ),
199 );
200 }
201 }
202
203 /**
204 * Parses the variable type from a docblock.
205 *
206 * @param mixed $docblock The docblock to parse.
207 *
208 * @return string|bool The variable type if found, false otherwise.
209 */
210 private function parse_annotations_var( $docblock ) {
211 $pattern = '/@var\s(.+)/';
212 if ( preg_match( $pattern, $docblock, $matches ) ) {
213 $type = trim( $matches[1] );
214
215 // Only allow right type.
216 if ( in_array(
217 $type,
218 array( 'boolean', 'bool', 'integer', 'int', 'float', 'double', 'string', 'array', 'object' ),
219 true
220 ) ) {
221 return $type;
222 }
223 }
224
225 return false;
226 }
227
228 /**
229 * Get the sanitize function.
230 *
231 * @param mixed $docblock The docblock to parse.
232 *
233 * @return bool|string
234 */
235 private function parse_annotation_sanitize( $docblock ) {
236 $pattern = '/@(sanitize_.+)/';
237 if ( preg_match( $pattern, $docblock, $matches ) ) {
238 return trim( $matches[1] );
239 }
240
241 return false;
242 }
243
244 /**
245 * Get the validation rule.
246 *
247 * @param mixed $docblock The docblock to parse.
248 *
249 * @return bool|string
250 */
251 private function parse_annotation_rule( $docblock ) {
252 $pattern = '/@(rule_.+)/';
253 if ( preg_match( $pattern, $docblock, $matches ) ) {
254 return trim( $matches[1] );
255 }
256
257 return false;
258 }
259
260 /**
261 * Logs a message with an optional category.
262 *
263 * @param mixed $message The message to log. If it is not a string, array, or object, it will be converted to a
264 * string using print_r().
265 * @param string $category Optional. The category of the log. If provided, the log will be saved to a file with the
266 * category as the filename. If not provided, the log will not be saved to a file.
267 *
268 * @return void
269 */
270 protected function log( $message, $category = '' ): void {
271 global $wp_filesystem;
272 // Initialize the WP filesystem, no more using 'file-put-contents' function.
273 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
274 require_once ABSPATH . '/wp-admin/includes/file.php';
275 WP_Filesystem();
276 }
277 if ( ! is_string( $message ) || is_array( $message ) || is_object( $message ) ) {
278 $message = wp_json_encode( $message, JSON_PRETTY_PRINT );
279 }
280
281 $this->internal_logging[] = wp_date( 'Y-m-d H:i:s' ) . ' ' . $message;
282 /**
283 * Uncomment it for detailed logging on wp cli.
284 * if ( 'cli' === PHP_SAPI ) {
285 * echo $message . PHP_EOL;
286 * }
287 */
288 $message = '[' . wp_date( 'c' ) . '] ' . $message . PHP_EOL;
289
290 if ( $this->has_method( 'get_log_path' ) ) {
291 if ( '' !== $category && 0 === preg_match( '/\.log$/', $category ) ) {
292 $category .= '.log';
293 }
294
295 $file_path = $this->get_log_path( $category );
296 $dir_name = pathinfo( $file_path, PATHINFO_DIRNAME );
297
298 if ( $wp_filesystem->exists( $file_path ) ) {
299 $message = $wp_filesystem->get_contents( $file_path ) . $message;
300 } elseif ( ! is_dir( $dir_name ) ) {
301 wp_mkdir_p( $dir_name );
302 }
303
304 if ( $wp_filesystem->is_writable( $dir_name ) ) {
305 $wp_filesystem->put_contents( $file_path, $message );
306 }
307 }
308 }
309 }
310