ownCloud Developer Manual Release 6.0 The ownCloud developers September 15, 2014 CONTENTS 1 ownCloud 6.0 Developer Documentation 1.1 Index and Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 Development Environment 2.1 Set up web server and database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Get the source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Enabling debug mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 4 3 App Development 3.1 General . . . . . . . . . . . . . . . . . . . . . . . 3.2 App Development Intro . . . . . . . . . . . . . . 3.3 App Development (ownCloud App API) . . . . . 3.4 App Development (App Framework) . . . . . . . 3.5 Intro . . . . . . . . . . . . . . . . . . . . . . . . 3.6 App Development using ownCloud App API . . . 3.7 App Development using the App Framework App 3.8 Additional APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 23 25 53 124 124 125 127 Core Development 4.1 Translation . . . . . . . . . . . . . 4.2 Unit-Testing . . . . . . . . . . . . 4.3 Theming ownCloud . . . . . . . . 4.4 Creating and activating a new theme 4.5 Structure . . . . . . . . . . . . . . 4.6 How to change images and the logo 4.7 Testing the new theme out . . . . . 4.8 Notes for Updates . . . . . . . . . 4.9 App config . . . . . . . . . . . . . 4.10 OCS Share API . . . . . . . . . . . 4.11 Intro . . . . . . . . . . . . . . . . 4.12 Core related docs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 129 131 133 133 133 134 135 135 135 138 140 140 ownCloud Test Pilots 5.1 Why do you want to join 5.2 Who can join . . . . . . 5.3 How do you join . . . . 5.4 What do you do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 141 141 141 142 4 5 6 . . . . . . . . . . . . . . . . . . . . . . . . Bugtracker 143 6.1 Code Reviews on GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 i 6.2 Kanban Board . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 7 Help and Communication 149 7.1 Mailing lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 7.2 IRC channels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 7.3 Maintainers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 8 Android Application Development 151 8.1 Library Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 8.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 9 iOS Application Development 161 9.1 Library Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 9.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 10 Indices and tables 181 PHP Namespace Index 183 Index 185 ii CHAPTER ONE OWNCLOUD 6.0 DEVELOPER DOCUMENTATION If you want to contribute please read the Contributor agreement App Development Develop apps for ownCloud and publish on the ownCloud appstore Core Development Develop on the ownCloud internals Documentation Create and enhance documentation ownCloud Test Pilots Help us to test ownCloud by joining the testing team Bugtracker Report, triage or fix bugs to improve quality Translation Translate ownCloud into your language Help and Communication Help on IRC, the mailinglist and forum iOS Application Development Use ownCloud in iOS apps Android Application Development Use ownCloud in Android apps 1.1 Index and Tables • genindex • modindex 1 ownCloud Developer Manual, Release 6.0 2 Chapter 1. ownCloud 6.0 Developer Documentation CHAPTER TWO DEVELOPMENT ENVIRONMENT Please follow the steps on this page to set up your development environment. 2.1 Set up web server and database First set up your web server and database (Section: Manual Installation - Prerequisites). 2.2 Get the source There are two ways to obtain ownCloud sources: • Using the stable version • Using the development version from GitHub which will be explained below. To check out the source from GitHub you will need to install git (see Setting up git from the GitHub help) 2.2.1 Identify the web server’s directories To get started the basic git repositories need to be cloned into the web server’s directory. Depending on the distribution this will either be • /var/www • /var/www/html • /srv/http 2.2.2 Identify the user and group the web server is running as and the Apache user and group for the chown command will either be • http • www-data • apache 3 ownCloud Developer Manual, Release 6.0 2.2.3 Check out the code The following commands are using “/var/www” as the web server’s directory and “www-data” as user name and group. sudo chmod o+rw /var/www cd /var/www git clone https://github.com/owncloud/core.git owncloud git clone https://github.com/owncloud/apps.git cd owncloud/ git submodule init git submodule update mkdir data sudo chown -R www-data:www-data config/ sudo chown -R www-data:www-data data/ sudo chown -R www-data:www-data apps/ sudo chmod -R o-rw /var/www Now restart the web server. 2.2.4 Check out code from additional apps (optional) If you would like to develop on non-core apps, you can check them out into the apps directory as well. For example for the calendar, contact and notes apps: cd /var/www git clone https://github.com/owncloud/calendar.git git clone https://github.com/owncloud/contacts.git git clone https://github.com/owncloud/notes.git 2.2.5 Set up ownCloud Open http://localhost/owncloud (or the corresponding URL) in your web browser to set up your instance. 2.2.6 Start developing • App Development • Core Development 2.3 Enabling debug mode Note: Do not enable this for production! This can create security problems and is only meant for debugging and development! To disable JavaScript and CSS caching debugging has to be enabled in owncloud/config/config.php by adding this to the end of the file: DEFINE(’DEBUG’, true); This is often overwritten after a git pull from core. Always check owncloud/config/config.php afterwards. 4 Chapter 2. Development Environment CHAPTER THREE APP DEVELOPMENT 3.1 General 3.1.1 Security Guidelines This guideline highlights some of the most common security problems and how to prevent them. Please review your app if it contains any of the following security holes. Note: Program defensively: for instance always check for CSRF or escape strings, even if you do not need it. This prevents future problems where you might miss a change that leads to a security hole. Note: All App Framework security features depend on the call of the controller through OCA\AppFramework\App::main. If the controller method is executed directly, no security checks are being performed! SQL Injection SQL Injection occurs when SQL query strings are concatenated with variables. To prevent this, always use prepared queries: <?php $sql = ’SELECT * FROM ‘users‘ WHERE ‘id‘ = ?’; $query = \OCP\DB::prepare($sql); $params = array(1); $result = $query->execute($params); If the App Framework is used, write SQL queries like this in the a class that extends the Mapper: <?php // inside a child mapper class $sql = ’SELECT * FROM ‘users‘ WHERE ‘id‘ = ?’; $params = array(1); $result = $this->execute($sql, $params); Cross site scripting Cross site scripting happens when user input is passed directly to templates. A potential attacker might be able to 5 ownCloud Developer Manual, Release 6.0 inject HTML/JavaScript into the page to steal the users session, log keyboard entries, even perform DDOS attacks on other websites or other malicious actions. Despite of the fact that ownCloud uses Content-Security-Policy to prevent the execution of inline JavaScript code developers are still required to prevent XSS. CSP is just another layer of defense that is not implemented in all web browsers. To prevent XSS in your app you have to sanitize the templates and all JavaScripts which performs a DOM manipulation. Templates Let’s assume you use the following example in your application: <?php echo $_GET[’username’]; An attacker might now easily send the user a link to: app.php?username=<script src="attacker.tld"></script> to overtake the user account. The same problem occurs when outputting content from the database or any other location that is writeable by users. Another attack vector that is often overlooked is XSS in href attributes. HTML allows to execute javascript in href attributes like this: <a href="javascript:alert(’xss’)"> To prevent XSS in your app, never use echo, print() or <%= - use p() instead which will sanitize the input. Also validate urls to start with the expected protocol (starts with http for instance)! Note: Should you ever require to print something unescaped, double check if it is really needed. If there is no other way (e.g. when including of subtemplates) use print_unescaped with care. If you use the App Framework with Twig templates everything is already escaped by default. JavaScript Avoid manipulating the HTML directly via JavaScript, this often leads to XSS since people often forget to sanitize variables: var html = ’<li>’ + username + ’</li>"’; If you really want to use JavaScript for something like this use escapeHTML to sanitize the variables: var html = ’<li>’ + escapeHTML(username) + ’</li>’; An even better way to make your app safer is to use the jQuery builtin function $.text() instead of $.html(). DON’T messageTd.html(username); DO 6 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 messageTd.text(username); It may also be wise to choose a proper JavaScript framework like AngularJS which automatically handles the JavaScript escaping for you. Clickjacking Clickjacking tricks the user to click into an invisible iframe to perform an arbitrary action (e.g. delete an user account) To prevent such attacks ownCloud sends the X-Frame-Options header to all template responses. Don’t remove this header if you don’t really need it! This is already built into ownCloud if OC_Template or Twig Templatse are used. Code executions / File inclusions Code Execution means that an attacker is able to include an arbitrary PHP file. This PHP file runs with all the privileges granted to the normal application and can do an enourmous amount of damage. Code executions and file inclusions can be easily prevented by never allowing user-input to run through the following functions: • include() • require() • require_once() • eval() • fopen() Note: Also never allow the user to upload files into a folder which is reachable from the URL! DON’T <?php require("/includes/" . $_GET[’file’]); Note: If you have to pass user input to a potential dangerous, double check to be sure that there is no other way. If it is not possible otherwise sanitize every user parameter and ask people to audit your sanitize function. Directory Traversal Very often developers forget about sanitizing the file path (removing all and /), this allows an attacker to traversal through directories on the server which opens several potential attack vendors including privilege escalations, code executions or file disclosures. DON’T <?php $username = OC_User::getUser(); fopen("/data/" . $username . "/" . $_GET[’file’] . ".txt"); DO 3.1. General 7 ownCloud Developer Manual, Release 6.0 <?php $username = OC_User::getUser(); $file = str_replace(array(’/’, ’\\’), ’’, $_GET[’file’]); fopen("/data/" . $username . "/" . $file . ".txt"); Note: PHP also interprets the backslash () in paths, don’t forget to replace it too! Shell Injection Shell Injection occurs if PHP code executes shell commands (e.g. running a latex compiler). Before doing this, check if there is a PHP library that already provides the needed functionality. If you really need to execute a command be aware that you have to escape every user parameter passed to one of these functions: • exec() • shell_exec() • passthru() • proc_open() • system() • popen() Note: Please require/request additional programmers to audit your escape function. Without escaping the user input this will allow an attacker to execute arbitary shell commands on your server. PHP offers the following functions to escape user input: • escapeshellarg(): Escape a string to be used as a shell argument • escapeshellcmd(): Escape shell metacharacters DON’T <?php system(’ls ’.$_GET[’dir’]); DO <?php system(’ls ’.escapeshellarg($_GET[’dir’])); Auth bypass / Privilege escalations Auth bypass/privilege escalations happens when a user is able to perform not authorized actions. ownCloud offers three simple checks: • OCPJSON::checkLoggedIn(): Checks if the logged in user is logged in • OCPJSON::checkAdminUser(): Checks if the logged in user has admin privileges • OCPJSON::checkSubAdminUser(): Checks if the logged in user has group admin privileges 8 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Using the App Framework, these checks are already automatically performed for each request and have to be explicitely turned off by using annotations above your controller method, see Controllers. Additionally always check if the user has the right to perform that action. (e.g. a user should not be able to delete other users’ bookmarks). Sensitive data exposure Always store user data or configuration files in safe locations, e.g. owncloud/data/ and not in the webroot where they can be accessed by anyone using a webbrowser. Cross site request forgery Using CSRF one can trick a user into executing a request that he did not want to make. Thus every POST and GET request needs to be protected against it. The only places where no CSRF checks are needed are in the main template, which is rendering the application, or in externally callable interfaces. Note: Submitting a form is also a POST/GET request! To prevent CSRF in an app, be sure to call the following method at the top of all your files: <?php OCP\JSON::callCheck(); If you are using the App Framework, every controller method is automatically checked for CSRF unless you explicitely exclude it by setting the @CSRFExemption annotation before the controller method, see Controllers Unvalidated redirects This is more of an annoyance than a critical security vulnerability since it may be used for social engineering or phising. Always validate the URL before redirecting if the requested URL is on the same domain or an allowed ressource. DON’T <?php header(’Location:’. $_GET[’redirectURL’]); DO <?php header(’Location: http://www.example.com’. $_GET[’redirectURL’]); Getting help If you need help to ensure that a function is secure please ask on our mailing list or on our IRC channel #owncloud-dev on Freenode. 3.1. General 9 ownCloud Developer Manual, Release 6.0 3.1.2 Coding Style & General Guidelines General • Maximum line-length of 80 characters • Use tabs to indent • A tab is 4 spaces wide • Opening braces of blocks are on the same line as the definition • Quotes: ‘ for everything, ” for HTML attributes (<p class=”my_class”>) • End of Lines : Unix style (LF / ‘n’) only • No global variables or functions • Unittests • Software should work. Only put features into master when they are complete. It’s better to not have a feature instead of having one that works poorly. • Regularly reset your installation to see how the first-run experience is like. And improve it. • When you git pull, always git pull --rebase to avoid generating extra commits like: merged master into master • We need a signed contributor agreement from you to commit into the core repository. But no worries; it’s a nice one. All the information is in our Contributor agreement FAQ. Userinterface • Software should get out of the way. Do things automatically instead of offering configuration options. • Software should be easy to use. Show only the most important elements. Secondary elements only on hover or via Advanced function. • User data is sacred. Provide undo instead of asking for confirmation • The state of the application should be clear. If something loads, provide feedback. • Do not adapt broken concepts (for example design of desktop apps) just for the sake of consistency. We provide a better interface. • Ideally do usability testing to know how people use the software. • For further UX principles, read Alex Faaborg from Mozilla. PHP The ownCloud coding style guide is based on PEAR Coding Standards. Always use: <?php at the start of your php code. The final closing: ?> should not be used at the end of the file due to the possible issue of sending white spaces. 10 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Comments All API methods need to be marked with PHPDoc markup. An example would be: <?php /** * Description what method does * @param Controller $controller the controller that will be transformed * @param API $api an instance of the API class * @throws APIException if the api is broken * @since 4.5 * @return string a name of a user */ public function myMethod(Controller $controller, API $api) { // ... } Objects, Functions, Arrays & Variables Use Pascal case for Objects, Camel case for functions and variables. If you set a default function/method parameter, do not use spaces. Do not prepend private class members with underscores. class MyClass { } function myFunction($default=null) { } $myVariable = ’blue’; $someArray = array( ’foo’ => ’bar’, ’spam’ => ’ham’, ); ?> Operators Use === and !== instead of == and !=. Here’s why: <?php var_dump(0 == "a"); // 0 == 0 -> true var_dump("1" == "01"); // 1 == 1 -> true var_dump("10" == "1e1"); // 10 == 10 -> true var_dump(100 == "1e2"); // 100 == 100 -> true ?> 3.1. General 11 ownCloud Developer Manual, Release 6.0 Control Structures • Always use { } for one line ifs • Split long ifs into multiple lines • Always use break in switch statements and prevent a default block with warnings if it shouldn’t be accessed <?php // single line if if ($myVar === ’hi’) { $myVar = ’ho’; } else { $myVar = ’bye’; } // long ifs if ( $something === ’something’ || $condition2 && $condition3 ) { // your code } // for loop for ($i = 0; $i < 4; $i++) { // your code } switch ($condition) { case 1: // action1 break; case 2: // action2; break; default: // defaultaction; break; } ?> JavaScript In general take a look at JSLint without the whitespace rules. • Use a js/main.js or js/app.js where your program is started • Complete every statement with a ; • Use var to limit variable to local scope • To keep your code local, wrap everything in a self executing function. To access global objects or export things to the global namespace, pass all global objects to the self executing function. • Use JavaScript strict mode 12 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • Use a global namespace object where you bind publicly used functions and objects to DO: // set up namespace for sharing across multiple files var MyApp = MyApp || {}; (function(window, $, exports, undefined) { ’use strict’; // if this function or object should be global, attach it to the namespace exports.myGlobalFunction = function(params) { return params; }; })(window, jQuery, MyApp); DONT (Seriously): // This does not only make everything global but you’re programming // JavaScript like C functions with namespaces MyApp = { myFunction:function(params) { return params; }, ... }; Objects & Inheritance Try to use OOP in your JavaScript to make your code reusable and flexible. This is how you’d do inheritance in JavaScript: // create parent object and bind methods to it var ParentObject = function(name) { this.name = name; }; ParentObject.prototype.sayHello = function() { console.log(this.name); } // create childobject, call parents constructor and inherit methods var ChildObject = function(name, age) { ParentObject.call(this, name); this.age = age; }; ChildObject.prototype = Object.create(ParentObject.prototype); // overwrite parent method ChildObject.prototype.sayHello = function() { // call parent method if you want to ParentObject.prototype.sayHello.call(this); console.log(’childobject’); }; 3.1. General 13 ownCloud Developer Manual, Release 6.0 var child = new ChildObject(’toni’, 23); // prints: // toni // childobject child.sayHello(); Objects, Functions & Variables Use Pascal case for Objects, Camel case for functions and variables. var MyObject = function() { this.attr = "hi"; }; var myFunction = function() { return true; }; var myVariable = ’blue’; var objectLiteral = { value1: ’somevalue’ }; Operators Use === and !== instead of == and !=. Here’s why: ’’ == ’0’ 0 == ’’ 0 == ’0’ // false // true // true false == ’false’ false == ’0’ // false // true false == undefined false == null null == undefined // false // false // true ’ \t\r\n ’ == 0 // true Control Structures • Always use { } for one line ifs • Split long ifs into multiple lines • Always use break in switch statements and prevent a default block with warnings if it shouldn’t be accessed DO: 14 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 // single line if if (myVar === ’hi’) { myVar = ’ho’; } else { myVar = ’bye’; } // long ifs if ( something === ’something’ || condition2 && condition3 ) { // your code } // for loop for (var i = 0; i < 4; i++) { // your code } // switch switch (value) { case ’hi’: // yourcode break; default: console.warn(’Entered undefined default block in switch’); break; } CSS Take a look at the Writing Tactical CSS & HTML video on YouTube. Don’t bind your CSS too much to your HTML structure and try to avoid IDs. Also try to make your CSS reusable by grouping common attributes into classes. DO: .list { list-style-type: none; } .list > .list_item { display: inline-block; } .important_list_item { color: red; } DON’T: #content .myHeader ul { list-style-type: none; } 3.1. General 15 ownCloud Developer Manual, Release 6.0 #content .myHeader ul li.list_item { color: red; display: inline-block; } TBD 3.1.3 Debugging Debug mode When debug mode is enabled ownCloud, a variety of debugging features are enabled - see debugging documentation. Add the following to the very end of /config/config.php to enable it: define( "DEBUG", 1); Identifying errors ownCloud uses custom error PHP handling that prevents errors being printed to webserver log files or command line output. Instead, errors are generally stored in ownCloud’s own log file, located at: /data/owncloud.log Debugging variables You should use exceptions if you need to debug variable values manually, and not alternatives like trigger_error() (which may not be logged). e.g.: <?php throw new \Exception( "\$user = $user" ); // should be logged in ownCloud ?> not: <?php trigger_error( "\$user = $user" ); // may not be logged anywhere ?> To disable custom error handling in ownCloud (and have PHP and your webserver handle errors instead), see Debug mode. Debugging Javascript By default all Javascript files in ownCloud are minified (compressed) into a single file without whitespace. To prevent this, see Debug mode. Debugging HTML and templates By default ownCloud caches HTML generated by templates. This may prevent changes to app templates, for example, from being applied on page refresh. To disable caching, see Debug mode. Using alternative app directories It may be useful to have multiple app directories for testing purposes, so you can conveniently switch between different versions of applications. See the configuration file documentation for details. 16 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.1.4 AngularJS AngularJS is an MV* JavaScript framework by Google. Documentation is available at these sources: • Official tutorial • Developer guide • API reference • Screencasts on Youtube • More screencasts • Collection of resources What problem does it solve jQuery is a nice library but when it comes to building webapplications, one will soon reach a point where its becoming increasingly impossible to split view and logic. That problem is caused by jQueries habit to operate directly on dom elements. Most jQuery code looks like this: $(’.someElement’).doSomething(); That makes it incredible hard to refactor your view because your whole JavaScript code is tightly coupled to the structure and classes in your HTML. Another problem is the dynamic generation of DOM elements. You’d normally go with one of these three approaches: 1) Create new element, bind event listeners and append it to the dom: var $newButton = $(’<button>’).text(’A new button’); $newButton.click(function(){ alert(’I was clicked!’); }); $(’.someElement’).append($newButton); 2) Fetch HTML from the server and bind event listeners: $.post(’/some/url’, function(data){ $newButton = $(data); $newButton.click(function(){ alert(’I was clicked!’); }); $(’.someElement’).append($newButton); }); 3) Use jquery templates: var buttons = [ text: ’A new button’ ]; var markup = "<button>${text}</button>"; $.template( "myTemplate", markup ); $.tmpl( "myTemplate", buttons ).appendTo( ".someElement" ); // and bind the click listener 3.1. General 17 ownCloud Developer Manual, Release 6.0 All of the above split the HTML from the original HTML and its hard to bind event listeners (yes, there’s $.on(), but its slow). You are also in need of updating the DOM by hand. In contrast to the above solutions, Angular uses XML attributes to define the template logic. This approach does not only good for your editor, but you’re also less likely to create HTML errors. You can even validate the HTML. Furthermore, every value that is written into the HTML is escaped to prevent XSS. Concerning testability: Angular uses Dependency Injection to glue the code together and it’s easy to run your unittests(look at the examples in the official docs). Angular also ships with mocks for common areas like HTTP requests or logging. Thats how the code would look with Angular: <div ng-app="MyApp" class="someElement" ng-controller="ButtonController"> <button ng-repeat="button in buttons" ng-click="showClicked()">{{ button.text }}</button> </div> The button controller handles the complete logic. It would look something like this: var app = angular.module(’MyApp’, []); app.controller(’ButtonController’, [’$scope’, function($scope){ $scope.buttons = [ {text: ’A new button’} ]; $scope.showClicked = function(){ alert(’I was clicked!’); }; } ); Now your logic is nicely decoupled from your view and the template logic is where you would expect it to be: in the HTML markup. Angular also knows when your data has changed: when a new element is added to the $scope.buttons array, the view will automatically update. It also updates the view when an existing element in the array changes. Drawbacks of AngularJS That brings us also to the biggest problem of AngularJS: It can be slow at times. This is caused by the way Angular works Should you somehow require to show more than around 1000 complex elements at once (like 1000 buttons with lots of wiring inside the code and a ton of attributes) there will most likely be performance problems (To be fair: normal JavaScript would also run into performance problems). One way to tackle this is to use autopaging (progressive loading) that only renders X elements and loads the next batch when the user scrolls down for instance. This also reduces the traffic. Software that successfully uses this approach is Google Reader for instance. When porting the News app to AngularJS we found that the benefits outweighed the drawbacks and that we could optimize the Code well enough to offer a good user experience. But all in all you should build an optimized prototype and compare it to a non angular app to make sure that the user experience is good. 18 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.1.5 Dependency Injection Dependency Injection is a programming pattern that helps you decouple dependencies between classes. The result is cleaner and more testable code. A good overview over how it works and what the benefits are can be read on Google’s Clean Code Talks Basic Problem Consider the following class: <?php class PersonController { public function listNames(){ $sql = "SELECT ‘prename‘ FROM ‘persons‘"; $query = \OCP\DB::prepare($sql); $result = $query->execute(); while($row = $result->fetchRow()){ echo ’<p>’ . htmlentities($row[’prename’]) . ’</p>’; } } } This class prints out all prenames of a person and is called by using: <?php $controller = new PersonController(); $controller->listNames(); This looks like clean code until the first tests are written. Tests are absolutely necessary in every application! Do not think that your app is too small to require them. The code will eventuell grow bigger and will have to be refactored. If code is refactored code will be written. If code is being written there will be bugs. If there will be bugs then every possible failure must be tested. This is tiresome and must be automated. If the code already comes with tests, this is not a problem. The unittests ensure that the changes did not introduce regressions. Back to the above example: How would a test for the SQL query look like? • \OCP\DB needs to be Monkey Patched to make the query accessible • The monkey patching must not conflict with other tests which use the same class • There must be a database connection or the test will fail • If data is inserted into the database, it needs to be deleted afterwards This is a significant amount of work that needs to be done for every test. If something is hard to do, people tend to not do it that often or even won’t do it at all. Because tests are necessary they need to be written and therefore they must be as easy as possible. Furthermore Monkey Patching is not a good solution, so most static classes need to be built with testing methods built in. These methods are only for debugging purposes and do not add any value to the running product. 3.1. General 19 ownCloud Developer Manual, Release 6.0 Inject Dependencies The reason why the example class is hard to test is because it depends on \OCP\DB. To be able to test it, the class needs to be replaced with a mock object. This mock object is passed to the class either using a Setter or using an additional parameter in the constructor. The most common one is constructor injection. Using constructor injection, the example can be refactored by simply passing the object which executes the database request. At this point the first problem arises: \OCP\DB uses static methods and can not be instatiated as an object. This is because ownCloud uses static incorrectly. Note: Static methods and attributes should only be used for sharing information that needs to be available in all classes of this instance. Do not use static methods for utility functions! Instantiating an object is only one line in your code and can limitted to one place by putting it into a container. Note: Because of constructor injection, the constructor should not contain anything else than initializing attributes. Never open a connection in a constructor. Provide a seperate method that handles resourceintensive initialization. To get around the the static method call, which is actually more like a function call, the method needs to be wrapped in an object. This object can be passed into the class. The refactored class would look like this: <?php class API { public function prepareQuery($sql){ return \OCP\DB::prepare($sql); } } class PersonController { private $api; public function __construct($api){ $this->api = $api; } public function listNames(){ $sql = "SELECT ‘prename‘ FROM ‘persons‘"; $query = $this->api->prepareQuery($sql); $result = $query->execute(); while($row = $result->fetchRow()){ echo ’<p>’ . htmlentities($row[’prename’]) . ’</p>’; } } } // run controller $api = new API(); $controller = new PersonController($api); $controller->listNames(); Now a first, simple test can be written: 20 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Note: The other methods that are called on the mock object need to be implemented too, but for the sake of simplicity this is not done in this example <?php class PersonControllerTest extends \PHPUnit_Framework_TestCase { private $api; public function setUp(){ $this->api = $this->getMock(’API’, array(’prepareQuery’)); $this->controller = new PersonController($this->api); } public function testListNamesQuery(){ $sql = "SELECT ‘prename‘ FROM ‘persons‘"; $this->api->expects($this->once()) ->method(’prepareQuery’) ->with($this->equalTo($sql)); $this->controller->listNames(); } } Limit input and output to one place The code also depends on another function: echo. Because this is usually hard to test, it is better to limit the use of input and output functions to one place. Remember that ownCloud uses PHP which likes to do output in functions like header or session_start. The refactored code would look like this: <?php class API { public function prepareQuery($sql){ return \OCP\DB::prepare($sql); } } class PersonController { private $api; public function __construct($api){ $this->api = $api; } public function listNames(){ $sql = "SELECT ‘prename‘ FROM ‘persons‘"; $query = $this->api->prepareQuery($sql); $result = $query->execute(); $output = ’’; 3.1. General 21 ownCloud Developer Manual, Release 6.0 while($row = $result->fetchRow()){ $output .= ’<p>’ . htmlentities($row[’prename’]) . ’</p>’; } return $output; } } // run controller $api = new API(); $controller = new PersonController($api); echo $controller->listNames(); The output test can now be implemented as a simple string comparison. Use a container The above example works fine in small cases, but if the class depends on four other classes that each depend on two other classes there will be eight instantiations. Also if one constructor parameter changes, every line that instantiates the class will have to change too. The solution is to define the injected classes as dependencies and let the system handle the rest. Pimple is a simple container implementation. The documentation on how to use it can be read on the Pimple Homepage The dependencies can now be defined like this: <?php class DIContainer extends \Pimple { public function __construct(){ $this[’API’] = $this->share(function($c){ return new API(); }); $this[’PersonController’] = function($c){ return new PersonController($c[’API’]); }; } The output could look like this: <?php $container = new DIContainer(); echo $container[’PersonController’]->listNames(); The container figures out all dependencies and instantiates the objects accordingly. Also by using the share method, the anti-pattern Singleton can be avoided. From the Pimple Tutorial: By default, each time you get an object, Pimple returns a new instance of it. If you want the same in 22 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Do not inject the container Injecting the container as a dependency is known as the Service Locator Pattern which is widely regarded as an antipattern. It makes your code dependant on the container and hides the class’ real dependencies. This makes testing and maintaining harder. 3.2 App Development Intro 3.2.1 Creating An App After the apps were cloned into either • /var/www • /var/www/html • /srv/http Depending on the used distribution change into that directory inside a terminal: cd /var/www/ Create the app automatically The scaffolding script provides an easy way to generate boilerplate code for an app. To install the tool, install Python 3 and python-pip, then run: sudo pip install owncloud_scaffolding To create an app using the App Framework run: owncloud.py startapp myapp To create a standard ownCloud app run: owncloud.py startapp --classic myapp This will create all the needed files in the current directory. For more information on how to customize the generated app, see the GitHub page Create the app manually If you dont want to use the scaffolding tool, heres how you create all the needed files: create a directory for the app and make it writable: mkdir apps/YOUR_APP sudo chown -R YOUR_USER:users apps/YOUR_APP If you are unsure about your user whoami to find out your user: whoami 3.2. App Development Intro 23 ownCloud Developer Manual, Release 6.0 Create basic files The following files need to be created: appinfo/info.xml, appinfo/version and appinfo/app.php. To do that change into the directory of your app: cd apps/YOUR_APP mkdir appinfo touch appinfo/version appinfo/app.php appinfo/info.xml Set app version To set the version of the app, simply write the following into appinfo/version: 0.1 Create metadata ownCloud has to know what your app is. This information is located inside the appinfo/info.xml: <?xml version="1.0"?> <info> <id>appname</id> <name>My App</name> <description>Simple app that does some computing</description> <version>1.0</version> <licence>AGPL</licence> <author>Your Name</author> <require>5</require> </info> For more information on the content of appinfo/info.xml see: App Metadata Enable the app The easiest way to enable is to symlink it into the owncloud/apps directory: ln -s /var/www/apps/YOUR_APP /var/www/owncloud/apps/ This is also how other apps from the apps directory have to be enabled. A second way is to tell ownCloud about the directory. Use App config to set up multiple app directories. The app can now be enabled on the ownCloud apps page. Note: The app does not show up yet in the navigation. This is intended. How to create an entry in the navigation is explained in the following tutorials. Start coding The basic files are now in place and the app is enabled. There are two ways to create the app: • Use the ownCloud app API • Use the App Framework app 24 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 If you are new to programming and want to create an app fast you migth want to use the ownCloud app API, if you are an advanced programmer or used to frameworks you might want to use the App Framework App. App Framework Comparison To simplify the decision see this comparison chart: Criteria Difficulty Architecture Testability Maintainability Templates Security ownCloud app API easy routes and templates hard hard OC_Template manual checks App Framework medium routes and MVC easy: built-in Dependency Injection and TDD tools easy OC_Template and Twig escapse XSS (Twig only), does CSRF and Authentication checks by default 3.3 App Development (ownCloud App API) 3.3.1 App Tutorial This tutorial contains the traditional approach to write an app and continues where Creating An App left off. The result will be a simple “Hello World” app. Navigation entry This file will always loaded for every app and can for instance be used to load additional JavaScript for the files app. Therefore the navigation entry has to be registered in this file. appinfo/app.php: <?php \OCP\App::addNavigationEntry(array( // the string under which your app will be referenced in owncloud ’id’ => ’myapp’, // sorting weight for the navigation. The higher the number, the higher // will it be listed in the navigation ’order’ => 74, // the route that will be shown on startup ’href’ => \OCP\Util::linkToRoute(’myapp_index’), // the icon that will be shown in the navigation // this file needs to exist in img/example.png ’icon’ => \OCP\Util::imagePath(’myapp’, ’nav-icon.png’), // the title of your application. This will be used in the // navigation or on the settings page of your app ’name’ => ’My App’ )); 3.3. App Development (ownCloud App API) 25 ownCloud Developer Manual, Release 6.0 Create the main route Routes map the URL to functions and allow to extract values. To show the content when the navigation entry is clicked, the index route which was defined in the appinfo/app.php needs to be created: appinfo/routes.php: <?php $this->create(’myapp_index’, ’/’)->action( function($params){ require __DIR__ . ’/../index.php’; } ); Write the logic In this example the logic is written procedurally in a PHP file. This file contains database queries and security checks and prints the final template: index.php: <?php // Look up other security checks in the docs! \OCP\User::checkLoggedIn(); \OCP\App::checkAppEnabled(’myapp’); $tpl = new OCP\Template("myapp", "main", "user"); $tpl->assign(’msg’, ’Hello World’); $tpl->printPage(); Create the template The last thing that needs to be done is to create the Templates file which was used in the index.php. templates/main.php: <p><?php p($_[’msg’]); ?></p> Congratulations! The message “Hello World” can now be seen on the main page of your app. .. 6.0 replace:: 6.0 3.3.2 App Metadata The appinfo/info.xml contains metadata about the app: <?xml version="1.0"?> <info> <id>yourappname</id> <name>Your App</name> <description>Your App description</description> <version>1.0</version> <licence>AGPL</licence> <author>Your Name</author> <require>5</require> 26 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 <types> <type>filesystem</type> </types> <remote> <file id="caldav">appinfo/caldav.php</file> </remote> <documentation> <user>http://doc.owncloud.org</user> <admin>http://doc.owncloud.org</admin> </documentation> <website>http://www.owncloud.org</website> <public> <file id="caldav">appinfo/caldav.php</file> </public> <standalone /> <default_enable /> <shipped>true</shipped> </info> id Required: This field contains the internal app name, and has to be the same as the foldername of the app. This id needs to be unique in ownCloud, meaning no other app should have this id. name Required: This is the human-readable name/title of the app that will be displayed in the app overview page. description Required: This contains the description of the app which will be shown in the apps overview page. version Contains the version of your app. Please also provide the same version in the appinfo/version. licence Required: The licence of the app. This licence must be compatible with the AGPL and must not be proprietary, for instance: • AGPL 3 (recommended) • MIT If a proprietary/non AGPL compatible licence should be used, the ownCloud Enterprise Edition must be used. 3.3. App Development (ownCloud App API) 27 ownCloud Developer Manual, Release 6.0 author Required: The name of the app author or authors. require Required: The minimal version of ownCloud. types ownCloud allows to specify four kind of “types”. Currently supported “types”: • prelogin: apps which needs to load on the login page • filesystem: apps which provides filesystem functionality (e.g. files sharing app) • authentication: apps which provided authentication backends • logging: apps which implement a logging system public Used to provide a public interface (requires no login) for the app. cloud/index.php/public. Example with id set to ‘calendar’: The id is appended to the URL /own- /owncloud/index.php/public/calendar Also take a look at External API. remote Same as public but requires login. The id is appended to the URL /owncloud/index.php/remote. Example with id set to ‘calendar’: /owncloud/index.php/remote/calendar Also take a look at External API. documentation link to ‘admin’ and ‘user’ documentation website link to project webpage standalone Can be set to true to indicate that this app is a webapp. This can be used to tell GNOME Web for instance to treat this like a native application. 28 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 default_enable Core apps only: Used to tell ownCloud to enable them after the installation. shipped Core apps only: Used to tell ownCloud that the app is in the standard release. Please note that if this attribute is set to FALSE or not set at all, every time you disable the application, all the files of the application itself will be REMOVED from the server! 3.3.3 Classloader The classloader is provided by ownCloud and loads all your classes automatically. The only thing left to include by yourself are 3rdparty libraries. Note that this means that the classes need to be named and organized in folders according to their full qualifier. The classloader works like this: • Take the full qualifier of a class: \OCA\AppTemplateAdvanced\Db\ItemMapper • If it starts with \OCA include file from the apps directory • Cut off \OCA: \AppTemplateAdvanced\Db\ItemMapper • Convert all charactes to lowercase: \apptemplateadvanced\db\itemmapper • Replace \ with /: /apptemplateadvanced/db/itemmapper • Append .php: /apptemplateadvanced/db/itemmapper.php • Prepend /apps because of the OCA namespace and include the file: require ’/apps/apptemplateadvanced/db/itemmapper.php’; Remember: for it to be autoloaded, the itemmapper.php needs to either be stored in the /apps/apptemplateadvanced/db/ folder, or adjust its namespace according to the folder it’s stored in. .. 6.0 replace:: 6.0 3.3.4 Routes PHP usually treats the URL like a filepath. This is easy for beginners but gets more complicated if a good architecture is required. For instance if an URL should call a certain function/method or if values should be extracted from the URL. Routing connects your URLs with your controller methods and allows you to create constant and nice URLs. Its also easy to extract values from the URLs. 3.3. App Development (ownCloud App API) 29 ownCloud Developer Manual, Release 6.0 ownCloud uses Symphony Routing Routes are declared in appinfo/routes.php A simple route would look like this: <?php // this route matches /index.php/yourapp/myurl/SOMEVALUE $this->create(’yourappname_routename’, ’/myurl/{key}’)->action( function($params){ require __DIR__ . ’/../index.php’; } ); The first argument is the name of your route. This is used as an identifier to get the URL of the route and is a nice way to generate the URL in your templates or JavaScript for certain links since it does not force you to hardcode your URLs. Note: The identifier should always start with the appid since they are global and you could overwrite a route of a different app The second parameter is the URL which should be matched. You can extract values from the URL by using {key} in the section that you want to get. That value is then available under $params[’key’], for the above example it would be $params[’key’]. You can omit the parameter if you dont extract any values from the URL at all. If a default value should be used for an URL parameter, it can be set via the defaults method: <?php $this->create(’yourappname_routename’, ’/myurl/{key}’)->action( function($params){ require __DIR__ . ’/../index.php’; } )->defaults(’key’ => ’john’); The action method allows you to register a callback which gets called if the route is matched. You can use this to call a controller or simply include a PHP file. Using routes in templates and JavaScript To use routes in OC_Template, use: <? print_unescaped(\OCP\Util::linkToRoute( ’yourappname_routename’, array(’key’ => 1))); In JavaScript you can get the URL for a route like this: var params = {key: 1}; var url = OC.Router.generate(’yourappname_routename’, params); console.log(url); // prints /index.php//yourappname/myurl/1 Note: Be sure to only use the routes generator after the routes are loaded. This can be done by registering a callback with OC.Router.registerLoadedCallback(callback) 30 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.3.5 Database Schema ownCloud uses a database abstraction layer on top of either MDB2 or PDO, depending on the availability of PDO on the server. The database schema is inside appinfo/database.xml in MDB2’s XML scheme notation where the placeholders *dbprefix* (*PREFIX* in your SQL) and *dbname* can be used for the configured database table prefix and database name. An example database XML file would look like this: <?xml version="1.0" encoding="ISO-8859-1" ?> <database> <name>*dbname*</name> <create>true</create> <overwrite>false</overwrite> <charset>utf8</charset> <table> <name>*dbprefix*yourapp_items</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <length>4</length> </field> <field> <name>user</name> <type>text</type> <notnull>true</notnull> <length>64</length> </field> <field> <name>name</name> <type>text</type> <notnull>true</notnull> <length>100</length> </field> <field> <name>path</name> <type>clob</type> <notnull>true</notnull> </field> </declaration> </table> </database> To update the tables used by the app, simply adjust the database.xml file and increase the app version number in appinfo/version to trigger an update. 3.3.6 Database Access After the schema has been defined it is possible to query the database. ownCloud uses prepared statements. A simple query would look like this: 3.3. App Development (ownCloud App API) 31 ownCloud Developer Manual, Release 6.0 <?php // *PREFIX* is being replaced with the ownCloud installation prefix $sql = ’SELECT * FROM ‘*PREFIX*myusers‘ WHERE id = ?’; $args = array(1); $query = \OCP\DB::prepare($sql); $result = $query->execute($args); while($row = $result->fetchRow()) { $userName = $row[’username’]; } If a new element is saved to the database the inserted id can be accessed by using: <?php $id = \OCP\DB::insertid(); It is also possible to use transactions: <?php \OCP\DB::beginTransaction(); $sql = ’SELECT * FROM ‘*PREFIX*myusers‘ WHERE id = ?’; $args = array(1); $query = \OCP\DB::prepare($sql); $result = $query->execute($args); \OCP\DB::commit(); 3.3.7 Templates ownCloud provides its own templating system. In every template file you can easily access the template functions listed in OC Templates. To access the assigned variables in the template, use the $_[] array. The variable will be availabe under the key that you defined (e.g. $_[’key’]). templates/main.php <?php foreach($_[’entries’] as $entry){ ?> <p><?php p($entry); ?></p> <?php } print_unescaped($this->inc(’sub.inc’)); ?> Warning: Changed in version 5.0. To prevent XSS the following PHP functions for printing are forbidden: echo, print() and <?=. Instead use the p() function for printing your values. Should you require unescaped printing, double check for XSS and use: print_unescaped. Templates can also include other templates by using the $this->inc(‘templateName’) method. Use this if you find yourself repeating a lot of the same HTML constructs. 32 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 The parent variables will also be available in the included templates, but should you require it, you can also pass new variables to it by using the second optional parameter as array for $this->inc. templates/sub.inc.php <div>I am included but i can still access the parents variables!</div> <?php p($_[’name’]); ?> <?php print_unescaped($this->inc(’other_template’, array(’variable’ => ’value’))); ?> For more info, see OC Templates .. 6.0 replace:: 6.0 3.3.8 Static content Static content consists of: • img/: all images • js/: all JavaScript files • css/: all CSS files Because ownCloud templates do not support template inheritance it is not possible to add your own script tags. Therefore ownCloud provides the methods \OCPUtil::addScript(‘your_app_id’, ‘script’) and \OCPUtil::addStyle(‘your_app_id’, ‘style’). The first parameter is the app’s id, the second one is the path relative to the js/ respectively the css/ folder without the file extension. If you use Twig Templates, there is the script and style function, see Templates. CSS and JavaScript are compressed by ownCloud so if the CSS or JavaScript do not seem to get updated, check if the debug mode is enabled. To enable it see Enabling debug mode 3.3.9 CSS Stylesheets have to be included in your templates, see Static content If you have to include an image or css file in your CSS, prepend the following to your path: • %appswebroot%: gets the absolute path to your app • %webroot%: gets the absolute path to owncloud For example: .folder > .title { background-image: url(’%webroot%/core/img/places/folder.svg’); } CSS for apps ownCloud comes with special CSS rules for apps to make app development easier. Todo document this 3.3. App Development (ownCloud App API) 33 ownCloud Developer Manual, Release 6.0 Formfactors ownCloud automatically detects what kind of form factor you are using. Currently supported are: • mobile: works well on mobiles • tablet: optimized for devices like iPads or Android Tablets • standalone: mode where only the content of an App is shown. The header, footer and side navigation is not visible. This is useful if ownCloud is embedded in other applications. The auto detection can be overwritten by using the “formfactor” GET variable in the url: index.php/myapp?formfactor=mobile If you want to provide a different stylesheet or javascript file for mobile devices just suffix the formfactor in the filename, like: style.mobile.css or: script.tablet.css 3.3.10 External API Introduction The external API inside ownCloud allows third party developers to access data provided by ownCloud apps. ownCloud version 5.0 will follow the OCS v1.7 specification (draft). Usage Registering Methods Methods are registered inside the appinfo/routes.php using OCP\API <?php \OCP\API::register( ’get’, ’/apps/yourapp/url’, function($urlParameters) { return new \OC_OCS_Result($data); }, ’yourapp’, \OC_API::ADMIN_AUTH ); Returning Data Once the API backend has matched your URL, your callable function as defined in $action will be executed. This method is passed as array of parameters that you defined in $url. To return data back the the client, you should return an instance of OC_OCS_Result. The API backend will then use this to construct the XML or JSON response. 34 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Authentication & Basics Because REST is stateless you have to send user and password each time you access the API. Therefore running ownCloud with SSL is highly recommended otherwise everyone in your network can log your credentials: https://user:[email protected]/ocs/v1.php/apps/yourapp Output The output defaults to XML. If you want to get JSON append this to the URL: ?format=json Output from the application is wrapped inside a data element: XML: <?xml version="1.0"?> <ocs> <meta> <status>ok</status> <statuscode>100</statuscode> <message/> </meta> <data> <!-- data here --> </data> </ocs> JSON: { "ocs": { "meta": { "status": "ok", "statuscode": 100, "message": null }, "data": { // data here } } } Statuscodes The statuscode can be any of the following numbers: • 100 - successfull • 996 - server error • 997 - not authorized • 998 - not found • 999 - unknown error 3.3. App Development (ownCloud App API) 35 ownCloud Developer Manual, Release 6.0 3.3.11 Filesystem ownCloud handling of filesystems is very flexible. A variety of local and remote filesystem types are supported, as well as a variety of hooks and optional features such as encryption and version control. It is important that apps use the correct methods for interacting with files in order to maintain this flexibility. In some cases using PHP’s internal filesystem functions directly will be sufficient, such as unlink() and mkdir(). Most of the time however it is necessary to use one of ownCloud’s filesystem classes. This documentation assumes that you are working with files stored within a user’s directory (as opposed to ownCloud core files), and therefore need to use OC\Files\View. Todo write the rest 3.3.12 Hooks In ownCloud apps, function or methods (event handlers) which are used by the app and called by ownCloud core hooks, are generally stored in apps/appname/lib/hooks.php. Hooks are a way of implementing the observer pattern, and are commonly used by web platform applications to provide clean interfaces to third party applications which need to modify core application functionality. In ownCloud, a hook is a function whose name can be used by developers of plug-ins to ensure that additional code is executed at a precise place during the execution of other parts of ownCloud code. For example, when an ownCloud user is deleted, the ownCloud core hook post_deleteUser is executed. In the calendar app’s appinfo/app.php, this hook is connected to the app’s own event handler deleteUser (user here refers to an ownCloud user; deleteUser deletes all addressbooks for that a given ownCloud user). When post_deleteUser calls the calender app’s deleteUser event handler, it supplies it with an argument, which is an array containing the user ID of the user that has just been deleted. This user ID is then used by the event handler to specify which address books to delete. There are three components to the use of hooks in this example: 1. The ownCloud core hook post_deleteUser, (see what arguments / data it will provide in lib/user.php, where it is defined) 2. The event handler deleteUser, defined in apps/contacts/lib/hooks.php. 3. The connection of the hook to the event handler, in apps/contacts/appinfo/app.php. Hook Terminology • Signal class / emitter class: the class that contains the method which contains the creation of the hook (and a call to the emit() method) e.g. OC_User • Signal / signal name: the name of the hook, e.g. post_deleteUser • Slot class: class housing the event handling method, e.g. OC_Contacts_Hooks • Slot name: event handler method, e.g. deleteUser (function that deletes all contact address books for a user) Available hooks File: apps/calendar/ajax/events.php, Class: OC_Calendar • Hook: getEvents 36 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 File: apps/calendar/index.php, Class: OC_Calendar • Hook: getSources File: dav.php, Class: OC_DAV • Hook: initialize File: lib/migrate.php, Class: OC_User • Hook: pre_createUser • Hook: post_createUser File: lib/filecache.php, Class: OC_Filesystem • Hook: post_write • Hook: post_write • Hook: post_delete • Hook: post_write File: lib/user.php, Class: OC_User • Hook: pre_createUser • Hook: post_createUser • Hook: pre_deleteUser • Hook: post_deleteUser • Hook: pre_login • Hook: post_login • Hook: logout • Hook: pre_setPassword • Hook: post_setPassword File: lib/group.php, Class: OC_Group • Hook: pre_createGroup • Hook: post_createGroup • Hook: pre_deleteGroup • Hook: post_deleteGroup • Hook: pre_addToGroup • Hook: post_addToGroup • Hook: pre_removeFromGroup • Hook: post_removeFromGroup 3.3.13 Data Migration As of OC4, user migration is supported. To include migration support in your app (which is highly recommended and does not take long) you must provide a appinfo/migrate.php. The function of the migrate.php file is to provide an import and export functions for app data. To assist in this, we set the user id of the user being exported / user being 3.3. App Development (ownCloud App API) 37 ownCloud Developer Manual, Release 6.0 imported in $this->uid. There is also an instance of the OC_Migration_Content class stored in $this->content. The OC_Migration_Content class helps to make importing and exporting data easy for app developers. Export In this function, you must do everything necessary to export a user from the current ownCloud instance, given a user id. For most apps this is just the case of saving a few rows from the database. Database Data To make exporting database data really easy, the class OC_Migration_Content has a method called copyRows() which will save these rows for you given some options. Take a look at the export function for the bookmarks app: <?php function export( ){ OC_Log::write(’migration’,’starting export for bookmarks’,OC_Log::INFO); // migrate two tables $bookmarkOptions = array( ’table’=>’bookmarks’, ’matchcol’=>’user_id’, ’matchval’=>$this->uid, ’idcol’=>’id’ ); $bookmarkIds = $this->content->copyRows( $bookmarkOptions ); $bookmarkTagsOptions = array( ’table’=>’bookmarks_tags’, ’matchcol’=>’bookmark_id’, ’matchval’=>$ids ); $bookmarkTagsIds = $this->content->copyRows( $bookmarkTagsOptions ); // If both returned some ids then they worked if( is_array( $bookmarkIds ) && is_array( $bookmarkTagsIds ) ) { return true; } else { return false; } } The bookmarks app stores all of its data in the database, in two tables: *PREFIX*bookmarks and *PREFIX*bookmarks_tags so to export this, we need to run copyRows() twice. Here is an explanation of the options passed to it: • table: string name of the table to export (without any prefix) • matchcol: (optional) string name of the column that will be matched with the value in matchval (Basically the column used in the WHERE sql query) • matchval: (optional) the value that will be searched for in the table • idcol: the name of the column that will be returned To export the bookmarks, matchcol is set to the user_id column and matchval is set to the user being exported: $this>content->uid. idcol is set to the id of the bookmark, as we need to retrive the tags associated with the bookmarks for 38 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 the user being exported. The function will return an array of ids.Next we run the copyRows() method again, this time on the bookmarks_tags table, matching a range of values (as we want to find all tags, related to all bookmarks owned by the exported user).Finally we check that both functions returned arrays which confirms that they were successful and return a boolean value to represent the success of the export. Files If you use files to hold some app data in data/userid/appname/, they will be automatically copied exported for you. Import Import is a little more tricky as we have to take into account data from different versions of your app, and also handle changing primary keys. Here is the import function for the bookmarks app which imports bookmarks and tags: <?php function import(){ switch( $this->appinfo->version ){ default: // All versions of the app have had the same db structure // so all can use the same import function $query = $this->content->prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); $results = $query->execute( array( $this->olduid )); $idmap = array(); while( $row = $results->fetchRow() ){ // Import each bookmark, saving its id into the map $sql = "INSERT INTO *PREFIX*bookmarks" . "(url, title, user_id, public, added, lastmodified)" . " VALUES (?, ?, ?, ?, ?, ?)"; $query = OC_DB::prepare($sql); $query->execute( array( $row[’url’], $row[’title’], $this->uid, $row[’public’], $row[’added’], $row[’lastmodified’] ) ); // Map the id $idmap[$row[’id’]] = OC_DB::insertid(); } // Now tags foreach($idmap as $oldid => $newid){ $sql = "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?"; $query = $this->content->prepare($sql); $results = $query->execute( array( $oldid ) ); while( $row = $data->fetchRow() ){ // Import the tags for this bookmark, using the new bookmark id $sql = "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag)". " VALUES (?, ?)"; $query = OC_DB::prepare($sql); $query->execute( array( $newid, $row[’tag’] ) ); } } // All done! break; 3.3. App Development (ownCloud App API) 39 ownCloud Developer Manual, Release 6.0 } return true; } We start off by using a switch to run different import code for different versions of your app. $this->appinfo->version contains the version string from the appinfo/info.xml of your app. In the case of the bookmarks app the db structure has not changed, so only one version of import code is needed. To import the db data, first we must retrive it from the migration.db. To do this we use the prepare method from OC_Migration_Content, which returns a MDB2 db object. We then cycle through the bookmarks in migration.db and insert them into the owncloud database. The important bit is the idmapping. After inserting a boookmark, The new id of the bookmark is saved in an array, with the key being the old id of the bookmark. This means when inserting the tags, we know what the new id of the bookmark is simply by getting the value of $idmap[’oldid’]. Remember this part of the import code may be a good place to emit some hooks depending on your app. For example the contacts app could emit some hooks to show some contacts have been added. After importing the bookmarks, we must import the tags. It is a very similar process to importing the bookmarks, except we have to take into account the changes in primary keys. This is done by using a foreach key in the $idmap array, and then inserting the tags using the new id. After all this, we must return a boolean value to indicate the success or failure of the import. Again, app data files stored in data/userid/appname/ will be automatically copied over before the apps import function is executed, this allows you to manipulate the imported files if necessary. Conclusion To fully support user migration for your app you must provide an import and export function under an instance of OC_Migration_Provider and put this code in the file appinfo/migrate.php 3.3.14 ownCloud API OCS Manages the backend of the external API class OCP\API register($method, $url, $action, $app, $authlevel, $defaults, $requirements) Registers an API route with the backend. Parameters • $method (string) – The HTTP method (get, post, put or delete) • $url (string) – The URL that defines the API route which can also include params (See the Symfony Documentation) • $action (callable) – The function to call • $app (string) – The app id • $authlevel (int) – The required level of authentication to access the API method. The following constants can be passed: OC_API::ADMIN_AUTH, OC_API::SUBADMIN_AUTH, OC_API::USER_AUTH, OC_API::GUEST_AUTH • $defaults (array) – associative array of defaults for the URL parameters. Keys are the parameter names as defined in the url 40 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $requirements (array) – associative array of requirements for the url parameters (See the Symfony Documentation: Adding Requirements) OCS_Result Holds data on the result of the API method. class OC_OCS_Result __construct($data=null, $code=100, $message=null) Creates an OC_OCS_Result object Parameters • $data (mixed) – The data you wish to return, this may be a string, integer or array • $code (int) – The response code you wish to return, defaults to success (100) • $message (string) – An optional message to return with the response (explaining the result) setTotalItems($items) Sets the <totalitems> value in the response. Use this to inform the client of the range of data available. Parameters • $items (int) – The number of items in the full data set setItemsPerPage($items) Sets the <itemsperpage> value in the response. Use this to inform the client of the number of pieces of data per page. Parameters • $items (int) – The number of items per page of data. OC Templates OC_Template class OC_Template This class provides the templates for owncloud. It is used for loading template files, assign variables to it and render the whole template. __construct($app, $name[, $renderas ]) Parameters • $app (string) – the name of the app • $file (string) – name of the template file (without suffix) • $renderas (string) – If $renderas is set, OC_Template will try to produce a full page in the according layout. For now, renderas can be set to “guest”, “user” or “admin” Returns OC_Template object Example: 3.3. App Development (ownCloud App API) 41 ownCloud Developer Manual, Release 6.0 <?php $mainTemplate = new OC_Template(’news’, ’main’, ’user’); ?> addHeader($tag, $attributes[, $text=’‘ ]) Parameters • $tag (string) – tag name of the element • $attributes (array) – array of attrobutes for the element • $text (string) – the text content for the element Add a custom element to the html <head> Example: <?php $mainTemplate = new OC_Template(’news’, ’main’, ’user’); $mainTemplate->addHeader(’title’, array(), ’My new Page’); ?> append($key, $value) Parameters • $key (string) – the key under which the variable can be accessed in the template • $value – the value that we want to pass Returns bool This function assigns a variable in an array context. If the key already exists, the value will be appended. It can be accessed via $_[$key][$position] in the template. Example: <?php $customers = array("john", "frank"); $mainTemplate = new OC_Template(’news’, ’main’, ’user’); $mainTemplate->assign(’customers’, $customers); $mainTemplate->append(’customers’, ’hanna’); ?> assign($key, $value) Parameters • $key (string) – the key under which the variable can be accessed in the template • $value – the value that we want to pass Returns bool This function assigns a variable. It can be accessed via $_[$key] in the template. If the key existed before, it will be overwritten Example: <?php $customers = array("john", "frank"); $mainTemplate = new OC_Template(’news’, ’main’, ’user’); 42 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 $mainTemplate->assign(’customers’, $customers); ?> detectFormfactor() Returns The mode of the client as a string. default -> the normal desktop browser interface, mobile -> interface for smartphones, tablet -> interface for tablets, standalone -> the default interface but without header, footer and sidebar, just the application. Useful to use just a specific app on the desktop in a standalone window. Example: <?php $mainTemplate = new OC_Template(’news’, ’main’, ’user’); $formFactor = $mainTemplate->detectFormfactor(); ?> fetchPage() Returns the HTML of the template as string This function proceeds the template and but prints no output. Example: Todo provide example getFormFactorExtension() Returns Returns the formfactor extension for current formfactor (like .mobile or .tablet) Example: <?php $mainTemplate = new OC_Template(’news’, ’main’, ’user’); $formFactorExtension = $mainTemplate->detectFormfactorExtension(); ?> inc($file[, $additionalparams ]) Parameters • $file (string) – the name of the template • $additionalparams (array) – an array with additional variables which should be used for the included template Returns returns content of included template as a string Includes another template. use <?php print_unescaped($this->inc(‘template’)); ?> to do this. The included template has access to all parent template variables! Example: <div> <?php print_unescaped($this->inc(’nav.inc’, array(’active’ => ’nav_entry_1’)); ?> </div> printPage() Returns true when there is content to print 3.3. App Development (ownCloud App API) 43 ownCloud Developer Manual, Release 6.0 This function proceeds the template and prints its output. Example: <?php $mainTemplate = new OC_Template(’news’, ’main’, ’user’); $mainTemplate->assign(’test’, array("test", "test2")); $mainTemplate->printPage(); ?> printAdminPage($application, $name[, $parameters ]) Parameters • $application (string) – The application we render the template for • $name (string) – Name of the template • $parameters (array) – Parameters for the template Returns bool Shortcut to print a simple page for admin Example: Todo provide example printGuestPage($application, $name[, $parameters ]) Parameters • $application (string) – The application we render the template for • $name (string) – Name of the template • $parameters (array) – Parameters for the template Returns bool Shortcut to print a simple page for guests Example: Todo provide example printUserPage($application, $name[, $parameters ]) Parameters • $application (string) – The application we render the template for • $name (string) – Name of the template • $parameters (array) – Parameters for the template Returns bool Shortcut to print a simple page for users Example: 44 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Todo provide example Template functions These functions are automatically available in all templates. html_select_options html_select_options($options, $selected[, $params ]) Parameters • $options (array) – an array of the form value => label • $selected (string/array) – an array containing strings or a simple string which sets a value as selected • $params (array) – optional parameters that are done in key => value Returns the html as string of preset <option> tags Todo Fix parameters and add example human_file_size human_file_size($bytes) Parameters • $bytes (int) – the bytes that we want to convert to a more readable format Returns the human readable size as string Turns bytes into human readable formats, for instance 1024 bytes get turned into 1kb, 1024*1024 bytes get turned into 1mb <?php // this would print <li>2kB</li> ?> <li><?php p($this->human_file_size(’2048’)); ?></li> image_path image_path($app, $image) Parameters • $app (string) – the name of your app as a string. If the string is empty, ownCloud looks for the image in core • $image (array) – the filename of the image Returns the absolute URL to the image as a string This function looks up images in several common directories and returns the full link to it. The following directories are being searched: • /themes/$theme/apps/$app/img/$image 3.3. App Development (ownCloud App API) 45 ownCloud Developer Manual, Release 6.0 • /themes/$theme/$app/img/$image • /$app/img/$image When you pass an empty string for $app, the following directories will be searched: • /themes/$theme/apps/$app/img/$image • /themes/$theme/core/img/$image • /core/img/$image Example: <img src="<?php print_unescaped( image_path(’news’, ’starred.svg’); ); ?>" /> link_to link_to($app, $file[, $args ]) Parameters • $app (string) – the name of your app as a string. If the string is empty, ownCloud asumes that the file is in /core/ • $file (string) – the relative path from your apps root to the file you want to access • $args (array) – the GET parameters that you want set in the URL in form key => value. The value will be run through urlencode() Returns the absolute URL to the file This function is used to produce generate clean and absolute links to your files or pages. Example: <?php // this will produce the link: // index.php/news/pages/weather.php?show=berlin ?> <ul> <li><a href="<?php print_unescaped( link_to(’news’, ’pages/weather.php’, array("show" => "berlin")); ); ?>">Show Weather for Berlin</a></li> </ul> mimetype_icon mimetype_icon($mimetype) Parameters • $mimetype (array) – the mimetype for which we want to look up the icon Returns the absolute URL to the icon A shortcut for getting a mimetype icon. Example: 46 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 <img src="<?php print_unescaped( mimetype_icon(’application/xml’); ); ?>" /> p p($data) Parameters • $data – the variable/array/object that should be printed New in version 5.0. This is the print statement which prints out XSS escaped values. ownCloud does not allow the direct usage of echo or print but enforces wrapper functions to prevent unwanted XSS vulnerabilities. If you want to print unescaped data, look at print_unescaped Example: <?php $names = array("John", "Jakob", "Tom"); ?> <div> <ul> <?php foreach($names as $name){ ?> <li><?php p($name); ?></li> <?php } ?> </ul> </div> print_unescaped print_unescaped($data) Parameters • $data – the variable/array/object that should be printed New in version 5.0. This function does not escape the content for XSS. This would typically be used to print HTML or JavaScript that is generated by the server and checked for XSS vulnerabilities. Example: <?php $html = "<div>Some HTML</div>"; ?> <div> <?php print_unescaped($html); ?> </div> relative_modified_date relative_modified_date($timestamp) Parameters • $timestamp (int) – the timestamp from whom we compute the time span until now Returns a relative date as string Instead of displaying a date, it is often better to give a relative date like: “2 days ago” or “3 hours ago”. This function turns a timestamp into a relative date. <?php // this would print <span>5 minutes ago</span> ?> <span><?php p(relative_modified_date(’29393992912’)); ?></span> 3.3. App Development (ownCloud App API) 47 ownCloud Developer Manual, Release 6.0 simple_file_size simple_file_size($bytes) Parameters • $bytes (int) – the bytes that we want to convert to a more readable format in megabytes Returns the human readable size as string A more simpler function that only turns bytes into megabytes. If its smaller than 0.1 megabytes, < 0.1 is being returned. If its bigger than 1000 megabytes, > 1000 is being returned. <?php // this would print <li>< 0.1</li> ?> <li><?php p(simple_file_size(’2048’)); ?></li> Further reading • http://en.wikipedia.org/wiki/Cross-site_scripting • https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet • https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29 View class OC\Files\View __construct($root) Parameters • $root (mixed) – getAbsolutePath($path=’/’) Parameters • $path (mixed) – chroot($fakeRoot) Parameters • $fakeRoot (string) – Returns bool change the root to a fake root getRoot() Returns string get the fake root getRelativePath($path) Parameters • $path (string) – Returns string 48 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 get path relative to the root of the view getMountPoint($path) Parameters • $path (string) – Returns string get the mountpoint of the storage object for a path( note: because a storage is not always mounted inside the fakeroot, thereturned mountpoint is relative to the absolute root of the filesystemand doesn’t take the chroot into account ) resolvePath($path) Parameters • $path (string) – Returns array consisting of the storage and the internal path resolve a path to a storage and internal path getLocalFile($path) Parameters • $path (string) – Returns string return the path to a local version of the filewe need this because we can’t know if a file is stored local or not fromoutside the filestorage and for some purposes a local file is needed getLocalFolder($path) Parameters • $path (string) – Returns string mkdir($path) Parameters • $path (mixed) – the following functions operate with arguments and return values identicalto those of their PHP built-in equivalents. Mostly they are merely wrappersfor OCFilesStorageStorage via basicOperation(). rmdir($path) Parameters • $path (mixed) – opendir($path) Parameters • $path (mixed) – readdir($handle) Parameters • $handle (mixed) – 3.3. App Development (ownCloud App API) 49 ownCloud Developer Manual, Release 6.0 is_dir($path) Parameters • $path (mixed) – is_file($path) Parameters • $path (mixed) – stat($path) Parameters • $path (mixed) – filetype($path) Parameters • $path (mixed) – filesize($path) Parameters • $path (mixed) – readfile($path) Parameters • $path (mixed) – isCreatable($path) Parameters • $path (mixed) – isReadable($path) Parameters • $path (mixed) – isUpdatable($path) Parameters • $path (mixed) – isDeletable($path) Parameters • $path (mixed) – isSharable($path) Parameters • $path (mixed) – file_exists($path) Parameters • $path (mixed) – 50 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 filemtime($path) Parameters • $path (mixed) – touch($path, $mtime=null) Parameters • $path (mixed) – • $mtime (mixed) – file_get_contents($path) Parameters • $path (mixed) – file_put_contents($path, $data) Parameters • $path (mixed) – • $data (mixed) – unlink($path) Parameters • $path (mixed) – deleteAll($directory, $empty=false) Parameters • $directory (mixed) – • $empty (mixed) – rename($path1, $path2) Parameters • $path1 (mixed) – • $path2 (mixed) – copy($path1, $path2) Parameters • $path1 (mixed) – • $path2 (mixed) – fopen($path, $mode) Parameters • $path (mixed) – • $mode (mixed) – toTmpFile($path) Parameters • $path (mixed) – 3.3. App Development (ownCloud App API) 51 ownCloud Developer Manual, Release 6.0 fromTmpFile($tmpFile, $path) Parameters • $tmpFile (mixed) – • $path (mixed) – getMimeType($path) Parameters • $path (mixed) – hash($type, $path, $raw=false) Parameters • $type (mixed) – • $path (mixed) – • $raw (mixed) – free_space($path=’/’) Parameters • $path (mixed) – hasUpdated($path, $time) Parameters • $path (string) – • $time (int) – Returns bool check if a file or folder has been updated since $time getFileInfo($path) Parameters • $path (string) – Returns array returns an associative array with the following keys:- size- mtime- mimetypeencrypted- versioned get the filesystem info getDirectoryContent($directory, $mimetype_filter=’‘) Parameters • $directory (string) – path under datadirectory • $mimetype_filter (mixed) – Returns array get the content of a directory putFileInfo($path, $data) Parameters • $path (string) – 52 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $data (array) – Returns int returns the fileid of the updated file change file metadata search($query) Parameters • $query (string) – Returns array search for files with the name matching $query searchByMime($mimetype) Parameters • $mimetype (mixed) – Returns array search for files by mimetype getOwner($path) Parameters • $path (string) – Returns string Get the owner for a file or folder getETag($path) Parameters • $path (string) – Returns string get the ETag for a file or folder getPath($id) Parameters • $id (int) – Returns string Get the path of a file by id, relative to the view Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file 3.4 App Development (App Framework) 3.4.1 App Tutorial This tutorial contains the MVC approach to write an app and continues where Creating An App left off. The result will be a simple “Hello World” app. To make use of the App Framwork app it must be cloned and activated first by linking it into the app directory: 3.4. App Development (App Framework) 53 ownCloud Developer Manual, Release 6.0 cd /var/www sudo git clone https://github.com/owncloud/appframework.git sudo chown -R user:group /var/www/appframework sudo ln -s /var/www/appframework /var/www/owncloud/apps Note: This is only recommended for development! If a normal installation is used, place it inside the apps directory! After that activate it on the apps page. Create an navigation entry The app.php will always loaded for every app and can for instance be used to load additional JavaScript for the files app. Therefore the navigation entry has to be registered in this file. Note: The icon img/example.png needs to exist or the navigation will throw an error appinfo/app.php <?php namespace OCA\MyApp; // dont break owncloud when the appframework is not enabled if(\OCP\App::isEnabled(’appframework’)){ $api = new \OCA\AppFramework\Core\API(’myapp’); $api->addNavigationEntry(array( // the string under which your app will be referenced in owncloud ’id’ => $api->getAppName(), // sorting weight for the navigation. The higher the number, the higher // will it be listed in the navigation ’order’ => 10, // the route that will be shown on startup ’href’ => $api->linkToRoute(’myapp_index’), // the icon that will be shown in the navigation // this file needs to exist in img/example.png ’icon’ => $api->imagePath(’example.png’), // the title of your application. This will be used in the // navigation or on the settings page of your app ’name’ => $api->getTrans()->t(’My notes app’) )); } else { $msg = ’Can not enable the MyApp app because the App Framework App is disabled’; \OCP\Util::writeLog(’myapp’, $msg, \OCP\Util::ERROR); } 54 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 First Page Now that the basic files are created, the following things are needed to create a page: • A route: The URL which links to the controller • A controller: Gets the request and returns a response • An entry in the DIContainer: This makes the controller available for the application • A template: HTML which should be displayed on the page Create the main route Routes map the URL to functions and allow to extract values. To show the content when the navigation entry is clicked, the index route which was defined in the appinfo/app.php needs to be created: appinfo/routes.php <?php namespace OCA\MyApp; use \OCA\AppFramework\App; use \OCA\MyApp\DependencyInjection\DIContainer; $this->create(’myapp_index’, ’/’)->action( function($params){ // call the index method on the class PageController App::main(’PageController’, ’index’, $params, new DIContainer()); } ); Write the logic (Controller) The Controllers to which the route links does not exist yet and it has to be created: controller/pagecontroller.php <?php namespace OCA\MyApp\Controller; use \OCA\AppFramework\Controller\Controller; class PageController extends Controller { public function __construct($api, $request){ parent::__construct($api, $request); } /** * ATTENTION!!! * The following comments turn off security checks * Please look up their meaning in the documentation! 3.4. App Development (App Framework) 55 ownCloud Developer Manual, Release 6.0 * * @CSRFExemption * @IsAdminExemption * @IsSubAdminExemption */ public function index(){ return $this->render(’main’, array( ’msg’ => ’Hello World’ )); } } Create the template Now create the Templates which contains the HTML templates/main.php <div>{{ msg }}</div> Wire everything together The last thing that is left is to tell the application how the controller needs to be created. The App Framework makes heavy use of Dependency Injection and provides an IoC Container. Inside this container, the controller needs to be created: dependencyinjection/dicontainer.php <?php namespace OCA\MyApp\DependencyInjection; use \OCA\AppFramework\DependencyInjection\DIContainer as BaseContainer; use \OCA\MyApp\Controller\PageController; class DIContainer extends BaseContainer { public function __construct(){ parent::__construct(’myapp’); // use this to specify the template directory $this[’TwigTemplateDirectory’] = __DIR__ . ’/../templates’; $this[’PageController’] = function($c){ return new PageController($c[’API’], $c[’Request’]); }; } } Congratulations! The message “Hello World” can now be seen on the main page of your app. 56 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.4.2 App Metadata The appinfo/info.xml contains metadata about the app: <?xml version="1.0"?> <info> <id>yourappname</id> <name>Your App</name> <description>Your App description</description> <version>1.0</version> <licence>AGPL</licence> <author>Your Name</author> <require>5</require> <types> <type>filesystem</type> </types> <remote> <file id="caldav">appinfo/caldav.php</file> </remote> <documentation> <user>http://doc.owncloud.org</user> <admin>http://doc.owncloud.org</admin> </documentation> <website>http://www.owncloud.org</website> <public> <file id="caldav">appinfo/caldav.php</file> </public> <standalone /> <default_enable /> <shipped>true</shipped> </info> id Required: This field contains the internal app name, and has to be the same as the foldername of the app. This id needs to be unique in ownCloud, meaning no other app should have this id. name Required: This is the human-readable name/title of the app that will be displayed in the app overview page. description Required: This contains the description of the app which will be shown in the apps overview page. 3.4. App Development (App Framework) 57 ownCloud Developer Manual, Release 6.0 version Contains the version of your app. Please also provide the same version in the appinfo/version. licence Required: The licence of the app. This licence must be compatible with the AGPL and must not be proprietary, for instance: • AGPL 3 (recommended) • MIT If a proprietary/non AGPL compatible licence should be used, the ownCloud Enterprise Edition must be used. author Required: The name of the app author or authors. require Required: The minimal version of ownCloud. types ownCloud allows to specify four kind of “types”. Currently supported “types”: • prelogin: apps which needs to load on the login page • filesystem: apps which provides filesystem functionality (e.g. files sharing app) • authentication: apps which provided authentication backends • logging: apps which implement a logging system public Used to provide a public interface (requires no login) for the app. cloud/index.php/public. Example with id set to ‘calendar’: The id is appended to the URL /own- /owncloud/index.php/public/calendar Also take a look at externalapi. remote Same as public but requires login. The id is appended to the URL /owncloud/index.php/remote. Example with id set to ‘calendar’: /owncloud/index.php/remote/calendar Also take a look at externalapi. 58 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 documentation link to ‘admin’ and ‘user’ documentation website link to project webpage standalone Can be set to true to indicate that this app is a webapp. This can be used to tell GNOME Web for instance to treat this like a native application. default_enable Core apps only: Used to tell ownCloud to enable them after the installation. shipped Core apps only: Used to tell ownCloud that the app is in the standard release. Please note that if this attribute is set to FALSE or not set at all, every time you disable the application, all the files of the application itself will be REMOVED from the server! 3.4.3 Classloader The classloader is provided by ownCloud and loads all your classes automatically. The only thing left to include by yourself are 3rdparty libraries. Note that this means that the classes need to be named and organized in folders according to their full qualifier. The classloader works like this: • Take the full qualifier of a class: \OCA\AppTemplateAdvanced\Db\ItemMapper • If it starts with \OCA include file from the apps directory • Cut off \OCA: \AppTemplateAdvanced\Db\ItemMapper • Convert all charactes to lowercase: \apptemplateadvanced\db\itemmapper • Replace \ with /: /apptemplateadvanced/db/itemmapper • Append .php: /apptemplateadvanced/db/itemmapper.php • Prepend /apps because of the OCA namespace and include the file: 3.4. App Development (App Framework) 59 ownCloud Developer Manual, Release 6.0 require ’/apps/apptemplateadvanced/db/itemmapper.php’; Remember: for it to be autoloaded, the itemmapper.php needs to either be stored in the /apps/apptemplateadvanced/db/ folder, or adjust its namespace according to the folder it’s stored in. 3.4.4 Runtime configuration The App Framework assembles the application by using an Inversion of Control container which does Dependency Injection. Dependency Injection helps you to create testable code. For a very simple and good Tutorial, watch the Dependency Injection and the art of Services and Containers Tutorial on YouTube. A broader overview over how it works and what the benefits are can be seen on Google’s Clean Code Talks. The container is configured in dependencyinjection/dicontainer.php. By default Pimple is used as dependency injection container. A tutorial can be found here To add your own classes simply open the dependencyinjection/dicontainer.php and add a line like this to the constructor: <?php class DIContainer extends OCA\AppFramework\DependencyInjection\DIContainer { public function __construct(){ // tell parent container about the app name parent::__construct(’myapp’); $this[’MyClass’] = function($c){ return new MyClass($c[’SomeOtherClass’]); }; } } You can also inject and overwrite already existing items from the App Framework. The App Framework lets you inject/overwrite the following items: • API: The API layer. Overwrite this if you use an API layer that inherited from the App Framework API layer and provides additional methods. • Request: The Request object which holds the $_POST, $_GET, etc. variables • TwigTemplateDirectory: If set to the template directory, Twig templates can be used. • TwigTemplateCacheDirectory: Set this to enable caching for Twig templates • MiddlewareDispatcher: Can be used to add aditional middleware 3.4.5 API abstraction layer ownCloud currently has a ton of static methods which is a very bad thing concerning testability. Therefore the App Framework comes with an OCA\AppFramework\Core\API abstraction layer (basically a facade) which wraps the static method calls inside an object. Note: This is a temporary solution until ownCloud offers a proper API with normal classes that can be used in the DIContainer. This will allow you to easily mock the API in your unittests. 60 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Extend the API If you find yourself in need to use more ownCloud internal static methods simply inherit from the API class: core/api.php <?php namespace MyApp\Core; class API extends \OCA\AppFramework\Core\API { public function __construct($appName){ parent::__construct($appName); } public function methodName($someParam){ \OCP\Util::methodName($this->appName, $someParam); } } and wire it up in the container: dependencyinjection/dicontainer.php <?php use \OCA\MyApp\Core\API; class DIContainer extends OCA\AppFramework\DependencyInjection\DIContainer { public function __construct(){ // tell parent container about the app name parent::__construct(’myapp’); $this[’API’] = $this->share(function($c){ return new API($c[’AppName’]); }); } } ?> 3.4.6 Routes PHP usually treats the URL like a filepath. This is easy for beginners but gets more complicated if a good architecture is required. For instance if an URL should call a certain function/method or if values should be extracted from the URL. Routing connects your URLs with your controller methods and allows you to create constant and nice URLs. Its also easy to extract values from the URLs. ownCloud uses Symphony Routing Routes are declared in appinfo/routes.php A simple route would look like this: 3.4. App Development (App Framework) 61 ownCloud Developer Manual, Release 6.0 <?php // this route matches /index.php/yourapp/myurl/SOMEVALUE $this->create(’yourappname_routename’, ’/myurl/{key}’)->action( function($params){ require __DIR__ . ’/../index.php’; } ); The first argument is the name of your route. This is used as an identifier to get the URL of the route and is a nice way to generate the URL in your templates or JavaScript for certain links since it does not force you to hardcode your URLs. Note: The identifier should always start with the appid since they are global and you could overwrite a route of a different app The second parameter is the URL which should be matched. You can extract values from the URL by using {key} in the section that you want to get. That value is then available under $params[’key’], for the above example it would be $params[’key’]. You can omit the parameter if you dont extract any values from the URL at all. If a default value should be used for an URL parameter, it can be set via the defaults method: <?php $this->create(’yourappname_routename’, ’/myurl/{key}’)->action( function($params){ require __DIR__ . ’/../index.php’; } )->defaults(’key’ => ’john’); The action method allows you to register a callback which gets called if the route is matched. You can use this to call a controller or simply include a PHP file. Using routes in templates and JavaScript To use routes in OC_Template, use: <? print_unescaped(\OCP\Util::linkToRoute( ’yourappname_routename’, array(’key’ => 1))); In JavaScript you can get the URL for a route like this: var params = {key: 1}; var url = OC.Router.generate(’yourappname_routename’, params); console.log(url); // prints /index.php//yourappname/myurl/1 Note: Be sure to only use the routes generator after the routes are loaded. This can be done by registering a callback with OC.Router.registerLoadedCallback(callback) Using Controllers To call your controllers the App Framework provides a main method: OCA\AppFramework\App. 62 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Note: If you call a controller directly no security checks will be performed! Security checks are handled by the OCA\AppFramework\Middleware\Security\SecurityMiddleware and called inside the OCA\AppFramework\App::main method! Always use the OCA\AppFramework\App::main method! <?php use \OCA\AppFramework\App; use \OCA\YourApp\DependencyInjection\DIContainer; $this->create(’yourappname_routename’, ’/myurl/{key}’)->action( function($params){ App::main(’MyController’, ’methodName’, $params, new DIContainer()); } )->defaults(’key’ => ’john’); The first parameter is the name under dependencyinjection/dicontainer.php. which the controller was defined in the The second parameter is the name of the method that should be called on the controller. The third parameter is the $params array which is passed to the controller and available by using $this->params($key) in the controller method. In the following example, the parameter in the URL would be accessible by using: $this>params(‘key’) You can also limit the route to GET or POST requests by simply adding ->post() or ->get() before the action method like: <?php use \OCA\AppFramework\App; use \OCA\YourApp\DependencyInjection\DIContainer; $this->create(’yourappname_routename’, ’/myurl/{key}’)->post()->action( function($params){ App::main(’MyController’, ’methodName’, $params, new DIContainer()); } ); ?> The fourth parameter is an instance of the DIContainer (see Dependency Injection). If you want to replace objects in the container only for a certain request, you can do it like this: <?php use \OCA\AppFramework\App; use \OCA\YourApp\DependencyInjection\DIContainer; $this->create(’yourappname_routename’, ’/myurl/{key}’)->post()->action( function($params){ $container = new DIContainer(); $container[’SomeClass’] = function($c){ return new SomeClass(’different’); } App::main(’MyController’, ’methodName’, $params, $container); } ); ?> Twig The Twig templates also provide a function to create a link from a route url() function: 3.4. App Development (App Framework) 63 ownCloud Developer Manual, Release 6.0 {{ url(’yourappname_routename’, {key: ’1’}) }} 3.4.7 Controllers The App Framework provides a simple baseclass for adding controllers: OCA\AppFramework\Controller\Controller. Controllers connect your view (templates) with your database and contain the logic of your app. Controllers themselves are connected to one or more routes. Controllers go into the controller/ directory. A controller should be created for each resource. Think of it as an URL scheme: /controller/method/params For instance: /file/1 In this case we would create a controller named FileController and the method would be called get(). A simple controller would look like: <?php namespace OCA\YourApp\Controller; use \OCA\AppFramework\Controller\Controller; use \OCA\AppFramework\Http\JSONResponse; class MyController extends Controller { /** * @param Request $request an instance of the request * @param API $api an api wrapper instance */ public function __construct($api, $request){ parent::__construct($api, $request); } /** * @Ajax * * sets a global system value */ public function myControllerMethod(){ return new JSONResponse(array(’value’ => $this->params(’somesetting’))); } } ?> An instance of the API is passed via Dependency Injection, the same goes for a OCA\AppFramework\Http\Request instance. URL Parameters, POST, GET and FILES parameters are partly abstracted by the Request class and can be accessed via $this->params(‘myURLParamOrPostOrGetKey’) and $this->getUploadedFile($key) inside the controller. This has been done to make the app better testable. 64 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 If you want to access environment variables($_ENV), use $this->env($key). Session and cookie variables can be accessed via session and cookie methods. For example, to get a value of a variable from cookie: $this>cookie($key_name). Not like cookie, which is read only, session variables can also be changed using: $this>session($key_name, $value_name). Every controller method has to return a Response object. The currently available Responses from the App Framework include: • OCA\AppFramework\Http\Response: response for sending headers only • OCA\AppFramework\Http\JSONResponse: sends JSON to the client • OCA\AppFramework\Http\TemplateResponse: renders a template • OCA\AppFramework\Http\RedirectResponse: redirects to a new URL • OCA\AppFramework\Http\TextDownloadResponse: prompts the user to download a text file containing a passed string • OCA\AppFramework\Http\TextResponse: for printing text like XML New in version 6.0. • OCA\AppFramework\Http\ForbiddenResponse: returns 403 Forbidden HTTP status. If you want to transport content, use a different response and set the HTTP status code in there • OCA\AppFramework\Http\NotFoundResponse: returns 404 Not Found HTTP status. If you want to transport content, use a different response and set the HTTP status code in there Should you require to set additional headers, you can use the OCA\AppFramework\Http\Response::addHeader method that every Response has. Because TemplateResponse is quite common, the controller provides a shortcut method for both of them, namely $this->render: <? /** * @CSRFExemption */ public function index(){ $templateName = ’main’; $params = array( ’somesetting’ => ’How long will it take’ ); return $this->render($templateName, $params); } For security reasons, all security checks for controller methods are turned on by default. To explicitely turn off checks, you must use exemption annotations above the desired method. In this example, all security checks would be disabled (not recommended): <?php /** * @CSRFExemption * @IsAdminExemption * @IsLoggedInExemption * @IsSubAdminExemption */ public function index(){ $templateName = ’main’; 3.4. App Development (App Framework) 65 ownCloud Developer Manual, Release 6.0 $params = array( ’somesetting’ => ’How long will it take’ ); return $this->render($templateName, $params); } Possible Annotations contain: • @CSRFExemption: Turns off the check for the CSRF token. Only use this for the index page! • @IsAdminExemption: Turns off the check if the user is an admin • @IsLoggedInExemption: Turns off the check if the user is logged in • @IsSubAdminExemption: Turns off the check if the user is a subadmin • @Ajax: Use this for Ajax Requests. It prevents the unneeded rendering of the apps navigation and returns error messages in JSON format Don’t forget to add your controller dependencyinjection/dicontainer.php to the dependency injection container in <?php // in the constructor function $this[’MyController’] = function($c){ return new MyController($c[’API’], $c[’Request’]); }; ?> 3.4.8 Database Schema ownCloud uses a database abstraction layer on top of either MDB2 or PDO, depending on the availability of PDO on the server. The database schema is inside appinfo/database.xml in MDB2’s XML scheme notation where the placeholders *dbprefix* (*PREFIX* in your SQL) and *dbname* can be used for the configured database table prefix and database name. An example database XML file would look like this: <?xml version="1.0" encoding="ISO-8859-1" ?> <database> <name>*dbname*</name> <create>true</create> <overwrite>false</overwrite> <charset>utf8</charset> <table> <name>*dbprefix*yourapp_items</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <length>4</length> 66 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 </field> <field> <name>user</name> <type>text</type> <notnull>true</notnull> <length>64</length> </field> <field> <name>name</name> <type>text</type> <notnull>true</notnull> <length>100</length> </field> <field> <name>path</name> <type>clob</type> <notnull>true</notnull> </field> </declaration> </table> </database> To update the tables used by the app, simply adjust the database.xml file and increase the app version number in appinfo/version to trigger an update. 3.4.9 Database Access Your database layer should go into the db/ folder. It’s recommended to split the data entities from the database queries. You can do that by creating a very simple PHP object which extends the class OCA\AppFramework\Db\Entity. This object will hold the data. Getters and setters will automatically be created for all public attributes. Should it be required to use special setter or getters, simply create the method. Make sure to mark the field as updated by calling the parent method. Note: Setters mark a field as updated. The OCA\AppFramework\Db\Entity::fromRow method should only be used to pass in rows from a database result. Fields will not be marked as updated by using OCA\AppFramework\Db\Entity::fromRow! OCA\AppFramework\Db\Entity::fromRow maps the database columns to attributes. The conversion works like the this: • database column my_name gets transformed to attribute myName. • the attribute myAwesomeProperty gets transformed to the database column my_awesome_property Changed in version 6.0: Instead of manually creating and mapping all the entities, this is is now done by extending a parent class. db/item.php <?php namespace \OCA\YourApp\Db; use \OCA\AppFramework\Db\Entity; class Item extends Entity { // Note: a field id is set automatically by the parent class public $name; 3.4. App Development (App Framework) 67 ownCloud Developer Manual, Release 6.0 public $path; public $user; public $timestamp; public function __construct(){ // cast timestamp to an int when fromRow is being called // the second parameter is the argument that is passed to // the php function settype() $this->addType(’timestamp’, ’int’); } // transform username to lower case public function setName($name){ $name = strtolower($name); parent::setName($name); } } All database queries for that object should be put into a mapper class. This follows the data mapper pattern. Simply inherit the OCA\AppFramework\Db\Mapper. Changed in version 6.0:: Methods from the old mapper have been removed and deprecated to allow a small ORM. db/itemmapper.php <?php namespace \OCA\YourApp\Db; use \OCA\AppFramework\Db\Mapper; class ItemMapper extends Mapper { public function __construct(API $api) { parent::__construct($api, ’news_feeds’); // tablename is news_feeds } public function find($id, $userId){ $sql = ’SELECT * FROM ‘’ . $this->getTableName() . ’‘ ’ . ’WHERE ‘id‘ = ? ’ . ’AND ‘user_id‘ = ?’; // use findOneQuery to throw exceptions when no entry or more than one // entries were found $row = $this->findOneQuery($sql, array($id, $userId)); $feed = new Item(); $feed->fromRow($row); return $feed; } public function findByName($name){ $sql = ’SELECT * FROM ‘’ . $this->getTableName() . ’‘ ’ . ’WHERE ‘name‘ = ? ’; $row = $this->execute($sql, array($name)); 68 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 $feed = new Item(); $feed->fromRow($row); return $feed; } } Note: Always use ? to mark placeholders for arguments in SQL queries and pass the arguments as a second parameter to the execute function to prevent SQL Injection DONT: <?php $sql = ’SELECT * FROM ‘’ . $this->getTableName() . ’‘ WHERE ‘user‘ = ’ . $user; $result = $this->execute($sql); DO: <?php $sql = ’SELECT * FROM ‘’ . $this->getTableName() . ’‘ WHERE ‘user‘ = ?’; $params = array($userId); $result = $this->execute($sql, $params); The mapper class comes with simple methods for deleting, updating and finding items. To delete a database entry, simply pass an entity with an id to the OCA\AppFramework\Db\Mapper::delete method. Example: <?php // delete the item with id 4 $item = new Item(); $item->setId(4); $mapper = new ItemMapper($api); // inject API class for db access $mapper->delete($item); The same works for updating. Only the fields which have been set with setters will be updated. Example: <?php // change the name of item with id 4 $item = new Item(); $item->setId(4); $item->setName(’tony’); $mapper = new ItemMapper($api); // inject API class for db access $mapper->update($item); 3.4.10 Templates ownCloud provides its own templating system. 3.4. App Development (App Framework) 69 ownCloud Developer Manual, Release 6.0 In every template file you can easily access the template functions listed in OC Templates. To access the assigned variables in the template, use the $_[] array. The variable will be availabe under the key that you defined (e.g. $_[’key’]). templates/main.php <?php foreach($_[’entries’] as $entry){ ?> <p><?php p($entry); ?></p> <?php } print_unescaped($this->inc(’sub.inc’)); ?> Warning: Changed in version 5.0. To prevent XSS the following PHP functions for printing are forbidden: echo, print() and <?=. Instead use the p() function for printing your values. Should you require unescaped printing, double check for XSS and use: print_unescaped. Templates can also include other templates by using the $this->inc(‘templateName’) method. Use this if you find yourself repeating a lot of the same HTML constructs. The parent variables will also be available in the included templates, but should you require it, you can also pass new variables to it by using the second optional parameter as array for $this->inc. templates/sub.inc.php <div>I am included but i can still access the parents variables!</div> <?php p($_[’name’]); ?> <?php print_unescaped($this->inc(’other_template’, array(’variable’ => ’value’))); ?> For more info, see OC Templates Templates are abstracted by the TemplateResponse object and used and returned side the controller method. Variables can be assigned to the Template by using OCA\AppFramework\Http\TemplateResponse::setParams method: inthe controllers/yourcontroller.php <?php use \OCA\AppFramework\Http\TemplateResponse; // inside the controller public function index(){ // main is the template name. Owncloud will look for template/main.php $response = new TemplateResponse($this->api, ’main’); $params = array(’entries’ => array(’this’, ’is’, ’your’, ’father’, ’speaking’)); $response->setParams($params); return $response; } The App Framework also provides the option of using Twig Templates which can optionally be enabled. Templates reside in the templates/ folder. 70 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Twig Templates (recommended) ownCloud templates do a bad job at preventing XSS. Therefore the App Framework comes with a second option: the Twig Templating Language. Twig Templates are enabled by using the Twig Middleware. If a Twig template directory is set in the dependencyinjection/dicontainer.php, the middleware gets loaded automatically. If no directory is set, theres no additional overhead. To enable them in the dependencyinjection/dicontainer.php, add the following line to the constructor: <?php // in the constructor // use this to specify the template directory $this[’TwigTemplateDirectory’] = __DIR__ . ’/../templates’; Twig can also cache templates as simple PHP files. To make use of this, create a cache/ directory in your app and add the following line to the dependencyinjection/dicontainer.php: <?php // in the constructor // if you want to cache the template directory, add this path $this[’TwigTemplateCacheDirectory’] = __DIR__ . ’/../cache’; A full reference can be found on the Twig Template Reference. If you want to use Twig together with AngularJS the variable print characters {{}} of Angular will have to be adjusted. You can do that by setting a different $interpolateProvider in the coffee/app.coffee config section: app.config([’$interpolateProvider’, function($interpolateProvider) { $interpolateProvider.startSymbol(’[[’); $interpolateProvider.endSymbol(’]]’); }]); After adding the above lines, Angular will use [[]] for evaluation variables. Additional Twig Extensions The App Framework comes with additional template functions for Twig to better integrate with ownCloud. The following additional functions are provided: url(route, params=null) Arguments • route (string) – the name of the route • params (string) – the params written like a JavaScript object Prints the URL for a route. An example would be: {{ url(’yourapp_route_name’, {value: ’hi’}) }} abs_url(route, params=null) Arguments 3.4. App Development (App Framework) 71 ownCloud Developer Manual, Release 6.0 • route (string) – the name of the route • params (string) – the params written like a JavaScript object Same as url() but prints an absolute URL An example would be: {{ abs_url(’yourapp_route_name’, {value: ’hi’}) }} trans(toTranslate, params=null) Arguments • toTranslate (string) – the string which should be translated • params (string) – the params that should be replaced in the string Enables translation in the templates An example would be: {{ trans(’Translate %s %s’, ’this’, ’and this’) }} script(path, appName=null) Arguments • path (string) – path to the JavaScript file in the js/ folder in the app. The .js extension is automatically added. • appName (string) – name of the app. If no value is given, the current app will be used. New in version 6.0. Adds a JavaScript file inside the template An example would be: // to include the js/public/app.js in your app use {{ script(’public/app’) }} style(path, appName=null) Arguments • path (string) – path to the CSS file in the css/ folder in the app. The .css extension is automatically added. • appName (string) – name of the app. If no value is given, the current app will be used. New in version 6.0. Adds a CSS file inside the template An example would be: // to include the css/style.css in your app use {{ style(’style’) }} image_path(path, appName=null) Arguments • path (string) – path to an image file in the img/ folder in the app. • appName (string) – name of the app. If no value is given, the current app will be used. New in version 6.0. Returns the link to an image An example would be: 72 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 // to include the img/icon.png in your app use <img src="{{ image_path(’icon.png’) }}" /> link_to(path, appName=null) Arguments • path (string) – path to a file • appName (string) – name of the app. If no value is given, the current app will be used. New in version 6.0. Returns the link to a file An example would be: // to include the files/my.pdf in your app use <a href="{{ link_to(’files/my.pdf’) }}">my pdf</a> 3.4.11 Static content Static content consists of: • img/: all images • js/: all JavaScript files • css/: all CSS files Because ownCloud templates do not support template inheritance it is not possible to add your own script tags. Therefore ownCloud provides the methods \OCPUtil::addScript(‘your_app_id’, ‘script’) and \OCPUtil::addStyle(‘your_app_id’, ‘style’). The first parameter is the app’s id, the second one is the path relative to the js/ respectively the css/ folder without the file extension. If you use Twig Templates, there is the script and style function, see Templates. CSS and JavaScript are compressed by ownCloud so if the CSS or JavaScript do not seem to get updated, check if the debug mode is enabled. To enable it see Enabling debug mode 3.4.12 CSS Stylesheets have to be included in your templates, see Static content If you have to include an image or css file in your CSS, prepend the following to your path: • %appswebroot%: gets the absolute path to your app • %webroot%: gets the absolute path to owncloud For example: .folder > .title { background-image: url(’%webroot%/core/img/places/folder.svg’); } CSS for apps ownCloud comes with special CSS rules for apps to make app development easier. Todo 3.4. App Development (App Framework) 73 ownCloud Developer Manual, Release 6.0 document this Formfactors ownCloud automatically detects what kind of form factor you are using. Currently supported are: • mobile: works well on mobiles • tablet: optimized for devices like iPads or Android Tablets • standalone: mode where only the content of an App is shown. The header, footer and side navigation is not visible. This is useful if ownCloud is embedded in other applications. The auto detection can be overwritten by using the “formfactor” GET variable in the url: index.php/myapp?formfactor=mobile If you want to provide a different stylesheet or javascript file for mobile devices just suffix the formfactor in the filename, like: style.mobile.css or: script.tablet.css 3.4.13 Angular Setup The following tools are recommended when including AngularJS into the app. Recommended layout If AngularJS should be used for the app, the following files layout is recommended. If CoffeeScript is used, use the same layout only with the .coffee extension instead of the .js extension. The main logic goes into: • js/app/app.js: The main file where the module is being initiated • js/app/directives/: folder for directives • js/app/controllers/: folder for controllers • js/app/filters/: folder for filters • js/app/services/: folder for services Tests go into: • js/tests/stubs/app.js: Use this for initializing the container for tests • js/tests/directives/: folder for directive tests • js/tests/controllers/: folder for controller tests • js/tests/filters/: folder for filter tests • js/tests/services/: folder for service tests 74 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • js/tests/vendor/: folder for js libs that are only included in the tests for instance jquery Configuration files go into: • js/config/testacular_conf.js: Testacular (used for unittests) configuration The compiled files go into: • js/public/app.js: The compiled JavaScript Additional JavaScript libs that are used in the app go into: • js/vendor/ Build The app will likely depend on various node.js tools for building the JavaScript/CoffeeScript. To make setup and development easy it is recommended to provide a package.json inside the js/ directory. This example contains additional dependencies for CoffeeScript. Adjust this to your needs. js/package.json { "name": "owncloud-appframework", "description": "ownCloud App Framework", "version": "0.0.1", "author": { "name": "Bernhard Posselt", "email": "[email protected]" }, "private": true, "homepage": "https://github.com/owncloud/apps/tree/appframework-js/appframework/", "repository": { "type": "git", "url": "[email protected]:owncloud/apps.git" }, "bugs": "https://github.com/owncloud/apps/issues", "contributors": [], "dependencies": {}, "devDependencies": { "grunt": "~0.4.0", "grunt-cli": "~0.1.6", "coffee-script": "~1.4.0", "grunt-contrib-concat": "~0.1.2", "grunt-contrib-watch": "~0.2.0", "grunt-coffeelint": "0.0.6", "grunt-wrap": "~0.2.0", "phantomjs": "~1.8.1-3", "grunt-phpunit": "0.2.0", "gruntacular": "~0.3.0" }, "engine": "node >= 0.8" } To build the JavaScript/CoffeeScript a buildsystem like Grunt is recommended. To get a good overview watch the Tutorial video with Ben Alman. The configfile for Grunt should be placed in the js/ directory and can either contain CoffeeScript or JavaScript. An example for a CoffeeScript configuration would be: 3.4. App Development (App Framework) 75 ownCloud Developer Manual, Release 6.0 js/Gruntfile.coffee module.exports = (grunt) -> grunt.loadNpmTasks(’grunt-contrib-concat’) grunt.loadNpmTasks(’grunt-contrib-watch’) grunt.loadNpmTasks(’grunt-coffeelint’) grunt.loadNpmTasks(’grunt-wrap’); grunt.loadNpmTasks(’grunt-phpunit’); grunt.loadNpmTasks(’gruntacular’); grunt.initConfig meta: pkg: grunt.file.readJSON(’package.json’) version: ’<%= meta.pkg.version %>’ banner: ’/**\n’ + ’ * <%= meta.pkg.description %> - v<%= meta.version %>\n’ + ’ *\n’ + ’ * Copyright (c) <%= grunt.template.today("yyyy") %> - ’ + ’<%= meta.pkg.author.name %> <<%= meta.pkg.author.email %>>\n’ + ’ *\n’ + ’ * This file is licensed under the Affero General Public License version 3 or later.\n’ + ’ * See the COPYING-README file\n’ + ’ *\n’ + ’ */\n\n’ build: ’build/’ production: ’public/’ concat: app: options: banner: ’<%= meta.banner %>\n’ stripBanners: options: ’block’ src: [ ’<%= meta.build %>app/app.js’ ’<%= meta.build %>app/directives/*.js’ ’<%= meta.build %>app/services/**/*.js’ ] dest: ’<%= meta.production %>app.js’ wrap: app: src: ’<%= meta.production %>app.js’ dest: ’’ # adjust this to include more top level js libs wrapper: [ ’(function(angular, $, undefined){\n\n’ ’\n})(window.angular, jQuery);’ ] coffeelint: app: [ ’app/**/*.coffee’ ’tests/**/*.coffee’ ] options: ’no_tabs’: ’level’: ’ignore’ 76 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 ’indentation’: ’level’: ’ignore’ ’no_trailing_whitespace’: ’level’: ’warn’ watch: concat: files: ’<%= ’<%= ] tasks: phpunit: files: tasks: [ meta.build %>app/**/*.js’ meta.build %>tests/**/*.js’ ’compile’ ’../**/*.php’ [’phpunit’] testacular: unit: configFile: ’config/testacular.conf.js’ continuous: configFile: ’config/testacular.conf.js’ singleRun: true browsers: [’PhantomJS’] reporters: [’progress’, ’junit’] junitReporter: outputFile: ’test-results.xml’ phpunit: classes: dir: ’../tests’ options: colors: true grunt.registerTask(’run’, [’watch:concat’]) grunt.registerTask(’compile’, [’concat’, ’wrap’, ’coffeelint’]) grunt.registerTask(’ci’, [’testacular:continuous’]) grunt.registerTask(’testphp’, [’watch:phpunit’]) If no CoffeeScript is being used, coffeelint should be replaced with jshint and jslint. To give people a well known environment a Makefile is recommended to start the various tasks: firefox_bin=/usr/bin/firefox chrome_bin=/usr/bin/chromium coffee=$(CURDIR)/node_modules/coffee-script/bin/coffee grunt=$(CURDIR)/node_modules/grunt-cli/bin/grunt phantomjs=$(CURDIR)/node_modules/phantomjs/bin/phantomjs all: compile deps: cd $(CURDIR)/ npm install --deps watch: compile $(coffee) --compile --watch --output $(CURDIR)/build/app $(CURDIR)/app/ & \ $(coffee) --compile --watch --output $(CURDIR)/build/tests $(CURDIR)/tests/ & \ $(grunt) --config $(CURDIR)/Gruntfile.coffee run 3.4. App Development (App Framework) 77 ownCloud Developer Manual, Release 6.0 testacular: deps export CHROME_BIN=$(chrome_bin) && export FIREFOX_BIN=$(firefox_bin) && \ $(grunt) --config $(CURDIR)/Gruntfile.coffee testacular:unit phpunit: deps $(grunt) --config $(CURDIR)/Gruntfile.coffee testphp compile: deps mkdir -p $(CURDIR)/build/app mkdir -p $(CURDIR)/build/tests mkdir -p $(CURDIR)/public $(coffee) --compile --output $(CURDIR)/build/app $(CURDIR)/app/ $(coffee) --compile --output $(CURDIR)/build/tests $(CURDIR)/tests/ $(grunt) --config $(CURDIR)/Gruntfile.coffee compile test: deps compile export PHANTOMJS_BIN=$(phantomjs) && \ $(grunt) --config $(CURDIR)/Gruntfile.coffee ci clean: rm -rf $(CURDIR)/build rm -rf $(CURDIR)/test-results.xml The above makefile can be used to watch and compile the changes with: make watch The unittests can be automatically run on change in a second terminal window: make testacular Set up Testacular Testacular is able to run unittests when a JavaScript file changes. On the continues integration server these tests can be run with PhantomJS (or if a graphical environment is installed also with other browsers). A JUnit compatible testresult can be configured. Note: The config values can be overwritten in the Gruntfile An example file would look like: js/config/testacular_conf.js // base path, that will be used to resolve files and exclude // since this is in the config/ folder we have to go one directory higher basePath = ’../’; // list of files / patterns to load in the browser files = [ // your favorite test library, needs to have an adapter JASMINE, JASMINE_ADAPTER, 78 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 // commonly included libraries that are provided by owncloud need to be // loaded because we dont have access to those in the test environment ’tests/vendor/jquery-1.9.1/jquery-1.9.1.js’, ’tests/vendor/jquery-ui-1.10.0/jquery-ui-1.10.0.custom.js’, ’tests/vendor/angular-1.0.4/angular.js’, ’tests/vendor/angular-1.0.4/angular-mocks.js’, // you want to use the ngMocks container thats why you have to redefine the // main js file ’tests/stubs/app.js’, // these are your js and testfiles that you want to use ’build/app/directives/*.js’, ’build/app/filters/*.js’, ’build/app/services/**/*.js’, ’build/tests/**/*Spec.js’ ]; // list of files to exclude // reason: see the files array exclude = [ ’build/app/app.js’ ]; // test results reporter to use // possible values: ’dots’, ’progress’, ’junit’ reporters = [’progress’]; // web server port port = 8080; // cli runner port runnerPort = 9100; // enable / disable colors in the output (reporters and logs) colors = true; // level of logging // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG logLevel = LOG_INFO; // enable / disable watching file and executing tests whenever any file changes autoWatch = true; // Start these browsers, currently available: // - Chrome // - ChromeCanary // - Firefox // - Opera // - Safari (only Mac) // - PhantomJS // - IE (only Windows) browsers = [’Chrome’]; 3.4. App Development (App Framework) 79 ownCloud Developer Manual, Release 6.0 // If browser does not capture in given timeout [ms], kill it captureTimeout = 5000; // Continuous Integration mode // if true, it capture browsers, run tests and exit singleRun = false; 3.4.14 Angular App Framework Libraries New in version 6.0. The App Framework comes with additional libraries which help to interact with the ownCloud server. Include script files To make use of the the tools include them in your templates. Using ownCloud Templates: templates/main.php <?php \OCP\Util::addScript(’appframework’, ’public/app’); ?> Using Twig Templates: templates/main.php {{ script(’public/app’, ’appframework’) }} After the script has been included the modules can be used inside the angular module by injecting them: js/app/app.coffee # create your application and inject OC angular.module(’YourApp’, [’OC’]) General Now that the library is loaded and set up they can be used inside the module container. The App Framework follows the convention of marking class names with a leading underscore and object instances without it. Note: The App Framework uses CoffeeScript so if JavaScript objects should extend the provided objects, you need to implement the inheritance like CoffeeScript does it. Services The App Framework provides the following services: Router The OC.Router object 80 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Notification The OC.Notification object Utils The OC object _Loading Simple object that can be used for displaying loading notifcations. It has an internal counter that starts at 0 and can be increased and decreased. If the counter is bigger than 0 isLoading is true. Example: loading = new Loading() loading.isLoading() # false loading.increase() loading.isLoading() # true loading.increase() loading.getCount() # 2 loading.decrease() loading.decrease() loading.isLoading() # false _Request Used to perform AJAX requests. Example: // simple GET request req = new _Request($http, new _Publisher(), Router) req.get(’mail_index’) class _Request($http http, _Publisher publisher, Router router) request(route[, data ]) Arguments • route (object) – The name of the route that should be used • data (object) – an object containing optional parameters Creates an AJAX request. The following data attributes can be set: •routeParams: object with parameters for the route •data: ajax data objec which is passed to PHP •onSuccess: callback for successful requests •onFailure: callback for failed requests 3.4. App Development (App Framework) 81 ownCloud Developer Manual, Release 6.0 •config: a config which should be passed to $http get(route[, data ]) Arguments • route (object) – The name of the route that should be used • data (object) – an object containing optional parameters Shortcut for doing a GET request, for data attributes see _Request.request() post(route[, data ]) Arguments • route (object) – The name of the route that should be used • data (object) – an object containing optional parameters Shortcut for doing a POST request, for data attributes see _Request.request() put(route[, data ]) Arguments • route (object) – The name of the route that should be used • data (object) – an object containing optional parameters Shortcut for doing a PUT request, for data attributes see _Request.request() delete(route[, data ]) Arguments • route (object) – The name of the route that should be used • data (object) – an object containing optional parameters Shortcut for doing a DELETE request, for data attributes see _Request.request() _Publisher Used to automatically distribute JSON from AJAX Requests to the models. This is especially effective when you need to query for data and dont want to provide a callback to pass the return value to your models. Example: Passing folders from the server to the client’s FolderModel. <?php /** * @Ajax */ public function getAllFolders(){ // the keys on the first level can be used return $this->renderJSON(array( ’foldersKey’ => array( array(’id’ => 1, ’name’ => ’Books’), array(’id’ => 2, ’name’ => ’Stuff’) ) )); } They key foldersKey can now be registered on the client side by subscribing to it with: 82 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 angular.module(’YourApp’).factory ’Publisher’, [’_Publisher’, ’FolderModel’, (_Publisher, FolderModel) -> publisher = new _Publisher() publisher.subscribeObjectTo(FolderModel, ’foldersKey’) return publisher ] Now everytime you call the getAllFolders controller method the returned JSON will be passed directly to the FolderModel. Internally it works like this: • For each successful request the data JSON array is iterated over • If a key is found that a model subscribed to the data will be passed to its handle() method The default handle() method of the model only adds/updates the new object. To add custom behaviour you can overwrite the method. _Model Used as a model parent class and provides CRUD and caching logic for the JSON data. class _Model() add(data) Arguments • data (object) – The object that should be added Adds a new item. If the item id is already present, it will be updated update(data[, clearCache=true ]) Arguments • data (object) – The object that should be updated • clearCache (boolean) – clears the existing queries cache. Set this to false if the update does not affect the queries on the model to improve performance Updates an existing object by copying the provided attributes to the old one. That means that if you want to update only one field simply pass an object with the correct id and the fields that should be overwritten. Example: // udpate name and email itemModel.update({id: 3, name: ’newName’, email: ’newEmail’}) add(data[, clearCache=true ]) Arguments • data (object) – The object that should be added • clearCache (boolean) – clears the existing queries cache. Set this to false if the update does not affect the queries on the model to improve performance getById(id[, clearCache=true ]) 3.4. App Development (App Framework) 83 ownCloud Developer Manual, Release 6.0 Arguments • id (int) – The id of the object • clearCache (boolean) – clears the existing queries cache. Set this to false if the update does not affect the queries on the model to improve performance Returns object a data object by its id getAll() Returns array an array with all stored objects clear() Deletes all stored data objects size() Returns int the count of all stored data objects get(Query query) Arguments • query (Query) – an instance of a Query class or subclass Returns mixed the returnvalue of the query Runs a query over all stored objects and returns the result which is calculated in the query. This is cached by params and query. The cache is deleted after a new add/update/remove method was called. Queries Because AngularJS getters have to be fast (Angular checks for changed objects after each digest) the App Framework provides cachable queries. The following queries are available: • _BiggerThanQuery • _BiggerThanEqualQuery • _LessThanQuery • _LessThanEqualQuery • _EuqalQuery • _NotEuqalQuery • _ContainsQuery • _DoesNotContainQuery • _MinimumQuery • _MaximumQuery To query an object with a _BiggerThanQuery use its get method: valuesBiggerThan4 = myModel.get(new _BiggerThanQuery(’id’, 4)) This query is cached until a new entry is added, removed or updated. Note: Do not update the objects by hand only. Always use the model’s update method to tell it that a model has changed. Otherwise you run into an invalid cache! 84 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Writing your own queries For more complex queries the _Query object can be extended. Each query object has a hashCode and exec method. The hashCode method is used to generate a unique hash for the query and its arguments so that it can be cached. The built in method works like this: • Take all the arguments values • replace _ with __ in the argument values • Construct the hash by: QUERYNAME_ARG1Value_ARG2Value etc. You can override this method if you need to. The exec method is used to run the query. It receives an array with all objects and returns the filtered content. A query that would select only ids between a range of numbers would look like this: angular.module(’YourApp’).factory ’_LessThanQuery’, [’_Query’, (_Query) -> class RangeQuery extends _Query # @_field is the attribute name of the object constructor: (@_field, @_lowerBound, @_upperBound) -> name = ’range’ super(name, [@_field, @_lowerBound, @_upperBound]) exec: (data) -> filtered = [] for entry in data if entry[@_field] < @_upperBound and entry[@_field] > @_lowerBound filtered.push(entry) return filtered return RangeQuery ] If hashCode is not overwritten it would produce the following output: query = new _RangeQuery(’id’, 3, 6) query.hashCode() # prints range_id_3_6 Directives The App Framework provides the following directives: ocClickSlideToggle Can be used for the settings slideup or to slide up any area and hide it on focus lost. Can be enhanced by passing an expression: { selector: ’#jquery .selector’ hideOnFocusLost: true cssClass: ’opened’ } • selector: if defined, a different area is slid up on click 3.4. App Development (App Framework) 85 ownCloud Developer Manual, Release 6.0 • hideOnFocusLost: if defined, the slid up area will hide when the focus is lost • cssClass: if defined, the class which should be toggled on the element where the directive is bound to Example: <button oc-click-slide-toggle="{selector: ’#settings’, hideOnFocusLost: true}" /> <div id="settings"></div> ocClickFocus Can be used to focus a different element when the element is being clicked that has this directive Must pass an expression: { selector: ’#jquery .selector’, timeout: 300 } • selector: the area that should be focused • timeout: optional, if the focus should be done after a timeout Example: <button oc-click-focus="{selector: ’#settings’}" /> <input id="settings" type="text" /> ocReadFile Can be used to pass the contents of a file input field to a function. The directive binds to the change event of the input. The read content will be assigned to the scope as $fileContent variable and the given function will be called. Example: <input type="file" name="import" oc-read-file="import($fileContent)"/> ocDraggable Shortcut for using jquery-ui draggable. The expression is passed to $.draggable. These two are equivalent: $(’#settings’).draggable({ cursor: "move", cursorAt: { top: 56, left: 56 } }); <div id="settings" oc-draggable="{ cursor: ’move’, cursorAt: { top: 56, left: 56 } }"></div> ocForwardClick Used to forward a click. Useful to trigger a hidden file upload field by clicking a visible button. Needs an expression: 86 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 { selector: ’#jquery .selector’ } • selector: the are where the click needs to redirected to Example: <button oc-forward-click="{selector: ’#upload’}" /> <input type="file" id="upload" /> ocTooltip Binds a bootstrap tooltip on the item. Example: <button oc-tooltip title="tooltip text" /> Filters The App Framework provides the following filters: ocRemoveTags Used to remove certain HTML tags from a string. You can pass in a string or an array of strings which HTML tags which will be removed. Example: <!-- remove all em tags --> <h1>{{ title|ocRemoveTags:’em’ }}</h1> <!-- remove all em,b and i tags --> <h1>{{ title|ocRemoveTags:[’em’, ’i’, ’b’] }}</h1> ocSanitizeURL Used to sanitize an URL to prevent XSS in src and href attributes (e.g. <a href=”javascript:alert(1)”>). Example: <a href="{{ link|ocSanitizeURL }}">My link</a> 3.4.15 Acceptance Tests https://github.com/cucumber/cucumber/wiki/Given-When-Then http://www.elabs.se/blog/15you-re-cuking-it-wrong https://github.com/cucumber/cucumber/wiki/Cucumber-Backgrounder https://github.com/jnicklas/capybara#capybara .. 6.0 replace:: 6.0 3.4. App Development (App Framework) 87 ownCloud Developer Manual, Release 6.0 3.4.16 Unittests The App Framework ships with several useful tools to do unittesting PHP Note: App Unittests should not depend on a running ownCloud instance! They should be able to run in isolation. To achieve that, abstract the ownCloud core functions and static methods in the App Framework core/api.php and use a mock for testing. If a class is not static, you can simply add it in the dependencyinjection/dicontainer.php Note: Also use your app’s namespace in your test classes to avoid possible conflicts when the test is run on the buildserver Unittests go into your tests/ directory. Create the same folder structure in the tests directory like on your app to make it easier to find tests for certain classes. ownCloud uses PHPUnit Because of Dependency Injection, unittesting has become very easy: you can easily substitute complex classes with mocks by simply passing a different object to the constructor. Also using a container like Pimple frees us from doing complex instantiation and object passing in our application by hand. A simple test for a controller would look like this: tests/controllers/ItemControllerTest.php <?php namespace OCA\YourApp; use OCA\AppFramework\Http\Request; use OCA\AppFramework\Db\DoesNotExistException; use OCA\AppFramework\Utility\ControllerTestUtility; require_once(__DIR__ . "/../classloader.php"); class ItemControllerTest extends ControllerTestUtility { public function testSetSystemValue(){ $post = array(’somesetting’ => ’this is a test’); $request = new Request(array(), $post); // create an api mock object $api = $this->getAPIMock(); // expects to be called once with the method // setAppValue(’somesetting’, ’this is a test’) $api->expects($this->once()) ->method(’setAppValue’) ->with( $this->equalTo(’somesetting’), $this->equalTo(’this is a test’)); 88 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 // we want to return the appname yourapp when this method // is being called $api->expects($this->any()) ->method(’getAppName’) ->will($this->returnValue(’yourapp’)); $controller = new ItemController($api, $request, null); $response = $controller->setAppValue(null); // check if the correct parameters of the json response are set $this->assertEquals($post, $response->getParams()); } } You can now execute the test by running this in your app directory: phpunit tests/ Note: PHPUnit executes all PHP Files that end with Test.php. Be sure to consider that in your file naming. New in version 6.0. TDD can also be used if the Angular Setup is performed and grunt is used. To automatically run all PHP unittests on change simply use: cd js/ make phpunit Classloader The generated app has an extra classloader tests/classloader.php that loads the the classes. Require this file at the top of your tests. Note: The classloader in the tests/ directory assumes that the appframework/ folder is in the same directory as the your app. If you run your app in a different apps folder, you will need to link the App Framework into the same folder where your app folder resides. JavaScript New in version 6.0. If the Angular Setup was performed Testacular was already successfully set up and can be started with: cd js/ make testacular Testacular now watches for changes and executes all tests if a JavaScript file is changed. To run the tests once use: cd js/ make test A JUnit compatible result file will be generated for the continous integration server. 3.4. App Development (App Framework) 89 ownCloud Developer Manual, Release 6.0 Like stated in Angular Setup tests go into the folder js/tests/. The default setup uses Jasmine but also other test frameworks like Mocha or QUnit can be used but have to be configured first. AngularJS To include mocks the main container creation has to be overwritten with another file. js/tests/stubs/app.js angular.module(’YourApp’, [’ngMock’]); This file should be included in the testfiles in js/config/testacular_conf.js instead of js/app/app.js Create a testfile for each JavaScript/CoffeeScript file in the tests/ folder. Example: describe ’_Request’, -> # tell inject to ready the app container beforeEach module ’YourApp’ # get _Request and _Publisher from the container beforeEach inject (_Request, _Publisher) => @router = generate: (route, values) -> return ’url’ registerLoadedCallback: (callback) -> callback() @publisher = new _Publisher() @request = _Request it ’should not send requests if not initialized’, => # create a mock http = jasmine.createSpy(’http’) @router.registerLoadedCallback = -> req = new @request(http, @publisher, @router) req.request(’route’) expect(http).not.toHaveBeenCalled() 3.4.17 Middleware Middleware is logic that is run before and after each request and is modelled after Django’s Middleware system. It offers the following hooks: • beforeController: This is executed before a controller method is being executed. This allows you to plug additional checks or logic before that method, like for instance security checks • afterException: This is being run when either the beforeController method or the controller method itself is throwing an exception. The middleware is asked in reverse order to handle the exception and to return a response. If the middleware can’t handle the exception, it throws the exception again • afterController: This is being run after a successful controllermethod call and allows the manipulation of a Response object. The middleware is run in reverse order 90 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • beforeOutput: This is being run after the response object has been rendered and allows the manipulation of the outputted text. The middleware is run in reverse order To generate your own middleware, simply inherit from the Middleware OCA\AppFramework\Middleware\Middleware: and overwrite the methods that you want to use. class <?php use \OCA\AppFramework\Middleware\Middleware; class CensorMiddleware extends Middleware { private $api; /** * @param API $api an instance of the api */ public function __construct($api){ $this->api = $api; } /** * this replaces "fuck" with "****"" in the output */ public function beforeOutput($controller, $methodName, $output){ return str_replace($output, ’fuck’, ’****’); } } To activate the middleware, you have to overwrite the OCA\AppFramework\Middleware\MiddlewareDispatcher: in the DIContainer constructor: Note: If you ship your own middleware, be sure to also enable the existing ones like the SecurityMiddleware if you overwrite the MiddlewareDispatcher in the Dependency Injection Container! If this is forgotten, there will be security issues! <?php // in the constructor $this[’CensorMiddleware’] = function($c){ return new CensorMiddleware($c[’API’]); }; $this[’MiddlewareDispatcher’] = function($c){ $dispatcher = new \OCA\AppFramework\Middleware\MiddlewareDispatcher(); $dispatcher->registerMiddleware($c[’HttpMiddleware’]); $dispatcher->registerMiddleware($c[’SecurityMiddleware’]); $dispatcher->registerMiddleware($c[’CensorMiddleware’]); return $dispatcher; }; Note: The order is important! The middleware that is registered first gets run first in the beforeController method. For all other hooks, the order is being reversed, meaning: if a middleware is registered first, it gets run last. 3.4. App Development (App Framework) 91 ownCloud Developer Manual, Release 6.0 3.4.18 Filesystem ownCloud handling of filesystems is very flexible. A variety of local and remote filesystem types are supported, as well as a variety of hooks and optional features such as encryption and version control. It is important that apps use the correct methods for interacting with files in order to maintain this flexibility. In some cases using PHP’s internal filesystem functions directly will be sufficient, such as unlink() and mkdir(). Most of the time however it is necessary to use one of ownCloud’s filesystem classes. This documentation assumes that you are working with files stored within a user’s directory (as opposed to ownCloud core files), and therefore need to use OC\Files\View. Todo write the rest 3.4.19 Hooks In ownCloud apps, function or methods (event handlers) which are used by the app and called by ownCloud core hooks, are generally stored in apps/appname/lib/hooks.php. Hooks are a way of implementing the observer pattern, and are commonly used by web platform applications to provide clean interfaces to third party applications which need to modify core application functionality. In ownCloud, a hook is a function whose name can be used by developers of plug-ins to ensure that additional code is executed at a precise place during the execution of other parts of ownCloud code. For example, when an ownCloud user is deleted, the ownCloud core hook post_deleteUser is executed. In the calendar app’s appinfo/app.php, this hook is connected to the app’s own event handler deleteUser (user here refers to an ownCloud user; deleteUser deletes all addressbooks for that a given ownCloud user). When post_deleteUser calls the calender app’s deleteUser event handler, it supplies it with an argument, which is an array containing the user ID of the user that has just been deleted. This user ID is then used by the event handler to specify which address books to delete. There are three components to the use of hooks in this example: 1. The ownCloud core hook post_deleteUser, (see what arguments / data it will provide in lib/user.php, where it is defined) 2. The event handler deleteUser, defined in apps/contacts/lib/hooks.php. 3. The connection of the hook to the event handler, in apps/contacts/appinfo/app.php. Hook Terminology • Signal class / emitter class: the class that contains the method which contains the creation of the hook (and a call to the emit() method) e.g. OC_User • Signal / signal name: the name of the hook, e.g. post_deleteUser • Slot class: class housing the event handling method, e.g. OC_Contacts_Hooks • Slot name: event handler method, e.g. deleteUser (function that deletes all contact address books for a user) 92 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Available hooks File: apps/calendar/ajax/events.php, Class: OC_Calendar • Hook: getEvents File: apps/calendar/index.php, Class: OC_Calendar • Hook: getSources File: dav.php, Class: OC_DAV • Hook: initialize File: lib/migrate.php, Class: OC_User • Hook: pre_createUser • Hook: post_createUser File: lib/filecache.php, Class: OC_Filesystem • Hook: post_write • Hook: post_write • Hook: post_delete • Hook: post_write File: lib/user.php, Class: OC_User • Hook: pre_createUser • Hook: post_createUser • Hook: pre_deleteUser • Hook: post_deleteUser • Hook: pre_login • Hook: post_login • Hook: logout • Hook: pre_setPassword • Hook: post_setPassword File: lib/group.php, Class: OC_Group • Hook: pre_createGroup • Hook: post_createGroup • Hook: pre_deleteGroup • Hook: post_deleteGroup • Hook: pre_addToGroup • Hook: post_addToGroup • Hook: pre_removeFromGroup • Hook: post_removeFromGroup 3.4. App Development (App Framework) 93 ownCloud Developer Manual, Release 6.0 3.4.20 Data Migration As of OC4, user migration is supported. To include migration support in your app (which is highly recommended and does not take long) you must provide a appinfo/migrate.php. The function of the migrate.php file is to provide an import and export functions for app data. To assist in this, we set the user id of the user being exported / user being imported in $this->uid. There is also an instance of the OC_Migration_Content class stored in $this->content. The OC_Migration_Content class helps to make importing and exporting data easy for app developers. Export In this function, you must do everything necessary to export a user from the current ownCloud instance, given a user id. For most apps this is just the case of saving a few rows from the database. Database Data To make exporting database data really easy, the class OC_Migration_Content has a method called copyRows() which will save these rows for you given some options. Take a look at the export function for the bookmarks app: <?php function export( ){ OC_Log::write(’migration’,’starting export for bookmarks’,OC_Log::INFO); // migrate two tables $bookmarkOptions = array( ’table’=>’bookmarks’, ’matchcol’=>’user_id’, ’matchval’=>$this->uid, ’idcol’=>’id’ ); $bookmarkIds = $this->content->copyRows( $bookmarkOptions ); $bookmarkTagsOptions = array( ’table’=>’bookmarks_tags’, ’matchcol’=>’bookmark_id’, ’matchval’=>$ids ); $bookmarkTagsIds = $this->content->copyRows( $bookmarkTagsOptions ); // If both returned some ids then they worked if( is_array( $bookmarkIds ) && is_array( $bookmarkTagsIds ) ) { return true; } else { return false; } } The bookmarks app stores all of its data in the database, in two tables: *PREFIX*bookmarks and *PREFIX*bookmarks_tags so to export this, we need to run copyRows() twice. Here is an explanation of the options passed to it: • table: string name of the table to export (without any prefix) • matchcol: (optional) string name of the column that will be matched with the value in matchval (Basically the column used in the WHERE sql query) 94 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • matchval: (optional) the value that will be searched for in the table • idcol: the name of the column that will be returned To export the bookmarks, matchcol is set to the user_id column and matchval is set to the user being exported: $this>content->uid. idcol is set to the id of the bookmark, as we need to retrive the tags associated with the bookmarks for the user being exported. The function will return an array of ids.Next we run the copyRows() method again, this time on the bookmarks_tags table, matching a range of values (as we want to find all tags, related to all bookmarks owned by the exported user).Finally we check that both functions returned arrays which confirms that they were successful and return a boolean value to represent the success of the export. Files If you use files to hold some app data in data/userid/appname/, they will be automatically copied exported for you. Import Import is a little more tricky as we have to take into account data from different versions of your app, and also handle changing primary keys. Here is the import function for the bookmarks app which imports bookmarks and tags: <?php function import(){ switch( $this->appinfo->version ){ default: // All versions of the app have had the same db structure // so all can use the same import function $query = $this->content->prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); $results = $query->execute( array( $this->olduid )); $idmap = array(); while( $row = $results->fetchRow() ){ // Import each bookmark, saving its id into the map $sql = "INSERT INTO *PREFIX*bookmarks" . "(url, title, user_id, public, added, lastmodified)" . " VALUES (?, ?, ?, ?, ?, ?)"; $query = OC_DB::prepare($sql); $query->execute( array( $row[’url’], $row[’title’], $this->uid, $row[’public’], $row[’added’], $row[’lastmodified’] ) ); // Map the id $idmap[$row[’id’]] = OC_DB::insertid(); } // Now tags foreach($idmap as $oldid => $newid){ $sql = "SELECT * FROM bookmarks_tags WHERE user_id LIKE ?"; $query = $this->content->prepare($sql); $results = $query->execute( array( $oldid ) ); while( $row = $data->fetchRow() ){ // Import the tags for this bookmark, using the new bookmark id $sql = "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag)". " VALUES (?, ?)"; $query = OC_DB::prepare($sql); 3.4. App Development (App Framework) 95 ownCloud Developer Manual, Release 6.0 $query->execute( array( $newid, $row[’tag’] ) ); } } // All done! break; } return true; } We start off by using a switch to run different import code for different versions of your app. $this->appinfo->version contains the version string from the appinfo/info.xml of your app. In the case of the bookmarks app the db structure has not changed, so only one version of import code is needed. To import the db data, first we must retrive it from the migration.db. To do this we use the prepare method from OC_Migration_Content, which returns a MDB2 db object. We then cycle through the bookmarks in migration.db and insert them into the owncloud database. The important bit is the idmapping. After inserting a boookmark, The new id of the bookmark is saved in an array, with the key being the old id of the bookmark. This means when inserting the tags, we know what the new id of the bookmark is simply by getting the value of $idmap[’oldid’]. Remember this part of the import code may be a good place to emit some hooks depending on your app. For example the contacts app could emit some hooks to show some contacts have been added. After importing the bookmarks, we must import the tags. It is a very similar process to importing the bookmarks, except we have to take into account the changes in primary keys. This is done by using a foreach key in the $idmap array, and then inserting the tags using the new id. After all this, we must return a boolean value to indicate the success or failure of the import. Again, app data files stored in data/userid/appname/ will be automatically copied over before the apps import function is executed, this allows you to manipulate the imported files if necessary. Conclusion To fully support user migration for your app you must provide an import and export function under an instance of OC_Migration_Provider and put this code in the file appinfo/migrate.php 3.4.21 AppFramework API App Entry point for every request in your app. You can consider this as your public static void main() method Handles all the dependency injection, controllers and output flow class OCA\AppFramework\App static App::main($controllerName, $methodName, $urlParams, $container) Parameters • $controllerName (string) – the name of the controller under which it is stored in the DI container • $methodName (string) – the method that you want to call • $urlParams (array) – an array with variables extracted from the routes • $container (\Pimple) – an instance of a pimple container. 96 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Shortcut for calling a controller method and printing the result Controller Baseclass to inherit your controllers from class OCA\AppFramework\Controller\Controller •Abstract property $api •Protected None property $request •Protected __construct($api, $request) Parameters • $api (\OCA\AppFramework\Core\API) – an api wrapper instance • $request (\OCA\AppFramework\Http\Request) – an instance of the request params($key, $default=null) Parameters • $key (string) – the key which you want to access in the URL Parameter placeholder, $_POST or $_GET array. The priority how they’re returned is the following: 1. URL parameters 2. POST parameters 3. GET parameters • $default (mixed) – If the key is not found, this value will be returned Returns mixed the content of the array Lets you access post and get parameters by the index getParams() Returns array the array with all parameters Returns all params that were received, be it from the request(as GET or POST) or throuh the URL by the route method() Returns string the method of the request (POST, GET, etc) Returns the method of the request getUploadedFile($key) Parameters • $key (string) – the key that will be taken from the $_FILES array Returns array the file in the $_FILES element Shortcut for accessing an uploaded file through the $_FILES array env($key) Parameters 3.4. App Development (App Framework) 97 ownCloud Developer Manual, Release 6.0 • $key (string) – the key that will be taken from the $_ENV array Returns array the value in the $_ENV element Shortcut for getting env variables session($key) Parameters • $key (string) – the key that will be taken from the $_SESSION array Returns array the value in the $_SESSION element Shortcut for getting session variables cookie($key) Parameters • $key (string) – the key that will be taken from the $_COOKIE array Returns array the value in the $_COOKIE element Shortcut for getting cookie variables render($templateName, $params=array(), $renderAs=’user’, $headers=array()) Parameters • $templateName (string) – the name of the template • $params (array) – the template parameters in key => value structure • $renderAs (string) – user renders a full page, blank only your template admin an entry in the admin settings • $headers (array) – set additional headers in name/value pairs Returns \OCA\AppFramework\Http\TemplateResponse containing the page Shortcut for rendering a template renderJSON($data=array(), $errorMsg=null) Parameters • $data (array) – the PHP array that will be put into the JSON data indexempty by default • $errorMsg (string) – If you want to return an error message, pass one Returns \OCA\AppFramework\Http\JSONResponse containing the values Shortcut for rendering a JSON response API This is used to wrap the owncloud static api calls into an object to make the code better abstractable for use in the dependency injection container Should you find yourself in need for more methods, simply inherit from this class and add your methods class OCA\AppFramework\Core\API __construct($appName) Parameters 98 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $appName (string) – the name of your application constructor getAppName() Returns string the name of your application used to return the appname of the set application addNavigationEntry($entry) Parameters • $entry (array) – containing: id, name, order, icon and href key Creates a new navigation entry getUserId() Returns string the user id of the current user Gets the userid of the current user activateNavigationEntry() Sets the current navigation entry to the currently running app addScript($scriptName, $appName=null) Parameters • $scriptName (string) – the name of the javascript in js/ without the suffix • $appName (string) – the name of the app, defaults to the current one Adds a new javascript file addStyle($styleName, $appName=null) Parameters • $styleName (string) – the name of the css file in css/without the suffix • $appName (string) – the name of the app, defaults to the current one Adds a new css file add3rdPartyScript($name) Parameters • $name (string) – the name of the file without the suffix shorthand for addScript for files in the 3rdparty directory add3rdPartyStyle($name) Parameters • $name (string) – the name of the file without the suffix shorthand for addStyle for files in the 3rdparty directory getSystemValue($key) Parameters • $key (string) – the key of the value, under which it was saved Returns string the saved value 3.4. App Development (App Framework) 99 ownCloud Developer Manual, Release 6.0 Looks up a systemwide defined value setSystemValue($key, $value) Parameters • $key (string) – the key of the value, under which will be saved • $value (string) – the value that should be stored Sets a new systemwide value getAppValue($key, $appName=null) Parameters • $key (string) – the key of the value, under which it was saved • $appName (mixed) – Returns string the saved value Looks up an appwide defined value setAppValue($key, $value, $appName=null) Parameters • $key (string) – the key of the value, under which will be saved • $value (string) – the value that should be stored • $appName (mixed) – Writes a new appwide value setUserValue($key, $value, $userId=null) Parameters • $key (string) – the key under which the value is being stored • $value (string) – the value that you want to store • $userId (string) – the userId of the user that we want to store the value under, defaults to the current one Shortcut for setting a user defined value getUserValue($key, $userId=null) Parameters • $key (string) – the key under which the value is being stored • $userId (string) – the userId of the user that we want to store the value under, defaults to the current one Shortcut for getting a user defined value getTrans() Returns \OC_L10N the translation object Returns the translation object prepareQuery($sql, $limit=null, $offset=null) Parameters • $sql (string) – the sql query with ? placeholder for params 100 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $limit (int) – the maximum number of rows • $offset (int) – from which row we want to start Returns \OCP\DB a query object Used to abstract the owncloud database access away getInsertId($tableName) Parameters • $tableName (string) – the name of the table where we inserted the item Returns int the id of the inserted element Used to get the id of the just inserted element linkToRoute($routeName, $arguments=array()) Parameters • $routeName (string) – the name of the route • $arguments (array) – an array with arguments which will be filled into the url Returns string the url Returns the URL for a route linkTo($file, $appName=null) Parameters • $file (string) – the name of the file • $appName (string) – the name of the app, defaults to the current one Returns an URL for an image or file imagePath($file, $appName=null) Parameters • $file (string) – the name of the file • $appName (string) – the name of the app, defaults to the current one Returns the link to an image, like link to but only with prepending img/ getAbsoluteURL($url) Parameters • $url (string) – the url Returns string the absolute url Makes an URL absolute linkToAbsolute($file, $appName=null) Parameters • $file (string) – the name of the file • $appName (string) – the name of the app, defaults to the current one Returns string the url 3.4. App Development (App Framework) 101 ownCloud Developer Manual, Release 6.0 Warning: DEPRECATED: replaced with linkToRoute() links to a file isLoggedIn() Returns bool true if logged in Checks if the current user is logged in isAdminUser($userId) Parameters • $userId (string) – the id of the user Returns bool true if admin Checks if a user is an admin isSubAdminUser($userId) Parameters • $userId (string) – the id of the user Returns bool true if subadmin Checks if a user is an subadmin passesCSRFCheck() Returns bool true if CSRF check passed Checks if the CSRF check was correct isAppEnabled($appName) Parameters • $appName (string) – the name of an app Returns bool true if app is enabled Checks if an app is enabled log($msg, $level=null) Parameters • $msg (string) – the error message to be logged • $level (int) – the error level Writes a function into the error log getTemplate($templateName, $renderAs=’user’, $appName=null) Parameters • $templateName (string) – the name of the template • $renderAs (string) – how it should be rendered • $appName (string) – the name of the app Returns \OCP\Template a new template Returns a template 102 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 getLocalFilePath($path) Parameters • $path (string) – path the path to the file on the oc filesystem Returns string the filepath in the filesystem turns an owncloud path into a path on the filesystem openEventSource() Returns \OC_EventSource a new open EventSource class used to return and open a new eventsource connectHook($signalClass, $signalName, $slotClass, $slotName) Parameters • $signalClass (string) – class name of emitter • $signalName (string) – name of signal • $slotClass (string) – class name of slot • $slotName (string) – name of slot, in another word, this is the name of the method that will be called when registered signal is emitted. Returns \OCA\AppFramework\Core\bool, always true connects a function to a hook emitHook($signalClass, $signalName, $params=array()) Parameters • $signalClass (string) – class name of emitter • $signalName (string) – name of signal • $params (array) – defautl: array() array with additional data Returns \OCA\AppFramework\Core\bool, true if slots exists or false if not Emits a signal. To get data from the slot use references! clearHook($signalClass=false, $signalName=false) Parameters • $signalClass (string) – • $signalName (string) – clear hooks getUrlContent($url) Parameters • $url (string) – the url that should be fetched Returns string the content of the webpage Gets the content of an URL by using CURL or a fallback if it is notinstalled addRegularTask($className, $methodName) Parameters 3.4. App Development (App Framework) 103 ownCloud Developer Manual, Release 6.0 • $className (string) – full namespace and class name of the class • $methodName (string) – the name of the static method that should becalled Register a backgroundjob task registerAdmin($mainPath, $appName=null) Parameters • $mainPath (string) – the path to the main php file without the phpsuffix, relative to your apps directory! not the template directory • $appName (string) – the name of the app, defaults to the current one Tells ownCloud to include a template in the admin overview DoesNotExistException This is returned or should be returned when a find request does not find an entry in the database class OCA\AppFramework\Db\DoesNotExistException __construct($msg) Parameters • $msg (string) – the error message Constructor MultipleObjectsReturnedException This is returned or should be returned when a find request finds more than one row class OCA\AppFramework\Db\MultipleObjectsReturnedException __construct($msg) Parameters • $msg (string) – the error message Constructor Mapper Simple parent class for inheriting your data access layer from. This class may be subject to change in the future class OCA\AppFramework\Db\Mapper •Abstract __construct($api, $tableName) Parameters • $api (\OCA\AppFramework\Core\API) – Instance of the API abstraction layer • $tableName (string) – the name of the table. set this to allow entity queries without using sql 104 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 getTableName() Returns string the table name delete($entity) Parameters • $entity (\OCA\AppFramework\Db\Entity) – Deletes an entity from the table insert($entity) Parameters • $entity (\OCA\AppFramework\Db\Entity) – Returns \OCA\AppFramework\Db\the saved entity with the set id Creates a new entry in the db from an entity update($entity) Parameters • $entity (\OCA\AppFramework\Db\Entity) – Throws \InvalidArgumentException if entity has no id Updates an entry in the db from an entity findOneQuery($sql, $params) Parameters • $sql (string) – the sql query • $params (array) – the parameters of the sql query Throws \OCA\AppFramework\Db\DoesNotExistException if the item does not exist Returns array the result as row •Protected Returns an db result and throws exceptions when there are more or lessresults execute($sql, $params=array(), $limit=null, $offset=null) Parameters • $sql (string) – the prepare string • $params (array) – the params which should replace the ? in the sql query • $limit (int) – the maximum number of rows • $offset (int) – from which row we want to start Returns \PDOStatement the database query result •Protected Runs an sql query 3.4. App Development (App Framework) 105 ownCloud Developer Manual, Release 6.0 Entity class OCA\AppFramework\Db\Entity •Abstract property $id static Entity::fromParams($params) Parameters • $params (array) – the array which was obtained via $this->params(‘key’)in the controller Returns \OCA\AppFramework\Db\Entity Simple alternative constructor for building entities from a request fromRow($row) Parameters • $row (array) – the row to map onto the entity Maps the keys of the row array to the attributes resetUpdatedFields() Marks the entity as clean needed for setting the id after the insertion __call($methodName, $args) Parameters • $methodName (mixed) – • $args (mixed) – Each time a setter is called, push the part after setinto an array: for instance setId will save Id in theupdated fields array so it can be easily used to create thegetter method markFieldUpdated($attribute) Parameters • $attribute (string) – the name of the attribute •Protected Mark am attribute as updated columnToProperty($columnName) Parameters • $columnName (string) – the name of the column Returns string the property name Transform a database columnname to a property propertyToColumn($property) Parameters • $property (string) – the name of the property Returns string the column name Transform a property to a database column name 106 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 getUpdatedFields() Returns array array of updated fields for update query addType($fieldName, $type) Parameters • $fieldName (string) – the name of the attribute • $type (string) – the type which will be used to call settype() •Protected Adds type information for a field so that its automatically casted tothat value once its being returned from the database DIContainer This class extends Pimple (http://pimple.sensiolabs.org/) for reusability To use this class, extend your own container from this. Should you require it you can overwrite the dependencies with your own classes by simply redefining a dependency class OCA\AppFramework\DependencyInjection\DIContainer __construct($appName) Parameters • $appName (string) – the name of the app Put your class dependencies in here Dispatcher Class to dispatch the request to the middleware disptacher class OCA\AppFramework\Http\Dispatcher __construct($protocol, $middlewareDispatcher) Parameters • $protocol (\OCA\AppFramework\Http\Http) – the http protocol with contains all status headers • $middlewareDispatcher (\OCA\AppFramework\Middleware\MiddlewareDispatcher) – the dispatcher which runs the middleware dispatch($controller, $methodName) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller which will be called • $methodName (string) – the method name which will be called onthe controller Returns array $array[0] contains a string with the http main header,$array[1] contains headers in the form: $key => value, $array[2] containsthe response output 3.4. App Development (App Framework) 107 ownCloud Developer Manual, Release 6.0 Handles a request and calls the dispatcher on the controller Http class OCA\AppFramework\Http\Http property $headers •Protected __construct($server, $protocolVersion=’HTTP/1.1’) Parameters • $server (mixed) – • $protocolVersion (string) – the http version to use defaults to HTTP/1.1 getStatusHeader($status, $lastModified=null, $ETag=null) :param \OCA\AppFramework\Http\Http::CONSTANT $status: the constant from the Http class :param \DateTime $lastModified: formatted last modified date :param mixed $ETag: Gets the correct header Response Baseclass for responses. Also used to just send headers class OCA\AppFramework\Http\Response cacheFor($cacheSeconds) Parameters • $cacheSeconds (int) – the amount of seconds that should be cachedif 0 then caching will be disabled Caches the response addHeader($name, $value) Parameters • $name (string) – The name of the HTTP header • $value (string) – The value, null will delete it Adds a new header to the response that will be called before the renderfunction getHeaders() Returns array the headers Returns the set headers render() Returns null By default renders no output setStatus($status) Parameters 108 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $status (int) – a HTTP status code, see also the STATUS constants Set response status getStatus() Get response status getETag() Returns string the etag getLastModified() Returns string RFC2822 formatted last modified date setETag($ETag) Parameters • $ETag (string) – setLastModified($lastModified) Parameters • $lastModified (\DateTime) – DownloadResponse Prompts the user to download the a file class OCA\AppFramework\Http\DownloadResponse •Abstract __construct($filename, $contentType) Parameters • $filename (string) – the name that the downloaded file should have • $contentType (string) – the mimetype that the downloaded file should have Creates a response that prompts the user to download the file JSONResponse A renderer for JSON calls class OCA\AppFramework\Http\JSONResponse property $error •Protected property $data •Protected __construct() setParams($params) Parameters 3.4. App Development (App Framework) 109 ownCloud Developer Manual, Release 6.0 • $params (array) – an array with key => value structure which will be transformed to JSON Sets values in the data json array getParams() Returns array the params Used to get the set parameters setErrorMessage($msg) Parameters • $msg (mixed) – in case we want to render an error message, also logs into the owncloud log render() Returns string the rendered json Returns the rendered json RedirectResponse Redirects to a different URL class OCA\AppFramework\Http\RedirectResponse __construct($redirectURL) Parameters • $redirectURL (string) – the url to redirect to Creates a response that redirects to a url getRedirectURL() Returns string the url to redirect TemplateResponse Response for a normal template class OCA\AppFramework\Http\TemplateResponse property $templateName •Protected property $params •Protected property $api •Protected property $renderAs •Protected 110 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 property $appName •Protected __construct($api, $templateName, $appName=null) Parameters • $api (\OCA\AppFramework\Core\API) – an API instance • $templateName (string) – the name of the template • $appName (string) – optional if you want to include a template from a different app setParams($params) Parameters • $params (array) – an array with key => value structure which sets template variables Sets template parameters getParams() Returns array the params Used for accessing the set parameters getTemplateName() Returns string the name of the used template Used for accessing the name of the set template renderAs($renderAs) Parameters • $renderAs (string) – admin, user or blank. Admin also prints the admin settings header and footer, user renders the normal normal page including footer and header and blank just renders the plain template Sets the template page getRenderAs() Returns string the renderAs value Returns the set renderAs render() Returns string the rendered html Returns the rendered html TextResponse Just outputs text to the browser class OCA\AppFramework\Http\TextResponse __construct($content, $contentType=’plain’) Parameters • $content (string) – the content that should be written into the file 3.4. App Development (App Framework) 111 ownCloud Developer Manual, Release 6.0 • $contentType (string) – the mimetype. text/ is added automatically soonly plain or html can be added to get text/plain or text/html Creates a response that just outputs text render() Returns string the file contents Simply sets the headers and returns the file contents TextDownloadResponse Prompts the user to download the a textfile class OCA\AppFramework\Http\TextDownloadResponse __construct($content, $filename, $contentType) Parameters • $content (string) – the content that should be written into the file • $filename (string) – the name that the downloaded file should have • $contentType (string) – the mimetype that the downloaded file should have Creates a response that prompts the user to download a file whichcontains the passed string render() Returns string the file contents Simply sets the headers and returns the file contents TwigResponse Response for twig templates. Do not use this directly to render your templates, unless you want a blank page because the owncloud header and footer won’t be included class OCA\AppFramework\Http\TwigResponse __construct($api, $templateName, $twig) Parameters • $api (\OCA\AppFramework\Core\API) – an api instance • $templateName (string) – the name of the twig template • $twig (\OCA\AppFramework\Http\Twig_Environment) – an instance of the twig environment for rendering Instantiates the Twig Template render() Returns string rendered output Returns the rendered result 112 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Request Class for accessing variables in the request. This class provides an immutable object with request variables. class OCA\AppFramework\Http\Request property $items •Protected property $allowedKeys •Protected __construct($vars=array()) Parameters • $vars (array) – An associative array with the following optional values: Note: OCAAppFrameworkHttphttp://www.php.net/manual/en/reserved.variables.php count() offsetExists($offset) Parameters • $offset (string) – offset The key to lookup Returns string|null ArrayAccess methods Gives access to the combined GET, POST and urlParams arraysExamples:$var = $request[’myvar’];orif(!isset($request[’myvar’]) { // Do something}$request[’myvar’] = ‘something’; // This throws an exception. offsetGet($offset) Parameters • $offset (mixed) – Note: OCAAppFrameworkHttpoffsetExists offsetSet($offset, $value) Parameters • $offset (mixed) – • $value (mixed) – Note: OCAAppFrameworkHttpoffsetExists offsetUnset($offset) Parameters • $offset (mixed) – 3.4. App Development (App Framework) 113 ownCloud Developer Manual, Release 6.0 Note: OCAAppFrameworkHttpoffsetExists __set($name, $value) Parameters • $name (mixed) – • $value (mixed) – __get($name) Parameters • $name (string) – The key to look for. Returns mixed|null Access request variables by method and name. Examples:$request->post[’myvar’]; // Only look for POST variables$request->myvar; or $request->{‘myvar’}; or $request->{$myvar}Looks in the combined GET, POST and urlParams array.if($request->method !== ‘POST’) { throw new Exception(‘This function can only be invoked using POST’);} __isset($name) Parameters • $name (mixed) – __unset($id) Parameters • $id (mixed) – NotFoundResponse Pure hader response, Just return 404 status to the browser class OCA\AppFramework\Http\NotFoundResponse __construct() Creates a response that just returns 404 status ForbiddenResponse Pure hader response, Just return 403 status to the browser class OCA\AppFramework\Http\ForbiddenResponse __construct() Creates a response that just returns 403 status 114 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Middleware Middleware is used to provide hooks before or after controller methods and deal with possible exceptions raised in the controller methods. They’re modeled after Django’s middleware system: https://docs.djangoproject.com/en/dev/topics/http/middleware/ class OCA\AppFramework\Middleware\Middleware •Abstract beforeController($controller, $methodName) Parameters • $controller (\OCA\AppFramework\Middleware\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller This is being run in normal order before the controller is beingcalled which allows several modifications and checks afterException($controller, $methodName, $exception) Parameters • $controller (\OCA\AppFramework\Middleware\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $exception (\Exception) – the thrown exception Returns \OCA\AppFramework\Http\Response a Response object or null in case that the exception could not be handled This is being run when either the beforeController method or thecontroller method itself is throwing an exception. The middleware isasked in reverse order to handle the exception and to return a response.If the response is null, it is assumed that the exception could not behandled and the error will be thrown again afterController($controller, $methodName, $response) Parameters • $controller (\OCA\AppFramework\Middleware\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $response (\OCA\AppFramework\Http\Response) – the generated response from the controller Returns \OCA\AppFramework\Http\Response a Response object This is being run after a successful controllermethod call and allowsthe manipulation of a Response object. The middleware is run in reverse order beforeOutput($controller, $methodName, $output) Parameters • $controller (\OCA\AppFramework\Middleware\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $output (string) – the generated output from a response 3.4. App Development (App Framework) 115 ownCloud Developer Manual, Release 6.0 Returns string the output that should be printed This is being run after the response object has been rendered andallows the manipulation of the output. The middleware is run in reverse order MiddlewareDispatcher This class is used to store and run all the middleware in correct order class OCA\AppFramework\Middleware\MiddlewareDispatcher __construct() Constructor registerMiddleware($middleware) Parameters • $middleware (\OCA\AppFramework\Middleware\Middleware) – the middleware which will be added Adds a new middleware getMiddlewares() Returns array the middlewares returns an array with all middleware elements beforeController($controller, $methodName) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller This is being run in normal order before the controller is beingcalled which allows several modifications and checks afterException($controller, $methodName, $exception) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $exception (\Exception) – the thrown exception Returns \OCA\AppFramework\Http\Response a Response object or null in case that the exception could not behandled This is being run when either the beforeController method or thecontroller method itself is throwing an exception. The middleware is askedin reverse order to handle the exception and to return a response.If the response is null, it is assumed that the exception could not behandled and the error will be thrown again afterController($controller, $methodName, $response) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called 116 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 • $methodName (string) – the name of the method that will be called on the controller • $response (\OCA\AppFramework\Http\Response) – the generated response from the controller Returns \OCA\AppFramework\Http\Response a Response object This is being run after a successful controllermethod call and allowsthe manipulation of a Response object. The middleware is run in reverse order beforeOutput($controller, $methodName, $output) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $output (string) – the generated output from a response Returns string the output that should be printed This is being run after the response object has been rendered andallows the manipulation of the output. The middleware is run in reverse order SecurityMiddleware Used to do all the authentication and checking stuff for a controller method It reads out the annotations of a controller method and checks which if security things should be checked and also handles errors in case a security check fails class OCA\AppFramework\Middleware\Security\SecurityMiddleware __construct($api) Parameters • $api (\OCA\AppFramework\Core\API) – an instance of the api beforeController($controller, $methodName) Parameters • $controller (\OCA\AppFramework\Middleware\Security\string/Controller) – the controllername or string • $methodName (string) – the name of the method Throws \OCA\AppFramework\Middleware\Security\SecurityException when a security check fails This runs all the security checks before a method call. Thesecurity checks are determined by inspecting the controller methodannotations afterException($controller, $methodName, $exception) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $exception (\Exception) – the thrown exception 3.4. App Development (App Framework) 117 ownCloud Developer Manual, Release 6.0 Returns \OCA\AppFramework\Http\Response a Response object or null in case that the exception could not be handled If an SecurityException is being caught, ajax requests return a JSON errorresponse and non ajax requests redirect to the index SecurityException Thrown when the security middleware encounters a security problem class OCA\AppFramework\Middleware\Security\SecurityException __construct($msg, $ajax) Parameters • $msg (string) – the security error message • $ajax (bool) – true if it resulted because of an ajax request isAjax() Returns bool true if exception resulted because of an ajax request Used to check if a security exception occured in an ajax request TwigMiddleware This template is used to add the possibility to add twig templates By default it is only loaded when the templatepath is set class OCA\AppFramework\Middleware\Twig\TwigMiddleware __construct($api, $twig) Parameters • $api (\OCA\AppFramework\Core\API) – an instance of the api • $twig (\OCA\AppFramework\Middleware\Twig\Twig_Environment) – an instance of the twig environment Sets the twig loader instance afterController($controller, $methodName, $response) Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $response (\OCA\AppFramework\Http\Response) – the generated response from the controller Returns \OCA\AppFramework\Http\Response a Response object Swaps the template response with the twig response and stores if atemplate needs to be printed for the user or admin page beforeOutput($controller, $methodName, $output) 118 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Parameters • $controller (\OCA\AppFramework\Controller\Controller) – the controller that is being called • $methodName (string) – the name of the method that will be called on the controller • $output (string) – the generated output from a response Returns string the output that should be printed In case the output is not rendered as blank page, we need to include theowncloud header and output ControllerTestUtility Simple utility class for testing controllers class OCA\AppFramework\Utility\ControllerTestUtility •Abstract assertAnnotations($controller, $method, $expected, $valid=array()) Parameters • $controller (\OCA\AppFramework\Utility\Controller/string) – name or instance of the controller • $method (mixed) – • $expected (array) – an array containing the expected annotations • $valid (array) – if you define your own annotations, pass them here •Protected Checks if a controllermethod has the expected annotations assertHeaders($expected=array(), $response) Parameters • $expected (array) – an array with the expected headers • $response (\OCA\AppFramework\Http\Response) – the response which we want to test for headers •Protected Shortcut for testing expected headers of a response getRequest($params) Parameters • $params (array) – a hashmap with the parameters for request Returns \OCA\AppFramework\Http\Request a request instance •Protected Instead of using positional parameters this function instantiatesa request by using a hashmap so its easier to only set specific params 3.4. App Development (App Framework) 119 ownCloud Developer Manual, Release 6.0 MapperTestUtility Simple utility class for testing mappers class OCA\AppFramework\Utility\MapperTestUtility •Abstract property $api •Protected beforeEach() •Protected Run this function before the actual test to either set or initialize theapi. After this the api can be accessed by using $this->api setMapperResult($sql, $arguments=array(), $returnRows=array(), $limit=null, $offset=null) Parameters • $sql (string) – the sql query that you expect to receive • $arguments (array) – the expected arguments for the prepare querymethod • $returnRows (array) – the rows that should be returned for the resultof the database query. If not provided, it wont be assumed that fetchRowwill be called on the result • $limit (mixed) – • $offset (mixed) – •Protected Create mocks and set expected results for database queries TestUtility Simple utility class for testing anything using an api class OCA\AppFramework\Utility\TestUtility •Abstract getAPIMock($apiClass=’OCAAppFrameworkCoreAPI’, $constructor=array(‘appname’)) Parameters • $apiClass (string) – the class inclusive namespace of the api that we want to use • $constructor (array) – constructor parameters of the api class •Protected Boilerplate function for getting an API Mock class 120 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 MethodAnnotationReader Reads and parses annotations from doc comments class OCA\AppFramework\Utility\MethodAnnotationReader __construct($object, $method) Parameters • $object (object) – an object or classname • $method (string) – the method which we want to inspect for annotations hasAnnotation($name) Parameters • $name (string) – the name of the annotation Returns bool true if the annotation is found Check if a method contains an annotation FaviconFetcher class OCA\AppFramework\Utility\FaviconFetcher __construct($apiFactory) Parameters • $apiFactory (\OCA\AppFramework\Utility\SimplePieAPIFactory) – Inject a factory to build a simplepie file object. This is needed becausethe file object contains logic in its constructor which makes itimpossible to inject and test fetch($url) Parameters • $url (string|null) – the url where to fetch it from Fetches a favicon from a given URL extractFromPage($url) Parameters • $url (string) – the url to the page Returns string the full url to the page •Protected Tries to get a favicon from a page isImage($url) Parameters • $url (string) – the url to the file Returns bool true if image 3.4. App Development (App Framework) 121 ownCloud Developer Manual, Release 6.0 •Protected Test if the file is an image buildURL($url) Parameters • $url (string) – the url that should be built Returns array an array containing the http and https address •Protected Get HTTP and HTTPS adresses from an incomplete URL SimplePieAPIFactory class OCA\AppFramework\Utility\SimplePieAPIFactory getFile($url, $timeout=10, $redirects=5, $headers=null, $useragent=null, $force_fsockopen=false) Parameters • $url (mixed) – • $timeout (mixed) – • $redirects (mixed) – • $headers (mixed) – • $useragent (mixed) – • $force_fsockopen (mixed) – Returns \OCA\AppFramework\Utility\SimplePie_File a new object Builds a simplepie file object. This is needed becausethe file object contains logic in its constructor which makes itimpossible to inject and test getCore() Returns \SimplePie_Core instance Returns a new instance of a SimplePie_Core() object. This is neededbecause the class relies on external dependencies which are not passedin via the constructor and thus making it nearly impossible to unittestcode that uses this class TimeFactory Needed to mock calls to time() class OCA\AppFramework\Utility\TimeFactory getTime() Returns int the result of a call to time() 122 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 Main • App • DIContainer API Layer • API Request • Dispatcher • Http • Request Controllers • Controller Database • DoesNotExistException • MultipleObjectsReturnedException • Mapper Responses • Response • DownloadResponse • JSONResponse • RedirectResponse • TemplateResponse • TextResponse • TextDownloadResponse Middleware • Middleware • MiddlewareDispatcher 3.4. App Development (App Framework) 123 ownCloud Developer Manual, Release 6.0 Security & Authentication • SecurityMiddleware • MethodAnnotationReader • SecurityException Twig Templates • TwigMiddleware • TwigResponse Utilities • FaviconFetcher • SimplePieAPIFactory • TimeFactory Testing • ControllerTestUtility • MapperTestUtility • TestUtility 3.5 Intro Before you start, please check if there already is a similar app you could contribute to. Also, feel free to communicate your idea and plans to the user mailing list or developer mailing list so other contributors might join in. Then, please make sure you have set up a development environment: • Development Environment Before starting to write an app please read the security and coding guidelines: • Security Guidelines • Coding Style & General Guidelines After this you can start to write your app: • Creating An App 3.6 App Development using ownCloud App API You can choose between the traditional and MVC style (App Framework) approach. For a comparison see App Framework Comparison. This approach uses the basic ownCloud libraries and provides no classes to use for MVC development and testing. • App Tutorial 124 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.6.1 General Inner parts of an app • Classloader • Routes • App Metadata • Debugging 3.6.2 Database Database access • Database Schema | Database Access 3.6.3 Templates HTML and inclusion of JavaScript and CSS • Templates 3.6.4 JavaScript & CSS • Static content • CSS 3.6.5 Testing • Acceptance Tests 3.6.6 API Documentation • ownCloud App API 3.7 App Development using the App Framework App Develop an app using an MVC Framework. The App Framework provides enhanced Security, MVC classes and testing tools but you need to read more until you can produce the first output. • App Tutorial 3.7. App Development using the App Framework App 125 ownCloud Developer Manual, Release 6.0 3.7.1 General Inner parts of an app • Classloader • Runtime configuration | Dependency Injection • Routes • App Metadata • Debugging 3.7.2 Controllers Contain the logic for each request • Controllers 3.7.3 Database Database access • Database Schema | Database Access 3.7.4 Templates HTML and inclusion of JavaScript and CSS • Templates 3.7.5 JavaScript & CSS • Static content • AngularJS | Angular Setup | Angular App Framework Libraries • CSS 3.7.6 Middleware Hook before or after controller execution • Middleware 3.7.7 Testing • Unittests • Acceptance Tests 126 Chapter 3. App Development ownCloud Developer Manual, Release 6.0 3.7.8 API Documentation • AppFramework API • ownCloud App API 3.8 Additional APIs Can be used with and without App Framework • Data Migration • Hooks • Filesystem 3.8. Additional APIs 127 ownCloud Developer Manual, Release 6.0 128 Chapter 3. App Development CHAPTER FOUR CORE DEVELOPMENT 4.1 Translation 4.1.1 Make text translatable In HTML or PHP wrap it like this <?php p($l->t(’This is some text’));?> or this <?php print_unescaped($l->t(’This is some text’));?> For the right date format use <?php p($l->l(’date’, time()));?>. Change the way dates are shown by editing /core/l10n/l10n-[lang].php To translate text in javascript use: t(’appname’,’text to translate’); Note: print_unescaped() should be preferred only if you would like to display HTML code. Otherwise, using p() is strongly preferred to escape HTML characters against XSS attacks. 4.1.2 You shall never split sentences! Reason: Translators lose the context and they have no chance to possible re-arrange words. Example: <?php p($l->t(’Select file from’)) . ’ ’; ?><a href=’#’ id="browselink"><?php p($l->t(’local filesyst Translators will translate: • Select file from • local filesystem • ‘ or “ • cloud By translating these individual strings for sure the case will be lost of local filesystem and cloud. The two white spaces with the or will get lost while translating as well. 129 ownCloud Developer Manual, Release 6.0 Html on translation string: Html tags in translation strings is ugly but usually translators can handle this. What about variable in the strings? In case you need to add variables to the translation strings do it like that: $l->t(’%s is available. Get <a href="%s">more information</a>’, array($data[’versionstring’], $data[’ 4.1.3 Automated synchronization of translations Multiple nightly jobs have been setup in order to synchronize translations - it’s a multi-step process: perl l10n.pl read will rescan all php and javascript files and generate the templates. The templates are pushed to Transifex (tx push -s). All translations are pulled from Transifex (tx pull -a). perl l10n.pl write will write the php files containing the translations. Finally the changes are pushed to git. Please follow the steps below to add translation support to your app: Create a folder l10n. Create the file ignorelist which can contain files which shall not be scanned during step 4. Edit l10n/.tx/config and copy/past a config section and adopt it by changing the app/folder name. Run perl l10n.pl read with l10n Add the newly created translation template (l10n/Templates/<appname>.pot) to git and commit the changes above. After the next nightly sync job a new resource will appear on Transifex and from now on every night the latest translations will arrive. Translation sync jobs: https://ci.owncloud.org/view/translation-sync/ Caution: information below is in general not needed! 4.1.4 Manual quick translation update: cd l10n/ && perl l10n.pl read && tx push -s && tx pull -a && perl l10n.pl write && cd .. The translation script requires Locale::PO, installable via apt-get install liblocale-po-perl 4.1.5 Configure transifex tx init for resource in calendar contacts core files media gallery settings do tx set --auto-local -r owncloud.$resource "<lang>/$resource.po" --source-language=en \ --source-file "templates/$resource.pot" --execute done 130 Chapter 4. Core Development ownCloud Developer Manual, Release 6.0 4.2 Unit-Testing 4.2.1 Getting PHPUnit ownCloud uses PHPUnit >= 3.7 for unit testing. To install it, either get it via your packagemanager: sudo apt-get install phpunit or install it via PEAR: pear config-set auto_discover 1 pear install pear.phpunit.de/PHPUnit After the installation the ‘’phpunit” command is available. 4.2.2 Writing unit tests To get started, do the following: • Create a directory called tests in the top level of your application • Create a php file in the directory and require_once your class which you want to test. Then you can simply run the created test with phpunit. Note: If you use owncloud functions in your class under test (i.e: OC::getUser()) you’ll need to bootstrap owncloud or use dependency injection. Note: You’ll most likely run your tests under a different user than the webserver. This might cause problems with your PHP settings (i.e: open_basedir) and requires you to adjust your configuration. An example for a simple test would be: /srv/http/owncloud/apps/myapp/tests/testsuite.php <?php require_once("../myfolder/myfunction.php"); class TestAddTwo extends PHPUnit_Framework_TestCase { public function testAddTwo(){ $this->assertEquals(5, addTwo(3)); } } ?> /srv/http/owncloud/apps/myapp/tests/testsuite.php <?php function addTwo($number){ return $number + 2; } ?> 4.2. Unit-Testing 131 ownCloud Developer Manual, Release 6.0 In /srv/http/owncloud/apps/myapp/ you run the test with: phpunit tests/testsuite.php For more resources on PHPUnit visit: http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html 4.2.3 Bootstrapping ownCloud If you use ownCloud functions or classes in your code, you’ll need to make them available to your test by bootstrapping ownCloud. To do this, you’ll need to provide the --bootstrap argument when running PHPUnit /srv/http/owncloud: phpunit --bootstrap tests/bootstrap.php apps/myapp/tests/testsuite.php If you run the test under a different user than your webserver, you’ll have to adjust your php.ini and file rights. /etc/php/php.ini: open_basedir = none /srv/http/owncloud: su -c "chmod a+r config/config.php" su -c "chmod a+rx data/" su -c "chmod a+w data/owncloud.log" 4.2.4 Running unit tests for the ownCloud core project The core project provides a script that runs all the core unit tests using different database backends like sqlite, mysql, pgsql, oci (for Oracle): ./autotest.sh To run tests only for sqlite: ./autotest.sh sqlite To run a specific test suite (note that the test file path is relative to the “tests” directory): ./autotest.sh sqlite lib/share/share.php 4.2.5 Further Reading • http://googletesting.blogspot.de/2008/08/by-miko-hevery-so-you-decided-to.html • http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html • http://www.youtube.com/watch?v=4E4672CS58Q&feature=bf_prev&list=PLBDAB2BA83BB6588E • Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin) 132 Chapter 4. Core Development ownCloud Developer Manual, Release 6.0 4.3 Theming ownCloud Themes can be used to customize the look and feel of ownCloud. Themes can relate to the following topics of owncloud: • Theming the web-frontend • Theming the owncloud Desktop client This documentation contains only the Web-frontend adaptions so far. 4.3.1 Getting started A good idea getting starting with a dynamically created website is to inspect it with webdeveloper tools, that are found in almost any browser. They show the generated HTML and the CSS Code, that the client/browser is recieving: With this facts you can easyly determine, where the following object-related attributes for the phenomenons are settled: • place • colour • links • graphics In owncloud standard theme everything is held very simple. This allows you quick adpating. In an unchanged ownCloud version css files and the standard pictures reside in /owncloud/themes/default folder. The next thing you should do, before starting any changes is: Make a backup of your current theme(s) e.g.: • Goto . . . /owncloud/themes • cp -r default default.old 4.4 Creating and activating a new theme There are two basic ways of creating new themings: • Doing all new from scratch • Starting from an existing theme and doing everything step by step and more experimentally Depending on how you created your new theme it will be necessary to • put a new theme into the /themes -folder. The theme can be activated by putting “theme” => ‘themename’, into the config.php file. • make all changes in the /themes/default -file 4.5 Structure The folder structure of a theme is exactly the same as the main ownCloud structure. You can override js files, images and templates with own versions. css files are loaded additionally to the default files so you can override css properties. 4.3. Theming ownCloud 133 ownCloud Developer Manual, Release 6.0 4.6 How to change images and the logo A new logo which you may want to insert can be added as follows: 4.6.1 Figure out the path of the old logo Replace the old pictue, which postition you found out as described under 1.3. by adding an extension in case you want to re-use it later. 4.6.2 Creating an own logo If you want to do a quick exchange like (1) it’s important to know the size of the picture before you start creating an own logo: • Go to the place in the filesystem, that has been shown by the webdeveloper tool/s • You can look up sizing in most cases via the file properties inside your file-manager • Create an own picture/logo with the same size then The (main) pictures, that can be found inside ownCloud standard theming are the following: • The logo at the login-page above the credentials-box: . . . /owncloud/themes/default/core/img/logo.svg • The logo, that’s always in the left upper corner after login: . . . /owncloud/themes/default/core/img/logo-wide.svg 4.6.3 Inserting your new logo Inserting a new logo into an existing theme is as simple as replacing the old logo with the new (genreated) one. You can use: scalable vector graphics (.svg) or common graphics formats for the internet such as portable network graphics (.png) or .jepg Just insert the new created picture by using the unchanged name of the old picture. 4.6.4 changing the default colours With a web-developer tool like Mozilla-Inspector, you also get easyly displayed the color of the background you klicked on. On the top of the login page you can see a case- destinguished setting for different browsers: The different backround-assignements indicate the headers for a lot of different browser types. What you most likely want to do is change the #35537a (lighter blue) and #ld2d42 (dark blue) color to the colours of our choice. In some older and other browsers, there is just one color, but in the rest showing gradients is possible. The login page background is a horizontal gradient. The first hex number, #35537a, is the top color of the gradient at the login screen. The second hex number, #ld2d42 is the bottom color of the gradient at the login screen. The gradient in top of the normal view after login is also defined by these css-settings, so that they take effect in logged in situation as well. Change these colors to the hex color of your choice: As usual: • the first two figures give the intensity of the red channel, • the second two give the green intensity and the • tird pair gives the blue value. Save your css-file and refresh to see the new login screen. The other major color scheme is the blue header bar on the main navigation page once you log in to ownCloud. This color we will change with the above as well. Save the file and refresh the browser for the changes to take effect. 134 Chapter 4. Core Development ownCloud Developer Manual, Release 6.0 4.7 Testing the new theme out There are different options for doing so: • If you’re using a tool like the Inspector tools inside Mozilla, you can test out the CSS-Styles immediately inside the css-attributes, while looking at them. • If you have a developing/testing server as desciribed in 1. you can test out the effects in a real environment permanently. 4.8 Notes for Updates In case of theming it is recommended to the user, not to perform these adaptions inside the folder /themes/default. Please perform the following steps, to avoid conflicts with other upcoming updates: • create a new folder inside /themes: for example: /themes/MyTheme • Copy the folders /themes/default/core and /themes/default/settings to /themes/MyTheme • edit the file /config/config.php • Insert: ‘theme’ => ‘MyTheme’, into this file Within the folder /themes/MyTheme all files, which are needed for theming are save from coming updates. All company theming must be done exclusively here from now on. In case, that one of the following files is affected due to an upgrade, • /themes/default/settings/templates/apps.php • /themes/default/defaults.php the files listed below, have to be replaced by those listed above: • /themes/MyTheme/settings/templates/apps.php • /themes/MyTheme/defaults.php But this is unlikely at least in the upcoming updates (5.0.x). ownCloud aims to give further information in this case. 4.9 App config <?php define("DEBUG", true); $CONFIG = array( /* Flag to indicate ownCloud is successfully installed (true = installed) */ "installed" => false, /* Type of database, can be sqlite, mysql or pgsql */ "dbtype" => "sqlite", /* Name of the ownCloud database */ "dbname" => "owncloud", /* User to access the ownCloud database */ 4.7. Testing the new theme out 135 ownCloud Developer Manual, Release 6.0 "dbuser" => "", /* Password to access the ownCloud database */ "dbpassword" => "", /* Host running the ownCloud database */ "dbhost" => "", /* Prefix for the ownCloud tables in the database */ "dbtableprefix" => "", /* Define the salt used to hash the user passwords. All your user passwords are lost if you lose this "passwordsalt" => "", /* Force use of HTTPS connection (true = use HTTPS) */ "forcessl" => false, /* Theme to use for ownCloud */ "theme" => "", /* Path to the 3rdparty directory */ "3rdpartyroot" => "", /* URL to the 3rdparty directory, as seen by the browser */ "3rdpartyurl" => "", /* Default app to load on login */ "defaultapp" => "files", /* Enable the help menu item in the settings */ "knowledgebaseenabled" => true, /* URL to use for the help page, server should understand OCS */ "knowledgebaseurl" => "http://api.apps.owncloud.com/v1", /* Enable installing apps from the appstore */ "appstoreenabled" => true, /* URL of the appstore to use, server should understand OCS */ "appstoreurl" => "http://api.apps.owncloud.com/v1", /* Mode to use for sending mail, can be sendmail, smtp, qmail or php, see PHPMailer docs */ "mail_smtpmode" => "sendmail", /* Host to use for sending mail, depends on mail_smtpmode if this is used */ "mail_smtphost" => "127.0.0.1", /* authentication needed to send mail, depends on mail_smtpmode if this is used * (false = disable authentication) */ "mail_smtpauth" => false, /* Username to use for sendmail mail, depends on mail_smtpauth if this is used */ "mail_smtpname" => "", /* Password to use for sendmail mail, depends on mail_smtpauth if this is used */ "mail_smtppassword" => "", 136 Chapter 4. Core Development ownCloud Developer Manual, Release 6.0 /* Check 3rdparty apps for malicious code fragments */ "appcodechecker" => "", /* Check if ownCloud is up to date */ "updatechecker" => true, /* Place to log to, can be owncloud and syslog (owncloud is log menu item in admin menu) */ "log_type" => "owncloud", /* File for the owncloud logger to log to, (default is ownloud.log in the data dir */ "logfile" => "", /* Loglevel to start logging at. 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR (default is WARN) */ "loglevel" => "", /* Lifetime of the remember login cookie, default is 15 days */ "remember_login_cookie_lifetime" => 60*60*24*15, /* The directory where the user data is stored, default to data in the owncloud * directory. The sqlite database is also stored here, when sqlite is used. */ // "datadirectory" => "", "apps_paths" => array( /* Set an array of path for your apps directories key ’path’ is for the fs path and the key ’url’ is for the http path to your applications paths. ’writable’ indicate if the user can install apps in this folder. You must have at least 1 app folder writable or you must set the parameter : appstoreenabled to fals */ array( ’path’=> ’/var/www/owncloud/apps’, ’url’ => ’/apps’, ’writable’ => true, ), ), ); 4.9.1 Using alternative app directories ownCloud can be set to use a custom app directory in /config/config.php. Customise the following code and add it to your config file: ’apps_paths’ => array ( 0 => array ( ’path’ => OC::$SERVERROOT.’/apps’, ’url’ => ’/apps’, ’writable’ => true, ), 1 => array ( ’path’ => OC::$SERVERROOT.’/apps2’, ’url’ => ’/apps2’, ’writable’ => false, ), 4.9. App config 137 ownCloud Developer Manual, Release 6.0 ), ownCloud will use the first app directory which it finds in the array with ‘writable’ set to true. 4.10 OCS Share API The OCS Share API allows you to access the sharing API from outside over pre-defined OCS calls. The base URL for all calls to the share API is: <owncloud_base_url>/ocs/v1.php/apps/files_sharing/api/v1 4.10.1 Get All Shares Get all shares from the user. • Syntax: /shares • Method: GET • Result: XML with all shares Statuscodes: • 100 - successful • 404 - couldn’t fetch shares 4.10.2 Get Shares from a specific file or folder Get all shares from a given file/folder. • Syntax: /shares • Method: GET • URL Arguments: path - (string) path to file/folder • URL Arguments: reshares - (boolean) returns not only the shares from the current user but all shares from the given file. • URL Arguments: subfiles - (boolean) returns all shares within a folder, given that path defines a folder • Mandatory fields: path • Result: XML with the shares Statuscodes • 100 - successful • 400 - not a directory (if the ‘subfile’ argument was used) • 404 - file doesn’t exist 4.10.3 Get information about a known Share Get information about a given share. • Syntax: /shares/<share_id> • Method: GET 138 Chapter 4. Core Development ownCloud Developer Manual, Release 6.0 • Arguments: share_id - (int) share ID • Result: XML with the share information Statuscodes: • 100 - successful • 404 - share doesn’t exist 4.10.4 Create a new Share Share a file/folder with a user/group or as public link. • Syntax: /shares • Method: POST • POST Arguments: path - (string) path to the file/folder which should be shared • POST Arguments: shareType - (int) ‘0’ = user; ‘1’ = group; ‘3’ = public link • POST Arguments: shareWith - (string) user / group id with which the file should be shared • POST Arguments: publicUpload - (boolean) allow public upload to a public shared folder (true/false) • POST Arguments: password - (string) password to protect public link Share with • POST Arguments: permissions - (int) 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1) • Mandatory fields: shareType, path and shareWith for shareType 0 or 1. • Result: XML containing the share ID (int) of the newly created share Statuscodes: • 100 - successful • 400 - unknown share type • 403 - public upload was disabled by the admin • 404 - file couldn’t be shared 4.10.5 Delete Share Remove the given share. • Syntax: /shares/<share_id> • Method: DELETE • Arguments: share_id - (int) share ID Statuscodes: • 100 - successful • 404 - file couldn’t be deleted 4.10. OCS Share API 139 ownCloud Developer Manual, Release 6.0 4.10.6 Update Share Update a given share. Only one value can be updated per request. • Syntax: /shares/<share_id> • Method: PUT • Arguments: share_id - (int) share ID • PUT Arguments: permissions - (int) update permissions (see “Create share” above) • PUT Arguments: password - (string) updated password for public link Share • PUT Arguments: publicUpload - (boolean) enable (true) /disable (false) public upload for public shares Note that only one of “password” or “publicUpload” can be specified at once. Statuscodes: • 100 - successful • 400 - wrong or no update parameter given • 403 - public upload disabled by the admin • 404 - couldn’t update share 4.11 Intro Please make sure you have set up a development environment: • Development Environment 4.12 Core related docs • Translation • Unit-Testing • Theming ownCloud • App config • OCS Share API 140 Chapter 4. Core Development CHAPTER FIVE OWNCLOUD TEST PILOTS The ownCloud Test Pilots take care of helping to test and improve different server setups with ownCloud. 5.1 Why do you want to join There are many different setups and people have different interests. If you want ownCloud to run well on NginX for instance someone has to test this configuration. Furthermore during bugfixing the ownCloud developers often do not have the possibility to reproduce the bug in a given environment nor they are able confirm that it was fixed. As a member of the Test Pilot Team you could act as a contact person for a specific area to help developers fix the bugs you care about. Another benefit is a more efficient relationship to the developers: You know what people are responsible for which parts and it is easier to get help. If you want you will be listed as an active contributor on the owncloud.org page. 5.2 Who can join Anyone who is interested in improving the quality on his/her setup and is willing to communicate with developers and other testers. 5.3 How do you join Simply register on the testpilot mailing list and send an introduction of your personal setup and interests to [email protected] You can also join the #owncloud-testing channel on irc.freenode.net but keep in mind that we may take longer to answer ;) For further questions or help you can also send a mail to: • [email protected] (IRC: dragotin) • [email protected] (IRC: Raydiation) 141 ownCloud Developer Manual, Release 6.0 5.4 What do you do You will receive mails from the mailinglist and also from the bugtracker if developers need your help. Also there will be announcements of new releases and preview releases on the mailing list which give you the possibility to test releases early on and help developers to fix them. We are looking forward to working with you :) 142 Chapter 5. ownCloud Test Pilots CHAPTER SIX BUGTRACKER 6.1 Code Reviews on GitHub Given enough eyeballs, all bugs are shallow —Linus’ Law 6.1.1 Introduction In order to increase the code quality within ownCloud, developers are requested to perform code reviews. As we are now heavily using the GitHub platform these code review shall take place on GitHub as well. 6.1.2 Precondition From now on no direct commits/pushes to master or any of the stable branches are allowed in general. Every code change - even one liners - have to be reviewed! 6.1.3 How will it work? 1. A developer will submit his changes on GitHub via a pull request. GitHub:help - using pull requests #. Within the pull request the developer could already name other developers (using @GitHubusername) and ask them for review. 2. Other developers (either named or at free will) have a look at the changes and are welcome to write comments within the comment field. #. In case the reviewer is okay with the changes and thinks all his comments and suggestions have been take into account a :+1 on the comment will signal a positive review. 3. Before a pull request will be merged into master or the corresponding branch at least 2 reviewers need to give :+1 score. 4. Our continuous integration server will give an additional indicator for the quality of the pull rquest. 6.1.4 Examples These are two examples that are considered to be good examples of how pull requests should be handled • https://github.com/owncloud/core/pull/121 • https://github.com/owncloud/core/pull/146 143 ownCloud Developer Manual, Release 6.0 6.1.5 Questions? Feel free to drop a line on the mailing list or join us on IRC. 6.2 Kanban Board This chapter contains a lot of information about the development process the ownCloud community tries to follow, so please take your time to digest all the information. In any case remember this page as the documentation on how it should be done. Nothing here is set in stone, so if you think something should be changed please discuss it on the mailing list. 6.2.1 Kanban Board = github issues + huboard We are using http://huboard.com to visualize ownCloud github issues as a kanban board (see: core, apps, mirall): As you may have noticed, the columns of the kanban board represent the life-cycle of an issue (be it a Bug or an Enhancement). An issue flows from the 1 - Backlog on the left to the 7 - To release column on the right and is not closed until it has been released. Instead we pull an issue to the next column by changing the label. 6.2.2 The Labels The following list shows what the labels mean in the life-cycle and will hopefully help you decide how to label an issue. Backlog Why do we have it? To keep us focused on finishing issues that we started, new issues will be hidden in this column. In huboard you can see the list of things that we could think about by clicking the small arrow in the top left corner of the concept column header. What does a developer think? “Maybe later.” When can I pull? Since this is the bucket for whatever might be done you should only pick issues from the backlog when there is no other issue that you can work on. It is more important to finish an issue currently on the Kanban board than to pull a new one into the flow because only released issues have a value to our users! 144 Chapter 6. Bugtracker ownCloud Developer Manual, Release 6.0 Who is Assigned? Either a maintainer feels directly responsible for the issue and assigns himself or the gatekeeper (the guys having a look at unassigned bugs) will try to determine the responsible developer. Concept Why do we have it? Our think before you act phase serves two purposes. A Bug is in the concept phase while we are trying to figure out why something is broken (analysis). An Enhancement is in the concept phase until we have decided how to implement it (design). What does a developer think? “I’ll write a Scenario for our BDD in Gherkin and post it as a comment. I can always look at the existing ones to get an inspiration how to phrase them as “Given . . . when . . . then . . . ““ When can I pull? As long as you think and discuss on how to implement an enhancement or how to solve a bug you should leave the concept label assigned. Two things should be documented in a comment to the issue before moving it to the “To develop” step: • At least one Scenario – written in Gherkin – that tells you and the tester when the issue is ready to be released. • A concept describing the planned implementation. This can be as simple as a “this just needs changes to the login screen css” or so complex that you link to a blog entry somewhere else. Who is Assigned? The maintainer that feels responsible for the issue. To Develop Why do we have it? Now that we have a plan, any developer can pick an issue from this column and start implementing it. If the issue is also marked with Junior Job this might be a good starting point for new developers. What does a developer think? “Nice! I can safely implement it that way because more than one person has put his brain to the task of coming up with a good solution. Here! Me! I’ll do it!” When can I pull? If you feel like diving into the code and getting your hands dirty you should look for issues with this label. In the comments, there should be a gherkin scenario to tell you when you are done and a concept describing how to implement it. Before you start move the issue to the “Developing” step by assigning the “4 – Developing” label. Who is Assigned? No one. Especially not if you are working on something else! Developing Why do we have it? This is where the magic happens. If it’s a Bug the fix will be submitted as a PULL REQUEST to the master or corresponding stable branch. If its an Enhancement code will be committed to a feature branch. What does a developer think? “You know, I’m at it. By the way, I’ll also write unit tests. When I’m done I’ll push the issue with a commit containing “push GH-#” where # is the issue number. If I have an idea of who should review it I can also notify them with @githubusername” When can I pull? As long as you are writing code for the issue or if any unit test fails you should leave the “4 – Developing” label assigned. Two things should have been implemented before moving the issue to the “To review” step: • The enhancement or bug in question • Unit tests for the changed and added code. Who is Assigned? The most active developer should assign himself. 6.2. Kanban Board 145 ownCloud Developer Manual, Release 6.0 To Review Why do we have it? Instead of directly committing to master we agree that a second set of eyes will spot bugs and increase our code quality and give us an opportunity to learn from each other. See also our Code Review Documentation What does a developer think? “I’ll check the Scenario described earlier works as expected. If necessary I’ll update the related Gherkin Scenarios. Jenkins will test the scenario on all kinds of platforms, webserver and database combinations with cucumber.” When can I pull? If you feel like making sure an issue works as expected you should look for issues with this label. In the comments you should find a gherkin scenario that can be used as a checklist for what to try. Before you start move the issue to the “Reviewing” step by assigning the “6 – Reviewing” label. Who is Assigned? No one. Especially not if you are working on something else! Reviewing Why do we have it? With the Gherkin Scenario from the Concept Phase reviewers have a checklist to test if a Bug has been solved and if an Enhancement works as expected. The most eager reviewer we have is Jenkins. When it comes to testing he soldiers on going through the different combinations of platform, webserver and database. What does a developer think? “Damn! If I had written the Gherkin Scenarios and Cucumber Step Definitions I could leave the task of testing this on the different combinations of platform, webserver and database to Jenkins. I’ll miss something when doing this manually.* When can I pull? As long as you are reviewing the issue the you should leave the “6 – Reviewing” label assigned. Before moving the issue to the “To review” step the issue should have been resolved, meaning that not only the issue has been implemented but also no other functionality has been broken. Who is Assigned? The most active reviewer should assign himself. To Release Why do we have it? This is a list of issues that will make it into the next release. It serves as a source for the changelog, as well as a reminder of the work we can already be proud of. What does a developer think? “Look at all the shiny things we will release with the next version of ownCloud!” When can I pull? This is the last step of the Kanban board. When the Release finally happens the issue will be closed and removed from the board. Who is Assigned? No one. While we stated before that said that we push issues to the next column, we can of course move the item back and forth arbitrarily. Basically you can drag the issue around in the huboard or just change the label when viewing the issue in the GitHub. 6.2.3 Reviewing considered impossible? How can you possibly review an issue when it requires you to test various combinations of browsers, platforms, databases and maybe even app combinations? Well, you can’t. But you can write a gherkin scenario that can be used to write an automated test that is executed by Jenkins on every commit to the main repositories. If for some reason Jenkins cannot be used for the review you will find yourself in the very uncomfortable situation where you release half tested code that will hopefully not eat user data. Seriously! Write gherkin scenarios! 146 Chapter 6. Bugtracker ownCloud Developer Manual, Release 6.0 6.2.4 Other Labels Priority Labels • Panic should be used with caution. It is reserved for Bugs that would result in the loss of files or other user data. An Enhancement marked as Panic is expected by ownCloud users for the next release. In either case an open Panic issue will prevent a release. • Attention is not as hard as Panic. But we really want this in the next release and will dedicate more effort for it. But if we think the issue is not ready for the next release we will postpone it to the next one. • Regression is something that worked in a previous release but is now not working as expected or missing. If a certain functionality is up for code refactoring, the developer should describe all possible use cases as a Gherkin scenarios beforehand, so that any scenarios that isn’t implemented before the required milestone can be marked as a regression. If a regression is found after a release, the reporter – or the developer triaging the issue – should describe the functionality as a Gherkin scenario and either fix it or assign it to the developer in charge of that part. App Labels In the apps repository there are labels like app:gallery and app:calendar. The app: prefix is used to allow developers to filter issues related to a specific app. Resolution Status • Fixed – Should be assigned to issues in to Release • Won’t fix – Reason is given as a comment • Duplicate – Corresponding bug is given in a comment (using #guthubissuenumber) Misc Labels • Needs info – Either from a developer or the bug reporter. This is nearly as severe as Panic, because no further action can be taken • L18n – A translation issue go see our transifex • Junior Job – The issue is considered a good starting point to get involved in ownCloud development 6.2.5 Milestones equal Releases Releases are planned via milestones which contain all the Enhancements and Bugs that we plan to release when the Deadline is met. When the Deadline approaches we will push new Enhancement request and less important bugs to the next milestone. This way a milestone will upon release contain all the issues that make up the changelog for the release. Furthermore, huboard allows us to filter the Kanban board by Milestone, making it especially easy to focus on the current Release. Thank you for helping ownCloud by reporting bugs. Before submitting an issue, please read Issue submission guidelines first. Bugs related to the core should be entered into the ownCloud Core Repository Issues. If your report is related to an application, their bugtrackers will be also found in the same guideline page. 6.2. Kanban Board 147 ownCloud Developer Manual, Release 6.0 148 Chapter 6. Bugtracker CHAPTER SEVEN HELP AND COMMUNICATION 7.1 Mailing lists Communicate via mail on our mailing lists. 7.2 IRC channels Chat with us on IRC. All channels are on irc.freenode.net • Setup: #owncloud • Testing: #owncloud-testing • Development: #owncloud-dev • Design: #owncloud-design 7.3 Maintainers • Contact a maintainer of a certain app or division 149 ownCloud Developer Manual, Release 6.0 150 Chapter 7. Help and Communication CHAPTER EIGHT ANDROID APPLICATION DEVELOPMENT This document will describe how to the use ownCloud Android Library. The ownCloud Android Library allows a developer to communicate with any ownCloud server; among the features included are file synchronization, upload and download of files, delete rename files and folders, etc. This library may be added to a project and seamlessly integrates any application with ownCloud. The tool needed is any IDE for Android. This guide includes some screenshots showing examples in Eclipse. 8.1 Library Installation 8.1.1 Obtaining the library The ownCloud Android library may be obtained from the following Github repository: https://github.com/owncloud/android-library Once obtained, this code should be compiled. The Github repository not only contains the library, but also a sample project, sample_client sample_client properties/android/librerias , which will assist in learning how to use the library. 8.1.2 Add the library to a project There are different methods to add an external library to a project, then we will describe one of them. 1. Compile the ownCloud Android Library 2. Define a dependency within your project. For that, access to Properties > Android > Library ** ** and click on add and select the ownCloud Android library 151 ownCloud Developer Manual, Release 6.0 Then all the public classes and methods of the library will be available for your own app. 8.2 Examples 8.2.1 Init the library Start using the library; it is needed to init the object mClient that will be in charge of keeping the communication with the server. 152 Chapter 8. Android Application Development ownCloud Developer Manual, Release 6.0 Code example public class MainActivity extends Activity implements OnRemoteOperationListener, OnDatatransferProgressListener { private OwnCloudClient mClient; private Handler mHandler = new Handler(); ... public void onCreate(Bundle savedInstanceState) { ... // Parse URI to the base URL of the ownCloud server Uri serverUri = Uri.parse(getString(R.string.server_base_url)); // Create client object to perform remote operations mClient = OwnCloudClientFactory.createOwnCloudClient( serverUri, this, // Activity or Service context true); 8.2.2 Set credentials Authentication on the app is possible by 3 different methods: • Basic authentication, user name and password • Bearer access token (oAuth2) • Cookie (SAML-based single-sign-on) Code example package com.owncloud.android.lib.common; public class OwnCloudClient extends HttpClient { ... // Set basic credentials client.setCredentials( OwnCloudCredentialsFactory.newBasicCredentials(username, password) ); // Set bearer access token client.setCredentials( OwnCloudCredentialsFactory.newBearerCredentials(accessToken) ); // Set SAML2 session token client.setCredentials( OwnCloudCredentialsFactory.newSamlSsoCredentials(cookie) ); } 8.2. Examples 153 ownCloud Developer Manual, Release 6.0 8.2.3 Create a folder Create a new folder on the cloud server, the info needed to be sent is the path of the new folder. Code example private void startFolderCreation(String newFolderPath) { CreateRemoteFolderOperation createOperation = new CreateRemoteFolderOperation(newFolderPath, false) createOperation.execute( mClient , this , mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof CreateRemoteFolderOperation) { if (result.isSuccess()) { // do your stuff here } } ... } 8.2.4 Read folder Get the content of an existing folder on the cloud server, the info needed to be sent is the path of the folder, in the example shown it has been asked the content of the root folder. As answer of this method, it will be received an array with all the files and folders stored in the selected folder. Code example private void startReadRootFolder() { ReadRemoteFolderOperation refreshOperation = new ReadRemoteFolderOperation(FileUtils.PATH_SEPARATOR // root folder refreshOperation.execute(mClient, this, mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof ReadRemoteFolderOperation) { if (result.isSuccess()) { List< RemoteFile > files = result.getData(); // do your stuff here } } ... } 8.2.5 Read file Get information related to a certain file or folder, information obtained is: filePath, filename, isDirectory, size and date. 154 Chapter 8. Android Application Development ownCloud Developer Manual, Release 6.0 Code example private void startReadFileProperties(String filePath) { ReadRemoteFileOperation readOperation = new ReadRemoteFileOperation(filePath); readOperation.execute(mClient, this, mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof ReadRemoteFileOperation) { if (result.isSuccess()) { RemoteFile file = result.getData()[0]; // do your stuff here } } ... } 8.2.6 Delete file or folder Delete a file or folder on the cloud server. The info needed is the path of folder/file to be deleted. Code example private void startRemoveFile(String filePath) { RemoveRemoteFileOperation removeOperation = new RemoveRemoteFileOperation(remotePath); removeOperation.execute( mClient , this , mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof RemoveRemoteFileOperation) { if (result.isSuccess()) { // do your stuff here } } ... } 8.2.7 Download a file Download an existing file on the cloud server. The info needed is path of the file on the server and targetDirectory, path where the file will be stored on the device. Code example private void startDownload(String filePath, File targetDirectory) { DownloadRemoteFileOperation downloadOperation = new DownloadRemoteFileOperation(filePath, targetDir downloadOperation.addDatatransferProgressListener(this); downloadOperation.execute( mClient, this, mHandler); } 8.2. Examples 155 ownCloud Developer Manual, Release 6.0 @Override public void onRemoteOperationFinish( RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof DownloadRemoteFileOperation) { if (result.isSuccess()) { // do your stuff here } } } @Override public void onTransferProgress( long progressRate, long totalTransferredSoFar, long totalToTransfer, mHandler.post( new Runnable() { @Override public void run() { // do your UI updates about progress here } }); } 8.2.8 Upload a file Upload a new file to the cloud server. The info needed is fileToUpload, path where the file is stored on the device, remotePath, path where the file will be stored on the server and mimeType. Code example private void startUpload (File fileToUpload, String remotePath, String mimeType) { UploadRemoteFileOperation uploadOperation = new UploadRemoteFileOperation( fileToUpload.getAbsolute uploadOperation.addDatatransferProgressListener(this); uploadOperation.execute(mClient, this, mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof UploadRemoteFileOperation) { if (result.isSuccess()) { // do your stuff here } } } @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, S mHandler.post( new Runnable() { @Override public void run() { // do your UI updates about progress here } }); } 156 Chapter 8. Android Application Development ownCloud Developer Manual, Release 6.0 8.2.9 Move a file or folder Move an exisintg file or folder to a different location in the ownCloud server. Parameters needed are the path to the file or folder to move, and the new path desired for it. The parent folder of the new path must exist in the server. When the parameter ‘overwrite’ is set to ‘true’, the file or folder is moved even if the new path is already used by a different file or folder. This one will be replaced by the former. Code example private void startFileMove(String filePath, String newFilePath, boolean overwrite) { MoveRemoteFileOperation moveOperation = new MoveRemoteFileOperation(filePath, newFilePath, overwrit moveOperation.execute( mClient , this , mHandler); } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof MoveRemoteFileOperation) { if (result.isSuccess()) { // do your stuff here } } ... } 8.2.10 Read shared items by link Get information about what files and folder are shared by link (the object mClient contains the information about the server url and account) Code example private void startAllSharesRetrieval() { GetRemoteSharesOperation getSharesOp = new GetRemoteSharesOperation(); getSharesOp.execute( mClient , this , mHandler); } @Override public void onRemoteOperationFinish( RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof GetRemoteSharesOperation) { if (result.isSuccess()) { ArrayList< OCShare > shares = new ArrayList< OCShare >(); for (Object obj: result.getData()) { shares.add(( OCShare) obj); } // do your stuff here } } } 8.2. Examples 157 ownCloud Developer Manual, Release 6.0 8.2.11 Get the share resources for a given file or folder Get information about what files and folder are shared by link on a certain folder. The info needed is filePath, path of the file/folder on the server, the Boolean variable, getReshares, come from the Sharing api, from the moment it is not in use within the ownCloud Android library. Code example private void startSharesRetrievalForFileOrFolder(String filePath, boolean getReshares) { GeteRemoteSharesForFileOperation operation = new GetRemoteSharesForFileOperation(filePath, getResha operation.execute( mClient, this, mHandler); } private void startSharesRetrievalForFilesInFolder(String folderPath, boolean getReshares) { GetRemoteSharesForFileOperation operation = new GetRemoteSharesForFileOperation(folderPath, getResh operation.execute( mClient, this, mHandler); } @Override public void onRemoteOperationFinish( RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof GetRemoteSharesForFileOperation) { if (result.isSuccess()) { ArrayList< OCShare > shares = new ArrayList< OCShare >(); for (Object obj: result.getData()) { shares.add(( OCShare) obj); } // do your stuff here } } } 8.2.12 Share link of file or folder Share a file or a folder from your cloud server by link. The info needed is filePath, the path of the item that you want to share and Password, this comes from the Sharing api, from the moment it is not in use within the ownCloud Android library. Code example private void startCreationOfPublicShareForFile(String filePath, String password) { CreateRemoteShareOperation operation = new CreateRemoteShareOperation(filePath, ShareType.PUBLIC_LI operation.execute( mClient , this , mHandler); } private void startCreationOfGroupShareForFile(String filePath, String groupId) { CreateRemoteShareOperation operation = new CreateRemoteShareOperation(filePath, ShareType.GROUP, gr operation.execute(mClient, this, mHandler); } private void startCreationOfUserShareForFile(String filePath, String userId) { CreateRemoteShareOperation operation = new CreateRemoteShareOperation(filePath, ShareType.USER, use operation.execute(mClient, this, mHandler); } 158 Chapter 8. Android Application Development ownCloud Developer Manual, Release 6.0 @Override public void onRemoteOperationFinish( RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof CreateRemoteShareOperation) { if (result.isSuccess()) { OCShare share = (OCShare) result.getData ().get(0); // do your stuff here } } } 8.2.13 Delete a share resource Stop sharing by link a file or a folder from your cloud server. The info needed is the object OCShare that you want to stop sharing by link. Code example private void startShareRemoval(OCShare share) { RemoveRemoteShareOperation operation = new RemoveRemoteShareOperation((int) share.getIdRemoteShared operation.execute( mClient, this, mHandler); } @Override public void onRemoteOperationFinish( RemoteOperation operation, RemoteOperationResult result) { if (operation instanceof RemoveRemoteShareOperation) { if (result.isSuccess()) { // do your stuff here } } } 8.2.14 Tips • Credentials must be set before calling any method • Paths must not be on URL Encoding • Correct path: http://www.myowncloudserver.com/owncloud/remote.php/webdav/PopMusic • Wrong path: http://www.myowncloudserver.com/owncloud/remote.php/webdav/Pop%20Music/ • There are some forbidden characters to be used in folder and files names on the server, same on the ownCloud Android Library “”,”/”,”<”,”>”,”:”,”“”,”|”,”?”,”*” • Upload and download actions may be cancelled thanks to the objects uploadOperation.cancel(), downloadOperation.cancel() • Unit tests, before launching unit tests you have to enter your account information (server url, user and password) on TestActivity.java 8.2. Examples 159 ownCloud Developer Manual, Release 6.0 160 Chapter 8. Android Application Development CHAPTER NINE IOS APPLICATION DEVELOPMENT This document will describe how to the use ownCloud iOS library. The ownCloud iOS library for iOS allows a developer to communicate with any ownCloud server; among the features included are file synchronization, upload and download of files, delete rename and move of files and folders and share files or folders by link among others. This library may be added to a project and seamlessly integrates any application with ownCloud. The tool needed is Xcode 5, this guide includes some screenshots showing examples in Xcode 5. 9.1 Library Installation 9.1.1 Obtaining the library The ownCloud iOS library may be obtained from the following Github repository: [email protected]:owncloud/ios-library.git Once obtained, this code should be compiled with Xcode 5. The Github repository not only contains the library, ownCloud iOS library, but also contains a sample project, OCLibraryExample, which will assist in learning how to use the library. 9.1.2 Add the library to a project There are two methods to add this library to a project. • Reference the headers and library binary file (.a) directly. • Include the library as a subproject. Which method to choose depends on user preference as well as whether the source code and project file of the static library are available. Reference headers and library binary files Follow these steps if this is the desired method. 1. Compile the ownCloud iOS library and run the project. A libownCloudiOS.a file will be generated. The following files are required: Library file • libownCloudiOS.a (Library) 161 ownCloud Developer Manual, Release 6.0 Library Classes • OCCommunication.h (Accessors) Import in the communication class • OCErrorMsg.h (Error Messages) Import in the communication class • OCFileDto.h and OCFileDto.m (File/Folder object) Import when using • readFolder and readFile methods • OCFrameworkConstants.h (Customize constants) 2. Add the library file to the project. From the “Build Phases” tab, scroll to “Link binary files” and select the ‘+’ to add a library. Select the library file. 3. Add the path of the library header files. Under the “Build Settings” tab, select the target library and add the path in the “Header Search Paths” field. 162 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 4. Remaining in the “Build Setting” tab, add the flag -Obj-C under the “Other Linker Flags” option. At this stage, the library is included on your project and you can start communicating with the ownCloud server. 9.1. Library Installation 163 ownCloud Developer Manual, Release 6.0 Include the library as a subproject Follow these steps if this is the desired method. 5. Add the file ownCloud iOS library.xcodeproj to the project via drag and drop. 6. Within the project, navigate to the “Build Phases” tab. Under the “Target Dependencies” section, select the ‘+’ and choose the library target. 164 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 7. Link the library file to the project target. Under the “Build Phases” tab, select the ‘+’ under the “Link Binary with Libraries” section and select the library file. 9.1. Library Installation 165 ownCloud Developer Manual, Release 6.0 8. Add the flag -Obj-C to “Other Linker Flags” under the project target on the “Build Settings” tab. 9. Finally add the path of the library headers. Under the “Build Settings” tab, add the path under the “Header Search Paths” option. 9.1.3 Sources • Creating a static library in iOS tutorial (raywenderlich.com) 166 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 • Apple iOS static library documentation 9.2 Examples 9.2.1 Init the library Start using the library, it is needed to init the object OCCommunication. We recommend using the singleton method in the AppDelegate class in order to use the ownCloud iOS library. Code example #import "OCCommunication.h" + (OCCommunication *)sharedOCCommunication { static OCCommunication* sharedOCCommunication = nil; if (sharedOCCommunication == nil) { sharedOCCommunication = [ [ OCCommunicationalloc] init ]; } return sharedOCCommunication; } Also could happen that you need to overwrite the class AFURLSessionManager to manage SSL Certificates #import "OCCommunication.h" + (OCCommunication*)sharedOCCommunication { static OCCommunication* sharedOCCommunication = nil; if (sharedOCCommunication == nil) { //Network Upload queue for NSURLSession (iOS 7) NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurat configuration.HTTPMaximumConnectionsPerHost = 1; configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; OCURLSessionManager *uploadSessionManager = [[OCURLSessionManager alloc] initWithSessionConfigura [uploadSessionManager.operationQueue setMaxConcurrentOperationCount:1]; [uploadSessionManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallenge return NSURLSessionAuthChallengePerformDefaultHandling; }]; sharedOCCommunication = [[OCCommunication alloc] initWithUploadSessionManager:uploadSessionManage } return sharedOCCommunication; } 9.2.2 Set credentials Authentication on the app is possible by 3 different methods: 9.2. Examples 167 ownCloud Developer Manual, Release 6.0 • Basic authentication, user name and password • Cookie • Token (oAuth) Code example #Basic authentication, user name and password [[ AppDelegate sharedOCCommunication ] setCredentialsWithUser : userName andPassword : password ]; #Authentication with cookie [[ AppDelegate sharedOCCommunication ] setCredentialsWithCookie : cookie ]; #Authentication with token [[ AppDelegate sharedOCCommunication ] setCredentialsOauthWithToken : token ]; 9.2.3 Create a folder Create a new folder on the cloud server, the info needed to be sent is the path of the new folder. Code example [[ AppDelegate sharedOCCommunication ] createFolder :path onCommunication : [ AppDelegate sharedOCCom successRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer) { //Folder Created } failureRequest :^( NSHTTPURLResponse *response, NSError *error) { //Failure switch (response.statusCode) { case kOCErrorServerUnauthorized : //Bad credentials break; case kOCErrorServerForbidden : //Forbidden break; case kOCErrorServerPathNotFound : //Not Found break; case kOCErrorServerTimeout : //timeout break; default: //default break; } } errorBeforeRequest :^( NSError *error) { //Error before request 168 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 if (error.code == OCErrorForbidenCharacters) { //Forbidden characters } else { //Other error } }]; 9.2.4 Read folder Get the content of an existing folder on the cloud server, the info needed to be sent is the path of the folder. As answer of this method, it will be received an array with all the files and folders stored in the selected folder. Code example [[ AppDelegate sharedOCCommunication] readFolder:path onCommunication:[ AppDelegate sharedOCCommunica successRequest:^( NSHTTPURLResponse *response, NSArray *items, NSString *redirectedServer) { //Success for ( OCFileDto * ocFileDto in items) { NSLog( @"item path: %@%@" , ocFileDto.filePath, ocFileDto.fileName); } } failureRequest:^( NSHTTPURLResponse *response, NSError *error) { //Failure switch (response.statusCode) { case kOCErrorServerPathNotFound : //Path not found break; case kOCErrorServerUnauthorized : //Bad credentials break; case kOCErrorServerForbidden : //Forbidden break; case kOCErrorServerTimeout : //Timeout break ; default : break; } }]; 9.2.5 Read file Get information related to a certain file or folder. Although, more information can be obtained, the library only gets the eTag. Other properties of the file or folder may be obtained: filePath, filename, isDirectory, size and date 9.2. Examples 169 ownCloud Developer Manual, Release 6.0 Code example [[ AppDelegate sharedOCCommunication ] readFile :path onCommunication :[ AppDelegate sharedOCCommunic successRequest :^( NSHTTPURLResponse *response, NSArray *items, NSString *redirectedServer) { OCFileDto *ocFileDto = [items objectAtIndex : 0 ]; NSLog ( @"item etag: %lld" , ocFileDto. etag); } failureRequest :^( NSHTTPURLResponse *response, NSError *error) { switch (response.statusCode) { case kOCErrorServerPathNotFound: //Path not found break; case kOCErrorServerUnauthorized: //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorServerTimeout: //Timeout break; default: break; } }]; 9.2.6 Move file or folder Move a file or folder from their current path to a new one on the cloud server. The info needed is the origin path and the destiny path. Code example [[ AppDelegate sharedOCCommunication ] moveFileOrFolder :sourcePath toDestiny :destinyPath onCommunic successRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer) { //File/Folder moved or renamed } failureRequest :^( NSHTTPURLResponse *response, NSError *error) { //Failure switch (response.statusCode) { case kOCErrorServerPathNotFound: //Path not found break; case kOCErrorServerUnauthorized: //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorServerTimeout: //Timeout break; default: 170 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 break; } } errorBeforeRequest :^( NSError *error) { if (error.code == OCErrorMovingTheDestinyAndOriginAreTheSame) { //The destiny and the origin are the same } else if (error.code == OCErrorMovingFolderInsideHimself) { //Moving folder inside himself } else if (error.code == OCErrorMovingDestinyNameHaveForbiddenCharacters) { //Forbidden Characters } else { //Default } }]; 9.2.7 Delete file or folder Delete a file or folder on the cloud server. The info needed is the path to delete. Code example [[ AppDelegate sharedOCCommunication ] deleteFileOrFolder :path onCommunication :[ AppDelegate sharedOCCommunication ] successRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer) { //File or Folder deleted } failureRequest :^( NSHTTPURLResponse *response, NSError *error) { switch (response.statusCode) { case kOCErrorServerPathNotFound: //Path not found break; case kOCErrorServerUnauthorized: //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorServerTimeout: //Timeout break; default: break; } }]; 9.2. Examples 171 ownCloud Developer Manual, Release 6.0 9.2.8 Download a file Download an existing file on the cloud server. The info needed is the server URL, path of the file on the server and localPath, path where the file will be stored on the device and a boolean to indicate if is neccesary to use LIFO queue or FIFO. Code example NSOperation *op = nil; op = [[ AppDelegate sharedOCCommunication ] downloadFile :remotePath toDestiny :localPath withLIFOSys progressDownload :^( NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRe //Calculate percent float percent = ( float)totalBytesRead / totalBytesExpectedToRead; NSLog ( @"Percent of download: %f" , percent); } successRequest :^(NSHTTPURLResponse *response, NSString *redirectedServer) { //Download complete } failureRequest :^(NSHTTPURLResponse *response, NSError *error) { switch (response. statusCode) { case kOCErrorServerUnauthorized: //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorProxyAuth: //Proxy access required break; case kOCErrorServerPathNotFound: //Path not found break; default: //Default break; } } shouldExecuteAsBackgroundTaskWithExpirationHandler :^{ [op cancel ]; }]; 9.2.9 Download a file with background session Download an existing file storaged on the cloud server using background session, only supported by iOS 7 and higher. The info needed is, the server URL: path where the file is stored on the server; localPath: path where the file will be stored on the device; and NSProgress: object where get the callbacks of the upload progress. To get the callbacks of the progress is needed use a KVO in the progress object. We add the code in this example of the call to set the KVO and the method where catch the notifications. 172 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 Code example NSURLSessionDownloadTask *downloadTask = nil; NSProgress *progress = nil; downloadTask = [_sharedOCCommunication downloadFileSession:serverUrl toDestiny:localPath defaultPrior //Upload complete } failureRequest:^(NSURLResponse *response, NSError *error) { switch (error.code) { case kCFURLErrorUserCancelledAuthentication: //Authentication cancelled break; default: switch (response.statusCode) { case kOCErrorServerUnauthorized : //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorProxyAuth: //Proxy access required break; case kOCErrorServerPathNotFound: //Path not found break; default: //Default break; } break; } }]; // Observe fractionCompleted using KVO [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew cont //Method to catch the progress notifications with callbacks - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change { if ([keyPath isEqualToString:@"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) NSProgress *progress = (NSProgress *)object; float percent = roundf (progress.fractionCompleted * 100); //We make it on the main thread because we came from a delegate dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Progress is %f", percent); }); } } 9.2. Examples 173 ownCloud Developer Manual, Release 6.0 9.2.10 Set callback when background download task finishes Method to set callbacks of the pending download transfers when the app starts. It’s used when there are pendings download background transfers. The block is executed when a pending background task finishes. Code example [[AppDelegate sharedOCCommunication] setDownloadTaskComleteBlock:^NSURL *(NSURLSession *session, NSUR }]; 9.2.11 Set progress callback with pending background download tasks Method to set progress callbacks of the pending download transfers. It’s used when there are pendings background download transfers. The block is executed when a pending task get a input porgress. Code example [[AppDelegate sharedOCCommunication] setDownloadTaskDidGetBodyDataBlock:^(NSURLSession *session, NSUR }]; 9.2.12 Upload a file Upload a new file to the cloud server. The info needed is localPath, path where the file is stored on the device and server URL, path where the file will be stored on the server. Code example NSOperation *op = nil; op = [[ AppDelegate sharedOCCommunication ] uploadFile :localPath toDestiny : remotePath onCommunicat progressUpload :^( NSUInteger bytesWrote, long long totalBytesWrote, long long totalBytesExpectedToWr //Calculate upload percent if ( totalBytesExpectedToRead/1024 != 0) { if ( bytesWrote > 0) { float percent = totalBytesWrote* 100 / totalBytesExpectedToRead; NSLog ( @"Percent: %f" , percent); } } } successRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer) { //Upload complete } failureRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer, NSError *error) { switch (response. statusCode) { case kOCErrorServerUnauthorized : //Bad credentials break; 174 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 case kOCErrorServerForbidden: //Forbidden break; case kOCErrorProxyAuth: //Proxy access required break; case kOCErrorServerPathNotFound: //Path not found break; default: //Default break; } } failureBeforeRequest :^( NSError *error) { switch (error.code) { case OCErrorFileToUploadDoesNotExist: //File does not exist break; default: //Default break; } } shouldExecuteAsBackgroundTaskWithExpirationHandler :^{ [op cancel]; }]; 9.2.13 Upload a file with background session Upload a new file to the cloud server using background session, only supported by iOS 7 and higher. The info needed is localPath, path where the file is stored on the device and server URL, path where the file will be stored on the server and NSProgress object where get the callbacks of the upload progress. To get the callbacks of the progress is needed use a KVO in the progress object. We add the code in this example of the call to set the KVO and the method where catch the notifications. Code example NSURLSessionUploadTask *uploadTask = nil; NSProgress *progress = nil; uploadTask = [[AppDelegate sharedOCCommunication] uploadFileSession:localPath toDestiny:remotePath on //Upload complete } failureRequest:^(NSURLResponse *response, NSString *redirectedServer, NSError *error) { switch (response.statusCode) { case kOCErrorServerUnauthorized : //Bad credentials break; case kOCErrorServerForbidden: //Forbidden break; case kOCErrorProxyAuth: //Proxy access required 9.2. Examples 175 ownCloud Developer Manual, Release 6.0 break; case kOCErrorServerPathNotFound: //Path not found break; default: //Default break; } }]; // Observe fractionCompleted using KVO [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew cont //Method to catch the progress notifications with callbacks - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change { if ([keyPath isEqualToString:@"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) NSProgress *progress = (NSProgress *)object; float percent = roundf (progress.fractionCompleted * 100); //We make it on the main thread because we came from a delegate dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Progress is %f", percent); }); } } 9.2.14 Set callback when background task finish Method to set callbacks of the pending transfers when the app starts. It’s used when there are pendings background transfers. The block is executed when a pending background task finished. Code example [[AppDelegate sharedOCCommunication] setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTas }]; 9.2.15 Set progress callback with pending background tasks Method to set progress callbacks of the pending transfers. It’s used when there are pendings background transfers. The block is executed when a pending task get a input porgress. 176 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 Code example [[AppDelegate sharedOCCommunication] setTaskDidSendBodyDataBlock:^(NSURLSession *session, NSURLSessio }]; 9.2.16 Check if the server supports Sharing api The Sharing API is included in ownCloud 5.0.13 and greater versions. The info needed is activeUser.url, the server URL that you want to check. Code Example [[ AppDelegate sharedOCCommunication ] hasServerShareSupport :_activeUser.url onCommunication :[ AppD successRequest :^( NSHTTPURLResponse *response, BOOL hasSupport, NSString *redirectedServer) { } failureRequest :^( NSHTTPURLResponse *response, NSError *error){ } }]; 9.2.17 Read shared all items by link Get information about what files and folder are shared by link. The info needed is Path, the server URL that you want to check. Code example [[ AppDelegate sharedOCCommunication ] readSharedByServer :path onCommunication :[ AppDelegate shared successRequest :^( NSHTTPURLResponse *response, NSArray *items, NSString *redirectedServer) { NSLog ( @"Item: %d" , items); } failureRequest :^( NSHTTPURLResponse *response, NSError *error){ NSLog ( @"error: %@" , error); NSLog ( @"Operation error: %d" , response.statusCode); }]; 9.2.18 Read shared items by link of a path Get information about what files and folder are shared by link in a specific path. The info needed is the server URL that you want to check and the specific path tha you want to check. 9.2. Examples 177 ownCloud Developer Manual, Release 6.0 Code example [[AppDelegate sharedOCCommunication] readSharedByServer:serverPath andPath:path onCommunication:[AppD NSLog ( @"Item: %d" , items); } failureRequest:^(NSHTTPURLResponse *response, NSError *error) { NSLog ( @"error: %@" , error); NSLog ( @"Operation error: %d" , response.statusCode); }]; 9.2.19 Share link of file or folder Share a file or a folder from your cloud server by link. The info needed is Path, your server URL and the path of the item that you want to share (for example /folder/file.pdf) Code example [[ AppDelegate sharedOCCommunication ] shareFileOrFolderByServer :path andFileOrFolderPath :itemPath successRequest :^( NSHTTPURLResponse *response, NSString *token, NSString *redirectedServer) { NSString *sharedLink = [ NSString stringWithFormat:@ ‘path/public.php?service=files&t=%@ <mailto:path , token]; } failureRequest :^( NSHTTPURLResponse *response, NSError *error){ [ _delegate endLoading ]; DLog ( @”error.code: %d” , error. code); DLog (@”server.error: %d”, response. statusCode); int code = response. statusCode ; if (error.code == kOCErrorServerPathNotFound) { } switch (code) { case kOCErrorServerPathNotFound: //File to share not exists break; case kOCErrorServerUnauthorized: //Error login break; case kOCErrorServerForbidden: //Permission error break; case kOCErrorServerTimeout: //Not possible to connect to server break; default: if (error.code == kOCErrorServerPathNotFound) { //File to share not exists } else { //Not possible to connect to the server } break; 178 Chapter 9. iOS Application Development ownCloud Developer Manual, Release 6.0 } }]; } NSLog ( @"error: %@" , error); NSLog ( @"Operation error: %d" , response.statusCode); }]; 9.2.20 Unshare a folder or file by link Stop sharing by link a file or a folder from your cloud server. The info needed is Path, your server URL and the Id of the item that you want to Unshare. Before unsharing an item, you have to read the shared items on the selected server, using the method “ readSharedByServer ” so that you get the array “items” with all the shared elements. These are objects OCShareDto, one of their properties is idRemoteShared, parameter needed to unshared an element. Code example [[ AppDelegate sharedOCCommunication ] unShareFileOrFolderByServer :path andIdRemoteSharedShared :sha successRequest :^( NSHTTPURLResponse *response, NSString *redirectedServer) { //File unshared } failureRequest :^( NSHTTPURLResponse *response, NSError *error){ //Error } ]; 9.2.21 Check if file of folder is shared Check if a specific file or folder is shared in your cloud server. Teh info need is Path, your server URL and the Id of the item that you want. Before check an item, you have to read the shared items on the selected server, using the method “ readSharedByServer ” so that you get the array “items” with all the shared elements. These are objects OCShareDto, one of their properties is idRemoteShared, parameter needed to unshared an element. Code example [[AppDelegate sharedOCCommunication] isShareFileOrFolderByServer:path andIdRemoteShared:_shareDto.idR //File/Folder is shared } failureRequest:^(NSHTTPURLResponse *response, NSError *error) { //File/Folder is not shared }]; 9.2. Examples 179 ownCloud Developer Manual, Release 6.0 9.2.22 Tips • Credentials must be set before calling any method • Paths must not be on URL Encoding • Correct path: http://www.myowncloudserver.com/owncloud/remote.php/webdav/Pop_Music/ • Wrong path: http://www.myowncloudserver.com/owncloud/remote.php/webdav/Pop%20Music/ • There are some forbidden characters to be used in folder and files names on the server, same on the ownCloud iOS library “”, “/”,”<”,”>”,”:”,”“”,””,”?”,”*” • To move a folder the origin path and the destination path must end with “/” • To move a file the origin path and the destination path must not end with “/” • Upload and download actions may be cancelled thanks to the object “NSOperation” • Unit tests, before launching unit tests you have to enter your account information (server url, user and password) on OCCommunicationLibTests.m 180 Chapter 9. iOS Application Development CHAPTER TEN INDICES AND TABLES • genindex 181 ownCloud Developer Manual, Release 6.0 182 Chapter 10. Indices and tables PHP NAMESPACE INDEX o OC\Files, 48 OCA\AppFramework, 96 OCA\AppFramework\Controller, 97 OCA\AppFramework\Core, 98 OCA\AppFramework\Db, 104 OCA\AppFramework\DependencyInjection, 107 OCA\AppFramework\Http, 112 OCA\AppFramework\Middleware, 116 OCA\AppFramework\Middleware\Security, 117 OCA\AppFramework\Middleware\Twig, 118 OCA\AppFramework\Utility, 122 OCP, 40 183 ownCloud Developer Manual, Release 6.0 184 PHP Namespace Index INDEX Symbols method), 116 __construct() (OCA\AppFramework\Middleware\Security\SecurityExceptio _Model() (class), 83 method), 118 _Request() (class), 81 __construct() (OCA\AppFramework\Middleware\Security\SecurityMiddlew __call() (OCA\AppFramework\Db\Entity method), 106 method), 117 __construct() (OCA\AppFramework\Controller\Controller __construct() (OCA\AppFramework\Middleware\Twig\TwigMiddleware method), 97 method), 118 __construct() (OCA\AppFramework\Core\API method), __construct() (OCA\AppFramework\Utility\FaviconFetcher 98 method), 121 __construct() (OCA\AppFramework\Db\DoesNotExistException __construct() (OCA\AppFramework\Utility\MethodAnnotationReader method), 104 method), 121 __construct() (OCA\AppFramework\Db\Mapper __construct() (OC_OCS_Result method), 41 method), 104 __construct() (OC_Template method), 41 __construct() (OCA\AppFramework\Db\MultipleObjectsReturnedException __construct() (OC\Files\View method), 48 method), 104 __get() (OCA\AppFramework\Http\Request method), __construct() (OCA\AppFramework\DependencyInjection\DIContainer 114 method), 107 __construct() (OCA\AppFramework\Http\Dispatcher __isset() (OCA\AppFramework\Http\Request method), 114 method), 107 __set() (OCA\AppFramework\Http\Request method), __construct() (OCA\AppFramework\Http\DownloadResponse 114 method), 109 __unset() (OCA\AppFramework\Http\Request method), __construct() (OCA\AppFramework\Http\ForbiddenResponse 114 method), 114 __construct() (OCA\AppFramework\Http\Http method), A 108 __construct() (OCA\AppFramework\Http\JSONResponse abs_url() (built-in function), 71 activateNavigationEntry() method), 109 (OCA\AppFramework\Core\API method), __construct() (OCA\AppFramework\Http\NotFoundResponse 99 method), 114 __construct() (OCA\AppFramework\Http\RedirectResponseadd() (built-in function), 83 add3rdPartyScript() (OCA\AppFramework\Core\API method), 110 method), 99 __construct() (OCA\AppFramework\Http\Request add3rdPartyStyle() (OCA\AppFramework\Core\API method), 113 method), 99 __construct() (OCA\AppFramework\Http\TemplateResponse addHeader() (OC_Template method), 42 method), 111 addHeader() (OCA\AppFramework\Http\Response __construct() (OCA\AppFramework\Http\TextDownloadResponse method), 108 method), 112 __construct() (OCA\AppFramework\Http\TextResponse addNavigationEntry() (OCA\AppFramework\Core\API method), 99 method), 111 (OCA\AppFramework\Core\API __construct() (OCA\AppFramework\Http\TwigResponse addRegularTask() method), 103 method), 112 addScript() (OCA\AppFramework\Core\API method), 99 __construct() (OCA\AppFramework\Middleware\MiddlewareDispatcher addStyle() (OCA\AppFramework\Core\API method), 99 185 ownCloud Developer Manual, Release 6.0 addType() (OCA\AppFramework\Db\Entity method), method), 108 107 chroot() (OC\Files\View method), 48 afterController() (OCA\AppFramework\Middleware\Middleware clear() (built-in function), 84 method), 115 clearHook() (OCA\AppFramework\Core\API method), afterController() (OCA\AppFramework\Middleware\MiddlewareDispatcher 103 method), 116 columnToProperty() (OCA\AppFramework\Db\Entity afterController() (OCA\AppFramework\Middleware\Twig\TwigMiddleware method), 106 method), 118 connectHook() (OCA\AppFramework\Core\API afterException() (OCA\AppFramework\Middleware\Middleware method), 103 method), 115 Controller (class in OCA\AppFramework\Controller), 97 afterException() (OCA\AppFramework\Middleware\MiddlewareDispatcher ControllerTestUtility (class in method), 116 OCA\AppFramework\Utility), 119 afterException() (OCA\AppFramework\Middleware\Security\SecurityMiddleware cookie() (OCA\AppFramework\Controller\Controller method), 117 method), 98 allowedKeys (OCA\AppFramework\Http\Request prop- copy() (OC\Files\View method), 51 erty), 113 count() (OCA\AppFramework\Http\Request method), API (class in OCA\AppFramework\Core), 98 113 API (class in OCP), 40 api (OCA\AppFramework\Controller\Controller prop- D erty), 97 data (OCA\AppFramework\Http\JSONResponse propapi (OCA\AppFramework\Http\TemplateResponse property), 109 erty), 110 delete() (built-in function), 82 api (OCA\AppFramework\Utility\MapperTestUtility delete() (OCA\AppFramework\Db\Mapper method), 105 property), 120 deleteAll() (OC\Files\View method), 51 App (class in OCA\AppFramework), 96 detectFormfactor() (OC_Template method), 43 append() (OC_Template method), 42 DIContainer (class in OCA\AppFramework\DependencyInjection), appName (OCA\AppFramework\Http\TemplateResponse 107 property), 110 dispatch() (OCA\AppFramework\Http\Dispatcher assertAnnotations() (OCA\AppFramework\Utility\ControllerTestUtility method), 107 method), 119 Dispatcher (class in OCA\AppFramework\Http), 107 assertHeaders() (OCA\AppFramework\Utility\ControllerTestUtility DoesNotExistException (class in method), 119 OCA\AppFramework\Db), 104 assign() (OC_Template method), 42 DownloadResponse (class in OCA\AppFramework\Http), 109 B E beforeController() (OCA\AppFramework\Middleware\Middleware emitHook() (OCA\AppFramework\Core\API method), method), 115 103 beforeController() (OCA\AppFramework\Middleware\MiddlewareDispatcher Entity (class in OCA\AppFramework\Db), 106 method), 116 env() (OCA\AppFramework\Controller\Controller beforeController() (OCA\AppFramework\Middleware\Security\SecurityMiddleware method), 97 method), 117 error (OCA\AppFramework\Http\JSONResponse propbeforeEach() (OCA\AppFramework\Utility\MapperTestUtility erty), 109 method), 120 execute() (OCA\AppFramework\Db\Mapper method), beforeOutput() (OCA\AppFramework\Middleware\Middleware 105 method), 115 extractFromPage() (OCA\AppFramework\Utility\FaviconFetcher beforeOutput() (OCA\AppFramework\Middleware\MiddlewareDispatcher method), 121 method), 117 beforeOutput() (OCA\AppFramework\Middleware\Twig\TwigMiddleware F method), 118 buildURL() (OCA\AppFramework\Utility\FaviconFetcher FaviconFetcher (class in OCA\AppFramework\Utility), method), 122 121 fetch() (OCA\AppFramework\Utility\FaviconFetcher C method), 121 cacheFor() (OCA\AppFramework\Http\Response fetchPage() (OC_Template method), 43 186 Index ownCloud Developer Manual, Release 6.0 file_exists() (OC\Files\View method), 50 file_get_contents() (OC\Files\View method), 51 file_put_contents() (OC\Files\View method), 51 filemtime() (OC\Files\View method), 50 filesize() (OC\Files\View method), 50 filetype() (OC\Files\View method), 50 findOneQuery() (OCA\AppFramework\Db\Mapper method), 105 fopen() (OC\Files\View method), 51 ForbiddenResponse (class in OCA\AppFramework\Http), 114 free_space() (OC\Files\View method), 52 fromParams() (OCA\AppFramework\Db\Entity method), 106 fromRow() (OCA\AppFramework\Db\Entity method), 106 fromTmpFile() (OC\Files\View method), 51 getMountPoint() (OC\Files\View method), 49 getOwner() (OC\Files\View method), 53 getParams() (OCA\AppFramework\Controller\Controller method), 97 getParams() (OCA\AppFramework\Http\JSONResponse method), 110 getParams() (OCA\AppFramework\Http\TemplateResponse method), 111 getPath() (OC\Files\View method), 53 getRedirectURL() (OCA\AppFramework\Http\RedirectResponse method), 110 getRelativePath() (OC\Files\View method), 48 getRenderAs() (OCA\AppFramework\Http\TemplateResponse method), 111 getRequest() (OCA\AppFramework\Utility\ControllerTestUtility method), 119 getRoot() (OC\Files\View method), 48 getStatus() (OCA\AppFramework\Http\Response G method), 109 getStatusHeader() (OCA\AppFramework\Http\Http get() (built-in function), 82, 84 method), 108 getAbsolutePath() (OC\Files\View method), 48 (OCA\AppFramework\Core\API getAbsoluteURL() (OCA\AppFramework\Core\API getSystemValue() method), 99 method), 101 getTableName() (OCA\AppFramework\Db\Mapper getAll() (built-in function), 84 method), 104 getAPIMock() (OCA\AppFramework\Utility\TestUtility getTemplate() (OCA\AppFramework\Core\API method), method), 120 102 getAppName() (OCA\AppFramework\Core\API getTemplateName() (OCA\AppFramework\Http\TemplateResponse method), 99 method), 111 getAppValue() (OCA\AppFramework\Core\API method), getTime() (OCA\AppFramework\Utility\TimeFactory 100 method), 122 getById() (built-in function), 83 getTrans() (OCA\AppFramework\Core\API method), 100 getCore() (OCA\AppFramework\Utility\SimplePieAPIFactory getUpdatedFields() (OCA\AppFramework\Db\Entity method), 122 method), 106 getDirectoryContent() (OC\Files\View method), 52 getUploadedFile() (OCA\AppFramework\Controller\Controller getETag() (OC\Files\View method), 53 method), 97 getETag() (OCA\AppFramework\Http\Response getUrlContent() (OCA\AppFramework\Core\API method), 109 method), 103 getFile() (OCA\AppFramework\Utility\SimplePieAPIFactory getUserId() (OCA\AppFramework\Core\API method), 99 method), 122 getUserValue() (OCA\AppFramework\Core\API getFileInfo() (OC\Files\View method), 52 method), 100 getFormFactorExtension() (OC_Template method), 43 getHeaders() (OCA\AppFramework\Http\Response H method), 108 getInsertId() (OCA\AppFramework\Core\API method), hasAnnotation() (OCA\AppFramework\Utility\MethodAnnotationReader method), 121 101 getLastModified() (OCA\AppFramework\Http\Response hash() (OC\Files\View method), 52 hasUpdated() (OC\Files\View method), 52 method), 109 headers (OCA\AppFramework\Http\Http property), 108 getLocalFile() (OC\Files\View method), 49 getLocalFilePath() (OCA\AppFramework\Core\API html_select_options() (global function), 45 Http (class in OCA\AppFramework\Http), 108 method), 102 human_file_size() (global function), 45 getLocalFolder() (OC\Files\View method), 49 getMiddlewares() (OCA\AppFramework\Middleware\MiddlewareDispatcher I method), 116 getMimeType() (OC\Files\View method), 52 id (OCA\AppFramework\Db\Entity property), 106 Index 187 ownCloud Developer Manual, Release 6.0 image_path() (built-in function), 72 MiddlewareDispatcher (class in image_path() (global function), 45 OCA\AppFramework\Middleware), 116 imagePath() (OCA\AppFramework\Core\API method), mimetype_icon() (global function), 46 101 mkdir() (OC\Files\View method), 49 inc() (OC_Template method), 43 MultipleObjectsReturnedException (class in insert() (OCA\AppFramework\Db\Mapper method), 105 OCA\AppFramework\Db), 104 is_dir() (OC\Files\View method), 49 N is_file() (OC\Files\View method), 50 isAdminUser() (OCA\AppFramework\Core\API NotFoundResponse (class in OCA\AppFramework\Http), method), 102 114 isAjax() (OCA\AppFramework\Middleware\Security\SecurityException O method), 118 isAppEnabled() (OCA\AppFramework\Core\API OC_OCS_Result (class), 41 method), 102 OC_Template (class), 41 isCreatable() (OC\Files\View method), 50 OC\Files (namespace), 48 isDeletable() (OC\Files\View method), 50 OCA\AppFramework (namespace), 96 isImage() (OCA\AppFramework\Utility\FaviconFetcher OCA\AppFramework\Controller (namespace), 97 method), 121 OCA\AppFramework\Core (namespace), 98 isLoggedIn() (OCA\AppFramework\Core\API method), OCA\AppFramework\Db (namespace), 104, 106 102 OCA\AppFramework\DependencyInjection (namesisReadable() (OC\Files\View method), 50 pace), 107 isSharable() (OC\Files\View method), 50 OCA\AppFramework\Http (namespace), 107–114 isSubAdminUser() (OCA\AppFramework\Core\API OCA\AppFramework\Middleware (namespace), 115, 116 method), 102 OCA\AppFramework\Middleware\Security (namespace), isUpdatable() (OC\Files\View method), 50 117, 118 items (OCA\AppFramework\Http\Request property), 113 OCA\AppFramework\Middleware\Twig (namespace), 118 J OCA\AppFramework\Utility (namespace), 119–122 JSONResponse (class in OCA\AppFramework\Http), 109 OCP (namespace), 40 offsetExists() (OCA\AppFramework\Http\Request L method), 113 offsetGet() (OCA\AppFramework\Http\Request method), link_to() (built-in function), 73 113 link_to() (global function), 46 offsetSet() (OCA\AppFramework\Http\Request method), linkTo() (OCA\AppFramework\Core\API method), 101 113 linkToAbsolute() (OCA\AppFramework\Core\API offsetUnset() (OCA\AppFramework\Http\Request method), 101 method), 113 linkToRoute() (OCA\AppFramework\Core\API method), opendir() (OC\Files\View method), 49 101 openEventSource() (OCA\AppFramework\Core\API log() (OCA\AppFramework\Core\API method), 102 method), 103 M main() (OCA\AppFramework\App method), 96 Mapper (class in OCA\AppFramework\Db), 104 MapperTestUtility (class in OCA\AppFramework\Utility), 120 markFieldUpdated() (OCA\AppFramework\Db\Entity method), 106 method() (OCA\AppFramework\Controller\Controller method), 97 MethodAnnotationReader (class in OCA\AppFramework\Utility), 121 Middleware (class in OCA\AppFramework\Middleware), 115 188 P p() (global function), 47 params (OCA\AppFramework\Http\TemplateResponse property), 110 params() (OCA\AppFramework\Controller\Controller method), 97 passesCSRFCheck() (OCA\AppFramework\Core\API method), 102 post() (built-in function), 82 prepareQuery() (OCA\AppFramework\Core\API method), 100 print_unescaped() (global function), 47 printAdminPage() (OC_Template method), 44 Index ownCloud Developer Manual, Release 6.0 printGuestPage() (OC_Template method), 44 SecurityException (class in printPage() (OC_Template method), 43 OCA\AppFramework\Middleware\Security), printUserPage() (OC_Template method), 44 118 propertyToColumn() (OCA\AppFramework\Db\Entity SecurityMiddleware (class in method), 106 OCA\AppFramework\Middleware\Security), put() (built-in function), 82 117 putFileInfo() (OC\Files\View method), 52 session() (OCA\AppFramework\Controller\Controller method), 98 R setAppValue() (OCA\AppFramework\Core\API method), 100 readdir() (OC\Files\View method), 49 setErrorMessage() (OCA\AppFramework\Http\JSONResponse readfile() (OC\Files\View method), 50 method), 110 RedirectResponse (class in OCA\AppFramework\Http), setETag() (OCA\AppFramework\Http\Response 110 method), 109 register() (OCP\API method), 40 registerAdmin() (OCA\AppFramework\Core\API setItemsPerPage() (OC_OCS_Result method), 41 setLastModified() (OCA\AppFramework\Http\Response method), 104 method), 109 registerMiddleware() (OCA\AppFramework\Middleware\MiddlewareDispatcher setMapperResult() (OCA\AppFramework\Utility\MapperTestUtility method), 116 method), 120 relative_modified_date() (global function), 47 setParams() (OCA\AppFramework\Http\JSONResponse rename() (OC\Files\View method), 51 method), 109 render() (OCA\AppFramework\Controller\Controller setParams() (OCA\AppFramework\Http\TemplateResponse method), 98 method), 111 render() (OCA\AppFramework\Http\JSONResponse setStatus() (OCA\AppFramework\Http\Response method), 110 method), 108 render() (OCA\AppFramework\Http\Response method), setSystemValue() (OCA\AppFramework\Core\API 108 method), 100 render() (OCA\AppFramework\Http\TemplateResponse setTotalItems() (OC_OCS_Result method), 41 method), 111 (OCA\AppFramework\Core\API render() (OCA\AppFramework\Http\TextDownloadResponsesetUserValue() method), 100 method), 112 render() (OCA\AppFramework\Http\TextResponse simple_file_size() (global function), 48 SimplePieAPIFactory (class in method), 112 OCA\AppFramework\Utility), 122 render() (OCA\AppFramework\Http\TwigResponse size() (built-in function), 84 method), 112 renderAs (OCA\AppFramework\Http\TemplateResponse stat() (OC\Files\View method), 50 style() (built-in function), 72 property), 110 renderAs() (OCA\AppFramework\Http\TemplateResponse T method), 111 renderJSON() (OCA\AppFramework\Controller\Controller templateName (OCA\AppFramework\Http\TemplateResponse method), 98 property), 110 Request (class in OCA\AppFramework\Http), 113 TemplateResponse (class in OCA\AppFramework\Http), request (OCA\AppFramework\Controller\Controller 110 property), 97 TestUtility (class in OCA\AppFramework\Utility), 120 request() (built-in function), 81 TextDownloadResponse (class in resetUpdatedFields() (OCA\AppFramework\Db\Entity OCA\AppFramework\Http), 112 method), 106 TextResponse (class in OCA\AppFramework\Http), 111 resolvePath() (OC\Files\View method), 49 TimeFactory (class in OCA\AppFramework\Utility), 122 Response (class in OCA\AppFramework\Http), 108 toTmpFile() (OC\Files\View method), 51 rmdir() (OC\Files\View method), 49 touch() (OC\Files\View method), 51 trans() (built-in function), 72 S TwigMiddleware (class in OCA\AppFramework\Middleware\Twig), script() (built-in function), 72 118 search() (OC\Files\View method), 53 TwigResponse (class in OCA\AppFramework\Http), 112 searchByMime() (OC\Files\View method), 53 Index 189 ownCloud Developer Manual, Release 6.0 U unlink() (OC\Files\View method), 51 update() (built-in function), 83 update() (OCA\AppFramework\Db\Mapper method), 105 url() (built-in function), 71 V View (class in OC\Files), 48 190 Index
© Copyright 2024