TaskFreak php 5.5

De EjnTricks
Révision de 1 mars 2014 à 16:20 par Etienne (discussion | contributions)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

Suite à une mise à jour de Ubuntu, la version de php disponible est devenue la 5.5.6-1ubuntu1. Or l'application comporte des bugs et les traces comportent de nombreux messages d'erreur ou warning. Cet article se propose de décrire l'ensemble des actions menées pour corriger les anomalies remontées.


Hand-icon.png Votre avis

Current user rating: 93/100 (5 votes)

 You need to enable JavaScript to vote


Fonction session_unregister

Le message d'erreur suivant était affiché dans les logs Apache:

PHP Fatal error:  Call to undefined function session_unregister() in /var/opt/taskfreak-ldap-0.6.4/_common.php on line 67

Ce problème a été décrit sur le forum de l'application à l'adresse http://forum.taskfreak.com/index.php/topic,4677.0.html.

Trois fichiers, à la racine de l'application, doivent être modifiés:

  • _common.php
  • index.php
  • print_list.php

L'archive suivante contient l'ensemble des fichiers modifiés, patch.tar.gz.

Il suffit d'extraire les fichiers dans le répertoire cible à l'aide de la commande suivante:

#sudo tar -C /var/opt/taskfreak -xzvf taskfreak-0.6.4_patch.tar.gz
_common.php
index.php
print_list.php

A noter que les fichiers impactés n'entrent pas en collision avec les modifications faites pour activer l'authentification LDAP.


Définition timezone

En parcourant les logs d'erreur Apache, il est possible de constater le warning suivant:

