From 68ea1d2b30db8ab295e47e9ac5f6c8e81676caf7 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 8 Mar 2016 10:00:53 +0100 Subject: Fixes #512: retrieving title didn't match the first closing tag --- application/LinkUtils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/LinkUtils.php b/application/LinkUtils.php index 26dd6b67..d8dc8b5e 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php @@ -9,7 +9,7 @@ */ function html_extract_title($html) { - if (preg_match('!(.*)!is', $html, $matches)) { + if (preg_match('!(.*?)!is', $html, $matches)) { return trim(str_replace("\n", ' ', $matches[1])); } return false; -- cgit v1.2.3 From 8395d0b76145969f4b8940a415af5e46528f04a5 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 10 Mar 2016 18:48:21 +0100 Subject: Adds a route for ATOM and RSS feeds page --- application/Router.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'application') diff --git a/application/Router.php b/application/Router.php index 6185f08e..d98312b5 100644 --- a/application/Router.php +++ b/application/Router.php @@ -15,6 +15,10 @@ class Router public static $PAGE_DAILY = 'daily'; + public static $PAGE_FEED_ATOM = 'atom'; + + public static $PAGE_FEED_RSS = 'rss'; + public static $PAGE_TOOLS = 'tools'; public static $PAGE_CHANGEPASSWORD = 'changepasswd'; @@ -79,6 +83,14 @@ class Router return self::$PAGE_DAILY; } + if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) { + return self::$PAGE_FEED_ATOM; + } + + if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) { + return self::$PAGE_FEED_RSS; + } + // At this point, only loggedin pages. if (!$loggedIn) { return self::$PAGE_LINKLIST; -- cgit v1.2.3 From 69c474b96612dc64fc2cb66f1196251cafa08445 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 10 Mar 2016 19:01:30 +0100 Subject: Refactor showAtom, and make it use the ATOM template Minor changes: * Fix the date which was in a invalid format. * Avoid empty categories (tags). * Use the locale to set the language --- application/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/Utils.php b/application/Utils.php index 3d819716..bcf5bdb5 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -226,7 +226,7 @@ function space2nbsp($text) * * @return string formatted description. */ -function format_description($description, $redirector) { +function format_description($description, $redirector = false) { return nl2br(space2nbsp(text2clickable($description, $redirector))); } -- cgit v1.2.3 From ecd051905f0c8aa97590eb453d553dc678125a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Can=C3=A9vet?= Date: Sun, 8 Nov 2015 23:06:21 +0100 Subject: Fix issue 366, Problem when shaaring a link in Reader View of Firefox. --- application/Url.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'application') diff --git a/application/Url.php b/application/Url.php index a4ac2e73..e7c3a4cc 100644 --- a/application/Url.php +++ b/application/Url.php @@ -125,6 +125,19 @@ class Url } } + + private function removeFirefoxAboutReader($input){ + $output_array = []; + preg_match("%^about://reader\?url=(.*)%", $input, $output_array); + if(!empty($output_array)){ + $extractedUrl = preg_replace("%^about://reader\?url=(.*)%", "$1", $input); + $url = urldecode($extractedUrl); + }else{ + $url = $input; + } + return $url; + } + /** * Returns a string representation of this URL */ @@ -187,7 +200,8 @@ class Url { $this->cleanupQuery(); $this->cleanupFragment(); - return $this->toString(); + $url = $this->toString(); + return $this->removeFirefoxAboutReader($url); } /** -- cgit v1.2.3 From 82e3680203896f024958ae969e2c4fccee9682f4 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 12 Mar 2016 16:08:01 +0100 Subject: Create a FeedBuilder class which build data for both ATOM and RSS feed. --- application/FeedBuilder.php | 295 ++++++++++++++++++++++++++++++++++++++++++++ application/Router.php | 2 +- 2 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 application/FeedBuilder.php (limited to 'application') diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php new file mode 100644 index 00000000..50e09831 --- /dev/null +++ b/application/FeedBuilder.php @@ -0,0 +1,295 @@ +linkDB = $linkDB; + $this->feedType = $feedType; + $this->serverInfo = $serverInfo; + $this->userInput = $userInput; + $this->isLoggedIn = $isLoggedIn; + } + + /** + * Build data for feed templates. + * + * @return array Formatted data for feeds templates. + */ + public function buildData() + { + // Optionally filter the results: + $searchtags = !empty($this->userInput['searchtags']) ? escape($this->userInput['searchtags']) : ''; + $searchterm = !empty($this->userInput['searchterm']) ? escape($this->userInput['searchterm']) : ''; + if (! empty($searchtags) && ! empty($searchterm)) { + $linksToDisplay = $this->linkDB->filter( + LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, + array($searchtags, $searchterm) + ); + } + elseif ($searchtags) { + $linksToDisplay = $this->linkDB->filter(LinkFilter::$FILTER_TAG, $searchtags); + } + elseif ($searchterm) { + $linksToDisplay = $this->linkDB->filter(LinkFilter::$FILTER_TEXT, $searchterm); + } + else { + $linksToDisplay = $this->linkDB; + } + + $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); + + // Can't use array_keys() because $link is a LinkDB instance and not a real array. + $keys = array(); + foreach ($linksToDisplay as $key => $value) { + $keys[] = $key; + } + + $pageaddr = escape(index_url($this->serverInfo)); + $linkDisplayed = array(); + for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) { + $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr); + } + + $data['language'] = $this->getTypeLanguage(); + $data['pubsubhub_url'] = $this->pubsubhubUrl; + $data['last_update'] = $this->getLatestDateFormatted(); + $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; + // Remove leading slash from REQUEST_URI. + $data['self_link'] = $pageaddr . escape(ltrim($this->serverInfo['REQUEST_URI'], '/')); + $data['index_url'] = $pageaddr; + $data['usepermalinks'] = $this->usePermalinks === true; + $data['links'] = $linkDisplayed; + + return $data; + } + + /** + * Build a feed item (one per shaare). + * + * @param array $link Single link array extracted from LinkDB. + * @param string $pageaddr Index URL. + * + * @return array Link array with feed attributes. + */ + protected function buildItem($link, $pageaddr) + { + $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']); + // Check for both signs of a note: starting with ? and 7 chars long. + if ($link['url'][0] === '?' && strlen($link['url']) === 7) { + $link['url'] = $pageaddr . $link['url']; + } + if ($this->usePermalinks === true) { + $permalink = 'Direct link'; + } else { + $permalink = 'Permalink'; + } + $link['description'] = format_description($link['description']) . PHP_EOL .'
— '. $permalink; + + $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); + + if ($this->feedType == self::$FEED_RSS) { + $link['iso_date'] = $date->format(DateTime::RSS); + } else { + $link['iso_date'] = $date->format(DateTime::ATOM); + } + + // Save the more recent item. + if (empty($this->latestDate) || $this->latestDate < $date) { + $this->latestDate = $date; + } + + $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); + uasort($taglist, 'strcasecmp'); + $link['taglist'] = $taglist; + + return $link; + } + + /** + * Assign PubSub hub URL. + * + * @param string $pubsubhubUrl PubSub hub url. + */ + public function setPubsubhubUrl($pubsubhubUrl) + { + $this->pubsubhubUrl = $pubsubhubUrl; + } + + /** + * Set this to true to use permalinks instead of direct links. + * + * @param boolean $usePermalinks true to force permalinks. + */ + public function setUsePermalinks($usePermalinks) + { + $this->usePermalinks = $usePermalinks; + } + + /** + * Set this to true to hide timestamps in feeds. + * + * @param boolean $hideDates true to enable. + */ + public function setHideDates($hideDates) + { + $this->hideDates = $hideDates; + } + + /** + * Set the locale. Used to show feed language. + * + * @param string $locale The locale (eg. 'fr_FR.UTF8'). + */ + public function setLocale($locale) + { + $this->locale = strtolower($locale); + } + + /** + * Get the language according to the feed type, based on the locale: + * + * - RSS format: en-us (default: 'en-en'). + * - ATOM format: fr (default: 'en'). + * + * @return string The language. + */ + public function getTypeLanguage() + { + // Use the locale do define the language, if available. + if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) { + $length = ($this->feedType == self::$FEED_RSS) ? 5 : 2; + return str_replace('_', '-', substr($this->locale, 0, $length)); + } + return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en'; + } + + /** + * Format the latest item date found according to the feed type. + * + * Return an empty string if invalid DateTime is passed. + * + * @return string Formatted date. + */ + protected function getLatestDateFormatted() + { + if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) { + return ''; + } + + $type = ($this->feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM; + return $this->latestDate->format($type); + } + + /** + * Returns the number of link to display according to 'nb' user input parameter. + * + * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS. + * If 'nb' is set to 'all', display all filtered links (max parameter). + * + * @param int $max maximum number of links to display. + * + * @return int number of links to display. + */ + public function getNbLinks($max) + { + if (empty($this->userInput['nb'])) { + return self::$DEFAULT_NB_LINKS; + } + + if ($this->userInput['nb'] == 'all') { + return $max; + } + + $intNb = intval($this->userInput['nb']); + if (! is_int($intNb) || $intNb == 0) { + return self::$DEFAULT_NB_LINKS; + } + + return $intNb; + } +} diff --git a/application/Router.php b/application/Router.php index d98312b5..a1e594a0 100644 --- a/application/Router.php +++ b/application/Router.php @@ -53,7 +53,7 @@ class Router * @param array $get $_SERVER['GET']. * @param bool $loggedIn true if authenticated user. * - * @return self::page found. + * @return string page found. */ public static function findPage($query, $get, $loggedIn) { -- cgit v1.2.3 From ee88a4bcc29da721cf43b750663aebeac4969517 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sun, 20 Mar 2016 14:14:38 +0100 Subject: Makes escape a recursive function which handle array of strings --- application/Utils.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'application') diff --git a/application/Utils.php b/application/Utils.php index bcf5bdb5..5b8ca508 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -63,14 +63,22 @@ function endsWith($haystack, $needle, $case=true) /** * 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); } /** -- cgit v1.2.3 From 528a6f8a232c060faf024008e4f8a09b4aa8dabc Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Mon, 21 Mar 2016 21:40:49 +0100 Subject: Refactor filter in LinkDB * search type now carried by LinkDB in order to factorize code between different search sources. * LinkDB->filter split in 3 method: filterSearch, filterHash, filterDay (we know what type of filter is needed). * filterHash now throw a LinkNotFoundException if it doesn't exist: internal implementation choice, still displays a 404. * Smallhash regex has been rewritten. * Unit tests update --- application/FeedBuilder.php | 18 +------------ application/LinkDB.php | 64 +++++++++++++++++++++++++++++++++++++++++---- application/LinkFilter.php | 14 +++++++++- application/Updater.php | 2 +- 4 files changed, 74 insertions(+), 24 deletions(-) (limited to 'application') diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php index 50e09831..ddefe6ce 100644 --- a/application/FeedBuilder.php +++ b/application/FeedBuilder.php @@ -103,23 +103,7 @@ class FeedBuilder public function buildData() { // Optionally filter the results: - $searchtags = !empty($this->userInput['searchtags']) ? escape($this->userInput['searchtags']) : ''; - $searchterm = !empty($this->userInput['searchterm']) ? escape($this->userInput['searchterm']) : ''; - if (! empty($searchtags) && ! empty($searchterm)) { - $linksToDisplay = $this->linkDB->filter( - LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT, - array($searchtags, $searchterm) - ); - } - elseif ($searchtags) { - $linksToDisplay = $this->linkDB->filter(LinkFilter::$FILTER_TAG, $searchtags); - } - elseif ($searchterm) { - $linksToDisplay = $this->linkDB->filter(LinkFilter::$FILTER_TEXT, $searchterm); - } - else { - $linksToDisplay = $this->linkDB; - } + $linksToDisplay = $this->linkDB->filterSearch($this->userInput); $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay)); diff --git a/application/LinkDB.php b/application/LinkDB.php index 1b505620..a62341fc 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php @@ -341,17 +341,71 @@ You use the community supported version of the original Shaarli project, by Seba } /** - * Filter links. + * Returns the shaare corresponding to a smallHash. * - * @param string $type Type of filter. - * @param mixed $request Search request, string or array. + * @param string $request QUERY_STRING server parameter. + * + * @return array $filtered array containing permalink data. + * + * @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link. + */ + public function filterHash($request) + { + $request = substr($request, 0, 6); + $linkFilter = new LinkFilter($this->_links); + return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request); + } + + /** + * Returns the list of articles for a given day. + * + * @param string $request day to filter. Format: YYYYMMDD. + * + * @return array list of shaare found. + */ + public function filterDay($request) { + $linkFilter = new LinkFilter($this->_links); + return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request); + } + + /** + * Filter links according to search parameters. + * + * @param array $filterRequest Search request content. Supported keys: + * - searchtags: list of tags + * - searchterm: term search * @param bool $casesensitive Optional: Perform case sensitive filter * @param bool $privateonly Optional: Returns private links only if true. * - * @return array filtered links + * @return array filtered links, all links if no suitable filter was provided. */ - public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false) + public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false) { + // Filter link database according to parameters. + $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : ''; + $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; + + // Search tags + fullsearch. + if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { + $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; + $request = array($searchtags, $searchterm); + } + // Search by tags. + elseif (! empty($searchtags)) { + $type = LinkFilter::$FILTER_TAG; + $request = $searchtags; + } + // Fulltext search. + elseif (! empty($searchterm)) { + $type = LinkFilter::$FILTER_TEXT; + $request = $searchterm; + } + // Otherwise, display without filtering. + else { + $type = ''; + $request = ''; + } + $linkFilter = new LinkFilter($this->_links); return $linkFilter->filter($type, $request, $casesensitive, $privateonly); } diff --git a/application/LinkFilter.php b/application/LinkFilter.php index 3fd803cb..5e0d8015 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php @@ -44,7 +44,7 @@ class LinkFilter * Filter links according to parameters. * * @param string $type Type of filter (eg. tags, permalink, etc.). - * @param string $request Filter content. + * @param mixed $request Filter content. * @param bool $casesensitive Optional: Perform case sensitive filter if true. * @param bool $privateonly Optional: Only returns private links if true. * @@ -110,6 +110,8 @@ class LinkFilter * @param string $smallHash permalink hash. * * @return array $filtered array containing permalink data. + * + * @throws LinkNotFoundException if the smallhash doesn't match any link. */ private function filterSmallHash($smallHash) { @@ -121,6 +123,11 @@ class LinkFilter return $filtered; } } + + if (empty($filtered)) { + throw new LinkNotFoundException(); + } + return $filtered; } @@ -318,3 +325,8 @@ class LinkFilter return array_filter(explode(' ', trim($tagsOut)), 'strlen'); } } + +class LinkNotFoundException extends Exception +{ + protected $message = 'The link you are trying to reach does not exist or has been deleted.'; +} diff --git a/application/Updater.php b/application/Updater.php index 773a1ffa..58c13c07 100644 --- a/application/Updater.php +++ b/application/Updater.php @@ -137,7 +137,7 @@ class Updater */ public function updateMethodRenameDashTags() { - $linklist = $this->linkDB->filter(); + $linklist = $this->linkDB->filterSearch(); foreach ($linklist as $link) { $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); -- cgit v1.2.3 From c9da01e749e5319d0c0f400cde06e71c0e7312d5 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 24 Mar 2016 19:01:40 +0100 Subject: Refactor and rebase #380: Firefox reader view links Fixes #366 Closes #380 --- application/Url.php | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'application') diff --git a/application/Url.php b/application/Url.php index e7c3a4cc..af38c4d9 100644 --- a/application/Url.php +++ b/application/Url.php @@ -118,24 +118,41 @@ class Url */ public function __construct($url) { - $this->parts = parse_url(trim($url)); + $url = self::cleanupUnparsedUrl(trim($url)); + $this->parts = parse_url($url); if (!empty($url) && empty($this->parts['scheme'])) { $this->parts['scheme'] = 'http'; } } + /** + * Clean up URL before it's parsed. + * ie. handle urlencode, url prefixes, etc. + * + * @param string $url URL to clean. + * + * @return string cleaned URL. + */ + protected static function cleanupUnparsedUrl($url) + { + return self::removeFirefoxAboutReader($url); + } - private function removeFirefoxAboutReader($input){ - $output_array = []; - preg_match("%^about://reader\?url=(.*)%", $input, $output_array); - if(!empty($output_array)){ - $extractedUrl = preg_replace("%^about://reader\?url=(.*)%", "$1", $input); - $url = urldecode($extractedUrl); - }else{ - $url = $input; - } - return $url; + /** + * Remove Firefox Reader prefix if it's present. + * + * @param string $input url + * + * @return string cleaned url + */ + protected static function removeFirefoxAboutReader($input) + { + $firefoxPrefix = 'about://reader?url='; + if (startsWith($input, $firefoxPrefix)) { + return urldecode(ltrim($input, $firefoxPrefix)); + } + return $input; } /** @@ -200,8 +217,7 @@ class Url { $this->cleanupQuery(); $this->cleanupFragment(); - $url = $this->toString(); - return $this->removeFirefoxAboutReader($url); + return $this->toString(); } /** -- cgit v1.2.3 From 043eae70c4c57b0447b56a58b64ce9d102895396 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 24 Mar 2016 19:40:12 +0100 Subject: Fixes #480: add an option to urlencode redirector URL * New config: `$GLOBALS['config']['REDIRECTOR_URLENCODE']` (default `true`). * Parameter added to LinkDB constructor. * Fixes a bug with urlencode and escaped url. * In `index.php`, LinkDB is now instanciate once for `importFile()` and `showDaily()`. * TU --- application/LinkDB.php | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'application') diff --git a/application/LinkDB.php b/application/LinkDB.php index a62341fc..1cb70de0 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php @@ -65,22 +65,40 @@ class LinkDB implements Iterator, Countable, ArrayAccess // link redirector set in user settings. private $_redirector; + /** + * Set this to `true` to urlencode link behind redirector link, `false` to leave it untouched. + * + * Example: + * anonym.to needs clean URL while dereferer.org needs urlencoded URL. + * + * @var boolean $redirectorEncode parameter: true or false + */ + private $redirectorEncode; + /** * Creates a new LinkDB * * Checks if the datastore exists; else, attempts to create a dummy one. * - * @param string $datastore datastore file path. - * @param boolean $isLoggedIn is the user logged in? - * @param boolean $hidePublicLinks if true all links are private. - * @param string $redirector link redirector set in user settings. + * @param string $datastore datastore file path. + * @param boolean $isLoggedIn is the user logged in? + * @param boolean $hidePublicLinks if true all links are private. + * @param string $redirector link redirector set in user settings. + * @param boolean $redirectorEncode Enable urlencode on redirected urls (default: true). */ - function __construct($datastore, $isLoggedIn, $hidePublicLinks, $redirector = '') + function __construct( + $datastore, + $isLoggedIn, + $hidePublicLinks, + $redirector = '', + $redirectorEncode = true + ) { $this->_datastore = $datastore; $this->_loggedIn = $isLoggedIn; $this->_hidePublicLinks = $hidePublicLinks; $this->_redirector = $redirector; + $this->redirectorEncode = $redirectorEncode === true; $this->_checkDB(); $this->_readDB(); } @@ -278,7 +296,12 @@ You use the community supported version of the original Shaarli project, by Seba // Do not use the redirector for internal links (Shaarli note URL starting with a '?'). if (!empty($this->_redirector) && !startsWith($link['url'], '?')) { - $link['real_url'] = $this->_redirector . urlencode($link['url']); + $link['real_url'] = $this->_redirector; + if ($this->redirectorEncode) { + $link['real_url'] .= urlencode(unescape($link['url'])); + } else { + $link['real_url'] .= $link['url']; + } } else { $link['real_url'] = $link['url']; -- cgit v1.2.3 From cd5327bee83f3e9467d786752bbd447963b941f7 Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Sun, 10 Apr 2016 17:34:07 +0200 Subject: Refactor Netscape bookmark exporting Relates to https://github.com/shaarli/netscape-bookmark-parser/issues/5 Fixes: - respect the Netscape bookmark format "specification" Modifications: - [application] introduce the NetscapeBookmarkUtils class - [template] export - improve formatting, rename export selection parameter - [template] export.bookmarks - template for Netscape exports - [tests] bookmark filtering, additional field generation Signed-off-by: VirtualTam --- application/NetscapeBookmarkUtils.php | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 application/NetscapeBookmarkUtils.php (limited to 'application') diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php new file mode 100644 index 00000000..8a296705 --- /dev/null +++ b/application/NetscapeBookmarkUtils.php @@ -0,0 +1,47 @@ +getTimestamp(); + $link['taglist'] = str_replace(' ', ',', $link['tags']); + $bookmarkLinks[] = $link; + } + + return $bookmarkLinks; + } +} -- cgit v1.2.3 From 20f89623c01696a05459c4b1191cfe3adfb0105b Mon Sep 17 00:00:00 2001 From: D Low Date: Thu, 14 Apr 2016 00:04:00 +0100 Subject: Fix error when filtering search tags Arrays are key-value maps. We should reindex the array after a filter since we are using the key and count to do array access in filterTags. An example would be searching for "foo, bar", after the array filter, our array is actually (0 -> foo, 2 -> bar) which will cause an error when trying to access $searchtags[1]. --- application/LinkFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/LinkFilter.php b/application/LinkFilter.php index 5e0d8015..e693b284 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php @@ -322,7 +322,7 @@ class LinkFilter $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'); $tagsOut = str_replace(',', ' ', $tagsOut); - return array_filter(explode(' ', trim($tagsOut)), 'strlen'); + return array_values(array_filter(explode(' ', trim($tagsOut)), 'strlen')); } } -- cgit v1.2.3 From ce7b0b6480aa854ee6893f5c889277b0e3b13efc Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 6 Apr 2016 22:00:52 +0200 Subject: Fixes #531 - Title retrieving is failing with multiple use case see https://github.com/shaarli/Shaarli/issues/531 for details --- application/HttpUtils.php | 60 +++++++++++++++++++++++++++++++++++++++-------- application/LinkUtils.php | 6 ++--- application/Url.php | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 13 deletions(-) (limited to 'application') diff --git a/application/HttpUtils.php b/application/HttpUtils.php index af7cb371..0e1ce879 100644 --- a/application/HttpUtils.php +++ b/application/HttpUtils.php @@ -27,7 +27,9 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) { $urlObj = new Url($url); - if (! filter_var($url, FILTER_VALIDATE_URL) || ! $urlObj->isHttp()) { + $cleanUrl = $urlObj->indToAscii(); + + if (! filter_var($cleanUrl, FILTER_VALIDATE_URL) || ! $urlObj->isHttp()) { return array(array(0 => 'Invalid HTTP Url'), false); } @@ -35,22 +37,27 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) 'http' => array( 'method' => 'GET', 'timeout' => $timeout, - 'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:23.0)' - .' Gecko/20100101 Firefox/23.0', - 'request_fulluri' => true, + 'user_agent' => 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:45.0)' + .' Gecko/20100101 Firefox/45.0', + 'accept_language' => substr(setlocale(LC_COLLATE, 0), 0, 2) . ',en-US;q=0.7,en;q=0.3', ) ); - $context = stream_context_create($options); stream_context_set_default($options); + list($headers, $finalUrl) = get_redirected_headers($cleanUrl); + if (! $headers || strpos($headers[0], '200 OK') === false) { + $options['http']['request_fulluri'] = true; + stream_context_set_default($options); + list($headers, $finalUrl) = get_redirected_headers($cleanUrl); + } - list($headers, $finalUrl) = get_redirected_headers($urlObj->cleanup()); if (! $headers || strpos($headers[0], '200 OK') === false) { return array($headers, false); } try { // TODO: catch Exception in calling code (thumbnailer) + $context = stream_context_create($options); $content = file_get_contents($finalUrl, false, $context, -1, $maxBytes); } catch (Exception $exc) { return array(array(0 => 'HTTP Error'), $exc->getMessage()); @@ -60,16 +67,19 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) } /** - * Retrieve HTTP headers, following n redirections (temporary and permanent). + * Retrieve HTTP headers, following n redirections (temporary and permanent ones). * - * @param string $url initial URL to reach. - * @param int $redirectionLimit max redirection follow.. + * @param string $url initial URL to reach. + * @param int $redirectionLimit max redirection follow.. * - * @return array + * @return array HTTP headers, or false if it failed. */ function get_redirected_headers($url, $redirectionLimit = 3) { $headers = get_headers($url, 1); + if (!empty($headers['location']) && empty($headers['Location'])) { + $headers['Location'] = $headers['location']; + } // Headers found, redirection found, and limit not reached. if ($redirectionLimit-- > 0 @@ -79,6 +89,7 @@ function get_redirected_headers($url, $redirectionLimit = 3) $redirection = is_array($headers['Location']) ? end($headers['Location']) : $headers['Location']; if ($redirection != $url) { + $redirection = getAbsoluteUrl($url, $redirection); return get_redirected_headers($redirection, $redirectionLimit); } } @@ -86,6 +97,35 @@ function get_redirected_headers($url, $redirectionLimit = 3) return array($headers, $url); } +/** + * Get an absolute URL from a complete one, and another absolute/relative URL. + * + * @param string $originalUrl The original complete URL. + * @param string $newUrl The new one, absolute or relative. + * + * @return string Final URL: + * - $newUrl if it was already an absolute URL. + * - if it was relative, absolute URL from $originalUrl path. + */ +function getAbsoluteUrl($originalUrl, $newUrl) +{ + $newScheme = parse_url($newUrl, PHP_URL_SCHEME); + // Already an absolute URL. + if (!empty($newScheme)) { + return $newUrl; + } + + $parts = parse_url($originalUrl); + $final = $parts['scheme'] .'://'. $parts['host']; + $final .= (!empty($parts['port'])) ? $parts['port'] : ''; + $final .= '/'; + if ($newUrl[0] != '/') { + $final .= substr(ltrim($parts['path'], '/'), 0, strrpos($parts['path'], '/')); + } + $final .= ltrim($newUrl, '/'); + return $final; +} + /** * Returns the server's base URL: scheme://domain.tld[:port] * diff --git a/application/LinkUtils.php b/application/LinkUtils.php index d8dc8b5e..2df76ba8 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php @@ -9,8 +9,8 @@ */ function html_extract_title($html) { - if (preg_match('!(.*?)!is', $html, $matches)) { - return trim(str_replace("\n", ' ', $matches[1])); + if (preg_match('!(.*?)!is', $html, $matches)) { + return trim(str_replace("\n", '', $matches[1])); } return false; } @@ -70,7 +70,7 @@ function headers_extract_charset($headers) function html_extract_charset($html) { // Get encoding specified in HTML header. - preg_match('#/]+)"? */?>#Usi', $html, $enc); + preg_match('#/]+)["\']? */?>#Usi', $html, $enc); if (!empty($enc[1])) { return strtolower($enc[1]); } diff --git a/application/Url.php b/application/Url.php index af38c4d9..61a30a78 100644 --- a/application/Url.php +++ b/application/Url.php @@ -62,7 +62,21 @@ function add_trailing_slash($url) { return $url . (!endsWith($url, '/') ? '/' : ''); } +/** + * Converts an URL with an IDN host to a ASCII one. + * + * @param string $url Input URL. + * + * @return string converted URL. + */ +function url_with_idn_to_ascii($url) +{ + $parts = parse_url($url); + $parts['host'] = idn_to_ascii($parts['host']); + $httpUrl = new \http\Url($parts); + return $httpUrl->toString(); +} /** * URL representation and cleanup utilities * @@ -220,6 +234,22 @@ class Url return $this->toString(); } + /** + * Converts an URL with an International Domain Name host to a ASCII one. + * This requires PHP-intl. If it's not available, just returns this->cleanup(). + * + * @return string converted cleaned up URL. + */ + public function indToAscii() + { + $out = $this->cleanup(); + if (! function_exists('idn_to_ascii') || ! isset($this->parts['host'])) { + return $out; + } + $asciiHost = idn_to_ascii($this->parts['host']); + return str_replace($this->parts['host'], $asciiHost, $out); + } + /** * Get URL scheme. * @@ -232,6 +262,18 @@ class Url return $this->parts['scheme']; } + /** + * Get URL host. + * + * @return string the URL host or false if none is provided. + */ + public function getHost() { + if (empty($this->parts['host'])) { + return false; + } + return $this->parts['host']; + } + /** * Test if the Url is an HTTP one. * -- cgit v1.2.3 From 12ff86c961b49727fcae97938e864766aa77a2a9 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 3 May 2016 20:09:24 +0200 Subject: Use correct 'UTC' timezone --- application/TimeZone.php | 4 ---- 1 file changed, 4 deletions(-) (limited to 'application') diff --git a/application/TimeZone.php b/application/TimeZone.php index e363d90a..26f2232d 100644 --- a/application/TimeZone.php +++ b/application/TimeZone.php @@ -101,10 +101,6 @@ function generateTimeZoneForm($preselectedTimezone='') */ function isTimeZoneValid($continent, $city) { - if ($continent == 'UTC' && $city == 'UTC') { - return true; - } - return in_array( $continent.'/'.$city, timezone_identifiers_list() -- cgit v1.2.3 From caa69b585381cc1c22df3dbb9c943855b8f13a70 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 5 May 2016 13:28:43 +0200 Subject: typo --- application/HttpUtils.php | 4 ++-- application/Url.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'application') diff --git a/application/HttpUtils.php b/application/HttpUtils.php index 0e1ce879..c84ba6f0 100644 --- a/application/HttpUtils.php +++ b/application/HttpUtils.php @@ -27,7 +27,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) { $urlObj = new Url($url); - $cleanUrl = $urlObj->indToAscii(); + $cleanUrl = $urlObj->idnToAscii(); if (! filter_var($cleanUrl, FILTER_VALIDATE_URL) || ! $urlObj->isHttp()) { return array(array(0 => 'Invalid HTTP Url'), false); @@ -70,7 +70,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304) * Retrieve HTTP headers, following n redirections (temporary and permanent ones). * * @param string $url initial URL to reach. - * @param int $redirectionLimit max redirection follow.. + * @param int $redirectionLimit max redirection follow. * * @return array HTTP headers, or false if it failed. */ diff --git a/application/Url.php b/application/Url.php index 61a30a78..77447c8d 100644 --- a/application/Url.php +++ b/application/Url.php @@ -240,7 +240,7 @@ class Url * * @return string converted cleaned up URL. */ - public function indToAscii() + public function idnToAscii() { $out = $this->cleanup(); if (! function_exists('idn_to_ascii') || ! isset($this->parts['host'])) { -- cgit v1.2.3 From bb4a23aa863b63e6148a085c15dedd7c960b4206 Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Thu, 5 May 2016 19:22:06 +0200 Subject: Export: allow prepending notes with the Shaarli instance's URL Relates to #102 Additions: - application: - export: allow prepending note permalinks with the instance's URL - test coverage Modifications: - export template: switch to an HTML form - link selection (all/private/public) - prepend note permalinks with the instance's URL Signed-off-by: VirtualTam --- application/NetscapeBookmarkUtils.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'application') diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php index 8a296705..fdbb0ad7 100644 --- a/application/NetscapeBookmarkUtils.php +++ b/application/NetscapeBookmarkUtils.php @@ -13,17 +13,19 @@ class NetscapeBookmarkUtils * - timestamp link addition date, using the Unix epoch format * - taglist comma-separated tag list * - * @param LinkDB $linkDb The link datastore - * @param string $selection Which links to export: (all|private|public) + * @param LinkDB $linkDb Link datastore + * @param string $selection Which links to export: (all|private|public) + * @param bool $prependNoteUrl Prepend note permalinks with the server's URL + * @param string $indexUrl Absolute URL of the Shaarli index page * * @throws Exception Invalid export selection * * @return array The links to be exported, with additional fields */ - public static function filterAndFormat($linkDb, $selection) + public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl) { // see tpl/export.html for possible values - if (! in_array($selection, array('all','public','private'))) { + if (! in_array($selection, array('all', 'public', 'private'))) { throw new Exception('Invalid export selection: "'.$selection.'"'); } @@ -39,6 +41,11 @@ class NetscapeBookmarkUtils $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $link['timestamp'] = $date->getTimestamp(); $link['taglist'] = str_replace(' ', ',', $link['tags']); + + if (startsWith($link['url'], '?') && $prependNoteUrl) { + $link['url'] = $indexUrl . $link['url']; + } + $bookmarkLinks[] = $link; } -- cgit v1.2.3 From 5046bcb6ab324a6b52669b2b76a41665022f653a Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 10 May 2016 23:31:41 +0200 Subject: Fix startsWith and endsWith case --- application/HttpUtils.php | 2 +- application/Router.php | 26 +++++++++++++------------- application/Utils.php | 16 ++++++++++++++-- 3 files changed, 28 insertions(+), 16 deletions(-) (limited to 'application') diff --git a/application/HttpUtils.php b/application/HttpUtils.php index c84ba6f0..2e0792f9 100644 --- a/application/HttpUtils.php +++ b/application/HttpUtils.php @@ -193,7 +193,7 @@ function server_url($server) function index_url($server) { $scriptname = $server['SCRIPT_NAME']; - if (endswith($scriptname, 'index.php')) { + if (endsWith($scriptname, 'index.php')) { $scriptname = substr($scriptname, 0, -9); } return server_url($server) . $scriptname; diff --git a/application/Router.php b/application/Router.php index a1e594a0..2c3934b0 100644 --- a/application/Router.php +++ b/application/Router.php @@ -63,19 +63,19 @@ class Router return self::$PAGE_LINKLIST; } - if (startswith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) { + if (startsWith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) { return self::$PAGE_LOGIN; } - if (startswith($query, 'do='. self::$PAGE_PICWALL)) { + if (startsWith($query, 'do='. self::$PAGE_PICWALL)) { return self::$PAGE_PICWALL; } - if (startswith($query, 'do='. self::$PAGE_TAGCLOUD)) { + if (startsWith($query, 'do='. self::$PAGE_TAGCLOUD)) { return self::$PAGE_TAGCLOUD; } - if (startswith($query, 'do='. self::$PAGE_OPENSEARCH)) { + if (startsWith($query, 'do='. self::$PAGE_OPENSEARCH)) { return self::$PAGE_OPENSEARCH; } @@ -96,23 +96,23 @@ class Router return self::$PAGE_LINKLIST; } - if (startswith($query, 'do='. self::$PAGE_TOOLS)) { + if (startsWith($query, 'do='. self::$PAGE_TOOLS)) { return self::$PAGE_TOOLS; } - if (startswith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) { + if (startsWith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) { return self::$PAGE_CHANGEPASSWORD; } - if (startswith($query, 'do='. self::$PAGE_CONFIGURE)) { + if (startsWith($query, 'do='. self::$PAGE_CONFIGURE)) { return self::$PAGE_CONFIGURE; } - if (startswith($query, 'do='. self::$PAGE_CHANGETAG)) { + if (startsWith($query, 'do='. self::$PAGE_CHANGETAG)) { return self::$PAGE_CHANGETAG; } - if (startswith($query, 'do='. self::$PAGE_ADDLINK)) { + if (startsWith($query, 'do='. self::$PAGE_ADDLINK)) { return self::$PAGE_ADDLINK; } @@ -120,19 +120,19 @@ class Router return self::$PAGE_EDITLINK; } - if (startswith($query, 'do='. self::$PAGE_EXPORT)) { + if (startsWith($query, 'do='. self::$PAGE_EXPORT)) { return self::$PAGE_EXPORT; } - if (startswith($query, 'do='. self::$PAGE_IMPORT)) { + if (startsWith($query, 'do='. self::$PAGE_IMPORT)) { return self::$PAGE_IMPORT; } - if (startswith($query, 'do='. self::$PAGE_PLUGINSADMIN)) { + if (startsWith($query, 'do='. self::$PAGE_PLUGINSADMIN)) { return self::$PAGE_PLUGINSADMIN; } - if (startswith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) { + if (startsWith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) { return self::$PAGE_SAVE_PLUGINSADMIN; } diff --git a/application/Utils.php b/application/Utils.php index 5b8ca508..da521cce 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -41,8 +41,14 @@ 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); @@ -52,8 +58,14 @@ function startsWith($haystack, $needle, $case=true) /** * 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); -- cgit v1.2.3 From 03eb19ac60d54442332077fa35a9b0d4e33df365 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 10 May 2016 23:48:51 +0200 Subject: Extract PageBuilder class from index.php --- application/PageBuilder.php | 145 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 application/PageBuilder.php (limited to 'application') diff --git a/application/PageBuilder.php b/application/PageBuilder.php new file mode 100644 index 00000000..82580787 --- /dev/null +++ b/application/PageBuilder.php @@ -0,0 +1,145 @@ +assign('myfield','myvalue'); + * $p->renderPage('mytemplate'); + */ +class PageBuilder +{ + /** + * @var RainTPL RainTPL instance. + */ + private $tpl; + + /** + * PageBuilder constructor. + * $tpl is initialized at false for lazy loading. + */ + function __construct() + { + $this->tpl = false; + } + + /** + * Initialize all default tpl tags. + */ + private function initialize() + { + $this->tpl = new RainTPL(); + + try { + $version = ApplicationUtils::checkUpdate( + shaarli_version, + $GLOBALS['config']['UPDATECHECK_FILENAME'], + $GLOBALS['config']['UPDATECHECK_INTERVAL'], + $GLOBALS['config']['ENABLE_UPDATECHECK'], + isLoggedIn(), + $GLOBALS['config']['UPDATECHECK_BRANCH'] + ); + $this->tpl->assign('newVersion', escape($version)); + $this->tpl->assign('versionError', ''); + + } catch (Exception $exc) { + logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage()); + $this->tpl->assign('newVersion', ''); + $this->tpl->assign('versionError', escape($exc->getMessage())); + } + + $this->tpl->assign('feedurl', escape(index_url($_SERVER))); + $searchcrits = ''; // Search criteria + if (!empty($_GET['searchtags'])) { + $searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']); + } + if (!empty($_GET['searchterm'])) { + $searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']); + } + $this->tpl->assign('searchcrits', $searchcrits); + $this->tpl->assign('source', index_url($_SERVER)); + $this->tpl->assign('version', shaarli_version); + $this->tpl->assign('scripturl', index_url($_SERVER)); + $this->tpl->assign('pagetitle', 'Shaarli'); + $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? + if (!empty($GLOBALS['title'])) { + $this->tpl->assign('pagetitle', $GLOBALS['title']); + } + if (!empty($GLOBALS['titleLink'])) { + $this->tpl->assign('titleLink', $GLOBALS['titleLink']); + } + if (!empty($GLOBALS['pagetitle'])) { + $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']); + } + $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']); + if (!empty($GLOBALS['plugin_errors'])) { + $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']); + } + } + + /** + * The following assign() method is basically the same as RainTPL (except lazy loading) + * + * @param string $placeholder Template placeholder. + * @param mixed $value Value to assign. + */ + public function assign($placeholder, $value) + { + // Lazy initialization + if ($this->tpl === false) { + $this->initialize(); + } + $this->tpl->assign($placeholder, $value); + } + + /** + * Assign an array of data to the template builder. + * + * @param array $data Data to assign. + * + * @return false if invalid data. + */ + public function assignAll($data) + { + // Lazy initialization + if ($this->tpl === false) { + $this->initialize(); + } + + if (empty($data) || !is_array($data)){ + return false; + } + + foreach ($data as $key => $value) { + $this->assign($key, $value); + } + } + + /** + * Render a specific page (using a template file). + * e.g. $pb->renderPage('picwall'); + * + * @param string $page Template filename (without extension). + */ + public function renderPage($page) + { + // Lazy initialization + if ($this->tpl===false) { + $this->initialize(); + } + $this->tpl->draw($page); + } + + /** + * Render a 404 page (uses the template : tpl/404.tpl) + * usage : $PAGE->render404('The link was deleted') + * + * @param string $message A messate to display what is not found + */ + public function render404($message = 'The page you are trying to reach does not exist or has been deleted.') + { + header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); + $this->tpl->assign('error_message', $message); + $this->renderPage('404'); + } +} -- cgit v1.2.3 From 141a86c503af8e314381b3ee39ba4287fdfac63e Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 11 May 2016 00:05:22 +0200 Subject: Add private link counter --- application/LinkUtils.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'application') diff --git a/application/LinkUtils.php b/application/LinkUtils.php index 2df76ba8..da04ca97 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php @@ -77,3 +77,19 @@ function html_extract_charset($html) return false; } + +/** + * Count private links in given linklist. + * + * @param array $links Linklist. + * + * @return int Number of private links. + */ +function count_private($links) +{ + $cpt = 0; + foreach ($links as $link) { + $cpt = $link['private'] == true ? $cpt + 1 : $cpt; + } + return $cpt; +} -- cgit v1.2.3