Theming ownCloud

Themes can be used to customize the look and feel of any aspect of an ownCloud installation. They can override the default JavaScript, CSS, image, and template files, as well as the user interface translations with custom versions. They can also affect both the web front-end and the ownCloud Desktop client. However, this documentation only covers customizing the web front-end, so far.

Note

Throughout this section of the documentation, for sakes of simplicity, it will be assumed that your owncloud installation directory is /owncloud. If you’re following this guide to create or customise a theme, please make sure you change any references to match the location of your owncloud installation.

To save you time and effort, you can use the shell script below, to create the basis of a new theme from ownCloud’s example theme. Using this script, you will have a new theme, ready to go, in less than five seconds. You can execute this script with two variables; the first one is the theme name and the second one is your ownCloud directory.

For example:

theme-bootstrap.sh mynewtheme /var/www/owncloud

theme-bootstrap.sh

#!/bin/bash
# theme-bootstrap.sh
# Invoke this script with two arguments, the new theme's name and the path to ownCloud root.
# Written by Dmitry Mayorov <dmitry@owncloud.com>,  Matthew Setter <matthew@matthewsetter.com> & Martin Mattel <github@diemattels.at>
# Copyright (c) ownCloud 2018.
set -e

# Clone a copy of the ownCloud example theme
# It won't override an existing app directory of the same name.
function clone_example_theme
{
  local APP_NAME="$1"
  local INSTALL_BASE_DIR="$2"
  local MAINFILE=master.zip
  local UNZIPDIR=/tmp
  local MASTERNAME=theme-example-master
  local DOWNLOAD_FILE=$UNZIPDIR/$MAINFILE
  local THEME_ARCHIVE_URL=https://github.com/owncloud/theme-example/archive/master.zip

  # check if the app name already exists
  if  [ -d "$INSTALL_BASE_DIR/$APP_NAME" ]
  then
    echo "An app with name ('$INSTALL_BASE_DIR/$APP_NAME') already exists."
    echo "Please remove or rename it before running this script again."
    return 1
  fi;

  # delete an existing downloaded zip file
  if [ -e "$DOWNLOAD_FILE" ]
  then
	rm "$DOWNLOAD_FILE"
  fi

  echo "Downloading ownCloud example theme."

  # getting the exmple theme from git
  if ! wget --output-document="$DOWNLOAD_FILE" --tries=3 --continue \
    --timeout=3 --dns-timeout=3 --connect-timeout=3 --read-timeout=3  \
    "$THEME_ARCHIVE_URL" >/dev/null 2>&1
  then
    echo "Download error, exiting"
    return 1
  fi

  # first test if unzip would error then extract
  if unzip -t "$DOWNLOAD_FILE" >/dev/null 2>&1
  then
	# unzip with overwriting existing files and directories and suppressed output
	echo "Unzipping download"
	unzip -oq "$DOWNLOAD_FILE" -d "$UNZIPDIR"
	echo "Moving to target location"
    mv "$UNZIPDIR/$MASTERNAME" "$INSTALL_BASE_DIR/$APP_NAME" 
	echo "Removing download"
    rm "$DOWNLOAD_FILE" 
  else 
    echo "Cannot complete setup of the ownCloud example theme as it is corrupted."
    return 1
  fi
}
E_BADARGS=85

# Remembers the directory where this script was called from
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"

# Check if run as sudo (root), needed for sub script calling and changing file permissions
if (( $EUID != 0 )); then
    echo "Please run this script with sudo or as root"
    exit
fi

