Tuleap coding standards
=======================

Code formatting
---------------

As Tuleap is mainly written in PHP, we use the PSR standards:

* PSR-0_
* PSR-1_
* PSR-2_

Rule of thumb: *All new classes MUST respect PSR-2*

Internal conventions
~~~~~~~~~~~~~~~~~~~~

* Use an indent of 4 spaces, with no tabs. This helps to avoid problems with diffs, patches, git history…
* It is recommended to keep lines at approximately 85-100 characters long for better code readability.
* methodsInCamelCase()
* $variables_in_snake_case
* constants in UPPER_CASE
* public methods documented (at least @return statement)
* class documented (``I'm responsible of…``)
* All added code should follow PSR-2. Existing code should be converted to PSR-2 in a dedicated commit in
  order to not clutter the review of your functional change.
* No trailing whitespaces
* In DataAccessObject, convention is to name ``searchXxx()`` the methods that returns a DataAccessResult (eg. ``searchProjectsUserIsAdmin(…)``, and ``getXxx``, ``isXxx``, ``hasXxx`` for other cases (eg. ``doesUserHavePermission(…)``).

  **Note:** Contributions SHOULD NOT add/fix features AND fix coding standard of a legacy file in the same review.
  The code WONT be accepted. If your eyes are bleeding, conform to coding standard in a dedicated review, then
  contribute your change.

Copyright & license
~~~~~~~~~~~~~~~~~~~

All source code files (php, js, bash, ...) must contain a page-level docblock at the top of each file.
This header includes your copyright and a reference to the license GPLv2+ of the script.

  .. code-block:: php

    /**
     * Copyright (c) <You>, <Year>. All rights reserved
     *
     * This file is a part of Tuleap.
     *
     * Tuleap is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or
     * (at your option) any later version.
     *
     * Tuleap is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with Tuleap. If not, see <http://www.gnu.org/licenses/
     */

Adapt the copyright line to your situation.

Good quality code
-----------------

Tuleap is a big (`+600k LOC`_) and old (16 years) software and has probably an example of every existing bad designs that existed during those 16 years.

Yet, it's not a fatality and we are on the way to, slowly and carefully clean things up. On our road toward a Clean Code, some motto might help you to make your design choices:

- Test your code; TDD (Test Driven Development) should be the default.
- Follow SOLID_ design principles.
- Don't contribute STUPID_ code.

We also strongly suggest that you familiarize yourself with  `Code Smells`_ as it might pop up during code reviews.

Resources
~~~~~~~~~

A couple of documents worth to read when you consider contributing to Tuleap:

- http://www.phptherightway.com/

.. _+600k LOC: https://www.openhub.net/p/tuleap/analyses/latest/languages_summary
.. _SOLID: https://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
.. _STUPID: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
.. _Code Smells: https://blog.codinghorror.com/code-smells/
.. _PSR-0: http://www.php-fig.org/psr/psr-0/
.. _PSR-1: http://www.php-fig.org/psr/psr-1/
.. _PSR-2: http://www.php-fig.org/psr/psr-2/

Tuleap principles
-----------------

Output something / templating system
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

All new code must output content based on `Mustache <https://mustache.github.io/>`_ templates. The code is typically organized in 3 files:

- The template
- The presenter
- The calling code (in a Controller for instance)

Example of template:

  .. code-block:: html

    <h1>Hello</h1>

    <p>Welcome to {{ my_title }}</p>
    <!-- For readability, please note :                  -->
    <!--   * the spaces between {{, variable name and }} -->
    <!--   * the use of snake_case for variables         -->

Example of Presenter

  .. code-block:: php

    class Presenter
    {
        /** @var string */
        public $my_title;

        public function __construct()
        {
            $this->my_title = "My title";
        }
    }

Example of calling code:

  .. code-block:: php

    $renderer = TemplateRendererFactory::build()->getRenderer('/path/to/template/directory');

    // Output content directly (to the browser for instance)
    $renderer->renderToPage('template_name', new Presenter());

    // Return the content for futur reuse
    $string = $renderer->renderToString('template_name', new Presenter());

.. note::

    For existing code, it's acceptable to output content with "echo" to keep consistency.


Escaping
~~~~~~~~

You should rely on Mustache ``{{ }}`` notation to benefit from automatic escaping.

If you need to put light formatting in you localised string, then you should escape beforehand and use ``{{{ }}}`` notation. As it produces a code that is less auditable (reviewer has to manually check if injections are not possible), the convention is to prefix the variable with ``purified_`` and manually purify the variable in the presenter.

  .. code-block:: php
  
    class Presenter
    {
        public $purified_description;
        
        public function __construct()
        {
            $this->purified_description = Codendi_HTMLPurifier::instance()->purify(
                $GLOBALS['Language']->getText('key1', 'key2', 'https://example.com'),
                CODENDI_PURIFIER_LIGHT
            );
        }
    }
    
    // .tab file:
    // key1    key2    This is the <b>description</b> you can put <a href="$1">light formatting</a>
    
    // .mustache file:
    // <p>{{{ purified_description }}}</p>


Secure forms against CSRF
~~~~~~~~~~~~~~~~~~~~~~~~~

All state-changing actions MUST be protected against CSRF vulnerabilities.
In order to do that, a specific token must be added to your forms and verified
before the execution of the action.

Example:

Controller.php:

  .. code-block:: php

    namespace Tuleap/CsrfExample;

    use CSRFSynchronizerToken;
    use TemplateRendererFactory;

    class Controller
    {
        public function display()
        {
            $csrf_token = CSRFSynchronizerToken(CSRF_EXAMPLE_BASE_URL . '/do_things');
            $presenter  = new Presenter($csrf_token);
            $renderer   = TemplateRendererFactory::build()->getRenderer(CSRF_EXAMPLE_TEMPLATE_DIR);

            $renderer->renderToPage('csrf-example', $presenter);
        }

        public function process()
        {
            $csrf_token = CSRFSynchronizerToken(CSRF_EXAMPLE_BASE_URL . '/do_things');
            $csrf_token->check();

            do_things();
        }
    }

Presenter.php:

  .. code-block:: php

    namespace Tuleap/CsrfExample;

    use CSRFSynchronizerToken;

    class Presenter
    {
        /**
         * @var CSRFSynchronizerToken
         */
         public $csrf_token;

        public function __construct(CSRFSynchronizerToken $csrf_token)
        {
            $this->csrf_token = $csrf_token;
        }
    }

csrf-example.mustache:

  .. code-block:: html

    <form method="post">
        {{# csrf_token }}
            {{> csrf_token_input }}
        {{/ csrf_token }}
        <input type="submit">
    </form>


.. note::
    For existing code rendering HTML without using templates, it can be acceptable to use
    the fetchHTMLInput method of the CSRFSynchronizerToken class.


Secure DB against SQL injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

All code related to database MUST deal with data types and do the proper escaping
of values before executing the query.

Example of DataAccessObject:

  .. code-block:: php

    namespace Tuleap/Git;

    use DataAccessObject;

    class RepositoryDao extends DataAccessObject
    {
        public function searchByName($project_id, $name)
        {
            // project_id is supposed to be an int
            $project_id = $this->da->escapeInt($project_id);

            // name is supposed to be a string
            $name = $this->da->quoteSmart($name);

            $sql = "SELECT *
                    FROM plugin_git_repositories
                    WHERE project_id = $project_id
                      AND name = $name";
            return $this->retrieve($sql);
        }
    }