TaskFreak php 5.5
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.
Sommaire
- 1 Votre avis
- 2 Fonction session_unregister
- 3 Définition timezone
- 4 PHP Strict Standards - Appel statique
- 4.1 Fichier _common.php
- 4.2 Fichier index.php
- 4.3 Fichier login.php
- 4.4 Fichier logout.php
- 4.5 Fichier print_list.php
- 4.6 Fichier project_edit.php
- 4.7 Fichier project_list.php
- 4.8 Fichier public.php
- 4.9 Fichier rss.php
- 4.10 Fichier user_edit.php
- 4.11 Fichier user_list.php
- 4.12 Fichier user_password.php
- 4.13 Fichier user_register.php
- 4.14 Fichier xajax.task.php
- 4.15 Fichier include/classes/pkg_com.php
- 4.16 Fichier include/classes/pkg_member.php
- 4.17 Fichier /include/classes/pkg_project.php
- 4.18 Fichier include/classes/tzn_document.php
- 4.19 Fichier include/classes/tzn_generic.php
- 4.20 Fichier include/classes/tzn_mysql.php
- 4.21 Fichier include/classes/tzn_generic.php
- 4.22 Fichier include/html/header.php
- 5 PHP Strict Standards - Déclaration de méthode
- 6 PHP Warning - Creating default object
- 7 Fichiers
Votre avis
Current user rating: 93/100 (5 votes)
|
|
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.' -> ';
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.' -> ';
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 = "<",
$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 = "<",
$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 = ">",
$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 = ">",
$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 :
- patch.tar.gz avec la mise en place de l'authentification LDAP.
- patch.tar.gz pour une installation "standard".