1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/hubzilla_ynh.git synced 2024-09-03 19:26:21 +02:00

Merge pull request #5 from anmol26s/master

Updated to 1.3.1
This commit is contained in:
Andrew Manning 2016-03-20 06:40:06 -04:00
commit ca18e08f9d
370 changed files with 80521 additions and 45846 deletions

View file

@ -14,5 +14,5 @@ Before installing, read the [Hubzilla installation instructions](https://github.
Current snapshot in *sources*:
* https://github.com/redmatrix/hubzilla: 1.2
* https://github.com/redmatrix/hubzilla-addons: 1.2
* https://github.com/redmatrix/hubzilla: 1.3.1
* https://github.com/redmatrix/hubzilla-addons: 1.3.1

View file

@ -0,0 +1,164 @@
# Hubzilla at Home next to your Router
Run hubzilla-setup.sh for an unattended installation of hubzilla.
The script is known to work with Debian 8.3 stable (Jessie)
+ Home-PC (Debian-8.3.0-amd64)
+ DigitalOcean droplet (Debian 8.3 x64 / 512 MB Memory / 20 GB Disk / NYC3)
# Step-by-Step Overwiew
## Preconditions
Hardware
+ Internet connection and router at home
+ Mini-pc connected to your router
+ USB drive for backups
Software
+ Fresh installation of Debian on your mini-pc
+ Router with open ports 80 and 443 for your Debian
## The basic steps (quick overview)
+ Register your own domain (for example at selfHOST) or a free subdomain (for example at freeDNS)
+ Log on to your new debian (server)
- apt-get install git
- mkdir -p /var/www/html
- cd /var/www/html
- git clone https://github.com/redmatrix/hubzilla.git .
- cp .homeinstall/hubzilla-config.txt.template .homeinstall/hubzilla-config.txt
- nano .homeinstall/hubzilla-config.txt
- Enter your values there: db pass, domain, values for dyn DNS
- hubzilla-setup.sh as root
- ... wait, wait, wait until the script is finised
- reboot
+ Open your domain with a browser and step throught the initial configuration of hubzilla.
# Step-by-Step in Detail
## Preparations Hardware
### Mini-PC
### Recommended: USB Drive for Backups
The installation will create a daily backup.
If the backup process does not find an external device than the backup goes to
the internal disk.
The USB drive must be compatible with an encrpyted filesystem LUKS + ext4.
## Preparations Software
### Install Debian Linux on the Mini-PC
Download the stable Debian at https://www.debian.org/
Create bootable USB drive with Debian on it. You could use the programm
unetbootin, https://en.wikipedia.org/wiki/UNetbootin
Switch of your mini pc, plug in your USB drive and start the mini pc from the
stick. Install Debian. Follow the instructions of the installation.
### Configure your Router
Open the ports 80 and 443 on your router for your Debian
## Preparations Dynamic IP Address
Your Hubzilla must be reachable by a domain that you can type in your browser
cooldomain.org
You can use subdomains as well
my.cooldomain.org
There are two way to get a domain
- buy a domain (recommended) or
- register a free subdomain
### Method 1: Get yourself an own Domain (recommended)
...for example at selfHOST.de
### Method 2 Register a (free) Subdomain
Register a free subdomain for example at
- freeDNS
- selfHOST
WATCH THIS: A free subdomain is not the prefered way to get a domain name. Why?
Let's encrpyt issues a limited number of certificates each
day. Possibly other users of this domain will try to issue a certificate
at the same day as you do. So make sure you choose a domain with as less subdomains as
possible.
## Install Hubzilla on your Debian
Login to your debian
(Provided your username is "you" and the name of the mini pc is "debian". You
could take the IP address instead of "debian")
ssh -X you@debian
Change to root user
su -l
Install git
apt-get install git
Make the directory for apache and change diretory to it
mkdir /var/www
cd /var/www/
Clone hubzilla from git ("git pull" will update it later)
git clone https://github.com/redmatrix/hubzilla html
Change to the install script
cd html/.homeinstall/
Copy the template file
cp hubzilla-config.txt.template hubzilla-config.txt
Change the file "hubzilla-config.txt". Read the instructions there and enter your values.
nano hubzilla-config.txt
Run the script
./hubzilla-setup.sh
Wait... The script should not finish with an error message.
In a webbrowser open your domain.
Expected: A test page of hubzilla is shown. All checks there shoulg be
successfull. Go on...
Expected: A page for the Hubzilla server configuration shows up.
Leave db server name "127.0.0.1" and port "0" untouched.
Enter
- DB user name = hubzilla
- DB pass word = This is the password you entered in "hubzilla-config.txt"
- DB name = hubzilla
Leave db type "MySQL" untouched.
Follow the instructions in the next pages.

View file

@ -0,0 +1,177 @@
###############################################
### MANDATORY - database password #############
#
# Please give your database password
# Example: db_pass=pass_word_with_no_blanks_in_it
# Example: db_pass="this password has blanks in it"
db_pass=
###############################################
### MANDATORY - let's encrypt #################
#
# Hubilla requires encrypted communication via secure HTTP (HTTPS).
# This script automates installation of an SSL certificate from
# Let's Encrypt (https://letsencrypt.org)
#
# Please give the domain name of your hub
#
# Example: my.cooldomain.org
# Example: cooldomain.org
#
# Email is optional
#
#
le_domain=
le_email=
###############################################
### OPTIONAL - selfHOST - dynamic IP address ##
#
# 1. Register a domain at selfhost.de
# - choose offer "DOMAIN dynamisch" 1,50€/mon at 08.01.2016
# 2. Get your configuration for dynamic IP update
# - Log in at selfhost.de
# - go to "DynDNS Accounte"
# - klick "Details" of your (freshly) registered domain
# - You will find the configuration there
# - Benutzername (user name) > use this for "selfhost_user="
# - Passwort (pass word) > use this for "selfhost_pass="
#
#
selfhost_user=
selfhost_pass=
###############################################
### OPTIONAL - FreeDNS - dynamic IP address ###
#
# Please give the alpha-numeric-key of freedns
#
# Get a free subdomain from freedns and use it for your dynamic ip address
# Documentation under http://www.techjawab.com/2013/06/setup-dynamic-dns-dyndns-for-free-on.html
#
# - Register for a Free domain at http://freedns.afraid.org/signup/
# - WATCH THIS: Make sure you choose a domain with as less subdomains as
# possible. Why? Let's encrpyt issues a limited count of certificates each
# day. Possible other users of this domain will try to issue a certificate
# at the same day.
# - Logon to FreeDNS (where you just registered)
# - Goto http://freedns.afraid.org/dynamic/
# - Right click on "Direct Link" and copy the URL and paste it somewhere.
# - You should notice a large and unique alpha-numeric key in the URL
#
# http://freedns.afraid.org/dynamic/update.php?alpha-numeric-key
#
# Provided your url from freedns is
#
# http://freedns.afraid.org/dynamic/update.php?U1Z6aGt2R0NzMFNPNWRjbWxxZGpsd093OjE1Mzg5NDE5
#
# Then you have to provide
#
# freedns_key=U1Z6aGt2R0NzMFNPNWRjbWxxZGpsd093OjE1Mzg5NDE5
#
#
#freedns_key=
###############################################
### OPTIONAL - Backup to external device ######
#
# The script can use an external device for the daily backup.
# The file system of the device (USB stick for example) must be compatible
# with encrypted LUKS + ext4
#
# You should test to mount the device befor you run the script
# (hubzilla-setup.sh).
# How to find your (pluged-in) devices?
#
# fdisk -l
#
# Provided your device was listed as is /dev/sdb1. You could check with:
#
# blkid | grep /dev/sdb1
#
# Try to decrypt
# (You might install cryptsetup befor using apt-get install.
#
# apt-get install cryptsetup
# cryptsetup luksOpen /dev/sdb1 cryptobackup
#
# Try to mount
# You might create the directory /media/hubzilla_backup it it does not exist
# using mkdir.
#
# mkdir /media/hubzilla_backup
# mount /dev/mapper/cryptobackup /media/hubzilla_backup
#
# Unmounting device goes like this
#
# umount /media/hubzilla_backup
# cryptsetup luksClose cryptobackup
#
# To check if still mounted
#
# lsof /media/hubzilla_backup
#
# If you leave the following parameters
# - "backup_device_name" and
# - "backup_device_pass"
# empty the script will create daily backups on the internal disk (which could
# save you as well).
#
# Example: backup_device_name=/dev/sdc1
#
backup_device_name=
backup_device_pass=
###############################################
### OPTIONAL - Owncloud - deprecated ##########
#
# To install owncloud: owncloud=y
# Leave empty if you don't want to install owncloud
#
#owncloud=
###############################################
### OPTIONAL - do not mess with things below ##
# (...if you are not certain)
#
# Usually you are done here
# Everything below is OPTIONAL
#
###############################################
#
# Database for hubzilla
hubzilla_db_name=hubzilla
hubzilla_db_user=hubzilla
hubzilla_db_pass=$db_pass
#
#
# Password for package mysql-server
# Example: mysqlpass=aberhallo
# Example: mysqlpass="aber hallo has blanks in it"
#
mysqlpass=$db_pass
# Password for package phpmyadmin
# Example: phpmyadminpass=aberhallo
# Example: phpmyadminpass="aber hallo has blanks in it"
phpmyadminpass=$db_pass
# TODO Prepare hubzilla for programmers
# - install eclipse and plugins
# - install xdebug to debug the php with eclipse
# - weaken permissions on /var/www/html
# - manual steps after this script
# * in eclipse: install plugins for php git hub
# * in eclipse: configure firefox (chrome,...) as browser to run with the php debuger
# * in eclipse: switch php debugger from zend to xdebug
# * in eclipse: add local hubzilla github repository
#
# Which user will use eclipse?
# Leave this empty if you do not want to prepare hubzilla for debugging
#
#developer_name=

View file

@ -0,0 +1,949 @@
#!/bin/bash
#
# How to use
# ----------
#
# This file automates the installation of hubzilla under Debian Linux
#
# 1) Copy the file "hubzilla-config.txt.template" to "hubzilla-config.txt"
# Follow the instuctions there
#
# 2) Switch to user "root" by typing "su -"
#
# 3) Run with "./hubzilla-setup.sh"
# If this fails check if you can execute the script.
# - To make it executable type "chmod +x hubzilla-setup.sh"
# - or run "bash hubzilla-setup.sh"
#
#
# What does this script do basically?
# -----------------------------------
#
# This file automates the installation of hubzilla under Debian Linux
# - install
# * apache webserer,
# * php,
# * mysql - the database for hubzilla,
# * phpmyadmin,
# * git to download and update hubzilla itself
# - download hubzilla core and addons
# - configure cron
# * "poller.php" for regular background prozesses of hubzilla
# * to_do "apt-get update" and "apt-get dist-upgrade" to keep linux
# up-to-date
# * to_do backup hubzillas database and files (rsnapshot)
# - configure dynamic ip with cron
# - to_do letsencrypt
# - to_do redirection to https
#
#
# Discussion
# ----------
#
# Security - password is the same for mysql-server, phpmyadmin and hubzilla db
# - The script runs into installation errors for phpmyadmin if it uses
# different passwords. For the sake of simplicity one singel password.
#
# Security - suhosin for PHP
# - The script does not install suhosin.
# - Is the security package suhosin usefull or not usefull?
#
# Hubzilla - email verification
# - The script switches off email verification off in all htconfig.tpl.
# Example: /var/www/html/view/en/htconfig.tpl
# - Is this a silly idea or not?
#
#
# Remove Hubzilla (for a fresh start using the script)
# ----------------------------------------------------
#
# You could use /var/www/hubzilla-remove.sh
# that is created by hubzilla-setup.sh.
#
# The script will remove (almost everything) what was installed by the script.
# After the removal you could run the script again to have a fresh install
# of all applications including hubzilla and its database.
#
# How to restore from backup
# --------------------------
#
# Daily backup
# - - - - - -
#
# The installation
# - writes a script /var/www/hubzilla-daily.sh
# - creates a daily cron that runs the hubzilla-daily.sh
#
# hubzilla-daily.sh makes a (daily) backup of all relevant files
# - /var/lib/mysql/ > hubzilla database
# - /var/www/html/ > hubzilla from github
# - /var/www/letsencrypt/ > certificates
#
# hubzilla-daily.sh writes the backup
# - either to an external disk compatible to LUKS+ext4 (see hubzilla-config.txt)
# - or to /var/cache/rsnapshot in case the external disk is not plugged in
#
# Restore backup
# - - - - - - -
#
# This was not tested yet.
# Bacically you can copy the files from the backup to the server.
#
# Credits
# -------
#
# The script is based on Thomas Willinghams script "debian-setup.sh"
# which he used to install the red#matrix.
#
# The script uses another script from https://github.com/lukas2511/letsencrypt.sh
#
# The documentation for bash is here
# https://www.gnu.org/software/bash/manual/bash.html
#
function check_sanity {
# Do some sanity checking.
print_info "Sanity check..."
if [ $(/usr/bin/id -u) != "0" ]
then
die 'Must be run by root user'
fi
if [ -f /etc/lsb-release ]
then
die "Distribution is not supported"
fi
if [ ! -f /etc/debian_version ]
then
die "Ubuntu is not supported"
fi
}
function check_config {
print_info "config check..."
# Check for required parameters
if [ -z "$db_pass" ]
then
die "db_pass not set in $configfile"
fi
if [ -z "$le_domain" ]
then
die "le_domain not set in $configfile"
fi
# backup is important and should be checked
if [ -n "$backup_device_name" ]
then
device_mounted=0
if fdisk -l | grep -i "$backup_device_name.*linux"
then
print_info "ok - filesystem of external device is linux"
if [ -n "$backup_device_pass" ]
then
echo "$backup_device_pass" | cryptsetup luksOpen $backup_device_name cryptobackup
if [ ! -d /media/hubzilla_backup ]
then
mkdir /media/hubzilla_backup
fi
if mount /dev/mapper/cryptobackup /media/hubzilla_backup
then
device_mounted=1
print_info "ok - could encrypt and mount external backup device"
umount /media/hubzilla_backup
else
print_warn "backup to external device will fail because encryption failed"
fi
cryptsetup luksClose cryptobackup
else
if mount $backup_device_name /media/hubzilla_backup
then
device_mounted=1
print_info "ok - could mount external backup device"
umount /media/hubzilla_backup
else
print_warn "backup to external device will fail because mount failed"
fi
fi
else
print_warn "backup to external device will fail because filesystem is either not linux or 'backup_device_name' is not correct in $configfile"
fi
if [ $device_mounted == 0 ]
then
die "backup device not ready"
fi
fi
}
function die {
echo "ERROR: $1" > /dev/null 1>&2
exit 1
}
function update_upgrade {
print_info "updated and upgrade..."
# Run through the apt-get update/upgrade first. This should be done before
# we try to install any package
apt-get -q -y update && apt-get -q -y dist-upgrade
print_info "updated and upgraded linux"
}
function check_install {
if [ -z "`which "$1" 2>/dev/null`" ]
then
# export DEBIAN_FRONTEND=noninteractive ... answers from the package
# configuration database
# - q ... without progress information
# - y ... answer interactive questions with "yes"
# DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -q -y install $2
DEBIAN_FRONTEND=noninteractive apt-get -q -y install $2
print_info "installed $2 installed for $1"
else
print_warn "$2 already installed"
fi
}
function nocheck_install {
# export DEBIAN_FRONTEND=noninteractive ... answers from the package configuration database
# - q ... without progress information
# - y ... answer interactive questions with "yes"
# DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -q -y install $2
# DEBIAN_FRONTEND=noninteractive apt-get --install-suggests -q -y install $1
DEBIAN_FRONTEND=noninteractive apt-get -q -y install $1
print_info "installed $1"
}
function print_info {
echo -n -e '\e[1;34m'
echo -n $1
echo -e '\e[0m'
}
function print_warn {
echo -n -e '\e[1;31m'
echo -n $1
echo -e '\e[0m'
}
function stop_hubzilla {
if [ -d /etc/apache2 ]
then
print_info "stopping apache webserver..."
service apache2 stop
fi
if [ -f /etc/init.d/mysql ]
then
print_info "stopping mysql db..."
/etc/init.d/mysql stop
fi
}
function install_apache {
print_info "installing apache..."
nocheck_install "apache2 apache2-utils"
}
function install_curl {
print_info "installing curl..."
nocheck_install "curl"
}
function install_sendmail {
print_info "installing sendmail..."
nocheck_install "sendmail sendmail-bin"
}
function install_php {
# openssl and mbstring are included in libapache2-mod-php5
# to_to: php5-suhosin
print_info "installing php..."
nocheck_install "libapache2-mod-php5 php5 php-pear php5-xcache php5-curl php5-mcrypt php5-gd"
php5enmod mcrypt
}
function install_mysql {
# http://www.microhowto.info/howto/perform_an_unattended_installation_of_a_debian_package.html
#
# To determine the required package name, key and type you can perform
# a trial installation then search the configuration database.
#
# debconf-get-selections | grep mysql-server
#
# The command debconf-get-selections is provided by the package
# debconf-utils, which you may need to install.
#
# apt-get install debconf-utils
#
# If you want to supply an answer to a configuration question but do not
# want to be prompted for it then this can be arranged by preseeding the
# DebConf database with the required information.
#
# echo mysql-server-5.5 mysql-server/root_password password xyzzy | debconf-set-selections
# echo mysql-server-5.5 mysql-server/root_password_again password xyzzy | debconf-set-selections
#
print_info "installing mysql..."
if [ -z "$mysqlpass" ]
then
die "mysqlpass not set in $configfile"
fi
echo mysql-server-5.5 mysql-server/root_password password $mysqlpass | debconf-set-selections
echo mysql-server-5.5 mysql-server/root_password_again password $mysqlpass | debconf-set-selections
nocheck_install "php5-mysql mysql-server mysql-client"
php5enmod mcrypt
}
function install_phpmyadmin {
print_info "installing phpmyadmin..."
if [ -z "$phpmyadminpass" ]
then
die "phpmyadminpass not set in $configfile"
fi
echo phpmyadmin phpmyadmin/setup-password password $phpmyadminpass | debconf-set-selections
echo phpmyadmin phpmyadmin/mysql/app-pass password $phpmyadminpass | debconf-set-selections
echo phpmyadmin phpmyadmin/app-password-confirm password $phpmyadminpass | debconf-set-selections
echo phpmyadmin phpmyadmin/mysql/admin-pass password $phpmyadminpass | debconf-set-selections
echo phpmyadmin phpmyadmin/password-confirm password $phpmyadminpass | debconf-set-selections
echo phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2 | debconf-set-selections
nocheck_install "phpmyadmin"
# It seems to be not neccessary to check rewrite.load because it comes
# with the installation. To be sure you could check this manually by:
#
# nano /etc/apache2/mods-available/rewrite.load
#
# You should find the content:
#
# LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
a2enmod rewrite
if [ ! -f /etc/apache2/apache2.conf ]
then
die "could not find file /etc/apache2/apache2.conf"
fi
sed -i \
"s/AllowOverride None/AllowOverride all/" \
/etc/apache2/apache2.conf
if [ -z "`grep 'Include /etc/phpmyadmin/apache.conf' /etc/apache2/apache2.conf`" ]
then
echo "Include /etc/phpmyadmin/apache.conf" >> /etc/apache2/apache2.conf
fi
service apache2 restart
}
function create_hubzilla_db {
print_info "creating hubzilla database..."
if [ -z "$hubzilla_db_name" ]
then
die "hubzilla_db_name not set in $configfile"
fi
if [ -z "$hubzilla_db_user" ]
then
die "hubzilla_db_user not set in $configfile"
fi
if [ -z "$hubzilla_db_pass" ]
then
die "hubzilla_db_pass not set in $configfile"
fi
Q1="CREATE DATABASE IF NOT EXISTS $hubzilla_db_name;"
Q2="GRANT USAGE ON *.* TO $hubzilla_db_user@localhost IDENTIFIED BY '$hubzilla_db_pass';"
Q3="GRANT ALL PRIVILEGES ON $hubzilla_db_name.* to $hubzilla_db_user@localhost identified by '$hubzilla_db_pass';"
Q4="FLUSH PRIVILEGES;"
SQL="${Q1}${Q2}${Q3}${Q4}"
mysql -uroot -p$phpmyadminpass -e "$SQL"
}
function run_freedns {
print_info "run freedns (dynamic IP)..."
if [ -z "$freedns_key" ]
then
print_info "freedns was not started because 'freedns_key' is empty in $configfile"
else
if [ -n "$selfhost_user" ]
then
die "You can not use freeDNS AND selfHOST for dynamic IP updates ('freedns_key' AND 'selfhost_user' set in $configfile)"
fi
wget --no-check-certificate -O - https://freedns.afraid.org/dynamic/update.php?$freedns_key
fi
}
function install_run_selfhost {
print_info "install and start selfhost (dynamic IP)..."
if [ -z "$selfhost_user" ]
then
print_info "selfHOST was not started because 'selfhost_user' is empty in $configfile"
else
if [ -n "$freedns_key" ]
then
die "You can not use freeDNS AND selfHOST for dynamic IP updates ('freedns_key' AND 'selfhost_user' set in $configfile)"
fi
if [ -z "$selfhost_pass" ]
then
die "selfHOST was not started because 'selfhost_pass' is empty in $configfile"
fi
if [ ! -d $selfhostdir ]
then
mkdir $selfhostdir
fi
# the old way
# https://carol.selfhost.de/update?username=123456&password=supersafe
#
# the prefered way
wget --output-document=$selfhostdir/$selfhostscript http://jonaspasche.de/selfhost-updater
echo "router" > $selfhostdir/device
echo "$selfhost_user" > $selfhostdir/user
echo "$selfhost_pass" > $selfhostdir/pass
bash $selfhostdir/$selfhostscript update
fi
}
function ping_domain {
print_info "ping domain $domain..."
# Is the domain resolved? Try to ping 6 times à 10 seconds
COUNTER=0
for i in {1..6}
do
print_info "loop $i for ping -c 1 $domain ..."
if ping -c 4 -W 1 $le_domain
then
print_info "$le_domain resolved"
break
else
if [ $i -gt 5 ]
then
die "Failed to: ping -c 1 $domain not resolved"
fi
fi
sleep 10
done
sleep 5
}
function configure_cron_freedns {
print_info "configure cron for freedns..."
if [ -z "$freedns_key" ]
then
print_info "freedns is not configured because freedns_key is empty in $configfile"
else
# Use cron for dynamich ip update
# - at reboot
# - every 30 minutes
if [ -z "`grep 'freedns.afraid.org' /etc/crontab`" ]
then
echo "@reboot root https://freedns.afraid.org/dynamic/update.php?$freedns_key > /dev/null 2>&1" >> /etc/crontab
echo "*/30 * * * * root wget --no-check-certificate -O - https://freedns.afraid.org/dynamic/update.php?$freedns_key > /dev/null 2>&1" >> /etc/crontab
else
print_info "cron for freedns was configured already"
fi
fi
}
function configure_cron_selfhost {
print_info "configure cron for selfhost..."
if [ -z "$selfhost_user" ]
then
print_info "freedns is not configured because freedns_key is empty in $configfile"
else
# Use cron for dynamich ip update
# - at reboot
# - every 30 minutes
if [ -z "`grep 'selfhost-updater.sh' /etc/crontab`" ]
then
echo "@reboot root bash /etc/selfhost/selfhost-updater.sh update > /dev/null 2>&1" >> /etc/crontab
echo "*/5 * * * * root /bin/bash /etc/selfhost/selfhost-updater.sh update > /dev/null 2>&1" >> /etc/crontab
else
print_info "cron for selfhost was configured already"
fi
fi
}
function install_git {
print_info "installing git..."
nocheck_install "git"
}
function install_letsencrypt {
print_info "installing let's encrypt ..."
# check if user gave domain
if [ -z "$le_domain" ]
then
die "Failed to install let's encrypt: 'le_domain' is empty in $configfile"
fi
# configure apache
apache_le_conf=/etc/apache2/sites-available/le-default.conf
if [ -f $apache_le_conf ]
then
print_info "$apache_le_conf exist already"
else
cat > $apache_le_conf <<END
# letsencrypt default Apache configuration
Alias /.well-known/acme-challenge /var/www/letsencrypt
<Directory /var/www/letsencrypt>
Options FollowSymLinks
Allow from all
</Directory>
END
a2ensite le-default.conf
service apache2 restart
fi
# download the shell script
if [ -d $le_dir ]
then
print_info "letsenrypt exists already (nothing downloaded > no certificate created and registered)"
return 0
fi
git clone https://github.com/lukas2511/letsencrypt.sh $le_dir
cd $le_dir
# create config file for letsencrypt.sh
echo "WELLKNOWN=$le_dir" > $le_dir/config.sh
if [ -n "$le_email" ]
then
echo "CONTACT_EMAIL=$le_email" >> $le_dir/config.sh
fi
# create domain file for letsencrypt.sh
# WATCH THIS:
# - It did not work wit "sub.domain.org www.sub.domain.org".
# - So just use "sub.domain.org" only!
echo "$le_domain" > $le_dir/domains.txt
# test apache config for letsencrpyt
url_http=http://$le_domain/.well-known/acme-challenge/domains.txt
wget_output=$(wget -nv --spider --max-redirect 0 $url_http)
if [ $? -ne 0 ]
then
die "Failed to load $url_http"
fi
# run letsencrypt.sh
#
./letsencrypt.sh --cron
}
function configure_apache_for_https {
print_info "configuring apache to use httpS ..."
# letsencrypt.sh
#
# "${BASEDIR}/certs/${domain}/privkey.pem"
# "${BASEDIR}/certs/${domain}/cert.pem"
# "${BASEDIR}/certs/${domain}/fullchain.pem"
#
SSLCertificateFile=${le_dir}/certs/${le_domain}/cert.pem
SSLCertificateKeyFile=${le_dir}/certs/${le_domain}/privkey.pem
SSLCertificateChainFile=${le_dir}/certs/${le_domain}/fullchain.pem
if [ ! -f $SSLCertificateFile ]
then
print_warn "Failed to configure apache for httpS: Missing certificate file $SSLCertificateFile"
return 0
fi
# make sure that the ssl mode is enabled
print_info "...configuring apache to use httpS - a2enmod ssl ..."
a2enmod ssl
# modify apach' ssl conf file
if grep -i "ServerName" $sslconf
then
print_info "seems that apache was already configered to use httpS with $sslconf"
else
sed -i "s/ServerAdmin.*$/ServerAdmin webmaster@localhost\\n ServerName ${le_domain}/" $sslconf
fi
sed -i s#/etc/ssl/certs/ssl-cert-snakeoil.pem#$SSLCertificateFile# $sslconf
sed -i s#/etc/ssl/private/ssl-cert-snakeoil.key#$SSLCertificateKeyFile# $sslconf
sed -i s#/etc/apache2/ssl.crt/server-ca.crt#$SSLCertificateChainFile# $sslconf
sed -i s/#SSLCertificateChainFile/SSLCertificateChainFile/ $sslconf
# apply changes
a2ensite default-ssl.conf
service apache2 restart
}
function check_https {
print_info "checking httpS > testing ..."
url_https=https://$le_domain
wget_output=$(wget -nv --spider --max-redirect 0 $url_https)
if [ $? -ne 0 ]
then
print_warn "check not ok"
else
print_info "check ok"
fi
}
function install_hubzilla {
print_info "installing hubzilla..."
# rm -R /var/www/html/ # for "stand alone" usage
cd /var/www/
# git clone https://github.com/redmatrix/hubzilla html # for "stand alone" usage
cd html/
git clone https://github.com/redmatrix/hubzilla-addons addon
mkdir -p "store/[data]/smarty3"
chmod -R 777 store
touch .htconfig.php
chmod ou+w .htconfig.php
install_hubzilla_plugins
cd /var/www/
chown -R www-data:www-data html
chown root:www-data /var/www/html/
chown root:www-data /var/www/html/.htaccess
chmod 0644 /var/www/html/.htaccess
# try to switch off email registration
sed -i "s/verify_email.*1/verify_email'] = 0/" /var/www/html/view/*/ht*
if [ -n "`grep -r 'verify_email.*1' /var/www/html/view/`" ]
then
print_warn "Hubzillas registration prozess might have email verification switched on."
fi
print_info "installed hubzilla"
}
function install_hubzilla_plugins {
print_info "installing hubzilla plugins..."
cd /var/www/html
plugin_install=.homeinstall/plugin_install.txt
theme_install=.homeinstall/theme_install.txt
# overwrite script to update the plugin and themes
rm -f $plugins_update
echo "cd /var/www/html" >> $plugins_update
###################
# write plugin file
if [ ! -f "$plugin_install" ]
then
echo "# To install a plugin" >> $plugin_install
echo "# 1. add the plugin in a new line and run" >> $plugin_install
echo "# 2. run" >> $plugin_install
echo "# cd /var/www/html/.homeinstall" >> $plugin_install
echo "# ./hubzilla-setup.sh" >> $plugin_install
echo "https://gitlab.com/zot/ownmapp.git ownMapp" >> $plugin_install
echo "https://gitlab.com/zot/hubzilla-chess.git chess" >> $plugin_install
fi
# install plugins
while read -r line; do
[[ "$line" =~ ^#.*$ ]] && continue
p_url=$(echo $line | awk -F' ' '{print $1}')
p_name=$(echo $line | awk -F' ' '{print $2}')
# basic check of format
if [ ${#p_url} -ge 1 ] && [ ${#p_name} -ge 1 ]
then
# install addon
util/add_addon_repo $line
util/update_addon_repo $p_name # not sure if this line is neccessary
echo "util/update_addon_repo $p_name" >> $plugins_update
else
print_info "skipping installation of a plugin from file $plugin_install - something wrong with format in line: $line"
fi
done < "$plugin_install"
###################
# write theme file
if [ ! -f "$theme_install" ]
then
echo "# To install a theme" >> $theme_install
echo "# 1. add the theme in a new line and run" >> $theme_install
echo "# 2. run" >> $theme_install
echo "# cd /var/www/html/.homeinstall" >> $theme_install
echo "# ./hubzilla-setup.sh" >> $theme_install
echo "https://github.com/DeadSuperHero/hubzilla-themes.git DeadSuperHeroThemes" >> $theme_install
fi
# install plugins
while read -r line; do
[[ "$line" =~ ^#.*$ ]] && continue
p_url=$(echo $line | awk -F' ' '{print $1}')
p_name=$(echo $line | awk -F' ' '{print $2}')
# basic check of format
if [ ${#p_url} -ge 1 ] && [ ${#p_name} -ge 1 ]
then
# install addon
util/add_theme_repo $line
util/update_theme_repo $p_name # not sure if this line is neccessary
echo "util/update_theme_repo $p_name" >> $plugins_update
else
print_info "skipping installation of a theme from file $theme_install - something wrong with format in line: $line"
fi
done < "$theme_install"
print_info "installed hubzilla plugins and themes"
}
function rewrite_to_https {
print_info "configuring apache to redirect http to httpS ..."
htaccessfile=/var/www/html/.htaccess
if grep -i "https" $htaccessfile
then
print_info "...configuring apache to redirect http to httpS was already done in $htaccessfile"
else
sed -i "s#QSA]#QSA]\\n RewriteCond %{SERVER_PORT} !^443$\\n RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L]#" $htaccessfile
fi
service apache2 restart
}
function install_owncloud {
if [ -z "$owncloud" ]
then
print_info "Do not install owncloud"
return 0
fi
if [ -f /etc/apt/sources.list.d/owncloud.list ]
then
print_info "owncloud is already installed and is left untouched"
return 0
fi
print_info "installing owncloud..."
# add the repository key to apt
wget -nv https://download.owncloud.org/download/repositories/stable/Debian_8.0/Release.key -O Release.key
apt-key add - < Release.key
# add the repository and install from there
sh -c "echo 'deb http://download.owncloud.org/download/repositories/stable/Debian_8.0/ /' >> /etc/apt/sources.list.d/owncloud.list"
apt-get update
nocheck_install "owncloud"
chown -R www-data:www-data /var/www/owncloud/
# set strong permissions
ocpath='/var/www/owncloud'
htuser='www-data'
htgroup='www-data'
rootuser='root' # On QNAP this is admin
find ${ocpath}/ -type f -print0 | xargs -0 chmod 0640
find ${ocpath}/ -type d -print0 | xargs -0 chmod 0750
chown -R ${rootuser}:${htgroup} ${ocpath}/
chown -R ${htuser}:${htgroup} ${ocpath}/apps/
chown -R ${htuser}:${htgroup} ${ocpath}/config/
chown -R ${htuser}:${htgroup} ${ocpath}/data/
chown -R ${htuser}:${htgroup} ${ocpath}/themes/
chown ${rootuser}:${htgroup} ${ocpath}/.htaccess
chown ${rootuser}:${htgroup} ${ocpath}/data/.htaccess
chmod 0644 ${ocpath}/.htaccess
chmod 0644 ${ocpath}/data/.htaccess
}
# This will allways overwrite both config files
# - internal disk
# - external disk (LUKS + ext4)
# of rsnapshot for hubzilla
function install_rsnapshot {
print_info "installing rsnapshot..."
nocheck_install "rsnapshot"
# internal disk
cp -f /etc/rsnapshot.conf $snapshotconfig
sed -i "/hourly/s/retain/#retain/" $snapshotconfig
sed -i "/monthly/s/#retain/retain/" $snapshotconfig
sed -i "s/^cmd_cp/#cmd_cp/" $snapshotconfig
sed -i "s/^backup/#backup/" $snapshotconfig
if [ -z "`grep 'letsencrypt' $snapshotconfig`" ]
then
echo "backup /var/lib/mysql/ localhost/" >> $snapshotconfig
echo "backup /var/www/html/ localhost/" >> $snapshotconfig
echo "backup /var/www/letsencrypt/ localhost/" >> $snapshotconfig
fi
# external disk
if [ -n "$backup_device_name" ] && [ -n "$backup_device_pass" ]
then
cp -f /etc/rsnapshot.conf $snapshotconfig_external_device
sed -i "s#snapshot_root.*#snapshot_root $backup_mount_point#" $snapshotconfig_external_device
sed -i "/hourly/s/retain/#retain/" $snapshotconfig_external_device
sed -i "/monthly/s/#retain/retain/" $snapshotconfig_external_device
sed -i "s/^cmd_cp/#cmd_cp/" $snapshotconfig_external_device
sed -i "s/^backup/#backup/" $snapshotconfig_external_device
if [ -z "`grep 'letsencrypt' $snapshotconfig_external_device`" ]
then
echo "backup /var/lib/mysql/ localhost/" >> $snapshotconfig_external_device
echo "backup /var/www/html/ localhost/" >> $snapshotconfig_external_device
echo "backup /var/www/letsencrypt/ localhost/" >> $snapshotconfig_external_device
fi
else
print_info "No backup configuration (rsnapshot) for external device configured. Reason: backup_device_name and/or backup_device_pass not given in $configfile"
fi
}
function install_cryptosetup {
print_info "installing cryptsetup..."
nocheck_install "cryptsetup"
}
function configure_cron_daily {
print_info "configuring cron..."
# every 10 min for poller.php
if [ -z "`grep 'poller.php' /etc/crontab`" ]
then
echo "*/10 * * * * www-data cd /var/www/html; php include/poller.php >> /dev/null 2>&1" >> /etc/crontab
fi
# Run external script daily at 05:30
# - stop apache and mysql-server
# - backup hubzilla
# - update hubzilla core and addon
# - update and upgrade linux
# - reboot
echo "#!/bin/sh" > /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "echo \" \"" >> /var/www/$hubzilladaily
echo "echo \"+++ \$(date) +++\"" >> /var/www/$hubzilladaily
echo "echo \" \"" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - renew certificat if 30 days old...\"" >> /var/www/$hubzilladaily
echo "bash /var/www/letsencrypt/letsencrypt.sh --cron" >> /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "# stop hubzilla" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - stoping apaache and mysql...\"" >> /var/www/$hubzilladaily
echo "service apache2 stop" >> /var/www/$hubzilladaily
echo "/etc/init.d/mysql stop # to avoid inconsistancies" >> /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "# backup" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - try to mount external device for backup...\"" >> /var/www/$hubzilladaily
echo "backup_device_name=$backup_device_name" >> /var/www/$hubzilladaily
echo "backup_device_pass=$backup_device_pass" >> /var/www/$hubzilladaily
echo "backup_mount_point=$backup_mount_point" >> /var/www/$hubzilladaily
echo "device_mounted=0" >> /var/www/$hubzilladaily
echo "if [ -n \"$backup_device_name\" ]" >> /var/www/$hubzilladaily
echo "then" >> /var/www/$hubzilladaily
echo " if blkid | grep $backup_device_name" >> /var/www/$hubzilladaily
echo " then" >> /var/www/$hubzilladaily
if [ -n "$backup_device_pass" ]
then
echo " echo \"decrypting backup device...\"" >> /var/www/$hubzilladaily
echo " echo "\"$backup_device_pass\"" | cryptsetup luksOpen $backup_device_name cryptobackup" >> /var/www/$hubzilladaily
fi
echo " if [ ! -d $backup_mount_point ]" >> /var/www/$hubzilladaily
echo " then" >> /var/www/$hubzilladaily
echo " mkdir $backup_mount_point" >> /var/www/$hubzilladaily
echo " fi" >> /var/www/$hubzilladaily
echo " echo \"mounting backup device...\"" >> /var/www/$hubzilladaily
if [ -n "$backup_device_pass" ]
then
echo " if mount /dev/mapper/cryptobackup $backup_mount_point" >> /var/www/$hubzilladaily
else
echo " if mount $backup_device_name $backup_mount_point" >> /var/www/$hubzilladaily
fi
echo " then" >> /var/www/$hubzilladaily
echo " device_mounted=1" >> /var/www/$hubzilladaily
echo " echo \"device $backup_device_name is now mounted. Starting backup...\"" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig_external_device daily" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig_external_device weekly" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig_external_device monthly" >> /var/www/$hubzilladaily
echo " echo \"\$(date) - disk sizes...\"" >> /var/www/$hubzilladaily
echo " df -h" >> /var/www/$hubzilladaily
echo " echo \"\$(date) - db size...\"" >> /var/www/$hubzilladaily
echo " du -h $backup_mount_point | grep mysql/hubzilla" >> /var/www/$hubzilladaily
echo " echo \"unmounting backup device...\"" >> /var/www/$hubzilladaily
echo " umount $backup_mount_point" >> /var/www/$hubzilladaily
echo " else" >> /var/www/$hubzilladaily
echo " echo \"failed to mount device $backup_device_name\"" >> /var/www/$hubzilladaily
echo " fi" >> /var/www/$hubzilladaily
if [ -n "$backup_device_pass" ]
then
echo " echo \"closing decrypted backup device...\"" >> /var/www/$hubzilladaily
echo " cryptsetup luksClose cryptobackup" >> /var/www/$hubzilladaily
fi
echo " fi" >> /var/www/$hubzilladaily
echo "fi" >> /var/www/$hubzilladaily
echo "if [ \$device_mounted == 0 ]" >> /var/www/$hubzilladaily
echo "then" >> /var/www/$hubzilladaily
echo " echo \"device could not be mounted $backup_device_name. Using internal disk for backup...\"" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig daily" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig weekly" >> /var/www/$hubzilladaily
echo " rsnapshot -c $snapshotconfig monthly" >> /var/www/$hubzilladaily
echo "fi" >> /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - db size...\"" >> /var/www/$hubzilladaily
echo "du -h /var/cache/rsnapshot/ | grep mysql/hubzilla" >> /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "# update" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - updating letsencrypt.sh...\"" >> /var/www/$hubzilladaily
echo "git -C /var/www/letsencrypt/ pull" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - updating hubhilla core...\"" >> /var/www/$hubzilladaily
echo "git -C /var/www/html/ pull" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - updating hubhilla addons...\"" >> /var/www/$hubzilladaily
echo "git -C /var/www/html/addon/ pull" >> /var/www/$hubzilladaily
echo "bash /var/www/html/$plugins_update" >> /var/www/$hubzilladaily
echo "chown -R www-data:www-data /var/www/html/ # make all accessable for the webserver" >> /var/www/$hubzilladaily
echo "chown root:www-data /var/www/html/.htaccess" >> /var/www/$hubzilladaily
echo "chmod 0644 /var/www/html/.htaccess # www-data can read but not write it" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - updating linux...\"" >> /var/www/$hubzilladaily
echo "apt-get -q -y update && apt-get -q -y dist-upgrade # update linux and upgrade" >> /var/www/$hubzilladaily
echo "echo \"\$(date) - Backup hubzilla and update linux finished. Rebooting...\"" >> /var/www/$hubzilladaily
echo "#" >> /var/www/$hubzilladaily
echo "reboot" >> /var/www/$hubzilladaily
if [ -z "`grep 'hubzilla-daily.sh' /etc/crontab`" ]
then
echo "30 05 * * * root /bin/bash /var/www/$hubzilladaily >> /var/www/html/hubzilla-daily.log 2>&1" >> /etc/crontab
echo "0 0 1 * * root rm /var/www/html/hubzilla-daily.log" >> /etc/crontab
fi
# This is active after either "reboot" or "/etc/init.d/cron reload"
print_info "configured cron for updates/upgrades"
}
function write_uninstall_script {
print_info "writing uninstall script..."
cat > /var/www/hubzilla-remove.sh <<END
#!/bin/sh
#
# This script removes Hubzilla.
# You might do this for a fresh start using the script.
# The script will remove (almost everything) what was installed by the script,
# all applications including hubzilla and its database.
#
# Backup the certificates of letsencrypt (you never know)
cp -a /var/www/letsencrypt/ ~/backup_le_certificats
#
# Removal
apt-get remove apache2 apache2-utils libapache2-mod-php5 php5 php-pear php5-xcache php5-curl php5-mcrypt php5-gd php5-mysql mysql-server mysql-client phpmyadmin
apt-get purge apache2 apache2-utils libapache2-mod-php5 php5 php-pear php5-xcache php5-curl php5-mcrypt php5-gd php5-mysql mysql-server mysql-client phpmyadmin
apt-get autoremove
apt-get clean
rm /etc/rsnapshot_hubzilla.conf
rm /etc/rsnapshot_hubzilla_external_device.conf
rm -R /etc/apache2/
rm -R /var/lib/mysql/
rm -R /var/www
rm -R /etc/selfhost/
# uncomment the next line if you want to remove the backups
# rm -R /var/cache/rsnapshot
nano /etc/crontab # remove entries there manually
END
chmod -x /var/www/hubzilla-remove.sh
}
########################################################################
# START OF PROGRAM
########################################################################
export PATH=/bin:/usr/bin:/sbin:/usr/sbin
check_sanity
# Read config file edited by user
configfile=hubzilla-config.txt
source $configfile
selfhostdir=/etc/selfhost
selfhostscript=selfhost-updater.sh
hubzilladaily=hubzilla-daily.sh
plugins_update=.homeinstall/plugins_update.sh
snapshotconfig=/etc/rsnapshot_hubzilla.conf
snapshotconfig_external_device=/etc/rsnapshot_hubzilla_external_device.conf
backup_mount_point=/media/hubzilla_backup
le_dir=/var/www/letsencrypt
sslconf=/etc/apache2/sites-available/default-ssl.conf
#set -x # activate debugging from here
check_config
stop_hubzilla
update_upgrade
install_curl
install_sendmail
install_apache
install_php
install_mysql
install_phpmyadmin
create_hubzilla_db
run_freedns
install_run_selfhost
ping_domain
configure_cron_freedns
configure_cron_selfhost
install_git
install_letsencrypt
configure_apache_for_https
check_https
install_hubzilla
rewrite_to_https
# install_owncloud # deprecated
install_rsnapshot
configure_cron_daily
install_cryptosetup
write_uninstall_script
#set +x # stop debugging from here

View file

@ -24,6 +24,9 @@ AddType audio/ogg .oga
# Also place auth information into REMOTE_USER for sites running
# in CGI mode.
RewriteCond %{REQUEST_URI} ^/\.well\-known/.*
RewriteRule ^(.*)$ index.php?q=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA]

