slug = $slug; $this->destination_scheme = $destination_scheme; $this->destination_host = $destination_host; $this->temp_files_dir = $temp_files_dir; $this->additional_urls = $additional_urls; } /** * Get the list of URLs in the archive * * @param array $export_log URLs that were successfully added to the archive */ public function get_export_log() { return $this->export_log; } /** * Create a static version of the site * * @return void */ public function create_archive() { global $blog_id; // TODO: Do ajax calls instead of just running forever and ever set_time_limit(0); // Create archive directory $current_user = wp_get_current_user(); $archive_name = join( '-', array( $this->slug, $blog_id, time(), $current_user->user_login ) ); $this->archive_dir = trailingslashit( $this->temp_files_dir . $archive_name ); if ( ! file_exists( $this->archive_dir ) ) { wp_mkdir_p( $this->archive_dir ); } // Add URLs to queue $origin_url = home_url(); $destination_url = $this->destination_scheme . '://' . $this->destination_host; $urls_queue = array_unique( array_merge( array( trailingslashit( $origin_url ) ), // using preg_split to intelligently break at newlines // see: http://stackoverflow.com/questions/1483497/how-to-put-string-in-array-split-by-new-line preg_split( "/\r\n|\n|\r/", $this->additional_urls ) ) ); while ( count( $urls_queue ) ) { $current_url = array_shift( $urls_queue ); $response = Simply_Static_Url_Fetcher::fetch( $current_url ); // If we get a WP_Error then somehow our request failed (e.g. space in URL) // TODO: Keep a queue of failed urls too if ( is_wp_error( $response ) ) { continue; } $url_parts = parse_url( $response->url ); $path = $url_parts['path']; $is_html = $response->is_html(); // If we get a 30x redirect... if ( in_array( $response->code, array( 301, 302, 303, 307 ) ) ) { $redirect_url = $response->get_redirect_url(); // WP likes to 301 redirect `/path` to `/path/` -- we want to // check for this and just add the trailing slashed version if ( $redirect_url === trailingslashit( $current_url ) ) { $urls_queue = $this->add_url_to_queue( $urls_queue, $redirect_url ); } else { /// convert our potentially relative URL to an absolute URL $redirect_url = sist_relative_to_absolute_url( $redirect_url, $current_url ); if ( $redirect_url ) { // check if this is a local URL if ( sist_is_local_url( $redirect_url ) ) { // add the redirected page to the queue $urls_queue = $this->add_url_to_queue( $urls_queue, $redirect_url ); // and update the URL $redirect_url = str_replace( $origin_url, $destination_url, $redirect_url ); } $view = new Simply_Static_View(); $content = $view->set_template( 'redirect' ) ->assign( 'redirect_url', $redirect_url ) ->render_to_string(); $this->save_url_to_file( $path, $content, $is_html ); $this->export_log[] = $current_url; } } continue; } // Not a 200 for the response code? Move on. // TODO: Keep a queue of failed urls too if ( $response->code != 200 ) { continue; } $this->export_log[] = $current_url; // Fetch all URLs from the page and add them to the queue... $urls = $response->extract_urls(); foreach ( $urls as $url ) { $urls_queue = $this->add_url_to_queue( $urls_queue, $url ); } // Replace the origin URL with the destination URL $response->replace_url( $origin_url, $destination_url ); // Save the page to our archive $content = $response->body; $this->save_url_to_file( $path, $content, $is_html ); } } /** * Add a URL to the processing queue if we haven't already processed it and * if it's not in the queue to be processed * * @param array $urls_queue Queue of URLs to be processed * @param string $url URL to add to the processing queue * @return array Queue of URLs to be processed */ private function add_url_to_queue( $urls_queue, $url ) { if ( ! in_array( $url, $this->export_log ) && ! in_array( $url, $urls_queue ) ) { $urls_queue[] = $url; } return $urls_queue; } /** * Create a ZIP file using the archive directory * * @return string|WP_Error $temporary_zip The path to the archive zip file */ public function create_zip() { $temporary_zip = untrailingslashit( $this->archive_dir ) . '.tmp'; $zip_archive = new ZipArchive(); if ( $zip_archive->open( $temporary_zip, ZIPARCHIVE::CREATE ) !== true ) { return new WP_Error( 'create_zip_failed', __( 'Unable to create ZIP archive', $this->slug ) ); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $this->archive_dir ) ); foreach ( $iterator as $file_name => $file_object ) { $base_name = basename( $file_name ); if ( $base_name != '.' && $base_name != '..' ) { if ( ! $zip_archive->addFile( realpath( $file_name ), str_replace( $this->archive_dir, '', $file_name ) ) ) { return new WP_Error( 'cannot_add_file_to_zip', sprintf( __( "Could not add file: %s", $this->slug ), $file_name ) ); } } } $zip_archive->close(); $zip_file = untrailingslashit( $this->archive_dir ) . '.zip'; rename( $temporary_zip, $zip_file ); $archive_url = str_replace( plugin_dir_path( dirname( __FILE__ ) ), plugin_dir_url( dirname( __FILE__ ) ), $zip_file ); return $archive_url; } /** * Publish to AWS S3 bucket * * @return boolean|WP_Error */ public function publish_to_s3( $bucket, $aws_access_key_id, $aws_secret_access_key, $aws_s3_region = 'us-east-1', $aws_s3_endpoint = 's3.amazonaws.com' ) { $directory_iterator = new RecursiveDirectoryIterator( $this->archive_dir, RecursiveDirectoryIterator::SKIP_DOTS ); $recursive_iterator = new RecursiveIteratorIterator( $directory_iterator, RecursiveIteratorIterator::SELF_FIRST ); S3::$useExceptions = true; $useSSL = true; $endpoint = $aws_s3_endpoint; if ( preg_match( '#^https://#i', $endpoint ) ) { $useSSL = true; $endpoint = preg_replace( '#^https://#i', '', $endpoint ); } elseif ( preg_match( '#^http://#i', $endpoint ) ) { $useSSL = false; $endpoint = preg_replace( '#^http://#i', '', $endpoint ); } $endpoint = untrailingslashit( $endpoint ); if ( empty( $endpoint ) ) { $endpoint = 's3.amazonaws.com'; } $s3 = new S3( $aws_access_key_id, $aws_secret_access_key, $useSSL, $endpoint, $aws_s3_region ); S3::setRegion( $aws_s3_region ); foreach ( $recursive_iterator as $item ) { if (!$item->isDir()) { $path = $recursive_iterator->getSubPathName(); try { $s3->putObject( S3::inputFile( $item->getRealPath() ), $bucket, $path, S3::ACL_PUBLIC_READ ); } catch (any $err) { return new WP_Error( 'cannot_publish_to_s3', sprintf( __( "Could not publish file to S3: %s: %s", $this->slug, $err ), $path ) ); } } } return true; } /** * Copy static files to a local directory * * @return boolean|WP_Error */ public function copy_static_files( $local_dir ) { $directory_iterator = new RecursiveDirectoryIterator( $this->archive_dir, RecursiveDirectoryIterator::SKIP_DOTS ); $recursive_iterator = new RecursiveIteratorIterator( $directory_iterator, RecursiveIteratorIterator::SELF_FIRST ); foreach ( $recursive_iterator as $item ) { $path = $local_dir . $recursive_iterator->getSubPathName(); $success = $item->isDir() ? wp_mkdir_p( $path ) : copy( $item, $path ); if ( ! $success ) { return new WP_Error( 'cannot_create_file_or_dir', sprintf( __( "Could not create file or directory: %s", $this->slug ), $path ) ); } } return true; } /** * Delete generated static files * * @param $archive_dir The archive directory path * @return boolean|WP_Error */ public function delete_static_files() { $directory_iterator = new RecursiveDirectoryIterator( $this->archive_dir, FilesystemIterator::SKIP_DOTS ); $recursive_iterator = new RecursiveIteratorIterator( $directory_iterator, RecursiveIteratorIterator::CHILD_FIRST ); // recurse through the entire directory and delete all files / subdirectories foreach ( $recursive_iterator as $item ) { $success = $item->isDir() ? rmdir( $item ) : unlink( $item ); if ( ! $success ) { return new WP_Error( 'cannot_delete_file_or_dir', sprintf( __( "Could not delete temporary file or directory: %s", $this->slug ), $item ) ); } } // must make sure to delete the original directory at the end $success = rmdir( $this->archive_dir ); if ( ! $success ) { return new WP_Error( 'cannot_delete_file_or_dir', sprintf( __( "Could not delete temporary file or directory: %s", $this->slug ), $item ) ); } return true; } /** * Save the contents of a page to a file in our archive directory * * @param string $path The relative path for the URL to save * @param string $content The contents of the page we want to save * @param boolean $is_html Is this an html page? * @return boolean $success Did we successfully save the file? */ protected function save_url_to_file( $path, $content, $is_html ) { $path_info = pathinfo( $path && $path != '/' ? $path : 'index.html' ); // Create file directory if it doesn't exist $file_dir = $this->archive_dir . ( $path_info['dirname'] ? $path_info['dirname'] : '' ); if ( empty( $path_info['extension'] ) && $path_info['basename'] == $path_info['filename'] ) { $file_dir .= '/' . $path_info['basename']; $path_info['filename'] = 'index'; } if ( ! file_exists( $file_dir ) ) { wp_mkdir_p( $file_dir ); } // Save file contents $file_extension = ( $is_html || ! isset( $path_info['extension'] ) ) ? 'html' : $path_info['extension']; $file_name = $file_dir . '/' . $path_info['filename'] . '.' . $file_extension; $success = file_put_contents( $file_name, $content ); return $success; } }