# Check if enough parameters have been applied
if (( $# != 2 ))
then
  echo "Not enough arguments provided."
  echo "Usage: $( basename "$0" ) [new theme name] [owncloud root directory]"
  exit $E_BADARGS
fi

# Check if read-config.php file exists in the same directory
if [ ! -f $SCRIPT_DIR/read-config.php ]
then
    echo "File read-config.php not found! Must be in the same dir as this script"
    exit
fi

# Check if php file is set to be executable, script will else not work
if [ ! -x $SCRIPT_DIR/read-config.php ]
then
    echo "File read-config.php is not set executable"
    exit
fi

app_name="$1"
owncloud_root="$2"
apps=$(php "$SCRIPT_DIR/read-config.php" "$owncloud_root")

# Check if the php script returned an error message. This is when the string does not start with /
if [[ ! $apps = '/'* ]]
then
	echo $apps
	echo "Script read-config.php returned no usable app path"
	exit
fi

if clone_example_theme "$app_name" "$apps" 
then
  # Remove the default signature, which will cause a code integrity violation
  [ -f "$apps/$app_name/appinfo/signature.json" ] && rm "$apps/$app_name/appinfo/signature.json"

  # Replace the default theme id / theme name
  echo "Updating theme id / theme name"
  sed -i "s#<id>theme-example<#<id>$app_name<#" "$apps/$app_name/appinfo/info.xml"

  # Set the appropriate permissions
  echo "Setting new theme file permissions"
  chown -R www-data:www-data "$apps/$app_name"

  # Enable the new theme app
  if [ -e "$owncloud_root/occ" ]
  then
    echo "Enabling new theme in ownCloud"
    php "$owncloud_root/occ" app:enable "$app_name"
  else
    echo
    echo "occ command not found, please enable the app manually"
  fi

  echo
  echo "Finished bootstrapping the new theme."
fi

read-config.php

#!/usr/bin/php
<?php
/**
 * @author Matthew Setter <matthew@matthewsetter.com> & Martin Mattel <github@diemattels.at>
 * @copyright Copyright (c) 2018, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

/**
 * Class SimpleConfigReader
 * @package ConfigReader
 */
class SimpleConfigReader
{
	/**
	 * @var array
	 */
	private $config = [];

	/**
	 * String returned to the user.
	 * @var string
	 */
	private $output = '';

	/**
	 * SimpleConfigReader constructor.
	 * @param string $config
	 */
	public function __construct($config = '')
	{
		$this->config = $config;
	}

	/**
	 * Find a writable app directory path that is either defined by key 'apps_paths'
	 * or use the default owncloud_root/apps path if the key is not set 
	 *
	 * @return string
	 * @throws \Exception
	 */
	function findPath($ocAppsPath)	{

		// default path = /apps
		if (!array_key_exists('apps_paths', $this->config)) {
			$this->output = $ocAppsPath;
			return $this->output;
		}

		foreach ($this->config['apps_paths'] as $path) {
			if ($path['writable'] == true && is_writable($path['path'])) {
				$this->output = $path['path'];
				return $this->output;
			}
		}
		return "Key 'apps_paths' found, but no writable path defined or path found not writeable";
	}
}

/*
 * As per the PHP manual: The first argument $argv[0] is always the name that
 * was used to run the script. So we need at least two to access the new app's
 * name, as well as the running script's name.
 * @see https://secure.php.net/manual/en/reserved.variables.argv.php
 */
if (count($argv) != 2) {
	echo "Command usage: read-config.php <full path to ownCloud root dir> \n";
	echo "Please provide the path to the ownCloud directory. \n";
	exit(1);
}

// create a realpath and remove trailing "/" from argument if present
$ocRoot = rtrim( (string) $argv[1], "/");
$ownCloudConfigFile = sprintf("%s/config/config.php", $ocRoot);

if (!realpath($ownCloudConfigFile)) {
	// if path/file does not exist, return an error message
	echo 'File not found: ' . $ownCloudConfigFile . PHP_EOL;
} else {
	// return the path, identified by a leading "/" and no new line character at the end
    require_once($ownCloudConfigFile);
    $result = (new SimpleConfigReader($CONFIG))->findPath($ocRoot . '/apps');
	if (!strpos($result, '/')) {
		// return an error string which does not start with a leading "/"
		echo $result  . PHP_EOL;
	} else {
		// return the path, identified by a leading "/" and no new line character at the end
		echo $result;
	}
}

How to Create a Theme

Before you can customize:

  1. Download the theme-example app <https://github.com/owncloud/theme-example/archive/master.zip> from github
  2. Extract the folder and copy it in your ownCloud apps directory
  3. Rename theme-example-master to your theme’s name
  4. Change the appinfo/info.xml to the new name in the appID and appName fields
  5. Don’t forget to change ownership of the folder to www-data

Theming

ownCloud’s default look.

This is ownCloud’s default look.

../_images/2_ow_login.png

Note

This guide assumes that you are in the folder of your custom theme, for example: /var/www/owncloud/apps/my_custom_theme/.

You can change the background login image by following these steps:

  1. Put the new background image in core/img/background.jpg.
  2. Change the owner and group permissions of the file to your web server user and group.
  3. Update background-image in core/css/styles.css.
#body-login {
     background-image: url("../img/background.jpg"); /* path to image /*
     background-position: 50% 50%; /* ensure optimal scalability /*
     background-repeat: no-repeat; /* prevent tiled background /*
     background-size: cover; /* ensure screen coverage /*
     text-align: center; /* Center Entitlement text, copyright /*
     background-color: #000000 !important; /* Fallback for old browsers */
}

If your image size is 1920px x 1680px, then you don’t need the lines below the path. However, they ensure optimal positioning and scaling.

../_images/3_cu_login.png

You can also change the background color, logo, and the slogan.

In core/css/styles.css, search for:

#body-login {
    background: #745bca; /* Old browsers */
    background: -moz-linear-gradient(top, #947bea 0%, #745bca 100%);
}

Replace it with the following:

#body-login {
    background: rgb(31,9,121);
    background: linear-gradient(90deg, rgba(31,9,121,1) 38%, rgba(2,0,36,1) 58%);
}