View file

@ -181,6 +181,7 @@ echo "chmod done, permissions set to 777 on poller script."
# to make Hubzilla on OpenShift a more pleasant experience
echo "Changing default configuration to conserve space and autocreate a social private channel upon account registration"
cd ${OPENSHIFT_REPO_DIR}
util/config system auto_channel_create
util/config system default_permissions_role social_private
util/config system workflow_channel_next channel
util/config system expire_delivery_reports 3

135
sources/CHANGELOG Normal file
View file

@ -0,0 +1,135 @@
Hubzilla 1.3
Admin Security configuration page created which consolidates several previously hidden settings:
Communication white/black lists
Channel white/black lists
OEmbed white/black lists
Admin Profile Fields page created which manages the availability and order of standard profile fields and allows new fields to be created/managed
"Poke" module reworked - page UI updated and "poke basic" setting introduced which limits the available poke "verbs".
"Mood" module UI reworked
"profile_photo" module UI reworked
"cover_photo" module UI reworked
"new_channel" module UI reworked
"register" module UI reworked
"pubsites" module UI reworked
item-meta ("iconfig") created which implements arbitrary storage for item metadata for plugins
abook-meta ("abconfig") created which implements arbitrary storage for connection metadata for plugins
"Strict transport security header" made optional as it conflicts with some existing Apache/nginx configurations
"Hubzilla UNO" (Hubzilla with radically simplified and locked site settings) implemented as an install configuration.
.well-known directory conflict worked out to support LetsEncrypt cert ownership checks without disrupting webfinger and other internal uses of .well-known
Lots of work on 'zcards' which are self-contained HTML representations of a channel including cover photos, profile photos, and some text information
Long standing bug uncovered which failed to properly restrict the lower time limit for public feed requests
A number of fixes to "readmore" to fix page jumping
Bugfix: persons other than the channel owner who have permission to upload photos to a channel could not do so if the js_upload plugin/addon was enabled
Siteinfo incorrectly identifying secondary directory servers
Allow admin to set and lock features when UNO is configured
Atom feeds: alter how events are formatted to be compatible with GNU-social
Allow guest/visitor access to view personal calendar
Moved several more classes to "composer format" and provided an autoloader.
Bugfix: require existing password to change password
Bugfix: allow relative_date() to be translated to Polish which has more than two plural forms.
Plugin API: add "requires" keyword to module header to indicate dependent addons
ActivityStreams improvements and cleanup: photo and file activities
UI cleanup for editing profile when multiple profiles enabled
Removed the "markdown" feature as there are numerous issues and no maintainer.
Provide "footer" bbcode to ease theming of post footer content
Bugfix: install issues caused by composer code refactor and typo in postgres load file
Plugins:
keepout - "block public on steroids"
pubsubhubbub - provides PuSH support to Atom feeds, required for GNU-social federation
GNUsocial protocol - under development
Diaspora protocol - some work to ease migration to the new signing format
Diaspost - disabled; numerous issues and no maintainer
smileybutton - theme work and fixed compatibility with other jot-tools plugins
Hubzilla 1.2
Provide extra HTTP security headers (several of them).
Allow a site to disable delivery reports if disk space is limited
Regression: Wrong theme when viewing single post as non-member
Some Diaspora profile photos use relative URLs - force absolute
Add locked features to siteinfo report to aid remote debugging
Provide version compatibility checking to plugins (minversion, maxversion, and minphpversion)
Account config storage
Provide optional integrated registration and channel create form
cli utility for managing addons
issue with sharing photo "items"
cover photo manager: upload, crop, and store
cover photo widget created
rework the connections list page and provide a few management features there
fixed issue with Comanche layout definitions loaded by plugins
provide ability to separate delivery functions from item_store() and item_store_update() - some forum messages were being redelivered when cloned.
call build_sync_packet() on pdledit changes
Abstract the project name and version so these can be customised or removed
Allow hiding the ratings links on a per-site basis
db_type not present in international setup templates - was unable to choose postgres.
item_photo_menu logically divided into a) actions on the post, b) actions related to the author
bug: default channel not reset to 0 when last channel removed
create widget containing only the contact block
regression: public forums granted send stream permissions to connections
workaround Firefox's refusal to honour disabling autocomplete of passwords
regression: photo's uploaded to a channel by a guest (with file write permissions) not saved correctly.
provide mechanisms for custom .well-known handlers (needed for LetsEncrypt ownership verification)
proc_run modified to use exec() instead of proc_open() - causing issues on some PHP installations
remote delegation failure under a specific set of circumstances which we were finally able to duplicate
Delegation section of Channel Manager was missing names and contained useless notification icons.
Change "expire" channel setting to show system limit if there is one.
Regression: provide a one-click ignore of pending connection
Config to control directory keyword generation on client and server.
"Collections" renamed to "Privacy Groups", documentation improved
widget_item - allow use of page title instead of message id
Add site black/white list checking to all .well-known services
reduce incidents of screen jumping when "showmore" is activated
add oembed provider for photos
Addons:
CSS theming of pageheader plugin
xmpp addon ported from Friendica
Diaspora private mail issues after the third reply
Occasional issue with Diaspora connection requests
Add notification email to Diaspora PMs
Allow anonymising platform and version for statistics
msgfooter addon created
removed embedly plugin
sync clones after superblock addition
"keepout" plugin created
Hubzilla 1.1
Rewrote and simplified the Queue manager and delivery system
Rewrote and simplified the outer layers of the Zot protocol
Use a standard version numbering scheme in addition to the snapshot tags
Provide a channel blacklist for blocking channels with abusive or illegal content at the hub level
Make the black/white lists pluggable
Update template library
Support for letsencrypt certs in various places
Cleanup of login and register pages
Better error responses for permission denied on channel file repositories
Disabled the public stream by default for new installs (can be enabled if desired)
Cleanup of API authentication and rework the old OAuth1 stuff
Add API "status with media" support compatible with Twitter and conflicting method for GNU-social
Rework photo ActivityStreams objects to align better with ActivityStreams producers/consumers
Several minor API fixes to work better with AndStatus client
Invitation only site - experimental support added, needs more work
Fix delivery loop condition due to corrupted data which resulted in recursive upstream delivery
Provide more support for external (git) widget collections.
Extend the Queue API to 3rd-party network addons which have experienced downtime recently.
Regression: Inherited permissions were not explicitly set
Regression: "Xyz posted on your wall" notification sent when creating webpages at another channel
Regression: Custom permissions not pre-populated on channel creation with named role.
Provide "Public" string when a post can be made public, instead of "visible to default audience"
Allow hub admin to specify a default role type for the first channel created, reducing complexity
Ability for a hub admin to set feature defaults and lock them, reducing complexity
Change default expiration of delivery reports to 10 days to accomodate sites with reduced resources
Addons/Plugins:
Pageheader addon ported from Friendica
Hubwall (allow admin to send email to all accounts on this hub) created
GNU-social - queueing added
Diaspora - fixes for various failures to update profile photos, updates to queue API
Cross Domain Authenticated Chess (Andrew Manning's repository)
And... the normal "lots of bugs fixed, translations updated, and documentation improved"

View file

@ -0,0 +1,92 @@
<?php
namespace Zotlabs\Access;
class AccessList {
private $allow_cid;
private $allow_gid;
private $deny_cid;
private $deny_gid;
/* indicates if we are using the default constructor values or values that have been set explicitly. */
private $explicit;
function __construct($channel) {
if($channel) {
$this->allow_cid = $channel['channel_allow_cid'];
$this->allow_gid = $channel['channel_allow_gid'];
$this->deny_cid = $channel['channel_deny_cid'];
$this->deny_gid = $channel['channel_deny_gid'];
}
else {
$this->allow_cid = '';
$this->allow_gid = '';
$this->deny_cid = '';
$this->deny_gid = '';
}
$this->explicit = false;
}
function get_explicit() {
return $this->explicit;
}
/**
* Set AccessList from strings such as those in already
* existing stored data items
*/
function set($arr,$explicit = true) {
$this->allow_cid = $arr['allow_cid'];
$this->allow_gid = $arr['allow_gid'];
$this->deny_cid = $arr['deny_cid'];
$this->deny_gid = $arr['deny_gid'];
$this->explicit = $explicit;
}
/**
* return an array consisting of the current
* access list components where the elements
* are directly storable.
*/
function get() {
return array(
'allow_cid' => $this->allow_cid,
'allow_gid' => $this->allow_gid,
'deny_cid' => $this->deny_cid,
'deny_gid' => $this->deny_gid,
);
}
/**
* Set AccessList from arrays, such as those provided by
* acl_selector(). For convenience, a string (or non-array) input is
* assumed to be a comma-separated list and auto-converted into an array.
*/
function set_from_array($arr,$explicit = true) {
$this->allow_cid = perms2str((is_array($arr['contact_allow']))
? $arr['contact_allow'] : explode(',',$arr['contact_allow']));
$this->allow_gid = perms2str((is_array($arr['group_allow']))
? $arr['group_allow'] : explode(',',$arr['group_allow']));
$this->deny_cid = perms2str((is_array($arr['contact_deny']))
? $arr['contact_deny'] : explode(',',$arr['contact_deny']));
$this->deny_gid = perms2str((is_array($arr['group_deny']))
? $arr['group_deny'] : explode(',',$arr['group_deny']));
$this->explicit = $explicit;
}
function is_private() {
return (($this->allow_cid || $this->allow_gid || $this->deny_cid || $this->deny_gid) ? true : false);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Zotlabs\Project;
class System {
function get_platform_name() {
$a = get_app();
if(is_array($a->config) && is_array($a->config['system']) && $a->config['system']['platform_name'])
return $a->config['system']['platform_name'];
return PLATFORM_NAME;
}
function get_project_version() {
$a = get_app();
if(is_array($a->config) && is_array($a->config['system']) && $a->config['system']['hide_version'])
return '';
return RED_VERSION;
}
function get_update_version() {
$a = get_app();
if(is_array($a->config) && is_array($a->config['system']) && $a->config['system']['hide_version'])
return '';
return DB_UPDATE_VERSION;
}
function get_notify_icon() {
$a = get_app();
if(is_array($a->config) && is_array($a->config['system']) && $a->config['system']['email_notify_icon_url'])
return $a->config['system']['email_notify_icon_url'];
return z_root() . '/images/hz-white-32.png';
}
function get_site_icon() {
$a = get_app();
if(is_array($a->config) && is_array($a->config['system']) && $a->config['system']['site_icon_url'])
return $a->config['system']['site_icon_url'];
return z_root() . '/images/hz-32.png';
}
function get_server_role() {
if(UNO)
return 'basic';
return 'advanced';
}
// return the standardised version. Since we can't easily compare
// before the STD_VERSION definition was applied, we have to treat
// all prior release versions the same. You can dig through them
// with other means (such as RED_VERSION) if necessary.
function get_std_version() {
if(defined('STD_VERSION'))
return STD_VERSION;
return '0.0.0';
}
}

View file

@ -0,0 +1,212 @@
<?php
namespace Zotlabs\Storage;
use Sabre\DAV;
/**
* @brief Authentication backend class for DAV.
*
* This class also contains some data which is not necessary for authentication
* like timezone settings.
*
* @extends Sabre\DAV\Auth\Backend\AbstractBasic
*
* @link http://github.com/friendica/red
* @license http://opensource.org/licenses/mit-license.php The MIT License (MIT)
*/
class BasicAuth extends DAV\Auth\Backend\AbstractBasic {
/**
* @brief This variable holds the currently logged-in channel_address.
*
* It is used for building path in filestorage/.
*
* @var string|null
*/
protected $channel_name = null;
/**
* channel_id of the current channel of the logged-in account.
*
* @var int
*/
public $channel_id = 0;
/**
* channel_hash of the current channel of the logged-in account.
*
* @var string
*/
public $channel_hash = '';
/**
* Set in mod/cloud.php to observer_hash.
*
* @var string
*/
public $observer = '';
/**
*
* @see Browser::set_writeable()
* @var \Sabre\DAV\Browser\Plugin
*/
public $browser;
/**
* channel_id of the current visited path. Set in Directory::getDir().
*
* @var int
*/
public $owner_id = 0;
/**
* channel_name of the current visited path. Set in Directory::getDir().
*
* Used for creating the path in cloud/
*
* @var string
*/
public $owner_nick = '';
/**
* Timezone from the visiting channel's channel_timezone.
*
* Used in @ref RedBrowser
*
* @var string
*/
protected $timezone = '';
/**
* @brief Validates a username and password.
*
* Guest access is granted with the password "+++".
*
* @see \Sabre\DAV\Auth\Backend\AbstractBasic::validateUserPass
* @param string $username
* @param string $password
* @return bool
*/
protected function validateUserPass($username, $password) {
if (trim($password) === '+++') {
logger('guest: ' . $username);
return true;
}
require_once('include/auth.php');
$record = account_verify_password($username, $password);
if ($record && $record['account_default_channel']) {
$r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1",
intval($record['account_id']),
intval($record['account_default_channel'])
);
if ($r) {
return $this->setAuthenticated($r[0]);
}
}
$r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1",
dbesc($username)
);
if ($r) {
$x = q("SELECT account_flags, account_salt, account_password FROM account WHERE account_id = %d LIMIT 1",
intval($r[0]['channel_account_id'])
);
if ($x) {
// @fixme this foreach should not be needed?
foreach ($x as $record) {
if ((($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED))
&& (hash('whirlpool', $record['account_salt'] . $password) === $record['account_password'])) {
logger('password verified for ' . $username);
return $this->setAuthenticated($r[0]);
}
}
}
}
$error = 'password failed for ' . $username;
logger($error);
log_failed_login($error);
return false;
}
/**
* @brief Sets variables and session parameters after successfull authentication.
*
* @param array $r
* Array with the values for the authenticated channel.
* @return bool
*/
protected function setAuthenticated($r) {
$this->channel_name = $r['channel_address'];
$this->channel_id = $r['channel_id'];
$this->channel_hash = $this->observer = $r['channel_hash'];
$_SESSION['uid'] = $r['channel_id'];
$_SESSION['account_id'] = $r['channel_account_id'];
$_SESSION['authenticated'] = true;
return true;
}
/**
* Sets the channel_name from the currently logged-in channel.
*
* @param string $name
* The channel's name
*/
public function setCurrentUser($name) {
$this->channel_name = $name;
}
/**
* Returns information about the currently logged-in channel.
*
* If nobody is currently logged in, this method should return null.
*
* @see \Sabre\DAV\Auth\Backend\AbstractBasic::getCurrentUser
* @return string|null
*/
public function getCurrentUser() {
return $this->channel_name;
}
/**
* @brief Sets the timezone from the channel in RedBasicAuth.
*
* Set in mod/cloud.php if the channel has a timezone set.
*
* @param string $timezone
* The channel's timezone.
* @return void
*/
public function setTimezone($timezone) {
$this->timezone = $timezone;
}
/**
* @brief Returns the timezone.
*
* @return string
* Return the channel's timezone.
*/
public function getTimezone() {
return $this->timezone;
}
/**
* @brief Set browser plugin for SabreDAV.
*
* @see RedBrowser::set_writeable()
* @param \Sabre\DAV\Browser\Plugin $browser
*/
public function setBrowserPlugin($browser) {
$this->browser = $browser;
}
/**
* @brief Prints out all BasicAuth variables to logger().
*
* @return void
*/
public function log() {
logger('channel_name ' . $this->channel_name, LOGGER_DATA);
logger('channel_id ' . $this->channel_id, LOGGER_DATA);
logger('channel_hash ' . $this->channel_hash, LOGGER_DATA);
logger('observer ' . $this->observer, LOGGER_DATA);
logger('owner_id ' . $this->owner_id, LOGGER_DATA);
logger('owner_nick ' . $this->owner_nick, LOGGER_DATA);
}
}

View file

@ -0,0 +1,372 @@
<?php
namespace Zotlabs\Storage;
use Sabre\DAV;
/**
* @brief Provides a DAV frontend for the webbrowser.
*
* RedBrowser is a SabreDAV server-plugin to provide a view to the DAV storage
* for the webbrowser.
*
* @extends \Sabre\DAV\Browser\Plugin
*
* @link http://github.com/friendica/red
* @license http://opensource.org/licenses/mit-license.php The MIT License (MIT)
*/
class Browser extends DAV\Browser\Plugin {
/**
* @see set_writeable()
* @see \Sabre\DAV\Auth\Backend\BackendInterface
* @var RedBasicAuth
*/
private $auth;
/**
* @brief Constructor for RedBrowser class.
*
* $enablePost will be activated through set_writeable() in a later stage.
* At the moment the write_storage permission is only valid for the whole
* folder. No file specific permissions yet.
* @todo disable enablePost by default and only activate if permissions
* grant edit rights.
*
* Disable assets with $enableAssets = false. Should get some thumbnail views
* anyway.
*
* @param RedBasicAuth &$auth
*/
public function __construct(&$auth) {
$this->auth = $auth;
parent::__construct(true, false);
}
/**
* The DAV browser is instantiated after the auth module and directory classes
* but before we know the current directory and who the owner and observer
* are. So we add a pointer to the browser into the auth module and vice versa.
* Then when we've figured out what directory is actually being accessed, we
* call the following function to decide whether or not to show web elements
* which include writeable objects.
*
* @fixme It only disable/enable the visible parts. Not the POST handler
* which handels the actual requests when uploading files or creating folders.
*
* @todo Maybe this whole way of doing this can be solved with some
* $server->subscribeEvent().
*/
public function set_writeable() {
if (! $this->auth->owner_id) {
$this->enablePost = false;
}
if (! perm_is_allowed($this->auth->owner_id, get_observer_hash(), 'write_storage')) {
$this->enablePost = false;
} else {
$this->enablePost = true;
}
}
/**
* @brief Creates the directory listing for the given path.
*
* @param string $path which should be displayed
*/
public function generateDirectoryIndex($path) {
// (owner_id = channel_id) is visitor owner of this directory?
$is_owner = ((local_channel() && $this->auth->owner_id == local_channel()) ? true : false);
if ($this->auth->getTimezone())
date_default_timezone_set($this->auth->getTimezone());
require_once('include/conversation.php');
require_once('include/text.php');
if ($this->auth->owner_nick) {
$html = profile_tabs(get_app(), (($is_owner) ? true : false), $this->auth->owner_nick);
}
$files = $this->server->getPropertiesForPath($path, array(
'{DAV:}displayname',
'{DAV:}resourcetype',
'{DAV:}getcontenttype',
'{DAV:}getcontentlength',
'{DAV:}getlastmodified',
), 1);
$parent = $this->server->tree->getNodeForPath($path);
$parentpath = array();
// only show parent if not leaving /cloud/; TODO how to improve this?
if ($path && $path != "cloud") {
list($parentUri) = DAV\URLUtil::splitPath($path);
$fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
$parentpath['icon'] = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="' . t('parent') . '"></a>' : '';
$parentpath['path'] = $fullPath;
}
$f = array();
foreach ($files as $file) {
$ft = array();
$type = null;
// This is the current directory, we can skip it
if (rtrim($file['href'],'/') == $path) continue;
list(, $name) = DAV\URLUtil::splitPath($file['href']);
if (isset($file[200]['{DAV:}resourcetype'])) {
$type = $file[200]['{DAV:}resourcetype']->getValue();
// resourcetype can have multiple values
if (!is_array($type)) $type = array($type);
foreach ($type as $k=>$v) {
// Some name mapping is preferred
switch ($v) {
case '{DAV:}collection' :
$type[$k] = t('Collection');
break;
case '{DAV:}principal' :
$type[$k] = t('Principal');
break;
case '{urn:ietf:params:xml:ns:carddav}addressbook' :
$type[$k] = t('Addressbook');
break;
case '{urn:ietf:params:xml:ns:caldav}calendar' :
$type[$k] = t('Calendar');
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' :
$type[$k] = t('Schedule Inbox');
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' :
$type[$k] = t('Schedule Outbox');
break;
case '{http://calendarserver.org/ns/}calendar-proxy-read' :
$type[$k] = 'Proxy-Read';
break;
case '{http://calendarserver.org/ns/}calendar-proxy-write' :
$type[$k] = 'Proxy-Write';
break;
}
}
$type = implode(', ', $type);
}
// If no resourcetype was found, we attempt to use
// the contenttype property
if (!$type && isset($file[200]['{DAV:}getcontenttype'])) {
$type = $file[200]['{DAV:}getcontenttype'];
}
if (!$type) $type = t('Unknown');
$size = isset($file[200]['{DAV:}getcontentlength']) ? (int)$file[200]['{DAV:}getcontentlength'] : '';
$lastmodified = ((isset($file[200]['{DAV:}getlastmodified'])) ? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') : '');
$fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/'));
$displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name;
$displayName = $this->escapeHTML($displayName);
$type = $this->escapeHTML($type);
$icon = '';
if ($this->enableAssets) {
$node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name);
foreach (array_reverse($this->iconMap) as $class=>$iconName) {
if ($node instanceof $class) {
$icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24"></a>';
break;
}
}
}
$parentHash = '';
$owner = $this->auth->owner_id;
$splitPath = explode('/', $fullPath);
if (count($splitPath) > 3) {
for ($i = 3; $i < count($splitPath); $i++) {
$attachName = urldecode($splitPath[$i]);
$attachHash = $this->findAttachHash($owner, $parentHash, $attachName);
$parentHash = $attachHash;
}
}
$attachIcon = ""; // "<a href=\"attach/".$attachHash."\" title=\"".$displayName."\"><i class=\"icon-download\"></i></a>";
// put the array for this file together
$ft['attachId'] = $this->findAttachIdByHash($attachHash);
$ft['fileStorageUrl'] = substr($fullPath, 0, strpos($fullPath, "cloud/")) . "filestorage/" . $this->auth->getCurrentUser();
$ft['icon'] = $icon;
$ft['attachIcon'] = (($size) ? $attachIcon : '');
// @todo Should this be an item value, not a global one?
$ft['is_owner'] = $is_owner;
$ft['fullPath'] = $fullPath;
$ft['displayName'] = $displayName;
$ft['type'] = $type;
$ft['size'] = $size;
$ft['sizeFormatted'] = userReadableSize($size);
$ft['lastmodified'] = (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(), $lastmodified) : '');
$ft['iconFromType'] = getIconFromType($type);
$f[] = $ft;
}
$output = '';
if ($this->enablePost) {
$this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output));
}
$html .= replace_macros(get_markup_template('cloud.tpl'), array(
'$header' => t('Files') . ": " . $this->escapeHTML($path) . "/",
'$total' => t('Total'),
'$actionspanel' => $output,
'$shared' => t('Shared'),
'$create' => t('Create'),
'$upload' => t('Upload'),
'$is_owner' => $is_owner,
'$parentpath' => $parentpath,
'$entries' => $f,
'$name' => t('Name'),
'$type' => t('Type'),
'$size' => t('Size'),
'$lastmod' => t('Last Modified'),
'$parent' => t('parent'),
'$edit' => t('Edit'),
'$delete' => t('Delete'),
'$nick' => $this->auth->getCurrentUser()
));
$a = get_app();
$a->page['content'] = $html;
load_pdl($a);
$theme_info_file = "view/theme/" . current_theme() . "/php/theme.php";
if (file_exists($theme_info_file)){
require_once($theme_info_file);
if (function_exists(str_replace('-', '_', current_theme()) . '_init')) {
$func = str_replace('-', '_', current_theme()) . '_init';
$func($a);
}
}
construct_page($a);
}
/**
* @brief Creates a form to add new folders and upload files.
*
* @param \Sabre\DAV\INode $node
* @param string &$output
*/
public function htmlActionsPanel(DAV\INode $node, &$output) {
if (! $node instanceof DAV\ICollection)
return;
// We also know fairly certain that if an object is a non-extended
// SimpleCollection, we won't need to show the panel either.
if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
return;
// Storage and quota for the account (all channels of the owner of this directory)!
$limit = service_class_fetch($owner, 'attach_upload_limit');
$r = q("SELECT SUM(filesize) AS total FROM attach WHERE aid = %d",
intval($this->auth->channel_account_id)
);
$used = $r[0]['total'];
if ($used) {
$quotaDesc = t('You are using %1$s of your available file storage.');
$quotaDesc = sprintf($quotaDesc,
userReadableSize($used));
}
if ($limit && $used) {
$quotaDesc = t('You are using %1$s of %2$s available file storage. (%3$s&#37;)');
$quotaDesc = sprintf($quotaDesc,
userReadableSize($used),
userReadableSize($limit),
round($used / $limit, 1) * 100);
}
// prepare quota for template
$quota = array();
$quota['used'] = $used;
$quota['limit'] = $limit;
$quota['desc'] = $quotaDesc;
$quota['warning'] = ((($limit) && ((round($used / $limit, 1) * 100) >= 90)) ? t('WARNING:') : ''); // 10485760 bytes = 100MB
$output .= replace_macros(get_markup_template('cloud_actionspanel.tpl'), array(
'$folder_header' => t('Create new folder'),
'$folder_submit' => t('Create'),
'$upload_header' => t('Upload file'),
'$upload_submit' => t('Upload'),
'$quota' => $quota
));
}
/**
* This method takes a path/name of an asset and turns it into url
* suiteable for http access.
*
* @param string $assetName
* @return string
*/
protected function getAssetUrl($assetName) {
return z_root() . '/cloud/?sabreAction=asset&assetName=' . urlencode($assetName);
}
/**
* @brief Return the hash of an attachment.
*
* Given the owner, the parent folder and and attach name get the attachment
* hash.
*
* @param int $owner
* The owner_id
* @param string $hash
* The parent's folder hash
* @param string $attachName
* The name of the attachment
* @return string
*/
protected function findAttachHash($owner, $parentHash, $attachName) {
$r = q("SELECT hash FROM attach WHERE uid = %d AND folder = '%s' AND filename = '%s' ORDER BY edited DESC LIMIT 1",
intval($owner),
dbesc($parentHash),
dbesc($attachName)
);
$hash = "";
if ($r) {
foreach ($r as $rr) {
$hash = $rr['hash'];
}
}
return $hash;
}
/**
* @brief Returns an attachment's id for a given hash.
*
* This id is used to access the attachment in filestorage/
*
* @param string $attachHash
* The hash of an attachment
* @return string
*/
protected function findAttachIdByHash($attachHash) {
$r = q("SELECT id FROM attach WHERE hash = '%s' ORDER BY edited DESC LIMIT 1",
dbesc($attachHash)
);
$id = "";
if ($r) {
foreach ($r as $rr) {
$id = $rr['id'];
}
}
return $id;
}
}

