-
[vendredi 31 octobre 2008 - 14:34:00]
la classe met à disposition une couche d'abstraction pour la librarie TCPDF. L'intérêt principal de cette extension de FPDF réside dans le fait que l'on peut manipuler n'importe quelle chaîne de carctères en UTF-8.
Installation
-
Installer le plugin
symfony plugin-install http://plugins.symfony-project.com/sfTCPDFPlugin
ou téléchargez le package et décompresser le dans le répertoire /plugins
-
Décompressez là dans le répertoire /plugins/sfTCPDFplugin/lib
A ce niveau là vous devriez avoir un répertoire tcpdf dans /plugins/sfTCPDFPlugin/lib
-
copiez les fichiers de /web/ directement dans le répertoire /web de votre application
-
supprimez le cache
symfony cc
Configuration
-
initialisez correctement sf_tcpdf_dir dans le config.php de votre application si la librairie TCPDF n'est pas dans le répertoire /plugins/sfTCPDFplugin/lib (vérifiez /plugins/sfTCPDFPlugin/config/config.php)
-
Si vous voulez des paramètres distincts pour chaque PDF généré vérifiez /plugins/sfTCPDFPlugin/config/config.php et tcpdf/config/tcpdf_config.php.
-
Si vous voulez tester, activez le module sfTCPDF dans votre fichier settings.yml, et ensuite appelez l'url sfTCPDF/test ou sfTCPDF/test2
Usage
//Hello World test (sfTCPDF/test)
public function executeTest() {
// pdf object
$pdf = new sfTCPDF();
// settings
$pdf->SetFont("FreeSerif", "", 12);
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
$pdf->setHeaderFont(array(PDF_FONT_NAME_MAIN, *, PDF_FONT_SIZE_MAIN));
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE, PDF_HEADER_STRING);
$pdf->setFooterFont(array(PDF_FONT_NAME_DATA, *, PDF_FONT_SIZE_DATA)); $pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
// init pdf doc
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->Cell(80, 10, "Hello World !!! & é € U û いろは");
// output
$pdf->Output();
return sfView::NONE;
} -
-
[lundi 20 octobre 2008 - 16:23:00]
Précedemment dans symfony
L'appication askeet a juste été mise en ligne hier, et nous avons déjà beaucoup de retour sur des améliorations. L'entrée utilisateur est fondamentale dans le design d'une application web 2.0, et même si ce concept d'application est nouveau, il doit être pris en compte le plus vite possible.
Mais nous implémenterons des fonctionnalités non prévues pour le jour 21. Avant cela, nous avons planifié de vous montrer quelques techniques de développement web avancées sur askeet, et la première présentée aujourd'hui est la programmation d'un API externe requierant une authentification HTTP.
Comme nous avons fait pas mal de petites modificaitons hier, vous avez tout intérêt à commencer le tutoriel d'aujourd'hui avec une version vierge de askeet d u jour 16 téléchargée dans le dépot de askeet.
L'APIun Application Programming Interface, ou API, est une interface du développeur pour un service de votre application en particulier, elle peut donc être utilisée à partir d'autres sites. Pansez à Google Maps ou Flickr, qui sont utilisés par de nombreux sites via leur API.
Askeet ne fait pas exception, et nous pensons que dans le but de développer la popularité des services, ils doivent être disponible pour d'autres sites. Le filet RSS développé pendant le jour 11 était une première approche de ce cahier des charges, mais nous devons pouvoir faire mieux.
Askeet mettra à disposition une API de réponse au questions posées par l'utilisateur. L'accès à cette API sera restreint aux seul utlisateurs de Askeet, via une authentification HTTP. Le format de réponse de l'API choisi est Representational State Transfer, ou REST - qui signifie que la réponse est un simple block XML similaire à la plupart des sorties de toutes les API sur le web:<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok" version="1.0">
<question href="http://www.askeet.com/question/what-shall-i-do-tonight-with-my-girlfriend" time="2005-11-21T21:19:18Z" >
<title>What shall I do tonight with my girlfriend?</title>
<tags>
<tag>activities</tag>
<tag>relatives</tag>
<tag>girl</tag>
<tags>
<answers>
<answer relevancy="50" time="2005-11-22T12:21:53Z">You can try to read her poetry. Chicks love that kind of things.</answer>
<answer relevancy="0" time="2005-11-22T15:45:03Z">Don't bring her to a doughnuts shop. Ever. Girls don't like to be seen eating with their fingers - although it's nice.</answer>
</answers>
</question>
</rsp>Nous implémenterons l'API dans un nouveau module de l'application frontend, donc nous utilisons la ligne de commande pour générer le squelette du module:
symfony init-module frontend api
Authentification HTTPNous choisissons de llimiter l'accès à l'API au xseuls utilisateurs de Askeet. Pour cela, nous allons utiliser uneauthentificaiton HTTP, qui sera basé sur le mécanisme d'authentification du protocole HTTP. Ce processus est différent de l'authentification que nous avons mis en place précédemment puisqu'il ne fait pas intervenir de page web - tout est dans les en-têtes HTTP.
Nous aurons besoin de la méthode inclue dans le validateur lors du jour 6, donc dans un premier temps on fait un peu de factorisation et déplacement de code dans le modèle de la classe de UserPeer:public static function getAuthenticatedUser($login, $password)
{
$c = new Criteria();
$c->add(UserPeer::NICKNAME, $login);
$user = UserPeer::doSelectOne($c);
// nickname exists?
if ($user)
{
// password is OK?
if (sha1($user->getSalt().$password) == $user->getSha1Password())
{
return $user;
}
}
return null;
}La nouvelle méthode UserPeer::getAtenticatedUser() peut maintenant être utilisée dans myLoginValidator.class.php (nous vous laissons le soin de le faire) et dans la nouvelle api/index du web service:
<?php
class apiActions extends sfActions
{
public function preExecute()
{
sfConfig::set('sf_web_debug', false);
}
public function executeIndex()
{
$user = $this->authenticateUser();
if (!$user)
{
$this->error_code = 1;
$this->error_message = 'login failed';
$this->forward('api', 'error');
}
// do some stuff
}
private function authenticateUser()
{
if (isset($_SERVER['PHP_AUTH_USER']))
{
if ($user = UserPeer::getAuthenticatedUser($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']))
{
$this->getContext()->getUser()->signIn($user);
return $user;
}
}
header('WWW-Authenticate: Basic realm="askeet API"');
header('HTTP/1.0 401 Unauthorized');
}
public function executeError()
{
}
}
?>
Dans un premier temps, avant d'exécuter quoique ce soit comme action du module API (celles dans la méthode preExecute()), nous allons désactiver la barre d'outils de débogage. La vue étant du XML pure l'insertion de la barre d'outils de débogage entrainerait une réponse non valide.
La première des choses que l'action index aura à faire sera de vérifier que le login et le mot de passe fourni, et s'ils correspondent à un compte existant dans l'application askeet. SI ce n'est pas le ca, la méthode authenticateUser() renverra une en-tête 401. cela déclenchera l'affichage d'un prompt d'authetification dans le navigateur de l'utilisateur; l'utilisateur aura alors à resoumettre une requête avec son login mot de passe.// first request to the API, without authentication
GET /api/index HTTP/1.1
Host: mysite.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8) Gecko/20051111 Firefox/1.5
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
...
// the API returns a 401 header with no content
HTTP/1.x 401 Authorization Required
Date: Thu, 15 Dec 2005 10:32:44 GMT
Server: Apache
WWW-Authenticate: Basic realm="Order Answers Feed"
Content-Length: 401
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
// a login box will then appear on the user's window.
// Once the user enters his login/password, a new GET is sent to the server
GET /api/index HTTP/1.1
Host: mysite.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8) Gecko/20051111 Firefox/1.5
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
...
Authorization: Basic ZmFicG90OnN5bWZvbnk=
Un attribut d'autorisation est ajouté à la requête HTTP, qui est renvoyé une nouvelle fois. Elle contient le 'login:mot de passe' encrypté en base 64. c'est ce que représentent les variables $_SERVER['PHP_AUTH_USER'] et $_SERVER['PHP_AUTH_PW'] dans notre méthode authenticateUser().
Base64 ne renvoie pas une version encryptée de sa sortie. Decodé une chaîne de caractère encodée en Base64 est trés simple, et cela révèle le mot de passe en clair. Par exmple, décoder la chaîne de carctère ZmFicG90OnN5bWZvbnk= donne fabpot:symfony. Donc vous devez considérer que les mots de passe tranistent en clair sur Internet ( comme si'ls étaient entrés dans un formulaire) et peuvent être intercéptés. L'authentification HTTP doit être réservé aux contenus et service non critique pour cette raison. Une protection supplémentaire peut être mùise en place en utilisant le protocol HTTPS pour les appels à l'API.<?php echo '<?' ?>xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail" version="1.0">
<err code="<?php echo $error_code ?>" msg="<?php echo $error_message ?>" />
</rsp>Bien sûr, vous devez définir toutes les vues du module api à un content-type XML, et désactiver le décorateur. Cela ce fait en ajoutant le fichier view.yml suivant dans le répertoire askeet/apps/frontend/modules/api/config/ directory:
all:
has_layout: off
http_metas:
content-type: text/xmlréponse de l'API
La raison pour laquelle l'action index renvoie un forward('api','error') au lieu d'un sfView::ERROR en cas d'erreur est que toutes les actions du module api utilisent la même vue. Imganiez que notre action index et une autre, par exemple popular, se termine par sfView::ERROR : nous aurions à renvoyer deux vues d'erreur identique (indexError.php te popularError.php) avec le même contenu. Le choix du forward() limite la répétition du code. De toute façon, il faut exécuter une autre action.
API response
construire une réponse XML est exactement comme construire une page XHTML. donc rien de ce uqi suit ne vous surprendra, maintenant que vous avez le jour 16 d'askeet derrière vous.api/index action
public function executeQuestion()
{
$user = $this->authenticateUser();
if (!$user)
{
$this->error_code = 1;
$this->error_message = 'login failed';
$this->forward('api', 'error');
}
if (!$this->getRequestParameter('stripped_title'))
{
$this->error_code = 2;
$this->error_message = 'The API returns answers to a specific question. Please provide a stripped_title parameter';
$this->forward('api', 'error');
}
else
{
// get the question
$question = QuestionPeer::getQuestionFromTitle($this->getRequestParameter('stripped_title'));
if ($question->getUserId() != $user->getId())
{
$this->error_code = 3;
$this->error_message = 'You can only use the API for the questions you asked';
$this->forward('api', 'error');
}
else
{
// get the answers
$this->answers = $question->getAnswers();
$this->question = $question;
}
}
}le template questionSuccess.php
<?php echo '<?' ?>xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok" version="1.0">
<question href="<?php echo url_for('@question?stripped_title='.$question->getStrippedTitle(), true) ?>" time="<?php echo strftime('%Y-%m-%dT%H:%M:%SZ', $question->getCreatedAt('U')) ?>">
<title><?php echo $question->getTitle() ?></title>
<tags>
<?php foreach ($sf_user->getSubscriber()->getTagsFor($question) as $tag): ?>
<tag><?php echo $tag ?></tag>
<?php endforeach ?>
</tags>
<answers>
<?php foreach ($answers as $answer): ?>
<answer relevancy="<?php echo $answer->getRelevancyUpPercent() ?>" time="<?php echo strftime('%Y-%m-%dT%H:%M:%SZ', $answer->getCreatedAt('U')) ?>"><?php echo $answer->getBody() ?></answer>
<?php endforeach ?>
</answers>
</question>
</rsp>Ajouter une nouvelle règle de routage pour l'appel à l'API
api_question:
url: /api/question/:stripped_title
param: { module: api, action: question }Test
Comme la réponse d'une API REST est simplement du XML, vous pouvez tester avec un navigateur en demandant:
http://askeet/api/question/what-shall-i-do-tonight-with-my-girlfriend
Intégration d'une API externe
intégrer une API externe n'est pas plus complexe que de lire un fichier XML en PHP. Comme il n'y a pas d'intérêt immédiat à intégrer une API externe dans askeet, nous allons décrire en quelques mots comment intégrer l'API askeet dans un autre site web - qu'il soit basé sur symfony ou non.
PHP5 intègre SimpleXML, quelques outils faciles d'utilisation pour interpréter et boucler sur un document XML. avec SimpleXML tous les noms d'éléments sont automatiquement mappés en propriétés d'un objet. et ce de manière récursive. Les attributs eux sont mappés comme des index.
Pour reconstruire une liste de réponses à une question donnée par l'API dans une simple page, tout ce qu'il y a besoin c'est de ces quelques lignes de PHP.<?php $xml = simplexml_load_file(dirname(__FILE__).'/question.xml') ?>
<h1><?php echo $xml->question->title ?></h1>
<p>Published on <?php echo $xml->question['time'] ?></p>
<h2>Tags</h2>
<ul>
<?php foreach ($xml->question->tags->tag as $tag): ?>
<li><?php echo $tag ?></li>
<?php endforeach ?>
</ul>
<h2>Answers to this question from askeet users</h2>
<ul>
<?php foreach ($xml->question->answers->answer as $answer): ?>
<li>
<?php echo $answer ?>
<br />
Relevancy: <?php echo $answer['relevancy'] ?>% - Pulished on <?php echo $answer['time'] ?>
</li>
<?php endforeach ?>
</ul>donation paypal
Tant que nous parlons des APIs externes certaines sont très simple à intégrer et peuvent apporter beaucoup à votre site. L'API de donation paypal est un simple bloque d'HTML dans lequel l'email du receveur doit être inclus.
Est ce que ce ne serait pas une bonne motivation pour les utilisateurs d'askeet qui répondent généreusemen aux question que de pouvoir recevoir une petite donation de tout utilisateur stisfait par la qualité de leur réponse? le bouton "donate" pourrait apparître sur la page de profil de l'utilisateur, et être liée à sa page de donation Paypal.
Premièrement, ajoutez la colonne add_paypal à la table User dans schema.xml:<column name="has_paypal" type="boolean" default="0" />
Reconstruisez le modèle, et ajoutez au template user/show le code suivant
<?php if ($subscriber->getHasPaypal()): ?>
<p>If you appreciated this user's contributions, you can grant him a small donation.</p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="<?php echo $subscriber->getEmail() ?>">
<input type="hidden" name="item_name" value="askeet">
<input type="hidden" name="return" value="http://www.askeet.com">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="no_note" value="1">
<input type="hidden" name="tax" value="0">
<input type="hidden" name="bn" value="PP-DonationsBF">
<input type="image" src="http://images.paypal.com/images/x-click-but04.gif" border="0" name="submit" alt="Donate to this user">
</form>
<?php endif ?>Désormais un tuilisateur a la possibilité de déclarer un compte paypal lié à son adresse amil. Ce seriat une bonne occasion pour autoriser l'utilisateur à pouvoir modifier son profil, un bouton 'edit profile' devrait apparaître. Il sera lié à l'action user/edit, utilisée à la fois pour afficher le formulaire et pour effectuer le traitement lié à la soumission. Le formulaire de modification autorisera la modification du mot de passe et de l'adresse email. le surnom comme il est utilisé comme clé, ne peut pas être modifié. Maintenant que vous êtes à l'aise avec symfony, le code ne sera pas décrit ici mais simplement inclu dans le SVN.
A demain
Développer un webservice ou en intégrer un externe ne devrait pas vous poser de problème avec symfony.
Demain sera l'occasion d'aborder les filtres, et de diviser askeet.com en plusieurs sous projets comme php.askeet.com et symfony.askeet.com avec seulement quelques lignes de code. Si vous n'êtes pas convaincu par la vitesse de développement et la puissance de symfony, vous devriez changer d'avis.
Comme d'habitud, le code d'aujourd'hui a été soumis sur le depot SVN d'askeet; sous /tags/release_day_17_tag. Les quesiton et suggestions à propos de askeet et les tutoriels du calendrier de l'avent sont les bienvenues dans le forum askeet. A demain!
-
[lundi 09 juin 2008 - 23:47:15]C'était un des obstacles pour migrer complètement sous linux: comment synchroniser les données de mon pocketPC (essentiellement les contacts et le calendrier) avec mon PC sans outlook?
Quand je dis sans Outlook, l'extension lightning de Thunderbird qui permet de gérer des calendriers sunbird, me paraissait assez sexy pour avoir envie de l'utiliser.
Et bien funambol met à disposition un ensemble de produits permettant de réaliser cette opération.
parmi ces produits j'ai utilisé les suivants pour libérer la synchro
funambol
la version Outlook qui permet d'extraire vos données (contacts & calendrier) "emprisonnées"
la version PocketPC
l'extension Thunderbird de synchro via syncML
Coté Mozilla
Sunbird pour la gestion des calendriers
l'extension thunderbird lightning pour gérer le calendrier directement dans thunderbird
l'extension google pour unbuntu qui corrige en fait un bug de lightning-linux
L'idée
L'idée est simplement d'avoir un serveur prêt à synchroniser des contacts et des calendriers via syncML. La solution la plus rapide est de se créer un compte chez scheduleworld et de paramétrer l'extension thunderbird funambol ainsi que le client pocketpc (synchronisable de n'importe ou via l'internet :-) avec les données qui vous auront été fournies lors de l'inscription).
serveur funambol
Funambol fournit par ailleurs toute la gamme de produit, c'est à dire outre les clients en tout genre, les serveurs pour windows & linux 8-p ... Je n'ai testé que la version windows sans creuser mais ça fonctionne très bien ... Rendez vous pour la mise en place du serveur sous debian.
-
[vendredi 11 avril 2008 - 08:44:11]L' admin-generator gère l'upload de fichier. Pour l'objet sfCmsContent d'accordéon CMS par exemple, l'attribut file_path est déclaré comme suit dans le generator.yml
file_path:
Cela permet d'avoir un formulaire d'upload avec une checkbox pour la suppression du fichier. Si le fichier est nouveau un nom aléatoire lui sera attribué, sinon il remplacera l'ancienne version du fichier et ce sans écrire une ligne de code.
name: fichier
type: admin_input_file_tag
upload_dir: sfCmsContent/file
params: disabled=false include_remove=true
help: appuyer sur le bouton 'Parcourir...' pour sélectionner un fichier contenu dans votre ordinateur
Les problèmes pointent leur nez quand on tente d'uploader des fichiers avec des extensions un peu exotiques, dans mon cas l'extension .pot (modèle de conception sous power point) était systématiquement remplacée par l'extension .ppt.
C'est dû à la méthode getFileExtension() de sfWebRequest, et en particulier au fichier mime_types.dat qui contient les associations entre type mime et extension. La difficulté dans le cas des .pot est que ce type de fichier renvoie le même type mime que les .ppt
application/vnd.ms-powerpoint
Or mime_types.dat permet d'associer un et un seul type mime à une et une seule extension ... j'ai donc opté pour la ruse en surchargeant updateSfCmsContentFromRequest() comme suit
protected function updateSfCmsContentFromRequest()
{
$sfCmsContent = $this->getRequestParameter('sf_cms_content');
parent::updateSfCmsContentFromRequest();
if (!$this->getRequest()->hasErrors())
{
$tmp = $this->getRequest()->getFileName('sf_cms_content');
if($tmp['file_path'])
{
$path = sfConfig::get('sf_upload_dir').'/'.sfCmsContent/file
if(!is_dir($path))
{
mkdir($path);
}
$extension = myTools::MimeTypeInfo($tmp['file_path']);
$filename = myTools::FileNameInfo($this->sf_cms_content->getFilePath());
rename($path.'/'.$this->sf_cms_content->getFilePath(), $path.'/'.$filename.'.'.$extension);
$this->sf_cms_content->setFilePath($filename.'.'.$extension);
$this->sf_cms_content->setMimeType(myTools::MimeTypeInfo($tmp['file_path']));
}
}où
class myTools
on se sert ainsi de l'extension du fichier pour déterminer son type mime, et on est assuré que cette extension est préservée
{
public static function MimeTypeInfo($file_name)
{
return substr($file_name, strrpos($file_name, ".")+1);
}
public static function FileNameInfo($file_name)
{
return substr($file_name, 0, strrpos($file_name, "."));
}
}
-
[jeudi 20 mars 2008 - 09:43:40]Voici une des premières réalisations à base d'accordéon CMS

Un big up à l'euipe com qui a effectué un travail remarquable.
Une petite trace de l'ancien qui, après 5 années de bons et loyaux services, va passer dans la rubrique nécrologique d'ici quelques heures ...
Adieu Vercingétorix et le Puy de Dôme ...
-
[mardi 18 mars 2008 - 17:40:44]En attendant la publication de la première version stable d'accordéon CMS (c'est pour bientôt), voici un petit trace back de l'intégration de sfSimpleForumPlugin.
Une fois l'installation du plugin proprement dit voici ce que j'ai dû modifier:
Hacking et correction de Bug
Le seul fichier que j'ai du hacker dans le plugin est le schéma de sfSimpleForum dans plugins/sfSimpleForumPlugin/config/schema.yml. En effet sfCms (le cms symfony qui constitue la base d'accrodéon CMS) n'utilise pas sfGuardPlugin.
sfSimpleForum prévoit que les utilisateurs puissent être géré par un autre module de gestion d'utilisateur ... ça tombe bien.
Ce qui est assez mal fait (mais je n'ai pas réfléchi s'il y avait mieux à faire) c'est que les références aux champs de bases de données de sfGuard sont écrits en dur dans le schema.yml. Il m'a donc fallu remplacer dans ce fichier, toutes les occurrences de sf_guard_user par sf_cms_user qui est le module de gestion d'utilisateur de sfCms, avant de reconstruire entièrement le projet
php symfony propel-build-all
L' autre Hack du plugin, mais qui lui est temporaire, se situe au niveau du fichier plugins/lib/sfSimpleForumTools.class.php. En effet il y a incohérence entre les variables à "copier / coller" dans le fichier app.yml et la référence qui y est faite dans cette classe statique (méthode getUserByUsername). Il faut donc modifier
$method_name = sfConfig::get('app_sfSimpleForumPlugin_user_retrieve_by_name_method', 'retrieveByUsername');
par
$method_name = sfConfig::get('app_sfSimpleForumPlugin_retrieve_by_name_method', 'retrieveByUsername');
A toutes fins utiles, j'ai déjà pris la peine d'ouvrir un ticket.
surcharge au niveau de l'application
A ce niveau là, le sale boulot est maintenant fini. Dans toute la suite on va pouvoir surcharger sans toucher au code du plugin.
Dans le app.yml on spécifie donc le module de gestion des utilisateurs et la méthode permettant des les retrouver. Dans sfCms l'idée est plus d'appeler un utilisateur par son nom prénom que par son nom d'utilisateur. Or par défaut, c'est ce que propose sfSimpleForum ...
il va falloir ruser!
La méthode charnière est précisément la méthode où se trouve le bug cité plus haut : getUserByUsername.
Au niveau de l'affichage c'est bien le prénom nom qui s'affiche (en réalité ce que renvoie la méthode __toString() de la classe SfCmsUser), En revanche c'est la même chose qui est passé en paramètre de sfSimpleForumTools::getUserByUsername() ...
Les homonymies vont donc poser problème.
En explorant un peu la classe de base d'un post (BasesfSimpleForumPost.php) on s'aperçoit qu'elle peut renvoyer le nom de l'auteur (ça on savait déjà) via getAuthorName(), mais aussi l'identifiant du l'utilisateur associé ... et s'il y a bien une méthode fiable poru retrouver un objet propel dans symfony c'est retrieveByPk!
Donc
all:
Et maintenant il n'y a plus qu'à surcharger tous les templates ou partial qui mentionnent un lien vers l'utilisateur afin de remplacer le paramètre username='.$post->getAuthorName() par username='.$post->getUserId().
sfSimpleForumPlugin:
user_class: SfCmsUser # name of the class used to manage users
retrieve_by_name_method: retrieveByPk # name of the static method used to retrieve a user by its username
Il y a 3 partials à "copier / coller" puis à modifier dans l' (les) application(s) qui intègre(nt) sfSimpleForumPlugin
- apps/myapp/sfSimpleForumPlugin/templates/_post.php
- apps/myapp/sfSimpleForumPlugin/templates/_latestPost.php
- apps/myapp/sfSimpleForumPlugin/templates/_forum.php
Il reste ensuite à traduire en français
<trans-unit id="18">
plutôt probant non?
<source>New topic</source>
<target>Nouveau sujet</target>
</trans-unit>
<trans-unit id="19">
<source>There is no topic in this discussion yet. Perhaps you would like to %start%?</source>
<target>Il n'y a pas encore de sujet dans cette discussion. Peut-être voulez vous %start%?</target>
</trans-unit>
<trans-unit id="20">
<source>start a new one</source>
<target>en créer un nouveau</target>
</trans-unit>
<trans-unit id="21">
<source>Moderator</source>
<target>Modérateur</target>
</trans-unit>
<trans-unit id="22">
<source>Title</source>
<target>Titre</target>
</trans-unit>
<trans-unit id="23">
<source>Forum</source>
<target>Forum</target>
</trans-unit>
<trans-unit id="24">
<source>Body</source>
<target>Corps</target>
</trans-unit>
<trans-unit id="25">
<source>Sticked topic</source>
<target>Sujet collé</target>
</trans-unit>
<trans-unit id="26">
<source>Locked topic</source>
<target>Sujet verrouillé</target>
</trans-unit>
<trans-unit id="26">
<source>Post</source>
<target>Post</target>
</trans-unit>
<trans-unit id="27">
<source>%date% ago by %author%</source>
<target>il y a %date% par %author%</target>
</trans-unit>
<trans-unit id="28">
<source>Delete</source>
<target>Supprimer</target>
</trans-unit>
<trans-unit id="29">
<source>Are you sure you want to delete this post?</source>
<target>Etes vous sûr de vouloir supprimer ce post?</target>
</trans-unit>
<trans-unit id="30">
<source>Last reply</source>
<target>Dernière réponse</target>
</trans-unit>
<trans-unit id="31">
<source>Posted</source>
<target>Posté</target>
</trans-unit>
<trans-unit id="32">
<source>View</source>
<target>Voir</target>
</trans-unit>
<trans-unit id="33">
<source>Views</source>
<target>Vues</target>
</trans-unit>
<trans-unit id="34">
<source>Topic</source>
<target>Sujet</target>
</trans-unit>
<trans-unit id="35">
<source>Replies</source>
<target>Réponses</target>
</trans-unit>
<trans-unit id="36">
<source>Last Message</source>
<target>Dernier message</target>
</trans-unit>
<trans-unit id="37">
<source>Create a new topic</source>
<target>Créer un nouveau sujet</target>
</trans-unit>
<trans-unit id="38">
<source>Topics</source>
<target>Sujets</target>
</trans-unit>
<trans-unit id="39">
<source>Lastest Messages</source>
<target>Derniers messages</target>
</trans-unit>
<trans-unit id="40">
<source>Lastest Topics</source>
<target>Derniers sujets</target>
</trans-unit>
<trans-unit id="41">
<source>Unstick</source>
<target>Décoller</target>
</trans-unit>
<trans-unit id="42">
<source>Unlock</source>
<target>Déverrouiller</target>
</trans-unit>
<trans-unit id="43">
<source>Stick</source>
<target>Coller</target>
</trans-unit>
<trans-unit id="44">
<source>Lock</source>
<target>Verrouiller</target>
</trans-unit>
<trans-unit id="45">
<source>Post a reply</source>
<target>Poster une réponse</target>
</trans-unit>
<trans-unit id="46">
<source>This topic was locked by a forum moderator. No reply can be added.</source>
<target>Ce sujet a été verrouillé par un modérateur du forum. Aucune réponse ne peut être ajoutée.</target>
</trans-unit>
<trans-unit id="47">
<source>Messages by %user%</source>
<target>Messages de %user%</target>
</trans-unit>
<trans-unit id="48">
<source>less than a minute</source>
<target>moins d'une minute</target>
</trans-unit>
<trans-unit id="49">
<source>less than 5 seconds</source>
<target>moins de 5 secondes</target>
</trans-unit>
<trans-unit id="50">
<source>less than 10 seconds</source>
<target>moins de 10 secondes</target>
</trans-unit>
<trans-unit id="51">
<source>less than 20 seconds</source>
<target>moins de 20 secondes</target>
</trans-unit>
<trans-unit id="52">
<source>half a minute</source>
<target>30 secondes</target>
</trans-unit>
<trans-unit id="53">
<source>less than a minute</source>
<target>moins d'un minute</target>
</trans-unit>
<trans-unit id="54">
<source>1 minute</source>
<target>1 minute</target>
</trans-unit>
<trans-unit id="55">
<source>%minutes% minutes</source>
<target>%minutes% minutes</target>
</trans-unit>
<trans-unit id="56">
<source>about 1 hour</source>
<target>environ 1 heure</target>
</trans-unit>
<trans-unit id="57">
<source>about %hours% hours</source>
<target>environ %hours% heures</target>
</trans-unit>
<trans-unit id="58">
<source>1 day</source>
<target>1 jour</target>
</trans-unit>
<trans-unit id="59">
<source>%days% days</source>
<target>%days% jours</target>
</trans-unit>
<trans-unit id="60">
<source>about 1 month</source>
<target>environ 1 mois</target>
</trans-unit>
<trans-unit id="61">
<source>%months% months</source>
<target>%months% mois</target>
</trans-unit>
<trans-unit id="62">
<source>about 1 year</source>
<target>environ 1 année</target>
</trans-unit>
<trans-unit id="63">
<source>over %years% years</source>
<target>plus de %years% années</target>
</trans-unit>
<trans-unit id="64">
<source>[0]No topic yet|[1]One topic|(1,+Inf]%topics% topics</source>
<target>[0]Pas encore de sujet|[1]Un sujet|(1,+Inf]%topics% sujets</target>
</trans-unit>
<trans-unit id="65">
<source>[0]No message|[1]One message|(1,+Inf]%posts% messages</source>
<target>[0]Pas de message|[1]Un message|(1,+Inf]%posts% messages</target>
</trans-unit> -
[vendredi 22 février 2008 - 10:26:54]c'est toujours un exercice assez compliqué. La première des choses
-
[samedi 05 janvier 2008 - 21:38:53]
-
[jeudi 20 décembre 2007 - 16:54:28]Voici ce qu'il faut ajouter pour que l'i18n fonctionne de manière transparente avec les modules genérer via l'admin-generator de symfony. Pour illustrer l'exemple et s'extasier une nouvelle fois sur le temps qu'on gagne avec l'admin-generator, nous allons mettres en place la partie administration d'un système de news catégorisées et multilingue.
Soit le MLD suivant:

