diff --git a/src/Api/V1/Base_Api.php b/src/Api/V1/Base_Api.php new file mode 100644 index 000000000..8aca54e33 --- /dev/null +++ b/src/Api/V1/Base_Api.php @@ -0,0 +1,96 @@ +has_capability( $capability ); + } + + /** + * Register WordPress REST API endpoint(s) + * + * @return void + * + * @internal Use `register_rest_route()` to register WordPress REST API endpoint(s) + * + * @since 5.2 + */ + abstract public function register(); +} diff --git a/src/Api/V1/Fonts/Api_Fonts.php b/src/Api/V1/Fonts/Api_Fonts.php new file mode 100644 index 000000000..cc5bf322b --- /dev/null +++ b/src/Api/V1/Fonts/Api_Fonts.php @@ -0,0 +1,408 @@ +log = $log; + $this->misc = $misc; + $this->data = $data; + $this->options = $options; + } + + /** + * Register our PDF save font endpoint + * + * @Internal Use this endpoint to save fonts + * + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/fonts/', + [ + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'save_font' ], + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_edit_settings' ); + }, + ] + ); + + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/fonts/', + [ + 'methods' => \WP_REST_Server::DELETABLE, + 'callback' => [ $this, 'delete_font' ], + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_edit_settings' ); + }, + ] + ); + } + + /** + * Register our PDF save font endpoint + * + * @param WP_REST_Request $request + * + * @return \WP_REST_Response + * + * @since 5.2 + */ + public function save_font( \WP_REST_Request $request ) { + + /* get the json parameter */ + $params = $request->get_json_params(); + + /* Handle the validation and saving of the font */ + $payload = isset( $params['payload'] ) ? $params : null; + + if (! $payload) { + return new \WP_Error( 'required_fields_missing', 'Required fields have not been included', [ 'status' => 400 ] ); + } + + $results = $this->process_font( $payload ); + + /* There was an issue downloading and saving fonts */ + if ( $results->errors ) { + return $results ; + } + + /* If we reached this point the results were successful so return the new object */ + $this->log->addNotice( + 'AJAX – Successfully Saved Font', + [ + 'results' => $results, + ] + ); + + return [ 'message' => 'Font saved successfully' ]; + + } + + /** + * Validate user input and save as new font + * + * @param array $font The four font fields to be processed + * + * @return array + * + * @since 5.2 + */ + public function process_font( $font ) { + + /* remove any empty fields */ + $font = array_filter( $font ); + + /* Check we have the required data */ + if ( empty( $font['payload']['font_name'] ) || empty( $font['payload']['regular'] ) ) { + + $error = esc_html__( 'Required fields have not been included.', 'gravity-forms-pdf-extended' ); + + $this->log->addWarning( 'Font Validation Failed', (array)$error ); + + return new \WP_Error( 'required_fields_missing', $error, [ 'status' => 400 ] ); + } + + /* Check we have a valid font name */ + $name = $font['payload']['font_name']; + + if ( ! $this->is_font_name_valid( $name ) ) { + $error = esc_html__( 'Font name is not valid. Only alphanumeric characters and spaces are accepted.', 'gravity-forms-pdf-extended' ); + + $this->log->addWarning( 'Font Validation Failed', (array)$error ); + + return new \WP_Error( 'invalid_font_name', $error, [ 'status' => 400 ] ); + } + + /* Check the font name is unique */ + $shortname = $this->options->get_font_short_name( $name ); + + $id = ( isset( $font['id'] ) ) ? $font['id'] : ''; + + if ( ! $this->is_font_name_unique( $shortname, $id ) ) { + $error = esc_html__( 'A font with the same name already exists. Try a different name.', 'gravity-forms-pdf-extended' ); + + $this->log->addWarning( 'Font Validation Failed', (array)$error ); + + return new \WP_Error( 'font_name_exist', $error, [ 'status' => 422 ] ); + } + + /* Move fonts to our Gravity PDF font folder */ + $installation = $this->install_fonts( $font['payload'] ); + + /* If we got here the installation was successful so return the data */ + /* Return whatever is thrown by install_fonts function */ + return $installation; + } + + /** + * Check that the font name passed conforms to our expected nameing convesion + * + * @param string $name The font name to check + * + * @return boolean True on valid, false on failure + * + * @since 5.2 + */ + public function is_font_name_valid( $name ) { + $regex = '^[A-Za-z0-9 ]+$'; + + if ( preg_match( "/$regex/", $name ) ) { + return true; + } + + return false; + } + + /** + * Handles the database updates required to save a new font + * + * @param array $fonts + * + * @return array + * + * @since 5.2 + */ + public function install_fonts( $fonts ) { + $types = [ 'regular', 'bold', 'italics', 'bolditalics' ]; + $errors = []; + + foreach ( $types as $type ) { + + /* Check if a key exists for this type and process */ + if ( isset( $fonts[ $type ] ) ) { + + $path = $this->misc->convert_url_to_path( $fonts[ $type ] ); + + /* Couldn't find file so throw error */ + if ( ! $path ) { + return new \WP_Error( "font_installation_error", "Could not locate font on web server: " . $fonts[ 'font_name' ] . " " . $fonts[ $type ], [ "status" => 404 ] ); + } + + /* Copy font to our fonts folder */ + $filename = basename( $path ); + + if ( ! is_file( $this->data->template_font_location . $filename ) && ! copy( $path, $this->data->template_font_location . $filename ) ) { + + return new \WP_Error( "font_installation_error", "There was a problem installing the font: " . $filename . "Please try again.", [ "status" => 500 ] ); + } + } + } + + /* Insert our font into the database */ + $custom_fonts = $this->options->get_option( 'custom_fonts' ); + + /* Prepare our font data and give it a unique id */ + if ( empty( $fonts['id'] ) ) { + $id = uniqid(); + $fonts['id'] = $id; + } + + $custom_fonts[ $fonts['id'] ] = $fonts; + + /* Update our font database */ + $this->options->update_option( 'custom_fonts', $custom_fonts ); + + /* Cleanup the mPDF tmp directory to prevent font caching issues */ + $this->misc->cleanup_dir( $this->data->mpdf_tmp_location ); + + /* Fonts sucessfully installed so return font data */ + return $fonts; + } + + /** + * Query our custom fonts options table and check if the font name already exists + * + * @param string $name The font name to check + * @param int|string $id The configuration ID (if any) + * + * @return bool True if valid, false on failure + * + * @since 5.2 + */ + public function is_font_name_unique( $name, $id = '' ) { + + /* Get the shortname of the current font */ + $name = $this->options->get_font_short_name( $name ); + + /* Loop through default fonts and check for duplicate */ + $default_fonts = $this->options->get_installed_fonts(); + + unset( $default_fonts[ esc_html__( 'User-Defined Fonts', 'gravity-forms-pdf-extended' ) ] ); + + /* check for exact match */ + foreach ( $default_fonts as $group ) { + if ( isset( $group[ $name ] ) ) { + return false; + } + } + + $custom_fonts = $this->options->get_option( 'custom_fonts' ); + + if ( is_array( $custom_fonts ) ) { + foreach ( $custom_fonts as $font ) { + + /* Skip over itself */ + if ( ! empty( $id ) && $font['id'] == $id ) { + continue; + } + + if ( $name == $this->options->get_font_short_name( $font['font_name'] ) ) { + return false; + } + } + } + + return true; + } + + /** + * Delete custom font + * + * @return \WP_REST_Response + * + * @since 5.2 + */ + public function delete_font( \WP_REST_Request $request ) { + + /* get the json parameter */ + $params = $request->get_json_params(); + + /* Get the required details for deleting fonts */ + $id = ( isset( $params['id'] ) ) ? $params['id'] : ''; + $fonts = $this->options->get_option( 'custom_fonts' ); + + /* Check font actually exists and remove */ + if ( ! isset( $fonts[ $id ] ) || ! $this->remove_font_file( $fonts[ $id ] ) ) { + $error = ['error' => esc_html__( 'Could not delete Gravity PDF font correctly. Please try again.', 'gravity-forms-pdf-extended' ) ]; + + $this->log->addError( 'AJAX Endpoint Error', $error ); + + return new \WP_Error( 'delete_font', $error, [ 'status' => 500 ] ); + } + + unset( $fonts[ $id ] ); + + /* Cleanup the mPDF tmp directory to prevent font caching issues */ + $this->misc->cleanup_dir( $this->data->mpdf_tmp_location ); + + if ( $this->options->update_option( 'custom_fonts', $fonts ) ) { + $this->log->addNotice( 'Successfully Deleted Font' ); + return true; + } + } + + /** + * Removes the current font's TTF files from our font directory + * + * @param array $fonts The font config + * + * @return boolean True on success, false on failure + * + * @since 5.2 + */ + public function remove_font_file( $fonts ) { + $fonts = array_filter( $fonts ); + $types = [ 'regular', 'bold', 'italics', 'bolditalics' ]; + + foreach ( $types as $type ) { + if ( isset( $fonts[ $type ] ) ) { + $filename = basename( $fonts[ $type ] ); + + if ( is_file( $this->data->template_font_location . $filename ) && ! unlink( $this->data->template_font_location . $filename ) ) { + return false; + } + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/Api/V1/Fonts/Core/Api_Fonts_Core.php b/src/Api/V1/Fonts/Core/Api_Fonts_Core.php new file mode 100644 index 000000000..5a005a857 --- /dev/null +++ b/src/Api/V1/Fonts/Core/Api_Fonts_Core.php @@ -0,0 +1,186 @@ +log = $log; + $this->template_font_location = $template_font_location; + } + + /** + * Register WordPress REST API endpoint(s) + * + * @return void + * + * @internal Use `register_rest_route()` to register WordPress REST API endpoint(s) + * + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/fonts/core/', + [ + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'save_core_font' ], + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_edit_settings' ); + }, + ] + ); + + } + + /** + * Processes the rest API endpoint + * + * @param \WP_REST_Request $request + * + * @return array|\WP_Error + * + * @since 5.2 + */ + public function save_core_font( \WP_REST_Request $request ) { + + $params = $request->get_json_params(); + + /* Download and save our font */ + $fontname = isset( $params['font_name'] ) ? $params['font_name'] : ''; + $results = $this->download_and_save_font( $fontname ); + + if ( ! $results ) { + return new \WP_Error( 'download_and_save_font', 'Core Font Download Failed', [ 'status' => 400 ] ); + } + + return [ 'message' => 'Font saved successfully' ]; + } + + /** + * Stream files from remote server and save them locally + * + * @param $fontname + * + * @return bool + * + * @since 5.2 + */ + protected function download_and_save_font( $fontname ) { + + if ( empty( $fontname ) ) { + return false; + } + + /* Only the font name is passed via AJAX. The Repo we download from is fixed (prevent security issues) */ + $response = wp_remote_get( + $this->github_repo . $fontname, + [ + 'timeout' => 60, + 'stream' => true, + 'filename' => $this->template_font_location . $fontname, + ] + ); + + /* Check for errors and log them to file */ + if ( is_wp_error( $response ) ) { + $this->log->addError( + 'Core Font Download Failed', + [ + 'name' => $fontname, + 'WP_Error_Message' => $response->get_error_message(), + 'WP_Error_Code' => $response->get_error_code(), + ] + ); + + return false; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + if ( $response_code !== 200 ) { + $this->log->addError( + 'Core Font API Response Failed', + [ + 'response_code' => $response_code, + ] + ); + + return false; + } + + /* If we got here, the call was successfull */ + return true; + } +} diff --git a/src/Api/V1/License/Api_License.php b/src/Api/V1/License/Api_License.php new file mode 100644 index 000000000..1e738cd06 --- /dev/null +++ b/src/Api/V1/License/Api_License.php @@ -0,0 +1,173 @@ +log = $log; + $this->data = $data; + } + + /** + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/license/(?P\d+)/deactivate', + [ + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'process_license_deactivation' ], + + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_edit_settings' ); + }, + ] + ); + } + + /** + * Processes the REST API endpoint + * + * @param \WP_REST_Request $request + * + * @return array|WP_Error + * + * @since 5.2 + */ + public function process_license_deactivation( \WP_REST_Request $request ) { + $params = $request->get_json_params(); + if ( ! isset( $params['addon_name'], $params['license'] ) ) { + return new WP_Error( 'license_deactivation_fields_missing', 'Required Field is missing, please try again', [ 'status' => 400 ] ); + } + + /* Check add-on currently installed */ + if ( empty( $this->data->addon[ $params['addon_name'] ] ) ) { + return new WP_Error( 'license_deactivation_addon_not_found', 'An error occurred during deactivation, please try again', [ 'status' => 404 ] ); + } + + $addon = $this->data->addon[ $params['addon_name'] ]; + $was_deactivated = $this->deactivate_license_key( $addon, $params['license'] ); + + if ( ! $was_deactivated ) { + $license_info = $addon->get_license_info(); + + return new WP_Error( 'license_deactivation_schedule_license_check', $license_info['message'], [ 'status' => 400 ] ); + } + + $this->log->addNotice( 'Successfully Deactivated License' ); + + return [ 'success' => esc_html__( 'License deactivated.', 'gravity-forms-pdf-extended' ) ]; + } + + /** + * Do API call to GravityPDF.com to deactivate add-on license + * + * @param Helper_Abstract_Addon $addon + * @param string $license_key + * + * @return bool + * + * @since 5.2 + */ + protected function deactivate_license_key( Helper_Abstract_Addon $addon, $license_key ) { + $response = wp_remote_post( + $this->data->store_url, + [ + 'timeout' => 15, + 'body' => [ + 'edd_action' => 'deactivate_license', + 'license' => $license_key, + 'item_name' => urlencode( $addon->get_short_name() ), // the name of our product in EDD + 'url' => home_url(), + ], + ] + ); + + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { + return false; + } + + /* Verify license is now deactivated */ + $license_data = json_decode( wp_remote_retrieve_body( $response ) ); + if ( ! isset( $license_data->license ) || $license_data->license !== 'deactivated' ) { + return false; + } + + $addon->delete_license_info(); + + $this->log->addNotice( + 'License successfully deactivated', + [ + 'slug' => $addon->get_slug(), + 'license' => $license_key, + ] + ); + + return true; + } +} diff --git a/src/Api/V1/Migration/Multisite/Api_Migration_v4.php b/src/Api/V1/Migration/Multisite/Api_Migration_v4.php new file mode 100644 index 000000000..386fdbaeb --- /dev/null +++ b/src/Api/V1/Migration/Multisite/Api_Migration_v4.php @@ -0,0 +1,216 @@ +. +*/ + +/** + * Class ApiMigrationv4Endpoint + * + * @package GFPDF\Plugins\GravityPDF\API + */ +class Api_Migration_v4 extends Base_Api { + + /** + * Holds our log class + * + * @var \Monolog\Logger + * + * @since 4.0 + */ + public $log; + + /** + * Holds our Helper_Abstract_Options / Helper_Options_Fields object + * Makes it easy to access global PDF settings and individual form PDF settings + * + * @var \GFPDF\Helper\Helper_Options_Fields + * + * @since 4.0 + */ + protected $options; + + /** + * Holds our Helper_Data object + * which we can autoload with any data needed + * + * @var \GFPDF\Helper\Helper_Data + * + * @since 4.0 + */ + protected $data; + + /** + * Holds our Helper_Data object + * which we can autoload with any data needed + * + * @var \GFPDF\Helper\Helper_Migration + * + * @since 5.2 + */ + protected $migration; + + /** + * Api_Migration_v4 constructor. + * + * @param Helper_Abstract_Options $options + * @param Helper_Data $data + * @param Helper_Migration $migration + * + * @since 5.2 + */ + public function __construct( LoggerInterface $log, Helper_Abstract_Options $options, Helper_Data $data, Helper_Migration $migration ) { + $this->log = $log; + $this->options = $options; + $this->data = $data; + $this->migration = $migration; + } + + /** + * Register our Multisite Migration endpoint + * + * @Internal Use this endpoint register multisite migration + * + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/migration/multisite/', + [ + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'multisite_v3_migration' ], + + 'permission_callback' => function() { + return $this->has_capabilities( 'manage_sites' ); + }, + ] + ); + } + + /** + * Register our PDF save font endpoint + * + * @param WP_REST_Request $request + * + * @return \WP_REST_Response + * + * @since 5.2 + */ + public function multisite_v3_migration( \WP_REST_Request $request ) { + $params = $request->get_json_params(); + + /* Ensure multisite website */ + if ( ! is_multisite() ) { + + return new \WP_Error( 'multisite_v3_migration', 'You are not authorized to perform this action. Please try again.', [ 'status' => 401 ] ); + } + + /* Check there's a configuration file to migrate */ + $blog_id = ( isset( $params['blog_id'] ) ) ? (int) $params['blog_id'] : 0; + + /* Check if we have a config file that should be migrated */ + $path = $this->data->template_location . $blog_id . '/'; + + if ( ! is_file( $path . 'configuration.php' ) ) { + + $this->log->addError( 'Configuration file not found', $path ); + + return new \WP_Error( 'no_configuration_file', 'No configuration file found for site #%s', [ 'status' => 404 ] ); + } + + /* Setup correct migration settings */ + switch_to_blog( $blog_id ); + $this->data->multisite_template_location = $path; + + /* Do migration */ + if ( ! $this->migrate_v3( $path ) ) { + + $this->log->addError( 'AJAX Endpoint Failed' ); + + return new \WP_Error( 'unable_to_connect_to_server', 'Database import problem for site #%s', [ 'status' => 500 ] ); + } + + /* migration successful */ + return [ 'message' => 'Migration completed successfully' ]; + } + + /** + * Does the migration and notice clearing (if unsuccessful) + * + * @param string $path Path to the current site's template directory + * + * @return boolean + * + * @since 5.2 + */ + private function migrate_v3( $path ) { + + /* Start migration */ + if ( $this->migration->begin_migration() ) { + /** + * Migration Successful. + * + * If there was a problem removing the configuration file we'll automatically prevent the migration message displaying again + */ + if ( is_file( $path . 'configuration.php' ) ) { + $this->dismiss_notice( 'migrate_v3_to_v4' ); + } + + return true; + } + + return false; + } + + /** + * Mark the current notice as being dismissed + * + * @param string $type The current notice ID + * + * @return void + * + * @since 5.2 + */ + public function dismiss_notice( $type ) { + + $dismissed_notices = $this->options->get_option( 'action_dismissal', [] ); + $dismissed_notices[ $type ] = $type; + $this->options->update_option( 'action_dismissal', $dismissed_notices ); + } + +} diff --git a/src/Api/V1/Pdf/Settings/Api_Pdf_Settings.php b/src/Api/V1/Pdf/Settings/Api_Pdf_Settings.php new file mode 100644 index 000000000..0193f3e72 --- /dev/null +++ b/src/Api/V1/Pdf/Settings/Api_Pdf_Settings.php @@ -0,0 +1,137 @@ +. +*/ + +/** + * Class ApiPdfSettingEndpoint + * + * @package GFPDF\Plugins\GravityPDF\API + */ +class Api_Pdf_Settings extends Base_Api { + + /** + * @var string + * @since 5.2 + */ + protected $test_file_path; + + /** + * @var string + * @since 5.2 + */ + protected $test_file_url; + + /** + * Api_Pdf_Settings constructor. + * + * @param string $test_file_path + * @param string $test_file_url + */ + public function __construct( $test_file_path, $test_file_url ) { + $this->test_file_path = $test_file_path; + $this->test_file_url = $test_file_url; + } + + /** + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/pdf/settings/', + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'check_temporary_pdf_directory_security' ], + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_view_settings' ); + }, + ] + ); + } + + /** + * Create a test file in the temporary folder and try read its content via a remote GET request + * + * @return bool|WP_Error + * + * @since 5.2 + */ + public function check_temporary_pdf_directory_security() { + try { + $this->create_test_file(); + + if ( $this->test_file_url === false ) { + throw new GravityPdfRuntimeException( 'Could not determine the temporary file URL' ); + } + + $response = wp_remote_get( $this->test_file_url ); + + if ( is_wp_error( $response ) ) { + throw new GravityPdfRuntimeException( 'Remote request for temporary file URL failed' ); + } + + /* File was successfully accessed over public URL. Test failed */ + if ( wp_remote_retrieve_response_code( $response ) === 200 && wp_remote_retrieve_response_message( $response ) === 'failed-if-read' ) { + return false; + } + + return true; + } catch ( GravityPdfRuntimeException $e ) { + return new WP_Error( 'runtime_error', $e->getMessage(), [ 'status' => 500 ] ); + } + } + + /** + * Create the temporary test file + * + * @return bool + * @throws GravityPdfRuntimeException + * + * @since 5.2 + */ + protected function create_test_file() { + if ( ! is_file( $this->test_file_path ) ) { + file_put_contents( $this->test_file_path, 'failed-if-read' ); + + if ( ! is_file( $this->test_file_path ) ) { + throw new GravityPdfRuntimeException( 'Could not create temporary test file' ); + } + } + + return true; + } +} diff --git a/src/Api/V1/Security/Tmp/Api_Security_Tmp_Directory.php b/src/Api/V1/Security/Tmp/Api_Security_Tmp_Directory.php new file mode 100644 index 000000000..5ea45b18d --- /dev/null +++ b/src/Api/V1/Security/Tmp/Api_Security_Tmp_Directory.php @@ -0,0 +1,193 @@ +. +*/ + +/** + * Class ApiSecurityTmpDirectoryEndpoint + * + * @package GFPDF\Plugins\GravityPDF\API + */ +class Api_Security_Tmp_Directory extends Base_Api { + + /** + * Holds our log class + * + * @var \Monolog\Logger + * + * @since 4.0 + */ + public $log; + + /** + * @var boolean + * + * @since 5.2 + */ + protected $has_access = true; + + /** + * @var string the temporary text file to write to directory + * + * @since 5.2 + */ + protected $tmp_test_file = 'public_tmp_directory_test.txt'; + + /** + * Holds our Helper_Misc object + * Makes it easy to access common methods throughout the plugin + * + * @var \GFPDF\Helper\Helper_Misc + * + * @since 5.2 + */ + protected $misc; + + /** + * Holds our Helper_Data object + * which we can autoload with any data needed + * + * @var \GFPDF\Helper\Helper_Data + * + * @since 5.2 + */ + protected $data; + + /** + * Api_Security_Tmp_Directory constructor. + * + * @param Helper_Data $data + * + * @since 5.2 + */ + public function __construct( LoggerInterface $log, Helper_Misc $misc, Helper_Data $data ) { + $this->log = $log; + $this->misc = $misc; + $this->data = $data; + + } + + /** + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/security/tmp/', + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'check_tmp_pdf_security' ], + 'permission_callback' => function() { + return $this->has_capabilities( 'gravityforms_view_settings' ); + }, + ] + ); + } + + /** + * Create a file in our tmp directory and check if it is publically accessible (i.e no .htaccess protection) + * + * @param $_POST ['nonce'] + * + * @return boolean + * + * @since 5.2 + */ + public function check_tmp_pdf_security( ) { + + /* check if we can access tmp directory */ + $result = $this->test_public_tmp_directory_access(); + + if (!$result) { + + return new \WP_Error( 'check_tmp_pdf_security', 'There was an error creating access to tmp directory', [ 'status' => 500 ] ); + } + + return [ 'message' => 'Tmp Directory Accessible' ]; + } + + /** + * Create a file in our tmp directory and verify if it's protected from the public + * + * @return boolean + * + * @since 5.2 + */ + public function test_public_tmp_directory_access() { + + /* create our file */ + file_put_contents( $this->data->template_tmp_location . $this->tmp_test_file, 'failed-if-read' ); + + /* verify it exists */ + if ( is_file( $this->data->template_tmp_location . $this->tmp_test_file ) ) { + + /* Run our test */ + $site_url = $this->misc->convert_path_to_url( $this->data->template_tmp_location ); + + if ( $site_url !== false ) { + + $response = wp_remote_get( $site_url . $this->tmp_test_file ); + + if ( ! is_wp_error( $response ) ) { + + /* Check if the web server responded with a OK status code and we can read the contents of our file, then fail our test */ + if ( isset( $response['response']['code'] ) && $response['response']['code'] === 200 && + isset( $response['body'] ) && $response['body'] === 'failed-if-read' + ) { + $response_object = $response['http_response']; + $raw_response = $response_object->get_response_object(); + $this->log->warning( + 'PDF temporary directory not protected', + [ + 'url' => $raw_response->url, + 'status_code' => $raw_response->status_code, + 'response' => $raw_response->raw, + ] + ); + + $this->has_access = false; + } + } + } + } + + /* Cleanup our test file */ + @unlink( $this->data->template_tmp_location . $this->tmp_test_file ); + + return $this->has_access; + } +} diff --git a/src/Api/V1/Template/Api_Template.php b/src/Api/V1/Template/Api_Template.php new file mode 100644 index 000000000..c43c6736e --- /dev/null +++ b/src/Api/V1/Template/Api_Template.php @@ -0,0 +1,466 @@ +. +*/ + +/** + * Class ApiTemplateEndpoint + * + * @package GFPDF\Plugins\GravityPDF\API + */ +class Api_Template extends Base_Api { + + /** + * Holds our log class + * + * @var \Monolog\Logger + * + * @since 4.0 + */ + public $log; + + /** + * Holds our Helper_Misc object + * Makes it easy to access common methods throughout the plugin + * + * @var \GFPDF\Helper\Helper_Misc + * + * @since 5.2 + */ + protected $misc; + + /** + * Holds our Helper_Data object + * which we can autoload with any data needed + * + * @var \GFPDF\Helper\Helper_Data + * + * @since 5.2 + */ + protected $data; + + /** + * Holds our Helper_Abstract_Options / Helper_Options_Fields object + * Makes it easy to access global PDF settings and individual form PDF settings + * + * @var \GFPDF\Helper\Helper_Options_Fields + * + * @since 5.2 + */ + protected $options; + + /** + * Holds our Helper_Templates object + * used to ease access to our PDF templates + * + * @var \GFPDF\Helper\Helper_Templates + * + * @since 5.2 + */ + public $templates; + + /** + * Api_Pdf_Settings constructor. + * + * @param Helper_Misc $misc + * + * @param Helper_Data $data + * + * @since 5.2 + */ + public function __construct( LoggerInterface $log, Helper_Misc $misc, Helper_Data $data, Helper_Abstract_Options $options, Helper_Templates $templates ) { + $this->log = $log; + $this->misc = $misc; + $this->data = $data; + $this->options = $options; + $this->templates = $templates; + } + + /** + * Register our PDF save font endpoint + * + * @Internal Use this endpoint to save fonts + * + * @since 5.2 + */ + public function register() { + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/template/', + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'ajax_process_build_template_options_html' ], + + 'permission_callback' => function() { + return current_user_can( 'gravityforms_edit_settings' ); + }, + ] + ); + + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/template/', + [ + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'ajax_process_uploaded_template' ], + + 'permission_callback' => function() { + return current_user_can( 'gravityforms_edit_settings' ); + }, + ] + ); + + register_rest_route( + self::ENTRYPOINT . '/' . self::VERSION, + '/template/', + [ + 'methods' => \WP_REST_Server::DELETABLE, + 'callback' => [ $this, 'ajax_process_delete_template' ], + + 'permission_callback' => function() { + return current_user_can( 'gravityforms_edit_settings' ); + }, + ] + ); + + } + + /** + * AJAX Endpoint for building the template select box options (so we don't have to recreate the logic in React) + * + * @param string $_POST ['nonce'] a valid nonce + * + * @since 5.2 + */ + public function ajax_process_build_template_options_html() { + + $registered_settings = $this->options->get_registered_fields(); + + $template_settings = $registered_settings['form_settings']['template']; + + header( 'Content-Type: application/text' ); + echo $this->options->build_options_for_select( $template_settings['options'], $this->options->get_form_value( $template_settings ) ); + + /* Okay Response */ + return [ 'message' => 'Template options built successfully' ]; + } + + /** + * AJAX Endpoint to handle the uploading of PDF templates + * + * @param string $_POST ['nonce'] a valid nonce + * + * @since 5.2 + */ + public function ajax_process_uploaded_template() { + + /* Validate uploaded file */ + try { + $storage = new FileSystem( $this->data->template_tmp_location ); + $file = new File( 'template', $storage ); + $zip_path = $this->move_template_to_tmp_dir( $file ); + } catch ( Exception $e ) { + $this->log->addWarning( + 'File validation and move failed', + [ + 'file' => $_FILES, + 'error' => $e->getMessage(), + ] + ); + + /* Bad Request */ + return new \WP_Error( 'validate_upload_file', 'File validation and move failed', [ 'status' => 400 ] ); + } + + /* Unzip and check the PDF templates look valid */ + try { + $this->unzip_and_verify_templates( $zip_path ); + } catch ( Exception $e ) { + $this->cleanup_template_files( $zip_path ); + + $this->log->addWarning( + 'File validation and move failed', + [ + 'file' => $_FILES, + 'error' => $e->getMessage(), + ] + ); + + header( 'Content-Type: application/json' ); + $error = json_encode( [ 'error' => $e->getMessage() ] ); + + /* Bad Response */ + return new \WP_Error( 'validate_upload_file', $error, [ 'status' => 400 ] ); + } + + /* Copy all the files to the active PDF working directory */ + $unzipped_dir_name = $this->get_unzipped_dir_name( $zip_path ); + $template_path = $this->templates->get_template_path(); + + $results = $this->misc->copyr( $unzipped_dir_name, $template_path ); + + /* Get the template headers now all the files are in the right location */ + $this->templates->flush_template_transient_cache(); + $headers = $this->get_template_info( glob( $unzipped_dir_name . '*.php' ) ); + + /* Fix template path */ + $headers = array_map( + function( $header ) use ( $unzipped_dir_name, $template_path ) { + $header['path'] = str_replace( $unzipped_dir_name, $template_path, $header['path'] ); + return $header; + }, + $headers + ); + + /* Run PDF template SetUp method if required */ + $this->maybe_run_template_setup( $headers ); + + /* Cleanup tmp uploaded files */ + $this->cleanup_template_files( $zip_path ); + + if ( is_wp_error( $results ) ) { + /* Internal Server Error */ + return new \WP_Error( 'unable_to_copy_files', $results, [ 'status' => 500 ] ); + } + + /* Return newly-installed template headers */ + header( 'Content-Type: application/json' ); + echo json_encode( + [ + 'templates' => $headers, + ] + ); + + /* Okay Response */ + // wp_die( '', 200 ); + return [ 'message' => 'Template uploaded successfully' ]; + } + + /** + * Extracts the zip file, checks there are valid PDF template files found and retreives information about them + * + * @param $zip_path The full path to the zip file + * + * @return array The PDF template headers from the valid files + * + * @throws Exception Thrown if a PDF template file isn't valid + * + * @since 5.2 + */ + public function unzip_and_verify_templates( $zip_path ) { + $this->enable_wp_filesystem(); + + $dir = $this->get_unzipped_dir_name( $zip_path ); + $results = unzip_file( $zip_path, $dir ); + + /* If the unzip failed we'll throw an error */ + if ( is_wp_error( $results ) ) { + return new \WP_Error( 'unzip_failed', $results->get_error_message(), [ 'status' => 500 ] ); + } + + /* Check unziped templates for a valid v4 header, or v3 string pattern */ + $files = glob( $dir . '*.php' ); + + if ( ! is_array( $files ) || sizeof( $files ) === 0 ) { + return new \WP_Error( 'no_valid_template_found', 'No valid PDF template found in Zip archive.', [ 'status' => 404 ] ); + } + + $this->check_for_valid_pdf_templates( $files ); + } + + /** + * Remove the zip file and the unzipped directory + * + * @param $zip_path The full path to the zip file + * + * @since 5.2 + */ + public function cleanup_template_files( $zip_path ) { + $dir = $this->get_unzipped_dir_name( $zip_path ); + + $this->misc->rmdir( $dir ); + unlink( $zip_path ); + } + + /** + * Gets the full path to a new directory which is based on the zip file's unique name + * + * @param string $zip_path The full path to the zip file + * + * @return string + * + * @since 5.2 + */ + public function get_unzipped_dir_name( $zip_path ) { + return dirname( $zip_path ) . '/' . basename( $zip_path, '.zip' ) . '/'; + } + + /** + * Get the PDF template info to pass to our application + * + * @param array $files + * + * @return array + * + * @since 5.2 + */ + public function get_template_info( $files = [] ) { + return array_map( + function( $file ) { + return $this->templates->get_template_info_by_path( $file ); + }, + $files + ); + } + + /** + * Execute the setUp method on any templates that impliment it + * + * @param array $headers Contains the array returned from $this->get_template_info() + * + * @since 5.2 + */ + public function maybe_run_template_setup( $headers = [] ) { + foreach ( $headers as $template ) { + $config = $this->templates->get_config_class( $template['id'] ); + + /* Check if the PDF config impliments our Setup/TearDown interface and run the tear down */ + if ( in_array( 'GFPDF\Helper\Helper_Interface_Setup_TearDown', class_implements( $config ) ) ) { + $config->setUp(); + } + } + } + + /** + * Sniffs the PHP file for signs that it's a valid Gravity PDF tempalte file + * + * @param array $files The full paths to the PDF templates + * + * @return array The PDF template header information + * + * @throws Exception Thrown if file found not to be valid + * + * @since 5.2 + */ + public function check_for_valid_pdf_templates( $files = [] ) { + foreach ( $files as $file ) { + + /* Check if we have a valid v4 template header in the file */ + $info = $this->templates->get_template_info_by_path( $file ); + + if ( ! isset( $info['template'] ) || strlen( $info['template'] ) === 0 ) { + /* Check if it's a v3 template */ + $fp = fopen( $file, 'r' ); + $file_data = fread( $fp, 8192 ); + fclose( $fp ); + + /* Check the first 8kiB contains the string RGForms or GFForms, which signifies our v3 templates */ + if ( strpos( $file_data, 'RGForms' ) === false && strpos( $file_data, 'GFForms' ) === false ) { + // throw new Exception( sprintf( esc_html__( 'The PHP file %s is not a valid PDF Template.', 'gravity-forms-pdf-extended' ), + // basename( $file ) ) ); + return new \WP_Error( 'invalid_template', 'The PHP file is not a valid PDF Template.', [ 'status' => 422 ] ); + } + } + } + } + + + /** + * Register our PDF save font endpoint + * + * @param WP_REST_Request $request + * + * @return \WP_REST_Response + * + * @since 5.2 + */ + public function ajax_process_delete_template( \WP_REST_Request $request ) { + + /* get the json parameter */ + $params = $request->get_json_params(); + + $template_id = ( isset( $params['id'] ) ) ? $params['id'] : ''; + + /* Get all the necessary PDF template files to delete */ + try { + $this->delete_template( $template_id ); + } catch ( Exception $e ) { + /* Bad Request */ + return new \WP_Error( 'process_delete_template', $e->getMessage(), [ 'status' => 400 ] ); + } + + $this->templates->flush_template_transient_cache(); + + // header( 'Content-Type: application/json' ); + // echo json_encode( true ); + + /* Okay Response */ + return [ 'message' => 'Template deleted successfully' ]; + } + + /** + * Delete's a PDF templates files + * + * @param string $template_id + * + * @throws Exception + * + * @since 5.2 + */ + public function delete_template( $template_id ) { + try { + $files = $this->templates->get_template_files_by_id( $template_id ); + $config = $this->templates->get_config_class( $template_id ); + + /* Check if the PDF config impliments our Setup/TearDown interface and run the tear down */ + if ( in_array( 'GFPDF\Helper\Helper_Interface_Setup_TearDown', class_implements( $config ) ) ) { + $config->tearDown(); + } + + /* Remove the PDF template files */ + foreach ( $files as $file ) { + unlink( $file ); + } + } catch ( Exception $e ) { + // throw $e; /* throw further down the chain */ + return new \WP_Error( 'delete_template', $e->getMessage(), [ 'status' => 500 ] ); + } + } + +} diff --git a/src/Exceptions/GravityPdfException.php b/src/Exceptions/GravityPdfException.php new file mode 100644 index 000000000..d3f4e9ea5 --- /dev/null +++ b/src/Exceptions/GravityPdfException.php @@ -0,0 +1,38 @@ +templates ); + /* Set up our migration object */ + $this->migration = new Helper\Helper_Migration( + $this->gform, + $this->log, + $this->data, + $this->options, + $this->misc, + $this->notices, + $this->templates + ); + /* Setup our Singleton object */ $this->singleton = new Helper\Helper_Singleton(); @@ -232,6 +257,7 @@ public function init() { $this->template_manager(); $this->load_core_font_handler(); $this->load_debug(); + $this->api(); /* Add localisation support */ $this->add_localization_support(); @@ -266,6 +292,7 @@ public function add_actions() { /* Cache our Gravity PDF Settings and register our settings fields with the Options API */ add_action( 'init', [ $this, 'init_settings_api' ], 1 ); add_action( 'admin_init', [ $this, 'setup_settings_fields' ], 1 ); + } /** @@ -277,6 +304,7 @@ public function add_actions() { */ public function add_filters() { + /* Automatically handle GF noconflict mode */ add_filter( 'gform_noconflict_scripts', [ $this, 'auto_noconflict_scripts' ] ); add_filter( 'gform_noconflict_styles', [ $this, 'auto_noconflict_styles' ] ); @@ -891,6 +919,34 @@ public function load_debug() { $this->singleton->add_class( $class ); } + public function api() { + + $test_file_path = $this->data->template_font_location . 'public_tmp_directory_test.txt'; + $test_file_url = $this->misc->convert_path_to_url( $test_file_path ); + + $apis = [ + new Api\V1\Fonts\Core\Api_Fonts_Core( $this->log, $this->data->template_font_location ), + new Api\V1\Fonts\Api_Fonts( $this->log, $this->misc, $this->data, $this->options ), + new Api\V1\License\Api_License( $this->log, $this->data ), + new Api\V1\Migration\Multisite\Api_Migration_v4( $this->log, $this->options, $this->data, $this->migration ), + new Api\V1\Pdf\Settings\Api_Pdf_Settings( $test_file_path, $test_file_url ), + new Api\V1\Security\Tmp\Api_Security_Tmp_Directory( $this->log, $this->misc, $this->data ), + new Api\V1\Template\Api_Template( $this->log, $this->misc, $this->data, $this->options, $this->templates ), + ]; + + foreach ( $apis as $api ) { + $trait = class_uses( $api ); + if ( isset( $trait['GFPDF\Helper\Helper_Trait_Logger'] ) ) { + $api->set_logger( $this->log ); + } + + $api->init(); + + $this->singleton->add_class( $api ); + } + + } + /** * Initialise our background PDF processing handler * diff --git a/tests/phpunit/unit-tests/Api/V1/Fonts/Core/Test_Fonts_Core.php b/tests/phpunit/unit-tests/Api/V1/Fonts/Core/Test_Fonts_Core.php new file mode 100644 index 000000000..13737d63c --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/Fonts/Core/Test_Fonts_Core.php @@ -0,0 +1,131 @@ +log = GPDFAPI::get_log_class(); + + $this->class = new Api_Fonts_Core( $this->log, '' ); + $this->class->init(); + + parent::setUp(); + } + + /** + * Test our endpoints are registered correctly + * + * @since 5.2 + */ + public function test_rest_api_font_core_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/fonts/core', $wp_rest_server->get_routes() ); + } + + /** + * @since 5.2 + */ + public function test_save_core_font() { + + $request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/gravity-pdf/v1/fonts/core' ); + + $request->set_body_params( [ + 'font_name' => '', + ] ); + + /* Test empty font name */ + $response = $this->class->save_core_font( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'download_and_save_font' ); + $this->assertSame( 400, $res['status'] ); + } + + /* Mock remote request and simulate success */ + $request->set_body_params( [ + 'font_name' => 'Test', + ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->save_core_font( $request ); + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + + protected function stub_remote_request( $response ) { + + } + + protected function unstub_remote_request() { + + } + +} diff --git a/tests/phpunit/unit-tests/Api/V1/Fonts/Test_Api_Fonts.php b/tests/phpunit/unit-tests/Api/V1/Fonts/Test_Api_Fonts.php new file mode 100644 index 000000000..66203cb20 --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/Fonts/Test_Api_Fonts.php @@ -0,0 +1,168 @@ +log = GPDFAPI::get_log_class(); + $this->options = GPDFAPI::get_options_class(); + $this->data = GPDFAPI::get_data_class(); + $this->misc = GPDFAPI::get_misc_class(); + + $this->class = new Api_Fonts( $this->log, $this->misc, $this->data, $this->options ); + $this->class->init(); + + parent::setUp(); + } + + /** + * Test our endpoints are registered correctly + * + * @since 5.2 + */ + public function test_rest_api_fonts_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/fonts', $wp_rest_server->get_routes() ); + } + + /** + * @since 5.2 + */ + public function test_rest_api_save_font() { + + $request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/gravity-pdf/v1/fonts/save_font' ); + + $request->set_body_params( [ + 'payload' => [], + ] ); + + /* Test empty font name */ + $response = $this->class->save_font( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'required_fields_missing' ); + $this->assertSame( 400, $res['status'] ); + } + + /* Mock remote request and simulate success */ + $request->set_body_params( [ + 'payload' => 'Test', + ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->save_font( $request ); + + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + + + /** + * @since 5.2 + */ + public function test_rest_api_delete_font() { + + $request = new WP_REST_Request( \WP_REST_Server::DELETABLE, '/gravity-pdf/v1/fonts/delete_font' ); + + $request->set_body_params( [ + 'payload' => '', + ] ); + + /* Test empty font name */ + $response = $this->class->delete_font( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'delete_font' ); + $this->assertSame( 500, $res['status'] ); + } + + /* Mock remote request and simulate success */ + $request->set_body_params( [ + 'payload' => 'Test', + ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->delete_font( $request ); + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + +} diff --git a/tests/phpunit/unit-tests/Api/V1/License/Test_Api_License.php b/tests/phpunit/unit-tests/Api/V1/License/Test_Api_License.php new file mode 100644 index 000000000..e2ca84e77 --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/License/Test_Api_License.php @@ -0,0 +1,156 @@ +data = GPDFAPI::get_data_class(); + $this->class = new Api_License( GPDFAPI::get_log_class(), $this->data ); + $this->class->init(); + + parent::setUp(); + } + + /** + * @since 5.2 + */ + public function test_rest_api_license_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/license/(?P\d+)/deactivate', $wp_rest_server->get_routes() ); + } + + /** + * @param array $data + * + * @return WP_REST_Request + * + * @since 5.2 + */ + protected function get_request( $data ) { + $request = new WP_REST_Request(); + $request->set_body( json_encode( $data ) ); + $request->set_header( 'content-type', 'application/json' ); + $request->get_json_params(); + + return $request; + } + + /** + * @since 5.2 + */ + public function test_deactivate_license_key_validation() { + $request = $this->get_request( [ 'addon_name' => '', 'license' => '' ] ); + $response = $this->class->process_license_deactivation( $request ); + $this->assertSame( 400, $response->get_error_data( 'license_deactivation_fields_missing' )['status'] ); + + /* Test unregistered addon */ + $request = $this->get_request( [ 'addon_name' => 'test', 'license' => '12345' ] ); + $response = $this->class->process_license_deactivation( $request ); + $this->assertSame( 404, $response->get_error_data( 'license_deactivation_addon_not_found' )['status'] ); + } + + /** + * @since 5.2 + */ + public function test_deactivate_license_key_api() { + $request = $this->get_request( [ 'addon_name' => 'test', 'license' => '12345' ] ); + + /* Mock remote request and simulate success */ + $this->data->addon['test'] = new TestAddon( 'test', 'Test', 'Gravity PDF', '1.0', '', $this->data, GPDFAPI::get_options_class(), new Helper_Singleton(), new Helper_Logger( 'test', 'Test' ), GPDFAPI::get_notice_class() ); + + $api_response = function() { + return new \WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->process_license_deactivation( $request ); + $this->assertSame( 400, $response->get_error_data( 'license_deactivation_schedule_license_check' )['status'] ); + + remove_filter( 'pre_http_request', $api_response ); + + /* Get a success test */ + $api_response = function() { + return [ + 'response' => [ 'code' => 200 ], + 'body' => json_encode( [ 'license' => 'deactivated' ] ), + ]; + }; + + add_filter( 'pre_http_request', $api_response ); + $response = $this->class->process_license_deactivation( $request ); + $this->assertArrayHasKey( 'success', $response ); + remove_filter( 'pre_http_request', $api_response ); + } +} + +class TestAddon extends Helper_Abstract_Addon { + public function plugin_updater() { + + } +} diff --git a/tests/phpunit/unit-tests/Api/V1/Migration/Multisite/Test_Api_Migration_v4.php b/tests/phpunit/unit-tests/Api/V1/Migration/Multisite/Test_Api_Migration_v4.php new file mode 100644 index 000000000..7e85634ce --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/Migration/Multisite/Test_Api_Migration_v4.php @@ -0,0 +1,128 @@ +data = GPDFAPI::get_data_class(); + $this->class = new Api_Migration_v4( GPDFAPI::get_log_class(), GPDFAPI::get_options_class(), $this->data, GPDFAPI::get_migration_class() ); + + $this->class->init(); + + parent::setUp(); + } + + /** + * @since 5.2 + */ + public function test_rest_api_license_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/migration/multisite', $wp_rest_server->get_routes() ); + } + + /** + * @param array $data + * + * @return WP_REST_Request + * + * @since 5.2 + */ + protected function get_request( $data ) { + $request = new WP_REST_Request(); + $request->set_body( json_encode( $data ) ); + $request->set_header( 'content-type', 'application/json' ); + $request->get_json_params(); + + return $request; + } + + /** + * @since 5.2 + */ + public function test_multisite_v3_migration() { + + $request = $this->get_request( [ 'addon_name' => '', 'license' => '' ] ); + $response = $this->class->multisite_v3_migration( $request ); + + $this->assertSame( 400, $response->get_error_data( 'license_deactivation_fields_missing' )['status'] ); + + /* Test unregistered addon */ + $request = $this->get_request( [ 'addon_name' => 'test', 'license' => '12345' ] ); + $response = $this->class->process_license_deactivation( $request ); + $this->assertSame( 404, $response->get_error_data( 'license_deactivation_addon_not_found' )['status'] ); + } + + +} + +// class TestAddon extends Helper_Abstract_Addon { +// public function plugin_updater() { + +// } +// } diff --git a/tests/phpunit/unit-tests/Api/V1/Pdf/Settings/Test_Api_Pdf_Settings.php b/tests/phpunit/unit-tests/Api/V1/Pdf/Settings/Test_Api_Pdf_Settings.php new file mode 100644 index 000000000..a95457737 --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/Pdf/Settings/Test_Api_Pdf_Settings.php @@ -0,0 +1,119 @@ +class = new Api_Pdf_Settings( GPDFAPI::get_log_class(), GPDFAPI::get_misc_class(), $this->template_font_location ); + $this->class->init(); + + parent::setUp(); + } + + /** + * @since 5.2 + */ + public function test_rest_api_license_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/pdf/settings', $wp_rest_server->get_routes() ); + } + + /** + * @since 5.2 + */ + public function test_create_public_tmp_directory_test_file() { + /* Test able to write to directory */ + $response = $this->class->create_public_tmp_directory_test_file(); + $this->assertTrue( $response ); + + /* Test unable to write to directory */ + // $this->assertFileExists( $this->template_font_location . $this->tmp_test_file ); + } + + /** + * @since 5.2 + */ + public function test_check_tmp_pdf_security( ) { + + $response = $this->class->check_tmp_pdf_security(); + + /* Test unable to conver path to URL */ + $this->assertSame( 404, $response->get_error_data( 'convert_path_to_url' )['status'] ); + + /* Test unable to access generated URL */ + // $this->assertSame( 400, $response->get_error_data( 'wp_remote_get_response' )['status'] ); + + // Test able to read the content of file + } + +} diff --git a/tests/phpunit/unit-tests/Api/V1/Template/Test_Api_Template.php b/tests/phpunit/unit-tests/Api/V1/Template/Test_Api_Template.php new file mode 100644 index 000000000..e9201f101 --- /dev/null +++ b/tests/phpunit/unit-tests/Api/V1/Template/Test_Api_Template.php @@ -0,0 +1,211 @@ +log = GPDFAPI::get_log_class(); + $this->options = GPDFAPI::get_options_class(); + $this->data = GPDFAPI::get_data_class(); + $this->misc = GPDFAPI::get_misc_class(); + + $this->class = new Api_Template( $this->log, $this->misc, $this->data, $this->options ); + $this->class->init(); + + parent::setUp(); + } + + /** + * Test our endpoints are registered correctly + * + * @since 5.2 + */ + public function test_rest_api_template_endpoints() { + $wp_rest_server = rest_get_server(); + do_action( 'rest_api_init' ); + + $this->assertContains( 'gravity-pdf/v1', $wp_rest_server->get_namespaces() ); + $this->assertArrayHasKey( '/gravity-pdf/v1/template', $wp_rest_server->get_routes() ); + } + + /** + * @since 5.2 + */ + public function test_ajax_process_build_template_options_html() { + + $request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/gravity-pdf/v1/template' ); + + // $request->set_body_params( [ + // 'font_name' => '', + // ] ); + + /* Test empty font name */ + $response = $this->class->ajax_process_build_template_options_html( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'ajax_process_build_template_options_html' ); + $this->assertSame( 400, $res['status'] ); + } + + /* Mock remote request and simulate success */ + // $request->set_body_params( [ + // 'font_name' => 'Test', + // ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->save_core_font( $request ); + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + + + /** + * @since 5.2 + */ + public function test_ajax_process_uploaded_template() { + + $request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/gravity-pdf/v1/template' ); + + // $request->set_body_params( [ + // 'font_name' => '', + // ] ); + + /* Test empty font name */ + $response = $this->class->test_ajax_process_uploaded_template( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'test_ajax_process_uploaded_template' ); + $this->assertSame( 400, $res['status'] ); + } + + /* Mock remote request and simulate success */ + // $request->set_body_params( [ + // 'font_name' => 'Test', + // ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->save_core_font( $request ); + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + + + /** + * @since 5.2 + */ + public function test_ajax_process_delete_template() { + + $request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/gravity-pdf/v1/template' ); + + // $request->set_body_params( [ + // 'font_name' => '', + // ] ); + + /* Test empty font name */ + $response = $this->class->ajax_process_delete_template( $request ); + + if ( is_wp_error( $response ) ) { + $res = $response->get_error_data( 'ajax_process_delete_template' ); + $this->assertSame( 400, $res['status'] ); + } + + /* Mock remote request and simulate success */ + // $request->set_body_params( [ + // 'font_name' => 'Test', + // ] ); + + $api_response = function() { + return new WP_Error(); + }; + + add_filter( 'pre_http_request', $api_response ); + + $response = $this->class->save_core_font( $request ); + $this->assertTrue( is_wp_error( $response ) ); + + remove_filter( 'pre_http_request', $api_response ); + + } + + + protected function stub_remote_request( $response ) { + + } + + protected function unstub_remote_request() { + + } + +}