diff --git a/INSTALL.txt b/INSTALL.txt
index 70e4f24e..12dca9c5 100644
--- a/INSTALL.txt
+++ b/INSTALL.txt
@@ -29,7 +29,7 @@ php.ini file
- Mysql 5.x
- ability to schedule jobs with cron (Linux/Mac) or Scheduled Tasks
-(Windows)
+(Windows) [Note: other options are presented in Section 7 of this document]
- Installation into a top-level domain or sub-domain (without a
directory/path component in the URL) is preferred. Directory paths will
@@ -89,3 +89,18 @@ You can generally find the location of PHP by executing "which php". If you
have troubles with this section please contact your hosting provider for
assistance. Friendika will not work correctly if you cannot perform this step.
+Alternative: You may be able to use the 'poormancron' plugin to perform this
+step if you are using a recent Friendika release. To do this, edit the file
+".htconfig.php" and look for a line describing your plugins. On a fresh
+installation, it will look like
+
+$a->config['system']['addon'] = 'js_upload';
+
+This indicates the "js_upload" addon module is enabled. You may add additional
+addons/plugins using this same line in the configuration file. Change it to
+read
+
+$a->config['system']['addon'] = 'js_upload,poormancron';
+
+and save your changes.
+
diff --git a/addon/oembed/oembed.php b/addon/oembed/oembed.php
index 82183f3c..4bbd7538 100644
--- a/addon/oembed/oembed.php
+++ b/addon/oembed/oembed.php
@@ -10,13 +10,11 @@
function oembed_install() {
register_hook('jot_tool', 'addon/oembed/oembed.php', 'oembed_hook_jot_tool');
register_hook('page_header', 'addon/oembed/oembed.php', 'oembed_hook_page_header');
- register_hook('bbcode', 'addon/oembed/oembed.php', 'oembed_hook_bbcode');
}
function oembed_uninstall() {
unregister_hook('jot_tool', 'addon/oembed/oembed.php', 'oembed_hook_jot_tool');
unregister_hook('page_header', 'addon/oembed/oembed.php', 'oembed_hook_page_header');
- unregister_hook('bbcode', 'addon/oembed/oembed.php', 'oembed_hook_bbcode');
}
function oembed_hook_page_header($a, &$b){
@@ -53,49 +51,7 @@ function oembed_hook_jot_tool($a, &$b) {
';
}
-function oembed_replacecb($matches){
- $embedurl=$matches[1];
- $ourl = "http://oohembed.com/oohembed/?url=".urlencode($embedurl);
- $txt = fetch_url($ourl);
- $j = json_decode($txt);
- $ret="";
- switch ($j->type) {
- case "video": {
- if (isset($j->thumbnail_url)) {
- $tw = (isset($j->thumbnail_width)) ? $j->thumbnail_width:200;
- $th = (isset($j->thumbnail_height)) ? $j->thumbnail_height:180;
- $ret = "";
- $ret.= " ";
- $ret.= " ";
- } else {
- $ret=$j->html;
- }
- $ret.=" ";
- }; break;
- case "photo": {
- $ret = " ";
- $ret.=" ";
- }; break;
- case "link": {
- //$ret = "".$j->title." ";
- }; break;
- case "rich": {
- // not so safe..
- $ret = "
".$j->html." ";
- }; break;
- }
-
- $embedlink = (isset($j->title))?$j->title:$embedurl;
- $ret .= "$embedlink ";
- if (isset($j->author_name)) $ret.=" by ".$j->author_name;
- if (isset($j->provider_name)) $ret.=" on ".$j->provider_name;
- $ret.="";
- return $ret;
-}
-function oembed_hook_bbcode($a, &$text){
- $text = preg_replace_callback("/\[embed\](.+?)\[\/embed\]/is", oembed_replacecb ,$text);
-}
?>
\ No newline at end of file
diff --git a/boot.php b/boot.php
index 223c9416..ac010c19 100644
--- a/boot.php
+++ b/boot.php
@@ -2,7 +2,7 @@
set_time_limit(0);
-define ( 'BUILD_ID', 1033 );
+define ( 'BUILD_ID', 1034 );
define ( 'FRIENDIKA_VERSION', '2.10.0902' );
define ( 'DFRN_PROTOCOL_VERSION', '2.0' );
@@ -10,6 +10,16 @@ define ( 'EOL', " \r\n" );
define ( 'ATOM_TIME', 'Y-m-d\TH:i:s\Z' );
define ( 'DOWN_ARROW', '⇩' );
+
+/**
+ * SSL redirection policies
+ */
+
+define ( 'SSL_POLICY_NONE', 0 );
+define ( 'SSL_POLICY_FULL', 1 );
+define ( 'SSL_POLICY_SELFSIGN', 2 );
+
+
/**
* log levels
*/
@@ -270,10 +280,17 @@ class App {
}
function get_baseurl($ssl = false) {
- if(strlen($this->baseurl))
- return $this->baseurl;
- $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
+ $scheme = $this->scheme;
+
+ if(x($this->config,'ssl_policy')) {
+ if(($ssl) || ($this->config['ssl_policy'] == SSL_POLICY_FULL))
+ $scheme = 'https';
+ if(($this->config['ssl_policy'] == SSL_POLICY_SELFSIGN) && (local_user() || x($_POST,'auth-params')))
+ $scheme = 'https';
+ }
+
+ $this->baseurl = $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
return $this->baseurl;
}
diff --git a/database.sql b/database.sql
index 54950f0f..817fd092 100644
--- a/database.sql
+++ b/database.sql
@@ -471,4 +471,8 @@ CREATE TABLE IF NOT EXISTS `event` (
`deny_gid` MEDIUMTEXT NOT NULL
) ENGINE = MYISAM DEFAULT CHARSET=utf8;
-
+CREATE TABLE IF NOT EXISTS 'cache' (
+ `k` CHAR( 255 ) NOT NULL PRIMARY KEY ,
+ `v` TEXT NOT NULL,
+ `updated` DATETIME NOT NULL
+) ENGINE = MYISAM DEFAULT CHARSET=utf8;
diff --git a/htconfig.php b/htconfig.php
index 13c065e6..5f5c76cb 100644
--- a/htconfig.php
+++ b/htconfig.php
@@ -72,4 +72,7 @@ $a->config['system']['rino_encrypt'] = true;
$a->config['system']['addon'] = 'js_upload';
-
\ No newline at end of file
+// Disable oembed embedding
+// This disable the conversion of [embed]$url[/embed] tag in html
+// $a->config['system']['no_oembed'] = true;
+
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index d0952421..269dc3e3 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -17,8 +17,9 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) {
$selected = " selected=\"selected\" ";
else
$selected = '';
+ $trimmed = substr($rr['name'],0,12);
- $o .= "{$rr['name']} \r\n";
+ $o .= "$trimmed \r\n";
}
}
@@ -30,7 +31,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) {
-function contact_select($selname, $selclass, $preselected = false, $size = 4, $privmail = false, $celeb = false) {
+function contact_select($selname, $selclass, $preselected = false, $size = 4, $privmail = false, $celeb = false, $privatenet = false) {
$o = '';
@@ -43,6 +44,10 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p
$sql_extra .= sprintf(" AND `rel` = %d ", intval(REL_BUD));
}
+ if($privmail || $privatenet) {
+ $sql_extra .= " AND `network` IN ( 'dfrn' ) ";
+ }
+
if($privmail)
$o .= "\r\n";
else
@@ -61,11 +66,10 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p
$selected = " selected=\"selected\" ";
else
$selected = '';
- if(($privmail) && ($rr['network'] === 'stat'))
- $disabled = ' disabled="true" ' ;
- else
- $disabled = '';
- $o .= "{$rr['name']} \r\n";
+
+ $trimmed = substr($rr['name'],0,22);
+
+ $o .= "$trimmed \r\n";
}
}
@@ -110,7 +114,7 @@ function populate_acl($user = null,$celeb = false) {
$o .= '';
$o .= '';
$o .= '' . t('Contacts') . ' ';
- $o .= contact_select('contact_allow','contact_allow',$allow_cid,4,false,$celeb);
+ $o .= contact_select('contact_allow','contact_allow',$allow_cid,4,false,$celeb,true);
$o .= '
';
$o .= '' . "\r\n";
$o .= '
' . "\r\n";
@@ -125,7 +129,7 @@ function populate_acl($user = null,$celeb = false) {
$o .= '';
$o .= '';
$o .= '' . t('Contacts') . ' ';
- $o .= contact_select('contact_deny','contact_deny', $deny_cid,4,false, $celeb);
+ $o .= contact_select('contact_deny','contact_deny', $deny_cid,4,false, $celeb,true);
$o .= '
';
$o .= '' . "\r\n";
$o .= '
' . "\r\n";
diff --git a/include/bbcode.php b/include/bbcode.php
index 8382cc80..eb0806dc 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -1,5 +1,5 @@
', $Text);
+ // oembed tag
+ $Text = oembed_bbcode2html($Text);
+
call_hooks('bbcode',$Text);
return $Text;
diff --git a/include/items.php b/include/items.php
index e238280f..f204745b 100644
--- a/include/items.php
+++ b/include/items.php
@@ -1,6 +1,7 @@
]+>.+?' . 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s',
'[youtube]$1[/youtube]', $res['body']);
+ $res['body'] = oembed_html2bbcode($res['body']);
+
$config = HTMLPurifier_Config::createDefault();
$config->set('Cache.DefinitionImpl', null);
@@ -903,6 +906,10 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0) {
$feed->enable_order_by_date(false);
$feed->init();
+ if($feed->error())
+ logger('consume_feed: Error parsing XML: ' . $feed->error());
+
+
// Check at the feed level for updated contact name and/or photo
$name_updated = '';
diff --git a/include/main.js b/include/main.js
index fcd1d6fe..63b34bd2 100644
--- a/include/main.js
+++ b/include/main.js
@@ -44,12 +44,14 @@
$('#pause').html('');
}
}
- if(event.keyCode == '36' && event.shiftKey == true) {
- if(homebase !== undefined) {
- event.preventDefault();
- document.location = homebase;
- }
- }
+// this is shift-home on FF, but $ on IE, disabling until I figure out why the diff.
+// update: incompatible usage of onKeyDown vs onKeyPress
+// if(event.keyCode == '36' && event.shiftKey == true) {
+// if(homebase !== undefined) {
+// event.preventDefault();
+// document.location = homebase;
+// }
+// }
});
});
diff --git a/include/notifier.php b/include/notifier.php
index dd5d55ed..87095e81 100644
--- a/include/notifier.php
+++ b/include/notifier.php
@@ -255,7 +255,7 @@ function notifier_run($argv, $argc){
$recip_str = implode(', ', $recipients);
- $r = q("SELECT * FROM `contact` WHERE `id` IN ( %s ) AND `blocked` = 0 ",
+ $r = q("SELECT * FROM `contact` WHERE `id` IN ( %s ) AND `blocked` = 0 AND `pending` = 0 ",
dbesc($recip_str)
);
if(! count($r)){
@@ -363,13 +363,61 @@ function notifier_run($argv, $argc){
continue;
$params = 'hub.mode=publish&hub.url=' . urlencode($a->get_baseurl() . '/dfrn_poll/' . $owner['nickname'] );
post_url($h,$params);
- logger('pubsub: publish: ' . $h . ' returned ' . $a->get_curl_code());
+ logger('pubsub: publish: ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code());
if(count($hubs) > 1)
sleep(7); // try and avoid multiple hubs responding at precisely the same time
}
}
}
+ if($notify_hub) {
+
+ /**
+ *
+ * If you have less than 150 dfrn friends and it's a public message,
+ * we'll just go ahead and push them out securely with dfrn/rino.
+ * If you've got more than that, you'll have to rely on PuSH delivery.
+ *
+ */
+
+ $max_allowed = ((get_config('system','maxpubdeliver') === false) ? 150 : intval(get_config('system','maxdeliver')));
+
+
+ /**
+ *
+ * Only get the bare essentials and go back for the full record.
+ * If you've got a lot of friends and we grab all the details at once it could exhaust memory.
+ *
+ */
+
+ $r = q("SELECT `id`, `name` FROM `contact`
+ WHERE `network` = 'dfrn' AND `uid` = %d AND `blocked` = 0 AND `pending` = 0
+ AND `rel` != %d ",
+ intval($owner['uid']),
+ intval(REL_FAN)
+ );
+
+ if((count($r)) && ($max_allowed < count($r))) {
+ foreach($r as $rr) {
+
+ /* Don't deliver to folks who have already been delivered to */
+
+ if(! in_array($rr['id'], $conversants)) {
+ $n = q("SELECT * FROM `contact` WHERE `id` = %d LIMIT 1",
+ intval($rr['id'])
+ );
+ if(count($n)) {
+
+ logger('notifier: dfrnpubdelivery: ' . $n[0]['name']);
+ $deliver_status = dfrn_deliver($owner,$n[0],$atom);
+ }
+ }
+ else
+ logger('notifier: dfrnpubdelivery: ignoring ' . $rr['name']);
+ }
+ }
+ }
+
return;
}
diff --git a/include/oembed.php b/include/oembed.php
new file mode 100644
index 00000000..a130357a
--- /dev/null
+++ b/include/oembed.php
@@ -0,0 +1,97 @@
+";
+ switch ($j->type) {
+ case "video": {
+ if (isset($j->thumbnail_url)) {
+ $tw = (isset($j->thumbnail_width)) ? $j->thumbnail_width:200;
+ $th = (isset($j->thumbnail_height)) ? $j->thumbnail_height:180;
+ $ret = "";
+ $ret.= " ";
+ $ret.= " ";
+ } else {
+ $ret=$j->html;
+ }
+ $ret.=" ";
+ }; break;
+ case "photo": {
+ $ret = " ";
+ $ret.=" ";
+ }; break;
+ case "link": {
+ //$ret = "".$j->title." ";
+ }; break;
+ case "rich": {
+ // not so safe..
+ $ret = "".$j->html." ";
+ }; break;
+ }
+
+ $embedlink = (isset($j->title))?$j->title:$embedurl;
+ $ret .= "$embedlink ";
+ if (isset($j->author_name)) $ret.=" by ".$j->author_name;
+ if (isset($j->provider_name)) $ret.=" on ".$j->provider_name;
+ $ret.="";
+ return $ret;
+}
+
+function oembed_bbcode2html($text){
+ $stopoembed = get_config("system","no_oembed");
+ if ($stopoembed == true){
+ return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "". t('Embedding disabled') ." : $1 " ,$text);
+ }
+ return preg_replace_callback("/\[embed\](.+?)\[\/embed\]/is", oembed_replacecb ,$text);
+}
+
+
+function oe_build_xpath($attr, $value){
+ // http://westhoffswelt.de/blog/0036_xpath_to_select_html_by_class.html
+ return "contains( normalize-space( @$attr ), ' $value ' ) or substring( normalize-space( @$attr ), 1, string-length( '$value' ) + 1 ) = '$value ' or substring( normalize-space( @$attr ), string-length( @$attr ) - string-length( '$value' ) ) = ' $value' or @$attr = '$value'";
+}
+
+function oe_get_inner_html( $node ) {
+ $innerHTML= '';
+ $children = $node->childNodes;
+ foreach ($children as $child) {
+ $innerHTML .= $child->ownerDocument->saveXML( $child );
+ }
+ return $innerHTML;
+}
+
+/**
+ * Find ....
+ * and replace it with [embed]url[/embed]
+ */
+function oembed_html2bbcode($text) {
+ $dom = DOMDocument::loadHTML($text);
+ $xpath = new DOMXPath($dom);
+ $attr = "oembed";
+
+ $xattr = oe_build_xpath("class","oembed");
+ $entries = $xpath->query("//span[$xattr]");
+
+ $xattr = oe_build_xpath("rel","oembed");
+ foreach($entries as $e) {
+ $href = $xpath->evaluate("a[$xattr]/@href", $e)->item(0)->nodeValue;
+ if(!is_null($href)) $e->parentNode->replaceChild(new DOMText("[embed]".$href."[embed]"), $e);
+ }
+ return oe_get_inner_html( $dom->getElementsByTagName("body")->item(0) );
+}
+
+?>
\ No newline at end of file
diff --git a/include/poller.php b/include/poller.php
index 1003b2f0..2ba285b7 100644
--- a/include/poller.php
+++ b/include/poller.php
@@ -30,6 +30,10 @@ function poller_run($argv, $argc){
$php_path = ((x($a->config,'php_path') && strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
//proc_close(proc_open("\"$php_path\" \"include/queue.php\" &", array(), $foo));
proc_run($php_path,"include/queue.php");
+
+ // clear old cache
+ q("DELETE FROM `cache` WHERE `updated`<'%s'",
+ dbesc(datetime_convert('UTC','UTC',"now - 30 days")));
$hub_update = false;
@@ -58,6 +62,9 @@ function poller_run($argv, $argc){
foreach($contacts as $contact) {
+ if($manual_id)
+ $contact['last-update'] = '0000-00-00 00:00:00';
+
if($contact['priority'] || $contact['subhub']) {
$hub_update = true;
@@ -76,7 +83,7 @@ function poller_run($argv, $argc){
$contact['priority'] = (($interval !== false) ? intval($interval) : 3);
$hub_update = false;
- if(datetime_convert('UTC','UTC', 'now') > datetime_convert('UTC','UTC', $t . " + 1 day"))
+ if((datetime_convert('UTC','UTC', 'now') > datetime_convert('UTC','UTC', $t . " + 1 day")) || $force)
$hub_update = true;
}
diff --git a/index.php b/index.php
index ae6a578c..685a76a1 100644
--- a/index.php
+++ b/index.php
@@ -140,7 +140,7 @@ if(strlen($a->module)) {
}
else {
if((x($_SERVER,'QUERY_STRING')) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
- logger('index.php: dreamhost_error_hack invoked');
+ logger('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']);
goaway($a->get_baseurl() . $_SERVER['REQUEST_URI']);
}
diff --git a/mod/contacts.php b/mod/contacts.php
index 177ca997..4c627c88 100644
--- a/mod/contacts.php
+++ b/mod/contacts.php
@@ -121,6 +121,15 @@ function contacts_content(&$a) {
return; // NOTREACHED
}
+ if($cmd === 'update') {
+
+ // pull feed and consume it, which should subscribe to the hub.
+
+ $php_path = ((x($a->config,'php_path') && strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
+ proc_run($php_path,"include/poller.php","$contact_id");
+ goaway($a->get_baseurl() . '/contacts/' . $contact_id);
+ // NOTREACHED
+ }
if($cmd === 'block') {
$blocked = (($orig_record[0]['blocked']) ? 0 : 1);
@@ -248,6 +257,7 @@ function contacts_content(&$a) {
'$last_update' => (($r[0]['last-update'] == '0000-00-00 00:00:00')
? t('Never')
: datetime_convert('UTC',date_default_timezone_get(),$r[0]['last-update'],'D, j M Y, g:i A')),
+ '$udnow' => t('Update now'),
'$profile_select' => contact_profile_assign($r[0]['profile-id'],(($r[0]['network'] !== 'dfrn') ? true : false)),
'$contact_id' => $r[0]['id'],
'$block_text' => (($r[0]['blocked']) ? t('Unblock this contact') : t('Block this contact') ),
diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php
index 1061cdb8..85e7fc0a 100644
--- a/mod/dfrn_poll.php
+++ b/mod/dfrn_poll.php
@@ -26,6 +26,7 @@ function dfrn_poll_init(&$a) {
}
if(($dfrn_id === '') && (! x($_POST,'dfrn_id')) && ($a->argc > 1)) {
+ logger('dfrn_poll: public feed request from ' . $_SERVER['REMOTE_ADDR'] );
header("Content-type: application/atom+xml");
$o = get_feed_for($a, '*', $a->argv[1],$last_update);
echo $o;
diff --git a/mod/follow.php b/mod/follow.php
index a90ae3f5..62ba2585 100644
--- a/mod/follow.php
+++ b/mod/follow.php
@@ -10,7 +10,9 @@ function follow_post(&$a) {
// NOTREACHED
}
- $url = notags(trim($_POST['url']));
+ $url = $orig_url = notags(trim($_POST['url']));
+
+ $email_conversant = false;
if($url) {
$links = lrdd($url);
@@ -29,6 +31,11 @@ function follow_post(&$a) {
}
}
+ else {
+ if((strpos($orig_url,'@')) && validate_email($orig_url)) {
+ $email_conversant = true;
+ }
+ }
}
// If we find a DFRN site, send our subscriber to the other person's
diff --git a/mod/item.php b/mod/item.php
index 11e79a7a..7125ae1f 100644
--- a/mod/item.php
+++ b/mod/item.php
@@ -71,6 +71,7 @@ function item_post(&$a) {
$location = notags(trim($_POST['location']));
$coord = notags(trim($_POST['coord']));
$verb = notags(trim($_POST['verb']));
+ $emailcc = notags(trim($_POST['emailcc']));
if(! strlen($body)) {
notice( t('Empty post discarded.') . EOL );
@@ -421,12 +422,39 @@ function item_post(&$a) {
logger('mod_item: notifier invoked: ' . "\"$php_path\" \"include/notifier.php\" \"$notify_type\" \"$post_id\" &");
- proc_run($php_path, "include/notifier.php", $notify_type, "$post_id");
+ proc_run($php_path, "include/notifier.php", $notify_type, "$post_id");
$datarray['id'] = $post_id;
call_hooks('post_local_end', $datarray);
-
+
+ if(strlen($emailcc) && $profile_uid == local_user()) {
+ $erecips = explode(',', $emailcc);
+ if(count($erecips)) {
+ foreach($erecips as $recip) {
+ $addr = trim($recip);
+ if(! strlen($addr))
+ continue;
+ $disclaimer = ' ' . t('This message was sent to you by ') . $a->user['username']
+ . t(', a member of the Friendika social network.') . ' ';
+ $disclaimer .= t('You may visit them online at') . ' '
+ . $a->get_baseurl() . '/profile/' . $a->user['nickname'] . ' ';
+ $disclaimer .= t('Please contact the sender by replying to this post if you do not wish to receive these messages.') . ' ';
+
+ $subject = '[Friendika]' . ' ' . $a->user['username'] . ' ' . t('posted an update.');
+ $headers = 'From: ' . $a->user['username'] . ' <' . $a->user['email'] . '>' . "\n";
+ $headers .= 'MIME-Version: 1.0' . "\n";
+ $headers .= 'Content-Type: text/html; charset=UTF-8' . "\n";
+ $headers .= 'Content-Transfer-Encoding: 8bit' . "\n\n";
+ $link = ' ';
+ $html = prepare_body($datarray);
+ $message = '' . $link . $html . $disclaimer . '';
+ @mail($addr, $subject, $message, $headers);
+ }
+ }
+ }
+
+
goaway($a->get_baseurl() . "/" . $_POST['return'] );
return; // NOTREACHED
diff --git a/mod/network.php b/mod/network.php
index 50d7134c..80edca1d 100644
--- a/mod/network.php
+++ b/mod/network.php
@@ -87,6 +87,8 @@ function network_content(&$a, $update = 0) {
'$baseurl' => $a->get_baseurl(),
'$defloc' => $a->user['default-location'],
'$visitor' => 'block',
+ '$emailcc' => t('CC: email addresses'),
+ '$emtitle' => t('Example: bob@example.com, mary@example.com'),
'$lockstate' => $lockstate,
'$acl' => populate_acl((($group) ? $group_acl : $a->user), $celeb),
'$bang' => (($group) ? '!' : ''),
diff --git a/mod/profile.php b/mod/profile.php
index c84b493c..91c1ba6d 100644
--- a/mod/profile.php
+++ b/mod/profile.php
@@ -144,6 +144,8 @@ function profile_content(&$a, $update = 0) {
'$return_path' => $a->cmd,
'$visitor' => (($is_owner || $commvisitor) ? 'block' : 'none'),
'$lockstate' => $lockstate,
+ '$emailcc' => t('CC: email addresses'),
+ '$emtitle' => t('Example: bob@example.com, mary@example.com'),
'$bang' => '',
'$acl' => (($is_owner) ? populate_acl($a->user, $celeb) : ''),
'$profile_uid' => $a->profile['profile_uid'],
diff --git a/update.php b/update.php
index 0bd58d6d..f7bf9da1 100644
--- a/update.php
+++ b/update.php
@@ -320,3 +320,11 @@ function update_1031() {
function update_1032() {
q("ALTER TABLE `profile` ADD `pdesc` CHAR( 255 ) NOT NULL AFTER `name` ");
}
+
+function update_1033() {
+ q("CREATE TABLE IF NOT EXISTS `cache` (
+ `k` CHAR( 255 ) NOT NULL PRIMARY KEY ,
+ `v` TEXT NOT NULL,
+ `updated` DATETIME NOT NULL
+ ) ENGINE = MYISAM DEFAULT CHARSET=utf8;");
+}
diff --git a/view/de/contact_edit.tpl b/view/de/contact_edit.tpl
index fcd3f2d6..0b32bdd6 100644
--- a/view/de/contact_edit.tpl
+++ b/view/de/contact_edit.tpl
@@ -3,6 +3,8 @@
$name
+