Note qui vaut des points:
folklore symfoniste, bug de la commande symfony qui convertit le xml en yml ou bug du batch-db-18n, j'en sais rien, mais le fait est qu'il faut toujours avoir la clé étrangère en premier et le champ culture en second (ainsi que clé primaire tous les deux (penser à virer l'auto incrémente sur le champs culture dans DBDesigner aussi (il se met automatiquement)))dans les tables *_i18n pour avoir un shema.yml correct!!
une fois stocké le fichier généré par DBdesigner 4 dans le répertoire data/schema.xml, il est temps de faire tourner le petit batch qui change la vie afin d'obtenir le fichier config/shema.yml que symfony utilisera pour générer les classes et le SQL adéquat.
php batch/convert_db.php
Le fait que les news soit multilingue nécessite d'exécuter la commande de patch pour l'internationalisation afin que symfony puisse faire le lien entre les objets et leurs attributs traduits:
symfony patch-db-i18n myapp
Ensuite on laisse la magie opérée
symfony propel-build-all
Les classes sont crées et les tables sql aussi, on peut donc générer les parties administrations des objets news et news_category
symfony propel-init-admin myapp new News
un petit vidage de cache
symfony propel-init-admin myapp newCategory NewsCategory
symfony cc
et on peut admirer le résultat sur http://monsite.com/news et http://monsite.com/newsCategory
Mais, enfer et damnation, si on tente de créer une nouvelle news, on va s'apercevoir que les champs internationalisés ne sont pas affichés ... voici ce qu'il faut modifier pour que ça marche:
Dans le modèle de l'objet news dans lib/model/News.php
<?php
Cette surcharge de la méthode hydrate va permettre de charger l'objet dans la langue cournate du user. Si la traduction n'est pas disponible on essaie la langue suivante, et sinon la langue suivante.
/**
* Subclass for representing a row from the 'news' table.
*
*
*
* @package lib.model
*/
class News extends BaseNews
{
public function hydrate(ResultSet $rs, $startcol = 1)
{
parent::hydrate($rs, $startcol);
$this->setCulture(sfContext::getInstance()->getUser()->getCulture());
$lang = array_keys(sfConfig::get('app_lang_available'));
$lg = array_pop($lang);
while($this->getTitle()=='' && $lg )
{
$this->setCulture($lg);
$lg = array_pop($lang);
}
}
}
N.B. le app.yml contient
all:
Donc dans ce cas précis le while à un sens mais la logique est à adapter selon les cas. Typiquement, dans le cas présent, il existe forcément une version française, et la version anglaise sera affichée si la version chinoise n'existe pas (c'est la traduction la plus rare dans ce cas).
lang:
available:
fr: francais
en: anglais
zh: chinois
De manière symétrique modifier le modèle de l'objet newsCategory dans lib/model/NewsCategory.php
<?php
Voilà pour l'affichage. Maintenant Il serait bon qu'en modification la logique suivie soit différente, à savoir si la traduction existe dans la langue courante du user elle s'affiche et si ce n'est pas le cas les champs s'affichent vides afin qu'ils puissent être traduits.
/**
* Subclass for representing a row from the 'news_category' table.
*
*
*
* @package lib.model
*/
public function hydrate(ResultSet $rs, $startcol = 1)
{
parent::hydrate($rs, $startcol);
$this->setCulture(sfContext::getInstance()->getUser()->getCulture());
$lang = array_keys(sfConfig::get('app_lang_available'));
$lg = array_pop($lang);
while($this->getTitle()=='' && $lg )
{
$this->setCulture($lg);
$lg = array_pop($lang);
}
}
Pour se faire il faut surcharger le controleur de l'objet News dans apps/myapp/modules/news/actions/actions.class.php
<?php
et de manière symétrique celui de newsCategory dans apps/myapp/modules/newsCategory/actions/actions.class.php.
/**
* news actions.
*
* @package u-clermont1
* @subpackage news
* @author Your name here
* @version SVN: $Id: actions.class.php 2288 2006-10-02 15:22:13Z fabien $
*/
class newsActions extends autonewsActions
{
//http://trac.symfony-project.com/trac/wiki/HowToHandlei18nDbFieldsWithAdminGenerator
//enable i18n in admin generator
protected function getNewsOrCreate ($id = 'id')
{
if (!$this->getRequestParameter('id', 0))
{
$news = new News();
}
else
{
$news = NewsPeer::retrieveByPk($this->getRequestParameter($id));
$this->forward404Unless($news);
}
$news->setCulture($this->getUser()->getCulture());
return $news;
}
}
<?php
Maintenant il faut manipuler un peu le generator.yml de chaque objet afin de forcer l'affichage des champs internationalisés
/**
* newsCategory actions.
*
* @package u-clermont1
* @subpackage newsCategory
* @author Your name here
* @version SVN: $Id: actions.class.php 2288 2006-10-02 15:22:13Z fabien $
*/
class newsCategoryActions extends autonewsCategoryActions
{
protected function getNewsCategoryOrCreate ($id = 'id')
{
if (!$this->getRequestParameter('id', 0))
{
$newsCategory = new NewsCategory();
}
else
{
$newsCategory = NewsCategoryPeer::retrieveByPk($this->getRequestParameter($id));
$this->forward404Unless($newsCategory);
}
$newsCategory->setCulture($this->getUser()->getCulture());
return $newsCategory;
}
}
pour les news apps/myapp/modules/news/config/generator.yml
generator:
pour les newsCategory apps/myapp/modules/news/config/generatorCategory.yml
class: sfPropelAdminGenerator
param:
model_class: News
theme: default
list:
display: [id, title]
edit:
display:
NONE: [id, title, hook, detail]
fields:
title:
name: titre
type: input_tag
params: size=80 disabled=false
help: le titre est obligatoire
hook:
name: accroche
type: textarea_tag
params: tool=Basic rich=fck disabled=false
help: donnez un bref résumé cette actualité
detail:
name: detail
type: textarea_tag
params: tool=Basic rich=fck disabled=false
help: donnez le détail cette actualité
generator:
Voilà l'administration du système de news est désormais multilingue
class: sfPropelAdminGenerator
param:
model_class: NewsCategory
theme: default
list:
display: [id, title, rank]
edit:
display:
NONE: [id, title, rank]
fields:
title:
name: titre
type: input_tag
params: size=80 disabled=false
help: le titre est obligatoire
rank:
name: ordre
params: size=2 disabled=false
help: détermine l'ordre d'affichage, notamment sur la page d'accueil -
[mercredi 21 novembre 2007 - 16:15:00]Quand j'ai introduit les tables d'internationalisation symfony (i18n) dans mon MCD, je me suis aperçu que le petit batch pour symfony qui change la vie ne fonctionnait plus :-/ C'est bien normal car en plus d'une simple dépendance de clé étrangère il faut spécifier deux attributs supplémentaires dans schema.yml:
required: true
simplement il serait utile d'avoir un script qui ajoute ces lignes au champs culture automatiquement. C'est exactement le propos de How To Patch Tables For I18n From Generated Schema. Il s'installe dans le répertoire tasks de l'installation pear de symfony et s'utilise en commande symfony juste avant de construire le modèle ...
isCulture: true
Basiquement il suffit à présent de créer un lien [ 1: n ] entre une entité et ces attributs internationalisés.
Les prérequis sont un suffixe discriminatnt pour les tables i18n (_i18n par défaut) et un nom pour le champs contenant la langue (culture par defaut). Et c'est reparti pour le design du MCD en mode graphique :-D -
[mercredi 21 novembre 2007 - 12:03:49]Pour ceux qui comme moi ne sont pas des fanas de la gravure. Voici un power tools XP qui fait bien son job. Une fois l'archive téléchargée et décompressée on dispose d'un fichier VCdControlTool.exe et d'un fichier VCdRom.sys.
VCdRom.sys est à copier dans le répertoire c:\windows\system32\drivers ou plutot %systemroot%\system32\drivers.
Il faut ensuite
- Lancer VCdControlTool.exe
- cliquer sur "Driver control"
- cliquer sur "Install Driver" s'il est cliquable, et parcourir le disque dur jusqu'à %systemroot%\system32\drivers et y sélectionner VCdRom.sys
- cliquer sur "Start"
- cliquer sur "OK"
- cliquer sur "Add Drive" pour crééer le lecteur
- cliquer sur "Mount" pour sélectionner l'image ISO à monter sur le lecteur qui vient d'être créé.
-
[mercredi 31 octobre 2007 - 17:44:51]voici deux références bien utiles pour l'utilisation quotidienne de svn:
-
[mercredi 31 octobre 2007 - 12:50:15]
- Pour télécharger le convertisseur Microsoft Office 2007 vers Microsoft Office 2003. cliquer ici
- Installer ensuite le convertisseur en double cliquant sur le fichier téléchargé, puis se laisser guider par l'assistant d'installation
- Redémarrer la machine
- Double cliquer sur le fichier Microsoft Office 2007 à convertir, il doit alors s'ouvrir avec Microsoft Office 2003
Word 2007
* Word Document (.docx) –Default Format
* Word Macro-enabled Document (.docm)
* Word Template (.dotx)
* Word Macro-enabled Document Template (.dotm)
Excel 2007
* Excel Workbook (.xlsx) - Default format
* Excel Macro-enabled Workbook (.xlsm)
* Excel Template (.xltx)
* Excel Macro-enabled Workbook Template (.xltm)
* Excel Binary Workbook (.xlsb)
* Excel Add-in (.xlam)
PowerPoint 2007
* PowerPoint Presentation (.pptx) - Default format
* PowerPoint Macro-enabled Presentation (.pptm)
* PowerPoint Slide Show (.ppsx)
* PowerPoint Macro-enabled Slide Show (.ppsx)
* PowerPoint Template (.potx)
* PowerPoint Macro-enabled Presentation Template (.potm)
* PowerPoint Add-in (.ppam)
Powered by ScribeFire.
-
[dimanche 28 octobre 2007 - 02:29:58]
ou comment tuer un samedi soir ...
Quand on peut s'en prendre qu'à soit '-( ...
Voilà j'ai un HTC 9600 , vendu en TyTn chez SFR et SPV M3100 chez orange. Ca fait pompeux certes, mais ce sont détails qui ont leur importance au moment de courir après les paramètres d'utilisation des réseaux data (c.f. plus loin), quand on a acheté son Pocket PC nu (c'est à dire pas un TyTn de SFR, ni un SVP M3100).
NPPT: C'est pas parce qu'on a la bonne référence et qu'on va dans la bonne boutique, qu'on trouve les bons renseignements ou qu'on est bien reçu!
J'ai utilisé pendant pas loin d'un an Windows Mobile 5 et j'ai eu naturellement envie de passer au 6.
D'après les forums ça paraissait compliqué et hasardeux. Heureusement HTC (le constructeur de mon téléphone donc!) propose un truc tout packagé, bien propre, en échange d'une création de compte sur leur site.
Je me permets une citation de forum parce que le post est vraiment noyé :
pour ceux qui ont un HTC et qui cherchent encore WM6, il suffit d'aller sur le site HTC:
Bon ça rend un fier service, parce que la mise à jour se fait vraiment toute seule.
http://www.europe.htc.com/index.html?lang=fr
bien entendu si vous voulez la mise a jour en une autre langue il vous suffit d'aller en haut a gauche de la page puis de changer la langue.
Pour la mise a jour, il vous faut aller dans l'onglet "Support" puis dans "connexion" si vous avez un conte ou dans "crée un nouveau conte".
Une fois cette étape terminer il vous faut enregistrer votre appareil en allant dans "enregistrer un produit" et en suivant les instructions.
Il vous faut aller dans l'onglet "changer de téléphone" pour selectionner la marque de votre téléphone et enfin aller dans "Mise a jour logicielles de telephone" qui se trouve en bas a gauche de votre écran, ne vous resteras plus qu'à cliquer sur votre téléphone puis a telecharger.
Une fois fait il faut mettre à jour sa version d' ActiveSync (4.5 actuellement).
Compter une petite demi-heure tout compris pour la manip!
Parcontre, on perd évidemment tout ses programmes, ce qui donne une bonne occasion de refaire un tour du côté des outils indispensables ...
La première des choses à faire est de pouvoir bosser à partir de son PC et ne pas passer son temps à chercher le stylet pour faire les manip qui doivent se faire sur le pocket.
Jusque là j'utilisais remote display et j'en étais satisfait ... Mais bon il manquait quelques détails comme l'émulation de la molette de la souris, le copier / coller ...
Bref j'ai trouvé My Mobile qui possède tout ce qui manquait à remote display, et qui est également gratuit.
Première étape, installer les paramètres de mon opérateur pour pouvoir utiliser les réseaux data ... Ce post CONFIGURATION MMS ET DATA POUR ORANGE BOUYGUES SFR et le zip de configuration automatique qu'il mentionne sont plus efficaces que les services clients des trois opérateurs réunis (qui la plupart du temps ne savent pas!!)!
Pour se sentir un peu plus comme à la maison je commence par installer spb plus qui rend la page d'accueil un peu plus convivial. spb weather, spb GPRS monitor et spb clock sont également assez sympas ...
Une fois fait on peut faire un peu de tuning ;-) Voice Command marche très bien (je n'ai pas essayé JETware qui visiblement ne pose pas autant de problème que ceux qui vont suivre ...). Les problèmes commencent à venir quand on veut utiliser son oreillette bluetooth et qu'on veut faire le boss en téléphonant sans toucher à son téléphone et en se payant en plus le luxe de ne pas avoir enregistré une seule étiquette vocale!!!
En effet l'oreillette bluetooth déclenche le speed dial (qui ne reconnaît que les étiquettes vocales :-/) et pour le remplacer par voice command voilà la marche à suivre :
- utiliser un éditeur de base de registre (faire une sauvegarde auparavant)
- Ouvrir ou créer (new string) HKEY_LOCAL_MACHINE/Software/OEM/VoiceCommand/Path
- Changer ou taper la valeur du chemin en \Program Files\Voice Command\voicecmd.exe
- PHM Regedit : qui permet d'éditer la bdr à partir du pocket
- Mobile Registry Editor : qui permet d'éditer la bdr directement à partir du PC
Voilà normalement maintenant une pression sur l'oreillette met Voice command en écoute pour une commande vocale ... Le seul truc con que je n'ai pas encore résolu c'est que c'est bien le micro de l'oreillette qui est utilisé mais ce sont les HP du QTek qui émettent la réponse Voice Command... je l'aurai préféré dans mon oreillette ... à suivre...
Ensuite il me faut un petit client ssh histoire de pouvoir me connecter à mes serveurs d'absolument n'importe où. PocketPuTTy est le client ssh seul gratuit et PuTTy est mon meilleur ami sur PC. Le problème c'est que PocketPuTTy n'est pas mûr et lent. Le meilleur client ssh à l'heure actuelle sur Pocket est PockeTTy.
Dernière fonctionnalité fondamentale le SIP : ce tutoriel SJPhone pour l'utilisation de Free est parfait! Il mentionne égalemetn le non moins parfait tutoriel pour utiliser la freephonie avec X-LITE.
Environ 4 h00 pour pouvoir faire le kéké avec son pocket, je suis vraiment pas sur que la technologie ne me fasse que gagner du temps ...
'Fin heureusement je viens de gagner une heure avec le changement d'heure! -
[lundi 08 octobre 2007 - 21:55:45]J'ai un serveur pro (renater réseau de la recherche) et un serveur perso (dedibox groupe illiade).
Tous les deux tournent sur FreeBSD.
Je souhaite effectuer une synchro de mes sites web à j -1 d'un serveur à l'autre.
Vu les débits de chaque serveur ça doit pouvoir se passer dans la nuit.
L'idée c'est de faire ça bien mais quand même un peu à la sauvage donc j'utiliserai rsync plutôt que rsnapshot.
je vais effectuer le rsync via ssh ce qui me permettra de crypter la copie.
Au préalable il faut bien sûr avoir installé openSSH et rsync.
La suite est une traduction très libre de http://troy.jdmz.net/rsync/index.html, je la poserai sur onfamp plus tard, elle est intéressante parce que sécuritaire
Préparation de thishost
D'abord vérifier que rsync est installé et qu'il fonctionne via ssh. Je préfère m'assurer que ça fonctionne avant d'automatiser, donc je teste cette commande avec les droits de thisuser
$ rsync -avz -e ssh remoteuser@remotehost:/remote/dir /this/dir/
je tape le mot de passe au prompt et mes deux dossiers ont le même contenu. thisuser doit évidemment avoir les droits corrects sur les deux répertoires.
il faut maintenant générer une paire clé publique / clé privée avec un mot de passe vide pour pouvoir se loguer en ssh sans mot de passe. Ca paraît un peu dangereux, mais c'est quand même mieux que de stocker son mot de passe en claire dans le script. Et puis je peux limiter un peu le champs d'action de cette clé. Bref je génère cette clé que j'utiliserai sur thishost (en tant que thisuser) :
$ ssh-keygen -t dsa -b 2048 -f /home/thisuser/cron/thishost-rsync-key
On a maintenant une clé avec aucun mot de passe répartie en deux fichiers. Assurez vous qu'aucun utilisateur non autorisé ne puisse lire le fichier de la clé privée (celui qui n'a pas l'extension .pub)
Generating public/private dsa key pair.
Enter passphrase (empty for no passphrase): [press enter here]
Enter same passphrase again: [press enter here]
Your identification has been saved in /home/thisuser/cron/thishost-rsync-key.
Your public key has been saved in /home/thisuser/cron/thishost-rsync-key.pub.
The key fingerprint is:
2e:28:d9:ec:85:21:e7:ff:73:df:2e:07:78:f0:d0:a0 thisuser@thishost
Cette clé ne sert à rien tant qu'on n'a pas mis sa partie publique dans le fichier 'authorized_keys' du remotehost, et en partculier celui de thisuser
/usr/home/remoteuser/.ssh/authorized_keys
J'utilise scp (secure copy) pour copier le fichier sur remotehost
$ scp /usr/home/thisuser/cron/thishost-rsync-key.pub remoteuser@remotehost:/usr/home/remoteuser/
Et maintenant je peux préparer remotehost
Configurer remotehost
Je me connecte en ssh sur remotehost.
$ssh remoteuser@remotehost
Je dois m'assurer que j'ai bien les bons répertoires et les bons fichiers pour l'autoristation
remoteuser@remotehost's password: [type correct password here]
$ if [ ! -d .ssh ]; then mkdir .ssh ; chmod 700 .ssh ; fi
Maintenant on peut utiliser la clé pour se connecter à ce serveur, mais les connexions peuvent se faire à partir de n'importe où (ce que le démon ssh de remotehost autorise comme connexion) et peuvent tout faire (ce que remoteuser peut faire), et ça ça ne me plaît pas. J'édite le 'authorized_keys' et je modifie la ligne avec les informations de 'thishost-rsync-key.pub'. Je ne vais changer que peu de chose au début de ce qu'il y a déjà, modifier les lignes comme suit:
$ mv thishost-rsync-key.pub .ssh/
$ cd .ssh/
$ if [ ! -f authorized_keys ]; then touch authorized_keys ; chmod 600 authorized_keys ; fi
$ cat thishost-rsync-key.pub >> authorized_keys
ssh-dss AAAAB3NzaC1kc3MAAAEBAKYJenaYvMG3nHwWxKwlWLjHb77CT2hXwmC8Ap+fG8wjlaY/9t4u
en
A+2qx9JNorgdrWKhHSKHokFFlWRj+qk3q+lGHS+hsXuvta44W0yD0y0sW62wrEVegz+JVmntxeYc0nDz
5tVGfZe6ydlgomzj1bhfdpYe+BAwop8L+EMqKLS4iSacNjoPlHsmqHMnbibn3tBqJEq2QJjEPaiYj1iP
5IaCuYBhuTKQGa+oyH3mXEif5CKdsIKBj46B0tCy0/GC7oWcUN92QdLrUyTeRJZsTWsxKpRbMliD2pBh
4oyX/aXEf8+HZBrO5vQjDBCfTFQA+35Xrd3eTVEjkGkncI0SAeUAAAAVAMZSASmQ9Pi38mdm6oiVXD55
Kk2rAAABAE/bA402VuCsOLg9YS0NKxugT+o4UuIjyl6b2/cMmBVWO39lWAjcsKK/zEdJbrOdt/sKsxIK
1/ZIvtl92DLlMhci5c4tBjCODey4yjLhApjWgvX9D5OPp89qhah4zu509uNX7uH58Zw/+m6ZOLHN28mV
5KLUl7FTL2KZ583KrcWkUA0Id4ptUa9CAkcqn/gWkHMptgVwaZKlqZ+QtEa0V2IwUDWS097p3SlLvozw
46+ucWxwTJttCHLzUmNN7w1cIv0w/OHh5IGh+wWjV9pbO0VT3/r2jxkzqksKOYAb5CYzSNRyEwp+NIKr
Y+aJz7myu4Unn9de4cYsuXoAB6FQ5I8AAAEBAJSmDndXJCm7G66qdu3ElsLT0Jlz/es9F27r+xrg5pZ5
GjfBCRvHNo2DF4YW9MKdUQiv+ILMY8OISduTeu32nyA7dwx7z5M8b+DtasRAa1U03EfpvRQps6ovu79m
bt1OE8LS9ql8trx8qyIpYmJxmzIdBQ+kzkY+9ZlaXsaU0Ssuda7xPrX4405CbnKcpvM6q6okMP86Ejjn
75Cfzhv65hJkCjbiF7FZxosCRIuYbhEEKu2Z9Dgh+ZbsZ+9FETZVzKBs4fySA6dIw6zmGINd+KY6umMW
yJNej2Sia70fu3XLHj2yBgN5cy8arlZ80q1Mcy763RjYGkR/FkLJ611HWIA= thisuser@thishost
from="10.1.1.1",command="/home/remoteuser/cron/validate-rsync" ssh-dss AAAAB3Nza
Où "10.1.1.1" est l'IP de thishost, et "/home/remoteuser/cron/validate-rsync" est le script qui ressemble à quelque chose du genre
C1kc3MAAAEBAKYJenaYvMG3nHwWxKwlWLjHb77CT2hXwmC8Ap+fG8wjlaY/9t4uA+2qx9JNorgdrWKhH
SKHokFFlWRj+qk3q+lGHS+hsXuvta44W0yD0y0sW62wrEVegz+JVmntxeYc0nDz5tVGfZe6ydlgomzj1
bhfdpYe+BAwop8L+EMqKLS4iSacNjoPlHsmqHMnbibn3tBqJEq2QJjEPaiYj1iP5IaCuYBhuTKQGa+oy
H3mXEif5CKdsIKBj46B0tCy0/GC7oWcUN92QdLrUyTeRJZsTWsxKpRbMliD2pBh4oyX/aXEf8+HZBrO5
vQjDBCfTFQA+35Xrd3eTVEjkGkncI0SAeUAAAAVAMZSASmQ9Pi38mdm6oiVXD55Kk2rAAABAE/bA402V
uCsOLg9YS0NKxugT+o4UuIjyl6b2/cMmBVWO39lWAjcsKK/zEdJbrOdt/sKsxIK1/ZIvtl92DLlMhci5
c4tBjCODey4yjLhApjWgvX9D5OPp89qhah4zu509uNX7uH58Zw/+m6ZOLHN28mV5KLUl7FTL2KZ583Kr
cWkUA0Id4ptUa9CAkcqn/gWkHMptgVwaZKlqZ+QtEa0V2IwUDWS097p3SlLvozw46+ucWxwTJttCHLzU
mNN7w1cIv0w/OHh5IGh+wWjV9pbO0VT3/r2jxkzqksKOYAb5CYzSNRyEwp+NIKrY+aJz7myu4Unn9de4
cYsuXoAB6FQ5I8AAAEBAJSmDndXJCm7G66qdu3ElsLT0Jlz/es9F27r+xrg5pZ5GjfBCRvHNo2DF4YW9
MKdUQiv+ILMY8OISduTeu32nyA7dwx7z5M8b+DtasRAa1U03EfpvRQps6ovu79mbt1OE8LS9ql8trx8q
yIpYmJxmzIdBQ+kzkY+9ZlaXsaU0Ssuda7xPrX4405CbnKcpvM6q6okMP86Ejjn75Cfzhv65hJkCjbiF
7FZxosCRIuYbhEEKu2Z9Dgh+ZbsZ+9FETZVzKBs4fySA6dIw6zmGINd+KY6umMWyJNej2Sia70fu3XLH
j2yBgN5cy8arlZ80q1Mcy763RjYGkR/FkLJ611HWIA= thisuser@thishost
#!/bin/sh
si thishost a une adresse variable, enlever le 10.1.1.1, la partie de la ligne (incluant la virgule), mais laisser la partie 'commande'. Comme ça seule la commande rsync sera accessible avec cette clé. Assurez vous que 'validate-rsync' est exécutable par remoteuser sur remotehost et tester le.
case "$SSH_ORIGINAL_COMMAND" in
*\&*)
echo "Rejected"
;;
*\(*)
echo "Rejected"
;;
*\{*)
echo "Rejected"
;;
*\;*)
echo "Rejected"
;;
*\<*)
echo "Rejected"
;;
*\`*)
echo "Rejected"
;;
rsync\ --server*)
$SSH_ORIGINAL_COMMAND
;;
*)
echo "Rejected"
;;
esac
NB : La clé privée, est maintenant limitée dans ce qu'elle peut faire (et aussi d'où elle peut le faire), elle autorise son possesseur à copier n'importe quel fichier de remote hostauquel remotuser accès. Ceci est dangereux et je devrais prendre toutes les précautions que j'estime nécessaire pour maintenir la sécurité du système et le secret de cette clé.
Une possibilité serait d'assigner des permissions correctes à ce fichier, une autre d'envisager l'utilisation d'un "key caching daemon", et enfin me demander si j'ai vraiment besoin de ce script par rapport au risque encouru.
NB2: Un autre détail de sécurité est la configuration du démon ssh sur remote host. Cet exemple se base sur un ustilisateur qui n'est pas root. Je recommande de ne pas utiliser root comme utilisateur distant pour accéder à tous les fichiers de remotehost. Cette permissions est dangeruese à elle seule, et les risques encourus sur les erreurs ou les mauvaises configuration, plus gros qu'avec un utilisateur normal.
Si vous n'utilisez pas du tout root comme utilisateur distant, et que vous voulez encore sécurisé remotehost, je recommande :
PermitRootLogin forced-commands-only
ou
PermitRootLogin forced-commands-only
dans le fichier '/etc/ssh/sshd_config' de remotehost. Ceux sont des paramètres globaux, pas juste liés à cette connexion, donc vérifiez bien que vous n'avez pas besoin de fonctionnalités que cette configuration interdit.
les entrées 'AllowUsers', 'AllowGroups', 'DenyUsers', et 'DenyGroups' peuvent être utilisées pour restreindre l'accès SSH par utilisateur ou par groupe. Elles sont documentées dans le man de "sshd_config", mais je dirais juste qu'elles utilisent '*' et '?' comme caractères jokers pour autoriser ou interdire les utilisateur et les groupes par expressions régulières. 'AllowUsers' et 'DenyUsers' peuvent aussi interdire par machine distante avec le motif USER@HOST form.
Maintenant j'ai une clé sans mot de passe dans ma configuration, je vais la tester avant de la planifier. je me délogue de remotehost et j'essaie
$ rsync -avz -e "ssh -i /home/thisuser/cron/thishost-rsync-key" remoteuser@remotehost:/remote/dir /this/dir/
Si ca ne marche pas, j'enleverai la restriction "commande" sur la clé et je réessaierai. Si le mot de passe est demandé je vérifierais les permissions de la clé privée (sur thishost ce doit être 600), et sur 'authorized_keys' (sur remotehost, ce doit être 600), et sur le répertoire '~/.ssh/' (sur les deux machines ce doit être). S'il ya quelques messages d'erreur de protocol de cryptage dans 'rsync' mentionnant le script 'validate-rsync', je vérifierais les permissions de 'validate-rsync' (sur remotehost, ce doit être 755 si tous les utilisateurs de remotehost sont sur) qui doivent être 'lecture' et 'exécution' pour remoteuser.
La dernière étape est de planifier le script. J'utilise un truc comme ça
#!/bin/sh
C'et ainsi plus adaptable pour les différents machines et user. Je l'appellerais normalement 'rsync-remotehost-backups' s'il contenait des sauvegrades. Je teste le script juste au cas où j'aurais fait une erreur quelque part.
RSYNC=/usr/bin/rsync
SSH=/usr/bin/ssh
KEY=/home/thisuser/cron/thishost-rsync-key
RUSER=remoteuser
RHOST=remotehost
RPATH=/remote/dir
LPATH=/this/dir/
$RSYNC -az -e "$SSH -i $KEY" $RUSER@$RHOST:$RPATH $LPATH
Quand le script s'est exécuté correctement, j'utilise crontab -e pour mettre une nouvelle ligne dans ma table de cron
0 5 * * * /home/thisuser/cron/rsync-remotehost-backups
Pour une sauvegarde quotidienne à 5h00 (du matin)
0 5 * * 5 /home/thisuser/cron/rsync-remotehost-backups
Pour une sauvegarde hebdomadaire à 5h00 (du matin les vendredi) -
[mardi 02 octobre 2007 - 11:20:33]Le charset est toujours quelque chose d'un peu délicat dans le cas d'un site web ...
En effet il y a celui de la page (ou des templates), mais il y a aussi celui de(s) la base de donnée(s), et enfin il y a celui dans lequel PHP s'adresse à mySQL. Sachant qu'il n'y a pas trop de questions à se poser quant au charset à privilégier : l'utf-8
La fonction PHP/mySQL magique c'est celle là
mysql_query("SET CHARACTER SET 'utf8'");
Seulement avec Symfony on est bien éloigné de ces considérations (c'est précisément le rôle d'un frameWork!). La fonction magique ce transforme alors en option magique à ajouter au config/databases.yml du projet symfony:
encoding: utf8
et le tour est joué -
[mercredi 26 septembre 2007 - 10:09:04]http://caspian.dotconf.net/menu/Software/SendEmail/
Voilà un petit soft utilisable en ligne de commande qui du coup peut s'intégrer facilement dans un script.
Powered by ScribeFire.
-
[lundi 24 septembre 2007 - 23:24:13]Le plugins est amusant et marche étonnament bien.
La config automatique marche nickel avec wordPress.
Volia une extension indispensable de plus.
Powered by ScribeFire.
-
[lundi 24 septembre 2007 - 16:35:59]Le marché de l'éditeur WYSIWYG en ligne me surprendra toujours. J'avais d'abord était surpris que Symfony propose TinyMCE en quasi natif, en se préoccupant moins de FCK (mais ça marche quand même)...
TinyMCE est gratuit jusqu'à ce qu'on cherche son file manager :-/
En bricolant un peu avec les players flash précédemment cités j'avais besoin d'un file manager un peu plus luxueux que le natif de FCK. J'ai donc exploré 3 pistes:
- ckfinder : est la solution proposée par FCK, ça a l'air vraiment pas mal, mais c'est tout sauf open source. si vous n'avez pas de clé d' enregistrement, le filemanager tourne en mode démo ... Outre le fait que ce genre de limitation est assez désagréable, elle interdit de faire fonctionner le mode démo avec un path d'entrepot des fichiers uploadés stocké dans la session de l'utilisateur (les fichiers sont cryptés). Le CMS que j'ai réalisé est basé sur ce hack du filemanager en natif ce qui m'interdit le test de ce produit ... et c'est sûr queje ne l'achèterai pas.
- FBXP File Browser eXtension Project est intéressant parce que gratuit, mais sembnle un peu délaissé. Le problème est que c'est un hack sur une version de FCK et qu'il faudrait l'adapter à une version à jour. Sachant que la dernière release date de mars 2005 ... Bof bof!
- KFM est LA solution opensource et de luxe. Outre le fait qu'il s'intègre en plugins FCK, il est très complet : il gère jusqu'au traitement de l'image (redimenssionnement, rotation) voir jusqu'à l'édition des fihicers texte. Le seul point faible c'est que le projet à l'air jeune donc la stabilité n'est pas vraiment au rendez vous. J'ai réussi à m'en tirer avec a version 1.0 dans l'immédiat, et je ne doute pas que ca ne va aller qu'en s'arrangeant.
-
[vendredi 14 septembre 2007 - 14:46:07]Je les ai activé hier soir dans wordpress et ....
Ils ne fonctionnent pas avec le plugin flv_player. C'est le seul défaut que je vois à ce player, mais il est de taille quant à son intégration : il ne gère pas les path en absolu :-/
Je jetterai un oeil du côté des autres plugins wordpress et des autres player Flash dés que j'ai 5 minuites ....
En ce qui concerne le module FLV pour symfony il n'est pas encore officiel parce que tout récent, mais il existe bel et bien : http://trac.symfony-project.com/trac/wiki/sfFLVPlayerPlugin
Ca aussi je teste bientôt!
Pour ce qui est des players Flash : http://flv-player.net






