PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-helper-script-manager.php

https://gitlab.com/chernushov881/charity-fund
PHP | 347 lines | 171 code | 52 blank | 124 comment | 30 complexity | 54a4a5c3a9f3cc16fb71eac739e0bdd0 MD5 | raw file
  1. <?php
  2. /**
  3. * The Jetpack Backup Helper Script Manager class.
  4. *
  5. * @package automattic/jetpack-backup
  6. */
  7. namespace Automattic\Jetpack\Backup;
  8. /**
  9. * Helper_Script_Manager manages installation, deletion and cleanup of Helper Scripts
  10. * to assist with backing up Jetpack Sites.
  11. */
  12. class Helper_Script_Manager {
  13. const TEMP_DIRECTORY = 'jetpack-temp';
  14. const HELPER_HEADER = "<?php /* Jetpack Backup Helper Script */\n";
  15. const EXPIRY_TIME = 8 * 3600; // 8 hours
  16. const MAX_FILESIZE = 1024 * 1024; // 1 MiB
  17. const README_LINES = array(
  18. 'These files have been put on your server by Jetpack to assist with backups and restores of your site content. They are cleaned up automatically when we no longer need them.',
  19. 'If you no longer have Jetpack connected to your site, you can delete them manually.',
  20. 'If you have questions or need assistance, please contact Jetpack Support at https://jetpack.com/support/',
  21. 'If you like to build amazing things with WordPress, you should visit automattic.com/jobs and apply to join the fun – mention this file when you apply!;',
  22. );
  23. const INDEX_FILE = '<?php // Silence is golden';
  24. /**
  25. * Installs a Helper Script, and returns its filesystem path and access url.
  26. *
  27. * @access public
  28. * @static
  29. *
  30. * @param string $script_body Helper Script file contents.
  31. * @return array|WP_Error Either an array containing the path and url of the helper script, or an error.
  32. */
  33. public static function install_helper_script( $script_body ) {
  34. // Check that the script body contains the correct header.
  35. if ( strncmp( $script_body, self::HELPER_HEADER, strlen( self::HELPER_HEADER ) ) !== 0 ) {
  36. return new \WP_Error( 'invalid_helper', 'Invalid Helper Script header' );
  37. }
  38. // Refuse to install a Helper Script that is too large.
  39. if ( strlen( $script_body ) > self::MAX_FILESIZE ) {
  40. return new \WP_Error( 'invalid_helper', 'Invalid Helper Script size' );
  41. }
  42. // Replace '[wp_path]' in the Helper Script with the WordPress installation location. Allows the Helper Script to find WordPress.
  43. $script_body = str_replace( '[wp_path]', addslashes( ABSPATH ), $script_body );
  44. // Create a jetpack-temp directory for the Helper Script.
  45. $temp_directory = self::create_temp_directory();
  46. if ( \is_wp_error( $temp_directory ) ) {
  47. return $temp_directory;
  48. }
  49. // Generate a random filename, avoid clashes.
  50. $max_attempts = 5;
  51. for ( $attempt = 0; $attempt < $max_attempts; $attempt++ ) {
  52. $file_key = wp_generate_password( 10, false );
  53. $file_name = 'jp-helper-' . $file_key . '.php';
  54. $file_path = trailingslashit( $temp_directory['path'] ) . $file_name;
  55. if ( ! file_exists( $file_path ) ) {
  56. // Attempt to write helper script.
  57. if ( ! self::put_contents( $file_path, $script_body ) ) {
  58. if ( file_exists( $file_path ) ) {
  59. unlink( $file_path );
  60. }
  61. continue;
  62. }
  63. // Always schedule a cleanup run shortly after EXPIRY_TIME.
  64. \wp_schedule_single_event( time() + self::EXPIRY_TIME + 60, 'jetpack_backup_cleanup_helper_scripts' );
  65. // Success! Figure out the URL and return the path and URL.
  66. return array(
  67. 'path' => $file_path,
  68. 'url' => trailingslashit( $temp_directory['url'] ) . $file_name,
  69. );
  70. }
  71. }
  72. return new \WP_Error( 'install_faied', 'Failed to install Helper Script' );
  73. }
  74. /**
  75. * Given a path, verify it looks like a helper script and then delete it if so.
  76. *
  77. * @access public
  78. * @static
  79. *
  80. * @param string $path Path to Helper Script to delete.
  81. * @return boolean True if the file is deleted (or does not exist).
  82. */
  83. public static function delete_helper_script( $path ) {
  84. if ( ! file_exists( $path ) ) {
  85. return true;
  86. }
  87. // Check this file looks like a JPR helper script.
  88. if ( ! self::verify_file_header( $path, self::HELPER_HEADER ) ) {
  89. return false;
  90. }
  91. return unlink( $path );
  92. }
  93. /**
  94. * Search for Helper Scripts that are suspiciously old, and clean them out.
  95. *
  96. * @access public
  97. * @static
  98. */
  99. public static function cleanup_expired_helper_scripts() {
  100. self::cleanup_helper_scripts( time() - self::EXPIRY_TIME );
  101. }
  102. /**
  103. * Search for and delete all Helper Scripts. Used during uninstallation.
  104. *
  105. * @access public
  106. * @static
  107. */
  108. public static function delete_all_helper_scripts() {
  109. self::cleanup_helper_scripts( null );
  110. }
  111. /**
  112. * Search for and delete Helper Scripts. If an $expiry_time is specified, only delete Helper Scripts
  113. * with an mtime older than $expiry_time. Otherwise, delete them all.
  114. *
  115. * @access public
  116. * @static
  117. *
  118. * @param int|null $expiry_time If specified, only delete scripts older than $expiry_time.
  119. */
  120. public static function cleanup_helper_scripts( $expiry_time = null ) {
  121. foreach ( self::get_install_locations() as $directory => $url ) {
  122. $temp_dir = trailingslashit( $directory ) . self::TEMP_DIRECTORY;
  123. if ( is_dir( $temp_dir ) ) {
  124. // Find expired helper scripts and delete them.
  125. $helper_scripts = glob( trailingslashit( $temp_dir ) . 'jp-helper-*.php' );
  126. if ( is_array( $helper_scripts ) ) {
  127. foreach ( $helper_scripts as $filename ) {
  128. if ( null === $expiry_time || filemtime( $filename ) < $expiry_time ) {
  129. self::delete_helper_script( $filename );
  130. }
  131. }
  132. }
  133. // Delete the directory if it's empty now.
  134. self::delete_empty_helper_directory( $temp_dir );
  135. }
  136. }
  137. }
  138. /**
  139. * Delete a helper script directory if it's empty
  140. *
  141. * @access public
  142. * @static
  143. *
  144. * @param string $dir Path to Helper Script directory.
  145. * @return boolean True if the directory is deleted
  146. */
  147. private static function delete_empty_helper_directory( $dir ) {
  148. if ( ! is_dir( $dir ) ) {
  149. return false;
  150. }
  151. // Tally the files in the target directory, and reject if there are too many.
  152. $glob_path = trailingslashit( $dir ) . '*';
  153. $dir_contents = glob( $glob_path );
  154. if ( count( $dir_contents ) > 2 ) {
  155. return false;
  156. }
  157. // Check that the only remaining files are a README and index.php generated by this system.
  158. $allowed_files = array(
  159. 'README' => self::README_LINES[0],
  160. 'index.php' => self::INDEX_FILE,
  161. );
  162. foreach ( $dir_contents as $path ) {
  163. $basename = basename( $path );
  164. if ( ! isset( $allowed_files[ $basename ] ) ) {
  165. return false;
  166. }
  167. // Verify the file starts with the expected contents.
  168. if ( ! self::verify_file_header( $path, $allowed_files[ $basename ] ) ) {
  169. return false;
  170. }
  171. if ( ! unlink( $path ) ) {
  172. return false;
  173. }
  174. }
  175. // If the directory is now empty, delete it.
  176. if ( count( glob( $glob_path ) ) === 0 ) {
  177. return rmdir( $dir );
  178. }
  179. return false;
  180. }
  181. /**
  182. * Find an appropriate location for a jetpack-temp folder, and create one
  183. *
  184. * @access public
  185. * @static
  186. *
  187. * @return WP_Error|array Array containing the url and path of the temp directory if successful, WP_Error if not.
  188. */
  189. private static function create_temp_directory() {
  190. foreach ( self::get_install_locations() as $directory => $url ) {
  191. // Check if the install location is writeable.
  192. if ( ! is_writeable( $directory ) ) {
  193. continue;
  194. }
  195. // Create if one doesn't already exist.
  196. $temp_dir = trailingslashit( $directory ) . self::TEMP_DIRECTORY;
  197. if ( ! is_dir( $temp_dir ) ) {
  198. if ( ! mkdir( $temp_dir ) ) {
  199. continue;
  200. }
  201. // Temp directory created. Drop a README and index.php file in there.
  202. self::write_supplementary_temp_files( $temp_dir );
  203. }
  204. return array(
  205. 'path' => trailingslashit( $directory ) . self::TEMP_DIRECTORY,
  206. 'url' => trailingslashit( $url ) . self::TEMP_DIRECTORY,
  207. );
  208. }
  209. return new \WP_Error( 'temp_directory', 'Failed to create jetpack-temp directory' );
  210. }
  211. /**
  212. * Write out an index.php file and a README file for a new jetpack-temp directory.
  213. *
  214. * @access public
  215. * @static
  216. *
  217. * @param string $dir Path to Helper Script directory.
  218. */
  219. private static function write_supplementary_temp_files( $dir ) {
  220. $readme_path = trailingslashit( $dir ) . 'README';
  221. self::put_contents( $readme_path, implode( "\n\n", self::README_LINES ) );
  222. $index_path = trailingslashit( $dir ) . 'index.php';
  223. self::put_contents( $index_path, self::INDEX_FILE );
  224. }
  225. /**
  226. * Write a file to the specified location with the specified contents.
  227. *
  228. * @access private
  229. * @static
  230. *
  231. * @param string $file_path Path to write to.
  232. * @param string $contents File contents to write.
  233. * @return boolean True if successfully written.
  234. */
  235. private static function put_contents( $file_path, $contents ) {
  236. global $wp_filesystem;
  237. if ( ! function_exists( '\\WP_Filesystem' ) ) {
  238. require_once ABSPATH . 'wp-admin/includes/file.php';
  239. }
  240. if ( ! \WP_Filesystem() ) {
  241. return false;
  242. }
  243. return $wp_filesystem->put_contents( $file_path, $contents );
  244. }
  245. /**
  246. * Checks that a file exists, is readable, and has the expected header.
  247. *
  248. * @access private
  249. * @static
  250. *
  251. * @param string $file_path File to verify.
  252. * @param string $expected_header Header that the file should have.
  253. * @return boolean True if the file exists, is readable, and the header matches.
  254. */
  255. private static function verify_file_header( $file_path, $expected_header ) {
  256. global $wp_filesystem;
  257. if ( ! function_exists( '\\WP_Filesystem' ) ) {
  258. require_once ABSPATH . 'wp-admin/includes/file.php';
  259. }
  260. if ( ! \WP_Filesystem() ) {
  261. return false;
  262. }
  263. // Verify the file exists and is readable.
  264. if ( ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) {
  265. return false;
  266. }
  267. // Verify that the file isn't too big or small.
  268. $file_size = $wp_filesystem->size( $file_path );
  269. if ( $file_size < strlen( $expected_header ) || $file_size > self::MAX_FILESIZE ) {
  270. return false;
  271. }
  272. // Read the file and verify its header.
  273. $contents = $wp_filesystem->get_contents( $file_path );
  274. return ( strncmp( $contents, $expected_header, strlen( $expected_header ) ) === 0 );
  275. }
  276. /**
  277. * Gets an associative array of possible places to install a jetpack-temp directory, along with the URL to access each.
  278. *
  279. * @access private
  280. * @static
  281. *
  282. * @return array Array, with keys specifying the full path of install locations, and values with the equivalent URL.
  283. */
  284. public static function get_install_locations() {
  285. // Include WordPress root and wp-content.
  286. $install_locations = array(
  287. \ABSPATH => \get_site_url(),
  288. \WP_CONTENT_DIR => \WP_CONTENT_URL,
  289. );
  290. // Include uploads folder.
  291. $upload_dir_info = \wp_upload_dir();
  292. $install_locations[ $upload_dir_info['basedir'] ] = $upload_dir_info['baseurl'];
  293. return $install_locations;
  294. }
  295. }