If you simply want one color replace the existing code with this:

#body-login {
    background: rgb(31,9,121);
}

If you are not sure what color to pick, here is a site for you: https://cssgradient.io/ To change the icon replace the files in core/img (logo.png, and logo.svg) with your icons. The reason for the PNG files is to have a fallback option for older browsers.

If you keep the names - you don’t need to change the path in core/css/styles.css. If you have changed the names - adjust styles.css file accordingly, as in this example:

#header .logo {
    background-image: url('../img/logo.svg');
    width: 250px;
    height: 121px;
}
../_images/4_oc_header.png

This is the default ownCloud header.

../_images/5_cu_header.png

You can change it to a custom color with a custom logo. Search for body-public #header in styles.css.

#body-public #header {
    background-color: #745bca;
}

You can also write your own, like the one from the login page, for example:

#body-public #header {
    background: rgb(31,9,121);
    background: linear-gradient(90deg, rgba(31,9,121,1) 38%, rgba(2,0,36,1) 58%);
}

Change the logo by replacing logo-icon.png and logo-icon.svg with your logos, in core/img. If you change the names of the logos, adjust the path accordingly in core/css/styles.css.

#header .logo-icon {
    background-image: url('../img/logo-icon.svg');
    height: 34px;
}

To change the title or slogan, you can do so by editing defaults.php in your theme folder.

Theme Signing

If you are going to publish the theme as an app in the marketplace, you need to sign it. However, if you are only creating a private theme for your own ownCloud installation, then you do not need to.

That said, to avoid a signature warning in the ownCloud UI, you need to add it to the integrity.ignore.missing.app.signature list in config/config.php. The following example allows the app whose application id is app-id to have no signature.

'integrity.ignore.missing.app.signature' => [
      'app-id',
 ],

How to Override Images

Any image, such as the default logo, can be overridden by including one with the same path structure in your theme. For example, let’s say that you want to replace the logo on the login-page above the credentials-box which, by default has the path: owncloud/core/img/logo-icon.svg. To override it, assuming that your custom theme was called theme-example (which will be assumed for the remainder of the theming documentation), add a new file with the following path: owncloud/apps/theme-example/core/img/logo-icon.svg. After the theme is activated, this image will override the default one.

Default Image Paths

To make building a new theme that much easier, below is a list of a range of the image paths used in the default theme.

