<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><atom:link rel="hub" href="http://tumblr.superfeedr.com/" xmlns:atom="http://www.w3.org/2005/Atom"/><description></description><title>margenn</title><generator>Tumblr (3.0; @margenn)</generator><link>http://blog.margenn.com/</link><item><title>PHP: Lightweight detector of mobile devices, OSs &amp; browsers</title><description>&lt;p&gt;On a recent project I needed a &lt;em&gt;way &lt;/em&gt;to know if a user is using a mobile device to visit the site I was building and, if so, I wanted a little more info about the device, OS and browser used.&lt;/p&gt;
&lt;p&gt;Before baking my own solution, I checked existing solutions such as the great &lt;a href="http://wurfl.sourceforge.net/" target="_blank"&gt;WURFL&lt;/a&gt;, well known framework’s own solutions like &lt;a href="http://codeigniter.com/user_guide/libraries/user_agent.html" target="_blank"&gt;CodeIgniter’s&lt;/a&gt; and other quite good scripts like &lt;a href="http://code.google.com/p/php-mobile-detect/" target="_blank"&gt;php-mobile-detect&lt;/a&gt;, &lt;a href="http://detectmobilebrowsers.mobi/" target="_blank"&gt;detectmobilebrowsers.mobi&lt;/a&gt; or &lt;a href="http://mobiforge.com/developing/story/lightweight-device-detection-php" target="_blank"&gt;mobiforge.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;None did what I needed in a simple and lightweight way:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WURFL&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Not straightforward to implement. &lt;/li&gt;
&lt;li&gt;Device database updates have to be done manually and periodically (at least twice a year) to stay up to date (Ughh). &lt;/li&gt;
&lt;li&gt;Heavy on the server (poor performance). &lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;CodeIgniter’s user agent class&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Works well for mobile detection but it does not tell you either the OS or browser used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;php-mobile-detect&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I like this one a lot but, unfortunately it doesn’t detect every browser/os/device I wanted, it has some bugs and I haven’t tested its regular expressions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;detectmobilebrowsers.mobi&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Very good script but, unfortunately it doesn’t detect every browser/os/device I wanted. However, I used it as a reference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mobiforge.com&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This script is more suited towards detecting old mobile devices (feature phones).&lt;/p&gt;
&lt;p&gt;So in the end &lt;strong&gt;I made my own script&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;All I wanted was a helper function that would execute once per page load and set some global variables.&lt;/p&gt;
&lt;p&gt;Those of you who prefer or need an OOP approach should modify the php-mobile-detect class to your needs.&lt;/p&gt;
&lt;p&gt;Here’s my function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;?php
/*
 * Lightweight detector of mobile devices, OSs &amp; browsers
 * Copyright 2012  Túbal Martín  (email: tubalmartin@gmail.com)
 * License: GPL2
 */

