Compare commits
183 commits
Author | SHA1 | Date | |
---|---|---|---|
Hypolite Petovan | abda067ca1 | ||
Roland Häder | e13182d913 | ||
Roland Häder | eb8e70d09e | ||
Hypolite Petovan | 11ee566479 | ||
Hypolite Petovan | fa4882b09c | ||
Hypolite Petovan | 23e01bb4cb | ||
401a92a872 | |||
c397e93698 | |||
4075a42e54 | |||
Tobias Diekershoff | b26995a78b | ||
95be3462ac | |||
b384f838da | |||
f110a98464 | |||
Andy H | 7bef7010b8 | ||
82e529df2a | |||
Andy H | 8adf1b8421 | ||
bf69f44036 | |||
Andy H | 0bf3aa5cc1 | ||
Andy H | c7b336570e | ||
945c1d1c00 | |||
15563be5cc | |||
9031423278 | |||
4de3fcf5f5 | |||
649cbbf466 | |||
95ea839fa7 | |||
27153fab68 | |||
ac41f7caf3 | |||
4da0d68bad | |||
741e72ecc7 | |||
5039d4202b | |||
ca12f85c92 | |||
61468d93d5 | |||
Roland Häder | b9ba616b7b | ||
ec0e3e431a | |||
54f9bd695a | |||
64d70c4fc3 | |||
1a608a50ad | |||
09bc3cb175 | |||
37960dcd38 | |||
a6f6f92a24 | |||
7701347cd5 | |||
e24172ab05 | |||
522cc7bb17 | |||
Tobias Diekershoff | bbe2fca6ac | ||
6306a618dc | |||
83232f4946 | |||
Tobias Diekershoff | 0d8fa3e2bd | ||
6615a31c08 | |||
Tobias Diekershoff | 3494fe491d | ||
Tobias Diekershoff | 21ca069227 | ||
99d182915d | |||
733894723e | |||
be5939e02f | |||
Tobias Diekershoff | 2db783721e | ||
fedfa7da4f | |||
b21b493613 | |||
e91ae5d4af | |||
4cc55f4302 | |||
16158983c0 | |||
163d7d2b4b | |||
ef7551df81 | |||
cdb8670dea | |||
b833a8d255 | |||
20a5e3678d | |||
386e70fd1f | |||
9a7503d4dd | |||
109633d2a6 | |||
f79df56a56 | |||
687eef90f7 | |||
c6d60df8ac | |||
4601c4bd8a | |||
9633d3393f | |||
e5416b0023 | |||
a5a186f911 | |||
Tobias Diekershoff | 8aea4acc54 | ||
98406dfcaf | |||
b38d171b08 | |||
fb159c2862 | |||
9a4ac58f67 | |||
2cabf7f37b | |||
381e68edc7 | |||
5559df82a6 | |||
4ea1211ea8 | |||
7eada3d4da | |||
e7bfa257b5 | |||
Roland Häder | d400ed3342 | ||
9105272d4f | |||
Roland Häder | 96ad5a1d39 | ||
c45a2c1454 | |||
2360b60e3d | |||
901c66186e | |||
a93184b38d | |||
de87a461c0 | |||
9af47634e3 | |||
a35dab70b8 | |||
f297c670b3 | |||
68c1939d12 | |||
0747ce0e6e | |||
0159e73544 | |||
a8dc2e65f4 | |||
3095c9cad3 | |||
280d55f183 | |||
783c15c207 | |||
750f081078 | |||
5fb56281d0 | |||
34a5adb9e9 | |||
e82a1e3816 | |||
31d3cb4889 | |||
5c719ba34e | |||
dff9a77968 | |||
52a19916e2 | |||
7277e29c63 | |||
Roland Häder | 2e18a053bb | ||
Roland Häder | 6eafd24872 | ||
Roland Häder | ae04088e39 | ||
Roland Häder | 12e4bff880 | ||
Roland Häder | 1940f6b499 | ||
Roland Häder | c353b31569 | ||
b1f1ac0d62 | |||
18ca878113 | |||
f8906282ef | |||
6b4da31a97 | |||
128f653992 | |||
cd976b8dcc | |||
3f5da87485 | |||
735ca9d6fa | |||
bab5446614 | |||
50b60f6735 | |||
2be31c767c | |||
3e05ac762e | |||
6becf45e73 | |||
a54e512669 | |||
154f33ca86 | |||
1febb1a414 | |||
d89e155947 | |||
ec4a641013 | |||
9b8eaa7aa7 | |||
a11ecf4140 | |||
8cc61e0c68 | |||
9bafa316d4 | |||
dd6987ff02 | |||
cbf217a829 | |||
cf1f60e092 | |||
1bde8a76c4 | |||
e87160d509 | |||
65a6c71496 | |||
b803aa30a0 | |||
1fe9bb9b5b | |||
0026b08a33 | |||
5c477f9b7f | |||
717303e475 | |||
5e1be6b2fe | |||
7276efc1bc | |||
a69a9d2278 | |||
1bac9fb268 | |||
c7d3173080 | |||
40202ea948 | |||
fbf58ea0c4 | |||
55907cf599 | |||
ec7f06ec54 | |||
49507a5149 | |||
574f074dc0 | |||
9d01103af7 | |||
17a5f8cb03 | |||
ed1d37658e | |||
7898d297df | |||
211f729622 | |||
dcf4bf5ec4 | |||
ac23b394cc | |||
eb17d4b9f4 | |||
c153449475 | |||
8cc603f045 | |||
dc73c854d1 | |||
19e37cbf3d | |||
34f83f184c | |||
0e6f862c11 | |||
7ddae278aa | |||
1681dca76b | |||
cc0612173f | |||
a23569cb20 | |||
704857ff63 | |||
3fe0f3fcef | |||
ea309f20ee |
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
*.out
|
||||
.htaccess
|
||||
.htconfig.php
|
||||
#*
|
||||
favicon.*
|
||||
tests/coverage.html
|
||||
/vendor
|
||||
/nbproject/private/
|
23
.htaccess-dist
Normal file
|
@ -0,0 +1,23 @@
|
|||
Options -Indexes
|
||||
AddType application/x-java-archive .jar
|
||||
AddType audio/ogg .oga
|
||||
|
||||
<FilesMatch "\.(out|log)$">
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
# Protect repository directory from browsing
|
||||
RewriteRule "(^|/)\.git" - [F]
|
||||
|
||||
# Rewrite current-style URLs of the form 'index.php?q=x'.
|
||||
# Also place auth information into REMOTE_USER for sites running
|
||||
# in CGI mode.
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php?q=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA]
|
||||
|
||||
</IfModule>
|
||||
|
2
Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
test:
|
||||
cd tests && phpunit --coverage-html=coverage.html && x-www-browser ./coverage.html/index.html
|
144
README.md
|
@ -1,4 +1,142 @@
|
|||
dir
|
||||
===
|
||||
# Decentralized Friendica Directory
|
||||
|
||||
Friendica Global Directory
|
||||
## Installing
|
||||
|
||||
### 1. Copy configuration
|
||||
|
||||
Copy the `htconfig.php` to `.htconfig.php` and enter the database credentials.
|
||||
|
||||
### 2. Initialize the database
|
||||
|
||||
Create a database with a username and a password.
|
||||
Then import ````dfrndir.sql```` to it.
|
||||
|
||||
mysql -u YOURDBUSER -p YOURDBNAME < dfrnlr.sql
|
||||
|
||||
### 3. Create an autoloader with composer
|
||||
|
||||
Make sure you have [composer](https://getcomposer.org/download/) installed globally, or rewrite the command to use a `.phar`.
|
||||
|
||||
```sh
|
||||
composer dump-autoload
|
||||
```
|
||||
|
||||
### 4. Set up the cronjobs.
|
||||
|
||||
Example cronjob using `www-data` user.
|
||||
|
||||
```
|
||||
*/30 * * * * www-data cd /var/www/friendica-directory; php include/cron_maintain.php
|
||||
*/5 * * * * www-data cd /var/www/friendica-directory; php include/cron_sync.php
|
||||
```
|
||||
|
||||
### 5. Copy .htaccess-dist to .htaccess and make local modifications
|
||||
|
||||
## How syncing works
|
||||
|
||||
The new syncing features include: pushing and pulling.
|
||||
|
||||
### Pushing
|
||||
|
||||
Submissions you receive can be submitted to other directories using a push target.
|
||||
|
||||
You do this by creating an entry in the sync-targets table with the push bit set to `1`.
|
||||
Also, you must enable pushing in your `.htconfig` settings.
|
||||
|
||||
The next time `include/cron_sync.php` is run from your cronjob, the queued items will be submitted to your push targets.
|
||||
|
||||
### Pulling
|
||||
|
||||
For pulling to work, the target server must enable pulling.
|
||||
This makes the `/sync/pull/all` and `/sync/pull/since/[when]` methods work on that server.
|
||||
|
||||
Next you can add an entry in the sync-targets table with the pull bit set to `1`.
|
||||
Also, you must enable pulling in your `.htconfig` settings.
|
||||
|
||||
The next time `include/cron_sync.php` is run from your cronjob, the pulling sources will be checked.
|
||||
New items will be queued in your pull queue.
|
||||
The queue will be gradually cleared based on your `syncing.max_pull_items` settings.
|
||||
You can check the backlog of this queue at the `/admin` page.
|
||||
|
||||
**Note**: If you set the bit for pulling or pushing in the MySQL console, it won't be visible in a `SELECT` query.
|
||||
MySQL will however inform you about changed rows after `UPDATE` queries.
|
||||
|
||||
## How submissions are processed
|
||||
|
||||
1. The /submit endpoint takes a `?url=` parameter.
|
||||
This parameter is an encoded URL, the original ASCII is treated as binary and base16 encoded.
|
||||
This URL should be a profile location, such as `https://fc.oscp.info/profile/admin`.
|
||||
This URL will be checked in the database for existing accounts.
|
||||
This check includes a normalization, http vs https is ignored as well as www. prefixes.
|
||||
|
||||
2. If noscrape is supported by the site, this will be used instead of a scrape request.
|
||||
In this case `https://fc.oscp.info/noscrape/admin`.
|
||||
If noscrape fails or is not supported, the url provided (as is) will be scraped for meta information.
|
||||
* `<meta name="dfrn-global-visibility" content="true" />`
|
||||
* `<meta name="friendica.community" content="true" />`
|
||||
or `<meta name="friendika.community" content="true" />`
|
||||
* `<meta name="keywords" content="these,are,your,public,tags" />`
|
||||
* `<link rel="dfrn-*" href="https://fc.oscp.info/*" />`
|
||||
any dfrn-* prefixed link and it's href attribute.
|
||||
* `.vcard .fn` as `fn`
|
||||
* `.vcard .title` as `pdesc`
|
||||
* `.vcard .photo` as `photo`
|
||||
* `.vcard .key` as `key`
|
||||
* `.vcard .locality` as `locality`
|
||||
* `.vcard .region` as `region`
|
||||
* `.vcard .postal-code` as `postal-code`
|
||||
* `.vcard .country-name` as `country-name`
|
||||
|
||||
3. If the `dfrn-global-visibility` value is set to false. Any existing records will be deleted.
|
||||
And the process exits here.
|
||||
|
||||
4. A submission is IGNORED when at least the following data could not be scraped.
|
||||
* `key` the public key from the hCard.
|
||||
* `dfrn-request` required for the DFRN protocol.
|
||||
* `dfrn-confirm` required for the DFRN protocol.
|
||||
* `dfrn-notify` required for the DFRN protocol.
|
||||
* `dfrn-poll` required for the DFRN protocol.
|
||||
|
||||
5. If the profile existed in the database and the profile is not explicitly set to
|
||||
public using the `dfrn-global-visibility` meta tag. It will be deleted.
|
||||
|
||||
6. If the profile existed in the database and the profile lacks either an `fn` or `photo`
|
||||
attribute, it will be deleted.
|
||||
|
||||
7. The profile is now inserted/updated based on the found information.
|
||||
Notable database fields are:
|
||||
* `homepage` the originally (decoded) `?url=` parameter.
|
||||
* `nurl` the normalized URL created to remove http vs https and www vs non-www urls.
|
||||
* `created` the creation date and time in UTC (now if the entry did not exist yet).
|
||||
* `updated` the current date and time in UTC.
|
||||
|
||||
8. If an insert has occurred, the URL will now be used to check for duplicates.
|
||||
The highest insert ID will be kept, anything else deleted.
|
||||
|
||||
9. If provided, your public tags are now split by ` ` (space character) and stored in the tags table.
|
||||
This uses your normalized URL as unique key for your profile.
|
||||
|
||||
10. The `photo` provided will be downloaded and resized to 80x80, regardless of source size.
|
||||
|
||||
11. Should there somehow have been an error at this point such as that there is no profile ID known.
|
||||
Everything will get deleted based on the original `?url=` parameter.
|
||||
|
||||
## Note about search
|
||||
|
||||
The Directory uses MySQL fulltext capabilities to index profiles and offer a search feature.
|
||||
However, the default minimum word size MySQL will index is 4, which ignores words like `PHP` and `USA`.
|
||||
|
||||
To index words smaller than 4 characters, you will have to edit your my.cnf/my.ini file to include this:
|
||||
|
||||
````
|
||||
[mysqld]
|
||||
ft_min_word_len = 3
|
||||
````
|
||||
|
||||
Then restart your MySQL server.
|
||||
|
||||
If you already had data in your profile table, you will need to rebuild the index by executing the following query:
|
||||
|
||||
````
|
||||
REPAIR TABLE `profile` QUICK;
|
||||
````
|
||||
|
|
43
Vagrantfile
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
|
||||
server_ip = "192.168.33.10"
|
||||
server_memory = "384" # MB
|
||||
server_timezone = "UTC"
|
||||
|
||||
public_folder = "/vagrant"
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
|
||||
# Set server to Ubuntu 14.04
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
|
||||
# Disable automatic box update checking. If you disable this, then
|
||||
# boxes will only be checked for updates when the user runs
|
||||
# `vagrant box outdated`. This is not recommended.
|
||||
# config.vm.box_check_update = false
|
||||
|
||||
# Create a hostname, don't forget to put it to the `hosts` file
|
||||
# This will point to the server's default virtual host
|
||||
# TO DO: Make this work with virtualhost along-side xip.io URL
|
||||
config.vm.hostname = "friendica.dev"
|
||||
|
||||
# Create a static IP
|
||||
config.vm.network :private_network, ip: server_ip
|
||||
|
||||
# Share a folder between host and guest
|
||||
config.vm.synced_folder ".", "/vagrant", id: "vagrant-root", owner: "www-data", group: "vagrant"
|
||||
|
||||
# Provider-specific configuration so you can fine-tune various
|
||||
# backing providers for Vagrant. These expose provider-specific options.
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
# # Display the VirtualBox GUI when booting the machine
|
||||
# vb.gui = true
|
||||
#
|
||||
# # Customize the amount of memory on the VM:
|
||||
vb.memory = server_memory
|
||||
end
|
||||
|
||||
# Enable provisioning with a shell script.
|
||||
config.vm.provision "shell", path: "./util/vagrant_provision.sh"
|
||||
# run: "always"
|
||||
# run: "once"
|
||||
end
|
299
assets/css/awesome/font-awesome.min.css
vendored
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*!
|
||||
* Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
|
||||
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License
|
||||
)
|
||||
*/@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eo
|
||||
t?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('e
|
||||
mbedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('wof
|
||||
f2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fon
|
||||
ts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesom
|
||||
e-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font
|
||||
-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwes
|
||||
ome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-mo
|
||||
z-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.3333
|
||||
3333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-si
|
||||
ze:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-
|
||||
align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none
|
||||
}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2
|
||||
.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.f
|
||||
a-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pu
|
||||
ll-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.p
|
||||
ull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear
|
||||
;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s inf
|
||||
inite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin
|
||||
{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform
|
||||
:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transfor
|
||||
m:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);tran
|
||||
sform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.Bas
|
||||
icImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);
|
||||
transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.
|
||||
BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180
|
||||
deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Micr
|
||||
osoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rota
|
||||
te(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTra
|
||||
nsform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1)
|
||||
;-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:prog
|
||||
id:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform
|
||||
:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate
|
||||
-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .f
|
||||
a-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;wid
|
||||
th:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2
|
||||
x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height
|
||||
:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{cont
|
||||
ent:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.
|
||||
fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:b
|
||||
efore{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:
|
||||
"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-
|
||||
th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{co
|
||||
ntent:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d
|
||||
"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"
|
||||
}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear
|
||||
:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-ho
|
||||
me:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{c
|
||||
ontent:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f01
|
||||
9"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{c
|
||||
ontent:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content
|
||||
:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:b
|
||||
efore{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{conten
|
||||
t:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}
|
||||
.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa
|
||||
-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:
|
||||
before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\
|
||||
f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-pr
|
||||
int:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{con
|
||||
tent:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.
|
||||
fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-
|
||||
align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-al
|
||||
ign-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-li
|
||||
st:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.
|
||||
fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-pho
|
||||
to:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:befor
|
||||
e{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{conten
|
||||
t:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:be
|
||||
fore{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-
|
||||
o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:bef
|
||||
ore{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before
|
||||
{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c
|
||||
"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-fo
|
||||
rward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:b
|
||||
efore{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:
|
||||
before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:b
|
||||
efore{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:b
|
||||
efore{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle
|
||||
:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o
|
||||
:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before
|
||||
{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{co
|
||||
ntent:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:
|
||||
"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:befo
|
||||
re{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"
|
||||
\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-
|
||||
exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-le
|
||||
af:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content
|
||||
:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation
|
||||
-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:b
|
||||
efore{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{conte
|
||||
nt:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f07
|
||||
7"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.f
|
||||
a-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-fol
|
||||
der-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h
|
||||
:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f0
|
||||
80"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content
|
||||
:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}
|
||||
.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f
|
||||
086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f
|
||||
088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa
|
||||
-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-
|
||||
thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-s
|
||||
ign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-squar
|
||||
e:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{co
|
||||
ntent:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f09
|
||||
6"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"
|
||||
}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{co
|
||||
ntent:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c
|
||||
"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-
|
||||
o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{con
|
||||
tent:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{cont
|
||||
ent:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:
|
||||
"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{cont
|
||||
ent:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:be
|
||||
fore{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:befo
|
||||
re{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\
|
||||
f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa
|
||||
-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f
|
||||
0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"
|
||||
\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{conte
|
||||
nt:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:befo
|
||||
re{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-squar
|
||||
e:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{c
|
||||
ontent:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f
|
||||
0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0c
|
||||
d"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:b
|
||||
efore{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:
|
||||
before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-
|
||||
plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:befo
|
||||
re{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{con
|
||||
tent:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"
|
||||
\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,
|
||||
.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{cont
|
||||
ent:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0
|
||||
e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-
|
||||
gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:
|
||||
"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e
|
||||
6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"
|
||||
\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before
|
||||
{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{cont
|
||||
ent:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{co
|
||||
ntent:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:
|
||||
"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.f
|
||||
a-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text
|
||||
-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:b
|
||||
efore{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{con
|
||||
tent:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0
|
||||
fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.
|
||||
fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{conten
|
||||
t:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:befor
|
||||
e{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{c
|
||||
ontent:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content
|
||||
:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.f
|
||||
a-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{conten
|
||||
t:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f1
|
||||
0d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.f
|
||||
a-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"
|
||||
\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"
|
||||
}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa
|
||||
-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:bef
|
||||
ore{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{cont
|
||||
ent:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{conten
|
||||
t:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-al
|
||||
l:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.f
|
||||
a-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.
|
||||
fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:b
|
||||
efore,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128
|
||||
"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-sup
|
||||
erscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:
|
||||
before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:be
|
||||
fore{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:befo
|
||||
re{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:b
|
||||
efore{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{conten
|
||||
t:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-righ
|
||||
t:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevro
|
||||
n-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:b
|
||||
efore{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{co
|
||||
ntent:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:
|
||||
"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f1
|
||||
43"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa
|
||||
-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.
|
||||
fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-che
|
||||
ck-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-ex
|
||||
ternal-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d
|
||||
"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-d
|
||||
own:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{con
|
||||
tent:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f
|
||||
152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f15
|
||||
4"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:bef
|
||||
ore{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{
|
||||
content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158
|
||||
"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:befor
|
||||
e{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"
|
||||
\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{cont
|
||||
ent:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:befo
|
||||
re{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-
|
||||
desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down
|
||||
:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:be
|
||||
fore{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{cont
|
||||
ent:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"
|
||||
\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\
|
||||
f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbu
|
||||
cket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumb
|
||||
lr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arro
|
||||
w-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-
|
||||
arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.f
|
||||
a-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:be
|
||||
fore{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{conten
|
||||
t:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"
|
||||
}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:be
|
||||
fore{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-s
|
||||
un-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before
|
||||
{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.f
|
||||
a-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:b
|
||||
efore{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle
|
||||
-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.
|
||||
fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-cir
|
||||
cle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-squ
|
||||
are:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195
|
||||
"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f1
|
||||
97"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}
|
||||
.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-insti
|
||||
tution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-b
|
||||
oard:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"
|
||||
\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-r
|
||||
eddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3
|
||||
"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.f
|
||||
a-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-pip
|
||||
er-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:befor
|
||||
e{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f
|
||||
1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-pa
|
||||
w:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{conten
|
||||
t:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.f
|
||||
a-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-ste
|
||||
am-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobi
|
||||
le:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:
|
||||
"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-d
|
||||
eviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-datab
|
||||
ase:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o
|
||||
:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpo
|
||||
int-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.
|
||||
fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:
|
||||
before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"
|
||||
\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-
|
||||
code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before
|
||||
{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-li
|
||||
fe-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{con
|
||||
tent:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:b
|
||||
efore{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-sq
|
||||
uare:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:befor
|
||||
e{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content
|
||||
:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa
|
||||
-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{c
|
||||
ontent:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circ
|
||||
le-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:b
|
||||
efore{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{co
|
||||
ntent:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{conten
|
||||
t:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:b
|
||||
efore{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{cont
|
||||
ent:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1
|
||||
e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-w
|
||||
ifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:befo
|
||||
re{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{c
|
||||
ontent:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{c
|
||||
ontent:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"
|
||||
\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6
|
||||
"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-c
|
||||
opyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:bef
|
||||
ore{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:bef
|
||||
ore{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{c
|
||||
ontent:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"
|
||||
\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\
|
||||
f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.f
|
||||
a-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:be
|
||||
fore{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:
|
||||
before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-bu
|
||||
ysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-da
|
||||
shcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:be
|
||||
fore{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{c
|
||||
ontent:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{conten
|
||||
t:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{conten
|
||||
t:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa
|
||||
-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-st
|
||||
reet-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:
|
||||
before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{conten
|
||||
t:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{cont
|
||||
ent:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{conte
|
||||
nt:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:
|
||||
"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{conten
|
||||
t:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content
|
||||
:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f2
|
||||
32"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-u
|
||||
ser-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"
|
||||
}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:
|
||||
before{content:"\f239"}.fa-medium:before{content:"\f23a"}
|
453
assets/css/style.css
Normal file
|
@ -0,0 +1,453 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
a {color: inherit;}
|
||||
|
||||
p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#top-bar {
|
||||
background: #f5f5f5;
|
||||
padding: 8px 14px;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
height: 55px;
|
||||
width: 100%;
|
||||
font-weight: 300;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#top-bar-spacer {
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
#top-bar .header {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
line-height: 19px;
|
||||
background: url('/images/friendica.svg') top left no-repeat;
|
||||
background-size: 14px;
|
||||
padding-left: 18px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#top-bar .search-form {
|
||||
display: inline-block;
|
||||
}
|
||||
#top-bar .search-wrapper {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
nav#links {
|
||||
line-height: 39px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
nav#links a {
|
||||
display: inline-block;
|
||||
margin: 0 4px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.sub-menu-outer {
|
||||
background: #eee;
|
||||
line-height: 45px;
|
||||
height: 45px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.sub-menu-outer .sub-menu-inner {
|
||||
width: 500px;
|
||||
margin: auto;
|
||||
}
|
||||
.sub-menu-inner .option {
|
||||
padding: 9px 6px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.sub-menu-inner .option.active {
|
||||
border-bottom: 2px solid #f00;
|
||||
}
|
||||
|
||||
.homepage-wrapper {
|
||||
width: 500px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-results,
|
||||
.directory-results {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.directory-results {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.directory-results > aside {
|
||||
flex: 1 6 20%;
|
||||
order: 1;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.directory-results > section {
|
||||
flex: 3 1 80%;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.homepage-wrapper {
|
||||
margin: 120px auto;
|
||||
}
|
||||
|
||||
.homepage-wrapper .header {
|
||||
font-size: 68px;
|
||||
font-weight: 100;
|
||||
line-height: 0.9em;
|
||||
background: url('/images/friendica.svg') top left no-repeat;
|
||||
background-size: 61px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
.homepage-wrapper .about {
|
||||
text-align: justify;
|
||||
line-height: 1.5em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.homepage-wrapper .profiles {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
.profiles {
|
||||
padding: 0 10px;
|
||||
column-width: 400px;
|
||||
}
|
||||
|
||||
.profiles > figure {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sites {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
nav#links a,
|
||||
.sub-menu-outer a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.search-results .intro {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.site,
|
||||
.profile {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
padding: 2px;
|
||||
outline: none;
|
||||
margin: 5px 0;
|
||||
display: inline;
|
||||
background-color: rgba(0, 0, 0, .01);
|
||||
border-radius: 23px .5em 23px .5em;
|
||||
}
|
||||
|
||||
.site {
|
||||
padding: 14px 2px;
|
||||
margin: 20px 0;
|
||||
border-bottom: 1px dashed #ccc;
|
||||
}
|
||||
|
||||
.profile.selected,
|
||||
.profile: focus,
|
||||
.profile: hover {
|
||||
padding: 1px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.site .site-info,
|
||||
.site .site-supports,
|
||||
.profile .profile-photo,
|
||||
.profile .profile-info {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
.site .site-supports {
|
||||
text-align: right;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.profile .profile-photo {
|
||||
float: left;
|
||||
margin: 8px;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.site .site-info,
|
||||
.profile .profile-info {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.site .site-info strong,
|
||||
.profile .profile-info strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
.site .site-info .fa,
|
||||
.profile .profile-info .fa {
|
||||
line-height: 1.1em;
|
||||
color: #999;
|
||||
}
|
||||
.site .site-info .url,
|
||||
.profile .profile-info .url {
|
||||
font-size: 80%;
|
||||
margin-bottom: 3px;
|
||||
color: #555;
|
||||
|
||||
/** @see https: //css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.profile .profile-info .description {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 500px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
height: 38px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.search-wrapper .search-field {
|
||||
line-height: 22px;
|
||||
display: block;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
padding: 8px 12px;
|
||||
padding-right: 117px;
|
||||
width: 100%;
|
||||
}
|
||||
.search-wrapper .reset {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 65px;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin: 6px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
background: #eee;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 20px;
|
||||
font-family: 'FontAwesome';
|
||||
font-weight: 100;
|
||||
line-height: 25px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.search-wrapper .search {
|
||||
border: none;
|
||||
border-left: 1px solid #ddd;
|
||||
color: #555;
|
||||
background: #eee;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 36px;
|
||||
width: 65px;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.health {font-size: 120%; vertical-align: bottom;}
|
||||
.health.very-bad { color: #f99; }
|
||||
.health.bad { color: #f1ba7a; }
|
||||
.health.neutral { color: #e6e782; }
|
||||
.health.ok { color: #bef273; }
|
||||
.health.good { color: #7cf273; }
|
||||
.health.perfect { color: #33ff80; }
|
||||
|
||||
.btn {
|
||||
border: 1px solid #ddd;
|
||||
color: #555;
|
||||
background: #eee;
|
||||
border-radius: 8px 8px;
|
||||
height: 36px;
|
||||
line-height: 34px;
|
||||
min-width: 80px;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* smaller than tablet in portrait */
|
||||
@media screen and (max-width: 880px) {
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
#top-bar .header {
|
||||
overflow: hidden;
|
||||
width: 0px;
|
||||
padding-left: 28px;
|
||||
height: 28px;
|
||||
background-size: 28px;
|
||||
}
|
||||
|
||||
#top-bar .search-form {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 35px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.homepage-wrapper,
|
||||
.search-wrapper {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
.homepage-wrapper .search-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.homepage-wrapper {
|
||||
margin: 90px auto;
|
||||
}
|
||||
|
||||
.search-results,
|
||||
.sub-menu-outer .sub-menu-inner {
|
||||
width: 95%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
padding: 8px;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nav#links {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 54px;
|
||||
right: 0;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
nav#links .viewport {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 100%;
|
||||
width: 100%;
|
||||
transition: left ease 200ms;
|
||||
background: #fff;
|
||||
padding: 20px 0;
|
||||
box-shadow: 1px 3px 3px rgba(0,0,0,0.2);
|
||||
font-size: 150%;
|
||||
text-align: center;
|
||||
}
|
||||
nav#links.open .viewport {
|
||||
left: 0%;
|
||||
}
|
||||
#top-bar nav#links .viewport {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
nav#links h3 {
|
||||
margin: 0;
|
||||
display: block;
|
||||
line-height: 2em;
|
||||
}
|
||||
nav#links a {
|
||||
display: block;
|
||||
line-height: 2.5em;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.profile,
|
||||
.profile .profile-info {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.directory-results {
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.directory-results > aside {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.directory-results > section {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* The moment the header starts getting squashed */
|
||||
@media screen and (max-width: 520px) {
|
||||
.homepage-wrapper {
|
||||
margin: 30px auto;
|
||||
}
|
||||
.homepage-wrapper .header {
|
||||
font-size: 40px;
|
||||
background-size: 36px;
|
||||
display: inline-block;
|
||||
margin-left: 18px;
|
||||
width: 275px;
|
||||
}
|
||||
}
|
||||
|
||||
/* close to mobile in portrait */
|
||||
@media screen and (max-width: 400px) {
|
||||
|
||||
.profile .profile-photo {
|
||||
width: 60px;
|
||||
border-radius: 11.25px;
|
||||
margin: 8px 4px;
|
||||
}
|
||||
.profile .profile-info .url {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
692
assets/js/ajaxupload.js
Normal file
|
@ -0,0 +1,692 @@
|
|||
/**
|
||||
* AJAX Upload ( http://valums.com/ajax-upload/ )
|
||||
* Copyright (c) Andris Valums
|
||||
* Licensed under the MIT license ( http://valums.com/mit-license/ )
|
||||
* Thanks to Gary Haran, David Mark, Corey Burns and others for contributions.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
/* global window */
|
||||
/* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */
|
||||
|
||||
/**
|
||||
* Wrapper for FireBug's console.log
|
||||
*/
|
||||
function log(){
|
||||
if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){
|
||||
Array.prototype.unshift.call(arguments, '[Ajax Upload]');
|
||||
console.log( Array.prototype.join.call(arguments, ' '));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches event to a dom element.
|
||||
* @param {Element} el
|
||||
* @param type event name
|
||||
* @param fn callback This refers to the passed element
|
||||
*/
|
||||
function addEvent(el, type, fn){
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener(type, fn, false);
|
||||
} else if (el.attachEvent) {
|
||||
el.attachEvent('on' + type, function(){
|
||||
fn.call(el);
|
||||
});
|
||||
} else {
|
||||
throw new Error('not supported or DOM not loaded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches resize event to a window, limiting
|
||||
* number of event fired. Fires only when encounteres
|
||||
* delay of 100 after series of events.
|
||||
*
|
||||
* Some browsers fire event multiple times when resizing
|
||||
* http://www.quirksmode.org/dom/events/resize.html
|
||||
*
|
||||
* @param fn callback This refers to the passed element
|
||||
*/
|
||||
function addResizeEvent(fn){
|
||||
var timeout;
|
||||
|
||||
addEvent(window, 'resize', function(){
|
||||
if (timeout){
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(fn, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Needs more testing, will be rewriten for next version
|
||||
// getOffset function copied from jQuery lib (http://jquery.com/)
|
||||
if (document.documentElement.getBoundingClientRect){
|
||||
// Get Offset using getBoundingClientRect
|
||||
// http://ejohn.org/blog/getboundingclientrect-is-awesome/
|
||||
var getOffset = function(el){
|
||||
var box = el.getBoundingClientRect();
|
||||
var doc = el.ownerDocument;
|
||||
var body = doc.body;
|
||||
var docElem = doc.documentElement; // for ie
|
||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
||||
|
||||
// In Internet Explorer 7 getBoundingClientRect property is treated as physical,
|
||||
// while others are logical. Make all logical, like in IE8.
|
||||
var zoom = 1;
|
||||
if (body.getBoundingClientRect) {
|
||||
var bound = body.getBoundingClientRect();
|
||||
zoom = (bound.right - bound.left) / body.clientWidth;
|
||||
}
|
||||
|
||||
if (zoom > 1) {
|
||||
clientTop = 0;
|
||||
clientLeft = 0;
|
||||
}
|
||||
|
||||
var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;
|
||||
|
||||
return {
|
||||
top: top,
|
||||
left: left
|
||||
};
|
||||
};
|
||||
} else {
|
||||
// Get offset adding all offsets
|
||||
var getOffset = function(el){
|
||||
var top = 0, left = 0;
|
||||
do {
|
||||
top += el.offsetTop || 0;
|
||||
left += el.offsetLeft || 0;
|
||||
el = el.offsetParent;
|
||||
} while (el);
|
||||
|
||||
return {
|
||||
left: left,
|
||||
top: top
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns left, top, right and bottom properties describing the border-box,
|
||||
* in pixels, with the top-left relative to the body
|
||||
* @param {Element} el
|
||||
* @return {Object} Contains left, top, right,bottom
|
||||
*/
|
||||
function getBox(el){
|
||||
var left, right, top, bottom;
|
||||
var offset = getOffset(el);
|
||||
left = offset.left;
|
||||
top = offset.top;
|
||||
|
||||
right = left + el.offsetWidth;
|
||||
bottom = top + el.offsetHeight;
|
||||
|
||||
return {
|
||||
left: left,
|
||||
right: right,
|
||||
top: top,
|
||||
bottom: bottom
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that takes object literal
|
||||
* and add all properties to element.style
|
||||
* @param {Element} el
|
||||
* @param {Object} styles
|
||||
*/
|
||||
function addStyles(el, styles){
|
||||
for (var name in styles) {
|
||||
if (styles.hasOwnProperty(name)) {
|
||||
el.style[name] = styles[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function places an absolutely positioned
|
||||
* element on top of the specified element
|
||||
* copying position and dimentions.
|
||||
* @param {Element} from
|
||||
* @param {Element} to
|
||||
*/
|
||||
function copyLayout(from, to){
|
||||
var box = getBox(from);
|
||||
|
||||
addStyles(to, {
|
||||
position: 'absolute',
|
||||
left : box.left + 'px',
|
||||
top : box.top + 'px',
|
||||
width : from.offsetWidth + 'px',
|
||||
height : from.offsetHeight + 'px'
|
||||
});
|
||||
to.title = from.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns element from html chunk
|
||||
* Uses innerHTML to create an element
|
||||
*/
|
||||
var toElement = (function(){
|
||||
var div = document.createElement('div');
|
||||
return function(html){
|
||||
div.innerHTML = html;
|
||||
var el = div.firstChild;
|
||||
return div.removeChild(el);
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Function generates unique id
|
||||
* @return unique id
|
||||
*/
|
||||
var getUID = (function(){
|
||||
var id = 0;
|
||||
return function(){
|
||||
return 'ValumsAjaxUpload' + id++;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Get file name from path
|
||||
* @param {String} file path to file
|
||||
* @return filename
|
||||
*/
|
||||
function fileFromPath(file){
|
||||
return file.replace(/.*(\/|\\)/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension lowercase
|
||||
* @param {String} file name
|
||||
* @return file extenstion
|
||||
*/
|
||||
function getExt(file){
|
||||
return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : '';
|
||||
}
|
||||
|
||||
function hasClass(el, name){
|
||||
var re = new RegExp('\\b' + name + '\\b');
|
||||
return re.test(el.className);
|
||||
}
|
||||
function addClass(el, name){
|
||||
if ( ! hasClass(el, name)){
|
||||
el.className += ' ' + name;
|
||||
}
|
||||
}
|
||||
function removeClass(el, name){
|
||||
var re = new RegExp('\\b' + name + '\\b');
|
||||
el.className = el.className.replace(re, '');
|
||||
}
|
||||
|
||||
function removeNode(el){
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy styling and uploading
|
||||
* @constructor
|
||||
* @param button An element you want convert to
|
||||
* upload button. Tested dimentions up to 500x500px
|
||||
* @param {Object} options See defaults below.
|
||||
*/
|
||||
window.AjaxUpload = function(button, options){
|
||||
this._settings = {
|
||||
// Location of the server-side upload script
|
||||
action: 'upload.php',
|
||||
// File upload name
|
||||
name: 'userfile',
|
||||
// Additional data to send
|
||||
data: {},
|
||||
// Submit file as soon as it's selected
|
||||
autoSubmit: true,
|
||||
// The type of data that you're expecting back from the server.
|
||||
// html and xml are detected automatically.
|
||||
// Only useful when you are using json data as a response.
|
||||
// Set to "json" in that case.
|
||||
responseType: false,
|
||||
// Class applied to button when mouse is hovered
|
||||
hoverClass: 'hover',
|
||||
// Class applied to button when button is focused
|
||||
focusClass: 'focus',
|
||||
// Class applied to button when AU is disabled
|
||||
disabledClass: 'disabled',
|
||||
// When user selects a file, useful with autoSubmit disabled
|
||||
// You can return false to cancel upload
|
||||
onChange: function(file, extension){
|
||||
},
|
||||
// Callback to fire before file is uploaded
|
||||
// You can return false to cancel upload
|
||||
onSubmit: function(file, extension){
|
||||
},
|
||||
// Fired when file upload is completed
|
||||
// WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
|
||||
onComplete: function(file, response){
|
||||
}
|
||||
};
|
||||
|
||||
// Merge the users options with our defaults
|
||||
for (var i in options) {
|
||||
if (options.hasOwnProperty(i)){
|
||||
this._settings[i] = options[i];
|
||||
}
|
||||
}
|
||||
|
||||
// button isn't necessary a dom element
|
||||
if (button.jquery){
|
||||
// jQuery object was passed
|
||||
button = button[0];
|
||||
} else if (typeof button == "string") {
|
||||
if (/^#.*/.test(button)){
|
||||
// If jQuery user passes #elementId don't break it
|
||||
button = button.slice(1);
|
||||
}
|
||||
|
||||
button = document.getElementById(button);
|
||||
}
|
||||
|
||||
if ( ! button || button.nodeType !== 1){
|
||||
throw new Error("Please make sure that you're passing a valid element");
|
||||
}
|
||||
|
||||
if ( button.nodeName.toUpperCase() == 'A'){
|
||||
// disable link
|
||||
addEvent(button, 'click', function(e){
|
||||
if (e && e.preventDefault){
|
||||
e.preventDefault();
|
||||
} else if (window.event){
|
||||
window.event.returnValue = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// DOM element
|
||||
this._button = button;
|
||||
// DOM element
|
||||
this._input = null;
|
||||
// If disabled clicking on button won't do anything
|
||||
this._disabled = false;
|
||||
|
||||
// if the button was disabled before refresh if will remain
|
||||
// disabled in FireFox, let's fix it
|
||||
this.enable();
|
||||
|
||||
this._rerouteClicks();
|
||||
};
|
||||
|
||||
// assigning methods to our class
|
||||
AjaxUpload.prototype = {
|
||||
setData: function(data){
|
||||
this._settings.data = data;
|
||||
},
|
||||
disable: function(){
|
||||
addClass(this._button, this._settings.disabledClass);
|
||||
this._disabled = true;
|
||||
|
||||
var nodeName = this._button.nodeName.toUpperCase();
|
||||
if (nodeName == 'INPUT' || nodeName == 'BUTTON'){
|
||||
this._button.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
// hide input
|
||||
if (this._input){
|
||||
// We use visibility instead of display to fix problem with Safari 4
|
||||
// The problem is that the value of input doesn't change if it
|
||||
// has display none when user selects a file
|
||||
this._input.parentNode.style.visibility = 'hidden';
|
||||
}
|
||||
},
|
||||
enable: function(){
|
||||
removeClass(this._button, this._settings.disabledClass);
|
||||
this._button.removeAttribute('disabled');
|
||||
this._disabled = false;
|
||||
|
||||
},
|
||||
/**
|
||||
* Creates invisible file input
|
||||
* that will hover above the button
|
||||
* <div><input type='file' /></div>
|
||||
*/
|
||||
_createInput: function(){
|
||||
var self = this;
|
||||
|
||||
var input = document.createElement("input");
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('name', this._settings.name);
|
||||
|
||||
addStyles(input, {
|
||||
'position' : 'absolute',
|
||||
// in Opera only 'browse' button
|
||||
// is clickable and it is located at
|
||||
// the right side of the input
|
||||
'right' : 0,
|
||||
'margin' : 0,
|
||||
'padding' : 0,
|
||||
'fontSize' : '480px',
|
||||
// in Firefox if font-family is set to
|
||||
// 'inherit' the input doesn't work
|
||||
'fontFamily' : 'sans-serif',
|
||||
'cursor' : 'pointer'
|
||||
});
|
||||
|
||||
var div = document.createElement("div");
|
||||
addStyles(div, {
|
||||
'display' : 'block',
|
||||
'position' : 'absolute',
|
||||
'overflow' : 'hidden',
|
||||
'margin' : 0,
|
||||
'padding' : 0,
|
||||
'opacity' : 0,
|
||||
// Make sure browse button is in the right side
|
||||
// in Internet Explorer
|
||||
'direction' : 'ltr',
|
||||
//Max zIndex supported by Opera 9.0-9.2
|
||||
'zIndex': 2147483583
|
||||
});
|
||||
|
||||
// Make sure that element opacity exists.
|
||||
// Otherwise use IE filter
|
||||
if ( div.style.opacity !== "0") {
|
||||
if (typeof(div.filters) == 'undefined'){
|
||||
throw new Error('Opacity not supported by the browser');
|
||||
}
|
||||
div.style.filter = "alpha(opacity=0)";
|
||||
}
|
||||
|
||||
addEvent(input, 'change', function(){
|
||||
|
||||
if ( ! input || input.value === ''){
|
||||
return;
|
||||
}
|
||||
|
||||
// Get filename from input, required
|
||||
// as some browsers have path instead of it
|
||||
var file = fileFromPath(input.value);
|
||||
|
||||
if (false === self._settings.onChange.call(self, file, getExt(file))){
|
||||
self._clearInput();
|
||||
return;
|
||||
}
|
||||
|
||||
// Submit form when value is changed
|
||||
if (self._settings.autoSubmit) {
|
||||
self.submit();
|
||||
}
|
||||
});
|
||||
|
||||
addEvent(input, 'mouseover', function(){
|
||||
addClass(self._button, self._settings.hoverClass);
|
||||
});
|
||||
|
||||
addEvent(input, 'mouseout', function(){
|
||||
removeClass(self._button, self._settings.hoverClass);
|
||||
removeClass(self._button, self._settings.focusClass);
|
||||
|
||||
// We use visibility instead of display to fix problem with Safari 4
|
||||
// The problem is that the value of input doesn't change if it
|
||||
// has display none when user selects a file
|
||||
input.parentNode.style.visibility = 'hidden';
|
||||
|
||||
});
|
||||
|
||||
addEvent(input, 'focus', function(){
|
||||
addClass(self._button, self._settings.focusClass);
|
||||
});
|
||||
|
||||
addEvent(input, 'blur', function(){
|
||||
removeClass(self._button, self._settings.focusClass);
|
||||
});
|
||||
|
||||
div.appendChild(input);
|
||||
document.body.appendChild(div);
|
||||
|
||||
this._input = input;
|
||||
},
|
||||
_clearInput : function(){
|
||||
if (!this._input){
|
||||
return;
|
||||
}
|
||||
|
||||
// this._input.value = ''; Doesn't work in IE6
|
||||
removeNode(this._input.parentNode);
|
||||
this._input = null;
|
||||
this._createInput();
|
||||
|
||||
removeClass(this._button, this._settings.hoverClass);
|
||||
removeClass(this._button, this._settings.focusClass);
|
||||
},
|
||||
/**
|
||||
* Function makes sure that when user clicks upload button,
|
||||
* the this._input is clicked instead
|
||||
*/
|
||||
_rerouteClicks: function(){
|
||||
var self = this;
|
||||
|
||||
// IE will later display 'access denied' error
|
||||
// if you use using self._input.click()
|
||||
// other browsers just ignore click()
|
||||
|
||||
addEvent(self._button, 'mouseover', function(){
|
||||
if (self._disabled){
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! self._input){
|
||||
self._createInput();
|
||||
}
|
||||
|
||||
var div = self._input.parentNode;
|
||||
copyLayout(self._button, div);
|
||||
div.style.visibility = 'visible';
|
||||
|
||||
});
|
||||
|
||||
|
||||
// commented because we now hide input on mouseleave
|
||||
/**
|
||||
* When the window is resized the elements
|
||||
* can be misaligned if button position depends
|
||||
* on window size
|
||||
*/
|
||||
//addResizeEvent(function(){
|
||||
// if (self._input){
|
||||
// copyLayout(self._button, self._input.parentNode);
|
||||
// }
|
||||
//});
|
||||
|
||||
},
|
||||
/**
|
||||
* Creates iframe with unique name
|
||||
* @return {Element} iframe
|
||||
*/
|
||||
_createIframe: function(){
|
||||
// We can't use getTime, because it sometimes return
|
||||
// same value in safari :(
|
||||
var id = getUID();
|
||||
|
||||
// We can't use following code as the name attribute
|
||||
// won't be properly registered in IE6, and new window
|
||||
// on form submit will open
|
||||
// var iframe = document.createElement('iframe');
|
||||
// iframe.setAttribute('name', id);
|
||||
|
||||
var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
|
||||
// src="javascript:false; was added
|
||||
// because it possibly removes ie6 prompt
|
||||
// "This page contains both secure and nonsecure items"
|
||||
// Anyway, it doesn't do any harm.
|
||||
iframe.setAttribute('id', id);
|
||||
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
return iframe;
|
||||
},
|
||||
/**
|
||||
* Creates form, that will be submitted to iframe
|
||||
* @param {Element} iframe Where to submit
|
||||
* @return {Element} form
|
||||
*/
|
||||
_createForm: function(iframe){
|
||||
var settings = this._settings;
|
||||
|
||||
// We can't use the following code in IE6
|
||||
// var form = document.createElement('form');
|
||||
// form.setAttribute('method', 'post');
|
||||
// form.setAttribute('enctype', 'multipart/form-data');
|
||||
// Because in this case file won't be attached to request
|
||||
var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
|
||||
|
||||
form.setAttribute('action', settings.action);
|
||||
form.setAttribute('target', iframe.name);
|
||||
form.style.display = 'none';
|
||||
document.body.appendChild(form);
|
||||
|
||||
// Create hidden input element for each data key
|
||||
for (var prop in settings.data) {
|
||||
if (settings.data.hasOwnProperty(prop)){
|
||||
var el = document.createElement("input");
|
||||
el.setAttribute('type', 'hidden');
|
||||
el.setAttribute('name', prop);
|
||||
el.setAttribute('value', settings.data[prop]);
|
||||
form.appendChild(el);
|
||||
}
|
||||
}
|
||||
return form;
|
||||
},
|
||||
/**
|
||||
* Gets response from iframe and fires onComplete event when ready
|
||||
* @param iframe
|
||||
* @param file Filename to use in onComplete callback
|
||||
*/
|
||||
_getResponse : function(iframe, file){
|
||||
// getting response
|
||||
var toDeleteFlag = false, self = this, settings = this._settings;
|
||||
|
||||
addEvent(iframe, 'load', function(){
|
||||
|
||||
if (// For Safari
|
||||
iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
|
||||
// For FF, IE
|
||||
iframe.src == "javascript:'<html></html>';"){
|
||||
// First time around, do not delete.
|
||||
// We reload to blank page, so that reloading main page
|
||||
// does not re-submit the post.
|
||||
|
||||
if (toDeleteFlag) {
|
||||
// Fix busy state in FF3
|
||||
setTimeout(function(){
|
||||
removeNode(iframe);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var doc = iframe.contentDocument ? iframe.contentDocument : window.frames[iframe.id].document;
|
||||
|
||||
// fixing Opera 9.26,10.00
|
||||
if (doc.readyState && doc.readyState != 'complete') {
|
||||
// Opera fires load event multiple times
|
||||
// Even when the DOM is not ready yet
|
||||
// this fix should not affect other browsers
|
||||
return;
|
||||
}
|
||||
|
||||
// fixing Opera 9.64
|
||||
if (doc.body && doc.body.innerHTML == "false") {
|
||||
// In Opera 9.64 event was fired second time
|
||||
// when body.innerHTML changed from false
|
||||
// to server response approx. after 1 sec
|
||||
return;
|
||||
}
|
||||
|
||||
var response;
|
||||
|
||||
if (doc.XMLDocument) {
|
||||
// response is a xml document Internet Explorer property
|
||||
response = doc.XMLDocument;
|
||||
} else if (doc.body){
|
||||
// response is html document or plain text
|
||||
response = doc.body.innerHTML;
|
||||
|
||||
if (settings.responseType && settings.responseType.toLowerCase() == 'json') {
|
||||
// If the document was sent as 'application/javascript' or
|
||||
// 'text/javascript', then the browser wraps the text in a <pre>
|
||||
// tag and performs html encoding on the contents. In this case,
|
||||
// we need to pull the original text content from the text node's
|
||||
// nodeValue property to retrieve the unmangled content.
|
||||
// Note that IE6 only understands text/html
|
||||
if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {
|
||||
doc.normalize();
|
||||
response = doc.body.firstChild.firstChild.nodeValue;
|
||||
}
|
||||
|
||||
if (response) {
|
||||
response = eval("(" + response + ")");
|
||||
} else {
|
||||
response = {};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// response is a xml document
|
||||
response = doc;
|
||||
}
|
||||
|
||||
settings.onComplete.call(self, file, response);
|
||||
|
||||
// Reload blank page, so that reloading main page
|
||||
// does not re-submit the post. Also, remember to
|
||||
// delete the frame
|
||||
toDeleteFlag = true;
|
||||
|
||||
// Fix IE mixed content issue
|
||||
iframe.src = "javascript:'<html></html>';";
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Upload file contained in this._input
|
||||
*/
|
||||
submit: function(){
|
||||
var self = this, settings = this._settings;
|
||||
|
||||
if ( ! this._input || this._input.value === ''){
|
||||
return;
|
||||
}
|
||||
|
||||
var file = fileFromPath(this._input.value);
|
||||
|
||||
// user returned false to cancel upload
|
||||
if (false === settings.onSubmit.call(this, file, getExt(file))){
|
||||
this._clearInput();
|
||||
return;
|
||||
}
|
||||
|
||||
// sending request
|
||||
var iframe = this._createIframe();
|
||||
var form = this._createForm(iframe);
|
||||
|
||||
// assuming following structure
|
||||
// div -> input type='file'
|
||||
removeNode(this._input.parentNode);
|
||||
removeClass(self._button, self._settings.hoverClass);
|
||||
removeClass(self._button, self._settings.focusClass);
|
||||
|
||||
form.appendChild(this._input);
|
||||
|
||||
form.submit();
|
||||
|
||||
// request set, clean up
|
||||
removeNode(form); form = null;
|
||||
removeNode(this._input); this._input = null;
|
||||
|
||||
// Get response from iframe and fire onComplete event when ready
|
||||
this._getResponse(iframe, file);
|
||||
|
||||
// get ready for next request
|
||||
this._createInput();
|
||||
}
|
||||
};
|
||||
})();
|
4
assets/js/jquery/jquery.min.js
vendored
Normal file
30
assets/js/main.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
jQuery(function($){
|
||||
|
||||
//Mobile menu, hamburger button.
|
||||
$('body').on('click', '.hamburger', function(e){
|
||||
e.preventDefault();
|
||||
$('nav#links').toggleClass('open');
|
||||
});
|
||||
|
||||
// Show/hide the reset link if the search field is populated/empty
|
||||
function updateResetButton() {
|
||||
if ($('.search-field').val()) {
|
||||
$('.reset').show();
|
||||
} else {
|
||||
$('.reset').hide();
|
||||
}
|
||||
}
|
||||
$('body').on('keyup', '.search-field', updateResetButton)
|
||||
|
||||
updateResetButton();
|
||||
|
||||
// Makes the entire profile card clickable...
|
||||
$('body').on('click', '.profile', function(event) {
|
||||
window.open(event.currentTarget.dataset.href, '_blank');
|
||||
});
|
||||
|
||||
// ...while keeping inner a tags clickable
|
||||
$('body').on('click', '.profile a', function(event) {
|
||||
event.stopPropagation();
|
||||
})
|
||||
});
|
15
assets/js/raphael/g_line.min.js
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*!
|
||||
* g.Raphael 0.51 - Charting library, based on Raphaël
|
||||
*
|
||||
* Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
|
||||
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
|
||||
*/
|
||||
(function(){function S(h,o){for(var p=h.length/o,m=0,k=p,b=0,i=[];m<h.length;)k--,0>k?(b+=h[m]*(1+k),i.push(b/p),b=h[m++]*-k,k+=p):b+=1*h[m++];return i}function E(h,o,p,m,k,b,i,c){var F,f,u,w;function J(a){for(var s=[],e=0,G=b.length;e<G;e++)s=s.concat(b[e]);s.sort(function(a,e){return a-e});for(var c=[],g=[],e=0,G=s.length;e<G;e++)s[e]!=s[e-1]&&c.push(s[e])&&g.push(o+d+(s[e]-v)*A);for(var s=c,G=s.length,l=a||h.set(),e=0;e<G;e++){var c=g[e]-(g[e]-(g[e-1]||o))/2,f=((g[e+1]||o+m)-g[e])/2+(g[e]-(g[e-
|
||||
1]||o))/2,j;a?j={}:l.push(j=h.rect(c-1,p,Math.max(f+1,1),k).attr({stroke:"none",fill:"#000",opacity:0}));j.values=[];j.symbols=h.set();j.y=[];j.x=g[e];j.axis=s[e];for(var f=0,r=i.length;f<r;f++)for(var c=b[f]||b[0],n=0,u=c.length;n<u;n++)c[n]==s[e]&&(j.values.push(i[f][n]),j.y.push(p+k-d-(i[f][n]-y)*H),j.symbols.push(q.symbols[f][n]));a&&a.call(j)}!a&&(t=l)}function N(a){for(var g=a||h.set(),e,c=0,j=i.length;c<j;c++)for(var f=0,m=i[c].length;f<m;f++){var l=o+d+((b[c]||b[0])[f]-v)*A,n=o+d+((b[c]||
|
||||
b[0])[f?f-1:1]-v)*A,r=p+k-d-(i[c][f]-y)*H;a?e={}:g.push(e=h.circle(l,r,Math.abs(n-l)/2).attr({stroke:"#000",fill:"#000",opacity:1}));e.x=l;e.y=r;e.value=i[c][f];e.line=q.lines[c];e.shade=q.shades[c];e.symbol=q.symbols[c][f];e.symbols=q.symbols[c];e.axis=(b[c]||b[0])[f];a&&a.call(e)}!a&&(C=g)}c=c||{};h.raphael.is(b[0],"array")||(b=[b]);h.raphael.is(i[0],"array")||(i=[i]);for(var d=c.gutter||10,l=Math.max(b[0].length,i[0].length),O=c.symbol||"",P=c.colors||this.colors,t=null,C=null,q=h.set(),g=[],a=
|
||||
0,n=i.length;a<n;a++)l=Math.max(l,i[a].length);for(var K=h.set(),a=0,n=i.length;a<n;a++)c.shade&&K.push(h.path().attr({stroke:"none",fill:P[a],opacity:c.nostroke?1:0.3})),i[a].length>m-2*d&&(i[a]=S(i[a],m-2*d),l=m-2*d),b[a]&&b[a].length>m-2*d&&(b[a]=S(b[a],m-2*d));var g=Array.prototype.concat.apply([],b),l=Array.prototype.concat.apply([],i),g=this.snapEnds(Math.min.apply(Math,g),Math.max.apply(Math,g),b[0].length-1),v=g.from,g=g.to,l=this.snapEnds(Math.min.apply(Math,l),Math.max.apply(Math,l),i[0].length-
|
||||
1),y=l.from,a=l.to,A=(m-2*d)/(g-v||1),H=(k-2*d)/(a-y||1),l=h.set();c.axis&&(n=(c.axis+"").split(/[,\s]+/),+n[0]&&l.push(this.axis(o+d,p+d,m-2*d,v,g,c.axisxstep||Math.floor((m-2*d)/20),2,h)),+n[1]&&l.push(this.axis(o+m-d,p+k-d,k-2*d,y,a,c.axisystep||Math.floor((k-2*d)/20),3,h)),+n[2]&&l.push(this.axis(o+d,p+k-d,m-2*d,v,g,c.axisxstep||Math.floor((m-2*d)/20),0,h)),+n[3]&&l.push(this.axis(o+d,p+k-d,k-2*d,y,a,c.axisystep||Math.floor((k-2*d)/20),1,h)));for(var Q=h.set(),R=h.set(),E,a=0,n=i.length;a<n;a++){c.nostroke||
|
||||
Q.push(E=h.path().attr({stroke:P[a],"stroke-width":c.width||2,"stroke-linejoin":"round","stroke-linecap":"round","stroke-dasharray":c.dash||""}));for(var D=Raphael.is(O,"array")?O[a]:O,I=h.set(),g=[],j=0,T=i[a].length;j<T;j++){var x=o+d+((b[a]||b[0])[j]-v)*A,z=p+k-d-(i[a][j]-y)*H;(Raphael.is(D,"array")?D[j]:D)&&I.push(h[Raphael.is(D,"array")?D[j]:D](x,z,3*(c.width||2)).attr({fill:P[a],stroke:"none"}));if(c.smooth){if(j&&j!=T-1){f=o+d+((b[a]||b[0])[j-1]-v)*A;var L=p+k-d-(i[a][j-1]-y)*H;u=x;w=z;var r=
|
||||
o+d+((b[a]||b[0])[j+1]-v)*A,B=p+k-d-(i[a][j+1]-y)*H,M=(u-f)/2;F=(r-u)/2;f=Math.atan((u-f)/Math.abs(w-L));r=Math.atan((r-u)/Math.abs(w-B));f=L<w?Math.PI-f:f;r=B<w?Math.PI-r:r;B=Math.PI/2-(f+r)%(2*Math.PI)/2;L=M*Math.sin(B+f);f=M*Math.cos(B+f);M=F*Math.sin(B+r);r=F*Math.cos(B+r);F=u-L;f=w+f;u+=M;w+=r;g=g.concat([F,f,x,z,u,w])}j||(g=["M",x,z,"C",x,z])}else g=g.concat([j?"L":"M",x,z])}c.smooth&&(g=g.concat([x,z,x,z]));R.push(I);c.shade&&K[a].attr({path:g.concat(["L",x,p+k-d,"L",o+d+((b[a]||b[0])[0]-v)*
|
||||
A,p+k-d,"z"]).join(",")});!c.nostroke&&E.attr({path:g.join(",")})}q.push(Q,K,R,l,t,C);q.lines=Q;q.shades=K;q.symbols=R;q.axis=l;q.hoverColumn=function(a,c){!t&&J();t.mouseover(a).mouseout(c);return this};q.clickColumn=function(a){!t&&J();t.click(a);return this};q.hrefColumn=function(a){var c=h.raphael.is(arguments[0],"array")?arguments[0]:arguments;if(!(arguments.length-1)&&typeof a=="object")for(var e in a)for(var b=0,d=t.length;b<d;b++)t[b].axis==e&&t[b].attr("href",a[e]);!t&&J();b=0;for(d=c.length;b<
|
||||
d;b++)t[b]&&t[b].attr("href",c[b]);return this};q.hover=function(a,b){!C&&N();C.mouseover(a).mouseout(b);return this};q.click=function(a){!C&&N();C.click(a);return this};q.each=function(a){N(a);return this};q.eachColumn=function(a){J(a);return this};return q}var I=function(){};I.prototype=Raphael.g;E.prototype=new I;Raphael.fn.linechart=function(h,o,p,m,k,b,i){return new E(this,h,o,p,m,k,b,i)}})();
|
7
assets/js/raphael/g_rahael.js
Normal file
11
assets/js/raphael/raphael.js
Normal file
10
bin/console
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
dir=$(cd "${0%[/\\]*}" > /dev/null; pwd)
|
||||
|
||||
if [[ -d /proc/cygdrive && $(which php) == $(readlink -n /proc/cygdrive)/* ]]; then
|
||||
# We are in Cgywin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
fi
|
||||
|
||||
php "${dir}/console.php" "$@"
|
4
bin/console.bat
Normal file
|
@ -0,0 +1,4 @@
|
|||
@echo OFF
|
||||
:: in case DelayedExpansion is on and a path contains !
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
php "%~dp0console.php" %*
|
6
bin/console.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
include_once dirname(__DIR__) . '/boot.php';
|
||||
|
||||
(new Friendica\Directory\Core\Console($argv))->execute();
|
348
boot.php
Normal file
|
@ -0,0 +1,348 @@
|
|||
<?php
|
||||
|
||||
require_once 'include/session.php';
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
define('BUILD_ID', 1000);
|
||||
|
||||
define('EOL', "<br />\r\n");
|
||||
|
||||
define('REGISTER_CLOSED', 0);
|
||||
define('REGISTER_APPROVE', 1);
|
||||
define('REGISTER_OPEN', 2);
|
||||
|
||||
define('DIRECTION_NONE', 0);
|
||||
define('DIRECTION_IN', 1);
|
||||
define('DIRECTION_OUT', 2);
|
||||
define('DIRECTION_BOTH', 3);
|
||||
|
||||
define('NOTIFY_INTRO', 0x0001);
|
||||
define('NOTIFY_CONFIRM', 0x0002);
|
||||
define('NOTIFY_WALL', 0x0004);
|
||||
define('NOTIFY_COMMENT', 0x0008);
|
||||
define('NOTIFY_MAIL', 0x0010);
|
||||
|
||||
define('NAMESPACE_DFRN', 'http://purl.org/macgirvin/dfrn/1.0');
|
||||
|
||||
/**
|
||||
* log levels
|
||||
*/
|
||||
define('LOGGER_NORMAL', 0);
|
||||
define('LOGGER_TRACE', 1);
|
||||
define('LOGGER_DEBUG', 2);
|
||||
define('LOGGER_DATA', 3);
|
||||
define('LOGGER_ALL', 4);
|
||||
|
||||
if (!function_exists('x')) {
|
||||
function x($s, $k = null)
|
||||
{
|
||||
if ($k != null) {
|
||||
if ((is_array($s)) && (array_key_exists($k, $s))) {
|
||||
if ($s[$k]) {
|
||||
return (int) 1;
|
||||
}
|
||||
return (int) 0;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (isset($s)) {
|
||||
if ($s) {
|
||||
return (int) 1;
|
||||
}
|
||||
return (int) 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('system_unavailable')) {
|
||||
function system_unavailable()
|
||||
{
|
||||
include('system_unavailable.php');
|
||||
killme();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('logger')) {
|
||||
function logger($msg, $level = 0)
|
||||
{
|
||||
$debugging = 1;
|
||||
$loglevel = LOGGER_ALL;
|
||||
$logfile = 'logfile.out';
|
||||
|
||||
if ((!$debugging) || (!$logfile) || ($level > $loglevel)) {
|
||||
return;
|
||||
}
|
||||
require_once('include/datetime.php');
|
||||
|
||||
@file_put_contents($logfile, datetime_convert() . ' [#' . getmypid() . '] ' . $msg . "\n", FILE_APPEND);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('replace_macros')) {
|
||||
function replace_macros($s, $r)
|
||||
{
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
if (is_array($r) && count($r)) {
|
||||
foreach ($r as $k => $v) {
|
||||
$search[] = $k;
|
||||
$replace[] = $v;
|
||||
}
|
||||
}
|
||||
return str_replace($search, $replace, $s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('load_translation_table')) {
|
||||
function load_translation_table($lang)
|
||||
{
|
||||
global $a;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('t')) {
|
||||
function t($s)
|
||||
{
|
||||
global $a;
|
||||
|
||||
if ($a->strings[$s]) {
|
||||
return $a->strings[$s];
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('fetch_url')) {
|
||||
function fetch_url($url, $binary = false, $timeout = 20)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if (!$ch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, max(intval($timeout), 1)); //Minimum of 1 second timeout.
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 8);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
if ($binary) {
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$s = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return($s);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('post_url')) {
|
||||
function post_url($url, $params)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if (!$ch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 8);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
|
||||
$s = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return($s);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('random_string')) {
|
||||
function random_string()
|
||||
{
|
||||
return(hash('sha256', uniqid(rand(), true)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('notags')) {
|
||||
function notags($string)
|
||||
{
|
||||
// protect against :<> with high-bit set
|
||||
return(str_replace(array("<", ">", "\xBA", "\xBC", "\xBE"), array('[', ']', '', '', ''), $string));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('escape_tags')) {
|
||||
function escape_tags($string)
|
||||
{
|
||||
return(htmlspecialchars($string));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('login')) {
|
||||
function login($register = false)
|
||||
{
|
||||
$o = "";
|
||||
$register_html = (($register) ? file_get_contents("view/register-link.tpl") : "");
|
||||
|
||||
|
||||
if (x($_SESSION, 'authenticated')) {
|
||||
$o = file_get_contents("view/logout.tpl");
|
||||
} else {
|
||||
$o = file_get_contents("view/login.tpl");
|
||||
|
||||
$o = replace_macros($o, array('$register_html' => $register_html));
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('killme')) {
|
||||
function killme()
|
||||
{
|
||||
session_write_close();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('goaway')) {
|
||||
function goaway($s)
|
||||
{
|
||||
header("Location: $s");
|
||||
killme();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('local_user')) {
|
||||
function local_user()
|
||||
{
|
||||
if ((x($_SESSION, 'authenticated')) && (x($_SESSION, 'uid'))) {
|
||||
return $_SESSION['uid'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('notice')) {
|
||||
function notice($s)
|
||||
{
|
||||
if (!isset($_SESSION['sysmsg'])) {
|
||||
$_SESSION['sysmsg'] = '';
|
||||
}
|
||||
$_SESSION['sysmsg'] .= $s;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('hex2bin')) {
|
||||
function hex2bin($s)
|
||||
{
|
||||
return(pack("H*", $s));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('paginate')) {
|
||||
function paginate(&$a)
|
||||
{
|
||||
$o = '';
|
||||
$stripped = preg_replace("/&page=[0-9]*/", "", $a->query_string);
|
||||
$stripped = str_replace('q=', '', $stripped);
|
||||
$stripped = trim($stripped, '/');
|
||||
$pagenum = $a->pager['page'];
|
||||
$url = $a->get_baseurl() . '/' . $stripped;
|
||||
|
||||
if ($a->pager['total'] > $a->pager['itemspage']) {
|
||||
$o .= '<div class="pager">';
|
||||
if ($a->pager['page'] != 1) {
|
||||
$o .= '<span class="pager_prev">' . "<a href=\"$url" . '&page=' . ($a->pager['page'] - 1) . '">' . t('prev') . '</a></span> ';
|
||||
}
|
||||
|
||||
$o .= "<span class=\"pager_first\"><a href=\"$url" . "&page=1\">" . t('first') . "</a></span> ";
|
||||
|
||||
$numpages = $a->pager['total'] / $a->pager['itemspage'];
|
||||
|
||||
$numstart = 1;
|
||||
$numstop = $numpages;
|
||||
|
||||
if ($numpages > 14) {
|
||||
$numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
|
||||
$numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
|
||||
}
|
||||
|
||||
for ($i = $numstart; $i <= $numstop; $i++) {
|
||||
if ($i == $a->pager['page']) {
|
||||
$o .= '<span class="pager_current">' . (($i < 10) ? ' ' . $i : $i);
|
||||
} else {
|
||||
$o .= "<span class=\"pager_n\"><a href=\"$url" . "&page=$i\">" . (($i < 10) ? ' ' . $i : $i) . "</a>";
|
||||
}
|
||||
$o .= '</span> ';
|
||||
}
|
||||
|
||||
if (($a->pager['total'] % $a->pager['itemspage']) != 0) {
|
||||
if ($i == $a->pager['page']) {
|
||||
$o .= '<span class="pager_current">' . (($i < 10) ? ' ' . $i : $i);
|
||||
} else {
|
||||
$o .= "<span class=\"pager_n\"><a href=\"$url" . "&page=$i\">" . (($i < 10) ? ' ' . $i : $i) . "</a>";
|
||||
}
|
||||
$o .= '</span> ';
|
||||
}
|
||||
|
||||
$lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages);
|
||||
$o .= "<span class=\"pager_last\"><a href=\"$url" . "&page=$lastpage\">" . t('last') . "</a></span> ";
|
||||
|
||||
if (($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0) {
|
||||
$o .= '<span class="pager_next">' . "<a href=\"$url" . "&page=" . ($a->pager['page'] + 1) . '">' . t('next') . '</a></span>';
|
||||
}
|
||||
$o .= '</div>' . "\r\n";
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
|
||||
function get_my_url()
|
||||
{
|
||||
if (x($_SESSION, 'my_url')) {
|
||||
return $_SESSION['my_url'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function zrl($s, $force = false)
|
||||
{
|
||||
if (!strlen($s)) {
|
||||
return $s;
|
||||
}
|
||||
if ((!strpos($s, '/profile/')) && (!$force)) {
|
||||
return $s;
|
||||
}
|
||||
$achar = strpos($s, '?') ? '&' : '?';
|
||||
$mine = get_my_url();
|
||||
if ($mine and ! link_compare($mine, $s)) {
|
||||
return $s . $achar . 'zrl=' . urlencode($mine);
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
if (!function_exists('link_compare')) {
|
||||
function link_compare($a, $b)
|
||||
{
|
||||
if (strcasecmp(normalise_link($a), normalise_link($b)) === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('normalise_link')) {
|
||||
function normalise_link($url)
|
||||
{
|
||||
$ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
|
||||
return(rtrim($ret, '/'));
|
||||
}
|
||||
}
|
15
composer.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "friendica/dir",
|
||||
"description": "The internet is our social network",
|
||||
"license": "AGPL3",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Friendica\\Directory\\": "src"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"mrpetovan/net_ping": "^1.0",
|
||||
"asika/simple-console": "^1.0"
|
||||
}
|
||||
}
|
171
composer.lock
generated
Normal file
|
@ -0,0 +1,171 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "92a5912148dfd08b1bc438eace6cede9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asika/simple-console",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/asika32764/php-simple-console.git",
|
||||
"reference": "0b624c1a999849dc6481a47182e58d593bf65068"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/asika32764/php-simple-console/zipball/0b624c1a999849dc6481a47182e58d593bf65068",
|
||||
"reference": "0b624c1a999849dc6481a47182e58d593bf65068",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Asika\\SimpleConsole\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Simon Asika",
|
||||
"email": "asika32764@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "One file console framework to help you write build scripts.",
|
||||
"time": "2018-03-08T12:05:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mrpetovan/net_ping",
|
||||
"version": "1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MrPetovan/Net_Ping.git",
|
||||
"reference": "cb82b6f068012cec757c7ac93f79c5af2a3f8255"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MrPetovan/Net_Ping/zipball/cb82b6f068012cec757c7ac93f79c5af2a3f8255",
|
||||
"reference": "cb82b6f068012cec757c7ac93f79c5af2a3f8255",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"pear/pear_exception": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Net": "./"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"include-path": [
|
||||
"./"
|
||||
],
|
||||
"license": [
|
||||
"PHP-3.01"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Craig Constantine",
|
||||
"email": "cconstantine@php.net",
|
||||
"role": "Lead"
|
||||
},
|
||||
{
|
||||
"name": "Martin Jansen",
|
||||
"email": "mj@php.net",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Jan Lehnardt",
|
||||
"email": "jan@php.net",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Thomas V.V.Cox",
|
||||
"email": "cox@php.net",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Hypolite Petovan",
|
||||
"email": "hypolite@mrpetovan.com",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "More info available on: http://pear.php.net/package/Net_Ping",
|
||||
"time": "2018-11-08T12:14:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pear/pear_exception",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pear/PEAR_Exception.git",
|
||||
"reference": "8c18719fdae000b690e3912be401c76e406dd13b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/8c18719fdae000b690e3912be401c76e406dd13b",
|
||||
"reference": "8c18719fdae000b690e3912be401c76e406dd13b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=4.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"type": "class",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PEAR": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"include-path": [
|
||||
"."
|
||||
],
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Helgi Thormar",
|
||||
"email": "dufuz@php.net"
|
||||
},
|
||||
{
|
||||
"name": "Greg Beaver",
|
||||
"email": "cellog@php.net"
|
||||
}
|
||||
],
|
||||
"description": "The PEAR Exception base class.",
|
||||
"homepage": "https://github.com/pear/PEAR_Exception",
|
||||
"keywords": [
|
||||
"exception"
|
||||
],
|
||||
"time": "2015-02-10T20:07:52+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
261
dfrndir.sql
Normal file
|
@ -0,0 +1,261 @@
|
|||
-- Generation Time: Apr 21, 2017 at 03:58 AM
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
--
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `flag`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `flag` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`pid` int(11) NOT NULL,
|
||||
`reason` int(11) NOT NULL,
|
||||
`total` int(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `photo`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `photo` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`profile-id` int(11) NOT NULL,
|
||||
`data` mediumblob NOT NULL,
|
||||
`score` float NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=2516 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `profile`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `profile` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` char(255) NOT NULL,
|
||||
`nurl` char(255) NOT NULL,
|
||||
`comm` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`pdesc` char(255) NOT NULL,
|
||||
`locality` char(255) NOT NULL,
|
||||
`region` char(255) NOT NULL,
|
||||
`postal-code` char(32) NOT NULL,
|
||||
`country-name` char(255) NOT NULL,
|
||||
`homepage` char(255) NOT NULL,
|
||||
`photo` char(255) NOT NULL,
|
||||
`tags` longtext NOT NULL,
|
||||
`available` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`censored` tinyint(4) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`(250)),
|
||||
KEY `nurl` (`nurl`(250)),
|
||||
KEY `comm` (`comm`),
|
||||
KEY `pdesc` (`pdesc`(250)),
|
||||
KEY `locality` (`locality`(250)),
|
||||
KEY `region` (`region`(250)),
|
||||
KEY `country-name` (`country-name`(250)),
|
||||
KEY `homepage` (`homepage`(250))
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=2518 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `session`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `session` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`sid` char(255) NOT NULL,
|
||||
`data` mediumtext NOT NULL,
|
||||
`expire` int(10) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sid` (`sid`(250)),
|
||||
KEY `expire` (`expire`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=22917 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `site` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` char(255) NOT NULL,
|
||||
`url` char(255) NOT NULL,
|
||||
`version` char(16) NOT NULL,
|
||||
`plugins` mediumtext NOT NULL,
|
||||
`reg_policy` char(32) NOT NULL,
|
||||
`info` mediumtext NOT NULL,
|
||||
`admin_name` char(255) NOT NULL,
|
||||
`admin_profile` char(255) NOT NULL,
|
||||
`updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site-health`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `site-health` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`base_url` varchar(255) NOT NULL,
|
||||
`effective_base_url` varchar(255) DEFAULT NULL,
|
||||
`health_score` int(11) NOT NULL DEFAULT '0',
|
||||
`no_scrape_url` varchar(255) DEFAULT NULL,
|
||||
`dt_first_noticed` datetime NOT NULL,
|
||||
`dt_last_seen` datetime DEFAULT NULL,
|
||||
`dt_last_probed` datetime DEFAULT NULL,
|
||||
`dt_last_heartbeat` datetime DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`version` varchar(255) DEFAULT NULL,
|
||||
`addons` mediumtext,
|
||||
`reg_policy` char(32) DEFAULT NULL,
|
||||
`info` mediumtext,
|
||||
`admin_name` varchar(255) DEFAULT NULL,
|
||||
`admin_profile` varchar(255) DEFAULT NULL,
|
||||
`ssl_state` bit(1) DEFAULT NULL,
|
||||
`ssl_grade` varchar(3) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `base_url` (`base_url`(250)),
|
||||
KEY `health_score` (`health_score`),
|
||||
KEY `dt_last_seen` (`dt_last_seen`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=10035 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site-probe`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `site-probe` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`site_health_id` int(10) UNSIGNED NOT NULL,
|
||||
`dt_performed` datetime NOT NULL,
|
||||
`request_time` int(10) UNSIGNED NOT NULL,
|
||||
`avg_ping` int(11) DEFAULT NULL,
|
||||
`speed_score` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `site_health_id` (`site_health_id`),
|
||||
KEY `dt_performed` (`dt_performed`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=28987 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site-scrape`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `site-scrape` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`site_health_id` int(10) UNSIGNED NOT NULL,
|
||||
`dt_performed` datetime NOT NULL,
|
||||
`request_time` int(10) UNSIGNED NOT NULL,
|
||||
`scrape_time` int(10) UNSIGNED NOT NULL,
|
||||
`photo_time` int(10) UNSIGNED NOT NULL,
|
||||
`total_time` int(10) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `site_health_id` (`site_health_id`),
|
||||
KEY `dt_performed` (`dt_performed`)
|
||||
) ENGINE=MyISAM AUTO_INCREMENT=177675 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sync-pull-queue`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sync-pull-queue` (
|
||||
`url` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`url`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sync-push-queue`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sync-push-queue` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`url` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `url` (`url`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sync-targets`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sync-targets` (
|
||||
`base_url` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
`pull` bit(1) NOT NULL DEFAULT b'0',
|
||||
`push` bit(1) NOT NULL DEFAULT b'1',
|
||||
`dt_last_pull` bigint(20) UNSIGNED DEFAULT NULL,
|
||||
PRIMARY KEY (`base_url`),
|
||||
KEY `push` (`push`),
|
||||
KEY `pull` (`pull`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `sync-timestamps`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sync-timestamps` (
|
||||
`url` varchar(255) NOT NULL,
|
||||
`modified` datetime NOT NULL,
|
||||
PRIMARY KEY (`url`),
|
||||
KEY `modified` (`modified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tag`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `tag` (
|
||||
`term` varchar(50) NOT NULL,
|
||||
`profile_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`term`,`profile_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `user`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`uid` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` char(255) NOT NULL,
|
||||
`password` char(255) NOT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `profile`
|
||||
--
|
||||
ALTER TABLE `profile` ADD FULLTEXT KEY `tags` (`tags`);
|
||||
ALTER TABLE `profile` ADD FULLTEXT KEY `profile-ft` (`name`,`pdesc`,`homepage`,`locality`,`region`,`country-name`,`tags`);
|
14
example.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
//Add the auto loader. This makes sure that we can find the files we need for a class.
|
||||
require_once('vendor/autoload.php');
|
||||
|
||||
//This says, we want Hello to mean Friendica\Directory\Example\Hello.
|
||||
//It's a shortcut.
|
||||
use Friendica\Directory\Example\Hello;
|
||||
|
||||
//Here we use the shortcut and create a new Hello object.
|
||||
$instance = new Hello();
|
||||
|
||||
//Let the Hello object call
|
||||
echo $instance->sayHello();
|
91
htconfig.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This is the default config file. Copy it to ".htconfig.php" and make your
|
||||
* local changes there.
|
||||
*/
|
||||
|
||||
//MySQL host.
|
||||
$db_host = 'localhost';
|
||||
$db_user = 'root';
|
||||
$db_pass = 'root';
|
||||
$db_data = 'friendica_dir';
|
||||
|
||||
// Choose a legal default timezone. If you are unsure, use "America/Los_Angeles".
|
||||
// It can be changed later and only applies to timestamps for anonymous viewers.
|
||||
$default_timezone = 'Europe/Amsterdam';
|
||||
|
||||
// What is your site name?
|
||||
$a->config['sitename'] = "EXPERIMENTAL Friendica public directory";
|
||||
|
||||
//Statistic display settings.
|
||||
$a->config['stats'] = array(
|
||||
|
||||
//For site health, the max age for which to display data.
|
||||
'maxDataAge' => 3600*24*30*4 //120 days = ~4 months
|
||||
|
||||
);
|
||||
|
||||
//Settings related to the syncing feature.
|
||||
$a->config['syncing'] = array(
|
||||
|
||||
//Pulling may be quite intensive at first when it has to do a full sync and your directory is empty.
|
||||
//This timeout should be shorter than your cronjob interval. Preferably with a little breathing room.
|
||||
'timeout' => 3*60, //3 minutes
|
||||
|
||||
//Push new submits to the `sync-target` entries?
|
||||
'enable_pushing' => true,
|
||||
|
||||
//Maximum amount of items per batch per target to push to other sync-targets.
|
||||
//For example: 3 targets x20 items = 60 requests.
|
||||
'max_push_items' => 10,
|
||||
|
||||
//Pull updates from the `sync-target` entries?
|
||||
'enable_pulling' => true,
|
||||
|
||||
//This is your normal amount of threads for pulling.
|
||||
//With regular intervals, there's no need to give this a high value.
|
||||
//But when your server is brand new, you may want to keep this high for the first day or two.
|
||||
'pulling_threads' => 25,
|
||||
|
||||
//How many items should we crawl per sync?
|
||||
'max_pull_items' => 250
|
||||
|
||||
);
|
||||
|
||||
//Things related to site-health monitoring.
|
||||
$a->config['site-health'] = array(
|
||||
|
||||
//Wait for at least ... before probing a site again.
|
||||
//The longer this value, the more "stable" site-healths will be over time.
|
||||
//Note: If a bad (negative) health site submits something, a probe will be performed regardless.
|
||||
'min_probe_delay' => 24*3600, // 1 day
|
||||
|
||||
//Probes get a simple /friendica/json file from the server.
|
||||
//Feel free to set this timeout to a very tight value.
|
||||
'probe_timeout' => 5, // seconds
|
||||
|
||||
//Imports should be fast. Feel free to prioritize healthy sites.
|
||||
'skip_import_threshold' => -20
|
||||
|
||||
);
|
||||
|
||||
//Things related to the maintenance cronjob.
|
||||
$a->config['maintenance'] = array(
|
||||
|
||||
//This is to prevent I/O blocking. Will cost you some RAM overhead though.
|
||||
//A good server should handle much more than this default, so you can tweak this.
|
||||
'threads' => 10,
|
||||
|
||||
//Limit the amount of scrapes per execution of the maintainer.
|
||||
//This will depend a lot on the frequency with which you call the maintainer.
|
||||
//If you have 10 threads and 80 max_scrapes, that means each thread will handle 8 scrapes.
|
||||
'max_scrapes' => 80,
|
||||
|
||||
//Wait for at least ... before scraping a profile again.
|
||||
'min_scrape_delay' => 3*24*3600, // 3 days
|
||||
|
||||
//At which health value should we start removing profiles?
|
||||
'remove_profile_health_threshold' => -60
|
||||
|
||||
);
|
BIN
images/b_block.gif
Normal file
After Width: | Height: | Size: 83 B |
BIN
images/b_drop.gif
Normal file
After Width: | Height: | Size: 138 B |
BIN
images/b_drop.png
Normal file
After Width: | Height: | Size: 311 B |
BIN
images/b_drophide.gif
Normal file
After Width: | Height: | Size: 111 B |
BIN
images/b_dropshow.gif
Normal file
After Width: | Height: | Size: 138 B |
BIN
images/b_edit.gif
Normal file
After Width: | Height: | Size: 311 B |
BIN
images/b_edit.png
Normal file
After Width: | Height: | Size: 451 B |
BIN
images/blue_flag_16.png
Normal file
After Width: | Height: | Size: 1,003 B |
BIN
images/camera-icon.gif
Normal file
After Width: | Height: | Size: 1,015 B |
BIN
images/default-profile-sm.jpg
Normal file
After Width: | Height: | Size: 325 B |
BIN
images/default-profile.jpg
Normal file
After Width: | Height: | Size: 469 B |
BIN
images/dfrn.gif
Normal file
After Width: | Height: | Size: 104 B |
BIN
images/dislike.gif
Normal file
After Width: | Height: | Size: 133 B |
BIN
images/friendica-128.jpg
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
images/friendica-128.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
images/friendica-16.jpg
Normal file
After Width: | Height: | Size: 659 B |
BIN
images/friendica-16.png
Normal file
After Width: | Height: | Size: 756 B |
BIN
images/friendica-1600.png
Normal file
After Width: | Height: | Size: 280 KiB |
BIN
images/friendica-256.jpg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
images/friendica-256.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
images/friendica-32.jpg
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
images/friendica-32.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
images/friendica-48.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
images/friendica-64.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/friendica-64.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
images/friendica-96.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
185
images/friendica.svg
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
width="100"
|
||||
height="100"
|
||||
id="svg2">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="highlightgradient">
|
||||
<stop
|
||||
id="stop3833"
|
||||
style="stop-color:#ffffff;stop-opacity:0.74374998"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3829"
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="shadowgradient">
|
||||
<stop
|
||||
id="stop3833-5"
|
||||
style="stop-color:#000000;stop-opacity:0.5"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3829-9"
|
||||
style="stop-color:#818080;stop-opacity:0"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="44.948269"
|
||||
y1="0"
|
||||
x2="54.103466"
|
||||
y2="46.797421"
|
||||
id="linearGradient4011"
|
||||
xlink:href="#highlightgradient"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1,0.54545455)" />
|
||||
<linearGradient
|
||||
x1="52.016712"
|
||||
y1="96"
|
||||
x2="42.867535"
|
||||
y2="41.837971"
|
||||
id="linearGradient4021"
|
||||
xlink:href="#shadowgradient"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.5,0,48)" />
|
||||
<filter
|
||||
x="-0.029999999"
|
||||
y="-0.12"
|
||||
width="1.0599999"
|
||||
height="1.24"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter4055">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4057"
|
||||
stdDeviation="1.2" />
|
||||
</filter>
|
||||
<filter
|
||||
x="-0.029877551"
|
||||
y="-0.122"
|
||||
width="1.0597551"
|
||||
height="1.244"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter4059">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4061"
|
||||
stdDeviation="1.22" />
|
||||
</filter>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-952.3622)"
|
||||
id="layer1"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="m 18,954.3622 c -8.9908981,0.0431 -16,7.05218 -16,16 0,0 0,41.4991 0,64 0,9.1201 7.0091019,16 16,16 l 16,0 0,-26 32,0 -0.08398,-23.9316 -31.91602,0.1679 0,-20.2363 32,0 0,-26 c 0,0 -40,0 -48,0 z"
|
||||
id="rect2993"
|
||||
style="fill:#ffc019;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
d="m 82,1050.3622 c 8.990898,0 16.086165,-6.966 16,-16 0,0 0,-41.4991 0,-64 0.07767,-9.01639 -7.067354,-16 -16,-16 l -16,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26 c 0,0 32,0 48,0 z"
|
||||
id="rect2993-6"
|
||||
style="fill:#1872a2;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,4)"
|
||||
id="g3997"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="m 66,-2 0,26 -32,0 0,22 m 32,0 0,22 -32,0 0,26"
|
||||
id="path3999"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
width="96"
|
||||
height="96"
|
||||
rx="16"
|
||||
ry="16"
|
||||
x="2"
|
||||
y="-2"
|
||||
id="rect4001"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,4)"
|
||||
id="layer3"
|
||||
style="display:none">
|
||||
<path
|
||||
d="m 64,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26"
|
||||
id="path3926"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
width="96"
|
||||
height="96"
|
||||
rx="16"
|
||||
ry="16"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect3928"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,4)"
|
||||
id="layer2"
|
||||
style="display:inline">
|
||||
<rect
|
||||
width="96"
|
||||
height="48.04369"
|
||||
rx="15.214664"
|
||||
ry="15.215644"
|
||||
x="2"
|
||||
y="-2"
|
||||
id="rect3823"
|
||||
style="fill:url(#linearGradient3930);fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="96"
|
||||
height="47.86721"
|
||||
rx="15.214664"
|
||||
ry="15.159752"
|
||||
x="2"
|
||||
y="-94"
|
||||
transform="scale(1,-1)"
|
||||
id="rect3823-8"
|
||||
style="fill:url(#linearGradient3904);fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="98"
|
||||
height="24"
|
||||
rx="15.214664"
|
||||
ry="8.2994423"
|
||||
x="0"
|
||||
y="0"
|
||||
transform="matrix(1.0296115,0,0,1.1963836,-0.901924,-6.713207)"
|
||||
id="rect4003"
|
||||
style="fill:url(#linearGradient4011);fill-opacity:1;stroke:none;filter:url(#filter4059)" />
|
||||
<rect
|
||||
width="96"
|
||||
height="24"
|
||||
rx="14.008356"
|
||||
ry="12"
|
||||
x="0"
|
||||
y="72"
|
||||
transform="matrix(0.9768331,0,0,0.91974646,3.1649641,6.098115)"
|
||||
id="rect4013"
|
||||
style="opacity:0.5674603;fill:url(#linearGradient4021);fill-opacity:1;stroke:none;filter:url(#filter4055)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
BIN
images/friendika-128.jpg
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
images/friendika-128.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
images/friendika-16.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
images/friendika-16.jpg
Normal file
After Width: | Height: | Size: 659 B |
BIN
images/friendika-16.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
images/friendika-1600.png
Normal file
After Width: | Height: | Size: 280 KiB |
BIN
images/friendika-256.jpg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
images/friendika-256.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
images/friendika-32.jpg
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
images/friendika-32.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
images/friendika-64.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/friendika-64.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
240
images/friendika.svg
Normal file
|
@ -0,0 +1,240 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.0 r9654"
|
||||
sodipodi:docname="friendika.svg"
|
||||
inkscape:export-filename="/home/meta/Documents/My random images/friendika.png"
|
||||
inkscape:export-xdpi="80.552788"
|
||||
inkscape:export-ydpi="80.552788">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="highlightgradient">
|
||||
<stop
|
||||
id="stop3833"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:0.74374998;" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3829" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="shadowgradient">
|
||||
<stop
|
||||
id="stop3833-5"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:0.5;" />
|
||||
<stop
|
||||
style="stop-color:#818080;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3829-9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#highlightgradient"
|
||||
id="linearGradient4011"
|
||||
x1="44.948269"
|
||||
y1="0"
|
||||
x2="54.103466"
|
||||
y2="46.797421"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="scale(1,0.54545455)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#shadowgradient"
|
||||
id="linearGradient4021"
|
||||
x1="52.016712"
|
||||
y1="96"
|
||||
x2="42.867535"
|
||||
y2="41.837971"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.5,0,48)" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter4055"
|
||||
x="-0.03"
|
||||
width="1.06"
|
||||
y="-0.12"
|
||||
height="1.24">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="1.2"
|
||||
id="feGaussianBlur4057" />
|
||||
</filter>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter4059"
|
||||
x="-0.029877551"
|
||||
width="1.0597551"
|
||||
y="-0.122"
|
||||
height="1.244">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="1.22"
|
||||
id="feGaussianBlur4061" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.9132799"
|
||||
inkscape:cx="53.033009"
|
||||
inkscape:cy="2.8284271"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
width="256px"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1010"
|
||||
inkscape:window-x="194"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="3"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="2px"
|
||||
spacingy="2px" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Colors"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-956.3622)"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#ffc019;fill-opacity:1;stroke:none"
|
||||
d="M 16,0 C 7.0091019,0.04308252 0,7.0521845 0,16 0,16 0,57.499123 0,80 0,89.120146 7.0091019,96 16,96 L 32,96 32,70 64,70 63.916016,46.068359 32,46.236328 32,26 64,26 64,0 C 64,0 24,0 16,0 z"
|
||||
transform="translate(0,956.3622)"
|
||||
id="rect2993"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccsccccccccc" />
|
||||
<path
|
||||
style="fill:#1872a2;fill-opacity:1;stroke:none"
|
||||
d="m 80,1052.3622 c 8.990898,0 16.086165,-6.966 16,-16 0,0 0,-41.4991 0,-64 0.07767,-9.01639 -7.067354,-16 -16,-16 l -16,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26 c 0,0 32,0 48,0 z"
|
||||
id="rect2993-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccsccccccccc" />
|
||||
</g>
|
||||
<g
|
||||
style="display:inline"
|
||||
inkscape:label="Lines as original logo"
|
||||
id="g3997"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3999"
|
||||
d="m 64,0 0,26 -32,0 0,22 m 32,0 0,22 -32,0 0,26"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
ry="16"
|
||||
rx="16"
|
||||
y="0"
|
||||
x="0"
|
||||
height="96"
|
||||
width="96"
|
||||
id="rect4001"
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Lines with center break"
|
||||
style="display:none">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 64,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26"
|
||||
id="path3926"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3928"
|
||||
width="96"
|
||||
height="96"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="16"
|
||||
ry="16" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Effects"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:url(#linearGradient3930);fill-opacity:1;stroke:none"
|
||||
id="rect3823"
|
||||
width="96"
|
||||
height="48.04369"
|
||||
x="-3.1086245e-15"
|
||||
y="1.8024861e-14"
|
||||
ry="15.215644"
|
||||
rx="15.214664" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient3904);fill-opacity:1;stroke:none"
|
||||
id="rect3823-8"
|
||||
width="96"
|
||||
height="47.86721"
|
||||
x="1.5376101e-14"
|
||||
y="-96"
|
||||
ry="15.159752"
|
||||
rx="15.214664"
|
||||
transform="scale(1,-1)" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient4011);fill-opacity:1;stroke:none;filter:url(#filter4059)"
|
||||
id="rect4003"
|
||||
width="98"
|
||||
height="24"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="15.214664"
|
||||
ry="8.2994423"
|
||||
transform="matrix(1.0296115,0,0,1.1963836,-2.901924,-4.7132067)" />
|
||||
<rect
|
||||
style="opacity:0.56746030000000003;fill:url(#linearGradient4021);fill-opacity:1;stroke:none;filter:url(#filter4055)"
|
||||
id="rect4013"
|
||||
width="96"
|
||||
height="24"
|
||||
x="0"
|
||||
y="72"
|
||||
rx="14.008356"
|
||||
ry="12"
|
||||
transform="matrix(0.9768331,0,0,0.91974646,1.1649641,8.098115)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
BIN
images/larrow.gif
Normal file
After Width: | Height: | Size: 212 B |
BIN
images/larrw.gif
Normal file
After Width: | Height: | Size: 1,004 B |
BIN
images/like.gif
Normal file
After Width: | Height: | Size: 132 B |
BIN
images/link-icon.gif
Normal file
After Width: | Height: | Size: 145 B |
BIN
images/lock_icon.gif
Normal file
After Width: | Height: | Size: 932 B |
BIN
images/lrarrow.gif
Normal file
After Width: | Height: | Size: 236 B |
BIN
images/no.gif
Normal file
After Width: | Height: | Size: 631 B |
BIN
images/pen.png
Normal file
After Width: | Height: | Size: 252 B |
BIN
images/penhover.png
Normal file
After Width: | Height: | Size: 270 B |
BIN
images/rarrow.gif
Normal file
After Width: | Height: | Size: 212 B |
BIN
images/rarrw.gif
Normal file
After Width: | Height: | Size: 999 B |
BIN
images/rotator.gif
Normal file
After Width: | Height: | Size: 826 B |
BIN
images/shield_2_16.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
images/star.jpg
Normal file
After Width: | Height: | Size: 589 B |
BIN
images/unlock_icon.gif
Normal file
After Width: | Height: | Size: 938 B |
188
include/Photo.php
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
class Photo
|
||||
{
|
||||
private $image;
|
||||
private $width;
|
||||
private $height;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->image = @imagecreatefromstring($data);
|
||||
if ($this->image !== FALSE) {
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->image) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function getImage()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
public function scaleImage($max)
|
||||
{
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
$dest_width = $dest_height = 0;
|
||||
|
||||
if ((!$width) || (!$height)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($width > $max && $height > $max) {
|
||||
if ($width > $height) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(( $height * $max ) / $width);
|
||||
} else {
|
||||
$dest_width = intval(( $width * $max ) / $height);
|
||||
$dest_height = $max;
|
||||
}
|
||||
} else {
|
||||
if ($width > $max) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(( $height * $max ) / $width);
|
||||
} else {
|
||||
if ($height > $max) {
|
||||
$dest_width = intval(( $width * $max ) / $height);
|
||||
$dest_height = $max;
|
||||
} else {
|
||||
$dest_width = $width;
|
||||
$dest_height = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dest = imagecreatetruecolor($dest_width, $dest_height);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function scaleImageUp($min)
|
||||
{
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
$dest_width = $dest_height = 0;
|
||||
|
||||
if ((!$width) || (!$height)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($width < $min && $height < $min) {
|
||||
if ($width > $height) {
|
||||
$dest_width = $min;
|
||||
$dest_height = intval(( $height * $min ) / $width);
|
||||
} else {
|
||||
$dest_width = intval(( $width * $min ) / $height);
|
||||
$dest_height = $min;
|
||||
}
|
||||
} else {
|
||||
if ($width < $min) {
|
||||
$dest_width = $min;
|
||||
$dest_height = intval(( $height * $min ) / $width);
|
||||
} else {
|
||||
if ($height < $min) {
|
||||
$dest_width = intval(( $width * $min ) / $height);
|
||||
$dest_height = $min;
|
||||
} else {
|
||||
$dest_width = $width;
|
||||
$dest_height = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dest = imagecreatetruecolor($dest_width, $dest_height);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function scaleImageSquare($dim)
|
||||
{
|
||||
$dest = imagecreatetruecolor($dim, $dim);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function cropImage($max, $x, $y, $w, $h)
|
||||
{
|
||||
$dest = imagecreatetruecolor($max, $max);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function saveImage($path)
|
||||
{
|
||||
imagejpeg($this->image, $path, 100);
|
||||
}
|
||||
|
||||
public function imageString()
|
||||
{
|
||||
ob_start();
|
||||
imagejpeg($this->image, NULL, 100);
|
||||
$s = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return $s;
|
||||
}
|
||||
|
||||
public function store($profile_id)
|
||||
{
|
||||
$r = q("SELECT `id` FROM `photo` WHERE `profile-id` = %d LIMIT 1",
|
||||
intval($profile_id)
|
||||
);
|
||||
if (is_array($r) && count($r)) {
|
||||
q("UPDATE `photo` SET `data` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc($this->imageString()),
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
} else {
|
||||
q("INSERT INTO `photo`
|
||||
( `profile-id`, `data` ) VALUES ( %d , '%s') ",
|
||||
intval($profile_id),
|
||||
dbesc($this->imageString())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
201
include/Scrape.php
Normal file
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
require_once('library/HTML5/Parser.php');
|
||||
|
||||
if(! function_exists('attribute_contains')) {
|
||||
function attribute_contains($attr,$s) {
|
||||
$a = explode(' ', $attr);
|
||||
if(count($a) && in_array($s,$a))
|
||||
return true;
|
||||
return false;
|
||||
}}
|
||||
|
||||
|
||||
if (!function_exists('noscrape_dfrn')) {
|
||||
|
||||
function noscrape_dfrn($url)
|
||||
{
|
||||
$submit_noscrape_start = microtime(true);
|
||||
$data = fetch_url($url);
|
||||
$submit_noscrape_request_end = microtime(true);
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parms = json_decode($data, true);
|
||||
if (!$parms || !count($parms)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($parms['tags'])) {
|
||||
$parms['tags'] = implode(' ', (array) $parms['tags']);
|
||||
} else {
|
||||
$parms['tags'] = '';
|
||||
}
|
||||
|
||||
$submit_noscrape_end = microtime(true);
|
||||
$parms['_timings'] = array(
|
||||
'fetch' => round(($submit_noscrape_request_end - $submit_noscrape_start) * 1000),
|
||||
'scrape' => round(($submit_noscrape_end - $submit_noscrape_request_end) * 1000)
|
||||
);
|
||||
|
||||
return $parms;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(! function_exists('scrape_dfrn')) {
|
||||
function scrape_dfrn($url, $max_nodes=3500) {
|
||||
|
||||
$minNodes = 100; //Lets do at least 100 nodes per type.
|
||||
$timeout = 10; //Timeout will affect batch processing.
|
||||
|
||||
//Try and cheat our way into faster profiles.
|
||||
if(strpos($url, 'tab=profile') === false){
|
||||
$url .= (strpos($url, '?') > 0 ? '&' : '?').'tab=profile';
|
||||
}
|
||||
|
||||
$scrape_start = microtime(true);
|
||||
|
||||
$ret = array();
|
||||
$s = fetch_url($url, $timeout);
|
||||
|
||||
$scrape_fetch_end = microtime(true);
|
||||
|
||||
if(! $s)
|
||||
return $ret;
|
||||
|
||||
$dom = HTML5_Parser::parse($s);
|
||||
|
||||
if(! $dom)
|
||||
return $ret;
|
||||
|
||||
$items = $dom->getElementsByTagName('meta');
|
||||
|
||||
// get DFRN link elements
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
$targets = array('hide', 'comm', 'tags');
|
||||
$targets_left = count($targets);
|
||||
foreach($items as $item) {
|
||||
$x = $item->getAttribute('name');
|
||||
if($x == 'dfrn-global-visibility') {
|
||||
$z = strtolower(trim($item->getAttribute('content')));
|
||||
if($z != 'true')
|
||||
$ret['hide'] = 1;
|
||||
if($z === 'false')
|
||||
$ret['explicit-hide'] = 1;
|
||||
$targets_left = pop_scrape_target($targets, 'hide');
|
||||
}
|
||||
if($x == 'friendika.community' || $x == 'friendica.community') {
|
||||
$z = strtolower(trim($item->getAttribute('content')));
|
||||
if($z == 'true')
|
||||
$ret['comm'] = 1;
|
||||
$targets_left = pop_scrape_target($targets, 'comm');
|
||||
}
|
||||
if($x == 'keywords') {
|
||||
$z = str_replace(',',' ',strtolower(trim($item->getAttribute('content'))));
|
||||
if(strlen($z))
|
||||
$ret['tags'] = $z;
|
||||
$targets_left = pop_scrape_target($targets, 'tags');
|
||||
}
|
||||
$nodes_left--;
|
||||
if($nodes_left <= 0 || $targets_left <= 0) break;
|
||||
}
|
||||
|
||||
$items = $dom->getElementsByTagName('link');
|
||||
|
||||
// get DFRN link elements
|
||||
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
foreach($items as $item) {
|
||||
$x = $item->getAttribute('rel');
|
||||
if(substr($x,0,5) == "dfrn-")
|
||||
$ret[$x] = $item->getAttribute('href');
|
||||
$nodes_left--;
|
||||
if($nodes_left <= 0) break;
|
||||
}
|
||||
|
||||
// Pull out hCard profile elements
|
||||
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
$items = $dom->getElementsByTagName('*');
|
||||
$targets = array('fn', 'pdesc', 'photo', 'key', 'locality', 'region', 'postal-code', 'country-name');
|
||||
$targets_left = count($targets);
|
||||
foreach($items as $item) {
|
||||
if(attribute_contains($item->getAttribute('class'), 'vcard')) {
|
||||
$level2 = $item->getElementsByTagName('*');
|
||||
foreach($level2 as $x) {
|
||||
if(attribute_contains($x->getAttribute('class'),'fn')){
|
||||
$ret['fn'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'fn');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'title')){
|
||||
$ret['pdesc'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'pdesc');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'photo')){
|
||||
$ret['photo'] = $x->getAttribute('src');
|
||||
$targets_left = pop_scrape_target($targets, 'photo');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'key')){
|
||||
$ret['key'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'key');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'locality')){
|
||||
$ret['locality'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'locality');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'region')){
|
||||
$ret['region'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'region');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'postal-code')){
|
||||
$ret['postal-code'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'postal-code');
|
||||
}
|
||||
if(attribute_contains($x->getAttribute('class'),'country-name')){
|
||||
$ret['country-name'] = $x->textContent;
|
||||
$targets_left = pop_scrape_target($targets, 'country-name');
|
||||
}
|
||||
}
|
||||
}
|
||||
$nodes_left--;
|
||||
if($nodes_left <= 0 || $targets_left <= 0) break;
|
||||
}
|
||||
|
||||
$scrape_end = microtime(true);
|
||||
$fetch_time = round(($scrape_fetch_end - $scrape_start) * 1000);
|
||||
$scrape_time = round(($scrape_end - $scrape_fetch_end) * 1000);
|
||||
|
||||
$ret['_timings'] = array(
|
||||
'fetch' => $fetch_time,
|
||||
'scrape' => $scrape_time
|
||||
);
|
||||
|
||||
return $ret;
|
||||
|
||||
}}
|
||||
|
||||
|
||||
if(! function_exists('validate_dfrn')) {
|
||||
function validate_dfrn($a) {
|
||||
$errors = 0;
|
||||
if(! x($a,'key'))
|
||||
$errors ++;
|
||||
if(! x($a,'dfrn-request'))
|
||||
$errors ++;
|
||||
if(! x($a,'dfrn-confirm'))
|
||||
$errors ++;
|
||||
if(! x($a,'dfrn-notify'))
|
||||
$errors ++;
|
||||
if(! x($a,'dfrn-poll'))
|
||||
$errors ++;
|
||||
return $errors;
|
||||
}}
|
||||
|
||||
if(! function_exists('pop_scrape_target')) {
|
||||
function pop_scrape_target(&$array, $name) {
|
||||
$at = array_search($name, $array);
|
||||
unset($array[$at]);
|
||||
return count($array);
|
||||
}}
|
74
include/auth.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
// login/logout
|
||||
|
||||
if((x($_SESSION,'authenticated')) && (! ($_POST['auth-params'] == 'login'))) {
|
||||
if($_POST['auth-params'] == 'logout' || $a->module == "logout") {
|
||||
unset($_SESSION['authenticated']);
|
||||
unset($_SESSION['uid']);
|
||||
unset($_SESSION['visitor_id']);
|
||||
unset($_SESSION['is_visitor']);
|
||||
unset($_SESSION['administrator']);
|
||||
unset($_SESSION['cid']);
|
||||
unset($_SESSION['theme']);
|
||||
notice( t('Logged out.') . EOL);
|
||||
goaway($a->get_baseurl());
|
||||
}
|
||||
if(x($_SESSION,'uid')) {
|
||||
$r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
|
||||
intval($_SESSION['uid']));
|
||||
if($r === NULL || (! count($r))) {
|
||||
goaway($a->get_baseurl());
|
||||
}
|
||||
$a->user = $r[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
unset($_SESSION['authenticated']);
|
||||
unset($_SESSION['uid']);
|
||||
unset($_SESSION['visitor_id']);
|
||||
unset($_SESSION['is_visitor']);
|
||||
unset($_SESSION['administrator']);
|
||||
unset($_SESSION['cid']);
|
||||
$encrypted = hash('whirlpool',trim($_POST['password']));
|
||||
|
||||
if((x($_POST,'auth-params')) && $_POST['auth-params'] == 'login') {
|
||||
$r = q("SELECT * FROM `user`
|
||||
WHERE `email` = '%s' AND `password` = '%s' LIMIT 1",
|
||||
dbesc(trim($_POST['login-name'])),
|
||||
dbesc($encrypted));
|
||||
|
||||
if(($r === false) || (! count($r))) {
|
||||
notice( t('Login failed.') . EOL);
|
||||
goaway($a->get_baseurl());
|
||||
}
|
||||
$_SESSION['uid'] = $r[0]['uid'];
|
||||
$_SESSION['theme'] = $r[0]['theme'];
|
||||
$_SESSION['authenticated'] = 1;
|
||||
$_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $r[0]['nickname'];
|
||||
|
||||
notice( t("Welcome back ") . $r[0]['username'] . EOL);
|
||||
$a->user = $r[0];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of group id's this contact is a member of.
|
||||
// This array will only contain group id's related to the uid of this
|
||||
// DFRN contact. They are *not* neccessarily unique across the entire site.
|
||||
|
||||
|
||||
if(! function_exists('init_groups_visitor')) {
|
||||
function init_groups_visitor($contact_id) {
|
||||
$groups = array();
|
||||
$r = q("SELECT `gid` FROM `group_member`
|
||||
WHERE `contact-id` = %d ",
|
||||
intval($contact_id)
|
||||
);
|
||||
if(count($r)) {
|
||||
foreach($r as $rr)
|
||||
$groups[] = $rr['gid'];
|
||||
}
|
||||
return $groups;
|
||||
}}
|
78
include/bbcode.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
//BBcode 2 HTML was written by WAY2WEB.net
|
||||
|
||||
function bbcode($Text)
|
||||
{
|
||||
// Replace any html brackets with HTML Entities to prevent executing HTML or script
|
||||
// Don't use strip_tags here because it breaks [url] search by replacing & with amp
|
||||
$Text = str_replace("<", "<", $Text);
|
||||
$Text = str_replace(">", ">", $Text);
|
||||
|
||||
// Convert new line chars to html <br /> tags
|
||||
$Text = nl2br($Text);
|
||||
|
||||
// Set up the parameters for a URL search string
|
||||
$URLSearchString = " a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'";
|
||||
// Set up the parameters for a MAIL search string
|
||||
$MAILSearchString = $URLSearchString . " a-zA-Z0-9\.@";
|
||||
|
||||
// Perform URL Search
|
||||
$Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/", '<a href="$1" target="_blank">$1</a>', $Text);
|
||||
$Text = preg_replace("(\[url\=([$URLSearchString]*)\](.+?)\[/url\])", '<a href="$1" target="_blank">$2</a>', $Text);
|
||||
//$Text = preg_replace("(\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[/url\])", '<a href="$1" target="_blank">$2</a>', $Text);
|
||||
|
||||
// Perform MAIL Search
|
||||
$Text = preg_replace("(\[mail\]([$MAILSearchString]*)\[/mail\])", '<a href="mailto:$1">$1</a>', $Text);
|
||||
$Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.+?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $Text);
|
||||
|
||||
// Check for bold text
|
||||
$Text = preg_replace("(\[b\](.+?)\[\/b])is",'<strong>$1</strong>',$Text);
|
||||
|
||||
// Check for Italics text
|
||||
$Text = preg_replace("(\[i\](.+?)\[\/i\])is",'<em>$1</em>',$Text);
|
||||
|
||||
// Check for Underline text
|
||||
$Text = preg_replace("(\[u\](.+?)\[\/u\])is",'<u>$1</u>',$Text);
|
||||
|
||||
// Check for strike-through text
|
||||
$Text = preg_replace("(\[s\](.+?)\[\/s\])is",'<strike>$1</strike>',$Text);
|
||||
|
||||
// Check for over-line text
|
||||
$Text = preg_replace("(\[o\](.+?)\[\/o\])is",'<span class="overline">$1</span>',$Text);
|
||||
|
||||
// Check for colored text
|
||||
$Text = preg_replace("(\[color=(.+?)\](.+?)\[\/color\])is","<span style=\"color: $1\">$2</span>",$Text);
|
||||
|
||||
// Check for sized text
|
||||
$Text = preg_replace("(\[size=(.+?)\](.+?)\[\/size\])is","<span style=\"font-size: $1px\">$2</span>",$Text);
|
||||
|
||||
// Check for list text
|
||||
$Text = preg_replace("/\[list\](.+?)\[\/list\]/is", '<ul class="listbullet">$1</ul>' ,$Text);
|
||||
$Text = preg_replace("/\[list=1\](.+?)\[\/list\]/is", '<ul class="listdecimal">$1</ul>' ,$Text);
|
||||
$Text = preg_replace("/\[list=i\](.+?)\[\/list\]/s",'<ul class="listlowerroman">$1</ul>' ,$Text);
|
||||
$Text = preg_replace("/\[list=I\](.+?)\[\/list\]/s", '<ul class="listupperroman">$1</ul>' ,$Text);
|
||||
$Text = preg_replace("/\[list=a\](.+?)\[\/list\]/s", '<ul class="listloweralpha">$1</ul>' ,$Text);
|
||||
$Text = preg_replace("/\[list=A\](.+?)\[\/list\]/s", '<ul class="listupperalpha">$1</ul>' ,$Text);
|
||||
$Text = str_replace("[*]", "<li>", $Text);
|
||||
|
||||
// Check for font change text
|
||||
$Text = preg_replace("(\[font=(.+?)\](.+?)\[\/font\])","<span style=\"font-family: $1;\">$2</span>",$Text);
|
||||
|
||||
// Declare the format for [code] layout
|
||||
$CodeLayout = '<code>$1</code>';
|
||||
// Check for [code] text
|
||||
$Text = preg_replace("/\[code\](.+?)\[\/code\]/is","$CodeLayout", $Text);
|
||||
// Declare the format for [quote] layout
|
||||
$QuoteLayout = '<blockquote>$1</blockquote>';
|
||||
// Check for [quote] text
|
||||
$Text = preg_replace("/\[quote\](.+?)\[\/quote\]/is","$QuoteLayout", $Text);
|
||||
|
||||
// Images
|
||||
// [img]pathtoimage[/img]
|
||||
$Text = preg_replace("/\[img\](.+?)\[\/img\]/", '<img src="$1">', $Text);
|
||||
|
||||
// [img=widthxheight]image source[/img]
|
||||
$Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.+?)\[\/img\]/", '<img src="$3" height="$2" width="$1">', $Text);
|
||||
|
||||
return $Text;
|
||||
}
|
141
include/cron_maintain.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
// Debug stuff.
|
||||
// ini_set('display_errors', 1);
|
||||
// ini_set('log_errors','0');
|
||||
error_reporting(E_ALL ^ E_NOTICE);
|
||||
|
||||
$start_maintain = time();
|
||||
|
||||
$verbose = $argv[1] === 'verbose';
|
||||
|
||||
$a = new App();
|
||||
|
||||
//Config and DB.
|
||||
require_once '.htconfig.php';
|
||||
require_once 'dba.php';
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
|
||||
//Get the maintenance backlog size.
|
||||
$res = q("SELECT count(*) as `count`
|
||||
FROM `profile`
|
||||
WHERE `updated` < '%s'",
|
||||
dbesc(date('Y-m-d H:i:s', time() - $a->config['maintenance']['min_scrape_delay']))
|
||||
);
|
||||
$maintenance_backlog = 'unknown';
|
||||
if (is_array($res) && count($res)) {
|
||||
$maintenance_backlog = $res[0]['count'] . ' entries left';
|
||||
}
|
||||
|
||||
//Get our set of items. Oldest items first, after the threshold.
|
||||
$res = q("SELECT `id`, `homepage`, `censored`
|
||||
FROM `profile`
|
||||
WHERE `updated` < '%s'
|
||||
ORDER BY `updated` ASC
|
||||
LIMIT %u",
|
||||
dbesc(date('Y-m-d H:i:s', time() - $a->config['maintenance']['min_scrape_delay'])),
|
||||
intval($a->config['maintenance']['max_scrapes'])
|
||||
);
|
||||
|
||||
//Nothing to do.
|
||||
if (!$res || !count($res)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
//Close DB here. Threads need their private connection.
|
||||
$db->getdb()->close();
|
||||
|
||||
//We need the scraper.
|
||||
require_once 'include/submit.php';
|
||||
|
||||
//POSIX threads only.
|
||||
if (!function_exists('pcntl_fork')) {
|
||||
logger('Error: no pcntl_fork support. Are you running a different OS? Report an issue please.');
|
||||
die('Error: no pcntl_fork support. Are you running a different OS? Report an issue please.');
|
||||
}
|
||||
|
||||
//Create the threads we need.
|
||||
$items = count($res);
|
||||
$threadc = min($a->config['maintenance']['threads'], $items); //Don't need more threads than items.
|
||||
$threads = array();
|
||||
|
||||
//Debug...
|
||||
if ($verbose) {
|
||||
echo "Creating $threadc maintainer threads for $items profiles, $maintenance_backlog" . PHP_EOL;
|
||||
}
|
||||
logger("Creating $threadc maintainer threads for $items profiles. $maintenance_backlog");
|
||||
|
||||
for ($i = 0; $i < $threadc; $i++) {
|
||||
|
||||
$pid = pcntl_fork();
|
||||
if ($pid === -1) {
|
||||
if ($verbose) {
|
||||
echo('Error: something went wrong with the fork. ' . pcntl_strerror());
|
||||
}
|
||||
logger('Error: something went wrong with the fork. ' . pcntl_strerror());
|
||||
die('Error: something went wrong with the fork. ' . pcntl_strerror());
|
||||
}
|
||||
|
||||
//You're a child, go do some labor!
|
||||
if ($pid === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
//Store the list of PID's.
|
||||
if ($pid > 0) {
|
||||
$threads[] = $pid;
|
||||
}
|
||||
}
|
||||
|
||||
//The work for child processes.
|
||||
if ($pid === 0) {
|
||||
|
||||
//Lets be nice, we're only doing maintenance here...
|
||||
pcntl_setpriority(5);
|
||||
|
||||
//Get personal DBA's.
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
//Get our (round-robin) workload from the DB results.
|
||||
$myIndex = $i + 1;
|
||||
$workload = array();
|
||||
while (isset($res[$i])) {
|
||||
$entry = $res[$i];
|
||||
$workload[] = $entry;
|
||||
$ids[] = $entry['id'];
|
||||
$i += $threadc;
|
||||
}
|
||||
|
||||
while (count($workload)) {
|
||||
$entry = array_pop($workload);
|
||||
set_time_limit(20); //This should work for 1 submit.
|
||||
if ($verbose) {
|
||||
echo "Submitting " . $entry['homepage'] . PHP_EOL;
|
||||
}
|
||||
run_submit($entry['homepage']);
|
||||
}
|
||||
|
||||
exit;
|
||||
} else {
|
||||
//The main process.
|
||||
foreach ($threads as $pid) {
|
||||
pcntl_waitpid($pid, $status);
|
||||
if ($status !== 0) {
|
||||
if ($verbose) {
|
||||
echo "Bad process return value $pid:$status" . PHP_EOL;
|
||||
}
|
||||
logger("Bad process return value $pid:$status");
|
||||
}
|
||||
}
|
||||
$time = time() - $start_maintain;
|
||||
if ($verbose) {
|
||||
echo("Maintenance completed. Took $time seconds." . PHP_EOL);
|
||||
}
|
||||
logger("Maintenance completed. Took $time seconds.");
|
||||
}
|
103
include/cron_sync.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|
||||
#TODO:
|
||||
|
||||
* First do the pulls then the pushes.
|
||||
If pull prevents the push, the push queue just creates a backlog until it gets a chance to push.
|
||||
|
||||
* When doing a first-pull, there's a safety mechanism for the timeout and detecting duplicate attempts.
|
||||
|
||||
1. Perform all JSON pulls on the source servers.
|
||||
2. Combine the results into one giant pool of URLs.
|
||||
3. Write this pool to a file (TODO-file).
|
||||
4. Shuffle the pool in RAM.
|
||||
5. Start threads for crawling.
|
||||
6. Every finished crawl attempt (successful or not) should write to a 2nd file (DONE-file).
|
||||
|
||||
IF the first-pull times out, don't do anything else.
|
||||
Otherwise, mark the dates we last performed a pull from each server.
|
||||
|
||||
* When resuming a first-pull.
|
||||
|
||||
1. Check for the TODO-file and the DONE-file.
|
||||
2. Remove the entries in the DONE-file from the pool in the TODO-file.
|
||||
3. Replace the TODO-file with the updated pool.
|
||||
4. Perform steps 4, 5 and 6 (shuffle, create threads and crawl) from before.
|
||||
|
||||
This way you can resume without repeating attempts.
|
||||
|
||||
* Write documentation about syncing.
|
||||
|
||||
* Create "official" directory policy for my directory.
|
||||
|
||||
* Decide if a retry mechanism is desirable for pulling (for the failed attempts).
|
||||
After all, you did imply trust when you indicated to pull from that source...
|
||||
This could be done easily by doing a /sync/pull/all again from those sources.
|
||||
|
||||
* Decide if cron_sync.php should be split into push pull and pull-all commands.
|
||||
|
||||
*/
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
// Debug stuff.
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('log_errors', '0');
|
||||
error_reporting(E_ALL ^ E_NOTICE);
|
||||
|
||||
$start_syncing = time();
|
||||
|
||||
$a = new App();
|
||||
|
||||
//Create a simple log function for CLI use.
|
||||
global $verbose;
|
||||
$verbose = $argv[1] === 'verbose';
|
||||
|
||||
function msg($message, $fatal = false)
|
||||
{
|
||||
global $verbose;
|
||||
if ($verbose || $fatal)
|
||||
echo($message . PHP_EOL);
|
||||
logger($message);
|
||||
if ($fatal) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Config.
|
||||
require_once '.htconfig.php';
|
||||
|
||||
//Connect the DB.
|
||||
require_once 'dba.php';
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
//Import syncing functions.
|
||||
require_once 'sync.php';
|
||||
|
||||
//Get work for pulling.
|
||||
$pull_batch = get_pulling_job($a);
|
||||
|
||||
//Get work for pushing.
|
||||
list($push_targets, $push_batch) = get_pushing_job($a);
|
||||
|
||||
//Close the connection for now. Process forking and DB connections are not the best of friends.
|
||||
$db->getdb()->close();
|
||||
|
||||
if (count($pull_batch)) {
|
||||
run_pulling_job($a, $pull_batch, $db_host, $db_user, $db_pass, $db_data, $install);
|
||||
}
|
||||
|
||||
//Do our multi-fork job, if we have a batch and targets.
|
||||
if (count($push_targets) && count($push_batch)) {
|
||||
run_pushing_job($push_targets, $push_batch, $db_host, $db_user, $db_pass, $db_data, $install);
|
||||
}
|
||||
|
||||
//Log the time it took.
|
||||
$time = time() - $start_syncing;
|
||||
msg("Syncing completed. Took $time seconds.");
|
168
include/datetime.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
if(! function_exists('timezone_cmp')) {
|
||||
function timezone_cmp($a, $b) {
|
||||
if(strstr($a,'/') && strstr($b,'/')) {
|
||||
if ($a == $b) return 0;
|
||||
return ($a < $b) ? -1 : 1;
|
||||
}
|
||||
if(strstr($a,'/')) return -1;
|
||||
if(strstr($b,'/')) return 1;
|
||||
if ( t($a) == t($b)) return 0;
|
||||
return ( t($a) < t($b)) ? -1 : 1;
|
||||
}}
|
||||
|
||||
|
||||
if(! function_exists('select_timezone')) {
|
||||
function select_timezone($current = 'America/Los_Angeles') {
|
||||
|
||||
$timezone_identifiers = DateTimeZone::listIdentifiers();
|
||||
|
||||
$o ='<select id="timezone_select" name="timezone">';
|
||||
|
||||
usort($timezone_identifiers, 'timezone_cmp');
|
||||
$continent = '';
|
||||
foreach($timezone_identifiers as $value) {
|
||||
$ex = explode("/", $value);
|
||||
if(count($ex) > 1) {
|
||||
if($ex[0] != $continent) {
|
||||
if($continent != '')
|
||||
$o .= '</optgroup>';
|
||||
$continent = $ex[0];
|
||||
$o .= '<optgroup label="' . t($continent) . '">';
|
||||
}
|
||||
if(count($ex) > 2)
|
||||
$city = substr($value,strpos($value,'/')+1);
|
||||
else
|
||||
$city = $ex[1];
|
||||
}
|
||||
else {
|
||||
$city = $ex[0];
|
||||
if($continent != 'Miscellaneous') {
|
||||
$o .= '</optgroup>';
|
||||
$continent = 'Miscellaneous';
|
||||
$o .= '<optgroup label="' . t($continent) . '">';
|
||||
}
|
||||
}
|
||||
$city = str_replace('_', ' ', t($city));
|
||||
$selected = (($value == $current) ? " selected=\"selected\" " : "");
|
||||
$o .= "<option value=\"$value\" $selected >$city</option>";
|
||||
}
|
||||
$o .= '</optgroup></select>';
|
||||
return $o;
|
||||
}}
|
||||
|
||||
// General purpose date parse/convert function.
|
||||
// $from = source timezone
|
||||
// $to = dest timezone
|
||||
// $s = some parseable date/time string
|
||||
// $fmt = output format
|
||||
|
||||
if(! function_exists('datetime_convert')) {
|
||||
function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") {
|
||||
|
||||
// Slight hackish adjustment so that 'zero' datetime actually returns what is intended
|
||||
// otherwise we end up with -0001-11-30 ...
|
||||
// add 32 days so that we at least get year 00, and then hack around the fact that
|
||||
// months and days always start with 1.
|
||||
|
||||
if(substr($s,0,10) == '0000-00-00') {
|
||||
$d = new DateTime($s . ' + 32 days', new DateTimeZone('UTC'));
|
||||
return str_replace('1','0',$d->format($fmt));
|
||||
}
|
||||
|
||||
$d = new DateTime($s, new DateTimeZone($from));
|
||||
$d->setTimeZone(new DateTimeZone($to));
|
||||
return($d->format($fmt));
|
||||
}}
|
||||
|
||||
function dob($dob) {
|
||||
list($year,$month,$day) = sscanf($dob,'%4d-%2d-%2d');
|
||||
$y = datetime_convert('UTC',date_default_timezone_get(),'now','Y');
|
||||
$o = datesel('',1920,$y,true,$year,$month,$day);
|
||||
return $o;
|
||||
}
|
||||
|
||||
if(! function_exists('datesel')) {
|
||||
function datesel($pre,$ymin,$ymax,$allow_blank,$y,$m,$d) {
|
||||
|
||||
$o = '';
|
||||
$o .= "<select name=\"{$pre}year\" class=\"{$pre}year\" size=\"1\">";
|
||||
if($allow_blank) {
|
||||
$sel = (($y == '0000') ? " selected=\"selected\" " : "");
|
||||
$o .= "<option value=\"0000\" $sel ></option>";
|
||||
}
|
||||
|
||||
for($x = $ymax; $x >= $ymin; $x --) {
|
||||
$sel = (($x == $y) ? " selected=\"selected\" " : "");
|
||||
$o .= "<option value=\"$x\" $sel>$x</option>";
|
||||
}
|
||||
|
||||
$o .= "</select> <select name=\"{$pre}month\" class=\"{$pre}month\" size=\"1\">";
|
||||
for($x = 0; $x <= 12; $x ++) {
|
||||
$sel = (($x == $m) ? " selected=\"selected\" " : "");
|
||||
$y = (($x) ? $x : '');
|
||||
$o .= "<option value=\"$x\" $sel>$y</option>";
|
||||
}
|
||||
|
||||
$o .= "</select> <select name=\"{$pre}day\" class=\"{$pre}day\" size=\"1\">";
|
||||
for($x = 0; $x <= 31; $x ++) {
|
||||
$sel = (($x == $d) ? " selected=\"selected\" " : "");
|
||||
$y = (($x) ? $x : '');
|
||||
$o .= "<option value=\"$x\" $sel>$y</option>";
|
||||
}
|
||||
|
||||
$o .= "</select>";
|
||||
return $o;
|
||||
}}
|
||||
|
||||
|
||||
if(! function_exists('relative_date')) {
|
||||
function relative_date($posted_date) {
|
||||
|
||||
$localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date);
|
||||
|
||||
$abs = strtotime($localtime);
|
||||
$etime = time() - $abs;
|
||||
|
||||
if ($etime < 1) {
|
||||
return t('less than a second ago');
|
||||
}
|
||||
|
||||
$a = array( 12 * 30 * 24 * 60 * 60 => array( t('year'), t('years')),
|
||||
30 * 24 * 60 * 60 => array( t('month'), t('months')),
|
||||
7 * 24 * 60 * 60 => array( t('week'), t('weeks')),
|
||||
24 * 60 * 60 => array( t('day'), t('days')),
|
||||
60 * 60 => array( t('hour'), t('hours')),
|
||||
60 => array( t('minute'), t('minutes')),
|
||||
1 => array( t('second'), t('seconds'))
|
||||
);
|
||||
|
||||
foreach ($a as $secs => $str) {
|
||||
$d = $etime / $secs;
|
||||
if ($d >= 1) {
|
||||
$r = round($d);
|
||||
return $r . ' ' . (($r == 1) ? $str[0] : $str[1]) . t(' ago');
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
function age($dob,$owner_tz = '',$viewer_tz = '') {
|
||||
if(! intval($dob))
|
||||
return 0;
|
||||
if(! $owner_tz)
|
||||
$owner_tz = date_default_timezone_get();
|
||||
if(! $viewer_tz)
|
||||
$viewer_tz = date_default_timezone_get();
|
||||
|
||||
$birthdate = datetime_convert('UTC',$owner_tz,$dob . ' 00:00:00+00:00','Y-m-d');
|
||||
list($year,$month,$day) = explode("-",$birthdate);
|
||||
$year_diff = datetime_convert('UTC',$viewer_tz,'now','Y') - $year;
|
||||
$curr_month = datetime_convert('UTC',$viewer_tz,'now','m');
|
||||
$curr_day = datetime_convert('UTC',$viewer_tz,'now','d');
|
||||
|
||||
if(($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day)))
|
||||
$year_diff--;
|
||||
return $year_diff;
|
||||
}
|
173
include/dba.php
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
// MySQL database class
|
||||
//
|
||||
// For debugging, insert 'dbg(x);' anywhere in the program flow.
|
||||
// x = 1: display db success/failure following content
|
||||
// x = 2: display full queries following content
|
||||
// x = 3: display full queries using echo; which will mess up display
|
||||
// really bad but will return output in stubborn cases.
|
||||
|
||||
class dba
|
||||
{
|
||||
private $debug = 0;
|
||||
public $db;
|
||||
|
||||
public function __construct($server, $user, $pass, $db, $install = false)
|
||||
{
|
||||
$this->db = @new mysqli($server, $user, $pass, $db);
|
||||
|
||||
if (mysqli_connect_errno() && ! $install) {
|
||||
system_unavailable();
|
||||
}
|
||||
}
|
||||
|
||||
public function getdb()
|
||||
{
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
public function q($sql)
|
||||
{
|
||||
global $debug_text;
|
||||
|
||||
if (! $this->db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = @$this->db->query($sql);
|
||||
|
||||
if ($this->debug) {
|
||||
$mesg = '';
|
||||
|
||||
if ($this->db->errno) {
|
||||
$debug_text .= $this->db->error . EOL;
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
$mesg = 'false';
|
||||
} elseif ($result === true) {
|
||||
$mesg = 'true';
|
||||
} else {
|
||||
$mesg = $result->num_rows . ' results' . EOL;
|
||||
}
|
||||
|
||||
$str = 'SQL = ' . printable($sql) . EOL . 'SQL returned ' . $mesg . EOL;
|
||||
|
||||
switch ($this->debug) {
|
||||
case 3:
|
||||
echo $str;
|
||||
break;
|
||||
default:
|
||||
$debug_text .= $str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (($result === true) || ($result === false)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$r = array();
|
||||
if ($result->num_rows) {
|
||||
while ($x = $result->fetch_array(MYSQLI_ASSOC)) {
|
||||
$r[] = $x;
|
||||
}
|
||||
$result->free_result();
|
||||
}
|
||||
|
||||
if ($this->debug == 2) {
|
||||
$debug_text .= printable(print_r($r, true) . EOL);
|
||||
} elseif ($this->debug == 3) {
|
||||
echo printable(print_r($r, true) . EOL);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function dbg($dbg)
|
||||
{
|
||||
$this->debug = $dbg;
|
||||
}
|
||||
|
||||
public function escape($str)
|
||||
{
|
||||
return @$this->db->real_escape_string($str);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
@$this->db->close();
|
||||
}
|
||||
}
|
||||
|
||||
function printable($s)
|
||||
{
|
||||
$s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~", ".", $s);
|
||||
$s = str_replace("\x00", '.', $s);
|
||||
if (x($_SERVER, 'SERVER_NAME')) {
|
||||
$s = escape_tags($s);
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
// Procedural functions
|
||||
function dbg($state)
|
||||
{
|
||||
global $db;
|
||||
$db->dbg($state);
|
||||
}
|
||||
|
||||
function dbesc($str)
|
||||
{
|
||||
global $db;
|
||||
if ($db) {
|
||||
return($db->escape($str));
|
||||
}
|
||||
}
|
||||
|
||||
// Function: q($sql,$args);
|
||||
// Description: execute SQL query with printf style args.
|
||||
// Example: $r = q("SELECT * FROM `%s` WHERE `uid` = %d",
|
||||
// 'user', 1);
|
||||
|
||||
function q($sql)
|
||||
{
|
||||
global $db;
|
||||
$args = func_get_args();
|
||||
unset($args[0]);
|
||||
|
||||
$ret = null;
|
||||
|
||||
if ($db) {
|
||||
$final_sql = vsprintf($sql, $args);
|
||||
|
||||
$ret = $db->q($final_sql);
|
||||
|
||||
if ($db->db->errno) {
|
||||
logger('dba: ' . $db->db->error . ' sql: ' . $final_sql);
|
||||
}
|
||||
} else {
|
||||
error_log(__FILE__ . ':' . __LINE__ . ' $db has gone');
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring that any integer arguments to
|
||||
// dbesc_array are actually integers and not malformed strings containing
|
||||
// SQL injection vectors. All integer array elements should be specifically
|
||||
// cast to int to avoid trouble.
|
||||
function dbesc_array_cb(&$item, $key)
|
||||
{
|
||||
if (is_string($item)) {
|
||||
$item = dbesc($item);
|
||||
}
|
||||
}
|
||||
|
||||
function dbesc_array(&$arr)
|
||||
{
|
||||
if (is_array($arr) && count($arr)) {
|
||||
array_walk($arr, 'dbesc_array_cb');
|
||||
}
|
||||
}
|
34
include/directory.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
$a = new App();
|
||||
|
||||
@include '.htconfig.php';
|
||||
|
||||
require_once 'dba.php';
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
unset($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
if ($argc != 2) {
|
||||
exit;
|
||||
}
|
||||
|
||||
load_config('system');
|
||||
|
||||
$a->set_baseurl(get_config('system', 'url'));
|
||||
|
||||
$dir = get_config('system', 'directory_submit_url');
|
||||
|
||||
if (!strlen($dir)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
fetch_url($dir . '?url=' . bin2hex($argv[1]));
|
||||
|
||||
exit;
|
167
include/group.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
|
||||
function group_add($name) {
|
||||
|
||||
$ret = false;
|
||||
if(x($name)) {
|
||||
$r = group_byname($name); // check for dups
|
||||
if($r !== false)
|
||||
return true;
|
||||
$r = q("INSERT INTO `group` ( `name` )
|
||||
VALUES( '%s' ) ",
|
||||
dbesc($name)
|
||||
);
|
||||
$ret = $r;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
function group_rmv($name) {
|
||||
$ret = false;
|
||||
if(x($name)) {
|
||||
$r = q("SELECT * FROM `group` WHERE `name` = '%s' LIMIT 1",
|
||||
dbesc($name)
|
||||
);
|
||||
if(count($r))
|
||||
$group_id = $r[0]['id'];
|
||||
if(! $group_id)
|
||||
return false;
|
||||
|
||||
|
||||
// Removing a group has broad security implications for posts that were created with this
|
||||
// group in their ACL. The posts could suddenly be made visible to somebody who
|
||||
// was not authorised to see them before. We can't take the group out of the ACL's
|
||||
// because this could inadvertantly make a post public which was restricted.
|
||||
|
||||
// So we are going to keep the group in place, but hide it so you can't use it any more.
|
||||
// All _existing_ post permissions remain intact, you just can't use this group going
|
||||
// forward. Since this is a trade-off solution, we should probably document it
|
||||
// on the page and suggest that if you want to affect already published posts, you should edit
|
||||
// the group membership before "deleting" it.
|
||||
|
||||
// $r = q("DELETE FROM `group_member` WHERE `gid` = %d ",
|
||||
// intval($group_id)
|
||||
// );
|
||||
|
||||
// remove group
|
||||
$r = q("UPDATE `group` SET `deleted` = 1 WHERE `name` = '%s' LIMIT 1",
|
||||
dbesc($name)
|
||||
);
|
||||
|
||||
$ret = $r;
|
||||
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
function group_byname($name) {
|
||||
if((! strlen($name)))
|
||||
return false;
|
||||
$r = q("SELECT * FROM `group` WHERE `name` = '%s' LIMIT 1",
|
||||
dbesc($name)
|
||||
);
|
||||
if(count($r))
|
||||
return $r[0]['id'];
|
||||
return false;
|
||||
}
|
||||
|
||||
function group_rmv_member($name,$member) {
|
||||
$gid = group_byname($name);
|
||||
if(! $gid)
|
||||
return false;
|
||||
if(! ($gid && $member))
|
||||
return false;
|
||||
$r = q("DELETE FROM `group_member` WHERE `gid` = %d AND `contact-id` = %d LIMIT 1 ",
|
||||
intval($gid),
|
||||
intval($member)
|
||||
);
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
function group_add_member($name,$member) {
|
||||
$gid = group_byname($name);
|
||||
if((! $gid) || (! $member))
|
||||
return false;
|
||||
|
||||
$r = q("SELECT * FROM `group_member` WHERE `id` = %d AND `contact-id` = %d LIMIT 1",
|
||||
intval($gid),
|
||||
intval($member)
|
||||
);
|
||||
if(count($r))
|
||||
return true; // You might question this, but
|
||||
// we indicate success because the group was in fact created
|
||||
// -- It was just created at another time
|
||||
if(! count($r))
|
||||
$r = q("INSERT INTO `group_member` (`gid`, `contact-id`)
|
||||
VALUES( %d, %d ) ",
|
||||
intval($gid),
|
||||
intval($member)
|
||||
);
|
||||
return $r;
|
||||
}
|
||||
|
||||
function group_get_members($gid) {
|
||||
$ret = array();
|
||||
if(intval($gid)) {
|
||||
$r = q("SELECT `group_member`.`contact-id`, `contact`.* FROM `group_member`
|
||||
LEFT JOIN `contact` ON `contact`.`id` = `group_member`.`contact-id`
|
||||
WHERE `gid` = %d ",
|
||||
intval($gid)
|
||||
);
|
||||
if(count($r))
|
||||
$ret = $r;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function group_side($every="contacts",$each="group") {
|
||||
|
||||
if(! local_user())
|
||||
return;
|
||||
|
||||
$createtext = t('Create a new group');
|
||||
$linktext= t('Everybody');
|
||||
|
||||
$o .= <<< EOT
|
||||
|
||||
<div id="group-sidebar">
|
||||
<h3>Groups</h3>
|
||||
|
||||
<div id="sidebar-new-group">
|
||||
<a href="group/new">$createtext</a>
|
||||
</div>
|
||||
|
||||
<div id="sidebar-group-list">
|
||||
<ul id="sidebar-group-ul">
|
||||
<li class="sidebar-group-li" ><a href="$every" >$linktext</a></li>
|
||||
|
||||
EOT;
|
||||
|
||||
$r = q("SELECT * FROM `group` WHERE `deleted` = 0 ");
|
||||
if(count($r)) {
|
||||
foreach($r as $rr)
|
||||
$o .= " <li class=\"sidebar-group-li\"><a href=\"$each/{$rr['id']}\">{$rr['name']}</a></li>\r\n";
|
||||
}
|
||||
$o .= " </ul>\r\n </div>\r\n</div>";
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
function expand_groups($a) {
|
||||
if(! (is_array($a) && count($a)))
|
||||
return array();
|
||||
$groups = implode(',', $a);
|
||||
$groups = dbesc($groups);
|
||||
$r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )");
|
||||
$ret = array();
|
||||
if(count($r))
|
||||
foreach($r as $rr)
|
||||
$ret[] = $rr['contact-id'];
|
||||
return $ret;
|
||||
}
|
6
include/hostxrd.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
$tpl = file_get_contents('view/xrd_host.tpl');
|
||||
echo str_replace('$domain',$this->hostname,$tpl);
|
||||
session_write_close();
|
||||
exit();
|
243
include/items.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
|
||||
function get_feed_for(&$a,$dfrn_id,$owner_id,$last_update) {
|
||||
|
||||
// default permissions - anonymous user
|
||||
|
||||
$sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
|
||||
|
||||
if(strlen($owner_id) && ! intval($owner_id)) {
|
||||
$r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
|
||||
dbesc($owner_id)
|
||||
);
|
||||
if(count($r))
|
||||
$owner_id = $r[0]['uid'];
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `self` = 1 LIMIT 1");
|
||||
if(count($r))
|
||||
$owner = $r[0];
|
||||
else
|
||||
killme();
|
||||
|
||||
if($dfrn_id != '*') {
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `issued-id` = '%s' LIMIT 1",
|
||||
dbesc($dfrn_id)
|
||||
);
|
||||
if(! count($r))
|
||||
return false;
|
||||
|
||||
$contact = $r[0];
|
||||
$groups = init_groups_visitor($contact['id']);
|
||||
|
||||
if(count($groups)) {
|
||||
for($x = 0; $x < count($groups); $x ++)
|
||||
$groups[$x] = '<' . intval($groups[$x]) . '>' ;
|
||||
$gs = implode('|', $groups);
|
||||
}
|
||||
else
|
||||
$gs = '<<>>' ; // Impossible to match
|
||||
|
||||
$sql_extra = sprintf(
|
||||
" AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
|
||||
AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
|
||||
AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
|
||||
AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s') ",
|
||||
|
||||
intval($contact['id']),
|
||||
intval($contact['id']),
|
||||
dbesc($gs),
|
||||
dbesc($gs)
|
||||
);
|
||||
}
|
||||
|
||||
if(! strlen($last_update))
|
||||
$last_update = 'now - 30 days';
|
||||
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
|
||||
|
||||
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
|
||||
`contact`.`name`, `contact`.`photo`, `contact`.`url`,
|
||||
`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
|
||||
`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
|
||||
`contact`.`id` AS `contact-id`
|
||||
FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||
WHERE `item`.`visible` = 1
|
||||
AND NOT `item`.`type` IN ( 'remote', 'net-comment' ) AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
|
||||
AND `item`.`edited` > '%s'
|
||||
$sql_extra
|
||||
ORDER BY `parent` ASC, `created` ASC LIMIT 0, 300",
|
||||
dbesc($check_date)
|
||||
);
|
||||
if(! count($r))
|
||||
killme();
|
||||
|
||||
$items = $r;
|
||||
|
||||
$feed_template = file_get_contents('view/atom_feed.tpl');
|
||||
$tomb_template = file_get_contents('view/atom_tomb.tpl');
|
||||
$item_template = file_get_contents('view/atom_item.tpl');
|
||||
$cmnt_template = file_get_contents('view/atom_cmnt.tpl');
|
||||
|
||||
$atom = '';
|
||||
|
||||
|
||||
$atom .= replace_macros($feed_template, array(
|
||||
'$feed_id' => xmlify($a->get_baseurl()),
|
||||
'$feed_title' => xmlify($owner['name']),
|
||||
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00' , 'Y-m-d\TH:i:s\Z')) ,
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$photo' => xmlify($owner['photo']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , 'Y-m-d\TH:i:s\Z')) ,
|
||||
'$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , 'Y-m-d\TH:i:s\Z')) ,
|
||||
'$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , 'Y-m-d\TH:i:s\Z'))
|
||||
|
||||
));
|
||||
|
||||
foreach($items as $item) {
|
||||
if($item['deleted']) {
|
||||
$atom .= replace_macros($tomb_template, array(
|
||||
'$id' => xmlify($item['uri']),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00' , 'Y-m-d\TH:i:s\Z'))
|
||||
));
|
||||
}
|
||||
else {
|
||||
|
||||
if($item['parent'] == $item['id']) {
|
||||
$atom .= replace_macros($item_template, array(
|
||||
'$name' => xmlify($item['name']),
|
||||
'$profile_page' => xmlify($item['url']),
|
||||
'$thumb' => xmlify($item['thumb']),
|
||||
'$owner_name' => xmlify($item['owner-name']),
|
||||
'$owner_profile_page' => xmlify($item['owner-link']),
|
||||
'$owner_thumb' => xmlify($item['owner-avatar']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$title' => xmlify($item['title']),
|
||||
'$published' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , 'Y-m-d\TH:i:s\Z')),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00' , 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' =>xmlify($item['body']),
|
||||
'$comment_allow' => (($item['last-child'] && strlen($contact['dfrn-id'])) ? 1 : 0)
|
||||
));
|
||||
}
|
||||
else {
|
||||
$atom .= replace_macros($cmnt_template, array(
|
||||
'$name' => xmlify($item['name']),
|
||||
'$profile_page' => xmlify($item['url']),
|
||||
'$thumb' => xmlify($item['thumb']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$title' => xmlify($item['title']),
|
||||
'$published' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , 'Y-m-d\TH:i:s\Z')),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00' , 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' =>xmlify($item['body']),
|
||||
'$parent_id' => xmlify($item['parent-uri']),
|
||||
'$comment_allow' => (($item['last-child']) ? 1 : 0)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$atom .= '</feed>' . "\r\n";
|
||||
return $atom;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function get_atom_elements($item) {
|
||||
|
||||
$res = array();
|
||||
|
||||
$author = $item->get_author();
|
||||
$res['author-name'] = unxmlify($author->get_name());
|
||||
$res['author-link'] = unxmlify($author->get_link());
|
||||
$res['author-avatar'] = unxmlify($author->get_avatar());
|
||||
$res['uri'] = unxmlify($item->get_id());
|
||||
$res['title'] = unxmlify($item->get_title());
|
||||
$res['body'] = unxmlify($item->get_content());
|
||||
|
||||
$maxlen = get_max_import_size();
|
||||
if($maxlen && (strlen($res['body']) > $maxlen))
|
||||
$res['body'] = substr($res['body'],0, $maxlen);
|
||||
|
||||
$allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
|
||||
if($allow && $allow[0]['data'] == 1)
|
||||
$res['last-child'] = 1;
|
||||
else
|
||||
$res['last-child'] = 0;
|
||||
|
||||
$rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
|
||||
if($rawcreated)
|
||||
$res['created'] = unxmlify($rawcreated[0]['data']);
|
||||
|
||||
$rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
|
||||
if($rawedited)
|
||||
$res['edited'] = unxmlify($rawcreated[0]['data']);
|
||||
|
||||
$rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
|
||||
if($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
|
||||
$res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
|
||||
if($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
|
||||
$res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
|
||||
if($rawowner[0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])
|
||||
$res['owner-avatar'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function post_remote($a,$arr) {
|
||||
|
||||
|
||||
if(! x($arr,'type'))
|
||||
$arr['type'] = 'remote';
|
||||
$arr['uri'] = notags(trim($arr['uri']));
|
||||
$arr['author-name'] = notags(trim($arr['author-name']));
|
||||
$arr['author-link'] = notags(trim($arr['author-link']));
|
||||
$arr['author-avatar'] = notags(trim($arr['author-avatar']));
|
||||
$arr['owner-name'] = notags(trim($arr['owner-name']));
|
||||
$arr['owner-link'] = notags(trim($arr['owner-link']));
|
||||
$arr['owner-avatar'] = notags(trim($arr['owner-avatar']));
|
||||
$arr['created'] = datetime_convert('UTC','UTC',$arr['created'],'Y-m-d H:i:s');
|
||||
$arr['edited'] = datetime_convert('UTC','UTC',$arr['edited'],'Y-m-d H:i:s');
|
||||
$arr['title'] = notags(trim($arr['title']));
|
||||
$arr['body'] = escape_tags(trim($arr['body']));
|
||||
$arr['last-child'] = intval($arr['last-child']);
|
||||
$arr['visible'] = 1;
|
||||
$arr['deleted'] = 0;
|
||||
$arr['parent-uri'] = notags(trim($arr['parent-uri']));
|
||||
|
||||
$parent_id = 0;
|
||||
|
||||
dbesc_array($arr);
|
||||
|
||||
$r = q("INSERT INTO `item` (`"
|
||||
. implode("`, `", array_keys($arr))
|
||||
. "`) VALUES ('"
|
||||
. implode("', '", array_values($arr))
|
||||
. "')" );
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
|
||||
dbesc($arr['parent-uri'])
|
||||
);
|
||||
|
||||
if(count($r))
|
||||
$parent_id = $r[0]['id'];
|
||||
else {
|
||||
// if parent is missing, what do we do?
|
||||
}
|
||||
|
||||
$r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
|
||||
$arr['uri']
|
||||
);
|
||||
if(count($r))
|
||||
$current_post = $r[0]['id'];
|
||||
|
||||
$r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d LIMIT 1",
|
||||
intval($parent_id),
|
||||
intval($current_post)
|
||||
);
|
||||
|
||||
return $current_post;
|
||||
}
|
38
include/nav.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
if(x($_SESSION['uid'])) {
|
||||
$a->page['nav'] .= '<a id="nav-logout-link" class="nav-link" href="logout">' . t('Logout') . "</a>\r\n";
|
||||
}
|
||||
else {
|
||||
$a->page['nav'] .= '<a id="nav-login-link" class="nav-login-link" href="login">' . t('Login') . "</a>\r\n";
|
||||
}
|
||||
|
||||
$a->page['nav'] .= "<span id=\"nav-link-wrapper\" >\r\n";
|
||||
|
||||
|
||||
if(x($_SESSION,'uid')) {
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-network-link" class="nav-commlink" href="network">' . t('Network')
|
||||
. '</a><span id="net-update" class="nav-ajax-left"></span>' . "\r\n";
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-home-link" class="nav-commlink" href="profile/' . $a->user['nickname'] . '">'
|
||||
. t('Home') . '</a><span id="home-update" class="nav-ajax-left"></span>' . "\r\n";
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-notify-link" class="nav-commlink" href="notifications">' . t('Notifications')
|
||||
. '</a><span id="notify-update" class="nav-ajax-left"></span>' . "\r\n";
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-messages-link" class="nav-commlink" href="message">' . t('Messages')
|
||||
. '</a><span id="mail-update" class="nav-ajax-left"></span>' . "\r\n";
|
||||
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-settings-link" class="nav-link" href="settings">' . t('Settings') . "</a>\r\n";
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-profiles-link" class="nav-link" href="profiles">' . t('Profiles') . "</a>\r\n";
|
||||
|
||||
$a->page['nav'] .= '<a id="nav-contacts-link" class="nav-link" href="contacts">' . t('Contacts') . "</a>\r\n";
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$a->page['nav'] .= "</span>\r\n<span id=\"nav-end\"></span>\r\n";
|
293
include/notifier.php
Normal file
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
$a = new App();
|
||||
|
||||
@include '.htconfig.php';
|
||||
|
||||
require_once 'dba.php';
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
unset($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
require_once 'datetime.php';
|
||||
|
||||
if ($argc < 3) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$a->set_baseurl(get_config('system', 'url'));
|
||||
|
||||
$cmd = $argv[1];
|
||||
|
||||
switch ($cmd) {
|
||||
case 'mail':
|
||||
default:
|
||||
$item_id = intval($argv[2]);
|
||||
if (!$item_id) {
|
||||
killme();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$recipients = array();
|
||||
|
||||
if ($cmd == 'mail') {
|
||||
|
||||
$message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id));
|
||||
if (!count($message)) {
|
||||
killme();
|
||||
}
|
||||
$recipients[] = $message[0]['contact-id'];
|
||||
$item = $message[0];
|
||||
} else {
|
||||
// find ancestors
|
||||
|
||||
$r = q("SELECT `parent`, `edited` FROM `item` WHERE `id` = %d LIMIT 1", intval($item_id));
|
||||
if (!count($r)) {
|
||||
killme();
|
||||
}
|
||||
|
||||
$parent = $r[0]['parent'];
|
||||
$updated = $r[0]['edited'];
|
||||
|
||||
$items = q("SELECT * FROM `item` WHERE `parent` = %d ORDER BY `id` ASC", intval($parent));
|
||||
|
||||
if (!count($items)) {
|
||||
killme();
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `self` = 1 LIMIT 1");
|
||||
|
||||
if (count($r)) {
|
||||
$owner = $r[0];
|
||||
} else {
|
||||
killme();
|
||||
}
|
||||
|
||||
if ($cmd != 'mail') {
|
||||
|
||||
require_once 'include/group.php';
|
||||
|
||||
$parent = $items[0];
|
||||
|
||||
if ($parent['type'] == 'remote') {
|
||||
// local followup to remote post
|
||||
$followup = true;
|
||||
$conversant_str = dbesc($parent['contact-id']);
|
||||
} else {
|
||||
$followup = false;
|
||||
|
||||
$allow_people = expand_acl($parent['allow_cid']);
|
||||
$allow_groups = expand_groups(expand_acl($parent['allow_gid']));
|
||||
$deny_people = expand_acl($parent['deny_cid']);
|
||||
$deny_groups = expand_groups(expand_acl($parent['deny_gid']));
|
||||
|
||||
$conversants = array();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$recipients[] = $item['contact-id'];
|
||||
$conversants[] = $item['contact-id'];
|
||||
}
|
||||
|
||||
$conversants = array_unique($conversants, SORT_NUMERIC);
|
||||
|
||||
$recipients = array_unique(array_merge($recipients, $allow_people, $allow_groups), SORT_NUMERIC);
|
||||
$deny = array_unique(array_merge($deny_people, $deny_groups), SORT_NUMERIC);
|
||||
$recipients = array_diff($recipients, $deny);
|
||||
|
||||
$conversant_str = dbesc(implode(', ', $conversants));
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `id` IN ( $conversant_str ) AND `blocked` = 0 AND `pending` = 0");
|
||||
|
||||
if (!count($r)) {
|
||||
killme();
|
||||
}
|
||||
|
||||
$contacts = $r;
|
||||
|
||||
$tomb_template = file_get_contents('view/atom_tomb.tpl');
|
||||
$item_template = file_get_contents('view/atom_item.tpl');
|
||||
$cmnt_template = file_get_contents('view/atom_cmnt.tpl');
|
||||
}
|
||||
|
||||
$feed_template = file_get_contents('view/atom_feed.tpl');
|
||||
$mail_template = file_get_contents('view/atom_mail.tpl');
|
||||
|
||||
$atom = '';
|
||||
|
||||
$atom .= replace_macros($feed_template, array(
|
||||
'$feed_id' => xmlify($a->get_baseurl()),
|
||||
'$feed_title' => xmlify($owner['name']),
|
||||
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$photo' => xmlify($owner['photo']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$picdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['avatar-date'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$uridate' => xmlify(datetime_convert('UTC', 'UTC', $owner['uri-date'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$namdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['name-date'] . '+00:00', 'Y-m-d\TH:i:s\Z'))
|
||||
));
|
||||
|
||||
if ($cmd == 'mail') {
|
||||
$atom .= replace_macros($mail_template, array(
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$subject' => xmlify($item['title']),
|
||||
'$created' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' => xmlify($item['body']),
|
||||
'$parent_id' => xmlify($item['parent-uri'])
|
||||
));
|
||||
} else {
|
||||
if ($followup) {
|
||||
foreach ($items as $item) {
|
||||
if ($item['id'] == $item_id) {
|
||||
$atom .= replace_macros($cmnt_template, array(
|
||||
'$name' => xmlify($owner['name']),
|
||||
'$profile_page' => xmlify($owner['url']),
|
||||
'$thumb' => xmlify($owner['thumb']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$title' => xmlify($item['title']),
|
||||
'$published' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' => xmlify($item['body']),
|
||||
'$parent_id' => xmlify($item['parent-uri']),
|
||||
'$comment_allow' => 0
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($items as $item) {
|
||||
if ($item['deleted']) {
|
||||
$atom .= replace_macros($tomb_template, array(
|
||||
'$id' => xmlify($item['uri']),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00', 'Y-m-d\TH:i:s\Z'))
|
||||
));
|
||||
} else {
|
||||
foreach ($contacts as $contact) {
|
||||
if ($item['contact-id'] == $contact['id']) {
|
||||
if ($item['parent'] == $item['id']) {
|
||||
$atom .= replace_macros($item_template, array(
|
||||
'$name' => xmlify($contact['name']),
|
||||
'$profile_page' => xmlify($contact['url']),
|
||||
'$thumb' => xmlify($contact['thumb']),
|
||||
'$owner_name' => xmlify($item['owner-name']),
|
||||
'$owner_profile_page' => xmlify($item['owner-link']),
|
||||
'$owner_thumb' => xmlify($item['owner-avatar']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$title' => xmlify($item['title']),
|
||||
'$published' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' => xmlify($item['body']),
|
||||
'$comment_allow' => (($item['last-child'] && strlen($contact['dfrn-id'])) ? 1 : 0)
|
||||
));
|
||||
} else {
|
||||
$atom .= replace_macros($cmnt_template, array(
|
||||
'$name' => xmlify($contact['name']),
|
||||
'$profile_page' => xmlify($contact['url']),
|
||||
'$thumb' => xmlify($contact['thumb']),
|
||||
'$item_id' => xmlify($item['uri']),
|
||||
'$title' => xmlify($item['title']),
|
||||
'$published' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$updated' => xmlify(datetime_convert('UTC', 'UTC', $item['edited'] . '+00:00', 'Y-m-d\TH:i:s\Z')),
|
||||
'$content' => xmlify($item['body']),
|
||||
'$parent_id' => xmlify($item['parent-uri']),
|
||||
'$comment_allow' => (($item['last-child']) ? 1 : 0)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$atom .= "</feed>\r\n";
|
||||
|
||||
// create a clone of this feed but with comments disabled to send to those who can't respond.
|
||||
|
||||
$atom_nowrite = str_replace('<dfrn:comment-allow>1', '<dfrn:comment-allow>0', $atom);
|
||||
|
||||
if ($followup) {
|
||||
$recip_str = $parent['contact-id'];
|
||||
} else {
|
||||
$recip_str = implode(', ', $recipients);
|
||||
}
|
||||
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `id` IN ( %s ) ", dbesc($recip_str));
|
||||
if (!count($r)) {
|
||||
killme();
|
||||
}
|
||||
|
||||
// delivery loop
|
||||
|
||||
foreach ($r as $rr) {
|
||||
if ($rr['self']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strlen($rr['dfrn-id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $rr['notify'] . '?dfrn_id=' . $rr['dfrn-id'];
|
||||
|
||||
$xml = fetch_url($url);
|
||||
|
||||
if (!$xml) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$res = simplexml_load_string($xml);
|
||||
|
||||
if ((intval($res->status) != 0) || (!strlen($res->challenge)) || (!strlen($res->dfrn_id))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$postvars = array();
|
||||
$sent_dfrn_id = hex2bin($res->dfrn_id);
|
||||
|
||||
$final_dfrn_id = '';
|
||||
openssl_public_decrypt($sent_dfrn_id, $final_dfrn_id, $rr['pubkey']);
|
||||
$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
|
||||
if ($final_dfrn_id != $rr['dfrn-id']) {
|
||||
// did not decode properly - cannot trust this site
|
||||
continue;
|
||||
}
|
||||
|
||||
$postvars['dfrn_id'] = $rr['dfrn-id'];
|
||||
|
||||
$challenge = hex2bin($res->challenge);
|
||||
|
||||
openssl_public_decrypt($challenge, $postvars['challenge'], $rr['pubkey']);
|
||||
|
||||
if ($cmd == 'mail') {
|
||||
$postvars['data'] = $atom;
|
||||
} elseif (strlen($rr['dfrn-id']) && (!($rr['blocked']) || ($rr['readonly']))) {
|
||||
$postvars['data'] = $atom;
|
||||
} else {
|
||||
$postvars['data'] = $atom_nowrite;
|
||||
}
|
||||
|
||||
$xml = post_url($rr['notify'], $postvars);
|
||||
|
||||
$res = simplexml_load_string($xml);
|
||||
|
||||
// Currently there is no retry attempt for failed mail delivery.
|
||||
// We need to handle this in the UI, report the non-deliverables and try again
|
||||
|
||||
if (($cmd == 'mail') && (intval($res->status) == 0)) {
|
||||
$r = q("UPDATE `mail` SET `delivered` = 1 WHERE `id` = %d LIMIT 1", intval($item_id));
|
||||
}
|
||||
}
|
||||
|
||||
killme();
|
268
include/poller.php
Normal file
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
$a = new App();
|
||||
|
||||
@include('.htconfig.php');
|
||||
|
||||
require_once 'dba.php';
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
unset($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
require_once 'datetime.php';
|
||||
require_once 'simplepie/simplepie.inc';
|
||||
require_once 'include/items.php';
|
||||
|
||||
$a->set_baseurl(get_config('system', 'url'));
|
||||
|
||||
$contacts = q("SELECT * FROM `contact`
|
||||
WHERE `dfrn-id` != '' AND `self` = 0 AND `blocked` = 0
|
||||
AND `readonly` = 0 ORDER BY RAND()");
|
||||
|
||||
if (!count($contacts)) {
|
||||
killme();
|
||||
}
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact['priority']) {
|
||||
$update = false;
|
||||
$t = $contact['last-update'];
|
||||
|
||||
switch ($contact['priority']) {
|
||||
case 5:
|
||||
if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', t . " + 1 month"))
|
||||
$update = true;
|
||||
break;
|
||||
case 4:
|
||||
if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', t . " + 1 week"))
|
||||
$update = true;
|
||||
break;
|
||||
case 3:
|
||||
if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', t . " + 1 day"))
|
||||
$update = true;
|
||||
break;
|
||||
case 2:
|
||||
if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', t . " + 12 hour"))
|
||||
$update = true;
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', t . " + 1 hour"))
|
||||
$update = true;
|
||||
break;
|
||||
}
|
||||
if (!$update) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `self` = 1 LIMIT 1");
|
||||
if (!count($r)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$importer = $r[0];
|
||||
|
||||
$last_update = (($contact['last-update'] == '0000-00-00 00:00:00') ? datetime_convert('UTC', 'UTC', 'now - 30 days', 'Y-m-d\TH:i:s\Z') : datetime_convert('UTC', 'UTC', $contact['last-update'], 'Y-m-d\TH:i:s\Z'));
|
||||
|
||||
$url = $contact['poll'] . '?dfrn_id=' . $contact['dfrn-id'] . '&type=data&last_update=' . $last_update;
|
||||
|
||||
$xml = fetch_url($url);
|
||||
if (!$xml) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$res = simplexml_load_string($xml);
|
||||
|
||||
if ((intval($res->status) != 0) || (!strlen($res->challenge)) || (!strlen($res->dfrn_id))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$postvars = array();
|
||||
|
||||
$sent_dfrn_id = hex2bin($res->dfrn_id);
|
||||
|
||||
$final_dfrn_id = '';
|
||||
openssl_public_decrypt($sent_dfrn_id, $final_dfrn_id, $contact['pubkey']);
|
||||
$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
|
||||
if ($final_dfrn_id != $contact['dfrn-id']) {
|
||||
// did not decode properly - cannot trust this site
|
||||
continue;
|
||||
}
|
||||
|
||||
$postvars['dfrn_id'] = $contact['dfrn-id'];
|
||||
$challenge = hex2bin($res->challenge);
|
||||
|
||||
openssl_public_decrypt($challenge, $postvars['challenge'], $contact['pubkey']);
|
||||
|
||||
$xml = post_url($contact['poll'], $postvars);
|
||||
|
||||
if (!strlen($xml)) {
|
||||
// an empty response may mean there's nothing new - record the fact that we checked
|
||||
$r = q(
|
||||
"UPDATE `contact` SET `last-update` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact['id'])
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$feed = new SimplePie();
|
||||
$feed->set_raw_data($xml);
|
||||
$feed->enable_order_by_date(false);
|
||||
$feed->init();
|
||||
|
||||
$photo_rawupdate = $feed->get_feed_tags(NAMESPACE_DFRN, 'icon-updated');
|
||||
|
||||
if ($photo_rawupdate) {
|
||||
$photo_timestamp = datetime_convert('UTC', 'UTC', $photo_rawupdate[0]['data']);
|
||||
$photo_url = $feed->get_image_url();
|
||||
|
||||
if (strlen($photo_url) && $photo_timestamp > $contact['avatar-date']) {
|
||||
require_once 'Photo.php';
|
||||
|
||||
$photo_failure = false;
|
||||
|
||||
$r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d LIMIT 1", intval($contact['id']));
|
||||
|
||||
if (count($r)) {
|
||||
$resource_id = $r[0]['resource-id'];
|
||||
$img_str = fetch_url($photo_url, true);
|
||||
$img = new Photo($img_str);
|
||||
|
||||
if ($img) {
|
||||
q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND contact-id` = %d ",
|
||||
dbesc($resource_id),
|
||||
intval($contact['id'])
|
||||
);
|
||||
|
||||
$img->scaleImageSquare(175);
|
||||
|
||||
$hash = $resource_id;
|
||||
|
||||
$r = $img->store($contact['id'], $hash, basename($photo_url), t('Contact Photos'), 4);
|
||||
|
||||
$img->scaleImage(80);
|
||||
$r = $img->store($contact['id'], $hash, basename($photo_url), t('Contact Photos'), 5);
|
||||
if ($r) {
|
||||
q("UPDATE `contact` SET `avatar-date` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($feed->get_items() as $item) {
|
||||
$deleted = false;
|
||||
|
||||
$rawdelete = $item->get_item_tags("http://purl.org/atompub/tombstones/1.0", 'deleted-entry');
|
||||
|
||||
if (isset($rawdelete[0]['attribs']['']['ref'])) {
|
||||
$uri = $rawthread[0]['attribs']['']['ref'];
|
||||
$deleted = true;
|
||||
|
||||
if (isset($rawdelete[0]['attribs']['']['when'])) {
|
||||
$when = $rawthread[0]['attribs']['']['when'];
|
||||
$when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s');
|
||||
} else {
|
||||
$when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted) {
|
||||
$r = q("SELECT * FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($uri));
|
||||
if (count($r)) {
|
||||
if ($r[0]['uri'] == $r[0]['parent-uri']) {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `parent-uri` = '%s'",
|
||||
dbesc($when),
|
||||
dbesc($r[0]['uri'])
|
||||
);
|
||||
} else {
|
||||
$r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s',
|
||||
`body` = '', `title` = ''
|
||||
WHERE `uri` = '%s' LIMIT 1",
|
||||
dbesc($when),
|
||||
dbesc($uri)
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_reply = false;
|
||||
$item_id = $item->get_id();
|
||||
$rawthread = $item->get_item_tags("http://purl.org/syndication/thread/1.0", 'in-reply-to');
|
||||
|
||||
if (isset($rawthread[0]['attribs']['']['ref'])) {
|
||||
$is_reply = true;
|
||||
$parent_uri = $rawthread[0]['attribs']['']['ref'];
|
||||
}
|
||||
|
||||
if ($is_reply) {
|
||||
// Have we seen it? If not, import it.
|
||||
$item_id = $item->get_id();
|
||||
|
||||
$r = q("SELECT `last-child`, `edited` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item_id));
|
||||
|
||||
// FIXME update content if 'updated' changes
|
||||
if (count($r)) {
|
||||
$allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow');
|
||||
if ($allow && $allow[0]['data'] != $r[0]['last-child']) {
|
||||
$r = q("UPDATE `item` SET `last-child` = %d WHERE `uri` = '%s' LIMIT 1",
|
||||
intval($allow[0]['data']),
|
||||
dbesc($item_id)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$datarray = get_atom_elements($item);
|
||||
$datarray['parent-uri'] = $parent_uri;
|
||||
$datarray['contact-id'] = $contact['id'];
|
||||
$r = post_remote($a, $datarray);
|
||||
|
||||
continue;
|
||||
} else {
|
||||
// Head post of a conversation. Have we seen it? If not, import it.
|
||||
|
||||
$item_id = $item->get_id();
|
||||
$r = q("SELECT `last-child`, `edited` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item_id));
|
||||
|
||||
if (count($r)) {
|
||||
$allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow');
|
||||
if ($allow && $allow[0]['data'] != $r[0]['last-child']) {
|
||||
$r = q("UPDATE `item` SET `last-child` = %d WHERE `uri` = '%s' LIMIT 1",
|
||||
intval($allow[0]['data']),
|
||||
dbesc($item_id)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$datarray = get_atom_elements($item);
|
||||
$datarray['parent-uri'] = $item_id;
|
||||
$datarray['contact-id'] = $contact['id'];
|
||||
$r = post_remote($a, $datarray);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$r = q("UPDATE `contact` SET `last-update` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc(datetime_convert()),
|
||||
intval($contact['id'])
|
||||
);
|
||||
}
|
||||
|
||||
killme();
|
167
include/rockstar.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
//Startup.
|
||||
require_once 'boot.php';
|
||||
|
||||
$a = new App();
|
||||
|
||||
@include(".htconfig.php");
|
||||
require_once 'dba.php';
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
unset($db_host, $db_user, $db_pass, $db_data);
|
||||
|
||||
require_once 'datetime.php';
|
||||
|
||||
$a->set_baseurl(get_config('system', 'url'));
|
||||
|
||||
$u = q("SELECT * FROM `user` WHERE 1 LIMIT 1");
|
||||
|
||||
if (!count($u)) {
|
||||
killme();
|
||||
}
|
||||
|
||||
$uid = $u[0]['uid'];
|
||||
$nickname = $u[0]['nickname'];
|
||||
|
||||
$intros = q("SELECT `intro`.*, `intro`.`id` AS `intro_id`, `contact`.*
|
||||
FROM `intro` LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id`
|
||||
WHERE `intro`.`blocked` = 0 AND `intro`.`ignore` = 0");
|
||||
|
||||
if (!count($intros)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($intros as $intro) {
|
||||
$intro_id = intval($intro['intro_id']);
|
||||
|
||||
$dfrn_id = $intro['issued-id'];
|
||||
$contact_id = $intro['contact-id'];
|
||||
$relation = $intro['rel'];
|
||||
$site_pubkey = $intro['site-pubkey'];
|
||||
$dfrn_confirm = $intro['confirm'];
|
||||
$aes_allow = $intro['aes_allow'];
|
||||
|
||||
$res = openssl_pkey_new(array(
|
||||
'digest_alg' => 'whirlpool',
|
||||
'private_key_bits' => 4096,
|
||||
'encrypt_key' => false));
|
||||
|
||||
$private_key = '';
|
||||
|
||||
openssl_pkey_export($res, $private_key);
|
||||
|
||||
$pubkey = openssl_pkey_get_details($res);
|
||||
$public_key = $pubkey["key"];
|
||||
|
||||
$r = q("UPDATE `contact` SET `issued-pubkey` = '%s', `prvkey` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc($public_key),
|
||||
dbesc($private_key),
|
||||
intval($contact_id)
|
||||
);
|
||||
|
||||
$params = array();
|
||||
|
||||
$src_aes_key = random_string();
|
||||
$result = "";
|
||||
|
||||
openssl_private_encrypt($dfrn_id, $result, $u[0]['prvkey']);
|
||||
|
||||
$params['dfrn_id'] = $result;
|
||||
$params['public_key'] = $public_key;
|
||||
|
||||
$my_url = $a->get_baseurl() . '/profile/' . $nickname;
|
||||
|
||||
openssl_public_encrypt($my_url, $params['source_url'], $site_pubkey);
|
||||
|
||||
if ($aes_allow && function_exists('openssl_encrypt')) {
|
||||
openssl_public_encrypt($src_aes_key, $params['aes_key'], $site_pubkey);
|
||||
$params['public_key'] = openssl_encrypt($public_key, 'AES-256-CBC', $src_aes_key);
|
||||
}
|
||||
|
||||
$res = post_url($dfrn_confirm, $params);
|
||||
|
||||
$xml = simplexml_load_string($res);
|
||||
$status = (int) $xml->status;
|
||||
switch ($status) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
// birthday paradox - generate new dfrn-id and fall through.
|
||||
$new_dfrn_id = random_string();
|
||||
$r = q("UPDATE contact SET `issued-id` = '%s' WHERE `id` = %d LIMIT 1",
|
||||
dbesc($new_dfrn_id),
|
||||
intval($contact_id)
|
||||
);
|
||||
case 2:
|
||||
break;
|
||||
|
||||
case 3:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (($status == 0 || $status == 3) && ($intro_id)) {
|
||||
// delete the notification
|
||||
$r = q("DELETE FROM `intro` WHERE `id` = %d LIMIT 1", intval($intro_id));
|
||||
}
|
||||
|
||||
if ($status != 0) {
|
||||
killme();
|
||||
}
|
||||
|
||||
require_once 'Photo.php';
|
||||
|
||||
$photo_failure = false;
|
||||
|
||||
$filename = basename($intro['photo']);
|
||||
$img_str = fetch_url($intro['photo'], true);
|
||||
$img = new Photo($img_str);
|
||||
|
||||
if ($img) {
|
||||
$img->scaleImageSquare(175);
|
||||
$hash = hash('md5', uniqid(mt_rand(), true));
|
||||
|
||||
$r = $img->store($contact_id, $hash, $filename, t('Contact Photos'), 4);
|
||||
|
||||
if ($r === false) {
|
||||
$photo_failure = true;
|
||||
}
|
||||
$img->scaleImage(80);
|
||||
|
||||
$r = $img->store($contact_id, $hash, $filename, t('Contact Photos'), 5);
|
||||
|
||||
if ($r === false) {
|
||||
$photo_failure = true;
|
||||
}
|
||||
|
||||
$photo = $a->get_baseurl() . '/photo/' . $hash . '-4.jpg';
|
||||
$thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.jpg';
|
||||
} else {
|
||||
$photo_failure = true;
|
||||
}
|
||||
|
||||
if ($photo_failure) {
|
||||
$photo = $a->get_baseurl() . '/images/default-profile.jpg';
|
||||
$thumb = $a->get_baseurl() . '/images/default-profile-sm.jpg';
|
||||
}
|
||||
|
||||
$r = q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `rel` = %d,
|
||||
`name-date` = '%s', `uri-date` = '%s', `avatar-date` = '%s',
|
||||
`readonly` = %d, `profile-id` = %d, `blocked` = 0, `pending` = 0,
|
||||
`network` = 'dfrn' WHERE `id` = %d LIMIT 1",
|
||||
dbesc($photo),
|
||||
dbesc($thumb),
|
||||
intval(($relation == DIRECTION_OUT) ? DIRECTION_BOTH : DIRECTION_IN),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert()),
|
||||
intval((x($a->config, 'rockstar-readonly')) ? $a->config['rockstar-readonly'] : 0),
|
||||
intval((x($a->config, 'rockstar-profile')) ? $a->config['rockstar-profile'] : 0),
|
||||
intval($contact_id)
|
||||
);
|
||||
}
|
||||
killme();
|
17
include/security.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
function can_write_wall(&$a,$owner) {
|
||||
if((! (local_user())) && (! (remote_user())))
|
||||
return false;
|
||||
if((local_user()) && ($_SESSION['uid'] == $owner))
|
||||
return true;
|
||||
$sql_extra = (($a->config['rockstar']) ? '' : " AND `readonly` = 0 ");
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `blocked` = 0 AND `pending` = 0 $sql_extra LIMIT 1",
|
||||
intval($_SESSION['visitor_id'])
|
||||
);
|
||||
if(count($r))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
}
|
89
include/session.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
// Session management functions. These provide database storage of PHP
|
||||
// session info.
|
||||
|
||||
$session_exists = 0;
|
||||
$session_expire = 180000;
|
||||
|
||||
function ref_session_open($s, $n)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function ref_session_read($id)
|
||||
{
|
||||
global $session_exists;
|
||||
|
||||
if (x($id)) {
|
||||
$r = q("SELECT `data` FROM `session` WHERE `sid`= '%s'", dbesc($id));
|
||||
}
|
||||
|
||||
if (count($r)) {
|
||||
$session_exists = true;
|
||||
return $r[0]['data'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function ref_session_write($id, $data)
|
||||
{
|
||||
global $session_exists, $session_expire;
|
||||
|
||||
if (!$id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$expire = time() + $session_expire;
|
||||
$default_expire = time() + 300;
|
||||
|
||||
if ($session_exists) {
|
||||
$r = q("UPDATE `session`
|
||||
SET `data` = '%s', `expire` = '%s'
|
||||
WHERE `sid` = '%s' LIMIT 1", dbesc($data), dbesc($expire), dbesc($id));
|
||||
} else {
|
||||
$r = q("INSERT INTO `session`
|
||||
SET `sid` = '%s', `expire` = '%s', `data` = '%s'", dbesc($id), dbesc($default_expire), dbesc($data));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function ref_session_close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function ref_session_destroy($id)
|
||||
{
|
||||
q("DELETE FROM `session` WHERE `sid` = '%s'", dbesc($id));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function ref_session_gc($expire)
|
||||
{
|
||||
q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time()));
|
||||
q("OPTIMIZE TABLE `sess_data`");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$gc_probability = 50;
|
||||
|
||||
ini_set('session.gc_probability', $gc_probability);
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
|
||||
session_set_save_handler(
|
||||
'ref_session_open',
|
||||
'ref_session_close',
|
||||
'ref_session_read',
|
||||
'ref_session_write',
|
||||
'ref_session_destroy',
|
||||
'ref_session_gc'
|
||||
);
|
379
include/site-health.php
Normal file
|
@ -0,0 +1,379 @@
|
|||
<?php
|
||||
/*
|
||||
Based on a submitted URL, take note of the site it mentions.
|
||||
Ensures that the site health will be tracked if it wasn't already.
|
||||
If $check_health is set to true, this function may trigger some health checks (CURL requests) when needed.
|
||||
Do not enable it unless you have enough execution time to do so.
|
||||
But when you do, it's better to check for health whenever a site submits something.
|
||||
After all, the highest chance for the server to be online is when it submits activity.
|
||||
*/
|
||||
if (!function_exists('notice_site')) {
|
||||
function notice_site($url, $check_health = false)
|
||||
{
|
||||
global $a;
|
||||
|
||||
//Parse the domain from the URL.
|
||||
$site = parse_site_from_url($url);
|
||||
|
||||
//Search for it in the site-health table.
|
||||
$result = q(
|
||||
"SELECT * FROM `site-health` WHERE `base_url`= '%s' ORDER BY `id` ASC LIMIT 1",
|
||||
dbesc($site)
|
||||
);
|
||||
|
||||
//If it exists, see if we need to update any flags / statuses.
|
||||
if (!empty($result) && isset($result[0])) {
|
||||
$entry = $result[0];
|
||||
|
||||
//If we are allowed to do health checks...
|
||||
if ($check_health) {
|
||||
//And the site is in bad health currently, do a check now.
|
||||
//This is because you have a high certainty the site may perform better now.
|
||||
if ($entry['health_score'] < -40) {
|
||||
run_site_probe($entry['id'], $entry);
|
||||
}
|
||||
|
||||
//Or if the site has not been probed for longer than the minimum delay.
|
||||
//This is to make sure not everything is postponed to the batches.
|
||||
elseif (strtotime($entry['dt_last_probed']) < time() - $a->config['site-health']['min_probe_delay']) {
|
||||
run_site_probe($entry['id'], $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If it does not exist.
|
||||
else {
|
||||
|
||||
//Add it and make sure it is ready for probing.
|
||||
q(
|
||||
"INSERT INTO `site-health` (`base_url`, `dt_first_noticed`) VALUES ('%s', NOW())",
|
||||
dbesc($site)
|
||||
);
|
||||
|
||||
//And in case we should probe now, do so.
|
||||
if ($check_health) {
|
||||
|
||||
$result = q(
|
||||
"SELECT * FROM `site-health` WHERE `base_url`= '%s' ORDER BY `id` ASC LIMIT 1",
|
||||
dbesc($site)
|
||||
);
|
||||
if (!empty($result) && isset($result[0])) {
|
||||
$entry = $result[0];
|
||||
run_site_probe($result[0]['id'], $entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Give other scripts the site health.
|
||||
return isset($entry) ? $entry : false;
|
||||
}
|
||||
}
|
||||
|
||||
//Extracts the site from a given URL.
|
||||
if (!function_exists('parse_site_from_url')) {
|
||||
function parse_site_from_url($url)
|
||||
{
|
||||
//Currently a simple implementation, but may improve over time.
|
||||
#TODO: support subdirectories?
|
||||
$urlMeta = parse_url($url);
|
||||
return $urlMeta['scheme'] . '://' . $urlMeta['host'];
|
||||
}
|
||||
}
|
||||
|
||||
//Performs a ping to the given site ID
|
||||
//You may need to notice the site first before you know it's ID.
|
||||
//TODO: Probe server location using IP address or using the info the friendica server provides (preferred).
|
||||
// If IP needs to be used only provide country information.
|
||||
//TODO: Check SSLLabs Grade
|
||||
// Check needs to be asynchronous, meaning that the check at SSLLabs will be initiated in one run while
|
||||
// the results must be fetched later. It might be good to mark sites, where a check has been inititated
|
||||
// f.e. using the ssl_grade field. In the next run, results of these sites could be fetched.
|
||||
if (!function_exists('run_site_probe')) {
|
||||
function run_site_probe($id, &$entry_out)
|
||||
{
|
||||
global $a;
|
||||
|
||||
//Get the site information from the DB, based on the ID.
|
||||
$result = q(
|
||||
"SELECT * FROM `site-health` WHERE `id`= %u ORDER BY `id` ASC LIMIT 1",
|
||||
intval($id)
|
||||
);
|
||||
|
||||
//Abort the probe if site is not known.
|
||||
if (!$result || !isset($result[0])) {
|
||||
logger('Unknown site-health ID being probed: ' . $id);
|
||||
throw new \Exception('Unknown site-health ID being probed: ' . $id);
|
||||
}
|
||||
|
||||
//Shortcut.
|
||||
$entry = $result[0];
|
||||
$base_url = $entry['base_url'];
|
||||
$probe_location = $base_url . '/friendica/json';
|
||||
|
||||
$net_ping = Net_Ping::factory();
|
||||
$net_ping->setArgs(['count' => 5]);
|
||||
$result = $net_ping->ping(parse_url($base_url, PHP_URL_HOST));
|
||||
|
||||
if (is_a($result, 'Net_Ping_Result')) {
|
||||
$avg_ping = $result->getAvg();
|
||||
} else {
|
||||
$avg_ping = null;
|
||||
}
|
||||
|
||||
//Prepare the CURL call.
|
||||
$handle = curl_init();
|
||||
$options = array(
|
||||
//Timeouts
|
||||
CURLOPT_TIMEOUT => max($a->config['site-health']['probe_timeout'], 1), //Minimum of 1 second timeout.
|
||||
CURLOPT_CONNECTTIMEOUT => 1,
|
||||
//Redirecting
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 8,
|
||||
//SSL
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
// CURLOPT_VERBOSE => true,
|
||||
// CURLOPT_CERTINFO => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||
//Basic request
|
||||
CURLOPT_USERAGENT => 'friendica-directory-probe-1.0',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_URL => $probe_location
|
||||
);
|
||||
curl_setopt_array($handle, $options);
|
||||
|
||||
//Probe the site.
|
||||
$probe_start = microtime(true);
|
||||
$probe_data = curl_exec($handle);
|
||||
$probe_end = microtime(true);
|
||||
|
||||
//Check for SSL problems.
|
||||
$curl_statuscode = curl_errno($handle);
|
||||
$sslcert_issues = in_array($curl_statuscode, array(
|
||||
60, //Could not authenticate certificate with known CA's
|
||||
83 //Issuer check failed
|
||||
));
|
||||
|
||||
//When it's the certificate that doesn't work.
|
||||
if ($sslcert_issues) {
|
||||
//Probe again, without strict SSL.
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
|
||||
//Replace the handle.
|
||||
curl_close($handle);
|
||||
$handle = curl_init();
|
||||
curl_setopt_array($handle, $options);
|
||||
|
||||
//Probe.
|
||||
$probe_start = microtime(true);
|
||||
$probe_data = curl_exec($handle);
|
||||
$probe_end = microtime(true);
|
||||
|
||||
//Store new status.
|
||||
$curl_statuscode = curl_errno($handle);
|
||||
}
|
||||
|
||||
//Gather more meta.
|
||||
$time = round(($probe_end - $probe_start) * 1000);
|
||||
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
|
||||
$type = curl_getinfo($handle, CURLINFO_CONTENT_TYPE);
|
||||
$info = curl_getinfo($handle);
|
||||
|
||||
//Done with CURL now.
|
||||
curl_close($handle);
|
||||
|
||||
if ($time && $avg_ping) {
|
||||
$speed_score = max(1, $avg_ping > 10 ? $time / $avg_ping : $time / 50);
|
||||
} else {
|
||||
$speed_score = null;
|
||||
}
|
||||
|
||||
#TODO: if the site redirects elsewhere, notice this site and record an issue.
|
||||
$effective_base_url = parse_site_from_url($info['url']);
|
||||
$wrong_base_url = $effective_base_url !== $entry['base_url'];
|
||||
|
||||
try {
|
||||
$data = json_decode($probe_data);
|
||||
} catch (\Exception $ex) {
|
||||
$data = false;
|
||||
}
|
||||
|
||||
$parse_failed = !$data;
|
||||
|
||||
$parsedDataQuery = '';
|
||||
|
||||
logger('Effective Base URL: ' . $effective_base_url);
|
||||
|
||||
if ($wrong_base_url) {
|
||||
$parsedDataQuery .= sprintf(
|
||||
"`effective_base_url` = '%s',",
|
||||
dbesc($effective_base_url)
|
||||
);
|
||||
} else {
|
||||
$parsedDataQuery .= "`effective_base_url` = NULL,";
|
||||
}
|
||||
|
||||
if (!$parse_failed) {
|
||||
$given_base_url_match = $data->url == $base_url;
|
||||
|
||||
//Record the probe speed in a probes table.
|
||||
q(
|
||||
"INSERT INTO `site-probe` (`site_health_id`, `dt_performed`, `request_time`, `avg_ping`, `speed_score`)" .
|
||||
"VALUES (%u, NOW(), %u, %u, %u)",
|
||||
$entry['id'],
|
||||
$time,
|
||||
$avg_ping,
|
||||
$speed_score
|
||||
);
|
||||
|
||||
if (isset($data->addons)) {
|
||||
$addons = $data->addons;
|
||||
} else {
|
||||
// Backward compatibility
|
||||
$addons = $data->plugins;
|
||||
}
|
||||
|
||||
//Update any health calculations or otherwise processed data.
|
||||
$parsedDataQuery .= sprintf(
|
||||
"`dt_last_seen` = NOW(),
|
||||
`name` = '%s',
|
||||
`version` = '%s',
|
||||
`addons` = '%s',
|
||||
`reg_policy` = '%s',
|
||||
`info` = '%s',
|
||||
`admin_name` = '%s',
|
||||
`admin_profile` = '%s',
|
||||
",
|
||||
dbesc($data->site_name),
|
||||
dbesc($data->version),
|
||||
dbesc(implode("\r\n", $addons)),
|
||||
dbesc($data->register_policy),
|
||||
dbesc($data->info),
|
||||
dbesc($data->admin->name),
|
||||
dbesc($data->admin->profile)
|
||||
);
|
||||
|
||||
//Did we use HTTPS?
|
||||
$urlMeta = parse_url($probe_location);
|
||||
if ($urlMeta['scheme'] == 'https') {
|
||||
$parsedDataQuery .= sprintf("`ssl_state` = b'%u',", $sslcert_issues ? '0' : '1');
|
||||
} else {
|
||||
$parsedDataQuery .= "`ssl_state` = NULL,";
|
||||
}
|
||||
|
||||
//Do we have a no scrape supporting node? :D
|
||||
if (isset($data->no_scrape_url)) {
|
||||
$parsedDataQuery .= sprintf("`no_scrape_url` = '%s',", dbesc($data->no_scrape_url));
|
||||
}
|
||||
}
|
||||
|
||||
//Get the new health.
|
||||
$version = $parse_failed ? '' : $data->version;
|
||||
$health = health_score_after_probe($entry['health_score'], !$parse_failed, $time, $version, $sslcert_issues);
|
||||
|
||||
//Update the health.
|
||||
q("UPDATE `site-health` SET
|
||||
`health_score` = '%d',
|
||||
$parsedDataQuery
|
||||
`dt_last_probed` = NOW()
|
||||
WHERE `id` = %d LIMIT 1",
|
||||
$health,
|
||||
$entry['id']
|
||||
);
|
||||
|
||||
//Get the site information from the DB, based on the ID.
|
||||
$result = q(
|
||||
"SELECT * FROM `site-health` WHERE `id`= %u ORDER BY `id` ASC LIMIT 1",
|
||||
$entry['id']
|
||||
);
|
||||
|
||||
//Return updated entry data.
|
||||
if ($result && isset($result[0])) {
|
||||
$entry_out = $result[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Determines the new health score after a probe has been executed.
|
||||
if (!function_exists('health_score_after_probe')) {
|
||||
function health_score_after_probe($current, $probe_success, $time = null, $version = null, $ssl_issues = null)
|
||||
{
|
||||
//Probe failed, costs you 30 points.
|
||||
if (!$probe_success) {
|
||||
return max($current - 30, -100);
|
||||
}
|
||||
|
||||
//A good probe gives you 20 points.
|
||||
$current += 20;
|
||||
|
||||
//Speed scoring.
|
||||
if (intval($time) > 0) {
|
||||
//Pentaly / bonus points.
|
||||
if ($time > 800) {
|
||||
$current -= 10; //Bad speed.
|
||||
} elseif ($time > 400) {
|
||||
$current -= 5; //Still not good.
|
||||
} elseif ($time > 250) {
|
||||
$current += 0; //This is normal.
|
||||
} elseif ($time > 120) {
|
||||
$current += 5; //Good speed.
|
||||
} else {
|
||||
$current += 10; //Excellent speed.
|
||||
}
|
||||
|
||||
//Cap for bad speeds.
|
||||
if ($time > 800) {
|
||||
$current = min(40, $current);
|
||||
} elseif ($time > 400) {
|
||||
$current = min(60, $current);
|
||||
}
|
||||
}
|
||||
|
||||
//Version check.
|
||||
if (!empty($version)) {
|
||||
$versionParts = explode('.', $version);
|
||||
|
||||
//Older than 3.x.x?
|
||||
//Your score can not go above 30 health.
|
||||
if (intval($versionParts[0]) < 3) {
|
||||
$current = min($current, 30);
|
||||
}
|
||||
|
||||
//Older than 3.3.x?
|
||||
elseif (!empty($versionParts[1]) && intval($versionParts[1] < 3)) {
|
||||
$current -= 5; //Somewhat outdated.
|
||||
}
|
||||
|
||||
#TODO: See if this needs to be more dynamic.
|
||||
#TODO: See if this is a proper indicator of health.
|
||||
}
|
||||
|
||||
//SSL problems? That's a big deal.
|
||||
if ($ssl_issues === true) {
|
||||
$current -= 10;
|
||||
}
|
||||
|
||||
//Don't go beyond +100 or -100.
|
||||
return max(min(100, $current), -100);
|
||||
}
|
||||
}
|
||||
|
||||
//Changes a score into a name. Used for classes and such.
|
||||
if (!function_exists('health_score_to_name')) {
|
||||
function health_score_to_name($score)
|
||||
{
|
||||
if ($score < -50) {
|
||||
return 'very-bad';
|
||||
} elseif ($score < 0) {
|
||||
return 'bad';
|
||||
} elseif ($score < 30) {
|
||||
return 'neutral';
|
||||
} elseif ($score < 50) {
|
||||
return 'ok';
|
||||
} elseif ($score < 80) {
|
||||
return 'good';
|
||||
} else {
|
||||
return 'perfect';
|
||||
}
|
||||
}
|
||||
}
|
81
include/smoothing.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
(function(){
|
||||
|
||||
window.Smoothing = {
|
||||
|
||||
/**
|
||||
* Applies both a moving average bracket and and exponential smoothing.
|
||||
* @param {array} raw The raw Y values.
|
||||
* @param {float} factor The exponential smoothing factor to apply (between o and 1).
|
||||
* @param {int} bracket The amount of datapoints to add to the backet on each side! (2 = 5 data points)
|
||||
* @return {array} The smoothed Y values.
|
||||
*/
|
||||
exponentialMovingAverage: function(raw, factor, bracket){
|
||||
|
||||
var output = [];
|
||||
var smoother = new ExponentialSmoother(factor);
|
||||
|
||||
//Transform each data point with the smoother.
|
||||
for (var i = 0; i < raw.length; i++){
|
||||
|
||||
var input = raw[i];
|
||||
|
||||
//See if we should bracket.
|
||||
if(bracket > 0){
|
||||
|
||||
//Cap our start and end so it doesn't go out of bounds.
|
||||
var start = Math.max(i-bracket, 0);
|
||||
var end = Math.min(i+bracket, raw.length);
|
||||
|
||||
//Push the range to our input.
|
||||
input = [];
|
||||
for(var j = start; j < end; j++){
|
||||
input.push(raw[j]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
output.push(
|
||||
smoother.transform(input)
|
||||
);
|
||||
};
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Exponential Smoother class.
|
||||
var ExponentialSmoother = function(factor){
|
||||
this.currentValue = null;
|
||||
this.smoothingFactor = factor || 1;
|
||||
};
|
||||
|
||||
ExponentialSmoother.prototype.transform = function(input){
|
||||
|
||||
// In case our input is a bracket, first average it.
|
||||
if(input.length){
|
||||
var len = input.length;
|
||||
var sum = 0;
|
||||
for (var i = input.length - 1; i >= 0; i--)
|
||||
sum += input[i]
|
||||
input = sum/len;
|
||||
}
|
||||
|
||||
// Start with our initial value.
|
||||
if(this.currentValue === null){
|
||||
this.currentValue = input;
|
||||
}
|
||||
|
||||
// Our output is basically an updated value.
|
||||
return this.currentValue =
|
||||
|
||||
// Weigh our current value with the smoothing factor.
|
||||
(this.currentValue * this.smoothingFactor) +
|
||||
|
||||
// Add the input to it with the inverse value of the smoothing factor.
|
||||
( (1-this.smoothingFactor) * input );
|
||||
|
||||
};
|
||||
|
||||
})();
|
261
include/submit.php
Normal file
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
require_once 'datetime.php';
|
||||
require_once 'site-health.php';
|
||||
require_once 'Scrape.php';
|
||||
require_once 'Photo.php';
|
||||
|
||||
function run_submit($url)
|
||||
{
|
||||
global $a;
|
||||
|
||||
if (!strlen($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logger('Updating: ' . $url);
|
||||
|
||||
//First run a notice script for the site it is hosted on.
|
||||
$site_health = notice_site($url, true);
|
||||
|
||||
$submit_start = microtime(true);
|
||||
|
||||
$nurl = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
|
||||
|
||||
$profile_exists = false;
|
||||
|
||||
$r = q("SELECT * FROM `profile` WHERE ( `homepage` = '%s' OR `nurl` = '%s' )",
|
||||
dbesc($url),
|
||||
dbesc($nurl)
|
||||
);
|
||||
|
||||
$profile_id = null;
|
||||
|
||||
if (count($r)) {
|
||||
$profile_exists = true;
|
||||
$profile_id = $r[0]['id'];
|
||||
|
||||
$r = q("UPDATE `profile` SET
|
||||
`available` = 0,
|
||||
`updated` = '%s'
|
||||
WHERE `id` = %d LIMIT 1", dbesc(datetime_convert()), intval($profile_id)
|
||||
);
|
||||
$r = q("DELETE FROM `tag` WHERE `profile_id` = %d",
|
||||
intval($profile_id)
|
||||
);
|
||||
}
|
||||
|
||||
//Remove duplicates.
|
||||
if (is_array($r) && count($r) > 1) {
|
||||
for ($i = 1; $i < count($r); $i++) {
|
||||
logger('Removed duplicate profile ' . intval($r[$i]['id']));
|
||||
q("DELETE FROM `photo` WHERE `profile-id` = %d LIMIT 1",
|
||||
intval($r[$i]['id'])
|
||||
);
|
||||
q("DELETE FROM `profile` WHERE `id` = %d LIMIT 1",
|
||||
intval($r[$i]['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Skip the scrape? :D
|
||||
$noscrape = $site_health && $site_health['no_scrape_url'];
|
||||
|
||||
if ($noscrape) {
|
||||
//Find out who to look up.
|
||||
$which = str_replace($site_health['base_url'], '', $url);
|
||||
$noscrape = preg_match('~/profile/([^/]+)~', $which, $matches) === 1;
|
||||
|
||||
//If that did not fail...
|
||||
if ($noscrape) {
|
||||
$params = noscrape_dfrn($site_health['no_scrape_url'] . '/' . $matches[1]);
|
||||
$noscrape = !!$params; //If the result was false, do a scrape after all.
|
||||
}
|
||||
}
|
||||
|
||||
if (!$noscrape) {
|
||||
$params = scrape_dfrn($url);
|
||||
}
|
||||
|
||||
// Empty result is due to an offline site.
|
||||
if (!count($params) > 1) {
|
||||
//For large sites this could lower the health too quickly, so don't track health.
|
||||
//But for sites that are already in bad status. Do a cleanup now.
|
||||
if ($profile_exists && $site_health['health_score'] < $a->config['maintenance']['remove_profile_health_threshold']) {
|
||||
logger('Nuked bad health record.');
|
||||
nuke_record($url);
|
||||
}
|
||||
|
||||
return false;
|
||||
} elseif (x($params, 'explicit-hide') && $profile_exists) {
|
||||
// We don't care about valid dfrn if the user indicates to be hidden.
|
||||
logger('User opted out of the directory.');
|
||||
nuke_record($url);
|
||||
return true; //This is a good update.
|
||||
}
|
||||
|
||||
if ((x($params, 'hide')) || (!(x($params, 'fn')) && (x($params, 'photo')))) {
|
||||
if ($profile_exists) {
|
||||
logger('Profile inferred to be opted out of the directory.');
|
||||
nuke_record($url);
|
||||
}
|
||||
return true; //This is a good update.
|
||||
}
|
||||
|
||||
// This is most likely a problem with the site configuration. Ignore.
|
||||
if (validate_dfrn($params)) {
|
||||
logger('Site is unavailable');
|
||||
return false;
|
||||
}
|
||||
|
||||
$photo = $params['photo'];
|
||||
|
||||
dbesc_array($params);
|
||||
|
||||
if (x($params, 'comm')) {
|
||||
$params['comm'] = intval($params['comm']);
|
||||
}
|
||||
|
||||
if ($profile_exists) {
|
||||
$r = q("UPDATE `profile` SET
|
||||
`name` = '%s',
|
||||
`pdesc` = '%s',
|
||||
`locality` = '%s',
|
||||
`region` = '%s',
|
||||
`postal-code` = '%s',
|
||||
`country-name` = '%s',
|
||||
`homepage` = '%s',
|
||||
`nurl` = '%s',
|
||||
`comm` = %d,
|
||||
`tags` = '%s',
|
||||
`available` = 1,
|
||||
`updated` = '%s'
|
||||
WHERE `id` = %d LIMIT 1",
|
||||
$params['fn'],
|
||||
isset($params['pdesc']) ? $params['pdesc'] : '',
|
||||
isset($params['locality']) ? $params['locality'] : '',
|
||||
isset($params['region']) ? $params['region'] : '',
|
||||
isset($params['postal-code']) ? $params['postal-code'] : '',
|
||||
isset($params['country-name']) ? $params['country-name'] : '',
|
||||
dbesc($url),
|
||||
dbesc($nurl),
|
||||
intval($params['comm']),
|
||||
$params['tags'],
|
||||
dbesc(datetime_convert()),
|
||||
intval($profile_id)
|
||||
);
|
||||
logger('Update returns: ' . $r);
|
||||
} else {
|
||||
$r = q("INSERT INTO `profile` ( `name`, `pdesc`, `locality`, `region`, `postal-code`, `country-name`, `homepage`, `nurl`, `comm`, `tags`, `created`, `updated` )
|
||||
VALUES ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s' )",
|
||||
$params['fn'],
|
||||
x($params, 'pdesc') ? $params['pdesc'] : '',
|
||||
x($params, 'locality') ? $params['locality'] : '',
|
||||
x($params, 'region') ? $params['region'] : '',
|
||||
x($params, 'postal-code') ? $params['postal-code'] : '',
|
||||
x($params, 'country-name') ? $params['country-name'] : '',
|
||||
dbesc($url),
|
||||
dbesc($nurl),
|
||||
intval($params['comm']),
|
||||
x($params, 'tags') ? $params['tags'] : '',
|
||||
dbesc(datetime_convert()),
|
||||
dbesc(datetime_convert())
|
||||
);
|
||||
logger('Insert returns: ' . $r);
|
||||
|
||||
$r = q("SELECT `id` FROM `profile` WHERE ( `homepage` = '%s' or `nurl` = '%s' ) ORDER BY `id` ASC",
|
||||
dbesc($url),
|
||||
dbesc($nurl)
|
||||
);
|
||||
|
||||
if (count($r)) {
|
||||
$profile_id = $r[count($r) - 1]['id'];
|
||||
}
|
||||
|
||||
if (count($r) > 1) {
|
||||
q("DELETE FROM `photo` WHERE `profile-id` = %d LIMIT 1",
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
q("DELETE FROM `profile` WHERE `id` = %d LIMIT 1",
|
||||
intval($r[0]['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($params['tags']) {
|
||||
$arr = explode(' ', $params['tags']);
|
||||
foreach ($arr as $t) {
|
||||
$t = strip_tags(trim($t));
|
||||
$t = substr($t, 0, 254);
|
||||
|
||||
if (strlen($t)) {
|
||||
$r = q("INSERT INTO `tag` (`term`, `profile_id`) VALUES ('%s', %d) ",
|
||||
dbesc($t),
|
||||
intval($profile_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$submit_photo_start = microtime(true);
|
||||
|
||||
$photo_failure = false;
|
||||
|
||||
$status = false;
|
||||
|
||||
if ($profile_id) {
|
||||
$img_str = fetch_url($photo, true);
|
||||
$img = new Photo($img_str);
|
||||
if ($img) {
|
||||
$img->scaleImageSquare(80);
|
||||
$r = $img->store($profile_id);
|
||||
}
|
||||
$r = q("UPDATE `profile` SET `photo` = '%s' WHERE `id` = %d LIMIT 1", dbesc($a->get_baseurl() . '/photo/' . $profile_id . '.jpg'),
|
||||
intval($profile_id)
|
||||
);
|
||||
$status = true;
|
||||
} else {
|
||||
nuke_record($url);
|
||||
return false;
|
||||
}
|
||||
|
||||
$submit_end = microtime(true);
|
||||
$photo_time = round(($submit_end - $submit_photo_start) * 1000);
|
||||
$time = round(($submit_end - $submit_start) * 1000);
|
||||
|
||||
//Record the scrape speed in a scrapes table.
|
||||
if ($site_health && $status) {
|
||||
q(
|
||||
"INSERT INTO `site-scrape` (`site_health_id`, `dt_performed`, `request_time`, `scrape_time`, `photo_time`, `total_time`)" .
|
||||
"VALUES (%u, NOW(), %u, %u, %u, %u)",
|
||||
$site_health['id'],
|
||||
$params['_timings']['fetch'],
|
||||
$params['_timings']['scrape'],
|
||||
$photo_time,
|
||||
$time
|
||||
);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
function nuke_record($url)
|
||||
{
|
||||
$nurl = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
|
||||
|
||||
$r = q("SELECT `id` FROM `profile` WHERE ( `homepage` = '%s' OR `nurl` = '%s' ) ",
|
||||
dbesc($url),
|
||||
dbesc($nurl)
|
||||
);
|
||||
|
||||
if (count($r)) {
|
||||
foreach ($r as $rr) {
|
||||
q("DELETE FROM `photo` WHERE `profile-id` = %d LIMIT 1",
|
||||
intval($rr['id'])
|
||||
);
|
||||
q("DELETE FROM `profile` WHERE `id` = %d LIMIT 1",
|
||||
intval($rr['id'])
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
454
include/sync.php
Normal file
|
@ -0,0 +1,454 @@
|
|||
<?php
|
||||
|
||||
use Friendica\Directory\App;
|
||||
|
||||
/**
|
||||
* Pull this URL to our pulling queue.
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
function sync_pull($url)
|
||||
{
|
||||
global $a;
|
||||
|
||||
//If we support it that is.
|
||||
if ($a->config['syncing']['enable_pulling']) {
|
||||
q("REPLACE INTO `sync-pull-queue` (`url`) VALUES ('%s')", dbesc($url));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push this URL to our pushing queue as well as mark it as modified using sync_mark.
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
function sync_push($url)
|
||||
{
|
||||
global $a;
|
||||
|
||||
//If we support it that is.
|
||||
if ($a->config['syncing']['enable_pushing']) {
|
||||
q("REPLACE INTO `sync-push-queue` (`url`) VALUES ('%s')", dbesc($url));
|
||||
}
|
||||
|
||||
sync_mark($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a URL as modified in some way or form.
|
||||
* This will cause anyone that pulls our changes to see this profile listed.
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
function sync_mark($url)
|
||||
{
|
||||
global $a;
|
||||
|
||||
//If we support it that is.
|
||||
if (!$a->config['syncing']['enable_pulling']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = count(q("SELECT * FROM `sync-timestamps` WHERE `url`='%s'", dbesc($url)));
|
||||
|
||||
if (is_array($exists) && count($exists)) {
|
||||
q("UPDATE `sync-timestamps` SET `modified`=NOW() WHERE `url`='%s'", dbesc($url));
|
||||
} else {
|
||||
q("INSERT INTO `sync-timestamps` (`url`, `modified`) VALUES ('%s', NOW())", dbesc($url));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a single fork during the push jobs.
|
||||
* Takes a lower priority and pushes a batch of items.
|
||||
* @param string $target A sync-target database row.
|
||||
* @param array $batch The batch of items to submit.
|
||||
* @return void
|
||||
*/
|
||||
function push_worker($target, $batch)
|
||||
{
|
||||
//Lets be nice, we're only doing a background job here...
|
||||
pcntl_setpriority(5);
|
||||
|
||||
//Find our target's submit URL.
|
||||
$submit = $target['base_url'] . '/submit';
|
||||
|
||||
foreach ($batch as $item) {
|
||||
set_time_limit(30); //This should work for 1 submit.
|
||||
msg("Submitting {$item['url']} to $submit");
|
||||
fetch_url($submit . '?url=' . bin2hex($item['url']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of push targets.
|
||||
* @return array Push targets.
|
||||
*/
|
||||
function get_push_targets()
|
||||
{
|
||||
$res = q("SELECT * FROM `sync-targets` WHERE `push`=b'1'");
|
||||
|
||||
return is_array($res) ? $res : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a batch of URL's to push.
|
||||
* @param object $a The App instance.
|
||||
* @return array Batch of URL's.
|
||||
*/
|
||||
function get_push_batch(App $a)
|
||||
{
|
||||
$res = q("SELECT * FROM `sync-push-queue` ORDER BY `id` LIMIT %u", intval($a->config['syncing']['max_push_items']));
|
||||
|
||||
return is_array($res) ? $res : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the push targets as well as a batch of URL's for a pushing job.
|
||||
* @param object $a The App instance.
|
||||
* @return list($targets, $batch) A list of both the targets array and batch array.
|
||||
*/
|
||||
function get_pushing_job(App $a)
|
||||
{
|
||||
//When pushing is requested...
|
||||
if (!!$a->config['syncing']['enable_pushing']) {
|
||||
|
||||
//Find our targets.
|
||||
$targets = get_push_targets();
|
||||
|
||||
//No targets?
|
||||
if (!count($targets)) {
|
||||
msg('Pushing enabled, but no push targets.');
|
||||
$batch = array();
|
||||
} else {
|
||||
//If we have targets, get our batch.
|
||||
$batch = get_push_batch($a);
|
||||
if (!count($batch)) {
|
||||
msg('Empty pushing queue.'); //No batch, means no work.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//No pushing if it's disabled.
|
||||
$targets = array();
|
||||
$batch = array();
|
||||
}
|
||||
|
||||
return array($targets, $batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a pushing job, creating a thread for each target.
|
||||
* @param array $targets Pushing targets.
|
||||
* @param array $batch Batch of URL's to push.
|
||||
* @param string $db_host DB host to connect to.
|
||||
* @param string $db_user DB user to connect with.
|
||||
* @param string $db_pass DB pass to connect with.
|
||||
* @param mixed $db_data Nobody knows.
|
||||
* @param mixed $install Maybe a boolean.
|
||||
* @return void
|
||||
*/
|
||||
function run_pushing_job($targets, $batch, $db_host, $db_user, $db_pass, $db_data, $install)
|
||||
{
|
||||
//Create a thread for each target we want to serve push messages to.
|
||||
//Not good creating more, because it would stress their server too much.
|
||||
$threadc = count($targets);
|
||||
$threads = array();
|
||||
|
||||
//Do we only have 1 target? No need for threads.
|
||||
if ($threadc === 1) {
|
||||
msg('No threads needed. Only one pushing target.');
|
||||
push_worker($targets[0], $batch);
|
||||
} elseif ($threadc > 1) {
|
||||
//When we need threads.
|
||||
|
||||
//POSIX threads only.
|
||||
if (!function_exists('pcntl_fork')) {
|
||||
msg('Error: no pcntl_fork support. Are you running a different OS? Report an issue please.', true);
|
||||
}
|
||||
|
||||
//Debug...
|
||||
$items = count($batch);
|
||||
msg("Creating $threadc push threads for $items items.");
|
||||
|
||||
//Loop while we need more threads.
|
||||
for ($i = 0; $i < $threadc; $i++) {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid === -1)
|
||||
msg('Error: something went wrong with the fork. ' . pcntl_strerror(), true);
|
||||
|
||||
//You're a child, go do some labor!
|
||||
if ($pid === 0) {
|
||||
push_worker($targets[$i], $batch);
|
||||
exit;
|
||||
}
|
||||
|
||||
//Store the list of PID's.
|
||||
if ($pid > 0) {
|
||||
$threads[] = $pid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Wait for all child processes.
|
||||
$theading_problems = false;
|
||||
|
||||
foreach ($threads as $pid) {
|
||||
pcntl_waitpid($pid, $status);
|
||||
|
||||
if ($status !== 0) {
|
||||
$theading_problems = true;
|
||||
msg("Bad process return value $pid:$status");
|
||||
}
|
||||
}
|
||||
|
||||
//If we did not have any "threading" problems.
|
||||
if (!$theading_problems) {
|
||||
//Reconnect
|
||||
global $db;
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
//Create a query for deleting this queue.
|
||||
$where = array();
|
||||
foreach ($batch as $item) {
|
||||
$where[] = dbesc($item['url']);
|
||||
}
|
||||
$where = "WHERE `url` IN ('" . implode("', '", $where) . "')";
|
||||
|
||||
//Remove the items from queue.
|
||||
q("DELETE FROM `sync-push-queue` $where LIMIT %u", count($batch));
|
||||
msg('Removed items from push queue.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a batch of URL's to push.
|
||||
* @param object $a The App instance.
|
||||
* @return array Batch of URL's.
|
||||
*/
|
||||
function get_queued_pull_batch(App $a)
|
||||
{
|
||||
//Randomize this, to prevent scraping the same servers too much or dead URL's.
|
||||
$res = q("SELECT * FROM `sync-pull-queue` ORDER BY RAND() LIMIT %u", intval($a->config['syncing']['max_pull_items']));
|
||||
$batch = is_array($res) ? $res : [];
|
||||
|
||||
msg(sprintf('Pulling %u items from queue.', count($batch)));
|
||||
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of pull targets.
|
||||
* @return array Pull targets.
|
||||
*/
|
||||
function get_pull_targets()
|
||||
{
|
||||
$res = q("SELECT * FROM `sync-targets` WHERE `pull`=b'1'");
|
||||
|
||||
return is_array($res) ? $res : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a batch of URL's to push.
|
||||
* @param object $a The App instance.
|
||||
* @return array Batch of URL's.
|
||||
*/
|
||||
function get_remote_pull_batch(App $a)
|
||||
{
|
||||
//Find our targets.
|
||||
$targets = get_pull_targets();
|
||||
|
||||
msg(sprintf('Pulling from %u remote targets.', count($targets)));
|
||||
|
||||
//No targets, means no batch.
|
||||
if (!count($targets)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
//Pull a list of URL's from each target.
|
||||
$urls = array();
|
||||
|
||||
foreach ($targets as $i => $target) {
|
||||
//First pull, or an update?
|
||||
if (!$target['dt_last_pull']) {
|
||||
$url = $target['base_url'] . '/sync/pull/all';
|
||||
} else {
|
||||
$url = $target['base_url'] . '/sync/pull/since/' . intval($target['dt_last_pull']);
|
||||
}
|
||||
|
||||
//Go for it :D
|
||||
$targets[$i]['pull_data'] = json_decode(fetch_url($url), true);
|
||||
|
||||
//If we didn't get any JSON.
|
||||
if (!$targets[$i]['pull_data']) {
|
||||
msg(sprintf('Failed to pull from "%s".', $url));
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add all entries as keys, to remove duplicates.
|
||||
foreach ($targets[$i]['pull_data']['results'] as $url) {
|
||||
$urls[$url] = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Now that we have our URL's. Store them in the queue.
|
||||
foreach ($urls as $url => $bool) {
|
||||
if ($url) {
|
||||
sync_pull($url);
|
||||
}
|
||||
}
|
||||
|
||||
//Since this all worked out, mark each source with the timestamp of pulling.
|
||||
foreach ($targets as $target) {
|
||||
if ($target['pull_data'] && $target['pull_data']['now']) {
|
||||
msg('New pull timestamp ' . $target['pull_data']['now'] . ' for ' . $target['base_url']);
|
||||
q("UPDATE `sync-targets` SET `dt_last_pull`=%u WHERE `base_url`='%s'", $target['pull_data']['now'], dbesc($target['base_url']));
|
||||
}
|
||||
}
|
||||
|
||||
//Finally, return a batch of this.
|
||||
return get_queued_pull_batch($a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers an array of URL's to scrape from the pulling targets.
|
||||
* @param object $a The App instance.
|
||||
* @return array URL's to scrape.
|
||||
*/
|
||||
function get_pulling_job(App $a)
|
||||
{
|
||||
//No pulling today...
|
||||
if (!$a->config['syncing']['enable_pulling']) {
|
||||
return array();
|
||||
}
|
||||
|
||||
//Firstly, finish the items from our queue.
|
||||
$batch = get_queued_pull_batch($a);
|
||||
if (count($batch)) {
|
||||
return $batch;
|
||||
}
|
||||
|
||||
//If that is empty, fill the queue with remote items and return a batch of that.
|
||||
$batch = get_remote_pull_batch($a);
|
||||
if (count($batch)) {
|
||||
return $batch;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* For a single fork during the pull jobs.
|
||||
* Takes a lower priority and pulls a batch of items.
|
||||
* @param int $i The index number of this worker (for round-robin).
|
||||
* @param int $threadc The amount of workers (for round-robin).
|
||||
* @param array $pull_batch A batch of URL's to pull.
|
||||
* @param string $db_host DB host to connect to.
|
||||
* @param string $db_user DB user to connect with.
|
||||
* @param string $db_pass DB pass to connect with.
|
||||
* @param mixed $db_data Nobody knows.
|
||||
* @param mixed $install Maybe a boolean.
|
||||
* @return void
|
||||
*/
|
||||
function pull_worker($i, $threadc, $pull_batch, $db_host, $db_user, $db_pass, $db_data, $install)
|
||||
{
|
||||
//Lets be nice, we're only doing maintenance here...
|
||||
pcntl_setpriority(5);
|
||||
|
||||
//Get personal DBA's.
|
||||
global $db;
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
//Get our (round-robin) workload from the batch.
|
||||
$workload = array();
|
||||
while (isset($pull_batch[$i])) {
|
||||
$entry = $pull_batch[$i];
|
||||
$workload[] = $entry;
|
||||
$i += $threadc;
|
||||
}
|
||||
|
||||
//While we've got work to do.
|
||||
while (count($workload)) {
|
||||
$entry = array_pop($workload);
|
||||
set_time_limit(20); //This should work for 1 submit.
|
||||
msg("Submitting " . $entry['url']);
|
||||
run_submit($entry['url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a pulling job, creating several threads to do so.
|
||||
* @param object $a The App instance.
|
||||
* @param array $pull_batch A batch of URL's to pull.
|
||||
* @param string $db_host DB host to connect to.
|
||||
* @param string $db_user DB user to connect with.
|
||||
* @param string $db_pass DB pass to connect with.
|
||||
* @param mixed $db_data Nobody knows.
|
||||
* @param mixed $install Maybe a boolean.
|
||||
* @return void
|
||||
*/
|
||||
function run_pulling_job(App $a, array $pull_batch, $db_host, $db_user, $db_pass, $db_data, $install)
|
||||
{
|
||||
//We need the scraper.
|
||||
require_once 'include/submit.php';
|
||||
|
||||
//POSIX threads only.
|
||||
if (!function_exists('pcntl_fork')) {
|
||||
msg('Error: no pcntl_fork support. Are you running a different OS? Report an issue please.', true);
|
||||
}
|
||||
|
||||
//Create the threads we need.
|
||||
$items = count($pull_batch);
|
||||
$threadc = min($a->config['syncing']['pulling_threads'], $items); //Don't need more threads than items.
|
||||
$threads = array();
|
||||
|
||||
msg("Creating $threadc pulling threads for $items profiles.");
|
||||
|
||||
//Build the threads.
|
||||
for ($i = 0; $i < $threadc; $i++) {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid === -1) {
|
||||
msg('Error: something went wrong with the fork. ' . pcntl_strerror(), true);
|
||||
}
|
||||
|
||||
//You're a child, go do some labor!
|
||||
if ($pid === 0) {
|
||||
pull_worker($i, $threadc, $pull_batch, $db_host, $db_user, $db_pass, $db_data, $install);
|
||||
exit;
|
||||
}
|
||||
|
||||
//Store the list of PID's.
|
||||
if ($pid > 0) {
|
||||
$threads[] = $pid;
|
||||
}
|
||||
}
|
||||
|
||||
//Wait for all child processes.
|
||||
$theading_problems = false;
|
||||
foreach ($threads as $pid) {
|
||||
pcntl_waitpid($pid, $status);
|
||||
if ($status !== 0) {
|
||||
$theading_problems = true;
|
||||
msg("Bad process return value $pid:$status");
|
||||
}
|
||||
}
|
||||
|
||||
//If we did not have any "threading" problems.
|
||||
if (!$theading_problems) {
|
||||
//Reconnect
|
||||
global $db;
|
||||
|
||||
$db = new dba($db_host, $db_user, $db_pass, $db_data, $install);
|
||||
|
||||
//Create a query for deleting this queue.
|
||||
$where = array();
|
||||
foreach ($pull_batch as $item) {
|
||||
$where[] = dbesc($item['url']);
|
||||
}
|
||||
$where = "WHERE `url` IN ('" . implode("', '", $where) . "')";
|
||||
|
||||
//Remove the items from queue.
|
||||
q("DELETE FROM `sync-pull-queue` $where LIMIT %u", count($pull_batch));
|
||||
msg('Removed items from pull queue.');
|
||||
}
|
||||
}
|
6
include/system_unavailable.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<head><title>System Unavailable</title></head>
|
||||
<body>
|
||||
Apologies but this site is unavailable at the moment. Please try again later.
|
||||
</body>
|
||||
</html>
|