customize_offline_donations', true); $featureAnonymous = get_post_meta($formID, '_give_anonymous_donation', true); $featureCompany = get_post_meta($formID, '_give_company_field', true); $currency = $this->settingsRepository->get('currency') ?: 'USD'; $baseCountry = $this->settingsRepository->get('base_country') ?: 'US'; $baseState = $this->settingsRepository->get('base_state') ?: ''; $data = [ 'apiRoot' => esc_url_raw(rest_url()), 'apiNonce' => wp_create_nonce('wp_rest'), 'setupUrl' => SetupPage::getSetupPageEnabledOrDisabled() === SetupPage::ENABLED ? admin_url('edit.php?post_type=give_forms&page=give-setup') : DonationFormsAdminPage::getUrl(), 'formPreviewUrl' => $formPreviewUrl, 'localeCurrency' => $this->localeCollection->pluck('currency_code'), 'currencies' => FormatList::fromKeyValue(give_get_currencies_list()), 'currencySelected' => $currency, 'countries' => LocationList::getCountries(), 'countrySelected' => $baseCountry, 'states' => LocationList::getStates($baseCountry), 'stateSelected' => $baseState, 'features' => FormatList::fromValueKey( [ 'donation-goal' => ('enabled' === $featureGoal), 'donation-comments' => ('enabled' === $featureComments), 'terms-conditions' => ('enabled' === $featureTerms), 'offline-donations' => ('enabled' === $offlineDonations), 'anonymous-donations' => ('enabled' === $featureAnonymous), 'company-donations' => in_array($featureCompany, ['required', 'optional']), // Note: The company field has two values for enabled, "required" and "optional". ] ), 'causeTypes' => FormatList::fromKeyValue( include GIVE_PLUGIN_DIR . 'src/Onboarding/Config/CauseTypes.php' ), 'adminEmail' => $current_user->user_email, 'adminFirstName' => $current_user->first_name, 'adminLastName' => $current_user->last_name, 'adminUserID' => $current_user->ID, 'websiteUrl' => get_bloginfo('url'), 'websiteName' => get_bloginfo('sitename'), 'addons' => $this->onboardingSettingsRepository->get('addons') ?: [], ]; EnqueueScript::make( 'give-admin-onboarding-wizard-app', 'build/assets/dist/js/admin-onboarding-wizard.js' )->loadInFooter() ->registerTranslations() ->registerLocalizeData('giveOnboardingWizardData', $data) ->enqueue(); } public function redirect() { // Bail if no activation redirect if (!\Give_Cache::get('_give_activation_redirect', true) || wp_doing_ajax()) { return; } // Delete the redirect transient \Give_Cache::delete(\Give_Cache::get_key('_give_activation_redirect')); // Bail if activating from network, or bulk if (is_network_admin() || isset($_GET['activate-multi'])) { return; } $redirect = add_query_arg('page', 'give-onboarding-wizard', admin_url()); $upgrade = get_option('give_version_upgraded_from'); if (!$upgrade) { // First time install wp_safe_redirect($redirect); exit; } } } register_meta('user', 'marketing_optin', [ 'type' => 'string', 'show_in_rest' => true, 'single' => true, ]); gments, -1 )[0]; } else { $parsed['source'] = implode( '-', $segments ); } $rotation_marker = strrpos( $parsed['source'], '.', -1 ); if ( false !== $rotation_marker ) { $rotation = substr( $parsed['source'], -1 ); if ( is_numeric( $rotation ) ) { $parsed['rotation'] = intval( $rotation ); } $parsed['source'] = substr( $parsed['source'], 0, $rotation_marker ); } $parsed['file_id'] = static::generate_file_id( $parsed['source'], $parsed['rotation'], $parsed['created'] ); return $parsed; } /** * Generate a public ID for a log file based on its properties. * * The file ID is the basename of the file without the hash part. It allows us to identify a file without revealing * its full name in the filesystem, so that it's difficult to access the file directly with an HTTP request. * * @param string $source The source of the log entries contained in the file. * @param int|null $rotation Optional. The 0-based incremental rotation marker, if the file has been rotated. * Should only be a single digit. * @param int $created Optional. The date the file was created, as a Unix timestamp. * * @return string */ public static function generate_file_id( string $source, ?int $rotation = null, int $created = 0 ): string { $file_id = static::sanitize_source( $source ); if ( ! is_null( $rotation ) ) { $file_id .= '.' . $rotation; } if ( $created > 0 ) { $file_id .= '-' . gmdate( 'Y-m-d', $created ); } return $file_id; } /** * Generate a hash to use as the suffix on a log filename. * * @param string $file_id A file ID (file basename without the hash). * * @return string */ public static function generate_hash( string $file_id ): string { $key = Constants::get_constant( 'AUTH_SALT' ) ?? 'wc-logs'; return hash_hmac( 'md5', $file_id, $key ); } /** * Sanitize the source property of a log file. * * @param string $source The source of the log entries contained in the file. * * @return string */ public static function sanitize_source( string $source ): string { return sanitize_file_name( $source ); } /** * Parse the log file path and assign various properties to this class instance. * * @return void */ protected function ingest_path(): void { $parsed_path = static::parse_path( $this->path ); $this->source = $parsed_path['source']; $this->rotation = $parsed_path['rotation']; $this->created = $parsed_path['created']; $this->hash = $parsed_path['hash']; } /** * Check if the filename structure is in the expected format. * * @see parse_path(). * * @return bool */ public function has_standard_filename(): bool { return ! ! $this->get_hash(); } /** * Check if the file represented by the class instance is a file and is readable. * * @return bool */ public function is_readable(): bool { try { $filesystem = FilesystemUtil::get_wp_filesystem(); $is_readable = $filesystem->is_file( $this->path ) && $filesystem->is_readable( $this->path ); } catch ( Exception $exception ) { return false; } return $is_readable; } /** * Check if the file represented by the class instance is a file and is writable. * * @return bool */ public function is_writable(): bool { try { $filesystem = FilesystemUtil::get_wp_filesystem(); $is_writable = $filesystem->is_file( $this->path ) && $filesystem->is_writable( $this->path ); } catch ( Exception $exception ) { return false; } return $is_writable; } /** * Open a read-only stream for this file. * * @return resource|false */ public function get_stream() { if ( ! $this->is_readable() ) { return false; } if ( ! is_resource( $this->stream ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen -- No suitable alternative. $this->stream = fopen( $this->path, 'rb' ); } return $this->stream; } /** * Close the stream for this file. * * The stream will also close automatically when the class instance destructs, but this can be useful for * avoiding having a large number of streams open simultaneously. * * @return bool */ public function close_stream(): bool { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- No suitable alternative. return fclose( $this->stream ); } /** * Get the full absolute path of the file. * * @return string */ public function get_path(): string { return $this->path; } /** * Get the name of the file, with extension, but without full path. * * @return string */ public function get_basename(): string { return basename( $this->path ); } /** * Get the file's source property. * * @return string */ public function get_source(): string { return $this->source; } /** * Get the file's rotation property. * * @return int|null */ public function get_rotation(): ?int { return $this->rotation; } /** * Get the file's hash property. * * @return string */ public function get_hash(): string { return $this->hash; } /** * Get the file's public ID. * * @return string */ public function get_file_id(): string { $created = 0; if ( $this->has_standard_filename() ) { $created = $this->get_created_timestamp(); } $file_id = static::generate_file_id( $this->get_source(), $this->get_rotation(), $created ); return $file_id; } /** * Get the file's created property. * * @return int */ public function get_created_timestamp(): int { if ( ! $this->created && $this->is_readable() ) { $this->created = filectime( $this->path ); } return $this->created; } /** * Get the time of the last modification of the file, as a Unix timestamp. Or false if the file isn't readable. * * @return int|false */ public function get_modified_timestamp() { try { $filesystem = FilesystemUtil::get_wp_filesystem(); $timestamp = $filesystem->mtime( $this->path ); } catch ( Exception $exception ) { return false; } return $timestamp; } /** * Get the size of the file in bytes. Or false if the file isn't readable. * * @return int|false */ public function get_file_size() { try { $filesystem = FilesystemUtil::get_wp_filesystem(); if ( ! $filesystem->is_readable( $this->path ) ) { return false; } $size = $filesystem->size( $this->path ); } catch ( Exception $exception ) { return false; } return $size; } /** * Create and set permissions on the file. * * @return bool */ protected function create(): bool { try { $filesystem = FilesystemUtil::get_wp_filesystem(); $created = $filesystem->touch( $this->path ); $modded = $filesystem->chmod( $this->path ); } catch ( Exception $exception ) { return false; } return $created && $modded; } /** * Write content to the file, appending it to the end. * * @param string $text The content to add to the file. * * @return bool */ public function write( string $text ): bool { if ( '' === $text ) { return false; } if ( ! $this->is_writable() ) { $created = $this->create(); if ( ! $created || ! $this->is_writable() ) { return false; } } // Ensure content ends with a line ending. $eol_pos = strrpos( $text, PHP_EOL ); if ( false === $eol_pos || strlen( $text ) !== $eol_pos + 1 ) { $text .= PHP_EOL; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen -- No suitable alternative. $resource = fopen( $this->path, 'ab' ); mbstring_binary_safe_encoding(); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite -- No suitable alternative. $bytes_written = fwrite( $resource, $text ); reset_mbstring_encoding(); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- No suitable alternative. fclose( $resource ); if ( strlen( $text ) !== $bytes_written ) { return false; } return true; } /** * Rename this file with an incremented rotation number. * * @return bool True if the file was successfully rotated. */ public function rotate(): bool { if ( ! $this->is_writable() ) { return false; } $created = 0; if ( $this->has_standard_filename() ) { $created = $this->get_created_timestamp(); } if ( is_null( $this->get_rotation() ) ) { $new_rotation = 0; } else { $new_rotation = $this->get_rotation() + 1; } $new_file_id = static::generate_file_id( $this->get_source(), $new_rotation, $created ); $search = array( $this->get_file_id() ); $replace = array( $new_file_id ); if ( $this->has_standard_filename() ) { $search[] = $this->get_hash(); $replace[] = static::generate_hash( $new_file_id ); } $old_filename = $this->get_basename(); $new_filename = str_replace( $search, $replace, $old_filename ); $new_path = str_replace( $old_filename, $new_filename, $this->path ); try { $filesystem = FilesystemUtil::get_wp_filesystem(); $moved = $filesystem->move( $this->path, $new_path, true ); } catch ( Exception $exception ) { return false; } if ( ! $moved ) { return false; } $this->path = $new_path; $this->ingest_path(); return $this->is_readable(); } /** * Delete the file from the filesystem. * * @return bool True on success, false on failure. */ public function delete(): bool { try { $filesystem = FilesystemUtil::get_wp_filesystem(); $deleted = $filesystem->delete( $this->path, false, 'f' ); } catch ( Exception $exception ) { return false; } return $deleted; } }