Jump to content

Joachim Kobuss


schwarzdesign
 Share

Recommended Posts

We have relaunched the old website of design business expert Joachim Kobuss. The old site desperataly needed a facelift and a streamlined CMS that allowed our client to fill over 130 pages of content in a span of two weeks. We at schwarzdesign used ProcessWire + ProFields for the Backend and built a Bootstrap 4.1-based frontend.

Features / Services provided

  • Responsive design featuring a desktop layout with two fixed navigation areas and a fixed sidebar that form a frame around the content.
  • Carefully set typography and spacing for a smooth reading experience for long texts on all devices.
  • A hierarchical page tree with three levels of nested pages, all using the same simple set of fields.
  • Password protection for individual pages and an optional password request form.
  • Privacy by design: There's no tracking, no Google Map, no Facebook Like Button or anything like that on the page. Because we don't set any cookies or include third-party code, we don't need a cookie notice ?

Modules used

  • Profields (especially Repeater Matrix, Textareas and Functional Fields)
  • Form Builder
  • Tracy Debugger
  • Wire Mail SMTP
  • Duplicator
  • Admin Links in Frontend
  • Sitemap

Optimizing for content

The most distinguishing feature is the large amount of long texts our client has written. So it was important to provide a good editing experience in the frontend and have a content centered design that made long text sections easy to follow.

We didn't want to give our client just a large CK Editor body text, since in our experience, those WYSIWYG editors aren't actually that easy to use for non-technical folk. It also reduces the amount of control we as designers and CSS-authors have over the presentation of the content.

So instead, we built a flexible section system using the Repeater Matrix field of the ProFields module. There are three sections: text content, downloads and single image. Each section has a headline field and a radio selection field to switch between two levels of headline hierarchy (corresponding to h2 and h3). For the text section, there's a CK Editor textarea stripped to the bare minimum (strong, italic, lists, blockquotes and links), with no confusing styles or format dropdown. The image section features an additional option for full- or half-width images and an option to display the image description as a caption. And of course, the download section has a multivalue file field that is displayed as download buttons in the frontend.

This simple sectioning system allowed us to build the entire site (except for the homepage) using just one template fieldset. There are three templates: Basic Page, Topic and Article. Basic pages are for generic pages like imprint and data policy. Topics may only be children of the homepage, while articles may be children of a topic or of another article, so the pages can be nested indefinitely. This provides the hierarchical structure used for the page layout. Topic and article inherit the fieldset of the basic page, so it's just one template under the hood.

There's also smart Open Graph tag generation; title, description and preview image are automatically generated based on the first suitable sections on each page and can be manually overridden using the SEO tab available on all pages.

Laying out the page

Our design has three fixed areas: The top navigation, the left side navigation and the right sidebar, which shows some general contact info and a random image. The top navigation always shows the top-level topic pages, the sidebar navigation contains all articles in the current topic, displayed in a hierarchical structure.

This was technically challenging, as the many fixed layout areas broke the normal pageflow and didn't work well for smaller devices. I solved this using four seperate Bootstrap containers, three of which are fixed. Only the container with the main page content flows normally. The left sidebar only fills the three leftmost columns of its container, the right sidebar the three rightmost columns, and the main content takes up the six center columns of it's container. All three have an offset to the top to make space for the fixed header navigation. This way, all the seperate layout areas can never overlap. The CSS that applies position: fixed to the containers is wrapped in a media query with a min-width, so on tablets and mobile devices, the areas simply stack. Check out the source code to see how those containers are laid out!

The logo is text by the way, not an image. It's positioned using CSS3 transforms.

Password protection

Our client wanted to password protect some of his pages with varying passwords, so he can give different people access to different sets of pages. Also, he wanted to have a password request form, where people can submit their name and contact information to request access to a specific page.

In the backend, we added a password field that activates the password protection for that page. There's also a checkbox to display the password request form. The form is built using the Form Builder module (and a simple hook to add the current page title to the outgoing mail). However, for the password protection, we built a custom solution. While there is a readymade module available, we didn't want something based on user accounts. A visitor should be able to simply input the password on a protected page and start reading, without bothering with registering an account. The authentification is stored in the session, so after closing the browser you have to input the password again, but that seemed to be a worthy tradeoff.

