aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--Zotlabs/Storage/Directory.php32
-rw-r--r--Zotlabs/Storage/File.php27
-rwxr-xr-xboot.php4
-rw-r--r--doc/context/admin/security/help.html3
-rw-r--r--doc/context/channel/help.html24
-rw-r--r--doc/context/cloud/help.html22
-rw-r--r--doc/context/mail/help.html10
-rw-r--r--doc/context/network/help.html26
-rw-r--r--doc/context/photos/help.html22
-rw-r--r--doc/context/profile/help.html22
-rw-r--r--doc/federate.bb7
-rw-r--r--doc/filesync.md61
-rw-r--r--include/attach.php92
-rw-r--r--include/auth.php4
-rw-r--r--include/conversation.php3
-rw-r--r--include/features.php2
-rw-r--r--include/identity.php3
-rw-r--r--include/import.php251
-rwxr-xr-xinclude/items.php26
-rw-r--r--include/nav.php11
-rw-r--r--include/network.php22
-rw-r--r--include/photos.php12
-rw-r--r--include/reddav.php3
-rw-r--r--include/session.php4
-rw-r--r--include/text.php25
-rw-r--r--include/widgets.php8
-rw-r--r--include/zot.php3
-rw-r--r--library/justifiedGallery/jquery.justifiedGallery.js162
-rw-r--r--library/justifiedGallery/jquery.justifiedGallery.min.js4
-rw-r--r--library/justifiedGallery/justifiedGallery.css2
-rw-r--r--library/justifiedGallery/justifiedGallery.min.css2
-rw-r--r--mod/chat.php6
-rw-r--r--mod/getfile.php97
-rw-r--r--mod/help.php14
-rw-r--r--mod/notes.php12
-rw-r--r--mod/photos.php71
-rw-r--r--mod/wall_attach.php17
-rwxr-xr-xutil/add_addon_repo14
-rwxr-xr-xutil/add_theme_repo14
-rwxr-xr-xutil/add_widget_repo13
-rw-r--r--view/css/conversation.css6
-rw-r--r--view/js/autocomplete.js120
-rw-r--r--view/js/main.js2
-rwxr-xr-xview/tpl/jot-header.tpl1
-rwxr-xr-xview/tpl/login.tpl2
-rwxr-xr-xview/tpl/nav.tpl50
47 files changed, 1186 insertions, 168 deletions
diff --git a/README.md b/README.md
index 645065948..ad7a4a9ca 100644
--- a/README.md
+++ b/README.md
@@ -3,26 +3,26 @@
Hubzilla - Community Server
===========================
-Help us redefine the web - using integrated and united community websites.
---------------------------------------------------------------------------
+Connected and linked web communities.
+-------------------------------------
<p align="center" markdown="1">
<em><a href="https://github.com/redmatrix/hubzilla/blob/master/install/INSTALL.txt">Installing Hubzilla</a></em>
</p>
-**What are Hubs?**
+**What are Hubz?**
-Hubs are independent general-purpose websites that not only connect with their associated members and viewers, but also connect together to exchange personal communications and other information with each other.
+Hubz are independent general-purpose websites that not only connect with their associated members and viewers, but also connect together to exchange personal communications and other information with each other.
This allows hub members on any hub to securely and privately share anything; with anybody, on any hub - anywhere; or share stuff publicly with anybody on the internet if desired.
-**Hubzilla** is the server software which makes this possible. It is a sophisticated and unique combination of an open source content management system and a decentralised identity, communications, and permissions framework and protocol suite, built using common webserver technology (PHP/MySQL/Apache, although Mariadb or Postgres and Nginx could also be used - we're pretty easy). The end result is a level of systems integration, privacy control, and communications features that you wouldn't think are possible in either a content management system or a decentralised communications network. It also brings a new level of cooperation and privacy to the web and introduces the concept of personally owned "single sign-on" to web services across the entire internet.
+**Hubzilla** is the server software which makes this possible. It is a sophisticated and unique combination of an open source content management system and a decentralised identity, communications, and permissions framework and protocol suite, built using common webserver technology (PHP/MySQL/Apache and popular variants). The end result is a level of systems integration, privacy control, and communications features that you wouldn't think are possible in either a content management system or a decentralised communications network. It also brings a new level of cooperation and privacy to the web and introduces the concept of personally owned "single sign-on" to web services across the entire internet.
-Hubzilla hubs are
+Hubzilla hubz are
* decentralised
* inherently social
-* optionally inter-networked with other hubs
-* privacy-enabled (privacy exclusions work across the entire internet to any registered identity on any compatible hubs)
+* optionally inter-networked with other hubz
+* privacy-enabled (privacy exclusions work across the entire internet to any registered identity on any compatible hubz)
Possible website applications include
diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php
index 0347ce087..edbef5a95 100644
--- a/Zotlabs/Storage/Directory.php
+++ b/Zotlabs/Storage/Directory.php
@@ -168,6 +168,14 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
intval($this->auth->owner_id)
);
+
+ $ch = channelx_by_n($this->auth->owner_id);
+ if($ch) {
+ $sync = attach_export_data($ch,$this->folder_hash);
+ if($sync)
+ build_sync_packet($ch['channel_id'],array('file' => array($sync)));
+ }
+
$this->red_path = $new_path;
}
@@ -335,6 +343,12 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
$p = photo_upload($c[0],\App::get_observer(),$args);
}
+ $sync = attach_export_data($c[0],$hash);
+
+ if($sync)
+ build_sync_packet($c[0]['channel_id'],array('file' => array($sync)));
+
+
}
/**
@@ -356,7 +370,14 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
if ($r) {
$result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash));
- if (! $result['success']) {
+
+ if($result['success']) {
+ $sync = attach_export_data($r[0],$ret['data']['hash']);
+ if($sync) {
+ build_sync_packet($r[0]['channel_id'],array('file' => array($sync)));
+ }
+ }
+ else {
logger('error ' . print_r($result, true), LOGGER_DEBUG);
}
}
@@ -380,6 +401,15 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
}
attach_delete($this->auth->owner_id, $this->folder_hash);
+
+ $ch = channelx_by_n($this->auth->owner_id);
+ if($ch) {
+ $sync = attach_export_data($ch,$this->folder_hash,true);
+ if($sync)
+ build_sync_packet($ch['channel_id'],array('file' => array($sync)));
+ }
+
+
}
diff --git a/Zotlabs/Storage/File.php b/Zotlabs/Storage/File.php
index a4bf3f49d..897f24edd 100644
--- a/Zotlabs/Storage/File.php
+++ b/Zotlabs/Storage/File.php
@@ -84,6 +84,20 @@ class File extends DAV\Node implements DAV\IFile {
dbesc($this->data['hash']),
intval($this->data['id'])
);
+
+ if($this->data->is_photo) {
+ $r = q("update photo set filename = '%s' where resource_id = '%s' and uid = %d",
+ dbesc($newName),
+ dbesc($this->data['hash']),
+ intval($this->auth->owner_id)
+ );
+ }
+ $ch = channelx_by_n($this->auth->owner_id);
+ if($ch) {
+ $sync = attach_export_data($ch,$this->data['hash']);
+ if($sync)
+ build_sync_packet($ch['channel_id'],array('file' => array($sync)));
+ }
}
/**
@@ -205,6 +219,12 @@ class File extends DAV\Node implements DAV\IFile {
return;
}
}
+
+ $sync = attach_export_data($c[0],$this->data['hash']);
+
+ if($sync)
+ build_sync_packet($c[0]['channel_id'],array('file' => array($sync)));
+
}
/**
@@ -318,5 +338,12 @@ class File extends DAV\Node implements DAV\IFile {
}
attach_delete($this->auth->owner_id, $this->data['hash']);
+
+ $ch = channelx_by_n($this->auth->owner_id);
+ if($ch) {
+ $sync = attach_export_data($ch,$this->data['hash'],true);
+ if($sync)
+ build_sync_packet($ch['channel_id'],array('file' => array($sync)));
+ }
}
}
diff --git a/boot.php b/boot.php
index e913dbc96..386a419dc 100755
--- a/boot.php
+++ b/boot.php
@@ -454,7 +454,7 @@ define ( 'TERM_OBJ_APP', 7 );
/**
* various namespaces we may need to parse
*/
-
+define ( 'PROTOCOL_ZOT', 'http://purl.org/zot/protocol' );
define ( 'NAMESPACE_ZOT', 'http://purl.org/zot' );
define ( 'NAMESPACE_DFRN' , 'http://purl.org/macgirvin/dfrn/1.0' );
define ( 'NAMESPACE_THREAD' , 'http://purl.org/syndication/thread/1.0' );
@@ -1581,7 +1581,7 @@ function login($register = false, $form_id = 'main-login', $hiddens=false) {
'$form_id' => $form_id,
'$lname' => array('username', t('Email') , '', ''),
'$lpassword' => array('password', t('Password'), '', ''),
- '$remember' => array('remember', t('Remember me'), '', '',array(t('No'),t('Yes'))),
+ '$remember_me' => array('remember_me', t('Remember me'), '', '',array(t('No'),t('Yes'))),
'$hiddens' => $hiddens,
'$register' => $reg,
'$lostpass' => t('Forgot your password?'),
diff --git a/doc/context/admin/security/help.html b/doc/context/admin/security/help.html
new file mode 100644
index 000000000..e9a741a5e
--- /dev/null
+++ b/doc/context/admin/security/help.html
@@ -0,0 +1,3 @@
+<h3>Security Settings</h3>
+<p>This page contains various administrator settings related to security.</p>
+<p>To save any changes you make to these settings, you must press the Submit button.</p> \ No newline at end of file
diff --git a/doc/context/channel/help.html b/doc/context/channel/help.html
new file mode 100644
index 000000000..810913ff3
--- /dev/null
+++ b/doc/context/channel/help.html
@@ -0,0 +1,24 @@
+<script>
+ var contextualHelp1 = function (target, openSidePanel) {
+ $("#help-content").removeClass('help-content-open'); // Close the help panel
+ $("#navbar-collapse-1").removeClass('in'); // Collapse the navbar for small screens
+ if (openSidePanel) {
+ $("main").addClass('region_1-on'); // Open the side panel to highlight element
+ } else {
+ $("main").removeClass('region_1-on');
+ }
+ // Animate the page scroll to the element and then pulse the element to direct attention
+ $('html,body').animate({scrollTop: $(target).offset().top - $('#navbar-collapse-1').height() - 20}, 'slow');
+ for (i = 0; i < 3; i++) {
+ $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0);
+ }
+ }
+</script>
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>This is the home page of a channel. It is similar to someone's profile "wall" in a social network context. Posts created by the channel are displayed according to the observer's viewing permissions.</dd>
+ <dt>Create a Post</dt>
+ <dd>If you have permission to create posts on the channel page, then you will see the post editor at the top.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
+ <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/context/cloud/help.html b/doc/context/cloud/help.html
new file mode 100644
index 000000000..105947517
--- /dev/null
+++ b/doc/context/cloud/help.html
@@ -0,0 +1,22 @@
+<script>
+ var contextualHelp1 = function (target, openSidePanel) {
+ $("#help-content").removeClass('help-content-open'); // Close the help panel
+ $("#navbar-collapse-1").removeClass('in'); // Collapse the navbar for small screens
+ if (openSidePanel) {
+ $("main").addClass('region_1-on'); // Open the side panel to highlight element
+ } else {
+ $("main").removeClass('region_1-on');
+ }
+ // Animate the page scroll to the element and then pulse the element to direct attention
+ $('html,body').animate({scrollTop: $(target).offset().top - $('#navbar-collapse-1').height() - 20}, 'slow');
+ for (i = 0; i < 3; i++) {
+ $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0);
+ }
+ }
+</script>
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>This page displays a channel's "cloud" files. The files visible to the observer depend on the individual file permissions set by the channel owner. If you have permission to create/upload files you will see control buttons above the file list.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
+ <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/context/mail/help.html b/doc/context/mail/help.html
new file mode 100644
index 000000000..a2361a135
--- /dev/null
+++ b/doc/context/mail/help.html
@@ -0,0 +1,10 @@
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>The messages displayed in private mail are visible only to you and the single recipient. </dd>
+ <dt>Combined View</dt>
+ <dd>Complete conversations can be viewed in a continuous thread by selecting <b>Combined View</b>. Available conversations are displayed beneath the menu in the side panel.</dd>
+ <dt>Inbox/Outbox</dt>
+ <dd>Individual sent messages are viewed by selecting <b>Outbox</b>, and incoming messages are viewed using the <b>Inbox</b> filter.</dd>
+ <dt>New Message</dt>
+ <dd>Individual messages have delivery reports that can be viewed using the drop-down menu. Messages can also be recalled from the same menu, which can prevent the recipient from viewing the message <i>if they have not already read it</i>.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/context/network/help.html b/doc/context/network/help.html
new file mode 100644
index 000000000..956af7380
--- /dev/null
+++ b/doc/context/network/help.html
@@ -0,0 +1,26 @@
+<script>
+ var contextualHelp1 = function (target, openSidePanel) {
+ $("#help-content").removeClass('help-content-open'); // Close the help panel
+ $("#navbar-collapse-1").removeClass('in'); // Collapse the navbar for small screens
+ if (openSidePanel) {
+ $("main").addClass('region_1-on'); // Open the side panel to highlight element
+ } else {
+ $("main").removeClass('region_1-on');
+ }
+ // Animate the page scroll to the element and then pulse the element to direct attention
+ $('html,body').animate({scrollTop: $(target).offset().top - $('#navbar-collapse-1').height() - 20}, 'slow');
+ for (i = 0; i < 3; i++) {
+ $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0);
+ }
+ }
+</script>
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>The network page displays a stream of posts and conversations, typically ordered by the most recently updated. This page is highly customizable.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#profile-jot-wrapper", 0); return false;' title="Click to highlight element...">Create a Post</a></dt>
+ <dd>At the top of the page there is a text box that says "Share". Clicking this box opens a new post editor. The post editor is customizable, but the basic editor provides fields for a post body and an optional post <b>Title</b>. Buttons below the text area to the left provide shortcuts to text formatting and inserting links, images, and other data into the post. The buttons to the right provide a post preview, the post permissions setting, and a <b>Submit</b> button to send the post.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#group-sidebar", 1); return false;' title="Click to highlight element...">Privacy Groups</a></dt>
+ <dd>The privacy groups you have created are displayed in the side panel. Selecting them filters posts to those created by channels in the chosen group.</dd>
+ <dt><a href='#' onclick='$("#dbtn-acl").click(); return false;' title="Click to highlight element...">Post Permissions</a></dt>
+ <dd>The access control list (ACL) is what you use to set who can see your new post. Pressing the ACL button beside the Submit button will display a dialog in which you can select what channels and/or privacy groups can see the post. You can also select who is explicitly denied access. For example, say you are planning a surprise party for a friend. You can send an invitation post to everyone in your <b>Friends</b> group <i>except</i> the friend you are surprising. In this case you "show" the <b>Friends</b> group but "don't show" that one person.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/context/photos/help.html b/doc/context/photos/help.html
new file mode 100644
index 000000000..f41611f8d
--- /dev/null
+++ b/doc/context/photos/help.html
@@ -0,0 +1,22 @@
+<script>
+ var contextualHelp1 = function (target, openSidePanel) {
+ $("#help-content").removeClass('help-content-open'); // Close the help panel
+ $("#navbar-collapse-1").removeClass('in'); // Collapse the navbar for small screens
+ if (openSidePanel) {
+ $("main").addClass('region_1-on'); // Open the side panel to highlight element
+ } else {
+ $("main").removeClass('region_1-on');
+ }
+ // Animate the page scroll to the element and then pulse the element to direct attention
+ $('html,body').animate({scrollTop: $(target).offset().top - $('#navbar-collapse-1').height() - 20}, 'slow');
+ for (i = 0; i < 3; i++) {
+ $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0);
+ }
+ }
+</script>
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>This page displays a channel's photo albums. The images visible to the observer depend on the individual image permissions.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
+ <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/context/profile/help.html b/doc/context/profile/help.html
new file mode 100644
index 000000000..0d4abb8cb
--- /dev/null
+++ b/doc/context/profile/help.html
@@ -0,0 +1,22 @@
+<script>
+ var contextualHelp1 = function (target, openSidePanel) {
+ $("#help-content").removeClass('help-content-open'); // Close the help panel
+ $("#navbar-collapse-1").removeClass('in'); // Collapse the navbar for small screens
+ if (openSidePanel) {
+ $("main").addClass('region_1-on'); // Open the side panel to highlight element
+ } else {
+ $("main").removeClass('region_1-on');
+ }
+ // Animate the page scroll to the element and then pulse the element to direct attention
+ $('html,body').animate({scrollTop: $(target).offset().top - $('#navbar-collapse-1').height() - 20}, 'slow');
+ for (i = 0; i < 3; i++) {
+ $(target).fadeTo('slow', 0.1).fadeTo('slow', 1.0);
+ }
+ }
+</script>
+<dl class="dl-horizontal">
+ <dt>General</dt>
+ <dd>This is the profile page of a channel. It typically displays information describing the channel. If the channel represents a person in a social network, for example, then the profile might provide contact information and other personal details about the person. Channels can have multiple profiles, where the displayed profile depends on the observer.</dd>
+ <dt><a href='#' onclick='contextualHelp1("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
+ <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
+</dl> \ No newline at end of file
diff --git a/doc/federate.bb b/doc/federate.bb
index 5d253913d..9137ec160 100644
--- a/doc/federate.bb
+++ b/doc/federate.bb
@@ -8,7 +8,7 @@ There are three main components to writing federation plugins. These are:
In addition, federation drivers must handle
-[4] differences in privacy policies
+[4] differences in privacy policies (and content formats)
[h3]Making connections[/h3]
@@ -36,8 +36,11 @@ Additional information that your plugin requires for communication can be stored
When a connection is made, we generally call the notifier (include/notifier.php) to send a message to the remote channel. This is bound to the hook 'permissions_create'. Your plugin will need to handle this in order to send a "follow" or "make friends" message to the other network.
-Note: The first stage zot lookup will be replaced with a webfinger lookup. This work is in progress. A separate lookup was required initially as webfinger does not allow non-SSL connections. We will provide non-SSL zot lookups (usually test and development sites) via the "old" XRD based webfinger to avoid this limitation.
+Notes: The first stage zot lookup will be replaced with a webfinger lookup. This work is in progress. A separate lookup was required initially as webfinger does not allow non-SSL connections. We will provide non-SSL zot lookups (usually test and development sites) via the "old" XRD based webfinger to avoid this limitation.
+The core application will attempt to create xchan records for projects identified as members of the "open web"; currently Hubzilla, Friendica, Diaspora, GNU-Social and Pump.io. This is so that comments can be passed amongst project sites and the network correctly identified. A federation plugin is required to fully federate with other networks, but comments may be passed to sites without such a plugin installed so that there are no unexplained holes in conversations.
+
+The core application must also provide signing ability for Diaspora comments since they require a special signing format and must be signed by the comment author regardless of whether that channel federates with Diaspora. The owner of the conversation may federate with Diaspora so the comments must be signed. This is unfortunate but necessary.
[h3]Sending Messages[/h3]
diff --git a/doc/filesync.md b/doc/filesync.md
new file mode 100644
index 000000000..4c64bdb09
--- /dev/null
+++ b/doc/filesync.md
@@ -0,0 +1,61 @@
+File Sync and Clone
+===================
+
+
+
+File cloning across multiple instances of a channel is a very hard problem, due to the nature of PHP memory allocation. This needs to be handled dramatically differently than cloning or syncing of other information. (Processing one large video file or 40-50 photos could exhaust memory). Therefore we can't easily just dump all the data to a dump file and sequentially process it. Loading the dump file itself is likely to exhaust memory.
+
+There are also two primary operations we are considering. The first is the hardest - saving and then importing all your channel information into a new channel clone. The second is synchronising file changes as they occur across two or more "active" clones.
+
+For the first cut at this tool we will concentrate on the second case, while trying to maintain some measure of compatibility with the first case so that we can re-use the same tools.
+
+Meta Data
+=========
+
+
+First we need the metadata for the file in order to precisely re-construct its structure on another site. This requires the following information:
+
+'attach' structure (without file contents - which is the default) for the file itself **and** its parent directories so that we can re-create its precise place in the file system, since we do not know if the parent directory has been imported previously or ever.
+
+'photo' structure for any photo elements which were created as a result of uploading this file into the system. This typically contains several different 'scales' or thumbnail images, some of which may be cropped for profile photo use or cover photo use. We need to retain the cropping information which is not present in the metadata, but only in the stored data. The actual thumbnail image data may or may not be included in the metadata. A cover photo of large scale (scale #7) could potentially cause memory issues. Not as bad as a 100M video, but if you have several of these they could add up.
+
+'item' entries which are linked to this file. These can be file share activities, the "parent item" linked to photos, and any attached conversation items (photo likes, comments, etc.)
+
+All of these items will require URL replacement and re-signing of the item as they are relocated to another site.
+
+
+File Data
+=========
+
+Then we have the actual file data we need to reconstruct the file. This needs to be stored separately from the meta-data to avoid memory exhaustion when processing. The actual file data can be used to reconstruct the attach structure and the first four photo scales. If this is a photo, we need access to the "#4 scale" (profile photo) and the #7 scale (cover photo) as they were originally cropped. All other thumbnails can be generated from these.
+
+
+
+File Sync
+=========
+
+
+We will consider this operation first because it is probably the most straightforward to implement. When a photo is added to or removed or changed from the source system, we will send a clone sync packet to all known clones containing the metadata - but **no file data** . We can only send one sync packet per file operation that needs to be synced.
+
+The receiving end will create and perform URL translation on all the metadata structures and store them. Then it will need to fetch the actual data. Assuming CURL supports streaming, an authenticated request is sent to the original site and the original file is requested and streamed directly to disk (bypassing all processing). If photo scale #4 or scale #7 is required, these are requested and stored into their respective structures. We're assuming in this case that the cover photo large scale will not exhaust memory. If CURL cannot be made to support streaming, request packets need to be queued and sent to the origination site to obtain "chunks" of the file and re-assembled once all chunks have been retrieved.
+
+The authenticated request depends on the mechanism. For CURL streaming, some signed secret with a timestamp will probably need to be generated and posted to the file origination site. Then the data can be retrieved with minimal internal processing and dumped directly to disk using stdio buffering. In the case of a zot request, the zot request packet will be validated, however scheduling chunk batches and re-assembling them could be tricky.
+
+
+File Backup/Restore
+===================
+
+This is much more complicated as we do not have an authenticate web server to request data from. The metadata can be mostly the same, but we need some form of signalling that we will not be fetching the file via the web. This will likely require a client side process to parse each metadata file and locate a file on disk which it is associated with. Then the data would need to be streamed to the destination server with a special endpoint designed for this task. A java app might be the best option here to retain platform neutrality.
+
+Another option would be to use WebDAV for this step. The metadata files would be uploaded first, and then the data files. If a data file corresponded to an existing metadata file, the metadata would be processed; the file stored appropriately, and the metadata file then removed. In this case, photos of scales 4 and 7 would need to be provided in the metadata.
+
+
+Optionally, this step could also be performed with a filesystem local to the destination server. This would be the highest performance, and a suite of shell-based tools (in the case of Linux) could perform the "client-side" of the task.
+
+The complexity of this task mandates careful planning into how the data is organised and stored and if necessary backed up remotely or transmitted for backup by the source website.
+
+
+Backward Compatibility
+======================
+
+There are some obvious issues with making data available for backup or cloning which existed on the system prior to the existence of restore/sync tools. To keep the tools themselves relatively uncomplicated (to the extent possible given the constraints) backward compatibility may have to be preformed by dedicated plugin or addon. \ No newline at end of file
diff --git a/include/attach.php b/include/attach.php
index 4ecc273e9..ae4681994 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -864,6 +864,12 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
// This would've been called already with a success result in photos_upload() if it was a photo.
call_hooks('photo_upload_end',$ret);
}
+
+ $sync = attach_export_data($channel,$hash);
+
+ if($sync)
+ build_sync_packet($channel['channel_id'],array('file' => array($sync)));
+
return $ret;
}
@@ -1814,3 +1820,89 @@ function filepath_macro($s) {
}
+function attach_export_data($channel, $resource_id, $deleted = false) {
+
+ $ret = array();
+
+ $paths = array();
+
+ $hash_ptr = $resource_id;
+
+ $ret['fetch_url'] = z_root() . '/getfile';
+ $ret['original_channel'] = $channel['channel_address'];
+
+
+ if($deleted) {
+ $ret['attach'] = array(array('hash' => $resource_id, 'deleted' => 1));
+ return $ret;
+ }
+
+ do {
+ $r = q("select * from attach where hash = '%s' and uid = %d limit 1",
+ dbesc($hash_ptr),
+ intval($channel['channel_id'])
+ );
+ if(! $r)
+ break;
+
+ if($hash_ptr === $resource_id) {
+ $attach_ptr = $r[0];
+ }
+
+ $hash_ptr = $r[0]['folder'];
+ $paths[] = $r[0];
+ } while($hash_ptr);
+
+
+
+
+ $paths = array_reverse($paths);
+
+ $ret['attach'] = $paths;
+
+
+ if($attach_ptr['is_photo']) {
+ $r = q("select * from photo where resource_id = '%s' and uid = %d order by scale asc",
+ dbesc($resource_id),
+ intval($channel['channel_id'])
+ );
+ if($r) {
+ for($x = 0; $x < count($r); $x ++) {
+ $r[$x]['data'] = base64_encode($r[$x]['data']);
+ }
+ $ret['photo'] = $r;
+ }
+
+ $r = q("select * from item where resource_id = '%s' and resource_type = 'photo' and uid = %d ",
+ dbesc($resource_id),
+ intval($channel['channel_id'])
+ );
+ if($r) {
+ $ret['item'] = array();
+ $items = q("select item.*, item.id as item_id from item where item.parent = %d ",
+ intval($r[0]['id'])
+ );
+ if($items) {
+ xchan_query($items);
+ $items = fetch_post_tags($items,true);
+ foreach($items as $rr)
+ $ret['item'][] = encode_item($rr,true);
+ }
+ }
+ }
+
+ return $ret;
+
+}
+
+
+/* strip off 'store/nickname/' from the provided path */
+
+function get_attach_binname($s) {
+ $p = $s;
+ if(strpos($s,'store/') === 0) {
+ $p = substr($s,6);
+ $p = substr($p,strpos($p,'/')+1);
+ }
+ return $p;
+} \ No newline at end of file
diff --git a/include/auth.php b/include/auth.php
index aaec45c40..228143e12 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -246,7 +246,7 @@ else {
notice( t('Failed authentication') . EOL);
}
- logger('authenticate: ' . print_r(App::$account, true), LOGGER_DEBUG);
+ logger('authenticate: ' . print_r(App::$account, true), LOGGER_ALL);
}
if((! $record) || (! count($record))) {
@@ -274,7 +274,7 @@ else {
// (i.e. expire when the browser is closed), even when there's a time expiration
// on the cookie
- if($_POST['remember']) {
+ if($_POST['remember_me']) {
new_cookie(31449600); // one year
}
else {
diff --git a/include/conversation.php b/include/conversation.php
index 829e85382..5e9ba16a1 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -1592,6 +1592,7 @@ function network_tabs() {
function profile_tabs($a, $is_owner = false, $nickname = null){
// Don't provide any profile tabs if we're running as the sys channel
+
if (App::$is_sys)
return;
@@ -1669,7 +1670,7 @@ function profile_tabs($a, $is_owner = false, $nickname = null){
}
- if ($p['chat']) {
+ if ($p['chat'] && feature_enabled($uid,'ajaxchat')) {
require_once('include/chat.php');
$has_chats = chatroom_list_count($uid);
if ($has_chats) {
diff --git a/include/features.php b/include/features.php
index ff6b71d4c..38700f9f5 100644
--- a/include/features.php
+++ b/include/features.php
@@ -56,7 +56,7 @@ function get_features($filtered = true) {
array('private_notes', t('Private Notes'), t('Enables a tool to store notes and reminders (note: not encrypted)'),false,get_config('feature_lock','private_notes')),
array('nav_channel_select', t('Navigation Channel Select'), t('Change channels directly from within the navigation dropdown menu'),false,get_config('feature_lock','nav_channel_select')),
array('photo_location', t('Photo Location'), t('If location data is available on uploaded photos, link this to a map.'),false,get_config('feature_lock','photo_location')),
-
+ array('ajaxchat', t('Access Controlled Chatrooms'), t('Provide chatrooms and chat services with access control.'),true,get_config('feature_lock','ajaxchat')),
array('smart_birthdays', t('Smart Birthdays'), t('Make birthday events timezone aware in case your friends are scattered across the planet.'),true,get_config('feature_lock','smart_birthdays')),
array('expert', t('Expert Mode'), t('Enable Expert Mode to provide advanced configuration options'),false,get_config('feature_lock','expert')),
array('premium_channel', t('Premium Channel'), t('Allows you to set restrictions and terms on those that connect with your channel'),false,get_config('feature_lock','premium_channel')),
diff --git a/include/identity.php b/include/identity.php
index 849742c8e..9bb4fb3c5 100644
--- a/include/identity.php
+++ b/include/identity.php
@@ -550,7 +550,8 @@ function identity_basic_export($channel_id, $items = false) {
if($r)
$ret['config'] = $r;
- $r = q("select type, data, os_storage from photo where scale = 4 and profile = 1 and uid = %d limit 1",
+ $r = q("select type, data, os_storage from photo where scale = 4 and photo_usage = %d and uid = %d limit 1",
+ intval(PHOTO_PROFILE),
intval($channel_id)
);
diff --git a/include/import.php b/include/import.php
index 9a57012b2..f15dedfe0 100644
--- a/include/import.php
+++ b/include/import.php
@@ -870,6 +870,257 @@ function import_mail($channel,$mails) {
+function sync_files($channel,$files) {
+ require_once('include/attach.php');
+
+ if($channel && $files) {
+ foreach($files as $f) {
+ if(! $f)
+ continue;
+
+ $fetch_url = $f['fetch_url'];
+ $oldbase = dirname($fetch_url);
+ $original_channel = $f['original_channel'];
+
+ if(! ($fetch_url && $original_channel))
+ continue;
+
+ if($f['attach']) {
+ $attachment_stored = false;
+ foreach($f['attach'] as $att) {
+
+ if($att['deleted']) {
+ attach_delete($channel,$att['hash']);
+ continue;
+ }
+
+ $attach_exists = false;
+ $x = attach_by_hash($att['hash']);
+
+ if($x) {
+ $attach_exists = true;
+ $attach_id = $x[0]['id'];
+ }
+
+ $newfname = 'store/' . $channel['channel_address'] . '/' . get_attach_binname($att['data']);
+
+ unset($att['id']);
+ $att['aid'] = $channel['channel_account_id'];
+ $att['uid'] = $channel['channel_id'];
+
+
+ // check for duplicate folder names with the same parent.
+ // If we have a duplicate that doesn't match this hash value
+ // change the name so that the contents won't be "covered over"
+ // by the existing directory. Use the same logic we use for
+ // duplicate files.
+
+ if(strpos($att['filename'],'.') !== false) {
+ $basename = substr($att['filename'],0,strrpos($att['filename'],'.'));
+ $ext = substr($att['filename'],strrpos($att['filename'],'.'));
+ }
+ else {
+ $basename = $att['filename'];
+ $ext = '';
+ }
+
+ $r = q("select filename from attach where ( filename = '%s' OR filename like '%s' ) and folder == '%s' and hash != '%s' ",
+ dbesc($basename . $ext),
+ dbesc($basename . '(%)' . $ext),
+ dbesc($att['folder']),
+ dbesc($att['hash'])
+ );
+
+ if($r) {
+ $x = 1;
+
+ do {
+ $found = false;
+ foreach($r as $rr) {
+ if($rr['filename'] === $basename . '(' . $x . ')' . $ext) {
+ $found = true;
+ break;
+ }
+ }
+ if($found)
+ $x++;
+ }
+ while($found);
+ $att['filename'] = $basename . '(' . $x . ')' . $ext;
+ }
+ else
+ $att['filename'] = $basename . $ext;
+
+ // end duplicate detection
+
+// @fixme - update attachment structures if they are modified rather than created
+
+ $att['data'] = $newfname;
+
+ // Note: we use $att['hash'] below after it has been escaped to
+ // fetch the file contents.
+ // If the hash ever contains any escapable chars this could cause
+ // problems. Currently it does not.
+
+ dbesc_array($att);
+
+
+ if($attach_exists) {
+ $str = '';
+ foreach($att as $k => $v) {
+ if($str)
+ $str .= ",";
+ $str .= " `" . $k . "` = '" . $v . "' ";
+ }
+ $r = dbq("update `attach` set " . $str . " where id = " . intval($attach_id) );
+ }
+ else {
+ $r = dbq("INSERT INTO attach (`"
+ . implode("`, `", array_keys($att))
+ . "`) VALUES ('"
+ . implode("', '", array_values($att))
+ . "')" );
+ }
+
+
+ // is this a directory?
+
+ if($att['filetype'] === 'multipart/mixed' && $att['is_dir']) {
+ os_mkdir($newfname, STORAGE_DEFAULT_PERMISSIONS,true);
+ continue;
+ }
+ else {
+
+ // it's a file
+ // for the sync version of this algorithm (as opposed to 'offline import')
+ // we will fetch the actual file from the source server so it can be
+ // streamed directly to disk and avoid consuming PHP memory if it's a huge
+ // audio/video file or something.
+
+ $time = datetime_convert();
+
+ $parr = array('hash' => $channel['channel_hash'],
+ 'time' => $time,
+ 'resource' => $att['hash'],
+ 'revision' => 0,
+ 'signature' => base64url_encode(rsa_sign($channel['channel_hash'] . '.' . $time, $channel['channel_prvkey']))
+ );
+
+ $store_path = $newfname;
+
+ $fp = fopen($newfname,'w');
+ if(! $fp) {
+ logger('failed to open storage file.',LOGGER_NORMAL,LOG_ERR);
+ continue;
+ }
+ $redirects = 0;
+ $x = z_post_url($fetch_url,$parr,$redirects,array('filep' => $fp));
+ fclose($fp);
+
+ if($x['success']) {
+ $attachment_stored = true;
+ }
+ continue;
+ }
+ }
+ }
+ if(! $attachment_stored) {
+ // @TODO should we queue this and retry or delete everything or what?
+ logger('attachment store failed',LOGGER_NORMAL,LOG_ERR);
+ }
+ if($f['photo']) {
+ foreach($f['photo'] as $p) {
+ unset($p['id']);
+ $p['aid'] = $channel['channel_account_id'];
+ $p['uid'] = $channel['channel_id'];
+
+ // if this is a profile photo, undo the profile photo bit
+ // for any other photo which previously held it.
+
+ if($p['photo_usage'] == PHOTO_PROFILE) {
+ $e = q("update photo set photo_usage = %d where photo_usage = %d
+ and resource_id != '%s' and uid = %d ",
+ intval(PHOTO_NORMAL),
+ intval(PHOTO_PROFILE),
+ dbesc($p['resource_id']),
+ intval($channel['channel_id'])
+ );
+ }
+
+ // same for cover photos
+
+ if($p['photo_usage'] == PHOTO_COVER) {
+ $e = q("update photo set photo_usage = %d where photo_usage = %d
+ and resource_id != '%s' and uid = %d ",
+ intval(PHOTO_NORMAL),
+ intval(PHOTO_COVER),
+ dbesc($p['resource_id']),
+ intval($channel['channel_id'])
+ );
+ }
+
+ if($p['scale'] === 0 && $p['os_storage'])
+ $p['data'] = $store_path;
+ else
+ $p['data'] = base64_decode($p['data']);
+
+
+ $exists = q("select * from photo where resource_id = '%s' and scale = %d and uid = %d limit 1",
+ dbesc($p['resource_id']),
+ intval($p['scale']),
+ intval($channel['channel_id'])
+ );
+
+ dbesc_array($p);
+
+ if($exists) {
+ $str = '';
+ foreach($p as $k => $v) {
+ if($str)
+ $str .= ",";
+ $str .= " `" . $k . "` = '" . $v . "' ";
+ }
+ $r = dbq("update `photo` set " . $str . " where id = " . intval($exists[0]['id']) );
+ }
+ else {
+ $r = dbq("INSERT INTO photo (`"
+ . implode("`, `", array_keys($p))
+ . "`) VALUES ('"
+ . implode("', '", array_values($p))
+ . "')" );
+ }
+ }
+ }
+ if($f['item']) {
+ sync_items($channel,$f['item']);
+ foreach($f['item'] as $i) {
+ if($i['message_id'] !== $i['message_parent'])
+ continue;
+ $r = q("select * from item where mid = '%s' and uid = %d limit 1",
+ dbesc($i['message_id']),
+ intval($channel['channel_id'])
+ );
+ if($r) {
+ $item = $r[0];
+ item_url_replace($channel,$item,$oldbase,z_root(),$original_channel);
+
+ dbesc_array($item);
+ $item_id = $item['id'];
+ unset($item['id']);
+ $str = '';
+ foreach($item as $k => $v) {
+ if($str)
+ $str .= ",";
+ $str .= " `" . $k . "` = '" . $v . "' ";
+ }
+
+ $r = dbq("update `item` set " . $str . " where id = " . $item_id );
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/include/items.php b/include/items.php
index 07cf2e0e8..95822c0ba 100755
--- a/include/items.php
+++ b/include/items.php
@@ -4118,22 +4118,26 @@ function feed_meta($xml) {
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
logger('rawauthor: ' . print_r($rawauthor,true));
- 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']);
- break;
- }
- }
- }
+ if($rawauthor) {
+ if($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']);
+ break;
+ }
+ }
+ }
+ }
if($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data'])
$author['full_name'] = unxmlify($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data']);
+ if($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
+ $author['author_uri'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
+
}
}
-
if(substr($author['author_link'],-1,1) == '/')
$author['author_link'] = substr($author['author_link'],0,-1);
diff --git a/include/nav.php b/include/nav.php
index 3f5c7963a..541ab3aed 100644
--- a/include/nav.php
+++ b/include/nav.php
@@ -92,7 +92,7 @@ EOT;
$nav['usermenu'][] = Array('photos/' . $channel['channel_address'], t('Photos'), "", t('Your photos'),'photos_nav_btn');
$nav['usermenu'][] = Array('cloud/' . $channel['channel_address'],t('Files'),"",t('Your files'),'cloud_nav_btn');
- if(! UNO)
+ if((! UNO) && feature_enabled(local_channel(),'ajaxchat'))
$nav['usermenu'][] = Array('chat/' . $channel['channel_address'], t('Chat'),"",t('Your chatrooms'),'chat_nav_btn');
@@ -149,9 +149,11 @@ EOT;
$help_url = z_root() . '/help?f=&cmd=' . App::$cmd;
- if(! get_config('system','hide_help'))
- $nav['help'] = array($help_url, t('Help'), "", t('Help and documentation'),'help_nav_btn');
-
+ if(! get_config('system','hide_help')) {
+ require_once('mod/help.php');
+ $context_help = load_context_help();
+ $nav['help'] = array($help_url, t('Help'), "", t('Help and documentation'),'help_nav_btn',$context_help);
+ }
if(! UNO)
$nav['apps'] = array('apps', t('Apps'), "", t('Applications, utilities, links, games'),'apps_nav_btn');
@@ -256,7 +258,6 @@ $powered_by = '';
*
*/
function nav_set_selected($item){
- $a = get_app();
App::$nav_sel = array(
'community' => null,
'network' => null,
diff --git a/include/network.php b/include/network.php
index ac14548a5..ec255581d 100644
--- a/include/network.php
+++ b/include/network.php
@@ -27,11 +27,12 @@ function get_capath() {
* * \b http_auth => username:password
* * \b novalidate => do not validate SSL certs, default is to validate using our CA list
* * \b nobody => only return the header
+ * * \b filep => stream resource to write body to. header and body are not returned when using this option.
*
* @return array an assoziative array with:
* * \e int \b return_code => HTTP return code or 0 if timeout or failure
* * \e boolean \b success => boolean true (if HTTP 2xx result) or false
- * * \e string \b header => HTTP headers
+ * * \e string \b header => HTTP headers
* * \e string \b body => fetched content
*/
function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@@ -53,6 +54,11 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
+ if(x($opts,'filep')) {
+ @curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
+ @curl_setopt($ch, CURLOPT_HEADER, $false);
+ }
+
if(x($opts,'headers'))
@curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
@@ -158,6 +164,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
* 'timeout' => int seconds, default system config value or 60 seconds
* 'http_auth' => username:password
* 'novalidate' => do not validate SSL certs, default is to validate using our CA list
+ * 'filep' => stream resource to write body to. header and body are not returned when using this option.
* @return array an assoziative array with:
* * \e int \b return_code => HTTP return code or 0 if timeout or failure
* * \e boolean \b success => boolean true (if HTTP 2xx result) or false
@@ -185,6 +192,11 @@ function z_post_url($url,$params, $redirects = 0, $opts = array()) {
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
+ if(x($opts,'filep')) {
+ @curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
+ @curl_setopt($ch, CURLOPT_HEADER, $false);
+ }
+
if(x($opts,'headers')) {
@curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
logger('headers: ' . print_r($opts['headers'],true) . 'redir: ' . $redirects);
@@ -1089,7 +1101,7 @@ function discover_by_webbie($webbie) {
// If we discover zot - don't search further; grab the info and get out of
// here.
- if($link['rel'] == 'http://purl.org/zot/protocol') {
+ if($link['rel'] === PROTOCOL_ZOT) {
logger('discover_by_webbie: zot found for ' . $webbie, LOGGER_DEBUG);
if(array_key_exists('zot',$x) && $x['zot']['success'])
$i = import_xchan($x['zot']);
@@ -1265,6 +1277,12 @@ function discover_by_webbie($webbie) {
if($feed_meta['author']['author_photo'])
$avatar = $feed_meta['author']['author_photo'];
}
+
+ // for GNU-social over-ride any url aliases we may have picked up in webfinger
+ // The author.uri element in the feed is likely to be more accurate
+
+ if($gnusoc && $feed_meta['author']['author_uri'])
+ $location = $feed_meta['author']['author_uri'];
}
}
else {
diff --git a/include/photos.php b/include/photos.php
index 5838c013f..943d7d503 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -194,7 +194,7 @@ function photo_upload($channel, $observer, $args) {
$link[0] = array(
'rel' => 'alternate',
'type' => 'text/html',
- 'href' => $url = rawurlencode(z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt()),
+ 'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
);
@@ -212,7 +212,7 @@ function photo_upload($channel, $observer, $args) {
$link[1] = array(
'rel' => 'alternate',
'type' => 'text/html',
- 'href' => $url = rawurlencode(z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt()),
+ 'href' => z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
);
@@ -227,7 +227,7 @@ function photo_upload($channel, $observer, $args) {
$link[2] = array(
'rel' => 'alternate',
'type' => 'text/html',
- 'href' => $url = rawurlencode(z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt()),
+ 'href' => z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
);
@@ -242,7 +242,7 @@ function photo_upload($channel, $observer, $args) {
$link[3] = array(
'rel' => 'alternate',
'type' => 'text/html',
- 'href' => $url = rawurlencode(z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt()),
+ 'href' => z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
);
@@ -312,7 +312,7 @@ function photo_upload($channel, $observer, $args) {
'title' => $title,
'created' => $p['created'],
'edited' => $p['edited'],
- 'id' => rawurlencode(z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash),
+ 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash,
'link' => $link,
'body' => $obj_body
);
@@ -320,7 +320,7 @@ function photo_upload($channel, $observer, $args) {
$target = array(
'type' => ACTIVITY_OBJ_ALBUM,
'title' => (($album) ? $album : '/'),
- 'id' => rawurlencode(z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album))
+ 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album)
);
// Create item container
diff --git a/include/reddav.php b/include/reddav.php
index a0bd1b1fc..abf21b86d 100644
--- a/include/reddav.php
+++ b/include/reddav.php
@@ -23,9 +23,6 @@ use Zotlabs\Storage;
require_once('vendor/autoload.php');
require_once('include/attach.php');
-//require_once('Zotlabs/Storage/File.php');
-//require_once('Zotlabs/Storage/Directory.php');
-//require_once('Zotlabs/Storage/BasicAuth.php');
/**
* @brief Returns an array with viewable channels.
diff --git a/include/session.php b/include/session.php
index 71bfdc12a..4a7c8052e 100644
--- a/include/session.php
+++ b/include/session.php
@@ -45,12 +45,13 @@ function nuke_session() {
function new_cookie($time) {
+
$old_sid = session_id();
// ??? This shouldn't have any effect if called after session_start()
// We probably need to set the session expiration and change the PHPSESSID cookie.
+ // session_set_cookie_params($time);
- session_set_cookie_params($time);
session_regenerate_id(false);
q("UPDATE session SET sid = '%s' WHERE sid = '%s'",
@@ -66,6 +67,7 @@ function new_cookie($time) {
}
setcookie('jsAvailable', $_COOKIE['jsAvailable'], $expires);
}
+ setcookie(session_name(),session_id(),$expires);
}
diff --git a/include/text.php b/include/text.php
index a1a1cfb1c..0a7f84b01 100644
--- a/include/text.php
+++ b/include/text.php
@@ -2730,22 +2730,37 @@ function json_url_replace($old,$new,&$s) {
}
-function item_url_replace($channel,&$item,$old,$new) {
+function item_url_replace($channel,&$item,$old,$new,$oldnick = '') {
- if($item['attach'])
+ if($item['attach']) {
json_url_replace($old,$new,$item['attach']);
- if($item['object'])
+ if($oldnick)
+ json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['attach']);
+ }
+ if($item['object']) {
json_url_replace($old,$new,$item['object']);
- if($item['target'])
+ if($oldnick)
+ json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['object']);
+ }
+ if($item['target']) {
json_url_replace($old,$new,$item['target']);
+ if($oldnick)
+ json_url_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['target']);
+ }
if(string_replace($old,$new,$item['body'])) {
$item['sig'] = base64url_encode(rsa_sign($item['body'],$channel['channel_prvkey']));
$item['item_verified'] = 1;
}
- // @fixme item['plink'] and item['llink']
+ $item['plink'] = str_replace($old,$new,$item['plink']);
+ if($oldnick)
+ $item['plink'] = str_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['plink']);
+ $item['llink'] = str_replace($old,$new,$item['llink']);
+ if($oldnick)
+ $item['llink'] = str_replace('/' . $oldnick . '/' ,'/' . $channel['channel_address'] . '/' ,$item['llink']);
+
}
diff --git a/include/widgets.php b/include/widgets.php
index 0116e5bd1..0355ebd8c 100644
--- a/include/widgets.php
+++ b/include/widgets.php
@@ -785,6 +785,7 @@ function widget_menu_preview($arr) {
function widget_chatroom_list($arr) {
+
require_once("include/chat.php");
$r = chatroom_list(App::$profile['profile_uid']);
@@ -808,6 +809,10 @@ function widget_chatroom_members() {
}
function widget_bookmarkedchats($arr) {
+
+ if(! feature_enabled(App::$profile['profile_uid'],'ajaxchat'))
+ return '';
+
$h = get_observer_hash();
if(! $h)
return;
@@ -827,6 +832,9 @@ function widget_bookmarkedchats($arr) {
function widget_suggestedchats($arr) {
+ if(! feature_enabled(App::$profile['profile_uid'],'ajaxchat'))
+ return '';
+
// probably should restrict this to your friends, but then the widget will only work
// if you are logged in locally.
diff --git a/include/zot.php b/include/zot.php
index fa3563085..a5ab56154 100644
--- a/include/zot.php
+++ b/include/zot.php
@@ -3138,6 +3138,9 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
if(array_key_exists('menu',$arr) && $arr['menu'])
sync_menus($channel,$arr['menu']);
+ if(array_key_exists('file',$arr) && $arr['file'])
+ sync_files($channel,$arr['file']);
+
if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) {
if(array_key_exists('channel_pageflags',$arr['channel']) && intval($arr['channel']['channel_pageflags'])) {
diff --git a/library/justifiedGallery/jquery.justifiedGallery.js b/library/justifiedGallery/jquery.justifiedGallery.js
index 7c63149a3..d3259fe44 100644
--- a/library/justifiedGallery/jquery.justifiedGallery.js
+++ b/library/justifiedGallery/jquery.justifiedGallery.js
@@ -1,5 +1,5 @@
/*!
- * Justified Gallery - v3.6.0
+ * Justified Gallery - v3.6.1
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
@@ -23,6 +23,7 @@
this.buildingRow = {
entriesBuff : [],
width : 0,
+ height : 0,
aspectRatio : 0
};
this.lastAnalyzedIndex = -1;
@@ -97,11 +98,18 @@
* @returns {String} the suffix to use
*/
JustifiedGallery.prototype.newSrc = function (imageSrc, imgWidth, imgHeight) {
- var matchRes = imageSrc.match(this.settings.extension);
- var ext = (matchRes != null) ? matchRes[0] : '';
- var newImageSrc = imageSrc.replace(this.settings.extension, '');
- newImageSrc = this.removeSuffix(newImageSrc, this.getUsedSuffix(newImageSrc));
- newImageSrc += this.getSuffix(imgWidth, imgHeight) + ext;
+ var newImageSrc;
+
+ if (this.settings.thumbnailPath) {
+ newImageSrc = this.settings.thumbnailPath(imageSrc, imgWidth, imgHeight);
+ } else {
+ var matchRes = imageSrc.match(this.settings.extension);
+ var ext = (matchRes !== null) ? matchRes[0] : '';
+ newImageSrc = imageSrc.replace(this.settings.extension, '');
+ newImageSrc = this.removeSuffix(newImageSrc, this.getUsedSuffix(newImageSrc));
+ newImageSrc += this.getSuffix(imgWidth, imgHeight) + ext;
+ }
+
return newImageSrc;
};
@@ -211,10 +219,10 @@
var $imgCaption = this.captionFromEntry($entry);
// Create it if it doesn't exists
- if ($imgCaption == null) {
+ if ($imgCaption === null) {
var caption = $image.attr('alt');
- if (typeof caption === 'undefined') caption = $entry.attr('title');
- if (typeof caption !== 'undefined') { // Create only we found something
+ if (!this.isValidCaption(caption)) caption = $entry.attr('title');
+ if (this.isValidCaption(caption)) { // Create only we found something
$imgCaption = $('<div class="caption">' + caption + '</div>');
$entry.append($imgCaption);
$entry.data('jg.createdCaption', true);
@@ -232,6 +240,16 @@
};
/**
+ * Validates the caption
+ *
+ * @param caption The caption that should be validated
+ * @return {boolean} Validation result
+ */
+ JustifiedGallery.prototype.isValidCaption = function (caption) {
+ return (typeof caption !== 'undefined' && caption.length > 0);
+ };
+
+ /**
* The callback for the event 'mouseenter'. It assumes that the event currentTarget is an entry.
* It shows the caption using jQuery (or using CSS if it is configured so)
*
@@ -299,7 +317,7 @@
* Justify the building row, preparing it to
*
* @param isLastRow
- * @returns {*}
+ * @returns a boolean to know if the row has been justified or not
*/
JustifiedGallery.prototype.prepareBuildingRow = function (isLastRow) {
var i, $entry, imgAspectRatio, newImgW, newImgH, justify = true;
@@ -322,7 +340,7 @@
}
// With lastRow = nojustify, justify if is justificable (the images will not become too big)
- if (isLastRow && !justifiable && this.settings.lastRow === 'nojustify') justify = false;
+ if (isLastRow && !justifiable && this.settings.lastRow !== 'justify' && this.settings.lastRow !== 'hide') justify = false;
for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
$entry = this.buildingRow.entriesBuff[i];
@@ -355,7 +373,8 @@
if (this.settings.fixedHeight && minHeight > this.settings.rowHeight)
minHeight = this.settings.rowHeight;
- return {minHeight: minHeight, justify: justify};
+ this.buildingRow.height = minHeight;
+ return justify;
};
/**
@@ -374,33 +393,53 @@
*/
JustifiedGallery.prototype.flushRow = function (isLastRow) {
var settings = this.settings;
- var $entry, minHeight, buildingRowRes, offX = this.border;
+ var $entry, buildingRowRes, offX = this.border, i;
buildingRowRes = this.prepareBuildingRow(isLastRow);
- minHeight = buildingRowRes.minHeight;
- if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
+ if (isLastRow && settings.lastRow === 'hide' && this.buildingRow.height === -1) {
this.clearBuildingRow();
return;
}
- if (this.maxRowHeight.percentage) {
- if (this.maxRowHeight.value * settings.rowHeight < minHeight) minHeight = this.maxRowHeight.value * settings.rowHeight;
+ if (this.maxRowHeight.isPercentage) {
+ if (this.maxRowHeight.value * settings.rowHeight < this.buildingRow.height) {
+ this.buildingRow.height = this.maxRowHeight.value * settings.rowHeight;
+ }
} else {
- if (this.maxRowHeight.value > 0 && this.maxRowHeight.value < minHeight) minHeight = this.maxRowHeight.value;
+ if (this.maxRowHeight.value > 0 && this.maxRowHeight.value < this.buildingRow.height) {
+ this.buildingRow.height = this.maxRowHeight.value;
+ }
}
- for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ //Align last (unjustified) row
+ if (settings.lastRow === 'center' || settings.lastRow === 'right') {
+ var availableWidth = this.galleryWidth - 2 * this.border - (this.buildingRow.entriesBuff.length - 1) * settings.margins;
+
+ for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ $entry = this.buildingRow.entriesBuff[i];
+ availableWidth -= $entry.data('jg.jwidth');
+ }
+
+ if (settings.lastRow === 'center')
+ offX += availableWidth / 2;
+ else if (settings.lastRow === 'right')
+ offX += availableWidth;
+ }
+
+
+ for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
$entry = this.buildingRow.entriesBuff[i];
- this.displayEntry($entry, offX, this.offY, $entry.data('jg.jwidth'), $entry.data('jg.jheight'), minHeight);
+ this.displayEntry($entry, offX, this.offY, $entry.data('jg.jwidth'), $entry.data('jg.jheight'), this.buildingRow.height);
offX += $entry.data('jg.jwidth') + settings.margins;
}
//Gallery Height
- this.$gallery.height(this.offY + minHeight + this.border + (this.isSpinnerActive() ? this.getSpinnerHeight() : 0));
+ this.$gallery.height(this.offY + this.buildingRow.height +
+ this.border + (this.isSpinnerActive() ? this.getSpinnerHeight() : 0));
- if (!isLastRow || (minHeight <= this.settings.rowHeight && buildingRowRes.justify)) {
+ if (!isLastRow || (this.buildingRow.height <= settings.rowHeight && buildingRowRes)) {
//Ready for a new row
- this.offY += minHeight + this.settings.margins;
+ this.offY += this.buildingRow.height + settings.margins;
this.clearBuildingRow();
this.$gallery.trigger('jg.rowflush');
}
@@ -411,8 +450,8 @@
*/
JustifiedGallery.prototype.checkWidth = function () {
this.checkWidthIntervalId = setInterval($.proxy(function () {
- var galleryWidth = parseInt(this.$gallery.width(), 10);
- if (this.galleryWidth !== galleryWidth) {
+ var galleryWidth = parseFloat(this.$gallery.width());
+ if (Math.abs(galleryWidth - this.galleryWidth) > this.settings.refreshSensitivity) {
this.galleryWidth = galleryWidth;
this.rewind();
@@ -426,7 +465,7 @@
* @returns {boolean} a boolean saying if the spinner is active or not
*/
JustifiedGallery.prototype.isSpinnerActive = function () {
- return this.spinner.intervalId != null;
+ return this.spinner.intervalId !== null;
};
/**
@@ -454,7 +493,7 @@
var $spinnerPoints = spinnerContext.$el.find('span');
clearInterval(spinnerContext.intervalId);
this.$gallery.append(spinnerContext.$el);
- this.$gallery.height(this.offY + this.getSpinnerHeight());
+ this.$gallery.height(this.offY + this.buildingRow.height + this.getSpinnerHeight());
spinnerContext.intervalId = setInterval(function () {
if (spinnerContext.phase < $spinnerPoints.length) {
$spinnerPoints.eq(spinnerContext.phase).fadeTo(spinnerContext.timeSlot, 1);
@@ -475,20 +514,6 @@
};
/**
- * Hide the image of the buildingRow to prevent strange effects when the row will be
- * re-justified again
- */
- JustifiedGallery.prototype.hideBuildingRowImages = function () {
- for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) {
- if (this.settings.cssAnimation) {
- this.buildingRow.entriesBuff[i].removeClass('entry-visible');
- } else {
- this.buildingRow.entriesBuff[i].stop().fadeTo(0, 0);
- }
- }
- };
-
- /**
* Update the entries searching it from the justified gallery HTML element
*
* @param norewind if norewind only the new entries will be changed (i.e. randomized, sorted or filtered)
@@ -645,9 +670,9 @@
if ($entry.data('jg.createdCaption')) {
// remove also the caption element (if created by jg)
$entry.data('jg.createdCaption', undefined);
- if ($caption != null) $caption.remove();
+ if ($caption !== null) $caption.remove();
} else {
- if ($caption != null) $caption.fadeTo(0, 1);
+ if ($caption !== null) $caption.fadeTo(0, 1);
}
}, this));
@@ -782,8 +807,8 @@
/* If we have the height and the width, we don't wait that the image is loaded, but we start directly
* with the justification */
if (that.settings.waitThumbnailsLoad === false) {
- var width = parseInt($image.attr('width'), 10);
- var height = parseInt($image.attr('height'), 10);
+ var width = parseFloat($image.attr('width'));
+ var height = parseFloat($image.attr('height'));
if (!isNaN(width) && !isNaN(height)) {
$entry.data('jg.width', width);
$entry.data('jg.height', height);
@@ -798,9 +823,7 @@
imagesToLoad = true;
// Spinner start
- if (!that.isSpinnerActive()) {
- that.startLoadingSpinnerAnimation();
- }
+ if (!that.isSpinnerActive()) that.startLoadingSpinnerAnimation();
that.onImageEvent(imageSrc, function (loadImg) { // image loaded
$entry.data('jg.width', loadImg.width);
@@ -814,8 +837,8 @@
} else {
$entry.data('jg.loaded', true);
- $entry.data('jg.width', $entry.width() | $entry.css('width') | 1);
- $entry.data('jg.height', $entry.height() | $entry.css('height') | 1);
+ $entry.data('jg.width', $entry.width() | parseFloat($entry.css('width')) | 1);
+ $entry.data('jg.height', $entry.height() | parseFloat($entry.css('height')) | 1);
}
}
@@ -883,15 +906,15 @@
if ($.type(this.settings.maxRowHeight) === 'string') {
if (this.settings.maxRowHeight.match(/^[0-9]+%$/)) {
- newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight.match(/^([0-9])+%$/)[1]) / 100;
- newMaxRowHeight.percentage = false;
+ newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight.match(/^([0-9]+)%$/)[1]) / 100;
+ newMaxRowHeight.isPercentage = false;
} else {
newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight);
- newMaxRowHeight.percentage = true;
+ newMaxRowHeight.isPercentage = true;
}
} else if ($.type(this.settings.maxRowHeight) === 'number') {
newMaxRowHeight.value = this.settings.maxRowHeight;
- newMaxRowHeight.percentage = false;
+ newMaxRowHeight.isPercentage = false;
} else {
throw 'maxRowHeight must be a number or a percentage';
}
@@ -900,7 +923,7 @@
if (isNaN(newMaxRowHeight.value)) throw 'invalid number for maxRowHeight';
// check values
- if (newMaxRowHeight.percentage) {
+ if (newMaxRowHeight.isPercentage) {
if (newMaxRowHeight.value < 100) newMaxRowHeight.value = 100;
} else {
if (newMaxRowHeight.value > 0 && newMaxRowHeight.value < this.settings.rowHeight) {
@@ -922,10 +945,12 @@
this.checkOrConvertNumber(this.settings, 'margins');
this.checkOrConvertNumber(this.settings, 'border');
- if (this.settings.lastRow !== 'nojustify' &&
- this.settings.lastRow !== 'justify' &&
+ if (this.settings.lastRow !== 'justify' &&
+ this.settings.lastRow !== 'nojustify' && this.settings.lastRow !== 'left' &&
+ this.settings.lastRow !== 'center' &&
+ this.settings.lastRow !== 'right' &&
this.settings.lastRow !== 'hide') {
- throw 'lastRow must be "nojustify", "justify" or "hide"';
+ throw 'lastRow must be "justify", "nojustify", "left", "center", "right" or "hide"';
}
this.checkOrConvertNumber(this.settings, 'justifyThreshold');
@@ -954,6 +979,7 @@
if ($.type(this.settings.fixedHeight) !== 'boolean') throw 'fixedHeight must be a boolean';
this.checkOrConvertNumber(this.settings, 'imagesAnimationDuration');
this.checkOrConvertNumber(this.settings, 'refreshTime');
+ this.checkOrConvertNumber(this.settings, 'refreshSensitivity');
if ($.type(this.settings.randomize) !== 'boolean') throw 'randomize must be a boolean';
if ($.type(this.settings.selector) !== 'string') throw 'selector must be a string';
@@ -961,7 +987,7 @@
throw 'sort must be false or a comparison function';
}
- if (this.settings.filter !== false && !$.isFunction(this.settings.sort) &&
+ if (this.settings.filter !== false && !$.isFunction(this.settings.filter) &&
$.type(this.settings.filter) !== 'string') {
throw 'filter must be false, a string or a filter function';
}
@@ -1018,13 +1044,14 @@
if (typeof controller === 'undefined') {
// Create controller and assign it to the object data
if (typeof arg !== 'undefined' && arg !== null && $.type(arg) !== 'object') {
+ if (arg === 'destroy') return; // Just a call to an unexisting object
throw 'The argument must be an object';
}
controller = new JustifiedGallery($gallery, $.extend({}, $.fn.justifiedGallery.defaults, arg));
$gallery.data('jg.controller', controller);
} else if (arg === 'norewind') {
// In this case we don't rewind: we analyze only the latest images (e.g. to complete the last unfinished row
- controller.hideBuildingRowImages();
+ // ... left to be more readable
} else if (arg === 'destroy') {
controller.destroy();
return;
@@ -1055,14 +1082,18 @@
1024: '_b' // used as else case because it is the last
}
*/
+ thumbnailPath: undefined, /* If defined, sizeRangeSuffixes is not used, and this function is used to determine the
+ path relative to a specific thumbnail size. The function should accept respectively three arguments:
+ current path, width and height */
rowHeight: 120,
- maxRowHeight: '200%', // negative value = no limits, number to express the value in pixels,
- // '[0-9]+%' to express in percentage (e.g. 200% means that the row height
- // can't exceed 2 * rowHeight)
+ maxRowHeight: -1, // negative value = no limits, number to express the value in pixels,
+ // '[0-9]+%' to express in percentage (e.g. 300% means that the row height
+ // can't exceed 3 * rowHeight)
margins: 1,
border: -1, // negative value = same as margins, 0 = disabled, any other value to set the border
- lastRow: 'nojustify', // or can be 'justify' or 'hide'
+ lastRow: 'nojustify', // … which is the same as 'left', or can be 'justify', 'center', 'right' or 'hide'
+
justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified
* (i.e. lastRow setting is not considered) */
fixedHeight: false,
@@ -1078,7 +1109,8 @@
rel: null, // rewrite the rel of each analyzed links
target: null, // rewrite the target of all links
extension: /\.[^.\\/]+$/, // regexp to capture the extension of an image
- refreshTime: 100, // time interval (in ms) to check if the page changes its width
+ refreshTime: 200, // time interval (in ms) to check if the page changes its width
+ refreshSensitivity: 0, // change in width allowed (in px) without re-building the gallery
randomize: false,
sort: false, /*
- false: to do not sort
diff --git a/library/justifiedGallery/jquery.justifiedGallery.min.js b/library/justifiedGallery/jquery.justifiedGallery.min.js
index 74f333208..030636ccc 100644
--- a/library/justifiedGallery/jquery.justifiedGallery.min.js
+++ b/library/justifiedGallery/jquery.justifiedGallery.min.js
@@ -1,7 +1,7 @@
/*!
- * Justified Gallery - v3.6.0
+ * Justified Gallery - v3.6.1
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
*/
-!function(a){var b=function(b,c){this.settings=c,this.checkSettings(),this.imgAnalyzerTimeout=null,this.entries=null,this.buildingRow={entriesBuff:[],width:0,aspectRatio:0},this.lastAnalyzedIndex=-1,this.yield={every:2,flushed:0},this.border=c.border>=0?c.border:c.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges(),this.offY=this.border,this.spinner={phase:0,timeSlot:150,$el:a('<div class="spinner"><span></span><span></span><span></span></div>'),intervalId:null},this.checkWidthIntervalId=null,this.galleryWidth=b.width(),this.$gallery=b};b.prototype.getSuffix=function(a,b){var c,d;for(c=a>b?a:b,d=0;d<this.suffixRanges.length;d++)if(c<=this.suffixRanges[d])return this.settings.sizeRangeSuffixes[this.suffixRanges[d]];return this.settings.sizeRangeSuffixes[this.suffixRanges[d-1]]},b.prototype.removeSuffix=function(a,b){return a.substring(0,a.length-b.length)},b.prototype.endsWith=function(a,b){return-1!==a.indexOf(b,a.length-b.length)},b.prototype.getUsedSuffix=function(a){for(var b in this.settings.sizeRangeSuffixes)if(this.settings.sizeRangeSuffixes.hasOwnProperty(b)){if(0===this.settings.sizeRangeSuffixes[b].length)continue;if(this.endsWith(a,this.settings.sizeRangeSuffixes[b]))return this.settings.sizeRangeSuffixes[b]}return""},b.prototype.newSrc=function(a,b,c){var d=a.match(this.settings.extension),e=null!=d?d[0]:"",f=a.replace(this.settings.extension,"");return f=this.removeSuffix(f,this.getUsedSuffix(f)),f+=this.getSuffix(b,c)+e},b.prototype.showImg=function(a,b){this.settings.cssAnimation?(a.addClass("entry-visible"),b&&b()):a.stop().fadeTo(this.settings.imagesAnimationDuration,1,b)},b.prototype.extractImgSrcFromImage=function(a){var b="undefined"!=typeof a.data("safe-src")?a.data("safe-src"):a.attr("src");return a.data("jg.originalSrc",b),b},b.prototype.imgFromEntry=function(a){var b=a.find("> img");return 0===b.length&&(b=a.find("> a > img")),0===b.length?null:b},b.prototype.captionFromEntry=function(a){var b=a.find("> .caption");return 0===b.length?null:b},b.prototype.displayEntry=function(b,c,d,e,f,g){b.width(e),b.height(g),b.css("top",d),b.css("left",c);var h=this.imgFromEntry(b);if(null!==h){h.css("width",e),h.css("height",f),h.css("margin-left",-e/2),h.css("margin-top",-f/2);var i=h.attr("src"),j=this.newSrc(i,e,f);h.one("error",function(){h.attr("src",h.data("jg.originalSrc"))});var k=function(){i!==j&&h.attr("src",j)};"skipped"===b.data("jg.loaded")?this.onImageEvent(i,a.proxy(function(){this.showImg(b,k),b.data("jg.loaded",!0)},this)):this.showImg(b,k)}else this.showImg(b);this.displayEntryCaption(b)},b.prototype.displayEntryCaption=function(b){var c=this.imgFromEntry(b);if(null!==c&&this.settings.captions){var d=this.captionFromEntry(b);if(null==d){var e=c.attr("alt");"undefined"==typeof e&&(e=b.attr("title")),"undefined"!=typeof e&&(d=a('<div class="caption">'+e+"</div>"),b.append(d),b.data("jg.createdCaption",!0))}null!==d&&(this.settings.cssAnimation||d.stop().fadeTo(0,this.settings.captionSettings.nonVisibleOpacity),this.addCaptionEventsHandlers(b))}else this.removeCaptionEventsHandlers(b)},b.prototype.onEntryMouseEnterForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.addClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.visibleOpacity)},b.prototype.onEntryMouseLeaveForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.removeClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.nonVisibleOpacity)},b.prototype.addCaptionEventsHandlers=function(b){var c=b.data("jg.captionMouseEvents");"undefined"==typeof c&&(c={mouseenter:a.proxy(this.onEntryMouseEnterForCaption,this),mouseleave:a.proxy(this.onEntryMouseLeaveForCaption,this)},b.on("mouseenter",void 0,void 0,c.mouseenter),b.on("mouseleave",void 0,void 0,c.mouseleave),b.data("jg.captionMouseEvents",c))},b.prototype.removeCaptionEventsHandlers=function(a){var b=a.data("jg.captionMouseEvents");"undefined"!=typeof b&&(a.off("mouseenter",void 0,b.mouseenter),a.off("mouseleave",void 0,b.mouseleave),a.removeData("jg.captionMouseEvents"))},b.prototype.prepareBuildingRow=function(a){var b,c,d,e,f,g=!0,h=0,i=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,j=i/this.buildingRow.aspectRatio,k=this.buildingRow.width/i>this.settings.justifyThreshold;if(a&&"hide"===this.settings.lastRow&&!k){for(b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],this.settings.cssAnimation?c.removeClass("entry-visible"):c.stop().fadeTo(0,0);return-1}for(a&&!k&&"nojustify"===this.settings.lastRow&&(g=!1),b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],d=c.data("jg.width")/c.data("jg.height"),g?(e=b===this.buildingRow.entriesBuff.length-1?i:j*d,f=j):(e=this.settings.rowHeight*d,f=this.settings.rowHeight),i-=Math.round(e),c.data("jg.jwidth",Math.round(e)),c.data("jg.jheight",Math.ceil(f)),(0===b||h>f)&&(h=f);return this.settings.fixedHeight&&h>this.settings.rowHeight&&(h=this.settings.rowHeight),{minHeight:h,justify:g}},b.prototype.clearBuildingRow=function(){this.buildingRow.entriesBuff=[],this.buildingRow.aspectRatio=0,this.buildingRow.width=0},b.prototype.flushRow=function(a){var b,c,d,e=this.settings,f=this.border;if(d=this.prepareBuildingRow(a),c=d.minHeight,a&&"hide"===e.lastRow&&-1===c)return void this.clearBuildingRow();this.maxRowHeight.percentage?this.maxRowHeight.value*e.rowHeight<c&&(c=this.maxRowHeight.value*e.rowHeight):this.maxRowHeight.value>0&&this.maxRowHeight.value<c&&(c=this.maxRowHeight.value);for(var g=0;g<this.buildingRow.entriesBuff.length;g++)b=this.buildingRow.entriesBuff[g],this.displayEntry(b,f,this.offY,b.data("jg.jwidth"),b.data("jg.jheight"),c),f+=b.data("jg.jwidth")+e.margins;this.$gallery.height(this.offY+c+this.border+(this.isSpinnerActive()?this.getSpinnerHeight():0)),(!a||c<=this.settings.rowHeight&&d.justify)&&(this.offY+=c+this.settings.margins,this.clearBuildingRow(),this.$gallery.trigger("jg.rowflush"))},b.prototype.checkWidth=function(){this.checkWidthIntervalId=setInterval(a.proxy(function(){var a=parseInt(this.$gallery.width(),10);this.galleryWidth!==a&&(this.galleryWidth=a,this.rewind(),this.startImgAnalyzer(!0))},this),this.settings.refreshTime)},b.prototype.isSpinnerActive=function(){return null!=this.spinner.intervalId},b.prototype.getSpinnerHeight=function(){return this.spinner.$el.innerHeight()},b.prototype.stopLoadingSpinnerAnimation=function(){clearInterval(this.spinner.intervalId),this.spinner.intervalId=null,this.$gallery.height(this.$gallery.height()-this.getSpinnerHeight()),this.spinner.$el.detach()},b.prototype.startLoadingSpinnerAnimation=function(){var a=this.spinner,b=a.$el.find("span");clearInterval(a.intervalId),this.$gallery.append(a.$el),this.$gallery.height(this.offY+this.getSpinnerHeight()),a.intervalId=setInterval(function(){a.phase<b.length?b.eq(a.phase).fadeTo(a.timeSlot,1):b.eq(a.phase-b.length).fadeTo(a.timeSlot,0),a.phase=(a.phase+1)%(2*b.length)},a.timeSlot)},b.prototype.rewind=function(){this.lastAnalyzedIndex=-1,this.offY=this.border,this.clearBuildingRow()},b.prototype.hideBuildingRowImages=function(){for(var a=0;a<this.buildingRow.entriesBuff.length;a++)this.settings.cssAnimation?this.buildingRow.entriesBuff[a].removeClass("entry-visible"):this.buildingRow.entriesBuff[a].stop().fadeTo(0,0)},b.prototype.updateEntries=function(b){return this.entries=this.$gallery.find(this.settings.selector).toArray(),0===this.entries.length?!1:(this.settings.filter?this.modifyEntries(this.filterArray,b):this.modifyEntries(this.resetFilters,b),a.isFunction(this.settings.sort)?this.modifyEntries(this.sortArray,b):this.settings.randomize&&this.modifyEntries(this.shuffleArray,b),!0)},b.prototype.insertToGallery=function(b){var c=this;a.each(b,function(){a(this).appendTo(c.$gallery)})},b.prototype.shuffleArray=function(a){var b,c,d;for(b=a.length-1;b>0;b--)c=Math.floor(Math.random()*(b+1)),d=a[b],a[b]=a[c],a[c]=d;return this.insertToGallery(a),a},b.prototype.sortArray=function(a){return a.sort(this.settings.sort),this.insertToGallery(a),a},b.prototype.resetFilters=function(b){for(var c=0;c<b.length;c++)a(b[c]).removeClass("jg-filtered");return b},b.prototype.filterArray=function(b){var c=this.settings;return"string"===a.type(c.filter)?b.filter(function(b){var d=a(b);return d.is(c.filter)?(d.removeClass("jg-filtered"),!0):(d.addClass("jg-filtered"),!1)}):a.isFunction(c.filter)?b.filter(c.filter):void 0},b.prototype.modifyEntries=function(a,b){var c=b?this.entries.splice(this.lastAnalyzedIndex+1,this.entries.length-this.lastAnalyzedIndex-1):this.entries;c=a.call(this,c),this.entries=b?this.entries.concat(c):c},b.prototype.destroy=function(){clearInterval(this.checkWidthIntervalId),a.each(this.entries,a.proxy(function(b,c){var d=a(c);d.css("width",""),d.css("height",""),d.css("top",""),d.css("left",""),d.data("jg.loaded",void 0),d.removeClass("jg-entry");var e=this.imgFromEntry(d);e.css("width",""),e.css("height",""),e.css("margin-left",""),e.css("margin-top",""),e.attr("src",e.data("jg.originalSrc")),e.data("jg.originalSrc",void 0),this.removeCaptionEventsHandlers(d);var f=this.captionFromEntry(d);d.data("jg.createdCaption")?(d.data("jg.createdCaption",void 0),null!=f&&f.remove()):null!=f&&f.fadeTo(0,1)},this)),this.$gallery.css("height",""),this.$gallery.removeClass("justified-gallery"),this.$gallery.data("jg.controller",void 0)},b.prototype.analyzeImages=function(b){for(var c=this.lastAnalyzedIndex+1;c<this.entries.length;c++){var d=a(this.entries[c]);if(d.data("jg.loaded")===!0||"skipped"===d.data("jg.loaded")){var e=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,f=d.data("jg.width")/d.data("jg.height");if(e/(this.buildingRow.aspectRatio+f)<this.settings.rowHeight&&(this.flushRow(!1),++this.yield.flushed>=this.yield.every))return void this.startImgAnalyzer(b);this.buildingRow.entriesBuff.push(d),this.buildingRow.aspectRatio+=f,this.buildingRow.width+=f*this.settings.rowHeight,this.lastAnalyzedIndex=c}else if("error"!==d.data("jg.loaded"))return}this.buildingRow.entriesBuff.length>0&&this.flushRow(!0),this.isSpinnerActive()&&this.stopLoadingSpinnerAnimation(),this.stopImgAnalyzerStarter(),this.$gallery.trigger(b?"jg.resize":"jg.complete")},b.prototype.stopImgAnalyzerStarter=function(){this.yield.flushed=0,null!==this.imgAnalyzerTimeout&&clearTimeout(this.imgAnalyzerTimeout)},b.prototype.startImgAnalyzer=function(a){var b=this;this.stopImgAnalyzerStarter(),this.imgAnalyzerTimeout=setTimeout(function(){b.analyzeImages(a)},.001)},b.prototype.onImageEvent=function(b,c,d){if(c||d){var e=new Image,f=a(e);c&&f.one("load",function(){f.off("load error"),c(e)}),d&&f.one("error",function(){f.off("load error"),d(e)}),e.src=b}},b.prototype.init=function(){var b=!1,c=!1,d=this;a.each(this.entries,function(e,f){var g=a(f),h=d.imgFromEntry(g);if(g.addClass("jg-entry"),g.data("jg.loaded")!==!0&&"skipped"!==g.data("jg.loaded"))if(null!==d.settings.rel&&g.attr("rel",d.settings.rel),null!==d.settings.target&&g.attr("target",d.settings.target),null!==h){var i=d.extractImgSrcFromImage(h);if(h.attr("src",i),d.settings.waitThumbnailsLoad===!1){var j=parseInt(h.attr("width"),10),k=parseInt(h.attr("height"),10);if(!isNaN(j)&&!isNaN(k))return g.data("jg.width",j),g.data("jg.height",k),g.data("jg.loaded","skipped"),c=!0,d.startImgAnalyzer(!1),!0}g.data("jg.loaded",!1),b=!0,d.isSpinnerActive()||d.startLoadingSpinnerAnimation(),d.onImageEvent(i,function(a){g.data("jg.width",a.width),g.data("jg.height",a.height),g.data("jg.loaded",!0),d.startImgAnalyzer(!1)},function(){g.data("jg.loaded","error"),d.startImgAnalyzer(!1)})}else g.data("jg.loaded",!0),g.data("jg.width",g.width()|g.css("width")|1),g.data("jg.height",g.height()|g.css("height")|1)}),b||c||this.startImgAnalyzer(!1),this.checkWidth()},b.prototype.checkOrConvertNumber=function(b,c){if("string"===a.type(b[c])&&(b[c]=parseFloat(b[c])),"number"!==a.type(b[c]))throw c+" must be a number";if(isNaN(b[c]))throw"invalid number for "+c},b.prototype.checkSizeRangesSuffixes=function(){if("object"!==a.type(this.settings.sizeRangeSuffixes))throw"sizeRangeSuffixes must be defined and must be an object";var b=[];for(var c in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(c)&&b.push(c);for(var d={0:""},e=0;e<b.length;e++)if("string"===a.type(b[e]))try{var f=parseInt(b[e].replace(/^[a-z]+/,""),10);d[f]=this.settings.sizeRangeSuffixes[b[e]]}catch(g){throw"sizeRangeSuffixes keys must contains correct numbers ("+g+")"}else d[b[e]]=this.settings.sizeRangeSuffixes[b[e]];this.settings.sizeRangeSuffixes=d},b.prototype.retrieveMaxRowHeight=function(){var b={};if("string"===a.type(this.settings.maxRowHeight))this.settings.maxRowHeight.match(/^[0-9]+%$/)?(b.value=parseFloat(this.settings.maxRowHeight.match(/^([0-9])+%$/)[1])/100,b.percentage=!1):(b.value=parseFloat(this.settings.maxRowHeight),b.percentage=!0);else{if("number"!==a.type(this.settings.maxRowHeight))throw"maxRowHeight must be a number or a percentage";b.value=this.settings.maxRowHeight,b.percentage=!1}if(isNaN(b.value))throw"invalid number for maxRowHeight";return b.percentage?b.value<100&&(b.value=100):b.value>0&&b.value<this.settings.rowHeight&&(b.value=this.settings.rowHeight),b},b.prototype.checkSettings=function(){if(this.checkSizeRangesSuffixes(),this.checkOrConvertNumber(this.settings,"rowHeight"),this.checkOrConvertNumber(this.settings,"margins"),this.checkOrConvertNumber(this.settings,"border"),"nojustify"!==this.settings.lastRow&&"justify"!==this.settings.lastRow&&"hide"!==this.settings.lastRow)throw'lastRow must be "nojustify", "justify" or "hide"';if(this.checkOrConvertNumber(this.settings,"justifyThreshold"),this.settings.justifyThreshold<0||this.settings.justifyThreshold>1)throw"justifyThreshold must be in the interval [0,1]";if("boolean"!==a.type(this.settings.cssAnimation))throw"cssAnimation must be a boolean";if("boolean"!==a.type(this.settings.captions))throw"captions must be a boolean";if(this.checkOrConvertNumber(this.settings.captionSettings,"animationDuration"),this.checkOrConvertNumber(this.settings.captionSettings,"visibleOpacity"),this.settings.captionSettings.visibleOpacity<0||this.settings.captionSettings.visibleOpacity>1)throw"captionSettings.visibleOpacity must be in the interval [0, 1]";if(this.checkOrConvertNumber(this.settings.captionSettings,"nonVisibleOpacity"),this.settings.captionSettings.nonVisibleOpacity<0||this.settings.captionSettings.nonVisibleOpacity>1)throw"captionSettings.nonVisibleOpacity must be in the interval [0, 1]";if("boolean"!==a.type(this.settings.fixedHeight))throw"fixedHeight must be a boolean";if(this.checkOrConvertNumber(this.settings,"imagesAnimationDuration"),this.checkOrConvertNumber(this.settings,"refreshTime"),"boolean"!==a.type(this.settings.randomize))throw"randomize must be a boolean";if("string"!==a.type(this.settings.selector))throw"selector must be a string";if(this.settings.sort!==!1&&!a.isFunction(this.settings.sort))throw"sort must be false or a comparison function";if(this.settings.filter!==!1&&!a.isFunction(this.settings.sort)&&"string"!==a.type(this.settings.filter))throw"filter must be false, a string or a filter function"},b.prototype.retrieveSuffixRanges=function(){var a=[];for(var b in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(b)&&a.push(parseInt(b,10));return a.sort(function(a,b){return a>b?1:b>a?-1:0}),a},b.prototype.updateSettings=function(b){this.settings=a.extend({},this.settings,b),this.checkSettings(),this.border=this.settings.border>=0?this.settings.border:this.settings.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges()},a.fn.justifiedGallery=function(c){return this.each(function(d,e){var f=a(e);f.addClass("justified-gallery");var g=f.data("jg.controller");if("undefined"==typeof g){if("undefined"!=typeof c&&null!==c&&"object"!==a.type(c))throw"The argument must be an object";g=new b(f,a.extend({},a.fn.justifiedGallery.defaults,c)),f.data("jg.controller",g)}else if("norewind"===c)g.hideBuildingRowImages();else{if("destroy"===c)return void g.destroy();g.updateSettings(c),g.rewind()}g.updateEntries("norewind"===c)&&g.init()})},a.fn.justifiedGallery.defaults={sizeRangeSuffixes:{},rowHeight:120,maxRowHeight:"200%",margins:1,border:-1,lastRow:"nojustify",justifyThreshold:.75,fixedHeight:!1,waitThumbnailsLoad:!0,captions:!0,cssAnimation:!1,imagesAnimationDuration:500,captionSettings:{animationDuration:500,visibleOpacity:.7,nonVisibleOpacity:0},rel:null,target:null,extension:/\.[^.\\/]+$/,refreshTime:100,randomize:!1,sort:!1,filter:!1,selector:"> a, > div:not(.spinner)"}}(jQuery); \ No newline at end of file
+!function(a){var b=function(b,c){this.settings=c,this.checkSettings(),this.imgAnalyzerTimeout=null,this.entries=null,this.buildingRow={entriesBuff:[],width:0,height:0,aspectRatio:0},this.lastAnalyzedIndex=-1,this.yield={every:2,flushed:0},this.border=c.border>=0?c.border:c.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges(),this.offY=this.border,this.spinner={phase:0,timeSlot:150,$el:a('<div class="spinner"><span></span><span></span><span></span></div>'),intervalId:null},this.checkWidthIntervalId=null,this.galleryWidth=b.width(),this.$gallery=b};b.prototype.getSuffix=function(a,b){var c,d;for(c=a>b?a:b,d=0;d<this.suffixRanges.length;d++)if(c<=this.suffixRanges[d])return this.settings.sizeRangeSuffixes[this.suffixRanges[d]];return this.settings.sizeRangeSuffixes[this.suffixRanges[d-1]]},b.prototype.removeSuffix=function(a,b){return a.substring(0,a.length-b.length)},b.prototype.endsWith=function(a,b){return-1!==a.indexOf(b,a.length-b.length)},b.prototype.getUsedSuffix=function(a){for(var b in this.settings.sizeRangeSuffixes)if(this.settings.sizeRangeSuffixes.hasOwnProperty(b)){if(0===this.settings.sizeRangeSuffixes[b].length)continue;if(this.endsWith(a,this.settings.sizeRangeSuffixes[b]))return this.settings.sizeRangeSuffixes[b]}return""},b.prototype.newSrc=function(a,b,c){var d;if(this.settings.thumbnailPath)d=this.settings.thumbnailPath(a,b,c);else{var e=a.match(this.settings.extension),f=null!==e?e[0]:"";d=a.replace(this.settings.extension,""),d=this.removeSuffix(d,this.getUsedSuffix(d)),d+=this.getSuffix(b,c)+f}return d},b.prototype.showImg=function(a,b){this.settings.cssAnimation?(a.addClass("entry-visible"),b&&b()):a.stop().fadeTo(this.settings.imagesAnimationDuration,1,b)},b.prototype.extractImgSrcFromImage=function(a){var b="undefined"!=typeof a.data("safe-src")?a.data("safe-src"):a.attr("src");return a.data("jg.originalSrc",b),b},b.prototype.imgFromEntry=function(a){var b=a.find("> img");return 0===b.length&&(b=a.find("> a > img")),0===b.length?null:b},b.prototype.captionFromEntry=function(a){var b=a.find("> .caption");return 0===b.length?null:b},b.prototype.displayEntry=function(b,c,d,e,f,g){b.width(e),b.height(g),b.css("top",d),b.css("left",c);var h=this.imgFromEntry(b);if(null!==h){h.css("width",e),h.css("height",f),h.css("margin-left",-e/2),h.css("margin-top",-f/2);var i=h.attr("src"),j=this.newSrc(i,e,f);h.one("error",function(){h.attr("src",h.data("jg.originalSrc"))});var k=function(){i!==j&&h.attr("src",j)};"skipped"===b.data("jg.loaded")?this.onImageEvent(i,a.proxy(function(){this.showImg(b,k),b.data("jg.loaded",!0)},this)):this.showImg(b,k)}else this.showImg(b);this.displayEntryCaption(b)},b.prototype.displayEntryCaption=function(b){var c=this.imgFromEntry(b);if(null!==c&&this.settings.captions){var d=this.captionFromEntry(b);if(null===d){var e=c.attr("alt");this.isValidCaption(e)||(e=b.attr("title")),this.isValidCaption(e)&&(d=a('<div class="caption">'+e+"</div>"),b.append(d),b.data("jg.createdCaption",!0))}null!==d&&(this.settings.cssAnimation||d.stop().fadeTo(0,this.settings.captionSettings.nonVisibleOpacity),this.addCaptionEventsHandlers(b))}else this.removeCaptionEventsHandlers(b)},b.prototype.isValidCaption=function(a){return"undefined"!=typeof a&&a.length>0},b.prototype.onEntryMouseEnterForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.addClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.visibleOpacity)},b.prototype.onEntryMouseLeaveForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.removeClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.nonVisibleOpacity)},b.prototype.addCaptionEventsHandlers=function(b){var c=b.data("jg.captionMouseEvents");"undefined"==typeof c&&(c={mouseenter:a.proxy(this.onEntryMouseEnterForCaption,this),mouseleave:a.proxy(this.onEntryMouseLeaveForCaption,this)},b.on("mouseenter",void 0,void 0,c.mouseenter),b.on("mouseleave",void 0,void 0,c.mouseleave),b.data("jg.captionMouseEvents",c))},b.prototype.removeCaptionEventsHandlers=function(a){var b=a.data("jg.captionMouseEvents");"undefined"!=typeof b&&(a.off("mouseenter",void 0,b.mouseenter),a.off("mouseleave",void 0,b.mouseleave),a.removeData("jg.captionMouseEvents"))},b.prototype.prepareBuildingRow=function(a){var b,c,d,e,f,g=!0,h=0,i=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,j=i/this.buildingRow.aspectRatio,k=this.buildingRow.width/i>this.settings.justifyThreshold;if(a&&"hide"===this.settings.lastRow&&!k){for(b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],this.settings.cssAnimation?c.removeClass("entry-visible"):c.stop().fadeTo(0,0);return-1}for(a&&!k&&"justify"!==this.settings.lastRow&&"hide"!==this.settings.lastRow&&(g=!1),b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],d=c.data("jg.width")/c.data("jg.height"),g?(e=b===this.buildingRow.entriesBuff.length-1?i:j*d,f=j):(e=this.settings.rowHeight*d,f=this.settings.rowHeight),i-=Math.round(e),c.data("jg.jwidth",Math.round(e)),c.data("jg.jheight",Math.ceil(f)),(0===b||h>f)&&(h=f);return this.settings.fixedHeight&&h>this.settings.rowHeight&&(h=this.settings.rowHeight),this.buildingRow.height=h,g},b.prototype.clearBuildingRow=function(){this.buildingRow.entriesBuff=[],this.buildingRow.aspectRatio=0,this.buildingRow.width=0},b.prototype.flushRow=function(a){var b,c,d,e=this.settings,f=this.border;if(c=this.prepareBuildingRow(a),a&&"hide"===e.lastRow&&-1===this.buildingRow.height)return void this.clearBuildingRow();if(this.maxRowHeight.isPercentage?this.maxRowHeight.value*e.rowHeight<this.buildingRow.height&&(this.buildingRow.height=this.maxRowHeight.value*e.rowHeight):this.maxRowHeight.value>0&&this.maxRowHeight.value<this.buildingRow.height&&(this.buildingRow.height=this.maxRowHeight.value),"center"===e.lastRow||"right"===e.lastRow){var g=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*e.margins;for(d=0;d<this.buildingRow.entriesBuff.length;d++)b=this.buildingRow.entriesBuff[d],g-=b.data("jg.jwidth");"center"===e.lastRow?f+=g/2:"right"===e.lastRow&&(f+=g)}for(d=0;d<this.buildingRow.entriesBuff.length;d++)b=this.buildingRow.entriesBuff[d],this.displayEntry(b,f,this.offY,b.data("jg.jwidth"),b.data("jg.jheight"),this.buildingRow.height),f+=b.data("jg.jwidth")+e.margins;this.$gallery.height(this.offY+this.buildingRow.height+this.border+(this.isSpinnerActive()?this.getSpinnerHeight():0)),(!a||this.buildingRow.height<=e.rowHeight&&c)&&(this.offY+=this.buildingRow.height+e.margins,this.clearBuildingRow(),this.$gallery.trigger("jg.rowflush"))},b.prototype.checkWidth=function(){this.checkWidthIntervalId=setInterval(a.proxy(function(){var a=parseFloat(this.$gallery.width());Math.abs(a-this.galleryWidth)>this.settings.refreshSensitivity&&(this.galleryWidth=a,this.rewind(),this.startImgAnalyzer(!0))},this),this.settings.refreshTime)},b.prototype.isSpinnerActive=function(){return null!==this.spinner.intervalId},b.prototype.getSpinnerHeight=function(){return this.spinner.$el.innerHeight()},b.prototype.stopLoadingSpinnerAnimation=function(){clearInterval(this.spinner.intervalId),this.spinner.intervalId=null,this.$gallery.height(this.$gallery.height()-this.getSpinnerHeight()),this.spinner.$el.detach()},b.prototype.startLoadingSpinnerAnimation=function(){var a=this.spinner,b=a.$el.find("span");clearInterval(a.intervalId),this.$gallery.append(a.$el),this.$gallery.height(this.offY+this.buildingRow.height+this.getSpinnerHeight()),a.intervalId=setInterval(function(){a.phase<b.length?b.eq(a.phase).fadeTo(a.timeSlot,1):b.eq(a.phase-b.length).fadeTo(a.timeSlot,0),a.phase=(a.phase+1)%(2*b.length)},a.timeSlot)},b.prototype.rewind=function(){this.lastAnalyzedIndex=-1,this.offY=this.border,this.clearBuildingRow()},b.prototype.updateEntries=function(b){return this.entries=this.$gallery.find(this.settings.selector).toArray(),0===this.entries.length?!1:(this.settings.filter?this.modifyEntries(this.filterArray,b):this.modifyEntries(this.resetFilters,b),a.isFunction(this.settings.sort)?this.modifyEntries(this.sortArray,b):this.settings.randomize&&this.modifyEntries(this.shuffleArray,b),!0)},b.prototype.insertToGallery=function(b){var c=this;a.each(b,function(){a(this).appendTo(c.$gallery)})},b.prototype.shuffleArray=function(a){var b,c,d;for(b=a.length-1;b>0;b--)c=Math.floor(Math.random()*(b+1)),d=a[b],a[b]=a[c],a[c]=d;return this.insertToGallery(a),a},b.prototype.sortArray=function(a){return a.sort(this.settings.sort),this.insertToGallery(a),a},b.prototype.resetFilters=function(b){for(var c=0;c<b.length;c++)a(b[c]).removeClass("jg-filtered");return b},b.prototype.filterArray=function(b){var c=this.settings;return"string"===a.type(c.filter)?b.filter(function(b){var d=a(b);return d.is(c.filter)?(d.removeClass("jg-filtered"),!0):(d.addClass("jg-filtered"),!1)}):a.isFunction(c.filter)?b.filter(c.filter):void 0},b.prototype.modifyEntries=function(a,b){var c=b?this.entries.splice(this.lastAnalyzedIndex+1,this.entries.length-this.lastAnalyzedIndex-1):this.entries;c=a.call(this,c),this.entries=b?this.entries.concat(c):c},b.prototype.destroy=function(){clearInterval(this.checkWidthIntervalId),a.each(this.entries,a.proxy(function(b,c){var d=a(c);d.css("width",""),d.css("height",""),d.css("top",""),d.css("left",""),d.data("jg.loaded",void 0),d.removeClass("jg-entry");var e=this.imgFromEntry(d);e.css("width",""),e.css("height",""),e.css("margin-left",""),e.css("margin-top",""),e.attr("src",e.data("jg.originalSrc")),e.data("jg.originalSrc",void 0),this.removeCaptionEventsHandlers(d);var f=this.captionFromEntry(d);d.data("jg.createdCaption")?(d.data("jg.createdCaption",void 0),null!==f&&f.remove()):null!==f&&f.fadeTo(0,1)},this)),this.$gallery.css("height",""),this.$gallery.removeClass("justified-gallery"),this.$gallery.data("jg.controller",void 0)},b.prototype.analyzeImages=function(b){for(var c=this.lastAnalyzedIndex+1;c<this.entries.length;c++){var d=a(this.entries[c]);if(d.data("jg.loaded")===!0||"skipped"===d.data("jg.loaded")){var e=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,f=d.data("jg.width")/d.data("jg.height");if(e/(this.buildingRow.aspectRatio+f)<this.settings.rowHeight&&(this.flushRow(!1),++this.yield.flushed>=this.yield.every))return void this.startImgAnalyzer(b);this.buildingRow.entriesBuff.push(d),this.buildingRow.aspectRatio+=f,this.buildingRow.width+=f*this.settings.rowHeight,this.lastAnalyzedIndex=c}else if("error"!==d.data("jg.loaded"))return}this.buildingRow.entriesBuff.length>0&&this.flushRow(!0),this.isSpinnerActive()&&this.stopLoadingSpinnerAnimation(),this.stopImgAnalyzerStarter(),this.$gallery.trigger(b?"jg.resize":"jg.complete")},b.prototype.stopImgAnalyzerStarter=function(){this.yield.flushed=0,null!==this.imgAnalyzerTimeout&&clearTimeout(this.imgAnalyzerTimeout)},b.prototype.startImgAnalyzer=function(a){var b=this;this.stopImgAnalyzerStarter(),this.imgAnalyzerTimeout=setTimeout(function(){b.analyzeImages(a)},.001)},b.prototype.onImageEvent=function(b,c,d){if(c||d){var e=new Image,f=a(e);c&&f.one("load",function(){f.off("load error"),c(e)}),d&&f.one("error",function(){f.off("load error"),d(e)}),e.src=b}},b.prototype.init=function(){var b=!1,c=!1,d=this;a.each(this.entries,function(e,f){var g=a(f),h=d.imgFromEntry(g);if(g.addClass("jg-entry"),g.data("jg.loaded")!==!0&&"skipped"!==g.data("jg.loaded"))if(null!==d.settings.rel&&g.attr("rel",d.settings.rel),null!==d.settings.target&&g.attr("target",d.settings.target),null!==h){var i=d.extractImgSrcFromImage(h);if(h.attr("src",i),d.settings.waitThumbnailsLoad===!1){var j=parseFloat(h.attr("width")),k=parseFloat(h.attr("height"));if(!isNaN(j)&&!isNaN(k))return g.data("jg.width",j),g.data("jg.height",k),g.data("jg.loaded","skipped"),c=!0,d.startImgAnalyzer(!1),!0}g.data("jg.loaded",!1),b=!0,d.isSpinnerActive()||d.startLoadingSpinnerAnimation(),d.onImageEvent(i,function(a){g.data("jg.width",a.width),g.data("jg.height",a.height),g.data("jg.loaded",!0),d.startImgAnalyzer(!1)},function(){g.data("jg.loaded","error"),d.startImgAnalyzer(!1)})}else g.data("jg.loaded",!0),g.data("jg.width",g.width()|parseFloat(g.css("width"))|1),g.data("jg.height",g.height()|parseFloat(g.css("height"))|1)}),b||c||this.startImgAnalyzer(!1),this.checkWidth()},b.prototype.checkOrConvertNumber=function(b,c){if("string"===a.type(b[c])&&(b[c]=parseFloat(b[c])),"number"!==a.type(b[c]))throw c+" must be a number";if(isNaN(b[c]))throw"invalid number for "+c},b.prototype.checkSizeRangesSuffixes=function(){if("object"!==a.type(this.settings.sizeRangeSuffixes))throw"sizeRangeSuffixes must be defined and must be an object";var b=[];for(var c in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(c)&&b.push(c);for(var d={0:""},e=0;e<b.length;e++)if("string"===a.type(b[e]))try{var f=parseInt(b[e].replace(/^[a-z]+/,""),10);d[f]=this.settings.sizeRangeSuffixes[b[e]]}catch(g){throw"sizeRangeSuffixes keys must contains correct numbers ("+g+")"}else d[b[e]]=this.settings.sizeRangeSuffixes[b[e]];this.settings.sizeRangeSuffixes=d},b.prototype.retrieveMaxRowHeight=function(){var b={};if("string"===a.type(this.settings.maxRowHeight))this.settings.maxRowHeight.match(/^[0-9]+%$/)?(b.value=parseFloat(this.settings.maxRowHeight.match(/^([0-9]+)%$/)[1])/100,b.isPercentage=!1):(b.value=parseFloat(this.settings.maxRowHeight),b.isPercentage=!0);else{if("number"!==a.type(this.settings.maxRowHeight))throw"maxRowHeight must be a number or a percentage";b.value=this.settings.maxRowHeight,b.isPercentage=!1}if(isNaN(b.value))throw"invalid number for maxRowHeight";return b.isPercentage?b.value<100&&(b.value=100):b.value>0&&b.value<this.settings.rowHeight&&(b.value=this.settings.rowHeight),b},b.prototype.checkSettings=function(){if(this.checkSizeRangesSuffixes(),this.checkOrConvertNumber(this.settings,"rowHeight"),this.checkOrConvertNumber(this.settings,"margins"),this.checkOrConvertNumber(this.settings,"border"),"justify"!==this.settings.lastRow&&"nojustify"!==this.settings.lastRow&&"left"!==this.settings.lastRow&&"center"!==this.settings.lastRow&&"right"!==this.settings.lastRow&&"hide"!==this.settings.lastRow)throw'lastRow must be "justify", "nojustify", "left", "center", "right" or "hide"';if(this.checkOrConvertNumber(this.settings,"justifyThreshold"),this.settings.justifyThreshold<0||this.settings.justifyThreshold>1)throw"justifyThreshold must be in the interval [0,1]";if("boolean"!==a.type(this.settings.cssAnimation))throw"cssAnimation must be a boolean";if("boolean"!==a.type(this.settings.captions))throw"captions must be a boolean";if(this.checkOrConvertNumber(this.settings.captionSettings,"animationDuration"),this.checkOrConvertNumber(this.settings.captionSettings,"visibleOpacity"),this.settings.captionSettings.visibleOpacity<0||this.settings.captionSettings.visibleOpacity>1)throw"captionSettings.visibleOpacity must be in the interval [0, 1]";if(this.checkOrConvertNumber(this.settings.captionSettings,"nonVisibleOpacity"),this.settings.captionSettings.nonVisibleOpacity<0||this.settings.captionSettings.nonVisibleOpacity>1)throw"captionSettings.nonVisibleOpacity must be in the interval [0, 1]";if("boolean"!==a.type(this.settings.fixedHeight))throw"fixedHeight must be a boolean";if(this.checkOrConvertNumber(this.settings,"imagesAnimationDuration"),this.checkOrConvertNumber(this.settings,"refreshTime"),this.checkOrConvertNumber(this.settings,"refreshSensitivity"),"boolean"!==a.type(this.settings.randomize))throw"randomize must be a boolean";if("string"!==a.type(this.settings.selector))throw"selector must be a string";if(this.settings.sort!==!1&&!a.isFunction(this.settings.sort))throw"sort must be false or a comparison function";if(this.settings.filter!==!1&&!a.isFunction(this.settings.filter)&&"string"!==a.type(this.settings.filter))throw"filter must be false, a string or a filter function"},b.prototype.retrieveSuffixRanges=function(){var a=[];for(var b in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(b)&&a.push(parseInt(b,10));return a.sort(function(a,b){return a>b?1:b>a?-1:0}),a},b.prototype.updateSettings=function(b){this.settings=a.extend({},this.settings,b),this.checkSettings(),this.border=this.settings.border>=0?this.settings.border:this.settings.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges()},a.fn.justifiedGallery=function(c){return this.each(function(d,e){var f=a(e);f.addClass("justified-gallery");var g=f.data("jg.controller");if("undefined"==typeof g){if("undefined"!=typeof c&&null!==c&&"object"!==a.type(c)){if("destroy"===c)return;throw"The argument must be an object"}g=new b(f,a.extend({},a.fn.justifiedGallery.defaults,c)),f.data("jg.controller",g)}else if("norewind"===c);else{if("destroy"===c)return void g.destroy();g.updateSettings(c),g.rewind()}g.updateEntries("norewind"===c)&&g.init()})},a.fn.justifiedGallery.defaults={sizeRangeSuffixes:{},thumbnailPath:void 0,rowHeight:120,maxRowHeight:-1,margins:1,border:-1,lastRow:"nojustify",justifyThreshold:.75,fixedHeight:!1,waitThumbnailsLoad:!0,captions:!0,cssAnimation:!1,imagesAnimationDuration:500,captionSettings:{animationDuration:500,visibleOpacity:.7,nonVisibleOpacity:0},rel:null,target:null,extension:/\.[^.\\/]+$/,refreshTime:200,refreshSensitivity:0,randomize:!1,sort:!1,filter:!1,selector:"> a, > div:not(.spinner)"}}(jQuery); \ No newline at end of file
diff --git a/library/justifiedGallery/justifiedGallery.css b/library/justifiedGallery/justifiedGallery.css
index 0d45475ce..99be92ff2 100644
--- a/library/justifiedGallery/justifiedGallery.css
+++ b/library/justifiedGallery/justifiedGallery.css
@@ -1,5 +1,5 @@
/*!
- * Justified Gallery - v3.6.0
+ * Justified Gallery - v3.6.1
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
diff --git a/library/justifiedGallery/justifiedGallery.min.css b/library/justifiedGallery/justifiedGallery.min.css
index d7b1c6726..09ae4e1f0 100644
--- a/library/justifiedGallery/justifiedGallery.min.css
+++ b/library/justifiedGallery/justifiedGallery.min.css
@@ -1,5 +1,5 @@
/*!
- * Justified Gallery - v3.6.0
+ * Justified Gallery - v3.6.1
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
diff --git a/mod/chat.php b/mod/chat.php
index 75c364008..375d069be 100644
--- a/mod/chat.php
+++ b/mod/chat.php
@@ -208,6 +208,12 @@ function chat_content(&$a) {
$o = profile_tabs($a,((local_channel() && local_channel() == App::$profile['profile_uid']) ? true : false),App::$profile['channel_address']);
+ if(! feature_enabled(App::$profile['profile_uid'],'ajaxchat')) {
+ notice( t('Feature disabled.') . EOL);
+ return $o;
+ }
+
+
$acl = new Zotlabs\Access\AccessList($channel);
$channel_acl = $acl->get();
diff --git a/mod/getfile.php b/mod/getfile.php
new file mode 100644
index 000000000..c0916de79
--- /dev/null
+++ b/mod/getfile.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * module: getfile
+ *
+ * used for synchronising files and photos across clones
+ *
+ * The site initiating the file operation will send a sync packet to known clones.
+ * They will respond by building the DB structures they require, then will provide a
+ * post request to this site to grab the file data. This is sent as a stream direct to
+ * disk at the other end, avoiding memory issues.
+ *
+ * Since magic-auth cannot easily be used by the CURL process at the other end,
+ * we will require a signed request which includes a timestamp. This should not be
+ * used without SSL and is potentially vulnerable to replay if an attacker decrypts
+ * the SSL traffic fast enough. The amount of time slop is configurable but defaults
+ * to 3 minutes.
+ *
+ */
+
+
+
+require_once('include/Contact.php');
+require_once('include/attach.php');
+
+function getfile_post(&$a) {
+
+ $hash = $_POST['hash'];
+ $time = $_POST['time'];
+ $sig = $_POST['signature'];
+ $resource = $_POST['resource'];
+ $revision = intval($_POST['revision']);
+
+ if(! $hash)
+ killme();
+
+ $channel = channelx_by_hash($hash);
+
+ if((! $channel) || (! $time) || (! $sig))
+ killme();
+
+ $slop = intval(get_pconfig($channel['channel_id'],'system','getfile_time_slop'));
+ if($slop < 1)
+ $slop = 3;
+
+ $d1 = datetime_convert('UTC','UTC',"now + $slop minutes");
+ $d2 = datetime_convert('UTC','UTC',"now - $slop minutes");
+
+ if(($time > $d1) || ($time < $d2)) {
+ logger('time outside allowable range');
+ killme();
+ }
+
+ if(! rsa_verify($hash . '.' . $time,base64url_decode($sig),$channel['channel_pubkey'])) {
+ logger('verify failed.');
+ killme();
+ }
+
+
+ $r = attach_by_hash($resource,$revision);
+
+ if(! $r['success']) {
+ notice( $r['message'] . EOL);
+ return;
+ }
+
+
+ $unsafe_types = array('text/html','text/css','application/javascript');
+
+ if(in_array($r['data']['filetype'],$unsafe_types)) {
+ header('Content-type: text/plain');
+ }
+ else {
+ header('Content-type: ' . $r['data']['filetype']);
+ }
+
+ header('Content-disposition: attachment; filename="' . $r['data']['filename'] . '"');
+ if(intval($r['data']['os_storage'])) {
+ $fname = dbunescbin($r['data']['data']);
+ if(strpos($fname,'store') !== false)
+ $istream = fopen($fname,'rb');
+ else
+ $istream = fopen('store/' . $channel['channel_address'] . '/' . $fname,'rb');
+ $ostream = fopen('php://output','wb');
+ if($istream && $ostream) {
+ pipe_streams($istream,$ostream);
+ fclose($istream);
+ fclose($ostream);
+ }
+ }
+ else
+ echo dbunescbin($r['data']['data']);
+ killme();
+
+
+
+} \ No newline at end of file
diff --git a/mod/help.php b/mod/help.php
index a266dbf7f..fb0339cd9 100644
--- a/mod/help.php
+++ b/mod/help.php
@@ -84,7 +84,21 @@ function doc_rank_sort($s1,$s2) {
}
+function load_context_help() {
+
+ $path = App::$cmd;
+ $args = App::$argv;
+
+ while($path) {
+ $context_help = load_doc_file('doc/context/' . $path . '/help.html');
+ if($context_help)
+ break;
+ array_pop($args);
+ $path = implode($args,'/');
+ }
+ return $context_help;
+}
function store_doc_file($s) {
diff --git a/mod/notes.php b/mod/notes.php
index 4bb97fc9e..9bf37d0f9 100644
--- a/mod/notes.php
+++ b/mod/notes.php
@@ -6,8 +6,18 @@ function notes_init(&$a) {
return;
$ret = array('success' => true);
- if($_REQUEST['note_text'] || $_REQUEST['note_text'] == '') {
+ if(array_key_exists('note_text',$_REQUEST)) {
$body = escape_tags($_REQUEST['note_text']);
+
+ // I've had my notes vanish into thin air twice in four years.
+ // Provide a backup copy if there were contents previously
+ // and there are none being saved now.
+
+ if(! $body) {
+ $old_text = get_pconfig(local_channel(),'notes','text');
+ if($old_text)
+ set_pconfig(local_channel(),'notes','text.bak',$old_text);
+ }
set_pconfig(local_channel(),'notes','text',$body);
}
diff --git a/mod/photos.php b/mod/photos.php
index 0adbf752a..bf904db22 100644
--- a/mod/photos.php
+++ b/mod/photos.php
@@ -126,6 +126,34 @@ function photos_post(&$a) {
if($_REQUEST['dropalbum'] == t('Delete Album')) {
+
+ // This is dangerous because we combined file storage and photos into one interface
+ // This function will remove all photos from any directory with the same name since
+ // we have not passed the path value.
+
+ // The correct solution would be to use a full pathname from your storage root for 'album'
+ // We also need to prevent/block removing the storage root folder.
+
+ $folder_hash = '';
+
+ $r = q("select * from attach where is_dir = 1 and uid = %d and filename = '%s'",
+ intval($page_owner_uid),
+ dbesc($album)
+ );
+ if(! $r) {
+ notice( t('Album not found.') . EOL);
+ return;
+ }
+ if(count($r) > 1) {
+ notice( t('Multiple storage folders exist with this album name, but within different directories. Please remove the desired folder or folders using the Files manager') . EOL);
+ return;
+ }
+ else {
+ $folder_hash = $r[0]['hash'];
+ }
+
+
+
$res = array();
// get the list of photos we are about to delete
@@ -149,9 +177,6 @@ function photos_post(&$a) {
if($r) {
foreach($r as $i) {
attach_delete($page_owner_uid, $i['resource_id'], 1 );
- // This is now being done in attach_delete()
- // drop_item($i['id'],false,DROPITEM_PHASE1,true /* force removal of linked items */);
- // proc_run('php','include/notifier.php','drop',$i['id']);
}
}
@@ -163,6 +188,15 @@ function photos_post(&$a) {
// @FIXME do the same for the linked attach
+ if($folder_hash) {
+ attach_delete($page_owner_uid,$folder_hash, 1);
+
+ $sync = attach_export_data(App::$data['channel'],$folder_hash, true);
+
+ if($sync)
+ build_sync_packet($page_owner_uid,array('file' => array($sync)));
+ }
+
}
goaway(z_root() . '/photos/' . App::$data['channel']['channel_address']);
@@ -183,23 +217,11 @@ function photos_post(&$a) {
);
if($r) {
-/*
- q("DELETE FROM `photo` WHERE `uid` = %d AND `resource_id` = '%s'",
- intval($page_owner_uid),
- dbesc($r[0]['resource_id'])
- );
-*/
attach_delete($page_owner_uid, $r[0]['resource_id'], 1 );
-/*
- $i = q("SELECT * FROM `item` WHERE `resource_id` = '%s' AND resource_type = 'photo' and `uid` = %d LIMIT 1",
- dbesc($r[0]['resource_id']),
- intval($page_owner_uid)
- );
- if(count($i)) {
- drop_item($i[0]['id'],true,DROPITEM_PHASE1);
- $url = z_root();
- }
-*/
+ $sync = attach_export_data(App::$data['channel'],$r[0]['resource_id'], true);
+
+ if($sync)
+ build_sync_packet($page_owner_uid,array('file' => array($sync)));
}
goaway(z_root() . '/photos/' . App::$data['channel']['channel_address'] . '/album/' . $_SESSION['album_return']);
@@ -218,7 +240,7 @@ function photos_post(&$a) {
$acl->set_from_array($_POST);
$perm = $acl->get();
- $resource_id = App::$argv[2];
+ $resource_id = argv(2);
if(! strlen($albname))
$albname = datetime_convert('UTC',date_default_timezone_get(),'now', 'Y');
@@ -443,6 +465,11 @@ function photos_post(&$a) {
goaway(z_root() . '/' . $_SESSION['photo_return']);
return; // NOTREACHED
+ $sync = attach_export_data(App::$data['channel'],$resource_id);
+
+ if($sync)
+ build_sync_packet($page_owner_uid,array('file' => array($sync)));
+
}
@@ -555,8 +582,8 @@ function photos_content(&$a) {
$o = "";
- $o .= "<script> var profile_uid = " . App::$profile['profile_uid']
- . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
+ $o .= "<script> var profile_uid = " . App::$profile['profile_uid']
+ . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
// tabs
diff --git a/mod/wall_attach.php b/mod/wall_attach.php
index 75786b479..7f054705f 100644
--- a/mod/wall_attach.php
+++ b/mod/wall_attach.php
@@ -23,23 +23,6 @@ function wall_attach_post(&$a) {
$observer = App::get_observer();
-// if($_FILES['userfile']['tmp_name']) {
-// $x = @getimagesize($_FILES['userfile']['tmp_name']);
-// logger('getimagesize: ' . print_r($x,true), LOGGER_DATA);
-// if(($x) && ($x[2] === IMAGETYPE_GIF || $x[2] === IMAGETYPE_JPEG || $x[2] === IMAGETYPE_PNG)) {
-// $args = array( 'source' => 'editor', 'visible' => 0, 'contact_allow' => array($channel['channel_hash']));
-// $ret = photo_upload($channel,$observer,$args);
-// if($ret['success']) {
-// echo "\n\n" . $ret['body'] . "\n\n";
-// killme();
-// }
-// if($using_api)
-// return;
-// notice($ret['message']);
-// killme();
-// }
-// }
-
$def_album = get_pconfig($channel['channel_id'],'system','photo_path');
$def_attach = get_pconfig($channel['channel_id'],'system','attach_path');
diff --git a/util/add_addon_repo b/util/add_addon_repo
index decd9e091..a8dd9f49a 100755
--- a/util/add_addon_repo
+++ b/util/add_addon_repo
@@ -1,10 +1,21 @@
#!/bin/bash -f
-if [ $# -ne 2 ]; then
+if [ $# -lt 2 ]; then
echo usage: $0 repo_url nickname
exit 1
fi
+if [[ $1 != *"//github.com/redmatrix"* && $3 != 'insecure' ]]; then
+ echo "";
+ echo "This is NOT an official project repository.";
+ echo "In order to protect you from unverified and";
+ echo "possibly malicious content, this repository";
+ echo "will not be linked to your site unless you";
+ echo "append the word 'insecure' to the command.";
+ echo "";
+ exit 1
+fi
+
mkdir -p extend/addon/$2
mkdir addon > /dev/null 2>&1
git clone $1 extend/addon/$2
@@ -14,7 +25,6 @@ fi
filelist=(`ls extend/addon/$2`)
-
cd addon
for a in "${filelist[@]}" ; do
base=`basename $a`
diff --git a/util/add_theme_repo b/util/add_theme_repo
index d41eba6d9..8280c447b 100755
--- a/util/add_theme_repo
+++ b/util/add_theme_repo
@@ -1,11 +1,21 @@
#!/bin/bash -f
-
-if [ $# -ne 2 ]; then
+if [ $# -lt 2 ]; then
echo usage: $0 repo_url nickname
exit 1
fi
+if [[ $1 != *"//github.com/redmatrix"* && $3 != 'insecure' ]]; then
+ echo "";
+ echo "This is NOT an official project repository.";
+ echo "In order to protect you from unverified and";
+ echo "possibly malicious content, this repository";
+ echo "will not be linked to your site unless you";
+ echo "append the word 'insecure' to the command.";
+ echo "";
+ exit 1
+fi
+
mkdir -p extend/theme/$2
git clone $1 extend/theme/$2
if [ $? -ne 0 ]; then
diff --git a/util/add_widget_repo b/util/add_widget_repo
index 347e8e4e1..e7e316ba4 100755
--- a/util/add_widget_repo
+++ b/util/add_widget_repo
@@ -1,10 +1,21 @@
#!/bin/bash -f
-if [ $# -ne 2 ]; then
+if [ $# -lt 2 ]; then
echo usage: $0 repo_url nickname
exit 1
fi
+if [[ $1 != *"//github.com/redmatrix"* && $3 != 'insecure' ]]; then
+ echo "";
+ echo "This is NOT an official project repository.";
+ echo "In order to protect you from unverified and";
+ echo "possibly malicious content, this repository";
+ echo "will not be linked to your site unless you";
+ echo "append the word 'insecure' to the command.";
+ echo "";
+ exit 1
+fi
+
mkdir -p extend/widget/$2
mkdir widget > /dev/null 2>&1
git clone $1 extend/widget/$2
diff --git a/view/css/conversation.css b/view/css/conversation.css
index 39c973c14..304e0f196 100644
--- a/view/css/conversation.css
+++ b/view/css/conversation.css
@@ -296,3 +296,9 @@ a.wall-item-name-link {
.event-label {
font-weight: bold;
}
+
+/* bb-code */
+
+.overline {
+ text-decoration: overline;
+}
diff --git a/view/js/autocomplete.js b/view/js/autocomplete.js
index 437425a0e..0ba291004 100644
--- a/view/js/autocomplete.js
+++ b/view/js/autocomplete.js
@@ -100,6 +100,66 @@ function submit_form(e) {
$(e).parents('form').submit();
}
+function getWord(text, caretPos) {
+ var index = text.indexOf(caretPos);
+ var postText = text.substring(caretPos, caretPos+8);
+ if ((postText.indexOf("[/list]") > 0) || postText.indexOf("[/ul]") > 0 || postText.indexOf("[/ol]") > 0) {
+ return postText;
+ }
+}
+
+function getCaretPosition(ctrl) {
+ var CaretPos = 0; // IE Support
+ if (document.selection) {
+ ctrl.focus();
+ var Sel = document.selection.createRange();
+ Sel.moveStart('character', -ctrl.value.length);
+ CaretPos = Sel.text.length;
+ }
+ // Firefox support
+ else if (ctrl.selectionStart || ctrl.selectionStart == '0')
+ CaretPos = ctrl.selectionStart;
+ return (CaretPos);
+}
+
+function setCaretPosition(ctrl, pos){
+ if(ctrl.setSelectionRange) {
+ ctrl.focus();
+ ctrl.setSelectionRange(pos,pos);
+ }
+ else if (ctrl.createTextRange) {
+ var range = ctrl.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', pos);
+ range.moveStart('character', pos);
+ range.select();
+ }
+}
+
+function listNewLineAutocomplete(id) {
+ var text = document.getElementById(id);
+ var caretPos = getCaretPosition(text)
+ var word = getWord(text.value, caretPos);
+ if (word != null) {
+ var textBefore = text.value.substring(0, caretPos);
+ var textAfter = text.value.substring(caretPos, text.length);
+ $('#' + id).val(textBefore + '\r\n[*] ' + textAfter);
+ setCaretPosition(text, caretPos + 5);
+ return true;
+ }
+}
+
+function string2bb(element) {
+ if(element == 'bold') return 'b';
+ else if(element == 'italic') return 'i';
+ else if(element == 'underline') return 'u';
+ else if(element == 'overline') return 'o';
+ else if(element == 'strike') return 's';
+ else if(element == 'superscript') return 'sup';
+ else if(element == 'subscript') return 'sub';
+ else return element;
+}
+
/**
* jQuery plugin 'editor_autocomplete'
*/
@@ -197,3 +257,63 @@ function submit_form(e) {
a.on('textComplete:select', function(e, value, strategy) { onselect(value); });
};
})( jQuery );
+
+(function( $ ) {
+ $.fn.bbco_autocomplete = function(type) {
+
+ if(type=='bbcode') {
+ var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'superscript', 'subscript', 'quote', 'code', 'spoiler', 'map', 'nobb', 'list', 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'zrl', 'zmg', 'rpost', 'qr', 'observer'];
+ var open_elements = ['observer.baseurl', 'observer.address', 'observer.photo', 'observer.name', 'observer.webname', 'observer.url', '*', 'hr', ];
+
+ var elements = open_close_elements.concat(open_elements);
+ }
+
+ if(type=='comanche') {
+ var open_close_elements = ['region', 'widget', 'var', 'template', 'css', 'js'];
+ var open_elements = [];
+
+ var elements = open_close_elements.concat(open_elements);
+ }
+
+ bbco = {
+ match: /\[(\w*\**)$/,
+ search: function (term, callback) {
+ callback($.map(elements, function (element) {
+ return element.indexOf(term) === 0 ? element : null;
+ }));
+ },
+ index: 1,
+ replace: function (element) {
+ element = string2bb(element);
+ if(open_elements.indexOf(element) < 0) {
+ if(element === 'list' || element === 'ol' || element === 'ul') {
+ return ['\[' + element + '\]' + '\n\[*\] ', '\n\[/' + element + '\]'];
+ }
+ else if(element === 'table') {
+ return ['\[' + element + '\]' + '\n\[tr\]', '\[/tr\]\n\[/' + element + '\]'];
+ }
+ else {
+ return ['\[' + element + '\]', '\[/' + element + '\]'];
+ }
+ }
+ else {
+ return '\[' + element + '\] ';
+ }
+ }
+ };
+
+ this.attr('autocomplete','off');
+ var a = this.textcomplete([bbco], {className:'acpopup', zIndex:1020});
+
+ a.on('textComplete:select', function(e, value, strategy) { value; });
+
+ $(this).keypress(function(e){
+ if (e.keyCode == 13) {
+ x = listNewLineAutocomplete(this.id);
+ if(x)
+ e.preventDefault();
+ }
+ });
+ };
+})( jQuery );
+
diff --git a/view/js/main.js b/view/js/main.js
index 04b317914..ba476b576 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -617,6 +617,8 @@ function updateConvItems(mode,data) {
/* autocomplete @nicknames */
$(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl?f=&n=1");
+ /* autocomplete bbcode */
+ $(".comment-edit-form textarea").bbco_autocomplete('bbcode');
var bimgs = ((preloadImages) ? false : $(".wall-item-body img").not(function() { return this.complete; }));
var bimgcount = bimgs.length;
diff --git a/view/tpl/jot-header.tpl b/view/tpl/jot-header.tpl
index 84fccc105..2b62f2407 100755
--- a/view/tpl/jot-header.tpl
+++ b/view/tpl/jot-header.tpl
@@ -11,6 +11,7 @@ function initEditor(cb){
if(plaintext == 'none') {
$("#profile-jot-text-loading").spin(false).hide();
$("#profile-jot-text").css({ 'height': 200, 'color': '#000' });
+ $("#profile-jot-text").bbco_autocomplete('bbcode'); // autocomplete bbcode
if(typeof channelId === 'undefined')
$("#profile-jot-text").editor_autocomplete(baseurl+"/acl");
else
diff --git a/view/tpl/login.tpl b/view/tpl/login.tpl
index da38f3571..d56c8f272 100755
--- a/view/tpl/login.tpl
+++ b/view/tpl/login.tpl
@@ -5,7 +5,7 @@
<div id="login-input" class="form-group">
{{include file="field_input.tpl" field=$lname}}
{{include file="field_password.tpl" field=$lpassword}}
- {{include file="field_checkbox.tpl" field=$remember}}
+ {{include file="field_checkbox.tpl" field=$remember_me}}
<button type="submit" name="submit" class="btn btn-block btn-primary">{{$login}}</button>
</div>
<div id="login-extra-links">
diff --git a/view/tpl/nav.tpl b/view/tpl/nav.tpl
index 3d6809c22..886f73947 100755
--- a/view/tpl/nav.tpl
+++ b/view/tpl/nav.tpl
@@ -1,4 +1,45 @@
- <div class="container-fluid">
+<script>
+ $(document).mouseup(function (e)
+ {
+ var container = $("#help-content");
+
+ if (!container.is(e.target) // if the target of the click isn't the container...
+ && container.has(e.target).length === 0 // ... nor a descendant of the container
+ && container.hasClass('help-content-open'))
+ {
+ container.removeClass('help-content-open');
+ }
+ });
+</script>
+<style>
+.help-content {
+ background: rgba(255, 255, 255, 0.9);
+ color: #333333;
+ position: fixed;
+ top: 50px;
+ left: -80%;
+ width: 80%;
+ height: 60%;
+ padding: 20px;
+ transition: left 300ms cubic-bezier(0.17, 0.04, 0.03, 0.94);
+ box-sizing: border-box;
+ border: #CCC thin solid;
+ overflow: auto;
+}
+
+.help-content-open {
+ left: 0px;
+ -moz-box-shadow: 3px 3px 3px #ccc;
+ -webkit-box-shadow: 3px 3px 3px #ccc;
+ box-shadow: 3px 3px 3px #ccc;
+}
+
+.help-content dd {
+ margin-bottom: 1em;
+}
+</style>
+
+<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span class="icon-bar"></span>
@@ -189,8 +230,13 @@
{{if $nav.help}}
<li class="{{$sel.help}}">
- <a class="{{$nav.help.2}}" target="hubzilla-help" href="{{$nav.help.0}}" title="{{$nav.help.3}}" id="{{$nav.help.4}}"><i class="icon-question"></i></a>
+ <a class="{{$nav.help.2}}" target="hubzilla-help" href="{{$nav.help.0}}" title="{{$nav.help.3}}" id="{{$nav.help.4}}" onclick="$('#help-content').toggleClass('help-content-open'); return false;"><i class="icon-question"></i></a>
</li>
+
+ <div id="help-content" class="help-content">
+ {{$nav.help.5}}
+ <p class="pull-right"><a href="{{$nav.help.0}}">Click here for more documentation...</a></p>
+ </div>
{{/if}}
</ul>
</div>