if ( ! function_exists('mobile_detector') )
{
    // Global vars
    $is_mobile = false;
    $is_iphone = $is_ipad = $is_kindle = false;
    $is_ios = $is_android = $is_webos = $is_palmos = $is_windows = $is_symbian = $is_bbos = $is_bada = false;
    $is_opera_mobile = $is_webkit_mobile = $is_firefox_mobile = $is_ie_mobile = $is_netfront = $is_uc_browser = false;


    function mobile_detector($debug = false)
    {
        global $is_mobile;

        // Check user agent string
        $agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

        if (empty($agent)) {
            return;
        }

        $mobile_devices = array(
            'is_iphone' =&gt; 'iphone',
            'is_ipad' =&gt; 'ipad',
            'is_kindle' =&gt; 'kindle'
        );
        
        $mobile_oss = array(
            'is_ios' =&gt; 'ip(hone|ad|od)',
            'is_android' =&gt; 'android',
            'is_webos' =&gt; '(web|hpw)os',
            'is_palmos' =&gt; 'palm(\s?os|source)',
            'is_windows' =&gt; 'windows (phone|ce)',
            'is_symbian' =&gt; 'symbian(\s?os|)|symbos',
            'is_bbos' =&gt; 'blackberry(.*?version\/\d+|\d+\/\d+)',
            'is_bada' =&gt; 'bada'
        );
        
        $mobile_browsers = array(
            'is_opera_mobile' =&gt; 'opera (mobi|mini)', // Opera Mobile or Mini
            'is_webkit_mobile' =&gt; '(android|nokia|webos|hpwos|blackberry).*?webkit|webkit.*?(mobile|kindle|bolt|skyfire|dolfin|iris)', // Webkit mobile
            'is_firefox_mobile' =&gt; 'fennec', // Firefox mobile
            'is_ie_mobile' =&gt; 'iemobile|windows ce', // IE mobile
            'is_netfront' =&gt; 'netfront|kindle|psp|blazer|jasmine', // Netfront
            'is_uc_browser' =&gt; 'ucweb' // UC browser
        );
        
        $groups = array($mobile_devices, $mobile_oss, $mobile_browsers);
        
        foreach ($groups as $group) {
            foreach ($group as $name =&gt; $regex) {
                if (preg_match('/'.$regex.'/i', $agent)) {
                    global $$name;
                    $is_mobile = $$name = true;
                    break;
                }
            }
        }
        
        // Fallbacks
        if ($is_mobile === false) {
            $regex = 'nokia|motorola|sony|ericsson|lge?(-|;|\/|\s)|htc|samsung|asus|mobile|phone|tablet|pocket|wap|wireless|up\.browser|up\.link|j2me|midp|cldc|kddi|mmp|obigo|novarra|teleca|openwave|uzardweb|pre\/|hiptop|avantgo|plucker|xiino|elaine|vodafone|sprint|o2';
            $accept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : '';

            if (false !== strpos($accept,'text/vnd.wap.wml')
                || false !== strpos($accept,'application/vnd.wap.xhtml+xml')
                || isset($_SERVER['HTTP_X_WAP_PROFILE'])
                || isset($_SERVER['HTTP_PROFILE'])
                || preg_match('/'.$regex.'/i', $agent)
            ) {
                $is_mobile = true;
            }
        }

        // DEBUGGER OUTPUT
        if ($debug === true) {
            echo '&lt;strong&gt;User Agent: '.$agent.'&lt;/strong&gt;&lt;br&gt;';
            foreach ($GLOBALS as $k =&gt; $v) {
                if (strpos($k, 'is_') !== false) {
                    echo '&lt;span style="color:'.($v ? 'green':'red').';"&gt;$'.$k.'&lt;/span&gt;&lt;br&gt;';
                }
            }
        }

    }

    // execute inmmediatly
    mobile_detector();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have created a &lt;a href="https://gist.github.com/1555271" target="_blank"&gt;Gist&lt;/a&gt; so you can download or fork the code.&lt;/p&gt;
&lt;p&gt;This function should only be called once per page load and preferably as soon as possible.&lt;/p&gt;
&lt;p&gt;This function adds the following variables to the global scope:&lt;/p&gt;
&lt;p&gt;Is it a mobile?&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;$is_mobile&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Any famous device?&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;$is_iphone (Apple iPhone)&lt;/li&gt;
&lt;li&gt;$is_ipad (Apple iPad)&lt;/li&gt;
&lt;li&gt;$is_kindle (Amazon Kindle)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;What mobile OS?&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;$is_android (Android OS)&lt;/li&gt;
&lt;li&gt;$is_bada (Bada OS)&lt;/li&gt;
&lt;li&gt;$is_bbos (Blackberry OS)&lt;/li&gt;
&lt;li&gt;$is_ios (Apple iOS)&lt;/li&gt;
&lt;li&gt;$is_palmos (Palm OS)&lt;/li&gt;
&lt;li&gt;$is_symbian (Symbian OS)&lt;/li&gt;
&lt;li&gt;$is_webos (Hp WebOS)&lt;/li&gt;
&lt;li&gt;$is_windows (Windows Phone OS and older)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;What mobile browser?&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;$is_firefox_mobile (Mozilla Fennec)&lt;/li&gt;
&lt;li&gt;$is_ie_mobile (IE)&lt;/li&gt;
&lt;li&gt;$is_netfront (NetFront)&lt;/li&gt;
&lt;li&gt;$is_opera_mobile (Opera Mobile or Mini)&lt;/li&gt;
&lt;li&gt;$is_uc_browser (UC Browser)&lt;/li&gt;
&lt;li&gt;$is_webkit_mobile (Webkit)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The initial value of these variables is &lt;em&gt;false&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You can test/debug the plugin results (debug mode) &lt;a href="http://www.margenn.com/tubal/mobile_detector/" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you need to debug the function, call it passing &lt;em&gt;true &lt;/em&gt;as a parameter and the function will output the results.&lt;/p&gt;
&lt;p&gt;If you use Wordpress to build websites and you’re reading this post let me tell you I already developed a &lt;a href="http://wordpress.org/extend/plugins/mobile-detector/" target="_blank"&gt;plugin for Wordpress&lt;/a&gt; based on this function.&lt;/p&gt;
&lt;p&gt;Share your thoughts!!&lt;/p&gt;</description><link>http://blog.margenn.com/post/15251102586</link><guid>http://blog.margenn.com/post/15251102586</guid><pubDate>Tue, 03 Jan 2012 20:42:00 +0100</pubDate><category>mobile</category><category>detector</category><category>device</category><category>php</category><category>browser</category><dc:creator>tubalmartin</dc:creator></item><item><title>Wordpress: YouTube shortcode plugin. Anniversary.</title><description>&lt;p&gt;Happy 2012 to everyone!!&lt;/p&gt;
&lt;p&gt;As some of you may already know, it’s been a year now since I published my first ever plugin for Wordpress: &lt;a href="http://wordpress.org/extend/plugins/youtube-shortcode/" target="_blank"&gt;Youtube shortcode&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;A year ago, version 1.0 was born and currently we’re at version 1.8.2. &lt;strong&gt;Much has been improved since its first release and that wouldn’t have been possible without the great community Wordpress has.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Version 1.0 was dead simple and lacked many features that today are present such as:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Fluid &amp; static videos (responsive design approach)&lt;/li&gt;
&lt;li&gt;Mobile &amp; RSS readers compatibility&lt;/li&gt;
&lt;li&gt;TinyMCE button&lt;/li&gt;
&lt;li&gt;Support for many more custom and official parameters&lt;/li&gt;
&lt;li&gt;Support for the AS2 video player&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;During the past year I also showed some love for good documentation and support creating a &lt;a href="http://www.margenn.com/tubal/youtube_shortcode/" target="_blank"&gt;website for the plugin’s documentation&lt;/a&gt;. The purpose behind its creation was to give users the possibility to see the plugin in action. One month after its release Google Analytics reports it’s doing quite well with 130 unique visitors each day.&lt;/p&gt;
&lt;p&gt;Wordpress.org gives both users and developers essential data about a plugin’s success such as the total number of downloads or the community rating and so far, people all over the world have &lt;strong&gt;downloaded&lt;/strong&gt; the plugin &lt;strong&gt;27.000 times&lt;/strong&gt; and &lt;strong&gt;10 people have given it a five stars rating&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;All in all it’s been an exciting and very encouraging year!! A big thank you!!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s coming this year?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This year, I plan to release version 1.9 somewhere between january and march along with a Pro version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 1.9&lt;/strong&gt; will give you the option to &lt;strong&gt;store default settings for every YouTube video&lt;/strong&gt; on your website but will also allow you to override any default setting on a per video basis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro version&lt;/strong&gt; will offer a new and exciting &lt;strong&gt;performance mode&lt;/strong&gt; that will allow your websites to load much much faster and its price will be quite affordable.&lt;/p&gt;
&lt;p&gt;Stay tuned!!&lt;/p&gt;</description><link>http://blog.margenn.com/post/15244851584</link><guid>http://blog.margenn.com/post/15244851584</guid><pubDate>Tue, 03 Jan 2012 18:01:16 +0100</pubDate><category>wordpress</category><category>youtube</category><category>plugin</category><category>shortcode</category><dc:creator>tubalmartin</dc:creator></item><item><title>Javascript: getElementsByClassName cross-browser function</title><description>&lt;p&gt;Recently, I needed a cross-browser implementation of the &lt;code&gt;&lt;a href="https://developer.mozilla.org/en/DOM/document.getElementsByClassName" target="_blank"&gt;getElementsByClassName&lt;/a&gt;&lt;/code&gt; Javascript DOM method. I couldn’t use a JS library like jQuery because code size had to be as small as possible.&lt;/p&gt;
&lt;p&gt;Fortunately, &lt;a href="http://robertnyman.com/" target="_blank"&gt;Robert Nyman&lt;/a&gt; developed back in 2008 &lt;a href="http://robertnyman.com/2008/05/27/the-ultimate-getelementsbyclassname-anno-2008/" target="_blank"&gt;a very good function&lt;/a&gt; to handle this issue.&lt;/p&gt;
&lt;p&gt;I have refactored the function definition logic, optimized code as much as possible, fixed some bugs and added the missing &lt;code&gt;&lt;a href="https://developer.mozilla.org/En/DOM/Document.querySelectorAll" target="_blank"&gt;querySelectorAll&lt;/a&gt;&lt;/code&gt; DOM method.&lt;/p&gt;
&lt;p&gt;I’ve written some &lt;a href="http://www.margenn.com/tubal/javascript/getelementsbyclassname.html" title="getElementsByClassName QUnit tests" target="_blank"&gt;QUnit tests&lt;/a&gt; if you want to test wether it works as it should or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;className &lt;/code&gt;One or several class names, separated by space. Multiple class names demands that each match have all of the classes specified. &lt;strong&gt;Mandatory&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tag &lt;/code&gt;Tag name of the elements to match. &lt;strong&gt;Optional&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;elm &lt;/code&gt;Reference to a DOM element to look amongst its children for matches. Recommended for better performance in larger documents. &lt;strong&gt;Optional&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ChangeLog:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;7 Dic 2011&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Added a refactored and bugfixed XPath method. Robert Nyman’s version did not add &lt;code&gt;normalize-space(@class)&lt;/code&gt; (space normalization) so his method wouldn’t return elements with a class attribute containing space characters other than whitespace (newlines for example). Also, namespace try/catch was removed from his version since we’re dealing with HTML documents and therefore it’s not necessary.&lt;/li&gt;
&lt;li&gt;BugFix: Some elements (nodes) may not have a &lt;code&gt;getElementsByClassName&lt;/code&gt; method, so we test for this. As a fallback &lt;code&gt;document&lt;/code&gt; will be used. See &lt;a href="http://code.google.com/p/getelementsbyclassname/issues/detail?id=4" target="_blank"&gt;this issue&lt;/a&gt; for more info.&lt;/li&gt;
&lt;li&gt;BugFix for IE: &lt;code&gt;Array.slice&lt;/code&gt; cannot be called on StaticNodeLists in IE. Instead, a regular loop is used to populate the array.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;So here it is my 2011 version of the &lt;code&gt;getElementsByClassName&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        
/*
 *  Refactored &amp; improved in 2011 by Tubal Martin (&lt;a href="http://www.margenn.com" target="_blank"&gt;http://www.margenn.com&lt;/a&gt;)
 *  Unit tests: &lt;a href="http://www.margenn.com/tubal/javascript/getelementsbyclassname.html" target="_blank"&gt;http://www.margenn.com/tubal/javascript/getelementsbyclassname.html&lt;/a&gt;
 *  Original version developed by Robert Nyman in 2008 &lt;a href="http://code.google.com/p/getelementsbyclassname/" target="_blank"&gt;http://code.google.com/p/getelementsbyclassname/&lt;/a&gt;
 */	
var getElementsByClassName = document.getElementsByClassName ?
    function (className, tag, elm) {
        var nodeElm = elm &amp;&amp; elm.getElementsByClassName ? elm : document,
            elements = nodeElm.getElementsByClassName(className),
            nodeName = tag ? new RegExp("\\b" + tag + "\\b", "i") : null,
            rElements = [],
            i = 0, l = elements.length,
            current;
        for (; i &lt; l; i++) {
            current = elements[i];
            if (!nodeName || nodeName.test(current.nodeName)) {
                rElements.push(current);
            }
        }
        return rElements;
    } : document.querySelectorAll ?
    function (className, tag, elm) {
        var elements = (elm || document).querySelectorAll((tag || "") + "." + className.split(" ").join(".")),
            rElements = [], 
            i = 0, l = elements.length;
        for (; i &lt; l; i++) {
            rElements.push(elements[i]);
        }
        return rElements;
    } : document.evaluate ?
    function (className, tag, elm) {
        var classes = className.split(" "),
            classesToCheck = "",
            rElements = [],
            i = 0, l = classes.length, 
            elements, node;
        for(; i &lt; l; i++){
            classesToCheck += "[contains(concat(' ', normalize-space(@class), ' '), ' " + classes[i] + " ')]";
        }
        elements = document.evaluate(".//" + (tag || "*") + classesToCheck, (elm || document), null, 0, null);
        while (node = elements.iterateNext()) {
            rElements.push(node);
        }
        return rElements;
    } :
    function (className, tag, elm) {
        tag = tag || "*";
        elm = elm || document;
        var classes = className.split(" "),
            classesToCheck = [],
            elements = tag == "*" &amp;&amp; elm.all ? elm.all : elm.getElementsByTagName(tag),
            rElements = [],
            i = j = 0, 
            il = classes.length, 
            jl = elements.length,
            current, match, k, kl;
        for (; i &lt; il; i++) {
            classesToCheck.push(new RegExp("(^|\\s)" + classes[i] + "(\\s|$)"));
        }
        kl = classesToCheck.length;
        for (; j &lt; jl; j++) {
            current = elements[j];
            match = false;
            for (k = 0; k &lt; kl; k++) {
                match = classesToCheck[k].test(current.className);
                if (!match) {
                    break;
                }
            }
            if (match) {
                rElements.push(current);
            }
        }
        return rElements;
    };&lt;/code&gt;&lt;/pre&gt;</description><link>http://blog.margenn.com/post/13835045360</link><guid>http://blog.margenn.com/post/13835045360</guid><pubDate>Tue, 06 Dec 2011 20:34:00 +0100</pubDate><category>cross-browser</category><category>dom</category><category>getElementsByClassName</category><category>javascript</category><category>class</category><category>xpath</category><category>evaluate</category><dc:creator>tubalmartin</dc:creator></item><item><title>Optimizar rendimiento de JetBrains PHPStorm (Java IDE)</title><description>&lt;p&gt;&lt;img src="http://static.tumblr.com/frwzkhm/3b2ltd43v/phpstorm_logo.png" align="right" alt="PHPStorm" width="320" height="72"/&gt;&lt;/p&gt;
&lt;p&gt;Todos los que utiliceis a diario IDEs basados en Java para programar sabreis que no son especialmente rápidos, especialmente con archivos grandes o cuando tenemos muchos archivos abiertos.&lt;/p&gt;
&lt;p&gt;Sin embargo hay algunas recomendaciones y trucos para que nuestro IDE basado en Java funcione más rápida y agilmente.&lt;/p&gt;
&lt;p&gt;En &lt;a title="margenn - web studio" target="_blank" href="http://www.margenn.com"&gt;margenn&lt;/a&gt;, después de haber probado opciones como Netbeans, Eclipse PDT o Aptana, elegimos utilizar &lt;a title="PHPStorm" target="_blank" href="http://www.google.es/url?sa=t&amp;rct=j&amp;q=phpstrom&amp;source=web&amp;cd=1&amp;ved=0CC0QFjAA&amp;url=http%3A%2F%2Fwww.jetbrains.com%2Fphpstorm%2F&amp;ei=aASgToG2KIef-QbP7ZSABQ&amp;usg=AFQjCNEVxyVeV1WJrmtrbI9AHz5XnVDUFw"&gt;PHPStorm&lt;/a&gt;, de JetBrains, y la verdad es que para nosotros no hay vuelta atrás, sin duda es el mejor. &lt;/p&gt;
&lt;p&gt;A continuación expongo mi guía de optimización de rendimiento de PHPStorm:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Deshabilita todos los plugins que no utilices en tu día a día&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; &lt;/strong&gt;En mi caso, programo con PHP, JS, HTML y CSS por lo que todo lo demás sobra para mi. Estos son los plugins que he deshabilitado (y no son pocos):&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;ASP&lt;/li&gt;
&lt;li&gt;CVS Integration (Usamos Subversion, Mercurial o Git)&lt;/li&gt;
&lt;li&gt;Database Support (Desarrollo en remoto - FTP)&lt;/li&gt;
&lt;li&gt;Java Server Pages Integration&lt;/li&gt;
&lt;li&gt;LESS support&lt;/li&gt;
&lt;li&gt;Perforce Integration&lt;/li&gt;
&lt;li&gt;Phing Support&lt;/li&gt;
&lt;li&gt;QuirksMode&lt;/li&gt;
&lt;li&gt;Refactor-X&lt;/li&gt;
&lt;li&gt;RELAX-NG Support&lt;/li&gt;
&lt;li&gt;SASS support&lt;/li&gt;
&lt;li&gt;SpellChecker&lt;/li&gt;
&lt;li&gt;SQL support&lt;/li&gt;
&lt;li&gt;W3C Validators&lt;/li&gt;
&lt;li&gt;XPathView + XSLT Support&lt;/li&gt;
&lt;li&gt;YAML&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;2. Desactiva las “inspections” que no consideres necesarias&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Especialmente en el caso de Javascript desactivar las inspecciones oportunas agiliza mucho el editor ya que cada vez que editas tu archivo, tiene que escanear todo de nuevo para entender cómo se relaciona el nuevo código que escribes con el existente.&lt;/p&gt;
&lt;p&gt;Estas son las que he deshabilitado para Javascript por diferentes motivos:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Unnecessary Semicolon&lt;/li&gt;
&lt;li&gt;Unresolved Function&lt;/li&gt;
&lt;li&gt;Unresolved Variable&lt;/li&gt;
&lt;li&gt;Mismatched Collection Query Update Inspection&lt;/li&gt;
&lt;li&gt;Potentially Invalid Constructor Usages&lt;/li&gt;
&lt;li&gt;Suspicious Name Combination Inspection&lt;/li&gt;
&lt;li&gt;Implicitly Internal Declaration&lt;/li&gt;
&lt;li&gt;Untyped Declaration&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Ahora cuando edito un archivo el editor no se ralentiza y la CPU no se pone al 100%.&lt;/p&gt;
&lt;p&gt;Deshabilitadas para HTML:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Form input without an associated label&lt;/li&gt;
&lt;li&gt;Image size mismatch&lt;/li&gt;
&lt;li&gt;Malformed content of &lt;script&gt; tag&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Como nota adicional también he deshabilitado todas las inspecciones de PHPDoc, Probably bug y XML.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Desactiva las “intentions” que no consideres necesarias&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Las intenciones también tienen su impacto en el rendimiento aunque en un grado menor. Desactiva todas las que no consideres útiles teniendo en cuenta tu estilo de código (formato).&lt;/p&gt;
&lt;p&gt;Si observais, de nuevo, el lenguaje que más intentions tiene es Javascript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Amplia la memoria disponible para la máquina virtual&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En margenn utilizamos exclusivamente MAC OS X (Windows en virtual para testear unicamente):&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Buscad la aplicación PHPStorm, click secundario y “Mostrar contenidos del paquete”.&lt;/li&gt;
&lt;li&gt;En el directorio “Contents” encontrareis el archivo “info.plist”, abridlo con TextEdit o XCode.&lt;/li&gt;
&lt;li&gt;Cambiad la configuración del parámetro VMOptions.i386 a la siguiente:&lt;br/&gt;-Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=128m&lt;/li&gt;
&lt;li&gt;Cambiad la configuración del parámetro VMOptions.x86_64 a la siguiente:&lt;br/&gt;-Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=128m -XX:+UseCompressedOops&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;En Windows y Linux la ruta y el nombre del archivo de configuración es diferente pero los parámetros son los mismos.&lt;/p&gt;
&lt;p&gt;A continuación explico qué significa cada parámetro:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;-Xms512m&lt;/strong&gt;: Tamaño de inicio de la memoria de tipo heap de la máquina virtual de Java a 512Mb. El valor por defecto que establece PHPStorm es de 128Mb. Si se aumenta este valor, se elimina el tiempo que se tardaría en aumentar el tamaño en memoria de la máquina virtual si se llegara el caso de que se necesitara más memoria, por lo que aumentaría el rendimiento en los casos que la aplicación haga uso intensivo de la memoria.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-Xmx1024m&lt;/strong&gt;: Tamaño máximo de la memoria de tipo heap de la máquina virtual de Java a 1024Mb (1 Giga).  El valor por defecto que establece PHPStorm son 768Mb. Si la aplicación supera el tamaño máximo de memoria que marca este parámetro, se lanza la excepción java.lang.OutOfMemoryError.  No conviene asignar a este parámetro el máximo de la memoria de nuestro ordenador porque si ya no queda memoria RAM disponible (por la que usa el sistema operativo u otras aplicaciones) se pueden producir escrituras en memoria asignada a otros programas y provocar un auténtico lío.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-XX:MaxPermSize=512m&lt;/strong&gt;: Tamaño máximo de la memoria de tipo PermGen (non-heap) a 512Mb. El valor por defecto de PHPStorm es 250Mb (i386) o 350Mb (x86_64). Si la aplicación supera el tamaño máximo de memoria para este tipo que marca este parámetro, se lanza la excepción java.lang.OutOfMemoryError: PermGen space. El valor necesario para este parámetro siempre suele ser menor que el de la memoria de tipo heap. Si se quiere especificar un valor distinto de 128Mb bastaría con sustituir el valor 128 del parámetro con el que se desee, siempre que sean múltiplos de 2 (64, 128, 256, 512, 768, 1024, 2048…)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-XX:ReservedCodeCacheSize=128m&lt;/strong&gt;: Tamaño de la memoria reservada para caché de código. El tamaño asignado por defecto de PHPStorm es 64m.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Podeis echar un vistazo a la &lt;a title="Java HotSpot VM Options" target="_blank" href="http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html"&gt;referencia completa de las opciones de máquina virtual de Java Hotspot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Después de seguir estos pasos, os aseguro que notareis que PHPStorm funciona más ligero y no se ralentizará apenas al editar archivos grandes.&lt;/p&gt;
&lt;p&gt;Hasta el próximo artículo!!&lt;/p&gt;</description><link>http://blog.margenn.com/post/11690442310</link><guid>http://blog.margenn.com/post/11690442310</guid><pubDate>Thu, 20 Oct 2011 13:24:00 +0200</pubDate><category>phpstorm</category><category>webstorm</category><category>jetbrains</category><category>performance</category><category>jidea</category><category>java</category><category>ide</category><category>php</category><dc:creator>tubalmartin</dc:creator></item><item><title>YUI 2.8 vs. Dojo 1.6 (I) - Calendario </title><description>&lt;p&gt;&lt;strong&gt;A little bit about me&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Me encanta &lt;a href="http://es.wikipedia.org/wiki/JavaScript" target="_blank"&gt;Javascript&lt;/a&gt;! Mi compañero y socio Jano es quien mejor conoce cuánto me gusta. Ya desde mis inicios en el desarrollo web allá por 2006 cuando empezaba la revolución AJAX vi el potencial de dicho lenguaje para crear aplicaciones web altamente interactivas e incluso &lt;em&gt;one-page-apps&lt;/em&gt; como &lt;a href="http://mail.google.com/" target="_blank"&gt;Gmail&lt;/a&gt;. Además de tener potencial, el lenguaje me gustaba y era divertido jugar con él modificando una página, actualizando parte del contenido “por arte de magia” sin que la página sufriera un refresco, etc…&lt;/p&gt;
&lt;p&gt;En dicha época (2005-2006) afortunadamente surgieron muchos proyectos cuyo objetivo era dotar a los desarrolladores de Javascript de herramientas que nos hicieran la vida más fácil a la hora de añadir interactividad y experiencia de usuario a una página o aplicación web.&lt;/p&gt;
&lt;p&gt;Cuando por primera vez conocí &lt;a href="http://jquery.com/" target="_blank"&gt;jQuery&lt;/a&gt;, me pareció horrible…no era el javascript que yo conocía…me parecía alienígena y en un primer momento lo dejé de lado.&lt;/p&gt;
&lt;p&gt;Un tiempo más tarde, jQuery no sólo ya no me parecía alienígena sino que lo consideraba fundamental para desarrollar un proyecto cross-browser y en una cuarta parte del tiempo.&lt;/p&gt;
&lt;p&gt;Con el tiempo surgieron muchos frameworks, más de cien…pero eran menos de diez los favoritos y realmente buenos. De estos diez, dos llamaron mi atención: &lt;a href="http://developer.yahoo.com/yui/" target="_blank"&gt;YUI&lt;/a&gt; y &lt;a href="http://dojotoolkit.org" target="_blank"&gt;Dojo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img align="right" src="http://media.tumblr.com/tumblr_liam424xqO1qdaz2v.png" width="260"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_liamvi1Syz1qdaz2v.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;YUI y Dojo eran entonces y son hoy mucho más que una librería para manipular el DOM y realizar llamadas asíncronas con facilidad. Ofrecían y siguen ofreciendo un parque temático de &lt;em&gt;widgets &lt;/em&gt;muy amplio y útil además de otras &lt;em&gt;niceties &lt;/em&gt;como gestión de dependencias o internacionalización. Son &lt;em&gt;Javascript frameworks &lt;/em&gt;con los que puedes construir una aplicación web de principio a fin sin utilizar librerías o plugins adicionales. No era ni es el caso de jQuery.&lt;/p&gt;
&lt;p&gt;No quiero que se me entienda mal, me encanta jQuery pero para lo que fue concebido, ser la navaja suiza del DOM y aún sigue siendolo.&lt;/p&gt;
&lt;p&gt;De modo que alucinado por lo que ofrecían tanto YUI como Dojo en aquel tiempo y la potencia de jQuery para manipular el DOM, tomé dos decisiones:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;jQuery era (y sigue siendo) la mejor herramienta para manipular el DOM, mientras que YUI y Dojo ofrecían soluciones más pobres de modo que jQuery tenía que estar en mi arsenal.&lt;/li&gt;
&lt;li&gt;YUI vs. Dojo: La decisión más difícil…probé ambos durante un tiempo y me convenció YUI por diferentes motivos:&lt;br/&gt;&lt;span&gt; &lt;/span&gt;- Yahoo! &lt;br/&gt;&lt;span&gt; &lt;/span&gt;- Mejor documentación y organización&lt;br/&gt;&lt;span&gt; &lt;/span&gt;- Comunidad mayor&lt;br/&gt;&lt;span&gt; &lt;/span&gt;- Muy estable&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Así que tenía dos herramientas sobre la mesa, jQuery y YUI y vi que ambas se complementaban. jQuery lo usaría para manipulación del DOM y gestión de los eventos y YUI para todo lo demás. &lt;em&gt;So far, so good&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;He pasado los últimos años utilizando esta solución mixta y con muy buenos resultados y experiencias pero desde el lanzamiento de Dojo 1.5 y la firme apuesta de Dojo por mejorar la documentación me he vuelto a replantear la pregunta ¿Qué herramienta(s) debo utilizar para el &lt;em&gt;front-end development&lt;/em&gt;? Dojo 1.5 y esa pregunta han producido que escriba esta serie de artículos.&lt;/p&gt;
&lt;p&gt;Como jQuery está para mi en otra liga distinta a la de YUI y Dojo, la cuestión es clara, ¿cuál elijo entre YUI y Dojo? y si elijo Dojo…¿podrá sustituir también a jQuery y convertirse en mi única herramienta? Si seguis esta serie conocereis mi decisión final.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bien…y ¿cómo voy a comparar YUI y Dojo?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;De la única manera posible, utilizando ambos &lt;em&gt;head to head&lt;/em&gt; valorando qué ofrece cada uno y cuál resulta más productivo para las mismas tareas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hey…y ¿qué pasa con YUI 3?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://developer.yahoo.com/yui/3/" target="_blank"&gt;YUI 3&lt;/a&gt; lamentablemente está aún hoy bastante verde (incompleto y &lt;em&gt;beta&lt;/em&gt; en su mayoría) como para ser comparado con YUI 2.8 o Dojo 1.6.&lt;/p&gt;
&lt;p&gt;Se que YUI 2.x puede ser utilizado con YUI 3 para cubrir las carencias de este último pero no considero este hecho relevante ya que YUI 3 no es más que un &lt;em&gt;rewrite&lt;/em&gt; completo de YUI 2, es decir, más de lo mismo pero con arquitectura, rendimiento y flexibilidad mejorados.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;El calendario&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En este primer artículo de la serie voy a comparar un widget que he utilizado bastante en los proyectos, el famoso calendario.&lt;/p&gt;
&lt;p&gt;Os pondré en contexto, el objetivo del test o demo es asociar un calendario a un campo de texto de un formulario en el que el usuario debe introducir una fecha, de tal forma que cuando el usuario vaya a introducir la fecha, se despliegue un calendario para hacerle más cómoda la selección.&lt;/p&gt;
&lt;p&gt;Sin más preámbulos echad un ojo al código fuente de la magnífica &lt;a href="http://margenn.com/tubal/yui_vs_dojo/form_calendar.html" target="_blank"&gt;demo&lt;/a&gt; que he creado  :) y juguetead con ambas implementaciones del calendario antes de continuar leyendo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notas del desarrollo con Dojo:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Con &lt;strong&gt;4 lineas de código&lt;/strong&gt; terminado. &lt;/li&gt;
&lt;li&gt;El &lt;strong&gt;resultado&lt;/strong&gt; es &lt;strong&gt;genial&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;HTML, CSS, comportamiento, localización (i18n), validación y advertencias visuales y localizadas en caso de fechas imposibles &lt;strong&gt;100% automatizado&lt;/strong&gt;s.&lt;/li&gt;
&lt;li&gt;He tardado &lt;strong&gt;2 minutos&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&lt;form&gt; 
    &lt;fieldset&gt; 
        &lt;legend&gt;Dojo calendar input&lt;/legend&gt; 
        &lt;label&gt;Select a date:&lt;/label&gt; 
        &lt;input type="text" name="dojocalendar" data-dojo-type="dijit.form.DateTextBox" data-dojo-props="name:'dojocalendar',type:'text'"&gt; 
        &lt;input type="submit" value="Send"&gt; 
    &lt;/fieldset&gt; 
