Technologies : PhpBB, SharePoint 2010, base de données MySQL,
LinQ to Entity/MySQL, API SharePoint, application console C#, Visual Studio
2010.
Cet article a valeur de
tutoriel a pour but d’effectuer la migration d’un forum PhpBB basé sur des
technologies gratuites, vers un site SharePoint 2010 de type blog.
Dans le cadre d’un projet réalisable en entreprise ce projet
vous fera découvrir les possibilités en matière de développement qu’offre le C#
appliqué à SharePoint 2010 et ceci via une application console.
En effet SharePoint
2010 offre un point de partage des connaissances au sein de l’entreprise et la
réutilisation des données présentes dans l’entreprise avant l’implémentation de
cette plateforme à une forte valeur ajouté dans les arguments de vente de cet
outil.
Une forte majorité des projets de développement autour de la
plateforme SharePoint 2010 porte en effet sur des problématiques de migration
de l’existant.
Prérequis technique :
·
C#
·
Base en LinQ to Entity.
·
Connaissance de l’architecture logique d’un site
SharePoint.
Mise en place de l’environnement
Outils nécessaires :
1.
Machine avec SharePoint 2010.
2.
Wamp Server.
3.
PhpBB.
4.
Visual Studio 2010 ou 2008.
Structure de la base de données MySQL
Schéma relationnel de la portion de base de données utilisé.
Génération des classes C# pour LinQ to MySQL
Dans le cadre de l’utilisation d’une base de données MySQL
il est nécessaire de générer les classes correspondantes aux tables de la base
de données. Pour ce faire j’ai utilisé le logiciel DbLinq dont vous trouverez
l’archive en fin d’article.
Voici la commande à exécuter après avoir décompressé le
dossier
DbMetal.exe -provider=MySql
-database:phpbb -server:localhost
-user:root –password:password -namespace:MigrationPhpBB
-code:PhpBB.cs –sprocs
-code:PhpBB.cs –sprocs
En remplaçant les arguments, server, user, password et namespace par les valeurs
correspondantes à votre serveur.
Figure 1. Génération
des classes à partir de la base de données MySQL
Un fichier .cs sera alors généré dans le dossier de DbLinq avec
pour nom PhpBB.cs
Création du projet Visual Studio
Vous êtes maintenant prêt à développer l’application. Crée un projet de type Application console depuis les templates proposés par Visual Studio
et nommez le MigrationPhpBB.
Ajoutez le fichier qui a été
créé dans le dossier de DbLinq à votre solution (PhpBB.cs). Ainsi qu’un
référence vers les .dll DbLinq.dll et
DbLinq.MySql.dll contenu dans le dossier DbLinq.
Figure 2. Création
du projet application console.
Vos premiers échanges avec MySQL
Dans la Main méthode crée une instance de la classe PhPbB.
PhPbB entity = new PhPbB(new MySqlConnection(
"Server=localhost;
Database=phpbb; Uid=root; Pwd=password"));
Pour récupérer la liste de tous les forums contenus dans
votre site PhpBB utilisez la ligne de code suivant :
List<PhPbB3Forums>
list_forums = (from forums in entity.PhPbB3Forums
select forums).ToList();
Dans mon exemple je n’importe seulement qu’une partie des
forums contenus dans le site PhpBB en stockant le nom des forums dans une
liste.
List<string> forumAImporter
= new List<string>() { "Forum1"
,"Forum2", "Forum3" };
List<PhPbB3Forums>
list_categories = (from categories_filtres in
((from categories_toutes in
entity.PhPbB3Forums
select categories_toutes).ToList())
where
categories_cible.Contains(categories_filtres.ForumName)).ToList();
Ma list_categories contiendra mes 3 forums et me permettra
de faire un parcourt de ceux-ci pour récupérer les différents postes et
réponses.
Création des sous-sites SharePoint
Je vais donc dans un premier temps créer un sous site
SharePoint pour chacune de mes catégories.
Créez une classe nommé SiteHelper.cs qui contiendra le code suivant :
namespace
MigrationPhpBB
{
static class SiteHelper
{
public static string url = "http://formation";
}
}
Et une classe SharepointHelper.cs qui se
chargera de créer les sous-sites, les catégories ainsi que les billets et les
réponses.
Avec une première fonction pour créer le sous-site SharePoint :
/// <summary>
/// Création d'un site de typle Blog
/// </summary>
/// <param name="name">Nom du site</param>
/// <returns>Site
crée</returns>
public static SPWeb CreateBlog(string
name)
{
using
(SPSite site = new SPSite(SiteHelper.url))
{
return
EnsureWeb(site, name, SPWebTemplate.WebTemplate.Blog, true,
true);
}
}
/// <summary>
/// Site SharePoint sur lequel créer le blog.
/// </summary>
/// <param name="site">Site SharePoint</param>
/// <param name="webName">Nom du blog</param>
/// <param name="onQuickLaunch">Spécifie si le site
aura un lien de lancement rapide</param>
/// <returns>Site
crée</returns>
public
static SPWeb EnsureWeb(SPSite site, string webName, bool
onQuickLaunch)
{
SPWeb web = null;
Guid
webID = Guid.Empty;
if (site != null)
{
try
{
//parcous les sous-sites.
foreach (SPWeb
currentWeb in site.AllWebs)
{
//verifie si le site existe, si il existe il faut le supprimer.
if (currentWeb.Name.ToLower() == webName.ToLower())
{
Console.WriteLine("Blog
" + webName + " existe,
suppression");
currentWeb.Delete();
break;
}
}
#region
Creation du blog
if
(web == null)
{
//Création d'un site de type Blog.
SPWebTemplate
webTemplate = site.GetWebTemplates(1036)["BLOG#0"];
site.AllowUnsafeUpdates
= true;
web =
site.AllWebs.Add(webName, webName, "Blog
" + webName, 1036, webTemplate, false,
false);
//Supprime les billets
crée par défaut.
SPList
billets = web.Lists["Billets"];
foreach (SPListItem it in
billets.Items)
billets.Items.DeleteItemById(it.ID);
//Supprime les
catégories crée par défaut.
SPList
categories = web.Lists["Catégories"];
foreach (SPListItem it in
categories.Items)
categories.Items.DeleteItemById(it.ID);
web.Navigation.UseShared = (onQuickLaunch) ? true
: false;
//Ajout des liens de
navigations.
if (web.Navigation.UseShared)
{
//Ajoute un lien vers le forum dans la barre de navigation
Microsoft.SharePoint.Navigation.SPNavigationNode pNode = new
Microsoft.SharePoint.Navigation.SPNavigationNode(web.ParentWeb.Title,
web.ParentWeb.Url.Replace(web.ParentWeb.Site.Url, string.Empty));
Microsoft.SharePoint.Navigation.SPNavigationNode cNode = new Microsoft.SharePoint.Navigation.SPNavigationNode("Blog " + web.Title, web.Url);
web.ParentWeb.Navigation.TopNavigationBar.AddAsLast(cNode);
// add to quick launch
Microsoft.SharePoint.Navigation.SPNavigationNodeCollection quickLaunch =
web.ParentWeb.Navigation.QuickLaunch;
foreach
(Microsoft.SharePoint.Navigation.SPNavigationNode node in
quickLaunch)
{
//Ajoute le quick launch dans les sites.
if (node.Title == "Sites")
{
node.Children.AddAsLast(cNode);
break;
}
}
}
}
#endregion
//Mets à jour le site.
web.Update();
}
catch
(Exception e)
{
Console.WriteLine(e);
Console.ReadLine();
}
}
else
{
throw
new Exception("In EnsureSiteDataList SPWeb is null");
}
return web;
}
De retour dans Program.cs il faut
créer un sous-site par forum.
foreach
(var categorie in
list_categories)
{
#region
Création du sous-site
categorieID = categorie.ForumID;
categorieName
= categorie.ForumName;
Console.WriteLine();
Console.WriteLine("--------------------------------------");
Console.WriteLine("Création
du sous site " + categorieName);
}
SPWeb blog = SharepointHelper.CreateBlog(categorieName);
}
Figure 3. Création
des sous-sites via l'application.
Création des catégories pour les blogs SharePoint
Dans PhpBB les
catégories de forum contiennent des forums, qui seront traduit par des
catégories dans le blog SharePoint. Il faut donc utiliser la propriété de
navigation ParentID de la classe PhpbB3Forums pour retrouver les forums
appartenant à une catégorie de forum.
List<PhPbB3Forums>
forums = (from fo in
entity.PhPbB3Forums
where fo.ParentID == categorieID
select fo).ToList();
uint
forumID;
string
forumName;
foreach
(PhPbB3Forums forum in
forums)
{
forumID = forum.ForumID;
forumName = forum.ForumName;
SPFieldLookupValue lookUpCategorie = SharepointHelper.CreateCategorie(blog,forumName);
Console.WriteLine("Catégorie
crée : " + forumName);
}
Dans la classe SharepointHelper créer la
fonction
/// <summary>
/// Création de catégorie pour le site de type
blog sharepoint
/// </summary>
/// <param name="blog">Blog sur lequel crée
une catégorie</param>
/// <param name="categorie_name">Nom de la catégorie</param>
/// <returns>Lookup de la catégorie</returns>
public
static SPFieldLookupValue CreateCategorie(SPWeb
blog,string categorie_name)
{
SPList
categories = blog.Lists["Catégories"];
//Supprime la catégorie si elle existe déjà.
foreach (SPListItem it in
categories.Items)
if(it.Title
== categorie_name)
categories.Items.DeleteItemById(it.ID);
SPListItem item;
item = categories.Items.Add();
item["Title"]
= SiteHelper.Utf8_decode(categorie_name,false);
item.Update();
return new SPFieldLookupValue(item.ID, item.Title);
}
L’objet SPFieldLookupValue permet de
remplir les propriétés de type LookUp qui fera le lien entre un billet et le
forum auquel il appartient.
Crée également la fonction Utf8_decode
dans la classe SiteHelper.
public
static string
Utf8_decode(string s,bool
estBillet)
{
byte[] l = Encoding.Default.GetBytes(s);
string st = Encoding.UTF8.GetString(l);
string deco
= SPEncode.HtmlDecode(st);
return deco;
}
Figure 4. Création
des catégories depuis l'application
Création des billets et des réponses
A la suite de l’instruction permettant de créer les categories,
nous allons parcourir chaque forum PhpBB pour récupérer les topics appartenant
à la catégorie précédemment crée.
SPFieldLookupValue
lookUpCategorie = SharepointHelper.CreateCategorie(blog,forumName);
Console.WriteLine("Catégorie
crée : " + forumName);
#region
Création des billets
var
topics = (from top in
entity.PhPbB3Topics
where
top.ForumID == forumID
orderby
top.TopicLastPostTime
select top).ToList();
Le corps du topic est contenu dans le
premier post lié à ce topic, il faut donc parcourir la table PhpbB3Posts dans
le but de récupérer le corps du topic ainsi que les réponses apportés par les
utilisateurs.
On va ici utiliser l’adresse email de la personne inscrite sur le
forum PhpBB pour relier avec le compte SharePoint en utilisant la propriété
Email de l’objet SPUser.
Ajouté cette fonction à la classe SiteHelper.cs
/// <summary>
/// Retourne l'utilisateur grâce à son email.
/// </summary>
/// <param name="web">Site concerné</param>
/// <param name="email">Email de l'utilisateur</param>
/// <returns>Look
up Utilisateur</returns>
public
static SPFieldLookupValue lookUpAuthor(SPWeb
web, string email)
{
SPFieldLookupValue Lookup = null;
using
(SPSite site = new SPSite(SiteHelper.url))
{
try
{
foreach
(SPUser user in web.AllUsers)
if(email.ToLower()
== user.Email.ToLower())
Lookup = new SPFieldLookupValue(user.ID, user.Name);
}
catch
{
return
null;
}
}
return
Lookup;
}
Il faut ensuite parcourir la liste des topics ainsi que la
liste des posts pour ajouter les billets et les commentaires dans le blog
SharePoint.
PhPbB3Users user_topic;
DateTime created;
SPFieldLookupValue
lookUpUser=null;
SPFieldLookupValue
lookUpBillet = new SPFieldLookupValue();
string
user_topic_mail;
foreach
(PhPbB3Topics top in
topics)
{
//Le corps du billet se trouve dans le premier commentaire
posté, il faut donc aller le récupérer.
SPFieldLookupValue
lookUpCommentaire = null;
var
posts = (from post in
entity.PhPbB3Posts
where
post.TopicID == top.TopicID
orderby
post.PostTime
select
post).ToList();
for(int i = 0; i < posts.Count;i++)
{
PhPbB3Posts
post = new PhPbB3Posts();
post = posts[i];
created = new
DateTime(1970, 01,
01).AddSeconds(post.PostTime);
user_topic = (from us in
entity.PhPbB3Users
where us.UserID == post.PosterID
select us).First();
user_topic_mail =
user_topic.UserEmail;
correction_mail = "";
dictionnaire_correction_email.TryGetValue(user_topic_mail,
out correction_mail);
lookUpUser = SiteHelper.lookUpAuthor(blog, user_topic_mail);
if (i == 0)
{ // Le premier post contient le corps du
topic
// Créer le billet avec le
corps de message contenu dans le post.
lookUpBillet = SharepointHelper.CreateBillet(top.TopicTitle,
post.PostText, created, blog, lookUpCategorie, lookUpUser);
}
else
{
lookUpCommentaire = SharepointHelper.CreateCommentaire(lookUpCategorie,
post.PostSubject, post.PostText, created, blog, lookUpUser, lookUpBillet);
}
}
Il faut maintenant ajouter les fonctions CreateBillet et
CreateCommentaire dans la classe SharepointHelper.
/// <summary>
/// Crée un billet.
/// </summary>
/// <param name="title">Titre du billet</param>
/// <param name="body">Corps du billet</param>
/// <param name="created">Date de création du billet</param>
/// <param name="blog">Site sur lequel créer
le billet</param>
/// <param name="lookUpCategorie">Catégorie à laquel le
billet appartient</param>
/// <param name="lookUpUser">Utilisateur ayant crée
le billet</param>
/// <returns>Look
up vers le billet</returns>
public
static SPFieldLookupValue CreateBillet(
string
title, string body, DateTime
created, SPWeb blog, SPFieldLookupValue lookUpCategorie, SPFieldLookupValue
lookUpUser
{
SPList billets = blog.Lists["Billets"];
SPListItem item = billets.Items.Add();
item["Title"]
= SiteHelper.Utf8_decode(title,false);
if(body.Contains("SMILIES_PATH"))
body = UploadSmiley(body,blog);
item["Body"]
= SiteHelper.Utf8_decode(body, true);
if(lookUpCategorie
!= null)
item["PostCategory"]
= lookUpCategorie;
if(lookUpUser
!= null)
item["Author"]
= lookUpUser;
item["Created"]
= created;
item["PublishedDate"]
= created;
item["_ModerationStatus"]
= (int)SPFieldModStat.ListItemMenuState.Allowed;
item.Update();
return new SPFieldLookupValue(item.ID, item.Title);
}
public
static SPFieldLookupValue CreateCommentaire(
string
titre, string corps, DateTime
created, SPWeb blog, SPFieldLookupValue lookUpUser, SPFieldLookupValue
lookUpPost)
{
SPList
commentaires = blog.Lists["Commentaires"];
SPListItem
addComm = commentaires.Items.Add();
if (corps.Contains("SMILIES_PATH"))
corps = UploadSmiley(corps, blog);
addComm["Title"]
= SiteHelper.Utf8_decode(titre,false);
addComm["Body"]
= SiteHelper.Utf8_decode(corps,false);
addComm["Created"]
= created;
if(lookUpUser
!= null)
addComm["Author"]=
lookUpUser;
if
(lookUpPost != null)
{
addComm["PostTitle"]
= new SPFieldLookupValue(lookUpPost.LookupId, SiteHelper.Utf8_decode(titre,false));
var
tit = addComm["PostTitle"];
}
addComm["_ModerationStatus"]
= (int)SPFieldModStat.ListItemMenuState.Allowed;
addComm.Update();
return new SPFieldLookupValue(addComm.ID, addComm.Title);
}
Figure 5. Création
du billet et du commentaire via l'application.
Attacher les pièces jointes aux messages.
Les pièces jointes sont de deux types, des images ou des archives
de documents. Dans les deux cas il faut dans un premier temps ajouter les
fichiers à une bibliothèque de documents présente sur le site et ensuite lier
ce fichier au message.
Pour des raisons de facilité j’ai directement copié le
dossier contenant les pièces jointes trouvé sur le server PhpBB sur le serveur SharePoint.
Ajoutez dans la classe SiteHelper :
Public
static string
urlPiecesJointes = "C:/Users/spadm/Desktop/importPhpbb/piecesJointes/";
Etant le chemin vers votre dossier de pièces jointes.
En base de données MySQL dans la table PhpBB3Posts la
présence d’une pièce jointe est signalé par le champ PostAttachment, ajoutez ces quelques lignes à
la suite de la création de commentaire dans le programme principale.
If
(post.PostAttachment == 1)
{
List<PhPbB3Attachments> attach = (from atta in
entity.PhPbB3Attachments
where
atta.PostMsgID == post.PostID
select
atta).ToList();
foreach (PhPbB3Attachments attachment in attach)
{
try
{
FileStream
fStr = new FileStream(SiteHelper.urlPiecesJointes +
attachment.RealFilename, FileMode.Open);
SharepointHelper.AttachToBillet(blog,lookUpBillet,attachment.RealFilename,
fStr, attachment.MimeType,lookUpCommentaire);
}
catch (Exception
ex)
{
Console.WriteLine("!!!!! Le fichier "+attachment.RealFilename
+ " n'existe pas !!!!!");
}
}
}
Et enfin la fonction servant à construire le corps du
message avec un lien vers la pièce jointe.
public
static SPFieldLookupValue AttachToBillet(SPWeb
blog,
SPFieldLookupValue billet, string fileName, Stream
str,string mimeType,SPFieldLookupValue
commentaire)
{
SPList
liste_concerne;
SPListItem item_concerne;
try
{
if (commentaire == null)
// cas où la pièce jointe appartient au billet.
{
liste_concerne = blog.Lists["Billets"];
item_concerne = liste_concerne.Items.GetItemById(billet.LookupId);
}
else
{ //cas où la
pièce jointe appartient au commentaire.
liste_concerne = blog.Lists["Commentaires"];
item_concerne
= liste_concerne.Items.GetItemById(commentaire.LookupId);
}
SPList piecesJointes = blog.Lists["Photos"];
SPFolder folder =
piecesJointes.RootFolder;
//ajoute du fichier dans le dossier de
document.
SPFile
newFile = folder.Files.Add(fileName,str, true);
string
item_body = (string)item_concerne["Body"];
//retrouve l'emplacement de la pièce jointe
dans le texte.
int indexFileName = item_body.IndexOf(fileName);
string
partie_avant = "";
string
partie_apres = "";
if
(indexFileName != -1)
{
partie_avant = item_body.Substring(0,
indexFileName);
partie_apres =
item_body.Substring(indexFileName);
partie_avant = partie_avant.Substring(0,
partie_avant.LastIndexOf('['));
partie_apres =
partie_apres.Substring(partie_apres.IndexOf(']')
+ 1);
}
string
lien_vers_piece_jointe = "";
//chemin relatif
du fichier.
string
fullUrl = SiteHelper.url +
newFile.ServerRelativeUrl;
if
(mimeType.Contains("jpeg"))
{ //si
la pièce jointe est une image il faut l'afficher grâce à la balise html
<img>
if (commentaire == null)
lien_vers_piece_jointe = "<img
src='" + fullUrl+ "' alt='"
+ newFile.Name + "'>";
else
lien_vers_piece_jointe = fullUrl;
}
else
{
if
(mimeType.Contains("application"))
{ //si la pièce jointe est une
archive il faut faire un lien faire ce fichier grâce à la balise <html>
if(commentaire
==null)
lien_vers_piece_jointe = "<a href='" + fullUrl + "' title='" + newFile.Name + "'>" + newFile.Name + "</a>";
else
lien_vers_piece_jointe = fullUrl;
}
}
//reconstruit le
message autour de la balise de la pièce jointe.
if(indexFileName
== -1)
item_concerne["Body"]
= item_body+ lien_vers_piece_jointe;
else
item_concerne["Body"]
= partie_avant + lien_vers_piece_jointe + partie_apres;
item_concerne.Update();
newFile.Update();
folder.Update();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
return null;
}
Figure 6.
Intégration des pièces jointes présentes dans PhpBB
Ajouter les smileys PhpBB
Les smileys présent dans
les messages générés par PhpBB se trouvent dans le répertoire « phpBB3\images\smilies »
de votre dossier wamp. Copiez ce répertoire de façon temporaire sur votre
serveur SharePoint et préciser le chemin vers ce répertoire dans la classe SiteHelper.
public
static string
urlSmiley = "C:/Users/spadm/Desktop/importPhpbb/smilies/";
Et ajouter cette fonction dans la classe SharepointHelper:
public
static string
UploadSmiley(string body,SPWeb blog)
{
int ind =
-1;
SPList listImage = blog.Lists["Photos"];
SPFolder folder =
listImage.RootFolder;
while
((ind = body.IndexOf("{SMILIES_PATH}"))
>= 0)
{
string
url = body.Substring(ind-10);
url =
url.Substring(0,url.IndexOf("/>")+2);
string
filName = url.Substring(0,url.IndexOf(".gif")
+4);
filName =
filName.Substring(filName.LastIndexOf('/')+1);
SPFile smileyFile = null;
foreach
(SPFile file in folder.Files)
{
if
(file.Name == filName)
smileyFile = file;
}
if
(smileyFile == null)
{
FileStream
fStr = new FileStream(SiteHelper.urlSmiley+filName, FileMode.Open);
smileyFile =
folder.Files.Add(filName, fStr, true);
}
body = body.Replace(url,
"<img
alt='"+filName+"' src='"+SiteHelper.url+smileyFile.ServerRelativeUrl+"' style=\"margin:5px\" />");
}
return body;
}
Figure 7.
Intégration des smileys PhpBB
Conclusion et petites finitions
Vous aurez remarqué qu’à ce stade quelques balises générées
par PhpBB subsistent, celles-ci ne sont pas reconnues par SharePoint lors de la
génération des pages Html. Vous pouvez remplacer ces balises par celles
correspondantes à la norme Html.
Vous pouvez aussi dans les sources du projet récupérer la
fonction Utf8_decode de la classe SiteHelper qui se charge de les remplacer.
Grâce aux fonctionnalités de connections de C# avec toutes
sortes de base de données ou fichier de données (Xml, Excel par exemple), vous
pouvez importer des données venant de sources diverses.
Ces fonctionnalités permettent à SharePoint d’être encore
mieux accepté au niveau d’une entreprise du fait que les sites pourront
s’appuyer sur des données préexistante à l’installation de cette nouvelle
plateforme.
Hello
RépondreSupprimerCan I have the project Visual C files?
Thank you!