View file

@ -0,0 +1,536 @@
<?php
namespace Zotlabs\Storage;
use Sabre\DAV;
/**
* @brief RedDirectory class.
*
* A class that represents a directory.
*
* @extends \Sabre\DAV\Node
* @implements \Sabre\DAV\ICollection
* @implements \Sabre\DAV\IQuota
*
* @link http://github.com/friendica/red
* @license http://opensource.org/licenses/mit-license.php The MIT License (MIT)
*/
class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota {
/**
* @brief The path inside /cloud
*
* @var string
*/
private $red_path;
private $folder_hash;
/**
* @brief The full path as seen in the browser.
* /cloud + $red_path
* @todo I think this is not used anywhere, we always strip '/cloud' and only use it in debug
* @var string
*/
private $ext_path;
private $root_dir = '';
private $auth;
/**
* @brief The real path on the filesystem.
* The actual path in store/ with the hashed names.
*
* @var string
*/
private $os_path = '';
/**
* @brief Sets up the directory node, expects a full path.
*
* @param string $ext_path a full path
* @param RedBasicAuth &$auth_plugin
*/
public function __construct($ext_path, &$auth_plugin) {
// $ext_path = urldecode($ext_path);
logger('directory ' . $ext_path, LOGGER_DATA);
$this->ext_path = $ext_path;
// remove "/cloud" from the beginning of the path
$modulename = get_app()->module;
$this->red_path = ((strpos($ext_path, '/' . $modulename) === 0) ? substr($ext_path, strlen($modulename) + 1) : $ext_path);
if (! $this->red_path) {
$this->red_path = '/';
}
$this->auth = $auth_plugin;
$this->folder_hash = '';
$this->getDir();
if ($this->auth->browser) {
$this->auth->browser->set_writeable();
}
}
private function log() {
logger('ext_path ' . $this->ext_path, LOGGER_DATA);
logger('os_path ' . $this->os_path, LOGGER_DATA);
logger('red_path ' . $this->red_path, LOGGER_DATA);
}
/**
* @brief Returns an array with all the child nodes.
*
* @throw \Sabre\DAV\Exception\Forbidden
* @return array \Sabre\DAV\INode[]
*/
public function getChildren() {
logger('children for ' . $this->ext_path, LOGGER_DATA);
$this->log();
if (get_config('system', 'block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
if (($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'view_storage'))) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
$contents = RedCollectionData($this->red_path, $this->auth);
return $contents;
}
/**
* @brief Returns a child by name.
*
*
* @throw \Sabre\DAV\Exception\Forbidden
* @throw \Sabre\DAV\Exception\NotFound
* @param string $name
*/
public function getChild($name) {
logger($name, LOGGER_DATA);
if (get_config('system', 'block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
if (($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'view_storage'))) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
$modulename = get_app()->module;
if ($this->red_path === '/' && $name === $modulename) {
return new Directory('/' . $modulename, $this->auth);
}
$x = RedFileData($this->ext_path . '/' . $name, $this->auth);
if ($x) {
return $x;
}
throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found.');
}
/**
* @brief Returns the name of the directory.
*
* @return string
*/
public function getName() {
//logger(basename($this->red_path), LOGGER_DATA);
return (basename($this->red_path));
}
/**
* @brief Renames the directory.
*
* @todo handle duplicate directory name
*
* @throw \Sabre\DAV\Exception\Forbidden
* @param string $name The new name of the directory.
* @return void
*/
public function setName($name) {
logger('old name ' . basename($this->red_path) . ' -> ' . $name, LOGGER_DATA);
if ((! $name) || (! $this->auth->owner_id)) {
logger('permission denied ' . $name);
throw new DAV\Exception\Forbidden('Permission denied.');
}
if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) {
logger('permission denied '. $name);
throw new DAV\Exception\Forbidden('Permission denied.');
}
list($parent_path, ) = DAV\URLUtil::splitPath($this->red_path);
$new_path = $parent_path . '/' . $name;
$r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND uid = %d",
dbesc($name),
dbesc($this->folder_hash),
intval($this->auth->owner_id)
);
$this->red_path = $new_path;
}
/**
* @brief Creates a new file in the directory.
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* @throw \Sabre\DAV\Exception\Forbidden
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string ETag
*/
public function createFile($name, $data = null) {
logger($name, LOGGER_DEBUG);
if (! $this->auth->owner_id) {
logger('permission denied ' . $name);
throw new DAV\Exception\Forbidden('Permission denied.');
}
if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) {
logger('permission denied ' . $name);
throw new DAV\Exception\Forbidden('Permission denied.');
}
$mimetype = z_mime_content_type($name);
$c = q("SELECT * FROM channel WHERE channel_id = %d AND channel_removed = 0 LIMIT 1",
intval($this->auth->owner_id)
);
if (! $c) {
logger('no channel');
throw new DAV\Exception\Forbidden('Permission denied.');
}
$filesize = 0;
$hash = random_string();
$f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $hash;
$direct = null;
if($this->folder_hash) {
$r = q("select * from attach where hash = '%s' and is_dir = 1 and uid = %d limit 1",
dbesc($this->folder_hash),
intval($c[0]['channel_id'])
);
if($r)
$direct = $r[0];
}
if(($direct) && (($direct['allow_cid']) || ($direct['allow_gid']) || ($direct['deny_cid']) || ($direct['deny_gid']))) {
$allow_cid = $direct['allow_cid'];
$allow_gid = $direct['allow_gid'];
$deny_cid = $direct['deny_cid'];
$deny_gid = $direct['deny_gid'];
}
else {
$allow_cid = $c[0]['channel_allow_cid'];
$allow_gid = $c[0]['channel_allow_gid'];
$deny_cid = $c[0]['channel_deny_cid'];
$deny_gid = $c[0]['channel_deny_gid'];
}
$r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, os_storage, filetype, filesize, revision, is_photo, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid )
VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
intval($c[0]['channel_account_id']),
intval($c[0]['channel_id']),
dbesc($hash),
dbesc($this->auth->observer),
dbesc($name),
dbesc($this->folder_hash),
intval(1),
dbesc($mimetype),
intval($filesize),
intval(0),
intval($is_photo),
dbesc($f),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc($allow_cid),
dbesc($allow_gid),
dbesc($deny_cid),
dbesc($deny_gid)
);
// returns the number of bytes that were written to the file, or FALSE on failure
$size = file_put_contents($f, $data);
// delete attach entry if file_put_contents() failed
if ($size === false) {
logger('file_put_contents() failed to ' . $f);
attach_delete($c[0]['channel_id'], $hash);
return;
}
// returns now
$edited = datetime_convert();
$is_photo = 0;
$x = @getimagesize($f);
logger('getimagesize: ' . print_r($x,true), LOGGER_DATA);
if(($x) && ($x[2] === IMAGETYPE_GIF || $x[2] === IMAGETYPE_JPEG || $x[2] === IMAGETYPE_PNG)) {
$is_photo = 1;
}
// updates entry with filesize and timestamp
$d = q("UPDATE attach SET filesize = '%s', is_photo = %d, edited = '%s' WHERE hash = '%s' AND uid = %d",
dbesc($size),
intval($is_photo),
dbesc($edited),
dbesc($hash),
intval($c[0]['channel_id'])
);
// update the folder's lastmodified timestamp
$e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d",
dbesc($edited),
dbesc($this->folder_hash),
intval($c[0]['channel_id'])
);
$maxfilesize = get_config('system', 'maxfilesize');
if (($maxfilesize) && ($size > $maxfilesize)) {
attach_delete($c[0]['channel_id'], $hash);
return;
}
// check against service class quota
$limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit');
if ($limit !== false) {
$x = q("SELECT SUM(filesize) AS total FROM attach WHERE aid = %d ",
intval($c[0]['channel_account_id'])
);
if (($x) && ($x[0]['total'] + $size > $limit)) {
logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit);
attach_delete($c[0]['channel_id'], $hash);
return;
}
}
if($is_photo) {
$album = '';
if($this->folder_hash) {
$f1 = q("select filename from attach WHERE hash = '%s' AND uid = %d",
dbesc($this->folder_hash),
intval($c[0]['channel_id'])
);
if($f1)
$album = $f1[0]['filename'];
}
require_once('include/photos.php');
$args = array( 'resource_id' => $hash, 'album' => $album, 'os_path' => $f, 'filename' => $name, 'getimagesize' => $x, 'directory' => $direct);
$p = photo_upload($c[0],get_app()->get_observer(),$args);
}
}
/**
* @brief Creates a new subdirectory.
*
* @param string $name the directory to create
* @return void
*/
public function createDirectory($name) {
logger($name, LOGGER_DEBUG);
if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
$r = q("SELECT * FROM channel WHERE channel_id = %d AND channel_removed = 0 LIMIT 1",
intval($this->auth->owner_id)
);
if ($r) {
$result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash));
if (! $result['success']) {
logger('error ' . print_r($result, true), LOGGER_DEBUG);
}
}
}
/**
* @brief delete directory
*/
public function delete() {
logger('delete file ' . basename($this->red_path), LOGGER_DEBUG);
if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
if ($this->auth->owner_id !== $this->auth->channel_id) {
if (($this->auth->observer !== $this->data['creator']) || intval($this->data['is_dir'])) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
}
attach_delete($this->auth->owner_id, $this->folder_hash);
}
/**
* @brief Checks if a child exists.
*
* @param string $name
* The name to check if it exists.
* @return boolean
*/
public function childExists($name) {
// On /cloud we show a list of available channels.
// @todo what happens if no channels are available?
$modulename = get_app()->module;
if ($this->red_path === '/' && $name === $modulename) {
//logger('We are at ' $modulename . ' show a channel list', LOGGER_DEBUG);
return true;
}
$x = RedFileData($this->ext_path . '/' . $name, $this->auth, true);
//logger('RedFileData returns: ' . print_r($x, true), LOGGER_DATA);
if ($x)
return true;
return false;
}
/**
* @todo add description of what this function does.
*
* @throw \Sabre\DAV\Exception\NotFound
* @return void
*/
function getDir() {
logger('GetDir: ' . $this->ext_path, LOGGER_DEBUG);
$this->auth->log();
$modulename = get_app()->module;
$file = $this->ext_path;
$x = strpos($file, '/' . $modulename);
if ($x === 0) {
$file = substr($file, strlen($modulename) + 1);
}
if ((! $file) || ($file === '/')) {
return;
}
$file = trim($file, '/');
$path_arr = explode('/', $file);
if (! $path_arr)
return;
logger('paths: ' . print_r($path_arr, true), LOGGER_DATA);
$channel_name = $path_arr[0];
$r = q("SELECT channel_id FROM channel WHERE channel_address = '%s' AND channel_removed = 0 LIMIT 1",
dbesc($channel_name)
);
if (! $r) {
throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found.');
}
$channel_id = $r[0]['channel_id'];
$this->auth->owner_id = $channel_id;
$this->auth->owner_nick = $channel_name;
$path = '/' . $channel_name;
$folder = '';
$os_path = '';
for ($x = 1; $x < count($path_arr); $x++) {
$r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0",
dbesc($folder),
dbesc($path_arr[$x]),
intval($channel_id)
);
if ($r && intval($r[0]['is_dir'])) {
$folder = $r[0]['hash'];
if (strlen($os_path))
$os_path .= '/';
$os_path .= $folder;
$path = $path . '/' . $r[0]['filename'];
}
}
$this->folder_hash = $folder;
$this->os_path = $os_path;
}
/**
* @brief Returns the last modification time for the directory, as a UNIX
* timestamp.
*
* It looks for the last edited file in the folder. If it is an empty folder
* it returns the lastmodified time of the folder itself, to prevent zero
* timestamps.
*
* @return int last modification time in UNIX timestamp
*/
public function getLastModified() {
$r = q("SELECT edited FROM attach WHERE folder = '%s' AND uid = %d ORDER BY edited DESC LIMIT 1",
dbesc($this->folder_hash),
intval($this->auth->owner_id)
);
if (! $r) {
$r = q("SELECT edited FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1",
dbesc($this->folder_hash),
intval($this->auth->owner_id)
);
if (! $r)
return '';
}
return datetime_convert('UTC', 'UTC', $r[0]['edited'], 'U');
}
/**
* @brief Return quota usage.
*
* @fixme Should guests relly see the used/free values from filesystem of the
* complete store directory?
*
* @return array with used and free values in bytes.
*/
public function getQuotaInfo() {
// values from the filesystem of the complete <i>store/</i> directory
$limit = disk_total_space('store');
$free = disk_free_space('store');
if ($this->auth->owner_id) {
$c = q("select * from channel where channel_id = %d and channel_removed = 0 limit 1",
intval($this->auth->owner_id)
);
$ulimit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit');
$limit = (($ulimit) ? $ulimit : $limit);
$x = q("select sum(filesize) as total from attach where aid = %d",
intval($c[0]['channel_account_id'])
);
$free = (($x) ? $limit - $x[0]['total'] : 0);
}
return array(
$limit - $free,
$free
);
}
}

