* - are NOT cryptographically secure (they CAN be forged)
*
* In Shaarli, they are used as a tinyurl-like link to individual entries,
- * e.g. smallHash('20111006_131924') --> yZH23w
+ * built once with the combination of the date and item ID.
+ * e.g. smallHash('20111006_131924' . 142) --> eaWxtQ
+ *
+ * @warning before v0.8.1, smallhashes were built only with the date,
+ * and their value has been preserved.
+ *
+ * @param string $text Create a hash from this text.
+ *
+ * @return string generated small hash.
*/
function smallHash($text)
{
/**
* Tells if a string start with a substring
+ *
+ * @param string $haystack Given string.
+ * @param string $needle String to search at the beginning of $haystack.
+ * @param bool $case Case sensitive.
+ *
+ * @return bool True if $haystack starts with $needle.
*/
-function startsWith($haystack, $needle, $case=true)
+function startsWith($haystack, $needle, $case = true)
{
if ($case) {
return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
/**
* Tells if a string ends with a substring
+ *
+ * @param string $haystack Given string.
+ * @param string $needle String to search at the end of $haystack.
+ * @param bool $case Case sensitive.
+ *
+ * @return bool True if $haystack ends with $needle.
*/
-function endsWith($haystack, $needle, $case=true)
+function endsWith($haystack, $needle, $case = true)
{
if ($case) {
return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
/**
* Htmlspecialchars wrapper
+ * Support multidimensional array of strings.
*
- * @param string $str the string to escape.
+ * @param mixed $input Data to escape: a single string or an array of strings.
*
* @return string escaped.
*/
-function escape($str)
+function escape($input)
{
- return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false);
+ if (is_array($input)) {
+ $out = array();
+ foreach($input as $key => $value) {
+ $out[$key] = escape($value);
+ }
+ return $out;
+ }
+ return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false);
}
/**
}
/**
- * Link sanitization before templating
+ * Sanitize link before rendering.
+ *
+ * @param array $link Link to escape.
*/
function sanitizeLink(&$link)
{
}
/**
- * In a string, converts URLs to clickable links.
+ * Sniff browser language to set the locale automatically.
+ * Note that is may not work on your server if the corresponding locale is not installed.
+ *
+ * @param string $headerLocale Locale send in HTTP headers (e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3").
+ **/
+function autoLocale($headerLocale)
+{
+ // Default if browser does not send HTTP_ACCEPT_LANGUAGE
+ $attempts = array('en_US', 'en_US.utf8', 'en_US.UTF-8');
+ if (isset($headerLocale)) {
+ // (It's a bit crude, but it works very well. Preferred language is always presented first.)
+ if (preg_match('/([a-z]{2,3})[-_]?([a-z]{2})?/i', $headerLocale, $matches)) {
+ $first = [strtolower($matches[1]), strtoupper($matches[1])];
+ $separators = ['_', '-'];
+ $encodings = ['utf8', 'UTF-8'];
+ if (!empty($matches[2])) {
+ $second = [strtoupper($matches[2]), strtolower($matches[2])];
+ $attempts = cartesian_product_generator([$first, $separators, $second, ['.'], $encodings]);
+ } else {
+ $attempts = cartesian_product_generator([$first, $separators, $first, ['.'], $encodings]);
+ }
+ }
+ }
+ setlocale(LC_ALL, implode('implode', iterator_to_array($attempts)));
+}
+
+/**
+ * Build a Generator object representing the cartesian product from given $items.
*
- * @param string $text input string.
- * @param string $redirector if a redirector is set, use it to gerenate links.
+ * Example:
+ * [['a'], ['b', 'c']]
+ * will generate:
+ * [
+ * ['a', 'b'],
+ * ['a', 'c'],
+ * ]
*
- * @return string returns $text with all links converted to HTML links.
+ * @param array $items array of array of string
*
- * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
+ * @return Generator representing the cartesian product of given array.
+ *
+ * @see https://en.wikipedia.org/wiki/Cartesian_product
*/
-function text2clickable($text, $redirector)
+function cartesian_product_generator($items)
{
- $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
-
- if (empty($redirector)) {
- return preg_replace($regex, '<a href="$1">$1</a>', $text);
+ if (empty($items)) {
+ yield [];
+ }
+ $subArray = array_pop($items);
+ if (empty($subArray)) {
+ return;
+ }
+ foreach (cartesian_product_generator($items) as $item) {
+ foreach ($subArray as $value) {
+ yield $item + [count($item) => $value];
+ }
}
- // Redirector is set, urlencode the final URL.
- return preg_replace_callback(
- $regex,
- function ($matches) use ($redirector) {
- return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
- },
- $text
- );
}
/**
- * This function inserts where relevant so that multiple spaces are properly displayed in HTML
- * even in the absence of <pre> (This is used in description to keep text formatting).
+ * Generates a default API secret.
*
- * @param string $text input text.
+ * Note that the random-ish methods used in this function are predictable,
+ * which makes them NOT suitable for crypto.
+ * BUT the random string is salted with the salt and hashed with the username.
+ * It makes the generated API secret secured enough for Shaarli.
*
- * @return string formatted text.
+ * PHP 7 provides random_int(), designed for cryptography.
+ * More info: http://stackoverflow.com/questions/4356289/php-random-string-generator
+
+ * @param string $username Shaarli login username
+ * @param string $salt Shaarli password hash salt
+ *
+ * @return string|bool Generated API secret, 12 char length.
+ * Or false if invalid parameters are provided (which will make the API unusable).
*/
-function space2nbsp($text)
+function generate_api_secret($username, $salt)
{
- return preg_replace('/(^| ) /m', '$1 ', $text);
+ if (empty($username) || empty($salt)) {
+ return false;
+ }
+
+ return str_shuffle(substr(hash_hmac('sha512', uniqid($salt), $username), 10, 12));
}
/**
- * Format Shaarli's description
- * TODO: Move me to ApplicationUtils when it's ready.
+ * Trim string, replace sequences of whitespaces by a single space.
+ * PHP equivalent to `normalize-space` XSLT function.
*
- * @param string $description shaare's description.
- * @param string $redirector if a redirector is set, use it to gerenate links.
+ * @param string $string Input string.
*
- * @return string formatted description.
+ * @return mixed Normalized string.
*/
-function format_description($description, $redirector = false) {
- return nl2br(space2nbsp(text2clickable($description, $redirector)));
+function normalize_spaces($string)
+{
+ return preg_replace('/\s{2,}/', ' ', trim($string));
}
/**
- * Sniff browser language to set the locale automatically.
- * Note that is may not work on your server if the corresponding locale is not installed.
+ * Format the date according to the locale.
*
- * @param string $headerLocale Locale send in HTTP headers (e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3").
- **/
-function autoLocale($headerLocale)
+ * Requires php-intl to display international datetimes,
+ * otherwise default format '%c' will be returned.
+ *
+ * @param DateTime $date to format.
+ * @param bool $intl Use international format if true.
+ *
+ * @return bool|string Formatted date, or false if the input is invalid.
+ */
+function format_date($date, $intl = true)
{
- // Default if browser does not send HTTP_ACCEPT_LANGUAGE
- $attempts = array('en_US');
- if (isset($headerLocale)) {
- // (It's a bit crude, but it works very well. Preferred language is always presented first.)
- if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) {
- $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
- $attempts = array(
- $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
- $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
- $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
- $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
- );
- }
+ if (! $date instanceof DateTime) {
+ return false;
+ }
+
+ if (! $intl || ! class_exists('IntlDateFormatter')) {
+ return strftime('%c', $date->getTimestamp());
}
- setlocale(LC_ALL, $attempts);
-}
\ No newline at end of file
+
+ $formatter = new IntlDateFormatter(
+ setlocale(LC_TIME, 0),
+ IntlDateFormatter::LONG,
+ IntlDateFormatter::LONG
+ );
+
+ return $formatter->format($date);
+}