<?php
/**
 * Logging & Related Functions
 *
 * @package LifterLMS/Functions
 *
 * @since 3.0.0
 * @version 5.2.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Copy a log file that is greater than or equal to the max allowed log file size
 *
 * If the log file's size is larger than the maximum allowed log file size (5MB) it will rename the log, adding
 * the current timestamp as a suffix and `.bk` to the extension.
 *
 * Future logs to the same file will result in a new logfile being created, ensuring that log files never grow
 * too large which could cause performance issues during reads and writes.
 *
 * @since 4.5.0
 *
 * @see llms_backup_logs()
 *
 * @param string $handle Log file handle.
 * @return null|boolean|string Returns `null` if the log file is not larger than the max size, `false` if an error is encountered,
 *                             and the new log file path on success.
 */
function llms_backup_log( $handle ) {

	$file = llms_get_log_path( $handle );
	$size = file_exists( $file ) ? filesize( $file ) : 0;

	/**
	 * Filter the max filesize of a log file before the log is backed up
	 *
	 * The value of this filter, `$maxsize` is an integer representing the maximum number of megabytes
	 * a file can be before it is split.
	 *
	 * @since 4.5.0
	 *
	 * @param int $maxsize Maximum file size (in MB). The default value is `5` (5MB).
	 */
	$maxsize = absint( apply_filters( 'llms_log_max_filesize', 5 ) ) * 1000 * 1000;

	if ( $size >= $maxsize ) {

		$copy = str_replace( '.log', sprintf( '-%d.log.bk', time() ), $file );

		/**
		 * Filter the name of a log file copy that's being backed up because it's reached the maximum allowed size
		 *
		 * While it is possible to change the extension of the log file (`.log.bk`), it is not recommended. The cron
		 * which creates copies filters out `.log.bk` so that it doesn't scan backups and attempt to split them
		 * again (infinitely).
		 *
		 * @since 4.5.0
		 *
		 * @param string $copy   Full path for the copy log file (the backup).
		 * @param string $file   Full path for the original log file.
		 * @param string $handle Log file handle.
		 */
		$copy = apply_filters( 'llms_log_split_file_name', $copy, $file, $handle );
		if ( rename( $file, $copy ) ) {

			/**
			 * Action triggered immediately following the creation of a logfile backup.
			 *
			 * @since 4.5.0
			 *
			 * @param string $copy   Full path for the copy log file (the backup).
			 * @param string $file   Full path for the original log file.
			 * @param string $handle Log file handle.
			 */
			do_action( 'llms_log_file_backup_created', $copy, $file, $handle );

			return $copy;
		}
	}

	return null;

}

/**
 * Backup all log files in the LifterLMS log directory
 *
 * This function scans the `LLMS_LOG_DIR` and passes each log file to `llms_backup_log()` to
 * create backups of each log file.
 *
 * It does not include logs with the `.log.bk` extension as those logs are logs created by this process
 * and don't need to be scanned again.
 *
 * @since 4.5.0
 *
 * @see llms_backup_log()
 *
 * @return void
 */
function llms_backup_logs() {

	foreach ( glob( LLMS_LOG_DIR . '*.log' ) as $file ) {

		// Get the handle from the file path.
		$parts = explode( '-', basename( $file, '.log' ) );
		if ( $parts ) {
			llms_backup_log( implode( '-', array_slice( $parts, 0, -1 ) ) );
		}
	}

}
add_action( 'llms_backup_logs', 'llms_backup_logs' );

/**
 * Retrieve a string representing a PHP callable
 *
 * This can be used to log callables regardless of the callable format.
 *
 * @since 5.2.0
 *
 * @param mixed $callable PHP callable.
 * @return string
 */
function llms_get_callable_name( $callable ) {

	// Function name or static class -> method: 'function' or 'class::method'.
	if ( is_string( $callable ) ) {
		return $callable;
	}

	if ( is_array( $callable ) && ! empty( $callable ) ) {

		// Class and class method: [ $class, 'method' ].
		if ( is_object( $callable[0] ) ) {
			return get_class( $callable[0] ) . '->' . $callable[1];
		}

		// Static class + method: [ 'class', 'method' ].
		return implode( '::', $callable );

	}

	// Invokable class: $class.
	if ( is_object( $callable ) ) {
		return get_class( $callable );
	}

	return 'Unknown';

}

/**
 * Retrieve the full path to the log file for a given log handle
 *
 * @since 3.0.0
 *
 * @param string $handle Log handle.
 * @return string
 */
function llms_get_log_path( $handle ) {

	return trailingslashit( LLMS_LOG_DIR ) . $handle . '-' . sanitize_file_name( wp_hash( $handle ) ) . '.log';

}

/**
 * Log arbitrary messages to a log file
 *
 * @since 1.0.0
 * @since 3.7.5 Unknown.
 *
 * @param mixed  $message Data to log.
 * @param string $handle  Allow creation of multiple log files by handle.
 * @return boolean
 */
function llms_log( $message, $handle = 'llms' ) {

	/**
	 * Filter a log message before it's written to the logger.
	 *
	 * @since 4.12.0
	 *
	 * @param mixed  $message Data to log.
	 * @param string $handle  Allow creation of multiple log files by handle.
	 */
	$message = apply_filters( 'llms_log_message', $message, $handle );

	$ret = false;
	$fh  = fopen( llms_get_log_path( $handle ), 'a' );

	// Open the file (creates it if it doesn't already exist).
	if ( $fh ) {

		// Print array or objects with `print_r`.
		if ( is_array( $message ) || is_object( $message ) ) {
			$message = print_r( $message, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- This is intentional.
		}

		$ret = fwrite( $fh, gmdate( 'Y-m-d H:i:s' ) . ' - ' . $message . "\n" );

		fclose( $fh );

	}

	return $ret ? true : false;

}