View file

@ -0,0 +1,322 @@
<?php
namespace Zotlabs\Storage;
use Sabre\DAV;
/**
* @brief This class represents a file in DAV.
*
* It provides all functions to work with files in Red's cloud through DAV protocol.
*
* @extends \Sabre\DAV\Node
* @implements \Sabre\DAV\IFile
*
* @link http://github.com/friendica/red
* @license http://opensource.org/licenses/mit-license.php The MIT License (MIT)
*/
class File extends DAV\Node implements DAV\IFile {
/**
* The file from attach table.
*
* @var array
* data
* flags
* filename (string)
* filetype (string)
*/
private $data;
/**
* @see \Sabre\DAV\Auth\Backend\BackendInterface
* @var \RedMatrix\RedDAV\RedBasicAuth
*/
private $auth;
/**
* @var string
*/
private $name;
/**
* Sets up the node, expects a full path name.
*
* @param string $name
* @param array $data from attach table
* @param &$auth
*/
public function __construct($name, $data, &$auth) {
$this->name = $name;
$this->data = $data;
$this->auth = $auth;
logger(print_r($this->data, true), LOGGER_DATA);
}
/**
* @brief Returns the name of the file.
*
* @return string
*/
public function getName() {
//logger(basename($this->name), LOGGER_DATA);
return basename($this->name);
}
/**
* @brief Renames the file.
*
* @throw Sabre\DAV\Exception\Forbidden
* @param string $name The new name of the file.
* @return void
*/
public function setName($newName) {
logger('old name ' . basename($this->name) . ' -> ' . $newName, LOGGER_DATA);
if ((! $newName) || (! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) {
logger('permission denied '. $newName);
throw new DAV\Exception\Forbidden('Permission denied.');
}
$newName = str_replace('/', '%2F', $newName);
$r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND id = %d",
dbesc($newName),
dbesc($this->data['hash']),
intval($this->data['id'])
);
}
/**
* @brief Updates the data of the file.
*
* @param resource $data
* @return void
*/
public function put($data) {
logger('put file: ' . basename($this->name), LOGGER_DEBUG);
$size = 0;
// @todo only 3 values are needed
$c = q("SELECT * FROM channel WHERE channel_id = %d AND channel_removed = 0 LIMIT 1",
intval($this->auth->owner_id)
);
$is_photo = false;
$album = '';
$r = q("SELECT flags, folder, os_storage, filename, is_photo FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1",
dbesc($this->data['hash']),
intval($c[0]['channel_id'])
);
if ($r) {
if (intval($r[0]['os_storage'])) {
$d = q("select folder, data from attach where hash = '%s' and uid = %d limit 1",
dbesc($this->data['hash']),
intval($c[0]['channel_id'])
);
if($d) {
if($d[0]['folder']) {
$f1 = q("select * from attach where is_dir = 1 and hash = '%s' and uid = %d limit 1",
dbesc($d[0]['folder']),
intval($c[0]['channel_id'])
);
if($f1) {
$album = $f1[0]['filename'];
$direct = $f1[0];
}
}
$fname = dbunescbin($d[0]['data']);
if(strpos($fname,'store') === false)
$f = 'store/' . $this->auth->owner_nick . '/' . $fname ;
else
$f = $fname;
// @todo check return value and set $size directly
@file_put_contents($f, $data);
$size = @filesize($f);
logger('filename: ' . $f . ' size: ' . $size, LOGGER_DEBUG);
}
$gis = @getimagesize($f);
logger('getimagesize: ' . print_r($gis,true), LOGGER_DATA);
if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG)) {
$is_photo = 1;
}
}
else {
// this shouldn't happen any more
$r = q("UPDATE attach SET data = '%s' WHERE hash = '%s' AND uid = %d",
dbescbin(stream_get_contents($data)),
dbesc($this->data['hash']),
intval($this->data['uid'])
);
$r = q("SELECT length(data) AS fsize FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1",
dbesc($this->data['hash']),
intval($this->data['uid'])
);
if ($r) {
$size = $r[0]['fsize'];
}
}
}
// returns now()
$edited = datetime_convert();
$d = q("UPDATE attach SET filesize = '%s', is_photo = %d, edited = '%s' WHERE hash = '%s' AND uid = %d",
dbesc($size),
intval($is_photo),
dbesc($edited),
dbesc($this->data['hash']),
intval($c[0]['channel_id'])
);
if($is_photo) {
require_once('include/photos.php');
$args = array( 'resource_id' => $this->data['hash'], 'album' => $album, 'os_path' => $f, 'filename' => $r[0]['filename'], 'getimagesize' => $gis, 'directory' => $direct );
$p = photo_upload($c[0],get_app()->get_observer(),$args);
}
// update the folder's lastmodified timestamp
$e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d",
dbesc($edited),
dbesc($r[0]['folder']),
intval($c[0]['channel_id'])
);
// @todo do we really want to remove the whole file if an update fails
// because of maxfilesize or quota?
// There is an Exception "InsufficientStorage" or "PaymentRequired" for
// our service class from SabreDAV we could use.
$maxfilesize = get_config('system', 'maxfilesize');
if (($maxfilesize) && ($size > $maxfilesize)) {
attach_delete($c[0]['channel_id'], $this->data['hash']);
return;
}
$limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit');
if ($limit !== false) {
$x = q("select sum(filesize) as total from attach where aid = %d ",
intval($c[0]['channel_account_id'])
);
if (($x) && ($x[0]['total'] + $size > $limit)) {
logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit);
attach_delete($c[0]['channel_id'], $this->data['hash']);
return;
}
}
}
/**
* @brief Returns the raw data.
*
* @return string
*/
public function get() {
logger('get file ' . basename($this->name), LOGGER_DEBUG);
logger('os_path: ' . $this->os_path, LOGGER_DATA);
$r = q("SELECT data, flags, os_storage, filename, filetype FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1",
dbesc($this->data['hash']),
intval($this->data['uid'])
);
if ($r) {
// @todo this should be a global definition
$unsafe_types = array('text/html', 'text/css', 'application/javascript');
if (in_array($r[0]['filetype'], $unsafe_types)) {
header('Content-disposition: attachment; filename="' . $r[0]['filename'] . '"');
header('Content-type: text/plain');
}
if (intval($r[0]['os_storage'])) {
$x = dbunescbin($r[0]['data']);
if(strpos($x,'store') === false)
$f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $x;
else
$f = $x;
return fopen($f, 'rb');
}
return dbunescbin($r[0]['data']);
}
}
/**
* @brief Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file.
* If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined.
*
* @return null|string
*/
public function getETag() {
$ret = null;
if ($this->data['hash']) {
$ret = '"' . $this->data['hash'] . '"';
}
return $ret;
}
/**
* @brief Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return mixed
*/
public function getContentType() {
// @todo this should be a global definition.
$unsafe_types = array('text/html', 'text/css', 'application/javascript');
if (in_array($this->data['filetype'], $unsafe_types)) {
return 'text/plain';
}
return $this->data['filetype'];
}
/**
* @brief Returns the size of the node, in bytes.
*
* @return int
* filesize in bytes
*/
public function getSize() {
return $this->data['filesize'];
}
/**
* @brief Returns the last modification time for the file, as a unix
* timestamp.
*
* @return int last modification time in UNIX timestamp
*/
public function getLastModified() {
return datetime_convert('UTC', 'UTC', $this->data['edited'], 'U');
}
/**
* @brief Delete the file.
*
* This method checks the permissions and then calls attach_delete() function
* to actually remove the file.
*
* @throw \Sabre\DAV\Exception\Forbidden
*/
public function delete() {
logger('delete file ' . basename($this->name), LOGGER_DEBUG);
if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
if ($this->auth->owner_id !== $this->auth->channel_id) {
if (($this->auth->observer !== $this->data['creator']) || intval($this->data['is_dir'])) {
throw new DAV\Exception\Forbidden('Permission denied.');
}
}
attach_delete($this->auth->owner_id, $this->data['hash']);
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Zotlabs\Web;
class HttpMeta {
private $vars = null;
private $og = null;
function __construct() {
$this->vars = array();
$this->og = array();
}
function set($property,$value) {
if(strpos($property,'og:') === 0)
$this->og[$property] = $value;
else
$this->vars[$property] = $value;
}
function check_required() {
if(
($this->og)
&& array_key_exists('og:title',$this->og)
&& array_key_exists('og:type', $this->og)
&& array_key_exists('og:image',$this->og)
&& array_key_exists('og:url', $this->og)
)
return true;
return false;
}
function get_field($field) {
if(strpos($field,'og:') === 0)
$arr = $this->og;
else
$arr = $this->vars;
if($arr && array_key_exists($field,$arr) && $arr[$field])
return $arr[$field];
return false;
}
function get() {
$o = '';
if($this->vars) {
foreach($this->vars as $k => $v) {
$o .= '<meta property="' . $k . '" content="' . urlencode($v) . '" />' . "\r\n" ;
}
}
if($this->check_required()) {
foreach($this->og as $k => $v) {
$o .= '<meta property="' . $k . '" content="' . urlencode($v) . '" />' . "\r\n" ;
}
}
if($o)
return "\r\n" . $o;
return $o;
}
}

View file

@ -0,0 +1,199 @@
<?php
namespace Zotlabs\Web;
class Router {
function __construct(&$a) {
/**
*
* We have already parsed the server path into $a->argc and $a->argv
*
* $a->argv[0] is our module name. We will load the file mod/{$a->argv[0]}.php
* and use it for handling our URL request.
* The module file contains a few functions that we call in various circumstances
* and in the following order:
*
* "module"_init
* "module"_post (only called if there are $_POST variables)
* "module"_content - the string return of this function contains our page body
*
* Modules which emit other serialisations besides HTML (XML,JSON, etc.) should do
* so within the module init and/or post functions and then invoke killme() to terminate
* further processing.
*/
if(strlen($a->module)) {
/**
*
* We will always have a module name.
* First see if we have a plugin which is masquerading as a module.
*
*/
if(is_array($a->plugins) && in_array($a->module,$a->plugins) && file_exists("addon/{$a->module}/{$a->module}.php")) {
include_once("addon/{$a->module}/{$a->module}.php");
if(function_exists($a->module . '_module'))
$a->module_loaded = true;
}
if((strpos($a->module,'admin') === 0) && (! is_site_admin())) {
$a->module_loaded = false;
notice( t('Permission denied.') . EOL);
goaway(z_root());
}
/**
* If the site has a custom module to over-ride the standard module, use it.
* Otherwise, look for the standard program module in the 'mod' directory
*/
if(! $a->module_loaded) {
if(file_exists("mod/site/{$a->module}.php")) {
include_once("mod/site/{$a->module}.php");
$a->module_loaded = true;
}
elseif(file_exists("mod/{$a->module}.php")) {
include_once("mod/{$a->module}.php");
$a->module_loaded = true;
}
}
/**
* This provides a place for plugins to register module handlers which don't otherwise exist on the system.
* If the plugin sets 'installed' to true we won't throw a 404 error for the specified module even if
* there is no specific module file or matching plugin name.
* The plugin should catch at least one of the module hooks for this URL.
*/
$x = array('module' => $a->module, 'installed' => false);
call_hooks('module_loaded', $x);
if($x['installed'])
$a->module_loaded = true;
/**
* The URL provided does not resolve to a valid module.
*
* On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
* We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
* we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page
* this will often succeed and eventually do the right thing.
*
* Otherwise we are going to emit a 404 not found.
*/
if(! $a->module_loaded) {
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if((x($_SERVER, 'QUERY_STRING')) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
killme();
}
if((x($_SERVER, 'QUERY_STRING')) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && $a->config['system']['dreamhost_error_hack']) {
logger('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']);
goaway($a->get_baseurl() . $_SERVER['REQUEST_URI']);
}
logger('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], LOGGER_DEBUG);
header($_SERVER['SERVER_PROTOCOL'] . ' 404 ' . t('Not Found'));
$tpl = get_markup_template('404.tpl');
$a->page['content'] = replace_macros($tpl, array(
'$message' => t('Page not found.')
));
// pretend this is a module so it will initialise the theme
$a->module = '404';
$a->module_loaded = true;
}
}
}
function Dispatch(&$a) {
/**
* Call module functions
*/
if($a->module_loaded) {
$a->page['page_title'] = $a->module;
$placeholder = '';
/**
* No theme has been specified when calling the module_init functions
* For this reason, please restrict the use of templates to those which
* do not provide any presentation details - as themes will not be able
* to over-ride them.
*/
if(function_exists($a->module . '_init')) {
$arr = array('init' => true, 'replace' => false);
call_hooks($a->module . '_mod_init', $arr);
if(! $arr['replace']) {
$func = $a->module . '_init';
$func($a);
}
}
/**
* Do all theme initialiasion here before calling any additional module functions.
* The module_init function may have changed the theme.
* Additionally any page with a Comanche template may alter the theme.
* So we'll check for those now.
*/
/**
* In case a page has overloaded a module, see if we already have a layout defined
* otherwise, if a PDL file exists for this module, use it
* The member may have also created a customised PDL that's stored in the config
*/
load_pdl($a);
/**
* load current theme info
*/
$theme_info_file = 'view/theme/' . current_theme() . '/php/theme.php';
if (file_exists($theme_info_file)){
require_once($theme_info_file);
}
if(function_exists(str_replace('-', '_', current_theme()) . '_init')) {
$func = str_replace('-', '_', current_theme()) . '_init';
$func($a);
}
elseif (x($a->theme_info, 'extends') && file_exists('view/theme/' . $a->theme_info['extends'] . '/php/theme.php')) {
require_once('view/theme/' . $a->theme_info['extends'] . '/php/theme.php');
if(function_exists(str_replace('-', '_', $a->theme_info['extends']) . '_init')) {
$func = str_replace('-', '_', $a->theme_info['extends']) . '_init';
$func($a);
}
}
if(($_SERVER['REQUEST_METHOD'] === 'POST') && (! $a->error)
&& (function_exists($a->module . '_post'))
&& (! x($_POST, 'auth-params'))) {
call_hooks($a->module . '_mod_post', $_POST);
$func = $a->module . '_post';
$func($a);
}
if((! $a->error) && (function_exists($a->module . '_content'))) {
$arr = array('content' => $a->page['content'], 'replace' => false);
call_hooks($a->module . '_mod_content', $arr);
$a->page['content'] = $arr['content'];
if(! $arr['replace']) {
$func = $a->module . '_content';
$arr = array('content' => $func($a));
}
call_hooks($a->module . '_mod_aftercontent', $arr);
$a->page['content'] .= $arr['content'];
}
}
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Zotlabs\Zot;
class DReport {
private $location;
private $sender;
private $recipient;
private $message_id;
private $status;
private $date;
function __construct($location,$sender,$recipient,$message_id,$status = 'deliver') {
$this->location = $location;
$this->sender = $sender;
$this->recipient = $recipient;
$this->message_id = $message_id;
$this->status = $status;
$this->date = datetime_convert();
}
function update($status) {
$this->status = $status;
$this->date = datetime_convert();
}
function addto_recipient($name) {
$this->recipient = $this->recipient . ' ' . $name;
}
function addto_update($status) {
$this->status = $this->status . ' ' . $status;
}
function set($arr) {
$this->location = $arr['location'];
$this->sender = $arr['sender'];
$this->recipient = $arr['recipient'];
$this->message_id = $arr['message_id'];
$this->status = $arr['status'];
$this->date = $arr['date'];
}
function get() {
return array(
'location' => $this->location,
'sender' => $this->sender,
'recipient' => $this->recipient,
'message_id' => $this->message_id,
'status' => $this->status,
'date' => $this->date
);
}
}

View file

@ -41,9 +41,10 @@ class Receiver {
if(! $this->messagetype)
$this->error = true;
$this->sender = ((array_key_exists('sender',$this->data)) ? $this->data['sender'] : null);
$this->recipients = ((array_key_exists('recipients',$this->data)) ? $this->data['recipients'] : null);
if($this->data) {
$this->sender = ((array_key_exists('sender',$this->data)) ? $this->data['sender'] : null);
$this->recipients = ((array_key_exists('recipients',$this->data)) ? $this->data['recipients'] : null);
}
if($this->sender)
$this->ValidateSender();

View file

@ -2,9 +2,6 @@
namespace Zotlabs\Zot;
require_once('Zotlabs/Zot/IHandler.php');
class ZotHandler implements IHandler {
function Ping() {

View file

@ -3,8 +3,8 @@
/**
* Name: bookmarker
* Description: replace #^ with bookmark icon
* Version: 1.0
* Description: Replace #^ with a bookmark icon. Font awesome is used for Redbasic and derived themes. A neutral dark grey PNG file is used for other themes.
* Version: 1.1
* Author: Mike Macgirvin <mike@zothub.com>
* Maintainer: Mike Macgirvin <mike@macgirvin.com>
*
@ -28,14 +28,16 @@ function bookmarker_prepare_body(&$a,&$b) {
if(! strpos($b['html'],'bookmark-identifier'))
return;
if(! function_exists('redbasic_init'))
return;
if(function_exists('redbasic_init') || get_app()->theme_info['extends'] == 'redbasic')
$bookmarkicon = '<i class="icon-bookmark"></i>';
else
$bookmarkicon = '<img src="addon/bookmarker/bookmarker.png" width="19px" height="20px" alt="#^" />';
$id = $b['item']['id'];
if(local_channel())
$link = '<a class="fakelink" onclick="itemBookmark(' . $id . '); return false;" title="' . t('Save Bookmarks') . '" href="#"><i class="icon-bookmark"></i></a> ';
$link = '<a class="fakelink" onclick="itemBookmark(' . $id . '); return false;" title="' . t('Save Bookmarks') . '" href="#">'. $bookmarkicon . '</a> ';
else
$link = '<i class="icon-bookmark"></i></a> ';
$link = $bookmarkicon . '</a> ';
$b['html'] = str_replace('<span class="bookmark-identifier">#^</span>',$link,$b['html']);

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

View file

@ -362,8 +362,10 @@ function diaspora_process_outbound(&$a, &$arr) {
}
elseif($arr['top_level_post']) {
$qi = diaspora_send_status($target_item,$arr['channel'],$contact);
if($qi)
$arr['queued'][] = $qi;
if($qi) {
foreach($qi as $q)
$arr['queued'][] = $q;
}
continue;
}
}
@ -394,8 +396,10 @@ function diaspora_process_outbound(&$a, &$arr) {
if(perm_is_allowed($arr['channel'],'','view_stream')) {
logger('delivery: diaspora status: ' . $loc);
$qi = diaspora_send_status($target_item,$arr['channel'],$contact,true);
if($qi)
$arr['queued'][] = $qi;
if($qi) {
foreach($qi as $q)
$arr['queued'][] = $q;
}
return;
}
}
@ -889,7 +893,7 @@ function diaspora_request($importer,$xml) {
// End FIXME
$role = get_pconfig($channel['channel_id'],'system','permissions_role');
$role = get_pconfig($importer['channel_id'],'system','permissions_role');
if($role) {
$x = get_role_perms($role);
if($x['perms_auto'])
@ -938,7 +942,6 @@ function diaspora_request($importer,$xml) {
'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'],
));
if($default_perms) {
// Send back a sharing notification to them
$x = diaspora_share($importer,$new_connection[0]);
@ -956,6 +959,11 @@ function diaspora_request($importer,$xml) {
unset($clone['abook_id']);
unset($clone['abook_account']);
unset($clone['abook_channel']);
$abconfig = load_abconfig($importer['channel_hash'],$clone['abook_xchan']);
if($abconfig)
$clone['abconfig'] = $abconfig;
build_sync_packet($importer['channel_id'], array('abook' => array($clone)));
@ -2695,12 +2703,14 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['channel_prvkey'],$contact['xchan_pubkey'],$public_batch)));
$qi = diaspora_queue($owner,$contact,$slap,$public_batch,$item['mid']);
$qi = array(diaspora_queue($owner,$contact,$slap,$public_batch,$item['mid']));
// logger('diaspora_send_status: guid: '.$item['mid'].' result '.$return_code, LOGGER_DEBUG);
if(count($images)) {
diaspora_send_images($item,$owner,$contact,$images,$public_batch,$item['mid']);
$qim = diaspora_send_images($item,$owner,$contact,$images,$public_batch,$item['mid']);
if($qim)
$qi = array_merge($qi,$qim);
}
return $qi;
@ -2761,6 +2771,8 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals
return;
$mysite = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://') + 3) . '/photo';
$qi = array();
$tpl = get_markup_template('diaspora_photo.tpl','addon/diaspora');
foreach($images as $image) {
if(! stristr($image['path'],$mysite))
@ -2785,13 +2797,14 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals
'$created_at' => xmlify(datetime_convert('UTC','UTC',$r[0]['created'],'Y-m-d H:i:s \U\T\C'))
));
logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
$slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['channel_prvkey'],$contact['xchan_pubkey'],$public_batch)));
return(diaspora_queue($owner,$contact,$slap,$public_batch,$item['mid']));
$qi[] = diaspora_queue($owner,$contact,$slap,$public_batch,$item['mid']);
}
return $qi;
}
function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
@ -2848,9 +2861,9 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
$meta = $diaspora_meta;
}
$signed_text = $meta['signed_text'];
$authorsig = $meta['signature'];
$signer = $meta['signer'];
$text = $meta['body'];
$authorsig = $meta['signature'];
$signer = $meta['signer'];
$text = $meta['body'];
}
else {
$text = bb2diaspora_itembody($item);
@ -2858,7 +2871,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
// sign it
if($like)
$signed_text = $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $positive . ';' . $myaddr;
$signed_text = $positive . ';' . $item['mid'] . ';' . $target_type . ';' . $parent['mid'] . ';' . $myaddr;
else
$signed_text = $item['mid'] . ';' . $parent['mid'] . ';' . $text . ';' . $myaddr;
@ -3445,8 +3458,6 @@ function diaspora_feature_settings_post(&$a,&$b) {
function diaspora_feature_settings(&$a,&$s) {
$dspr_allowed = get_pconfig(local_channel(),'system','diaspora_allowed');
if($dspr_allowed === false)
$dspr_allowed = get_config('diaspora','allowed');
$pubcomments = get_pconfig(local_channel(),'system','diaspora_public_comments');
if($pubcomments === false)
$pubcomments = 1;

View file

@ -0,0 +1,87 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C)
# This file is distributed under the same license as the PACKAGE package.
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-02-05 00:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: p.php:46 diaspora.php:2679 diaspora.php:2690
msgid "$projectname"
msgstr ""
#: diaspora.php:2277
msgid "photo"
msgstr ""
#: diaspora.php:2277
msgid "status"
msgstr ""
#: diaspora.php:2306
#, php-format
msgid "%1$s likes %2$s's %3$s"
msgstr ""
#: diaspora.php:2639 diaspora.php:2651
msgid "Please choose"
msgstr ""
#: diaspora.php:2641
msgid "Agree"
msgstr ""
#: diaspora.php:2643
msgid "Disagree"
msgstr ""
#: diaspora.php:2645
msgid "Abstain"
msgstr ""
#: diaspora.php:2653
msgid "I will attend"
msgstr ""
#: diaspora.php:2655
msgid "I will not attend"
msgstr ""
#: diaspora.php:2657
msgid "I may attend"
msgstr ""
#: diaspora.php:3441
msgid "Diaspora Protocol Settings updated."
msgstr ""
#: diaspora.php:3456
msgid "Enable the (experimental) Diaspora protocol for this channel"
msgstr ""
#: diaspora.php:3460
msgid "Allow any Diaspora member to comment on your public posts"
msgstr ""
#: diaspora.php:3464
msgid "Prevent your hashtags from being redirected to other sites"
msgstr ""
#: diaspora.php:3469
msgid "Diaspora Protocol Settings"
msgstr ""
#: diaspora.php:3469
msgid "Submit"
msgstr ""

View file

@ -0,0 +1,20 @@
<?php
;
$a->strings["\$projectname"] = "\$projectname";
$a->strings["photo"] = "foto";
$a->strings["status"] = "status";
$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s vindt %2\$s's %3\$s leuk";
$a->strings["Please choose"] = "Maak een keuze";
$a->strings["Agree"] = "Eens";
$a->strings["Disagree"] = "Oneens";
$a->strings["Abstain"] = "Onthouding";
$a->strings["I will attend"] = "Aanwezig";
$a->strings["I will not attend"] = "Niet aanwezig";
$a->strings["I may attend"] = "Mogelijk aanwezig";
$a->strings["Diaspora Protocol Settings updated."] = "Diaspora-protocol-instellingen bijgewerkt.";
$a->strings["Enable the (experimental) Diaspora protocol for this channel"] = "Het (experimentele) Diaspora-protocol voor dit kanaal inschakelen";
$a->strings["Allow any Diaspora member to comment on your public posts"] = "Geef elk Diaspora-lid toestemming om op jouw openbare berichten te reageren";
$a->strings["Prevent your hashtags from being redirected to other sites"] = "Voorkom dat jouw hashtags naar andere websites worden doorverwezen";
$a->strings["Diaspora Protocol Settings"] = "Diaspora-protocol (compatibiliteit, incl Friendica)";
$a->strings["Submit"] = "Opslaan";

View file

@ -0,0 +1,11 @@
<XML>
<post>
<comment>
<guid>{{$guid}}</guid>
<parent_guid>{{$parent_guid}}</parent_guid>
<text>{{$body}}</text>
<diaspora_handle>{{$handle}}</diaspora_handle>
<author_signature>{{$authorsig}}</author_signature>
</comment>
</post>
</XML>

View file

@ -0,0 +1,12 @@
<XML>
<post>
<comment>
<guid>{{$guid}}</guid>
<parent_guid>{{$parent_guid}}</parent_guid>
<text>{{$body}}</text>
<diaspora_handle>{{$handle}}</diaspora_handle>
<parent_author_signature>{{$parentsig}}</parent_author_signature>
<author_signature>{{$authorsig}}</author_signature>
</comment>
</post>
</XML>

View file

@ -0,0 +1,17 @@
<poll>
<guid>{{$guid_q}}</guid>
<question>{{$question}}</question>
<poll_answer>
<guid>{{$guid_y}}</guid>
<answer>{{$agree}}</answer>
</poll_answer>
<poll_answer>
<guid>{{$guid_n}}</guid>
<answer>{{$disagree}}</answer>
</poll_answer>
<poll_answer>
<guid>{{$guid_a}}</guid>
<answer>{{$abstain}}</answer>
</poll_answer>
</poll>

View file

@ -0,0 +1,29 @@
<XML>
<post>
<conversation>
<guid>{{$conv.guid}}</guid>
<subject>{{$conv.subject}}</subject>
<created_at>{{$conv.created_at}}</created_at>
{{foreach $conv.messages as $msg}}
<message>
<guid>{{$msg.guid}}</guid>
<parent_guid>{{$msg.parent_guid}}</parent_guid>
{{if $msg.parent_author_signature}}
<parent_author_signature>{{$msg.parent_author_signature}}</parent_author_signature>
{{/if}}
<author_signature>{{$msg.author_signature}}</author_signature>
<text>{{$msg.text}}</text>
<created_at>{{$msg.created_at}}</created_at>
<diaspora_handle>{{$msg.diaspora_handle}}</diaspora_handle>
<conversation_guid>{{$msg.conversation_guid}}</conversation_guid>
</message>
{{/foreach}}
<diaspora_handle>{{$conv.diaspora_handle}}</diaspora_handle>
<participant_handles>{{$conv.participant_handles}}</participant_handles>
</conversation>
</post>
</XML>

View file

@ -0,0 +1,12 @@
<XML>
<post>
<like>
<positive>{{$positive}}</positive>
<guid>{{$guid}}</guid>
<target_type>{{$target_type}}</target_type>
<parent_guid>{{$parent_guid}}</parent_guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
<author_signature>{{$authorsig}}</author_signature>
</like>
</post>
</XML>

View file

@ -0,0 +1,13 @@
<XML>
<post>
<like>
<positive>{{$positive}}</positive>
<guid>{{$guid}}</guid>
<target_type>{{$target_type}}</target_type>
<parent_guid>{{$parent_guid}}</parent_guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
<parent_author_signature>{{$parentsig}}</parent_author_signature>
<author_signature>{{$authorsig}}</author_signature>
</like>
</post>
</XML>

View file

@ -0,0 +1,16 @@
<XML>
<post>
<message>
<guid>{{$msg.guid}}</guid>
<parent_guid>{{$msg.parent_guid}}</parent_guid>
{{if $msg.parent_author_signature}}
<parent_author_signature>{{$msg.parent_author_signature}}</parent_author_signature>
{{/if}}
<author_signature>{{$msg.author_signature}}</author_signature>
<text>{{$msg.text}}</text>
<created_at>{{$msg.created_at}}</created_at>
<diaspora_handle>{{$msg.diaspora_handle}}</diaspora_handle>
<conversation_guid>{{$msg.conversation_guid}}</conversation_guid>
</message>
</post>
</XML>

View file

@ -0,0 +1,13 @@
<XML>
<post>
<photo>
<guid>{{$guid}}</guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
<public>{{$public}}</public>
<created_at>{{$created_at}}</created_at>
<remote_photo_path>{{$path}}</remote_photo_path>
<remote_photo_name>{{$filename}}</remote_photo_name>
<status_message_guid>{{$msg_guid}}</status_message_guid>
</photo>
</post>
</XML>

View file

@ -0,0 +1,13 @@
<XML>
<post>
<status_message>
<raw_message>{{$body}}</raw_message>
<guid>{{$guid}}</guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
{{$poll}}
<public>{{$public}}</public>
<created_at>{{$created}}</created_at>
<provider_display_name>{{$provider}}</provider_display_name>
</status_message>
</post>
</XML>

View file

@ -0,0 +1,17 @@
<XML>
<post><profile>
<diaspora_handle>{{$handle}}</diaspora_handle>
<first_name>{{$first}}</first_name>
<last_name>{{$last}}</last_name>
<image_url>{{$large}}</image_url>
<image_url_medium>{{$medium}}</image_url_medium>
<image_url_small>{{$small}}</image_url_small>
{{if $dob}}<birthday>{{$dob}}</birthday>{{/if}}
<gender>{{$gender}}</gender>
<bio>{{$about}}</bio>
<location>{{$location}}</location>
<searchable>{{$searchable}}</searchable>
<nsfw>{{$nsfw}}</nsfw>
<tag_string>{{$tags}}</tag_string>
</profile></post>
</XML>

View file

@ -0,0 +1,10 @@
<XML>
<post>
<relayable_retraction>
<target_type>{{$type}}</target_type>
<target_guid>{{$guid}}</target_guid>
<target_author_signature>{{$signature}}</target_author_signature>
<sender_handle>{{$handle}}</sender_handle>
</relayable_retraction>
</post>
</XML>

View file

@ -0,0 +1,11 @@
<XML>
<post>
<relayable_retraction>
<parent_author_signature>{{$parentsig}}</parent_author_signature>
<target_guid>{{$guid}}</target_guid>
<target_type>{{$target_type}}</target_type>
<sender_handle>{{$handle}}</sender_handle>
<target_author_signature>{{$authorsig}}</target_author_signature>
</relayable_retraction>
</post>
</XML>

View file

@ -0,0 +1,13 @@
<XML>
<post>
<reshare>
<root_diaspora_id>{{$root_handle}}</root_diaspora_id>
<root_guid>{{$root_guid}}</root_guid>
<guid>{{$guid}}</guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
<public>{{$public}}</public>
<created_at>{{$created}}</created_at>
<provider_display_name>{{$provider}}</provider_display_name>
</reshare>
</post>
</XML>

View file

@ -0,0 +1,9 @@
<XML>
<post>
<retraction>
<post_guid>{{$guid}}</post_guid>
<diaspora_handle>{{$handle}}</diaspora_handle>
<type>{{$type}}</type>
</retraction>
</post>
</XML>

View file

@ -0,0 +1,8 @@
<XML>
<post>
<request>
<sender_handle>{{$sender}}</sender_handle>
<recipient_handle>{{$recipient}}</recipient_handle>
</request>
</post>
</XML>

View file

@ -0,0 +1,10 @@
<XML>
<post>
<signed_retraction>
<target_guid>{{$guid}}</target_guid>
<target_type>{{$type}}</target_type>
<sender_handle>{{$handle}}</sender_handle>
<target_author_signature>{{$signature}}</target_author_signature>
</signed_retraction>
</post>
</XML>

View file

@ -0,0 +1,57 @@
<div style="display:none;">
<dl class='entity_nickname'>
<dt>Nickname</dt>
<dd>
<a class="nickname url uid" href="{{$diaspora.podloc}}/" rel="me">{{$diaspora.fullname}}</a>
</dd>
</dl>
<dl class='entity_fn'>
<dt>Full name</dt>
<dd>
<span class='fn'>{{$diaspora.fullname}}</span>
</dd>
</dl>
<dl class='entity_given_name'>
<dt>First name</dt>
<dd>
<span class='given_name'>{{$diaspora.firstname}}</span>
</dd>
</dl>
<dl class='entity_family_name'>
<dt>Family name</dt>
<dd>
<span class='family_name'>{{$diaspora.lastname}}</span>
</dd>
</dl>
<dl class="entity_url">
<dt>URL</dt>
<dd>
<a class="url" href="{{$diaspora.podloc}}/" id="pod_location" rel="me">{{$diaspora.podloc}}/</a>
</dd>
</dl>
<dl class="entity_photo">
<dt>Photo</dt>
<dd>
<img class="photo avatar" height="300" width="300" src="{{$diaspora.photo300}}">
</dd>
</dl>
<dl class="entity_photo_medium">
<dt>Photo</dt>
<dd>
<img class="photo avatar" height="100" width="100" src="{{$diaspora.photo100}}">
</dd>
</dl>
<dl class="entity_photo_small">
<dt>Photo</dt>
<dd>
<img class="photo avatar" height="50" width="50" src="{{$diaspora.photo50}}">
</dd>
</dl>
<dl class="entity_searchable">
<dt>Searchable</dt>
<dd>
<span class="searchable">{{$diaspora.searchable}}</span>
</dd>
</dl>
</div>

View file

@ -6,6 +6,7 @@
* Version: 0.1
* Author: Michael Vogel <heluecht@pirati.ca>
* Maintainer: none
* MaxVersion: 1.0
*/
function diaspost_load() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,111 @@
<?php
/**
* Name: GNU-Social Protocol
* Description: GNU-Social Protocol (Experimental, Not-finished, Unsupported)
* Version: 1.0
* Author: Mike Macgirvin
* Maintainer: none
* Requires: pubsubhubbub
*/
require_once('include/crypto.php');
require_once('include/items.php');
require_once('include/bb2diaspora.php');
require_once('include/contact_selectors.php');
require_once('include/queue_fn.php');
require_once('include/salmon.php');
function gnusoc_load() {
register_hook('module_loaded', 'addon/gnusoc/gnusoc.php', 'gnusoc_load_module');
register_hook('webfinger', 'addon/gnusoc/gnusoc.php', 'gnusoc_webfinger');
register_hook('personal_xrd', 'addon/gnusoc/gnusoc.php', 'gnusoc_personal_xrd');
register_hook('follow_allow', 'addon/gnusoc/gnusoc.php', 'gnusoc_follow_allow');
register_hook('feature_settings_post', 'addon/gnusoc/gnusoc.php', 'gnusoc_feature_settings_post');
register_hook('feature_settings', 'addon/gnusoc/gnusoc.php', 'gnusoc_feature_settings');
// register_hook('notifier_hub', 'addon/gnusoc/gnusoc.php', 'gnusoc_process_outbound');
// register_hook('permissions_create', 'addon/gnusoc/gnusoc.php', 'gnusoc_permissions_create');
// register_hook('permissions_update', 'addon/gnusoc/gnusoc.php', 'gnusoc_permissions_update');
}
function gnusoc_unload() {
unregister_hook('module_loaded', 'addon/gnusoc/gnusoc.php', 'gnusoc_load_module');
unregister_hook('webfinger', 'addon/gnusoc/gnusoc.php', 'gnusoc_webfinger');
unregister_hook('personal_xrd', 'addon/gnusoc/gnusoc.php', 'gnusoc_personal_xrd');
unregister_hook('follow_allow', 'addon/gnusoc/gnusoc.php', 'gnusoc_follow_allow');
unregister_hook('feature_settings_post', 'addon/gnusoc/gnusoc.php', 'gnusoc_feature_settings_post');
unregister_hook('feature_settings', 'addon/gnusoc/gnusoc.php', 'gnusoc_feature_settings');
}
// @fixme - subscribe to hub(s) on follow
function gnusoc_load_module(&$a, &$b) {
if($b['module'] === 'salmon') {
require_once('addon/gnusoc/salmon.php');
$b['installed'] = true;
}
}
function gnusoc_webfinger(&$a,&$b) {
$b['result']['links'][] = array('rel' => 'salmon', 'href' => z_root() . '/salmon/' . $b['channel']['channel_address']);
}
function gnusoc_personal_xrd(&$a,&$b) {
$b['xml'] = str_replace('</XRD>',
'<Link rel="salmon" href="' . z_root() . '/salmon/' . $b['user']['channel_address'] . '" />' . "\r\n" . '</XRD>', $b['xml']);
}
function gnusoc_follow_allow(&$a, &$b) {
if($b['xchan']['xchan_network'] !== 'gnusoc')
return;
$allowed = get_pconfig($b['channel_id'],'system','gnusoc_allowed');
if($allowed === false)
$allowed = 1;
$b['allowed'] = $allowed;
$b['singleton'] = 1; // this network does not support channel clones
}
function gnusoc_feature_settings_post(&$a,&$b) {
if($_POST['gnusoc-submit']) {
set_pconfig(local_channel(),'system','gnusoc_allowed',intval($_POST['gnusoc_allowed']));
info( t('GNU-Social Protocol Settings updated.') . EOL);
}
}
function gnusoc_feature_settings(&$a,&$s) {
$gnusoc_allowed = get_pconfig(local_channel(),'system','gnusoc_allowed');
if($gnusoc_allowed === false)
$gnus_allowed = get_config('gnusoc','allowed');
$sc .= replace_macros(get_markup_template('field_checkbox.tpl'), array(
'$field' => array('gnusoc_allowed', t('Enable the (experimental) GNU-Social protocol for this channel'), $gnusoc_allowed, '', $yes_no),
));
$s .= replace_macros(get_markup_template('generic_addon_settings.tpl'), array(
'$addon' => array('gnusoc', '<img src="addon/gnusoc/gnusoc-32.png" style="width:auto; height:1em; margin:-3px 5px 0px 0px;">' . t('GNU-Social Protocol Settings'), '', t('Submit')),
'$content' => $sc
));
return;
}

View file

@ -0,0 +1,387 @@
<?php
require_once('include/salmon.php');
require_once('include/crypto.php');
require_once('include/items.php');
require_once('include/follow.php');
require_once('include/Contact.php');
function salmon_return($val) {
if($val >= 400)
$err = 'Error';
if($val >= 200 && $val < 300)
$err = 'OK';
logger('mod-salmon returns ' . $val);
header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
killme();
}
function salmon_post(&$a) {
$xml = file_get_contents('php://input');
logger('mod-salmon: new salmon ' . $xml, LOGGER_DATA);
$nick = ((argc() > 1) ? trim(argv(1)) : '');
// $mentions = (($a->argc > 2 && $a->argv[2] === 'mention') ? true : false);
$importer = channelx_by_nick($nick);
if(! $importer)
http_status_exit(500);
// @fixme check that this channel has the GNU-Social protocol enabled
// parse the xml
$dom = simplexml_load_string($xml,'SimpleXMLElement',0,NAMESPACE_SALMON_ME);
// figure out where in the DOM tree our data is hiding
if($dom->provenance->data)
$base = $dom->provenance;
elseif($dom->env->data)
$base = $dom->env;
elseif($dom->data)
$base = $dom;
if(! $base) {
logger('mod-salmon: unable to locate salmon data in xml ');
http_status_exit(400);
}
logger('data: ' . $xml, LOGGER_DATA);
// Stash the signature away for now. We have to find their key or it won't be good for anything.
$signature = base64url_decode($base->sig);
// unpack the data
// strip whitespace so our data element will return to one big base64 blob
$data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data);
// stash away some other stuff for later
$type = $base->data[0]->attributes()->type[0];
$keyhash = $base->sig[0]->attributes()->keyhash[0];
$encoding = $base->encoding;
$alg = $base->alg;
// Salmon magic signatures have evolved and there is no way of knowing ahead of time which
// flavour we have. We'll try and verify it regardless.
$stnet_signed_data = $data;
$signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg);
$compliant_format = str_replace('=','',$signed_data);
// decode the data
$data = base64url_decode($data);
logger('decoded: ' . $data, LOGGER_DATA);
// GNU-Social doesn't send a legal Atom feed over salmon, only an Atom entry. Unfortunately
// our parser is a bit strict about compliance so we'll insert just enough of a feed
// tag to trick it into believing it's a compliant feed.
if(! strstr($data,'<feed')) {
$data = str_replace('<entry ','<feed xmlns="http://www.w3.org/2005/Atom"><entry ',$data);
$data .= '</feed>';
}
$datarray = process_salmon_feed($data,$importer);
$author_link = $datarray['author']['author_link'];
if(! $author_link) {
logger('mod-salmon: Could not retrieve author URI.');
http_status_exit(400);
}
$r = q("select xchan_pubkey from xchan where xchan_guid = '%s' limit 1",
dbesc($author_link)
);
if($r) {
$pubkey = $r[0]['xchan_pubkey'];
}
else {
// Once we have the author URI, go to the web and try to find their public key
logger('mod-salmon: Fetching key for ' . $author_link);
$key = get_salmon_key($author_link,$keyhash);
if(! $key) {
logger('mod-salmon: Could not retrieve author key.');
http_status_exit(400);
}
$key_info = explode('.',$key);
$m = base64url_decode($key_info[1]);
$e = base64url_decode($key_info[2]);
logger('mod-salmon: key details: ' . print_r($key_info,true), LOGGER_DEBUG);
$pubkey = metopem($m,$e);
}
// We should have everything we need now. Let's see if it verifies.
$verify = rsa_verify($compliant_format,$signature,$pubkey);
if(! $verify) {
logger('mod-salmon: message did not verify using protocol. Trying padding hack.');
$verify = rsa_verify($signed_data,$signature,$pubkey);
}
if(! $verify) {
logger('mod-salmon: message did not verify using padding. Trying old statusnet hack.');
$verify = rsa_verify($stnet_signed_data,$signature,$pubkey);
}
if(! $verify) {
logger('mod-salmon: Message did not verify. Discarding.');
http_status_exit(400);
}
logger('mod-salmon: Message verified.');
/* lookup the author */
if(! $datarray['author']['author_link'])
logger('unable to probe - no author identifier');
http_status_exit(400);
}
$r = q("select * from xchan where xchan_guid = '%s' limit 1",
dbesc($datarray['author']['author_link'])
);
if(! $r) {
if(discover_by_webbie($datarray['author']['author_link'])) {
$r = q("select xchan_hash from xchan where xchan_guid = '%s' limit 1",
dbesc($datarray['author']['author_link'])
);
if(! $r) {
logger('discovery failed');
http_status_exit(400);
}
}
$xchan = $r[0];
/*
*
* If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff.
*
*/
// First check for and process follow activity
if(activity_match($datarray['verb'],ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) {
$r = q("select * from abook where abook_channel = %d and abook_hash = '%s' limit 1",
intval($importer['channel_id']),
dbesc($xchan['xchan_hash'])
);
if($r) {
$contact = $r[0];
$newperms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK|PERMS_W_STREAM|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT|PERMS_R_STORAGE|PERMS_R_PAGES;
$abook_instance = $contact['abook_instance'];
if($abook_instance)
$abook_instance .= ',';
$abook_instance .= z_root();
$r = q("update abook set abook_their_perms = %d, abook_instance = '%s' where abook_id = %d and abook_channel = %d",
intval($newperms),
dbesc($abook_instance),
intval($contact['abook_id']),
intval($importer['channel_id'])
);
}
else {
$role = get_pconfig($importer['channel_id'],'system','permissions_role');
if($role) {
$x = get_role_perms($role);
if($x['perms_auto'])
$default_perms = $x['perms_accept'];
}
if(! $default_perms)
$default_perms = intval(get_pconfig($importer['channel_id'],'system','autoperms'));
$their_perms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK|PERMS_W_STREAM|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT|PERMS_R_STORAGE|PERMS_R_PAGES;
$closeness = get_pconfig($importer['channel_id'],'system','new_abook_closeness');
if($closeness === false)
$closeness = 80;
$r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_my_perms, abook_their_perms, abook_closeness, abook_created, abook_updated, abook_connected, abook_dob, abook_pending, abook_instance ) values ( %d, %d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s', %d, '%s' )",
intval($importer['channel_account_id']),
intval($importer['channel_id']),
dbesc($contact['xchan_hash']),
intval($default_perms),
intval($their_perms),
intval($closeness),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc(NULL_DATE),
intval(($default_perms) ? 0 : 1),
dbesc(z_root())
);
if($r) {
logger("New GNU-Social follower received for {$importer['channel_name']}");
$new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash left join hubloc on hubloc_hash = xchan_hash where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1",
intval($importer['channel_id']),
dbesc($xchan['xchan_hash'])
);
if($new_connection) {
require_once('include/enotify.php');
notification(array(
'type' => NOTIFY_INTRO,
'from_xchan' => $xchan['xchan_hash'],
'to_xchan' => $importer['channel_hash'],
'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'],
));
if($default_perms) {
// @fixme!!!
// Send back a sharing notification to them
$x = gnusoc_follow($importer,$new_connection[0]);
if($x)
proc_run('php','include/deliver.php',$x);
}
$clone = array();
foreach($new_connection[0] as $k => $v) {
if(strpos($k,'abook_') === 0) {
$clone[$k] = $v;
}
}
unset($clone['abook_id']);
unset($clone['abook_account']);
unset($clone['abook_channel']);
$abconfig = load_abconfig($importer['channel_hash'],$clone['abook_xchan']);
if($abconfig)
$clone['abconfig'] = $abconfig;
build_sync_packet($importer['channel_id'], array('abook' => array($clone)));
}
http_status_exit(200);
}
}
}
//
// ... fixme
// Otherwise check general permissions
if(! perm_is_allowed($importer['channel_id'],$xchan['xchan_hash'],'send_stream')) {
// check for and process ostatus autofriend
// ... fixme
// otherwise
logger('mod-salmon: Ignoring this author.');
http_status_exit(202);
// NOTREACHED
}
unset($datarray['author']);
$parent_item = null;
if($datarray['parent_mid']) {
$r = q("select * from item where mid = '%s' and uid = %d limit 1",
dbesc($datarray['parent_mid']),
intval($importer['channel_id'])
);
if(! $r) {
logger('mod-salmon: parent item not found.');
http_status_exit(202);
}
$parent_item = $r[0];
}
if(! $datarray['author_xchan'])
$datarray['author_xchan'] = $xchan['xchan_hash'];
$datarray['owner_xchan'] = (($parent_item) ? $parent_item['owner_xchan'] : $xchan['xchan_hash']);
$r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
dbesc($datarray['mid']),
intval($importer['channel_id'])
);
// Update content if 'updated' changes
// currently a no-op @fixme
if($r) {
if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
// do not accept (ignore) an earlier edit than one we currently have.
if(datetime_convert('UTC','UTC',$datarray['edited']) > $r[0]['edited'])
update_feed_item($importer['channel_id'],$datarray);
}
http_status_exit(200);
}
if(! $datarray['parent_mid'])
$datarray['parent_mid'] = $datarray['mid'];
$datarray['aid'] = $importer['channel_account_id'];
$datarray['uid'] = $importer['channel_id'];
logger('consume_feed: ' . print_r($datarray,true),LOGGER_DATA);
$xx = item_store($datarray);
$r = $xx['item_id'];
// if this is a reply, do a relay?
http_status_exit(200);
}
function gnusoc_follow($importer,$xchan) {
}

