# cnWiredBolt ## Custom Features The wired website features these custom functions: ### Custom URLs The wired website uses different urls from the default Bolt url scheme. This is the list of custom urls: - Article Details - /collection// OK - Custom-Route, Redirect - Collection Details - /collection/ OK - Collection List - /collection OK - Custom-Route - Magazin List - /magazin OK - Custom-Route - Magazin Details - /magazin/ OK - Video List - /video OK - Custom-Route - Video Details - /video// OK - Custom-Route, Redirect - Series List - /video/serien OK - Custom-Route - Series Details - /video/serien/ OK - Custom-Route, Redirect These urls are implemented in two parts: **Routes** The custom routes are defined in `app/config/routes.yml`. Default routes for contentlinks **must stay** as they are still needed by bolt's backend. All other unused routes are removed. Custom routes are added aas needed for above URLs and are marked by a "WIRED" in their comment. **Redirects** All routes that are changed for wired and need to redirect wrong/default urls to the custom urls need to be directed to the `wired.controller.frontend` Controller. This controller (found in a local extension in `extensions/local/cnd/wired`) checks incoming requests for a record and redirects to the correct url if needed. The controller extends the basic Bolt Frontend Controller. and is called inside it's `record()` method. (As suggested by Bolt lead-dev ross) ### Content Class A custom content class in the Wired extension `extensions/local/cnd/wired` overrides the link method. The new method generates the custom links as needed for above custom urls. ### Paged Content The default teaser aggregation has a "load more" method that dynamically loads additional teasers from a special html page. This page is generated by the PagedController inside the Wired extension `extensions/local/cnd/wired`. In practice, when called via Ajax, you will need to provide a list of all **manually positioned** elements inside the `exclude` parameter and add a negative offset of that number to get a proper paged list of additional elements. This will make sure that these elements will not appear in the normal cronological list and that the offset is calculated properly, no matter how many teasers you actually showed. This also means, that the additional pages **will not have** additional positioned elements. **Urls** The URL is one of these two variants: - `/paged///` - `/paged/` **Optional GET parameters** - `page` - page number (sets an offset of limit * (page-1)) One-based - `limit` - number of elements to display - `offset` - additional offset. For example 0 - the number of fixed positioned elements on page one - `exclude` - An array (PHP notation) of "/" elements to exclude from the result **Example** - /paged/articles/collection/gadgets?page=1&exclude=["articles/9515"]&offset-1 ### Content from external sources An article can be copied from an external source. In this case, a special box must apear at the article bottom and the canonical of the page has to contain the source url. The title, icon and url of the source box is configured in two places: The selectable sources and source url for an article are configured in the `contenttypes.yml` in the article block for the fields **source** and **sourceurl** ``` # Source of content that originates from external providers/partners source: label: Source type: select values: bento: Bento bid: Business Insider Deutschland sourceurl: label: Source URL type: text variant: inline ``` The available sources are configured in the `theme.yml` ``` sources: bento: title: Bento logo: /theme/wired-2016/assets/image/sources/bento.png url: http://www.bento.de/ bid: title: Business Insider Deutschland logo: /theme/wired-2016/assets/image/sources/bid.jpg url: http://www.businessinsider.de/ ``` ### Plus Content / Valiton Wired articles marked as "plus" are only accessible for users logged into the Valiton SSO system. Since **all** content on the wired website **must** be cached, the pages can only contain the not-loggedin state. All plus logic has to be client side or outside the webpages. Valiton Wiki: https://tracker.valiton.com/redmine/projects/condenast_wired_sso/wiki This is done with three parts: ### Javascript The page contains controls with loggedin and loggedout states. Only the loggedin state is shown. An Ajax call via the Valiton SDK checks if a user is logged in and shows the loggedin state if so. The Controls are in this template, to be loaded inside the navigation: `components/_plus_controls.twig`
The SDK also sets click handlers on the links inside the controls to open necessary dialogs. The button `#openHmsProfile` opens the dialog in `#hmsProfileContainer`. The button `#openHmsSignin` opens the dialog in `#hmsProfileContainer`. The initialization and scripts are in this template loaded at the bottom of the page: `components/_plus_init.twig`
The init script also contains multiple empty containers for the login/register/profile dialogs. The dialogs will be filled and managed by the Valiton SDK. If a plus page is visited and we have a logged in user, the Javascript redirects to the plus route (see below). ### PHP An ajax call to our proxy function `/plus/plus_proxy.php` checks if a user is logged in. If so and after a successful response, the script will then redirect to the plus route. ### Plus Route A special "plus" route is configured in `app/config/routing.yml` that mimics the Bolt's normal Frontend controller with one small addition. If a Valiton header e.g. `HMS-GROUP-ABO-DIGITAL` (which can be configured in the config.yml) is set (or a special debug parameter is available) (checked in local wired Extension `Services/PlusService.php`) the page will show the article but redirect to the normal route with locked content if not. All links on the "plus" route will still sho the normal non "plus" links. There also is a small Twig function to check loggedin state. This function will only check the headers if it is accessed via the "plus" route to avoid caching of plus content! ## Performance Analysis ### Initial Version Relations with custom class to avoid inverse relation problems in bolt * Homepage: 6.0s 36MB 137Q * Collection Tech: 10.3s 75MB 52Q * Tag Mobilität: 7.5s 51MB 35Q * Article (http://wired.docker/collection/tech/renault-will-insel-elektrifizieren): 5.8s 34MB 23Q ### Update to 3.4.7 Identical version with relations with custom class to avoid inverse relation problems in bolt * Homepage: 6.0s 36MB 136Q * Collection Tech: 8.6s 76MB 49Q * Tag Mobilität: 5.8s 52MB 32Q * Article (http://wired.docker/collection/tech/renault-will-insel-elektrifizieren): 5.3s 35MB 20Q ### Converted Relations to Taxonomy/Selects All Tax-Relations changed to real Taxonomies. Magazin and Authors changed to Select fields. All logic tuned to new mechanics. * Homepage: 3.6s 20MB 110Q * Collection Tech: 3.6s 20MB 25Q * Tag Mobilität: 4.5s 40MB 15Q * Article (http://wired.docker/collection/tech/renault-will-insel-elektrifizieren): 4.5s 24MB 12Q ### OpCache enabled and Composer Optimized According to the debug profiler, around 80% (3.2s) time is spent on initializing symfony. Enabling Zend OpCache and creating optimized composer autoloaders reduced this time by at least 60%. * Homepage: 2.1s 20MB 110Q -> 35% * Collection Tech: 1.5s 19MB 25Q -> 15% * Tag Mobilität: 2.5s 40MB 15Q -> 33% * Article (http://wired.docker/collection/tech/renault-will-insel-elektrifizieren): 1.4s 18MB 12Q -> 24% ### Original Site with OpCache To rule out the possibility that opcache alone is sufficient. * Homepage: 5.2s 36MB 134Q -> 86% * Collection Tech: 4.7s 76MB 52Q -> 45% * Tag Mobilität: 5.5s 51MB 33Q -> 73% * Article (http://wired.docker/collection/tech/renault-will-insel-elektrifizieren): 4.5s 34MB 23Q -> 77% ## Conversion Scripts SQL Script for Conversion ``` -- ######## Create new columns ALTER TABLE bolt_articles ADD authors LONGTEXT DEFAULT NULL COMMENT '(DC2Type:json)', ADD magazine LONGTEXT DEFAULT NULL; ALTER TABLE bolt_videos ADD series LONGTEXT DEFAULT NULL; -- ######## Convert Taxonomy-Relations to Taxonomies TRUNCATE bolt_taxonomy; -- collections INSERT INTO bolt_taxonomy (id, content_id, contenttype, taxonomytype, slug, name, sortorder) SELECT null, from_id, from_contenttype, to_contenttype, slug, title, 0 FROM bolt_relations INNER JOIN bolt_collections ON bolt_relations.to_id = bolt_collections.id WHERE to_contenttype = 'collections'; -- tags INSERT INTO bolt_taxonomy (id, content_id, contenttype, taxonomytype, slug, name, sortorder) SELECT null, from_id, from_contenttype, to_contenttype, slug, title, 0 FROM bolt_relations INNER JOIN bolt_tags ON bolt_relations.to_id = bolt_tags.id WHERE to_contenttype = 'tags'; DELETE FROM bolt_relations WHERE to_contenttype IN ('tags','collections'); -- ######## Convert Relations to Select fields UPDATE bolt_articles LEFT JOIN bolt_relations ON to_contenttype = 'authors' AND from_contenttype = 'articles' AND from_id = bolt_articles.id SET authors = CONCAT('["',to_id,'"]'); UPDATE bolt_articles LEFT JOIN bolt_relations ON to_contenttype = 'magazines' AND from_contenttype = 'articles' AND from_id = bolt_articles.id SET magazine = to_id; UPDATE bolt_videos LEFT JOIN bolt_relations ON to_contenttype = 'series' AND from_contenttype = 'videos' AND from_id = bolt_videos.id SET series = to_id; DELETE FROM bolt_relations WHERE to_contenttype IN ('magazines','authors','series'); -- ######## Clean up unneeded stuff DELETE FROM bolt_relations WHERE to_contenttype IN ('articles'); ```