Compare commits

...

183 commits
3.01 ... master

Author SHA1 Message Date
Hypolite Petovan abda067ca1
Merge pull request #61 from Quix0r/cleanups/use-app-class
Cleanups of "use" and "new" statements
2019-01-03 19:42:36 -05:00
Roland Häder e13182d913
Some cleanups:
- "use" should be executed before any other line (except strict mode line)
- "import" App class before use it
- sure braces with "new" operator as this invokes the constructor
2019-01-03 22:54:57 +01:00
Roland Häder eb8e70d09e
Set CHMOD 777 for Shell script. 2019-01-03 22:50:56 +01:00
Hypolite Petovan 11ee566479 Bump mrpetovan/net_ping to version 1.1.1 2018-11-08 07:18:11 -05:00
Hypolite Petovan fa4882b09c Remove obsolete repositories key from composer.json 2018-11-08 07:11:43 -05:00
Hypolite Petovan 23e01bb4cb [Composer] Replace pear/Net_Ping with mrpetovan/net_ping 2018-11-07 21:47:48 -05:00
Michael Vogel 401a92a872
Merge pull request #57 from MrPetovan/master
[ImgBot] optimizes images
2018-07-15 13:32:00 +02:00
Hypolite Petovan c397e93698
Merge pull request #9 from MrPetovan/imgbot
[ImgBot] optimizes images
2018-07-14 02:31:08 -04:00
ImgBotApp 4075a42e54
[ImgBot] optimizes images
*Total -- 53.17kb -> 50.92kb (4.25%)

/images/like.gif -- 1.16kb -> 0.13kb (88.85%)
/images/default-profile-sm.jpg -- 0.34kb -> 0.32kb (6.07%)
/images/dfrn.gif -- 0.11kb -> 0.10kb (4.59%)
/view/theme/default/dfrn.gif -- 0.11kb -> 0.10kb (4.59%)
/images/default-profile.jpg -- 0.48kb -> 0.46kb (4.29%)
/images/star.jpg -- 0.59kb -> 0.58kb (3.13%)
/view/theme/default/star.jpg -- 0.59kb -> 0.58kb (3.13%)
/images/friendika-256.jpg -- 16.73kb -> 16.25kb (2.87%)
/images/friendica-256.jpg -- 16.73kb -> 16.25kb (2.87%)
/images/friendika-128.jpg -- 8.17kb -> 8.08kb (1.11%)
/images/friendica-128.jpg -- 8.17kb -> 8.08kb (1.11%)
2018-07-13 14:09:08 +00:00
Tobias Diekershoff b26995a78b
Merge pull request #56 from MrPetovan/master
Fix PHP 7 Warning about count()
2018-07-11 07:42:07 +02:00
Hypolite Petovan 95be3462ac Fix PHP Warning about count()
- Reformat include/Photo.php
2018-07-10 23:56:30 -04:00
Hypolite Petovan b384f838da
Merge pull request #51 from MrPetovan/master
Various improvements
2018-07-10 23:34:03 -04:00
Hypolite Petovan f110a98464
Merge pull request #54 from AndyHee/patch-2
Update health_search.tpl
2018-05-10 08:14:49 -04:00
Andy H 7bef7010b8
Update health_search.tpl
Improved wording.