View file

@ -0,0 +1,80 @@
<?php
/**
*
* Name: Hubwall
* Description: Send admin email message to all account holders
* Version: 1.0
* Author: Mike Macgirvin
* Maintainer: none
*/
require_once('include/enotify.php');
function hubwall_module() {}
function hubwall_post(&$a) {
if(! is_site_admin())
return;
$text = trim($_REQUEST['text']);
if(! $text)
return;
$sender_name = t('Hub Administrator');
$sender_email = 'sys@' . $a->get_hostname();
$subject = $_REQUEST['subject'];
$textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"),array( "", "\n"), $text))),ENT_QUOTES,'UTF-8'));
$htmlversion = bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$text)));
$sql_extra = ((intval($_REQUEST['test'])) ? sprintf(" and account_email = '%s' ", get_config('system','admin_email')) : '');
$recips = q("select account_email from account where account_flags = %d $sql_extra",
intval(ACCOUNT_OK)
);
if(! $recips) {
notice( t('No recipients found.') . EOL);
return;
}
foreach($recips as $recip) {
enotify::send(array(
'fromName' => $sender_name,
'fromEmail' => $sender_email,
'replyTo' => $sender_email,
'toEmail' => $recip['account_email'],
'messageSubject' => $subject,
'htmlVersion' => $htmlversion,
'textVersion' => $textversion
));
}
}
function hubwall_content(&$a) {
if(! is_site_admin())
return;
$title = t('Send email to all hub members.');
$o = replace_macros(get_markup_template('hubwall_form.tpl','addon/hubwall/'),array(
'$title' => $title,
'$text' => htmlspecialchars($_REQUEST['text']),
'$subject' => array('subject',t('Message subject'),$_REQUEST['subject'],''),
'$test' => array('test',t('Test mode (only send to hub administrator)'), 0,''),
'$submit' => t('Submit')
));
return $o;
}