Anyway, when visiting a password protected page, the template checks if the user is already authentificated to read this page; if so, it displays the page content, otherwise it displays the password input form and the password request form if it was also activated in the CMS. A nice-to-have feature that we built in addition is that if multiple pages use the same password and you input it on one of them, you are automatically authentificated to view all of them during the current session.

A caveat of this method is that since the page content depends on the current session, we couldn't use the inbuilt page cache at all. So instead we used the cache API to cache rendered section markup and the navigation menues, which still results in a blazingly fast site.

For the techies amongst you, here's the PasswordProtectedPage class we wrote for this functionality:

<?php
namespace schwarzdesign;

use Processwire\Wire;
use Processwire\Page;
use Processwire\PageArray;
use Processwire\User;

class PasswordProtectedPage extends Wire
{
    const PASSWORD_FIELD = 'page_password';
    const PASSWORD_INPUT_NAME = 'page_password';
    const STORAGE_KEY = 'authentificatedPages';

    const STATUS_FREE_ACCESS = 'free_access';
    const STATUS_AUTHENTIFICATED = 'authentificated';
    const STATUS_NOT_AUTHENTIFICATED = 'not_authentificated_yet';
    const STATUS_WRONG_PASSWORD = 'wrong_password';

    /**
     * The status of the authentification.
     */
    public $status = self::STATUS_NOT_AUTHENTIFICATED;

    private $page;

    private $authentificated_pages = [];

    public function __construct(Page $page)
    {
        $this->page = $page;
        $this->authentificated_pages = $this->getAuthentificatedPagesFromStorage();
    }

    public function handleRequest()
    {
        // pages with no password set
        if (empty($this->page->get(self::PASSWORD_FIELD))) {
            return $this->status = self::STATUS_FREE_ACCESS;
        }
        // pages that the user is already authentificated to use
        if (in_array($this->page->id, $this->authentificated_pages)) {
            return $this->status = self::STATUS_AUTHENTIFICATED;
        }
        // if the user set the input, check the password
        if ($password = $this->getInputPassword()) {
            if ($this->passwordIsCorrect($password)) {
                $this->authentificatePagesByPassword($password);
                $this->storeAuthentificatedPages();
                return $this->status = self::STATUS_AUTHENTIFICATED;
            } else {
                return $this->status = self::STATUS_WRONG_PASSWORD;
            }
        } else {
            return $this->status = self::STATUS_NOT_AUTHENTIFICATED;
        }
    }

    public function userCanAccess()
    {
        return in_array($this->status, [self::STATUS_AUTHENTIFICATED, self::STATUS_FREE_ACCESS]);
    }

    public function getInputPassword()
    {
        $password = $this->wire('input')->post(self::PASSWORD_INPUT_NAME);
        if (empty($password)) {
            return null;
        }
        return $password;
    }

    public function authentificatePagesByPassword(string $password)
    {
        $pages = $this->wire('pages')->find(self::PASSWORD_FIELD . '=' . $password);
        $ids = array_map(array($this, 'extractIdFromPage'), iterator_to_array($pages));
        $this->addAuthentificatedPages($ids);
    }


    /**
     * Private helper Functions.
     */


    private function passwordIsCorrect(string $password)
    {
        return (string) $this->page->get(self::PASSWORD_FIELD) === $password;
    }

    private function extractIdFromPage(Page $page)
    {
        return $page->id;
    }

    private function addAuthentificatedPages(array $page_ids)
    {
        $this->authentificated_pages = array_merge($this->authentificated_pages, $page_ids);
    }

    private function getAuthentificatedPagesFromStorage()
    {
        $ids = $this->wire('session')->get(self::STORAGE_KEY);
        return $ids ? $ids : [];
    }

    private function storeAuthentificatedPages()
    {
        $this->wire('session')->set(self::STORAGE_KEY, $this->authentificated_pages);
    }
}

 

This is used in the template like this:

<?php
namespace Processwire;

use schwarzdesign\PasswordProtectedPage;

$protectedPage = new PasswordProtectedPage($page);
$protectedPage->handleRequest();

if (!$protectedPage->userCanAccess()) {
    if ($user->hasPermission('display-page-password')) {
        /* Display the password in plaintext for editors & admins */
    }

    /* Display the password input form */

    if ($page->show_password_request_form) {
        /* Display the password request form */
    }
} else {
    /* Display the page content */
}