This is search result page. Relates to this https://github.com/friendica/dir/pull/52
2018-05-10 15:59:46 +07:00
Hypolite Petovan 82e529df2a
Merge pull request #53 from AndyHee/fix-patch-1
Update health_summary.tpl
2018-05-09 22:50:09 -04:00
Andy H 8adf1b8421
Update health_summary.tpl
Fixed non-standard example URL
2018-05-10 09:08:37 +07:00
Hypolite Petovan bf69f44036
Merge pull request #52 from AndyHee/patch-1
Improved wording and layout of health_summary.tpl
2018-05-09 09:23:31 -04:00
Andy H 0bf3aa5cc1
Update health_summary.tpl 2018-05-09 20:19:18 +07:00
Andy H c7b336570e
Update health_summary.tpl
Removed deprecated URL to personal profile
Improved wording and layout
2018-05-09 20:17:50 +07:00
Hypolite Petovan 945c1d1c00 Remove placeholder header 2018-05-08 03:50:43 -04:00
Hypolite Petovan 15563be5cc Fix PHP Notices 2018-05-07 22:02:16 -04:00
Hypolite Petovan 9031423278 Prevent infinite push loop between multiple directories
- Add auto increment id to sync-push-queue table to add new profile urls
at the end
- Use REPLACE instead of INSERT to prevent unique key errors
2018-05-07 22:02:16 -04:00
Hypolite Petovan 4de3fcf5f5 Simplify the tag table
- Remove auto increment id
- Replaced nurl by profile id
- Added primary key on the two remaining columns
2018-05-07 22:02:16 -04:00
Hypolite Petovan 649cbbf466
Merge branch 'master' into master 2018-05-07 20:58:40 -04:00
Hypolite Petovan 95ea839fa7 Add ping stats during probe 2018-05-04 08:22:01 -04:00
Hypolite Petovan 27153fab68 Updat database structure file
- Add a couple of fields in site-probe
2018-05-04 08:21:47 -04:00
Hypolite Petovan ac41f7caf3 Add console commands
- Add Config command
- Add Probe command
- Add PoToPhp command
- Remove po2php script
2018-05-04 08:16:36 -04:00
Hypolite Petovan 4da0d68bad Add console CLI command
- Fix App to be used in CLI mode
2018-05-04 08:16:03 -04:00
Hypolite Petovan 741e72ecc7 [Composer] Add dependencies
- Add pear/Net_Ping
- Add asika/simple-console
- Fix composer.json formatting
2018-05-04 08:15:21 -04:00
Hypolite Petovan 5039d4202b Fix Google+ support addon list 2018-05-04 08:12:08 -04:00
Hypolite Petovan ca12f85c92 Fix db error display 2018-05-04 08:11:49 -04:00
Hypolite Petovan 61468d93d5
Merge pull request #46 from Quix0r/fixes/htaccess-dist
Let's have .htaccess not being committed
2018-05-01 18:06:38 -04:00
Roland Häder b9ba616b7b
Let's have .htaccess not being committed
Signed-off-by: Roland Häder <roland@mxchange.org>
2018-05-01 23:40:57 +02:00
Hypolite Petovan ec0e3e431a
Merge pull request #41 from MrPetovan/task/rename-plugin-to-addon
Rename plugin to addon
2018-04-04 21:28:29 -04:00
Hypolite Petovan 54f9bd695a
Merge pull request #42 from MrPetovan/task/upgrade-to-utf8mb4
Upgrade database to utf8mb4
2018-04-04 21:28:02 -04:00
Hypolite Petovan 64d70c4fc3 Merge branch 'task/upgrade-to-utf8mb4' 2018-04-03 20:49:59 -04:00
Hypolite Petovan 1a608a50ad Actually use variable $addon 2018-02-24 12:21:07 -05:00
Hypolite Petovan 09bc3cb175 Set the database charset to utf8mb4 2018-02-24 10:59:32 -05:00
Hypolite Petovan 37960dcd38 Add backward compatibility test 2018-02-24 10:57:39 -05:00
Hypolite Petovan a6f6f92a24 Rename plugin to addon 2018-02-24 10:45:54 -05:00
Michael Vogel 7701347cd5
Merge pull request #38 from MrPetovan/task/fix-country-widget
Fix country widget + search + tags
2018-01-17 20:23:47 +01:00
Hypolite Petovan e24172ab05 Fix tag insertion/update
- Delete all tags related to a specific profile on submit
- Add tags only if the profile is available
2018-01-14 22:24:12 -05:00
Hypolite Petovan 522cc7bb17 Fix display issues
- Add baseurl to widgets search links
- Add aside to search page
- Fix number of available profiles in country widgets
2018-01-14 22:22:37 -05:00
Tobias Diekershoff bbe2fca6ac
Merge pull request #37 from MrPetovan/master
Updated dfrndir.sql
2017-11-26 17:17:22 +01:00
Hypolite Petovan 6306a618dc Updated dfrndir.sql
- Added profile.available
- Removed engine selection except for profile because of the fulltext
indices
- Removed auto increment values
- Various formatting changes
2017-11-26 10:46:16 -05:00
Hypolite Petovan 83232f4946
Merge pull request #36 from tobiasd/20171126-dirReadme
add a note about display of bit values in MySQL console to the README…
2017-11-26 09:12:31 -05:00
Tobias Diekershoff 0d8fa3e2bd add a note about display of bit values in MySQL console to the README file 2017-11-26 15:10:20 +01:00
Hypolite Petovan 6615a31c08
Merge pull request #35 from tobiasd/20171125-installsteps
some more hints for the installation
2017-11-25 08:36:21 -05:00
Tobias Diekershoff 3494fe491d some more hints for the installation 2017-11-25 13:58:55 +01:00
Tobias Diekershoff 21ca069227
Merge pull request #34 from MrPetovan/bug/fix-submit-inserts
Fix submit inserts
2017-11-01 08:11:03 +01:00
Hypolite Petovan 99d182915d Fix invalid sprintf string in submit process 2017-11-01 02:30:09 -04:00
Hypolite Petovan 733894723e Add debug opportunity in q() 2017-11-01 02:29:47 -04:00
Hypolite Petovan be5939e02f Code style cleanup 2017-11-01 02:29:31 -04:00
Tobias Diekershoff 2db783721e
Merge pull request #33 from MrPetovan/bug/fix-directory-filters
Missing variable change
2017-11-01 07:19:42 +01:00
Hypolite Petovan fedfa7da4f Missing variable change 2017-10-31 21:49:00 -04:00
Michael Vogel b21b493613
Merge pull request #32 from MrPetovan/bug/fix-directory-filters
Fix directory filter links
2017-10-31 06:49:36 +01:00
Hypolite Petovan e91ae5d4af Fix directory filter links
- Enable "People" and "Forums" links in the Directory
2017-10-30 22:05:23 -04:00
Michael Vogel 4cc55f4302 Merge pull request #30 from MrPetovan/task/improve-servers-page
Put the server list on a single fixed-width column
2017-10-23 07:38:12 +02:00
Michael Vogel 16158983c0 Merge pull request #31 from MrPetovan/bug/fix-maintenance-cron
Fix maintenance cron
2017-10-23 07:37:38 +02:00
Hypolite Petovan 163d7d2b4b Add profile availability field
- Add available field to profile table
- Make profile unavailable in directory until update suceeds
2017-10-23 00:34:04 -04:00
Hypolite Petovan ef7551df81 Fix empty $parms check
- $parms always contains the `_timings` key
2017-10-22 23:55:27 -04:00
Hypolite Petovan cdb8670dea Fix whitespaces in include/submit.php 2017-10-22 23:48:17 -04:00
Hypolite Petovan b833a8d255 Fix cron_maintain
- Update profile before fetching scrape url
- Honor $parms['hide'] before validating dfrn site
- Fetch maintenance items oldest first
- Add backlog size in log
- Add pid to logger for easier threaded cron debug
2017-10-22 23:48:00 -04:00
Hypolite Petovan 20a5e3678d Put the server list on a single fixed-width column 2017-10-22 17:04:01 -04:00
Hypolite Petovan 386e70fd1f Merge pull request #28 from Hypolite/issue/#25
wrong download link
2017-08-03 21:43:24 -04:00
Hypolite Petovan 9a7503d4dd Normalize formatting 2017-08-03 21:42:47 -04:00
Hypolite Petovan 109633d2a6 Merge pull request #27 from Hypolite/issue/#24
Fix health page not working
2017-08-03 21:38:20 -04:00
Hypolite Petovan f79df56a56 Change Friendica self-hosting URL 2017-08-01 21:34:09 -04:00
Hypolite Petovan 687eef90f7 Fix upcoming killme() in index.php change
- Add killme() in controllers instead of relying on index.php
- Fix formatting
2017-08-01 21:33:50 -04:00
Hypolite Petovan c6d60df8ac Fix health page not working
- Fix formatting
- Add references to App
- Fix parse error
- Fix a bunch of notices
- Remove inexplicable killme() for *_content pages preventing them from
displaying
2017-08-01 21:06:33 -04:00
Hypolite Petovan 4601c4bd8a Fix "PHP Warning: session_write_close(): Failed to write session data (user)" errors
- Changed returning true when there's a session ID but no session data
in ref_session_write()
- Moved session.php requires around
2017-08-01 21:02:24 -04:00
Hypolite Petovan 9633d3393f Merge branch 'master' of https://github.com/Hypolite/dir 2017-08-01 19:40:15 -04:00
Hypolite Petovan e5416b0023 Fix deprecated constant MYSQL_ASSOC
- Add maintenance HTML file
2017-08-01 19:39:34 -04:00
Michael Vogel a5a186f911 Merge pull request #23 from tobiasd/20170801-vagrant
Vagrant > 1.5 needs an additional parameter
2017-08-01 22:20:49 +02:00
Tobias Diekershoff 8aea4acc54 Vagrant > 1.5 needs an additional parameter 2017-08-01 17:04:03 +02:00
Hypolite Petovan 98406dfcaf Fix deprecated constant MYSQL_ASSOC
- Add maintenance HTML file
2017-06-25 10:17:26 -04:00
Michael Vogel b38d171b08 Merge pull request #22 from Hypolite/master
Re-enable forward zrl to profile pages
2017-06-03 08:01:48 +02:00
Hypolite Petovan fb159c2862 Re-enable forward zrl to profile pages 2017-06-02 22:45:30 -04:00
Michael Vogel 9a4ac58f67 Merge pull request #21 from Hypolite/bug/fix-dba-missing-db
Fix missing $db when session write on exit
2017-05-17 14:33:17 +02:00
Hypolite Petovan 2cabf7f37b Generalize the use of killme()
- Add fail condition to q()
2017-05-12 00:05:06 -04:00
Hypolite Petovan 381e68edc7 Remove closedb() 2017-05-12 00:04:37 -04:00
Hypolite Petovan 5559df82a6 Formatting 2017-05-12 00:04:04 -04:00
Hypolite Petovan 4ea1211ea8 Move q errno check inside db existence check
- Remove if ! function_exists
- Fix formatting
2017-05-11 23:29:56 -04:00
Hypolite Petovan 7eada3d4da Merge pull request #20 from Hypolite/bug/hide-stats-and-help
Hide unfinished Stats and Help pages
2017-05-11 23:07:34 -04:00
Hypolite Petovan e7bfa257b5 Merge pull request #19 from Quix0r/rewrites/privacy-decentralized-fixes
Rewrites/privacy decentralized fixes
2017-05-11 11:09:01 -04:00
Roland Häder d400ed3342
Rewrite:
- don't download files - more worse JavaScript - from external CDNs
- this is a security nightmare when the remote CDN is compromised and starts
  distributing worms/trojan horses (see NSA scandal)