View file

@ -0,0 +1,12 @@
<h3>{{$title}}</h3>
<form action="hubwall" method="post">
{{include file="field_checkbox.tpl" field=$test}}
{{include file="field_input.tpl" field=$subject}}
<textarea name="text" style="width:100%; height:150px;">{{$text}}</textarea>
<br />
<input type="submit" name="submit" value="{{$submit}}" />
</form>

View file

@ -0,0 +1,9 @@
<?php
$a->strings["Post to Insanejournal"] = "Doorplaatsen naar InsaneJournal";
$a->strings["InsaneJournal Post Settings"] = "InsaneJournal (berichten doorplaatsen)";
$a->strings["Enable InsaneJournal Post Plugin"] = "Doorplaatsen naar InsaneJournal inschakelen";
$a->strings["InsaneJournal username"] = "Gebruikersnaam InsaneJournal";
$a->strings["InsaneJournal password"] = "Wachtwoord InsaneJournal";
$a->strings["Post to InsaneJournal by default"] = "Berichten standaard naar InsaneJournal doorplaatsen";
$a->strings["Submit"] = "Opslaan";

View file

@ -0,0 +1,11 @@
<?php
$a->strings["Upload a file"] = "Bestand uploaden";
$a->strings["Drop files here to upload"] = "Sleep bestanden hier naartoe om te uploaden";
$a->strings["Cancel"] = "Annuleren";
$a->strings["Failed"] = "Mislukt";
$a->strings["No files were uploaded."] = "Er zijn geen bestanden geüpload.";
$a->strings["Uploaded file is empty"] = "Geüpload bestand is leeg";
$a->strings["Image exceeds size limit of "] = "Bestandsgrootte afbeelding overschrijdt limiet van ";
$a->strings["File has an invalid extension, it should be one of "] = "Het bestand heeft een ongeldige bestandsextensie, geldig zijn ";
$a->strings["Upload was cancelled, or server error encountered"] = "Uploaden is geannuleerd of er is sprake van een serverfout";

View file

@ -0,0 +1,45 @@
<?php
/**
* Name: Keep Out
* Description: Block public completely, IMPORTANT: off grid use ONLY
* Version: 1.0
* Author: Macgirvin
* Maintainer: none
* MinVersion: 1.1.4
*
*/
function keepout_urls() {
return array(
'blocks','bookmarks','channel','chat','cloud','connections','connedit','cover_photo','directory','dirsearch','display','editblock','editlayout','editpost','editwebpage','events','feed','filestorage','hcard','hostxrd','layouts','mail','manage','menu','mitem','network','online','page','pconfig','pdledit','photos','poco','profile','public','search','siteinfo','siteinfo_json','thing','viewsrc','webpages','wfinger','xchan','xpoco','xrd','zcard','zotfeed');
}
function keepout_load() {
foreach(keepout_urls() as $x) {
register_hook($x . '_mod_init', 'addon/keepout/keepout.php', 'keepout_mod_init');
register_hook($x . '_mod_content', 'addon/keepout/keepout.php', 'keepout_mod_content');
}
}
function keepout_unload() {
foreach(keepout_urls() as $x) {
unregister_hook($x . '_mod_init', 'addon/keepout/keepout.php', 'keepout_mod_init');
unregister_hook($x . '_mod_content', 'addon/keepout/keepout.php', 'keepout_mod_content');
}
}
function keepout_mod_init(&$a,&$b) {
if((get_config('system','block_public')) && (! get_account_id()) && (! remote_channel())) {
notice( t('Permission denied.') . EOL);
$b['replace'] = true;
}
}
function keepout_mod_content(&$a,&$b) {
if((get_config('system','block_public')) && (! get_account_id()) && (! remote_channel()))
$b['replace'] = true;
}

View file

@ -0,0 +1,9 @@
<?php
$a->strings["Post to Libertree"] = "Doorplaatsen naar Libertree";
$a->strings["Libertree Post Settings"] = "Libertree (berichten doorplaatsen)";
$a->strings["Enable Libertree Post Plugin"] = "Doorplaatsen naar Libertree inschakelen";
$a->strings["Libertree API token"] = "API-token Libertree";
$a->strings["Libertree site URL"] = "Website-URL Libertree";
$a->strings["Post to Libertree by default"] = "Berichten standaard naar Libertree doorplaatsen";
$a->strings["Submit"] = "Opslaan";

View file

@ -0,0 +1,9 @@
<?php
$a->strings["Post to LiveJournal"] = "Doorplaatsen naar LiveJournal";
$a->strings["LiveJournal Post Settings"] = "LiveJournal (berichten doorplaatsen)";
$a->strings["Enable LiveJournal Post Plugin"] = "Doorplaatsen naar LiveJournal inschakelen";
$a->strings["LiveJournal username"] = "Gebruikersnaam LiveJournal";
$a->strings["LiveJournal password"] = "Wachtwoord LiveJournal";
$a->strings["Post to LiveJournal by default"] = "Berichten standaard naar LiveJournal doorplaatsen";
$a->strings["Submit"] = "Opslaan";

View file

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -0,0 +1 @@
# release 1.2

View file

@ -0,0 +1,22 @@
Example of configuration in .htconfig.php
Uncomment hreflang and setup your on Language Code if you like to get index in teh search motors by Language
///////////////META TAGS CONFIGURATION HOME BASE////////////////////////////////
//$a->config['metatag']['hreflang'] = '<link rel="alternate" href="https://blablanet.com/" hreflang="en" />';
$a->config['metatag']['description'] = '<meta name="description" content="My Social NETWORK NAME Some description here." />
';
$a->config['metatag']['robots'] = '<meta name="robots" content="index" />';
$a->config['metatag']['keywords'] = '<meta name="keywords" content="social, network, networking, service, friends, family, vpn, blogs, photos, videos, files, directory, encryption" />';
//////Registration Description
$a->config['metatag']['descriptionR'] = '<meta name="description" content="Register in My Social NETWORK NAME Some description here.." />';
//////Login Description
$a->config['metatag']['descriptionL'] = '<meta name="description" content="Login in My Social NETWORK NAME Some description here.." />';
/////Directory Description
$a->config['metatag']['descriptionD'] = '<meta name="description" content="Here My Social NETWORK NAME Some description here.." />';
////Directory PUBLIC
$a->config['metatag']['descriptionP'] = '<meta name=" The latest Public Sites." />';
///Directory APPS
$a->config['metatag']['descriptionA'] = '<meta name="description" content=" Apps, utils and extras for enjoy." />';
////Latest News Yu can use that for any of your Aplications
$a->config['metatag']['descriptionN'] = '<meta name=" My Social NETWORK NAME Some description here.." />';

View file