&lt;/form&gt;          
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;dojo.require("dijit.form.DateTextBox");
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_lias7bLcGy1qa6pqe.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notas del desarrollo con YUI:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;He necesitado &lt;strong&gt;crear HTML adicional&lt;/strong&gt; (para poder mostrar el calendario) y asignar el atributo &lt;strong&gt;&lt;em&gt;autocomplete=”off”&lt;/em&gt;&lt;/strong&gt; al input para que el navegador no muestre un listado de fechas introducidas anteriormente por el usuario.&lt;/li&gt;
&lt;li&gt;He necesitado &lt;strong&gt;crear estilos CSS&lt;/strong&gt; adicionales.&lt;/li&gt;
&lt;li&gt;He necesitado &lt;strong&gt;implementar todos los eventos de interacción del usuario con el campo de texto&lt;/strong&gt; (&lt;em&gt;focus&lt;/em&gt; porque no hay un indicador visual de que existen valores seleccionables, &lt;em&gt;click&lt;/em&gt; para mostrar y ocultar el calendario y &lt;em&gt;keyup &lt;/em&gt;para actualizar el calendario si el usuario escribe o cambia la fecha manualmente) &lt;strong&gt;y el documento&lt;/strong&gt; (ocultar el calendario al hacer &lt;em&gt;click&lt;/em&gt; tanto fuera del campo de texto como del calendario) &lt;strong&gt;para igualar el comportamiento de Dojo&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;He necesitado&lt;strong&gt; localizar el calendario&lt;/strong&gt; a mi idioma traduciendo las cadenas de texto del mismo e indicando aspectos como qué día de la semana es el primero en aparecer en cada semana de un calendario (Lunes, no Domingo).&lt;/li&gt;
&lt;li&gt;He necesitado &lt;strong&gt;formatear la fecha&lt;/strong&gt; devuelta por el calendario de dos formas: una de un modo &lt;em&gt;humano &lt;/em&gt;y localizado para mostrarsela al usuario y otra en el formato literal &lt;em&gt;date&lt;/em&gt; en SQL (como Dojo).&lt;/li&gt;
&lt;li&gt;He necesitado &lt;strong&gt;bastantes más lineas de código&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;El resultado es bueno, pero no tan bueno como el de &lt;strong&gt;Dojo&lt;/strong&gt; ya que este &lt;strong&gt;incluso valida la fecha introducida por un usuario y si es una fecha imposible le alerta visual y descriptivamente además de en su localización&lt;/strong&gt;. Para igualar el mismo comportamiento en YUI necesitaría un icono, rutina de comprobación de fecha válida, el &lt;em&gt;widget&lt;/em&gt; para crear tooltips, el mensaje localizado a mostrar y más HTML y CSS.&lt;/li&gt;
&lt;li&gt;He necesitado &lt;strong&gt;más de 30 minutos&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&lt;form&gt; 
    &lt;fieldset&gt; 
        &lt;legend&gt;YUI calendar input&lt;/legend&gt; 
        &lt;label&gt;Select a date:&lt;/label&gt; 
        &lt;div class="yuicalendar-popup-wrapper"&gt; 
            &lt;input type="text" id="yuicalendarinput" autocomplete="off"&gt; 
            &lt;input type="hidden" id="yuicalendarhiddeninput" name="yuicalendar"&gt; 
            &lt;div id="yuiCalendarContainer"&gt;&lt;/div&gt; 
        &lt;/div&gt; 
        &lt;input type="submit" value="Send"&gt; 
    &lt;/fieldset&gt; 