- this will give them away your user's IP addresses
- removed no longer used js/main.js as there is assets/js/main.js around
- moved css/js files in their own folder (assets/<css|js>)
- removed executable right from include/dba.php, no need for this

Signed-off-by: Roland Häder <roland@mxchange.org>
2017-05-11 16:49:02 +02:00
Benjamin Lorteau 9105272d4f Hide unfinished Stats and Help pages 2017-05-11 10:45:37 -04:00
Roland Häder 96ad5a1d39
no executable flag needed for this file
Signed-off-by: Roland Häder <roland@mxchange.org>
2017-05-11 16:39:01 +02:00
Michael Vogel c45a2c1454 Merge pull request #18 from Hypolite/feature/redesign-prototype
Redesign prototype merge
2017-05-06 08:10:18 +02:00
Hypolite Petovan 2360b60e3d Plugins: Remove App.net, add Facebook through Buffer 2017-05-06 01:20:46 -04:00
Hypolite Petovan 901c66186e Fix mobile styles
- Use Flex box instead of text alignment for top bar
2017-05-05 23:34:57 -04:00
Hypolite Petovan a93184b38d Update CSS spacing
- Added space after colons
- Added space befor braces
2017-05-04 21:36:59 -04:00
Hypolite Petovan de87a461c0 Redesign directory page display
- Updated profile card display, added description and tags
- Added column display for profiles
- Tweaked reset button behavior
2017-05-04 21:35:43 -04:00
Hypolite Petovan 9af47634e3 Update search page display 2017-05-04 21:34:21 -04:00
Hypolite Petovan a35dab70b8 Remove Google webfont 2017-05-04 21:26:45 -04:00
Hypolite Petovan f297c670b3 Move App to src 2017-05-01 23:09:26 -04:00
Hypolite Petovan 68c1939d12 Enforcing standards ahead of moving App to src 2017-05-01 22:57:21 -04:00
Hypolite Petovan 0747ce0e6e Add yet more documentation about ft_min_word_len 2017-05-01 21:42:00 -04:00
Hypolite Petovan 0159e73544 Fixes yet another PHP notice 2017-04-20 23:42:21 -04:00
Hypolite Petovan a8dc2e65f4 Merge branch 'master' into feature/redesign-prototype
# Conflicts:
#	.gitignore
#	.htconfig.php
#	.htconfig.php-dist
#	boot.php
#	include/dba.php
#	mod/health.php
#	util/htconfig.vagrant.php
2017-04-20 22:33:41 -04:00
Hypolite Petovan 3095c9cad3 Fix fulltext earch
- Add fulltext index on multiple columns in the profile table
- Remove "IN BOOLEAN MODE"
- Add documentation about ft_min_word_len
2017-04-20 22:24:17 -04:00
Hypolite Petovan 280d55f183 Fix yet more PHP Notices 2017-04-20 22:03:34 -04:00
Hypolite Petovan 783c15c207 Fix PHP notices all around
- Improve SQL query formatting
- Revert spaces to tabs after PHP CS Fixer
2017-04-20 21:41:47 -04:00
Hypolite Petovan 750f081078 Enforce coding standards in include/dba.php 2017-04-20 19:59:57 -04:00
Hypolite Petovan 5fb56281d0 Enforce coding standards in include/directory.php 2017-04-20 19:57:26 -04:00
Hypolite Petovan 34a5adb9e9 Enforce coding standards in index.php 2017-04-20 19:53:51 -04:00
Hypolite Petovan e82a1e3816 Fix use warning
- Standards enforcing
2017-04-20 19:41:07 -04:00
Hypolite Petovan 31d3cb4889 Merge branch 'feature/redesign-prototype' of https://github.com/tugelbend/dir into feature/redesign-prototype 2017-04-20 19:32:52 -04:00
Hypolite Petovan 5c719ba34e Fix PHP notice about undefined index q 2017-03-22 00:36:44 -04:00
Hypolite Petovan dff9a77968 Trim trailing spaces in boot.php 2017-03-22 00:36:44 -04:00
Hypolite Petovan 52a19916e2 Fix readme headers 2017-03-22 00:36:44 -04:00
Hypolite Petovan 7277e29c63 Add default htconfig.php
Remove final .htconfig.php
2017-03-22 00:36:44 -04:00
Roland Häder 2e18a053bb
Continued:
- moved JS of Raphael (seems to be a lib?) to own sub folder
- removed redundant jquery-1.4.2.min.js
- added version number to almost all JS files (see cache-busting)

Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:31:41 +01:00
Roland Häder 6eafd24872
this file needs executable right again ...
Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:22:12 +01:00
Roland Häder ae04088e39
converted Windows -> Linux as this seems to be the common LF
Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:21:06 +01:00
Roland Häder 12e4bff880
Continued with cleanups:
- removed some empty lines at bottom of files
- added new-line character at end of file (as this else may confuse some old
  editors)
