url_shortener
[ class tree: url_shortener ] [ index: url_shortener ] [ all elements ]

Source for file class.url_shortener.php

Documentation is available at class.url_shortener.php

  1. <?php
  2. /**
  3.  * Class to shorten Urls
  4.  * @package url_shortener
  5.  * @author Julius Beckmann
  6.  * @link http://juliusbeckmann.de/classes/url_shortener/
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @filesource
  9.  */
  10. /* 
  11.  *             class.url_shortener.php
  12.  *      
  13.  *      Copyright 2009 Julius Beckmann
  14.  *      
  15.  *      This program is free software; you can redistribute it and/or modify
  16.  *      it under the terms of the GNU General Public License as published by
  17.  *      the Free Software Foundation; either version 2 of the License, or
  18.  *      (at your option) any later version.
  19.  *      
  20.  *      This program is distributed in the hope that it will be useful,
  21.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  22.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23.  *      GNU General Public License for more details.
  24.  *      
  25.  *      You should have received a copy of the GNU General Public License
  26.  *      along with this program; if not, write to the Free Software
  27.  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  28.  *      MA 02110-1301, USA.
  29.  */
  30.  
  31.  
  32.  
  33. /*
  34. MySQL Table for this Class:
  35.  
  36. CREATE TABLE IF NOT EXISTS `url_redirect` (
  37.   `id` bigint(20) unsigned NOT NULL auto_increment,
  38.   `url` varchar(255) NOT NULL,
  39.   `md5` char(32) NOT NULL,
  40.   `ts` int(10) unsigned NOT NULL,
  41.   `ip` int(10) unsigned NOT NULL,
  42.   PRIMARY KEY  (`id`),
  43.   KEY `md5` (`md5`)
  44. ) ENGINE=MyISAM AUTO_INCREMENT=1000 ;
  45.  
  46.  
  47.   
  48. If you want to log number of hits, create such a table:
  49.  
  50. CREATE TABLE IF NOT EXISTS `url_hits` (
  51.     `id` BIGINT UNSIGNED NOT NULL ,
  52.     `hits` BIGINT UNSIGNED NOT NULL ,
  53.     PRIMARY KEY (`id`)
  54. ) ENGINE = MYISAM;
  55.  
  56.  
  57. This database layout is abled to store more then 4 Billion links. 
  58. This limit is set by the MySQL BIGINT collumn used.
  59. The md5 collumn is used for validating and searching duplicate Urls.
  60. I did a short test and inserted about 100.000 Links and Hits
  61. which needed only ~17 MB of space. => 180 Byte per Link (with index and hits)
  62. The only limitation by the class is the convert method. 
  63. After 1.000.000.000.000.000.000 links there would be a overflow.
  64. */
  65.  
  66.  
  67. /**
  68.  * Class for shortening urls
  69.  * The urls will be stored in a MySQL database, read doku for table Format.
  70.  * @name Url shorten class
  71.  * @version v0.1_2009.09.10
  72.  * @access public
  73.  * @package url_shortener
  74.  */
  75. class url_shortener {
  76.     
  77.     /**
  78.      * Name of Database table for storing urls and keys
  79.      * @access public
  80.      * @var string 
  81.      */
  82.     var $db_table_redirect = 'url_redirect';
  83.  
  84.     /**
  85.      * Name of Database table for storing redirect hits
  86.      * @access public
  87.      * @var string 
  88.      */
  89.     var $db_table_hits = 'url_hits';
  90.  
  91.     /**
  92.      * Convertion basis for the key convert method.
  93.      * Default and maximum is 36 (36 => 0..9 and a..z)
  94.      * If you want to have hex keys set this to 16 (16 => 0..9 and a..f)
  95.      * @access public
  96.      * @var int 
  97.      */
  98.     var $key_base = 36;
  99.     
  100.     /**
  101.      * Regex for URL validation
  102.      * This one is very simple and does only allow urls
  103.      * starting with http:// and https://
  104.      * @access public
  105.      * @var string 
  106.      */
  107.     var $regex_valid_url = '@http(s?):\/\/@';
  108.     
  109.     /**
  110.      * List of forbidden URL parts
  111.      * Can be used to avoid making short links from short links
  112.      * @access public
  113.      * @var array 
  114.      */
  115.     var $url_parts_forbidden = array('http://www.example.com');
  116.  
  117.     // --- PUBLIC Methods ---
  118.     // public means: You can _safely_ use this functions from outside.
  119.     // They will not change.
  120.     
  121.     /**
  122.      * Method for creating a new redirect
  123.      * Will return data from database if url already shortened
  124.      * @access public
  125.      * @param string $url Url to shorten
  126.      * @return array Array with all available data inside, or empty one
  127.      */
  128.     function new_redirect($url{
  129.         $ret array();
  130.         $url trim($url);
  131.         if($url && $this->_valid_url($url&& !$this->_forbidden_url($url)) {
  132.             // Check if url is already in database
  133.             $get $this->_get_redirect_by_url($url);
  134.             if($get)
  135.                 $ret $get;
  136.             else
  137.                 // If not, insert
  138.                 $ret $this->_insert_url($url);
  139.         }
  140.         return $ret;
  141.     }
  142.     
  143.     /**
  144.      * Method for selecting a redirect from database
  145.      * @access public
  146.      * @param string $key Redirect key to search
  147.      * @return array Array with all available data inside, or empty one
  148.      */
  149.     function get_redirect($key{
  150.         return $this->_get_redirect_by_key($key);
  151.     }
  152.  
  153.     /**
  154.      * Logs a redirect hit by key
  155.      * @access public
  156.      * @param string $key Key of hitted link
  157.      * @return bool false on error
  158.      */
  159.     function log_redirect($key{
  160.         $ret false;
  161.         $id $this->_convert_id($keyfalse);
  162.         if($id{
  163.             // Try to update first
  164.             // We will use LOW_PRIORITY/DELAYED here to smoothen the database IO.
  165.             $sql 'UPDATE LOW_PRIORITY '.$this->db_table_hits.' SET hits=hits+1 
  166.                             WHERE id='.(int)$id.' LIMIT 1;';
  167.             if(mysql_query($sql))
  168.                 $ret = (bool)mysql_affected_rows();
  169.             // Insert if update failed
  170.             if(!$ret{
  171.                 $sql 'INSERT DELAYED INTO '.$this->db_table_hits
  172.                                 .' (id,hits)VALUES(\''.(int)$id.'\',\'1\');';
  173.                 if(mysql_query($sql))
  174.                     $ret = (bool)mysql_affected_rows();
  175.             }
  176.             
  177.         }
  178.         return $ret;
  179.     }
  180.  
  181.     /**
  182.      * Simply returns the number of current hits for this key
  183.      * @access public
  184.      * @param string $key Key of hitted link
  185.      * @return int|falsefalse on error
  186.      */
  187.     function get_redirect_count($key{
  188.         $ret false;
  189.         $sql 'SELECT count(*) AS count FROM '.$this->db_table_hits
  190.                         .' WHERE id = '.$this->_convert_id($keyfalse).';';
  191.         $query mysql_query($sql);
  192.         if($query{
  193.             $r mysql_fetch_assoc($query);
  194.             $ret = (int)$r['count'];
  195.         }
  196.         return $ret;
  197.     }
  198.  
  199.     /**
  200.      * Simply returns the number of current urls
  201.      * @access public
  202.      * @return int|falsefalse on error
  203.      */
  204.     function count_urls({
  205.         $ret false;
  206.         $sql 'SELECT count(*) AS count FROM '.$this->db_table_redirect.';';
  207.         $query mysql_query($sql);
  208.         if($query{
  209.             $r mysql_fetch_assoc($query);
  210.             $ret = (int)$r['count'];
  211.         }
  212.         return $ret;
  213.     }
  214.  
  215.     // --- PRIVATE Methods ---
  216.     // "private" means: do _not_ use these functions from outside!
  217.     // It is very likely that they will get change or removed.
  218.     
  219.     /**
  220.      * Validates a URL with global regex
  221.      * @access private
  222.      * @param strin $url URL to validate
  223.      * @return bool true if URL is ok
  224.      */
  225.     function _valid_url($url{
  226.         $ret true;
  227.         // Only validate if regex is set
  228.         if($this->regex_valid_url)    
  229.             $ret = (bool)preg_match($this->regex_valid_url$url);
  230.         return $ret;
  231.     }
  232.     
  233.     /**
  234.      * Checks if a URL contains forbidden parts
  235.      * @access private
  236.      * @param string $url URL to check
  237.      * @return bool True if URL is forbidden
  238.      */
  239.     function _forbidden_url($url{
  240.         $ret false;
  241.         if(is_array($this->url_parts_forbidden)) {
  242.             $i 0;
  243.             $count count($this->url_parts_forbidden);
  244.             while(!$ret && $i $count)
  245.                 $ret (strpos($url$this->url_parts_forbidden[$i++]!== false);
  246.         }
  247.         return $ret;
  248.     }
  249.  
  250.     /**
  251.      * Inserts a URL to the database and returns the whole dataset
  252.      * @access private
  253.      * @param string $url Url to shorten
  254.      * @return array Array with all available data inside, or empty one
  255.      */
  256.     function _insert_url($url{
  257.         $ret array();
  258.         // Clear unwanted chars from url
  259.         $url str_ireplace(array("\n","\r","\t")''$url);
  260.         // We save timestamp and IP of submitter.
  261.         // MD5 is needed for verification and faster search on multiple insert
  262.         $sql 'INSERT INTO '.$this->db_table_redirect.' (url,md5,ts,ip)
  263.                         VALUES (
  264.                         \''.mysql_real_escape_string($url).'\',
  265.                         \''.md5($url).'\',
  266.                         \''.time().'\',
  267.                         INET_ATON(\''.$this->_get_ip().'\'));';
  268.         if(mysql_query($sql))
  269.             $ret $this->get_redirect($this->_convert_id(mysql_insert_id()));
  270.         return $ret;
  271.     }
  272.  
  273.     /**
  274.      * Searches a Key in the database and returns the whole dataset
  275.      * @access private
  276.      * @param string $key Key to search
  277.      * @return array Array with all available data inside, or empty one
  278.      */
  279.     function _get_redirect_by_key($key{
  280.         $ret array();
  281.         // Search for the 
  282.         $sql 'SELECT *, INET_NTOA(ip) as ip_long FROM '.
  283.                         $this->db_table_redirect.' WHERE id = \''.
  284.                         $this->_convert_id($keyfalse).'\' LIMIT 1;';
  285.         $query mysql_query($sql);
  286.         if($query && $r mysql_fetch_assoc($query)) {
  287.             $ret $r;
  288.             $ret['key'$this->_convert_id($ret['id']);
  289.         }
  290.         return $ret;
  291.     }
  292.  
  293.     /**
  294.      * Searches a URL in the database and returns the whole dataset
  295.      * @access private
  296.      * @param string $url Url to search
  297.      * @return array Array with all available data inside, or empty one
  298.      */
  299.     function _get_redirect_by_url($url{
  300.         $ret array();
  301.         // Search for MD5 of URL because it is a CHAR Collumn with a INDEX on it.
  302.         $sql 'SELECT *, INET_NTOA(ip) as ip_long FROM '.
  303.                         $this->db_table_redirect.' WHERE md5 = \''.md5($url).'\';';
  304.         $query mysql_query($sql);
  305.         if($query{
  306.             while($r mysql_fetch_assoc($query))
  307.                 if($r['url'== $url{
  308.                     $ret $r;
  309.                     $ret['key'$this->_convert_id($ret['id']);
  310.                 }
  311.         }
  312.         return $ret;
  313.     }
  314.  
  315.     /**
  316.      * Converts a numeric id from decimal to alphanumeric.
  317.      * @access private
  318.      * @param int|string$id Integer to convert
  319.      * @param bool $dir Direction to convert, default=true => normal
  320.      * @return string Converted id
  321.      */
  322.     function _convert_id($id$dir=true{
  323.         if($dir)
  324.             return base_convert($id10$this->key_base);
  325.         else
  326.             return base_convert($id$this->key_base10);
  327.     }
  328.     
  329.     /**
  330.      * Returns the ip of current client
  331.      * Checks for clients behind proxys
  332.      * @access private
  333.      * @return string IP of current client
  334.      */
  335.     function _get_ip({
  336.         $ip $_SERVER['REMOTE_ADDR'];
  337.         if($_SERVER['HTTP_X_FORWARDED_FOR']
  338.             $ip $_SERVER['HTTP_X_FORWARDED_FOR'];
  339.         if($_SERVER['HTTP_FORWARDED_FOR']
  340.             $ip $_SERVER['HTTP_FORWARDED_FOR'];
  341.         if($_SERVER['HTTP_FORWARDED']
  342.             $ip $_SERVER['HTTP_FORWARDED'];
  343.         return $ip;
  344.     }
  345.     
  346. }
  347.  
  348. ?>

Documentation generated on Fri, 29 Jan 2010 08:49:11 +0100 by phpDocumentor 1.4.3