&lt;/form&gt;  
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(function(y){
    var initYUI = function(){
        var yue = y.util.Event,
            yud = y.util.Dom,
            yuiCalContainer = yud.get('yuiCalendarContainer'),
            yuiCalInput = yud.get('yuicalendarinput'),
            yuiCalHiddenInput = yud.get('yuicalendarhiddeninput'),
            
            setDate = function(type, args, obj) {
                var date = args[0][0], 
                    year = date[0], 
                    month = date[1], 
                    day = date[2],
                    userDate = (day &lt; 10 ? "0" + day : day) + "/" + (month &lt; 10 ? "0" + month : month) + "/" + year;

                yuiCalInput.value = userDate;
                updateInputDate(userDate);
                yuiCalendar.hide();
            },
            
            getDateFields = function(userDate){
                return userDate.split("/");
            },
            
            updateInputDate = function(userDate){
                var dateFields = getDateFields(userDate);
                yuiCalHiddenInput.value = dateFields[2] + "-" + dateFields[1] + "-" + dateFields[0];
            },
            
            updateCalendar = function(e){
                var month, year, dateFields, userDate = this.value;

                if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(userDate)) {
                    dateFields = getDateFields(userDate);
                    month = dateFields[1];
                    year = dateFields[2];
                    updateInputDate(userDate);
                    yuiCalendar.cfg.setProperty("selected", userDate, false);
                    yuiCalendar.cfg.setProperty("pagedate", month + "/" + year, false);
                    yuiCalendar.render();
                }
            },
            
            yuiCalendar = new y.widget.Calendar("yuiCalendarContainer", { 
                locale_weekdays: "1char",
                navigator: {
                    strings : { 
                        month: "Selecciona un Mes", 
                        year: "Introduce un Año", 
                        submit: "Hecho", 
                        cancel: "Cancelar", 
                        invalidYear: "Por favor, introduce un año válido" 
                    }
                },
                start_weekday: 1,
                MDY_DAY_POSITION: 1,
                MDY_MONTH_POSITION: 2,
                MDY_YEAR_POSITION: 3,
                MONTHS_LONG: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
                WEEKDAYS_1CHAR: ["D", "L", "M", "M", "J", "V", "S"]
            });
         
        yuiCalendar.selectEvent.subscribe(setDate);
        yuiCalendar.render();
        
        yue.on("yuicalendarinput", "keyup", updateCalendar);
        
        yue.on("yuicalendarinput", "focusin", function(e) {
            this.className = "focused";
            yuiCalendar.show(); 
        });
        
        yue.on("yuicalendarinput", "click", function(e) {
            if (this.className == "focused") {
                this.className = "";
                return;
            }
            
            if (yuiCalContainer.style.display == "block") {
                yuiCalendar.hide();
            } else {
                yuiCalendar.show();
            }
        });
        
        yue.on(document, "click", function(e) {
            var el = yue.getTarget(e);

            if (el != yuiCalInput &amp;&amp; el != yuiCalContainer &amp;&amp; !yud.isAncestor(yuiCalContainer, el)) {
                yuiCalendar.hide();
            }
        });
    };
    
    (new y.util.YUILoader({
        require: ["calendar"],
        loadOptional: false,
        onSuccess: initYUI,
        timeout: 10000,
        combine: false
    })).insert();
}(YAHOO));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_lias3vhM8Q1qa6pqe.png"/&gt;&lt;/p&gt;
&lt;p&gt;Bueno, esas son mis notas de desarrollo…sin duda alguna Dojo ofrece muuuchas más ventajas sobre YUI en este escenario tan real y típico como la vida misma. Dojo cubre de forma automatizada y productiva todas las necesidades típicas en este escenario y por ello le doy un 10.&lt;/p&gt;
&lt;p&gt;Por último he comparado las opciones que nos da cada framework en cuanto a usos del calendario más allá del típico (flexibilidad):&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Con YUI podemos crear grupos de calendarios (&lt;a href="http://developer.yahoo.com/yui/examples/calendar/calgrp.html" target="_blank"&gt;Multi-Page&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Con YUI podemos seleccionar varias fechas en un mismo calendario (&lt;a href="http://developer.yahoo.com/yui/examples/calendar/multi.html" target="_blank"&gt;Multi-Select&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Todo lo demás puede hacerse con ambos frameworks de un modo u otro.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Tanto el Multi-Page como el Multi-Select de YUI me parecen de bastante poca utilidad la verdad. No he encontrado hasta la fecha un escenario donde haya necesitado dichas soluciones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusión&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;YUI &lt;strong&gt;0 - 1&lt;/strong&gt; Dojo&lt;/p&gt;
&lt;p&gt;Dojo ha demostrado ser mejor en todos los aspectos en esta comparación, dejando por los suelos a YUI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Y vosotros qué opinais? ¿Con cuál os quedariais?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Espero que os haya gustado mi análisis y la demo, en el próximo combate entre YUI y Dojo analizaré otro widget, el &lt;strong&gt;autocomplete&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stay tuned!!&lt;/em&gt;&lt;/p&gt;</description><link>http://blog.margenn.com/post/3956133042</link><guid>http://blog.margenn.com/post/3956133042</guid><pubDate>Sat, 19 Mar 2011 08:06:00 +0100</pubDate><category>yui</category><category>dojo</category><category>calendar</category><category>calendario</category><category>javascript</category><category>framework</category><category>widget</category><dc:creator>tubalmartin</dc:creator></item><item><title>Wordpress: YouTube shortcode plugin</title><description>&lt;p&gt;&lt;img align="right" src="http://media.tumblr.com/tumblr_lez7dpOjmO1qdaz2v.png"/&gt;&lt;/p&gt;
&lt;p&gt;Durante el desarrollo de un proyecto basado en Wordpress 3 observé resignado la necesidad de crear mi propio plugin para insertar (&lt;em&gt;embed&lt;/em&gt;) videos de Youtube en cualquier parte del sitio web.&lt;/p&gt;
&lt;p&gt;Muchos pensareis que hay varios plugins que sirven para este propósito, pero lo cierto es que ninguno cumplía todos los requisitos:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Poder mostrar en una misma página más de &lt;em&gt;n&lt;/em&gt; videos de Youtube cada uno con una configuración del reproductor diferente y utilizando &lt;em&gt;&lt;a target="_blank" href="http://codex.wordpress.org/Shortcode_API"&gt;shortcodes&lt;/a&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Soporte de todos los parámetros oficiales del reproductor de Youtube.&lt;/li&gt;
&lt;li&gt;Interfaz del reproductor de video de Youtube automáticamente localizada en el idioma del usuario.&lt;/li&gt;
&lt;li&gt;Soporte de Flash y HTML5.&lt;/li&gt;
&lt;li&gt;Detección automática de navegadores web que no implementen Adobe Flash y en ese caso, utilización del &lt;a target="_blank" href="http://apiblog.youtube.com/2010/07/new-way-to-embed-youtube-videos.html"&gt;nuevo código de Youtube&lt;/a&gt; para estos (iPhone, iPad y otros dispositivos no sólo de Apple).&lt;/li&gt;
&lt;li&gt;Soporte de navegadores que no interpretan Javascript (Flash necesario para reproducir el video).&lt;/li&gt;
&lt;li&gt;Cálculo automático de la altura del reproductor de video de Youtube.&lt;/li&gt;
&lt;li&gt;Poder utilizar cualquier identificador de un video de Youtube.&lt;/li&gt;
&lt;li&gt;Sin interfaz de admin.&lt;/li&gt;
&lt;li&gt;Lo más ligero posible.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;De modo que creé hace un par de días mi primer plugin para Wordpress, que cumple con los requisitos expuestos, y al que he bautizado &lt;em&gt;&lt;a target="_blank" href="http://wordpress.org/extend/plugins/youtube-shortcode/"&gt;Youtube shortcode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Espero que os sea de utilidad ;)&lt;/p&gt;</description><link>http://blog.margenn.com/post/2731934120</link><guid>http://blog.margenn.com/post/2731934120</guid><pubDate>Thu, 13 Jan 2011 20:28:00 +0100</pubDate><category>wp</category><category>wordpress</category><category>plugin</category><category>youtube</category><category>shortcode</category><category>video</category><category>embed</category><dc:creator>tubalmartin</dc:creator></item><item><title>Función "slice" de Javascript para PHP</title><description>&lt;p&gt;Recientemente me embarqué en un proyecto personal (que daré a conocer dentro de poco) que implicaba reescribir en PHP código escrito previamente en Javascript.&lt;/p&gt;
&lt;p&gt;Al poco de empezar la tarea pude observar que el script dependía de la &lt;a href="https://developer.mozilla.org/es/Referencia_de_JavaScript_1.5/Objetos_globales/String/slice" target="_blank"&gt;función &lt;em&gt;slice&lt;/em&gt; de Javascript&lt;/a&gt;. Miré en la documentación de PHP y ninguna función hacía lo mismo. Consulté las funciones para los &lt;em&gt;arrays&lt;/em&gt;…y tampoco ninguna me servía. Finalmente busqué en Google y sólo encontré gente preguntando en foros si existía dicha función en PHP o algún &lt;em&gt;port&lt;/em&gt; pero ninguna solución.&lt;/p&gt;
&lt;p&gt;Así que me puse manos a la obra, al cabo de un rato escribí los tests y ¡Tachán! os presento la implementación en PHP de la función &lt;em&gt;slice&lt;/em&gt; de Javascript para &lt;em&gt;strings &lt;/em&gt;exclusivamente ya que el proyecto no requería tratar con &lt;em&gt;arrays&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;El comportamiento es idéntico a su “counterpart” en Javascript, de modo que si alguien necesita cambiar qué devuelve la función en caso de una excepción (“” string vacío) tendrá que modificar el código y crear sus tests.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** 
* PHP port of Javascript's "slice" function
* Author: Tubal Martin &lt;a href="http://margenn.com" target="_blank"&gt;http://margenn.com&lt;/a&gt;
* Tests: &lt;a href="http://margenn.com/tubal/str_slice/" target="_blank"&gt;http://margenn.com/tubal/str_slice/&lt;/a&gt;
*
* @param string $str
* @param int    $start index
* @param int    $end index (optional)
*/
function str_slice($str, $start, $end = FALSE)
{
    if ($start &lt; 0 || $end &lt;= 0) {
        
        if ($end === FALSE) {
            $slice = substr($str, $start);
            return ($slice === FALSE) ? '' : $slice;
        }
        
        $max = strlen($str);
        
        if ($start &lt; 0) {
            if (($start = $max + $start) &lt; 0) {
                return '';
            }
        }
    
        if ($end &lt; 0) {
            if (($end = $max + $end) &lt; 0) {
                return '';
            }
        }
        
        if ($end &lt;= $start) {
            return '';
        }
    }   
    
    $slice = substr($str, $start, $end - $start);
    return ($slice === FALSE) ? '' : $slice;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Podéis ejecutar los tests visitando la URL indicada en el comentario situado en la cabecera de la función.&lt;/p&gt;
&lt;p&gt;La función está optimizada para un rendimiento óptimo.&lt;/p&gt;
&lt;p&gt;Espero que os sirva de utilidad ;)&lt;/p&gt;</description><link>http://blog.margenn.com/post/2327848788</link><guid>http://blog.margenn.com/post/2327848788</guid><pubDate>Wed, 15 Dec 2010 22:20:00 +0100</pubDate><category>slice</category><category>javascript</category><category>php</category><category>port</category><category>function</category><dc:creator>tubalmartin</dc:creator></item><item><title>OAuth 1.0a: Introducción e implementación utilizando PHP, PECL OAuth y Twitter</title><description>&lt;p&gt; Como yo, somos muchos los que hemos tenido que aprender a utilizar este “nuevo” protocolo llamado &lt;a href="http://oauth.net/" target="_blank"&gt;OAuth&lt;/a&gt; para poder interactuar con la creciente lista de servicios y aplicaciones web que lo implementan. Algunos de los más conocidos que implementan la versión 1.0a del protocolo son Twitter, Youtube o LinkedIn.&lt;img align="right" height="250" src="http://media.tumblr.com/tumblr_lh1c1yJFB71qdaz2v.png" width="250"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Qué es OAuth?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Como bien resume Google, &lt;em&gt;el protocolo OAuth proporciona una manera estándar de acceder a los datos protegidos de diferentes sitios web&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;O dicho de otro modo, OAuth es un protocolo abierto y estándar que permite a un sitio web A acceder de un modo seguro, previa autorización del usuario, a datos de acceso restringido de dicho usuario albergados en otro sitio web B mediante una API que  soporta OAuth y que B pone a disposición de A.&lt;/p&gt;
&lt;p&gt;El aspecto más importante en cuanto a la seguridad que ofrece OAuth es que el sitio web A (&lt;em&gt;OAuth Consumer&lt;/em&gt;) no almacena los credenciales de acceso (usuario y contraseña) que el usuario utiliza en su cuenta en el sitio web B (&lt;em&gt;Service Provider).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Usuarios, OAuth Consumer y OAuth Service Provider&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Para poder hablar de OAuth, se necesitan tres partes, un servidor o proveedor de servicios, usuarios y un consumidor:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;em&gt;OAuth Service Provider&lt;/em&gt; o proveedor de servicios OAuth: Sitios o servicios web que contienen información de usuarios cuyo acceso es restringido. Algunos de los más conocidos son Facebook, Twitter o Youtube. Estos proveedores ponen a disposición de los desarrolladores una API que soporta el protocolo de autenticación OAuth.&lt;/li&gt;
&lt;li&gt;Usuarios: Sin los usuarios, no existiría OAuth. Por usuario se entiende cualquier persona que tiene una cuenta de usuario en un &lt;em&gt;Service Provider&lt;/em&gt;.&lt;em&gt; &lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;OAuth Consumer&lt;/em&gt; o consumidor OAuth: Cualquier sitio o aplicación web, móvil o de escritorio que solicita permiso a un usuario para acceder a sus datos de acceso restringido que alberga un &lt;em&gt;Service Provider&lt;/em&gt;. El usuario puede autorizar o denegar el acceso del &lt;em&gt;consumer&lt;/em&gt; a sus datos. Es necesario que el &lt;em&gt;consumer&lt;/em&gt; soporte el protocolo HTTP y que utilice la versión de OAuth que el &lt;em&gt;Service Provider &lt;/em&gt;haya implementado&lt;em&gt;.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;API Key y Callback URL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cada &lt;em&gt;OAuth Service Provider&lt;/em&gt; os proporcionará un API Key (un &lt;em&gt;string&lt;/em&gt; de letras y numeros) para identificar que las peticiones que recibe mediante su API vienen de un &lt;em&gt;OAuth Consumer&lt;/em&gt; autorizado, es decir, vuestra aplicación.&lt;/p&gt;
&lt;p&gt;A su vez, cada &lt;em&gt;OAuth Service Provider&lt;/em&gt; os pedirá que indiquéis un &lt;em&gt;Callback URL&lt;/em&gt;, es decir, una dirección URL que apunte a un archivo de vuestra aplicación el cual se encargará de procesar la respuesta de autorización (o desautorización) de acceso a los datos de la cuenta del Usuario en el &lt;em&gt;OAuth Service Provider.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Diagramas de flujo de un OAuth Consumer en OAuth 1.0a&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Echad un vistazo a los diagramas de flujo OAuth 1.0a de &lt;a href="http://a0.twimg.com/images/dev/oauth_diagram.png" target="_blank"&gt;Twitter&lt;/a&gt; y &lt;a href="http://code.google.com/intl/es-ES/apis/youtube/2.0/developers_guide_protocol_oauth.html#youtubeDataAPIOAuthProcessFlow" target="_blank"&gt;Youtube&lt;/a&gt; para haceros una idea general del proceso de autenticación (el flujo es idéntico pero explicado de diferente modo). Merece la pena.&lt;/p&gt;
&lt;p&gt;En ambos diagramas se explica el flujo de un &lt;em&gt;OAuth Consumer&lt;/em&gt;, sin embargo, en el diagrama de Youtube se incluye también al Usuario como parte del proceso y se explica punto por punto qué está ocurriendo, de modo que es el más ilustrativo.&lt;/p&gt;
&lt;p&gt;Podemos resumir que el flujo en la autenticación mediante OAuth 1.0a es el siguiente:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Obtenemos un &lt;em&gt;Request Token&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Solicitamos la autorización del &lt;em&gt;Usuario&lt;/em&gt; (para acceder a los datos de su cuenta) enviandole a una página especial de login del &lt;em&gt;Service Provider.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Cambiamos el &lt;em&gt;Request Token&lt;/em&gt; por un &lt;em&gt;Access Token&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Guardamos el &lt;em&gt;Access Token&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Librerías OAuth para PHP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implementar OAuth sin usar una librería o extensión de tu lenguaje es una locura y no se lo recomiendo a nadie. En la página oficial de OAuth existe una &lt;a href="http://oauth.net/code/" target="_blank"&gt;lista&lt;/a&gt; de las librerías y extensiones que existen para cada lenguaje, de modo que visitadla.&lt;/p&gt;
&lt;p&gt;En &lt;a href="http://www.margenn.com" target="_blank"&gt;margenn&lt;/a&gt; utilizamos el lenguaje PHP. De todas las librerías OAuth 1.0a que existen para este lenguaje os recomiendo alguna de estas tres opciones:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/oauth-php/" target="_blank"&gt;Librería oauth-php&lt;/a&gt;. Muy utilizada. Open source.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://framework.zend.com/manual/en/zend.oauth.html" target="_blank"&gt;Librería de Zend Framework&lt;/a&gt;. Podeis abstraerla del framework de Zend con un poco de trabajo.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://pecl.php.net/package/oauth" target="_blank"&gt;Extensión PECL&lt;/a&gt; para PHP. La mejor opción de las tres ya que es la extensión oficial para PHP y es nativa. Podéis encontrar en el manual de PHP su &lt;a href="http://es2.php.net/manual/es/book.oauth.php" target="_blank"&gt;documentación&lt;/a&gt;. El único inconveniente es que muy pocos proveedores de alojamiento la tienen instalada (en el caso de alojamiento compartido) por lo que la opción más utilizada es la librería oauth-php. &lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Todas estas librerías soportan exclusivamente la versión 1.0a del protocolo OAuth. Para la versión 2.0 (working draft, utilizada por &lt;a href="http://developers.facebook.com/docs/authentication/" target="_blank"&gt;Facebook&lt;/a&gt;) del protocolo no existe ninguna librería por ahora (aunque no es necesaria ya que es muy sencilla su implementación).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implementación utilizando PHP, PECL OAuth y Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En este artículo, explicaré cómo implementar el proceso de autenticación mediante OAuth 1.0a del lado de un &lt;em&gt;OAuth consumer.&lt;/em&gt; En este caso, la aplicación web que construiremos se conectará con Twitter para leer los &lt;em&gt;status&lt;/em&gt; de &lt;a href="http://twitter.com/margennstudio" target="_blank"&gt;nuestra cuenta en Twitter&lt;/a&gt; y utilizaremos la extensión PECL OAuth de PHP comentada anteriormente ya que en nuestro servidor sí la tenemos instalada y hay muy pocos ejemplos de su uso.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://es.php.net/manual/es/oauth.requirements.php" target="_blank"&gt;Requisitos&lt;/a&gt; de la extensión PECL OAuth de PHP:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;PHP 5.1 o superior&lt;/li&gt;
&lt;li&gt;ext/hash &lt;/li&gt;
&lt;li&gt;libcurl con soporte HTTPS (CURL).&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;En primer lugar, necesitamos registrar nuestra aplicación en Twitter. Para ello visitamos la página de &lt;a href="http://dev.twitter.com/apps/new" target="_blank"&gt;registro de aplicaciones&lt;/a&gt; y rellenamos toda la información poniendo especial atención en el campo “Callback URL”.&lt;/p&gt;
&lt;p&gt;Una vez registrada, Twitter nos dará nuestro API key y nos indicará las URLs que tendremos que utilizar en el proceso de autenticación OAuth, a saber:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;URL para solicitar un &lt;em&gt;Request Token&lt;/em&gt;: &lt;em&gt;&lt;a href="http://api.twitter.com/oauth/request_token" target="_blank"&gt;http://api.twitter.com/oauth/request_token&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;URL para solicitar autorización del &lt;em&gt;Usuario&lt;/em&gt;: &lt;em&gt;&lt;a href="https://api.twitter.com/oauth/authorize" target="_blank"&gt;https://api.twitter.com/oauth/authorize&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;URL para cambiar el &lt;em&gt;Request Token &lt;/em&gt;por un&lt;em&gt; Access Token&lt;/em&gt;: &lt;em&gt;&lt;a href="https://api.twitter.com/oauth/access_token" target="_blank"&gt;https://api.twitter.com/oauth/access_token&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Nota: Todos los Service Providers nos facilitan en su documentación para desarrolladores toda la información que necesitamos. En el caso de Twitter podeis encontrar toda la información &lt;a href="http://dev.twitter.com/pages/auth" target="_blank"&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Comencemos…&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En &lt;a href="http://www.margenn.com" target="_blank"&gt;margenn&lt;/a&gt; utilizamos el framework &lt;a href="http://codeigniter.com" target="_blank"&gt;CodeIgniter&lt;/a&gt; pero en este artículo voy a abstraer el código del framework.&lt;/p&gt;
&lt;p&gt;Mostraré el código de cada archivo. Como he comentado suficientemente (en mi opinión) el código en cada archivo, creo que no son necesarias explicaciones adicionales.&lt;/p&gt;
&lt;p&gt;Así mismo, os recomiendo que copies y peguéis el código de cada archivo en vuestro editor de texto favorito ya que podréis leerlo mucho mejor.&lt;/p&gt;
&lt;p&gt;He creado 4 archivos:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;index.php&lt;/strong&gt;: La vista inicial &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;controller.php&lt;/strong&gt;: Recibe las peticiones de la vista y actúa en consecuencia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;callback.php&lt;/strong&gt;: Archivo que recibirá la respuesta de Twitter a la autorización OAuth. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;class.twitter.php&lt;/strong&gt;: Clase para la API de Twitter que implementa la autenticación OAuth.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Contenido de &lt;strong&gt;index.php&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;?php session_start(); ?&gt;

&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;Twitter OAuth&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div&gt;
        &lt;p&gt;Esta aplicación (OAuth Consumer) se conectará a tu cuenta en Twitter utilizando el protocolo de autenticación OAuth 1.0a y mostrará tus últimos 10 "status".&lt;/p&gt;
	&lt;p&gt;&lt;a href="controller.php?q=latest_statuses"&gt;Muéstrame mis 10 últimos "status" en Twitter&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;	
&lt;/body&gt;
&lt;/html&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Contenido de &lt;strong&gt;controller.php&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;?php

session_start();

// Guardamos en la sesión la URL de la página que el usuario quería visitar
$_SESSION['previous_uri'] = 'http://margenn.com' . $_SERVER['REQUEST_URI'];

// Incluimos la clase Twitter
require_once 'class.twitter.php';

// Esta función muestra por pantalla de un modo legible tanto arrays como strings json
function console($data)
{
    header("Content-type: text/html; charset=UTF-8");
    $format = (is_array($data)) ? 'array' : 'json';
    highlight_string('&lt;?php ' . print_r(($format === 'json' ? json_decode($data, TRUE) : $data), TRUE) . ' ?&gt;');
    exit;
}

// Lógica del controlador
if (! empty($_GET))
{
    $TWITTER = new Twitter();
    $query   = $_GET['q'];
    
    switch($query)
    {
        case 'latest_statuses':
            console($TWITTER-&gt;get_latest_statuses());
            break;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Contenido de la clase &lt;strong&gt;class.twitter.php&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;?php

// OAuth 1.0a &lt;a href="http://dev.twitter.com/auth#at-twitter" target="_blank"&gt;http://dev.twitter.com/auth#at-twitter&lt;/a&gt;

class Twitter {

    // Flag for callback controller
    public $oauth_access_granted = FALSE;
    // Twitter
    const URL                    = 'http://twitter.com/';
    const API_URL                = 'http://api.twitter.com/';
    const VERSION                = '1';
    const FORMAT                 = '.json'; // JSON como formato de respuesta
    // OAuth
    private $oauth;
    private $consumer_key        = 'XXXXXXXXXXXXXXXXX'; // === API Key
    private $consumer_secret     = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
    private $request_token_url   = 'https://api.twitter.com/oauth/request_token';
    private $access_token_url    = 'https://api.twitter.com/oauth/access_token';
    private $auth_url            = 'https://api.twitter.com/oauth/authorize';

    
    
    function __construct()
    {
        // Al inicializar la clase, el constructor intenta autentificar al usuario mediante OAuth.
        $this-&gt;authenticate_user();
    }



    // OAUTH
    function authenticate_user()
    {
        $this-&gt;oauth = new OAuth($this-&gt;consumer_key, $this-&gt;consumer_secret, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_AUTHORIZATION);
		
        // Si no existe en la sesión el Access Token, lo obtenemos de la BBDD si ya lo teniamos guardado o, 
        //si no lo tenemos guardado en la BBDD (primera vez que el usuario se autentifica mediante OAuth), lo solicitamos a Twitter.

        if (! isset($_SESSION['twitter_access_token']))
        { 
            // Pedimos a la BBDD el Access Token y el Access Token Secret del usuario para Twitter
        	
            // $twitter_access_token = ...;
            // $twitter_access_token_secret = ...;
            
            // Si existen en la BBDD, almacenamos en la sesión sus valores:
            
            // $_SESSION['twitter_access_token'] = $twitter_access_token;
            // $_SESSION['twitter_access_token_secret'] = $twitter_access_token_secret;
            
            // Si no existen en la BBDD quiere decir que el usuario no ha autorizado el acceso a nuestra aplicación o
            // que es la primera vez que se conecta a Twitter utilizando nuestra aplicación y por tanto, iniciamos el 
            // proceso de autorización, necesitamos el Access Token:
            
            if ($this-&gt;get_access_token() === TRUE)
            {
                // Indicamos que hemos obtenido el Access Token satisfactoriamente
                $this-&gt;oauth_access_granted = TRUE;
            }    
        }
        
        $this-&gt;oauth-&gt;setToken($_SESSION['twitter_access_token'], $_SESSION['twitter_access_token_secret']);
    }



    function get_access_token()
    {
        // Si no existe el parámetro "oauth_token" en la URL quiere decir que aún no tenemos el Request Token
        // con la autorización del usuario para poder cambiarlo por un Access Token de modo que debemos solicitar primero el Request Token
        
        if (! isset($_GET['oauth_token'])) 
        {        
            $this-&gt;get_request_token();  
        }
        else 
        {
            $this-&gt;oauth-&gt;setToken($_SESSION['twitter_request_token'], $_SESSION['twitter_request_token_secret']);

            // Cambiamos el Request Token por un Access Token           
            $twitter_response = $this-&gt;oauth-&gt;getAccessToken($this-&gt;access_token_url); // CURL
            
            // Guardamos Access Token y Access Token Secret en la BBDD:
            // Access Token        -&gt; $twitter_response['oauth_token']
            // Access Token Secret -&gt; $twitter_response['oauth_token_secret']
            
            // Guardamos en sesion Access Token y Access Token Secret
            $_SESSION['twitter_access_token']        = $twitter_response['oauth_token'];
            $_SESSION['twitter_access_token_secret'] = $twitter_response['oauth_token_secret'];

            // Informamos que la obtención del Access Token ha sido exitosa
            return TRUE;
        }
    }
    
    
    
    function get_request_token()
    {    	
        $twitter_response = $this-&gt;oauth-&gt;getRequestToken($this-&gt;request_token_url, 'URL/absoluta/al/archivo/callback.php'); // CURL
            
        // Guardamos en sesion el Request Token y el Request Token Secret
        $_SESSION['twitter_request_token']        = $twitter_response['oauth_token'];
        $_SESSION['twitter_request_token_secret'] = $twitter_response['oauth_token_secret'];
		
        // Solicitamos la autorización al usuario para que nuestra aplicación pueda acceder a Twitter en su nombre	
        $this-&gt;get_authorization(); 
    }
    
    
    
    function get_authorization()
    {
        // Redirigimos al usuario a la página de autorización (login OAuth) del Service Provider (Twitter) 
        // incluyendo en la URL el Request Token obtenido. Una vez que el usuario autorice o no a nuestra aplicación (Oauth Consumer),
        // Twitter enviará la respuesta via GET al archivo callback.php (Callback URL) 

        header('Location: ' . $this-&gt;auth_url . '?oauth_token=' . $_SESSION['twitter_request_token']);
    }


    // Metodo "wrapper" responsable de realizar peticiones a Twitter mediante la clase OAuth de PHP (que usa CURL) 
    function fetch($url, $data = array(), $method = OAUTH_HTTP_METHOD_GET)
    {
        $this-&gt;oauth-&gt;fetch($url, $data, $method);
        
        // Tomar medidas en caso de respuestas erroneas
        
        // Devolvemos la respuesta 
        return $this-&gt;oauth-&gt;getLastResponse();   
    }

    
    // Método que solicita los últimos 10 statuses en Twitter
    function get_latest_statuses()
    {
        return $this-&gt;fetch(self::API_URL . self::VERSION . '/statuses/user_timeline' . self::FORMAT . '?count=10');
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Contenido del archivo &lt;strong&gt;callback.php&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;?php

session_start();


if (! empty($_GET))
{
    require_once 'class.twitter.php';
	
    // En este momento, si el usuario ha autorizado el acceso a nuestra aplicación, podremos cambiar el Request Token
    // que teniamos por un Access Token y si conseguimos el Access Token, la propiedad "oauth_access_granted" de la clase 
    // tendrá como valor el buleano TRUE. 
	
    $TWITTER = new Twitter();
	
    // Si el usuario ha autorizado el acceso a Twitter de nuestra aplicación, redirigimos al usuario a la pagina
    // en la que mostramos el contenido que esperaba. Si no ha autorizado el acceso, le informamos
	
    if ($TWITTER-&gt;oauth_access_granted === TRUE)
    {
        header('Location: ' . $_SESSION['previous_uri']);
    }
    else
    {
        die('&lt;h1&gt;Has impedido que LA APLICACIÓN acceda a tu cuenta en Twitter!!!&lt;/h1&gt;');
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Espero que este ejemplo de implementación os sirva de ayuda. Así mismo, pongo a vuestra disposición una &lt;a href="http://margenn.com/tubal/oauth" target="_blank"&gt;demo en vivo&lt;/a&gt; que utiliza el código que os he mostrado.&lt;/p&gt;
&lt;p&gt;Cualquier duda sobre el proceso o el código, sugerencias e inquietudes, comentadlas ;)&lt;/p&gt;
&lt;p&gt;Hasta el próximo artículo!!&lt;/p&gt;</description><link>http://blog.margenn.com/post/2135398085</link><guid>http://blog.margenn.com/post/2135398085</guid><pubDate>Tue, 07 Dec 2010 21:57:00 +0100</pubDate><category>oauth</category><category>php</category><category>pecl</category><category>twitter</category><category>oauth 1.0a</category><dc:creator>tubalmartin</dc:creator></item><item><title>Utiliza jQuery en tus iframes sin incluir la librería en cada uno de ellos</title><description>&lt;p&gt;En un proyecto personal utilizo la archiconocida librería javascript &lt;a href="http://jquery.com" target="_blank"&gt;jQuery&lt;/a&gt; y varios iframes en los que necesito utilizar la mencionada librería.&lt;/p&gt;
&lt;p&gt;Concretamente, la página contiene varios iframes, es decir, tenemos una ventana padre principal (top o parent window) y varias ventanas hijas (iframes o child windows).&lt;/p&gt;
&lt;p&gt;Como ya sabreis el lenguaje Javascript permite la comunicación entre scripts situados en diferentes ventanas anidadas (frames e iframes) mediante varias propiedades del objeto window (frames, parent, top y self).&lt;/p&gt;
&lt;p&gt;De modo que &lt;strong&gt;incluí jQuery exclusivamente en la ventana padre&lt;/strong&gt; (etiqueta script) &lt;strong&gt;y en cada iframe haría una referencia al objeto $ o jQuery de la ventana padre&lt;/strong&gt; como se suele hacer en javascript &lt;strong&gt;para no tener que incluir la librería jQuery en cada iframe&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Codigo en iframe, referencia al objeto jQuery de la ventana padre

$ = window.top.$;

// Utilizamos jQuery

$(...);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bien, probamos y tachan…no funciona el código  javascript (jQuery) del iframe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Por qué no funciona?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bien, el problema surge porque jQuery a partir de la versión 1.4 se autoinicializa “sandboxed” y establece por defecto la ventana (objeto window) en la que se incluye la librería (etiqueta script) como contexto padre para todos los selectores que pasemos a jQuery.&lt;/p&gt;
&lt;p&gt;Es decir, el contexto de nuestros selectores será la ventana padre, no la ventana del iframe (ventana hija) en la que se ejecuta nuestro código y, por tanto, la selección de nodos no funciona.&lt;/p&gt;
&lt;p&gt;Soluciones:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt; &lt;strong&gt;Establecer el contexto correcto en cada selección&lt;/strong&gt;:
&lt;pre&gt;&lt;code&gt;// Codigo en iframe, referencia al objeto jQuery de la ventana padre

$ = window.top.$;

// Utilizamos jQuery fijando el contexto de ventana correcto (ventana del iframe)

$("a", window.document); // Funciona!!
&lt;/code&gt;&lt;/pre&gt;
En mi opinión, esta solución es poco idónea ya que por cada selector tendremos que especificar el contexto de ventana o documento correcto y en los casos de selectores más complejos en los que es recomendable especificar un contexto a partir del cual realizar la selección el tema se complica más hasta hacerse incomodísimo, de modo que ideé a la siguiente solución.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;La función jQueryContext&lt;/strong&gt;:&lt;br/&gt;&lt;br/&gt; Dado que jQuery se inicializa sandboxed (dentro de una función anónima) y no nos provee de un método para cambiar el contexto de la ventana o documento sólo me quedaba la opción de modificar la inicialización de la librería. La modificación es muy sencilla, basta con modificar la primera y última línea de la librería y solucionado:
&lt;pre&gt;&lt;code&gt;// Modificar primera y última línea de jQuery 1.4.x:

(function( window, undefined ) {
   // ........
   // ........
})(window);

// por:

window.jQueryContext = function(window, undefined){
   // ......
   // ......
}; window.jQueryContext(window);
&lt;/code&gt;&lt;/pre&gt;
De este modo, en cada ventana hija (iframe o frame) podremos modificar el contexto del documento en una sola sentencia y utilizar jQuery normalmente:
&lt;pre&gt;&lt;code&gt;// Código en iframe. jQuery no incluido en iframe, unicamente en la ventana padre
// Inicializamos jQuery  y fijamos el contexto de los selectores a la ventana o documento del iframe

window.top.jQueryContext(window);

// Utilizamos jQuery en el iframe como si no hubiera pasado nada

$("a", $("#links")); // Funciona!!
&lt;/code&gt;&lt;/pre&gt;
Y eso es todo!! &lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Envié un &lt;a href="http://bugs.jquery.com/ticket/7436" target="_blank"&gt;ticket al equipo de jQuery&lt;/a&gt;, lo consideraron pero decidieron no dar soporte para este caso por ahora. De todos modos se tomaron la molestia de crear un archivo make para el compilador de Google para que pudiese compilar jQuery con mi modificación.&lt;/p&gt;
&lt;p&gt;Notas finales:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Si incluís jQuery en cada iframe (que lo considero una chapuza y es lo que quería evitar) no tendris ningún problema con los contextos ya que cada instancia de jQuery tendrá como contexto la ventana o documento que lo ha incluido.&lt;/li&gt;
&lt;li&gt;No es posible modificar el contexto del documento o ventana mediante un plugin debido al modo de autoinicialización de jQuery.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Espero que a alguno de vosotros le sirva si se encuentra con el problema y si necesita el script modificado, que lo pida y lo colgaré en nuestro servidor.&lt;/p&gt;</description><link>http://blog.margenn.com/post/1538057877</link><guid>http://blog.margenn.com/post/1538057877</guid><pubDate>Thu, 11 Nov 2010 01:38:00 +0100</pubDate><category>jquery</category><category>iframe</category><category>context</category><category>window</category><category>selector</category><dc:creator>tubalmartin</dc:creator></item><item><title>EllisLab 2.0 - El cambio que demanda la comunidad</title><description>&lt;img style="float:right" src="http://media.tumblr.com/tumblr_lbfhjuSsVj1qa6pqe.png"/&gt;&lt;p&gt;Estamos siendo testigos de un revuelo considerable en torno a &lt;a href="http://ellislab.com/" target="_blank"&gt;EllisLab&lt;/a&gt;, la compañía de Oregon que desarrolla los CMS comerciales &lt;a href="http://expressionengine.com/" target="_blank"&gt;ExpressionEngine&lt;/a&gt; y &lt;a href="http://mojomotor.com/" target="_blank"&gt;MojoMotor&lt;/a&gt;, los cuales corren sobre su framework de código libre &lt;a href="http://codeigniter.com/" target="_blank"&gt;CodeIgniter&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;En poco menos de dos meses hemos visto cómo empleados de EllisLab, que llegaron desde la comunidad de CodeIgniter, abandonan la compañía por &lt;strong&gt;falta de motivación&lt;/strong&gt;. Como es el caso de &lt;a href="http://derekallard.com/blog/post/new-challenges/" target="_blank"&gt;Derek Allard&lt;/a&gt; o  más recientemente  de &lt;a href="http://dhorrigan.com/blog/article/i-am-no-longer-with-ellislab" target="_blank"&gt;Dan Horrigan&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;Se ha hecho patente el hastío de relevantes seguidores de ExpressionEngine y su frustrante relación con EllisLab, condensado en el artículo &lt;a href="http://thenerdary.net/articles/entry/a_plea_to_ellislab" target="_blank"&gt;A Plea to EllisLab&lt;/a&gt;, el cual recibió respuesta oficial por parte del presidente Leslie Camacho, &lt;a href="http://expressionengine.com/blog/entry/i_hear_you/" target="_blank"&gt;I Hear You&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;En escasas dos semanas tras la tormenta de ExpressionEngine se abre otro frente, esta vez contra CodeIgniter, representado una vez más por varios destacados miembros de la comunidad y catalizado por el artículo &lt;a href="http://philsturgeon.co.uk/news/2010/10/what-happens-next" target="_blank"&gt;What happens next?&lt;/a&gt; de Phil Sturgeon, en respuesta al comunicado de Derek Jones, CTO de EllisLab, &lt;a href="http://codeigniter.com/news/whats_happening_now/" target="_blank"&gt;What’s Happening Now?&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;Con todo ésto, ya hubo quien no dudó en anunciar el &lt;strong&gt;declive y muerte de CodeIgniter&lt;/strong&gt;. Una oleada de agoreros lo promulgaba en twitter.&lt;/p&gt;


&lt;h2&gt;La comunidad&lt;/h2&gt;


&lt;p&gt;&lt;a href="http://www.google.com/trends?q=cakephp%2C+codeigniter+%2Csymfony%2C+kohana%2C+%22zend+framework%22" target="_blank"&gt;&lt;img src="http://www.google.com/trends/viz?q=cakephp,+codeigniter+,symfony,+kohana,+%22zend+framework%22&amp;graph=weekly_img&amp;sa=N"/&gt;&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;¿Ha muerto? No. De hecho &lt;a href="http://www.google.com/trends?q=cakephp%2C+codeigniter+%2Csymfony%2C+kohana%2C+%22zend+framework%22" target="_blank"&gt;está mejor que nunca&lt;/a&gt;. ¿Morirá? No a corto plazo. En mi opinión, tiene exactamente &lt;strong&gt;las mismas expectativas de vida que cualquier otro framework popular&lt;/strong&gt;. Y si lo hiciera, más bien sería una reencarnación, al estilo del nuevo framework que se está gestando, &lt;a href="http://fuelphp.com/" target="_blank"&gt;FuelPHP&lt;/a&gt;, o como ocurriera hace ya hace más de dos años con &lt;a href="http://kohanaframework.org/" target="_blank"&gt;Kohana&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;CodeIgniter es sinónimo de &lt;strong&gt;simplicidad, rendimiento y flexibilidad&lt;/strong&gt;. Además de tener una &lt;strong&gt;excelente documentación y comunidad&lt;/strong&gt;. Tiene la virtud de haber bajado la barrera de entrada a los frameworks MVC, al igual que lo hiciera PHP como lenguaje de programación web. Incluso Rasmus Lerdorf, creador de PHP, no dudó en &lt;a href="http://en.wikipedia.org/wiki/EllisLab#CodeIgniter" target="_blank"&gt;destacar esas cualidades&lt;/a&gt; frente a otros frameworks.&lt;/p&gt;


&lt;p&gt;Todo ésto ha atraído cada vez a más gente hasta convertirlo en mainstream. Y aquí es donde la compañía EllisLab ve la oportunidad…&lt;/p&gt;


&lt;h2&gt;La compañía&lt;/h2&gt;

&lt;img style="float:left;margin:0 10px 10px 0" src="http://media.tumblr.com/tumblr_lbfhtkiAAu1qa6pqe.jpg"/&gt;&lt;p&gt;EllisLab depende de CodeIgniter. Ahora es la base de sus productos. Ya no es un mero subproducto de ExpressionEngine, un experimento. Ahora CodeIgniter es la base de su estrategia, para bien o para mal. Han invertido muchos recursos en reescribir enteramente ExpressionEngine 2.0 sobre CodeIgniter (algo que según Joel Spolsky &lt;a href="http://www.joelonsoftware.com/articles/fog0000000069.html" target="_blank"&gt;nunca deberías hacer&lt;/a&gt;).&lt;/p&gt;


&lt;p&gt;Por un lado EllisLab está interesada en la comunidad de desarrolladores a la que poder atraer hacia sus productos y en el ecosistema de desarrolladores y proveedores de soluciones para sus productos. Pero por otro lado no quiere perder ni un ápice de control sobre lo que sustenta sus productos, CodeIgniter.&lt;/p&gt;


&lt;p&gt;En esa encrucijada se encuentra.&lt;/p&gt;


&lt;p&gt;Si ya cuando CodeIgniter no era la base de sus productos era difícil que aceptaran contribuciones ahora se plantea aún más complicado.&lt;/p&gt;


&lt;p&gt;Por un lado desean una comunidad de desarrolladores en torno a sus productos, pero por otro lado desconfían del valor que puedan aportar a la mejora y extensión del mismo. No es una relación bidireccional. No hay comunicación. Sin comunicación no hay confianza, y sin confianza no hay expectativas ni relación posible.&lt;/p&gt;


&lt;h2&gt;Entendámonos, hablemos&lt;/h2&gt;


&lt;p&gt;Éso es lo que quería decir con “&lt;strong&gt;EllisLab 2.0&lt;/strong&gt;”. Una &lt;strong&gt;comunicación más transparente y fluida&lt;/strong&gt; entre compañía y comunidad. Y unos &lt;strong&gt;mecanismos que garanticen la participación&lt;/strong&gt;.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;El cambio que demanda la comunidad&lt;/strong&gt;.&lt;/p&gt;


&lt;p&gt;Y &lt;a href="http://expressionengine.com/blog/entry/introducing_the_forecast/" target="_blank"&gt;en esa dirección&lt;/a&gt; han empezado a dar algún pequeño y tímido paso, con el nuevo &lt;a href="http://expressionengine.com/blog/forecast/" target="_blank"&gt;ExpressionEngine Forecast&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;Veamos como gestionan el crecimiento y su comunidad.&lt;/p&gt;
&lt;p&gt;Suerte.&lt;/p&gt;</description><link>http://blog.margenn.com/post/1489963377</link><guid>http://blog.margenn.com/post/1489963377</guid><pubDate>Fri, 05 Nov 2010 21:24:00 +0100</pubDate><category>codeigniter</category><category>expressionengine</category><category>mojomotor</category><category>framework</category><category>php</category><category>ellislab</category><dc:creator>janogarcia</dc:creator></item><item><title>CodeIgniter 2.0 Ya! No esperes más.</title><description>&lt;img src="http://media.tumblr.com/tumblr_lak590RRH51qa6pqe.png" style="float:right;margin:0 0 10px 10px"/&gt;&lt;p&gt;Vale, parece ser que a nadie le pilla por sorpresa que la publicación de CodeIgniter 2.0 no se haya realizado todavía. Al fin y al cabo, EllisLab no se ha caracterizado precisamente por la filosofía de “&lt;a href="http://en.wikipedia.org/wiki/Release_early,_release_often" target="_blank"&gt;Release early, release often&lt;/a&gt;”.&lt;/p&gt;
&lt;p&gt;A fecha de hoy, la última versión estable 1.7.2 fue publicada hace ya más de un año (septiembre 2009). Pero eso no es nada comparado con el enorme retraso que sufrió el anticipado anuncio de &lt;a href="http://expressionengine.com/" target="_blank"&gt;ExpressionEngine&lt;/a&gt; 2.0 (CMS de EllisLab desarrollado sobre CodeIgniter) en febrero de 2008, según el cual estaría disponible para el verano de 2008, pero no llegaron a publicar una versión estable hasta julio de 2010. Nada menos que dos años por encima de lo previsto.&lt;/p&gt;
&lt;p&gt;Con estos antecendentes podría parecer que &lt;a href="http://bitbucket.org/ellislab/codeigniter" target="_blank"&gt;CodeIgniter 2.0&lt;/a&gt; queda todavía muy lejos y que por tanto no deberíamos considerarlo para código de producción.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;¡Nada de eso!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;CodeIgniter 2.0 a pesar de no estar oficialmente lanzado es de hecho &lt;b&gt;más estable que la actual versión 1.7.2&lt;/b&gt;. Cabe destacar que los dos productos comerciales de EllisLab, &lt;a href="http://expressionengine.com/" target="_blank"&gt;ExpressionEgine&lt;/a&gt; y &lt;a href="http://mojomotor.com/" target="_blank"&gt;MojoMotor&lt;/a&gt;, están desarrollados sobre CodeIgniter 2.0.&lt;/p&gt;
&lt;p&gt;En palabras de &lt;a href="http://codeigniter.com/forums/viewthread/169287/#807543" target="_blank"&gt;Phil Sturgeon&lt;/a&gt; (miembro destacado de la comunidad de CodeIgniter):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ll mention that every bug noticed in 1.7.2 is fixed in 2.0. That means in many ways 2.0 is actually more stable than 1.7.2, even if it is not a “final release”. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;&lt;p&gt;Si finalmente te decides a dar el paso visita el &lt;a href="http://bitbucket.org/ellislab/codeigniter" target="_blank"&gt;repositorio de CodeIgniter&lt;/a&gt; para descargar el código fuente o para clonarlo. Y no dejes de echar un vistazo a los siguientes artículos:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://philsturgeon.co.uk/news/2010/10/codeigniter-2.0-is-stable" target="_blank"&gt;CodeIgniter 2.0 is stable (enough)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://philsturgeon.co.uk/news/2010/05/upgrading-to-codeigniter-2.0" target="_blank"&gt;Upgrading to CodeIgniter 2.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://codeigniter.com/forums/viewthread/169287/" target="_blank"&gt;Information on CodeIgniter 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;b&gt;¿A qué esperas?&lt;/b&gt;&lt;/p&gt;</description><link>http://blog.margenn.com/post/1357070197</link><guid>http://blog.margenn.com/post/1357070197</guid><pubDate>Wed, 20 Oct 2010 08:39:00 +0200</pubDate><category>codeigniter</category><category>development</category><category>desarrollo</category><category>php</category><category>framework</category><dc:creator>janogarcia</dc:creator></item><item><title>Bitbucket Free ¡La suerte de ser pequeño!</title><description>&lt;img src="http://media.tumblr.com/tumblr_lai6zfk5lN1qa6pqe.png" style="float:right"/&gt;&lt;p&gt;Hoy hemos hecho un pequeño pero significativo cambio interno. Hemos cambiado de sistema y alojamiento para el control de versiones.&lt;/p&gt;

&lt;p&gt;Hasta ahora habíamos usado el sistema centralizado &lt;a href="http://en.wikipedia.org/wiki/Subversion_%28software%29" target="_blank"&gt;Subversion&lt;/a&gt;, alojado en &lt;a href="http://springloops.com/" target="_blank"&gt;Springloops&lt;/a&gt;. Los motivos por los que elegimos Springloops frente a otros servicios dedicados para Subversion (Assembla, Beanstalk, unfuddle, ProjectLocker, Code Spaces, XP-Dev, repositoryhosting) fueron  los siguientes:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Relación características/precio (plan &lt;a href="https://www.springloops.com/signup/" target="_blank"&gt;Flowerpot&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Explorador de código amigable&lt;/li&gt;
&lt;li&gt;Integración con Basecamp&lt;/li&gt;
&lt;li&gt;Usuarios ilimitados&lt;/li&gt;
&lt;li&gt;Despliegue/publicación del código vía FTP a diferentes servidores&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Todo muy atractivo. Hay que decir que no hemos tenido problemas con este servicio. Pero como siempre, el tiempo ha ayudado a definir con mayor precision qué es lo que realmente necesitábamos. Reconocer lo que estaba aportando valor y descartar todo lo accesorio.&lt;/p&gt;
&lt;p&gt;Todas esas características que en un principio habían sido las decisivas, ahora eran insignificantes. Ni la integración con Basecamp, ni la publicación vía FTP ni los usuarios ilimitados. No necesitábamos nada de eso, bastaba con un sólido sistema de control versiones y un buen rendimiento de su alojamiento.&lt;/p&gt;
&lt;p&gt;Esta vez no ha hecho falta mucha investigación o tablas comparativas. La elección era clara, tan sólo contemplábamos una opción: &lt;a href="http://bitbucket.org/plans" target="_blank"&gt;Bitbucket Free&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;b&gt;Ilimitados repositorios privados o públicos&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Hasta 5 usuarios con acceso a los repositorios privados&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ilimitados usuarios con acceso a los repositorios públicos&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Y todo esto… GRATIS!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Nos gusta mucho su explorador de código o su Issue tracker, pero ahora no es lo que más nos importe. Dicho sea, no han influenciado en nuestra decisión.&lt;/p&gt;

&lt;p&gt;Bitbucket usa &lt;a href="http://en.wikipedia.org/wiki/Mercurial" target="_blank"&gt;Mercurial&lt;/a&gt;, un sistema distribuido/descentralizado de control de versiones . No ha supuesto el menor problema &lt;a href="http://mercurial.selenic.com/" target="_blank"&gt;instalarlo&lt;/a&gt; y comenzar a usarlo con &lt;a href="http://netbeans.org/features/php/" target="_blank"&gt;Netbeans&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Otros motivos más karmáticos, subjetivos e irracionales que quizás nos hayan empujado podrían ser que &lt;a href="http://codeigniter.com/news/ellislab_moves_to_mercurial_assembla_bitbucket_codeigniter_2.0_baking/" target="_blank"&gt;Bitbucket es usado por CodeIgniter&lt;/a&gt; (el framework PHP que usamos), o que &lt;a href="http://www.w3.org/blog/systeam/2010/06/16/why_we_chose_mercurial_as_our_dvcs" target="_blank"&gt;el W3C eligiera Mercurial&lt;/a&gt; como su DVCS, o incluso que Mercurial esté desarrollado en Python y Bitbucket sobre Django, los cuales admiro…&lt;/p&gt;

&lt;p&gt;Sea como sea ahora mismo no creo que nadie ofrezca más por tan poco (hace falta recordar que es &lt;b&gt;GRATIS?&lt;/b&gt;), tal y como apuntan en el &lt;a href="http://en.wikipedia.org/wiki/Bitbucket" target="_blank"&gt;artículo sobre Bitbucket en Wikipedia&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unusually - and possibly uniquely - for a project hosting service, as of September 2010, it offers free accounts with unlimited numbers of private repositories (which can have up to five users in the case of free accounts).&lt;/p&gt;
&lt;/blockquote&gt;


&lt;p&gt;&lt;b&gt;Y cosas de estas a veces pasan por ser pequeño&lt;/b&gt;. En este caso por ser 5 o menos.&lt;/p&gt;

&lt;hr&gt;&lt;p&gt;Y ya puestos dejo un par de herramientas de gestión de proyectos que te van a salir por… nada. ¡Enano!&lt;/p&gt;


&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_lai7hpOq3S1qa6pqe.png"/&gt;&lt;br/&gt;&lt;a href="http://www.fogcreek.com/FogBugz/" target="_blank"&gt;FogBugz&lt;/a&gt; integra herramientas de gestión de proyectos con diferentes sistemas de control de versiones. &lt;a href="http://www.fogcreek.com/FogBugz/StudentAndStartup.html" target="_blank"&gt;Gratis para startups de hasta 2 personas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_lai7c9fvXe1qa6pqe.png"/&gt;&lt;br/&gt;&lt;a href="http://www.pivotaltracker.com/" target="_blank"&gt;Pivotal Tracker&lt;/a&gt; está modelado según metodologías ágiles de desarrollo. Por ahora gratis hasta para los grandes.&lt;/p&gt;</description><link>http://blog.margenn.com/post/1346017370</link><guid>http://blog.margenn.com/post/1346017370</guid><pubDate>Mon, 18 Oct 2010 22:58:00 +0200</pubDate><category>project_management</category><category>collaboration</category><dc:creator>janogarcia</dc:creator></item><item><title>Test de rendimiento: $_SESSION + unserialize vs Mysql + json_decode </title><description>&lt;p&gt;En mi último proyecto, como en otros, trabajo mucho con JSON como formato de transporte de datos entre el cliente y el servidor ya que tanto en Javascript como en los lenguajes de servidor (PHP en mi caso) es muy cómodo utilizarlo.&lt;/p&gt;
&lt;p&gt;En este proyecto, estoy guardando una cadena de texto JSON en un campo de una tabla de una base de datos MySQL. Esta cadena de texto JSON contiene varios parámetros de configuración de un usuario que son utilizados por toda la aplicación.&lt;/p&gt;
&lt;p&gt;En el lado del servidor, es necesario que esa cadena de texto JSON sea convertida a objeto o &lt;em&gt;array &lt;/em&gt;(matriz) para poder acceder a sus datos. En PHP, utilizamos las funciones &lt;em&gt;json_encode&lt;/em&gt; y &lt;em&gt;json_decode &lt;/em&gt;para las conversiones&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Bien, explicado lo anterior, me plantee la siguiente cuestión:&lt;strong&gt; ¿Cual es el modo más eficiente de acceder a estos datos?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dos son las opciones que me planteé:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Cuando el usuario se loguea, pedimos a MySQL dicha cadena de texto JSON, la convertimos a &lt;em&gt;array&lt;/em&gt; mediante la función &lt;em&gt;json_decode&lt;/em&gt; y la almacenamos en &lt;em&gt;$_SESSION&lt;/em&gt;, pudiendo acceder a dichos valores hasta que la sesión expire o el usuario cierre la sesión. &lt;br/&gt;Es decir, accedemos una vez a MySQL, convertimos de JSON a &lt;em&gt;array, &lt;/em&gt;almacenamos el &lt;em&gt;array &lt;/em&gt;en &lt;em&gt;$_SESSION &lt;/em&gt;y accedemos muchas veces a &lt;em&gt;$_SESSION&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;El usuario se loguea y sólo cuando la aplicación necesite alguno de los datos contenidos en la cadena de texto JSON almacenada en la base de datos, se solicita a MySQL y se convierte a &lt;em&gt;array&lt;/em&gt; mediante la función &lt;em&gt;json_decode&lt;/em&gt;. &lt;br/&gt;Es decir, accedemos muchas veces a MySQL y cada vez convertimos de JSON a &lt;em&gt;array&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Para muchos, la solución puede parecer obvia, la primera opción debe ser la más eficiente. Es cierto, la primera opción es más eficiente ya que así lo refleja el test que realicé, pero entonces si a primera vista es tan obvia la respuesta &lt;strong&gt;¿porqué me planteé esta cuestión?&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;La duda que tenía pivotaba sobre varios aspectos:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;El tiempo de proceso que conlleva la deserialización que PHP debe realizar (función &lt;em&gt;unseralize&lt;/em&gt;) cada vez que accedemos a un dato almacenado en la variable superglobal &lt;em&gt;$_SESSION&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;El tiempo de localización, acceso y lectura del disco duro del servidor al archivo de sesión correspondiente almacenado en el típico directorio &lt;em&gt;/tmp&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;El tiempo de localización, acceso y lectura del disco duro del servidor al archivo de la base de datos, el de conexión a MySQL y el de ejecución de la petición.&lt;/li&gt;
&lt;li&gt;El tiempo de ejecución de la función &lt;em&gt;json_decode.&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Resumiendo: ¿&lt;strong&gt;&lt;em&gt;$_SESSION&lt;/em&gt; + &lt;em&gt;unserialize&lt;/em&gt; o Mysql + &lt;em&gt;json_decode&lt;/em&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Obviamente si la aplicación es utilizada por pocos usuarios, cualquiera de las opciones no supondría un impacto negativo en el rendimiento del servidor.&lt;/p&gt;
&lt;p&gt;Sin embargo, los tests realizados demuestran que &lt;strong&gt;a medida que el número de usuarios de la aplicación aumenta, la diferencia en rendimiento también aumenta exponencialmente&lt;/strong&gt; y revela claramente que&lt;strong&gt; la combinación &lt;em&gt;$_SESSION&lt;/em&gt; + &lt;em&gt;unserialize&lt;/em&gt; es mucho más eficiente que MySQL + &lt;em&gt;json_decode&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Espero que a alguien le sea de utilidad lo expuesto en sus proyectos.&lt;/p&gt;
&lt;p&gt;Si alguno de vosotros está interesado en el test, que comente y lo publicaré.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Os habeis encontrado con este caso u otros parecidos?&lt;/strong&gt;&lt;/p&gt;</description><link>http://blog.margenn.com/post/1295500594</link><guid>http://blog.margenn.com/post/1295500594</guid><pubDate>Tue, 12 Oct 2010 03:34:00 +0200</pubDate><category>php</category><category>json</category><category>json_decode</category><category>$_SESSION</category><category>array</category><category>unserialize</category><category>rendimiento</category><category>performance</category><category>MySQL</category><dc:creator>tubalmartin</dc:creator></item><item><title>IE7 también debe morir en 2010</title><description>&lt;p&gt;Puede que a muchos de vosotros este título os ruborice y muchos penséis que estoy loco, pero…por un segundo, imaginad vuestro día a día sin tener que padecer &lt;a target="_blank" href="http://www.quirksmode.org/bugreports/archives/explorer_7/index.html"&gt;todos&lt;/a&gt; &lt;a target="_blank" href="http://www.webdevout.net/browser-support"&gt;los&lt;/a&gt; &lt;a target="_blank" href="http://www.gtalbot.org/BrowserBugsSection/MSIE7Bugs/"&gt;bugs&lt;/a&gt; que este navegador padece ¿No sería nuestro trabajo más agradable? ¿No invertiríamos más tiempo mejorando un diseño en vez de resolviendo los bugs de este navegador?&lt;/p&gt;
&lt;p&gt;Este año, estudios, agencias y gigantes del sector como Google han empezado a dejar de dar soporte a IE6 en sus proyectos por tres motivos principales en mi opinión:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;El “market share” (cuota de mercado) de IE6 a nivel global ha bajado muchísimo este año (7% actualmente).&lt;/li&gt;
&lt;li&gt;Además IE6 supone un gran obstáculo para el desarrollo y mantenimiento normal de cualquier página o aplicación web avanzada.&lt;/li&gt;
&lt;li&gt;Y por último, incluso Microsoft empezó a animar a sus usuarios a que actualizasen su jurásico navegador. &lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;En &lt;a target="_blank" href="http://margenn.com"&gt;margenn&lt;/a&gt;, a principios del año pasado decidimos dejar de dar soporte a IE6 no sólo por los motivos anteriormente citados, sino por uno aún más importante, la relación del número de horas invertidas en desarrollar un producto compatible con IE6 y su coste económico.&lt;/p&gt;
&lt;p&gt;Para un estudio o agencia pequeña dar soporte, por defecto, a IE6 en todos los proyectos supone un coste enorme tanto en horas de desarrollo como a nivel económico.&lt;/p&gt;
&lt;p&gt;Desde entonces, cuando acordamos la realización de un proyecto con un cliente uno de los aspectos que tratamos con especial cuidado es el soporte de los navegadores. Por defecto no damos soporte a IE6 y salvo que el cliente nos lo exija expresamente, después de haber intentado convencerle con argumentos sólidos, realizamos el proyecto “compatible con IE6” si bien, le informamos de que dicha compatibilidad conlleva un aumento de precio del presupuesto (la cantidad varía dependiendo de la complejidad del proyecto).&lt;/p&gt;
&lt;p&gt;Hey!! pero… ¿este artículo no trataba de IE7?&lt;/p&gt;
&lt;p&gt;Teneis toda la razón. Sin embargo, he creido conveniente poneros en antecedentes para que entendais mi posición frente a IE7.&lt;/p&gt;
&lt;p&gt;Actualmente, las diferentes &lt;a target="_blank" href="http://gs.statcounter.com/#browser_version-ww-monthly-200909-201009"&gt;estadísticas&lt;/a&gt; que pululan por la gran red reflejan que el market share de IE7 está empezando a tocar fondo (13%) gracias a IE8 principalmente y en un segundo plano, gracias a la fama de Google y a su navegador multiplataforma Google Chrome.&lt;/p&gt;
&lt;p&gt;Si la memoria no me falla, en margenn decidimos dejar de dar soporte a IE6 cuando su market share se encontraba muy próximo al actual de IE7.&lt;/p&gt;
&lt;p&gt;Si tenemos en cuenta que IE9 llegará en breve y será un excelente navegador y que IE8 tiene una cuota de mercado muy importante y no padece tantos bugs como sus antecesores, os pregunto &lt;strong&gt;¿qué sentido tiene seguir dando hoy soporte a un navegador como IE7 que dentro de unos pocos meses tendrá un share ridículo, similar al de IE6?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esta pregunta nos la planteamos hace ya un tiempo, a principios de este año y si bien no opinabamos todos lo mismo, sí que llegamos a punto de acuerdo, ofreceríamos&lt;strong&gt;soporte básico a IE7 aplicando el principio en diseño conocido como “&lt;a target="_blank" href="http://en.wikipedia.org/wiki/Fault-tolerant_system"&gt;graceful degradation&lt;/a&gt;”&lt;/strong&gt; para aquellos proyectos que fuesen visibles o visitables por la audencia general, la gran masa de usuarios de la red, en cambio, no le daríamos soporte por defecto para aquellos proyectos utilizados por grupos reducidos y controlados de usuarios, léase aplicaciones privadas para empresas (intranets, extranets), administradores de contenido etc… todo aquello que requiera unos credenciales de acceso para poder visitar su contenido, un entorno controlado en definitiva.&lt;/p&gt;
&lt;p&gt;Para terminar comentaros que si en un proyecto, de la naturaleza que sea, no damos soporte a IE6 y/o IE7 siempre nos preocupamos de informar al usuario que visite dicho proyecto con un navegador no soportado de que con su navegador no va a poder disfrutar plenamente del contenido ofrecido. Así mismo, le animamos a actualizar su navegador a la última versión (IE8) o a probar una navegador alternativo, en nuestro caso, Google Chrome, ya que creemos que para los usuarios corrientes de Internet, Google es más conocido que Mozilla, Safari u Opera y por tanto, los usuarios son menos reticentes a probar Chrome.&lt;/p&gt;
&lt;p&gt;Si visitais &lt;a target="_blank" href="http://margenn.com"&gt;nuestra web&lt;/a&gt; con IE6, podreis ver un “dialog” que muestra lo comentado en el anterior párrafo.&lt;/p&gt;
&lt;p&gt;Desde estas líneas y de buena fe os animamos a que adopteis una postura similar a la nuestra en vuestra empresa y con vuestros clientes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Qué opinais del tema?&lt;/strong&gt; &lt;/p&gt;</description><link>http://blog.margenn.com/post/1291242661</link><guid>http://blog.margenn.com/post/1291242661</guid><pubDate>Mon, 11 Oct 2010 16:04:13 +0200</pubDate><category>ie</category><category>ie6</category><category>ie7</category><category>margenn</category><dc:creator>tubalmartin</dc:creator></item><item><title>Flaco favor</title><description>&lt;p&gt;Alguien pretende en un principio “ayudarte”, sorprenderte o no sabes bien qué… Desde el principio dudas de ese alguien por la  falta de transparencia pero no le das mayor importancia. Hasta que con el tiempo, que todo lo pone en su sitio, termina por convertirse en desconfianza absoluta y enfrentamiento. Eso es lo que pasa cuando los principios, valores y ética no son compartidos dentro de un grupo (ya sea en el ámbito personal o profesional), o ese grupo es alterado por un actor externo.&lt;/p&gt;
&lt;p&gt;Esto es lo que hemos podido comprobar hoy “gracias” a un allegado de Túbal Martín (la otra mitad de margenn). Como posible representante comercial, se tomó la libertad de darnos a conocer, sin saber nosotros a quién ni de qué manera. Lo único que supimos fue un “&lt;em&gt;Si algun día os sale algo de comercio electrónico, igual me tendréis que dar las gracias&lt;/em&gt;.”, confiándose en que así podríamos contar con él.&lt;/p&gt;
&lt;p&gt;No supimos más de esa patochada. &lt;strong&gt;Hasta hoy&lt;/strong&gt;. Este mediodía salía escopeteado hacia el tren cuando me para &lt;a href="http://www.phalax.com/" target="_blank"&gt;un compañero de otra oficina&lt;/a&gt; y me pregunta si habíamos hecho la web de Perfumerías IF (WTF! comorll?¿). No doy crédito. Inmediatamente trato de atar cabos y le pregunto si se trataba de comercio electrónico. Me lo confirma y comienza a hervirme la sangre. No hay más tiempo, tengo que coger el tren o no llegaré a la reunión.&lt;/p&gt;
&lt;p&gt;Lo que ha venido después ha sido un cabreo monumental, del que no hace falta entrar en detalles. Este candidato a representante comercial, que obviamente no lo será, ha admitido haber enviado varios emails en los que mencionaba la web de Perfumerías IF.&lt;strong&gt;Que quede claro, no tenemos nada que ver con esa web, y preventivamente nos desmarcamos del resto de webs que haya mencionado.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No sabemos con certeza quiénes ni cuánta gente ha podido recibir dicho email. Supuestamente fueron enviadas 3 ó 4 copias. Tampoco conocemos su contenido exacto. A los que lo hayan recibido les pedimos nuestras disculpas y les agradeceríamos que nos lo reenviaran.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Cómo puedes identificar el email?&lt;/strong&gt; El siguiente email que mostramos más abajo es genuino. &lt;strong&gt;Si el email &lt;a href="http://margenn.com/mail/partners_static.html" target="_blank"&gt;no se parece a éste&lt;/a&gt;, o difiere claramente su contenido, no se tratará entonces de un email nuestro&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Gracias por tu ayuda.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://margenn.com/mail/partners_static.html" target="_blank"&gt;&lt;img src="http://media.tumblr.com/tumblr_l3i5x3iMFb1qbjy8q.png"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://margenn.com/mail/partners_static.html" target="_blank"&gt;Ver email&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Todavía está pendiente integrar &lt;a href="http://disqus.com/" target="_blank"&gt;disqus&lt;/a&gt; en el blog, puedes comentar de mientras este artículo en twitter)&lt;/p&gt;</description><link>http://blog.margenn.com/post/1291054808</link><guid>http://blog.margenn.com/post/1291054808</guid><pubDate>Fri, 04 Jun 2010 20:22:00 +0200</pubDate><dc:creator>adminmargenn</dc:creator></item></channel></rss>