- moved JavaScript files out of "include" into own "js"
- if you set <basename>, you don't need full URLs in linked JavaScript files
  and CSS files

Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:19:12 +01:00
Roland Häder 1940f6b499
removed no needed empty lines
Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:04:12 +01:00
Roland Häder c353b31569
Cleanups:
- don't commit files that are being ignored, better provide a "template" file
  that needs copying to the right file and ignore the file that will have local
  changes like config files will always have.
- fixed CHMOD, no need for executable flag here as the server won't execute
  these files, but only load (read) them
- fixed E_NOTICE in boot.php when entrance/index page (no parameter) is being
  called

Signed-off-by: Roland Haeder <roland@mxchange.org>
2017-03-01 20:00:32 +01:00
Hypolite Petovan b1f1ac0d62 Add default htconfig.php 2017-01-22 10:19:49 -05:00
hauke 18ca878113 library for checking ssllabs grade 2015-12-24 11:21:35 +01:00
hauke f8906282ef added skeletons for help and stats page, text adjustements 2015-12-24 11:20:37 +01:00
hauke 6b4da31a97 prepared view and database to display SSLLabs grade 2015-12-24 09:49:35 +01:00
hauke 128f653992 Removed facebook feature listing as it is no longer supported by friendica 2015-12-24 09:17:27 +01:00
hauke cd976b8dcc Removed gender and marital status for better privacy 2015-12-24 08:42:48 +01:00
hauke 3f5da87485 fix: function_exists checked for wrong function name 2015-12-24 08:15:22 +01:00
hauke 735ca9d6fa populate the development server with default sync servers to get data 2015-11-08 18:01:23 +01:00
hauke bab5446614 added cron jobs 2015-11-08 17:41:28 +01:00
hauke 50b60f6735 added a vagrant configuration for development 2015-11-08 16:54:15 +01:00
redmatrix 2be31c767c change photo paths relative to current directory server 2015-06-03 22:40:54 -07:00
redmatrix 3e05ac762e increase widget counts 2015-05-21 18:36:46 -07:00
Beanow 6becf45e73 Implemented feedback and moved JS to a file.
See https://fc.oscp.info/display/beanow/245480
2015-02-25 14:58:50 +01:00
Beanow a54e512669 Responsive mobile theme + public server listing. 2015-02-21 23:20:23 +01:00
Beanow 154f33ca86 Added experimental pages for the new design. 2015-02-08 18:25:21 +01:00
Beanow 1febb1a414 Switched to composer generated autoloader. 2015-02-08 18:24:49 +01:00
Beanow d89e155947 Merge branch 'feature/better-graphs' into develop 2015-02-02 12:28:35 +01:00
Beanow ec4a641013 Improved site-health detection.
* Detects redirects.
* Better descriptions in site-health details.
* Changed healthy site requirements to require HTTPS and >= 5 users.
* Cleaned graph javascript a little.
2015-02-02 12:28:12 +01:00
Beanow 9b8eaa7aa7 Made some improvements to how server statistics are displayed. 2015-01-24 21:49:53 +01:00
Beanow a11ecf4140 Merge branch 'feature/psr4-setup' into develop 2014-10-11 01:35:23 +02:00
Beanow 8cc61e0c68 Add some code comments to the example. 2014-10-11 00:37:41 +02:00
Beanow 9bafa316d4 Add example code for psr4. 2014-10-11 00:24:17 +02:00
Beanow dd6987ff02 This is an example of autoloading classes and including unit tests. 2014-10-11 00:13:59 +02:00
RedMatrix cbf217a829 Merge pull request #2 from Beanow/develop
Bugfix: timestamps did not update for pull.
2014-08-11 09:41:59 +10:00
Beanow cf1f60e092 Bugfix: timestamps did not update for pull. 2014-08-10 14:42:13 +02:00
RedMatrix 1bde8a76c4 Merge pull request #1 from Beanow/develop
Release new features
2014-08-10 09:06:31 +10:00
Beanow e87160d509 Push and pull information. 2014-08-09 01:20:54 +02:00
Beanow 65a6c71496 Adding some information about how submits work. 2014-08-09 01:09:20 +02:00
Beanow b803aa30a0 Tweaks to site-health and osearch. Also removed a non-directory page. 2014-08-09 00:49:00 +02:00
Beanow 1fe9bb9b5b Added syncing (push and pull) and refactored a few functions. 2014-08-09 00:46:53 +02:00
Beanow 0026b08a33 Added a simple submit-forwarding feature through the syncing cronjob. 2014-07-11 19:30:02 +02:00
Beanow 5c477f9b7f Faster import, better admin panel. 2014-07-11 01:35:30 +02:00
Beanow 717303e475 Merged import tools with admin view. 2014-07-11 00:30:36 +02:00
Beanow 5e1be6b2fe Experimental maintenance features. 2014-07-10 23:48:23 +02:00
Beanow 7276efc1bc Remove notice error from tree builder. 2014-07-10 23:43:41 +02:00
Beanow a69a9d2278 Adding site-health and noscrape support. 2014-07-10 23:43:25 +02:00
Beanow 1bac9fb268 Better permission handling. 2014-07-01 22:30:52 +02:00
Beanow c7d3173080 Improved stability of the submit feature to sustain large batches. 2014-07-01 14:00:14 +02:00
Beanow 40202ea948 Made a first version of import functionality.
* A very crude extraction step that creates a file with URLs.
* A processor that reduces the file to nothing as it imports them.
2014-07-01 03:27:56 +02:00
friendica fbf58ea0c4 update the parser lib 2012-11-18 23:44:58 -08:00
friendica 55907cf599 tag lister 2012-07-05 22:06:42 -07:00
friendica ec7f06ec54 missing brackets 2012-06-19 16:38:03 -07:00
friendica 49507a5149 sql issue on admin page 2012-06-19 16:32:43 -07:00
friendica 574f074dc0 make it harder to create duplicate globaldir entries 2012-06-14 18:42:06 -07:00
friendica 9d01103af7 fix colour 2012-06-12 18:00:32 -07:00
friendica 17a5f8cb03 Merge https://github.com/friendica/dir into dpull 2012-06-12 17:57:55 -07:00
friendica ed1d37658e move some things around 2012-06-12 17:57:17 -07:00
friendica 7898d297df testing github for windows 2012-06-06 18:29:14 +10:00
friendica 211f729622 separate the demo sites 2012-05-30 03:37:30 -07:00
friendica dcf4bf5ec4 flag testdrive sites 2012-05-30 03:15:56 -07:00
friendica ac23b394cc use photo relative to current instance url 2012-05-18 05:31:59 -07:00
friendica eb17d4b9f4 smooth the input boxes 2012-05-18 05:18:00 -07:00
friendica c153449475 string update 2012-05-17 20:54:03 -07:00
friendica 8cc603f045 subdue the "flag this entry" link 2012-05-17 20:23:15 -07:00
friendica dc73c854d1 and images 2012-05-17 20:17:54 -07:00
friendica 19e37cbf3d favicon to global dir 2012-05-17 20:16:40 -07:00
friendica 34f83f184c localisation fixes 2012-05-17 20:10:10 -07:00
friendica 0e6f862c11 directory cleanup 2012-05-17 18:52:46 -07:00
friendica 7ddae278aa initial work for translation 2012-05-16 18:38:32 -07:00
friendica 1681dca76b now ignore the favicons - sheesh 2012-05-15 23:14:35 -07:00
friendica cc0612173f push this first 2012-05-15 23:13:44 -07:00
friendica a23569cb20 ignore favicons 2012-05-15 23:08:43 -07:00
friendica 704857ff63 global dir database schema 2012-05-15 23:07:41 -07:00
friendica 3fe0f3fcef Merge https://github.com/friendica/dir into dpull 2012-05-15 22:47:03 -07:00
friendica ea309f20ee initial 2012-05-15 22:31:36 -07:00
261 changed files with 22733 additions and 3 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
*.out
.htaccess
.htconfig.php
#*
favicon.*
tests/coverage.html
/vendor
/nbproject/private/

