' (path * relative to that plugin's folder). Only the core and plugin checks use * this; the uploads, autoloaded, config and users checks do not. * * Stores only paths and hashes — never file contents. * * @package MIINTG */ if ( ! defined( 'ABSPATH' ) ) { exit; } const MIINTG_REVIEWED_OPTION = 'miintg_reviewed'; const MIINTG_REVIEWED_MISSING = '__missing__'; /** * @return array key => entry. */ function miintg_reviewed_all() { $all = get_option( MIINTG_REVIEWED_OPTION, array() ); return is_array( $all ) ? $all : array(); } /** * @param string $scope 'core' or 'plugin:'. * @param string $path Path relative to the scope's base. * @return string */ function miintg_reviewed_key( $scope, $path ) { return md5( $scope . '|' . $path ); } /** * Resolves a scope + relative path to an absolute path, or null if either * is malformed. Rejects absolute paths and any '..' segment so a request * cannot point outside the scope's base directory. * * @param string $scope Scope. * @param string $path Relative path. * @return string|null */ function miintg_reviewed_resolve( $scope, $path ) { $path = wp_normalize_path( (string) $path ); if ( '' === $path || '/' === $path[0] || false !== strpos( $path, "\0" ) ) { return null; } foreach ( explode( '/', $path ) as $segment ) { if ( '..' === $segment ) { return null; } } if ( 'core' === $scope ) { $base = ABSPATH; } elseif ( 0 === strpos( (string) $scope, 'plugin:' ) ) { $slug = substr( $scope, 7 ); if ( ! preg_match( '/^[A-Za-z0-9._-]+$/', $slug ) || '.' === $slug || '..' === $slug ) { return null; } $base = trailingslashit( WP_PLUGIN_DIR ) . $slug . '/'; } else { return null; } return trailingslashit( $base ) . $path; } /** * Current state of a file for comparison with a stored review. * * @param string $scope Scope. * @param string $path Relative path. * @param string $kind 'modified' | 'missing' | 'unexpected'. * @return string|null md5, MIINTG_REVIEWED_MISSING, or null if it cannot be determined. */ function miintg_reviewed_state( $scope, $path, $kind ) { $abs = miintg_reviewed_resolve( $scope, $path ); if ( null === $abs ) { return null; } if ( 'missing' === $kind ) { return file_exists( $abs ) ? null : MIINTG_REVIEWED_MISSING; } if ( ! is_file( $abs ) ) { return null; } $hash = @md5_file( $abs ); return false === $hash ? null : $hash; } /** * Splits finding paths into those still counting and those reviewed. * * @param string $scope Scope. * @param string[] $paths Relative paths of one kind of finding. * @param string $kind 'modified' | 'missing' | 'unexpected'. * @return array{0:string[],1:string[]} [ active, reviewed ]. */ function miintg_reviewed_partition( $scope, $paths, $kind ) { $entries = miintg_reviewed_all(); $active = array(); $reviewed = array(); foreach ( $paths as $path ) { $key = miintg_reviewed_key( $scope, $path ); if ( ! isset( $entries[ $key ] ) ) { $active[] = $path; continue; } $state = miintg_reviewed_state( $scope, $path, $kind ); if ( null !== $state && $state === $entries[ $key ]['state'] ) { $reviewed[] = $path; } else { $active[] = $path; } } return array( $active, $reviewed ); } /** * Records a review. The caller has already validated the request. * * @param string $scope Scope. * @param string $path Relative path. * @param string $kind 'modified' | 'missing' | 'unexpected'. * @param string $state md5 or MIINTG_REVIEWED_MISSING. */ function miintg_reviewed_add( $scope, $path, $kind, $state ) { $all = miintg_reviewed_all(); $user = wp_get_current_user(); $all[ miintg_reviewed_key( $scope, $path ) ] = array( 'scope' => $scope, 'path' => $path, 'kind' => $kind, 'state' => $state, 'user' => ( $user && $user->exists() ) ? $user->user_login : 'system', 'time' => current_time( 'mysql', true ), ); update_option( MIINTG_REVIEWED_OPTION, $all, false ); } /** * @param string $key Entry key. * @return bool Whether an entry was removed. */ function miintg_reviewed_remove( $key ) { $all = miintg_reviewed_all(); if ( ! isset( $all[ $key ] ) ) { return false; } unset( $all[ $key ] ); update_option( MIINTG_REVIEWED_OPTION, $all, false ); return true; } function miintg_reviewed_clear() { delete_option( MIINTG_REVIEWED_OPTION ); }