diff --git a/include/errors.inc b/include/errors.inc index b21a75d780..f38f211d29 100644 --- a/include/errors.inc +++ b/include/errors.inc @@ -113,425 +113,6 @@ function status_header(int $status): bool return true; } -/****************************************************************************** -In the future every mirror will have SQLite instead of hacks like this. Or, some -other means like a ginormous array! But the point is we'll end up storing -everything like functions, variables, constants, common search terms, etc. - -This exists today because as of PHP 5.3.0 there are over 200 links within the -php.ini files so these create nicer urls and allow language detection to work. - -The most commonly searched terms have also been added. - -TODO: Determine if we want to continue 301 -OR- make these official URLs. -******************************************************************************/ - -function is_known_ini(string $ini): ?string { - $inis = [ - 'engine' => 'apache.configuration.php#ini.engine', - 'short-open-tag' => 'ini.core.php#ini.short-open-tag', - 'asp-tags' => 'ini.core.php#ini.asp-tags', - 'precision' => 'ini.core.php#ini.precision', - 'y2k-compliance' => 'ini.core.php#ini.y2k-compliance', - 'output-buffering' => 'outcontrol.configuration.php#ini.output-buffering', - 'output-handler' => 'outcontrol.configuration.php#ini.output-handler', - 'zlib.output-compression' => 'zlib.configuration.php#ini.zlib.output-compression', - 'zlib.output-compression-level' => 'zlib.configuration.php#ini.zlib.output-compression-level', - 'zlib.output-handler' => 'zlib.configuration.php#ini.zlib.output-handler', - 'implicit-flush' => 'outcontrol.configuration.php#ini.implicit-flush', - 'allow-call-time-pass-reference' => 'ini.core.php#ini.allow-call-time-pass-reference', - 'open-basedir' => 'ini.core.php#ini.open-basedir', - 'disable-functions' => 'ini.core.php#ini.disable-functions', - 'disable-classes' => 'ini.core.php#ini.disable-classes', - 'zend.assertions' => 'ini.core.php#ini.zend.assertions', - 'syntax-highlighting' => 'misc.configuration.php#ini.syntax-highlighting', - 'ignore-user-abort' => 'misc.configuration.php#ini.ignore-user-abort', - 'realpath-cache-size' => 'ini.core.php#ini.realpath-cache-size', - 'realpath-cache-ttl' => 'ini.core.php#ini.realpath-cache-ttl', - 'expose-php' => 'ini.core.php#ini.expose-php', - 'max-execution-time' => 'info.configuration.php#ini.max-execution-time', - 'max-input-time' => 'info.configuration.php#ini.max-input-time', - 'max-input-vars' => 'info.configuration.php#ini.max-input-vars', - 'max-input-nesting-level' => 'info.configuration.php#ini.max-input-nesting-level', - 'memory-limit' => 'ini.core.php#ini.memory-limit', - 'error-reporting' => 'errorfunc.configuration.php#ini.error-reporting', - 'display-errors' => 'errorfunc.configuration.php#ini.display-errors', - 'display-startup-errors' => 'errorfunc.configuration.php#ini.display-startup-errors', - 'log-errors' => 'errorfunc.configuration.php#ini.log-errors', - 'log-errors-max-len' => 'errorfunc.configuration.php#ini.log-errors-max-len', - 'ignore-repeated-errors' => 'errorfunc.configuration.php#ini.ignore-repeated-errors', - 'ignore-repeated-source' => 'errorfunc.configuration.php#ini.ignore-repeated-source', - 'report-memleaks' => 'errorfunc.configuration.php#ini.report-memleaks', - 'track-errors' => 'errorfunc.configuration.php#ini.track-errors', - 'xmlrpc-errors' => 'errorfunc.configuration.php#ini.xmlrpc-errors', - 'html-errors' => 'errorfunc.configuration.php#ini.html-errors', - 'docref-root' => 'errorfunc.configuration.php#ini.docref-root', - 'docref-ext' => 'errorfunc.configuration.php#ini.docref-ext', - 'error-prepend-string' => 'errorfunc.configuration.php#ini.error-prepend-string', - 'error-append-string' => 'errorfunc.configuration.php#ini.error-append-string', - 'error-log' => 'errorfunc.configuration.php#ini.error-log', - 'syslog.facility' => 'errorfunc.configuration.php#ini.syslog.facility', - 'syslog.filter' => 'errorfunc.configuration.php#ini.syslog.filter', - 'syslog.ident' => 'errorfunc.configuration.php#ini.syslog.ident', - 'arg-separator.output' => 'ini.core.php#ini.arg-separator.output', - 'arg-separator.input' => 'ini.core.php#ini.arg-separator.input', - 'variables-order' => 'ini.core.php#ini.variables-order', - 'request-order' => 'ini.core.php#ini.request-order', - 'register-globals' => 'ini.core.php#ini.register-globals', - 'register-long-arrays' => 'ini.core.php#ini.register-long-arrays', - 'register-argc-argv' => 'ini.core.php#ini.register-argc-argv', - 'auto-globals-jit' => 'ini.core.php#ini.auto-globals-jit', - 'post-max-size' => 'ini.core.php#ini.post-max-size', - 'magic-quotes-gpc' => 'info.configuration.php#ini.magic-quotes-gpc', - 'magic-quotes-runtime' => 'info.configuration.php#ini.magic-quotes-runtime', - 'auto-prepend-file' => 'ini.core.php#ini.auto-prepend-file', - 'auto-append-file' => 'ini.core.php#ini.auto-append-file', - 'default-mimetype' => 'ini.core.php#ini.default-mimetype', - 'default-charset' => 'ini.core.php#ini.default-charset', - 'always-populate-raw-post-data' => 'ini.core.php#ini.always-populate-raw-post-data', - 'include-path' => 'ini.core.php#ini.include-path', - 'doc-root' => 'ini.core.php#ini.doc-root', - 'user-dir' => 'ini.core.php#ini.user-dir', - 'extension-dir' => 'ini.core.php#ini.extension-dir', - 'enable-dl' => 'info.configuration.php#ini.enable-dl', - 'cgi.force-redirect' => 'ini.core.php#ini.cgi.force-redirect', - 'cgi.redirect-status-env' => 'ini.core.php#ini.cgi.redirect-status-env', - 'cgi.fix-pathinfo' => 'ini.core.php#ini.cgi.fix-pathinfo', - 'fastcgi.impersonate' => 'ini.core.php#ini.fastcgi.impersonate', - 'cgi.rfc2616-headers' => 'ini.core.php#ini.cgi.rfc2616-headers', - 'file-uploads' => 'ini.core.php#ini.file-uploads', - 'upload-tmp-dir' => 'ini.core.php#ini.upload-tmp-dir', - 'upload-max-filesize' => 'ini.core.php#ini.upload-max-filesize', - 'allow-url-fopen' => 'filesystem.configuration.php#ini.allow-url-fopen', - 'allow-url-include' => 'filesystem.configuration.php#ini.allow-url-include', - 'from' => 'filesystem.configuration.php#ini.from', - 'user-agent' => 'filesystem.configuration.php#ini.user-agent', - 'default-socket-timeout' => 'filesystem.configuration.php#ini.default-socket-timeout', - 'auto-detect-line-endings' => 'filesystem.configuration.php#ini.auto-detect-line-endings', - 'date.timezone' => 'datetime.configuration.php#ini.date.timezone', - 'date.default-latitude' => 'datetime.configuration.php#ini.date.default-latitude', - 'date.default-longitude' => 'datetime.configuration.php#ini.date.default-longitude', - 'date.sunrise-zenith' => 'datetime.configuration.php#ini.date.sunrise-zenith', - 'date.sunset-zenith' => 'datetime.configuration.php#ini.date.sunset-zenith', - 'filter.default' => 'filter.configuration.php#ini.filter.default', - 'filter.default-flags' => 'filter.configuration.php#ini.filter.default-flags', - 'pcre.backtrack-limit' => 'pcre.configuration.php#ini.pcre.backtrack-limit', - 'pcre.recursion-limit' => 'pcre.configuration.php#ini.pcre.recursion-limit', - 'pdo-odbc.connection-pooling' => 'ref.pdo-odbc.php#ini.pdo-odbc.connection-pooling', - 'phar.readonly' => 'phar.configuration.php#ini.phar.readonly', - 'phar.require-hash' => 'phar.configuration.php#ini.phar.require-hash', - 'define-syslog-variables' => 'network.configuration.php#ini.define-syslog-variables', - 'smtp' => 'mail.configuration.php#ini.smtp', - 'smtp-port' => 'mail.configuration.php#ini.smtp-port', - 'sendmail-from' => 'mail.configuration.php#ini.sendmail-from', - 'sendmail-path' => 'mail.configuration.php#ini.sendmail-path', - 'sql.safe-mode' => 'ini.core.php#ini.sql.safe-mode', - 'odbc.default-db' => 'odbc.configuration.php#ini.uodbc.default-db', - 'odbc.default-user' => 'odbc.configuration.php#ini.uodbc.default-user', - 'odbc.default-pw' => 'odbc.configuration.php#ini.uodbc.default-pw', - 'odbc.allow-persistent' => 'odbc.configuration.php#ini.uodbc.allow-persistent', - 'odbc.check-persistent' => 'odbc.configuration.php#ini.uodbc.check-persistent', - 'odbc.max-persistent' => 'odbc.configuration.php#ini.uodbc.max-persistent', - 'odbc.max-links' => 'odbc.configuration.php#ini.uodbc.max-links', - 'odbc.defaultlrl' => 'odbc.configuration.php#ini.uodbc.defaultlrl', - 'odbc.defaultbinmode' => 'odbc.configuration.php#ini.uodbc.defaultbinmode', - 'mysql.allow-local-infile' => 'mysql.configuration.php#ini.mysql.allow-local-infile', - 'mysql.allow-persistent' => 'mysql.configuration.php#ini.mysql.allow-persistent', - 'mysql.max-persistent' => 'mysql.configuration.php#ini.mysql.max-persistent', - 'mysql.max-links' => 'mysql.configuration.php#ini.mysql.max-links', - 'mysql.default-port' => 'mysql.configuration.php#ini.mysql.default-port', - 'mysql.default-socket' => 'mysql.configuration.php#ini.mysql.default-socket', - 'mysql.default-host' => 'mysql.configuration.php#ini.mysql.default-host', - 'mysql.default-user' => 'mysql.configuration.php#ini.mysql.default-user', - 'mysql.default-password' => 'mysql.configuration.php#ini.mysql.default-password', - 'mysql.connect-timeout' => 'mysql.configuration.php#ini.mysql.connect-timeout', - 'mysql.trace-mode' => 'mysql.configuration.php#ini.mysql.trace-mode', - 'mysqli.allow-local-infile' => 'mysqli.configuration.php#ini.mysqli.allow-local-infile', - 'mysqli.max-links' => 'mysqli.configuration.php#ini.mysqli.max-links', - 'mysqli.allow-persistent' => 'mysqli.configuration.php#ini.mysqli.allow-persistent', - 'mysqli.default-port' => 'mysqli.configuration.php#ini.mysqli.default-port', - 'mysqli.default-socket' => 'mysqli.configuration.php#ini.mysqli.default-socket', - 'mysqli.default-host' => 'mysqli.configuration.php#ini.mysqli.default-host', - 'mysqli.default-user' => 'mysqli.configuration.php#ini.mysqli.default-user', - 'mysqli.default-pw' => 'mysqli.configuration.php#ini.mysqli.default-pw', - 'oci8.privileged-connect' => 'oci8.configuration.php#ini.oci8.privileged-connect', - 'oci8.max-persistent' => 'oci8.configuration.php#ini.oci8.max-persistent', - 'oci8.persistent-timeout' => 'oci8.configuration.php#ini.oci8.persistent-timeout', - 'oci8.ping-interval' => 'oci8.configuration.php#ini.oci8.ping-interval', - 'oci8.statement-cache-size' => 'oci8.configuration.php#ini.oci8.statement-cache-size', - 'oci8.default-prefetch' => 'oci8.configuration.php#ini.oci8.default-prefetch', - 'oci8.old-oci-close-semantics' => 'oci8.configuration.php#ini.oci8.old-oci-close-semantics', - 'opcache.preload' => 'opcache.configuration.php#ini.opcache.preload', - 'pgsql.allow-persistent' => 'pgsql.configuration.php#ini.pgsql.allow-persistent', - 'pgsql.auto-reset-persistent' => 'pgsql.configuration.php#ini.pgsql.auto-reset-persistent', - 'pgsql.max-persistent' => 'pgsql.configuration.php#ini.pgsql.max-persistent', - 'pgsql.max-links' => 'pgsql.configuration.php#ini.pgsql.max-links', - 'pgsql.ignore-notice' => 'pgsql.configuration.php#ini.pgsql.ignore-notice', - 'pgsql.log-notice' => 'pgsql.configuration.php#ini.pgsql.log-notice', - 'sqlite3.extension-dir' => 'sqlite3.configuration.php#ini.sqlite3.extension-dir', - 'bcmath.scale' => 'bc.configuration.php#ini.bcmath.scale', - 'browscap' => 'misc.configuration.php#ini.browscap', - 'session.save-handler' => 'session.configuration.php#ini.session.save-handler', - 'session.save-path' => 'session.configuration.php#ini.session.save-path', - 'session.use-cookies' => 'session.configuration.php#ini.session.use-cookies', - 'session.cookie-secure' => 'session.configuration.php#ini.session.cookie-secure', - 'session.use-only-cookies' => 'session.configuration.php#ini.session.use-only-cookies', - 'session.name' => 'session.configuration.php#ini.session.name', - 'session.auto-start' => 'session.configuration.php#ini.session.auto-start', - 'session.cookie-lifetime' => 'session.configuration.php#ini.session.cookie-lifetime', - 'session.cookie-path' => 'session.configuration.php#ini.session.cookie-path', - 'session.cookie-domain' => 'session.configuration.php#ini.session.cookie-domain', - 'session.cookie-httponly' => 'session.configuration.php#ini.session.cookie-httponly', - 'session.serialize-handler' => 'session.configuration.php#ini.session.serialize-handler', - 'session.gc-probability' => 'session.configuration.php#ini.session.gc-probability', - 'session.gc-divisor' => 'session.configuration.php#ini.session.gc-divisor', - 'session.gc-maxlifetime' => 'session.configuration.php#ini.session.gc-maxlifetime', - 'session.bug-compat-42' => 'session.configuration.php#ini.session.bug-compat-42', - 'session.bug-compat-warn' => 'session.configuration.php#ini.session.bug-compat-warn', - 'session.referer-check' => 'session.configuration.php#ini.session.referer-check', - 'session.entropy-length' => 'session.configuration.php#ini.session.entropy-length', - 'session.entropy-file' => 'session.configuration.php#ini.session.entropy-file', - 'session.cache-limiter' => 'session.configuration.php#ini.session.cache-limiter', - 'session.cache-expire' => 'session.configuration.php#ini.session.cache-expire', - 'session.sid-length' => 'session.configuration.php#ini.session.sid-length', - 'session.use-trans-sid' => 'session.configuration.php#ini.session.use-trans-sid', - 'session.hash-function' => 'session.configuration.php#ini.session.hash-function', - 'session.hash-bits-per-character' => 'session.configuration.php#ini.session.hash-bits-per-character', - 'session.upload-progress.enabled' => 'session.configuration.php#ini.session.upload-progress.enabled', - 'session.upload-progress.cleanup' => 'session.configuration.php#ini.session.upload-progress.cleanup', - 'session.upload-progress.prefix' => 'session.configuration.php#ini.session.upload-progress.prefix', - 'session.upload-progress.name' => 'session.configuration.php#ini.session.upload-progress.name', - 'session.upload-progress.freq' => 'session.configuration.php#ini.session.upload-progress.freq', - 'session.upload-progress.min-freq' => 'session.configuration.php#ini.session.upload-progress.min-freq', - 'url-rewriter.tags' => 'session.configuration.php#ini.url-rewriter.tags', - 'assert.active' => 'info.configuration.php#ini.assert.active', - 'assert.exception' => 'info.configuration.php#ini.assert.exception', - 'assert.warning' => 'info.configuration.php#ini.assert.warning', - 'assert.bail' => 'info.configuration.php#ini.assert.bail', - 'assert.callback' => 'info.configuration.php#ini.assert.callback', - 'assert.quiet-eval' => 'info.configuration.php#ini.assert.quiet-eval', - 'zend.enable-gc' => 'info.configuration.php#ini.zend.enable-gc', - 'com.typelib-file' => 'com.configuration.php#ini.com.typelib-file', - 'com.allow-dcom' => 'com.configuration.php#ini.com.allow-dcom', - 'com.autoregister-typelib' => 'com.configuration.php#ini.com.autoregister-typelib', - 'com.autoregister-casesensitive' => 'com.configuration.php#ini.com.autoregister-casesensitive', - 'com.autoregister-verbose' => 'com.configuration.php#ini.com.autoregister-verbose', - 'mbstring.language' => 'mbstring.configuration.php#ini.mbstring.language', - 'mbstring.internal-encoding' => 'mbstring.configuration.php#ini.mbstring.internal-encoding', - 'mbstring.http-input' => 'mbstring.configuration.php#ini.mbstring.http-input', - 'mbstring.http-output' => 'mbstring.configuration.php#ini.mbstring.http-output', - 'mbstring.encoding-translation' => 'mbstring.configuration.php#ini.mbstring.encoding-translation', - 'mbstring.detect-order' => 'mbstring.configuration.php#ini.mbstring.detect-order', - 'mbstring.substitute-character' => 'mbstring.configuration.php#ini.mbstring.substitute-character', - 'mbstring.func-overload' => 'mbstring.configuration.php#ini.mbstring.func-overload', - 'gd.jpeg-ignore-warning' => 'image.configuration.php#ini.image.jpeg-ignore-warning', - 'exif.encode-unicode' => 'exif.configuration.php#ini.exif.encode-unicode', - 'exif.decode-unicode-motorola' => 'exif.configuration.php#ini.exif.decode-unicode-motorola', - 'exif.decode-unicode-intel' => 'exif.configuration.php#ini.exif.decode-unicode-intel', - 'exif.encode-jis' => 'exif.configuration.php#ini.exif.encode-jis', - 'exif.decode-jis-motorola' => 'exif.configuration.php#ini.exif.decode-jis-motorola', - 'exif.decode-jis-intel' => 'exif.configuration.php#ini.exif.decode-jis-intel', - 'tidy.default-config' => 'tidy.configuration.php#ini.tidy.default-config', - 'tidy.clean-output' => 'tidy.configuration.php#ini.tidy.clean-output', - 'soap.wsdl-cache-enabled' => 'soap.configuration.php#ini.soap.wsdl-cache-enabled', - 'soap.wsdl-cache-dir' => 'soap.configuration.php#ini.soap.wsdl-cache-dir', - 'soap.wsdl-cache-ttl' => 'soap.configuration.php#ini.soap.wsdl-cache-ttl', - ]; - - return $inis[$ini] ?? null; -} - -function is_known_variable(string $variable): ?string { - $variables = [ - // Variables - 'globals' => 'reserved.variables.globals.php', - '-server' => 'reserved.variables.server.php', - '-get' => 'reserved.variables.get.php', - '-post' => 'reserved.variables.post.php', - '-files' => 'reserved.variables.files.php', - '-request' => 'reserved.variables.request.php', - '-session' => 'reserved.variables.session.php', - '-cookie' => 'reserved.variables.cookies.php', - '-env' => 'reserved.variables.environment.php', - 'this' => 'language.oop5.basic.php', - 'php-errormsg' => 'reserved.variables.phperrormsg.php', - 'argv' => 'reserved.variables.argv.php', - 'argc' => 'reserved.variables.argc.php', - 'http-response-header' => 'reserved.variables.httpresponseheader.php', - 'http-server-vars' => 'reserved.variables.server.php', - 'http-get-vars' => 'reserved.variables.get.php', - 'http-post-vars' => 'reserved.variables.post.php', - 'http-session-vars' => 'reserved.variables.session.php', - 'http-post-files' => 'reserved.variables.files.php', - 'http-cookie-vars' => 'reserved.variables.cookies.php', - ]; - - return $variables[ltrim($variable, '$')] ?? null; -} - -function is_known_term(string $term): ?string { - $terms = [ - '<>' => 'language.operators.comparison.php', - '<=>' => 'language.operators.comparison.php', - 'spaceship' => 'language.operators.comparison.php', - '==' => 'language.operators.comparison.php', - '===' => 'language.operators.comparison.php', - '@' => 'language.operators.errorcontrol.php', - '__halt_compiler' => 'function.halt-compiler.php', - '__PHP_Incomplete_Class' => 'function.unserialize.php', - 'and' => 'language.operators.logical.php', - 'apache' => 'install.php', - 'array' => 'language.types.array.php', - 'arrays' => 'language.types.array.php', - 'as' => 'control-structures.foreach.php', - 'case' => 'control-structures.switch.php', - 'catch' => 'language.exceptions.php', - 'checkbox' => 'faq.html.php', - 'class' => 'language.oop5.basic.php', - 'classes' => 'language.oop5.basic.php', - 'closures' => 'functions.anonymous.php', - 'cookie' => 'features.cookies.php', - 'date' => 'function.date.php', - 'default' => 'control-structures.switch.php', - 'do' => 'control-structures.do.while.php', - 'enddeclare' => 'control-structures.declare.php', - 'endfor' => 'control-structures.alternative-syntax.php', - 'endforeach' => 'control-structures.alternative-syntax.php', - 'endif' => 'control-structures.alternative-syntax.php', - 'endswitch' => 'control-structures.alternative-syntax.php', - 'endwhile' => 'control-structures.alternative-syntax.php', - 'exception' => 'language.exceptions.php', - 'extends' => 'language.oop5.basic.php#language.oop5.basic.extends', - 'false' => 'language.types.boolean.php', - 'file' => 'function.file.php', - 'final' => 'language.oop5.final.php', - 'finally' => 'language.exceptions.php', - 'fopen' => 'function.fopen.php', - 'for' => 'control-structures.for.php', - 'foreach' => 'control-structures.foreach.php', - 'form' => 'language.variables.external.php', - 'forms' => 'language.variables.external.php', - 'function' => 'language.functions.php', - 'gd' => 'book.image.php', - 'get' => 'reserved.variables.get.php', - 'global' => 'language.variables.scope.php', - 'globals' => 'language.variables.scope.php', - 'header' => 'function.header.php', - 'heredoc' => 'language.types.string.php#language.types.string.syntax.heredoc', - 'htaccess' => 'configuration.file.php', - 'if' => 'control-structures.if.php', - 'implements' => 'language.oop5.interfaces.php', - 'include' => 'function.include.php', - 'insteadof' => 'language.oop5.traits.php#language.oop5.traits.conflict', - 'int' => 'language.types.integer.php', - 'ip' => 'reserved.variables.server.php', - 'iterable' => 'language.types.iterable.php', - 'juggling' => 'language.types.type-juggling.php', - 'location' => 'function.header.php', - 'mail' => 'function.mail.php', - 'mixed' => 'language.types.mixed.php', - 'modulo' => 'language.operators.arithmetic.php', - 'mysql' => 'mysql.php', - 'never' => 'language.types.never.php', - 'new' => 'language.oop5.basic.php#language.oop5.basic.new', - 'nowdoc' => 'language.types.string.php#language.types.string.syntax.nowdoc', - 'null' => 'language.types.null.php', - 'numeric' => 'reserved.other-reserved-words.php', - 'object' => 'language.types.object.php', - 'operator' => 'language.operators.php', - 'operators' => 'language.operators.php', - 'or' => 'language.operators.logical.php', - 'parent' => 'reserved.classes.php#reserved.classes.special', - 'php.ini' => 'configuration.file.php', - 'php-mysql.dll' => 'book.mysql.php', - 'php-self' => 'reserved.variables.server.php', - 'query-string' => 'reserved.variables.server.php', - 'readonly' => 'language.oop5.properties.php#language.oop5.properties.readonly-properties', - 'redirect' => 'function.header.php', - 'reference' => 'index.php', - 'referer' => 'reserved.variables.server.php', - 'referrer' => 'reserved.variables.server.php', - 'remote-addr' => 'reserved.variables.server.php', - 'request' => 'reserved.variables.request.php', - 'self' => 'reserved.classes.php#reserved.classes.special', - 'session' => 'features.sessions.php', - 'smtp' => 'book.mail.php', - 'ssl' => 'book.openssl.php', - 'static' => 'language.oop5.static.php', - 'stdin' => 'wrappers.php.php', - 'string' => 'language.types.string.php', - 'superglobal' => 'language.variables.superglobals.php', - 'superglobals' => 'language.variables.superglobals.php', - 'switch' => 'control-structures.switch.php', - 'timestamp' => 'function.time.php', - 'true' => 'language.types.boolean.php', - 'try' => 'language.exceptions.php', - 'upload' => 'features.file-upload.php', - 'use' => 'language.namespaces.php', - 'void' => 'language.types.void.php', - 'xor' => 'language.operators.logical.php', - 'yield from' => 'language.generators.syntax.php#control-structures.yield.from', - 'yield' => 'language.generators.syntax.php#control-structures.yield', - - '__COMPILER_HALT_OFFSET__' => 'function.halt-compiler.php', - 'DEFAULT_INCLUDE_PATH' => 'reserved.constants.php', - 'E_ALL' => 'errorfunc.constants.php', - 'E_COMPILE_ERROR' => 'errorfunc.constants.php', - 'E_COMPILE_WARNING' => 'errorfunc.constants.php', - 'E_CORE_ERROR' => 'errorfunc.constants.php', - 'E_CORE_WARNING' => 'errorfunc.constants.php', - 'E_DEPRECATED' => 'errorfunc.constants.php', - 'E_ERROR' => 'errorfunc.constants.php', - 'E_NOTICE' => 'errorfunc.constants.php', - 'E_PARSE' => 'errorfunc.constants.php', - 'E_RECOVERABLE_ERROR' => 'errorfunc.constants.php', - 'E_STRICT' => 'errorfunc.constants.php', - 'E_USER_DEPRECATED' => 'errorfunc.constants.php', - 'E_USER_ERROR' => 'errorfunc.constants.php', - 'E_USER_NOTICE' => 'errorfunc.constants.php', - 'E_USER_WARNING' => 'errorfunc.constants.php', - 'E_WARNING' => 'errorfunc.constants.php', - 'PEAR_EXTENSION_DIR' => 'reserved.constants.php', - 'PEAR_INSTALL_DIR' => 'reserved.constants.php', - 'PHP_BINARY' => 'reserved.constants.php', - 'PHP_BINDIR' => 'reserved.constants.php', - 'PHP_CONFIG_FILE_PATH' => 'reserved.constants.php', - 'PHP_CONFIG_FILE_SCAN_DIR' => 'reserved.constants.php', - 'PHP_DATADIR' => 'reserved.constants.php', - 'PHP_DEBUG' => 'reserved.constants.php', - 'PHP_EOL' => 'reserved.constants.php', - 'PHP_EXTENSION_DIR' => 'reserved.constants.php', - 'PHP_EXTRA_VERSION' => 'reserved.constants.php', - 'PHP_FD_SETSIZE' => 'reserved.constants.php', - 'PHP_FLOAT_DIG' => 'reserved.constants.php', - 'PHP_FLOAT_EPSILON' => 'reserved.constants.php', - 'PHP_FLOAT_MAX' => 'reserved.constants.php', - 'PHP_FLOAT_MIN' => 'reserved.constants.php', - 'PHP_INT_MAX' => 'reserved.constants.php', - 'PHP_INT_MIN' => 'reserved.constants.php', - 'PHP_INT_SIZE' => 'reserved.constants.php', - 'PHP_LIBDIR' => 'reserved.constants.php', - 'PHP_LOCALSTATEDIR' => 'reserved.constants.php', - 'PHP_MAJOR_VERSION' => 'reserved.constants.php', - 'PHP_MANDIR' => 'reserved.constants.php', - 'PHP_MAXPATHLEN' => 'reserved.constants.php', - 'PHP_MINOR_VERSION' => 'reserved.constants.php', - 'PHP_OS_FAMILY' => 'reserved.constants.php', - 'PHP_OS' => 'reserved.constants.php', - 'PHP_PREFIX' => 'reserved.constants.php', - 'PHP_RELEASE_VERSION' => 'reserved.constants.php', - 'PHP_SAPI' => 'reserved.constants.php', - 'PHP_SHLIB_SUFFIX' => 'reserved.constants.php', - 'PHP_SYSCONFDIR' => 'reserved.constants.php', - 'PHP_VERSION_ID' => 'reserved.constants.php', - 'PHP_VERSION' => 'reserved.constants.php', - 'PHP_WINDOWS_EVENT_CTRL_BREAK' => 'reserved.constants.php', - 'PHP_WINDOWS_EVENT_CTRL_C' => 'reserved.constants.php', - 'PHP_ZTS' => 'reserved.constants.php', - ]; - - return $terms[$term] ?? null; -} /* Search snippet provider: A dirty proof-of-concept: diff --git a/include/manual-lookup.inc b/include/manual-lookup.inc index 8d988041d1..23fbe0b9ce 100644 --- a/include/manual-lookup.inc +++ b/include/manual-lookup.inc @@ -89,6 +89,13 @@ function find_manual_page_slow($lang, $keyword) } } + // Fall back to the third-party extensions manual (English only) for the + // pages that moved to php/doc-extensions + foreach ($sections as $section) { + $found = tryprefix("extensions", $keyword, $section); + if ($found) { return $found; } + } + // BC: Few references pages where moved to book. if (strpos($keyword, "ref.") === 0) { $kw = substr_replace($keyword, "book.", 0, 4); diff --git a/include/shared-manual.inc b/include/shared-manual.inc index 4d37f98125..26e6bdfe56 100644 --- a/include/shared-manual.inc +++ b/include/shared-manual.inc @@ -282,7 +282,10 @@ function manual_setup($setup): void { } $PGI = $setup; // Set base href for this manual page - $base = 'manual/' . (new Languages())->convert($setup['head'][1]) . "/"; + // The third-party extensions manual (php/doc-extensions) is English only + // and served from manual/extensions/, which is not a language. + $isExtensionsManual = $setup['head'][1] === 'extensions'; + $base = 'manual/' . ($isExtensionsManual ? 'extensions' : (new Languages())->convert($setup['head'][1])) . "/"; $_SERVER['BASE_PAGE'] = $base . $setup['this'][0]; $_SERVER['BASE_HREF'] = $MYSITE . $_SERVER['BASE_PAGE']; @@ -314,7 +317,7 @@ function manual_setup($setup): void { $config = [ "current" => "docs", "breadcrumbs" => $breadcrumbs, - "languages" => array_keys(Languages::ACTIVE_ONLINE_LANGUAGES), + "languages" => $isExtensionsManual ? [] : array_keys(Languages::ACTIVE_ONLINE_LANGUAGES), "meta-navigation" => [ "contents" => $base . $setup["home"][0], "index" => $base . $setup["up"][0], @@ -331,6 +334,10 @@ function manual_setup($setup): void { ]; site_header($setup["this"][1] . " - Manual", $config); + if ($isExtensionsManual) { + return; + } + $languageChooser = manual_language_chooser($config['lang'], $config['thispage']); echo << /manual/{LANG}/{to}.php +// 'in_manual' - manual URI (/manual/{lang}/{from}[.php]) -> /manual/{lang}/{to}.php +// 'both' - matches at either entry point + +// A missing, unreadable or corrupted file gives an empty map. +$loadRedirects = static function (string $file): array { + $empty = ['shortcut' => [], 'in_manual' => [], 'both' => []]; + if (!is_file($file)) { + return $empty; + } -if ($path = is_known_ini($term)) { - status_header(301); - mirror_redirect("/manual/$LANG/$path"); - exit; -} -if ($path = is_known_variable($term)) { - status_header(301); - mirror_redirect("/manual/$LANG/$path"); - exit; -} -if ($path = is_known_term($term)) { - status_header(301); - mirror_redirect("/manual/$LANG/$path"); - exit; -} + $maps = json_decode((string) file_get_contents($file), true); -// ============================================================================ -// Major manual page modifications (need to handle shortcuts and pages in all languages) -// Used permanent HTTP redirects, so search engines will be able to pick up the correct -// new URLs for these pages. -$manual_page_moves = [ - // entry point changed - 'installation' => 'install', - - // XML Id changed (see https://github.com/php/doc-en/commit/0d51ca45814bbc60d7a1e6cf6fc7213f0b49c8d5) - 'enum.random.intervalboundary' => 'enum.random-intervalboundary', - 'enum.random.intervalboundary.intro' => 'enum.random-intervalboundary.intro', - 'enum.random.intervalboundary.synopsis' => 'enum.random-intervalboundary.synopsis', - - // was split among platforms (don't know where to redirect) - 'install.apache' => 'install', - 'install.apache2' => 'install', - 'install.netscape-enterprise' => 'install', - 'install.otherhttpd' => 'install', - - // moved to platform sections - 'install.caudium' => 'install.unix.caudium', - 'install.commandline' => 'install.unix.commandline', - 'install.fhttpd' => 'install.unix.fhttpd', - 'install.hpux' => 'install.unix.hpux', - 'install.iis' => 'install.windows.iis', - 'install.linux' => 'install.unix', - 'install.omnihttpd' => 'install.windows.omnihttpd', - 'install.openbsd' => 'install.unix.openbsd', - 'install.sambar' => 'install.windows.sambar', - 'install.solaris' => 'install.unix.solaris', - 'install.xitami' => 'install.windows.xitami', - 'install.windows.installer.msi' => 'install.windows', - 'install.windows.installer' => 'install.windows', - 'install.windows.tools' => 'install.windows', - - // Internals docs where moved - 'zend' => 'internals2.ze1.zendapi', - 'zend-api' => 'internals2.ze1.zendapi', - 'internals.pdo' => 'internals2.pdo', - 'phpdevel' => 'internals2.ze1.php3devel', - 'tsrm' => 'internals2.ze1.tsrm', - - // Replaced extensions - 'aspell' => 'pspell', - - // Refactored - 'regexp.reference' => 'regexp.introduction', - "security" => "manual/security", - - // MongoDB converted from set to book - 'set.mongodb' => 'book.mongodb', - 'mongodb.installation.homebrew' => 'mongodb.installation#mongodb.installation.homebrew', - 'mongodb.installation.manual' => 'mongodb.installation#mongodb.installation.manual', - 'mongodb.installation.pecl' => 'mongodb.installation#mongodb.installation.pecl', - 'mongodb.installation.windows' => 'mongodb.installation#mongodb.installation.windows', - 'mongodb.persistence.deserialization' => 'mongodb.persistence#mongodb.persistence.deserialization', - 'mongodb.persistence.serialization' => 'mongodb.persistence#mongodb.persistence.serialization', -]; + return is_array($maps) ? $maps + $empty : $empty; +}; -if (isset($manual_page_moves[$URI])) { - status_header(301); - mirror_redirect("/" . $manual_page_moves[$URI]); -} elseif (preg_match("!^manual/([^/]+)/([^/]+).php$!", $URI, $match) && - isset($manual_page_moves[$match[2]])) { - status_header(301); +$docRedirects = $loadRedirects(__DIR__ . '/manual/en/redirects.json'); +// Pages of the extensions moved to the third-party extensions manual +// (php/doc-extensions), same format. That manual is English only. +$extRedirects = $loadRedirects(__DIR__ . '/manual/extensions/redirects.json'); - $parts = explode('#', $manual_page_moves[$match[2]], 2); - if (count($parts) === 1) { - mirror_redirect("/manual/{$match[1]}/{$parts[0]}.php"); - } else { - mirror_redirect("/manual/{$match[1]}/{$parts[0]}.php#{$parts[1]}"); +if (preg_match("!^manual/([^/]+)/([^/]+?)(?:\.php)?$!", $URI, $match)) { + $target = $docRedirects['both'][$match[2]] ?? $docRedirects['in_manual'][$match[2]] ?? null; + if ($target !== null) { + [$id, $anchor] = array_pad(explode('#', $target, 2), 2, ''); + status_header(301); + mirror_redirect("/manual/$match[1]/$id.php" . ($anchor !== '' ? "#$anchor" : '')); } } -$manual_redirections = [ - 'class.oci-lob' => 'class.ocilob', - 'oci-lob.append' => 'ocilob.append', - 'oci-lob.close' => 'ocilob.close', - 'oci-lob.eof' => 'ocilob.eof', - 'oci-lob.erase' => 'ocilob.erase', - 'oci-lob.export' => 'ocilob.export', - 'oci-lob.flush' => 'ocilob.flush', - 'oci-lob.free' => 'ocilob.free', - 'oci-lob.getbuffering' => 'ocilob.getbuffering', - 'oci-lob.import' => 'ocilob.import', - 'oci-lob.load' => 'ocilob.load', - 'oci-lob.read' => 'ocilob.read', - 'oci-lob.rewind' => 'ocilob.rewind', - 'oci-lob.save' => 'ocilob.save', - 'oci-lob.seek' => 'ocilob.seek', - 'oci-lob.setbuffering' => 'ocilob.setbuffering', - 'oci-lob.size' => 'ocilob.size', - 'oci-lob.tell' => 'ocilob.tell', - 'oci-lob.truncate' => 'ocilob.truncate', - 'oci-lob.write' => 'ocilob.write', - 'oci-lob.writetemporary' => 'ocilob.writetemporary', - 'class.oci-collection' => 'class.ocicollection', - 'oci-collection.append' => 'ocicollection.append', - 'oci-collection.assign' => 'ocicollection.assign', - 'oci-collection.assignelem' => 'ocicollection.assignelem', - 'oci-collection.free' => 'ocicollection.free', - 'oci-collection.getelem' => 'ocicollection.getelem', - 'oci-collection.max' => 'ocicollection.max', - 'oci-collection.size' => 'ocicollection.size', - 'oci-collection.trim' => 'ocicollection.trim', - 'splstack.setiteratormode' => 'spldoublylinkedlist.setiteratormode', - 'splqueue.setiteratormode' => 'spldoublylinkedlist.setiteratormode', - 'class.allow-dynamic-properties' => 'class.allowdynamicproperties', - 'class.return-type-will-change' => 'class.returntypewillchange', - 'class.sensitive-parameter' => 'class.sensitiveparameter', - 'function.curl-file-create' => 'curlfile.construct', - 'simplexmliterator.current' => 'simplexmlelement.current', - 'simplexmliterator.getchildren' => 'simplexmlelement.getchildren', - 'simplexmliterator.haschildren' => 'simplexmlelement.haschildren', - 'simplexmliterator.key' => 'simplexmlelement.key', - 'simplexmliterator.next' => 'simplexmlelement.next', - 'simplexmliterator.rewind' => 'simplexmlelement.rewind', - 'simplexmliterator.valid' => 'simplexmlelement.valid', -]; +// Whatever the language of the requested page, it moves to the English +// extensions manual. Requests already in that manual are not redirected. +if (preg_match("#^manual/(?!extensions/)[^/]+/([^/]+?)(?:\.php)?$#", $URI, $match)) { + $target = $extRedirects['in_manual'][$match[1]] ?? null; + if ($target !== null) { + status_header(301); + mirror_redirect("/manual/extensions/$target.php"); + } +} + +// Bare-URI shortcut lookup. Try three forms in order +// 1. raw lowercased $URI ("if", "mysqli_get_client_version") +// 2. underscore -> hyphen $term ("short-open-tag" via /short_open_tag) +// 3. $-stripped $term ("-server" via /$_SERVER) +$target = null; +$term = str_replace('_', '-', $URI); +foreach ([$URI, $term, ltrim($term, '$')] as $key) { + if (isset($docRedirects['both'][$key])) { + $target = $docRedirects['both'][$key]; + break; + } + if (isset($docRedirects['shortcut'][$key])) { + $target = $docRedirects['shortcut'][$key]; + break; + } +} -if (preg_match("!^manual/([^/]+)/([^/]+?)(?:\.php)?$!", $URI, $match) && isset($manual_redirections[$match[2]])) { +if ($target !== null) { + [$id, $anchor] = array_pad(explode('#', $target, 2), 2, ''); + $url = "/manual/$LANG/$id.php"; + if ($queryString !== '') { + $url .= '?' . $queryString; + } + if ($anchor !== '') { + $url .= "#$anchor"; + } status_header(301); - mirror_redirect("/manual/$match[1]/" . $manual_redirections[$match[2]]); + mirror_redirect($url); } // ============================================================================ -// Define shortcuts for PHP files, manual pages and external redirects -$uri_aliases = [ - +// Site-level shortcuts to non-manual pages and external URL keys. +// (Doc-page shortcuts live in doc-en/redirects.xml.) +$site_aliases = [ # PHP page shortcuts "download" => "downloads", "getphp" => "downloads", @@ -362,224 +289,38 @@ "subscribe" => "mailing-lists", "logos" => "download-logos", - # manual shortcuts - "intro" => "introduction", - "whatis" => "introduction", - "whatisphp" => "introduction", - "what_is_php" => "introduction", - "composer" => "install.composer.intro", - - "windows" => "install.windows", - "win32" => "install.windows", - - "globals" => "language.variables.predefined", - "register_globals" => "security.globals", - "registerglobals" => "security.globals", - "manual/en/security.registerglobals.php" => "security.globals", // fix for 4.3.8 configure - "magic_quotes" => "security.magicquotes", - "magicquotes" => "security.magicquotes", - "gd" => "image", - "bcmath" => "bc", - 'streams' => 'book.stream', - "mongodb" => "book.mongodb", - "hrtime" => "function.hrtime", // Prefer function over PECL ext - - "callback" => "language.pseudo-types", - "number" => "language.pseudo-types", - "mixed" => "language.pseudo-types", - "bool" => "language.types.boolean", - "boolean" => "language.types.boolean", - "int" => "language.types.integer", - "integer" => "language.types.integer", - "float" => "language.types.float", - "string" => "language.types.string", - "heredoc" => "language.types.string", - "<<<" => "language.types.string", - "object" => "language.types.object", - "null" => "language.types.null", - 'callable' => 'language.types.callable', - - "htaccess" => "configuration.changes", - "php_value" => "configuration.changes", - - "operator.precedence" => "language.operators.precedence", - "operator-precedence" => "language.operators.precedence", - "precedence" => "language.operators.precedence", - "arithmetic.operators" => "language.operators.arithmetic", - "arithmetic-operators" => "language.operators.arithmetic", - "assignment.operators" => "language.operators.assignment", - "assignment-operators" => "language.operators.assignment", - "bitwise.operators" => "language.operators.bitwise", - "bitwise-operators" => "language.operators.bitwise", - "comparison.operators" => "language.operators.comparison", - "comparison-operators" => "language.operators.comparison", - "error.control" => "language.operators.errorcontrol", - "error-control" => "language.operators.errorcontrol", - "backtick" => "language.operators.execution", - "backtick.operator" => "language.operators.execution", - "backtick-operator" => "language.operators.execution", - "execution.operator" => "language.operators.execution", - "increment.operators" => "language.operators.increment", - "increment-operators" => "language.operators.increment", - "logical.operators" => "language.operators.logical", - "logical-operators" => "language.operators.logical", - "string.operators" => "language.operators.string", - "string-operators" => "language.operators.string", - "type.operators" => "language.operators.type", - "type-operators" => "language.operators.type", - "string.type" => "language.types.string", - "array.type" => "language.types.array", - "object.type" => "language.types.object", - "statements" => "control-structures.intro", - "control.structures" => "control-structures.intro", - "control-structures" => "control-structures.intro", - - "ternary" => "language.operators.comparison", - "instanceof" => "language.operators.type", - "if" => "language.control-structures", - "static" => "language.variables.scope", - "global" => "language.variables.scope", - "@" => "language.operators.errorcontrol", - "&" => "language.references", - "**" => "language.operators.arithmetic", - "..." => "functions.arguments", - "splat" => "functions.arguments", - "arrow" => "functions.arrow", - "fn" => "functions.arrow", - "pipe" => "operators.functional", - "|>" => "operators.functional", - // ?:, ??, ??= - // These shortcuts can not be captured here since they - // don't actually produce a 404 error. - // Instead, we have a small check in index.php directly. - - "dowhile" => "control-structures.do.while", - - "tut" => "tutorial", - "tut.php" => "tutorial", // BC - - "faq.php" => "faq", // BC - "bugs.php" => "bugs", // BC - "bugstats.php" => "bugstats", // BC - "docs-echm.php" => "download-docs", // BC - - "odbc" => "uodbc", // BC - - "links" => "support", // BC - "links.php" => "support", // BC - "oracle" => "oci8", - "cli" => "features.commandline", - - "oop" => "language.oop5", - "enum" => "language.enumerations", - "enums" => "language.enumerations", - - "const" => "language.constants.syntax", - "class" => "language.oop5.basic", - "new" => "language.oop5.basic", - "extends" => "language.oop5.basic", - "clone" => "language.oop5.cloning", - "construct" => "language.oop5.decon", - "destruct" => "language.oop5.decon", - "public" => "language.oop5.visibility", - "private" => "language.oop5.visibility", - "protected" => "language.oop5.visibility", - "var" => "language.oop5.visibility", - "abstract" => "language.oop5.abstract", - "interface" => "language.oop5.interfaces", - "interfaces" => "language.oop5.interfaces", - "autoload" => "language.oop5.autoload", - "__autoload" => "language.oop5.autoload", - "language.oop5.reflection" => "book.reflection", // BC - "::" => "language.oop5.paamayim-nekudotayim", - - "__construct" => "language.oop5.decon", - "__destruct" => "language.oop5.decon", - "__call" => "language.oop5.overloading", - "__callstatic" => "language.oop5.overloading", - "__get" => "language.oop5.overloading", - "__set" => "language.oop5.overloading", - "__isset" => "language.oop5.overloading", - "__unset" => "language.oop5.overloading", - "__sleep" => "language.oop5.magic", - "__wakeup" => "language.oop5.magic", - "__tostring" => "language.oop5.magic", - "__invoke" => "language.oop5.magic", - "__set_state" => "language.oop5.magic", - "__debuginfo" => "language.oop5.magic", - "__clone" => "language.oop5.cloning", - - "throw" => "language.exceptions", - "try" => "language.exceptions", - "catch" => "language.exceptions", - "lsb" => "language.oop5.late-static-bindings", - "namespace" => "language.namespaces", - "use" => "language.namespaces.using", - - "factory" => "language.oop5.patterns", - "singleton" => "language.oop5.patterns", - - "trait" => "language.oop5.traits", - "traits" => "language.oop5.traits", - - "news.php" => "archive/index", // BC - "readme.mirror" => "mirroring", // BC - - "php5" => "language.oop5", - "zend_changes.txt" => "language.oop5", // BC - "zend2_example.phps" => "language.oop5", // BC - "zend_changes_php_5_0_0b2.txt" => "language.oop5", // BC - "zend-engine-2" => "language.oop5", // BC - "zend-engine-2.php" => "language.oop5", // BC - - "news_php_5_0_0b2.txt" => "ChangeLog-5", // BC - "news_php_5_0_0b3.txt" => "ChangeLog-5", // BC - - "manual/about-notes.php" => "manual/add-note", // BC - "software/index.php" => "software", // BC - "releases.php" => "releases/index", // BC - - "migration7" => "migration70", // Consistent with migration5 - "update_5_2.txt" => "migration52", // BC - "readme_upgrade_51.php" => "migration51", // BC - "internals" => "internals2", // BC - "configuration.directives" => "ini.core", // BC - - # regexp. BC - "regexp.reference.backslash" => "regexp.reference.escape", - "regexp.reference.circudollar" => "regexp.reference.anchors", - "regexp.reference.squarebrackets" => "regexp.reference.character-classes", - "regexp.reference.verticalbar" => "regexp.reference.alternation", - - # external shortcut aliases ;) - "dochowto" => "phpdochowto", + # BC extensions + "bugs.php" => "bugs", // routed through $external_redirects + "bugstats.php" => "bugstats", // routed through $external_redirects + "docs-echm.php" => "download-docs", + "links" => "support", + "links.php" => "support", + + # News / releases / software + "news.php" => "archive/index", + "news_php_5_0_0b2.txt" => "ChangeLog-5", + "news_php_5_0_0b3.txt" => "ChangeLog-5", + "software/index.php" => "software", + "releases.php" => "releases/index", + "release-candidates.php" => "pre-release-builds", + "changelog-8.php" => "ChangeLog-8", - # CVS -> SVN + # Mirroring / VCS legacy + "readme.mirror" => "mirroring", + "cvsup" => "mirroring", "anoncvs.php" => "git", "cvs-php.php" => "git-php", - - # SVN -> Git "svn" => "git", "svn.php" => "git", "svn-php" => "git-php", "svn-php.php" => "git-php", - # CVSUp -> Nada - "cvsup" => "mirroring", - - # Other items - "security/crypt" => "security/crypt_blowfish", - - # Bugfixes - "array_sort" => "sort", // #64743 - "array-sort" => "sort", // #64743 + # External shortcut aliases (resolved via $external_redirects) + "dochowto" => "phpdochowto", # Removed pages - "tips.php" => "urlhowto", "tips" => "urlhowto", - "release-candidates.php" => "pre-release-builds", - - "changelog-8.php" => "ChangeLog-8", + "tips.php" => "urlhowto", ]; $external_redirects = [ @@ -608,116 +349,13 @@ "sunglasses" => "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // Temporary easter egg for bug#66144 ]; -// Temporary hack to fix bug #49956 for mysqli -- Please don't hate me for this. Data taken from mysqli/summary.xml -$mysqli_redirects = [ - "mysqli_affected_rows" => "mysqli.affected-rows", - "mysqli_get_client_version" => "mysqli.client-version", - "mysqli_connect_errno" => "mysqli.connect-errno", - "mysqli_connect_error" => "mysqli.connect-error", - "mysqli_errno" => "mysqli.errno", - "mysqli_error" => "mysqli.error", - "mysqli_field_count" => "mysqli.field-count", - "mysqli_get_host_info" => "mysqli.host-info", - "mysqli_get_proto_info" => "mysqli.protocol-version", - "mysqli_get_server_version" => "mysqli.server-version", - "mysqli_info" => "mysqli.info", - "mysqli_insert_id" => "mysqli.insert-id", - "mysqli_sqlstate" => "mysqli.sqlstate", - "mysqli_warning_count" => "mysqli.warning-count", - "mysqli_autocommit" => "mysqli.autocommit", - "mysqli_change_user" => "mysqli.change-user", - "mysqli_character_set_name" => "mysqli.character-set-name", - "mysqli_close" => "mysqli.close", - "mysqli_commit" => "mysqli.commit", - "mysqli_connect" => "mysqli.construct", - "mysqli_debug" => "mysqli.debug", - "mysqli_dump_debug_info" => "mysqli.dump-debug-info", - "mysqli_get_charset" => "mysqli.get-charset", - "mysqli_get_connection_stats" => "mysqli.get-connection-stats", - "mysqli_get_client_info" => "mysqli.get-client-info", - "mysqli_get_client_stats" => "mysqli.get-client-stats", - "mysqli_get_cache_stats" => "mysqli.get-cache-stats", - "mysqli_get_server_info" => "mysqli.get-server-info", - "mysqli_get_warnings" => "mysqli.get-warnings", - "mysqli_init" => "mysqli.init", - "mysqli_kill" => "mysqli.kill", - "mysqli_more_results" => "mysqli.more-results", - "mysqli_multi_query" => "mysqli.multi-query", - "mysqli_next_result" => "mysqli.next-result", - "mysqli_options" => "mysqli.options", - "mysqli_ping" => "mysqli.ping", - "mysqli_prepare" => "mysqli.prepare", - "mysqli_query" => "mysqli.query", - "mysqli_real_connect" => "mysqli.real-connect", - "mysqli_real_escape_string" => "mysqli.real-escape-string", - "mysqli_real_query" => "mysqli.real-query", - "mysqli_refresh" => "mysqli.refresh", - "mysqli_rollback" => "mysqli.rollback", - "mysqli_select_db" => "mysqli.select-db", - "mysqli_set_charset" => "mysqli.set-charset", - "mysqli_set_local_infile_default" => "mysqli.set-local-infile-default", - "mysqli_set_local_infile_handler" => "mysqli.set-local-infile-handler", - "mysqli_ssl_set" => "mysqli.ssl-set", - "mysqli_stat" => "mysqli.stat", - "mysqli_stmt_init" => "mysqli.stmt-init", - "mysqli_store_result" => "mysqli.store-result", - "mysqli_thread_id" => "mysqli.thread-id", - "mysqli_thread_safe" => "mysqli.thread-safe", - "mysqli_use_result" => "mysqli.use-result", - "mysqli_stmt_affected_rows" => "mysqli-stmt.affected-rows", - "mysqli_stmt_errno" => "mysqli-stmt.errno", - "mysqli_stmt_error" => "mysqli-stmt.error", - "mysqli_stmt_field_count" => "mysqli-stmt.field-count", - "mysqli_stmt_insert_id" => "mysqli-stmt.insert-id", - "mysqli_stmt_param_count" => "mysqli-stmt.param-count", - "mysqli_stmt_sqlstate" => "mysqli-stmt.sqlstate", - "mysqli_stmt_attr_get" => "mysqli-stmt.attr-get", - "mysqli_stmt_attr_set" => "mysqli-stmt.attr-set", - "mysqli_stmt_bind_param" => "mysqli-stmt.bind-param", - "mysqli_stmt_bind_result" => "mysqli-stmt.bind-result", - "mysqli_stmt_close" => "mysqli-stmt.close", - "mysqli_stmt_data_seek" => "mysqli-stmt.data-seek", - "mysqli_stmt_execute" => "mysqli-stmt.execute", - "mysqli_stmt_fetch" => "mysqli-stmt.fetch", - "mysqli_stmt_free_result" => "mysqli-stmt.free-result", - "mysqli_stmt_get_result" => "mysqli-stmt.get-result", - "mysqli_stmt_get_warnings" => "mysqli-stmt.get-warnings", - "mysqli_stmt_more_results" => "mysqli-stmt.more-results", - "mysqli_stmt_next_result" => "mysqli-stmt.next-result", - "mysqli_stmt_num_rows" => "mysqli-stmt.num-rows", - "mysqli_stmt_prepare" => "mysqli-stmt.prepare", - "mysqli_stmt_reset" => "mysqli-stmt.reset", - "mysqli_stmt_result_metadata" => "mysqli-stmt.result-metadata", - "mysqli_stmt_send_long_data" => "mysqli-stmt.send-long-data", - "mysqli_stmt_store_result" => "mysqli-stmt.store-result", - "mysqli_field_tell" => "mysqli-result.current-field", - "mysqli_num_fields" => "mysqli-result.field-count", - "mysqli_fetch_lengths" => "mysqli-result.lengths", - "mysqli_num_rows" => "mysqli-result.num-rows", - "mysqli_data_seek" => "mysqli-result.data-seek", - "mysqli_fetch_all" => "mysqli-result.fetch-all", - "mysqli_fetch_array" => "mysqli-result.fetch-array", - "mysqli_fetch_assoc" => "mysqli-result.fetch-assoc", - "mysqli_fetch_field_direct" => "mysqli-result.fetch-field-direct", - "mysqli_fetch_field" => "mysqli-result.fetch-field", - "mysqli_fetch_fields" => "mysqli-result.fetch-fields", - "mysqli_fetch_object" => "mysqli-result.fetch-object", - "mysqli_fetch_row" => "mysqli-result.fetch-row", - "mysqli_field_seek" => "mysqli-result.field-seek", - "mysqli_free_result" => "mysqli-result.free", - "mysqli_embedded_server_end" => "mysqli-driver.embedded-server-end", - "mysqli_embedded_server_start" => "mysqli-driver.embedded-server-start", -]; - -// Merge this temporary hack with $uri_aliases so it'll be treated as such -$uri_aliases = array_merge($uri_aliases, $mysqli_redirects); - // ============================================================================ -// "Rewrite" the URL, if it was a shortcut +// "Rewrite" the URL, if it was a site-level shortcut. If the target +// resolves to a real .php in web-php, redirect immediately; otherwise +// mutate $URI and fall through so $external_redirects can catch it. -if (isset($uri_aliases[$URI])) { - $URI = $uri_aliases[$URI]; - /* If it was a page alias, redirect right away */ +if (isset($site_aliases[$URI])) { + $URI = $site_aliases[$URI]; if (file_exists(ProjectGlobals::getPublicRoot() . "/$URI.php")) { $target = "/$URI.php"; if ($queryString !== '') { diff --git a/tests/EndToEnd/ExtensionsMigration/journeys.browser.spec.ts b/tests/EndToEnd/ExtensionsMigration/journeys.browser.spec.ts new file mode 100644 index 0000000000..794a81b8f8 --- /dev/null +++ b/tests/EndToEnd/ExtensionsMigration/journeys.browser.spec.ts @@ -0,0 +1,90 @@ +import {devices, expect, test} from '@playwright/test'; +import {language} from './manuals'; + +/** + * What a visitor does in a browser: follow an old link, go from one manual to + * the other and come back, type a shortcut. + */ + +test('an old URL lands on the page of the extensions manual after a 301', async ({page}) => { + const response = await page.goto(`/manual/${language}/function.apcu-add.php`); + + await expect(page).toHaveURL(/\/manual\/extensions\/function\.apcu-add\.php$/); + await expect(page.locator('h1.refname')).toHaveText('apcu_add'); + expect(response?.status()).toBe(200); + + const previous = response?.request().redirectedFrom(); + expect(previous, 'the browser was redirected').not.toBeNull(); + expect((await previous?.response())?.status()).toBe(301); +}); + +test('from the language manual to the extensions manual, and back', async ({page}) => { + await page.goto(`/manual/${language}/aliases.php`); + await expect(page.locator('#changelang'), 'the language manual has a chooser').toHaveCount(1); + + await page.locator('a[href*="../extensions/"]').first().click(); + await expect(page).toHaveURL(/\/manual\/extensions\//); + await expect(page.locator('#changelang'), 'the extensions manual has none').toHaveCount(0); + await expect(page.locator('#breadcrumbs-inner')).toContainText('Third-Party Extension'); + + // The way back: a page of the extensions manual that links to the main manual. + await page.goto('/manual/extensions/apcu.installation.php'); + await page.locator(`a[href$="/manual/${language}/install.pecl.php"]`).first().click(); + await expect(page).toHaveURL(new RegExp(`/manual/${language}/install\\.pecl\\.php$`)); + await expect(page.locator('#changelang')).toHaveCount(1); + await expect(page.locator('html')).toHaveAttribute('lang', language); +}); + +test('the breadcrumbs of the extensions manual stay inside it', async ({page}) => { + await page.goto('/manual/extensions/function.apcu-add.php'); + + await page.locator('#breadcrumbs-inner a', {hasText: 'Third-Party Extensions'}).click(); + await expect(page).toHaveURL(/\/manual\/extensions\/extensions\.php$/); + + await page.locator('#breadcrumbs-inner a', {hasText: 'Third-Party Extension Manual'}).click(); + await expect(page).toHaveURL(/\/manual\/extensions\/index\.php$/); +}); + +test('a shortcut finds the page of a function that moved', async ({page}) => { + await page.goto('/apcu_add'); + + await expect(page).toHaveTitle(/apcu_add/); + await expect(page.locator('h1.refname')).toHaveText('apcu_add'); +}); + +test('the extensions manual reads without JavaScript', async ({browser}) => { + const context = await browser.newContext({javaScriptEnabled: false}); + const page = await context.newPage(); + + await page.goto('/manual/extensions/function.apcu-add.php'); + await expect(page.locator('h1.refname')).toBeVisible(); + await page.locator('#breadcrumbs-inner a', {hasText: 'Third-Party Extensions'}).click(); + await expect(page).toHaveURL(/\/manual\/extensions\/extensions\.php$/); + + await context.close(); +}); + +test('a page of the extensions manual does not overflow on a phone', async ({browser}) => { + const context = await browser.newContext({...devices['Pixel 5']}); + const page = await context.newPage(); + + await page.goto('/manual/extensions/function.apcu-add.php'); + await expect(page.locator('h1.refname')).toBeVisible(); + + const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth); + expect(overflow, 'horizontal overflow in pixels').toBeLessThanOrEqual(1); + + await test.info().attach('phone', {body: await page.screenshot(), contentType: 'image/png'}); + await context.close(); +}); + +test('the pages of the two manuals carry the right language and canonical URL', async ({page}) => { + await page.goto('/manual/extensions/book.apcu.php'); + await expect(page.locator('html')).toHaveAttribute('lang', 'en'); + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', 'https://www.php.net/manual/extensions/book.apcu.php'); + await expect(page.locator('link[hreflang]'), 'no alternate language').toHaveCount(1); // only x-default + + await page.goto(`/manual/${language}/function.strlen.php`); + await expect(page.locator('html')).toHaveAttribute('lang', language); + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', `https://www.php.net/manual/${language}/function.strlen.php`); +}); diff --git a/tests/EndToEnd/ExtensionsMigration/links.api.spec.ts b/tests/EndToEnd/ExtensionsMigration/links.api.spec.ts new file mode 100644 index 0000000000..62cadcb108 --- /dev/null +++ b/tests/EndToEnd/ExtensionsMigration/links.api.spec.ts @@ -0,0 +1,101 @@ +import {expect, test} from '@playwright/test'; +import { + extensionPages, + inParallel, + knownMissingExtensionPages, + knownStaleLanguagePages, + language, + languagePages, + pageContent, + sample, +} from './manuals'; + +/** + * The links between the two manuals, in both directions, and the navigation + * links of the pages of the extensions manual. + */ + +function distinct(pattern: RegExp, pages: string[], directory: 'extensions' | 'language'): string[] { + const links = new Set(); + for (const page of pages) { + for (const match of pageContent(directory, page).matchAll(pattern)) { + links.add(match[1]); + } + } + + return [...links].sort(); +} + +test(`from the ${language} manual to the extensions manual`, async ({request}) => { + const links = distinct(/href="\.\.\/extensions\/([^"#]+)\.php/g, languagePages, 'language'); + expect(links.length, 'the language manual links to the extensions manual').toBeGreaterThan(10); + + const failures: string[] = []; + await inParallel(links, 20, async (page) => { + const response = await request.get(`/manual/extensions/${page}.php`, {maxRedirects: 0}); + if (response.status() !== 200 && !knownMissingExtensionPages.includes(page)) { + failures.push(`${page}: ${response.status()}`); + } + }); + + expect(failures, `${links.length} distinct links`).toEqual([]); +}); + +test(`from the extensions manual back to the ${language} manual`, async ({request}) => { + const links = distinct(new RegExp(`href="[^"]*/manual/${language}/([^"#]+)\\.php`, 'g'), extensionPages, 'extensions'); + expect(links.length, 'the extensions manual links back').toBeGreaterThan(50); + + const failures: string[] = []; + await inParallel(links, 20, async (page) => { + const response = await request.get(`/manual/${language}/${page}.php`, {maxRedirects: 0}); + if (response.status() !== 200 && !knownStaleLanguagePages.includes(page)) { + failures.push(`${page}: ${response.status()}`); + } + }); + + expect(failures, `${links.length} distinct links`).toEqual([]); +}); + +test('the navigation links of the pages of the extensions manual answer 200', async ({request}) => { + const failures: string[] = []; + + await inParallel(sample(extensionPages, 25), 10, async (page) => { + const response = await request.get(`/manual/extensions/${page}.php`); + const html = await response.text(); + + for (const match of html.matchAll(/ { + const failures: string[] = []; + const withoutBreadcrumbs: string[] = []; + + await inParallel(sample(extensionPages, 60), 10, async (page) => { + const url = `/manual/extensions/${page}.php`; + const html = await (await request.get(url)).text(); + const crumbs = html.match(/