23
.htaccess-dist Normal file
View 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
View file

@ -0,0 +1,2 @@
test:
cd tests && phpunit --coverage-html=coverage.html && x-www-browser ./coverage.html/index.html

144
README.md
View file

@ -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
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

30
assets/js/main.js Normal file
View 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
View 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)}})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10
bin/console Executable file
View 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
View 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
View 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
View 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) ? '&nbsp;' . $i : $i);
} else {
$o .= "<span class=\"pager_n\"><a href=\"$url" . "&page=$i\">" . (($i < 10) ? '&nbsp;' . $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) ? '&nbsp;' . $i : $i);
} else {
$o .= "<span class=\"pager_n\"><a href=\"$url" . "&page=$i\">" . (($i < 10) ? '&nbsp;' . $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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

BIN
images/b_drop.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
images/b_drop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
images/b_drophide.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
images/b_dropshow.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
images/b_edit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
images/b_edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

BIN
images/blue_flag_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,003 B

BIN
images/camera-icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

BIN
images/default-profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

BIN
images/dfrn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

BIN
images/dislike.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

BIN
images/friendica-128.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
images/friendica-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
images/friendica-16.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
images/friendica-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

BIN
images/friendica-1600.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
images/friendica-256.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/friendica-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/friendica-32.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/friendica-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/friendica-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/friendica-64.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
images/friendica-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
images/friendica-96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

185
images/friendica.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
images/friendika-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
images/friendika-16.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/friendika-16.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
images/friendika-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

BIN
images/friendika-1600.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
images/friendika-256.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/friendika-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/friendika-32.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/friendika-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/friendika-64.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
images/friendika-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

240
images/friendika.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

BIN
images/larrw.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,004 B

BIN
images/like.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

BIN
images/link-icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

BIN
images/lock_icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

BIN
images/lrarrow.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

BIN
images/no.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
images/pen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

BIN
images/penhover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

BIN
images/rarrow.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

BIN
images/rarrw.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

BIN
images/rotator.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

BIN
images/shield_2_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

BIN
images/star.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

BIN
images/unlock_icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

188
include/Photo.php Normal file
View 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
View 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
View 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
View 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("<", "&lt;", $Text);
$Text = str_replace(">", "&gt;", $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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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.');
}
}

View 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>

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