Description Section Location
The logo at the login-page above the credentials-box General owncloud/core/img/logo-icon.svg
The logo in the left upper corner after login   owncloud/core/img/logo-icon.svg
All files folder image   owncloud/core/img/folder.svg
Favorites star image   owncloud/core/img/star.svg
Shared with you/others image   owncloud/core/img/share.svg
Shared by link image   owncloud/core/img/public.svg
Tags image   owncloud/core/img/tag.svg
Deleted files image   owncloud/core/img/delete.svg
Settings image   owncloud/core/img/actions/settings.svg
Search image   owncloud/core/img/actions/search-white.svg
Breadcrumbs home image   owncloud/core/img/places/home.svg
Breadcrumbs separator   owncloud/core/img/breadcrumb.svg
Dropdown arrow Admin Menu owncloud/core/img/actions/caret.svg
Personal image   owncloud/settings/img/personal.svg
Users image   owncloud/settings/img/users.svg
Help image   owncloud/settings/img/help.svg
Admin image   owncloud/settings/img/admin.svg
Logout image   owncloud/core/img/actions/logout.svg
Apps menu - Files image   owncloud/apps/files/img/app.svg
Apps menu - Plus image   owncloud/settings/img/apps.svg
Upload image Personal owncloud/core/img/actions/upload.svg
Folder image   owncloud/core/img/filetypes/folder.svg
Trash can image   owncloud/core/img/actions/delete.svg

Note

When overriding the favicon, make sure your custom theme includes and override for both owncloud/apps/core/img/favicon.svg and owncloud/apps/core/img/favicon.png, to cover any future updates to favicon handling.

Note

When using custom filetype icons in a custom theme, it is necessary to run occ maintenance:mimetype:update-js to activate them. For more information please refer to mimetypes management.

How to Override the Default Colors

To override the default style sheet, create a new CSS style sheet in your theme, in the theme’s css directory, called styles.css.

How to Override Translations

You can override the translation of any string in your theme. To do so:

  1. Create the l10n folder inside your theme, for the app that you want to override.
  2. In the l10n folder, create the translation file for the language that you want to customize.

For example, if you want to overwrite the German translation of “Download” in the files app, you would create the file owncloud/apps/theme-example/apps/files/l10n/de_DE.js. Note that the structure is the same as for images. You just mimic the original file location inside your theme. You would then put the following code in the file:

OC.L10N.register(
  "files",
  {
    "Download" : "Herunterladen"
  },
  "nplurals=2; plural=(n != 1);"
);

You then need to create a second translation file, owncloud/apps/theme-example/apps/files/l10n/de_DE.json, which looks like this:

{
  "translations": {
    "Download" : "Herunterladen"
  },
  "pluralForm" :"nplurals=2; plural=(n != 1);"
}

Both files (.js and .json) are needed. The first is needed to enable translations in the JavaScript code and the second one is read by the PHP code and provides the data for translated terms.

How to Render Imprint and Privacy Policy Settings in a Theme

ownCloud provides the ability to configure Imprint and Privacy Policy URLs, both in the WebUI and within email notification templates. If one (or both) of these have been set, they are automatically displayed in the common Web UI and email footer templates.

However, these footer templates can be overridden in a custom theme, if it is necessary to adjust markup or use different wording. If that is required, here is how to do it.

In Your Custom Theme

Note

If your theme is based on ownCloud’s example theme, then you can skip this step. If it’s not, then follow the instructions below to obtain the necessary functionality.

First, paste the following code into the class OC_Theme, that is located inside defaults.php inside your custom application theme. Doing so gives you functions for retrieving the privacy policy and imprint URLs that have been configured in your ownCloud installation.

<?php

public function getPrivacyPolicyUrl() {
    try {
        return \OC::$server->getConfig()->getAppValue('core', 'legal.privacy_policy_url', '');
    } catch (\Exception $e) {
        return '';
    }
}

public function getImprintUrl() {
    try {
        return \OC::$server->getConfig()->getAppValue('core', 'legal.imprint_url', '');
    } catch (\Exception $e) {
        return '';
    }
}

public function getL10n() {
    return \OC::$server->getL10N('core');
}

Second, adjust either the getShortFooter, the getLongFooter method, or both, in the same file to call the new methods, using the code snippet below as a reference.

<?php

