Permettre le retour en arrière sur une page PHP avec une session [programmation]

Si vous utilisez les sessions avec PHP et que vous avez un formulaire qui fait du POST, alors le retour en arrière (en utilisant le bouton du navigateur) risque de retourner une page blanche avec un message d’erreur du style « Le document a expiré ».

Après avoir fouillé la documentation PHP pour session_cache_limiter, j’ai découvert qu’il suffit d’ajouter session_cache_limiter('private_no_expire, must-revalidate'); au tout début du fichier, avant quoi que ce soit. Et maintenant le retour en arrière fonctionne !

Modifier les permissions d’une liste Sharepoint en utilisant permissions.aspx et JavaScript [Sharepoint]

Il est possible d’utiliser les web services de Sharepoint pour effectuer un certain nombre d’opérations. Il existe divers API (comme http://spservices.codeplex.com que j’utiliserai ici) pour aider dans ces opérations.

Cependant il est difficile de trouver de la documentation / exemples sur comment utiliser AddPermission, RemovePermission et UpdatePermissions. Après avoir longuement fouillé le Net, voici ce que j’ai trouvé.

Tout d’abord, pour assigner un rôle défini sur votre site (comme « Contribute », « Full Control », « Read », etc) il va falloir trouver les droits associés. Pour cela on utilise GetRoleCollectionFromWeb

$().SPServices({
  operation:"GetRoleCollectionFromWeb",
  webURL:"http://mon.site.com/ma/collection/",
  completefunc: function (xData, Status) {
    var roles=[]; // va contenir les différents rôles existants et les permissions associées
    var dom=xData.responseXML.getElementsByTagName("Role");
    for (var i=dom.length; i--;) roles[dom[i].getAttribute("Name")]=dom[i].getAttribute("BasePermissions").split(", ");
  }
});

Maintenant on va avoir un tableau associatif qui lie un rôle (par exemple « Contribute ») à un ensemble de permissions (ici ça sera « ViewListItems, AddListItems, EditListItems, DeleteListItems, OpenItems, ViewVersions, DeleteVersions, ManagePersonalViews, ViewFormPages, Open, ViewPages, CreateSSCSite, BrowseDirectories, BrowseUserInfo, AddDelPrivateWebParts, UpdatePersonalWebParts, UseClientIntegration, UseRemoteAPIs, CreateAlerts, EditMyUserInfo »).

Grâce à la documentation de Microsoft portant sur les masks de permissions on va créer un autre tableau associatif pour chacune de ces BasePermissions.
Aussi on sait qu’il faut faire un OR entre chaque mask pour avoir celui qu’on souhaite.

// @param basePermissions est un tableau
function getPermissionMask(basePermissions) {
  // on crée un tableau basé sur la doc Microsoft
  var permissions=[];
  // on rajoute "FullMask" qui est le Full Control
  permissions["FullMask"] = -1;
  permissions["ViewListItems"] = parseInt("0x0000000000000001");
  permissions["AddListItems"] = parseInt("0x0000000000000002");
  permissions["EditListItems"] = parseInt("0x0000000000000004");
  permissions["DeleteListItems"] = parseInt("0x0000000000000008");
  permissions["ApproveItems"] = parseInt("0x0000000000000010");
  permissions["OpenItems"] = parseInt("0x0000000000000020");
  permissions["ViewVersions"] = parseInt("0x0000000000000040");
  permissions["DeleteVersions"] = parseInt("0x0000000000000080");
  permissions["CancelCheckout"] = parseInt("0x0000000000000100");
  permissions["ManagePersonalViews"] = parseInt("0x0000000000000200");
  permissions["ManageLists"] = parseInt("0x0000000000000800");
  permissions["ViewFormPages"] = parseInt("0x0000000000001000");
  permissions["Open"] = parseInt("0x0000000000010000");
  permissions["ViewPages"] = parseInt("0x0000000000020000");
  permissions["AddAndCustomizePages"] = parseInt("0x0000000000040000");
  permissions["ApplyThemeAndBorder"] = parseInt("0x0000000000080000");
  permissions["ApplyStyleSheets"] = parseInt("0x0000000000100000");
  permissions["ViewUsageData"] = parseInt("0x0000000000200000");
  permissions["CreateSSCSite"] = parseInt("0x0000000000400000");
  permissions["ManageSubwebs"] = parseInt("0x0000000000800000");
  permissions["CreateGroups"] = parseInt("0x0000000001000000");
  permissions["ManagePermissions"] = parseInt("0x0000000002000000");
  permissions["BrowseDirectories"] = parseInt("0x0000000004000000");
  permissions["BrowseUserInfo"] = parseInt("0x0000000008000000");
  permissions["AddDelPrivateWebParts"] = parseInt("0x0000000010000000");
  permissions["UpdatePersonalWebParts"] = parseInt("0x0000000020000000");
  permissions["ManageWeb"] = parseInt("0x0000000040000000");
  permissions["UseClientIntegration"] = parseInt("0x0000001000000000");
  permissions["UseRemoteAPIs"] = parseInt("0x0000002000000000");
  permissions["ManageAlerts"] = parseInt("0x0000004000000000");
  permissions["CreateAlerts"] = parseInt("0x0000008000000000");
  permissions["EditMyUserInfo"] = parseInt("0x0000010000000000");

  //maintenant on va faire des OR selon basePermissions
  var mask = 1;
  for (var i=basePermissions.length; i--;) mask |= permissions[basePermissions[i]];
  return mask;
}

Et enfin on utilise ce mask avec AddPermission :

$().SPServices({
  operation:"AddPermission",
  webURL:"http://mon.site.com/ma/collection/",
  objectName:"Nom de la Liste",
  objectType:"List",
  permissionIdentifier:"domain\\login", // par exemple "europe\\john_doe"
  permissionType:"user", // car c'est un username qu'on a donné... si on avait donné un Sharepoint Group alors on aurait mis "group"
  permissionMask:getPermissionMask(roles["Contribute"]),
  completefunc: function (xData, Status) { alert("Permissions ajoutés"); }
});

Plus d’infos sur les arguments disponibles dans la documentation de SPServices sur les Permissions.

On peut enfin tout mettre ensemble, ce qui donnera :

// @param basePermissions est un tableau
function getPermissionMask(basePermissions) {
  // on crée un tableau basé sur la doc Microsoft
  var permissions=[];
  // on rajoute "FullMask" qui est le Full Control
  permissions["FullMask"] = -1;
  permissions["ViewListItems"] = parseInt("0x0000000000000001");
  permissions["AddListItems"] = parseInt("0x0000000000000002");
  permissions["EditListItems"] = parseInt("0x0000000000000004");
  permissions["DeleteListItems"] = parseInt("0x0000000000000008");
  permissions["ApproveItems"] = parseInt("0x0000000000000010");
  permissions["OpenItems"] = parseInt("0x0000000000000020");
  permissions["ViewVersions"] = parseInt("0x0000000000000040");
  permissions["DeleteVersions"] = parseInt("0x0000000000000080");
  permissions["CancelCheckout"] = parseInt("0x0000000000000100");
  permissions["ManagePersonalViews"] = parseInt("0x0000000000000200");
  permissions["ManageLists"] = parseInt("0x0000000000000800");
  permissions["ViewFormPages"] = parseInt("0x0000000000001000");
  permissions["Open"] = parseInt("0x0000000000010000");
  permissions["ViewPages"] = parseInt("0x0000000000020000");
  permissions["AddAndCustomizePages"] = parseInt("0x0000000000040000");
  permissions["ApplyThemeAndBorder"] = parseInt("0x0000000000080000");
  permissions["ApplyStyleSheets"] = parseInt("0x0000000000100000");
  permissions["ViewUsageData"] = parseInt("0x0000000000200000");
  permissions["CreateSSCSite"] = parseInt("0x0000000000400000");
  permissions["ManageSubwebs"] = parseInt("0x0000000000800000");
  permissions["CreateGroups"] = parseInt("0x0000000001000000");
  permissions["ManagePermissions"] = parseInt("0x0000000002000000");
  permissions["BrowseDirectories"] = parseInt("0x0000000004000000");
  permissions["BrowseUserInfo"] = parseInt("0x0000000008000000");
  permissions["AddDelPrivateWebParts"] = parseInt("0x0000000010000000");
  permissions["UpdatePersonalWebParts"] = parseInt("0x0000000020000000");
  permissions["ManageWeb"] = parseInt("0x0000000040000000");
  permissions["UseClientIntegration"] = parseInt("0x0000001000000000");
  permissions["UseRemoteAPIs"] = parseInt("0x0000002000000000");
  permissions["ManageAlerts"] = parseInt("0x0000004000000000");
  permissions["CreateAlerts"] = parseInt("0x0000008000000000");
  permissions["EditMyUserInfo"] = parseInt("0x0000010000000000");

  //maintenant on va faire des OR selon basePermissions
  var mask = 1;
  for (var i=basePermissions.length; i--;) mask |= permissions[basePermissions[i]];
  return mask;
}

$().SPServices({
  operation:"GetRoleCollectionFromWeb",
  webURL:"http://mon.site.com/ma/collection/",
  completefunc: function (xData, Status) {
    var roles=[]; // va contenir les différents rôles existants et les permissions associées
    var dom=xData.responseXML.getElementsByTagName("Role");
    for (var i=dom.length; i--;) roles[dom[i].getAttribute("Name")]=dom[i].getAttribute("BasePermissions").split(", ");
    $().SPServices({
      operation:"AddPermission",
      webURL:"http://mon.site.com/ma/collection/",
      objectName:"Nom de la Liste",
      objectType:"List",
      permissionIdentifier:"domain\\login", // par exemple "europe\\john_doe"
      permissionType:"user", // car c'est un username qu'on a donné... si on avait donné un Sharepoint Group alors on aurait mis "group"
      permissionMask:getPermissionMask(roles["Contribute"]),
      completefunc: function (xData, Status) { alert("Permissions ajoutés"); }
    });
  }
});

A noter :
UpdatePermission va remplacer les permissions d’une personne;
– Toute personnalisation des permissions sur la liste va entrainer l’arrêt des droits inhérents (avec Sharepoint 2010 il sera possible de remettre les droits parents grâce à How to: Break Role Assignment Inheritance Using ECMAScript)

CSS3 PIE pour créer un border-radius sur IE [webdesign]

Si vous cherchez à faire un border-radius sur un vieux IE vous avez dû vous rendre compte que cela ne fonctionne pas… mais heureusement il existe CSS3 PIE qui est un « petit » fichier qui va permettre de créer l’effet désiré !

A noter cependant que je rencontre un problème : lorsque j’applique le fichier htc avec behavior alors j’ai ma couleur de fond (background-color) qui devient invisible / disparait. Ce problème peut-être résolu en appliquant la règle CSS suivante (solution trouvée sur leur forum):

* { position:relative }

Grâce à ça tout fonctionne sous IE 🙂

Transformer/convertir une vidéo vers le format mp4 et ogg (ogv) gratuitement [vidéo]

Il existe une extension pour Firefox qui permet de convertir tout type de vidéo (avi, wmv, mp4, etc) dans le format HTML5 Ogg ou WebM. Il s’agit de http://firefogg.org/.
Vous aurez besoin de Firefox 4 au moins. Cliquez sur le bouton rouge « Install Firefogg ». Vous devrez redémarrer votre Firefox, puis retournez sur http://firefogg.org/. Cette fois cliquez sur « Make web video » et suivez les instructions !

Trouver les paramètres dans une url en javascript [programmation]

[niveau débutant]

Voici une fonction courte pour récupérer l’ensemble des paramètres passés dans l’URL :

function getUrlVars(a,b,c,d){b=[];if(a=location.search.split('#')[0].match(/\?(.*)(#.*)?/)){d=a[1].split('&');for(i=d.length;i--;){c=d[i].split('=');b[i]=c[0];b[c[0]]=decodeURIComponent(c[1]).replace(/\+/g," ")}}return b}

var params=getUrlVars();
// si on a http://blog.kodono.info/?foo=bar&gniii=ok#something alors :
params[0]; // -> 'foo'
params[1]; // -> 'gniii'
params["foo"]; // -> 'bar'
params["gniii"]; // -> 'ok'

Comment cropper / rogner / recadrer un film [Vidéo]

[niveau débutant]

J’ai découvert qu’il était très simple d’enlever des bouts du cadrage d’un film grâce à AviDemux !

Il vous suffit d’ouvrir votre film avec AviDemux puis dans la colonne de gauche choisissez un format vidéo de sortie. On prendra par exemple xvid et ensuite cliquez sur « Filtres » :
Copie d'écran des boutons

Parmi les filtres disponibles double cliquez sur « Crop ». A partir de là vous pouvez définir la marge gauche, droite, haute et basse que vous voulez rogner :
Fenêtre de crop

Une fois terminé, cliquez sur « Fermer », puis dans la fenêtre principale du logiciel, dans la colonne de gauche, sélectionner le format audio voulu (par exemple mp3), et ainsi que le format de sortie (avi).
Si jamais, au moment où vous cliquez sur « Fermer » vous obtenez le message « Width is not a multiple of 8 » :
Message d'erreur
Pour régler ce problème il faut revenir à votre filtre « Crop ». Vous devez regarder la largeur de la vidéo que vous allez produire :
Exemple de largeur de vidéo
Dans cet exemple on a 478, or ce n’est pas un multiple de 8 (car 478/8=59.75). Il suffit donc de modifier légèrement la bordure droite ou gauche afin d’obtenir une taille multiple de huit. Si j’ai 480 alors ça sera bon (480/8=60).

Il ne vous reste plus qu’à enregistrer votre film et le tour est joué.

Un algorithme court et rapide pour Array.unique() [Javascript]

Voici un simple algorithme, et apparemment rapide, trouvé sur http://www.shamasis.net/2009/09/fast-algorithm-to-find-unique-items-in-javascript-array/ et que j’ai raccourci légèrement :

Array.prototype.unique=[].unique||function(){var o={},i,l=this.length,r=[];for(i=0;i<l;i++)o[this[i]]=this[i];for(i in o)r.push(o[i]);return r}

// or, if you don't want to override the Array prototype:
var ArrayUnique = function(arr){var o={},i,l=arr.length,r=[];for(i=0;i<l;i++)o[arr[i]]=arr[i];for(i in o)r.push(o[i]);return r}

// exemple
var tab=[1, 5, 2, 4, 1, 5, 4, 1, 5, 6, 8 ];
var tabUniq=tab.unique(); // [1, 5, 2, 4, 6, 8]

Alternatives à IMNRC() et ProcessImn() pour déterminer la présence d’une personne [Sharepoint]

Avec Sharepoint (2003 & 2007) on peut voir la présence (sur IM/MSN/Microsoft Messenger/Office Messenger/Microsoft Lynk) des utilisateurs directement dans une page Web grâce à des petites bulles de couleur. Quand on place le curseur au-dessus on aperçoit un popup avec diverses informations et possibilités. Cela n’est disponible que pour Internet Explorer puisqu’on utilise ici de l’ActiveX (et en particulier NameCtrl).

Je force le mode standard d’IE dans mon Sharepoint, et j’ai remarqué que les fonctions ProcessImn() et IMNRC() (l’une appelant l’autre) causaient un plantage d’IE avec un CPU qui montait en charge, et cela pour les pages qui contiennent un grand nombre de personnes et donc de bulles de présence (typiquement une liste AllItems avec des colonnes qui montrent cette information).
J’ai donc décidé de créer une alternative à l’affichage de la présence dans Sharepoint en utilisant mes propres fonctions.

Le résultat est le suivant :

/* on commence par supprimer les deux fonctions qui posent problèmes */
function IMNRC() {}
function ProcessImn() {}
var nameCtrl; // on s'assure de définir nameCtrl
var presence={}; // on crée un objet presence qui va nous servir à mettre en cache l'état de chaque individu
// Cette fonction fait apparaitre un menu/popup avec les informations de présence de l'utilisateur
function showIMMenu() {
  var el=window.event.srcElement;
  var pos=el.getBoundingClientRect();
  // on place le menu en question à la position pos.left et pos.top
  nameCtrl.ShowOOUI(el.getAttribute("sip"), 0, pos.left, pos.top);
}

// Cette fonction va cacher le menu
function hideIMMenu() { nameCtrl.HideOOUI() }

// Cette fonction va permettre de modifier la bulle avec celle qui correspond à l'état de présence de l'utilisateur
function ChangeIMPresence(sip, state, imgID) {
  if (presence[imgID] == state) return;
  presence[imgID]=state;
  var imgIM=document.getElementsByName(imgID);
  for (var i=0; i<imgIM.length; i++) {
    var alt="",src="imnhdr.gif"; // default image (grey)
    switch(state) {
      case 0: alt="Online"; src="imnon.png"; break;
      case 1: alt="Offline"; src="imnoff.png"; break;
      case 2: alt="Away"; src="imnaway.png"; break;
      case 4: alt="Be Right Back"; src="imnaway.png"; break;
      case 3: alt="Busy"; src="imnbusy.png"; break;
      case 5: alt="On the Phone"; src="imnbusy.png"; break;
      case 6: alt="Out to Lunch"; src="imnaway.png"; break;
      default: alt="Unknown"; src="imnhdr.gif";
    }
    imgIM[i].src="http://your_sharepoint/_layouts/images/"+src;
    imgIM[i].alt=alt;
  }
}

// Maintenant on initialise l'objet ActiveX
function initIMPresence() {
  if (typeof ActiveXObject == "function") { // seulement pour IE
    if (typeof nameCtrl=="undefined") nameCtrl=new ActiveXObject('Name.NameCtrl.1');
    var imgIM=document.getElementsByName("imnmark"); // toutes les bulles de présence ont le nom "imnmark"
    for (var i=0; i<imgIM.length; i++) {
      var sip=imgIM[i].getAttribute("sip"); // l'attribut "sip" retourne l'information que l'on souhaite
      if (sip!=null) {
        sip=sip.slice(4);
        var state=nameCtrl.GetStatus(sip, "imnmark_"+sip); // retourne la présence basée sur le SIP (adresse email)
        nameCtrl.OnStatusChange=ChangeIMPresence;
        imgIM[i].src="http://your_sharepoint/_layouts/images/imnhdr.gif";
        imgIM[i].alt="Unknown";
        imgIM[i].name+="_"+sip;
        imgIM[i].onclick=showIMMenu;
        imgIM[i].onmouseover=showIMMenu; // quand on passe la souris au-dessus d'une bulle on verra le menu
        imgIM[i].onmouseout=hideIMMenu;
      }
    }
  }
}

// et on lance tout ça !
initIMPresence();

En ajoutant ce bout de code à ma masterpage j’ai pu conserver les bulles de présence tout en évitant un plantage d’IE 🙂

DOMContentLoaded pour cross browser (multi-navigateurs) [Javascript]

Après avoir parcouru le Net pour un évènement DOMContentLoaded (qui s’enclenche avant window.onload) qui puisse fonctionner sur tous les navigateurs (et en particulier IE7), j’ai pu trouver un article qui explique bien les différentes étapes et la solution adaptée : http://javascript.info/tutorial/onload-ondomcontentloaded

Pour résumer la fonction à utiliser est :

function bindReady(handler){
  var called = false
  function ready() { 
    if (called) return
    called = true
    handler()
  }
  if ( document.addEventListener ) { // native event
    document.addEventListener( "DOMContentLoaded", ready, false )
  } else if ( document.attachEvent ) {  // IE
    try { var isFrame = window.frameElement != null } catch(e) {}

    // IE, the document is not inside a frame
    if ( document.documentElement.doScroll && !isFrame ) {
      function tryScroll(){
        if (called) return
        try {
          document.documentElement.doScroll("left")
          ready()
        } catch(e) {
          setTimeout(tryScroll, 10)
        }
      }
      tryScroll()
    }

    // IE, the document is inside a frame
    document.attachEvent("onreadystatechange", function(){
      if ( document.readyState === "complete" ) {
        ready()
      }
    })
  }

  // Old browsers
  if (window.addEventListener)
    window.addEventListener('load', ready, false)
  else if (window.attachEvent)
    window.attachEvent('onload', ready)
  else {
    var fn = window.onload // very old browser, copy old onload
    window.onload = function() { // replace by new onload and call the old one
      fn && fn()
      ready()
    }
  }
}

A partir de là vous pouvez appeler votre fonction avec :

bindReady(function() {
  /* quelque chose qui doit se lancer après le chargement du DOM */
});

Dans le cas où vous voudriez appeler plusieurs fonctions, alors vous pouvez simplement faire quelque chose comme :

var fonctionsACharger=[];
bindReady(function() {
  for (var i=0; i<functionsACharger.length;i++) functionsACharger[i]();
});

/* on ajoute les fonctions à charger dans notre tableau */
fonctionsACharger.push(function() {
  /* première chose à faire */
});
fonctionsACharger.push(function() {
  alert("Toutes les fonctions sont maintenant chargées !");
});