3 Copyright (c) 2009, Beau Lebens
6 Redistribution and use in source and binary forms, with or without modification,
7 are permitted provided that the following conditions are met:
9 - Redistributions of source code must retain the above copyright notice, this
10 list of conditions and the following disclaimer.
11 - Redistributions in binary form must reproduce the above copyright notice,
12 this list of conditions and the following disclaimer in the documentation
13 and/or other materials provided with the distribution.
14 - Neither the name of Dented Reality nor the names of the authors may be used
15 to endorse or promote products derived from this software without specific
16 prior written permission.
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 // Return options for Slinky_Service->url_get() and ->url_post()
32 define( 'SLINKY_BODY', 1 ); // Default
33 define( 'SLINKY_HEADERS', 2 ); // Not implemented yet
34 define( 'SLINKY_FINAL_URL', 3 ); // Default for lengthening URLs
36 // So that services may decide what to do with us
37 define( 'SLINKY_USER_AGENT', 'Slinky v1.0 +http://dentedreality.com.au/projects/slinky/' );
39 // How many seconds until remote requests should be cut?
40 define( 'SLINKY_TIMEOUT', 10 );
43 * Slinky allows you to go back and forth between "long" and shortened URLs
44 * using popular URL shortening services.
46 * Slinky assumes you have cURL installed and working, and requires the JSON
47 * extension installed if you're working with a service that uses JSON.
49 * Slinky will ONLY work with PHP5+
51 * It supports some of the more popular services, with easy extensibility for
52 * adding your own relatively easily. It defaults to using TinyURL
53 * for shortening URLs. If you want to use some of the other services, you need
54 * to set some configuration options before actually shortening/lengthening
55 * URLs. I'd strongly suggest that you cache results using memcached, a local
56 * DB or whatever to avoid having to hit APIs etc every time you encounter a
59 * Slinky supports shortening, and auto-detection (for lengthening URLs)
60 * using these services:
71 * - Snipr / Snipurl / Snurl.com / Sn.im
76 * $slinky = new Slinky( 'http://dentedreality.com.au/' );
77 * - Creates a new Slinky instance, will default to using TinyURL for ->short();
79 * $slinky = new Slinky( 'http://dentedreality.com.au', new Slinky_Bitly() );
80 * - Creates new Slinky, forces use of Bit.ly for ->short();
82 * $slinky = new Slinky( 'http://dentedreality.com.au/' );
83 * echo $slinky->short();
84 * - echos the short version of http://dentedreality.com.au/ (default to TinyURL)
86 * $slinky = new Slinky( 'http://tinyurl.com/jw5sh' );
87 * echo $slinky->long();
88 * - echos out the long version of http://tinyurl.com/jw5sh (which should be http://dentedreality.com.au/)
90 * $slinky = new Slinky( 'http://dentedreality.com.au/' );
91 * echo $slinky->long();
92 * - Attempts to lengthen the URL, but will not auto-detect which service it is
93 * so it will just output the original URL. Useful for always attempting to
94 * lengthen any URL you come across (fails gracefully)
96 * $slinky = new Slinky( 'http://dentedreality.com.au/' );
97 * $slinky->set_cascade( array( new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
98 * echo $slinky->short();
99 * - Uses the powerful cascade mode to make sure that we get a short URL from
100 * Tr.im, Is.Gd or TinyURL (in that order).
102 * See specific service class definitions below for examples of how to use them,
103 * as some services allow (or require) additional properties before you can use
104 * them (for authentication etc).
106 * To use a different service with Slinky, just create your own class and
107 * extend Slinky_Service(). Make sure you implement url_is_short(), url_is_long(),
108 * make_short() and make_long(). If you need to GET or POST a URL, you can use
109 * ->url_get() and ->url_post(), which your class will have inherited.
113 var $service = false;
114 var $cascade = false;
116 function __construct( $url = false, $service = false ) {
118 $this->service = $service;
122 * Specify which URL Service to use
124 * @param Slinky_Service $service Packaged or custom Service object
127 public function set_service( $service = false ) {
128 if ( is_object( $service ) ) {
129 $this->service = $service;
134 * If you pass an array of Slinky_Service objects to this method, they will
135 * be used in order to try to get a short URL, so if one fails, it will
136 * try the next and so on, until it gets a valid short URL, or it runs
139 * @param array $services List of Slinky_Service objects as an array
141 public function set_cascade( $services = false ) {
142 if ( !$services || !is_array( $services ) )
145 $this->cascade = $services;
149 * Guess the URL service to use from known domains of short URLs
153 public function set_service_from_url( $url = false ) {
157 $host = parse_url( $url, PHP_URL_HOST );
158 switch ( str_replace( 'www.', '', $host ) ) {
160 if ( class_exists( 'Slinky_Bitly' ) ) {
161 $this->service = new Slinky_Bitly();
165 if ( class_exists( 'Slinky_Trim' ) ) {
166 $this->service = new Slinky_Trim();
170 if ( class_exists( 'Slinky_TinyURL' ) ) {
171 $this->service = new Slinky_TinyURL();
175 if ( class_exists( 'Slinky_IsGd' ) ) {
176 $this->service = new Slinky_IsGd();
180 if ( class_exists( 'Slinky_Fongs' ) ) {
181 $this->service = new Slinky_Fongs();
185 if ( class_exists( 'Slinky_Micurl' ) ) {
186 $this->service = new Slinky_Micurl();
190 if ( class_exists( 'Slinky_Ur1ca' ) ) {
191 $this->service = new Slinky_Ur1ca();
195 if ( class_exists( 'Slinky_PtitURL' ) ) {
196 $this->service = new Slinky_PtitURL();
201 if ( class_exists( 'Slinky_TightURL' ) ) {
202 $this->service = new Slinky_TightURL();
209 if ( class_exists( 'Slinky_Snipr' ) ) {
210 $this->service = new Slinky_Snipr();
214 $this->service = new Slinky_Default();
220 * Take a long URL and make it short. Will avoid "re-shortening" a URL if it
221 * already seems to be short.
223 * @param string $url Optional URL to shorten, otherwise use $this->url
224 * @return The short version of the URL
226 public function short( $url = false ) {
230 if ( !$this->service )
231 $this->set_service( new Slinky_TinyURL() ); // Defaults to tinyurl because it doesn't require any configuration
233 if ( !$this->cascade )
234 $this->cascade = array( $this->service ); // Use a single service in cascade mode
236 foreach ( $this->cascade as $service ) {
237 if ( $service->url_is_short( $this->url ) )
238 return trim( $this->url ); // Identified as already short, using this service
240 $response = trim( $service->make_short( $this->url ) );
241 if ( $response && $this->url != $response )
242 return trim( $response );
245 return $this->url; // If all else fails, just send back the URL we already know about
249 * Take a short URL and make it long ("resolve" it).
251 * @param string $url The short URL
254 public function long( $url = false ) {
258 if ( !$this->service )
259 $this->set_service_from_url();
261 if ( $this->service->url_is_long( $this->url ) )
262 return trim( $this->url );
264 return trim( $this->service->make_long( $this->url ) );
269 * Use this class to create a Service implementation for your own URL
270 * shortening service. Extend the class and customize methods to suit your
271 * service. Note that it is an "abstract" class, so there are certain methods
272 * which you *must* define.
274 abstract class Slinky_Service {
277 * Determine, based on the input URL, if it's already a short URL, from
278 * this particular service. e.g. a Bit.ly URL contains "bit.ly/"
280 abstract function url_is_short( $url );
283 * Determine if this is a "long" URL (just means it hasn't been shortened)
284 * already. e.g. a no-Bit.ly URL would NOT contain "bit.ly/"
286 abstract function url_is_long( $url );
289 * Handle taking the $url and converting it to a short URL via whatever
290 * means is provided at the remote service.
292 abstract function make_short( $url );
295 * Return the long/expanded version of a URL via any API means available
296 * from this service. As a fallback, you might
297 * consider just following the URL and using SLINKY_FINAL_URL as the
298 * return method from a $this->url_get() call to find out.
300 * This one is optional for Services extending this class, if they don't
301 * then the following implementation will work on most services anyway.
303 public function make_long( $url ) {
304 return $this->url_get( $url, SLINKY_FINAL_URL );
308 * Method for getting properties that you might need during the process
309 * of shortening/lengthening a URL (e.g. auth credentials)
311 public function get( $prop ) {
312 if ( empty( $this->$prop ) )
319 * Method for setting properties that you might need during the process
320 * of shortening/lengthening a URL (e.g. auth credentials)
322 public function set( $prop, $val ) {
327 * Internal helper for performing a GET request on a remote URL.
329 * @param string $url The URL to GET
330 * @param const $return The return method [ SLINKY_BODY | SLINKY_FINAL_URL | SLINKY_HEADERS ]
331 * @return Mixed, based on the $return var passed in.
333 protected function url_get( $url, $return = SLINKY_BODY ) {
334 $ch = curl_init( $url );
335 curl_setopt( $ch, CURLOPT_USERAGENT, SLINKY_USER_AGENT );
336 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 ); // Don't stress about SSL validity
337 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // Return the response, don't output it
338 curl_setopt( $ch, CURLOPT_TIMEOUT, SLINKY_TIMEOUT ); // Limit how long we'll wait for a response
339 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); // Allow following of redirections
340 $r = curl_exec( $ch );
341 if ( curl_errno( $ch ) ) {
345 // Return whatever we were asked for
346 if ( SLINKY_FINAL_URL == $return )
347 return curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
348 else if ( SLINKY_BODY == $return )
355 * Internal helper for performing a POST request on a remote URL.
357 * @param string $url The URL to POST to
358 * @param array $payload Array containing name/value pairs of the parameters to POST
359 * @param const $return The return method [ SLINKY_BODY | SLINKY_FINAL_URL | SLINKY_HEADERS ]
360 * @return Mixed, based on the $return var passed in.
362 protected function url_post( $url, $payload = array(), $return = SLINKY_BODY ) {
363 $ch = curl_init( $url );
364 curl_setopt( $ch, CURLOPT_POST, true );
365 curl_setopt( $ch, CURLOPT_POSTFIELDS, (array) $payload );
366 curl_setopt( $ch, CURLOPT_USERAGENT, SLINKY_USER_AGENT );
367 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 ); // Don't stress about SSL validity
368 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // Return the response, don't output it
369 curl_setopt( $ch, CURLOPT_TIMEOUT, SLINKY_TIMEOUT ); // Limit how long we'll wait for a response
370 curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); // Allow following of redirections
371 $r = curl_exec( $ch );
372 if ( curl_errno( $ch ) ) {
376 // Return whatever we were asked for
377 if ( SLINKY_FINAL_URL == $return )
378 return curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
379 else if ( SLINKY_BODY == $return )
386 // This default service is used in cases when you try to do something based
387 // on auto-detection, but we can't detect anything. It will also resolve URLs
388 // to their "long" version by following all redirects.
389 class Slinky_Default extends Slinky_Service {
390 function url_is_short( $url ) {
394 function url_is_long( $url ) {
398 function make_short( $url ) {
403 // Implementation of TinyURL as a Slinky Service
404 class Slinky_TinyURL extends Slinky_Service {
405 function url_is_short( $url ) {
406 return stristr( $url, 'tinyurl.com/' );
409 function url_is_long( $url ) {
410 return !stristr( $url, 'tinyurl.com/' );
413 function make_short( $url ) {
414 return $this->url_get( 'http://tinyurl.com/api-create.php?url=' . urlencode( $url ) );
417 function make_long( $url ) {
418 $bits = parse_url( $url );
419 $result = $this->url_get( 'http://tinyurl.com/preview.php?num=' . substr( $bits['path'], 1 ) );
420 if ( preg_match('/<a id="redirecturl" href="([^"]+)">/is', $result, $matches ) )
427 // Implementation of Bit.ly as a Slinky Service
429 To use Bit.ly, you MUST set your login and apiKey for the service first, e.g.
431 $bitly = new Slinky_Bitly();
432 $bitly->set( 'login', 'bitly_login' );
433 $bitly->set( 'apiKey', 'bitly_apiKey' );
435 $slinky = new Slinky( $url, $bitly );
436 echo $slinky->short();
438 You could also do this if the URL was already a bit.ly URL and you
439 were going to make it longer, since Bitly is supported for auto-detection:
441 $slinky = new Slinky( $url );
442 $slinky->set_service_from_url();
443 $slinky->service->set( 'login', 'bitly_login' );
444 $slinky->service->set( 'apiKey', 'bitly_apiKey' );
445 echo $slinky->long();
448 class Slinky_Bitly extends Slinky_Service {
449 function url_is_short( $url ) {
450 return stristr( $url, 'bit.ly/' );
453 function url_is_long( $url ) {
454 return !stristr( $url, 'bit.ly/' );
457 function make_short( $url ) {
458 // Can't do anything unless these 2 properties are set first
459 if ( !$this->get( 'login' ) || !$this->get( 'apiKey' ) )
462 $result = $this->url_post( 'http://api.bit.ly/shorten?version=2.0.1&format=json&login=' . $this->get( 'login' ) . '&apiKey=' . $this->get( 'apiKey' ) . '&longUrl=' . urlencode( $url ) );
463 $result = json_decode( $result );
464 if ( !$result->errorCode ) {
465 foreach ( $result->results as $detail ) {
466 return $detail->shortUrl;
473 function make_long( $url ) {
474 // Can't do anything unless these 2 properties are set first
475 if ( !$this->get( 'login' ) || !$this->get( 'apiKey' ) )
478 $result = $this->url_post( 'http://api.bit.ly/expand?version=2.0.1&format=json&login=' . $this->get( 'login' ) . '&apiKey=' . $this->get( 'apiKey' ) . '&shortUrl=' . urlencode( $url ) );
479 $result = json_decode( $result );
480 if ( !$result->errorCode ) {
481 foreach ( $result->results as $detail ) {
482 return $detail->longUrl;
490 // Implementation of Tr.im as a Slinky Service
492 When using Tr.im, you MAY optionally set your username and password to tie
493 URLs to your account, e.g.
495 $trim = new Slinky_Trim();
496 $trim->set( 'username', 'trim_username' );
497 $trim->set( 'password', 'trim_password' );
499 $slinky = new Slinky( $url, $trim );
500 echo $slinky->short();
502 You could also do this if the URL was already a tr.im URL and you
503 were going to make it longer, since Tr.im is supported for auto-detection:
505 $slinky = new Slinky( $url );
506 $slinky->set_service_from_url();
507 echo $slinky->long();
510 class Slinky_Trim extends Slinky_Service {
511 function url_is_short( $url ) {
512 return stristr( $url, 'tr.im/' );
515 function url_is_long( $url ) {
516 return !stristr( $url, 'tr.im/' );
519 function make_short( $url ) {
520 $url = 'http://api.tr.im/api/trim_simple?url=' . urlencode( $url );
522 if ( $this->get( 'username' ) && $this->get( 'password' ) )
523 $url .= '&username=' . urlencode( $this->get( 'username' ) ) . '&password=' . urlencode( $this->get( 'password' ) );
525 return $this->url_get( $url );
528 function make_long( $url ) {
529 $bits = parse_url( $url );
530 $result = $this->url_get( 'http://api.tr.im/api/trim_destination.json?trimpath=' . substr( $bits['path'], 1 ) );
531 $result = json_decode($result);
532 if ( 'OK' == $result->status->result )
533 return $result->destination;
539 // Implementation of Is.Gd as a Slinky Service
540 class Slinky_IsGd extends Slinky_Service {
541 function url_is_short( $url ) {
542 return stristr( $url, 'is.gd/' );
545 function url_is_long( $url ) {
546 return !stristr( $url, 'is.gd/' );
549 function make_short( $url ) {
550 $response = $this->url_get( 'http://is.gd/api.php?longurl=' . urlencode( $url ) );
551 if ( 'error' == substr( strtolower( $response ), 0, 5 ) )
559 class Slinky_Fongs extends Slinky_Service {
560 function url_is_short( $url ) {
561 return stristr( $url, 'fon.gs/' );
564 function url_is_long( $url ) {
565 return !stristr( $url, 'fon.gs/' );
568 function make_short( $url ) {
569 $response = $this->url_get( 'http://fon.gs/create.php?url=' . urlencode( $url ) );
570 if ( 'OK:' == substr( $response, 0, 3 ) )
571 return str_replace( 'OK: ', '', $response );
578 class Slinky_Micurl extends Slinky_Service {
579 function url_is_short( $url ) {
580 return stristr( $url, 'micurl.com/' );
583 function url_is_long( $url ) {
584 return !stristr( $url, 'micurl.com/' );
587 function make_short( $url ) {
588 $result = $this->url_get( 'http://micurl.com/api.php?url=' . urlencode( $url ) );
589 if ( 1 != $result && 2 != $result )
590 return 'http://micurl.com/' . $result;
597 class Slinky_Ur1ca extends Slinky_Service {
598 function url_is_short( $url ) {
599 return stristr( $url, 'ur1.ca/' );
602 function url_is_long( $url ) {
603 return !stristr( $url, 'ur1.ca/' );
606 function make_short( $url ) {
607 $result = $this->url_post( 'http://ur1.ca/', array( 'longurl' => $url ) );
608 if ( preg_match( '/<p class="success">Your ur1 is: <a href="([^"]+)">/', $result, $matches ) )
616 class Slinky_PtitURL extends Slinky_Service {
617 function url_is_short( $url ) {
618 return stristr( $url, 'ptiturl.com/' );
621 function url_is_long( $url ) {
622 return !stristr( $url, 'ptiturl.com/' );
625 function make_short( $url ) {
626 $result = $this->url_get( 'http://ptiturl.com/index.php?creer=oui&url=' . urlencode( $url ) );
627 if ( preg_match( '/<pre><a href=\'?([^\'>]+)\'?>/', $result, $matches ) )
635 class Slinky_TightURL extends Slinky_Service {
636 function url_is_short( $url ) {
637 return stristr( $url, 'tighturl.com/' )
638 || stristr( $url, '2tu.us/' );
641 function url_is_long( $url ) {
642 return !stristr( $url, 'tighturl.com/' )
643 && !stristr( $url, '2tu.us/' );
646 function make_short( $url ) {
647 $response = $this->url_get( 'http://tighturl.com/?save=y&url=' . urlencode( $url ) );
648 if ( preg_match( '/Your tight URL is: <code><a href=\'([^\']+)\' target=\'_blank\'>/', $response, $matches ) ) {
658 To use Snipr, you MUST set your user_id and API (key) for the service first, e.g.
660 $snipr = new Slinky_Snipr();
661 $snipr->set( 'user_id', 'Snipr User ID' );
662 $snipr->set( 'API', 'Snipr API Key' );
664 $slinky = new Slinky( $url, $snipr );
665 echo $slinky->short();
667 NOTE: Snipr requires the SimpleXML extension to be installed for lengthening URLs
669 class Slinky_Snipr extends Slinky_Service {
670 // Snipurl, Snurl, Snipr, Sn.im
671 function url_is_short( $url ) {
672 return stristr( $url, 'snipr.com/' ) || stristr( $url, 'snipurl.com/' ) || stristr( $url, 'snurl.com/' ) || stristr( $url, 'sn.im/' );
675 function url_is_long( $url ) {
676 return !stristr( $url, 'snipr.com/' ) || !stristr( $url, 'snipurl.com/' ) || !stristr( $url, 'snurl.com/' ) || !stristr( $url, 'sn.im/' );
679 function make_short( $url ) {
680 if ( !$this->get( 'user_id' ) || !$this->get( 'API' ) )
683 $response = $this->url_post( 'http://snipr.com/site/getsnip', array( 'sniplink' => urlencode( $url ), 'snipuser' => $this->get( 'user_id'), 'snipapi' => $this->get( 'API' ), 'snipformat' => 'simple' ) );
684 if ( 'ERROR' != substr( $response, 0, 5 ) )
691 // If you're testing things out, http://dentedreality.com.au/ should convert to:
692 // - http://tinyurl.com/jw5sh
693 // - http://bit.ly/hEkAD
694 // - http://tr.im/sk1H
695 // - http://is.gd/1yJ81
696 // - http://fon.gs/tc1p8c
697 // - http://micurl.com/qen3uub
698 // - http://ur1.ca/7dcd
699 // - http://ptiturl.com/?id=bac8fb
700 // - http://tighturl.com/kgd
701 // - http://snipr.com/nbbw3
703 // $slinky = new Slinky( 'http://dentedreality.com.au/' );
704 // echo $slinky->short();