+ /**
+ * Given a section name or other user-generated or otherwise unsafe string, escapes it to be
+ * a valid HTML id attribute.
+ *
+ * WARNING: unlike escapeId(), the output of this function is not guaranteed to be HTML safe,
+ * be sure to use proper escaping.
+ *
+ * @param string $id String to escape
+ * @param int $mode One of ID_* constants, specifying whether the primary or fallback encoding
+ * should be used.
+ * @return string|bool Escaped ID or false if fallback encoding is requested but it's not
+ * configured.
+ *
+ * @since 1.30
+ */
+ public static function escapeIdForAttribute( $id, $mode = self::ID_PRIMARY ) {
+ global $wgFragmentMode;
+
+ if ( !isset( $wgFragmentMode[$mode] ) ) {
+ if ( $mode === self::ID_PRIMARY ) {
+ throw new UnexpectedValueException( '$wgFragmentMode is configured with no primary mode' );
+ }
+ return false;
+ }
+
+ $internalMode = $wgFragmentMode[$mode];
+
+ return self::escapeIdInternal( $id, $internalMode );
+ }
+
+ /**
+ * Given a section name or other user-generated or otherwise unsafe string, escapes it to be
+ * a valid URL fragment.
+ *
+ * WARNING: unlike escapeId(), the output of this function is not guaranteed to be HTML safe,
+ * be sure to use proper escaping.
+ *
+ * @param string $id String to escape
+ * @return string Escaped ID
+ *
+ * @since 1.30
+ */
+ public static function escapeIdForLink( $id ) {
+ global $wgFragmentMode;
+
+ if ( !isset( $wgFragmentMode[self::ID_PRIMARY] ) ) {
+ throw new UnexpectedValueException( '$wgFragmentMode is configured with no primary mode' );
+ }
+
+ $mode = $wgFragmentMode[self::ID_PRIMARY];
+
+ $id = self::escapeIdInternal( $id, $mode );
+ $id = self::urlEscapeId( $id, $mode );
+
+ return $id;
+ }
+
+ /**
+ * Given a section name or other user-generated or otherwise unsafe string, escapes it to be
+ * a valid URL fragment for external interwikis.
+ *
+ * @param string $id String to escape
+ * @return string Escaped ID
+ *
+ * @since 1.30
+ */
+ public static function escapeIdForExternalInterwiki( $id ) {
+ global $wgExternalInterwikiFragmentMode;
+
+ $id = self::escapeIdInternal( $id, $wgExternalInterwikiFragmentMode );
+ $id = self::urlEscapeId( $id, $wgExternalInterwikiFragmentMode );
+
+ return $id;
+ }
+
+ /**
+ * Helper for escapeIdFor*() functions. URL-escapes the ID if needed.
+ *
+ * @param string $id String to escape
+ * @param string $mode One of modes from $wgFragmentMode
+ * @return string
+ */
+ private static function urlEscapeId( $id, $mode ) {
+ if ( $mode === 'html5' ) {
+ $id = urlencode( $id );
+ $id = str_replace( '%3A', ':', $id );
+ }
+
+ return $id;
+ }
+
+ /**
+ * Helper for escapeIdFor*() functions. Performs most of the actual escaping.
+ *
+ * @param string $id String to escape
+ * @param string $mode One of modes from $wgFragmentMode
+ * @return string
+ */
+ private static function escapeIdInternal( $id, $mode ) {
+ $id = self::decodeCharReferences( $id );
+
+ switch ( $mode ) {
+ case 'html5':
+ $id = str_replace( ' ', '_', $id );
+ break;
+ case 'legacy':
+ // This corresponds to 'noninitial' mode of the old escapeId()
+ static $replace = [
+ '%3A' => ':',
+ '%' => '.'
+ ];
+
+ $id = urlencode( str_replace( ' ', '_', $id ) );
+ $id = strtr( $id, $replace );
+ break;
+ case 'html5-legacy':
+ $id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
+ $id = trim( $id, '_' );
+ if ( $id === '' ) {
+ // Must have been all whitespace to start with.
+ $id = '_';
+ }
+ break;
+ default:
+ throw new InvalidArgumentException( "Invalid mode '$mode' passed to '" . __METHOD__ );
+ }
+
+ return $id;
+ }
+