diff options
author | zotlabs <mike@macgirvin.com> | 2017-06-14 16:07:21 -0700 |
---|---|---|
committer | zotlabs <mike@macgirvin.com> | 2017-06-14 16:07:21 -0700 |
commit | 234c64574b4ee5ca22b85ae2fecc301286423068 (patch) | |
tree | 7786bed424457e327f30b067bf0ef39c7446ac99 | |
parent | f28fa0ab5a46a231695a851f2345a3669419c842 (diff) | |
parent | 1f075d92bc4e47ae282f811d10c31cd6f09191a6 (diff) | |
download | volse-hubzilla-234c64574b4ee5ca22b85ae2fecc301286423068.tar.gz volse-hubzilla-234c64574b4ee5ca22b85ae2fecc301286423068.tar.bz2 volse-hubzilla-234c64574b4ee5ca22b85ae2fecc301286423068.zip |
Merge branch 'dev' of https://github.com/redmatrix/hubzilla into dev_merge
-rw-r--r-- | Zotlabs/Lib/Techlevels.php | 12 | ||||
-rw-r--r-- | Zotlabs/Module/Oembed.php | 2 | ||||
-rw-r--r-- | Zotlabs/Widget/Portfolio.php | 108 | ||||
-rw-r--r-- | doc/about/about_hubzilla.bb | 6 | ||||
-rw-r--r-- | doc/developer/api_zot.bb | 17 | ||||
-rw-r--r-- | doc/hook/can_comment_on_post.bb | 13 | ||||
-rw-r--r-- | doc/hook/connection_remove.bb | 9 | ||||
-rw-r--r-- | doc/hooklist.bb | 3 | ||||
-rw-r--r-- | doc/member/member_guide.bb | 29 | ||||
-rw-r--r-- | include/bbcode.php | 32 | ||||
-rw-r--r-- | include/connections.php | 4 | ||||
-rw-r--r-- | include/feedutils.php | 319 | ||||
-rwxr-xr-x | include/items.php | 5 | ||||
-rw-r--r-- | include/text.php | 4 | ||||
-rwxr-xr-x | view/tpl/photo_album_portfolio.tpl | 11 | ||||
-rwxr-xr-x | view/tpl/photo_portfolio.tpl | 13 |
16 files changed, 447 insertions, 140 deletions
diff --git a/Zotlabs/Lib/Techlevels.php b/Zotlabs/Lib/Techlevels.php index 6a8c36fb3..380901678 100644 --- a/Zotlabs/Lib/Techlevels.php +++ b/Zotlabs/Lib/Techlevels.php @@ -7,12 +7,12 @@ class Techlevels { static public function levels() { $techlevels = [ - '0' => t('Beginner/Basic'), - '1' => t('Novice - not skilled but willing to learn'), - '2' => t('Intermediate - somewhat comfortable'), - '3' => t('Advanced - very comfortable'), - '4' => t('Expert - I can write computer code'), - '5' => t('Wizard - I probably know more than you do') + '0' => t('0. Beginner/Basic'), + '1' => t('1. Novice - not skilled but willing to learn'), + '2' => t('2. Intermediate - somewhat comfortable'), + '3' => t('3. Advanced - very comfortable'), + '4' => t('4. Expert - I can write computer code'), + '5' => t('5. Wizard - I probably know more than you do') ]; return $techlevels; } diff --git a/Zotlabs/Module/Oembed.php b/Zotlabs/Module/Oembed.php index 9394e5942..aee5ea079 100644 --- a/Zotlabs/Module/Oembed.php +++ b/Zotlabs/Module/Oembed.php @@ -22,7 +22,7 @@ class Oembed extends \Zotlabs\Web\Controller { } else { - echo "<html><head><base target=\"_blank\" /></head><body>"; + echo "<html><head><base target=\"_blank\" rel=\"nofollow noopener\" /></head><body>"; $src = base64url_decode(argv(1)); $j = oembed_fetch_url($src); echo $j['html']; diff --git a/Zotlabs/Widget/Portfolio.php b/Zotlabs/Widget/Portfolio.php new file mode 100644 index 000000000..216ca952c --- /dev/null +++ b/Zotlabs/Widget/Portfolio.php @@ -0,0 +1,108 @@ +<?php + +namespace Zotlabs\Widget; + +require_once('include/attach.php'); + +class Portfolio { + + function widget($args) { + + + $owner_uid = \App::$profile_uid; + $sql_extra = permissions_sql($owner_uid); + + + if(! perm_is_allowed($owner_uid,get_observer_hash(),'view_storage')) + return ''; + + if($args['album']) + $album = $args['album']; + if($args['title']) + $title = $args['title']; + + /** + * This may return incorrect permissions if you have multiple directories of the same name. + * It is a limitation of the photo table using a name for a photo album instead of a folder hash + */ + + if($album) { + $x = q("select hash from attach where filename = '%s' and uid = %d limit 1", + dbesc($album), + intval($owner_uid) + ); + if($x) { + $y = attach_can_view_folder($owner_uid,get_observer_hash(),$x[0]['hash']); + if(! $y) + return ''; + } + } + + $order = 'DESC'; + + $r = q("SELECT p.resource_id, p.id, p.filename, p.mimetype, p.imgscale, p.description, p.created FROM photo p INNER JOIN + (SELECT resource_id, max(imgscale) imgscale FROM photo WHERE uid = %d AND album = '%s' AND imgscale <= 4 AND photo_usage IN ( %d, %d ) $sql_extra GROUP BY resource_id) ph + ON (p.resource_id = ph.resource_id AND p.imgscale = ph.imgscale) + ORDER BY created $order ", + intval($owner_uid), + dbesc($album), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE) + ); + + //edit album name + $album_edit = null; + + $photos = array(); + if($r) { + $twist = 'rotright'; + foreach($r as $rr) { + + if($twist == 'rotright') + $twist = 'rotleft'; + else + $twist = 'rotright'; + + $ext = $phototypes[$rr['mimetype']]; + + $imgalt_e = $rr['filename']; + $desc_e = $rr['description']; + + $imagelink = (z_root() . '/photos/' . \App::$profile['channel_address'] . '/image/' . $rr['resource_id']); + + + $photos[] = array( + 'id' => $rr['id'], + 'twist' => ' ' . $twist . rand(2,4), + 'link' => $imagelink, + 'title' => t('View Photo'), + 'src' => z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['imgscale'] . '.' .$ext, + 'fullsrc' => z_root() . '/photo/' . $rr['resource_id'] . '-' . '1' . '.' .$ext, + 'resource_id' => $rr['resource_id'], + 'alt' => $imgalt_e, + 'desc'=> $desc_e, + 'ext' => $ext, + 'hash'=> $rr['resource_id'], + 'unknown' => t('Unknown') + ); + } + } + + + $tpl = get_markup_template('photo_album_portfolio.tpl'); + $o .= replace_macros($tpl, array( + '$photos' => $photos, + '$album' => (($title) ? $title : $album), + '$album_id' => rand(), + '$album_edit' => array(t('Edit Album'), $album_edit), + '$can_post' => false, + '$upload' => array(t('Upload'), z_root() . '/photos/' . \App::$profile['channel_address'] . '/upload/' . bin2hex($album)), + '$order' => false, + '$upload_form' => $upload_form, + '$usage' => $usage_message + )); + + return $o; + } +} + diff --git a/doc/about/about_hubzilla.bb b/doc/about/about_hubzilla.bb index e9485ffa6..bf42446a7 100644 --- a/doc/about/about_hubzilla.bb +++ b/doc/about/about_hubzilla.bb @@ -115,7 +115,7 @@ Posts and messages may be created with an expiration date, at which time they wi [h4]Service Federation[/h4] -In addition to addon "cross-post connectors" to a variety of alternate networks, there is native support for importation of content from RSS/Atom feeds and using this to create special channels. Also, an experimental but working implementation of the Diaspora protocol allows communication with people on the Friendica and Diaspora decentralised social networks. This is currently marked experimental because these networks do not have the same level of privacy and encryption features and abilities as $Projectname and may present privacy risks. +In addition to addon "cross-post connectors" to a variety of alternate networks, there is native support for importation of content from RSS/Atom feeds and using this to create special channels. Plugins are also available to communicate with others using the Diaspora and GNU-Social (OStatus) protocols. These networks do not support nomadic identity or cross-domain access control; however basic communications are supported to/from Diaspora, Friendica, GNU-Social, Mastodon and other providers which use these protocols. There is also experimental support for OpenID authentication which may be used in Access Control Lists. This is a work in progress. Your $Projectname hub may be used as an OpenID provider to authenticate you to external services which use this technology. @@ -195,7 +195,7 @@ Any content created in $Projectname remains under the control of the member (or Similar to any other modern blogging system, social network, or a micro-blogging service, $Projectname supports the uploading of files, embedding of videos, linking web pages. [h4]Previewing/Editing[/h4] -Post can be previewed prior to sending and edited after sending. +Posts and comments can be previewed prior to sending and edited after sending. [h4]Voting/Consensus[/h4] Posts can be turned into "consensus" items which allows readers to offer feedback, which is collated into "agree", "disagree", and "abstain" counters. This lets you gauge interest for ideas and create informal surveys. @@ -206,4 +206,4 @@ $Projectname can be extended in a number of ways, through site customisation, pe [h4]API[/h4] -An API is available for use by third-party services. This is based originally on the early Twitter API (for which hundreds of third-party tools exist). It is currently being extended to provide access to facilities and abilities which are specific to $Projectname. Access may be provided by login/password or OAuth and client registration of OAuth applications is provided. +An API is available for use by third-party services. A plugin also provides a basic implementation of the Twitter API (for which hundreds of third-party tools exist). Access may be provided by login/password or OAuth, and client registration of OAuth applications is provided. diff --git a/doc/developer/api_zot.bb b/doc/developer/api_zot.bb index ddca65b4d..b2c19d7a1 100644 --- a/doc/developer/api_zot.bb +++ b/doc/developer/api_zot.bb @@ -4,7 +4,20 @@ The API endpoints detailed below are relative to [code]api/z/1.0[/code], meaning [h3]channel/export/basic[/h3] -Export channel data +Export basic channel data + +Options: + - sections + comma-separated list of data types to export + + - posts + if true, return default sections plus 3 months of posts + + If no sections are requested, the following sections are returned: + channel, connections, config, apps, chatrooms, events, webpages, mail, wikis + + Files and large collections of posts may run into memory limits; these must generally be + requested separately. [h3]channel/stream[/h3] @@ -608,7 +621,7 @@ Optional: application or network name to display with item -- categories +- category comma separated categories for this item diff --git a/doc/hook/can_comment_on_post.bb b/doc/hook/can_comment_on_post.bb new file mode 100644 index 000000000..2cfd3b2da --- /dev/null +++ b/doc/hook/can_comment_on_post.bb @@ -0,0 +1,13 @@ +[h3]can_comment_on_post[/h3] + +Called when deciding whether or not to display a comment box for a post. + + +Hook data (array): + observer_hash => xchan_hash of current observer + item => posted item + allowed => 'unset' + + +To over-ride the default behaviour, change allowed to true or false + diff --git a/doc/hook/connection_remove.bb b/doc/hook/connection_remove.bb new file mode 100644 index 000000000..bd13ae5f2 --- /dev/null +++ b/doc/hook/connection_remove.bb @@ -0,0 +1,9 @@ +[h3]connection_remove[/h3] + +Called when deleting a connection. + + +Passed parameter array: + + 'channel_id' => channel_id of the channel removing the connection + 'abook_id' => abook_id of the connection being removed diff --git a/doc/hooklist.bb b/doc/hooklist.bb index db606cbdc..a9ba73e97 100644 --- a/doc/hooklist.bb +++ b/doc/hooklist.bb @@ -82,6 +82,9 @@ Hooks allow plugins/addons to "hook into" the code at many points and alter the [zrl=[baseurl]/help/hook/build_pagehead]build_pagehead[/zrl] Called when creating the HTML page header +[zrl=[baseurl]/help/hook/can_comment_on_post]can_comment_on_post[/zrl] + Called when deciding whether or not to present a comment box for a post + [zrl=[baseurl]/help/hook/change_channel]change_channel[/zrl] Called when logging in to a channel (either during login or afterward through the channel manager) diff --git a/doc/member/member_guide.bb b/doc/member/member_guide.bb index 50f0b581b..3e5172c23 100644 --- a/doc/member/member_guide.bb +++ b/doc/member/member_guide.bb @@ -174,19 +174,29 @@ You can also delegate control of your channels' posts and connections, but not i [h3]Connecting To Channels[/h3] -Connections in $Projectname can take on a great many different meanings. But let's keep it simple, you want to be friends with somebody like you are familiar with from social networking. How do you do it? +Connections in $Projectname can take on a great many different meanings. A connection is more accurately defined as a set of permissions that you have granted to somebody else. In traditional social network applications, all connections are granted the same permissions; or at most there two levels (friends and 'followers'). In $Projectname, a range of separate permissions may be set/adjusted depending on the siutation and relationship you have with the other channel. You can allow somebody to view your posts but not your photos. You can also deny them permission to comment on your posts or send private mail to you. But let's keep it simple, you want to be friends with somebody like you are familiar with from social networking. How do you do it? First, you need to find some channels to connect to. There are two primary ways of doing this. Firstly, setting the "Can send me their channel stream and posts" permission to "Anybody in this network" will bring posts from complete strangers to your matrix. This will give you a lot of public content and should hopefully help you find interesting, entertaing people, forums, and channels. -The next thing you can do is look at the Directory. The directory is available on every $Projectname website which means searching from your own site will bring in results from the entire network. You can search by name, interest, location and keyword. This is incomplete, so we'll improve this paragraph later. +The next thing you can do is look at the Directory. The directory is available on every $Projectname website which means searching from your own site will bring in results from the entire network. You can search by name, interest, location, and keyword. + +If you already know somebody's 'webbie' you can connect with them directly. A webbie looks just like an email address (for instance bob@example.com) but refers to somebody in the open social web. In order to connect they must be using a compatible network protocol. By default, this software supports the 'zot' protocol, however additional protocols may be provided through plugins/addons. See below for more information on connecting to channels on other networks. To connect with other $Projectname channels: -Visit their profile by clicking their photograph in the directory, matrix, or comments, and it will open their channel home page in the channel viewer. At the left hand side of the screen, you will usually see a link called "connect". Click it, and you're done. Depending on the settings of the channel you are connecting to, you may need to wait for them to approve your connection, but no further action is needed on your part. Once you've initiated the connection, you will be taken to the connection editor. This allows you to assign specific permissions for this channel. If you don't allow any permissions, communication will be very limited. There are some quick links which you can use to avoid setting individual permissions. To provide a social network environment, "Full Sharing" is recommended. You may review the settings that are applied with the quick links to ensure they are suitable for the channel you are connecting with and adjust if necessary. Then scroll to the bottom of the page and click "Submit". +Visit their profile by clicking their photograph in the directory, matrix, or comments, and it will open their channel home page in the channel viewer. At the left hand side of the screen, you will usually see a link called "connect". Click it, and you're done. Depending on the settings of the channel you are connecting to, you may need to wait for them to approve your connection, but no further action is needed on your part. Once you've initiated the connection, you will be taken to the connection editor. This allows you to assign specific permissions for this channel if you wish to make any changes. + +You may also connect with any channel by visiting the "Connections" page of your site or the Directory and typing their "webbie" into the "Add New Connection" field. Use this method if somebody tells you their webbie and you wish to connect with them. The process is the same as connecting via the "Connect" button - you will then be taken to the connection editor to set permissions. + +To connect with channels on other networks: -You may also connect with any channel by visiting the "Connections" page of your site or the Directory and typing their "webbie" into the "Add New Connection" field. Use this method if somebody tells you their webbie and you wish to connect with them. A webbie looks like an email address; for example "bob@example.com". The process is the same as connecting via the "Connect" button - you will then be taken to the connection editor to set permissions. +The process for connecting to channels on other networks (such as GNU-Social, Mastodon, and Diaspora) is similar - type their "webbie" into the "Add New Connections" box on the "Connections" page. Before you do this however, please visit your Settings page (Feature/Addon Settings) and ensure that the relevant protocol (Diaspora or GNU-Social) is provided on your hub and [b][i]activated[/i] for your channel[/b]. These networks/protocols do not support account migration and location independence so if you move location or clone your channel elsewhere, communications with these connections may fail. For this reason these protocols are not activated by default, but only through your consent. Activating these protocols involves an important decision between communicating with friends on these networks or providing fail-safe account resilience if your server fails. -[h4] Block/Ignore/Archive/Hide channels [/h4] +To connect with RSS feeds: + +Your hub admin may allow connecting to RSS feeds. The process for connecting to an RSS feed is the same, exept type (or paste) the URL of the feed into the "Add New Connection" box. Feeds are only processed once or twice per day and your hub admin may impose limits on how many feeds you may add. + +[h4]Block/Ignore/Archive/Hide channels [/h4] Channels in your address book can have statuses such as [i]blocked[/i], [i]ignored[/i], [i]archived[/i] and [i]hidden[/i]. From your connections page you can see tabs that display the channels with those statuses. From your edit connection pages you can change the statuses of a channel. @@ -279,7 +289,7 @@ We highly recommend that you use the "typical social network" settings when you [i]Note:[/i] Plugins/addons may provide special permission settings, so you may be offered additional permission settings beyond what is described here. -If you have set any of these permissions to "only those I specifically allow", you may specify indivudal permissions on the connnection edit screen. +If you have set any of these permissions to "only those I specifically allow", you may specify individual permissions on the connnection edit screen. [h4]Affinity[/h4] @@ -348,7 +358,7 @@ Topical tags are indicated by preceding the tag name with the # character. This Topical tags are also not linked if they are purely numeric, e.g. #1. If you wish to use a numeric hashtag, please add some descriptive text such as #2012-elections. [h4]Bookmarks[/h4] -Bookmarks indicate a link which can be saved to your bookmark folder. They use the sequence #^ followed by the link. Often these are generatd automatically. If the 'bookmarker' addon is installed, this sequence will be converted to a bookmark icon when viewing the post or comment online and clicking the icon will save the bookmark. If the bookmarker addon is not installed, the post 'dropdown menu' contains a link for saving the bookmark or bookmarks. +Bookmarks indicate a link which can be saved to your bookmark folder. They use the sequence #^ followed by the link. Often these are generated automatically. If the 'bookmarker' addon is installed, this sequence will be converted to a bookmark icon when viewing the post or comment online and clicking the icon will save the bookmark. If the bookmarker addon is not installed, the post 'dropdown menu' contains a link for saving the bookmark or bookmarks. [h4]Spaces in Tags and Mentions[/h4] Where possible please use the auto-complete window to select tag and mention recipients, because it will generate a coded tag which uniquely identifies one channel. Names are sometimes ambiguous. However, you can "manually" tag a channel by matching the channel name or address. @@ -995,7 +1005,7 @@ The saved search widget will provide autocompletion of channels (the results are [h4]Remove Channel[/h4] -Go to the bottom of your channel settings page or visit the URL: +Select the 'Remove Channel' link on your channel settings page or visit the URL: [baseurl]/removeme @@ -1007,7 +1017,7 @@ If you have identity clones on other hubs this only removes by default the chan [h4]Remove Account[/h4] -Go to the bottom of your account settings page or visit the URL: +Select 'Remove Account' from your account settings page or visit the URL: [baseurl]/removeaccount @@ -1016,3 +1026,4 @@ You will need to confirm your password and the account you are currently logged [hl][i][b]This is irreversible.[/b][/i][/hl] All your channels will be deleted. If you have identity clones on other hubs this only removes by default the channels instances which exists on this hub. + diff --git a/include/bbcode.php b/include/bbcode.php index 0d2fb8c74..f0ec15130 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -839,7 +839,7 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) if($tryoembed) { $Text = preg_replace_callback("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/$urlchars+)/ism", 'tryoembed', $Text); } - $Text = preg_replace("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/$urlchars+)/ism", '$1<a href="$2" target="_blank" >$2</a>', $Text); + $Text = preg_replace("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/$urlchars+)/ism", '$1<a href="$2" target="_blank" rel="nofollow noopener">$2</a>', $Text); } if (strpos($Text,'[/share]') !== false) { @@ -851,16 +851,16 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) } } if (strpos($Text,'[/url]') !== false) { - $Text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" target="_blank" >$1</a>', $Text); - $Text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" target="_blank" >$2</a>', $Text); - $Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank" >$1</a>', $Text); - $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" >$2</a>', $Text); + $Text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" target="_blank" rel="nofollow noopener" >$2</a>', $Text); + $Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); + $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="nofollow noopener" >$2</a>', $Text); } if (strpos($Text,'[/zrl]') !== false) { - $Text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" target="_blank" >$1</a>', $Text); - $Text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" target="_blank" >$2</a>', $Text); - $Text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" target="_blank" >$1</a>', $Text); - $Text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" target="_blank" >$2</a>', $Text); + $Text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" target="_blank" rel="nofollow noopener" >$2</a>', $Text); + $Text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); + $Text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" target="_blank" rel="nofollow noopener" >$2</a>', $Text); } if (get_account_techlevel() < 2) @@ -868,8 +868,8 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) // Perform MAIL Search if (strpos($Text,'[/mail]') !== false) { - $Text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" target="_blank" >$1</a>', $Text); - $Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" target="_blank" >$2</a>', $Text); + $Text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); + $Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" target="_blank" rel="nofollow noopener" >$2</a>', $Text); } @@ -1189,17 +1189,17 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) // if video couldn't be embedded, link to it instead. if (strpos($Text,'[/video]') !== false) { - $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" target="_blank" >$1</a>', $Text); + $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); } if (strpos($Text,'[/audio]') !== false) { - $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" target="_blank" >$1</a>', $Text); + $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); } if (strpos($Text,'[/zvideo]') !== false) { - $Text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" target="_blank" >$1</a>', $Text); + $Text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); } if (strpos($Text,'[/zaudio]') !== false) { - $Text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" target="_blank" >$1</a>', $Text); + $Text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); } // if ($tryoembed){ @@ -1208,7 +1208,7 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) // } // } else { // if (strpos($Text,'[/iframe]') !== false) { -// $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1" target="_blank" >$1</a>', $Text); +// $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1" target="_blank" rel="nofollow noopener" >$1</a>', $Text); // } // } diff --git a/include/connections.php b/include/connections.php index 85b961b65..56457ea10 100644 --- a/include/connections.php +++ b/include/connections.php @@ -634,8 +634,8 @@ function get_vcard_array($vc,$id) { 'address' => $adr->getParts() ]; $last_entry = end($adrs); - if($adrs[$last_entry]['address']) - array_walk($adrs[$last_entry]['address'],'array_escape_tags'); + if($last_entry['address']) + array_walk($last_entry['address'],'array_escape_tags'); } } diff --git a/include/feedutils.php b/include/feedutils.php index f1cee4194..02decca69 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -266,6 +266,8 @@ function get_atom_elements($feed, $item, &$author) { $res['item_rss'] = 1; + $summary = unxmlify($item->get_description(true)); + // removing the content of the title if its identically to the body // This helps with auto generated titles e.g. from tumblr @@ -278,6 +280,23 @@ function get_atom_elements($feed, $item, &$author) { $base_url = ''; + $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); + if($rawcreated) + $res['created'] = unxmlify($rawcreated[0]['data']); + + $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); + if($rawedited) + $res['edited'] = unxmlify($rawedited[0]['data']); + + if((x($res,'edited')) && (! (x($res,'created')))) + $res['created'] = $res['edited']; + + if(! $res['created']) + $res['created'] = $item->get_date('c'); + + if(! $res['edited']) + $res['edited'] = $item->get_date('c'); + $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); // select between supported verbs @@ -288,115 +307,95 @@ function get_atom_elements($feed, $item, &$author) { // translate OStatus unfollow to activity streams if it happened to get selected - if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) + if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) { $res['verb'] = ACTIVITY_UNFOLLOW; + } - if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE) { - // For Mastodon shares ("boosts"), we need to parse the original author information - // from the activity:object -> author structure - $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); - - if($rawobj) { - $rawauthor = $rawobj->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']) { - $author['author_name'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']); - } + // look for a photo. We should check media size and find the best one, + // but for now let's just find any author photo + + $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']) { - $author['author_link'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']); + if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { + $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; + foreach($base as $link) { + if(!x($author, 'author_photo') || ! $author['author_photo']) { + if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') + $author['author_photo'] = unxmlify($link['attribs']['']['href']); } - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if(!x($author, 'author_photo') || ! $author['author_photo']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $author['author_photo'] = unxmlify($link['attribs']['']['href']); - } + } + } + $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor'); + + if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) { + $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; + if($base && count($base)) { + foreach($base as $link) { + if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link'])) + $author['author_link'] = unxmlify($link['attribs']['']['href']); + if(!x($author, 'author_photo') || ! $author['author_photo']) { + if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') + $author['author_photo'] = unxmlify($link['attribs']['']['href']); } } } } - else { - // look for a photo. We should check media size and find the best one, - // but for now let's just find any author photo - $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); + // check for a yahoo media element (github etc.) + + if(! $author['author_photo']) { + $rawmedia = $item->get_item_tags(NAMESPACE_YMEDIA,'thumbnail'); + if($rawmedia && $rawmedia[0]['attribs']['']['url']) { + $author['author_photo'] = strip_tags(unxmlify($rawmedia[0]['attribs']['']['url'])); + } + } + + + // No photo/profile-link on the item - look at the feed level + if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) { + $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; foreach($base as $link) { - if(!x($author, 'author_photo') || ! $author['author_photo']) { + if($link['attribs']['']['rel'] === 'alternate' && (! $author['author_link'])) { + $author['author_link'] = unxmlify($link['attribs']['']['href']); + $author['author_is_feed'] = true; + } + if(! $author['author_photo']) { if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') $author['author_photo'] = unxmlify($link['attribs']['']['href']); } } } - $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor'); + $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject'); - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) { + if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'],ACTIVITY_OBJ_PERSON)) { $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; + if($base && count($base)) { foreach($base as $link) { if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link'])) $author['author_link'] = unxmlify($link['attribs']['']['href']); - if(!x($author, 'author_photo') || ! $author['author_photo']) { + if(! (x($author,'author_photo'))) { if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') $author['author_photo'] = unxmlify($link['attribs']['']['href']); } } } } + } - // check for a yahoo media element (github etc.) - - if(! $author['author_photo']) { - $rawmedia = $item->get_item_tags(NAMESPACE_YMEDIA,'thumbnail'); - if($rawmedia && $rawmedia[0]['attribs']['']['url']) { - $author['author_photo'] = strip_tags(unxmlify($rawmedia[0]['attribs']['']['url'])); - } - } - - - // No photo/profile-link on the item - look at the feed level - - if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) { - $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $author['author_link'])) { - $author['author_link'] = unxmlify($link['attribs']['']['href']); - $author['author_is_feed'] = true; - } - if(! $author['author_photo']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $author['author_photo'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject'); - - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) { - $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - if($base && count($base)) { - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link'])) - $author['author_link'] = unxmlify($link['attribs']['']['href']); - if(! (x($author,'author_photo'))) { - if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') - $author['author_photo'] = unxmlify($link['attribs']['']['href']); - } - } - } - } - } + $rawcnv = $item->get_item_tags(NAMESPACE_OSTATUS, 'conversation'); + if($rawcnv) { + $ostatus_conversation = unxmlify($rawcnv[0]['attribs']['']['ref']); + set_iconfig($res,'ostatus','conversation',$ostatus_conversation,true); } - $ostatus_protocol = (($item->get_item_tags(NAMESPACE_OSTATUS, 'conversation')) ? true : false); + $ostatus_protocol = (($ostatus_conversation) ? true : false); + $mastodon = (($item->get_item_tags('http://mastodon.social/schema/1.0','scope')) ? true : false); if($mastodon) $ostatus_protocol = true; @@ -489,6 +488,12 @@ function get_atom_elements($feed, $item, &$author) { ); } + // turn Mastodon content warning into a #nsfw hashtag + if($mastodon && $summary) { + $res['body'] .= "\n\n#nsfw\n"; + } + + $private = $item->get_item_tags(NAMESPACE_DFRN, 'private'); if($private && intval($private[0]['data']) > 0) $res['item_private'] = ((intval($private[0]['data'])) ? 1 : 0); @@ -499,23 +504,6 @@ function get_atom_elements($feed, $item, &$author) { if($rawlocation) $res['location'] = unxmlify($rawlocation[0]['data']); - $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); - if($rawcreated) - $res['created'] = unxmlify($rawcreated[0]['data']); - - $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); - if($rawedited) - $res['edited'] = unxmlify($rawedited[0]['data']); - - if((x($res,'edited')) && (! (x($res,'created')))) - $res['created'] = $res['edited']; - - if(! $res['created']) - $res['created'] = $item->get_date('c'); - - if(! $res['edited']) - $res['edited'] = $item->get_date('c'); - // Disallow time travelling posts @@ -597,6 +585,7 @@ function get_atom_elements($feed, $item, &$author) { $attach = $item->get_enclosures(); if($attach) { + $res['attach'] = array(); foreach($attach as $att) { $len = intval($att->get_length()); @@ -616,6 +605,7 @@ function get_atom_elements($feed, $item, &$author) { $res['attach'][] = array('href' => $link, 'length' => $len, 'type' => $type, 'title' => $title ); } } + $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); @@ -693,6 +683,10 @@ function get_atom_elements($feed, $item, &$author) { $res['target'] = $obj; } + if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE) { + feed_get_reshare($res,$item); + } + // build array to pass to hook $arr = [ 'feed' => $feed, @@ -709,6 +703,106 @@ function get_atom_elements($feed, $item, &$author) { return $arr['result']; } +function feed_get_reshare(&$res,$item) { + + $share = []; + + // For Mastodon shares ("boosts"), we need to parse the original author information + // from the activity:object -> author structure + $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); + + if($rawobj) { + + $rawauthor = $rawobj->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); + + if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']) { + $share['author'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']); + } + + if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']) { + $share['profile'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']); + } + + if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { + $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; + foreach($base as $link) { + if(! (array_key_exists('avatar',$share) && $share['avatar'])) { + if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') + $share['avatar'] = unxmlify($link['attribs']['']['href']); + } + } + } + + if(! $share['author']) + $share['author'] = t('unknown'); + if(! $share['avatar']) + $share['avatar'] = z_root() . '/' . get_default_profile_photo(80); + if(! $share['profile']) + $share['profile'] = z_root(); + + $child = $rawobj[0]['child']; + + + if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) + $share['link'] = encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']); + + $rawcreated = $rawobj->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); + if($rawcreated) + $share['created'] = unxmlify($rawcreated[0]['data']); + else + $share['created'] = $res['created']; + + if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']) + $share['message_id'] = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']); + + if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) { + $body = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']); + if(! $body) + $body = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']); + + if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { + $body = purify_html($body); + $body = html2bbcode($body); + } + } + + $attach = $rawobj->get_enclosures(); + if($attach) { + foreach($attach as $att) { + $len = intval($att->get_length()); + $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link())))); + $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title())))); + $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type())))); + if(strpos($type,';')) + $type = substr($type,0,strpos($type,';')); + if((! $link) || (strpos($link,'http') !== 0)) + continue; + + if(! $title) + $title = ' '; + if(! $type) + $type = 'application/octet-stream'; + + if((strpos($type,'image') === 0) && (strpos($body,$link) === false) && (strpos($link,'http') === 0)) { + $body .= "\n\n" . '[img]' . $link . '[/img]'; + } + } + } + + $res['body'] = "[share author='" . urlencode($share['author']) . + "' profile='" . $share['profile'] . + "' avatar='" . $share['avatar'] . + "' link='" . $share['link'] . + "' posted='" . $share['created'] . + "' message_id='" . $share['message_id'] . "']"; + $o .= $body; + $o .= "[/share]"; + } + +} + + + /** * @brief Encodes SimplePie_Item link arrays. * @@ -913,15 +1007,42 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { continue; } - $x = q("select mid from item where mid = '%s' and uid = %d limit 1", - dbesc($parent_mid), - intval($importer['channel_id']) - ); - if($x) - $parent_mid = $x[0]['mid']; + $pmid = ''; + $conv_id = get_iconfig($datarray,'ostatus','conversation'); + + if($conv_id) { + $c = q("select parent_mid from item left join iconfig on item.id = iconfig.iid where iconfig.cat = 'ostatus' and iconfig.k = 'conversation' and iconfig.v = '%s' and item.uid = %d order by item.id limit 1", + dbesc($conv_id), + intval($importer['channel_id']) + ); + if($c) { + $pmid = $x[0]['parent_mid']; + $datarray['parent_mid'] = $pmid; + } + } + else { + $x = q("select mid from item where mid = '%s' and uid = %d limit 1", + dbesc($parent_mid), + intval($importer['channel_id']) + ); + + if($x) { + $pmid = $x[0]['mid']; + $datarray['parent_mid'] = $pmid; + } + } - $datarray['parent_mid'] = $parent_mid; + if(! $pmid) { + // immediate parent wasn't found. Turn into a top-level post if permissions allow + // but save the thread_parent in case we need to refer to it later. + + if(! post_is_importable($datarray, $contact)) + continue; + $datarray['parent_mid'] = $datarray['mid']; + set_iconfig($datarray,'system','parent_mid',$parent_mid,true); + } + $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; @@ -1384,7 +1505,7 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ if(($item['parent'] != $item['id']) || ($item['parent_mid'] !== $item['mid']) || (($item['thr_parent'] !== '') && ($item['thr_parent'] !== $item['mid']))) { $parent_item = (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']); - $o .= '<thr:in-reply-to ref="' . 'X-ZOT:' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n"; + $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n"; } if(activity_match($item['obj_type'],ACTIVITY_OBJ_EVENT) && activity_match($item['verb'],ACTIVITY_POST)) { diff --git a/include/items.php b/include/items.php index 206a97c42..fa61d71de 100755 --- a/include/items.php +++ b/include/items.php @@ -219,6 +219,11 @@ function can_comment_on_post($observer_xchan, $item) { // logger('can_comment_on_post: comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); + $x = [ 'observer_hash' => $observer_xchan, 'item' => $item, 'allowed' => 'unset' ]; + call_hooks('can_comment_on_post',$x); + if($x['allowed'] !== 'unset') + return $x['allowed']; + if(! $observer_xchan) return false; diff --git a/include/text.php b/include/text.php index d8438b2b1..c633ba62a 100644 --- a/include/text.php +++ b/include/text.php @@ -1552,13 +1552,13 @@ function prepare_body(&$item,$attach = false) { // if original photo width is <= 640px prepend it to item body if($object['link'][0]['width'] && $object['link'][0]['width'] <= 640) { - $s .= '<div class="inline-photo-item-wrapper"><a href="' . zid(rawurldecode($object['id'])) . '" target="_blank"><img class="inline-photo-item" style="max-width:' . $object['link'][0]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][0]['href'])) . '"></a></div>' . $s; + $s .= '<div class="inline-photo-item-wrapper"><a href="' . zid(rawurldecode($object['id'])) . '" target="_blank" rel="nofollow noopener" ><img class="inline-photo-item" style="max-width:' . $object['link'][0]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][0]['href'])) . '"></a></div>' . $s; } // if original photo width is > 640px make it a cover photo if($object['link'][0]['width'] && $object['link'][0]['width'] > 640) { $scale = ((($object['link'][1]['width'] == 1024) || ($object['link'][1]['height'] == 1024)) ? 1 : 0); - $photo = '<a href="' . zid(rawurldecode($object['id'])) . '" target="_blank"><img style="max-width:' . $object['link'][$scale]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][$scale]['href'])) . '"></a>'; + $photo = '<a href="' . zid(rawurldecode($object['id'])) . '" target="_blank" rel="nofollow noopener"><img style="max-width:' . $object['link'][$scale]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][$scale]['href'])) . '"></a>'; } } diff --git a/view/tpl/photo_album_portfolio.tpl b/view/tpl/photo_album_portfolio.tpl new file mode 100755 index 000000000..7aadbc7e0 --- /dev/null +++ b/view/tpl/photo_album_portfolio.tpl @@ -0,0 +1,11 @@ +<div class="row column"> + <div id="photo-album-contents-{{$album_id}}"> + {{foreach $photos as $photo}} + {{include file="photo_portfolio.tpl"}} + {{/foreach}} + <div id="page-end"></div> + </div> +<div class="photos-end"></div> +<script>$(document).ready(function() { loadingPage = false; justifyPhotos('photo-album-contents-{{$album_id}}'); });</script> +<div id="page-spinner"></div> +</div>
\ No newline at end of file diff --git a/view/tpl/photo_portfolio.tpl b/view/tpl/photo_portfolio.tpl new file mode 100755 index 000000000..cbc8ea214 --- /dev/null +++ b/view/tpl/photo_portfolio.tpl @@ -0,0 +1,13 @@ + + + <a data-open="portfolioModal-{{$photo.resource_id}}" aria-controls="portfolioModal-{{$photo.resource_id}}" aria-haspopup="true" tabindex="0"> + <img class="thumbnail" src="{{$photo.src}}" alt="{{if $photo.album.name}}{{$photo.album.name}}{{elseif $photo.desc}}{{$photo.desc}}{{elseif $photo.alt}}{{$photo.alt}}{{else}}{{$photo.unknown}}{{/if}}" title="{{$photo.desc}}" id="photo-top-photo-{{$photo.resource_id}}"> + </a> + + <div class="full reveal without-overlay" id="portfolioModal-{{$photo.resource_id}}" data-reveal="f175mw-reveal" role="dialog" aria-hidden="true" data-yeti-box="portfolioModal-{{$photo.resource_id}}" data-resize="portfolioModal-{{$photo.resource_id}}"> + <h5>{{$photo.desc}}</h5> + <img class="thumbnail" src="{{$photo.fullsrc}}" alt="{{if $photo.album.name}}{{$photo.album.name}}{{elseif $photo.desc}}{{$photo.desc}}{{elseif $photo.alt}}{{$photo.alt}}{{else}}{{$photo.unknown}}{{/if}}" title="{{$photo.desc}}" id="photo-top-photo-{{$photo.resource_id}}x"> + <button class="close-button" data-close="" aria-label="Close reveal" type="button"> + <span aria-hidden="true">×</span> + </button> + </div> |