shurly.module in ShURLy 8
Same filename and directory in other branches
Description http://www.youtube.com/watch?v=Qo7qoonzTCE.
@todo click to copy link as a Views field @todo add some watchdog logging @todo remove "http://" from the long URL field when you click in @todo add hook for other modules to create additional/substitute long URL validation @todo add option/permission to reactivate URLs
File
shurly.moduleView source
<?php
/**
* @file
* Description http://www.youtube.com/watch?v=Qo7qoonzTCE.
*
* @todo click to copy link as a Views field
* @todo add some watchdog logging
* @todo remove "http://" from the long URL field when you click in
* @todo add hook for other modules to create additional/substitute long URL validation
* @todo add option/permission to reactivate URLs
*/
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Core\Language\Language;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_page_attachments().
*/
function shurly_page_attachments(array &$attachments) {
if (\Drupal::routeMatch()
->getRouteName() == 'shurly.create') {
$attachments['#attached']['library'][] = 'shurly/clipboardjs';
$attachments['#attached']['library'][] = 'shurly/shurly';
}
}
/**
* Implements hook_help().
*/
function shurly_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case "help.page.shurly":
return '<div style="white-space:pre-wrap">' . htmlentities(file_get_contents('README.txt', FILE_USE_INCLUDE_PATH)) . '</div>';
}
}
/**
* From http://www.php.net/manual/en/function.base-convert.php#52450.
*
* Parameters:
* $num - your decimal integer
* $base - base to which you wish to convert $num (leave it 0 if you are
* providing $index or omit if you're using default (62))
* $index - if you wish to use the default list of digits (0-1a-zA-Z), omit
* this option, otherwise provide a string (ex.: "zyxwvu")
*/
function shurly_dec2any($num, $base = 62, $index = FALSE) {
if (!$base) {
$base = strlen($index);
}
elseif (!$index) {
// note: we could rearrange this string to get more random looking URLs
// another note, to create printable URLs, omit the following characters: 01lIO.
$index = substr("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, $base);
}
$out = "";
for ($t = floor(log10($num) / log10($base)); $t >= 0; $t--) {
$a = floor($num / pow($base, $t));
$out = $out . substr($index, $a, 1);
$num = $num - $a * pow($base, $t);
}
return $out;
}
/* *****************************************************
* Flood Control
* *****************************************************
*/
/**
* Implements hook_cron().
*/
function shurly_cron() {
// Cleanup the flood.
\Drupal::database()
->delete('shurly_flood')
->condition('expiration', time(), '<')
->execute();
}
/**
* Function to store the flood event.
*/
function shurly_flood_register_event($name, $window = 3600, $identifier = NULL) {
if (!isset($identifier)) {
$identifier = \Drupal::request()
->getClientIp();
}
$request_time = \Drupal::time()
->getRequestTime();
\Drupal::database()
->query("INSERT INTO {shurly_flood} (event, identifier, timestamp, expiration) VALUES (:event, :identifier, :timestamp, :expiration)", [
'event' => $name,
'identifier' => \Drupal::request()
->getClientIp(),
'timestamp' => $request_time,
'expiration' => time() + $window,
]);
}
/**
* Function to check if the current user
* is in the flood table.
*/
function shurly_flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
if (!isset($identifier)) {
$identifier = \Drupal::request()
->getClientIp();
}
$request_time = \Drupal::time()
->getRequestTime();
$number = \Drupal::database()
->query("SELECT COUNT(*) FROM {shurly_flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", [
'event' => $name,
'identifier' => $identifier,
'timestamp' => $request_time - $window,
])
->fetchField();
return $number < $threshold;
}
/* *****************************************************
* API functions
* *****************************************************
*/
/**
* API function to shorten a URL.
*
* @arg $long_url - the long URL to shorten
* @arg $custom - optional custom short URL
*
* @return an array with the following keys
* 'success' => TRUE or FALSE
* 'error' => reason for for failure
* 'long_url' => the long url
* 'short_url' => the short url
*/
function shurly_shorten($long_url, $custom = NULL, $account = NULL) {
$success = FALSE;
$account = $account ? $account : \Drupal::currentUser();
$error = '';
$no_save = FALSE;
$rate_limit = shurly_rate_limit_allowed($account);
if (!$rate_limit['allowed']) {
$error = t('Rate limit exceeded. You are limited to @rate requests per @time minute period.', [
'@rate' => $rate_limit['rate'],
'@time' => $rate_limit['time'],
]);
}
elseif (!shurly_validate_long($long_url)) {
$error = t('Invalid long URL.');
}
elseif (is_null($custom)) {
$latest = shurly_get_latest_short($long_url, $account
->id());
if ($latest) {
$no_save = TRUE;
$success = TRUE;
$short = $latest;
}
else {
$short = shurly_next_url();
}
}
else {
$short = $custom;
if (!shurly_validate_custom($short) || !shurly_path_available($short)) {
$error .= $error ? ' ' : '';
$error .= t('Invalid short URL.');
}
elseif (shurly_url_exists($short)) {
$error .= $error ? ' ' : '';
$error .= t('Existing short URL.');
}
}
if (!$error && !$no_save) {
if (shurly_save_url($long_url, $short, $account, $custom)) {
$success = TRUE;
}
else {
$error = t('Unknown database error.');
}
}
return [
'success' => $success,
'error' => $error,
'longUrl' => $long_url,
'shortUrl' => isset($short) ? _surl($short, [
'absolute' => TRUE,
]) : '',
];
}
/**
* Function to get the long url.
*/
function shurly_expand($short, $account = NULL) {
global $base_url;
$error = '';
$success = FALSE;
$rate_limit = shurly_rate_limit_allowed($account);
if (!$rate_limit['allowed']) {
$error = t('Rate limit exceeded. You are limited to @rate requests per @time minute period.', [
'@rate' => $rate_limit['rate'],
'@time' => $rate_limit['time'],
]);
}
elseif ($redirect = shurly_get_redirect($short, TRUE)) {
$success = TRUE;
$long_url = $redirect->destination;
}
else {
$error = t('Not found');
}
return [
'success' => $success,
'error' => $error,
'longUrl' => $long_url,
'shortUrl' => _surl($short, [
'absolute' => TRUE,
]),
];
}
/**
* Check rate limit for this user
* return an array in the following format
* array(
* 'allowed' => TRUE/FALSE
* 'rate' => number of requests allowed
* 'time' => period of time in minutes
* )
*/
function shurly_rate_limit_allowed($account = NULL) {
if (!isset($account)) {
$account = \Drupal::currentUser();
}
// @FIXME
// Could not extract the default value because it is either indeterminate, or
// not scalar. You'll need to provide a default value in
// config/install/shurly.settings.yml and config/schema/shurly.schema.yml.
$settings = \Drupal::config('shurly.settings')
->get('shurly_throttle');
// Get the roles of a user.
$roles = $account
->getRoles();
if (is_array($roles)) {
$rids = array_keys($roles);
$use_rid = array_shift($rids);
// Get list of roles with permission to create short URLs.
$creating_roles = user_roles(FALSE, 'Create short URLs');
foreach ($roles as $rid => $name) {
// Check that this role has permission to create URLs, otherwise discard it.
if (array_key_exists($rid, $creating_roles)) {
// Find the lightest role... if roles are the same weight, use the next role.
$settings[$use_rid]['weight'] = isset($settings[$use_rid]['weight']) ? $settings[$use_rid]['weight'] : 0;
$settings[$rid]['weight'] = isset($settings[$rid]['weight']) ? $settings[$rid]['weight'] : 0;
$use_rid = $settings[$use_rid]['weight'] < $settings[$rid]['weight'] ? $use_rid : $rid;
// Create array index if not exists for rate and time.
$settings[$use_rid]['rate'] = isset($settings[$use_rid]['rate']) ? $settings[$use_rid]['rate'] : NULL;
$settings[$use_rid]['time'] = isset($settings[$use_rid]['time']) ? $settings[$use_rid]['time'] : NULL;
}
}
}
if (!empty($settings) && is_numeric($settings[$use_rid]['rate']) && is_numeric($settings[$use_rid]['time'])) {
// See if it's allowed.
$allowed = shurly_flood_is_allowed('shurly', $settings[$use_rid]['rate'], $settings[$use_rid]['time'] * 60);
// Increment the counter.
shurly_flood_register_event('shurly', $settings[$use_rid]['time'] * 60);
$return = [
'allowed' => $allowed,
'rate' => $settings[$use_rid]['rate'],
'time' => $settings[$use_rid]['time'],
];
}
else {
// Not set... don't do a flood check.
$return = [
'allowed' => TRUE,
];
}
return $return;
}
/**
* API function to save a URL.
*
* @arg $custom is a TRUE/FALSE
*/
function shurly_save_url($long_url, $short_path, $account = NULL, $custom = NULL) {
if (empty($account)) {
$account = \Drupal::currentUser();
}
$request_time = \Drupal::time()
->getRequestTime();
$record = [];
$record['destination'] = $long_url;
$record['hash'] = md5($long_url);
$record['custom'] = $custom ? 1 : 0;
$record['created'] = $request_time;
$record['source'] = $short_path;
$record['uid'] = $account
->id();
$record['count'] = $record['last_used'] = 0;
$record['active'] = 1;
return \Drupal::database()
->insert('shurly')
->fields($record)
->execute();
}
/**
* Activate or deactivate a link.
*/
function shurly_set_link_active($rid, $active) {
$record = \Drupal::database()
->query('SELECT * FROM {shurly} WHERE rid = :rid', [
'rid' => $rid,
])
->fetchObject();
if ($record) {
$rid = $record->rid;
$active = $active ? 1 : 0;
return \Drupal::database()
->merge('shurly')
->fields([
'rid' => $rid,
'active' => $active,
])
->key([
'rid' => $rid,
])
->execute();
}
else {
return FALSE;
}
}
/**
* Validate custom short URL string.
*
* @return TRUE if valid, FALSE if invalid
*/
function shurly_validate_custom($custom) {
// Check the length of the string.
if (strlen($custom) == 0) {
return FALSE;
}
// disallow: #%&@*{}\:;<>?/+.,'"$|`^[] and space character
// return preg_match('/[#%&\@\*\{\}\\:\;<>\?\+ \.\,\'\"\$\|`^\[\]]/u', $custom) ? FALSE : TRUE;.
return preg_match('/[\\/#%&\\@\\*\\{\\}\\:\\;<>\\?\\+ \\.\\,\'\\"\\$\\|`^\\[\\]]/u', $custom) ? FALSE : TRUE;
}
/**
* Validate a long URL.
*
* Checks for:
* - a valid URL
* - it's not a link to an existing short URL.
*
* @param
* $long url - the long URL entered by user
*
* @return
* BOOLEAN - TRUE if valid, FALSE if invalid
*/
function shurly_validate_long(&$long_url) {
$return = TRUE;
$match = FALSE;
// If the person didn't remove the original http:// from the field, pull it out.
$long_url = preg_replace('!^http\\://(http\\://|https\\://)!i', '\\1', $long_url);
$long_parse = parse_url($long_url);
$base_parse = parse_url($GLOBALS['base_url']);
$check_ip = \Drupal::config('shurly.settings')
->get('shurly_forbid_ips');
$check_localhost = \Drupal::config('shurly.settings')
->get('shurly_forbid_localhost');
$check_resolvability = \Drupal::config('shurly.settings')
->get('shurly_forbid_unresolvable_hosts');
$check_private_ip_ranges = \Drupal::config('shurly.settings')
->get('shurly_forbid_private_ips');
if ($long_parse === FALSE || !isset($long_parse['host'])) {
// Malformed URL
// or no host in the URL.
$return = FALSE;
}
elseif ($long_parse['scheme'] != 'http' && $long_parse['scheme'] != 'https') {
$return = FALSE;
}
elseif ($check_ip && preg_match('/^\\d/', $long_parse['host'])) {
// Host is given as IP address instead of a common hostname.
$return = FALSE;
// @todo Rework condition with respect to RFC 1123, which allows hostnames
// starting with a digit.
}
elseif ($check_localhost && shurly_host_is_local($long_parse['host'], TRUE)) {
// Host seems to be the local host.
$return = FALSE;
}
elseif ($check_resolvability && !shurly_host_is_resolveable($long_parse['host'], TRUE)) {
// Host cannot be resolved (at least not by this server!).
$return = FALSE;
}
elseif ($check_private_ip_ranges && shurly_host_is_private($long_parse['host'], TRUE)) {
// Host refers to a private IP address.
$return = FALSE;
}
else {
if (\Drupal::config('shurly.settings')
->get('shurly_forbid_custom')) {
$custom_pattern = \Drupal::config('shurly.settings')
->get('shurly_custom_restriction');
if (!empty($custom_pattern)) {
if (preg_match($custom_pattern, $long_url)) {
$return = FALSE;
}
}
}
$long_domain_parts = explode('.', $long_parse['host']);
$base_domain_parts = explode('.', $base_parse['host']);
$count_long_domain = count($long_domain_parts);
$last_long_part = isset($long_domain_parts[$count_long_domain - 1]) ? $long_domain_parts[$count_long_domain - 1] : NULL;
$last_base_part = isset($base_domain_parts[$count_long_domain - 1]) ? $base_domain_parts[$count_long_domain - 1] : NULL;
// If last domain part of entered URL matches last part of this domain.
if ($last_long_part == $last_base_part) {
// And (if there's a 2nd to last)
if ($count_long_domain >= 2) {
$last_long_penult = isset($long_domain_parts[$count_long_domain - 2]) ? $long_domain_parts[$count_long_domain - 2] : NULL;
$last_base_penult = isset($base_domain_parts[$count_long_domain - 2]) ? $base_domain_parts[$count_long_domain - 2] : NULL;
// Check that 2nd to last matches.
if ($last_long_penult == $last_base_penult) {
// Last 2 parts link to this domain.
$match = TRUE;
}
}
else {
// there's only one part, and it links here.
$match = TRUE;
}
// We only get down here if the long URL links to this domain
// by the way, we're ignoring any subdomain...
// so http://lbt.me/something and http://www.lbt.me/something are assumed to be the same.
if ($match) {
$queries = [];
if (isset($long_parse['query'])) {
// let's see if there's a $_GET['q'] in the long URL.
$query = $long_parse['query'];
$query = html_entity_decode($query);
$query_array = explode('&', $query);
foreach ($query_array as $val) {
$x = explode('=', $val);
$queries[$x[0]] = $x[1];
}
}
if (isset($queries['q'])) {
// If there's a 'q' query, Drupal uses this instead of anything in the path.
$path = $queries['q'];
}
else {
$path = $long_parse['path'];
}
// See if this is a link to an existing shortURL
// remove the leading "/" from path, if it exists.
$path = explode('/', $path, 2);
$path = array_pop($path);
if ($path) {
// Get the base path of this Drupal install.
$base = explode('/', base_path(), 2);
$base = array_pop($base);
// Remove the base from the path.
if ($base) {
$path = preg_replace('!' . preg_quote($base, '!') . '!i', '', $path);
}
if (shurly_url_exists($path)) {
$return = FALSE;
}
}
}
}
}
return $return;
}
/**
* Generate a random short URL
* Pretty much unused at this point
* this method could take a LOOOONG time on a site with lots of URLs
*/
function shurly_generate_random($len = NULL) {
if ($len == NULL) {
$len = \Drupal::config('shurly.settings')
->get('shurly_length');
}
$charset = "abcdefghijklmnopqrstuvwxyz123456789";
$charlen = strlen($charset) - 1;
do {
$str = '';
for ($i = 0; $i < $len; $i++) {
$str .= $charset[mt_rand(0, $charlen)];
}
// Check that this string hasn't been used already
// check that the string is a valid (available) path.
} while (shurly_url_exists($str) || !shurly_path_available($str));
// Allow extra operations.
\Drupal::moduleHandler()
->invokeAll('shurly_shorturl_extra', [
$str,
]);
return $str;
}
/**
* Return next available short URL.
*/
function shurly_next_url() {
$count = \Drupal::state()
->get('shurly.settings.shurly_counter', \Drupal::config('shurly.settings')
->get('shurly_counter'));
do {
$count++;
// Counter is stored as base 10
// $index is a-z, A-Z, 0-9, sorted randomly, with confusing characters (01lIO) removed - 57 characters
// a custom index can be created as a variable override in settings.php.
$index = \Drupal::config('shurly.settings')
->get('shurly_index');
$str = shurly_dec2any($count, NULL, $index);
// Check that this string hasn't been used already
// check that the string is a valid (available) path.
} while (shurly_url_exists($str) !== FALSE || shurly_path_available($str) === FALSE);
\Drupal::state()
->set('shurly.settings.shurly_counter', $count);
return $str;
}
/**
* Checks to see if there's a menu handler, path alias, or language prefix for
* a given path
*
* @return TRUE if there are no conflicts
*/
function shurly_path_available($path) {
// Check to see if path represents an enabled language.
$languages = \Drupal::LanguageManager()
->getLanguages();
if (array_key_exists($path, $languages)) {
return FALSE;
}
$return = TRUE;
// See if $path is an alias.
$source = \Drupal::service('path_alias.manager')
->getAliasByPath('/' . $path);
if ($source != $path) {
// If so, set alias source to $path.
$path = $source;
}
$url_object = \Drupal::service('path.validator')
->getUrlIfValid($path);
if ($url_object) {
$return = FALSE;
}
return $return;
}
/**
* Check to see if this short URL already exists.
*/
function shurly_url_exists($short, $long = NULL) {
$redirect = shurly_get_redirect($short);
$return = FALSE;
if ($redirect) {
$return = 'found';
}
if ($long && $redirect && $redirect->destination == $long) {
$return = 'match';
}
return $return;
}
/**
* Given the short URL, return the long one
* NOTE: Always check $redirect->active before using the result
*/
function shurly_get_redirect($short_url, $check_active = FALSE) {
$query = "SELECT * FROM {shurly} WHERE source = :short";
if ($check_active) {
$query .= ' AND active = 1';
}
$redirect = \Drupal::database()
->query($query, [
'short' => $short_url,
])
->fetchObject();
return $redirect;
}
/**
* Get the latest generated short URL by a given user for a given long URL.
*/
function shurly_get_latest_short($long, $uid) {
$hash = md5($long);
return \Drupal::database()
->query("SELECT source FROM {shurly} WHERE hash = :hash AND uid = :uid AND custom = 0 AND active = 1 ORDER BY rid DESC LIMIT 1", [
'hash' => $hash,
'uid' => $uid,
])
->fetchField();
}
/**
* Internal function to format a URL without language prefixing or subdomain
* rewrites
*/
function _surl($path = NULL, $options = []) {
$shurly_base = _shurly_get_shurly_base();
// Set default language object which will avoid redirects and subdomains.
$options['language'] = Language::$defaultValues;
return Url::fromUri($shurly_base . '/' . $path, $options)
->toString();
}
/**
* Internal function to generate a link without language prefixing or subdomain
* rewrites
*/
function _sl($text, $path, $options = []) {
$shurly_base = _shurly_get_shurly_base();
return Link::fromTextAndUrl($text, Url::fromUri($shurly_base . '/' . $path, $options))
->toString();
}
/**
* Return the base url used for the shortlinks.
*
* @return string
*/
function _shurly_get_shurly_base() {
$shurly_base = trim(\Drupal::config('shurly.settings')
->get('shurly_base'));
if (empty($shurly_base)) {
$shurly_base = \Drupal::request()
->getSchemeAndHttpHost();
}
return $shurly_base;
}
/**
* Wrapper function for PHP's `gethostbyname()`.
*
* This function should be used, when multiple encapsulated code parts need to
* resolve a hostname.
*
* @staticvar array $resolved_hosts
* Array of `gethostbyname()` return values.
*
* @param string $hostname
* Hostname to resolve.
*
* @return string
* Resolved host address on success or the input $hostname on failure.
*/
function _shurly_gethostbyname($hostname) {
static $resolved_hosts = [];
if (!isset($resolved_hosts[$hostname])) {
$resolved_hosts[$hostname] = gethostbyname($hostname);
}
return $resolved_hosts[$hostname];
}
/**
* Check whether the given test string matches the pattern of an IP address.
*
* @param string $test_string
* Host address or whatever should be tested.
*
* @return bool
* TRUE if the $test_string matches an IP address pattern; otherwise FALSE.
*/
function _shurly_is_ip_address($test_string) {
if (!!filter_var($test_string, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return TRUE;
}
if (!!filter_var($test_string, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return TRUE;
}
return FALSE;
}
/**
* Check whether the input $hostname can be resolved to a valid IP address.
*
* @param string $hostname
* Hostname to test.
*
* @return bool
* TRUE if the $hostname resolves to a valid IP address; otherwise FALSE.
*/
function shurly_host_is_resolveable($hostname) {
if (_shurly_is_ip_address($hostname)) {
return TRUE;
}
elseif (_shurly_is_ip_address(_shurly_gethostbyname($hostname))) {
return TRUE;
}
return FALSE;
}
/**
* Check whether the given resolved host is the localhost.
*
* @param string $hostname
* Return value of a `gethostbyname()` call.
*
* @return bool
* TRUE if the resolved hostname matches an IPv4 or IPv6 localhost address;
* otherwise FLASE.
*/
function shurly_host_is_local($hostname) {
$resolved_hostname = _shurly_gethostbyname($hostname);
$local_ip_address_pattern = '/^127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^\\[(?:0*\\:)*?:?0*1\\]$/';
if (preg_match($local_ip_address_pattern, $resolved_hostname)) {
return TRUE;
}
return FALSE;
}
/**
* Check whether the given hostname matches a private IP address.
*
* @param string $hostname
* Hostname to check.
*
* @return bool
* TRUE if the given $hostname matches a private IP address; otherwise FALSE.
*/
function shurly_host_is_private($hostname) {
$resolved_hostname = _shurly_gethostbyname($hostname);
$private_ip_address_pattern = '/^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.)/';
if (preg_match($private_ip_address_pattern, $resolved_hostname)) {
return TRUE;
}
return FALSE;
}
/**
* Check the URL against Google Safe browser for phishing, malware of both.
*
* @param string $url
* URL to check.
*
* @return bool
* TRUE if the given URL matches; otherwise FALSE.
*/
function shurly_gsb($url) {
$client = \Drupal::config('shurly.settings')
->get('shurly_gsb_config_client');
$api_key = \Drupal::config('shurly.settings')
->get('shurly_gsb_apikey');
$gsb_url = "https://safebrowsing.googleapis.com/v4/threatMatches:find?key=" . $api_key;
$rawdata = [
'client' => [
"clientId" => "lccx",
"clientVersion" => "1.5.2",
],
'threatInfo' => [
"threatTypes" => [
"MALWARE",
"SOCIAL_ENGINEERING",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION",
],
"platformTypes" => [
"ALL_PLATFORMS",
],
"threatEntryTypes" => [
"URL",
],
"threatEntries" => [
[
"url" => $url,
],
],
],
];
$data = json_encode($rawdata);
$client = \Drupal::httpClient();
$request = $client
->createRequest('POST', $gsb_url, $data);
$request
->addHeader('Content-Type', 'application/json');
try {
$response = $client
->get($gsb_url, [
'headers' => [
'Content-Type',
'application/json',
],
]);
$data = json_decode($response
->getBody());
if ($data->matches) {
return TRUE;
}
else {
return FALSE;
}
} catch (RequestException $e) {
watchdog_exception('shurly', $e);
}
}
function shurly_disable_url(&$shurly_item, $context) {
shurly_set_link_active($shurly_item->rid, 0);
}
Functions
Name | Description |
---|---|
shurly_cron | Implements hook_cron(). |
shurly_dec2any | From http://www.php.net/manual/en/function.base-convert.php#52450. |
shurly_disable_url | |
shurly_expand | Function to get the long url. |
shurly_flood_is_allowed | Function to check if the current user is in the flood table. |
shurly_flood_register_event | Function to store the flood event. |
shurly_generate_random | Generate a random short URL Pretty much unused at this point this method could take a LOOOONG time on a site with lots of URLs |
shurly_get_latest_short | Get the latest generated short URL by a given user for a given long URL. |
shurly_get_redirect | Given the short URL, return the long one NOTE: Always check $redirect->active before using the result |
shurly_gsb | Check the URL against Google Safe browser for phishing, malware of both. |
shurly_help | Implements hook_help(). |
shurly_host_is_local | Check whether the given resolved host is the localhost. |
shurly_host_is_private | Check whether the given hostname matches a private IP address. |
shurly_host_is_resolveable | Check whether the input $hostname can be resolved to a valid IP address. |
shurly_next_url | Return next available short URL. |
shurly_page_attachments | Implements hook_page_attachments(). |
shurly_path_available | Checks to see if there's a menu handler, path alias, or language prefix for a given path |
shurly_rate_limit_allowed | Check rate limit for this user return an array in the following format array( 'allowed' => TRUE/FALSE 'rate' => number of requests allowed 'time' => period of time in minutes ) |
shurly_save_url | API function to save a URL. |
shurly_set_link_active | Activate or deactivate a link. |
shurly_shorten | API function to shorten a URL. |
shurly_url_exists | Check to see if this short URL already exists. |
shurly_validate_custom | Validate custom short URL string. |
shurly_validate_long | Validate a long URL. |
_shurly_gethostbyname | Wrapper function for PHP's `gethostbyname()`. |
_shurly_get_shurly_base | Return the base url used for the shortlinks. |
_shurly_is_ip_address | Check whether the given test string matches the pattern of an IP address. |
_sl | Internal function to generate a link without language prefixing or subdomain rewrites |
_surl | Internal function to format a URL without language prefixing or subdomain rewrites |