aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorMario <mario@mariovavti.com>2021-07-29 06:59:13 +0000
committerMario <mario@mariovavti.com>2021-07-29 06:59:13 +0000
commit9722d157bf138753bc168f68c56b4963db6da4e9 (patch)
treea15bb81c35a2367fe4628614471d471ed86d2712 /include
parent219d47f04c7bb04dcc241b9ed6ca411fc9653f09 (diff)
parentc6133d2558ce29e44342fa7be8bb65e0059aea02 (diff)
downloadvolse-hubzilla-9722d157bf138753bc168f68c56b4963db6da4e9.tar.gz
volse-hubzilla-9722d157bf138753bc168f68c56b4963db6da4e9.tar.bz2
volse-hubzilla-9722d157bf138753bc168f68c56b4963db6da4e9.zip
Merge branch 'dev' into bs5
Diffstat (limited to 'include')
-rw-r--r--include/attach.php4
-rw-r--r--include/bbcode.php21
-rw-r--r--include/conversation.php32
-rw-r--r--include/dir_fns.php437
-rw-r--r--include/html2plain.php10
-rw-r--r--include/items.php4
-rw-r--r--include/photos.php100
-rw-r--r--include/queue_fn.php314
-rw-r--r--include/text.php87
9 files changed, 187 insertions, 822 deletions
diff --git a/include/attach.php b/include/attach.php
index db7046ef0..09d4005e8 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -668,8 +668,8 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$def_extension = '.jpg';
if($gis[2] === IMAGETYPE_PNG)
$def_extension = '.png';
- if($gis[2] === IMAGETYPE_WEBP)
- $def_extension = '.webp';
+ if($gis[2] === IMAGETYPE_WEBP)
+ $def_extension = '.webp';
}
// If we know it's a photo, over-ride the type in case the source system could not determine what it was
diff --git a/include/bbcode.php b/include/bbcode.php
index a362e9ce9..87a7e6af6 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -1099,6 +1099,27 @@ function bbcode($Text, $options = []) {
call_hooks('bbcode_filter', $Text);
+ if(isset($options['drop_media'])) {
+ if (strpos($Text,'[/img]') !== false) {
+ $Text = preg_replace('/\[img(.*?)\[\/(img)\]/ism', '', $Text);
+ }
+ if (strpos($Text,'[/audio]') !== false) {
+ $Text = preg_replace('/\[audio(.*?)\[\/(audio)\]/ism', '', $Text);
+ }
+ if (strpos($Text,'[/video]') !== false) {
+ $Text = preg_replace('/\[video(.*?)\[\/(video)\]/ism', '', $Text);
+ }
+ if (strpos($Text,'[/zmg]') !== false) {
+ $Text = preg_replace('/\[zmg(.*?)\[\/(zmg)\]/ism', '', $Text);
+ }
+ if (strpos($Text,'[/zaudio]') !== false) {
+ $Text = preg_replace('/\[zaudio(.*?)\[\/(zaudio)\]/ism', '', $Text);
+ }
+ if (strpos($Text,'[/zvideo]') !== false) {
+ $Text = preg_replace('/\[zvideo(.*?)\[\/(zvideo)\]/ism', '', $Text);
+ }
+ }
+
// Hide all [noparse] contained bbtags by spacefying them
if (strpos($Text,'[noparse]') !== false) {
$Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_spacefy',$Text);
diff --git a/include/conversation.php b/include/conversation.php
index 39ff8d7ad..c5a0df79a 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -730,10 +730,13 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'delete' => t('Delete'),
);
- $star = array(
- 'toggle' => t("Toggle Star Status"),
- 'isstarred' => ((intval($item['item_starred'])) ? true : false),
- );
+ $star = [];
+ if ((local_channel() && local_channel() === intval($item['uid'])) && intval($item['item_thread_top']) && feature_enabled(local_channel(), 'star_posts')) {
+ $star = [
+ 'toggle' => t("Toggle Star Status"),
+ 'isstarred' => ((intval($item['item_starred'])) ? true : false),
+ ];
+ }
$lock = (($item['item_private'] || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
? t('Private Message')
@@ -765,8 +768,12 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$conv_link_mid = (($mode == 'moderate') ? $item['parent_mid'] : $item['mid']);
- $conv_link = ((in_array($item['item_type'],[ ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE] )) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid));
+ $conv_link_module = 'display';
+ if(local_channel()) {
+ $conv_link_module = 'hq';
+ }
+ $conv_link = ((in_array($item['item_type'],[ ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE] )) ? $item['plink'] : z_root() . '/' . $conv_link_module . '/' . gen_link_id($conv_link_mid));
$tmp_item = array(
'template' => $tpl,
@@ -777,7 +784,8 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'delete' => t('Delete'),
'preview_lbl' => $preview_lbl,
'id' => (($preview) ? 'P0' : $item['item_id']),
- 'mids' => json_encode(['b64.' . base64url_encode($item['mid'])]),
+ 'mid' => gen_link_id($item['mid']),
+ 'mids' => json_encode([gen_link_id($item['mid'])]),
'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, $profile_url),
'profile_url' => $profile_link,
'thread_action_menu' => thread_action_menu($item,$mode),
@@ -819,7 +827,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'owner_photo' => $owner_photo,
'plink' => get_plink($item,false),
'edpost' => false,
- 'star' => ((feature_enabled(local_channel(),'star_posts')) ? $star : ''),
+ 'star' => $star,
'drop' => $drop,
'vote' => $likebuttons,
'like' => '',
@@ -932,7 +940,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'$user' => App::$user,
'$threads' => $threads,
'$wait' => t('Loading...'),
- '$conversation_tools' => t('Conversation Tools'),
+ '$conversation_tools' => t('Conversation Features'),
'$dropping' => ($page_dropping?t('Delete Selected Items'):False),
'$preview' => $preview
));
@@ -1053,7 +1061,7 @@ function thread_author_menu($item, $mode = '') {
}
else {
$url = (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']);
- if($local_channel && $url && (! in_array($item['author']['xchan_network'],[ 'rss', 'anon','unknown' ]))) {
+ if($local_channel && $url && (! in_array($item['author']['xchan_network'],[ 'rss', 'anon','unknown', 'zot' ]))) {
$follow_url = z_root() . '/follow/?f=&url=' . urlencode($url) . '&interactive=0';
}
}
@@ -1388,7 +1396,8 @@ function hz_status_editor($a, $x, $popup = false) {
'$nocomment_enabled' => t('Comments enabled'),
'$nocomment_disabled' => t('Comments disabled'),
'$auto_save_draft' => $feature_auto_save_draft,
- '$reset' => $reset
+ '$reset' => $reset,
+ '$popup' => $popup
];
call_hooks('jot_header_tpl_filter',$tplmacros);
@@ -1688,7 +1697,8 @@ function prepare_page($item) {
// ... other possible options
}
- $body = prepare_body($item, [ 'newwin' => false ]);
+ $body = prepare_body($item, true, [ 'newwin' => false ]);
+
if(App::$page['template'] == 'none') {
$tpl = 'page_display_empty.tpl';
diff --git a/include/dir_fns.php b/include/dir_fns.php
deleted file mode 100644
index 84b0e6d93..000000000
--- a/include/dir_fns.php
+++ /dev/null
@@ -1,437 +0,0 @@
-<?php
-/**
- * @file include/dir_fns.php
- */
-
-use Zotlabs\Lib\Crypto;
-use Zotlabs\Lib\Libzot;
-use Zotlabs\Lib\Webfinger;
-use Zotlabs\Lib\Zotfinger;
-
-require_once('include/permissions.php');
-
-/**
- * @brief
- *
- * @param int $dirmode
- * @return array
- */
-function find_upstream_directory($dirmode) {
-
- $preferred = get_config('system','directory_server');
-
- // Thwart attempts to use a private directory
-
- if(($preferred) && ($preferred != z_root())) {
- $r = q("select * from site where site_url = '%s' limit 1",
- dbesc($preferred)
- );
- if(($r) && ($r[0]['site_flags'] & DIRECTORY_MODE_STANDALONE)) {
- $preferred = '';
- }
- }
-
-
- if (! $preferred) {
-
- /*
- * No directory has yet been set. For most sites, pick one at random
- * from our list of directory servers. However, if we're a directory
- * server ourself, point at the local instance
- * We will then set this value so this should only ever happen once.
- * Ideally there will be an admin setting to change to a different
- * directory server if you don't like our choice or if circumstances change.
- */
-
- $directory_fallback_servers = get_directory_fallback_servers();
-
- $dirmode = intval(get_config('system','directory_mode'));
- if ($dirmode == DIRECTORY_MODE_NORMAL) {
- $toss = mt_rand(0,count($directory_fallback_servers));
- $preferred = $directory_fallback_servers[$toss];
- set_config('system','directory_server',$preferred);
- } else{
- set_config('system','directory_server',z_root());
- }
- }
-
- return array('url' => $preferred);
-}
-
-/**
- * Directories may come and go over time. We will need to check that our
- * directory server is still valid occasionally, and reset to something that
- * is if our directory has gone offline for any reason
- */
-function check_upstream_directory() {
-
- $directory = get_config('system', 'directory_server');
-
- // it's possible there is no directory server configured and the local hub is being used.
- // If so, default to preserving the absence of a specific server setting.
-
- $isadir = true;
-
- if ($directory) {
- $j = Zotfinger::exec($directory);
- if (array_path_exists('data/directory_mode',$j)) {
- if ($j['data']['directory_mode'] === 'normal') {
- $isadir = false;
- }
- }
- }
-
- if (! $isadir)
- set_config('system', 'directory_server', '');
-}
-
-function get_directory_setting($observer, $setting) {
-
- if ($observer)
- $ret = get_xconfig($observer, 'directory', $setting);
- else
- $ret = ((array_key_exists($setting,$_SESSION)) ? intval($_SESSION[$setting]) : false);
-
- if($ret === false)
- $ret = get_config('directory', $setting);
-
-
- // 'safemode' is the default if there is no observer or no established preference.
-
- if($setting == 'safemode' && $ret === false)
- $ret = 1;
-
- return $ret;
-}
-
-/**
- * @brief Called by the directory_sort widget.
- */
-function dir_sort_links() {
-
- $safe_mode = 1;
-
- $observer = get_observer_hash();
-
- $safe_mode = get_directory_setting($observer, 'safemode');
- $globaldir = get_directory_setting($observer, 'globaldir');
- $pubforums = get_directory_setting($observer, 'pubforums');
-
- // Build urls without order and pubforums so it's easy to tack on the changed value
- // Probably there's an easier way to do this
-
- $directory_sort_order = get_config('system','directory_sort_order');
- if(! $directory_sort_order)
- $directory_sort_order = 'date';
-
- $current_order = (($_REQUEST['order']) ? $_REQUEST['order'] : $directory_sort_order);
- $suggest = (($_REQUEST['suggest']) ? '&suggest=' . $_REQUEST['suggest'] : '');
-
- $url = 'directory?f=';
-
- $tmp = array_merge($_GET,$_POST);
- unset($tmp['suggest']);
- unset($tmp['pubforums']);
- unset($tmp['global']);
- unset($tmp['safe']);
- unset($tmp['q']);
- unset($tmp['f']);
- $forumsurl = $url . http_build_query($tmp) . $suggest;
-
- $o = replace_macros(get_markup_template('dir_sort_links.tpl'), array(
- '$header' => t('Directory Options'),
- '$forumsurl' => $forumsurl,
- '$safemode' => array('safemode', t('Safe Mode'),$safe_mode,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&safe="+(this.checked ? 1 : 0)\''),
- '$pubforums' => array('pubforums', t('Public Forums Only'),$pubforums,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&pubforums="+(this.checked ? 1 : 0)\''),
- '$globaldir' => array('globaldir', t('This Website Only'), 1-intval($globaldir),'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&global="+(this.checked ? 0 : 1)\''),
- ));
-
- return $o;
-}
-
-/**
- * @brief Checks the directory mode of this hub.
- *
- * Checks the directory mode of this hub to see if it is some form of directory server. If it is,
- * get the directory realm of this hub. Fetch a list of all other directory servers in this realm and request
- * a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB.
- * In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored
- * directly if the rater's signature matches.
- *
- * @param int $dirmode;
- */
-function sync_directories($dirmode) {
-
- if ($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL)
- return;
-
- $realm = get_directory_realm();
- if ($realm == DIRECTORY_REALM) {
- $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') ",
- intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
- dbesc(z_root()),
- intval(SITE_TYPE_ZOT),
- dbesc($realm)
- );
- } else {
- $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d ",
- intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
- dbesc(z_root()),
- dbesc(protect_sprintf('%' . $realm . '%')),
- intval(SITE_TYPE_ZOT)
- );
- }
-
- // If there are no directory servers, setup the fallback master
- /** @FIXME What to do if we're in a different realm? */
-
- if ((! $r) && (z_root() != DIRECTORY_FALLBACK_MASTER)) {
-
- $x = site_store_lowlevel(
- [
- 'site_url' => DIRECTORY_FALLBACK_MASTER,
- 'site_flags' => DIRECTORY_MODE_PRIMARY,
- 'site_update' => NULL_DATE,
- 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch',
- 'site_realm' => DIRECTORY_REALM,
- 'site_valid' => 1,
- 'site_crypto' => 'aes256cbc'
- ]
- );
-
- $r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d ",
- intval(DIRECTORY_MODE_PRIMARY),
- intval(DIRECTORY_MODE_SECONDARY),
- dbesc(z_root()),
- intval(SITE_TYPE_ZOT)
- );
- }
- if (! $r)
- return;
-
- foreach ($r as $rr) {
- if (! $rr['site_directory'])
- continue;
-
- logger('sync directories: ' . $rr['site_directory']);
-
- // for brand new directory servers, only load the last couple of days.
- // It will take about a month for a new directory to obtain the full current repertoire of channels.
- /** @FIXME Go back and pick up earlier ratings if this is a new directory server. These do not get refreshed. */
-
- $token = get_config('system','realm_token');
-
- $syncdate = (($rr['site_sync'] <= NULL_DATE) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']);
- $x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($syncdate) . (($token) ? '&t=' . $token : ''));
-
- if (! $x['success'])
- continue;
-
- $j = json_decode($x['body'],true);
- if (!($j['transactions']) || ($j['ratings']))
- continue;
-
- q("update site set site_sync = '%s' where site_url = '%s'",
- dbesc(datetime_convert()),
- dbesc($rr['site_url'])
- );
-
- logger('sync_directories: ' . $rr['site_url'] . ': ' . print_r($j,true), LOGGER_DATA);
-
- if (is_array($j['transactions']) && count($j['transactions'])) {
- foreach ($j['transactions'] as $t) {
- $r = q("select * from updates where ud_guid = '%s' limit 1",
- dbesc($t['transaction_id'])
- );
- if($r)
- continue;
-
- $ud_flags = 0;
- if (is_array($t['flags']) && in_array('deleted',$t['flags']))
- $ud_flags |= UPDATE_FLAGS_DELETED;
- if (is_array($t['flags']) && in_array('forced',$t['flags']))
- $ud_flags |= UPDATE_FLAGS_FORCED;
-
- $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr )
- values ( '%s', '%s', '%s', %d, '%s' ) ",
- dbesc($t['hash']),
- dbesc($t['transaction_id']),
- dbesc($t['timestamp']),
- intval($ud_flags),
- dbesc($t['address'])
- );
- }
- }
- if (is_array($j['ratings']) && count($j['ratings'])) {
- foreach ($j['ratings'] as $rr) {
- $x = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1",
- dbesc($rr['channel']),
- dbesc($rr['target'])
- );
- if ($x && $x[0]['xlink_updated'] >= $rr['edited'])
- continue;
-
- // Ratings are signed by the rater. We need to verify before we can accept it.
- /** @TODO Queue or defer if the xchan is not yet present on our site */
-
- $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1",
- dbesc($rr['channel'])
- );
- if (! $y) {
- logger('key unavailable on this site for ' . $rr['channel']);
- continue;
- }
- if (! Crypto::verify($rr['target'] . '.' . $rr['rating'] . '.' . $rr['rating_text'], base64url_decode($rr['signature']),$y[0]['xchan_pubkey'])) {
- logger('failed to verify rating');
- continue;
- }
-
- if ($x) {
- $z = q("update xlink set xlink_rating = %d, xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d",
- intval($rr['rating']),
- dbesc($rr['rating_text']),
- dbesc($rr['signature']),
- dbesc(datetime_convert()),
- intval($x[0]['xlink_id'])
- );
- logger('rating updated');
- } else {
- $z = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ",
- dbesc($rr['channel']),
- dbesc($rr['target']),
- intval($rr['rating']),
- dbesc($rr['rating_text']),
- dbesc($rr['signature']),
- dbesc(datetime_convert())
- );
- logger('rating created');
- }
- }
- }
- }
-}
-
-
-/**
- * @brief
- *
- * Given an update record, probe the channel, grab a zot-info packet and refresh/sync the data.
- *
- * Ignore updating records marked as deleted.
- *
- * If successful, sets ud_last in the DB to the current datetime for this
- * reddress/webbie.
- *
- * @param array $ud Entry from update table
- */
-function update_directory_entry($ud) {
-
- logger('update_directory_entry: ' . print_r($ud,true), LOGGER_DATA);
-
- if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) {
- $success = false;
- $uri = Webfinger::zot_url($ud['ud_addr']);
-
- if($uri) {
- $record = Zotfinger::exec($uri);
-
- // Check the HTTP signature
- $hsig = $record['signature'];
- if($hsig && $hsig['signer'] === $uri && $hsig['header_valid'] === true && $hsig['content_valid'] === true) {
- $x = Libzot::import_xchan($record['data'], 0, $ud);
- if($x['success']) {
- $success = true;
- }
- }
- }
- }
-}
-
-
-/**
- * @brief Push local channel updates to a local directory server.
- *
- * This is called from include/directory.php if a profile is to be pushed to the
- * directory and the local hub in this case is any kind of directory server.
- *
- * @param int $uid
- * @param boolean $force
- */
-function local_dir_update($uid, $force) {
-
- logger('local_dir_update: uid: ' . $uid, LOGGER_DEBUG);
-
- $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1",
- intval($uid)
- );
-
- $profile = array();
- $profile['encoding'] = 'zot';
-
- if ($p) {
- $hash = $p[0]['channel_hash'];
-
- $profile['description'] = $p[0]['pdesc'];
- $profile['birthday'] = $p[0]['dob'];
- if ($age = age($p[0]['dob'],$p[0]['channel_timezone'],''))
- $profile['age'] = $age;
-
- $profile['gender'] = $p[0]['gender'];
- $profile['marital'] = $p[0]['marital'];
- $profile['sexual'] = $p[0]['sexual'];
- $profile['locale'] = $p[0]['locality'];
- $profile['region'] = $p[0]['region'];
- $profile['postcode'] = $p[0]['postal_code'];
- $profile['country'] = $p[0]['country_name'];
- $profile['about'] = $p[0]['about'];
- $profile['homepage'] = $p[0]['homepage'];
- $profile['hometown'] = $p[0]['hometown'];
-
- if ($p[0]['keywords']) {
- $tags = array();
- $k = explode(' ', $p[0]['keywords']);
- if ($k)
- foreach ($k as $kk)
- if (trim($kk))
- $tags[] = trim($kk);
-
- if ($tags)
- $profile['keywords'] = $tags;
- }
-
- $hidden = (1 - intval($p[0]['publish']));
-
- logger('hidden: ' . $hidden);
-
- $r = q("select xchan_hidden from xchan where xchan_hash = '%s' limit 1",
- dbesc($p[0]['channel_hash'])
- );
-
- if(intval($r[0]['xchan_hidden']) != $hidden) {
- $r = q("update xchan set xchan_hidden = %d where xchan_hash = '%s'",
- intval($hidden),
- dbesc($p[0]['channel_hash'])
- );
- }
-
- $arr = array('channel_id' => $uid, 'hash' => $hash, 'profile' => $profile);
- call_hooks('local_dir_update', $arr);
-
- $address = channel_reddress($p[0]);
-
- if (perm_is_allowed($uid, '', 'view_profile')) {
- import_directory_profile($hash, $arr['profile'], $address, 0);
- } else {
- // they may have made it private
- $r = q("delete from xprof where xprof_hash = '%s'",
- dbesc($hash)
- );
- $r = q("delete from xtag where xtag_hash = '%s'",
- dbesc($hash)
- );
- }
- }
-
- $ud_hash = random_string() . '@' . App::get_hostname();
- update_modtime($hash, $ud_hash, channel_reddress($p[0]),(($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED));
-}
diff --git a/include/html2plain.php b/include/html2plain.php
index bf8581bdb..48bbe3d9e 100644
--- a/include/html2plain.php
+++ b/include/html2plain.php
@@ -78,10 +78,10 @@ function quotelevel($message, $wraplength = 75)
function collecturls($message) {
-
+
$pattern = '/<a.*?href="(.*?)".*?>(.*?)<\/a>/is';
preg_match_all($pattern, $message, $result, PREG_SET_ORDER);
-
+
$urls = [];
if ($result) {
$ignore = false;
@@ -104,15 +104,15 @@ function collecturls($message) {
foreach ($list as $listitem)
if (strpos($treffer[1], $listitem) !== false)
$ignore = true;
-
+
if ((strpos($treffer[1], "//plus.google.com/") !== false) and (strpos($treffer[1], "/posts") !== false))
$ignore = false;
-
+
if (! $ignore)
$urls[$treffer[1]] = $treffer[1];
}
}
-
+
return($urls);
}
diff --git a/include/items.php b/include/items.php
index 15de6c730..f451358f8 100644
--- a/include/items.php
+++ b/include/items.php
@@ -2494,7 +2494,7 @@ function send_status_notifications($post_id,$item) {
Enotify::submit(array(
- 'type' => NOTIFY_COMMENT,
+ 'type' => ((intval($item['item_private']) === 2) ? NOTIFY_MAIL : NOTIFY_COMMENT),
'from_xchan' => $item['author_xchan'],
'to_xchan' => $r[0]['channel_hash'],
'item' => $item,
@@ -3177,7 +3177,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
"' portable_id='" . $item['author']['xchan_hash'] .
"' avatar='" . $item['author']['xchan_photo_s'] .
"' link='" . $item['plink'] .
- "' auth='" . ((in_array($item['author']['xchan_network'], ['zot6','zot'])) ? 'true' : 'false') .
+ "' auth='" . (($item['author']['xchan_network'] === 'zot6') ? 'true' : 'false') .
"' posted='" . $item['created'] .
"' message_id='" . $item['mid'] .
"']";
diff --git a/include/photos.php b/include/photos.php
index 11dd07586..967acf955 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -4,6 +4,9 @@
* @brief Functions related to photo handling.
*/
+use Zotlabs\Lib\Activity;
+
+
require_once('include/permissions.php');
require_once('include/items.php');
require_once('include/photo/photo_driver.php');
@@ -256,10 +259,10 @@ function photo_upload($channel, $observer, $args) {
if($args['description'])
$p['description'] = $args['description'];
- $link = array();
+ $url = [];
$r0 = $ph->save($p);
- $link[0] = array(
+ $url[0] = array(
'rel' => 'alternate',
'type' => $type,
'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(),
@@ -278,7 +281,7 @@ function photo_upload($channel, $observer, $args) {
$ph->scaleImage(1024);
$r1 = $ph->storeThumbnail($p, PHOTO_RES_1024);
- $link[1] = array(
+ $url[1] = array(
'rel' => 'alternate',
'type' => $type,
'href' => z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt(),
@@ -292,7 +295,7 @@ function photo_upload($channel, $observer, $args) {
$ph->scaleImage(640);
$r2 = $ph->storeThumbnail($p, PHOTO_RES_640);
- $link[2] = array(
+ $url[2] = array(
'rel' => 'alternate',
'type' => $type,
'href' => z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt(),
@@ -306,7 +309,7 @@ function photo_upload($channel, $observer, $args) {
$ph->scaleImage(320);
$r3 = $ph->storeThumbnail($p, PHOTO_RES_320);
- $link[3] = array(
+ $url[3] = array(
'rel' => 'alternate',
'type' => $type,
'href' => z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt(),
@@ -353,18 +356,18 @@ function photo_upload($channel, $observer, $args) {
$large_photos = feature_enabled($channel['channel_id'], 'large_photos');
- linkify_tags($args['body'], $channel_id);
+ $found_tags = linkify_tags($args['body'], $channel_id);
if($large_photos) {
$scale = 1;
- $width = $link[1]['width'];
- $height = $link[1]['height'];
+ $width = $url[1]['width'];
+ $height = $url[1]['height'];
$tag = (($r1) ? '[zmg=' . $width . 'x' . $height . ']' : '[zmg]');
}
else {
$scale = 2;
- $width = $link[2]['width'];
- $height = $link[2]['height'];
+ $width = $url[2]['width'];
+ $height = $url[2]['height'];
$tag = (($r2) ? '[zmg=' . $width . 'x' . $height . ']' : '[zmg]');
}
@@ -382,22 +385,61 @@ function photo_upload($channel, $observer, $args) {
. $tag . z_root() . "/photo/{$photo_hash}-{$scale}." . $ph->getExt() . '[/zmg]'
. '[/zrl]';
- // Create item object
- $object = array(
- 'type' => ACTIVITY_OBJ_PHOTO,
- 'title' => $title,
- 'created' => $p['created'],
- 'edited' => $p['edited'],
- 'id' => z_root() . '/item/' . $photo_hash,
- 'link' => $link,
- 'body' => $summary
- );
+ $url[] = [
+ 'type' => 'Link',
+ 'mediaType' => 'text/html',
+ 'href' => z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash
+ ];
+
+ $post_tags = [];
+
+ if($found_tags) {
+ foreach($found_tags as $result) {
+ $success = $result['success'];
+ if($success['replaced']) {
+ $post_tags[] = array(
+ 'uid' => $channel['channel_id'],
+ 'ttype' => $success['termtype'],
+ 'otype' => TERM_OBJ_POST,
+ 'term' => $success['term'],
+ 'url' => $success['url']
+ );
+ }
+ }
+ }
- $target = array(
- 'type' => ACTIVITY_OBJ_ALBUM,
- 'title' => (($album) ? $album : '/'),
- 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album)
- );
+ //// Create item object
+ $object = [
+ 'type' => 'Image',
+ 'name' => $title,
+ 'published' => datetime_convert('UTC','UTC',$p['created'],ATOM_TIME),
+ 'updated' => datetime_convert('UTC','UTC',$p['edited'],ATOM_TIME),
+ // This is a placeholder and will get over-ridden by the item mid, which is critical for sharing as a conversational item over activitypub
+ 'id' => z_root() . '/photo/' . $photo_hash,
+ 'url' => $url,
+ 'source' => [ 'content' => $summary, 'mediaType' => 'text/bbcode' ],
+ 'content' => bbcode($summary)
+ ];
+
+ if ($post_tags) {
+ $object['tag'] = Activity::encode_taxonomy(['term' => $post_tags]);
+ }
+
+ $public = (($ac['allow_cid'] || $ac['allow_gid'] || $ac['deny_cid'] || $ac['deny_gid']) ? false : true);
+
+ if ($public) {
+ $object['to'] = [ ACTIVITY_PUBLIC_INBOX ];
+ $object['cc'] = [ z_root() . '/followers/' . $channel['channel_address'] ];
+ }
+ else {
+ $object['to'] = Activity::map_acl(array_merge($ac, ['item_private' => 1 - intval($public) ]));
+ }
+
+ $target = [
+ 'type' => 'orderedCollection',
+ 'name' => ((strlen($album)) ? $album : '/'),
+ 'id' => z_root() . '/album/' . $channel['channel_address'] . ((isset($args['folder'])) ? '/' . $args['folder'] : EMPTY_STR)
+ ];
// Create item container
if($args['item']) {
@@ -415,7 +457,9 @@ function photo_upload($channel, $observer, $args) {
$item['tgt_type'] = ACTIVITY_OBJ_ALBUM;
$item['target'] = json_encode($target);
-
+ if ($post_tags) {
+ $arr['term'] = $post_tags;
+ }
$force = true;
}
$r = q("select id, edited from item where mid = '%s' and uid = %d limit 1",
@@ -469,6 +513,10 @@ function photo_upload($channel, $observer, $args) {
'body' => $summary
];
+ if ($post_tags) {
+ $arr['term'] = $post_tags;
+ }
+
$arr['plink'] = $mid;
if($lat && $lon)
diff --git a/include/queue_fn.php b/include/queue_fn.php
deleted file mode 100644
index 1e8171b1d..000000000
--- a/include/queue_fn.php
+++ /dev/null
@@ -1,314 +0,0 @@
-<?php /** @file */
-
-use Zotlabs\Lib\Libzot;
-use Zotlabs\Zot6\Receiver;
-use Zotlabs\Zot6\Zot6Handler;
-
-function update_queue_item($id, $add_priority = 0) {
- logger('queue: requeue item ' . $id,LOGGER_DEBUG);
- $x = q("select outq_created, outq_posturl from outq where outq_hash = '%s' limit 1",
- dbesc($id)
- );
- if(! $x)
- return;
-
-
- $y = q("select outq_created as earliest from outq where outq_posturl = '%s' order by earliest limit 1",
- dbesc($x[0]['outq_posturl'])
- );
-
- // look for the oldest queue entry with this destination URL. If it's older than a couple of days,
- // the destination is considered to be down and only scheduled once an hour, regardless of the
- // age of the current queue item.
-
- $might_be_down = false;
-
- if($y)
- $might_be_down = ((datetime_convert('UTC','UTC',$y[0]['earliest']) < datetime_convert('UTC','UTC','now - 2 days')) ? true : false);
-
-
- // Set all other records for this destination way into the future.
- // The queue delivers by destination. We'll keep one queue item for
- // this destination (this one) with a shorter delivery. If we succeed
- // once, we'll try to deliver everything for that destination.
- // The delivery will be set to at most once per hour, and if the
- // queue item is less than 12 hours old, we'll schedule for fifteen
- // minutes.
-
- $r = q("UPDATE outq SET outq_scheduled = '%s' WHERE outq_posturl = '%s'",
- dbesc(datetime_convert('UTC','UTC','now + 5 days')),
- dbesc($x[0]['outq_posturl'])
- );
-
- $since = datetime_convert('UTC','UTC',$x[0]['outq_created']);
-
- if(($might_be_down) || ($since < datetime_convert('UTC','UTC','now - 12 hour'))) {
- $next = datetime_convert('UTC','UTC','now + 1 hour');
- }
- else {
- $next = datetime_convert('UTC','UTC','now + ' . intval($add_priority) . ' minutes');
- }
-
- q("UPDATE outq SET outq_updated = '%s',
- outq_priority = outq_priority + %d,
- outq_scheduled = '%s'
- WHERE outq_hash = '%s'",
-
- dbesc(datetime_convert()),
- intval($add_priority),
- dbesc($next),
- dbesc($id)
- );
-}
-
-function remove_queue_item($id,$channel_id = 0) {
- logger('queue: remove queue item ' . $id,LOGGER_DEBUG);
- $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : '');
-
- q("DELETE FROM outq WHERE outq_hash = '%s' $sql_extra",
- dbesc($id)
- );
-}
-
-
-function remove_queue_by_posturl($posturl) {
- logger('queue: remove queue posturl ' . $posturl,LOGGER_DEBUG);
-
- q("DELETE FROM outq WHERE outq_posturl = '%s' ",
- dbesc($posturl)
- );
-}
-
-
-
-function queue_set_delivered($id,$channel = 0) {
- logger('queue: set delivered ' . $id,LOGGER_DEBUG);
- $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : '');
-
- // Set the next scheduled run date so far in the future that it will be expired
- // long before it ever makes it back into the delivery chain.
-
- q("update outq set outq_delivered = 1, outq_updated = '%s', outq_scheduled = '%s' where outq_hash = '%s' $sql_extra ",
- dbesc(datetime_convert()),
- dbesc(datetime_convert('UTC','UTC','now + 5 days')),
- dbesc($id)
- );
-}
-
-
-
-function queue_insert($arr) {
-
- // do not queue anything with no destination
-
- if(! (array_key_exists('posturl',$arr) && trim($arr['posturl']))) {
- return false;
- }
-
- $x = q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_priority,
- outq_created, outq_updated, outq_scheduled, outq_notify, outq_msg )
- values ( '%s', %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s' )",
- dbesc($arr['hash']),
- intval($arr['account_id']),
- intval($arr['channel_id']),
- dbesc(($arr['driver']) ? $arr['driver'] : 'zot'),
- dbesc($arr['posturl']),
- intval(1),
- intval(($arr['priority']) ? $arr['priority'] : 0),
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
- dbesc($arr['notify']),
- dbesc(($arr['msg']) ? $arr['msg'] : '')
- );
- return $x;
-
-}
-
-
-
-function queue_deliver($outq, $immediate = false) {
-
- $base = null;
- $h = parse_url($outq['outq_posturl']);
- if($h !== false)
- $base = $h['scheme'] . '://' . $h['host'] . (isset($h['port']) ? ':' . $h['port'] : '');
-
- if(($base) && ($base !== z_root()) && ($immediate)) {
- $y = q("select site_update, site_dead from site where site_url = '%s' ",
- dbesc($base)
- );
- if($y) {
- if(intval($y[0]['site_dead'])) {
- remove_queue_by_posturl($outq['outq_posturl']);
- logger('dead site ignored ' . $base);
- return;
- }
- if($y[0]['site_update'] < datetime_convert('UTC','UTC','now - 1 month')) {
- update_queue_item($outq['outq_hash'],10);
- logger('immediate delivery deferred for site ' . $base);
- return;
- }
- }
- else {
-
- // zot sites should all have a site record, unless they've been dead for as long as
- // your site has existed. Since we don't know for sure what these sites are,
- // call them unknown
-
- site_store_lowlevel(
- [
- 'site_url' => $base,
- 'site_update' => datetime_convert(),
- 'site_dead' => 0,
- 'site_type' => intval(($outq['outq_driver'] === 'post') ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN),
- 'site_crypto' => ''
- ]
- );
- }
- }
-
-
-
-
-
-
- $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate);
- call_hooks('queue_deliver',$arr);
- if($arr['handled'])
- return;
-
- // "post" queue driver - used for diaspora and friendica-over-diaspora communications.
-
- if($outq['outq_driver'] === 'post') {
- $result = z_post_url($outq['outq_posturl'],$outq['outq_msg']);
- if($result['success'] && $result['return_code'] < 300) {
- logger('deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG);
- if($base) {
- q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ",
- dbesc(datetime_convert()),
- dbesc($base)
- );
- }
- q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'",
- dbesc('accepted for delivery'),
- dbesc(datetime_convert()),
- dbesc($outq['outq_hash'])
- );
- remove_queue_item($outq['outq_hash']);
-
- // server is responding - see if anything else is going to this destination and is piled up
- // and try to send some more. We're relying on the fact that do_delivery() results in an
- // immediate delivery otherwise we could get into a queue loop.
-
- if(! $immediate) {
- $x = q("select outq_hash from outq where outq_posturl = '%s' and outq_delivered = 0",
- dbesc($outq['outq_posturl'])
- );
-
- $piled_up = array();
- if($x) {
- foreach($x as $xx) {
- $piled_up[] = $xx['outq_hash'];
- }
- }
- if($piled_up) {
- // call do_delivery() with the force flag
- do_delivery($piled_up, true);
- }
- }
- }
- else {
- logger('deliver: queue post returned ' . $result['return_code']
- . ' from ' . $outq['outq_posturl'],LOGGER_DEBUG);
- update_queue_item($outq['outq_hash'],10);
- }
- return;
- }
-
- // normal zot delivery
-
- logger('deliver: dest: ' . $outq['outq_posturl'] . ' driver: ' . $outq['outq_driver'], LOGGER_DEBUG);
-
- if($outq['outq_driver'] === 'zot6') {
-
- if($outq['outq_posturl'] === z_root() . '/zot') {
- // local delivery
- $zot = new Receiver(new Zot6Handler(),$outq['outq_notify']);
- $result = $zot->run(true);
- logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA);
- logger('deliver: local zot6 delivery succeeded to ' . $outq['outq_posturl']);
- Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq);
- }
- else {
- logger('remote');
- $channel = null;
-
- if($outq['outq_channel']) {
- $channel = channelx_by_n($outq['outq_channel']);
- }
-
- $host_crypto = null;
- if($channel && $base) {
- $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc limit 1",
- dbesc($base)
- );
- if($h) {
- $host_crypto = $h[0];
- }
- }
-
- $msg = $outq['outq_notify'];
-
- $result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto);
-
- if($result['success']) {
- logger('deliver: remote zot6 delivery succeeded to ' . $outq['outq_posturl']);
- Libzot::process_response($outq['outq_posturl'],$result, $outq);
- }
- else {
- logger('deliver: remote zot6 delivery failed to ' . $outq['outq_posturl']);
- logger('deliver: remote zot6 delivery fail data: ' . print_r($result,true), LOGGER_DATA);
- update_queue_item($outq['outq_hash'],10);
- }
-
- }
- return;
- }
- else {
-
- $channel = null;
-
- if($outq['outq_msg'] && $outq['outq_channel']) {
- $channel = channelx_by_n($outq['outq_channel']);
- }
-
- $host_crypto = null;
-
- if($channel && $base) {
- $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' and hubloc_sitekey != '' order by hubloc_id desc limit 1",
- dbesc($base)
- );
- if($h) {
- $host_crypto = $h[0];
- }
- }
-
- $msg = $outq['outq_notify'];
-
- $result = zot_zot($outq['outq_posturl'],$msg,$channel,$host_crypto);
-
-
- if($result['success']) {
- logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']);
- zot_process_response($outq['outq_posturl'],$result, $outq);
- }
- else {
- logger('deliver: remote zot delivery failed to ' . $outq['outq_posturl']);
- logger('deliver: remote zot delivery fail data: ' . print_r($result,true), LOGGER_DATA);
- update_queue_item($outq['outq_hash'],10);
- }
- return;
- }
-
-}
diff --git a/include/text.php b/include/text.php
index 8dc5ee188..841abdbd3 100644
--- a/include/text.php
+++ b/include/text.php
@@ -11,6 +11,7 @@ use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Zotlabs\Lib\Crypto;
use Zotlabs\Lib\SvgSanitizer;
+use Zotlabs\Lib\Libzot;
require_once("include/bbcode.php");
@@ -1714,19 +1715,33 @@ function prepare_body(&$item,$attach = false,$opts = false) {
$photo = '';
$is_photo = ((($item['verb'] === ACTIVITY_POST) && ($item['obj_type'] === ACTIVITY_OBJ_PHOTO)) ? true : false);
- if($is_photo) {
-
+ if ($is_photo) {
$object = json_decode($item['obj'],true);
+ $ptr = null;
+ if (array_key_exists('url',$object) && is_array($object['url'])) {
+ if (array_key_exists(0,$object['url'])) {
+ foreach ($object['url'] as $link) {
+ if(array_key_exists('width',$link) && $link['width'] >= 640 && $link['width'] <= 1024) {
+ $ptr = $link;
+ }
+ }
+ if (! $ptr) {
+ $ptr = $object['url'][0];
+ }
+ }
+ else {
+ $ptr = $object['url'];
+ }
- // if original photo width is <= 640px prepend it to item body
- if($object['link'][0]['width'] && $object['link'][0]['width'] <= 640) {
- $s .= '<div class="inline-photo-item-wrapper"><a href="' . zid(rawurldecode($object['id'])) . '" target="_blank" rel="nofollow noopener" ><img class="inline-photo-item" style="max-width:' . $object['link'][0]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][0]['href'])) . '"></a></div>' . $s;
- }
-
- // if original photo width is > 640px make it a cover photo
- if($object['link'][0]['width'] && $object['link'][0]['width'] > 640) {
- $scale = ((($object['link'][1]['width'] == 1024) || ($object['link'][1]['height'] == 1024)) ? 1 : 0);
- $photo = '<a href="' . zid(rawurldecode($object['id'])) . '" target="_blank" rel="nofollow noopener"><img style="max-width:' . $object['link'][$scale]['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($object['link'][$scale]['href'])) . '"></a>';
+ // if original photo width is > 640px make it a cover photo
+ if ($ptr) {
+ if (array_key_exists('width',$ptr) && $ptr['width'] > 640) {
+ $photo = '<a href="' . zid(rawurldecode($object['id'])) . '" target="_blank" rel="nofollow noopener"><img style="max-width:' . $ptr['width'] . 'px; width:100%; height:auto;" src="' . zid(rawurldecode($ptr['href'])) . '"></a>';
+ }
+ else {
+ $item['body'] = '[zmg]' . $ptr['href'] . '[/zmg]' . "\n\n" . $item['body'];
+ }
+ }
}
}
@@ -2837,7 +2852,7 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
// BEGIN mentions
- if ( in_array($termtype, [ TERM_MENTION, TERM_FORUM ] )) {
+ if ($termtype === TERM_MENTION) {
// The @! tag will alter permissions
@@ -2864,14 +2879,13 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
$newname = substr($name,1);
$newname = substr($newname,0,-1);
- $r = q("SELECT * FROM xchan WHERE xchan_addr = '%s' OR xchan_url = '%s' AND xchan_deleted = 0",
+ $r = q("SELECT * FROM xchan WHERE ( xchan_addr = '%s' OR xchan_url = '%s' ) AND xchan_deleted = 0",
dbesc($newname),
dbesc($newname)
);
}
if(! $r) {
-
// look for matching names in the address book
// Double quote the entire mentioned term to include special characters
@@ -2926,7 +2940,10 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
// $r is set if we found something
if($r) {
- foreach($r as $xc) {
+
+ $xchan[0] = Libzot::zot_record_preferred($r, 'xchan_network');
+
+ foreach($xchan as $xc) {
$profile = $xc['xchan_url'];
$newname = $xc['xchan_name'];
// add the channel's xchan_hash to $access_tag if exclusive
@@ -2941,16 +2958,9 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
//create profile link
$profile = str_replace(',','%2c',$profile);
$url = $profile;
-/*
- if($termtype === TERM_FORUM) {
- $newtag = '!' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . '[/zrl]';
- $body = str_replace('!' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
- }
-*/
- if ($termtype === TERM_MENTION) {
- $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . '[/zrl]';
- $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
- }
+
+ $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . '[/zrl]';
+ $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
// append tag to str_tags
if(! stristr($str_tags,$newtag)) {
@@ -3880,3 +3890,30 @@ function sanitize_text_field($str) {
return preg_replace('/\s+/S', ' ', $str);
}
+/**
+ * @brief shortens a string to $max_length without cutting off words
+ * @param string $str
+ * @param intval $max_length
+ * @param string $suffix (optional)
+
+ * @return string
+ */
+function substr_words($str, $max_length, $suffix = '...') {
+
+ if (strlen($str) > $max_length) {
+ $words = preg_split('/\s/', $str);
+ $ret = '';
+ $i = 0;
+ while (true) {
+ $length = (strlen($ret) + strlen($words[$i]));
+ if ($length > $max_length) {
+ break;
+ }
+ $ret .= " " . $words[$i];
+ ++$i;
+ }
+ $ret .= $suffix;
+ }
+
+ return (($ret) ? $ret : $str);
+}