{"id":14984059,"url":"https://github.com/wikimedia/toolforgebundle","last_synced_at":"2025-10-20T01:31:20.687Z","repository":{"id":33023251,"uuid":"149207375","full_name":"wikimedia/ToolforgeBundle","owner":"wikimedia","description":"A Symfony 4 \u0026 5 bundle that provides some common parts of web-based tools running on Wikimedia's Toolforge. Maintained by the Wikimedia Foundation's Community Tech team.","archived":false,"fork":false,"pushed_at":"2025-01-18T23:29:36.000Z","size":182,"stargazers_count":13,"open_issues_count":1,"forks_count":5,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-01-30T12:11:11.724Z","etag":null,"topics":["i18n","oauth-client","symfony-bundle","toolforge"],"latest_commit_sha":null,"homepage":"https://packagist.org/packages/wikimedia/toolforge-bundle","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wikimedia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-18T00:52:45.000Z","updated_at":"2025-01-18T23:20:56.000Z","dependencies_parsed_at":"2024-06-20T19:08:16.050Z","dependency_job_id":"a8d15e22-4a5b-42d7-8d40-dbee80a3d319","html_url":"https://github.com/wikimedia/ToolforgeBundle","commit_stats":{"total_commits":91,"total_committers":8,"mean_commits":11.375,"dds":"0.27472527472527475","last_synced_commit":"4128802be82c7a94dc1329471332d97ed1efa0d0"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2FToolforgeBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2FToolforgeBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2FToolforgeBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wikimedia%2FToolforgeBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wikimedia","download_url":"https://codeload.github.com/wikimedia/ToolforgeBundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237243005,"owners_count":19278060,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["i18n","oauth-client","symfony-bundle","toolforge"],"created_at":"2024-09-24T14:08:23.302Z","updated_at":"2025-10-20T01:31:15.384Z","avatar_url":"https://github.com/wikimedia.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Toolforge Bundle\n================\n\nA Symfony 5/6/7 bundle that provides some common parts of web-based tools in Wikimedia Toolforge.\n\nFeatures:\n\n* OAuth user authentication against [Meta Wiki](https://meta.wikimedia.org/).\n* Internationalization with the [Intuition](https://intuition.toolforge.org/) and jQuery.i18n libraries.\n* Interface to connect and query the [replica databases](https://wikitech.wikimedia.org/wiki/Wiki_replicas)\n* PHP Code Sniffer ruleset\n* Base Wikimedia UI stylesheet (LESS)\n\nStill to come:\n\n* Universal Language Selector (ULS)\n* Localizable routes\n* OOUI\n* CSSJanus\n* Addwiki\n* Critical error reporting to tool maintainers' email\n\n[![Packagist](https://img.shields.io/packagist/v/wikimedia/toolforge-bundle.svg)](https://packagist.org/packages/wikimedia/toolforge-bundle)\n[![License](https://img.shields.io/github/license/wikimedia/ToolforgeBundle.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![GitHub issues](https://img.shields.io/github/issues/wikimedia/ToolforgeBundle.svg)](https://github.com/wikimedia/ToolforgeBundle/issues)\n[![Build Status](https://github.com/wikimedia/ToolforgeBundle/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/wikimedia/ToolforgeBundle/actions/workflows/ci.yml?query=branch%3Amaster)\n\nPlease report all issues either on [Github](https://github.com/wikimedia/ToolforgeBundle/issues)\nor on [Phabricator](https://phabricator.wikimedia.org/tag/community-tech) (tagged with `community-tech`).\n\n## Table of Contents\n\n* [Installation](#installation)\n* [Configuration](#configuration)\n    * [OAuth](#oauth)\n    * [Internationalization (Intuition and jQuery.i18n)](#internationalization-intuition-and-jqueryi18n)\n    * [Replicas connection manager](#replicas-connection-manager)\n    * [PHP Code Sniffer](#php-code-sniffer)\n    * [Wikimedia UI styles](#wikimedia-ui-styles)\n    * [Deployment script](#deployment-script)\n    * [Sessions](#sessions)\n* [Examples](#examples)\n* [License](#license)\n\n## Installation\n\n### New project\n\nTo get a new project up and running quickly\nfirst make sure you've got [Composer](https://getcomposer.org) and the [Symfony CLI](https://symfony.com/download) installed\nand then use the [Toolforge Skeleton](https://packagist.org/packages/wikimedia/toolforge-skeleton):\n\n    composer create-project wikimedia/toolforge-skeleton ./my-cool-tool\n    cd my-cool-tool\n    symfony server:start -d\n\nNavigate to http://localhost:8000 and you should see your new tool up and running.\n\n### Existing project\n\nInstall the code in an existing Symfony project:\n\n    composer require wikimedia/toolforge-bundle\n\nRegister the bundle in your `AppKernel`:\n\n    class AppKernel extends Kernel {\n        public function registerBundles() {\n            $bundles = [\n                new Wikimedia\\ToolforgeBundle\\ToolforgeBundle(),\n            ];\n            return $bundles;\n        }\n    }\n\nOr `config/bundles.php`\n\n    Wikimedia\\ToolforgeBundle\\ToolforgeBundle::class =\u003e ['dev' =\u003e true],\n\n## Configuration\n\n### OAuth\n\nThe bundle creates three new routes `/login`, `/oauth_callback`, and `/logout`.\nYour application should have a route called `home`.\nYou need to register these with your application\nby adding the following to your `config/routes.yaml` file (or equivalent):\n\n    toolforge:\n      resource: '@ToolforgeBundle/Resources/config/routes.yaml'\n\nTo configure OAuth, first\n[apply for an OAuth consumer](https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration/propose)\non Meta Wiki with a callback URL of `\u003cyour-base-url\u003e/oauth_callback`\nand add the consumer key and secret to your `.env` file.\nThen connect these to your application's config with the following in `config/packages/toolforge.yaml`:\n\n    toolforge:\n      oauth:\n        consumer_key: '%env(OAUTH_KEY)%'\n        consumer_secret: '%env(OAUTH_SECRET)%'\n\nIf you need to authenticate to a different wiki,\nyou can also set the `toolforge.oauth.url` parameter\nto the full URL to `Special:OAuth`.\n\nAdd a login link to the relevant Twig template (often `base.html.twig`), e.g.:\n\n    {% if logged_in_user() %}\n      {{ msg( 'toolforge-logged-in-as', [ logged_in_user().username ] ) }}\n      \u003ca href=\"{{ path('toolforge_logout') }}\"\u003e{{ msg('toolforge-logout') }}\u003c/a\u003e\n    {% else %}\n      \u003ca href=\"{{ path('toolforge_login') }}\"\u003e{{ msg('toolforge-login') }}\u003c/a\u003e\n    {% endif %}\n\nThe internationalization parts of this are explained below.\nThe OAuth-specific part is the `logged_in_user()`,\nwhich is a bungle-provided Twig function\nthat gives you access to the currently logged-in user.\n\nWhile in development, it can be useful to not have to log your user in all the time.\nTo force login of a particular user (but note that you still have to click the 'login' link),\nadd a `logged_in_user` key to your `config/packages/toolforge.yml` file, e.g.:\n\n    toolforge:\n      oauth:\n        logged_in_user: '%env(LOGGED_IN_USER)%'\n\nIn controllers, you can test whether the user is logged in by checking:\n\n    $this-\u003eget('session')-\u003eget('logged_in_user')\n\n#### Redirecting after login\n\nAfter the user logs in, you may want to redirect them back to the page they originally tried to\nview, instead of the `home` route. To do this, first make sure you registered your OAuth consumer\nto accept a callback URL using the \"Allow consumer to specify a callback...\" option. The value for\nthe callback would for example be `https://my-tool.toolforge.org/oauth_callback`.\n\nThe implementation in your views is best explained by example. Let's assume the current page\nthe user sees shows a login link, and you want to redirect them back to the same page after\nthey authenticate. The code in your Twig template should look something like:\n\n    \u003ca href=\"{{ path('toolforge_login', {'callback': url('toolforge_oauth_callback', {'redirect': app.request.uri})}) }}\"\u003eLogin\u003c/a\u003e\n\nHere `app.request.uri` evaluates to the current URL the user is viewing. It is provided as the\n`redirect` for the `oauth_callback` route, which is provided as the `callback` for the `login` route.\nThe URL for the login link ends up being something like:\n\n    https://my-tool.toolforge.org/login?callback=https%3A//my-tool.toolforge.org/oauth_callback%3Fredirect%3Dhttps%253A//my-tool.toolforge.org/my-page%253Ffoo%253Dbar\n\nNote the double-encoding of the URL used for the value of `redirect`. In this example the user will\nultimately be redirected back to `https://my-tool.toolforge.org/my-page?foo=bar`.\n\n### Internationalization (Intuition and jQuery.i18n)\n\nInternationalization is handled similarly to how it is done in MediaWiki,\nwith translated strings being stored in `i18n/` directories.\nThe bundle comes with some strings of its own, all prefixed with `toolforge_`;\nit is recommended that these are used where possible because it reduces the work for translators.\n\n#### 1. PHP\n\nIn PHP, set your application's i18n 'domain' with the following in `config/packages/toolforge.yaml`:\n\n    toolforge:\n        intuition:\n            domain: 'app-name-here'\n\nYou can inject (the bundle's subclass of) Intuition into your controllers via type hinting, e.g.:\n\n    public function indexAction( Request $request, \\Wikimedia\\ToolforgeBundle\\Service\\Intuition $intuition ) { /*...*/ }\n\nThe following Twig functions and filters are available:\n\n* `msg( msg, params )` *string* Get a single message.\n* `bdi( text )` *string* Wrap a string with \u003cbdi\u003e tags for bidirectional isolation\n* `msg_exists( msg )` *bool* Check to see if a given message exists.\n* `msg_if_exists( msg, params )` *string* Get a message if it exists, or else return the provided string.\n* `lang( lang )` *string* The code of the current or given language.\n* `lang_name( lang )` *string* The name of the current or given language.\n* `all_langs()` *string[]* List of all languages defined in JSON files in the `i18n/` directory (code =\u003e name).\n* `is_rtl()` *bool* Whether the current language is right-to-left.\n* `git_tag()` *string* The current Git tag, or the short hash if there are no tags.\n* `git_branch()` *string* The current Git branch.\n* `git_hash()` *string* The current Git hash.\n* `git_hash_short()` *string* The short version of the current Git hash.\n* `\u003cnumber\u003e|num_format` *int|float* Format a number according to the current Locale.\n* `\u003cstrings\u003e|list_format` *string[]* Format an array of strings as a separated inline-list. In English this is comma-separate with 'and' before the last item.\n\n#### 2. Javascript\n\nIn Javascript, you need to do three things to enable internationalisation:\n\n1. Add the following to your main JS file (e.g. `app.js`) or `webpack.config.js`:\n\n       require('../vendor/wikimedia/toolforge-bundle/Resources/assets/toolforge.js');\n\n2. This to your HTML template (before your `app.js`):\n\n       \u003cscript type=\"text/javascript\" src=\"https://tools-static.wmflabs.org/cdnjs/ajax/libs/jquery/3.3.1/jquery.min.js\"\u003e\u003c/script\u003e\n       {% include '@toolforge/i18n.html.twig' %}\n\n   (The jQuery can be left out if you're already loading that through other means.)\n\n3. And symlink your `i18n/` directory from `public/i18n/`,\n   so that the language files can be loaded by from Javascript.\n\nThen you can get i18n messages anywhere with: `$.i18n( 'msg-name', paramOne, paramTwo )`\n\n### Replicas connection manager\n\nIf your tool connects to multiple databases on the\n[Toolforge replicas](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database),\nyou can take advantage of ToolforgeBundle's `ReplicasClient` service to ensure your application\nopens no more connections than it needs to.\n\nFor this to work, you first need to add the following to your `config/packages/doctrine.yaml`:\n\n\u003cdetails\u003e\n\u003csummary\u003edoctrine.yaml\u003c/summary\u003e\n\n```\ndoctrine:\n  dbal:\n    connections:\n      toolforge_s1:\n        host: '%env(REPLICAS_HOST_S1)%'\n        port: '%env(REPLICAS_PORT_S1)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s2:\n        host: '%env(REPLICAS_HOST_S2)%'\n        port: '%env(REPLICAS_PORT_S2)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s3:\n        host: '%env(REPLICAS_HOST_S3)%'\n        port: '%env(REPLICAS_PORT_S3)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s4:\n        host: '%env(REPLICAS_HOST_S4)%'\n        port: '%env(REPLICAS_PORT_S4)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s5:\n        host: '%env(REPLICAS_HOST_S5)%'\n        port: '%env(REPLICAS_PORT_S5)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s6:\n        host: '%env(REPLICAS_HOST_S6)%'\n        port: '%env(REPLICAS_PORT_S6)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s7:\n        host: '%env(REPLICAS_HOST_S7)%'\n        port: '%env(REPLICAS_PORT_S7)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      toolforge_s8:\n        host: '%env(REPLICAS_HOST_S8)%'\n        port: '%env(REPLICAS_PORT_S8)%'\n        user: '%env(REPLICAS_USERNAME)%'\n        password: '%env(REPLICAS_PASSWORD)%'\n      # If you need to work with toolsdb\n      toolforge_toolsdb:\n        host: '%env(TOOLSDB_HOST)%'\n        port: '%env(TOOLSDB_PORT)%'\n        user: '%env(TOOLSDB_USERNAME)%'\n        password: '%env(TOOLSDB_PASSWORD)%'\n      # If you need to work with a Trove database\n      toolforge_trove:\n        host: '%env(TROVE_HOST)%'\n        port: '%env(TROVE_PORT)%'\n        user: '%env(TROVE_USERNAME)%'\n        password: '%env(TROVE_PASSWORD)%'\n```\n\n\u003c/details\u003e\n\nAlso adding the `REPLICAS_HOST_`, `REPLICAS_USERNAME`, `REPLICAS_PASSWORD` and each\n`REPLICAS_PORT_` to .env as necessary. If new sections are added (which is rare), you will\nneed to update these accordingly.\n\nIn **production**, the `REPLICAS_HOST_S1` variables should be `s1.web.db.svc.wikimedia.cloud`\n(or `analytics` instead of `web`), and similarly for each section. The `REPLICAS_PORT_` vars\nshould be `3306` in production. For **local environments**, use `127.0.0.1` for the host vars\nand any safe range of ports (such as 4711 for `s1`, 4712 for `s2`, and so on).\n\nNext, establish an SSH tunnel to the replicas (only necessary on local environments):\n\n    php bin/console toolforge:ssh\n\nUse the `--bind-address` flag to change the binding address, if needed. This may be necessary\nfor Docker installations.\n\nIf you need to work against [tools-db](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#User_databases),\npass the `--toolsdb` flag and make sure the `TOOLSBD_` env variables are set correctly.\nUnless you have a private database, you should be able to use the same username and password\nas `REPLICAS_USERNAME` and `REPLICAS_PASSWORD`.\n\nIf you need to work against a [Trove database](https://wikitech.wikimedia.org/wiki/Help:Trove_database_user_guide),\npass the `--trove` flag, supplying the hostname, and make sure the `TROVE_` env variables are set correctly.\nThe credentials for this are separate from the replicas and toolsdb.\n\nIf your Toolforge UNIX shell username is different than your local, you'll need to specify it with `--username`.\n\nTo query the replicas, inject the `ReplicasClient` service then call the `getConnection()`\nmethod, passing in a valid database, and you should get a `Doctrine\\DBAL\\Connection` object.\nFor example:\n\n    # src/Controller/MyController.php\n    public function myMethod(ReplicasClient $client) {\n        $frConnection = $client-\u003egetConnection('frwiki');\n        $frUserId = $frConnection-\u003eexecuteQuery(\"SELECT user_id FROM user LIMIT 1\")-\u003efetch();\n        $ruConnection = $client-\u003egetConnection('ruwiki');\n        $ruUserId = $ruConnection-\u003eexecuteQuery(\"SELECT user_id FROM user LIMIT 1\")-\u003efetch();\n        # ...\n    }\n\nIn this example, `$frConnection` and `$ruConnection` actually point to the same `Connection`\ninstance, since (at the time of writing) both `frwiki` and `ruwiki` live on the\n[same section](https://noc.wikimedia.org/conf/highlight.php?file=dblists/s6.dblist).\n`ReplicasClient` knows to do this because it queries (and caches) the dblists at\nhttps://noc.wikimedia.org.\n\n### PHP Code Sniffer\n\nYou can use the bundle's phpcs rules by adding the following\nto the `require-dev` section of your project's `composer.json`:\n\n    \"slevomat/coding-standard\": \"^4.8\"\n\nAnd then referencing the bundle's ruleset with the following in your project's `.phpcs.xml`:\n\n    \u003crule ref=\"./vendor/wikimedia/toolforge-bundle/Resources/phpcs/ruleset.xml\" /\u003e\n\n### Wikimedia UI styles\n\nYou may want your tool to conform to the\n[Wikimedia Design Style Guide](https://design.wikimedia.org/style-guide/).\nA basic [LESS](http://lesscss.org/) stylesheet that applies some of these design elements\nis available in the bundle. To use it, first install the required packages:\n\n    npm install wikimedia-ui-base less less-loader\n\nAnd then import both it and the bundle's CSS file for it\n(e.g. at the top of your `assets/app.less` file):\n\n    @import '../node_modules/wikimedia-ui-base/wikimedia-ui-base.less';\n    @import '../vendor/wikimedia/toolforge-bundle/Resources/assets/wikimedia-base.less';\n\n### Deployment script\n\nThe bundle comes with a deployment script for use on Toolforge\nwhere an application is run on the Kubernetes cluster.\n\nIt should be added to your tool's crontab to run e.g. every ten minutes:\n\n    */10 * * * * /usr/bin/jsub -once -quiet /data/project/\u003ctoolname\u003e/\u003capp-dir\u003e/vendor/wikimedia/toolforge-bundle/bin/deploy.sh prod /data/project/\u003ctoolname\u003e/\u003capp-dir\u003e/\n\n* The first argument is either `prod` or `dev`,\n  depending on whether you want to run the highest tagged version,\n  or the latest master branch.\n* The second is the path to the tool's top-level directory,\n  which is usually either the tool's home directory or a directory within it\n  (e.g. `/data/project/\u003ctoolname\u003e/app`).\n\n### Sessions\n\nBy default Symfony uses `/` for sessions' cookie path, but this isn't secure on Toolforge\nbecause it means that different tools can access each other's cookies. Additionally, Toolforge\nmay by default use the fallback to the session expiry defined in php.ini, which is only\n24 minutes. To fix this, set the following in your `framework.yaml`:\n\n    framework:\n      session:\n        storage_id: Wikimedia\\ToolforgeBundle\\Service\\NativeSessionStorage\n        handler_id: 'session.handler.native_file'\n        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'\n        cookie_lifetime: 604800 # one week\n\n## Examples\n\nThis bundle is currently in use on the following projects:\n\n1. [Event Metrics](https://meta.wikimedia.org/wiki/Special:MyLanguage/Event_Metrics)\n2. [SVG Translate](https://commons.wikimedia.org/wiki/Special:MyLanguage/Commons:SVG_Translate_tool)\n3. [Global Search](https://global-search.toolforge.org)\n4. [Flickr Dashboard](https://flickrdash.toolforge.org)\n5. [Wikisource Export](https://wsexport.wmcloud.org)\n6. [Wikimedia OCR](https://ocr.wmcloud.org)\n7. [CopyPatrol](https://copypatrol.toolforge.org)\n8. [Wikisource Contests](https://wscontest.toolforge.org)\n\n## License\n\nGPL 3.0 or later.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikimedia%2Ftoolforgebundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwikimedia%2Ftoolforgebundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwikimedia%2Ftoolforgebundle/lists"}