diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml deleted file mode 100644 index 36a9cb2..0000000 --- a/.github/workflows/deploy-gh-pages.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Deploy to GitHub pages - -on: - push: - branches: [ "master" ] - workflow_dispatch: - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "gh-pages" - cancel-in-progress: false - -permissions: {} - -jobs: - build: - name: Build site - runs-on: ubuntu-latest - if: github.repository_owner == 'silverstripe' - permissions: - contents: read - steps: - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.1 - - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup GH pages - id: pages - uses: actions/configure-pages@v5 - - - name: Build static files - shell: bash - run: | - composer install - ./makedoc.sh - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: "htdocs/" - - deploy: - name: Deploy site - needs: build - runs-on: ubuntu-latest - if: github.repository_owner == 'silverstripe' - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{steps.deployment.outputs.page_url}} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..d27fe4d --- /dev/null +++ b/.htaccess @@ -0,0 +1,59 @@ + + SetEnv HTTP_MOD_REWRITE On + RewriteEngine On + + + ### =============== Legacy redirects =============== + + # Packages no longer exist + RewriteCond %{REQUEST_URI} ^(.*)en/.*/package-.*\.html [NC] + RewriteRule ^(.*)$ %1 [L,R=302] + + # 2.x no longer exists + RewriteCond %{REQUEST_URI} ^(.*)en/2[.] [NC] + RewriteRule ^(.*)$ %1 [L,R=302] + + # Redirect non-languaged minor versions to major version, e.g. /3.1/DataObject.html to /3/DataObject.html + RewriteCond %{REQUEST_URI} ^(.*)(\d)[.]\d/(.*)$ [NC] + RewriteRule ^(\d)\.\d/(.*)$ %1%2/%3 [L,R=302] + + # Redirect language minor version to major version, e.g. /en/3.1/File.html to /3/File.html + RewriteCond %{REQUEST_URI} ^(.*)en/([\d]+)[.][\w.]+/(.*)$ [NC] + RewriteRule ^(.*)$ %1%2/%3 [L,R=302] + + # Class redirect. E.g. + # /4/class-SilverStripe.AssetAdmin.Forms.FileSearchFormFactory.html + # /4/SilverStripe/AssetAdmin/Forms/FileSearchFormFactory.html + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3.html [L,R=302] + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3/%4.html [L,R=302] + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.([\w]+)\.([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3/%4/%5.html [L,R=302] + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3/%4/%5/%6.html [L,R=302] + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3/%4/%5/%6/%7.html [L,R=302] + RewriteCond %{REQUEST_URI} ^(.*)([\w.]+)/class-([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.([\w]+)\.html [NC] + RewriteRule ^(.*)$ %1%2/%3/%4/%5/%6/%7/%8.html [L,R=302] + + ### ============= End Legacy redirects ============= + + # Lookup script (used to be a SS app, now just a simple script) + # E.g. http://api.silverstripe.org.loc/search/lookup/?q=SilverStripe\ORM\HasManyList&version=4&module=framework + RewriteCond %{REQUEST_URI} ^(.*)search/lookup [NC] + RewriteRule ^/?search/lookup(/|$) %1search/lookup.php [L,R=302,QSA] + + # Symlink to 5 index.html - default major version + RewriteCond %{REQUEST_URI} ^(.*)$ [NC] + RewriteRule ^/?$ %15/index.html [R=302,L] + + # Serve docs in htdocs + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} .* [NC] + RewriteRule ^(.*)$ htdocs/$1 + + # 404 handling + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule .* errors/404.html + diff --git a/.platform.yml b/.platform.yml new file mode 100644 index 0000000..c66e599 --- /dev/null +++ b/.platform.yml @@ -0,0 +1,17 @@ +infrastructure: ^3 +php_settings: + version: 8.1 + cli: + max_execution_time: 0 +shared_dirs: + mysite: + data: {} + htdocs: {} +crons: + mytask: + time: "0 3 * * *" + command: "/var/www/mysite/www/makedoc.sh | logger -t SilverStripe_cron" + vhost: "mysite" +url_rules: + mysite: + - '^.*': 'apache' diff --git a/browserconfig.xml b/browserconfig.xml new file mode 100644 index 0000000..83b318f --- /dev/null +++ b/browserconfig.xml @@ -0,0 +1,12 @@ + + + + + + + + + #005b94 + + + diff --git a/conf/doctum.json b/conf/doctum.json index 33f8eb9..bf9d5c6 100644 --- a/conf/doctum.json +++ b/conf/doctum.json @@ -8,7 +8,6 @@ "4": "4.x", "3": "3.x" }, - "default_version": "5", "paths": { "www": "htdocs", "cache": "data/cache", diff --git a/conf/doctum.php b/conf/doctum.php index eeb555a..7051914 100644 --- a/conf/doctum.php +++ b/conf/doctum.php @@ -2,13 +2,13 @@ use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use Doctum\Project; use Doctum\Doctum; use SilverStripe\ApiDocs\Data\ApiJsonStore; use SilverStripe\ApiDocs\Data\Config; use SilverStripe\ApiDocs\Inspections\RecipeFinder; use SilverStripe\ApiDocs\Inspections\RecipeVersionCollection; use SilverStripe\ApiDocs\RemoteRepository\SilverStripeRemoteRepository; -use SilverStripe\ApiDocs\SilverstripeProject; // Get config $config = Config::getConfig(); @@ -70,7 +70,7 @@ // Override project unset($doctum['project']); $doctum['project'] = function ($sc) { - $project = new SilverstripeProject($sc['store'], $sc['_versions'], array( + $project = new Project($sc['store'], $sc['_versions'], array( 'build_dir' => $sc['build_dir'], 'cache_dir' => $sc['cache_dir'], 'remote_repository' => $sc['remote_repository'], @@ -82,7 +82,6 @@ 'source_dir' => $sc['source_dir'], 'insert_todos' => $sc['insert_todos'], 'base_url' => $sc['base_url'], - 'favicon' => '/favicon.ico', 'footer_link' => [ 'href' => 'https://github.com/silverstripe/api.silverstripe.org', 'rel' => 'noreferrer noopener', diff --git a/conf/themes/silverstripe/404.twig b/conf/themes/silverstripe/404.twig deleted file mode 100644 index 4f3a558..0000000 --- a/conf/themes/silverstripe/404.twig +++ /dev/null @@ -1,40 +0,0 @@ - - - - {# NOTE: Do not put this file into the manifest.yml, as that would render it per version rather than in the base dir #} - Not Found | {{ project.config('title') }} - - - - {% if project.config('favicon') %} - - {%- endif %} - - - - -
-
-
-