PHP Warning:  strtotime(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting
or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled
the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone.
in /var/opt/taskfreak/include/classes/pkg_project.php on line 28, referer: 

Le message indique que la variable dat.timezone n'est pas définie au niveau de php. Il est donc possible d'y remédier de deux manières.

Modification code source

Il est possible d'ajouter le code suivant, dans un des fichiers php exécuté:

if( ! ini_get('date.timezone') )
{
	date_default_timezone_set('GMT');
}

Ce code devant être exécuté globalement et avant l'appel dans pkg_project, il paraît judicieux de le placer à la fin du fichier config.php de l'application. En effet, ce fichier étant utilisé pour la configuration applicative, il est forcément utilisé.

L'avantage de cette modification est qu'elle n'impacte que l'application déployée.

Modification configuration php

Il est également possible de modifier le paramètre de façon plus globale sur le serveur, en le configurant dans le fichier php.ini. Dans le cadre de cet article, le fichier est disponible sous /etc/php5/apache2/php.ini, avec un déploiement d'un serveur Apache sur Ubuntu. Le paramètre date.timezone y est commenté par défaut. Celui-ci est activé avec la zone souhaitée.

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = Europe/Paris


Valeur disponible

Les valeurs disponibles pour cette configuration sont listées sur le site officiel de php: http://www.php.net/manual/en/timezones.php


PHP Strict Standards - Appel statique

Les logs Apache présentent de nombreux messages de non respect des standards php.

Fichier _common.php

De nombreux messages concernent l'utilisation en statique de fonctions déclarées dans la classe Tzn. Lors de l'analyse, il est constaté que le fichier _common.php est régulièrement inclus dans le code. Le début du fichier présente les lignes suivantes:

include './_include.php';

include PRJ_CLASS_PATH.'tzn_generic.php';
include PRJ_CLASS_PATH.TZN_DB_CLASS;
include PRJ_CLASS_PATH.'tzn_user.php';

include PRJ_CLASS_PATH.'pkg_member.php';
include PRJ_CLASS_PATH.'pkg_project.php';

header("Content-type: text/html; charset=".FRK_CHARSET);

session_start();

$objUser = new Member();

Il faut alors s'inspirer que la variable $objUser pour créer une instance de Tzn

include './_include.php';

include PRJ_CLASS_PATH.'tzn_generic.php';
include PRJ_CLASS_PATH.TZN_DB_CLASS;
include PRJ_CLASS_PATH.'tzn_user.php';

$objTzn = new Tzn();

include PRJ_CLASS_PATH.'pkg_member.php';
include PRJ_CLASS_PATH.'pkg_project.php';

header("Content-type: text/html; charset=".FRK_CHARSET);

session_start();

$objUser = new Member();

Ainsi, une instance de Tzn sera disponible de façon globale et il sera possible d'appeler les fonctions / méthodes sans passer en statique.


Cependant, cette modification va entraîner une nouvelle anomalie. En effet, une fois connecté, des appels Ajax sont réalisés sur le fichier xajax.task.php. Or dans celui-ci, le fichier _common.php n'est pas inclus, et l'objet $objTzn ne sera pas disponible. La déclaration est donc déplacée dans le fichier /include/classes/tzn_generic.php, en ajoutant les lignes suivantes en fin de fichier:

$objTzn = new Tzn();

Fort de cette modification, les appels statiques suspects vont être modifiés. Les lignes suivantes:

                        Tzn::redirect('login.php','ERROR:Session invalid, please log in again');
                        Tzn::redirect('login.php','ERROR:Please log in to access your account');

deviennents:

                        $objTzn->redirect('login.php','ERROR:Session invalid, please log in again');
                        $objTzn->redirect('login.php','ERROR:Please log in to access your account');

Fichier index.php

Des indiquent des appels fautifs ayant pour origine le fichier index.php. En effet, il y a de nombreux appels à la fonction concatUrl et _getUserTZ:

    $pLink=Tzn::concatUrl($pLink,'sProject='.$pProject);
$pLink=Tzn::concatUrl($pLink,'sUser='.$pUser);
        $pContext = Tzn::getHttp($_REQUEST['sContext'],true);
    $pLink=Tzn::concatUrl($pLink,'sContext='.$pContext);
$pLink=Tzn::concatUrl($pLink,'show='.$pShow);
$pLink=Tzn::concatUrl($pLink,'sort='.$pSort);
$pLink=Tzn::concatUrl($pLink,'dir='.$pDir);

La correction s'effectue en appelant la variable $objTzn ainsi:

    $pLink=$objTzn->concatUrl($pLink,'sProject='.$pProject);
$pLink=$objTzn->concatUrl($pLink,'sUser='.$pUser);
        $pContext = $objTzn->getHttp($_REQUEST['sContext'],true);
    $pLink=$objTzn->concatUrl($pLink,'sContext='.$pContext);
$pLink=$objTzn->concatUrl($pLink,'show='.$pShow);
$pLink=$objTzn->concatUrl($pLink,'sort='.$pSort);
$pLink=$objTzn->concatUrl($pLink,'dir='.$pDir);

Fichier login.php

Dès l'accès à la page d'accueil des messages sont constatés.

PHP Strict Standards:  Non-static method Tzn::qText() should not be called statically in /var/opt/taskfreak-ldap-0.6.4/login.php on line 74
PHP Strict Standards:  Non-static method Tzn::qCheckbox() should not be called statically in /var/opt/taskfreak-ldap-0.6.4/login.php on line 86

Les lignes de code impliquées sont les suivantes:

            <tr>
                <th><?php echo $langUser[TZN_USER_LOGIN]; ?>:</th>
                <td><?php Tzn::qText('username',$_REQUEST['username'],'width:130px'); ?></td>
            </tr>
            <tr>
                <th><?php echo $langUser['password']; ?>:</th>
                <td><input type="password" name="password" value="" style="width:130px" /></td>
            </tr>
            <?php
                if (@constant('PRJ_AUTO_LOGIN')) {
            ?>
            <tr>
                <td colspan="2" align="center">
                    <?php
                        Tzn::qCheckBox('remember','','vertical-align:middle');
                        echo '<label>'.$langUser['auto_login'].'</label>';
                    ?>
                </td>
            </tr>

La correction apportée est:

            <tr>
                <th><?php echo $langUser[TZN_USER_LOGIN]; ?>:</th>
                <td><?php $objTzn->qText('username',$_REQUEST['username'],'width:130px'); ?></td>
            </tr>
            <tr>
                <th><?php echo $langUser['password']; ?>:</th>
                <td><input type="password" name="password" value="" style="width:130px" /></td>
            </tr>
            <?php
                if (@constant('PRJ_AUTO_LOGIN')) {
            ?>
            <tr>
                <td colspan="2" align="center">
                    <?php
                        $objTzn->qCheckBox('remember','','vertical-align:middle');
                        echo '<label>'.$langUser['auto_login'].'</label>';
                    ?>
                </td>
            </tr>

Ce type de modification est ensuite appliqué à l'ensemble du fichier.

Les lignes :

if ($_REQUEST['forgot']) {
        Tzn::redirect('user_password.php?username='.urlencode(Tzn::getHttp($_REQUEST['username'])));
}

deviennent:

if ($_REQUEST['forgot']) {
        $objTzn->redirect('user_password.php?username='.urlencode($objTzn->getHttp($_REQUEST['username'])));
}


Les lignes :

                if ($pRef) {
                        Tzn::redirect($pRef);
                } else {
                        Tzn::redirect('index.php');
                }

deviennent:

                if ($pRef) {
                        $objTzn->redirect($pRef);
                } else {
                        $objTzn->redirect('index.php');
                }

A cet instant, la modification apportée entraîne l'affichage du message suivant lors de la connexion:

PHP Strict Standards:  Non-static method Tzn::_style() should not be called statically, assuming $this from incompatible context
in /var/opt/taskfreak-ldap-0.6.4/include/classes/tzn_generic.php on line 1799, referer:...

Cela sera corrigé plus tard en modifiant les appels statiques dans la classe Tzn.

Fichier logout.php

Après déconnexion des messages sont constatés.

PHP Strict Standards:  Non-static method Tzn::getHttp() should not be called statically in /var/opt/taskfreak-ldap-0.6.4/logout.php on line 46, referer: ...

Les lignes de code impliquées sont les suivantes:

        <?php
        if ($pMessageStatus = Tzn::getHttp($_REQUEST['tznMessage'], 'html')) {
            echo '<p class="box error" style="text-align:center">'.$pMessageStatus.'</p>';
        }
        ?>

La correction apportée est:

        <?php
        if ($pMessageStatus = i$objTzn->getHttp($_REQUEST['tznMessage'], 'html')) {
            echo '<p class="box error" style="text-align:center">'.$pMessageStatus.'</p>';
        }
        ?>

Fichier print_list.php

Une fonctionnalité pemet d'avoir un affichage pour impression de la page en cours, utilisant le fichier print_list.php, avec des appels à la fonction concatUrl:

    $pLink=Tzn::concatUrl($pLink,'sProject='.$pProject);
    $pLink=Tzn::concatUrl($pLink,'sUser='.$pUser);
    $pLink=Tzn::concatUrl($pLink,'sContext='.$pContext);
$pLink=Tzn::concatUrl($pLink,'show='.$pShow);
$pLink=Tzn::concatUrl($pLink,'sort='.$pSort);
$pLink=Tzn::concatUrl($pLink,'dir='.$pDir);

La correction s'effectue en appelant la variable $objTzn ainsi:

    $pLink=$objTzn->concatUrl($pLink,'sProject='.$pProject);
    $pLink=$objTzn->concatUrl($pLink,'sUser='.$pUser);
    $pLink=$objTzn->concatUrl($pLink,'sContext='.$pContext);
$pLink=$objTzn->concatUrl($pLink,'show='.$pShow);
$pLink=$objTzn->concatUrl($pLink,'sort='.$pSort);
$pLink=$objTzn->concatUrl($pLink,'dir='.$pDir);

Fichier project_edit.php

Lors de l'édition d'un projet, fichier project_edit.php, il y a des appels à la fonction redirect:

                Tzn::redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied'].' (error #424)');
            Tzn::redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied']);
        Tzn::redirect('project_edit.php?id='.$objEditItem->id,$pMessageEditStatus);
    Tzn::redirect('project_list.php',$pMessageStatus);

La correction s'effectue en appelant la variable $objTzn ainsi:

                $objTzn->redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied'].' (error #424)');
            $objTzn->redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied']);
        $objTzn->redirect('project_edit.php?id='.$objEditItem->id,$pMessageEditStatus);
    $objTzn->redirect('project_list.php',$pMessageStatus);

Fichier project_list.php

Lors de l'affichage de la liste des projets, fichier project_list.php, il y a des appels aux fonctions concatUrl et redirect:

    Tzn::redirect($pLink,$pMessageStatus);
                                        <a href="<?php echo Tzn::concatUrl($pLink,'delete='.$objItem->id); ?>" onclick="return confirm('<?php echo
$langMessage['project_delete_confirm']; ?>');" title="<?php echo $langMessage['project_delete']; ?>"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_dele.png"
width="20" height="16" border="0" alt="<?php echo $langMessage['project_delete']; ?>" /></a>

La correction s'effectue en appelant la variable $objTzn ainsi:

    $objTzn->redirect($pLink,$pMessageStatus);
                                        <a href="<?php echo $objTzn->concatUrl($pLink,'delete='.$objItem->id); ?>" onclick="return confirm('<?php echo
$langMessage['project_delete_confirm']; ?>');" title="<?php echo $langMessage['project_delete']; ?>"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_dele.png"
width="20" height="16" border="0" alt="<?php echo $langMessage['project_delete']; ?>" /></a>

Fichier public.php

Il est possible d'afficher les tâches "publique" sans se connecter, fichier project_edit.php, il y a des appels à la fonction concatUrl:

    $pLink=Tzn::concatUrl($pLink,'sContext='.$pContext);
$pLink=Tzn::concatUrl($pLink,'show='.$pShow);
$pLink=Tzn::concatUrl($pLink,'sort='.$pSort);
$pLink=Tzn::concatUrl($pLink,'dir='.$pDir);

La correction s'effectue en appelant la variable $objTzn ainsi:

    $pLink=$objTzn->concatUrl($pLink,'sContext='.$pContext);
$pLink=$objTzn->concatUrl($pLink,'show='.$pShow);
$pLink=$objTzn->concatUrl($pLink,'sort='.$pSort);
$pLink=$objTzn->concatUrl($pLink,'dir='.$pDir);

Fichier rss.php

Une fonctionnalité de flux "rss" est disponible, fichier rss.php dans lequel il y a un appel à la fonction <cod>getReg</code>:

if ($pUserName = Tzn::getReg($_REQUEST['user'],"^[a-zA-Z0-9]+$")) {
        if ($objUser->loadByKey('username',$pUserName)) {
                $pUserIsLoggedIn = $objUser->checkRssCode($_REQUEST['c']);
        }
}

La correction s'effectue en appelant la variable $objTzn ainsi:

if ($pUserName = $objTzn->getReg($_REQUEST['user'],"^[a-zA-Z0-9]+$")) {
        if ($objUser->loadByKey('username',$pUserName)) {
                $pUserIsLoggedIn = $objUser->checkRssCode($_REQUEST['c']);
        }
}

Fichier user_edit.php

Lors de l'édition d'un utilisateur, fichier user_edit.php, il y a des appels à la fonction redirect:

                Tzn::redirect('user_details.php?id='.$objEditItem->id, $GLOBALS['langMessage']['information_saved']);

La correction s'effectue en appelant la variable $objTzn ainsi:

                $objTzn->redirect('user_details.php?id='.$objEditItem->id, $GLOBALS['langMessage']['information_saved']);

Fichier user_list.php

Lors de l'affichage de la liste des utilisateurs, fichier user_list.php, il y a des appels aux fonctions concatUrl, getReg et redirect:

    Tzn::redirect('logout.php','ERROR:'.$langMessage['denied']);
            //Tzn::redirect('user_list.php');
            //Tzn::redirect('user_list.php');
if ($sCountry = Tzn::getReg($_REQUEST['country'],'^[A-Z]+$')) {
if ($sState = Tzn::getReg($_REQUEST['state'],'^[A-Z]+$')) {
                    <a href="<?php echo Tzn::concatUrl($pLink,'mode='.$objItem->id); ?>" onclick="return confirm('<?php echo ($objItem->enabled)?$langUser['disable_confirm']:$langUser['enable_confirm']; ?>');"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_<?php echo $imgStatus; ?>y.png" width="20" height="16" border="0" /></a>
                                        <a href="<?php echo Tzn::concatUrl($pLink,'delete='.$objItem->id); ?>" onclick="return confirm('<?php echo $langUser['delete_confirm']; ?>');"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_dele.png" width="20" height="16" border="0" /></a>

La correction s'effectue en appelant la variable $objTzn ainsi:

    $objTzn->redirect('logout.php','ERROR:'.$langMessage['denied']);
            //$objTzn->redirect('user_list.php');
            //$objTzn->redirect('user_list.php');
if ($sCountry = $objTzn->getReg($_REQUEST['country'],'^[A-Z]+$')) {
if ($sState = $objTzn->getReg($_REQUEST['state'],'^[A-Z]+$')) {
                    <a href="<?php echo $objTzn->concatUrl($pLink,'mode='.$objItem->id); ?>" onclick="return confirm('<?php echo ($objItem->enabled)?$langUser['disable_confirm']:$langUser['enable_confirm']; ?>');"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_<?php echo $imgStatus; ?>y.png" width="20" height="16" border="0" /></a>
                                        <a href="<?php echo $objTzn->concatUrl($pLink,'delete='.$objItem->id); ?>" onclick="return confirm('<?php echo $langUser['delete_confirm']; ?>');"><img src="skins/<?php echo FRK_SKIN_FOLDER; ?>/images/b_dele.png" width="20" height="16" border="0" /></a>

Fichier user_password.php

Sur la demande d'un nouveau mot de passe, fichier user_password.php, il y a un appel à la fonction qText:

                        <tr>
                                <th>Email:</th>
                                <td><?php Tzn::qText('email',$_POST['email']); ?></td>
                                <td><input type="submit" name="reminder" value="Get new password"></td>
                        </tr>

La correction s'effectue en appelant la variable $objTzn ainsi:

                        <tr>
                                <th>Email:</th>
                                <td><?php $objTzn->qText('email',$_POST['email']); ?></td>
                                <td><input type="submit" name="reminder" value="Get new password"></td>
                        </tr>

Fichier user_register.php

Lors de la création d'un nouveau compte, fichier user_register.php, il y a un appel à la fonction redirect:

        Tzn::redirect('login.php','Sorry, accounts can be created by administrator only');
                Tzn::redirect('user_register.php?dude='.$objEditItem->id,'Information successfully saved');
                Tzn::redirect('login.php','Account already enabled, please log in');

La correction s'effectue en appelant la variable $objTzn ainsi:

        $objTzn->redirect('login.php','Sorry, accounts can be created by administrator only');
                $objTzn->redirect('user_register.php?dude='.$objEditItem->id,'Information successfully saved');
                $objTzn->redirect('login.php','Account already enabled, please log in');

Fichier xajax.task.php

Une fois connecté, des appels sont réalisés en Ajax sur el fichier xajax.task.php afin de rafraîchir périodiquement l'affichage. Il y a des appels à la fonction concatUrl:

        $link = Tzn::concatUrl($_SESSION['linkItems'],'sProject='.$objProject->id);
        $link = Tzn::concatUrl($link,'show=');

La correction s'effectue en appelant la variable $objTzn ainsi:

        $link = $objTzn->concatUrl($_SESSION['linkItems'],'sProject='.$objProject->id);
        $link = $objTzn->concatUrl($link,'show=');

Fichier include/classes/pkg_com.php

Les appels à la fonction utf8_substr s'effectue dans la classe EmailMessage. Dans la fonction setBody:

        function setBody($body='') {
        $bodyOk = false;
        if (!$this->body) {
            $bodyOk = true;
            $this->body = $body;
        } else if (preg_match_all('/\{[^}]*\}/',$this->body,$arrFound)) {
                        $arrFound = $arrFound[0];
                        if (count($arrFound)) {
                                $arrFound = array_unique($arrFound);
                                foreach ($arrFound as $pattern) {
                                        $value = '';
                                        $prop = Tzn::utf8_substr($pattern,1,-1);
                    if ($prop == 'body') {
                        $bodyOk = true;
                        $this->body = str_replace($pattern,$body,$this->body);
                        continue;
                    }
                    $arrProp = explode('->',$prop);
                    if (count($arrProp) > 1) {
                        $varObj = $arrProp[0];
                        if (is_object($GLOBALS[$var])) {
                            $objItem =& $GLOBALS[$var];
                            if (in_array($prop,$objItem->_properties)) {
                                $value = $objItem->$prop;
                            } else if (method_exists($objItem,$prop)) {
                                $value = $objItem->$prop();
                            }
                        }
                    } else {
                        $value = $GLOBALS[$prop];
                    }
                                        /*
                                        echo $prop.' -&gt; ';
                                        echo $value.'<br />';
                                        */
                                        if ($value) {
                                                $this->body = str_replace($pattern,$value,$this->body);
                                        }
                                }
                        }
                }
        if (!$bodyOk) {
            // supplied body not placed
            $this->body .= "\r\n".$body;
        }
                //echo '<pre>'.$this->body.'</pre>';
        }

Cette fonction se situe dans la classe EmailMessage qui est une extension de la classe TznDB. Cette dernière, définie dans le fichier include/classes/tzn_mysql.php, étends également la classe Tzn. Donc l'appel à la fonction utf8_substr peut s'effectuer directement sans passer par un appel statique.

        function setBody($body='') {
        $bodyOk = false;
        if (!$this->body) {
            $bodyOk = true;
            $this->body = $body;
        } else if (preg_match_all('/\{[^}]*\}/',$this->body,$arrFound)) {
                        $arrFound = $arrFound[0];
                        if (count($arrFound)) {
                                $arrFound = array_unique($arrFound);
                                foreach ($arrFound as $pattern) {
                                        $value = '';
                                        $prop = parent::utf8_substr($pattern,1,-1);
                    if ($prop == 'body') {
                        $bodyOk = true;
                        $this->body = str_replace($pattern,$body,$this->body);
                        continue;
                    }
                    $arrProp = explode('->',$prop);
                    if (count($arrProp) > 1) {
                        $varObj = $arrProp[0];
                        if (is_object($GLOBALS[$var])) {
                            $objItem =& $GLOBALS[$var];
                            if (in_array($prop,$objItem->_properties)) {
                                $value = $objItem->$prop;
                            } else if (method_exists($objItem,$prop)) {
                                $value = $objItem->$prop();
                            }
                        }
                    } else {
                        $value = $GLOBALS[$prop];
                    }
                                        /*
                                        echo $prop.' -&gt; ';
                                        echo $value.'<br />';
                                        */
                                        if ($value) {
                                                $this->body = str_replace($pattern,$value,$this->body);
                                        }
                                }
                        }
                }
        if (!$bodyOk) {
            // supplied body not placed
            $this->body .= "\r\n".$body;
        }
                //echo '<pre>'.$this->body.'</pre>';
        }

De la même façon, la fonction sendForm:

    function sendForm($objData, $address, $name=null) {
        // sending data from form: every field $_REQUEST["EMXXXXXXX"]
        $str = "";
        if ($this->html) {
            $str = "<table cellpadding=3 cellspacing=0 border=1>";
        }
        foreach($objData as $key => $value) {
            $pos = strpos($key, "EM");
            if (($pos === false) || ($pos > 0)) {
                continue;
            }
            $key = Tzn::utf8_substr($key,2);
            if (get_magic_quotes_gpc()) {
                $value = stripslashes($value);
            }
            if ($this->html) {
                $str .="<tr><td>".$key."</td><td>".nl2br(htmlspecialchars($value))."</td></tr>";
            } else {
                $str .= "\t".$key."=\t".$value."\n\n";
            }
        }
        if ($this->html) {
            $str .= "</table>";
        }
                if (preg_match("/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i",$address)
                        || preg_match("/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i",$name))
                {
                                $this->_error['send'] = $GLOBALS['langEmail']['check_injection'];
                                return false;
                }
        return $this->send($str, $address, $name);
        }

La modification est:

    function sendForm($objData, $address, $name=null) {
        // sending data from form: every field $_REQUEST["EMXXXXXXX"]
        $str = "";
        if ($this->html) {
            $str = "<table cellpadding=3 cellspacing=0 border=1>";
        }
        foreach($objData as $key => $value) {
            $pos = strpos($key, "EM");
            if (($pos === false) || ($pos > 0)) {
                continue;
            }
            $key = parent::utf8_substr($key,2);
            if (get_magic_quotes_gpc()) {
                $value = stripslashes($value);
            }
            if ($this->html) {
                $str .="<tr><td>".$key."</td><td>".nl2br(htmlspecialchars($value))."</td></tr>";
            } else {
                $str .= "\t".$key."=\t".$value."\n\n";
            }
        }
        if ($this->html) {
            $str .= "</table>";
        }
                if (preg_match("/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i",$address)
                        || preg_match("/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i",$name))
                {
                                $this->_error['send'] = $GLOBALS['langEmail']['check_injection'];
                                return false;
                }
        return $this->send($str, $address, $name);
        }

Fichier include/classes/pkg_member.php

Les appels à la fonction utf8_substr s'effectue dans la classe Member. De la même façon que pour la classe EmailMessage, la classe Member étends TznUser, définie dans le fichier tzn_user.php, elle même une extension de TznDb et donc de Tzn. Donc l'appel à la fonction utf8_substr peut s'effectuer directement sans passer par un appel statique.

Dans la fonction getShortName:

        function getShortName($default='') {
                $str = $this->firstName;
                if ($this->middleName) {
                        $str .= ' '.Tzn::utf8_substr($this->middleName,0,1).'.';
                }
                return ($str)?$str:$default;
        }

La modification est:

        function getShortName($default='') {
                $str = $this->firstName;
                if ($this->middleName) {
                        $str .= ' '.parent::utf8_substr($this->middleName,0,1).'.';
                }
                return ($str)?$str:$default;
        }

De la même façon, la fonction getName:

        function getName() {
                $str = $this->title.' '.$this->firstName;
                if ($this->middleName) {
                        $str .= ' '.Tzn::utf8_substr($this->middleName,0,1).'.';
                }
                $str .= ' '.$this->lastName;
                return $str;
        }

La modification est:

        function getName() {
                $str = $this->title.' '.$this->firstName;
                if ($this->middleName) {
                        $str .= ' '.parent::utf8_substr($this->middleName,0,1).'.';
                }
                $str .= ' '.$this->lastName;
                return $str;
        }

Fichier /include/classes/pkg_project.php

Le fichier _common.php inclus le fichier pkg_project.php et le message suivant est constaté:

PHP Strict Standards:  Non-static method Tzn::_getUserTZ() should not be called statically in /var/opt/taskfreak-ldap-0.6.4/include/classes/pkg_project.php on line 30

Les lignes :

//define('PRJ_DTE_NOW',strtotime(gmdate('Y-m-d',time()+Tzn::_getUserTZ())));
define('PRJ_DTE_NOW',strtotime(date('Y-m-d',time()+Tzn::_getUserTZ())));

deviennent:

//define('PRJ_DTE_NOW',strtotime(gmdate('Y-m-d',time()+Tzn::_getUserTZ())));
define('PRJ_DTE_NOW',strtotime(date('Y-m-d',time()+$objTzn->_getUserTZ())));

Les appels à la fonction _value s'effectue dans la classe Item. De la même façon que pour la classe EmailMessage, la classe Item étend TznDb. Donc l'appel à la fonction _value peut s'effectuer directement sans passer par un appel statique.

Dans la fonction getDescription:

    function getDescription() {
        $value = Tzn::_value('description');
                $value = preg_replace("/(?<!\")((http|ftp)+(s)?"
                        .":\/\/[^<>\s]+)/i", "<a href=\"\\0\" target=\"_blank\">\\0</a>", $value);
                return nl2br($value);
    }

La modification est:

    function getDescription() {
        $value = parent::_value('description');
                $value = preg_replace("/(?<!\")((http|ftp)+(s)?"
                        .":\/\/[^<>\s]+)/i", "<a href=\"\\0\" target=\"_blank\">\\0</a>", $value);
                return nl2br($value);
    }

De la même façon, pour l'appel à la fonction utf8_substr dans la fonction getContext:

    function getContext($mode=false) { // mode=0/short, 1/long
        $str = $GLOBALS['langItemContext'][$this->context];
                if ($mode) {
            return $str;
        } else {
            return '<span style="background-color:'.$GLOBALS['confContext'][$this->context].'" title="'.$str.'">'
                .Tzn::utf8_substr($str,0,1).'</span>';
        }
        }

La modification est:

    function getContext($mode=false) { // mode=0/short, 1/long
        $str = $GLOBALS['langItemContext'][$this->context];
                if ($mode) {
            return $str;
        } else {
            return '<span style="background-color:'.$GLOBALS['confContext'][$this->context].'" title="'.$str.'">'
                .parent::utf8_substr($str,0,1).'</span>';
        }
        }

Fichier include/classes/tzn_document.php

Les appels à la fonction getRdm s'effectue dans la classe TznFile, extension de la classe Tzn. Donc l'appel à la fonction getRdm peut s'effectuer directement sans passer par un appel statique.

Dans la fonction zGenFileName:

    function zGenFileName($fileName, $folder) {
                // returns a string for new file name
                $fileRoot = substr($fileName,0,strrpos($fileName,'.'));
                $fileRoot = preg_replace('/[^a-zA-Z0-9_\-\.]/','_',strtolower($fileRoot));
                $fileRoot .= '-';
                $ext = strrchr($fileName,'.');
                $i = 1;
                do {
                        if (TZN_FILE_RANDOM) {
                                $fileDest = Tzn::getRdm(16,
                                        "abcdefghijklmnopqrstuvwxyz0123456789");
                        } else {
                                $fileDest = $fileRoot.(($i<10)?'0'.$i:$i);
                        }
                        $fileDest .= $ext;
                        $i++;
                } while (file_exists(TZN_FILE_UPLOAD_PATH.$folder.$fileDest));
                return $fileDest;
        }

La modification est:

    function zGenFileName($fileName, $folder) {
                // returns a string for new file name
                $fileRoot = substr($fileName,0,strrpos($fileName,'.'));
                $fileRoot = preg_replace('/[^a-zA-Z0-9_\-\.]/','_',strtolower($fileRoot));
                $fileRoot .= '-';
                $ext = strrchr($fileName,'.');
                $i = 1;
                do {
                        if (TZN_FILE_RANDOM) {
                                $fileDest = parent::getRdm(16,
                                        "abcdefghijklmnopqrstuvwxyz0123456789");
                        } else {
                                $fileDest = $fileRoot.(($i<10)?'0'.$i:$i);
                        }
                        $fileDest .= $ext;
                        $i++;
                } while (file_exists(TZN_FILE_UPLOAD_PATH.$folder.$fileDest));
                return $fileDest;
        }

Fichier include/classes/tzn_generic.php

Une première modification a été mise en place, décrite au début du chapitre, pour référencer une variable $objTzn accessible sur l'ensemble de l'application.

Fichier include/classes/tzn_mysql.php

Les appels à la fonction _style s'effectue dans la classe TznDb, extension de la classe Tzn. Donc l'appel à la fonction _style peut s'effectuer directement sans passer par un appel statique.

Dans la fonction pSort:

    function pSort($link, $param, $field, $style='',
        $imgAsc = TZN_DB_ASC_OFF, $imgAscActive = TZN_DB_ASC_ON,
        $imgDesc = TZN_DB_DESC_OFF, $imgDescActive = TZN_DB_DESC_ON)
    {
                $begin = '<a href="'.$link;
                if (ereg('\?',$link)) {
                        $begin .= '&';
                } else {
                        $begin .= '?';
                }
                $begin.= $param.'=';
                $end = '"';
                $end .= Tzn::_style($style);
                $end .= '>';
                if (ereg('^'.$field.' ASC',$this->_sqlOrder)) {
            print '<img src="'.$imgAscActive.'">';
                } else {
            print $begin.$field.'+ASC'.$end.'<img src="'.$imgAsc
                .'" border="0"></a>';
                }
        if (ereg('^'.$field.' DESC',$this->_sqlOrder)) {
                        print '<img src="'.$imgDescActive.'">';
                } else {
                        print $begin.$field.'+DESC'.$end.'<img src="'.$imgDesc
                                .'" border="0"></a>';
                }
        }

La modification est:

    function pSort($link, $param, $field, $style='',
        $imgAsc = TZN_DB_ASC_OFF, $imgAscActive = TZN_DB_ASC_ON,
        $imgDesc = TZN_DB_DESC_OFF, $imgDescActive = TZN_DB_DESC_ON)
    {
                $begin = '<a href="'.$link;
                if (ereg('\?',$link)) {
                        $begin .= '&';
                } else {
                        $begin .= '?';
                }
                $begin.= $param.'=';
                $end = '"';
                $end .= parent::_style($style);
                $end .= '>';
                if (ereg('^'.$field.' ASC',$this->_sqlOrder)) {
            print '<img src="'.$imgAscActive.'">';
                } else {
            print $begin.$field.'+ASC'.$end.'<img src="'.$imgAsc
                .'" border="0"></a>';
                }
        if (ereg('^'.$field.' DESC',$this->_sqlOrder)) {
                        print '<img src="'.$imgDescActive.'">';
                } else {
                        print $begin.$field.'+DESC'.$end.'<img src="'.$imgDesc
                                .'" border="0"></a>';
                }
        }

Dans la fonction pPagination:

        function pPagination($link, $param, $style = TZN_DB_PAGING_OFF,
                $styleCurrent = TZN_DB_PAGING_ON) {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classCurrent = style for current
                $max = 0;
                // echo 'pageSize='.$this->_pageSize.' total='.$this->_total;
                // return false;
                if (!$this->_pageSize) return false;
                while (($max * $this->_pageSize) < $this->_total) {
                        $max++;
                }
                if ($max == 0) {
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= Tzn::_style($style);
                        $end .= '>';
                        $first = true;
                        for ($i=1; $i<=$max; $i++) {
                                if ($first) {
                                        $first = false;
                                } else {
                                        print ' | ';
                                }
                                if ($this->_page == $i) {
                                        if (!empty($styleCurrent)) {
                                                if ($styleCurrent == '<b>') {
                                                        print '<b>'.$i.'</b>';
                                                } else {
                                                        print '<span class="'.$styleCurrent.'">'
                                                                .$i.'</span>';
                                                }
                                        } else {
                                                echo($i);
                                        }
                                } else {
                                        echo $begin.$i.$end.$i.'</a>';
                                }
                        }
                }

La modification est:

        function pPagination($link, $param, $style = TZN_DB_PAGING_OFF,
                $styleCurrent = TZN_DB_PAGING_ON) {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classCurrent = style for current
                $max = 0;
                // echo 'pageSize='.$this->_pageSize.' total='.$this->_total;
                // return false;
                if (!$this->_pageSize) return false;
                while (($max * $this->_pageSize) < $this->_total) {
                        $max++;
                }
                if ($max == 0) {
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= parent::_style($style);
                        $end .= '>';
                        $first = true;
                        for ($i=1; $i<=$max; $i++) {
                                if ($first) {
                                        $first = false;
                                } else {
                                        print ' | ';
                                }
                                if ($this->_page == $i) {
                                        if (!empty($styleCurrent)) {
                                                if ($styleCurrent == '<b>') {
                                                        print '<b>'.$i.'</b>';
                                                } else {
                                                        print '<span class="'.$styleCurrent.'">'
                                                                .$i.'</span>';
                                                }
                                        } else {
                                                echo($i);
                                        }
                                } else {
                                        echo $begin.$i.$end.$i.'</a>';
                                }
                        }
                }

Dans la fonction pPageSelect:

    function pPageSelect($link, $param, $style = null, $styleCurrent = null) {
                if (!$this->_pageSize) return false;
                $max = 0;
                while (($max * $this->_pageSize) < $this->_total) {
                        $max++;
                }
                if ($max == 0) {
                        return false;
                }
                if (ereg('\?',$link)) {
                  $link .= '&';
                } else {
                  $link .= '?';
                }
                print '<select name="_'.$param.'"';
                print Tzn::_style($style);
                print ' onChange="location.href=\''.$link.$param
                .'=\'+this.options[this.selectedIndex].value">';
                for($i=1; $i<=$max; $i++) {
                  print '<option value="'.$i.'"';
                  if ($this->_page == $i) {
                          print ' selected="true"';
                          print Tzn::_style($styleCurrent);
                  }
                  print '>page '.$i.'</option>';
                }
                print '</select>';
    }

La modification est:

    function pPageSelect($link, $param, $style = null, $styleCurrent = null) {
                if (!$this->_pageSize) return false;
                $max = 0;
                while (($max * $this->_pageSize) < $this->_total) {
                        $max++;
                }
                if ($max == 0) {
                        return false;
                }
                if (ereg('\?',$link)) {
                  $link .= '&';
                } else {
                  $link .= '?';
                }
                print '<select name="_'.$param.'"';
                print parent::_style($style);
                print ' onChange="location.href=\''.$link.$param
                .'=\'+this.options[this.selectedIndex].value">';
                for($i=1; $i<=$max; $i++) {
                  print '<option value="'.$i.'"';
                  if ($this->_page == $i) {
                          print ' selected="true"';
                          print parent::_style($styleCurrent);
                  }
                  print '>page '.$i.'</option>';
                }
                print '</select>';
    }

Dans la fonction pPrevious:

        function pPrevious($link, $param, $text = "&lt;",
                $styleOn = TZN_DB_PAGING_ENABLED, $styleOff = TZN_DB_PAGING_DISABLED)
        {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classDisabled = style if disabled
                if (!$this->hasPrevious()) {
                        if (!empty($styleOff)) {
                                print '<span class="'.$styleOff.'">'.$text.'</span>';
                        } else {
                                print $text;
                        }
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= Tzn::_style($styleOn);
                        $end .= '>';
                        print $begin.($this->_page-1).$end.$text.'</a>';
                        return true;
                }
        }

La modification est:

        function pPrevious($link, $param, $text = "&lt;",
                $styleOn = TZN_DB_PAGING_ENABLED, $styleOff = TZN_DB_PAGING_DISABLED)
        {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classDisabled = style if disabled
                if (!$this->hasPrevious()) {
                        if (!empty($styleOff)) {
                                print '<span class="'.$styleOff.'">'.$text.'</span>';
                        } else {
                                print $text;
                        }
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= parent::_style($styleOn);
                        $end .= '>';
                        print $begin.($this->_page-1).$end.$text.'</a>';
                        return true;
                }
        }

Dans la fonction pNext:

        function pNext($link, $param, $text = "&gt;",
                $styleOn = TZN_DB_PAGING_ENABLED, $styleOff = TZN_DB_PAGING_DISABLED)
        {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classDisabled = style if disabled
                if (!$this->hasNext()) {
                        if (!empty($styleOff)) {
                                print '<span class="'.$styleOff.'">'.$text.'</span>';
                        } else {
                                print $text;
                        }
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= Tzn::_style($styleOn);
                        $end .= '>';
                        print $begin.($this->_page+1).$end.$text.'</a>';
                        return true;
                }
        }

La modification est:

        function pNext($link, $param, $text = "&gt;",
                $styleOn = TZN_DB_PAGING_ENABLED, $styleOff = TZN_DB_PAGING_DISABLED)
        {
                // link = page filename + original parameters
                // param = name of variable parameter for paging
                // current = current page (integer)
                // class = style for link
                // classDisabled = style if disabled
                if (!$this->hasNext()) {
                        if (!empty($styleOff)) {
                                print '<span class="'.$styleOff.'">'.$text.'</span>';
                        } else {
                                print $text;
                        }
                        return false;
                } else {
                        $begin = '<a href="'.$link;
                        if (ereg('\?',$link)) {
                                $begin .= '&';
                        } else {
                                $begin .= '?';
                        }
                        $begin.= $param.'=';
                        $end = '"';
                        $end .= parent::_style($styleOn);
                        $end .= '>';
                        print $begin.($this->_page+1).$end.$text.'</a>';
                        return true;
                }
        }

Fichier include/classes/tzn_generic.php

De très nombreux appels sur la classe Tzn sont réalisés au niveau de ce fichier:

                        return 'Error: Tzn::get (empty 1st parameter)';
                $value = Tzn::_value($keyval,$default);
                $value = Tzn::_value($keyval,$default);
                $value = Tzn::_value($keyval,$arrArgs[$i]);
                $value = Tzn::_value($keyval,$arrArgs[$i]);
                return Tzn::_strValue(func_get_args());
                return Tzn::_value($keyval,$default);
                return Tzn::_value($keyval,$default);
                return Tzn::_strValue(func_get_args());
                return Tzn::_value($keyval,$default);
                return Tzn::_value($keyval,$default);
                return Tzn::_value($keyval,$default);
                        // $value = Tzn::_value($keyval,$default);
                $value = Tzn::_value($keyval,$default);
                $value = Tzn::_value($keyval,$default);
                $value = Tzn::_value($arg[0],$default);
        $this->$key = Tzn::_dteValue($value);
                $this->$key = Tzn::_dtmValue($value,'SQL',$tz);
                print Tzn::_strValue(func_get_args());
                print Tzn::getNum($keyval,$default);
                print Tzn::getInt($keyval,$default);
                print Tzn::getDec($keyval,$param1,$param2);
                $value = Tzn::_value($keyval);
                        print '<a href="mailto:'.$value.'"'.Tzn::_style($style)
                $value = Tzn::_value($keyval);
                        $str = '<a href="'.$value.'" '.Tzn::_style($style);
                print nl2br(Tzn::_strValue(func_get_args()));
                $value = Tzn::_value($keyval,$default);
                print Tzn::_value($keyval,$default);
                $value = Tzn::getBol($keyval,$default);
                print Tzn::getDte($keyval,$format,$default);
                        $value = Tzn::getDtm($keyval,$format,$tz,$default);
                print Tzn::_tmz($value);
                        $value = Tzn::_value($keyval,$default);
                        $value = Tzn::_value($keyval,$default);
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = Tzn::_value($keyval,$default);
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = htmlspecialchars(Tzn::_value($keyval,$default));
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = Tzn::_value($keyval,$default);
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = Tzn::getHtm($keyval,$default);
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = Tzn::_value($keyval,$default);
                        Tzn::pImg($keyval,'',90,60);
                $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                $value = Tzn::getBol($keyval,$default);
                $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                $form .= Tzn::_style($style);
                $form .= Tzn::_style($style);
                Tzn::pError($name);
                $form .= Tzn::_style($style);
                Tzn::pError($name);
                        $value = Tzn::_value($keyval,$default);
        $form .= Tzn::_style($style);
            $form .=">".Tzn::_tmz($j)."</option>";
                        $value = Tzn::getDte($keyval,'FRM',$default);
                Tzn::_dateField($keyval,$value,$style,$xtra);
                Tzn::pError($keyval);
                        $value = Tzn::getDte($keyval,'FRM',$tz,$default);
                Tzn::_dateField($keyval,substr($value,0,10),$style,$xtra);
                        $form .= Tzn::_style($style);
                Tzn::pError($keyval);
                        $value = Tzn::get($keyval,$default);
                        $form .= Tzn::_style($style);
                // Tzn::pError($keyval);
                        $value = Tzn::get($keyval,$default);
                        $form .= Tzn::_style($style);
                // Tzn::pError($keyval);
                        $form .= Tzn::_style($style);
                                $url = Tzn::concatUrl($url,session_name()
                                $url = Tzn::concatUrl($url,'tznMessage='.urlencode($message));
                        $url = Tzn::concatUrl($url,'ref='.rawurlencode($_SERVER['REQUEST_URI']));
                $form .= Tzn::_style($style);

Il est donc compliqué de tous les expliquer.

Cependant, les appels qui sont effectué au sein de la déclaration de la classe Tzn seront remplacés par $this->. Ce qui corrigera également les messages dans les logs, apparus au fur et à mesure des modification:

 PHP Strict Standards:  Non-static method Tzn::_style() should not be called statically, assuming $this from incompatible context in
/var/opt/taskfreak/include/classes/tzn_generic.php on line 1799, referer: 

Tout les appels, sauf le dernier, sont effectués au sein de la classe Tzn. Comme il n'y a plus d'appel statique, il est possible de les remplacer par Tzn->.

Pour le cas du dernier appel, celui est réalisé au sein de la classe TznCollection dans la méthode qSelect.

        function qSelect($name, $default=null,$optional=false,
                $style='tznFormSelect',$xtra='')
        {
                $form = '<select name="'.$name.'" ';
                $form .= Tzn::_style($style);
                if ($xtra) {
                        $form .= $xtra;
                }
                $form .= '>';
                if ($optional) {
                        $form .='<option value="">'.$optional.'</option>';
                }
                foreach ($this->_data as $key => $value) {
                        $form .= '<option value="'.$key.'"';
                        if ($key == $default) {
                                $form .= ' selected="selected"';
                        }
                        $form .= '>'.$value.'</option>';
                }
                $form .= '</select>';
                echo $form;
        }

Cette fonction n'étant appelée à priori qu'une seul fois, il est envisageable de créer temporairement une instance de Tzn dans la méthode, la modification devent:

        function qSelect($name, $default=null,$optional=false,
                $style='tznFormSelect',$xtra='')
        {
                $form = '<select name="'.$name.'" ';
                $objTznTmp = new Tzn();
                $form .= $objTznTmp->_style($style);
                if ($xtra) {
                        $form .= $xtra;
                }
                $form .= '>';
                if ($optional) {
                        $form .='<option value="">'.$optional.'</option>';
                }
                foreach ($this->_data as $key => $value) {
                        $form .= '<option value="'.$key.'"';
                        if ($key == $default) {
                                $form .= ' selected="selected"';
                        }
                        $form .= '>'.$value.'</option>';
                }
                $form .= '</select>';
                echo $form;
        }

Fichier include/html/header.php

Des messages apparaîssent après connexion, avec pour origine le fichier include/html/header.php. En effet, il y a de nombreux appels à la fonction concatUrl et _getUserTZ:

            <div id="userdate"><?php echo strftime(TZN_DATETIME_SHX,time()-TZN_TZSERVER+Tzn::_getUserTZ()); ?></div>
      $pTmpLink = Tzn::concatUrl($_SESSION['linkItems'],'sProject=0');
                <li class="more"><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=today'); ?>" accesskey="l"><?php echo $langMenu['all_projects']; ?></a>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=future'); ?>" accesskey="f"><?php echo $langMenu['future_tasks']; ?></a></li>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=past'); ?>" accesskey="p"><?php echo $langMenu['past_tasks']; ?></a></li>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=all'); ?>" accesskey="a"><?php echo $langMenu['all_tasks']; ?></a></li>
                        $pTmpLink = Tzn::concatUrl($_SESSION['linkItems'],'sProject='.$objProj->id);
                <li class="more"><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=today'); ?>"><?php $objProj->p('name'); ?></a>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=future')?>"><?php echo $langMenu['future_tasks']; ?></a></li>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=past')?>"><?php echo $langMenu['past_tasks']; ?></a></li>
                    <li><a href="<?php echo Tzn::concatUrl($pTmpLink,'show=all')?>"><?php echo $langMenu['all_tasks']; ?></a></li>
            <a href="<?php echo Tzn::concatUrl($_SESSION['linkItems'],'sUser='.$objUser->id); ?>"><?php echo $langMenu['my_tasks']; ?></a> |
            <a href="<?php echo Tzn::concatUrl($_SESSION['linkItems'],'sUser=all'); ?>"><?php echo $langMenu['all_users']; ?></a> |

La correction s'effectue en appelant la variable $objTzn ainsi:

            <div id="userdate"><?php echo strftime(TZN_DATETIME_SHX,time()-TZN_TZSERVER+$objTzn->_getUserTZ()); ?></div>
      $pTmpLink = $objTzn->concatUrl($_SESSION['linkItems'],'sProject=0');
                <li class="more"><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=today'); ?>" accesskey="l"><?php echo $langMenu['all_projects']; ?></a>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=future'); ?>" accesskey="f"><?php echo $langMenu['future_tasks']; ?></a></li>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=past'); ?>" accesskey="p"><?php echo $langMenu['past_tasks']; ?></a></li>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=all'); ?>" accesskey="a"><?php echo $langMenu['all_tasks']; ?></a></li>
                        $pTmpLink = $objTzn->concatUrl($_SESSION['linkItems'],'sProject='.$objProj->id);
                <li class="more"><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=today'); ?>"><?php $objProj->p('name'); ?></a>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=future')?>"><?php echo $langMenu['future_tasks']; ?></a></li>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=past')?>"><?php echo $langMenu['past_tasks']; ?></a></li>
                    <li><a href="<?php echo $objTzn->concatUrl($pTmpLink,'show=all')?>"><?php echo $langMenu['all_tasks']; ?></a></li>
            <a href="<?php echo $objTzn->concatUrl($_SESSION['linkItems'],'sUser='.$objUser->id); ?>"><?php echo $langMenu['my_tasks']; ?></a> |
            <a href="<?php echo $objTzn->concatUrl($_SESSION['linkItems'],'sUser=all'); ?>"><?php echo $langMenu['all_users']; ?></a> |


PHP Strict Standards - Déclaration de méthode

Le précédent chapitre a permit de supprimer tous les appels statique en passant par une instance de la classe Tzn. Cependant des messages d'erreur demeurent concernant une compatibilité de fonction dans les extensions. Rien qu'après la connexion l'ensemble de ces messages sont affichés:

PHP Strict Standards:  Declaration of TznUser::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/tzn_user.php on line 0, referer: 
PHP Strict Standards:  Declaration of TznUser::update() should be compatible with TznDb::update($fields = NULL, $filter = NULL) in
/var/opt/taskfreak/include/classes/tzn_user.php on line 0, referer: 
PHP Strict Standards:  Declaration of Project::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of Project::delete() should be compatible with TznDb::delete($filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of ProjectStats::load() should be compatible with TznDb::load() in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of ProjectStats::loadList() should be compatible with TznDb::loadList($sql1 = NULL, $sql2 = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of ProjectStats::update() should be compatible with TznDb::update($fields = NULL, $filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of MemberProject::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of MemberProject::update() should be compatible with TznDb::update($fields = NULL, $filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of MemberProject::delete() should be compatible with TznDb::delete($filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of Member::delete() should be compatible with TznDb::delete($filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_member.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemStatus::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of Item::loadList() should be compatible with TznDb::loadList($sql1 = NULL, $sql2 = NULL) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of Item::delete() should be compatible with TznDb::delete($filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemStats::load() should be compatible with TznDb::load() in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemStats::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemStats::update() should be compatible with TznDb::update($fields = NULL, $filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemComment::add() should be compatible with TznDb::add($ignore = false) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemComment::update() should be compatible with TznDb::update($fields = NULL, $filter = NULL) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 
PHP Strict Standards:  Declaration of ItemCommentFull::loadList() should be compatible with TznDb::loadList($sql1 = NULL, $sql2 = NULL) in
/var/opt/taskfreak/include/classes/pkg_project.php on line 0, referer: 

L'écriture des extensions de la classe TznDb sont l'unique raison de ces messages. La correction aurait pu être difficiles en devant ouvrir la classe TznDb et de chercher la définition de chacune des méthodes. Heureusement, les warning sont suffisament parlant et permettent d'appliquer rapidement les correctifs.

Fichier include/classes/tzn_user.php

Les messages indiquent un nom respect au niveau des fonctions add et update.

Fonction add:

        function add() {
                $this->setDtm('creationDate','NOW');
                return parent::add();
        }

Devient:

        function add($ignore = false) {
                $this->setDtm('creationDate','NOW');
                return parent::add();
        }

Fonction update:

    function update($fields=null) {
        $this->setDtm('lastChangeDate','NOW');
        if ($fields && (!preg_match('/lastChangeDate/',$fields))) {
                $fields .= ",lastChangeDate";
        }
        return parent::update($fields);
    }

Devient:

    function update($fields=null, $filter = NULL) {
        $this->setDtm('lastChangeDate','NOW');
        if ($fields && (!preg_match('/lastChangeDate/',$fields))) {
                $fields .= ",lastChangeDate";
        }
        return parent::update($fields);
    }

Fichier include/classes/pkg_member.php

Les messages indiquent un nom respect au niveau pour différentes classes.

Classe Project

Fonction add est définie ainsi:

        function add($status,$userId) {
                if (parent::add()) {
            // add poroject initial status
                        if ($this->setStatus($status,$userId)) {
                // add user as project leader
                $objLeader = new MemberProject();
                $objLeader->initObjectProperties();
                $objLeader->project->id = $this->id;
                $objLeader->member->id = $userId;
                $objLeader->position = FRK_PROJECT_LEADER; // leader
                return $objLeader->add();
            } else {
                // -TODO- rollback
                return false;
            }
                } else {
                        return false;
                }
        }

La classe Project est une extension de TznDb où la fonction add est définie ainsi:

    function add($ignore=false) {
    	// create SQL statement
		$strSql = 'INSERT '.(($ignore)?'IGNORE ':'')
			.'INTO '.$this->gTable().' SET ';
		if ($this->id) {
			$strSql .= $this->getIdKey()."='".$this->id."', ";
		}
		$strSql .= $this->zPropsToSql();
		$this->getConnection();
		if ($this->query($strSql)) {
            $this->id = mysql_insert_id();
			if (!$this->id) {
				return true;
			} else {
    			return $this->id;
            }
		} else {
			return false;
		} 
    }

Pour cette fonction, la correction est donc plus compliquée. Il ne fait pas simplement rajouter un ou plusieurs arguments, c'est l'inverse. Il serait possible de modifier la définition de la fonction add dans TznDb mais cela risque d'entraîner plus de modifications, sur les autres sous classes.

Une bonne stratégie est de renommer la fonction en addItem dans la classe Project, et de modifier les différents appels. Le code devient donc:

        function addItem($status,$userId) {
                if (parent::add()) {
            // add poroject initial status
                        if ($this->setStatus($status,$userId)) {
                // add user as project leader
                $objLeader = new MemberProject();
                $objLeader->initObjectProperties();
                $objLeader->project->id = $this->id;
                $objLeader->member->id = $userId;
                $objLeader->position = FRK_PROJECT_LEADER; // leader
                return $objLeader->add();
            } else {
                // -TODO- rollback
                return false;
            }
                } else {
                        return false;
                }
        }

La classe Project possèdent deux sous classes ProjectStats et ProjectStatsFull.

Pour la première, la fonction add est étendue ainsi:

    function add($status,$userId) {
        $this->_cleanProperties();
        return parent::add($status,$userId);
    }

Le nom de celle-ci est donc modifié, ainsi que l'appel à la fonction parente:

    function addItem($status,$userId) {
        $this->_cleanProperties();
        return parent::addItem($status,$userId);
    }

Pour la classe ProjectStatsFull, il n'y a aucune référence à la fonction modifiée.


Il faut ensuite chercher les différents appels.

Fichier xajax.task.php, dans le fonction task_update_full, la classe Project est instanciée dans la variable $objProject. l'appel à la fonction add est réalisée quelques lignes plus tard:

        // create new project on the fly
        $objProject->set('name',$data['project2']);
        $objProject->add(0,$objUser->id);
        //$objResponse->addScript('freak_rld()');

L'appel est modifié ainsi:

        // create new project on the fly
        $objProject->set('name',$data['project2']);
        $objProject->addItem(0,$objUser->id);
        //$objResponse->addScript('freak_rld()');

Fichier project_edit.php, la classe Project est instanciée dans la variable $objProject. l'appel à la fonction add est réalisée quelques lignes plus tard:

if ($_POST['submit']) {
    // --- save general info ---
    if ($pUserCanEdit) {
        $objEditItem->setAuto($_POST);
        if ($objEditItem->check()) { // register form is valid
            if ($objEditItem->isLoaded()) {
                // --- save project status ---
                if ($pUserCanStatus && !$pMessageError && $_POST['status'] != $objEditItem->projectStatus->statusKey) {
                    $objEditItem->setStatus($_POST['status'],$objUser->id);
                    $pMessageStatus = $GLOBALS['langProject']['action_status_ok'];
                } else {
                    $pMessageStatus = $GLOBALS['langProject']['action_save_ok'];
                }
                $objEditItem->update();
            } else if ($objEditItem->add($_POST['status'],$objUser->id)) { // add in DB
                $pMessageStatus = $GLOBALS['langProject']['action_added_ok'];
            }
        } else {
    		$pMessageError = $GLOBALS['langTznCommon']['form_error'];
        }
	}

}

L'appel est modifié ainsi:

if ($_POST['submit']) {
    // --- save general info ---
    if ($pUserCanEdit) {
        $objEditItem->setAuto($_POST);
        if ($objEditItem->check()) { // register form is valid
            if ($objEditItem->isLoaded()) {
                // --- save project status ---
                if ($pUserCanStatus && !$pMessageError && $_POST['status'] != $objEditItem->projectStatus->statusKey) {
                    $objEditItem->setStatus($_POST['status'],$objUser->id);
                    $pMessageStatus = $GLOBALS['langProject']['action_status_ok'];
                } else {
                    $pMessageStatus = $GLOBALS['langProject']['action_save_ok'];
                }
                $objEditItem->update();
            } else if ($objEditItem->addItem($_POST['status'],$objUser->id)) { // add in DB
                $pMessageStatus = $GLOBALS['langProject']['action_added_ok'];
            }
        } else {
    		$pMessageError = $GLOBALS['langTznCommon']['form_error'];
        }
	}

}


Fonction delete:

        function delete() {
                if ($this->id) {
                        $this->getConnection();
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                // mySQL 4.1 and later (SQL subqueries allowed)
                                return $this->query('DELETE '.$this->gTable('project').', '.$this->gTable('memberProject').', '
                                        .$this->gTable('projectStatus').', '.$this->gTable('item').', '.$this->gTable('itemComment')
                                        .', '.$this->gTable('itemFile').', '.$this->gTable('itemStatus')
                                        .' FROM '.$this->gTable('project')
                                        .' INNER JOIN '.$this->gTable('memberProject').' ON '.$this->gTable('memberProject').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('projectStatus').' ON '.$this->gTable('projectStatus').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('item').' ON '.$this->gTable('item').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('itemComment').' ON '.$this->gTable('itemComment').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' LEFT JOIN '.$this->gTable('itemFile').' ON '.$this->gTable('itemFile').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' LEFT JOIN '.$this->gTable('itemStatus').' ON '.$this->gTable('itemStatus').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' WHERE '.$this->gTable('project').'.projectId = '.$this->id);
                        } else {
                                // mysql 3.23 and 4.0 -TODO- transactions
                                // 1. delete project
                                if (parent::delete()) {
                                        // 2. delete members
                                        $this->query('DELETE FROM '.$this->gTable('memberProject').' WHERE projectId='.$this->id);
                                        // 3. search project tasks
                                        if ($objResult = $this->query('SELECT itemId FROM '.$this->gTable('item')
                                                .' WHERE projectId='.$this->id))
                                        {
                                                $arrIds = array();
                                                while($objItem = $objResult->rNext()) {
                                                        $arrIds[] = $objItem->itemId;
                                                }
                                                if (count($arrIds)) {
                                                        $strIds = implode(',',$arrIds);
                                                        // 3.1. delete task comments
                                                        $this->query('DELETE FROM '.$this->gTable('itemComment')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                        // 3.2 delete task files
                                                        $this->query('DELETE FROM '.$this->gTable('itemFile')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                        // 3.3. delete task status
                                                        $this->query('DELETE FROM '.$this->gTable('itemStatus')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                }
                                                // 3.4. delete tasks
                                                $this->query('DELETE FROM '.$this->gTable('item').' WHERE projectId='.$this->id);
                                                // 4. delete status history
                                                $this->query('DELETE FROM '.$this->gTable('projectStatus').' WHERE projectId='.$this->id);
                                                return true;
                                        } // end search
                                }
                        }
                }
                return false;
        }

Devient:

        function delete($filter = null) {
                if ($this->id) {
                        $this->getConnection();
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                // mySQL 4.1 and later (SQL subqueries allowed)
                                return $this->query('DELETE '.$this->gTable('project').', '.$this->gTable('memberProject').', '
                                        .$this->gTable('projectStatus').', '.$this->gTable('item').', '.$this->gTable('itemComment')
                                        .', '.$this->gTable('itemFile').', '.$this->gTable('itemStatus')
                                        .' FROM '.$this->gTable('project')
                                        .' INNER JOIN '.$this->gTable('memberProject').' ON '.$this->gTable('memberProject').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('projectStatus').' ON '.$this->gTable('projectStatus').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('item').' ON '.$this->gTable('item').'.projectId = '.$this->gTable('project').'.projectId'
                                        .' LEFT JOIN '.$this->gTable('itemComment').' ON '.$this->gTable('itemComment').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' LEFT JOIN '.$this->gTable('itemFile').' ON '.$this->gTable('itemFile').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' LEFT JOIN '.$this->gTable('itemStatus').' ON '.$this->gTable('itemStatus').'.itemId = '.$this->gTable('item').'.itemId'
                                        .' WHERE '.$this->gTable('project').'.projectId = '.$this->id);
                        } else {
                                // mysql 3.23 and 4.0 -TODO- transactions
                                // 1. delete project
                                if (parent::delete()) {
                                        // 2. delete members
                                        $this->query('DELETE FROM '.$this->gTable('memberProject').' WHERE projectId='.$this->id);
                                        // 3. search project tasks
                                        if ($objResult = $this->query('SELECT itemId FROM '.$this->gTable('item')
                                                .' WHERE projectId='.$this->id))
                                        {
                                                $arrIds = array();
                                                while($objItem = $objResult->rNext()) {
                                                        $arrIds[] = $objItem->itemId;
                                                }
                                                if (count($arrIds)) {
                                                        $strIds = implode(',',$arrIds);
                                                        // 3.1. delete task comments
                                                        $this->query('DELETE FROM '.$this->gTable('itemComment')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                        // 3.2 delete task files
                                                        $this->query('DELETE FROM '.$this->gTable('itemFile')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                        // 3.3. delete task status
                                                        $this->query('DELETE FROM '.$this->gTable('itemStatus')
                                                                .' WHERE itemId IN ('.$strIds.')');
                                                }
                                                // 3.4. delete tasks
                                                $this->query('DELETE FROM '.$this->gTable('item').' WHERE projectId='.$this->id);
                                                // 4. delete status history
                                                $this->query('DELETE FROM '.$this->gTable('projectStatus').' WHERE projectId='.$this->id);
                                                return true;
                                        } // end search
                                }
                        }
                }
                return false;
        }

Classe ProjectStats

Fonction load est définie ainsi:

    function load($userId, $strict=true) {
        if (!$this->id) {
            return false;
        }
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sqlSelect = 'SELECT pp.*, ps.statusKey AS projectStatus_statusKey, '
                .'p1.position AS memberProject_position, ps.statusDate AS projectStatus_statusDate '
                .'FROM '.$this->gTable().' AS pp '
                .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
                .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
                .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId
                .' WHERE ps.statusDate=(SELECT MAX(ps2.statusDate) FROM '.$this->gTable('projectStatus')
                .' AS ps2 WHERE pp.projectId = ps2.projectId) '
                .' AND ps.projectId = '.$this->id
                .' GROUP BY ps.projectId';
        } else {
            $sqlSelect = 'SELECT pp.*, p1.position AS memberProject_position, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),1,19) AS projectStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),20) AS projectStatus_statusKey '
                .'FROM '.$this->gTable().' AS pp '
                .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
                .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
                    .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId
                .' WHERE ps.projectId = '.$this->id
                .' GROUP BY ps.projectId';
        }

        //echo '<div class="debug">'.$sqlSelect.'</div>'; exit;

        return $this->loadByQuery($sqlSelect);
    }

La classe ProjectStats est une extension de Project, elle même une sous classe de TznDb. Cependant, la fonction n'effectue qu'un appel à la fonction loadByQuery après avoir construit la requête. Une bonne stratégie est de renommer la fonction en loadItems et de modifier les différents appels. Le code devient donc:

    function loadItems($userId, $strict=true) {
        if (!$this->id) {
            return false;
        }
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sqlSelect = 'SELECT pp.*, ps.statusKey AS projectStatus_statusKey, '
                .'p1.position AS memberProject_position, ps.statusDate AS projectStatus_statusDate '
                .'FROM '.$this->gTable().' AS pp '
                .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
                .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
                .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId
                .' WHERE ps.statusDate=(SELECT MAX(ps2.statusDate) FROM '.$this->gTable('projectStatus')
                .' AS ps2 WHERE pp.projectId = ps2.projectId) '
                .' AND ps.projectId = '.$this->id
                .' GROUP BY ps.projectId';
        } else {
            $sqlSelect = 'SELECT pp.*, p1.position AS memberProject_position, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),1,19) AS projectStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),20) AS projectStatus_statusKey '
                .'FROM '.$this->gTable().' AS pp '
                .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
                .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
                    .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId
                .' WHERE ps.projectId = '.$this->id
                .' GROUP BY ps.projectId';
        }

        //echo '<div class="debug">'.$sqlSelect.'</div>'; exit;
        
        return $this->loadByQuery($sqlSelect);
    }

Il faut ensuite chercher les différents appels.

Fichier project_edit.php, la classe ProjectStats est instanciée dans la variable $objEditItem. l'appel à la fonction load est réalisée quelques lignes plus tard:

	if (!$objEditItem->load($objUser->id,false)) {
		$objTzn->redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied'].' (error #424)');

L'appel est modifié ainsi:

	if (!$objEditItem->loadItems($objUser->id,false)) {
		$objTzn->redirect('project_list.php','ERROR:'.$langMessage['not_found_or_denied'].' (error #424)');


Fonction loadList est définie ainsi:

        function loadList($userId, $strict=true) {
        $sqlCommon = 'FROM '.$this->gTable().' AS pp '
            .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
            .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
            .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId;
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sqlCount = 'SELECT COUNT(DISTINCT pp.projectId) as rowCount, ps.statusDate AS projectStatus_statusDate, '.$sqlCommon;
            $sqlSelect = 'SELECT pp.*, ps.statusKey AS projectStatus_statusKey, '
                .'p1.position AS memberProject_position, ps.statusDate as projectStatus_statusDate '.$sqlCommon;
            $this->addWhere('ps.statusDate=(SELECT MAX(ps2.statusDate) FROM '.$this->gTable('projectStatus')
                .' AS ps2 WHERE pp.projectId = ps2.projectId)');
        } else {
            $sqlCount = 'SELECT COUNT(DISTINCT pp.projectId) as rowCount '.$sqlCommon;
            $sqlSelect = 'SELECT pp.*, p1.position AS memberProject_position, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),1,19) AS projectStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),20) AS projectStatus_statusKey '
                .$sqlCommon;
        }
        $this->addGroup('ps.projectId');

                return parent::loadList($sqlCount,$sqlSelect);
        }

La classe ProjectStats est une extension de Project, elle même une sous classe de TznDb. Cependant, la fonction loadList n'est pas définie dans Project, et c'est celle définie dans TznDb qui est exécutée. Une bonne stratégie est de renommer la fonction en loadItems et de modifier les différents appels. Le code devient donc:

        function loadListItems($userId, $strict=true) {
        $sqlCommon = 'FROM '.$this->gTable().' AS pp '
            .'INNER JOIN '.$this->gTable('projectStatus').' AS ps ON ps.projectId = pp.projectId '
            .(($strict)?'INNER':'LEFT').' JOIN '.$this->gTable('memberProject')
            .' AS p1 ON p1.projectId=pp.projectId AND p1.memberId='.$userId;
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sqlCount = 'SELECT COUNT(DISTINCT pp.projectId) as rowCount, ps.statusDate AS projectStatus_statusDate, '.$sqlCommon;
            $sqlSelect = 'SELECT pp.*, ps.statusKey AS projectStatus_statusKey, '
                .'p1.position AS memberProject_position, ps.statusDate as projectStatus_statusDate '.$sqlCommon;
            $this->addWhere('ps.statusDate=(SELECT MAX(ps2.statusDate) FROM '.$this->gTable('projectStatus')
                .' AS ps2 WHERE pp.projectId = ps2.projectId)');
        } else {
            $sqlCount = 'SELECT COUNT(DISTINCT pp.projectId) as rowCount '.$sqlCommon;
            $sqlSelect = 'SELECT pp.*, p1.position AS memberProject_position, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),1,19) AS projectStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(ps.statusDate,ps.statusKey)),20) AS projectStatus_statusKey '
                .$sqlCommon;
        }
        $this->addGroup('ps.projectId');

                return parent::loadList($sqlCount,$sqlSelect);
        }



Fonction update:

    function update($param='') {
        $this->_cleanProperties();
        parent::update($param);
    }

Devient:

    function update($fields='', $filter=null) {
        $this->_cleanProperties();
        parent::update($fields);
    }

Classe MemberProject

Fonction add:

        function add() {
                if (!$this->project->id || !$this->member->id) {
                        return false;
                }
                $this->getConnection();
                if ($this->loadByFilter($this->gTable('memberProject').'.projectId='.$this->project->id
                        .' AND '.$this->gTable('memberProject').'.memberId='.$this->member->id))
                {
                        // already in project
                        return false;
                } else {
                        return parent::add();
                }
        }


Devient:

        function add($ignore=false) {
                if (!$this->project->id || !$this->member->id) {
                        return false;
                }
                $this->getConnection();
                if ($this->loadByFilter($this->gTable('memberProject').'.projectId='.$this->project->id
                        .' AND '.$this->gTable('memberProject').'.memberId='.$this->member->id))
                {
                        // already in project
                        return false;
                } else {
                        return parent::add();
                }
        }

Fonction update:

        function update($fields=null) {
                parent::update($fields,'projectId='.$this->project->id
                        .' AND memberId='.$this->member->id);
        }


Devient:

        function update($fields=null,$filter=null) {
                parent::update($fields,'projectId='.$this->project->id
                        .' AND memberId='.$this->member->id);
        }

Fonction delete:

        function delete() {
                if ($this->project->id && $this->member->id) {
                        $this->getConnection();
                        return $this->query('DELETE FROM '.$this->gTable()
                                .' WHERE projectId='.$this->project->id
                                .' AND memberId='.$this->member->id);
                } else {
                        return false;
                }
        }


Devient:

        function delete($filter=null) {
                if ($this->project->id && $this->member->id) {
                        $this->getConnection();
                        return $this->query('DELETE FROM '.$this->gTable()
                                .' WHERE projectId='.$this->project->id
                                .' AND memberId='.$this->member->id);
                } else {
                        return false;
                }
        }

Classe Member

Fonction delete:

    function delete() {
        if ($this->id) {
                        // 1. delete member
                        if (parent::delete()) {
                // 2. delete projects relations
                                $this->query('DELETE FROM '.$this->gTable('memberProject').' WHERE memberId='.$this->id);
                // 3. unassociate tasks
                $this->query('UPDATE '.$this->gTable('item').' SET memberId=0 WHERE memberId='.$this->id);
                // 4. delete comments
                $this->query('DELETE FROM '.$this->gTable('itemStatus').' WHERE memberId='.$this->id);
                // 5. unassociate users
                $this->query('UPDATE '.$this->gTable('member').' SET authorId=1 WHERE authorId='.$this->id);
                return true;
            }
        }
        return false;
    }


Devient:

    function delete($filter=null) {
        if ($this->id) {
                        // 1. delete member
                        if (parent::delete()) {
                // 2. delete projects relations
                                $this->query('DELETE FROM '.$this->gTable('memberProject').' WHERE memberId='.$this->id);
                // 3. unassociate tasks
                $this->query('UPDATE '.$this->gTable('item').' SET memberId=0 WHERE memberId='.$this->id);
                // 4. delete comments
                $this->query('DELETE FROM '.$this->gTable('itemStatus').' WHERE memberId='.$this->id);
                // 5. unassociate users
                $this->query('UPDATE '.$this->gTable('member').' SET authorId=1 WHERE authorId='.$this->id);
                return true;
            }
        }
        return false;
    }


Fichier include/classes/pkg_project.php

Les messages indiquent un nom respect au niveau pour différentes classes.

Classe ItemStatus

Fonction add:

    function add() {
        if (!$this->statusDate) {
            $this->setDtm('statusDate','NOW');
        }
        return parent::add();
    }

Devient:

    function add($ignore=false) {
        if (!$this->statusDate) {
            $this->setDtm('statusDate','NOW');
        }
        return parent::add();
    }

Classe Item

Fonction loadList:

        function loadList($sql='') {
                if ($sql) {
                        return parent::loadList(TZN_DB_COUNT_OFF,$sql);
                } else {
                        $sql = 'SELECT ii.*, ';
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                $sql .= 'iis.statusDate as itemStatus_statusDate, iis.statusKey as itemStatus_statusKey, ';
                        } else {
                                $sql .= 'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                                        .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, ';
                        }
                        $sql .= 'pp.name as project_name, '
                                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                                .'mm.lastName as member_lastName, mm.username as member_username';
                        if ($userId) {
                                $sql .= ', mp.position';
                        }
                        $sql .= ' FROM '.$this->gTable().' as ii '
                                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId';
                        if ($userId) {
                                $sql .= ' LEFT JOIN '.$this->gTable('memberProject')
                                        .' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId;
                        }
                        $sql .= ' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId ';
                        $this->addGroup('ii.itemId');
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                $this->addWhere('iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                                        .' AS iis2 WHERE ii.itemId = iis2.itemId)');
                        }
                        return parent::loadList(TZN_DB_COUNT_AUTO,$sql);
                }
        }

Devient:

        function loadList($sql='', $sql2=null) {
                if ($sql) {
                        return parent::loadList(TZN_DB_COUNT_OFF,$sql);
                } else {
                        $sql = 'SELECT ii.*, ';
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                $sql .= 'iis.statusDate as itemStatus_statusDate, iis.statusKey as itemStatus_statusKey, ';
                        } else {
                                $sql .= 'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                                        .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, ';
                        }
                        $sql .= 'pp.name as project_name, '
                                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                                .'mm.lastName as member_lastName, mm.username as member_username';
                        if ($userId) {
                                $sql .= ', mp.position';
                        }
                        $sql .= ' FROM '.$this->gTable().' as ii '
                                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId';
                        if ($userId) {
                                $sql .= ' LEFT JOIN '.$this->gTable('memberProject')
                                        .' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId;
                        }
                        $sql .= ' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId ';
                        $this->addGroup('ii.itemId');
                        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                                $this->addWhere('iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                                        .' AS iis2 WHERE ii.itemId = iis2.itemId)');
                        }
                        return parent::loadList(TZN_DB_COUNT_AUTO,$sql);
                }
        }

Fonction delete:

        function delete() {
                if (parent::delete()) {
                        $this->query('DELETE FROM '.$this->gTable('itemStatus').' WHERE itemId='.$this->id);
                        $this->query('DELETE FROM '.$this->gTable('itemComment').' WHERE itemId='.$this->id);
                        $this->query('DELETE FROM '.$this->gTable('itemFile').' WHERE itemId='.$this->id);
                        return true;
                }
                return false;
        }

Devient:

        function delete($filter = null) {
                if (parent::delete()) {
                        $this->query('DELETE FROM '.$this->gTable('itemStatus').' WHERE itemId='.$this->id);
                        $this->query('DELETE FROM '.$this->gTable('itemComment').' WHERE itemId='.$this->id);
                        $this->query('DELETE FROM '.$this->gTable('itemFile').' WHERE itemId='.$this->id);
                        return true;
                }
                return false;
        }

Classe ItemStats

Fonction load est définie ainsi:

        function load($userId) {
                if (!$this->id) {
                        return false;
                }
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sql = 'SELECT ii.*, count(iic.postDate) as itemCommentCount, count(iif.postDate) as itemFileCount, '
                .'iis.statusDate as itemStatus_statusDate, iis.statusDate, iis.statusKey as itemStatus_statusKey, '
                .'pp.name as project_name, '
                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                .'mm.lastName as member_lastName, mm.username as member_username, mp.position '
                .'FROM '.$this->gTable().' AS ii '
                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId '
                .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId
                .' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
                .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
                .'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId '
                .'WHERE iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                .' AS iis2 WHERE ii.itemId = iis2.itemId) AND ii.itemId = '.$this->id.' GROUP BY ii.itemId';
        } else {
            $sql = 'SELECT ii.*, count(iic.postDate) as itemCommentCount, count(iif.postDate) as itemFileCount, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, '
                .'pp.name as project_name, '
                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                .'mm.lastName as member_lastName, mm.username as member_username, mp.position '
                .'FROM '.$this->gTable().' AS ii '
                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId '
                .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId
                .' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
                .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
                .'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId '
                .'WHERE ii.itemId = '.$this->id.' GROUP BY ii.itemId';
        }
                $this->getConnection();
                if ($result = $this->query($sql)) {
                        if ($data = $result->rNext()) {
                                $this->setAuto($data);
                                $this->_loaded = true;
                                return $this->id;
                        }
        }
                return false;
        }

La classe ItemStats est une extension de Item, où la fonction loadByQuery. Cependant, la fonction aucun appel à la fonction parente. Une bonne stratégie est de renommer la fonction en loadItemStats et de modifier les différents appels. Le code devient donc:

	function loadItemStats($userId) {
		if (!$this->id) {
			return false;
		}
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            // optimize for mysql > 4.1
            $sql = 'SELECT ii.*, count(iic.postDate) as itemCommentCount, count(iif.postDate) as itemFileCount, '
                .'iis.statusDate as itemStatus_statusDate, iis.statusDate, iis.statusKey as itemStatus_statusKey, '
                .'pp.name as project_name, '
                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                .'mm.lastName as member_lastName, mm.username as member_username, mp.position '
                .'FROM '.$this->gTable().' AS ii '
                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId '
                .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId
                .' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
                .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
                .'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId '
                .'WHERE iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                .' AS iis2 WHERE ii.itemId = iis2.itemId) AND ii.itemId = '.$this->id.' GROUP BY ii.itemId';
        } else {
            $sql = 'SELECT ii.*, count(iic.postDate) as itemCommentCount, count(iif.postDate) as itemFileCount, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, '
                .'pp.name as project_name, '
                .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
                .'mm.lastName as member_lastName, mm.username as member_username, mp.position '
                .'FROM '.$this->gTable().' AS ii '
                .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
                .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId '
                .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId
                .' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
                .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
                .'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId '
                .'WHERE ii.itemId = '.$this->id.' GROUP BY ii.itemId';
        }
		$this->getConnection();
		if ($result = $this->query($sql)) {
			if ($data = $result->rNext()) {
				$this->setAuto($data);
				$this->_loaded = true;
				return $this->id;
			}   
        }
		return false;
	}

Il faut ensuite chercher les différents appels.

Fichier xajax.task.php, la fonction est appelée dans la fonction ajaxLoadTask:

function ajaxLoadTask($id,$level,$userCanToo,&$objUser,&$objResponse) {
    $objResponse = new xajaxResponse();
    if (!($objUser = ajaxCheckSession($objResponse))) {
        return false;
    }

    $objTask = new ItemStats();
        $objTask->setUid($id);
        if ($objTask->load($objUser->id)) {
                //error_log('task loaded:'.$id.', user '.$objUser->id.' has position '.$objTask->position);
        if ($objUser->checkLevel(13) || $objUser->checkLevel(14) || $objTask->checkRights($objUser->id,$level,$userCanToo)) {
            return $objTask;
        }
    }
    $objResponse->addScriptCall('freak_error',$GLOBALS['langMessage']['not_found_or_denied']);
    return false;
}

Devient:

function ajaxLoadTask($id,$level,$userCanToo,&$objUser,&$objResponse) {
    $objResponse = new xajaxResponse();
    if (!($objUser = ajaxCheckSession($objResponse))) {
        return false;
    }

    $objTask = new ItemStats();
        $objTask->setUid($id);
        if ($objTask->loadItemStats($objUser->id)) {
                //error_log('task loaded:'.$id.', user '.$objUser->id.' has position '.$objTask->position);
        if ($objUser->checkLevel(13) || $objUser->checkLevel(14) || $objTask->checkRights($objUser->id,$level,$userCanToo)) {
            return $objTask;
        }
    }
    $objResponse->addScriptCall('freak_error',$GLOBALS['langMessage']['not_found_or_denied']);
    return false;
}

Le même type de modification est réalisée dans la fonction ajaxLoadTaskComment:

function ajaxLoadTaskComment($id,$level,&$objUser,&$objTask,&$objResponse) {
	$objResponse = new xajaxResponse();
	if (!($objUser = ajaxCheckSession($objResponse))) {
        return false;
	}
	$objComment = new ItemComment();
	$objComment->setUid($id);
	if ($objComment->load()) {
		$objTask = new ItemStats();
		$objTask->setUid($objComment->itemId);
		if ($objTask->load($objUser->id)) {
			if ($objComment->checkRights($objUser->id,$level,$objTask)
				|| $objUser->checkLevel(14)
				|| $objTask->checkRights($objUser->id,0)) 
			{
				return $objComment;
			}
		}
	}
	return false;
}

Devient:

function ajaxLoadTaskComment($id,$level,&$objUser,&$objTask,&$objResponse) {
	$objResponse = new xajaxResponse();
	if (!($objUser = ajaxCheckSession($objResponse))) {
        return false;
	}
	$objComment = new ItemComment();
	$objComment->setUid($id);
	if ($objComment->load()) {
		$objTask = new ItemStats();
		$objTask->setUid($objComment->itemId);
		if ($objTask->loadItemStats($objUser->id)) {
			if ($objComment->checkRights($objUser->id,$level,$objTask)
				|| $objUser->checkLevel(14)
				|| $objTask->checkRights($objUser->id,0)) 
			{
				return $objComment;
			}
		}
	}
	return false;
}

Enfin, dans la fonction task_update_full, il y a un appel:

    $objTask = new ItemStats();
	$objTask->setUid($data['id']);
	if ($objTask->load($objUser->id)) {
        $objOldTask = $objTask->cloneme();
        $pUserCanEdit = ($objUser->checkLevel(14) || $objTask->checkRights($objUser->id,7));
    }

Devient:

    $objTask = new ItemStats();
	$objTask->setUid($data['id']);
	if ($objTask->loadItemStats($objUser->id)) {
        $objOldTask = $objTask->cloneme();
        $pUserCanEdit = ($objUser->checkLevel(14) || $objTask->checkRights($objUser->id,7));
    }


La fonction loadList a été modifiée dans la classe Item précédement. L'implémentation dans ItemStats doit donc être modifiée. Le code original est:

	function loadList($userId=0) {
		$sql = 'SELECT ii.*, ';
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
			$sql .= 'count(iic.postDate) as itemCommentCount, '
				.'count(iif.postDate) as itemFileCount, '
				.'iis.statusDate as itemStatus_statusDate, iis.statusKey as itemStatus_statusKey, ';
        } else {
            $sql .= 'count(DISTINCT iic.postDate) as itemCommentCount, '
				.'count(DISTINCT iif.postDate) as itemFileCount, '
				.'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, ';
        }
        $sql .= 'pp.name as project_name, '
            .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
            .'mm.lastName as member_lastName, mm.username as member_username';
        if ($userId) {
            $sql .= ', mp.position';
        }
        $sql .= ' FROM '.$this->gTable().' as ii '
            .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
            .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId';
        if ($userId) {
            $sql .= ' LEFT JOIN '.$this->gTable('memberProject')
                .' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId;
        }
        $sql .= ' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
            .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
			.'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId ';
		$this->addGroup('ii.itemId');
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            $this->addWhere('iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                .' AS iis2 WHERE ii.itemId = iis2.itemId)');
        }
		return parent::loadList($sql);
	}

Le même type de modification est réalisée, à savoir l'ajout d'un argument dans la signature. La fonction devient:

        function loadList($userId=0, $sql2=null) {
                $sql = 'SELECT ii.*, ';
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
                        $sql .= 'count(iic.postDate) as itemCommentCount, '
                                .'count(iif.postDate) as itemFileCount, '
                                .'iis.statusDate as itemStatus_statusDate, iis.statusKey as itemStatus_statusKey, ';
        } else {
            $sql .= 'count(DISTINCT iic.postDate) as itemCommentCount, '
                                .'count(DISTINCT iif.postDate) as itemFileCount, '
                                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),1,19) AS itemStatus_statusDate, '
                .'SUBSTRING(MAX(CONCAT(iis.statusDate,iis.statusKey)),20) AS itemStatus_statusKey, ';
        }
        $sql .= 'pp.name as project_name, '
            .'mm.title as member_title, mm.firstName as member_firstName, mm.middleName as member_middleName, '
            .'mm.lastName as member_lastName, mm.username as member_username';
        if ($userId) {
            $sql .= ', mp.position';
        }
        $sql .= ' FROM '.$this->gTable().' as ii '
            .'INNER JOIN '.$this->gTable('itemStatus').' AS iis ON ii.itemId = iis.itemId '
            .'LEFT JOIN '.$this->gTable('project').' AS pp ON ii.projectId = pp.projectId';
        if ($userId) {
            $sql .= ' LEFT JOIN '.$this->gTable('memberProject')
                .' AS mp ON ii.projectId = mp.projectId AND mp.memberId='.$userId;
        }
        $sql .= ' LEFT JOIN '.$this->gTable('member').' AS mm ON ii.memberId = mm.memberId '
            .'LEFT JOIN '.$this->gTable('itemComment').' AS iic ON ii.itemId=iic.itemId '
                        .'LEFT JOIN '.$this->gTable('itemFile').' AS iif ON ii.itemId=iif.itemId ';
                $this->addGroup('ii.itemId');
        if (@constant('FRK_MYSQL_VERSION_GT_4_1')) {
            $this->addWhere('iis.statusDate=(SELECT MAX(iis2.statusDate) FROM '.$this->gTable('itemStatus')
                .' AS iis2 WHERE ii.itemId = iis2.itemId)');
        }
                return parent::loadList($sql);
        }


Fonction add:

    function add() {
        $this->_cleanProperties();
        parent::add();
    }

Devient:

    function add($ignore=false) {
        $this->_cleanProperties();
        parent::add();
    }

Fonction update:

    function update($param='') {
        $this->_cleanProperties();
        parent::update($param);
    }

Devient:

    function update($fields='',$filter=null) {
        $this->_cleanProperties();
        parent::update($fields);
    }

Classe ItemComment

Fonction add:

        function add() {
                $this->setDtm('postDate','NOW');
                return parent::add();
        }

Devient:

        function add($ignore=false) {
                $this->setDtm('postDate','NOW');
                return parent::add();
        }

Fonction update:

        function update() {
                $this->setDtm('lastChangeDate','NOW');
                return parent::update();
        }

Devient:

        function update($fields=null,$filter=null) {
                $this->setDtm('lastChangeDate','NOW');
                return parent::update();
        }

Classe ItemComment

Fonction loadList:

        function loadList() {
                $sql = 'SELECT iic.*, mm.username as member_username, mm.timeZone as member_timeZone,'
                        .'mm.creationDate as member_creationDate, mm.firstName as member_firstName, '
                        .'mm.middleName as member_middleName, mm.lastName as member_lastName, '
                        .'mp.position as memberTeam_position '
                        .'FROM '.$this->gTable('itemComment').' AS iic '
            .'INNER JOIN '.$this->gTable('member').' AS mm ON iic.memberId=mm.memberId '
                        .'INNER JOIN '.$this->gTable('item').' AS ii ON ii.itemId = iic.itemId '
                        .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON iic.memberId = mp.memberId '
                        .'AND mp.teamId = ii.teamId';
                return parent::loadList($sql);
        }

Devient:

        function loadList($sql1 = null, $sql2 = null) {
                $sql = 'SELECT iic.*, mm.username as member_username, mm.timeZone as member_timeZone,'
                        .'mm.creationDate as member_creationDate, mm.firstName as member_firstName, '
                        .'mm.middleName as member_middleName, mm.lastName as member_lastName, '
                        .'mp.position as memberTeam_position '
                        .'FROM '.$this->gTable('itemComment').' AS iic '
            .'INNER JOIN '.$this->gTable('member').' AS mm ON iic.memberId=mm.memberId '
                        .'INNER JOIN '.$this->gTable('item').' AS ii ON ii.itemId = iic.itemId '
                        .'LEFT JOIN '.$this->gTable('memberProject').' AS mp ON iic.memberId = mp.memberId '
                        .'AND mp.teamId = ii.teamId';
                return parent::loadList($sql);
        }


PHP Warning - Creating default object

Lors de la création de tâches, les messages suivants sont constatés dans la log d'erreur Apache :

PHP Warning:  Creating default object from empty value in /var/opt/taskfreak-ldap-0.6.4/include/classes/pkg_project.php on line 90
PHP Warning:  Creating default object from empty value in /var/opt/taskfreak-ldap-0.6.4/include/classes/pkg_project.php on line 90
PHP Warning:  Creating default object from empty value in /var/opt/taskfreak-ldap-0.6.4/xajax.task.php on line 223

Fichier pkg_project.php

La fonction setStatus affecte l'identifiant de l'utilisateur connecté lors de la création d'une tâche. Or cette valeur est une propriété de l'objet member qui n'est pas initialisé sur l'instance de ItemStatus.

    function setStatus($status,$userId) {
        $objItemStatus = new ItemStatus();
        $objItemStatus->itemId = $this->id;
        $objItemStatus->member->id = $userId;
        $objItemStatus->statusKey = $status;
        $objItemStatus->add();
    }

La propriété member est visiblement une instance de la classe Member définie dans le fichier include/classes/pkg_member.php. La correction consiste à créer explicitement une instance de la classe Member. Le code de la fonction devient :

    function setStatus($status,$userId) {
        $objItemStatus = new ItemStatus();
        $objItemStatus->itemId = $this->id;
        $objMember = new Member();
        $objMember->id = $userId;
        $objItemStatus->member = $objMember;
        # $objItemStatus->member->id = $userId;
        $objItemStatus->statusKey = $status;
        $objItemStatus->add();
    }

Fichier xajax.task.php

Le message concernant ce script est observé après création d'une tâche. La fonction fautive est task_load_edit pour les mêmes raisons que le cas précédent.

function task_load_edit($id) {
        $objResponse = new xajaxResponse();

        if ($id) {
                // loading task
                if ($objTask = ajaxLoadTask($id,7,false,$objUser,$objResponse)) {
                        ob_start();
                        include PRJ_INCLUDE_PATH.'html/xajax_panel_edit.php';
                        $str = ob_get_contents();
                        ob_clean();
                        $objResponse->addScriptCall('freak_edit_text','id',$objTask->id);
                        $objResponse->addAssign('fviewcontent','innerHTML',$str);
                        /*
                        $objResponse->addScriptCall('freak_edit_select','priority',$objTask->priority);
                        if (@constant('FRK_CONTEXT_ENABLE')) {
                        }
                        $objResponse->addScriptCall('freak_edit_text','deadlineDate',$objTask->getDte('deadlineDate',SHT));
                        $objResponse->addScriptCall('freak_edit_text','title',$objTask->title);
                        $objResponse->addScriptCall('freak_edit_text','description',$objTask->description);
                        $objResponse->addScriptCall('freak_edit_select','project',$objTask->project->id);
                        $objResponse->addScriptCall('freak_edit_privacy',$objTask->showPrivate);
                        task_load_users_inside($objTask->project->id,$objUser,$objResponse);
                        $objResponse->addScriptCall('freak_edit_select','user',$objTask->member->id);
                        $objResponse->addScriptCall('freak_edit_select','status',$objTask->itemStatus->statusKey);
                         */
                        task_load_users_inside($objTask->project->id,$objUser,$objResponse);
                        $objResponse->addScriptCall('freak_edit_select','user',$objTask->member->id);
                        $objResponse->addScript('Calendar.setup({});');
                        $objResponse->addScript('hD(gE("fviewload"))');
                        $objResponse->addScript('sD(gE("fviewcontent"))');
                } else {
                        $objResponse->addScriptCall('freak_message',$GLOBALS['langMessage']['not_found_or_denied']);
                }
        } else if ($objUser = ajaxCheckSession($objResponse)) {
                // form to create new task
                $objTask = new Item();
                if ($_SESSION['linkItems']) {
                        preg_match('/sProject=([0-9]+)&/',$_SESSION['linkItems'],$matches);
                        $objTask->project->id = $matches[1];
                }
                ob_start();
                include PRJ_INCLUDE_PATH.'html/xajax_panel_edit.php';
                $str = ob_get_contents();
                ob_clean();
                $objResponse->addScriptCall('freak_edit_text','id','0');
                $objResponse->addAssign('fviewcontent','innerHTML',$str);
                task_load_users_inside(0,$objUser,$objResponse);
                $objResponse->addScriptCall('freak_edit_select','user',$objUser->id);
                $objResponse->addScript('Calendar.setup({});');
                $objResponse->addScript('hD(gE("fviewload"))');
                $objResponse->addScript('sD(gE("fviewcontent"))');
        }
        $objResponse->addScript('freak_label()');
        $objResponse->addScript('freak_stop()');
        $objResponse->addScript('document.forms[0].priority.focus()');

    return $objResponse->getXML();
}

La propriété project est visiblement une instance de la classe Project définie dans le fichier include/classes/pkg_member.php. La correction consiste à créer explicitement une instance de la classe Project. Le code de la fonction devient :

function task_load_edit($id) {
        $objResponse = new xajaxResponse();

        if ($id) {
                // loading task
                if ($objTask = ajaxLoadTask($id,7,false,$objUser,$objResponse)) {
                        ob_start();
                        include PRJ_INCLUDE_PATH.'html/xajax_panel_edit.php';
                        $str = ob_get_contents();
                        ob_clean();
                        $objResponse->addScriptCall('freak_edit_text','id',$objTask->id);
                        $objResponse->addAssign('fviewcontent','innerHTML',$str);
                        /*
                        $objResponse->addScriptCall('freak_edit_select','priority',$objTask->priority);
                        if (@constant('FRK_CONTEXT_ENABLE')) {
                        }
                        $objResponse->addScriptCall('freak_edit_text','deadlineDate',$objTask->getDte('deadlineDate',SHT));
                        $objResponse->addScriptCall('freak_edit_text','title',$objTask->title);
                        $objResponse->addScriptCall('freak_edit_text','description',$objTask->description);
                        $objResponse->addScriptCall('freak_edit_select','project',$objTask->project->id);
                        $objResponse->addScriptCall('freak_edit_privacy',$objTask->showPrivate);
                        task_load_users_inside($objTask->project->id,$objUser,$objResponse);
                        $objResponse->addScriptCall('freak_edit_select','user',$objTask->member->id);
                        $objResponse->addScriptCall('freak_edit_select','status',$objTask->itemStatus->statusKey);
                         */
                        task_load_users_inside($objTask->project->id,$objUser,$objResponse);
                        $objResponse->addScriptCall('freak_edit_select','user',$objTask->member->id);
                        $objResponse->addScript('Calendar.setup({});');
                        $objResponse->addScript('hD(gE("fviewload"))');
                        $objResponse->addScript('sD(gE("fviewcontent"))');
                } else {
                        $objResponse->addScriptCall('freak_message',$GLOBALS['langMessage']['not_found_or_denied']);
                }
        } else if ($objUser = ajaxCheckSession($objResponse)) {
                // form to create new task
                $objTask = new Item();
                if ($_SESSION['linkItems']) {
                        preg_match('/sProject=([0-9]+)&/',$_SESSION['linkItems'],$matches);
                        $objProject = new Project();
                        $objProject->id = $matches[1];
                        $objTask->project = $objProject;
                        # $objTask->project->id = $matches[1];
                }
                ob_start();
                include PRJ_INCLUDE_PATH.'html/xajax_panel_edit.php';
                $str = ob_get_contents();
                ob_clean();
                $objResponse->addScriptCall('freak_edit_text','id','0');
                $objResponse->addAssign('fviewcontent','innerHTML',$str);
                task_load_users_inside(0,$objUser,$objResponse);
                $objResponse->addScriptCall('freak_edit_select','user',$objUser->id);
                $objResponse->addScript('Calendar.setup({});');
                $objResponse->addScript('hD(gE("fviewload"))');
                $objResponse->addScript('sD(gE("fviewcontent"))');
        }
        $objResponse->addScript('freak_label()');
        $objResponse->addScript('freak_stop()');
        $objResponse->addScript('document.forms[0].priority.focus()');

    return $objResponse->getXML();
}


Fichiers

Reproduire ces modifications serait assez fastidieu. Les deux fichiers suivants contiennent les modifications mises en place :