public function getShortFooter() {
    $l10n = $this->getL10n();
    $footer = '© 2018 <a href="'.$this->getBaseUrl().'" target="_blank\">'.$this->getEntity().'</a>'.
        '<br/>' . $this->getSlogan();
    if ($this->getImprintUrl() !== '') {
        $footer .= '<span class="nowrap"> | <a href="' . $this->getImprintUrl() . '" target="_blank">' . $l10n->t('Imprint') . '</a></span>';
    }

    if ($this->getPrivacyPolicyUrl() !== '') {
        $footer .= '<span class="nowrap"> | <a href="'. $this->getPrivacyPolicyUrl() .'" target="_blank">'. $l10n->t('Privacy Policy')        .'</a></span>';
    }
    return $footer;
}
<?php

public function getLongFooter() {
    $l10n = $this->getL10n();
    $footer = '© 2018 <a href="'.$this->getBaseUrl().'" target="_blank\">'.$this->getEntity().'</a>'.
        '<br/>' . $this->getSlogan();
    if ($this->getImprintUrl() !== '') {
        $footer .= '<span class="nowrap"> | <a href="' . $this->getImprintUrl() . '" target="_blank">' . $l10n->t('Imprint') . '</a></span>';
    }

    if ($this->getPrivacyPolicyUrl() !== '') {
        $footer .= '<span class="nowrap"> | <a href="'. $this->getPrivacyPolicyUrl() .'" target="_blank">'. $l10n->t('Privacy Policy') .'</a></span>';
    }
    return $footer;
}

Reuse Common Email Footers in a 3rd Party App

To reuse common email footers in a 3rd Party app, add the following lines to the template of your email notification template:

For HTML emails:

<?php print_unescaped(
      $this->inc(
          'html.mail.footer',
          [
              'app' => 'core'
          ]
      )
  ); ?>

For plain text emails:

<?php print_unescaped(
      $this->inc(
          'plain.mail.footer',
          [
              'app' => 'core'
          ]
      )
  ); ?>

How Can an App Register a Section in the Admin or Personal Section?

As of ownCloud 10.0, apps must register admin and personal section settings in info.xml. As a result, all calls to OC_App::registerPersonal and OC_App::registerAdmin should now be removed. The settings panels of any apps that are still using these calls will now be rendered in the “Additional” section of the dashboard .

For each panel an app wishes to register, two things are required:

  1. An update to info.xml
  2. A controller class

First, an entry must be added into the <settings> element in info.xml, specifying the class name responsible for rendering the panel. These will be loaded automatically when an app is enabled. For example, to register an admin and a personal section would require the following configuration..

<settings>
      <personal>OCA\MyApp\PersonalPanel::class</personal>
      <admin>OCA\MyApp\AdminPanel::class</admin>
</settings>

Next, a controller class which implements the OCP\Settings\ISettings interface must be created to represent the panel. Doing so enforces that the necessary settings panel information is returned. The interface specifies three methods:

  • getSectionID
  • getPanel
  • getPriority

getSectionID: This method returns the identifier of the section that this panel should be shown under. ownCloud Server comes with a predefined list of sections which group related settings together; the intention of which is to improve the user experience. This can be found here in this example:

getPanel: This method returns the OCP\Template or OCP\TemplateReponse which is used to render the panel. The method may also return null if the panel should not be shown to the user.

getPriority: An integer between 0 and 100 representing the importance of the panel (higher is more important). Most apps should return a value:

  • between 20 and 50 for general information.
  • greater than 50 for security information and notices.
  • lower than 20 for tips and debug output.

Here’s an example implementation of a controller class for creating a personal panel in the security section.

<?php

namespace OCA\YourApp

use OCP\Settings\ISettings;
use OCP\Template;

class PersonalPanel extends ISettings {


    const PRIORITY = 10;


    public function getSectionID() {
        return 'security';
    }

    public function getPriority() {
        return self::PRIORITY;
    }

    public function getPanel() {
        // Set the template and assign a template variable
        return (new Template('app-name', 'template-name'))->assign('var', 'value');
    }
}

Create Custom Sections

At the moment, there is no provision for apps creating their own settings sections. This is to encourage sensible and intelligent grouping of the settings panels which in turn should improve the overall user experience. If you think a new section should be added to core however, please create a PR with the appropriate changes to OC\Settings\SettingsManager.

All documentation licensed under the Creative Commons Attribution 3.0 Unported license.