Whoa 404, either we broke something or you had a typing mishap :-/

-

Let's go back home.

-
-
- - diff --git a/conf/themes/silverstripe/main_index.twig b/conf/themes/silverstripe/main_index.twig deleted file mode 100644 index a498649..0000000 --- a/conf/themes/silverstripe/main_index.twig +++ /dev/null @@ -1,16 +0,0 @@ - - - - {# NOTE: Do not put this file into the manifest.yml, as that would render it per version rather than as the main index file #} - Redirecting... - - - - {% if project.config('favicon') %} - - {%- endif %} - - -

Redirecting... if you aren't redirected automatically, click here

- - diff --git a/conf/themes/silverstripe/main_search.twig b/conf/themes/silverstripe/main_search.twig deleted file mode 100644 index d819029..0000000 --- a/conf/themes/silverstripe/main_search.twig +++ /dev/null @@ -1,68 +0,0 @@ - - - - {# NOTE: Do not put this file into the manifest.yml, as that would render it per version rather than in the base dir #} - Redirecting... - - - {% if project.config('favicon') %} - - {%- endif %} - - - - {# Fall back to just sending users to the default search page if the redirect fails for some reason #} -

Redirecting... if you aren't redirected automatically, click here

- - diff --git a/errors/404.html b/errors/404.html new file mode 100644 index 0000000..92bab30 --- /dev/null +++ b/errors/404.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + Page not found » SilverStripe API docs + + + +
+
+
+

Whoa 404, either we broke something or you had a typing mishap :-/

+

Let's go back home.

+
+
+ + diff --git a/static/css/error-styles.css b/errors/error-styles.css similarity index 88% rename from static/css/error-styles.css rename to errors/error-styles.css index a46eb4b..ca56053 100644 --- a/static/css/error-styles.css +++ b/errors/error-styles.css @@ -1,5 +1,5 @@ .gif_holder { - background-image: url('/images/page-not-found.gif'); + background-image: url('https://www.silverstripe.com/themes/ssv3/img/banners/page-not-found.gif'); position: relative; z-index: 807; -webkit-background-size: cover; @@ -52,5 +52,5 @@ a:hover { body { padding: 0; - margin: 0; -} + margin: 0;" +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d73b243 --- /dev/null +++ b/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "SilverStripe", + "icons": [ + { + "src": "\/android-chrome-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-chrome-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-chrome-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-chrome-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} diff --git a/search/lookup.php b/search/lookup.php new file mode 100644 index 0000000..209fac3 --- /dev/null +++ b/search/lookup.php @@ -0,0 +1,14 @@ +setVersionMap(array( + '/^(\d+)[.].*$/' => '$1', +)); + +$lookup->handle(); diff --git a/src/Lookup.php b/src/Lookup.php new file mode 100644 index 0000000..719d63b --- /dev/null +++ b/src/Lookup.php @@ -0,0 +1,190 @@ +args = $args; + } + + /** + * @return string[] + */ + public function getArgs() + { + return $this->args; + } + + /** + * Return an argument from the array + * + * @param string $key + * @return string + */ + public function getArg($key) + { + if (array_key_exists($key, $this->args)) { + return $this->args[$key]; + } + return null; + } + + /** + * Redirect the user to where they need to go. + * E.g. http://api.silverstripe.org/search/lookup/?q=SilverStripe\ORM\HasManyList&version=4&module=framework + * redirects to /4/SilverStripe/ORM/HasManyList.html + * + * @param bool $return When true, the redirect URL will be returned instead of a header issued + * @return string|null + */ + public function handle($return = false) + { + $url = $this->getURL(); + if ($return) { + return $url; + } + header('Location: ' . $url); + return null; + } + + /** + * Allow setting the "version mapping" that can be used to convert "4" to "master", etc + * + * Useful in the event that modules have strange branching strategies + * + * @param array $map + * @return $this + */ + public function setVersionMap($map) + { + $this->versionMap = $map; + return $this; + } + + /** + * Get the version from the URL, check for manually set version mapping + * + * @return string + */ + public function getVersion() + { + $version = $this->getArg('version') ?: self::DEFAULT_BRANCH; + foreach ($this->versionMap as $rule => $substitution) { + // Check regular expression rule + if (strpos($rule, '/') === 0 && preg_match($rule, $version)) { + return preg_replace($rule, $substitution, $version); + } + // Check exact rule + if (strpos($rule, '/') === false && $rule === $version) { + return $substitution; + } + } + return $version; + } + + /** + * Base dir + * + * @return string + */ + public function getBaseDir() + { + return __DIR__ . '/..'; + } + + /** + * Set the server name to use for API reference links + * + * @param string $name + * @return $this + */ + public function setServerName($name) + { + $this->serverName = $name; + return $this; + } + + /** + * Given a config determine the URL to navigate to + * + * @param array $searchConfig + * @return string + */ + protected function getURLForClass(array $searchConfig): string + { + $searchPath = '/' . $this->getVersion() . '/' . str_replace('\\', '/', $searchConfig['class']) . '.html'; + + // If file doesn't exist, redirect to search + if (!file_exists($this->getBaseDir() . '/htdocs' . $searchPath)) { + return '/' . $this->getVersion() . '/search.html?search=' . urlencode($searchConfig['class']); + } + + // Add hash-link on end + if ($searchConfig['property'] && $searchConfig['type']) { + $searchPath .= '#' . $searchConfig['type'] . '_' . $searchConfig['property']; + } + + return $searchPath; + } + + /** + * Get url for this search + * + * @return string + */ + protected function getURL(): string + { + // Search + $searchOrig = $this->getArg('q'); + if (!$searchOrig) { + return '/'; // Just go to home + } + + $search = str_replace(array('()', '$'), '', $searchOrig); + $searchParts = preg_split('/(::|\->)/', $search); + $searchConfig = array(); + if (count($searchParts) == 2) { + $searchConfig['class'] = $searchParts[0]; + $searchConfig['property'] = $searchParts[1]; + $searchConfig['type'] = (strpos($searchOrig, '()') !== false) ? 'method' : 'property'; + } else { + $searchConfig['class'] = $search; + $searchConfig['property'] = ''; + $searchConfig['type'] = 'class'; + } + + return $this->getURLForClass($searchConfig); + } +} diff --git a/src/Renderer/SilverStripeRenderer.php b/src/Renderer/SilverStripeRenderer.php index 4d9ab1c..10b9929 100644 --- a/src/Renderer/SilverStripeRenderer.php +++ b/src/Renderer/SilverStripeRenderer.php @@ -8,33 +8,6 @@ class SilverStripeRenderer extends Renderer { - /** - * @inheritDoc - * This is implemented here to promote visibility to public - */ - public function save(Project $project, $uri, $template, $variables) - { - return parent::save($project, $uri, $template, $variables); - } - - /** - * Initialise the renderer so we can render versionless templates. - * This happens when rendering version templates normally, but we want to render the global stuff first. - */ - public function init(Project $project): void - { - // This is all copied from the render() method - $this->theme = $this->getTheme($project); - $dirs = $this->theme->getTemplateDirs(); - // add parent directory to be able to extends the same template as the current one but in the parent theme - foreach ($dirs as $dir) { - $dirs[] = dirname($dir); - } - $this->twig->getLoader()->setPaths(array_unique($dirs)); - $this->twig->addGlobal('has_namespaces', $project->hasNamespaces()); - $this->twig->addGlobal('project', $project); - } - protected function renderClassTemplates(array $classes, Project $project, $callback = null) { foreach ($classes as $class) { diff --git a/src/SilverstripeProject.php b/src/SilverstripeProject.php deleted file mode 100644 index cd5bb85..0000000 --- a/src/SilverstripeProject.php +++ /dev/null @@ -1,75 +0,0 @@ -renderMainPages(); - $this->copyStaticFiles(); - parent::update($callback, $force); - $this->outputStatus(); - } - - public function render($callback = null, $force = false): void - { - $this->renderMainPages(); - $this->copyStaticFiles(); - parent::render($callback, $force); - $this->outputStatus(); - } - - /** - * Renders pages that are not version dependent such as the main index.html - */ - private function renderMainPages(): void - { - $this->version = ''; - $this->read(); - $config = Config::getConfig(); - $variables = [ - // Get array of strings of major version numbers - 'versions' => array_map(fn($version) => (string)$version, array_keys($config['versions'])), - 'default_version' => $config['default_version'], - ]; - $this->renderer->init($this); - $this->renderer->save($this, 'index.html', 'main_index.twig', $variables); - $this->renderer->save($this, 'search.html', 'main_search.twig', $variables); - $this->renderer->save($this, '404.html', '404.twig', $variables); - } - - /** - * Copies static files such as images or css into the build directory - */ - private function copyStaticFiles(): void - { - $this->filesystem->mirror(Path::join(Config::getBase(), 'static'), $this->getBuildDir(), options: ['override' => true]); - } - - /** - * Outputs the number of items in htdocs. This can be a useful debugging tool if the site doesn't build correctly. - */ - private function outputStatus(): void - { - $fileIterator = new FilesystemIterator($this->getBuildDir(), FilesystemIterator::SKIP_DOTS); - $numItems = iterator_count($fileIterator); - $output = new ConsoleOutput(); - $output->writeln("Found $numItems files/directories in htdocs"); - } - - protected function replaceVars(string $pattern): string - { - if (!$this->version) { - // Remove the version pattern if we're rendering stuff directly into htdocs - $pattern = str_replace('%version%/', '', $pattern); - } - return parent::replaceVars($pattern); - } -} diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index f5d53b6..0000000 Binary files a/static/favicon.ico and /dev/null differ diff --git a/static/images/page-not-found.gif b/static/images/page-not-found.gif deleted file mode 100644 index ebd084b..0000000 Binary files a/static/images/page-not-found.gif and /dev/null differ diff --git a/tests/LookupTest.php b/tests/LookupTest.php new file mode 100644 index 0000000..c12cb5c --- /dev/null +++ b/tests/LookupTest.php @@ -0,0 +1,54 @@ + 'bar']); + $this->assertSame(['foo' => 'bar'], $lookup->getArgs()); + $this->assertSame('bar', $lookup->getArg('foo')); + } + + public function testGetUrl() + { + $mock = $this->createPartialMock(Lookup::class, ['getURL']); + $mock->expects($this->once())->method('getURL')->willReturn('foobar'); + + $this->assertSame('foobar', $mock->handle(true)); + } + + public function testGetVersionChecksRegularExpression() + { + $lookup = new Lookup(['version' => 'foobarbaz']); + $lookup->setVersionMap([ + '/bar/' => 'monkey', + ]); + $this->assertSame('foomonkeybaz', $lookup->getVersion()); + } + + public function testGetVersionExactRule() + { + $lookup = new Lookup(['version' => '5']); + $lookup->setVersionMap([ + '5' => '5', + ]); + $this->assertSame('5', $lookup->getVersion()); + } + + public function testGetVersionDefault() + { + $lookup = new Lookup(['version' => 'unknown']); + $lookup->setVersionMap([ + '5' => '5.x', + '4' => '4.x', + '3' => '3.x', + ]); + + $this->assertSame('unknown', $lookup->getVersion()); + } +}