/plugins/backupbuddy2/classes/fileoptions.php
PHP | 348 lines | 172 code | 62 blank | 114 comment | 43 complexity | 5c221014e3b96fb5309b1adbb156d9be MD5 | raw file
- <?php
- /* Class pb_backupbuddy_fileoptions
- *
- * @author Dustin Bolton
- * @date April, 2013
- *
- * Uses the filesystem for storing options data. Data is serialized & base64 encoded.
- * By default uses a locking mechanism to lock out accessing the options from another instance
- * while open with this instance. Lock automatically removed on class destruction.
- *
- * After construction check is_ok() function to verify === true. If not true returns error message.
- *
- * Example usage:
- * $backup_options = new pb_backupbuddy_fileoptions( $filename );
- * if ( $backup_options->is_ok() ) {
- * $backup_options->options = array( 'hello' => 'world' );
- * $backup_options->save(); // Optional force save now. If omitted destructor will hopefully save.
- * }
-
- Another in-use example:
-
- pb_backupbuddy::status( 'details', 'About to load fileoptions data.' );
- require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' );
- $fileoptions_obj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'fileoptions/send-' . $send_id . '.txt', $read_only = true, $ignore_lock = true, $create_file = false );
- if ( true !== ( $result = $fileoptions_obj->is_ok() ) ) {
- pb_backupbuddy::status( 'error', __('Fatal Error #9034.2344848. Unable to access fileoptions data.', 'it-l10n-backupbuddy' ) . ' Error: ' . $result );
- return false;
- }
- pb_backupbuddy::status( 'details', 'Fileoptions data loaded.' );
- $fileoptions = &$fileoptions_obj->options;
- *
- */
- class pb_backupbuddy_fileoptions {
-
- public $options = ''; // Current options.
- private $_options_hash = ''; // Hold hash of options so we don't perform file operations needlessly if things have not changed.
- private $_file; // Filename options are stored in.
- private $_is_ok = 'UNKNOWN'; // true on error; error message otherwise
- private $_read_only = false;
- private $_loaded = false; // Has file been succesfully loaded yet?
-
- /* __construct()
- *
- * Reads and creates file lock. If file does not exist, creates it. Places options into this class's $options.
- *
- * @param string $file Full filename to save fileoptions into.
- * @param bool $read_only true read only mode; false writable.
- * @param bool $ignore_lock When true ignore file locking. default: false
- * @param bool $create_file Create file if it does not yet exist and mark is_ok value to true.
- * @return null
- *
- */
- function __construct( $file, $read_only = false, $ignore_lock = false, $create_file = false ) {
- $this->_file = $file;
- $this->_read_only = $read_only;
-
- // If read-only then ignore locks is forced.
- if ( $read_only === true ) {
- $ignore_lock = true;
- }
-
- if ( ! file_exists( dirname( $file ) ) ) { // Directory exist?
- pb_backupbuddy::anti_directory_browsing( dirname( $file ), $die_on_fail = false, $deny_all = true );
- }
-
- /*
- if ( ! file_exists( $file ) ) { // File exist?
- //$this->save();
- }
- */
-
- $this->load( $ignore_lock, $create_file );
-
- } // End __construct().
-
-
-
- /* __destruct()
- *
- * Saves options on destruction.
- *
- * @return null
- *
- */
- function __destruct() {
-
- // IMPORTANT: We can NOT rely on any outside classes from here on out such as the framework status method. Pass true to unlock to insure it does not perform any status logging.
- $this->unlock( $destructorCalled = true );
-
- } // End __destruct().
-
-
-
- /* is_ok()
- *
- * Determine whether options was loaded correctly and is ok.
- *
- * @return true\string True on valid, else returns error message string.
- *
- */
- public function is_ok() {
-
- return $this->_is_ok;
-
- } // End is_ok().
-
-
-
- /* load()
- *
- * Load options from file. Use is_ok() to verify integrity. If is_ok() !== true, returns error message.
- *
- * @param bool $ignore_lock Whether or not to ignore the file being locked.
- * @param bool $create_file Create file if it does not yet exist and mark is_ok value to true.
- * @param int $retryCount If ERROR_EMPTY_FILE_NON_CREATE_MODE error then we will retry a couple of times after a slight pause in case there was a race condition while another process was updating the file.
- * @return bool true on load success, else false.
- *
- */
- public function load( $ignore_lock = false, $create_file = false, $retryCount = 0 ) {
-
- // Handle locked file.
- if ( ( false === $ignore_lock ) && ( true === $this->is_locked() ) ) {
- pb_backupbuddy::status( 'warning', 'Warning #54555. Unable to read fileoptions file `' . $this->_file . '` as it is currently locked. Lock file ID: ' . $this->_last_seen_lock_id . '.' );
- $this->_is_ok = 'ERROR_LOCKED';
- return false;
- }
-
- // Get options and decode into usable format.
- if ( file_exists( $this->_file ) ) {
- $options = @file_get_contents( $this->_file );
- } else {
- if ( true !== $create_file ) {
- pb_backupbuddy::status( 'warning', 'Fileoptions file `' . $this->_file . '` not found and NOT in create mode. Verify file exists & check permissions.' );
- $this->_is_ok = 'ERROR_FILE_MISSING_NON_CREATE_MODE';
- }
- $options = '';
- }
-
- if ( false === $options ) {
- pb_backupbuddy::status( 'error', 'Unable to read fileoptions file `' . $this->_file . '`. Verify permissions on this directory.' );
- $this->_is_ok = 'ERROR_READ';
- return false;
- }
- if ( false === ( $options = base64_decode( $options ) ) ) {
- pb_backupbuddy::status( 'error', 'Unable to base64 decode data from fileoptions file `' . $this->_file . '`.' );
- $this->_is_ok = 'ERROR_BASE64_DECODE';
- return false;
- }
- if ( false === ( $options = maybe_unserialize( $options ) ) ) {
- pb_backupbuddy::status( 'error', 'Unable to unserialize data from fileoptions file `' . $this->_file . '`.' );
- $this->_is_ok = 'ERROR_UNSERIALIZE';
- return false;
- }
-
- if ( false === $this->_read_only ) { // Only lock when not in read-only mode.
- if ( false === $this->_lock() ) { // If lock fails (possibly due to existing lock file) then fail load.
- $this->_is_ok = 'ERROR_UNABLE_TO_LOCK';
- return false;
- }
- }
-
- if ( true === $create_file ) {
- $this->_is_ok = true;
- } elseif ( '' != $options ) {
- $this->_is_ok = true;
- } else {
- $this->_is_ok = 'ERROR_EMPTY_FILE_NON_CREATE_MODE';
- if ( $retryCount < 2 ) { // Give it one more chance by sleeping then loading once more. Return whatever result that gives.
- $this->unlock();
- $retryCount++;
- pb_backupbuddy::status( 'details', 'Fileoptions file was EMPTY. Unlocking & sleeping momentarily and then trying again. Attempt #1' . $retryCount );
- sleep( 3 );
- return $this->load( $ignore_lock, $create_file, $retryCount );
- }
- }
- $this->options = $options;
- $this->_loaded = true;
- $this->_options_hash = md5( serialize( $options ) );
-
- return true;
- } // End load();
-
-
-
- /* save()
- *
- * Save the options into file now without removing lock.
- *
- * @param bool $remove_lock When true the lock will be removed as well. default: false
- * @return bool true on save success, else false.
- *
- */
- public function save( $remove_lock = false ) {
-
- if ( '' == $this->_file ) { // No file set yet. Just return.
- return true;
- }
-
- if ( true === $this->_read_only ) {
- pb_backupbuddy::status( 'error', 'Attempted to write to fileoptions while in readonly mode; denied.' );
- return false;
- }
-
- if ( false === $this->_loaded ) { // Skip saving if we have not successfully loaded yet to prevent overwriting data.
- return false;
- }
-
-
- /*
- if ( true === $this->is_locked() ) {
- error_log( 'saveislocked' );
- pb_backupbuddy::status( 'details', 'Unable to write to fileoptions as file is currently locked: `' . $this->_file . '`.' );
- return false;
- }
- */
-
- $serialized = serialize( $this->options );
- $options_hash = md5( $serialized );
-
- if ( $options_hash == $this->_options_hash ) { // Only update if options has changed so if equal then no change so return.
- if ( true === $remove_lock ) {
- $this->unlock();
- }
- return true;
- }
-
- $options = base64_encode( $serialized );
-
- if ( false === ( $bytesWritten = file_put_contents( $this->_file, $options ) ) ) { // unable to write.
- pb_backupbuddy::status( 'error', 'Unable to write fileoptions file `' . $this->_file . '`. Verify permissions.' );
- if ( true === $remove_lock ) {
- $this->unlock();
- }
- return false;
- } else { // wrote to file.
- pb_backupbuddy::status( 'details', 'Fileoptions saved. ' . $bytesWritten . ' bytes written.' );
- $this->_options_hash = $options_hash;
- if ( true === $remove_lock ) {
- $this->unlock();
- }
- return true;
- }
-
- } // End save().
-
-
-
- /* _lock()
- *
- * Lock file.
- *
- * @return bool true on lock success, else false.
- *
- */
- private function _lock() {
-
- $lockFile = $this->_file . '.lock';
-
- if ( true === $this->_read_only ) {
- pb_backupbuddy::status( 'error', 'Attempted to lock fileoptions while in readonly mode; denied.' );
- return false;
- }
-
- $handle = @fopen( $lockFile, 'x' );
- if ( false === $handle ) { // Failed creating file.
- if ( file_exists( $lockFile ) ) {
- $this->_last_seen_lock_id = @file_get_contents( $lockFile );
- pb_backupbuddy::status( 'error', 'Unable to create fileoptions lock file as it already exists: `' . $lockFile . '`. Lock file ID: ' . $this->_last_seen_lock_id . '.' );
- } else {
- pb_backupbuddy::status( 'error', 'Unable to create fileoptions lock file `' . $lockFile . '`. Verify permissions on this directory.' );
- }
- return false;
- } else { // Created file.
- $lockID = uniqid( '', true );
- if ( false === @fwrite( $handle, $lockID ) ) {
- pb_backupbuddy::status( 'warning', 'Unable to write unique lock ID `' . $lockID . '` to lock file `' . $lockFile . '`.' );
- } else {
- pb_backupbuddy::status( 'details', 'Created fileoptions lock file `' . $lockFile . '` with ID: ' . $lockID . '.' );
- }
- @fclose( $handle );
- }
-
- }
-
-
-
- /* unlock()
- *
- * Unlock file.
- * WARNING!!! IMPORTANT!!! We cannot reliably call pb_backupbuddy::status() here _IF_ calling via destructor, $destructorCalled = true.
- *
- * @param bool $destructorCalled Whether or not this function was called via the destructor. See warning in comments above.
- * @return bool true on unlock success, else false.
- *
- */
- public function unlock( $destructorCalled = false ) {
-
- $lockFile = $this->_file . '.lock';
-
- if ( file_exists( $lockFile ) ) { // Locked; continue to unlock;
- $this->_last_seen_lock_id = @file_get_contents( $lockFile );
- $result = @unlink( $lockFile );
- if ( true === $result ) {
- if ( false === $destructorCalled ) {
- pb_backupbuddy::status( 'details', 'Unlocked fileoptions lock file `' . $lockFile . '` with lock ID `' . $this->_last_seen_lock_id . '`.' );
- }
- return true;
- } else {
- if ( class_exists( 'pb_backupbuddy' ) ) {
- if ( false === $destructorCalled ) {
- pb_backupbuddy::status( 'error', 'Unable to delete fileoptions lock file `' . $lockFile . '` with lock ID `' . $this->_last_seen_lock_id . '`. Verify permissions on this file / directory.' );
- }
- }
- return false;
- }
- } else { // File already unlocked.
- return true;
- }
-
- } // End unlock().
-
-
-
- /* is_locked()
- *
- * Is this file locked / in use?
- *
- * @return bool Whether or not file is currenty locked.
- *
- */
- public function is_locked() {
-
- $lockFile = $this->_file . '.lock';
-
- if ( file_exists( $lockFile ) ) {
- $this->_last_seen_lock_id = @file_get_contents( $lockFile );
- return true;
- } else {
- return false;
- }
-
- } // End is_locked().
-
-
-
- } // End class pb_backupbuddy_fileoptions.