@ -0,0 +1,144 @@
<?php
/**
* Name: SEO MetaTags by Blablanet
* Description: Inserts metatag in every page.
* Version: 1.2
* Author: Jacob Maldonado <https://blablanet.com>
*
*/
/* Seo Meta Tags Plugin for Hubzilla
*
* Author: Jacob Maldonado
*
*
*
* Configuration:
* Use hreflang only if you like to be target only from users with a specific language
* The Search Engines will use hreflang language target for show in the search please Setup
* Your own SEO the words include here are only a Example
* Pleaase read Install for the lines in your .htconfig.php file
*
*/
function metatag_install() {
register_hook('page_content_top', 'addon/metatag/metatag.php', 'metatag_fetch');
}
function metatag_uninstall() {
unregister_hook('page_content_top', 'addon/metatag/metatag.php', 'metatag_fetch');
}
function metatag_fetch($a) {
$robots = get_config('metatag','robots');
$hreflang = get_config('metatag','hreflang');
$description = get_config('metatag','description');
$keywords = get_config('metatag','keywords');
$descriptionR = get_config('metatag','descriptionR');
$descriptionL = get_config('metatag','descriptionL');
$descriptionP = get_config('metatag','descriptionP');
$descriptionA = get_config('metatag','descriptionA');
$descriptionD = get_config('metatag','descriptionD');
$descriptionN = get_config('metatag','descriptionN');
$url = $_SERVER['REQUEST_URI'];
switch($url){
case "/";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$description" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$description" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/register";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionR" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/register&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionR" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/login&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionL" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/login";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionL" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/pubsites";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionP" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/pubsub&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionP" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/apps";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionA" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/apps&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionA" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/directory";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionD" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/directory&JS=1";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionD" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
case "/news";
$a->page['htmlhead'] .= "$hreflang" . "\r\n";
$a->page['htmlhead'] .= "$robots" . "\r\n";
$a->page['htmlhead'] .= "$descriptionN" . "\r\n";
$a->page['htmlhead'] .= "$keywords" . "\r\n";
break;
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* Name: Msg Footer
* Description: Provide legal or other text at the bottom of posts
* Version: 1.0
* Author: Mike Macgirvin
* Maintainer: none
*/
function msgfooter_load() {
/**
*
* Our demo plugin will attach in three places.
* The first is just prior to storing a local post.
*
*/
register_hook('post_local', 'addon/msgfooter/msgfooter.php', 'msgfooter_post_hook');
logger("loaded msgfooter");
}
function msgfooter_unload() {
/**
*
* unload unregisters any hooks created with register_hook
* during load. It may also delete configuration settings
* and any other cleanup.
*
*/
unregister_hook('post_local', 'addon/msgfooter/msgfooter.php', 'msgfooter_post_hook');
logger("removed msgfooter");
}
function msgfooter_plugin_admin(&$a,&$o) {
$t = get_markup_template("admin.tpl", "addon/msgfooter/");
$o = replace_macros($t, array(
'$submit' => t('Save Settings'),
'$msgfooter_text' => array('msgfooter_text', t('text to include in all outgoing posts from this site'), get_config('msgfooter', 'msgfooter_text'), '')
));
}
function msgfooter_plugin_admin_post(&$a){
$msgfooter_text = ((x($_POST,'msgfooter_text')) ? trim($_POST['msgfooter_text']) : '');
set_config('msgfooter','msgfooter_text',$msgfooter_text);
info( t('Settings updated.'). EOL );
}
function msgfooter_post_hook($a, &$item) {
/**
*
* An item was posted on the local system.
* We are going to look for specific items:
* - A status post by a profile owner
* - The profile owner must have allowed our plugin
*
*/
logger('msgfooter invoked');
if(! local_channel()) /* non-zero if this is a logged in user of this system */
return;
if(local_channel() != $item['uid']) /* Does this person own the post? */
return;
if($item['item_type'])
return;
if($item['parent']) /* If the item has a parent, this is a comment or something else, not a status post. */
return;
/* Retrieve our config setting */
$footer = get_config('msgfooter', 'msgfooter_text');
if(! $footer)
return;
$item['body'] .= '[footer]' . $footer . '[/footer]';
return;
}

View file

@ -0,0 +1,2 @@
{{include file="field_textarea.tpl" field=$msgfooter_text}}
<div class="submit"><input type="submit" name="page_site" value="{{$submit}}" /></div>

View file

@ -0,0 +1,26 @@
<?php
/**
* Name: Noembed
* Description: Use noembed.com as an addition to Hubzilla's native oembed functionality
* Version: 1.0
* Author: Jeroen van Riet Paap <jeroenpraat@hubzilla.nl>, Mike Macgirvin <mike@zothub.com>
* Maintainer: Jeroen van Riet Paap <jeroenpraat@hubzilla.nl>
*
*/
function noembed_load() {
register_hook('oembed_probe','addon/noembed/noembed.php','noembed_oembed_probe');
}
function noembed_unload() {
unregister_hook('oembed_probe','addon/noembed/noembed.php','noembed_oembed_probe');
}
function noembed_oembed_probe(&$a,&$b) {
// try noembed service
$ourl = 'https://noembed.com/embed?url=' . urlencode($b['url']);
$result = z_fetch_url($ourl);
if($result['success'])
$b['embed'] = $result['body'];
}

View file

@ -0,0 +1,11 @@
<?php
$a->strings["Not Safe For Work Settings"] = "Not Safe For Work (inhoudsfilter)";
$a->strings["General Purpose Content Filter"] = "Voor alles te gebruiken inhoudsfilter";
$a->strings["This plugin looks in posts for the words/text you specify below, and collapses any content containing those keywords so it is not displayed at inappropriate times, such as sexual innuendo that may be improper in a work setting. It is polite and recommended to tag any content containing nudity with #NSFW. This filter can also match any other word/text you specify, and can thereby be used as a general purpose content filter."] = "Deze plug-in controleert berichten op woorden die je hieronder kunt opgeven en klapt berichten in die deze woorden bevatten, zodat die berichten niet worden weergegeven op ongeschikte momenten. Denk hierbij aan berichten met erotische afbeeldingen, die waarschijnlijk niet geschikt zijn binnen (bijvoorbeeld) een werkomgeving. Het is beleefd en het wordt aangeraden om zulke berichten met #NSFW te taggen. Dit filter kan ook gebruikt worden met andere woorden en is dus voor alles inzetbaar.";
$a->strings["Enable Content filter"] = "Inhoudsfilter inschakelen";
$a->strings["Comma separated list of keywords to hide"] = "Door komma's gescheiden lijst met woorden die gefilterd moeten worden.";
$a->strings["Submit"] = "Opslaan";
$a->strings["Use /expression/ to provide regular expressions"] = "Gebruik /expressie/ voor reguliere expressies";
$a->strings["NSFW Settings saved."] = "NSFW-instellingen opgeslagen.";
$a->strings["%s - Click to open/close"] = "%s - Klik om te openen of te sluiten";

View file

@ -0,0 +1,8 @@
<?php
$a->strings["Submit"] = "Opslaan";
$a->strings["Tile Server URL"] = "URL tile-server";
$a->strings["A list of <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" target=\"_blank\">public tile servers</a>"] = "Een lijst met <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" target=\"_blank\">openbare tile-servers</a>";
$a->strings["Default zoom"] = "Standaard zoomniveau";
$a->strings["The default zoom level. (1:world, 18:highest)"] = "Het standaard zoomniveau. Van 1 (wereld) t/m 18 (maximaal).";
$a->strings["Settings updated."] = "Instellingen bijgewerkt.";

View file

@ -0,0 +1,8 @@
Page Header
For server admins only.
Displays a text message for system announcements'
The message is entered in the admin account at settings, Plugin settings.
If you want to use HTML in the pageheader, create a file called pageheader.html
in the document root of your friendica instance and add the html there.

View file

@ -0,0 +1,30 @@
# ADDON pageheader
# Copyright (C)
# This file is distributed under the same license as the Friendica pageheader addon package.
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: pageheader.php:50
msgid "\"pageheader\" Settings"
msgstr ""
#: pageheader.php:55
msgid "Submit"
msgstr ""
#: pageheader.php:68
msgid "pageheader Settings saved."
msgstr ""

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "Configuració de la capçalera de pàgina.";
$a->strings["Submit"] = "Enviar";
$a->strings["pageheader Settings saved."] = "guardada la configuració de la capçalera de pàgina.";

View file

@ -0,0 +1,32 @@
# ADDON pageheader
# Copyright (C)
# This file is distributed under the same license as the Friendica pageheader addon package.
#
#
# Translators:
# Michal Šupler <msupler@gmail.com>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: friendica\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"PO-Revision-Date: 2015-02-11 19:38+0000\n"
"Last-Translator: Michal Šupler <msupler@gmail.com>\n"
"Language-Team: Czech (http://www.transifex.com/projects/p/friendica/language/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: pageheader.php:50
msgid "\"pageheader\" Settings"
msgstr "Nastavení záhlaví stránky"
#: pageheader.php:55
msgid "Submit"
msgstr "Odeslat"
#: pageheader.php:68
msgid "pageheader Settings saved."
msgstr "Nastavení záhlaví stránky uloženo."

View file

@ -0,0 +1,10 @@
<?php
if(! function_exists("string_plural_select_cs")) {
function string_plural_select_cs($n){
return ($n==1) ? 0 : ($n>=2 && $n<=4) ? 1 : 2;;
}}
;
$a->strings["\"pageheader\" Settings"] = "Nastavení záhlaví stránky";
$a->strings["Submit"] = "Odeslat";
$a->strings["pageheader Settings saved."] = "Nastavení záhlaví stránky uloženo.";

View file

@ -0,0 +1,33 @@
# ADDON pageheader
# Copyright (C)
# This file is distributed under the same license as the Friendica pageheader addon package.
#
#
# Translators:
# Abrax <webmaster@a-zwenkau.de>, 2014
# bavatar <tobias.diekershoff@gmx.net>, 2014
msgid ""
msgstr ""
"Project-Id-Version: friendica\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"PO-Revision-Date: 2014-10-15 12:32+0000\n"
"Last-Translator: Abrax <webmaster@a-zwenkau.de>\n"
"Language-Team: German (http://www.transifex.com/projects/p/friendica/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: pageheader.php:50
msgid "\"pageheader\" Settings"
msgstr "\"pageheader\"-Einstellungen"
#: pageheader.php:55
msgid "Submit"
msgstr "Senden"
#: pageheader.php:68
msgid "pageheader Settings saved."
msgstr "pageheader-Einstellungen gespeichert."

View file

@ -0,0 +1,10 @@
<?php
if(! function_exists("string_plural_select_de")) {
function string_plural_select_de($n){
return ($n != 1);;
}}
;
$a->strings["\"pageheader\" Settings"] = "\"pageheader\"-Einstellungen";
$a->strings["Submit"] = "Senden";
$a->strings["pageheader Settings saved."] = "pageheader-Einstellungen gespeichert.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "\"pageheader\" Agordoj";
$a->strings["Submit"] = "Sendi";
$a->strings["pageheader Settings saved."] = "Konservis Agordojn de pageheader.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "Configuración de cabecera";
$a->strings["Submit"] = "Envíar";
$a->strings["pageheader Settings saved."] = "Configuración de cabecera de página guardada.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "Réglages de pageheader";
$a->strings["Submit"] = "Envoyer";
$a->strings["pageheader Settings saved."] = "Réglages pageheader sauvés.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "Stillingar \"pageheader\"";
$a->strings["Submit"] = "Senda inn";
$a->strings["pageheader Settings saved."] = "Stillingar pageheader vistaðar.";

View file

@ -0,0 +1,32 @@
# ADDON pageheader
# Copyright (C)
# This file is distributed under the same license as the Friendica pageheader addon package.
#
#
# Translators:
# fabrixxm <fabrix.xm@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: friendica\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"PO-Revision-Date: 2014-10-15 09:00+0000\n"
"Last-Translator: fabrixxm <fabrix.xm@gmail.com>\n"
"Language-Team: Italian (http://www.transifex.com/projects/p/friendica/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: pageheader.php:50
msgid "\"pageheader\" Settings"
msgstr "Impostazioni \"Intestazione pagina\""
#: pageheader.php:55
msgid "Submit"
msgstr ""
#: pageheader.php:68
msgid "pageheader Settings saved."
msgstr "Impostazioni \"Intestazione pagina\" salvate."

View file

@ -0,0 +1,10 @@
<?php
if(! function_exists("string_plural_select_it")) {
function string_plural_select_it($n){
return ($n != 1);;
}}
;
$a->strings["\"pageheader\" Settings"] = "Impostazioni \"Intestazione pagina\"";
$a->strings["Submit"] = "";
$a->strings["pageheader Settings saved."] = "Impostazioni \"Intestazione pagina\" salvate.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "";
$a->strings["Submit"] = "Lagre";
$a->strings["pageheader Settings saved."] = "";

View file

@ -0,0 +1,11 @@
<?php
if(! function_exists("string_plural_select_de")) {
function string_plural_select_de($n){
return ($n != 1);;
}}
;
$a->strings["Pageheader Settings"] = "Pageheader-instellingen";
$a->strings["Message to display on every page on this server"] = "Bericht om weer te geven op elke pagina van deze hub";
$a->strings["Submit"] = "Opslaan";
$a->strings["pageheader Settings saved."] = "Pageheader-instellingen opgeslagen.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "";
$a->strings["Submit"] = "Potwierdź";
$a->strings["pageheader Settings saved."] = "";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "Configurações do \"pageheader\"";
$a->strings["Submit"] = "Enviar";
$a->strings["pageheader Settings saved."] = "Configurações do pageheader armazenadas.";

View file

@ -0,0 +1,32 @@
# ADDON pageheader
# Copyright (C)
# This file is distributed under the same license as the Friendica pageheader addon package.
#
#
# Translators:
# Doru DEACONU <dumitrudeaconu@yahoo.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: friendica\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"PO-Revision-Date: 2014-11-27 14:16+0000\n"
"Last-Translator: Doru DEACONU <dumitrudeaconu@yahoo.com>\n"
"Language-Team: Romanian (Romania) (http://www.transifex.com/projects/p/friendica/language/ro_RO/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ro_RO\n"
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
#: pageheader.php:50
msgid "\"pageheader\" Settings"
msgstr "Configurări \"Pageheader\""
#: pageheader.php:55
msgid "Submit"
msgstr "Trimite"
#: pageheader.php:68
msgid "pageheader Settings saved."
msgstr "Configurările antetului de pagină au fost salvate."

View file

@ -0,0 +1,10 @@
<?php
if(! function_exists("string_plural_select_ro")) {
function string_plural_select_ro($n){
return ($n==1?0:((($n%100>19)||(($n%100==0)&&($n!=0)))?2:1));;
}}
;
$a->strings["\"pageheader\" Settings"] = "Configurări \"Pageheader\"";
$a->strings["Submit"] = "Trimite";
$a->strings["pageheader Settings saved."] = "Configurările antetului de pagină au fost salvate.";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "";
$a->strings["Submit"] = "Подтвердить";
$a->strings["pageheader Settings saved."] = "";

View file

@ -0,0 +1,3 @@
<?php
$a->strings["Submit"] = "Spara";

View file

@ -0,0 +1,5 @@
<?php
$a->strings["\"pageheader\" Settings"] = "\"pageheader";
$a->strings["Submit"] = "提交";
$a->strings["pageheader Settings saved."] = "pageHeader配置保存了。";

View file

@ -0,0 +1,9 @@
.pageheader {
padding: 21px 10px;
color: #31708f;
background-color: #d9edf7;
border: 1px solid #bce8f1;
text-align: center;
margin-bottom: 20px;
border-radius: 4px;
}

View file

@ -0,0 +1,83 @@
<?php
/**
* Name: Page Header
* Description: Inserts a page header
* Version: 1.1
* Author: Keith Fernie <http://friendika.me4.it/profile/keith>
* Hauke Altmann <https://snarl.de/profile/tugelblend>
*
*/
function pageheader_load() {
register_hook('page_content_top', 'addon/pageheader/pageheader.php', 'pageheader_fetch');
register_hook('feature_settings', 'addon/pageheader/pageheader.php', 'pageheader_addon_settings');
register_hook('feature_settings_post', 'addon/pageheader/pageheader.php', 'pageheader_addon_settings_post');
}
function pageheader_unload() {
unregister_hook('page_content_top', 'addon/pageheader/pageheader.php', 'pageheader_fetch');
unregister_hook('feature_settings', 'addon/pageheader/pageheader.php', 'pageheader_addon_settings');
unregister_hook('feature_settings_post', 'addon/pageheader/pageheader.php', 'pageheader_addon_settings_post');
// hook moved, uninstall the old one if still there.
unregister_hook('page_header', 'addon/pageheader/pageheader.php', 'pageheader_fetch');
}
function pageheader_addon_settings(&$a,&$s) {
if(! is_site_admin())
return;
$words = get_config('pageheader','text');
if(! $words)
$words = '';
$sc .= '<div class="settings-block">';
$sc .= '<div id="pageheader-wrapper">';
$sc .= '<label id="pageheader-label" for="pageheader-words">' . t('Message to display on every page on this server') . ' </label>';
$sc .= '<textarea class="form-control form-group" id="pageheader-words" type="text" name="pageheader-words">' . $words . '</textarea>';
$sc .= '</div><div class="clear"></div>';
$s .= replace_macros(get_markup_template('generic_addon_settings.tpl'), array(
'$addon' => array('pageheader', t('Pageheader Settings'), '', t('Submit')),
'$content' => $sc
));
return;
}
function pageheader_addon_settings_post(&$a,&$b) {
if(! is_site_admin())
return;
if($_POST['pageheader-submit']) {
set_config('pageheader','text',trim(strip_tags($_POST['pageheader-words'])));
info( t('pageheader Settings saved.') . EOL);
}
}
function pageheader_fetch($a,&$b) {
if(file_exists('pageheader.html')){
$s = file_get_contents('pageheader.html');
} else {
$s = get_config('pageheader', 'text');
$a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/pageheader/pageheader.css' . '" media="all" />' . "\r\n";
}
if($s)
$b .= '<div class="pageheader">' . $s . '</div>';
}

View file

@ -0,0 +1,113 @@
<?php
require_once('include/Contact.php');
function pubsub_init(&$a) {
$nick = ((argc() > 1) ? escape_tags(trim(argv(1))) : '');
$contact_id = ((argc() > 2) ? intval(argv(2)) : 0 );
if($_SERVER['REQUEST_METHOD'] === 'GET') {
$hub_mode = ((x($_GET,'hub_mode')) ? notags(trim($_GET['hub_mode'])) : '');
$hub_topic = ((x($_GET,'hub_topic')) ? notags(trim($_GET['hub_topic'])) : '');
$hub_challenge = ((x($_GET,'hub_challenge')) ? notags(trim($_GET['hub_challenge'])) : '');
$hub_lease = ((x($_GET,'hub_lease_seconds')) ? notags(trim($_GET['hub_lease_seconds'])) : '');
$hub_verify = ((x($_GET,'hub_verify_token')) ? notags(trim($_GET['hub_verify_token'])) : '');
logger('pubsub: Subscription from ' . $_SERVER['REMOTE_ADDR']);
logger('pubsub: data: ' . print_r($_GET,true), LOGGER_DATA);
$subscribe = (($hub_mode === 'subscribe') ? 1 : 0);
$channel = channelx_by_nick($nick);
if(! $channel)
http_status_exit(404,'not found.');
$connections = abook_connections($channel['channel_id'], ' and abook_id = ' . $contact_id);
if($connections)
$xchan = $connections[0];
else {
logger('connection ' . $contact_id . ' not found.');
http_status_exit(404,'not found.');
}
if($hub_verify) {
$verify = get_abconfig($channel['channel_hash'],$xchan['xchan_hash'],'pubsubhubbub','verify_token');
if($verify != $hub_verify) {
logger('hub verification failed.');
http_status_exit(404,'not found.');
}
}
$feed_url = z_root() . '/feed/' . $channel['channel_address'];
if($hub_topic) {
if(! link_compare($hub_topic,$feed_url)) {
logger('hub topic ' . $hub_topic . ' != ' . $feed_url);
// should abort but let's humour them.
}
}
$contact = $r[0];
// We must initiate an unsubscribe request with a verify_token.
// Don't allow outsiders to unsubscribe us.
if($hub_mode === 'unsubscribe') {
if(! strlen($hub_verify)) {
logger('pubsub: bogus unsubscribe');
http_status_exit(403,'permission denied.');
}
logger('pubsub: unsubscribe success');
}
if($hub_mode) {
set_abconfig($channel['channel_hash'],$xchan['xchan_hash'],'pubsubhubbub','subscribed',intval($subscribe));
}
header($_SERVER["SERVER_PROTOCOL"] . ' 200 ' . 'OK');
echo $hub_challenge;
killme();
}
}
function pubsub_post(&$a) {
$xml = file_get_contents('php://input');
logger('pubsub: feed arrived from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $a->cmd );
logger('pubsub: user-agent: ' . $_SERVER['HTTP_USER_AGENT'] );
logger('pubsub: data: ' . $xml, LOGGER_DATA);
$nick = ((argc() > 1) ? escape_tags(trim(argv(1))) : '');
$contact_id = ((argc() > 2) ? intval(argv(2)) : 0 );
$channel = channelx_by_nick($nick);
if(! $channel)
http_status_exit(200,'OK');
$connections = abook_connections($channel['channel_id'], ' and abook_id = ' . $contact_id);
if($connections)
$xchan = $connections[0];
else {
logger('connection ' . $contact_id . ' not found.');
http_status_exit(200,'OK');
}
if(! perm_is_allowed($channel['channel_id'],$xchan['xchan_hash'],'send_stream')) {
logger('permission denied.');
http_status_exit(200,'OK');
}
consume_feed($xml,$channel,$xchan,1);
consume_feed($xml,$channel,$xchan,2);
http_status_exit(200,'OK');
}

View file

@ -0,0 +1,317 @@
<?php
/**
* Name: PubSubHubBub
* Description: Add PuSH capability to channel feeds - based loosely on Friendica PuSH module by Mats Sjöberg
* Version: 1.0
* Author: Mike Macgirvin
* Maintainer: none
* MinVersion: 1.2.2
*/
require_once('include/Contact.php');
function pubsubhubbub_install() {
$r = q("CREATE TABLE IF NOT EXISTS `push_subscriber` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`callback_url` varchar(255) NOT NULL DEFAULT '',
`topic` varchar(255) NOT NULL DEFAULT '',
`last_update` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`secret` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8");
if($r) {
q("alter table push_subscriber add index ( callback_url ) ");
q("alter table push_subscriber add index ( topic ) ");
}
}
function pubsubhubbub_uninstall() {
$r = q("drop table push_subscriber");
}
function pubsubhubbub_load() {
register_hook('notifier_process','addon/pubsubhubbub/pubsubhubbub.php','push_notifier_process');
register_hook('queue_deliver','addon/pubsubhubbub/pubsubhubbub.php','push_queue_deliver');
register_hook('atom_feed','addon/pubsubhubbub/pubsubhubbub.php','push_atom_feed');
register_hook('module_loaded', 'addon/pubsubhubbub/pubsubhubbub.php','push_module_loaded');
}
function pubsubhubbub_unload() {
unregister_hook('notifier_process','addon/pubsubhubbub/pubsubhubbub.php','push_notifier_process');
unregister_hook('queue_deliver','addon/pubsubhubbub/pubsubhubbub.php','push_queue_deliver');
unregister_hook('atom_feed','addon/pubsubhubbub/pubsubhubbub.php','push_atom_feed');
unregister_hook('module_loaded', 'addon/pubsubhubbub/pubsubhubbub.php','push_module_loaded');
}
function push_atom_feed(&$a,&$b) {
$b = str_replace('</generator>','</generator>' . "\r\n" . ' <link href="' . z_root() . '/pubsubhubbub' . '" rel="hub" />',$b);
}
function push_module_loaded(&$a,&$b) {
if($b['module'] === 'pubsub') {
require_once('addon/pubsubhubbub/pubsub.php');
$b['installed'] = true;
}
}
function push_notifier_process(&$a,&$b) {
if(! $b['normal_mode'])
return;
if($b['private'])
return;
// find push_subscribers following this $owner
$channel = $b['channel'];
$r = q("select * from push_subscriber where topic = '%s'",
dbesc(z_root() . '/feed/' . $channel['channel_address'])
);
if(! $r)
return;
foreach($r as $rr) {
$feed = get_feed_for($channel,'',array('begin' => $rr['last_update']));
$hmac_sig = hash_hmac("sha1", $feed, $rr['secret']);
$slap = array('sig' => $hmac_sig, 'topic' => $rr['topic'], 'body' => $feed);
// Check for public post and create atom wrapper and stick in queue
// also need queue driver for 'push' since we need to set some extra headers
$hash = random_string();
queue_insert(array(
'hash' => $hash,
'account_id' => $channel['channel_account_id'],
'channel_id' => $channel['channel_id'],
'driver' => 'push',
'posturl' => $rr['callback_url'],
'notify' => '',
'msg' => json_encode($slap)
));
$b['queued'][] = $hash;
}
}
function push_queue_deliver(&$a,&$b) {
$outq = $b['outq'];
if($outq['outq_driver'] !== 'push')
return;
$b['handled'] = true;
$m = json_decode($outq['outq_msg'],true);
if($m) {
$headers = array("Content-type: application/atom+xml",
sprintf("Link: <%s>;rel=hub,<%s>;rel=self",z_root() . '/pubsubhubbub',$m['topic']),
"X-Hub-Signature: sha1=" . $m['sig']);
$counter = 0;
$result = z_post_url($outq['outq_posturl'], $m['body'], $counter, array('headers' => $headers, 'novalidate' => true));
if($result['success'] && $result['return_code'] < 300) {
logger('push_deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG);
if($b['base']) {
q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ",
dbesc(datetime_convert()),
dbesc($b['base'])
);
}
q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s' limit 1",
dbesc('accepted for delivery'),
dbesc(datetime_convert()),
dbesc($outq['outq_hash'])
);
q("update push_subscriber set last_update = '%s' where callback_url = '%s' and topic = '%s'",
dbesc(datetime_convert()),
dbesc($outq['outq_posturl']),
dbesc($m['topic'])
);
remove_queue_item($outq['outq_hash']);
}
else {
logger('push_deliver: queue post returned ' . $result['return_code']
. ' from ' . $outq['outq_posturl'],LOGGER_DEBUG);
update_queue_item($outq['outq_posturl']);
}
return;
}
}
function pubsubhubbub_module() {};
function push_post_var($name) {
return (x($_REQUEST, $name)) ? notags(trim($_REQUEST[$name])) : '';
}
function pubsubhubbub_init(&$a) {
// PuSH subscription must be considered "public" so just block it
// if public access isn't enabled.
if (get_config('system', 'block_public')) {
http_status_exit(403);
}
// Subscription request from subscriber
// https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4
// Example from GNU Social:
// [hub_mode] => subscribe
// [hub_callback] => http://status.local/main/push/callback/1
// [hub_verify] => sync
// [hub_verify_token] => af11...
// [hub_secret] => af11...
// [hub_topic] => http://friendica.local/dfrn_poll/sazius
if($_SERVER['REQUEST_METHOD'] === 'POST') {
$hub_mode = push_post_var('hub_mode');
$hub_callback = push_post_var('hub_callback');
$hub_verify = push_post_var('hub_verify');
$hub_verify_token = push_post_var('hub_verify_token');
$hub_secret = push_post_var('hub_secret');
$hub_topic = push_post_var('hub_topic');
// check for valid hub_mode
if ($hub_mode === 'subscribe') {
$subscribe = 1;
} else if ($hub_mode === 'unsubscribe') {
$subscribe = 0;
} else {
logger("pubsubhubbub: invalid hub_mode=$hub_mode, ignoring.");
http_status_exit(404);
}
logger("pubsubhubbub: $hub_mode request from " . $_SERVER['REMOTE_ADDR']);
// get the nick name from the topic, a bit hacky but needed
$nick = substr(strrchr($hub_topic, "/"), 1);
if (!$nick) {
logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.');
http_status_exit(404);
}
// fetch user from database given the nickname
$owner = channelx_by_nick($nick);
if(! $owner) {
logger('pubsubhubbub: local account not found: ' . $nick);
http_status_exit(404);
}
if(! perm_is_allowed($owner['channel_id'],'','view_stream')) {
logger('pubsubhubbub: local channel ' . $nick .
'has chosen to hide wall, ignoring.');
http_status_exit(403);
}
// sanity check that topic URLs are the same
if(! link_compare($hub_topic, z_root() . '/feed/' . $nick)) {
logger('pubsubhubbub: not a valid hub topic ' . $hub_topic );
http_status_exit(404);
}
// do subscriber verification according to the PuSH protocol
$hub_challenge = random_string(40);
$params = 'hub.mode=' .
($subscribe == 1 ? 'subscribe' : 'unsubscribe') .
'&hub.topic=' . urlencode($hub_topic) .
'&hub.challenge=' . $hub_challenge .
'&hub.lease_seconds=604800' .
'&hub.verify_token=' . $hub_verify_token;
// lease time is hard coded to one week (in seconds)
// we don't actually enforce the lease time because GNU
// Social/StatusNet doesn't honour it (yet)
$x = z_fetch_url($hub_callback . "?" . $params);
if(! $x['success']) {
logger("pubsubhubbub: subscriber verification at $hub_callback ".
"returned $ret, ignoring.");
http_status_exit(404);
}
// check that the correct hub_challenge code was echoed back
if (trim($x['body']) !== $hub_challenge) {
logger("pubsubhubbub: subscriber did not echo back ".
"hub.challenge, ignoring.");
logger("\"$hub_challenge\" != \"".trim($x['body'])."\"");
http_status_exit(404);
}
// fetch the old subscription if it exists
$orig = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'",
dbesc($hub_callback));
// delete old subscription if it exists
q("DELETE FROM push_subscriber WHERE callback_url = '%s' and topic = '%s'",
dbesc($hub_callback),
dbesc($hub_topic)
);
if($subscribe) {
$last_update = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
// if we are just updating an old subscription, keep the
// old values for last_update
if ($orig) {
$last_update = $orig[0]['last_update'];
}
// subscribe means adding the row to the table
q("INSERT INTO push_subscriber ( callback_url, topic, last_update, secret) values ('%s', '%s', '%s', '%s') ",
dbesc($hub_callback),
dbesc($hub_topic),
dbesc($last_update),
dbesc($hub_secret)
);
logger("pubsubhubbub: successfully subscribed [$hub_callback].");
}
else {
logger("pubsubhubbub: successfully unsubscribed [$hub_callback].");
// we do nothing here, since the row was already deleted
}
http_status_exit(202);
}
killme();
}
function pubsubhubbub_subscribe($url,$channel,$xchan,$hubmode = 'subscribe') {
$push_url = z_root() . '/pubsub/' . $channel['channel_address'] . '/' . $xchan['abook_id'];
$verify = get_abconfig($channel['channel_hash'],$xchan['xchan_hash'],'pubsubhubbub','verify_token');
if(! $verify)
$verify = set_abconfig($channel['channel_hash'],$xchan['xchan_hash'],'pubsubhubbub','verify_token',random_string(16));
$params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify;
logger('subscribe_to_hub: ' . $hubmode . ' ' . $xchan['xchan_name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify);
$x = z_post_url($url,$params);
logger('subscribe_to_hub: returns: ' . $x['return_code'], LOGGER_DEBUG);
return;
}

View file

@ -160,9 +160,9 @@ function show_button($a, &$b) {
*
*/
if (! $nobutton and ! $deactivated) {
$b = "<div id=\"profile-smiley-wrapper\" >\n";
//$b .= "\t<img src=\"" . $a->get_baseurl() . "/addon/smileybutton/icon.gif\" onclick=\"toggle_smileybutton()\" alt=\"smiley\">\n";
$b .= "\t<i id=\"profile-smiley-button\" class=\"icon-smile jot-icons\" onclick=\"toggle_smileybutton()\"></i>\n";
$b .= "<div id=\"profile-smiley-wrapper\" >\n";
//$b .= "\t<img src=\"" . $a->get_baseurl() . "/addon/smileybutton/icon.gif\" onclick=\"toggle_smileybutton(); return false;\" alt=\"smiley\">\n";
$b .= "\t<button class=\"btn btn-default btn-sm\" onclick=\"toggle_smileybutton(); return false;\"><i id=\"profile-smiley-button\" class=\"icon-smile jot-icons\" ></i></button>\n";
$b .= "\t</div>\n";
}

View file

@ -27,8 +27,8 @@ function statistics_json_init() {
$statistics = array(
"name" => get_config('system','sitename'),
"network" => get_platform_name(),
"version" => get_project_version(),
"network" => Zotlabs\Project\System::get_platform_name(),
"version" => Zotlabs\Project\System::get_project_version(),
"registrations_open" => (get_config('system','register_policy') != 0),
"total_users" => get_config('statistics_json','total_users'),
"active_users_halfyear" => get_config('statistics_json','active_users_halfyear'),

View file

@ -1,6 +1,6 @@
# ADDON statusnet
# SOME DESCRIPTIVE TITLE.
# Copyright (C)
# This file is distributed under the same license as the Friendica statusnet addon package.
# This file is distributed under the same license as the PACKAGE package.
#
#
#, fuzzy
@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-27 05:01-0500\n"
"POT-Creation-Date: 2016-02-04 23:14+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,161 +17,164 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: statusnet.php:138
msgid "Post to StatusNet"
#: statusnet.php:143
msgid "Post to GNU social"
msgstr ""
#: statusnet.php:181
#: statusnet.php:195
msgid ""
"Please contact your site administrator.<br />The provided API URL is not "
"valid."
msgstr ""
#: statusnet.php:210
msgid "We could not contact the StatusNet API with the Path you entered."
#: statusnet.php:232
msgid "We could not contact the GNU social API with the Path you entered."
msgstr ""
#: statusnet.php:240
msgid "StatusNet settings updated."
#: statusnet.php:266
msgid "GNU social settings updated."
msgstr ""
#: statusnet.php:271
msgid "StatusNet Posting Settings"
#: statusnet.php:310
msgid "Globally Available GNU social OAuthKeys"
msgstr ""
#: statusnet.php:285
msgid "Globally Available StatusNet OAuthKeys"
msgstr ""
#: statusnet.php:286
#: statusnet.php:312
msgid ""
"There are preconfigured OAuth key pairs for some StatusNet servers "
"available. If you are useing one of them, please use these credentials. If "
"not feel free to connect to any other StatusNet instance (see below)."
"There are preconfigured OAuth key pairs for some GNU social servers "
"available. If you are using one of them, please use these credentials.<br /"
">If not feel free to connect to any other GNU social instance (see below)."
msgstr ""
#: statusnet.php:292 statusnet.php:309 statusnet.php:335 statusnet.php:342
#: statusnet.php:379 statusnet.php:761
#: statusnet.php:322 statusnet.php:380 statusnet.php:432 statusnet.php:897
msgid "Submit"
msgstr ""
#: statusnet.php:294
#: statusnet.php:327
msgid "Provide your own OAuth Credentials"
msgstr ""
#: statusnet.php:295
#: statusnet.php:329
msgid ""
"No consumer key pair for StatusNet found. Register your Friendica Account as "
"an desktop client on your StatusNet account, copy the consumer key pair here "
"and enter the API base root.<br />Before you register your own OAuth key "
"pair ask the administrator if there is already a key pair for this Friendica "
"installation at your favorited StatusNet installation."
"No consumer key pair for GNU social found. Register your Hubzilla Account as "
"an desktop client on your GNU social account, copy the consumer key pair "
"here and enter the API base root.<br />Before you register your own OAuth "
"key pair ask the administrator if there is already a key pair for this "
"Hubzilla installation at your favourite GNU social installation."
msgstr ""
#: statusnet.php:297
#: statusnet.php:333
msgid "OAuth Consumer Key"
msgstr ""
#: statusnet.php:300
#: statusnet.php:337
msgid "OAuth Consumer Secret"
msgstr ""
#: statusnet.php:303
msgid "Base API Path (remember the trailing /)"
#: statusnet.php:341
msgid "Base API Path"
msgstr ""
#: statusnet.php:306
msgid "StatusNet application name"
#: statusnet.php:341
msgid "Remember the trailing /"
msgstr ""
#: statusnet.php:327
#: statusnet.php:345
msgid "GNU social application name"
msgstr ""
#: statusnet.php:368
msgid ""
"To connect to your StatusNet account click the button below to get a "
"security code from StatusNet which you have to copy into the input box below "
"and submit the form. Only your <strong>public</strong> posts will be posted "
"to StatusNet."
"To connect to your GNU social account click the button below to get a "
"security code from GNU social which you have to copy into the input box "
"below and submit the form. Only your <strong>public</strong> posts will be "
"posted to GNU social."
msgstr ""
#: statusnet.php:328
msgid "Log in with StatusNet"
#: statusnet.php:370
msgid "Log in with GNU social"
msgstr ""
#: statusnet.php:330
msgid "Copy the security code from StatusNet here"
#: statusnet.php:373
msgid "Copy the security code from GNU social here"
msgstr ""
#: statusnet.php:336
#: statusnet.php:383
msgid "Cancel Connection Process"
msgstr ""
#: statusnet.php:338
msgid "Current StatusNet API is"
#: statusnet.php:385
msgid "Current GNU social API is"
msgstr ""
#: statusnet.php:339
msgid "Cancel StatusNet Connection"
#: statusnet.php:389
msgid "Cancel GNU social Connection"
msgstr ""
#: statusnet.php:350
#: statusnet.php:389 statusnet.php:411 statusnet.php:415 statusnet.php:424
msgid "No"
msgstr ""
#: statusnet.php:389 statusnet.php:411 statusnet.php:415 statusnet.php:424
msgid "Yes"
msgstr ""
#: statusnet.php:401
msgid "Currently connected to: "
msgstr ""
#: statusnet.php:351
msgid ""
"If enabled all your <strong>public</strong> postings can be posted to the "
"associated StatusNet account. You can choose to do so by default (here) or "
"for every posting separately in the posting options when writing the entry."
msgstr ""
#: statusnet.php:353
#: statusnet.php:406
msgid ""
"<strong>Note</strong>: Due your privacy settings (<em>Hide your profile "
"details from unknown viewers?</em>) the link potentially included in public "
"postings relayed to StatusNet will lead the visitor to a blank page "
"postings relayed to GNU social will lead the visitor to a blank page "
"informing the visitor that the access to your profile has been restricted."
msgstr ""
#: statusnet.php:356
msgid "Allow posting to StatusNet"
#: statusnet.php:411
msgid "Allow posting to GNU social"
msgstr ""
#: statusnet.php:359
msgid "Send public postings to StatusNet by default"
msgstr ""
#: statusnet.php:363
#: statusnet.php:411
msgid ""
"Mirror all posts from statusnet that are no replies or repeated messages"
"If enabled your public postings can be posted to the associated GNU-social "
"account"
msgstr ""
#: statusnet.php:367
msgid "Shortening method that optimizes the post"
#: statusnet.php:415
msgid "Post to GNU social by default"
msgstr ""
#: statusnet.php:371
msgid "Send linked #-tags and @-names to StatusNet"
#: statusnet.php:415
msgid ""
"If enabled your public postings will be posted to the associated GNU-social "
"account by default"
msgstr ""
#: statusnet.php:376
#: statusnet.php:424
msgid "Clear OAuth configuration"
msgstr ""
#: statusnet.php:752
#: statusnet.php:432
msgid "GNU social Post Settings"
msgstr ""
#: statusnet.php:888
msgid "Site name"
msgstr ""
#: statusnet.php:753
#: statusnet.php:889
msgid "API URL"
msgstr ""
#: statusnet.php:754
#: statusnet.php:890
msgid "Consumer Secret"
msgstr ""
#: statusnet.php:755
#: statusnet.php:891
msgid "Consumer Key"
msgstr ""
#: statusnet.php:756
#: statusnet.php:892
msgid "Application name"
msgstr ""

View file

@ -0,0 +1,36 @@
<?php
;
$a->strings["Post to GNU social"] = "Doorplaatsen naar GNU social";
$a->strings["Please contact your site administrator.<br />The provided API URL is not valid."] = "Neem contact op met jouw hubbeheerder.<br />De verstrekte API-URL is ongeldig.";
$a->strings["We could not contact the GNU social API with the Path you entered."] = "Wij konden de GNU social-API niet bereiken door middel van het ingevulde pad.";
$a->strings["GNU social settings updated."] = "GNU social-instellingen bijgewerkt.";
$a->strings["Globally Available GNU social OAuthKeys"] = "Voor de hele hub te gebruiken GNU social OAuthkeys.";
$a->strings["There are preconfigured OAuth key pairs for some GNU social servers available. If you are using one of them, please use these credentials.<br />If not feel free to connect to any other GNU social instance (see below)."] = "Er zijn op deze hub enkele voorgeconfigureerde OAuthkey-paren voor GNU social servers beschikbaar. Wanneer je een van deze servers gebruikt, gebruik dan deze voorgeconfigureerde gegevens.<br />Wanneer dit niet het geval is, maak dan gerust met een andere GNU social-server verbinding (zie hieronder).";
$a->strings["Submit"] = "Opslaan";
$a->strings["Provide your own OAuth Credentials"] = "Verstrek jouw eigen OAuth-gegevens";
$a->strings["No consumer key pair for GNU social found. Register your Hubzilla Account as an desktop client on your GNU social account, copy the consumer key pair here and enter the API base root.<br />Before you register your own OAuth key pair ask the administrator if there is already a key pair for this Hubzilla installation at your favourite GNU social installation."] = "Geen consumerkey voor GNU social gevonden. Registreer jouw Hubzilla-account als een desktopclient in jouw GNU-social-account, kopieer en plak hier de consumerkey en de consumersecret, en vul de API-base-root in.<br />Voordat je jouw eigen OAuthkey-paar registreert, vraag dan eerst aan jouw hubbeheerder of er al een key-paar voor deze hub op jouw favoriete GNU social-server bestaat.";
$a->strings["OAuth Consumer Key"] = "OAuth-consumerkey";
$a->strings["OAuth Consumer Secret"] = "Oauth-consumersecret";
$a->strings["Base API Path"] = "Base API-pad";
$a->strings["Remember the trailing /"] = "Vergeet niet de afsluitende /";
$a->strings["GNU social application name"] = "Naam GNU social-applicatie";
$a->strings["To connect to your GNU social account click the button below to get a security code from GNU social which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to GNU social."] = "Om jouw GNU social-account te koppelen klik je op de knop hieronder. Je krijgt dan een veiligheidscode van GNU social die je kan kopiëren en dan hieronder in het invulveld kan plakken. Vervolgens klik je op Opslaan. Alleen jouw <strong>openbare</strong> berichten kunnen worden doorgeplaatst naar GNU social.";
$a->strings["Log in with GNU social"] = "Op GNU social inloggen";
$a->strings["Copy the security code from GNU social here"] = "Plak de veiligheidscode van GNU social hier";
$a->strings["Cancel Connection Process"] = "Annuleer het koppelingsproces";
$a->strings["Current GNU social API is"] = "De huidige GNU social-API is";
$a->strings["Cancel GNU social Connection"] = "Annuleer de GNU social-koppeling";
$a->strings["No"] = "Nee";
$a->strings["Yes"] = "Ja";
$a->strings["Currently connected to: "] = "Momenteel gekoppeld aan: ";
$a->strings["If enabled your public postings can be posted to the associated GNU-social account"] = "Wanneer dit is ingeschakeld kunnen jouw openbare berichten op het gekoppelde Twitter-account worden geplaatst";
$a->strings["Post to GNU social by default"] = "Berichten standaard naar GNU social doorplaatsen";
$a->strings["If enabled your public postings will be posted to the associated GNU-social account by default"] = "Wanneer dit is ingeschakeld worden al jouw openbare berichten standaard op het gekoppelde GNU social-account geplaatst";
$a->strings["Clear OAuth configuration"] = "OAuth-configuratie wissen";
$a->strings["GNU social Post Settings"] = "GNU social (berichten doorplaatsen)";
$a->strings["Site name"] = "Naam server";
$a->strings["API URL"] = "API-URL";
$a->strings["Consumer Secret"] = "Consumersecret";
$a->strings["Consumer Key"] = "Consumerkey";
$a->strings["Application name"] = "Naam applicatie";

View file

@ -552,6 +552,8 @@ function statusnet_shortenmsg($b, $max_char) {
while (strpos($msg, " ") !== false)
$msg = str_replace(" ", " ", $msg);
$msg = str_replace('#^http','http', $msg);
$origmsg = $msg;
// Removing URLs

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_ca")) {
function string_plural_select_de($n){
function string_plural_select_ca($n){
return ($n != 1);;
}}
;

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_es")) {
function string_plural_select_nl($n){
function string_plural_select_es($n){
return ($n != 1);;
}}
;

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_fr")) {
function string_plural_select_nl($n){
function string_plural_select_fr($n){
return ($n != 1);;
}}
;

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_it")) {
function string_plural_select_nl($n){
function string_plural_select_it($n){
return ($n != 1);;
}}
;

View file

@ -6,7 +6,7 @@ function string_plural_select_nl($n){
}}
;
$a->strings["Comma separated profile URLS to block"] = "Door komma's gescheiden profiel-URL's die geblokkeerd moeten worden";
$a->strings["\"Superblock\" Settings"] = "\"Superblock\"-instellingen";
$a->strings["\"Superblock\" Settings"] = "Superblock (kanalen volledig blokkeren)";
$a->strings["Submit"] = "Opslaan";
$a->strings["SUPERBLOCK Settings saved."] = "Superblock-instellingen opgeslagen.";
$a->strings["Block Completely"] = "Volledig blokkeren";

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_pt-br")) {
function string_plural_select_nl($n){
if(! function_exists("string_plural_select_pt_br")) {
function string_plural_select_pt_br($n){
return ($n != 1);;
}}
;

View file

@ -1,7 +1,7 @@
<?php
if(! function_exists("string_plural_select_ru")) {
function string_plural_select_nl($n){
function string_plural_select_ru($n){
return ($n != 1);;
}}
;

View file

@ -0,0 +1,11 @@
<?php
if(! function_exists("string_plural_select_de")) {
function string_plural_select_de($n){
return ($n != 1);;
}}
;
$a->strings["Administrator"] = "Beheerder";
$a->strings["Your account on %s will expire in a few days."] = "Jouw account op %s verloopt in een paar dagen.";
$a->strings["Your $Productname test account is about to expire."] = "Jouw $Productname-testaccount is bijna verlopen.";
$a->strings["Hi %1\$s,\n\nYour test account on %2\$s will expire in less than five days. We hope you enjoyed this test drive and use this opportunity to find or install a permanent hub and migrate your account to it. A list of public hubs is available at https://zothub.com/pubsites - and for more information on setting up your own $Projectname hub please see the project website at https://github.com/redmatrix/$projectname."] = "Hallo %1\$s,\n\njouw testaccount op %2\$s verloopt in minder dan vijf dagen. Wij hopen dat je hebt genoten van deze testrit en van deze gelegenheid gebruikt maakt om een permanente hub te vinden, waar je dit account naar toe kunt verhuizen. Je vind op https://zothub.com/pubsites een lijst met openbare hubs - en voor meer informatie over hoe jezelf een $Projectname-hub kunt inrichten verwijzen we je naar https://github.com/redmatrix/$projectname.";

View file

@ -0,0 +1,23 @@
<?php
$a->strings["Post to Twitter"] = "Doorplaatsen naar Twitter";
$a->strings["Twitter settings updated."] = "Twitter-instellingen bijgewerkt.";
$a->strings["Twitter Post Settings"] = "Twitter (berichten doorplaatsen)";
$a->strings["No consumer key pair for Twitter found. Please contact your site administrator."] = "Geen consumerkeys voor Twitter gevonden. Neem contact op met jouw hubbeheerder.";
$a->strings["At this Hubzilla instance the Twitter plugin was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter."] = "De Twitter-plugin is op deze Hubzilla-hub ingeschakeld, maar je hebt nog niet jouw Hubzilla-kanaal met jouw Twitter-account gekoppeld. Om dit te doen klik je op de knop hieronder om een PIN-code van Twitter te krijgen. Deze dien je te kopiëren en in het invoegveld beneden te plakken. Vervolgens klik je op Opslaan. Alleen jouw <strong>openbare</strong> berichten kunnen op Twitter worden geplaatst.";
$a->strings["Log in with Twitter"] = "Op Twitter inloggen";
$a->strings["Copy the PIN from Twitter here"] = "Plak of type de PIN-code hier";
$a->strings["Submit"] = "Opslaan";
$a->strings["Currently connected to: "] = "Momenteel gekoppeld aan: ";
$a->strings["Allow posting to Twitter"] = "Berichten op Twitter plaatsen";
$a->strings["If enabled your public postings can be posted to the associated Twitter account"] = "Wanneer dit is ingeschakeld kunnen jouw <strong>openbare</strong> berichten op het gekoppelde Twitter-account worden geplaatst";
$a->strings["No"] = "Nee";
$a->strings["Yes"] = "Ja";
$a->strings["Send public postings to Twitter by default"] = "Plaats berichten standaard op Twitter";
$a->strings["If enabled your public postings will be posted to the associated Twitter account by default"] = "Wanneer dit is ingeschakeld worden al jouw <strong>openbare</strong> berichten standaard op het gekoppelde Twitter-account geplaatst";
$a->strings["Clear OAuth configuration"] = "OAuth-configuratie wissen";
$a->strings["Settings updated."] = "Instellingen bijgewerkt.";
$a->strings["Submit Settings"] = "Instellingen opslaan";
$a->strings["Consumer Key"] = "Consumerkey";
$a->strings["Consumer Secret"] = "Consumersecret";
$a->strings["Name of the Twitter Application"] = "Naam van Twitter-applicatie";

Some files were not shown because too many files have changed in this diff Show more