Skip to content
wiki.fftac.org

Dictionariespage - Source Excerpt 02

Back to Dictionariespage

Summary

This source excerpt preserves a bounded section of Antichrist.net/wp-content/plugins/uaix-locale-router/src/Admin/DictionariesPage.php so readers can inspect the evidence without opening the full source file.

**Source path:** Antichrist.net/wp-content/plugins/uaix-locale-router/src/Admin/DictionariesPage.php

$contents = file_get_contents( $tmp_name );

		if ( false === $contents ) {
			Plugin::set_flash_notice( 'The uploaded file could not be read.', 'error' );
			wp_safe_redirect( AdminPages::page_url( 'uaixlr-dictionaries' ) );
			exit;
		}

		$result = DictionaryValidator::validate_json_string( $contents, $file_name );

		if ( self::has_error_issues( $result ) || empty( $result['localeTag'] ) ) {
			Plugin::set_flash_notice(
				sprintf( 'Dictionary upload failed. %d issue(s) were found.', isset( $result['issueCount'] ) ? $result['issueCount'] : 0 ),
				'error',
				self::upload_failure_notice_data( $result, $file_name )
			);
			wp_safe_redirect( AdminPages::page_url( 'uaixlr-dictionaries' ) );
			exit;
		}

		Plugin::ensure_upload_directories();

		$locale_tag = LocaleValidator::canonicalize_tag( $result['localeTag'] );
		$file_path  = DictionaryRepository::dictionary_path( $locale_tag );
		$written    = Json::write_file( $file_path, $result['normalized_dictionary'] );

		if ( ! $written ) {
			Plugin::set_flash_notice( 'The validated dictionary could not be written to the uploads directory.', 'error' );
			wp_safe_redirect( AdminPages::page_url( 'uaixlr-dictionaries' ) );
			exit;
		}

		$file_hash = hash( 'sha256', Json::encode( $result['normalized_dictionary'] ) );
		$manifest  = DictionaryRepository::dictionary_manifest();

		$manifest[ $locale_tag ] = array(
			'localeTag'            => $locale_tag,
			'fileName'             => $file_name,
			'fileHash'             => $file_hash,
			'templateVersion'      => isset( $result['templateVersion'] ) ? absint( $result['templateVersion'] ) : 1,
			'qualityStatus'        => isset( $result['qualityStatus'] ) ? (string) $result['qualityStatus'] : 'draft',
			'lastValidationStatus' => isset( $result['status'] ) ? (string) $result['status'] : 'draft',
			'issueCount'           => isset( $result['issueCount'] ) ? absint( $result['issueCount'] ) : 0,
			'lastIssues'           => isset( $result['issues'] ) ? $result['issues'] : array(),
			'uploadedAt'           => gmdate( DATE_ATOM ),
			'uploadedByUserId'     => get_current_user_id(),
		);

		DictionaryRepository::save_dictionary_manifest( $manifest );
		DictionaryRepository::clear_cache( $locale_tag );
		DictionaryValidator::record_validation_result( $result, get_current_user_id(), $file_hash );

		Plugin::set_flash_notice(
			sprintf(
				'%s uploaded successfully with %d warning(s).',
				$locale_tag,
				self::warning_count( $result )
			),
			self::warning_count( $result ) > 0 ? 'warning' : 'success'
		);

		wp_safe_redirect( AdminPages::page_url( 'uaixlr-dictionaries' ) );
		exit;
	}

	/**
	 * Download the bundled template dictionary.
	 *
	 * @return void
	 */
	public static function handle_download_template() {
		self::assert_permissions( 'uaixlr_download_template' );
		self::stream_dictionary_download( 'template' );
	}

	/**
	 * Download a locale dictionary export.
	 *
	 * @return void
	 */
	public static function handle_download_dictionary() {
		self::assert_permissions( 'uaixlr_download_dictionary' );

		$locale_tag = isset( $_POST['locale_tag'] ) ? LocaleValidator::canonicalize_tag( wp_unslash( $_POST['locale_tag'] ) ) : '';

		if ( '' === $locale_tag ) {
			Plugin::set_flash_notice( 'Choose a locale to download.', 'error' );
			wp_safe_redirect( AdminPages::page_url( 'uaixlr-dictionaries' ) );
			exit;
		}

		self::stream_dictionary_download( $locale_tag );
	}

	/**
	 * Determine whether a validation result has blocking errors.
	 *
	 * @param array $result Validation result.
	 * @return bool
	 */
	private static function has_error_issues( array $result ) {
		if ( empty( $result['issues'] ) || ! is_array( $result['issues'] ) ) {
			return false;
		}

		foreach ( $result['issues'] as $issue ) {
			if ( isset( $issue['severity'] ) && 'error' === $issue['severity'] ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Count warning issues.
	 *
	 * @param array $result Validation result.
	 * @return int
	 */
	private static function warning_count( array $result ) {
		$count = 0;

		if ( empty( $result['issues'] ) || ! is_array( $result['issues'] ) ) {
			return 0;
		}

		foreach ( $result['issues'] as $issue ) {
			if ( isset( $issue['severity'] ) && 'warning' === $issue['severity'] ) {
				++$count;
			}
		}

		return $count;
	}

	/**
	 * Build detailed admin-notice data for a failed upload.
	 *
	 * @param array  $result Validation result.
	 * @param string $file_name Uploaded file name.
	 * @return array
	 */
	private static function upload_failure_notice_data( array $result, $file_name ) {
		$locale_tag = isset( $result['localeTag'] ) ? LocaleValidator::canonicalize_tag( (string) $result['localeTag'] ) : '';
		$key_mode   = isset( $result['dictionaryKeyMode'] ) ? (string) $result['dictionaryKeyMode'] : '';
		$errors     = self::issue_count_by_severity( $result, 'error' );
		$warnings   = self::issue_count_by_severity( $result, 'warning' );
		$details    = array(
			'File'              => (string) $file_name,
			'Detected locale'   => '' !== $locale_tag ? $locale_tag : 'Missing or invalid',
			'Detected key mode' => self::describe_key_mode( $key_mode ),
			'Validation status' => isset( $result['status'] ) ? (string) $result['status'] : 'rejected',
			'Issue summary'     => sprintf( '%d error(s), %d warning(s)', $errors, $warnings ),
		);

		return array(
			'summary'  => 'The uploaded dictionary did not pass validation and was not saved.',
			'details'  => $details,
			'issues'   => isset( $result['issues'] ) && is_array( $result['issues'] ) ? $result['issues'] : array(),
			'guidance' => self::upload_failure_guidance( $result ),
		);
	}

	/**
	 * Count issues by severity.
	 *
	 * @param array  $result Validation result.
	 * @param string $severity Severity label.
	 * @return int
	 */
	private static function issue_count_by_severity( array $result, $severity ) {
		$count = 0;

		if ( empty( $result['issues'] ) || ! is_array( $result['issues'] ) ) {
			return 0;
		}

		foreach ( $result['issues'] as $issue ) {
			if ( isset( $issue['severity'] ) && $severity === $issue['severity'] ) {
				++$count;
			}
		}

		return $count;
	}

	/**
	 * Describe a detected dictionary key mode for admins.
	 *
	 * @param string $key_mode Detected key mode.
	 * @return string
	 */
	private static function describe_key_mode( $key_mode ) {
		if ( 'runtime-key' === $key_mode ) {
			return 'Legacy internal runtime keys';
		}

		if ( 'source-text' === $key_mode ) {
			return 'English source-text keys';
		}

		return 'Unknown';
	}

	/**
	 * Suggest likely fixes for the current upload failure.
	 *
	 * @param array $result Validation result.
	 * @return array
	 */
	private static function upload_failure_guidance( array $result ) {
		$guidance = array();
		$codes    = array();

		if ( ! empty( $result['issues'] ) && is_array( $result['issues'] ) ) {
			foreach ( $result['issues'] as $issue ) {
				if ( ! empty( $issue['code'] ) && is_string( $issue['code'] ) ) {
					$codes[ $issue['code'] ] = true;
				}
			}
		}

		if ( isset( $codes['missing_key'] ) ) {
			$guidance[] = 'Start from Download Template JSON or a full locale export so every expected source string is present before upload.';
		}

		if ( isset( $codes['filename_locale_mismatch'] ) ) {
			$guidance[] = 'Rename the file to match meta.locale, or update meta.locale so it matches the filename you are uploading.';
		}

		if ( isset( $codes['missing_locale'] ) || isset( $codes['invalid_locale_tag'] ) ) {
			$guidance[] = 'Set meta.locale to a valid locale tag such as zh-SG, en-US, fr-FR, or spiral.';
		}

		if ( isset( $codes['invalid_json'] ) ) {
			$guidance[] = 'Make sure the file is valid JSON with double-quoted keys and values before uploading it again.';
		}

		if ( isset( $result['dictionaryKeyMode'] ) && 'runtime-key' === $result['dictionaryKeyMode'] ) {
			$guidance[] = 'Legacy runtime-key dictionaries are still accepted, but new template and locale exports now use English source text as the key.';
		}

		if ( empty( $guidance ) ) {
			$guidance[] = 'Use the template or locale download on this page as your starting point, then re-upload the completed JSON file unchanged except for the translation values.';
		}

		return array_values( array_unique( $guidance ) );
	}

	/**
	 * Capability and nonce guard.
	 *
	 * @param string $action Nonce action.
	 * @return void
	 */
	private static function assert_permissions( $action ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You are not allowed to manage dictionaries.', 'uaix-locale-router' ) );
		}

		check_admin_referer( $action );
	}

	/**
	 * Build the list of locale exports available from the dictionaries screen.
	 *
	 * @return array
	 */