Server IP : 104.21.14.48 / Your IP : 3.147.51.16 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/wp-content/plugins/defender-security/src/component/audit/ |
Upload File : |
<?php /** * Abstract class used for handling audit events in the WP_Defender plugin. * * @package WP_Defender\Component\Audit */ namespace WP_Defender\Component\Audit; use Countable; use ReflectionMethod; use ReflectionException; use WP_Defender\Component; use WP_Defender\Traits\IP; use WP_Defender\Traits\User; use Calotes\Helper\Array_Cache; use WP_Defender\Model\Setting\Audit_Logging; /** * Base class for all audit events. */ abstract class Audit_Event extends Component { use User; use IP; public const ACTION_DELETED = 'deleted', ACTION_TRASHED = 'trashed', ACTION_RESTORED = 'restored', ACTION_UPDATED = 'updated'; /** * Return an array of hooks. * * @return mixed */ abstract public function get_hooks(); /** * Loop through the arguments array and process each argument based on its type. * * @param array $args The array of arguments to process. * @param array $params The parameters array to update with processed arguments. * @param mixed $leftover Additional leftover parameter. */ private static function get_custom_args( $args, &$params, &$leftover ) { foreach ( $args as $key => $arg ) { if ( is_string( $arg ) && preg_match( '/{{.*}}/', $arg ) ) { $search = str_replace( array( '{{', '}}' ), '', $arg ); $found = self::recursive_look_array( $params, $search ); if ( false !== $found ) { $params[ $key ] = $found; } } else { $params[ $key ] = $arg; } } } /** * Loop through the arguments array and process each argument based on its type. * * @param mixed $args The array of arguments to process. * @param array $params The parameters array to update with processed arguments. * @param mixed $leftover Additional leftover parameter. * * @throws ReflectionException If the class or method does not exist. */ private static function get_program_args( $args, &$params, &$leftover ) { foreach ( $args as $key => $arg ) { // Loop through each. $func_args = array(); if ( isset( $arg['params'] ) && is_array( $arg['params'] ) ) { foreach ( $arg['params'] as $value ) { if ( is_string( $value ) && preg_match( '/{{.*}}/', $value ) ) { $search = str_replace( array( '{{', '}}' ), '', $value ); $found = self::recursive_look_array( $params, $search ); if ( false !== $found ) { $func_args[] = $found; } } else { $func_args[] = $value; } } } // Call the function. if ( ! is_array( $arg['callable'] ) ) { $ret = call_user_func_array( $arg['callable'], $func_args ); } else { $reflection_method = new ReflectionMethod( $arg['callable'][0], $arg['callable'][1] ); $ret = $reflection_method->invokeArgs( new $arg['callable'][0](), $func_args ); } if ( isset( $arg['result_property'] ) ) { $ret = self::recursive_look( $ret, explode( '->', $arg['result_property'] ) ); } $params[ $key ] = $ret; } } /** * Recursively looks up a value within a nested array or object based on provided links. * * @param mixed $obj The object or array to access nested properties or array elements. * @param array $links The array of links to access nested properties or array elements of the $obj. * * @return mixed The final value of $obj if the iteration is successful, false otherwise. */ private static function recursive_look( $obj, $links ) { $look = null; $count = is_array( $links ) || $links instanceof Countable ? count( $links ) : 0; while ( $count ) { $link = array_shift( $links ); if ( is_array( $obj ) ) { $obj = $obj[ $link ]; } elseif ( is_object( $obj ) ) { $obj = $obj->$link; } else { $look = false; break; } $count = is_array( $links ) || $links instanceof Countable ? count( $links ) : 0; } if ( false === $look ) { return false; } return $obj; } /** * Performs a recursive lookup in an array based on a given set of links. * * @param array $data The array to perform the lookup on. * @param string $links The links to follow in the array, separated by '->'. * * @return mixed The value found at the end of the links, or null if not found. */ private static function recursive_look_array( $data, $links ) { $links = explode( '->', $links ); $obj = $data[ $links[0] ]; unset( $links[0] ); $obj = self::recursive_look( $obj, $links ); return $obj; } /** * This is a private method that is used to get the text based on the provided parameters. * In many case, text can be condition, example an array contain text 1, text 2, text 3, * if 2 matched, we get the first. * * @param mixed $text The text to be processed. It can be either a string or an array. * @param array $params The parameters used to replace placeholders in the text. * * @return mixed The processed text or false if no match is found. */ private function get_text( $text, $params ) { if ( ! is_array( $text ) ) { return $text; } $matched = array(); foreach ( $text as $row ) { $t = $row[0]; // This will be inside parameters. $value_placeholder = str_replace( array( '{{', '}}' ), '', $row[1] ); // No need to validate here, all should be passed by hardcode. $value = self::recursive_look_array( $params, $value_placeholder ); $compare = $row[2]; if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) { $search = str_replace( array( '{{', '}}' ), '', $compare ); $compare = self::recursive_look_array( $params, $search ); } $comparison = $row[3] ?? '=='; if ( is_array( $value ) && is_array( $compare ) ) { // Compare 2 arrays. $map = array( '==' => count( self::array_recursive_diff( $value, $compare ) ) === 0, '!=' => count( self::array_recursive_diff( $value, $compare ) ) !== 0, ); } elseif ( is_array( $compare ) ) { // In or not in array. $map = array( 'in' => in_array( $value, $compare, true ), 'not_in' => in_array( $value, $compare, true ) === false, ); } else { $map = array( '===' => $value === $compare, '!==' => $value !== $compare, '>' => $value > $compare, '>=' => $value >= $compare, '<' => $value < $compare, '<=' => $value <= $compare, '==' => $value === $compare, '!=' => $value !== $compare, ); } if ( $map[ $comparison ] ) { $matched[] = $t; } } return array_shift( $matched ); } /** * Recursively finds the difference between two arrays. * * @param array $array1 The first array to compare. * @param array $array2 The second array to compare against. * * @return array Difference between the two arrays. */ protected static function array_recursive_diff( $array1, $array2 ): array { $result = array(); foreach ( $array1 as $key => $value ) { if ( array_key_exists( $key, $array2 ) ) { if ( is_array( $value ) ) { $recursive_diff = self::array_recursive_diff( $value, $array2[ $key ] ); if ( count( $recursive_diff ) ) { $result[ $key ] = $recursive_diff; } } elseif ( $value !== $array2[ $key ] ) { $result[ $key ] = $value; } } else { $result[ $key ] = $value; } } return $result; } /** * Check conditions against parameters and return a boolean result. * * @param mixed $conditions The conditions to check against. * @param array $params The parameters to use for comparison. * * @return bool The boolean result of the condition check. */ private static function check_condition( $conditions, $params ): bool { $good = true; foreach ( $conditions as $condition ) { $links = $condition[0]; $value = self::recursive_look_array( $params, str_replace( array( '{{', '}}' ), '', $links ) ); $compare = $condition[1]; if ( is_string( $compare ) && preg_match( '/{{.*}}/', $compare ) ) { $search = str_replace( array( '{{', '}}' ), '', $compare ); $compare = self::recursive_look_array( $params, $search ); } if ( is_array( $compare ) && in_array( $value, $compare, true ) ) { $good = false; } elseif ( $value === $compare ) { $good = false; } } return $good; } /** * Build log data based on the provided hook data and parameters. * * @return mixed Returns false if the log data cannot be built, otherwise an array of log data. * @throws ReflectionException If the class or method does not exist. */ public function build_log_data() { $args = func_get_args(); $hook_name = $args[0]; $params = $args[1]; $user_id = get_current_user_id(); $hook_data = $args[2]; // Have to build iup params first. if ( ( is_array( $hook_data['args'] ) || $hook_data['args'] instanceof Countable ? count( $hook_data['args'] ) : 0 ) !== ( is_array( $params ) || $params instanceof Countable ? count( $params ) : 0 ) ) { return false; } elseif ( empty( $hook_data['args'] ) && empty( $params ) ) { $params = array(); } else { $params = array_combine( $hook_data['args'], $params ); } if ( isset( $hook_data['callback'] ) && ! empty( $hook_data['callback'] ) ) { // Custom callback provided, call it. $reflection_method = new ReflectionMethod( $hook_data['callback'][0], $hook_data['callback'][1] ); $ret = $reflection_method->invokeArgs( new $hook_data['callback'][0](), array( $hook_name, $params, ) ); if ( is_array( $ret ) && 2 === count( $ret ) ) { [ $text, $context ] = $ret; } elseif ( is_array( $ret ) && 3 === count( $ret ) ) { [ $text, $context, $action ] = $ret; } else { $text = false; } } else { /** * First we need to query all parameters, * we have to loop around custom args, program args to queries and maintain the params list, * then we will check the left over, till all done, * then we will build up the text. */ $leftover = array(); if ( isset( $hook_data['custom_args'] ) ) { self::get_custom_args( $hook_data['custom_args'], $params, $leftover ); } if ( isset( $hook_data['program_args'] ) ) { self::get_program_args( $hook_data['program_args'], $params, $leftover ); } $params = array_merge( $this->get_default_params(), $params ); // Still need to check if this condition okay. if ( isset( $hook_data['false_when'] ) && self::check_condition( $hook_data['false_when'], $params ) === false ) { return false; } // Now we got all params as key=>value, just build the text. $text = self::get_text( $hook_data['text'], $params ); if ( empty( $text ) ) { return false; } foreach ( $params as $key => $val ) { $replacer = $val; if ( is_array( $replacer ) || is_object( $replacer ) ) { continue; } $text = str_replace( '{{' . $key . '}}', $replacer, $text ); } $context = ''; if ( isset( $hook_data['context'] ) ) { if ( is_array( $hook_data['context'] ) && is_callable( $hook_data['context'] ) ) { $reflection_method = new ReflectionMethod( $hook_data['context'][0], $hook_data['context'][1] ); $context = $reflection_method->invokeArgs( new $hook_data['context'][0](), array( $hook_name, $params, ) ); } elseif ( preg_match( '/{{.*}}/', $hook_data['context'] ) ) { $context = self::recursive_look_array( $params, str_replace( array( '{{', '}}', ), '', $hook_data['context'] ) ); } else { $context = $hook_data['context']; } } } $anonymous_override_list = array( 'wp_login', 'wp_logout', 'retrieve_password', 'after_password_reset', ); if ( 0 === $user_id && in_array( $hook_name, $anonymous_override_list, true ) ) { // In this state, user id still 0, we have to get the id via hooks param. if ( isset( $params['user'] ) ) { $user = $params['user']; $user_id = $user->ID; } elseif ( isset( $params['user_id'] ) ) { $user_id = $params['user_id']; } else { // Incorrect data. Return. return false; } } // We don't get text. Return. if ( ! $text ) { return false; } // Build data. $settings = new Audit_Logging(); $time = time(); $event_type = $hook_data['event_type']; $action_type = $action ?? $hook_data['action_type']; $site_url = network_site_url(); $msg = wp_strip_all_tags( $text ); $blog_id = get_current_blog_id(); $ttl = strtotime( '+ ' . $settings->storage_days ); foreach ( $this->get_user_ip() as $ip ) { $post = array( 'timestamp' => $time, 'event_type' => $event_type, 'action_type' => $action_type, 'site_url' => $site_url, 'user_id' => $user_id, 'context' => $context, 'ip' => $ip, 'msg' => $msg, 'blog_id' => $blog_id, 'ttl' => $ttl, ); Array_Cache::append( 'logs', $post, 'audit' ); } } /** * Retrieves default parameters for building log data. * * @return array default parameters. */ private function get_default_params(): array { return array( 'wp_user' => $this->get_source_of_action(), 'wp_user_id' => get_current_user_id(), 'blog_name' => is_multisite() ? '[' . get_bloginfo( 'name' ) . ']' : '', ); } }