Screenshots

kobuss_front_basic_page.png

kobuss_front_home.png

kobuss_front_password_protection.png

kobuss_front_password_request_form.png

kobuss_cms_basic_page.png

kobuss_cms_password_settings.png

kobuss_cms_section_image.png

kobuss_cms_section_settings.png

kobuss_cms_sections_download.png

kobuss_cms_sections_text.png

kobuss_cms_template_settings.png

  • Like 18
Link to comment
Share on other sites

Wonderful write-up, and a very elegant and precise website. Thanks and congratulations!

Very much OT, but I was interested to see that one of your clients is the Osho UTA Institute. I saw a fascinating Netflix doco recently about the history of the Osho (aka Bhagwan Shree Rajneesh) movement in the USA: https://en.wikipedia.org/wiki/Wild_Wild_Country

  • Thanks 1
Link to comment
Share on other sites

Thanks for sharing! Awesome site ?

On 10/31/2018 at 10:03 AM, schwarzdesign said:

For the techies amongst you, here's the PasswordProtectedPage class we wrote for this functionality:

I wonder why you didn't create a module for that. There was a discussion about that recently: 

But in your case I think it would make sense to pack it into a module. You could then:

  • update it with one click across several websites
  • extend the $page object via hooks instead of creating a new class
  • share it with the community via github&co and the modules directory

This:

<?php namespace Processwire;

use schwarzdesign\PasswordProtectedPage;

$protectedPage = new PasswordProtectedPage($page);
$protectedPage->handleRequest();

if (!$protectedPage->userCanAccess()) {
  [...]

Would then become that:

<?php namespace Processwire;

$page->handleRequest();
if(!$page->userCanAccess()) {
  [...]

And it would be really easy to achieve in your (autoload) module:

$this->wire->addHook("Page::handleRequest", function($event) {
  // your code here
});

I think you could even get rid of the "handleRequest" call and do that automatically in the background.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

On 11/2/2018 at 12:21 AM, Robin S said:

Wonderful write-up, and a very elegant and precise website. Thanks and congratulations!

Very much OT, but I was interested to see that one of your clients is the Osho UTA Institute. I saw a fascinating Netflix doco recently about the history of the Osho (aka Bhagwan Shree Rajneesh) movement in the USA: https://en.wikipedia.org/wiki/Wild_Wild_Country

Thanks ? Yes we built multiple sites for them, all based on the same design with theme/color variations for individual sites. E.g. Osho UTA, UTA Akademie and Osho's Place. Those are built with Drupal though.

On 11/2/2018 at 1:38 PM, bernhard said:

Thanks for sharing! Awesome site ?

I wonder why you didn't create a module for that. There was a discussion about that recently: 

But in your case I think it would make sense to pack it into a module. You could then:

  • update it with one click across several websites
  • extend the $page object via hooks instead of creating a new class
  • share it with the community via github&co and the modules directory

This:


<?php namespace Processwire;

use schwarzdesign\PasswordProtectedPage;

$protectedPage = new PasswordProtectedPage($page);
$protectedPage->handleRequest();

if (!$protectedPage->userCanAccess()) {
  [...]

Would then become that:


<?php namespace Processwire;

$page->handleRequest();
if(!$page->userCanAccess()) {
  [...]

And it would be really easy to achieve in your (autoload) module:


$this->wire->addHook("Page::handleRequest", function($event) {
  // your code here
});

I think you could even get rid of the "handleRequest" call and do that automatically in the background.

Hi Bernhard, I agree that a module would be the more ProcessWire-friendly way, I mostly wrote it this way to save time. Since it is client work, we have to budget our development time; given that I haven't really started digging into module development yet, I didn't want the additional overhead. Since the site only has one template, I didn't need to duplicate any code, so I didn't think the benefits of building a module were worth the additional development time in this case.

I thought about extending the Page class, but I went with composition over inheritance in this case to avoid unforseen side effects. Though it probably would've been cleaner to use inheritance. I'm not sure this class is general enough to be used in different sites at the moment, but if I have some free time at work or need it for another client site, I'll try to turn this into a configurable module!

  • Like 4
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...