PageRenderTime 125ms CodeModel.GetById 44ms RepoModel.GetById 7ms app.codeStats 0ms

/plugins/backupbuddy2/classes/fileoptions.php

https://gitlab.com/mattswann/launch-housing
PHP | 348 lines | 172 code | 62 blank | 114 comment | 43 complexity | 5c221014e3b96fb5309b1adbb156d9be MD5 | raw file
  1. <?php
  2. /* Class pb_backupbuddy_fileoptions
  3. *
  4. * @author Dustin Bolton
  5. * @date April, 2013
  6. *
  7. * Uses the filesystem for storing options data. Data is serialized & base64 encoded.
  8. * By default uses a locking mechanism to lock out accessing the options from another instance
  9. * while open with this instance. Lock automatically removed on class destruction.
  10. *
  11. * After construction check is_ok() function to verify === true. If not true returns error message.
  12. *
  13. * Example usage:
  14. * $backup_options = new pb_backupbuddy_fileoptions( $filename );
  15. * if ( $backup_options->is_ok() ) {
  16. * $backup_options->options = array( 'hello' => 'world' );
  17. * $backup_options->save(); // Optional force save now. If omitted destructor will hopefully save.
  18. * }
  19. Another in-use example:
  20. pb_backupbuddy::status( 'details', 'About to load fileoptions data.' );
  21. require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' );
  22. $fileoptions_obj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'fileoptions/send-' . $send_id . '.txt', $read_only = true, $ignore_lock = true, $create_file = false );
  23. if ( true !== ( $result = $fileoptions_obj->is_ok() ) ) {
  24. pb_backupbuddy::status( 'error', __('Fatal Error #9034.2344848. Unable to access fileoptions data.', 'it-l10n-backupbuddy' ) . ' Error: ' . $result );
  25. return false;
  26. }
  27. pb_backupbuddy::status( 'details', 'Fileoptions data loaded.' );
  28. $fileoptions = &$fileoptions_obj->options;
  29. *
  30. */
  31. class pb_backupbuddy_fileoptions {
  32. public $options = ''; // Current options.
  33. private $_options_hash = ''; // Hold hash of options so we don't perform file operations needlessly if things have not changed.
  34. private $_file; // Filename options are stored in.
  35. private $_is_ok = 'UNKNOWN'; // true on error; error message otherwise
  36. private $_read_only = false;
  37. private $_loaded = false; // Has file been succesfully loaded yet?
  38. /* __construct()
  39. *
  40. * Reads and creates file lock. If file does not exist, creates it. Places options into this class's $options.
  41. *
  42. * @param string $file Full filename to save fileoptions into.
  43. * @param bool $read_only true read only mode; false writable.
  44. * @param bool $ignore_lock When true ignore file locking. default: false
  45. * @param bool $create_file Create file if it does not yet exist and mark is_ok value to true.
  46. * @return null
  47. *
  48. */
  49. function __construct( $file, $read_only = false, $ignore_lock = false, $create_file = false ) {
  50. $this->_file = $file;
  51. $this->_read_only = $read_only;
  52. // If read-only then ignore locks is forced.
  53. if ( $read_only === true ) {
  54. $ignore_lock = true;
  55. }
  56. if ( ! file_exists( dirname( $file ) ) ) { // Directory exist?
  57. pb_backupbuddy::anti_directory_browsing( dirname( $file ), $die_on_fail = false, $deny_all = true );
  58. }
  59. /*
  60. if ( ! file_exists( $file ) ) { // File exist?
  61. //$this->save();
  62. }
  63. */
  64. $this->load( $ignore_lock, $create_file );
  65. } // End __construct().
  66. /* __destruct()
  67. *
  68. * Saves options on destruction.
  69. *
  70. * @return null
  71. *
  72. */
  73. function __destruct() {
  74. // 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.
  75. $this->unlock( $destructorCalled = true );
  76. } // End __destruct().
  77. /* is_ok()
  78. *
  79. * Determine whether options was loaded correctly and is ok.
  80. *
  81. * @return true\string True on valid, else returns error message string.
  82. *
  83. */
  84. public function is_ok() {
  85. return $this->_is_ok;
  86. } // End is_ok().
  87. /* load()
  88. *
  89. * Load options from file. Use is_ok() to verify integrity. If is_ok() !== true, returns error message.
  90. *
  91. * @param bool $ignore_lock Whether or not to ignore the file being locked.
  92. * @param bool $create_file Create file if it does not yet exist and mark is_ok value to true.
  93. * @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.
  94. * @return bool true on load success, else false.
  95. *
  96. */
  97. public function load( $ignore_lock = false, $create_file = false, $retryCount = 0 ) {
  98. // Handle locked file.
  99. if ( ( false === $ignore_lock ) && ( true === $this->is_locked() ) ) {
  100. 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 . '.' );
  101. $this->_is_ok = 'ERROR_LOCKED';
  102. return false;
  103. }
  104. // Get options and decode into usable format.
  105. if ( file_exists( $this->_file ) ) {
  106. $options = @file_get_contents( $this->_file );
  107. } else {
  108. if ( true !== $create_file ) {
  109. pb_backupbuddy::status( 'warning', 'Fileoptions file `' . $this->_file . '` not found and NOT in create mode. Verify file exists & check permissions.' );
  110. $this->_is_ok = 'ERROR_FILE_MISSING_NON_CREATE_MODE';
  111. }
  112. $options = '';
  113. }
  114. if ( false === $options ) {
  115. pb_backupbuddy::status( 'error', 'Unable to read fileoptions file `' . $this->_file . '`. Verify permissions on this directory.' );
  116. $this->_is_ok = 'ERROR_READ';
  117. return false;
  118. }
  119. if ( false === ( $options = base64_decode( $options ) ) ) {
  120. pb_backupbuddy::status( 'error', 'Unable to base64 decode data from fileoptions file `' . $this->_file . '`.' );
  121. $this->_is_ok = 'ERROR_BASE64_DECODE';
  122. return false;
  123. }
  124. if ( false === ( $options = maybe_unserialize( $options ) ) ) {
  125. pb_backupbuddy::status( 'error', 'Unable to unserialize data from fileoptions file `' . $this->_file . '`.' );
  126. $this->_is_ok = 'ERROR_UNSERIALIZE';
  127. return false;
  128. }
  129. if ( false === $this->_read_only ) { // Only lock when not in read-only mode.
  130. if ( false === $this->_lock() ) { // If lock fails (possibly due to existing lock file) then fail load.
  131. $this->_is_ok = 'ERROR_UNABLE_TO_LOCK';
  132. return false;
  133. }
  134. }
  135. if ( true === $create_file ) {
  136. $this->_is_ok = true;
  137. } elseif ( '' != $options ) {
  138. $this->_is_ok = true;
  139. } else {
  140. $this->_is_ok = 'ERROR_EMPTY_FILE_NON_CREATE_MODE';
  141. if ( $retryCount < 2 ) { // Give it one more chance by sleeping then loading once more. Return whatever result that gives.
  142. $this->unlock();
  143. $retryCount++;
  144. pb_backupbuddy::status( 'details', 'Fileoptions file was EMPTY. Unlocking & sleeping momentarily and then trying again. Attempt #1' . $retryCount );
  145. sleep( 3 );
  146. return $this->load( $ignore_lock, $create_file, $retryCount );
  147. }
  148. }
  149. $this->options = $options;
  150. $this->_loaded = true;
  151. $this->_options_hash = md5( serialize( $options ) );
  152. return true;
  153. } // End load();
  154. /* save()
  155. *
  156. * Save the options into file now without removing lock.
  157. *
  158. * @param bool $remove_lock When true the lock will be removed as well. default: false
  159. * @return bool true on save success, else false.
  160. *
  161. */
  162. public function save( $remove_lock = false ) {
  163. if ( '' == $this->_file ) { // No file set yet. Just return.
  164. return true;
  165. }
  166. if ( true === $this->_read_only ) {
  167. pb_backupbuddy::status( 'error', 'Attempted to write to fileoptions while in readonly mode; denied.' );
  168. return false;
  169. }
  170. if ( false === $this->_loaded ) { // Skip saving if we have not successfully loaded yet to prevent overwriting data.
  171. return false;
  172. }
  173. /*
  174. if ( true === $this->is_locked() ) {
  175. error_log( 'saveislocked' );
  176. pb_backupbuddy::status( 'details', 'Unable to write to fileoptions as file is currently locked: `' . $this->_file . '`.' );
  177. return false;
  178. }
  179. */
  180. $serialized = serialize( $this->options );
  181. $options_hash = md5( $serialized );
  182. if ( $options_hash == $this->_options_hash ) { // Only update if options has changed so if equal then no change so return.
  183. if ( true === $remove_lock ) {
  184. $this->unlock();
  185. }
  186. return true;
  187. }
  188. $options = base64_encode( $serialized );
  189. if ( false === ( $bytesWritten = file_put_contents( $this->_file, $options ) ) ) { // unable to write.
  190. pb_backupbuddy::status( 'error', 'Unable to write fileoptions file `' . $this->_file . '`. Verify permissions.' );
  191. if ( true === $remove_lock ) {
  192. $this->unlock();
  193. }
  194. return false;
  195. } else { // wrote to file.
  196. pb_backupbuddy::status( 'details', 'Fileoptions saved. ' . $bytesWritten . ' bytes written.' );
  197. $this->_options_hash = $options_hash;
  198. if ( true === $remove_lock ) {
  199. $this->unlock();
  200. }
  201. return true;
  202. }
  203. } // End save().
  204. /* _lock()
  205. *
  206. * Lock file.
  207. *
  208. * @return bool true on lock success, else false.
  209. *
  210. */
  211. private function _lock() {
  212. $lockFile = $this->_file . '.lock';
  213. if ( true === $this->_read_only ) {
  214. pb_backupbuddy::status( 'error', 'Attempted to lock fileoptions while in readonly mode; denied.' );
  215. return false;
  216. }
  217. $handle = @fopen( $lockFile, 'x' );
  218. if ( false === $handle ) { // Failed creating file.
  219. if ( file_exists( $lockFile ) ) {
  220. $this->_last_seen_lock_id = @file_get_contents( $lockFile );
  221. pb_backupbuddy::status( 'error', 'Unable to create fileoptions lock file as it already exists: `' . $lockFile . '`. Lock file ID: ' . $this->_last_seen_lock_id . '.' );
  222. } else {
  223. pb_backupbuddy::status( 'error', 'Unable to create fileoptions lock file `' . $lockFile . '`. Verify permissions on this directory.' );
  224. }
  225. return false;
  226. } else { // Created file.
  227. $lockID = uniqid( '', true );
  228. if ( false === @fwrite( $handle, $lockID ) ) {
  229. pb_backupbuddy::status( 'warning', 'Unable to write unique lock ID `' . $lockID . '` to lock file `' . $lockFile . '`.' );
  230. } else {
  231. pb_backupbuddy::status( 'details', 'Created fileoptions lock file `' . $lockFile . '` with ID: ' . $lockID . '.' );
  232. }
  233. @fclose( $handle );
  234. }
  235. }
  236. /* unlock()
  237. *
  238. * Unlock file.
  239. * WARNING!!! IMPORTANT!!! We cannot reliably call pb_backupbuddy::status() here _IF_ calling via destructor, $destructorCalled = true.
  240. *
  241. * @param bool $destructorCalled Whether or not this function was called via the destructor. See warning in comments above.
  242. * @return bool true on unlock success, else false.
  243. *
  244. */
  245. public function unlock( $destructorCalled = false ) {
  246. $lockFile = $this->_file . '.lock';
  247. if ( file_exists( $lockFile ) ) { // Locked; continue to unlock;
  248. $this->_last_seen_lock_id = @file_get_contents( $lockFile );
  249. $result = @unlink( $lockFile );
  250. if ( true === $result ) {
  251. if ( false === $destructorCalled ) {
  252. pb_backupbuddy::status( 'details', 'Unlocked fileoptions lock file `' . $lockFile . '` with lock ID `' . $this->_last_seen_lock_id . '`.' );
  253. }
  254. return true;
  255. } else {
  256. if ( class_exists( 'pb_backupbuddy' ) ) {
  257. if ( false === $destructorCalled ) {
  258. 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.' );
  259. }
  260. }
  261. return false;
  262. }
  263. } else { // File already unlocked.
  264. return true;
  265. }
  266. } // End unlock().
  267. /* is_locked()
  268. *
  269. * Is this file locked / in use?
  270. *
  271. * @return bool Whether or not file is currenty locked.
  272. *
  273. */
  274. public function is_locked() {
  275. $lockFile = $this->_file . '.lock';
  276. if ( file_exists( $lockFile ) ) {
  277. $this->_last_seen_lock_id = @file_get_contents( $lockFile );
  278. return true;
  279. } else {
  280. return false;
  281. }
  282. } // End is_locked().
  283. } // End class pb_backupbuddy_fileoptions.