├── .gitignore ├── LICENSE ├── README.rst ├── bin └── stapibas ├── build.xml ├── composer.json ├── composer.lock ├── data ├── config.php.dist ├── tables.sql └── templates │ └── default │ ├── mentions-comment.htm │ ├── mentions-link.htm │ └── mentions.htm ├── src ├── phar-stub.php └── stapibas │ ├── Cli.php │ ├── Content │ ├── Extractor.php │ ├── Extractor │ │ ├── Base.php │ │ ├── Comment.php │ │ └── Link.php │ └── Fetcher.php │ ├── Dependencies.php │ ├── Feed │ ├── Manage.php │ ├── PingUrls.php │ ├── UpdateEntries.php │ └── UpdateFeeds.php │ ├── Linkback │ ├── DbStorage.php │ └── Mailer.php │ ├── Logger.php │ ├── PDO.php │ └── Renderer │ └── Html.php ├── tests ├── phpunit.xml └── stapibas │ └── Content │ └── Extractor │ ├── CommentTest.php │ ├── LinkTest.php │ └── data │ ├── aaron-parecki.html │ ├── laurent-eschenauer.html │ └── shadowbox-popup-positioning.htm └── www ├── api └── links.php ├── css ├── jquery-0.6.1.smallipop.css ├── jquery-0.6.1.smallipop.min.css └── show-links.css ├── index.php ├── js ├── jquery-0.6.1.smallipop.js ├── jquery-0.6.1.smallipop.min.js ├── jquery-2.1.0.js ├── jquery-2.1.0.min.js └── show-links.js ├── render.php ├── request-feed-update.php ├── robots.txt ├── www-header.php └── xmlrpc.php /.gitignore: -------------------------------------------------------------------------------- 1 | data/config.php 2 | www/test.htm 3 | lib/* 4 | /dist/ 5 | /bin/phar-stapibas.php 6 | /README.html 7 | /vendor/ 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | stapibas 3 | ******** 4 | The standalone Linkback server, written in PHP. 5 | 6 | - Receives linkbacks (`webmention`__ and `pingbacks`__) 7 | - Watches your website's Feed to send out linkbacks to all linked URLs 8 | 9 | Alternative to `Trackback ‘em All`__ and `Telegraph`__ 10 | 11 | __ https://www.w3.org/TR/webmention/ 12 | __ http://www.hixie.ch/specs/pingback/pingback 13 | __ http://scott.yang.id.au/code/trackback-em-all/ 14 | __ https://telegraph.p3k.io/ 15 | 16 | 17 | ================= 18 | Linkback receiver 19 | ================= 20 | stapibas receives linkbacks (webmentions + pingbacks) for your website 21 | and puts them into a database. 22 | 23 | It also sends them as email to a configured address. 24 | 25 | 26 | Setup 27 | ===== 28 | Let your website send out the following HTTP headers:: 29 | 30 | X-Pingback: http://stapibas.example.org/xmlrpc.php 31 | Link: '; rel="webmention"' 32 | 33 | In Apache you can do this with the following configuration:: 34 | 35 | Header set X-Pingback "http://stapibas.example.org/xmlrpc.php" 36 | Header append Link '; rel="webmention"' 37 | 38 | 39 | Now, whitelist your domain in the database: 40 | Add an ``lt_url`` of ``https://example.org/%`` in the ``linkbacktargets`` table. 41 | 42 | That's all. 43 | 44 | .. note:: 45 | stapibas does not display the linkbacks in any way - you have to do this yourself. 46 | 47 | If you're looking for a ready-made solution, look at the tools listed 48 | on https://indieweb.org/Webmention 49 | 50 | 51 | 52 | =============== 53 | Linkback sender 54 | =============== 55 | stapibas is able to send linkbacks out to other websites at behalf of 56 | your website. 57 | 58 | It does this by watching your website's Atom (or RSS) feed. 59 | Whenever it changes, it fetches the articles that are new or got updated and 60 | sends out pingbacks to the remote websites. 61 | 62 | It only works on links that are inside an ``e-content`` section 63 | that itself has to be inside a `h-entry`__. 64 | 65 | __ http://microformats.org/wiki/h-entry 66 | 67 | 68 | Setup 69 | ===== 70 | Add your feed URL:: 71 | 72 | $ ./bin/stapibas feed add http://example.org/feed.atom 73 | 74 | Whenever you update your website, tell stapibas about it via a 75 | HTTP POST request, sending the feed URL:: 76 | 77 | $ curl -d url=http://example.org/feed.atom http://stapibas.example.org/request-feed-update.php 78 | 79 | This tells stapibas to check this feed the next time the pinger runs. 80 | 81 | .. note:: 82 | stapibas does not check itself if the feed changed! 83 | 84 | You need to notify it manually. 85 | 86 | 87 | Run the pinger 88 | ============== 89 | Run stapibas every 5 minutes or every hour to check for feed updates, 90 | extract new URLs from the feed and send pingbacks to them. 91 | 92 | :: 93 | 94 | $ php bin/stapibas 95 | 96 | 97 | ============ 98 | Dependencies 99 | ============ 100 | - PHP 8.0+ 101 | - PDO 102 | - PHP libraries that get installed with ``composer install --no-dev`` 103 | -------------------------------------------------------------------------------- /bin/stapibas: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 9 | ?> 10 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cweiske/stapibas", 3 | "description": "The standalone Linkback server, written in PHP.", 4 | "autoload": { 5 | "classmap": ["src/"] 6 | }, 7 | "require": { 8 | "pear/console_commandline": "^1.2.6", 9 | "pear/net_url2": "^2.2", 10 | "pear/http_request2": "^2.5", 11 | "pear2/services_linkback": "^0.4.0", 12 | "simplepie/simplepie": "^1.8" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "a228cc8658bd875469df78992799f272", 8 | "packages": [ 9 | { 10 | "name": "pear/console_commandline", 11 | "version": "v1.2.6", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/pear/Console_CommandLine.git", 15 | "reference": "611c5bff2e47ec5a184748cb5fedc2869098ff28" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/pear/Console_CommandLine/zipball/611c5bff2e47ec5a184748cb5fedc2869098ff28", 20 | "reference": "611c5bff2e47ec5a184748cb5fedc2869098ff28", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-dom": "*", 25 | "ext-xml": "*", 26 | "pear/pear_exception": "^1.0.0", 27 | "php": ">=5.3.0" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "*" 31 | }, 32 | "type": "library", 33 | "autoload": { 34 | "psr-0": { 35 | "Console": "./" 36 | }, 37 | "exclude-from-classmap": [ 38 | "tests/" 39 | ] 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "include-path": [ 43 | "" 44 | ], 45 | "license": [ 46 | "MIT" 47 | ], 48 | "authors": [ 49 | { 50 | "name": "Richard Quadling", 51 | "email": "rquadling@gmail.com" 52 | }, 53 | { 54 | "name": "David Jean Louis", 55 | "email": "izimobil@gmail.com" 56 | } 57 | ], 58 | "description": "A full featured command line options and arguments parser.", 59 | "homepage": "https://github.com/pear/Console_CommandLine", 60 | "keywords": [ 61 | "console" 62 | ], 63 | "support": { 64 | "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine", 65 | "source": "https://github.com/pear/Console_CommandLine" 66 | }, 67 | "time": "2023-04-02T18:49:53+00:00" 68 | }, 69 | { 70 | "name": "pear/http2", 71 | "version": "v2.0.0", 72 | "source": { 73 | "type": "git", 74 | "url": "https://github.com/pear/HTTP2.git", 75 | "reference": "72e15b4faa86f6109c6fc3aa82c5515b6453b3b5" 76 | }, 77 | "dist": { 78 | "type": "zip", 79 | "url": "https://api.github.com/repos/pear/HTTP2/zipball/72e15b4faa86f6109c6fc3aa82c5515b6453b3b5", 80 | "reference": "72e15b4faa86f6109c6fc3aa82c5515b6453b3b5", 81 | "shasum": "" 82 | }, 83 | "require-dev": { 84 | "phpunit/phpunit": "^9" 85 | }, 86 | "type": "library", 87 | "autoload": { 88 | "psr-0": { 89 | "HTTP2": "./" 90 | } 91 | }, 92 | "notification-url": "https://packagist.org/downloads/", 93 | "license": [ 94 | "BSD-2-Clause" 95 | ], 96 | "authors": [ 97 | { 98 | "name": "Michael Wallner", 99 | "email": "mike@php.net", 100 | "role": "Lead" 101 | }, 102 | { 103 | "name": "Philippe Jausions", 104 | "email": "jausions@php.net", 105 | "role": "Lead" 106 | } 107 | ], 108 | "description": "Miscellaneous HTTP utilities", 109 | "homepage": "http://pear.php.net/package/HTTP2", 110 | "support": { 111 | "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=HTTP2", 112 | "source": "https://github.com/pear/HTTP2" 113 | }, 114 | "time": "2023-03-22T20:22:11+00:00" 115 | }, 116 | { 117 | "name": "pear/http_request2", 118 | "version": "v2.5.1", 119 | "source": { 120 | "type": "git", 121 | "url": "https://github.com/pear/HTTP_Request2.git", 122 | "reference": "db4ce7844f838d3adca0513a77420c0fec22ed2d" 123 | }, 124 | "dist": { 125 | "type": "zip", 126 | "url": "https://api.github.com/repos/pear/HTTP_Request2/zipball/db4ce7844f838d3adca0513a77420c0fec22ed2d", 127 | "reference": "db4ce7844f838d3adca0513a77420c0fec22ed2d", 128 | "shasum": "" 129 | }, 130 | "require": { 131 | "pear/net_url2": "^2.2.0", 132 | "pear/pear_exception": "^1.0.0", 133 | "php": ">=5.6.0" 134 | }, 135 | "require-dev": { 136 | "yoast/phpunit-polyfills": "^1.0.0" 137 | }, 138 | "suggest": { 139 | "ext-curl": "Allows using cURL as a request backend.", 140 | "ext-fileinfo": "Adds support for looking up mime-types using finfo.", 141 | "ext-openssl": "Allows handling SSL requests when not using cURL.", 142 | "ext-zlib": "Allows handling gzip compressed responses." 143 | }, 144 | "type": "library", 145 | "autoload": { 146 | "psr-0": { 147 | "HTTP_Request2": "" 148 | } 149 | }, 150 | "notification-url": "https://packagist.org/downloads/", 151 | "license": [ 152 | "BSD-3-Clause" 153 | ], 154 | "authors": [ 155 | { 156 | "name": "Alexey Borzov", 157 | "email": "avb@php.net" 158 | } 159 | ], 160 | "description": "Provides an easy way to perform HTTP requests.", 161 | "homepage": "https://pear.php.net/package/HTTP_Request2", 162 | "keywords": [ 163 | "PEAR", 164 | "curl", 165 | "http", 166 | "request" 167 | ], 168 | "support": { 169 | "docs": "https://pear.php.net/manual/en/package.http.http-request2.php", 170 | "issues": "https://github.com/pear/HTTP_Request2/issues", 171 | "source": "https://github.com/pear/HTTP_Request2" 172 | }, 173 | "time": "2022-01-06T18:20:25+00:00" 174 | }, 175 | { 176 | "name": "pear/net_url2", 177 | "version": "v2.2.2", 178 | "source": { 179 | "type": "git", 180 | "url": "https://github.com/pear/Net_URL2.git", 181 | "reference": "07fd055820dbf466ee3990abe96d0e40a8791f9d" 182 | }, 183 | "dist": { 184 | "type": "zip", 185 | "url": "https://api.github.com/repos/pear/Net_URL2/zipball/07fd055820dbf466ee3990abe96d0e40a8791f9d", 186 | "reference": "07fd055820dbf466ee3990abe96d0e40a8791f9d", 187 | "shasum": "" 188 | }, 189 | "require": { 190 | "php": ">=5.1.4" 191 | }, 192 | "require-dev": { 193 | "phpunit/phpunit": ">=3.3.0" 194 | }, 195 | "type": "library", 196 | "extra": { 197 | "branch-alias": { 198 | "dev-master": "2.2.x-dev" 199 | } 200 | }, 201 | "autoload": { 202 | "classmap": [ 203 | "Net/URL2.php" 204 | ] 205 | }, 206 | "notification-url": "https://packagist.org/downloads/", 207 | "include-path": [ 208 | "./" 209 | ], 210 | "license": [ 211 | "BSD-3-Clause" 212 | ], 213 | "authors": [ 214 | { 215 | "name": "David Coallier", 216 | "email": "davidc@php.net" 217 | }, 218 | { 219 | "name": "Tom Klingenberg", 220 | "email": "tkli@php.net" 221 | }, 222 | { 223 | "name": "Christian Schmidt", 224 | "email": "chmidt@php.net" 225 | } 226 | ], 227 | "description": "Class for parsing and handling URL. Provides parsing of URLs into their constituent parts (scheme, host, path etc.), URL generation, and resolving of relative URLs.", 228 | "homepage": "https://github.com/pear/Net_URL2", 229 | "keywords": [ 230 | "PEAR", 231 | "net", 232 | "networking", 233 | "rfc3986", 234 | "uri", 235 | "url" 236 | ], 237 | "support": { 238 | "issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_URL2", 239 | "source": "https://github.com/pear/Net_URL2" 240 | }, 241 | "time": "2017-08-25T06:16:11+00:00" 242 | }, 243 | { 244 | "name": "pear/pear_exception", 245 | "version": "v1.0.2", 246 | "source": { 247 | "type": "git", 248 | "url": "https://github.com/pear/PEAR_Exception.git", 249 | "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" 250 | }, 251 | "dist": { 252 | "type": "zip", 253 | "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", 254 | "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", 255 | "shasum": "" 256 | }, 257 | "require": { 258 | "php": ">=5.2.0" 259 | }, 260 | "require-dev": { 261 | "phpunit/phpunit": "<9" 262 | }, 263 | "type": "class", 264 | "extra": { 265 | "branch-alias": { 266 | "dev-master": "1.0.x-dev" 267 | } 268 | }, 269 | "autoload": { 270 | "classmap": [ 271 | "PEAR/" 272 | ] 273 | }, 274 | "notification-url": "https://packagist.org/downloads/", 275 | "include-path": [ 276 | "." 277 | ], 278 | "license": [ 279 | "BSD-2-Clause" 280 | ], 281 | "authors": [ 282 | { 283 | "name": "Helgi Thormar", 284 | "email": "dufuz@php.net" 285 | }, 286 | { 287 | "name": "Greg Beaver", 288 | "email": "cellog@php.net" 289 | } 290 | ], 291 | "description": "The PEAR Exception base class.", 292 | "homepage": "https://github.com/pear/PEAR_Exception", 293 | "keywords": [ 294 | "exception" 295 | ], 296 | "support": { 297 | "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", 298 | "source": "https://github.com/pear/PEAR_Exception" 299 | }, 300 | "time": "2021-03-21T15:43:46+00:00" 301 | }, 302 | { 303 | "name": "pear2/services_linkback", 304 | "version": "v0.4.0", 305 | "source": { 306 | "type": "git", 307 | "url": "https://github.com/pear2/Services_Linkback.git", 308 | "reference": "61790889871cae0bc6e54adb2637b9e0ec93275f" 309 | }, 310 | "dist": { 311 | "type": "zip", 312 | "url": "https://api.github.com/repos/pear2/Services_Linkback/zipball/61790889871cae0bc6e54adb2637b9e0ec93275f", 313 | "reference": "61790889871cae0bc6e54adb2637b9e0ec93275f", 314 | "shasum": "" 315 | }, 316 | "require": { 317 | "ext-xmlrpc": "*", 318 | "pear/http2": "^2.0", 319 | "pear/http_request2": "^2.5", 320 | "pear/net_url2": "^2.2" 321 | }, 322 | "require-dev": { 323 | "pear/stream_var": "^2.0", 324 | "phpunit/phpunit": "^9", 325 | "squizlabs/php_codesniffer": "~2.6" 326 | }, 327 | "type": "library", 328 | "autoload": { 329 | "psr-0": { 330 | "PEAR2\\Services\\Linkback\\": "src" 331 | } 332 | }, 333 | "notification-url": "https://packagist.org/downloads/", 334 | "license": [ 335 | "LGPL-3.0+" 336 | ], 337 | "authors": [ 338 | { 339 | "name": "Christian Weiske", 340 | "email": "cweiske@php.net", 341 | "homepage": "http://cweiske.de/" 342 | } 343 | ], 344 | "description": "Pingback+webmention client and server implementation ", 345 | "homepage": "http://pear2.php.net/PEAR2_Services_Linkback", 346 | "support": { 347 | "email": "pear-general@lists.php.net", 348 | "issues": "https://github.com/pear2/Services_Linkback/issues/", 349 | "source": "https://github.com/pear2/Services_Linkback/tree/v0.4.0" 350 | }, 351 | "time": "2023-03-22T21:18:04+00:00" 352 | }, 353 | { 354 | "name": "simplepie/simplepie", 355 | "version": "1.8.0", 356 | "source": { 357 | "type": "git", 358 | "url": "https://github.com/simplepie/simplepie.git", 359 | "reference": "65b095d87bc00898d8fa7737bdbcda93a3fbcc55" 360 | }, 361 | "dist": { 362 | "type": "zip", 363 | "url": "https://api.github.com/repos/simplepie/simplepie/zipball/65b095d87bc00898d8fa7737bdbcda93a3fbcc55", 364 | "reference": "65b095d87bc00898d8fa7737bdbcda93a3fbcc55", 365 | "shasum": "" 366 | }, 367 | "require": { 368 | "ext-pcre": "*", 369 | "ext-xml": "*", 370 | "ext-xmlreader": "*", 371 | "php": ">=7.2.0" 372 | }, 373 | "require-dev": { 374 | "friendsofphp/php-cs-fixer": "^2.19 || ^3.8", 375 | "psr/simple-cache": "^1 || ^2 || ^3", 376 | "yoast/phpunit-polyfills": "^1.0.1" 377 | }, 378 | "suggest": { 379 | "ext-curl": "", 380 | "ext-iconv": "", 381 | "ext-intl": "", 382 | "ext-mbstring": "", 383 | "mf2/mf2": "Microformat module that allows for parsing HTML for microformats" 384 | }, 385 | "type": "library", 386 | "autoload": { 387 | "psr-0": { 388 | "SimplePie": "library" 389 | }, 390 | "psr-4": { 391 | "SimplePie\\": "src" 392 | } 393 | }, 394 | "notification-url": "https://packagist.org/downloads/", 395 | "license": [ 396 | "BSD-3-Clause" 397 | ], 398 | "authors": [ 399 | { 400 | "name": "Ryan Parman", 401 | "homepage": "http://ryanparman.com/", 402 | "role": "Creator, alumnus developer" 403 | }, 404 | { 405 | "name": "Sam Sneddon", 406 | "homepage": "https://gsnedders.com/", 407 | "role": "Alumnus developer" 408 | }, 409 | { 410 | "name": "Ryan McCue", 411 | "email": "me@ryanmccue.info", 412 | "homepage": "http://ryanmccue.info/", 413 | "role": "Developer" 414 | } 415 | ], 416 | "description": "A simple Atom/RSS parsing library for PHP", 417 | "homepage": "http://simplepie.org/", 418 | "keywords": [ 419 | "atom", 420 | "feeds", 421 | "rss" 422 | ], 423 | "support": { 424 | "issues": "https://github.com/simplepie/simplepie/issues", 425 | "source": "https://github.com/simplepie/simplepie/tree/1.8.0" 426 | }, 427 | "time": "2023-01-20T08:37:35+00:00" 428 | } 429 | ], 430 | "packages-dev": [], 431 | "aliases": [], 432 | "minimum-stability": "stable", 433 | "stability-flags": [], 434 | "prefer-stable": false, 435 | "prefer-lowest": false, 436 | "platform": [], 437 | "platform-dev": [], 438 | "plugin-api-version": "2.3.0" 439 | } 440 | -------------------------------------------------------------------------------- /data/config.php.dist: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /data/tables.sql: -------------------------------------------------------------------------------- 1 | 2 | 3 | CREATE TABLE `feedentries` ( 4 | `fe_id` int(11) NOT NULL AUTO_INCREMENT, 5 | `fe_f_id` int(11) NOT NULL, 6 | `fe_url` varchar(2048) CHARACTER SET utf8 NOT NULL, 7 | `fe_updated` datetime NOT NULL, 8 | `fe_needs_update` tinyint(1) NOT NULL, 9 | PRIMARY KEY (`fe_id`), 10 | UNIQUE KEY `fe_id` (`fe_id`) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 12 | 13 | 14 | CREATE TABLE `feedentryurls` ( 15 | `feu_id` int(11) NOT NULL AUTO_INCREMENT, 16 | `feu_fe_id` int(11) NOT NULL, 17 | `feu_url` varchar(2048) CHARACTER SET utf8 NOT NULL, 18 | `feu_active` tinyint(1) NOT NULL COMMENT 'if the url still exists in the entry', 19 | `feu_pinged` tinyint(1) NOT NULL, 20 | `feu_updated` datetime NOT NULL, 21 | `feu_error` tinyint(1) NOT NULL, 22 | `feu_error_code` varchar(6) NOT NULL, 23 | `feu_error_message` varchar(4096) NOT NULL, 24 | `feu_tries` tinyint(4) NOT NULL, 25 | `feu_retry` tinyint(1) NOT NULL, 26 | PRIMARY KEY (`feu_id`), 27 | UNIQUE KEY `feu_id` (`feu_id`) 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 29 | 30 | 31 | CREATE TABLE `feeds` ( 32 | `f_id` int(11) NOT NULL AUTO_INCREMENT, 33 | `f_url` varchar(2048) CHARACTER SET utf8 NOT NULL, 34 | `f_updated` datetime NOT NULL, 35 | `f_needs_update` tinyint(1) NOT NULL, 36 | PRIMARY KEY (`f_id`), 37 | UNIQUE KEY `f_id` (`f_id`) 38 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 39 | 40 | 41 | CREATE TABLE `linkbackcontent` ( 42 | `lc_id` int(11) NOT NULL AUTO_INCREMENT, 43 | `lc_l_id` int(11) NOT NULL, 44 | `lc_mime_type` varchar(32) NOT NULL, 45 | `lc_fulltext` text NOT NULL, 46 | `lc_detected_type` varchar(16) NOT NULL, 47 | PRIMARY KEY (`lc_id`), 48 | UNIQUE KEY `lc_id` (`lc_id`) 49 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 50 | 51 | 52 | CREATE TABLE `linkbacks` ( 53 | `l_id` int(11) NOT NULL AUTO_INCREMENT, 54 | `l_source` varchar(1024) CHARACTER SET utf8 NOT NULL, 55 | `l_target` varchar(1024) CHARACTER SET utf8 NOT NULL, 56 | `l_time` datetime NOT NULL, 57 | `l_client_ip` varchar(40) CHARACTER SET utf8 NOT NULL, 58 | `l_client_agent` varchar(128) CHARACTER SET utf8 NOT NULL, 59 | `l_client_referer` varchar(1024) CHARACTER SET utf8 NOT NULL, 60 | `l_needs_review` tinyint(1) NOT NULL, 61 | `l_use` tinyint(1) NOT NULL, 62 | `l_needs_update` tinyint(1) NOT NULL, 63 | PRIMARY KEY (`l_id`), 64 | UNIQUE KEY `l_id` (`l_id`) 65 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 66 | 67 | 68 | CREATE TABLE `linkbacktargets` ( 69 | `lt_id` int(11) NOT NULL AUTO_INCREMENT, 70 | `lt_url` varchar(2048) NOT NULL, 71 | PRIMARY KEY (`lt_id`), 72 | UNIQUE KEY `lt_id` (`lt_id`) 73 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='List of pages that may receive linkbacks'; 74 | 75 | 76 | CREATE TABLE `rbookmarks` ( 77 | `rb_id` int(11) NOT NULL AUTO_INCREMENT, 78 | `rb_l_id` int(11) NOT NULL, 79 | `rb_lc_id` int(11) NOT NULL, 80 | `rb_target` varchar(2048) NOT NULL, 81 | `rb_source` varchar(2048) NOT NULL, 82 | `rb_source_title` varchar(256) NOT NULL, 83 | `rb_count` int(11) NOT NULL, 84 | PRIMARY KEY (`rb_id`), 85 | UNIQUE KEY `rb_id` (`rb_id`) 86 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Bookmarks, extracted from linkbackcontent'; 87 | 88 | 89 | CREATE TABLE `rcomments` ( 90 | `rc_id` int(11) NOT NULL AUTO_INCREMENT, 91 | `rc_l_id` int(11) NOT NULL, 92 | `rc_lc_id` int(11) NOT NULL, 93 | `rc_target` varchar(2048) NOT NULL, 94 | `rc_source` varchar(2048) NOT NULL, 95 | `rc_title` varchar(256) NOT NULL, 96 | `rc_updated` datetime NOT NULL, 97 | `rc_author_name` varchar(32) NOT NULL, 98 | `rc_author_url` varchar(2048) NOT NULL, 99 | `rc_author_image` varchar(2048) NOT NULL, 100 | `rc_content` text NOT NULL, 101 | PRIMARY KEY (`rc_id`), 102 | UNIQUE KEY `rb_id` (`rc_id`) 103 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Bookmarks, extracted from linkbackcontent'; 104 | 105 | 106 | CREATE TABLE `rlinks` ( 107 | `rl_id` int(11) NOT NULL AUTO_INCREMENT, 108 | `rl_l_id` int(11) NOT NULL, 109 | `rl_lc_id` int(11) NOT NULL, 110 | `rl_target` varchar(2048) NOT NULL, 111 | `rl_source` varchar(2048) NOT NULL, 112 | `rl_title` varchar(256) NOT NULL, 113 | `rl_updated` datetime NOT NULL, 114 | `rl_author_name` varchar(32) NOT NULL, 115 | `rl_author_url` varchar(2048) NOT NULL, 116 | `rl_author_image` varchar(2048) NOT NULL, 117 | PRIMARY KEY (`rl_id`), 118 | UNIQUE KEY `rb_id` (`rl_id`) 119 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Bookmarks, extracted from linkbackcontent'; 120 | 121 | 122 | -------------------------------------------------------------------------------- /data/templates/default/mentions-comment.htm: -------------------------------------------------------------------------------- 1 | {% if commentRow.rc_author_image %}Avatar for {{commentRow.rc_author_name}}{% endif %} 2 | 3 | {% if linkRow.rc_author_url %}{% endif %} 4 | {{commentRow.rc_author_name}} 5 | {% if linkRow.rc_author_url %}{% endif %} 6 | 7 |
8 | {% if commentRow.rc_title %}

{{commentRow.rc_title}}

{% endif %} 9 | {{commentRow.rl_comment|raw}} 10 |
11 | -------------------------------------------------------------------------------- /data/templates/default/mentions-link.htm: -------------------------------------------------------------------------------- 1 | {{linkRow.rl_title}} 2 | {% if linkRow.rl_author_name %} 3 | by 4 | {% if linkRow.rl_author_url %}{% endif %} 5 | {{linkRow.rl_author_name}} 6 | {% if linkRow.rl_author_url %}{% endif %} 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /data/templates/default/mentions.htm: -------------------------------------------------------------------------------- 1 | {% if arData.comments %} 2 |

Comments

3 | 8 | {% endif %} 9 | 10 | {% if arData.links %} 11 | 12 | 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /src/phar-stub.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2014 Christian Weiske 11 | * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 12 | * @link http://cweiske.de/bdrem.htm 13 | */ 14 | if (!in_array('phar', stream_get_wrappers()) || !class_exists('Phar', false)) { 15 | echo "Phar extension not avaiable\n"; 16 | exit(255); 17 | } 18 | 19 | $web = 'www/index.php'; 20 | $cli = 'bin/phar-stapibas.php'; 21 | 22 | /** 23 | * Rewrite the HTTP request path to an internal file. 24 | * Maps "" and "/" to "www/index.php". 25 | * 26 | * @param string $path Path from the browser, relative to the .phar 27 | * 28 | * @return string Internal path. 29 | */ 30 | function rewritePath($path) 31 | { 32 | if ($path == '' || $path == '/') { 33 | return 'www/index.php'; 34 | } 35 | return $path; 36 | } 37 | 38 | //Phar::interceptFileFuncs(); 39 | set_include_path( 40 | 'phar://' . __FILE__ 41 | . PATH_SEPARATOR . 'phar://' . __FILE__ . '/lib/' 42 | ); 43 | Phar::webPhar(null, $web, null, array(), 'rewritePath'); 44 | 45 | //work around https://bugs.php.net/bug.php?id=52322 46 | if (php_sapi_name() == 'cgi-fcgi') { 47 | echo "Your PHP has a bug handling phar files :/\n"; 48 | exit(10); 49 | } 50 | 51 | require 'phar://' . __FILE__ . '/' . $cli; 52 | __HALT_COMPILER(); 53 | ?> 54 | -------------------------------------------------------------------------------- /src/stapibas/Cli.php: -------------------------------------------------------------------------------- 1 | setupCli(); 11 | } 12 | 13 | public function run() 14 | { 15 | try { 16 | $result = $this->cliParser->parse(); 17 | } catch (\Exception $exc) { 18 | $this->cliParser->displayError($exc->getMessage()); 19 | } 20 | 21 | $log = new Logger(); 22 | if ($result->options['debug']) { 23 | $log->debug = true; 24 | } 25 | 26 | try { 27 | $deps = new Dependencies(); 28 | $deps->db = new PDO( 29 | $GLOBALS['dbdsn'], $GLOBALS['dbuser'], $GLOBALS['dbpass'] 30 | ); 31 | $deps->log = $log; 32 | $deps->options = array_merge( 33 | $result->options, 34 | $result->command ? $result->command->options : array(), 35 | ($result->command && $result->command->command) 36 | ? $result->command->command->options 37 | : array() 38 | ); 39 | 40 | if ($result->command_name == 'feed') { 41 | $this->runFeed($result->command, $deps); 42 | } else if ($result->command_name == 'handle') { 43 | $this->runPingbackHandler($result->command, $deps); 44 | } else { 45 | $this->cliParser->displayUsage(1); 46 | } 47 | } catch (\Exception $e) { 48 | $msg = 'stapibas exception!' . "\n" 49 | . 'Code: ' . $e->getCode() . "\n" 50 | . 'Message: ' . $e->getMessage() . "\n"; 51 | file_put_contents('php://stderr', $msg); 52 | exit(1); 53 | } 54 | } 55 | 56 | protected function runFeed( 57 | \Console_CommandLine_Result $command, Dependencies $deps 58 | ) { 59 | if ($command->command_name == 'update') { 60 | return $this->runFeedUpdate($command, $deps); 61 | } 62 | 63 | $mg = new Feed_Manage($deps); 64 | if ($command->command_name == 'add') { 65 | $mg->addFeed($command->command->args['feed']); 66 | } else if ($command->command_name == 'remove') { 67 | $mg->removeFeed($command->command->args['feed']); 68 | } else if ($command->command_name == 'list') { 69 | $mg->listAll(); 70 | } else { 71 | $this->cliParser->commands['feed']->displayUsage(1); 72 | } 73 | } 74 | 75 | protected function runFeedUpdate( 76 | \Console_CommandLine_Result $result, Dependencies $deps 77 | ) { 78 | $tasks = array_flip(explode(',', $result->command->options['tasks'])); 79 | 80 | //if an ID/url is given, only execute the matching task 81 | if (isset($result->command->options['feed'])) { 82 | $tasks = array('feeds' => 1); 83 | } else if (isset($result->command->options['entry'])) { 84 | $tasks = array('entries' => 1); 85 | } else if (isset($result->command->options['entryurl'])) { 86 | $tasks = array('urls' => 1); 87 | } 88 | 89 | if (isset($tasks['feeds'])) { 90 | $this->runFeedUpdateFeeds($deps); 91 | } 92 | if (isset($tasks['entries'])) { 93 | $this->runFeedUpdateEntries($deps); 94 | } 95 | if (isset($tasks['urls'])) { 96 | $this->runFeedUpdatePingUrls($deps); 97 | } 98 | } 99 | 100 | 101 | protected function runFeedUpdateFeeds($deps) 102 | { 103 | $uf = new Feed_UpdateFeeds($deps); 104 | if ($deps->options['feed'] === null) { 105 | $uf->updateAll(); 106 | } else { 107 | $urlOrIds = explode(',', $deps->options['feed']); 108 | $uf->updateSome($urlOrIds); 109 | } 110 | } 111 | 112 | protected function runFeedUpdateEntries($deps) 113 | { 114 | $ue = new Feed_UpdateEntries($deps); 115 | if ($deps->options['entry'] === null) { 116 | $ue->updateAll(); 117 | } else { 118 | $urlOrIds = explode(',', $deps->options['entry']); 119 | $ue->updateSome($urlOrIds); 120 | } 121 | } 122 | 123 | protected function runFeedUpdatePingUrls($deps) 124 | { 125 | $uf = new Feed_PingUrls($deps); 126 | if ($deps->options['entryurl'] === null) { 127 | $uf->pingAll(); 128 | } else { 129 | $urls = explode(',', $deps->options['entryurl']); 130 | $uf->pingSome($urls); 131 | } 132 | } 133 | 134 | 135 | protected function runPingbackHandler( 136 | \Console_CommandLine_Result $command, Dependencies $deps 137 | ) { 138 | //fetch content of pingback source pages 139 | $cf = new Content_Fetcher($deps); 140 | $cf->updateAll(); 141 | 142 | $cx = new Content_Extractor($deps); 143 | $cx->updateAll(); 144 | } 145 | 146 | 147 | public function setupCli() 148 | { 149 | $p = new \Console_CommandLine(); 150 | $p->description = 'Sends pingbacks to URLs linked in Atom feed entries'; 151 | $p->version = '0.0.1'; 152 | 153 | $p->addOption( 154 | 'debug', 155 | array( 156 | 'short_name' => '-d', 157 | 'long_name' => '--debug', 158 | 'description' => "Output debug messages", 159 | 'action' => 'StoreTrue' 160 | ) 161 | ); 162 | $p->addOption( 163 | 'force', 164 | array( 165 | 'short_name' => '-f', 166 | 'long_name' => '--force', 167 | 'description' => "Update even when resource did not change", 168 | 'action' => 'StoreTrue' 169 | ) 170 | ); 171 | 172 | $this->setupCliFeed($p); 173 | $this->setupCliPingback($p); 174 | 175 | $this->cliParser = $p; 176 | } 177 | 178 | protected function setupCliFeed($p) 179 | { 180 | $feed = $p->addCommand( 181 | 'feed', 182 | array( 183 | 'description' => 'Edit, list or delete feeds' 184 | ) 185 | ); 186 | 187 | $add = $feed->addCommand( 188 | 'add', 189 | array( 190 | 'description' => 'Add the feed', 191 | ) 192 | ); 193 | $add->addArgument('feed', array('description' => 'URL of feed')); 194 | 195 | $feed->addCommand( 196 | 'list', 197 | array( 198 | 'description' => 'List all feeds', 199 | ) 200 | ); 201 | 202 | $remove = $feed->addCommand( 203 | 'remove', 204 | array( 205 | 'description' => 'Remove the feed', 206 | ) 207 | ); 208 | $remove->addArgument('feed', array('description' => 'URL or ID of feed')); 209 | 210 | 211 | $update = $feed->addCommand( 212 | 'update', 213 | array( 214 | 'description' => 'Update feed data and send out pings' 215 | ) 216 | ); 217 | $update->addOption( 218 | 'feed', 219 | array( 220 | 'short_name' => '-i', 221 | 'long_name' => '--feed', 222 | 'description' => 'Update this feed URL or ID', 223 | 'help_name' => 'URL|ID', 224 | 'action' => 'StoreString' 225 | ) 226 | ); 227 | $update->addOption( 228 | 'entry', 229 | array( 230 | 'short_name' => '-e', 231 | 'long_name' => '--entry', 232 | 'description' => 'Update this feed entry URL or ID', 233 | 'help_name' => 'URL|ID', 234 | 'action' => 'StoreString' 235 | ) 236 | ); 237 | $update->addOption( 238 | 'tasks', 239 | array( 240 | 'short_name' => '-t', 241 | 'long_name' => '--tasks', 242 | 'description' => 'Execute the given tasks (comma-separated: feeds,entries,urls)', 243 | 'help_name' => 'tasks', 244 | 'action' => 'StoreString', 245 | 'default' => 'feeds,entries,urls', 246 | ) 247 | ); 248 | $update->addOption( 249 | 'list_tasks', 250 | array( 251 | 'long_name' => '--list-tasks', 252 | 'description' => 'Show all possible tasks', 253 | 'action' => 'List', 254 | 'action_params' => array( 255 | 'list' => array('feeds', 'entries', 'urls') 256 | ) 257 | ) 258 | ); 259 | $update->addOption( 260 | 'entryurl', 261 | array( 262 | 'short_name' => '-u', 263 | 'long_name' => '--url', 264 | 'description' => 'Ping this URL or ID', 265 | 'help_name' => 'URL|ID', 266 | 'action' => 'StoreString' 267 | ) 268 | ); 269 | } 270 | 271 | protected function setupCliPingback($p) 272 | { 273 | $handle = $p->addCommand( 274 | 'handle', 275 | array( 276 | 'description' => 'Handle pingbacks: Fetch content, extract data' 277 | ) 278 | ); 279 | } 280 | } 281 | ?> 282 | -------------------------------------------------------------------------------- /src/stapibas/Content/Extractor.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 12 | $this->db = $deps->db; 13 | $this->log = $deps->log; 14 | } 15 | 16 | /** 17 | * Extracts content from all pingbackcontent entries and puts it 18 | * into rbookmarks/rcomments/rlinks. 19 | */ 20 | public function updateAll() 21 | { 22 | $this->log->info('Extracting linkback content..'); 23 | $res = $this->db->query( 24 | 'SELECT * FROM linkbackcontent, linkbacks' 25 | . ' WHERE l_id = lc_l_id' . $this->sqlNeedsUpdate() 26 | ); 27 | $items = 0; 28 | while ($contentRow = $res->fetch(\PDO::FETCH_OBJ)) { 29 | ++$items; 30 | $this->extractContent($contentRow); 31 | } 32 | $this->log->info('Finished extracting %d linkback contents.', $items); 33 | } 34 | 35 | protected function extractContent($contentRow) 36 | { 37 | $doc = new \DOMDocument(); 38 | $typeParts = explode(';', $contentRow->lc_mime_type); 39 | $type = $typeParts[0]; 40 | if ($type == 'application/xhtml+xml' 41 | || $type == 'application/xml' 42 | || $type == 'text/xml' 43 | ) { 44 | $doc->loadXML($contentRow->lc_fulltext); 45 | } else { 46 | $doc->loadHTML($contentRow->lc_fulltext); 47 | } 48 | 49 | //delete old content 50 | $this->db->exec( 51 | 'DELETE FROM rbookmarks WHERE' 52 | . ' rb_lc_id = ' . $this->db->quote($contentRow->lc_id) 53 | ); 54 | $this->db->exec( 55 | 'DELETE FROM rcomments WHERE' 56 | . ' rc_lc_id = ' . $this->db->quote($contentRow->lc_id) 57 | ); 58 | $this->db->exec( 59 | 'DELETE FROM rlinks WHERE' 60 | . ' rl_lc_id = ' . $this->db->quote($contentRow->lc_id) 61 | ); 62 | 63 | $ce = new Content_Extractor_Comment($this->deps->log); 64 | $data = $ce->extract($doc, $contentRow->l_source, $contentRow->l_target); 65 | if ($data !== null) { 66 | $this->log->info('Comment found'); 67 | var_dump($data); 68 | $this->db->exec( 69 | 'INSERT INTO rcomments SET' 70 | . ' rc_l_id = ' . $this->db->quote($contentRow->l_id) 71 | . ', rc_lc_id = ' . $this->db->quote($contentRow->lc_id) 72 | . ', rc_source = ' . $this->db->quote($contentRow->l_source) 73 | . ', rc_target = ' . $this->db->quote($contentRow->l_target) 74 | . ', rc_title = ' . $this->db->quote($data['title']) 75 | . ', rc_author_name = ' . $this->db->quote($data['author_name']) 76 | . ', rc_author_url = ' . $this->db->quote($data['author_url']) 77 | . ', rc_author_image = ' . $this->db->quote($data['author_image']) 78 | . ', rc_content = ' . $this->db->quote($data['content']) 79 | . ', rc_updated = NOW()' 80 | ); 81 | $this->setDetectedType($contentRow, 'comment'); 82 | return; 83 | } 84 | 85 | //FIXME: bookmark 86 | 87 | $ce = new Content_Extractor_Link($this->deps->log); 88 | $data = $ce->extract($doc, $contentRow->l_source, $contentRow->l_target); 89 | if ($data !== null) { 90 | $this->log->info('Link found'); 91 | $this->db->exec( 92 | 'INSERT INTO rlinks SET' 93 | . ' rl_l_id = ' . $this->db->quote($contentRow->l_id) 94 | . ', rl_lc_id = ' . $this->db->quote($contentRow->lc_id) 95 | . ', rl_source = ' . $this->db->quote($contentRow->l_source) 96 | . ', rl_target = ' . $this->db->quote($contentRow->l_target) 97 | . ', rl_title = ' . $this->db->quote($data['title']) 98 | . ', rl_author_name = ' . $this->db->quote($data['author_name']) 99 | . ', rl_author_url = ' . $this->db->quote($data['author_url']) 100 | . ', rl_author_image = ' . $this->db->quote($data['author_image']) 101 | . ', rl_updated = NOW()' 102 | ); 103 | $this->setDetectedType($contentRow, 'link'); 104 | return; 105 | } 106 | 107 | $this->setDetectedType($contentRow, 'nothing'); 108 | $this->log->info('Nothing found'); 109 | } 110 | 111 | protected function setDetectedType($contentRow, $type) 112 | { 113 | $this->db->exec( 114 | 'UPDATE linkbackcontent' 115 | . ' SET lc_detected_type = ' . $this->db->quote($type) 116 | . ' WHERE lc_id = ' . $this->db->quote($contentRow->lc_id) 117 | ); 118 | } 119 | 120 | 121 | protected function sqlNeedsUpdate() 122 | { 123 | if ($this->deps->options['force']) { 124 | return ''; 125 | } 126 | return ' AND lc_detected_type = ""'; 127 | } 128 | 129 | } 130 | ?> 131 | -------------------------------------------------------------------------------- /src/stapibas/Content/Extractor/Base.php: -------------------------------------------------------------------------------- 1 | log = $log; 9 | } 10 | 11 | protected function extractAuthorData($hentry, $xpath, &$data, $source) 12 | { 13 | $data['author_name'] = null; 14 | $data['author_image'] = null; 15 | $data['author_url'] = null; 16 | 17 | $authors = $xpath->evaluate( 18 | './/*[' . $this->xpc('p-author') . ']' 19 | ); 20 | if ($authors->length != 1) { 21 | //no p-author, so use page author data 22 | $data['author_name'] = $this->getFirst( 23 | '/*[self::html or self::h:html]/*[self::head or self::h:head]' 24 | . '/*[(self::meta or self::h:meta) and @name="author"]', 25 | 'content', $hentry, $xpath 26 | ); 27 | 28 | $data['author_url'] = 29 | $this->absUrl( 30 | $this->getFirst( 31 | '/*[self::html or self::h:html]/*[self::head or self::h:head]' 32 | . '/*[(self::link or self::h:link) and @rel="author"]', 33 | 'href', $hentry, $xpath 34 | ), 35 | $source 36 | ); 37 | return; 38 | } 39 | 40 | $author = $authors->item(0); 41 | 42 | $data['author_name'] = $this->getFirst( 43 | './/*[' . $this->xpc('p-name') . ' or ' . $this->xpc('fn') . ']', 44 | null, $author, $xpath 45 | ); 46 | $data['author_image'] = $this->getFirst( 47 | './/*[' . $this->xpc('u-photo') . ']', 48 | 'src', $author, $xpath 49 | ); 50 | $data['author_url'] = $this->absUrl( 51 | $this->getFirst( 52 | './/*[' . $this->xpc('u-url') . ']', 53 | 'href', $author, $xpath 54 | ), 55 | $source 56 | ); 57 | } 58 | 59 | protected function getFirst($xpathExpr, $attrName, $elem, $xpath) 60 | { 61 | $items = $xpath->evaluate($xpathExpr, $elem); 62 | if (!$items instanceof \DOMNodeList || $items->length == 0) { 63 | return null; 64 | } 65 | 66 | if ($attrName === false) { 67 | return $items->item(0); 68 | } else if ($attrName == null) { 69 | return $items->item(0)->nodeValue; 70 | } else { 71 | return $items->item(0)->attributes->getNamedItem($attrName)->nodeValue; 72 | } 73 | } 74 | 75 | protected function innerHtml($element) 76 | { 77 | $innerHTML = ''; 78 | $children = $element->childNodes; 79 | foreach ($children as $child) { 80 | $tmp_dom = new \DOMDocument(); 81 | $tmp_dom->appendChild($tmp_dom->importNode($child, true)); 82 | $innerHTML .= rtrim($tmp_dom->saveHTML(), "\n"); 83 | } 84 | return trim($innerHTML); 85 | } 86 | 87 | protected function getXpath($node) 88 | { 89 | $xpath = new \DOMXPath($node); 90 | $xpath->registerNamespace('h', 'http://www.w3.org/1999/xhtml'); 91 | return $xpath; 92 | } 93 | 94 | protected function xpc($class) 95 | { 96 | return 'contains(' 97 | . 'concat(" ", normalize-space(@class), " "),' 98 | . '" ' . $class . ' "' 99 | . ')'; 100 | } 101 | 102 | protected function xpq($str) 103 | { 104 | return '"' . htmlspecialchars($str, ENT_QUOTES) . '"'; 105 | } 106 | 107 | protected function absUrl($url, $source) 108 | { 109 | if ($url === null) { 110 | return null; 111 | } 112 | $sourceUrl = new \Net_URL2($source); 113 | return (string)$sourceUrl->resolve($url); 114 | } 115 | 116 | } 117 | ?> 118 | -------------------------------------------------------------------------------- /src/stapibas/Content/Extractor/Comment.php: -------------------------------------------------------------------------------- 1 | getXpath($doc); 18 | $hentries = $xpath->query( 19 | '//*[(' . $this->xpc('h-entry') . ' or ' . $this->xpc('hentry') . ') and ' 20 | . '//*[(self::a or self::h:a) and ' 21 | . $this->xpc('u-in-reply-to') . ' and @href=' . $this->xpq($target) 22 | . ']' 23 | . ']' 24 | ); 25 | 26 | if ($hentries->length == 0) { 27 | return null; 28 | } 29 | 30 | $data = array( 31 | 'content' => null, 32 | 'title' => null, 33 | ); 34 | $hentry = $hentries->item(0); 35 | 36 | $this->extractAuthorData($hentry, $xpath, $data, $source); 37 | $content = $this->getFirst( 38 | './/*[' . $this->xpc('e-content') . ']', false, $hentry, $xpath 39 | ); 40 | if ($content) { 41 | $data['content'] = $this->innerHtml($content); 42 | } 43 | $data['title'] = $this->getFirst( 44 | './/*[' . $this->xpc('p-name') . ']', false, $hentry, $xpath 45 | ); 46 | 47 | return $data; 48 | } 49 | } 50 | ?> 51 | -------------------------------------------------------------------------------- /src/stapibas/Content/Extractor/Link.php: -------------------------------------------------------------------------------- 1 | getXpath($doc); 18 | $hentries = $xpath->query( 19 | '//*[(' . $this->xpc('h-entry') . ' or ' . $this->xpc('hentry') . ')' 20 | . ' and //*[' . $this->xpc('e-content') . ']' 21 | . ']' 22 | ); 23 | 24 | $sourceUrl = new \Net_URL2($source); 25 | $found = false; 26 | 27 | foreach ($hentries as $hentry) { 28 | $links = $xpath->query('.//*[self::a or self::h:a]', $hentry); 29 | foreach ($links as $link) { 30 | $url = (string)$sourceUrl->resolve( 31 | $link->attributes->getNamedItem('href')->nodeValue 32 | ); 33 | if ($url == $target) { 34 | $found = true; 35 | break 2; 36 | } 37 | } 38 | } 39 | 40 | if (!$found) { 41 | return null; 42 | } 43 | 44 | $data = array('title' => null); 45 | $hentry = $hentries->item(0); 46 | 47 | $this->extractAuthorData($hentry, $xpath, $data, $source); 48 | $data['title'] = trim( 49 | $this->getFirst( 50 | './/*[' . $this->xpc('p-name') . ']', null, $hentry, $xpath 51 | ) 52 | ); 53 | if ($data['title'] === null) { 54 | //use page title 55 | $data['title'] = trim( 56 | $this->getFirst( 57 | '/*[self::html or self::h:html]/*[self::head or self::h:head]' 58 | . '/*[self::title or self::h:title]', 59 | null, $hentry, $xpath 60 | ) 61 | ); 62 | } 63 | 64 | return $data; 65 | } 66 | } 67 | ?> 68 | -------------------------------------------------------------------------------- /src/stapibas/Content/Fetcher.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 12 | $this->db = $deps->db; 13 | $this->log = $deps->log; 14 | } 15 | 16 | /** 17 | * Fetches HTML content of all linkbacks that are marked as "needs update" 18 | */ 19 | public function updateAll() 20 | { 21 | $this->log->info('Fetching linkback content..'); 22 | $res = $this->db->query( 23 | 'SELECT * FROM linkbacks' 24 | . ' WHERE l_use = 1' . $this->sqlNeedsUpdate() 25 | ); 26 | $items = 0; 27 | while ($pingbackRow = $res->fetch(\PDO::FETCH_OBJ)) { 28 | ++$items; 29 | $this->updateContent($pingbackRow); 30 | } 31 | $this->log->info('Finished fetching %d linkback sources.', $items); 32 | } 33 | 34 | protected function updateContent($pingbackRow) 35 | { 36 | $this->log->info( 37 | 'Fetching pingback source #%d: %s', 38 | $pingbackRow->l_id, $pingbackRow->l_source 39 | ); 40 | 41 | $req = new \HTTP_Request2($pingbackRow->l_source); 42 | $req->setHeader('User-Agent', 'stapibas'); 43 | $req->setHeader( 44 | 'Accept', 45 | 'application/xhtml+xml; q=1' 46 | . ', text/html; q=0.5' 47 | ); 48 | 49 | $res = $req->send(); 50 | if (intval($res->getStatus() / 100) != 2) { 51 | //no 2xx is an error for us 52 | $this->log->err('Error fetching pingback source content'); 53 | return; 54 | } 55 | 56 | $qLid = $this->db->quote($pingbackRow->l_id); 57 | $this->db->exec('DELETE FROM linkbackcontent WHERE lc_l_id = ' . $qLid); 58 | $this->db->exec('DELETE FROM rbookmarks WHERE rb_l_id = ' . $qLid); 59 | $this->db->exec('DELETE FROM rcomments WHERE rc_l_id = ' . $qLid); 60 | $this->db->exec('DELETE FROM rlinks WHERE rl_l_id = ' . $qLid); 61 | 62 | $this->db->exec( 63 | 'INSERT INTO linkbackcontent SET' 64 | . ' lc_l_id = ' . $qLid 65 | . ', lc_mime_type = ' 66 | . $this->db->quote($res->getHeader('content-type')) 67 | . ', lc_fulltext = ' . $this->db->quote($res->getBody()) 68 | ); 69 | $this->db->exec( 70 | 'UPDATE linkbacks' 71 | . ' SET l_needs_update = 0' 72 | . ' WHERE l_id = ' . $this->db->quote($pingbackRow->l_id) 73 | ); 74 | } 75 | 76 | 77 | protected function sqlNeedsUpdate() 78 | { 79 | if ($this->deps->options['force']) { 80 | return ''; 81 | } 82 | return ' AND l_needs_update = 1'; 83 | } 84 | 85 | } 86 | 87 | ?> 88 | -------------------------------------------------------------------------------- /src/stapibas/Dependencies.php: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/stapibas/Feed/Manage.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 12 | $this->db = $deps->db; 13 | $this->log = $deps->log; 14 | } 15 | 16 | public function listAll() 17 | { 18 | $this->log->info('Listing all feeds..'); 19 | $res = $this->db->query('SELECT * FROM feeds ORDER BY f_id'); 20 | $items = 0; 21 | while ($feedRow = $res->fetch(\PDO::FETCH_OBJ)) { 22 | echo '#' . $feedRow->f_id . ' ' . $feedRow->f_url . "\n"; 23 | ++$items; 24 | } 25 | $this->log->info('Finished listing %d URLs.', $items); 26 | } 27 | 28 | public function addFeed($url) 29 | { 30 | if ($url == '') { 31 | echo "URL empty\n"; 32 | exit(1); 33 | } 34 | 35 | $this->db->exec( 36 | 'INSERT INTO feeds SET' 37 | . ' f_url = ' . $this->db->quote($url) 38 | . ', f_needs_update = 1' 39 | ); 40 | echo "Feed has been added\n"; 41 | } 42 | 43 | public function removeFeed($urlOrId) 44 | { 45 | if ($urlOrId == '') { 46 | echo "URL/ID empty\n"; 47 | exit(1); 48 | } 49 | 50 | if (is_numeric($urlOrId)) { 51 | $sqlWhere = ' f_id = ' . $this->db->quote($urlOrId); 52 | } else { 53 | $sqlWhere = ' f_url = ' . $this->db->quote($urlOrId); 54 | } 55 | 56 | $nRows = $this->db->exec( 57 | 'DELETE FROM feeds WHERE' . $sqlWhere 58 | ); 59 | echo sprintf("%d feed has been removed\n", $nRows);; 60 | } 61 | } 62 | ?> 63 | -------------------------------------------------------------------------------- /src/stapibas/Feed/PingUrls.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 16 | $this->db = $deps->db; 17 | $this->log = $deps->log; 18 | 19 | $this->pbc = new \PEAR2\Services\Linkback\Client(); 20 | $req = $this->pbc->getRequest(); 21 | $req->setConfig( 22 | array( 23 | 'ssl_verify_peer' => false, 24 | 'ssl_verify_host' => false 25 | ) 26 | ); 27 | $this->pbc->setRequestTemplate($req); 28 | $headers = $req->getHeaders(); 29 | $req->setHeader('user-agent', 'stapibas / ' . $headers['user-agent']); 30 | 31 | $this->pbc->setDebug(true); 32 | } 33 | 34 | public function pingAll() 35 | { 36 | $this->log->info('Pinging all URLs..'); 37 | $res = $this->db->query( 38 | 'SELECT fe_url, feu_id, feu_url, feu_tries' 39 | . ' FROM feedentries, feedentryurls' 40 | . ' WHERE fe_id = feu_fe_id' . $this->sqlNeedsUpdate() 41 | ); 42 | $items = 0; 43 | while ($row = $res->fetch(\PDO::FETCH_OBJ)) { 44 | $this->log->info( 45 | 'Pinging URL #%d: %s', $row->feu_id, $row->feu_url 46 | ); 47 | $this->ping($row); 48 | ++$items; 49 | } 50 | $this->log->info('Finished pinging %d URLs.', $items); 51 | } 52 | 53 | public function pingSome($urlOrIds) 54 | { 55 | $options = array(); 56 | foreach ($urlOrIds as $urlOrId) { 57 | if (is_numeric($urlOrId)) { 58 | $options[] = 'feu_id = ' . intval($urlOrId); 59 | } else { 60 | $options[] = 'feu_url = ' . $this->db->quote($urlOrId); 61 | } 62 | } 63 | 64 | $this->log->info('Pinging %d URLs..', count($options)); 65 | $res = $this->db->query( 66 | 'SELECT fe_url, feu_id, feu_url, feu_tries' 67 | . ' FROM feedentries, feedentryurls' 68 | . ' WHERE fe_id = feu_fe_id' 69 | . $this->sqlNeedsUpdate() 70 | . ' AND (' . implode(' OR ', $options) . ')' 71 | ); 72 | $items = 0; 73 | while ($row = $res->fetch(\PDO::FETCH_OBJ)) { 74 | $this->log->info( 75 | 'Pinging URL #%d: %s', $row->feu_id, $row->feu_url 76 | ); 77 | $this->ping($row); 78 | ++$items; 79 | } 80 | $this->log->info('Finished pinging %d URLs.', $items); 81 | } 82 | 83 | public function ping($row) 84 | { 85 | $from = $row->fe_url; 86 | $to = $row->feu_url; 87 | 88 | try { 89 | $res = $this->pbc->send($from, $to); 90 | } catch (\Exception $e) { 91 | $this->log->err('Exception: ' . $e->getMessage()); 92 | $this->db->exec( 93 | 'UPDATE feedentryurls SET' 94 | . ' feu_pinged = 1' 95 | . ', feu_updated = NOW()' 96 | . ', feu_error = 1' 97 | . ', feu_error_code = ' . $this->db->quote($e->getCode()) 98 | . ', feu_error_message = ' . $this->db->quote($e->getMessage()) 99 | . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1) 100 | . ', feu_retry = ' . $this->sqlRetry($e) 101 | . ' WHERE feu_id = ' . $this->db->quote($row->feu_id) 102 | ); 103 | return; 104 | } 105 | 106 | if (!$res->isError()) { 107 | //all fine 108 | $this->log->info('ok'); 109 | $this->db->exec( 110 | 'UPDATE feedentryurls SET' 111 | . ' feu_pinged = 1' 112 | . ', feu_updated = NOW()' 113 | . ', feu_error = 0' 114 | . ', feu_error_code = ""' 115 | . ', feu_error_message = ""' 116 | . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1) 117 | . ', feu_retry = 0' 118 | . ' WHERE feu_id = ' . $this->db->quote($row->feu_id) 119 | ); 120 | } else { 121 | //error 122 | $code = $res->getCode(); 123 | $this->log->err('Error: ' . $code . ': ' . $res->getMessage()); 124 | $httpRes = $res->getResponse(); 125 | if ($httpRes) { 126 | $this->log->info( 127 | 'Pingback response: Status code ' . $httpRes->getStatus() 128 | . ', headers: ' . print_r($httpRes->getHeader(), true) 129 | ); 130 | if ($code == 100 || $code == 101 || $code == -32600) { 131 | $this->log->info('HTTP body: ' . $httpRes->getBody()); 132 | } 133 | } 134 | $this->db->exec( 135 | 'UPDATE feedentryurls SET' 136 | . ' feu_pinged = 1' 137 | . ', feu_updated = NOW()' 138 | . ', feu_error = 1' 139 | . ', feu_error_code = ' . $this->db->quote($res->getCode()) 140 | . ', feu_error_message = ' . $this->db->quote($res->getMessage()) 141 | . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1) 142 | . ', feu_retry = ' . $this->sqlRetry($res) 143 | . ' WHERE feu_id = ' . $this->db->quote($row->feu_id) 144 | ); 145 | } 146 | } 147 | 148 | protected function sqlNeedsUpdate() 149 | { 150 | if ($this->deps->options['force']) { 151 | return ''; 152 | } 153 | $sqlRetry = '(feu_retry = 1 AND feu_tries < 5)'; 154 | //FIXME: wait at least 1 hour before retrying 155 | 156 | return ' AND feu_active = 1 AND (feu_pinged = 0 OR ' . $sqlRetry . ')'; 157 | } 158 | 159 | /** 160 | * Determines if it should be retried to pingback the URL after some time 161 | * 162 | * @param $obj mixed Exception or Pingback response 163 | */ 164 | protected function sqlRetry($obj) 165 | { 166 | if ($obj instanceof \Exception) { 167 | return '1'; 168 | } 169 | 170 | switch ($obj->getCode()) { 171 | case -32601: //they have xmp-rpc, but do not support pingback 172 | case 17: //they think we don't link to them 173 | case 18: //they think we send out pingback spam 174 | case 48: //already registered 175 | case 49: //access denied 176 | case 200: //pingback not supported 177 | case 201: //Unvalid target URI 178 | return '0'; 179 | } 180 | 181 | return '1'; 182 | } 183 | } 184 | ?> 185 | -------------------------------------------------------------------------------- /src/stapibas/Feed/UpdateEntries.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 15 | $this->db = $deps->db; 16 | $this->log = $deps->log; 17 | } 18 | 19 | public function updateAll() 20 | { 21 | $this->log->info('Updating feed entries..'); 22 | $res = $this->db->query( 23 | 'SELECT * FROM feedentries' 24 | . ' WHERE ' . $this->sqlNeedsUpdate() 25 | ); 26 | $items = 0; 27 | while ($entryRow = $res->fetch(\PDO::FETCH_OBJ)) { 28 | ++$items; 29 | $this->updateEntry($entryRow); 30 | } 31 | $this->log->info('Finished updating %d entries.', $items); 32 | } 33 | 34 | public function updateSome($urlOrIds) 35 | { 36 | $options = array(); 37 | foreach ($urlOrIds as $urlOrId) { 38 | if (is_numeric($urlOrId)) { 39 | $options[] = 'fe_id = ' . intval($urlOrId); 40 | } else { 41 | $options[] = 'fe_url = ' . $this->db->quote($urlOrId); 42 | } 43 | } 44 | 45 | $this->log->info('Updating %d feed entries..', count($options)); 46 | $res = $this->db->query( 47 | 'SELECT * FROM feedentries' 48 | . ' WHERE ' . $this->sqlNeedsUpdate() 49 | . ' AND (' . implode(' OR ', $options) . ')' 50 | ); 51 | 52 | $items = 0; 53 | while ($entryRow = $res->fetch(\PDO::FETCH_OBJ)) { 54 | ++$items; 55 | $this->updateEntry($entryRow); 56 | } 57 | $this->log->info('Finished updating %d entries.', $items); 58 | } 59 | 60 | protected function updateEntry($entryRow) 61 | { 62 | $this->log->info( 63 | 'Updating feed entry #%d: %s', $entryRow->fe_id, $entryRow->fe_url 64 | ); 65 | 66 | $req = new \HTTP_Request2($entryRow->fe_url); 67 | $req->setHeader('User-Agent', 'stapibas'); 68 | $req->setHeader( 69 | 'Accept', 70 | 'application/xhtml+xml; q=1' 71 | . ', application/xml; q=0.9' 72 | . ', text/xml; q=0.9' 73 | . ', text/html; q=0.5' 74 | . ', */*; q=0.1' 75 | ); 76 | 77 | if ($entryRow->fe_updated != '0000-00-00 00:00:00') { 78 | $req->setHeader( 79 | 'If-Modified-Since', 80 | gmdate('r', strtotime($entryRow->fe_updated)) 81 | ); 82 | } 83 | 84 | $res = $req->send(); 85 | if ($res->getStatus() == 304) { 86 | //not modified 87 | $this->setNoUpdate($entryRow); 88 | $this->log->info('Not modified'); 89 | return; 90 | } 91 | 92 | if (intval($res->getStatus() / 100) != 2) { 93 | //no 2xx is an error for us 94 | $this->log->err('Error fetching feed entry URL'); 95 | return; 96 | } 97 | 98 | $urls = $this->extractUrls($entryRow, $res); 99 | $this->updateUrls($entryRow, $urls); 100 | $this->setUpdated($entryRow, $res); 101 | } 102 | 103 | protected function updateUrls($entryRow, $urls) 104 | { 105 | $res = $this->db->query( 106 | 'SELECT * FROM feedentryurls' 107 | . ' WHERE feu_fe_id = ' . $this->db->quote($entryRow->fe_id) 108 | ); 109 | $urlRows = array(); 110 | while ($urlRow = $res->fetch(\PDO::FETCH_OBJ)) { 111 | $urlRows[$urlRow->feu_url] = $urlRow; 112 | } 113 | 114 | $urls = array_unique($urls); 115 | 116 | $new = $updated = $deleted = 0; 117 | $items = count($urls); 118 | 119 | foreach ($urls as $url) { 120 | if (!isset($urlRows[$url])) { 121 | //URL is not known - insert it 122 | $this->db->exec( 123 | 'INSERT INTO feedentryurls SET' 124 | . ' feu_fe_id = ' . $this->db->quote($entryRow->fe_id) 125 | . ', feu_url = ' . $this->db->quote($url) 126 | . ', feu_active = 1' 127 | . ', feu_pinged = 0' 128 | . ', feu_updated = NOW()' 129 | ); 130 | ++$new; 131 | } else if ($urlRows[$url]->feu_active == 0) { 132 | //URL is known already, but was once deleted and is back now 133 | $this->db->exec( 134 | 'UPDATE feedentryurls SET' 135 | . ' feu_active = 1' 136 | . ', feu_updated = NOW()' 137 | . ' WHERE feu_id = ' . $this->db->quote($urlRows[$url]->feu_id) 138 | ); 139 | ++$updated; 140 | unset($urlRows[$url]); 141 | } else { 142 | //already known, all fine 143 | unset($urlRows[$url]); 144 | } 145 | } 146 | 147 | //these URLs are in DB but not on the page anymore 148 | foreach ($urlRows as $urlRow) { 149 | ++$deleted; 150 | $this->db->exec( 151 | 'UPDATE feedentryurls SET' 152 | . ' feu_active = 0' 153 | . ', feu_updated = NOW()' 154 | . ' WHERE feu_id = ' . $this->db->quote($urlRow->feu_id) 155 | ); 156 | } 157 | $this->log->info( 158 | 'Feed entry #%d: %d new, %d updated, %d deleted of %d URLs', 159 | $entryRow->fe_id, $new, $updated, $deleted, $items 160 | ); 161 | } 162 | 163 | protected function extractUrls($entryRow, \HTTP_Request2_Response $res) 164 | { 165 | $doc = new \DOMDocument(); 166 | $typeParts = explode(';', $res->getHeader('content-type')); 167 | $type = $typeParts[0]; 168 | if ($type == 'application/xhtml+xml' 169 | || $type == 'application/xml' 170 | || $type == 'text/xml' 171 | ) { 172 | $doc->loadXML($res->getBody()); 173 | } else { 174 | $doc->loadHTML($res->getBody()); 175 | } 176 | 177 | $xpath = new \DOMXPath($doc); 178 | $xpath->registerNamespace('h', 'http://www.w3.org/1999/xhtml'); 179 | // all links in e-content AND u-in-reply-to links 180 | $query = '//*[' . $this->xpc('h-entry') . ' or ' . $this->xpc('hentry') . ']' 181 | . '//*[' . $this->xpc('e-content') . ' or ' . $this->xpc('entry-content') . ']' 182 | . '//*[(self::a or self::h:a) and @href and not(starts-with(@href, "#"))]' 183 | . ' | ' 184 | . '//*[' . $this->xpc('h-entry') . ' or ' . $this->xpc('hentry') . ']' 185 | . '//*[' 186 | . '(self::a or self::h:a) and @href and not(starts-with(@href, "#"))' 187 | . 'and ' . $this->xpc('u-in-reply-to') 188 | . ']'; 189 | ; 190 | $links = $xpath->query($query); 191 | $this->log->info('%d links found', $links->length); 192 | 193 | $entryUrl = new \Net_URL2($entryRow->fe_url); 194 | //FIXME: base URL in html code 195 | 196 | $urls = array(); 197 | foreach ($links as $link) { 198 | $url = (string)$entryUrl->resolve( 199 | $link->attributes->getNamedItem('href')->nodeValue 200 | ); 201 | $this->log->info('URL in entry: ' . $url); 202 | $urls[] = $url; 203 | } 204 | return $urls; 205 | } 206 | 207 | protected function xpc($class) 208 | { 209 | return 'contains(' 210 | . 'concat(" ", normalize-space(@class), " "),' 211 | . '" ' . $class . ' "' 212 | . ')'; 213 | } 214 | 215 | protected function setNoUpdate($entryRow) 216 | { 217 | $this->db->exec( 218 | 'UPDATE feedentries SET fe_needs_update = 0' 219 | . ' WHERE fe_id = ' . $this->db->quote($entryRow->fe_id) 220 | ); 221 | } 222 | 223 | protected function setUpdated($entryRow, \HTTP_Request2_Response $res) 224 | { 225 | $this->db->exec( 226 | 'UPDATE feedentries' 227 | . ' SET fe_needs_update = 0' 228 | . ', fe_updated = ' . $this->db->quote( 229 | gmdate('Y-m-d H:i:s', strtotime($res->getHeader('last-modified'))) 230 | ) 231 | . ' WHERE fe_id = ' . $this->db->quote($entryRow->fe_id) 232 | ); 233 | } 234 | 235 | protected function sqlNeedsUpdate() 236 | { 237 | if ($this->deps->options['force']) { 238 | return ' 1'; 239 | } 240 | return ' (fe_needs_update = 1 OR fe_updated = "0000-00-00 00:00:00")'; 241 | } 242 | } 243 | ?> 244 | -------------------------------------------------------------------------------- /src/stapibas/Feed/UpdateFeeds.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 15 | $this->db = $deps->db; 16 | $this->log = $deps->log; 17 | } 18 | 19 | public function updateAll() 20 | { 21 | $this->log->info('Updating feeds..'); 22 | $res = $this->db->query( 23 | 'SELECT * FROM feeds' 24 | . ' WHERE ' . $this->sqlNeedsUpdate() 25 | ); 26 | while ($feedRow = $res->fetch(\PDO::FETCH_OBJ)) { 27 | $this->updateFeed($feedRow); 28 | } 29 | $this->log->info('Finished updating feeds.'); 30 | } 31 | 32 | public function updateSome($urlOrIds) 33 | { 34 | $options = array(); 35 | foreach ($urlOrIds as $urlOrId) { 36 | if (is_numeric($urlOrId)) { 37 | $options[] = 'f_id = ' . intval($urlOrId); 38 | } else { 39 | $options[] = 'f_url = ' . $this->db->quote($urlOrId); 40 | } 41 | } 42 | 43 | $this->log->info('Updating %d feeds..', $options); 44 | $res = $this->db->query( 45 | 'SELECT * FROM feeds' 46 | . ' WHERE' 47 | . $this->sqlNeedsUpdate() 48 | . ' AND (' . implode(' OR ', $options) . ')' 49 | ); 50 | 51 | $items = 0; 52 | while ($feedRow = $res->fetch(\PDO::FETCH_OBJ)) { 53 | ++$items; 54 | $this->updateFeed($feedRow); 55 | } 56 | $this->log->info('Finished updating %d feeds.', $items); 57 | } 58 | 59 | protected function updateFeed($feedRow) 60 | { 61 | $this->log->info( 62 | 'Updating feed #%d: %s', $feedRow->f_id, $feedRow->f_url 63 | ); 64 | 65 | $req = new \HTTP_Request2($feedRow->f_url); 66 | $req->setHeader('User-Agent', 'stapibas'); 67 | 68 | if ($feedRow->f_updated != '0000-00-00 00:00:00') { 69 | $req->setHeader( 70 | 'If-Modified-Since', 71 | gmdate('r', strtotime($feedRow->f_updated)) 72 | ); 73 | } 74 | 75 | $res = $req->send(); 76 | if ($res->getStatus() == 304) { 77 | //not modified 78 | $this->setNoUpdate($feedRow); 79 | $this->log->info('Not modified'); 80 | return; 81 | } 82 | 83 | if (intval($res->getStatus() / 100) != 2) { 84 | //no 2xx is an error for us 85 | $this->log->err('Error fetching feed'); 86 | return; 87 | } 88 | 89 | $this->updateEntries($feedRow, $res); 90 | } 91 | 92 | protected function updateEntries($feedRow, \HTTP_Request2_Response $res) 93 | { 94 | require_once $GLOBALS['stapibas_libdir'] . '/simplepie/autoloader.php'; 95 | $sp = new \SimplePie(); 96 | $sp->set_raw_data($res->getBody()); 97 | $sp->init(); 98 | 99 | $new = $updated = $items = 0; 100 | foreach ($sp->get_items() as $item) { 101 | ++$items; 102 | $url = $item->get_permalink(); 103 | $entryRow = $this->db->query( 104 | 'SELECT fe_id, fe_updated, fe_needs_update FROM feedentries' 105 | . ' WHERE fe_url = ' . $this->db->quote($url) 106 | . ' AND fe_f_id = ' . $this->db->quote($feedRow->f_id) 107 | )->fetch(\PDO::FETCH_OBJ); 108 | 109 | if ($entryRow === false) { 110 | //new item! 111 | $this->db->exec( 112 | 'INSERT INTO feedentries SET' 113 | . ' fe_f_id = ' . $this->db->quote($feedRow->f_id) 114 | . ', fe_url = ' . $this->db->quote($url) 115 | . ', fe_needs_update = 1' 116 | ); 117 | ++$new; 118 | continue; 119 | } 120 | if ($entryRow->fe_needs_update == 0 121 | && $item->get_updated_gmdate('U') > strtotime($entryRow->fe_updated) 122 | ) { 123 | //updated 124 | $this->db->exec( 125 | 'UPDATE feedentries SET' 126 | . ' fe_url = ' . $this->db->quote($url) 127 | . ', fe_needs_update = 1' 128 | . ' WHERE fe_id = ' . $this->db->quote($entryRow->fe_id) 129 | ); 130 | ++$updated; 131 | continue; 132 | } 133 | } 134 | $this->log->info( 135 | 'Feed #%d: %d new, %d updated of %d entries', 136 | $feedRow->f_id, $new, $updated, $items 137 | ); 138 | $this->setUpdated($feedRow, $res); 139 | } 140 | 141 | protected function setNoUpdate($feedRow) 142 | { 143 | $this->db->exec( 144 | 'UPDATE feeds SET f_needs_update = 0' 145 | . ' WHERE f_id = ' . $this->db->quote($feedRow->f_id) 146 | ); 147 | } 148 | 149 | protected function setUpdated($feedRow, \HTTP_Request2_Response $res) 150 | { 151 | $this->db->exec( 152 | 'UPDATE feeds' 153 | . ' SET f_needs_update = 0' 154 | . ', f_updated = ' . $this->db->quote( 155 | gmdate('Y-m-d H:i:s', strtotime($res->getHeader('last-modified'))) 156 | ) 157 | . ' WHERE f_id = ' . $this->db->quote($feedRow->f_id) 158 | ); 159 | } 160 | 161 | protected function sqlNeedsUpdate() 162 | { 163 | if ($this->deps->options['force']) { 164 | return ' 1'; 165 | } 166 | return ' (f_needs_update = 1 OR f_updated = "0000-00-00 00:00:00")'; 167 | } 168 | } 169 | ?> -------------------------------------------------------------------------------- /src/stapibas/Linkback/DbStorage.php: -------------------------------------------------------------------------------- 1 | db = $db; 12 | } 13 | 14 | /** 15 | * Verifies that the given target URI exists in our system. 16 | * 17 | * @param string $target Target URI that got linked to 18 | * 19 | * @return boolean True if the target URI exists, false if not 20 | * 21 | * @throws Exception When something fatally fails 22 | */ 23 | public function verifyTargetExists($target) 24 | { 25 | $res = $this->db->query( 26 | 'SELECT COUNT(*) as count FROM linkbacktargets' 27 | . ' WHERE ' . $this->db->quote($target) . ' LIKE lt_url' 28 | ); 29 | $answer = $res->fetch(\PDO::FETCH_OBJ); 30 | if ($answer->count == 0) { 31 | throw new \Exception( 32 | 'The specified target URI is not allowed as target.', 33 | 33 34 | ); 35 | } 36 | 37 | return true; 38 | } 39 | 40 | public function storeLinkback( 41 | $target, $source, $sourceBody, \HTTP_Request2_Response $res 42 | ) { 43 | if ($this->alreadyExists($target, $source)) { 44 | throw new \Exception( 45 | 'Linkback from ' . $source . ' to ' . $target 46 | . ' has already been registered.', 47 | 48 48 | ); 49 | } 50 | $stmt = $this->db->prepare( 51 | 'INSERT INTO linkbacks SET' 52 | . ' l_source = :source' 53 | . ', l_target = :target' 54 | . ', l_time = NOW()' 55 | . ', l_client_ip = :ip' 56 | . ', l_client_agent = :agent' 57 | . ', l_client_referer = :referer' 58 | . ', l_needs_review = 1' 59 | . ', l_use = 1' 60 | . ', l_needs_update = 1' 61 | ); 62 | $stmt->execute( 63 | array( 64 | ':source' => $source, 65 | ':target' => $target, 66 | ':ip' => isset($_SERVER['REMOTE_ADDR']) 67 | ? $_SERVER['REMOTE_ADDR'] : '', 68 | ':agent' => isset($_SERVER['HTTP_USER_AGENT']) 69 | ? $_SERVER['HTTP_USER_AGENT'] : '', 70 | ':referer' => isset($_SERVER['HTTP_REFERER']) 71 | ? $_SERVER['HTTP_REFERER'] : '', 72 | ) 73 | ); 74 | } 75 | 76 | protected function alreadyExists($target, $source) 77 | { 78 | $res = $this->db->query( 79 | 'SELECT COUNT(*) as count FROM linkbacks' 80 | . ' WHERE l_source = ' . $this->db->quote($source) 81 | . ' AND l_target = ' . $this->db->quote($target) 82 | ); 83 | $answer = $res->fetch(\PDO::FETCH_OBJ); 84 | return $answer->count > 0; 85 | } 86 | 87 | /** 88 | * Verifies that a link from $source to $target exists. 89 | * 90 | * @param string $target Target URI that should be linked in $source 91 | * @param string $source Linkback source URI that should link to target 92 | * @param string $sourceBody Content of $source URI 93 | * @param object $res HTTP response from fetching $source 94 | * 95 | * @return boolean True if $source links to $target 96 | * 97 | * @throws Exception When something fatally fails 98 | */ 99 | public function verifyLinkExists( 100 | $target, $source, $sourceBody, \HTTP_Request2_Response $res 101 | ) { 102 | return false; 103 | } 104 | } 105 | ?> 106 | -------------------------------------------------------------------------------- /src/stapibas/Linkback/Mailer.php: -------------------------------------------------------------------------------- 1 | ' . $target . "\n" 15 | . "from\n" 16 | . '> ' . $source . "\n" 17 | . "\n\nLove, stapibas", 18 | "From: stapibas " 19 | ); 20 | } 21 | } 22 | ?> 23 | -------------------------------------------------------------------------------- /src/stapibas/Logger.php: -------------------------------------------------------------------------------- 1 | 1) { 12 | $msg = call_user_func_array('sprintf', $args); 13 | } 14 | file_put_contents('php://stderr', $msg . "\n"); 15 | } 16 | 17 | public function info($msg) 18 | { 19 | if ($this->debug == 1) { 20 | $args = func_get_args(); 21 | call_user_func_array(array($this, 'log'), $args); 22 | } 23 | } 24 | 25 | public function log($msg) 26 | { 27 | $args = func_get_args(); 28 | if (count($args) > 1) { 29 | $msg = call_user_func_array('sprintf', $args); 30 | } 31 | echo $msg . "\n"; 32 | } 33 | } 34 | 35 | ?> 36 | -------------------------------------------------------------------------------- /src/stapibas/PDO.php: -------------------------------------------------------------------------------- 1 | handleError(); 19 | } 20 | 21 | public function exec(string $statement): int|false 22 | { 23 | $res = parent::exec($statement); 24 | if ($res !== false) { 25 | return $res; 26 | } 27 | 28 | $this->handleError(); 29 | } 30 | 31 | protected function handleError() 32 | { 33 | echo "SQL error\n"; 34 | echo " " . $this->errorCode() . "\n"; 35 | echo " " . implode(' - ', $this->errorInfo()) . "\n"; 36 | exit(2); 37 | } 38 | } 39 | 40 | ?> 41 | -------------------------------------------------------------------------------- /src/stapibas/Renderer/Html.php: -------------------------------------------------------------------------------- 1 | deps = $deps; 12 | $this->db = $deps->db; 13 | $this->log = $deps->log; 14 | 15 | \Twig_Autoloader::register(); 16 | $loader = new \Twig_Loader_Filesystem($this->deps->options['template_dir']); 17 | $this->deps->twig = new \Twig_Environment( 18 | $loader, 19 | array( 20 | //'cache' => '/path/to/compilation_cache', 21 | 'debug' => true 22 | ) 23 | ); 24 | } 25 | 26 | public function render($url) 27 | { 28 | $arData = $this->loadData($url); 29 | header('Content-type: text/html'); 30 | $this->renderHtml('mentions', array('arData' => $arData)); 31 | } 32 | 33 | /** 34 | * Fetches all bookmarks, comments and links 35 | */ 36 | protected function loadData($url) 37 | { 38 | $arData = array( 39 | 'bookmarks' => array(), 40 | 'comments' => array(), 41 | 'links' => array(), 42 | ); 43 | 44 | $stmt = $this->db->query( 45 | 'SELECT * FROM linkbacks, rbookmarks' 46 | . ' WHERE l_id = rb_l_id AND l_use = 1' 47 | . ' AND l_target = ' . $this->db->quote($url) 48 | . ' ORDER BY l_time ASC' 49 | ); 50 | $arData['bookmarks'] = $stmt->fetchAll(); 51 | 52 | $stmt = $this->db->query( 53 | 'SELECT * FROM linkbacks, rcomments' 54 | . ' WHERE l_id = rc_l_id AND l_use = 1' 55 | . ' AND l_target = ' . $this->db->quote($url) 56 | . ' ORDER BY l_time ASC' 57 | ); 58 | $arData['comments'] = $stmt->fetchAll(); 59 | 60 | $stmt = $this->db->query( 61 | 'SELECT * FROM linkbacks, rlinks' 62 | . ' WHERE l_id = rl_l_id AND l_use = 1' 63 | . ' AND l_target = ' . $this->db->quote($url) 64 | . ' ORDER BY l_time ASC' 65 | ); 66 | $arData['links'] = $stmt->fetchAll(); 67 | 68 | return $arData; 69 | } 70 | 71 | protected function renderHtml($tplname, $vars = array()) 72 | { 73 | $template = $this->deps->twig->loadTemplate($tplname . '.htm'); 74 | echo $template->render($vars); 75 | } 76 | 77 | } 78 | ?> 79 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ../src/ 5 | ../tests/ 6 | 7 | 8 | 9 | ../src/ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/stapibas/Content/Extractor/CommentTest.php: -------------------------------------------------------------------------------- 1 | loadHtmlFile(__DIR__ . '/data/aaron-parecki.html'); 11 | $source = 'http://aaronparecki.com/replies/2013/04/19/2/indieweb'; 12 | $target = 'http://eschnou.com/entry/testing-indieweb-federation-with-waterpigscouk-aaronpareckicom-and--62-24908.html'; 13 | 14 | $logger = new Logger(); 15 | $logger->debug = true; 16 | $cec = new Content_Extractor_Comment($logger); 17 | $comment = $cec->extract($doc, $source, $target); 18 | 19 | $this->assertNotNull($comment, 'No extracted data'); 20 | $this->assertEquals( 21 | 'Aaron Parecki', 22 | $comment['author_name'], 23 | 'author name error' 24 | ); 25 | $this->assertEquals( 26 | 'http://aaronparecki.com/images/aaronpk.png', 27 | $comment['author_image'] 28 | ); 29 | $this->assertEquals( 30 | 'http://aaronparecki.com/', 31 | $comment['author_url'] 32 | ); 33 | 34 | $this->assertEquals( 35 | <<@eschnou It worked! Now here's a reply! #indieweb 37 | HTM 38 | , 39 | $comment['content'] 40 | ); 41 | } 42 | } 43 | ?> 44 | -------------------------------------------------------------------------------- /tests/stapibas/Content/Extractor/LinkTest.php: -------------------------------------------------------------------------------- 1 | loadHtmlFile(__DIR__ . '/data/shadowbox-popup-positioning.htm'); 11 | $source = 'http://www.bogo/tagebuch/shadowbox-popup-positioning.htm'; 12 | $target = 'http://www.bogo/tagebuch/demo/shadowbox-manual-positioning/static.html'; 13 | 14 | $logger = new Logger(); 15 | $logger->debug = true; 16 | $cel = new Content_Extractor_Link($logger); 17 | $link = $cel->extract($doc, $source, $target); 18 | 19 | $this->assertNotNull($link, 'No extracted data'); 20 | 21 | $this->assertEquals( 22 | 'Shadowbox: Manual popup positioning', 23 | $link['title'] 24 | ); 25 | 26 | $this->assertEquals('Christian Weiske', $link['author_name']); 27 | $this->assertNull($link['author_image']); 28 | $this->assertEquals('http://www.bogo/', $link['author_url']); 29 | } 30 | 31 | public function testExtractXmlShadowBox() 32 | { 33 | $doc = new \DOMDocument(); 34 | @$doc->load(__DIR__ . '/data/shadowbox-popup-positioning.htm'); 35 | $source = 'http://www.bogo/tagebuch/shadowbox-popup-positioning.htm'; 36 | $target = 'http://www.bogo/tagebuch/demo/shadowbox-manual-positioning/static.html'; 37 | 38 | $logger = new Logger(); 39 | $logger->debug = true; 40 | $cel = new Content_Extractor_Link($logger); 41 | $link = $cel->extract($doc, $source, $target); 42 | 43 | $this->assertNotNull($link, 'No extracted data'); 44 | 45 | $this->assertEquals( 46 | 'Shadowbox: Manual popup positioning', 47 | $link['title'] 48 | ); 49 | 50 | $this->assertEquals('Christian Weiske', $link['author_name']); 51 | $this->assertNull($link['author_image']); 52 | $this->assertEquals('http://www.bogo/', $link['author_url']); 53 | } 54 | 55 | public function testExtractLaurent() 56 | { 57 | $doc = new \DOMDocument(); 58 | @$doc->loadHtmlFile(__DIR__ . '/data/laurent-eschenauer.html'); 59 | $source = 'http://eschnou.com/entry/testing-indieweb-federation-with-waterpigscouk-aaronpareckicom-and--62-24908.html'; 60 | $target = 'http://indiewebcamp.com'; 61 | 62 | $logger = new Logger(); 63 | $logger->debug = true; 64 | $cel = new Content_Extractor_Link($logger); 65 | $link = $cel->extract($doc, $source, $target); 66 | 67 | $this->assertNotNull($link, 'No extracted data'); 68 | 69 | $this->assertEquals( 70 | 'Testing #indieweb federation with @waterpigs.co.uk, @aaronparecki.com and @indiewebcamp.com !', 71 | $link['title'] 72 | ); 73 | 74 | $this->assertNull($link['author_name']); 75 | $this->assertNull($link['author_image']); 76 | $this->assertNull($link['author_url']); 77 | } 78 | 79 | } 80 | ?> 81 | -------------------------------------------------------------------------------- /tests/stapibas/Content/Extractor/data/aaron-parecki.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @eschnou It worked! Now here's a reply! #indieweb - Aaron Parecki 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 60 | 61 | 62 | 63 | 64 |
65 | 72 | 85 | 97 | 98 |
99 | 100 |
101 |
102 |
103 | 104 |
105 | 112 |
113 | 114 |
115 | 116 |
117 | 118 | Aaron Parecki 119 | 120 | 121 |
122 |
123 | 124 |
@eschnou It worked! Now here's a reply! #indieweb
125 | 126 | 127 | 129 | 130 |
131 | 132 | 133 |
134 |
135 | 136 | 169 | 170 |
171 |
172 |
173 | 174 | 175 |
176 | 177 |
178 | 179 | 187 | 188 |
189 | 190 |
191 |

© 1999-2013 by Aaron Parecki.

192 |

193 | Except where otherwise noted, text content on this site is licensed under a Creative Commons Attribution 3.0 License. Creative Commons Attribution 3.0 194 |

195 |

196 | This site is powered by p3k. 197 |

198 |
199 |
200 |
201 | 202 | 203 | 204 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /tests/stapibas/Content/Extractor/data/shadowbox-popup-positioning.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shadowbox: Manual popup positioning 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 55 | 56 |
57 |

Shadowbox: Manual popup positioning

58 | 59 |
60 | 61 |
62 |

63 | This article has originally been published on my employer's 64 | blog: 65 | 66 | Shadowbox: Manual popup positioning @ netresearch 67 | . 68 |

69 |
70 | 71 |

72 | Shadowbox can be used to display 73 | images, videos or other HTML pages in a popup on your website. 74 | Sometimes it is necessary to manually adjust the position of the overlay 75 | window, for example when using it in an iframe with a very large 76 | height setting. 77 | Shadowbox itself does not offer a hook to modify the position, but with some 78 | JavaScript trickery it is possible to manipulate the position nevertheless. 79 |

80 |

81 | The idea is - since we have no hook to register with - to replace the 82 | original positioning method with our own. 83 | Since JavaScript allows method renaming, this is fairly easy. 84 |

85 | 86 | 87 |

Static position

88 |

89 | Shadowbox uses method setDimensions() to calculate and set position 90 | and size of the popup window. 91 | We rename it and put our own method at this place: 92 |

93 |

 94 | window.Shadowbox.setDimensionsOld = window.Shadowbox.setDimensions;
 95 | window.Shadowbox.setDimensions = function (height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect) {
 96 |     var S = window.Shadowbox;
 97 |     window.Shadowbox.setDimensionsOld(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect);
 98 |     window.Shadowbox.dimensions.top = 10;
 99 |     return window.Shadowbox.dimensions;
100 | }
101 | 
102 | ]]>
103 |

104 | Now we have our shadowbox popup fixed at 10 pixels from the top of the page. 105 |

106 |

107 | Have a look at the 108 | static positioning demo. 109 |

110 | 111 | 112 |

Dynamic position

113 |

114 | When you have an iframe with some several thousand pixels in height, 115 | you don't want to have a fixed position on top but a position near the mouse 116 | cursor or the element that has been clicked. 117 |

118 |

119 | The following code positions the popup 10 pixels below the object that has 120 | been clicked to open the overlay: 121 |

122 |

123 | window.Shadowbox.setDimensionsOld = window.Shadowbox.setDimensions;
124 | window.Shadowbox.setDimensions = function (height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect) {
125 |     var S = window.Shadowbox;
126 |     window.Shadowbox.setDimensionsOld(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect);
127 |     if (window.shadowboxClickObj && window.shadowboxClickObj.link) {
128 |         var offset = $(window.shadowboxClickObj.link).offset();
129 |         window.Shadowbox.dimensions.top = offset.top + 10;
130 |         $('#sb-container').css({position: 'absolute', 'height': $(document).height()});
131 |     }
132 |     return window.Shadowbox.dimensions
133 | }
134 | 
135 | window.Shadowbox.skin.onOpenOld = window.Shadowbox.skin.onOpen;
136 | window.Shadowbox.skin.onOpen = function(obj, callback) {
137 |     window.shadowboxClickObj = obj;
138 |     window.Shadowbox.skin.onOpenOld(obj, callback);
139 | }
140 | 
141 | ]]>
142 |

143 | Here, onOpen() needs to be overwritten as well because the clicked 144 | object is not available anymore in setDimensions(). 145 |

146 |

147 | Have a look at the 148 | dynamic positioning demo. 149 |

150 | 151 |
152 |
153 |

154 | Comments? Please 155 | send an e-mail. 156 |

157 |
158 |
159 | 160 | -------------------------------------------------------------------------------- /www/api/links.php: -------------------------------------------------------------------------------- 1 | query( 19 | 'SELECT * FROM feedentries WHERE fe_url = ' . $db->quote($url) 20 | ); 21 | $urlRow = $res->fetch(PDO::FETCH_OBJ); 22 | if ($urlRow === false) { 23 | header('HTTP/1.0 404 Not Found'); 24 | echo "Url not found\n"; 25 | exit(1); 26 | } 27 | $json = (object) array( 28 | 'url' => $urlRow->fe_url, 29 | 'updated' => $urlRow->fe_updated, 30 | 'needsUpdate' => (bool) $urlRow->fe_needs_update, 31 | 'links' => array() 32 | ); 33 | 34 | $res = $db->query( 35 | 'SELECT * FROM feedentryurls' 36 | . ' WHERE feu_fe_id = ' . $db->quote($urlRow->fe_id) 37 | ); 38 | while ($linkRow = $res->fetch(\PDO::FETCH_OBJ)) { 39 | $status = null; 40 | if (!$linkRow->feu_pinged) { 41 | $status = 'queued'; 42 | } else if ($linkRow->feu_retry && $linkRow->feu_tries < 5) { 43 | $status = 'pinging'; 44 | } else if ($linkRow->feu_error) { 45 | if ($linkRow->feu_error_code == 200) { 46 | $status = 'unsupported'; 47 | } else { 48 | $status = 'error'; 49 | } 50 | } else { 51 | $status = 'ok'; 52 | } 53 | $json->links[$linkRow->feu_url] = (object) array( 54 | 'url' => $linkRow->feu_url, 55 | 'pinged' => (bool) $linkRow->feu_pinged, 56 | 'updated' => $linkRow->feu_updated, 57 | 'status' => $status, 58 | 'error' => (object) array( 59 | 'code' => $linkRow->feu_error_code, 60 | 'message' => $linkRow->feu_error_message 61 | ), 62 | 'tries' => $linkRow->feu_tries 63 | ); 64 | } 65 | 66 | header('HTTP/1.0 200 OK'); 67 | header('Content-type: application/json'); 68 | echo json_encode($json) . "\n"; 69 | ?> 70 | -------------------------------------------------------------------------------- /www/css/jquery-0.6.1.smallipop.css: -------------------------------------------------------------------------------- 1 | /* smallipop css */ 2 | .smallipop-hint { 3 | display: none; 4 | } 5 | 6 | #smallipop-tour-overlay { 7 | position: fixed; 8 | left: 0; 9 | top: 0; 10 | bottom: 0; 11 | right: 0; 12 | } 13 | 14 | .smallipop-instance { 15 | position: absolute; 16 | display: none; 17 | top: 0; 18 | left: 0; 19 | background-color: #314b64; 20 | border: 1px solid #0f161e; 21 | color: #d2dfe7; 22 | z-index: 9999; 23 | max-width: 400px; 24 | } 25 | .smallipop-instance font { 26 | size: 11px; 27 | family: arial; 28 | } 29 | .smallipop-instance a { 30 | color: #98cbea; 31 | } 32 | .smallipop-instance:before, .smallipop-instance:after { 33 | content: ''; 34 | position: absolute; 35 | left: 50%; 36 | height: 0; 37 | width: 0; 38 | pointer-events: none; 39 | } 40 | .smallipop-instance:before { 41 | bottom: -20px; 42 | margin-left: -10px; 43 | border: 10px solid transparent; 44 | } 45 | .smallipop-instance:after { 46 | bottom: -24px; 47 | margin-left: -12px; 48 | border: 12px solid transparent; 49 | } 50 | 51 | .smallipop-align-left:before, .smallipop-align-left:after { 52 | margin-left: 0; 53 | left: auto; 54 | right: 20px; 55 | } 56 | .smallipop-align-left:after { 57 | right: 18px; 58 | } 59 | 60 | .smallipop-align-right:before, .smallipop-align-right:after { 61 | margin-left: 0; 62 | left: 20px; 63 | right: auto; 64 | } 65 | .smallipop-align-right:after { 66 | left: 18px; 67 | } 68 | 69 | .smallipop-bottom:before, .smallipop-bottom:after { 70 | bottom: auto; 71 | top: -20px; 72 | } 73 | .smallipop-bottom:after { 74 | top: -24px; 75 | } 76 | 77 | .smallipop-left:before, .smallipop-left:after, 78 | .smallipop-right:before, 79 | .smallipop-right:after { 80 | right: -16px; 81 | left: auto; 82 | top: 50%; 83 | bottom: auto; 84 | border-width: 8px; 85 | margin: -8px 0 0; 86 | } 87 | .smallipop-left:after, 88 | .smallipop-right:after { 89 | right: -20px; 90 | border-width: 10px; 91 | margin: -10px 0 0; 92 | } 93 | 94 | .smallipop-right:before, .smallipop-right:after { 95 | left: -16px; 96 | right: auto; 97 | } 98 | .smallipop-right:after { 99 | left: -20px; 100 | } 101 | 102 | .smallipop-content { 103 | padding: 10px; 104 | } 105 | 106 | .smallipop-tour-content { 107 | padding: 5px 0; 108 | min-width: 150px; 109 | } 110 | 111 | .smallipop-tour-footer { 112 | padding-top: 5px; 113 | position: relative; 114 | overflow: hidden; 115 | *zoom: 1; 116 | } 117 | 118 | .smallipop-tour-progress { 119 | color: #bbb; 120 | text-align: center; 121 | position: absolute; 122 | left: 50%; 123 | width: 80px; 124 | margin-left: -40px; 125 | top: 8px; 126 | } 127 | 128 | .smallipop-tour-close-icon { 129 | position: absolute; 130 | right: -8px; 131 | top: -8px; 132 | width: 16px; 133 | height: 16px; 134 | padding-top: 0px; 135 | font-size: 11px; 136 | background: #555; 137 | color: #ccc; 138 | text-align: center; 139 | text-shadow: 0 -1px 1px #666666; 140 | text-decoration: none; 141 | -webkit-border-radius: 8px; 142 | -moz-border-radius: 8px; 143 | -ms-border-radius: 8px; 144 | -o-border-radius: 8px; 145 | border-radius: 8px; 146 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); 147 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); 148 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); 149 | } 150 | .smallipop-tour-close-icon:hover { 151 | text-decoration: none; 152 | background: #666; 153 | color: #fff; 154 | } 155 | 156 | .smallipop-tour-prev, 157 | .smallipop-tour-next, 158 | .smallipop-tour-close { 159 | color: #ccc; 160 | display: block; 161 | padding: 3px 4px 2px; 162 | line-height: 1em; 163 | float: left; 164 | background: #203142; 165 | -webkit-border-radius: 3px; 166 | -moz-border-radius: 3px; 167 | -ms-border-radius: 3px; 168 | -o-border-radius: 3px; 169 | border-radius: 3px; 170 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 171 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 172 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 173 | } 174 | .smallipop-tour-prev:hover, 175 | .smallipop-tour-next:hover, 176 | .smallipop-tour-close:hover { 177 | color: #fff; 178 | background: #293e53; 179 | text-decoration: none; 180 | } 181 | 182 | .smallipop-tour-next, 183 | .smallipop-tour-close { 184 | float: right; 185 | } 186 | 187 | /* default theme */ 188 | .smallipop-theme-default { 189 | text-shadow: 0 -1px 1px #0f161e; 190 | -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 191 | -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 192 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 193 | -webkit-border-radius: 12px; 194 | -moz-border-radius: 12px; 195 | -ms-border-radius: 12px; 196 | -o-border-radius: 12px; 197 | border-radius: 12px; 198 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(49, 75, 100, 0.9)), color-stop(100%, rgba(26, 38, 52, 0.9))); 199 | background: -webkit-linear-gradient(rgba(49, 75, 100, 0.9), rgba(26, 38, 52, 0.9)); 200 | background: -moz-linear-gradient(rgba(49, 75, 100, 0.9), rgba(26, 38, 52, 0.9)); 201 | background: -o-linear-gradient(rgba(49, 75, 100, 0.9), rgba(26, 38, 52, 0.9)); 202 | background: linear-gradient(rgba(49, 75, 100, 0.9), rgba(26, 38, 52, 0.9)); 203 | /* Fallback for opera */ 204 | background: -webkit-gradient(radial, 50% -100px, 0, 50% -100px, 150, color-stop(66.66667%, rgba(49, 75, 100, 0.9)), color-stop(86.66667%, rgba(33, 50, 66, 0.9)), color-stop(100%, rgba(26, 38, 52, 0.9))); 205 | background: -webkit-radial-gradient(50% -100px, circle contain, rgba(49, 75, 100, 0.9) 100px, rgba(33, 50, 66, 0.9) 130px, rgba(26, 38, 52, 0.9) 150px); 206 | background: -moz-radial-gradient(50% -100px, circle contain, rgba(49, 75, 100, 0.9) 100px, rgba(33, 50, 66, 0.9) 130px, rgba(26, 38, 52, 0.9) 150px); 207 | background: -o-radial-gradient(50% -100px, circle contain, rgba(49, 75, 100, 0.9) 100px, rgba(33, 50, 66, 0.9) 130px, rgba(26, 38, 52, 0.9) 150px); 208 | background: radial-gradient(50% -100px, circle contain, rgba(49, 75, 100, 0.9) 100px, rgba(33, 50, 66, 0.9) 130px, rgba(26, 38, 52, 0.9) 150px); 209 | } 210 | .smallipop-theme-default a { 211 | text-shadow: 0 -1px 1px #0f161e; 212 | } 213 | .smallipop-theme-default .smallipop-content { 214 | border-top: 1px solid #586d82; 215 | -webkit-border-radius: 12px; 216 | -moz-border-radius: 12px; 217 | -ms-border-radius: 12px; 218 | -o-border-radius: 12px; 219 | border-radius: 12px; 220 | } 221 | .smallipop-theme-default:before { 222 | border-color: #1a2634 transparent transparent transparent; 223 | } 224 | .smallipop-theme-default:after { 225 | border-color: #0f161e transparent transparent transparent; 226 | } 227 | .smallipop-theme-default.smallipop-bottom:before { 228 | border-color: transparent transparent #1a2634 transparent; 229 | } 230 | .smallipop-theme-default.smallipop-bottom:after { 231 | border-color: transparent transparent #0f161e transparent; 232 | } 233 | .smallipop-theme-default.smallipop-left:before { 234 | border-color: transparent transparent transparent #1a2634; 235 | } 236 | .smallipop-theme-default.smallipop-left:after { 237 | border-color: transparent transparent transparent #0f161e; 238 | } 239 | .smallipop-theme-default.smallipop-right:before { 240 | border-color: transparent #1a2634 transparent transparent; 241 | } 242 | .smallipop-theme-default.smallipop-right:after { 243 | border-color: transparent #0f161e transparent transparent; 244 | } 245 | 246 | .cssgradients.rgba .smallipop-theme-default { 247 | background-color: transparent; 248 | } 249 | 250 | /* blue theme */ 251 | .smallipop-theme-blue { 252 | background: transparent; 253 | color: #111; 254 | border: 10px solid rgba(0, 100, 180, 0.9); 255 | -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 256 | -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 257 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 258 | -webkit-border-radius: 7px; 259 | -moz-border-radius: 7px; 260 | -ms-border-radius: 7px; 261 | -o-border-radius: 7px; 262 | border-radius: 7px; 263 | } 264 | .smallipop-theme-blue a { 265 | color: #2276aa; 266 | } 267 | .smallipop-theme-blue:after { 268 | bottom: -34px; 269 | border-color: rgba(0, 100, 180, 0.9) transparent transparent transparent; 270 | } 271 | .smallipop-theme-blue:before { 272 | display: none; 273 | } 274 | .smallipop-theme-blue.smallipop-bottom:after { 275 | top: -34px; 276 | border-color: transparent transparent rgba(0, 100, 180, 0.9) transparent; 277 | } 278 | .smallipop-theme-blue.smallipop-left { 279 | right: -26px; 280 | } 281 | .smallipop-theme-blue.smallipop-left:after { 282 | border-color: transparent transparent transparent rgba(0, 100, 180, 0.9); 283 | } 284 | .smallipop-theme-blue.smallipop-right { 285 | left: -26px; 286 | } 287 | .smallipop-theme-blue.smallipop-right:after { 288 | border-color: transparent rgba(0, 100, 180, 0.9) transparent transparent; 289 | } 290 | .smallipop-theme-blue .smallipop-content { 291 | border: 0; 292 | background: #fcfcfc; 293 | } 294 | .smallipop-theme-blue .smallipop-tour-progress { 295 | color: #777; 296 | } 297 | .smallipop-theme-blue .smallipop-tour-prev, 298 | .smallipop-theme-blue .smallipop-tour-next, 299 | .smallipop-theme-blue .smallipop-tour-close { 300 | color: #222; 301 | background: #efefef; 302 | } 303 | .smallipop-theme-blue .smallipop-tour-prev:hover, 304 | .smallipop-theme-blue .smallipop-tour-next:hover, 305 | .smallipop-theme-blue .smallipop-tour-close:hover { 306 | color: #111; 307 | background: #f7f7f7; 308 | } 309 | 310 | /* black theme */ 311 | .smallipop-theme-black { 312 | background-color: #222222; 313 | border-color: #111; 314 | text-shadow: 0 -1px 1px #111111; 315 | color: #f5f5f5; 316 | -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 317 | -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 318 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 319 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #333333), color-stop(100%, #222222)); 320 | background: -webkit-linear-gradient(#333333, #222222); 321 | background: -moz-linear-gradient(#333333, #222222); 322 | background: -o-linear-gradient(#333333, #222222); 323 | background: linear-gradient(#333333, #222222); 324 | -webkit-border-radius: 5px; 325 | -moz-border-radius: 5px; 326 | -ms-border-radius: 5px; 327 | -o-border-radius: 5px; 328 | border-radius: 5px; 329 | } 330 | .smallipop-theme-black a { 331 | color: #eef8ff; 332 | text-shadow: 0 -1px 1px #111111; 333 | } 334 | .smallipop-theme-black:before { 335 | border-color: #222222 transparent transparent transparent; 336 | } 337 | .smallipop-theme-black:after { 338 | border-color: #111111 transparent transparent transparent; 339 | } 340 | .smallipop-theme-black.smallipop-bottom:before { 341 | border-color: transparent transparent #222222 transparent; 342 | } 343 | .smallipop-theme-black.smallipop-bottom:after { 344 | border-color: transparent transparent #111111 transparent; 345 | } 346 | .smallipop-theme-black.smallipop-left:before { 347 | border-color: transparent transparent transparent #222222; 348 | } 349 | .smallipop-theme-black.smallipop-left:after { 350 | border-color: transparent transparent transparent #111111; 351 | } 352 | .smallipop-theme-black.smallipop-right:before { 353 | border-color: transparent #222222 transparent transparent; 354 | } 355 | .smallipop-theme-black.smallipop-right:after { 356 | border-color: transparent #111111 transparent transparent; 357 | } 358 | .smallipop-theme-black .smallipop-content { 359 | border-top: 1px solid #666; 360 | -webkit-border-radius: 5px; 361 | -moz-border-radius: 5px; 362 | -ms-border-radius: 5px; 363 | -o-border-radius: 5px; 364 | border-radius: 5px; 365 | } 366 | .smallipop-theme-black .smallipop-tour-progress { 367 | color: #888; 368 | } 369 | .smallipop-theme-black .smallipop-tour-prev, 370 | .smallipop-theme-black .smallipop-tour-next, 371 | .smallipop-theme-black .smallipop-tour-close { 372 | color: #ccc; 373 | background: #333; 374 | } 375 | .smallipop-theme-black .smallipop-tour-prev:hover, 376 | .smallipop-theme-black .smallipop-tour-next:hover, 377 | .smallipop-theme-black .smallipop-tour-close:hover { 378 | color: #fff; 379 | background: #3a3a3a; 380 | } 381 | 382 | .cssgradients .smallipop-theme-black { 383 | background-color: transparent; 384 | } 385 | 386 | /* orange theme */ 387 | .smallipop-theme-orange { 388 | background-color: #f9aa18; 389 | border-color: #e17500; 390 | text-shadow: 0 1px 1px #fff24d; 391 | color: #714900; 392 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fff24d), color-stop(100%, #f9aa18)); 393 | background: -webkit-linear-gradient(#fff24d, #f9aa18); 394 | background: -moz-linear-gradient(#fff24d, #f9aa18); 395 | background: -o-linear-gradient(#fff24d, #f9aa18); 396 | background: linear-gradient(#fff24d, #f9aa18); 397 | -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 398 | -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 399 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); 400 | -webkit-border-radius: 10px; 401 | -moz-border-radius: 10px; 402 | -ms-border-radius: 10px; 403 | -o-border-radius: 10px; 404 | border-radius: 10px; 405 | } 406 | .smallipop-theme-orange a { 407 | color: #145d95; 408 | text-shadow: 0 1px 1px #fff24d; 409 | } 410 | .smallipop-theme-orange:before { 411 | border-color: #f9aa18 transparent transparent transparent; 412 | } 413 | .smallipop-theme-orange:after { 414 | border-color: #e17500 transparent transparent transparent; 415 | } 416 | .smallipop-theme-orange.smallipop-bottom:before { 417 | border-color: transparent transparent #f9aa18 transparent; 418 | } 419 | .smallipop-theme-orange.smallipop-bottom:after { 420 | border-color: transparent transparent #e17500 transparent; 421 | } 422 | .smallipop-theme-orange.smallipop-left:before { 423 | border-color: transparent transparent transparent #f9aa18; 424 | } 425 | .smallipop-theme-orange.smallipop-left:after { 426 | border-color: transparent transparent transparent #e17500; 427 | } 428 | .smallipop-theme-orange.smallipop-right:before { 429 | border-color: transparent #f9aa18 transparent transparent; 430 | } 431 | .smallipop-theme-orange.smallipop-right:after { 432 | border-color: transparent #e17500 transparent transparent; 433 | } 434 | .smallipop-theme-orange .smallipop-content { 435 | border-top: 1px solid #fffabc; 436 | -webkit-border-radius: 10px; 437 | -moz-border-radius: 10px; 438 | -ms-border-radius: 10px; 439 | -o-border-radius: 10px; 440 | border-radius: 10px; 441 | } 442 | .smallipop-theme-orange .smallipop-tour-progress { 443 | color: #444; 444 | } 445 | .smallipop-theme-orange .smallipop-tour-prev, 446 | .smallipop-theme-orange .smallipop-tour-next, 447 | .smallipop-theme-orange .smallipop-tour-close { 448 | color: #444; 449 | background: #f19f06; 450 | } 451 | .smallipop-theme-orange .smallipop-tour-prev:hover, 452 | .smallipop-theme-orange .smallipop-tour-next:hover, 453 | .smallipop-theme-orange .smallipop-tour-close:hover { 454 | color: #333; 455 | background: #f9a509; 456 | } 457 | 458 | /* white theme */ 459 | .smallipop-theme-white { 460 | background-color: #fcfcfc; 461 | border-color: #ccc; 462 | text-shadow: 0 1px 1px #eee; 463 | color: #444; 464 | width: 200px; 465 | max-width: 200px; 466 | -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); 467 | -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); 468 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); 469 | -webkit-border-radius: 6px; 470 | -moz-border-radius: 6px; 471 | -ms-border-radius: 6px; 472 | -o-border-radius: 6px; 473 | border-radius: 6px; 474 | } 475 | .smallipop-theme-white:before { 476 | border-color: #fcfcfc transparent transparent transparent; 477 | } 478 | .smallipop-theme-white:after { 479 | border-color: #cccccc transparent transparent transparent; 480 | } 481 | .smallipop-theme-white.smallipop-bottom:before { 482 | border-color: transparent transparent #fcfcfc transparent; 483 | } 484 | .smallipop-theme-white.smallipop-bottom:after { 485 | border-color: transparent transparent #cccccc transparent; 486 | } 487 | .smallipop-theme-white.smallipop-left:before { 488 | border-color: transparent transparent transparent #fcfcfc; 489 | } 490 | .smallipop-theme-white.smallipop-left:after { 491 | border-color: transparent transparent transparent #cccccc; 492 | } 493 | .smallipop-theme-white.smallipop-right:before { 494 | border-color: transparent #fcfcfc transparent transparent; 495 | } 496 | .smallipop-theme-white.smallipop-right:after { 497 | border-color: transparent #cccccc transparent transparent; 498 | } 499 | .smallipop-theme-white .smallipop-content { 500 | text-align: center; 501 | -webkit-border-radius: 6px; 502 | -moz-border-radius: 6px; 503 | -ms-border-radius: 6px; 504 | -o-border-radius: 6px; 505 | border-radius: 6px; 506 | } 507 | .smallipop-theme-white .smallipop-tour-progress { 508 | color: #777; 509 | } 510 | .smallipop-theme-white .smallipop-tour-close-icon { 511 | background: #fafafa; 512 | color: #555; 513 | text-shadow: 0 1px 1px #fff; 514 | } 515 | .smallipop-theme-white .smallipop-tour-close-icon:hover { 516 | background: #f5f5f5; 517 | color: #222; 518 | } 519 | .smallipop-theme-white .smallipop-tour-prev, 520 | .smallipop-theme-white .smallipop-tour-next, 521 | .smallipop-theme-white .smallipop-tour-close { 522 | color: #666; 523 | background: #f0f0f0; 524 | } 525 | .smallipop-theme-white .smallipop-tour-prev:hover, 526 | .smallipop-theme-white .smallipop-tour-next:hover, 527 | .smallipop-theme-white .smallipop-tour-close:hover { 528 | color: #333; 529 | background: #f4f4f4; 530 | } 531 | 532 | /* white theme extended, requires rgba support */ 533 | .smallipop-theme-white-transparent { 534 | background-color: rgba(255, 255, 255, 0.8); 535 | } 536 | .smallipop-theme-white-transparent:before { 537 | bottom: -21px; 538 | border-color: rgba(255, 255, 255, 0.8) transparent transparent transparent; 539 | } 540 | .smallipop-theme-white-transparent:after { 541 | border-color: transparent; 542 | } 543 | .smallipop-theme-white-transparent.sipAlignBottom:before { 544 | top: -21px; 545 | border-color: transparent transparent rgba(255, 255, 255, 0.8) transparent; 546 | } 547 | .smallipop-theme-white-transparent.sipPositionedLeft:before { 548 | border-color: transparent transparent transparent rgba(255, 255, 255, 0.8); 549 | } 550 | .smallipop-theme-white-transparent.sipPositionedRight:before { 551 | border-color: transparent rgba(255, 255, 255, 0.8) transparent transparent; 552 | } 553 | 554 | /* fat shadow extension theme */ 555 | .smallipop-instance.smallipop-theme-fat-shadow { 556 | -webkit-box-shadow: 0 2px 20px rgba(0, 0, 0, 0.8); 557 | -moz-box-shadow: 0 2px 20px rgba(0, 0, 0, 0.8); 558 | box-shadow: 0 2px 20px rgba(0, 0, 0, 0.8); 559 | } 560 | 561 | /* wide content extension theme */ 562 | .smallipop-instance.smallipop-theme-wide { 563 | max-width: 600px; 564 | } 565 | -------------------------------------------------------------------------------- /www/css/jquery-0.6.1.smallipop.min.css: -------------------------------------------------------------------------------- 1 | .smallipop-hint{display:none}#smallipop-tour-overlay{position:fixed;left:0;top:0;bottom:0;right:0}.smallipop-instance{position:absolute;display:none;top:0;left:0;background-color:#314b64;border:1px solid #0f161e;color:#d2dfe7;z-index:9999;max-width:400px}.smallipop-instance font{size:11px;family:arial}.smallipop-instance a{color:#98cbea}.smallipop-instance:before,.smallipop-instance:after{content:'';position:absolute;left:50%;height:0;width:0;pointer-events:none}.smallipop-instance:before{bottom:-20px;margin-left:-10px;border:10px solid transparent}.smallipop-instance:after{bottom:-24px;margin-left:-12px;border:12px solid transparent}.smallipop-align-left:before,.smallipop-align-left:after{margin-left:0;left:auto;right:20px}.smallipop-align-left:after{right:18px}.smallipop-align-right:before,.smallipop-align-right:after{margin-left:0;left:20px;right:auto}.smallipop-align-right:after{left:18px}.smallipop-bottom:before,.smallipop-bottom:after{bottom:auto;top:-20px}.smallipop-bottom:after{top:-24px}.smallipop-left:before,.smallipop-left:after,.smallipop-right:before,.smallipop-right:after{right:-16px;left:auto;top:50%;bottom:auto;border-width:8px;margin:-8px 0 0}.smallipop-left:after,.smallipop-right:after{right:-20px;border-width:10px;margin:-10px 0 0}.smallipop-right:before,.smallipop-right:after{left:-16px;right:auto}.smallipop-right:after{left:-20px}.smallipop-content{padding:10px}.smallipop-tour-content{padding:5px 0;min-width:150px}.smallipop-tour-footer{padding-top:5px;position:relative;overflow:hidden;*zoom:1}.smallipop-tour-progress{color:#bbb;text-align:center;position:absolute;left:50%;width:80px;margin-left:-40px;top:8px}.smallipop-tour-close-icon{position:absolute;right:-8px;top:-8px;width:16px;height:16px;padding-top:0px;font-size:11px;background:#555;color:#ccc;text-align:center;text-shadow:0 -1px 1px #666;text-decoration:none;-webkit-border-radius:8px;-moz-border-radius:8px;-ms-border-radius:8px;-o-border-radius:8px;border-radius:8px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.3);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.3);box-shadow:0 1px 3px rgba(0,0,0,0.3)}.smallipop-tour-close-icon:hover{text-decoration:none;background:#666;color:#fff}.smallipop-tour-prev,.smallipop-tour-next,.smallipop-tour-close{color:#ccc;display:block;padding:3px 4px 2px;line-height:1em;float:left;background:#203142;-webkit-border-radius:3px;-moz-border-radius:3px;-ms-border-radius:3px;-o-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.3);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.3);box-shadow:0 1px 2px rgba(0,0,0,0.3)}.smallipop-tour-prev:hover,.smallipop-tour-next:hover,.smallipop-tour-close:hover{color:#fff;background:#293e53;text-decoration:none}.smallipop-tour-next,.smallipop-tour-close{float:right}.smallipop-theme-default{text-shadow:0 -1px 1px #0f161e;-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.5);-moz-box-shadow:0 2px 6px rgba(0,0,0,0.5);box-shadow:0 2px 6px rgba(0,0,0,0.5);-webkit-border-radius:12px;-moz-border-radius:12px;-ms-border-radius:12px;-o-border-radius:12px;border-radius:12px;background:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(49,75,100,0.9)), color-stop(100%, rgba(26,38,52,0.9)));background:-webkit-linear-gradient(rgba(49,75,100,0.9),rgba(26,38,52,0.9));background:-moz-linear-gradient(rgba(49,75,100,0.9),rgba(26,38,52,0.9));background:-o-linear-gradient(rgba(49,75,100,0.9),rgba(26,38,52,0.9));background:linear-gradient(rgba(49,75,100,0.9),rgba(26,38,52,0.9));background:-webkit-gradient(radial, 50% -100px, 0, 50% -100px, 150, color-stop(66.66667%, rgba(49,75,100,0.9)), color-stop(86.66667%, rgba(33,50,66,0.9)), color-stop(100%, rgba(26,38,52,0.9)));background:-webkit-radial-gradient(50% -100px, circle contain, rgba(49,75,100,0.9) 100px,rgba(33,50,66,0.9) 130px,rgba(26,38,52,0.9) 150px);background:-moz-radial-gradient(50% -100px, circle contain, rgba(49,75,100,0.9) 100px,rgba(33,50,66,0.9) 130px,rgba(26,38,52,0.9) 150px);background:-o-radial-gradient(50% -100px, circle contain, rgba(49,75,100,0.9) 100px,rgba(33,50,66,0.9) 130px,rgba(26,38,52,0.9) 150px);background:radial-gradient(50% -100px, circle contain, rgba(49,75,100,0.9) 100px,rgba(33,50,66,0.9) 130px,rgba(26,38,52,0.9) 150px)}.smallipop-theme-default a{text-shadow:0 -1px 1px #0f161e}.smallipop-theme-default .smallipop-content{border-top:1px solid #586d82;-webkit-border-radius:12px;-moz-border-radius:12px;-ms-border-radius:12px;-o-border-radius:12px;border-radius:12px}.smallipop-theme-default:before{border-color:#1a2634 transparent transparent transparent}.smallipop-theme-default:after{border-color:#0f161e transparent transparent transparent}.smallipop-theme-default.smallipop-bottom:before{border-color:transparent transparent #1a2634 transparent}.smallipop-theme-default.smallipop-bottom:after{border-color:transparent transparent #0f161e transparent}.smallipop-theme-default.smallipop-left:before{border-color:transparent transparent transparent #1a2634}.smallipop-theme-default.smallipop-left:after{border-color:transparent transparent transparent #0f161e}.smallipop-theme-default.smallipop-right:before{border-color:transparent #1a2634 transparent transparent}.smallipop-theme-default.smallipop-right:after{border-color:transparent #0f161e transparent transparent}.cssgradients.rgba .smallipop-theme-default{background-color:transparent}.smallipop-theme-blue{background:transparent;color:#111;border:10px solid rgba(0,100,180,0.9);-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.5);-moz-box-shadow:0 2px 6px rgba(0,0,0,0.5);box-shadow:0 2px 6px rgba(0,0,0,0.5);-webkit-border-radius:7px;-moz-border-radius:7px;-ms-border-radius:7px;-o-border-radius:7px;border-radius:7px}.smallipop-theme-blue a{color:#2276aa}.smallipop-theme-blue:after{bottom:-34px;border-color:rgba(0,100,180,0.9) transparent transparent transparent}.smallipop-theme-blue:before{display:none}.smallipop-theme-blue.smallipop-bottom:after{top:-34px;border-color:transparent transparent rgba(0,100,180,0.9) transparent}.smallipop-theme-blue.smallipop-left{right:-26px}.smallipop-theme-blue.smallipop-left:after{border-color:transparent transparent transparent rgba(0,100,180,0.9)}.smallipop-theme-blue.smallipop-right{left:-26px}.smallipop-theme-blue.smallipop-right:after{border-color:transparent rgba(0,100,180,0.9) transparent transparent}.smallipop-theme-blue .smallipop-content{border:0;background:#fcfcfc}.smallipop-theme-blue .smallipop-tour-progress{color:#777}.smallipop-theme-blue .smallipop-tour-prev,.smallipop-theme-blue .smallipop-tour-next,.smallipop-theme-blue .smallipop-tour-close{color:#222;background:#efefef}.smallipop-theme-blue .smallipop-tour-prev:hover,.smallipop-theme-blue .smallipop-tour-next:hover,.smallipop-theme-blue .smallipop-tour-close:hover{color:#111;background:#f7f7f7}.smallipop-theme-black{background-color:#222;border-color:#111;text-shadow:0 -1px 1px #111;color:#f5f5f5;-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.5);-moz-box-shadow:0 2px 6px rgba(0,0,0,0.5);box-shadow:0 2px 6px rgba(0,0,0,0.5);background:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #333333), color-stop(100%, #222222));background:-webkit-linear-gradient(#333333,#222222);background:-moz-linear-gradient(#333333,#222222);background:-o-linear-gradient(#333333,#222222);background:linear-gradient(#333333,#222222);-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px;border-radius:5px}.smallipop-theme-black a{color:#eef8ff;text-shadow:0 -1px 1px #111}.smallipop-theme-black:before{border-color:#222 transparent transparent transparent}.smallipop-theme-black:after{border-color:#111 transparent transparent transparent}.smallipop-theme-black.smallipop-bottom:before{border-color:transparent transparent #222 transparent}.smallipop-theme-black.smallipop-bottom:after{border-color:transparent transparent #111 transparent}.smallipop-theme-black.smallipop-left:before{border-color:transparent transparent transparent #222}.smallipop-theme-black.smallipop-left:after{border-color:transparent transparent transparent #111}.smallipop-theme-black.smallipop-right:before{border-color:transparent #222 transparent transparent}.smallipop-theme-black.smallipop-right:after{border-color:transparent #111 transparent transparent}.smallipop-theme-black .smallipop-content{border-top:1px solid #666;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px;border-radius:5px}.smallipop-theme-black .smallipop-tour-progress{color:#888}.smallipop-theme-black .smallipop-tour-prev,.smallipop-theme-black .smallipop-tour-next,.smallipop-theme-black .smallipop-tour-close{color:#ccc;background:#333}.smallipop-theme-black .smallipop-tour-prev:hover,.smallipop-theme-black .smallipop-tour-next:hover,.smallipop-theme-black .smallipop-tour-close:hover{color:#fff;background:#3a3a3a}.cssgradients .smallipop-theme-black{background-color:transparent}.smallipop-theme-orange{background-color:#f9aa18;border-color:#e17500;text-shadow:0 1px 1px #fff24d;color:#714900;background:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fff24d), color-stop(100%, #f9aa18));background:-webkit-linear-gradient(#fff24d,#f9aa18);background:-moz-linear-gradient(#fff24d,#f9aa18);background:-o-linear-gradient(#fff24d,#f9aa18);background:linear-gradient(#fff24d,#f9aa18);-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.5);-moz-box-shadow:0 2px 6px rgba(0,0,0,0.5);box-shadow:0 2px 6px rgba(0,0,0,0.5);-webkit-border-radius:10px;-moz-border-radius:10px;-ms-border-radius:10px;-o-border-radius:10px;border-radius:10px}.smallipop-theme-orange a{color:#145d95;text-shadow:0 1px 1px #fff24d}.smallipop-theme-orange:before{border-color:#f9aa18 transparent transparent transparent}.smallipop-theme-orange:after{border-color:#e17500 transparent transparent transparent}.smallipop-theme-orange.smallipop-bottom:before{border-color:transparent transparent #f9aa18 transparent}.smallipop-theme-orange.smallipop-bottom:after{border-color:transparent transparent #e17500 transparent}.smallipop-theme-orange.smallipop-left:before{border-color:transparent transparent transparent #f9aa18}.smallipop-theme-orange.smallipop-left:after{border-color:transparent transparent transparent #e17500}.smallipop-theme-orange.smallipop-right:before{border-color:transparent #f9aa18 transparent transparent}.smallipop-theme-orange.smallipop-right:after{border-color:transparent #e17500 transparent transparent}.smallipop-theme-orange .smallipop-content{border-top:1px solid #fffabc;-webkit-border-radius:10px;-moz-border-radius:10px;-ms-border-radius:10px;-o-border-radius:10px;border-radius:10px}.smallipop-theme-orange .smallipop-tour-progress{color:#444}.smallipop-theme-orange .smallipop-tour-prev,.smallipop-theme-orange .smallipop-tour-next,.smallipop-theme-orange .smallipop-tour-close{color:#444;background:#f19f06}.smallipop-theme-orange .smallipop-tour-prev:hover,.smallipop-theme-orange .smallipop-tour-next:hover,.smallipop-theme-orange .smallipop-tour-close:hover{color:#333;background:#f9a509}.smallipop-theme-white{background-color:#fcfcfc;border-color:#ccc;text-shadow:0 1px 1px #eee;color:#444;width:200px;max-width:200px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.1);box-shadow:0 1px 4px rgba(0,0,0,0.1);-webkit-border-radius:6px;-moz-border-radius:6px;-ms-border-radius:6px;-o-border-radius:6px;border-radius:6px}.smallipop-theme-white:before{border-color:#fcfcfc transparent transparent transparent}.smallipop-theme-white:after{border-color:#ccc transparent transparent transparent}.smallipop-theme-white.smallipop-bottom:before{border-color:transparent transparent #fcfcfc transparent}.smallipop-theme-white.smallipop-bottom:after{border-color:transparent transparent #ccc transparent}.smallipop-theme-white.smallipop-left:before{border-color:transparent transparent transparent #fcfcfc}.smallipop-theme-white.smallipop-left:after{border-color:transparent transparent transparent #ccc}.smallipop-theme-white.smallipop-right:before{border-color:transparent #fcfcfc transparent transparent}.smallipop-theme-white.smallipop-right:after{border-color:transparent #ccc transparent transparent}.smallipop-theme-white .smallipop-content{text-align:center;-webkit-border-radius:6px;-moz-border-radius:6px;-ms-border-radius:6px;-o-border-radius:6px;border-radius:6px}.smallipop-theme-white .smallipop-tour-progress{color:#777}.smallipop-theme-white .smallipop-tour-close-icon{background:#fafafa;color:#555;text-shadow:0 1px 1px #fff}.smallipop-theme-white .smallipop-tour-close-icon:hover{background:#f5f5f5;color:#222}.smallipop-theme-white .smallipop-tour-prev,.smallipop-theme-white .smallipop-tour-next,.smallipop-theme-white .smallipop-tour-close{color:#666;background:#f0f0f0}.smallipop-theme-white .smallipop-tour-prev:hover,.smallipop-theme-white .smallipop-tour-next:hover,.smallipop-theme-white .smallipop-tour-close:hover{color:#333;background:#f4f4f4}.smallipop-theme-white-transparent{background-color:rgba(255,255,255,0.8)}.smallipop-theme-white-transparent:before{bottom:-21px;border-color:rgba(255,255,255,0.8) transparent transparent transparent}.smallipop-theme-white-transparent:after{border-color:transparent}.smallipop-theme-white-transparent.sipAlignBottom:before{top:-21px;border-color:transparent transparent rgba(255,255,255,0.8) transparent}.smallipop-theme-white-transparent.sipPositionedLeft:before{border-color:transparent transparent transparent rgba(255,255,255,0.8)}.smallipop-theme-white-transparent.sipPositionedRight:before{border-color:transparent rgba(255,255,255,0.8) transparent transparent}.smallipop-instance.smallipop-theme-fat-shadow{-webkit-box-shadow:0 2px 20px rgba(0,0,0,0.8);-moz-box-shadow:0 2px 20px rgba(0,0,0,0.8);box-shadow:0 2px 20px rgba(0,0,0,0.8)}.smallipop-instance.smallipop-theme-wide{max-width:600px} 2 | -------------------------------------------------------------------------------- /www/css/show-links.css: -------------------------------------------------------------------------------- 1 | a.stapibas-link:after { 2 | margin-left: 0.3ex; 3 | } 4 | a.stapibas-status-error:after { 5 | color: red; 6 | content: "✘"; 7 | } 8 | a.stapibas-status-unsupported:after { 9 | color: grey; 10 | content: "✘"; 11 | } 12 | a.stapibas-status-queued:after { 13 | color: white; 14 | background-color: grey; 15 | content: "⌚"; 16 | } 17 | a.stapibas-status-pinging:after { 18 | color: white; 19 | background-color: #BB0; 20 | content: "…"; 21 | } 22 | a.stapibas-status-ok:after { 23 | color: white; 24 | background-color: #0B0; 25 | content: "✔"; 26 | } 27 | 28 | .smallipop-theme-white .smallipop-content { 29 | text-align: left !important; 30 | } 31 | .smallipop-theme-white { 32 | width: 400px !important; 33 | max-width: 600px !important; 34 | } 35 | 36 | .smallipop-content h2 { 37 | margin: 0px; 38 | margin-bottom: 0.5ex; 39 | font-size: 1em; 40 | } 41 | .smallipop-content dt, .smallipop-content dd { 42 | margin: 0px; 43 | padding: 0px; 44 | } 45 | .smallipop-content dt { 46 | font-style: italic; 47 | min-width: 10ex; 48 | float: left; 49 | clear: both; 50 | } 51 | .smallipop-content dd { 52 | float: left; 53 | margin-left: 1ex; 54 | } 55 | 56 | .stapibas-stats { 57 | text-align: center; 58 | padding: 0.5ex; 59 | background-color: #fcfcfc; 60 | border-color: #CCC; 61 | } 62 | .stapibas-stats-error { 63 | background-color: red; 64 | color: white; 65 | } 66 | 67 | .stapibas-stats ul { 68 | display: inline; 69 | margin: 0px; 70 | padding: 0px; 71 | } 72 | .stapibas-stats li { 73 | display: inline; 74 | margin-left: 5ex; 75 | } -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | stapibas: The standalone linkback server 14 | 15 | 16 | 17 |

Linkback stats

18 |

19 | Add the following bookmarklet to your browser's bookmarks (right-click): 20 |

21 |

22 | stapibas linkback stats 23 |

24 | 25 | 26 | -------------------------------------------------------------------------------- /www/js/jquery-0.6.1.smallipop.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Smallipop (06/21/2013) 3 | Copyright (c) 2011-2013 Small Improvements (http://www.small-improvements.com) 4 | 5 | Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 6 | 7 | @author Sebastian Helzle (sebastian@helzle.net) 8 | */ 9 | 10 | (function(factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | return define(['jquery'], factory); 13 | } else { 14 | return factory(jQuery); 15 | } 16 | })(function($) { 17 | var $document, $overlay, $window, classAlignLeft, classAlignRight, classBase, classBottom, classContent, classHint, classInitialized, classInstance, classLeft, classRight, classTheme, classTour, classTourClose, classTourCloseIcon, classTourContent, classTourFooter, classTourNext, classTourOverlay, classTourPrev, classTourProgress, cssAnimationsEnabled, currentTour, dataBeingShown, dataIsTour, dataPopupHovered, dataPosition, dataShown, dataTimerHide, dataTimerShow, dataTriggerHovered, dataXDistance, dataYDistance, dataZIndex, destroy, eventBlur, eventClick, eventFocus, eventKeyUp, eventMouseOut, eventMouseOver, eventResize, eventScroll, eventTouchEnd, fadeInPopup, fadeInPopupFinished, filterAlignmentClass, filterBaseClass, filterClass, forceRefreshPosition, getInstance, getOverlay, getTrigger, hideSmallipop, hideTourOverlay, instances, isElementFixed, killTimers, lastId, lastScrollCheck, nextInstanceId, onWindowKeyUp, onWindowScroll, popupTemplate, queueRefreshPosition, reAlignmentClass, reBaseClass, refreshPosition, refreshQueueTimer, resetTourZIndices, runTour, scrollTimer, setContent, showPopup, showSmallipop, showWhenVisible, sip, touchEnabled, tourClose, tourNext, tourPrev, tourShow, tours, triggerMouseout, triggerMouseover; 18 | classBase = 'smallipop'; 19 | classHint = classBase + '-hint'; 20 | classInstance = classBase + '-instance'; 21 | classContent = classBase + '-content'; 22 | classLeft = classBase + '-left'; 23 | classRight = classBase + '-right'; 24 | classBottom = classBase + '-bottom'; 25 | classAlignLeft = classBase + '-align-left'; 26 | classAlignRight = classBase + '-align-right'; 27 | classInitialized = classBase + '-initialized'; 28 | classTheme = classBase + '-theme-'; 29 | classTour = classBase + '-tour'; 30 | classTourContent = classTour + '-content'; 31 | classTourOverlay = classTour + '-overlay'; 32 | classTourFooter = classTour + '-footer'; 33 | classTourCloseIcon = classTour + '-close-icon'; 34 | classTourProgress = classTour + '-progress'; 35 | classTourClose = classTour + '-close'; 36 | classTourPrev = classTour + '-prev'; 37 | classTourNext = classTour + '-next'; 38 | eventFocus = 'focus.' + classBase; 39 | eventClick = 'click.' + classBase; 40 | eventBlur = 'blur.' + classBase; 41 | eventMouseOut = 'mouseout.' + classBase; 42 | eventMouseOver = 'mouseover.' + classBase; 43 | eventTouchEnd = 'touchend.' + classBase; 44 | eventResize = 'resize.' + classBase; 45 | eventScroll = 'scroll.' + classBase; 46 | eventKeyUp = 'keyup.' + classBase; 47 | dataZIndex = classBase + 'OriginalZIndex'; 48 | dataBeingShown = classBase + 'BeingShown'; 49 | dataTimerHide = classBase + 'HideDelayTimer'; 50 | dataTimerShow = classBase + 'ShowDelayTimer'; 51 | dataTriggerHovered = classBase + 'TriggerHovered'; 52 | dataPopupHovered = classBase + 'PopupHovered'; 53 | dataShown = classBase + 'Shown'; 54 | dataPosition = classBase + 'Position'; 55 | dataXDistance = classBase + 'XDistance'; 56 | dataYDistance = classBase + 'YDistance'; 57 | dataIsTour = classBase + 'IsTour'; 58 | reAlignmentClass = new RegExp(classBase + '-(align|bottom)\w*', "g"); 59 | reBaseClass = new RegExp(classBase + '\w+', "g"); 60 | $document = $(document); 61 | $window = $(window); 62 | $overlay = null; 63 | instances = {}; 64 | tours = {}; 65 | currentTour = null; 66 | lastId = 1; 67 | nextInstanceId = 1; 68 | scrollTimer = null; 69 | lastScrollCheck = 0; 70 | refreshQueueTimer = null; 71 | popupTemplate = "
"; 72 | $.smallipop = sip = { 73 | version: '0.6.1', 74 | defaults: { 75 | autoscrollPadding: 200, 76 | contentAnimationSpeed: 150, 77 | cssAnimations: { 78 | enabled: false, 79 | show: 'animated fadeIn', 80 | hide: 'animated fadeOut' 81 | }, 82 | funcEase: 'easeInOutQuad', 83 | handleInputs: true, 84 | hideDelay: 500, 85 | hideTrigger: false, 86 | hideOnPopupClick: true, 87 | hideOnTriggerClick: true, 88 | infoClass: classHint, 89 | invertAnimation: false, 90 | popupId: '', 91 | popupOffset: 31, 92 | popupYOffset: 0, 93 | popupDistance: 20, 94 | popupDelay: 100, 95 | popupAnimationSpeed: 200, 96 | preferredPosition: 'top', 97 | referencedContent: null, 98 | theme: 'default', 99 | touchSupport: true, 100 | tourHighlight: false, 101 | tourHighlightColor: '#222', 102 | tourHighlightFadeDuration: 200, 103 | tourHighlightOpacity: .5, 104 | tourHighlightZIndex: 9997, 105 | tourNavigationEnabled: true, 106 | triggerAnimationSpeed: 150, 107 | triggerOnClick: false, 108 | onAfterHide: null, 109 | onAfterShow: null, 110 | onBeforeHide: null, 111 | onBeforeShow: null, 112 | onTourClose: null, 113 | onTourNext: null, 114 | onTourPrev: null, 115 | windowPadding: 30, 116 | labels: { 117 | prev: 'Prev', 118 | next: 'Next', 119 | close: 'Close', 120 | of: 'of' 121 | } 122 | } 123 | }; 124 | if (!$.easing.easeInOutQuad) { 125 | $.easing.easeInOutQuad = function(x, t, b, c, d) { 126 | if ((t /= d / 2) < 1) { 127 | return c / 2 * t * t + b; 128 | } else { 129 | return -c / 2 * ((--t) * (t - 2) - 1) + b; 130 | } 131 | }; 132 | } 133 | resetTourZIndices = function() { 134 | var step, steps, tour, tourTrigger, _results; 135 | _results = []; 136 | for (tour in tours) { 137 | steps = tours[tour]; 138 | _results.push((function() { 139 | var _i, _len, _results1; 140 | _results1 = []; 141 | for (_i = 0, _len = steps.length; _i < _len; _i++) { 142 | step = steps[_i]; 143 | tourTrigger = step.trigger; 144 | if (tourTrigger.data(dataZIndex)) { 145 | _results1.push(tourTrigger.css('zIndex', tourTrigger.data(dataZIndex))); 146 | } else { 147 | _results1.push(void 0); 148 | } 149 | } 150 | return _results1; 151 | })()); 152 | } 153 | return _results; 154 | }; 155 | touchEnabled = typeof Modernizr !== "undefined" && Modernizr !== null ? Modernizr.touch : void 0; 156 | cssAnimationsEnabled = typeof Modernizr !== "undefined" && Modernizr !== null ? Modernizr.cssanimations : void 0; 157 | getTrigger = function(id) { 158 | return $("." + (classBase + id)); 159 | }; 160 | getOverlay = function() { 161 | if (!$overlay) { 162 | $overlay = $("
").appendTo($('body')).fadeOut(0); 163 | } 164 | return $overlay; 165 | }; 166 | hideTourOverlay = function(options) { 167 | getOverlay().fadeOut(options.tourHighlightFadeDuration); 168 | return resetTourZIndices(); 169 | }; 170 | hideSmallipop = function(e) { 171 | var direction, ignorePopupClick, ignoreTriggerClick, popup, popupData, popupId, shownId, target, trigger, triggerData, triggerIsTarget, triggerOptions, xDistance, yDistance, _base, _ref, _ref1, _results; 172 | clearTimeout(scrollTimer); 173 | target = (e != null ? e.target : void 0) ? $(e.target) : e; 174 | _results = []; 175 | for (popupId in instances) { 176 | popup = instances[popupId]; 177 | popupData = popup.data(); 178 | if (!(shownId = popupData[dataShown])) { 179 | continue; 180 | } 181 | trigger = getTrigger(shownId); 182 | triggerIsTarget = trigger.is(target); 183 | triggerData = trigger.data(classBase); 184 | triggerOptions = triggerData.options || sip.defaults; 185 | if ((popupData[dataIsTour] || triggerData.isFormElement) && !popup.is(target) && !(triggerIsTarget && popup.is(triggerOptions.popupInstance))) { 186 | continue; 187 | } 188 | if (popupData[dataIsTour]) { 189 | currentTour = null; 190 | if ((_ref = trigger.data(classBase)) != null) { 191 | if (typeof (_base = _ref.options).onTourClose === "function") { 192 | _base.onTourClose(); 193 | } 194 | } 195 | hideTourOverlay(triggerOptions); 196 | } 197 | ignoreTriggerClick = !triggerOptions.hideOnTriggerClick && triggerIsTarget; 198 | ignorePopupClick = !triggerOptions.hideOnPopupClick && popup.find(target).length; 199 | if (target && trigger.length && ((_ref1 = e != null ? e.type : void 0) === 'click' || _ref1 === 'touchend') && (ignoreTriggerClick || ignorePopupClick)) { 200 | continue; 201 | } 202 | if (shownId && triggerOptions.hideTrigger) { 203 | trigger.stop(true).fadeTo(triggerOptions.triggerAnimationSpeed, 1); 204 | } 205 | popup.data(dataTimerHide, null).data(dataBeingShown, false); 206 | if (triggerOptions.cssAnimations.enabled) { 207 | popup.removeClass(triggerOptions.cssAnimations.show).addClass(triggerOptions.cssAnimations.hide).data(dataShown, ''); 208 | if (triggerOptions.onAfterHide) { 209 | _results.push(window.setTimeout(triggerOptions.onAfterHide, triggerOptions.popupAnimationSpeed)); 210 | } else { 211 | _results.push(void 0); 212 | } 213 | } else { 214 | direction = triggerOptions.invertAnimation ? -1 : 1; 215 | xDistance = popupData[dataXDistance] * direction; 216 | yDistance = popupData[dataYDistance] * direction; 217 | _results.push(popup.stop(true).animate({ 218 | top: "-=" + yDistance, 219 | left: "+=" + xDistance, 220 | opacity: 0 221 | }, triggerOptions.popupAnimationSpeed, triggerOptions.funcEase, function() { 222 | var self; 223 | self = $(this); 224 | if (!self.data(dataBeingShown)) { 225 | self.css('display', 'none').data(dataShown, ''); 226 | } 227 | return typeof triggerOptions.onAfterHide === "function" ? triggerOptions.onAfterHide() : void 0; 228 | })); 229 | } 230 | } 231 | return _results; 232 | }; 233 | showSmallipop = function(e) { 234 | var triggerData, _ref; 235 | triggerData = $(this).data(classBase); 236 | if (!triggerData) { 237 | return; 238 | } 239 | if (triggerData.popupInstance.data(dataShown) !== triggerData.id && ((_ref = !triggerData.type) === 'checkbox' || _ref === 'radio')) { 240 | if (e != null) { 241 | e.preventDefault(); 242 | } 243 | } 244 | return triggerMouseover.call(this); 245 | }; 246 | killTimers = function(popup) { 247 | clearTimeout(popup.data(dataTimerHide)); 248 | return clearTimeout(popup.data(dataTimerShow)); 249 | }; 250 | queueRefreshPosition = function(delay) { 251 | if (delay == null) { 252 | delay = 50; 253 | } 254 | clearTimeout(refreshQueueTimer); 255 | return refreshQueueTimer = setTimeout(refreshPosition, delay); 256 | }; 257 | filterClass = function(classStr, re) { 258 | if (classStr) { 259 | return (classStr.match(re) || []).join(' '); 260 | } 261 | }; 262 | filterAlignmentClass = function(idx, classStr) { 263 | return filterClass(classStr, reAlignmentClass); 264 | }; 265 | filterBaseClass = function(idx, classStr) { 266 | return filterClass(classStr, reBaseClass); 267 | }; 268 | refreshPosition = function(resetTheme) { 269 | var isFixed, offset, opacity, options, popup, popupCenter, popupData, popupDistanceBottom, popupDistanceLeft, popupDistanceRight, popupDistanceTop, popupH, popupId, popupOffsetLeft, popupOffsetTop, popupW, popupY, preferredPosition, selfHeight, selfWidth, selfY, shownId, themes, trigger, triggerData, win, winHeight, winScrollLeft, winScrollTop, winWidth, windowPadding, xDistance, xOffset, xOverflow, yDistance, yOffset, yOverflow, _results; 270 | if (resetTheme == null) { 271 | resetTheme = true; 272 | } 273 | _results = []; 274 | for (popupId in instances) { 275 | popup = instances[popupId]; 276 | popupData = popup.data(); 277 | shownId = popupData[dataShown]; 278 | if (!shownId) { 279 | continue; 280 | } 281 | trigger = getTrigger(shownId); 282 | triggerData = trigger.data(classBase); 283 | options = triggerData.options; 284 | popup.removeClass(filterAlignmentClass); 285 | if (resetTheme) { 286 | themes = classTheme + options.theme.split(' ').join(" " + classTheme); 287 | popup.attr('class', "" + classInstance + " " + themes); 288 | } 289 | win = $(window); 290 | xDistance = yDistance = options.popupDistance; 291 | xOffset = options.popupOffset; 292 | yOffset = options.popupYOffset; 293 | isFixed = popup.data(dataPosition) === 'fixed'; 294 | popupH = popup.outerHeight(); 295 | popupW = popup.outerWidth(); 296 | popupCenter = popupW / 2; 297 | winWidth = win.width(); 298 | winHeight = win.height(); 299 | winScrollTop = win.scrollTop(); 300 | winScrollLeft = win.scrollLeft(); 301 | windowPadding = options.windowPadding; 302 | offset = trigger.offset(); 303 | selfWidth = trigger.outerWidth(); 304 | selfHeight = trigger.outerHeight(); 305 | selfY = offset.top - winScrollTop; 306 | popupOffsetLeft = offset.left + selfWidth / 2; 307 | popupOffsetTop = offset.top - popupH + yOffset; 308 | popupY = popupH + options.popupDistance - yOffset; 309 | popupDistanceTop = selfY - popupY; 310 | popupDistanceBottom = winHeight - selfY - selfHeight - popupY; 311 | popupDistanceLeft = offset.left - popupW - xOffset; 312 | popupDistanceRight = winWidth - offset.left - selfWidth - popupW; 313 | preferredPosition = options.preferredPosition; 314 | if (preferredPosition === 'left' || preferredPosition === 'right') { 315 | yDistance = 0; 316 | popupOffsetTop += selfHeight / 2 + popupH / 2; 317 | if ((preferredPosition === 'right' && popupDistanceRight > windowPadding) || popupDistanceLeft < windowPadding) { 318 | popup.addClass(classRight); 319 | popupOffsetLeft = offset.left + selfWidth + xOffset; 320 | } else { 321 | popup.addClass(classLeft); 322 | popupOffsetLeft = offset.left - popupW - xOffset; 323 | xDistance = -xDistance; 324 | } 325 | } else { 326 | xDistance = 0; 327 | if (popupOffsetLeft + popupCenter > winWidth - windowPadding) { 328 | popupOffsetLeft -= popupCenter * 2 - xOffset; 329 | popup.addClass(classAlignLeft); 330 | } else if (popupOffsetLeft - popupCenter < windowPadding) { 331 | popupOffsetLeft -= xOffset; 332 | popup.addClass(classAlignRight); 333 | } else { 334 | popupOffsetLeft -= popupCenter; 335 | } 336 | if (popupOffsetLeft < windowPadding) { 337 | popupOffsetLeft = windowPadding; 338 | } 339 | if ((preferredPosition === 'bottom' && popupDistanceBottom > windowPadding) || popupDistanceTop < windowPadding) { 340 | yDistance = -yDistance; 341 | popupOffsetTop += popupH + selfHeight - 2 * yOffset; 342 | popup.addClass(classBottom); 343 | } 344 | } 345 | if (popupH < selfHeight) { 346 | yOverflow = popupOffsetTop + popupH + windowPadding - yDistance + yOffset - winScrollTop - winHeight; 347 | if (yOverflow > 0) { 348 | popupOffsetTop = Math.max(popupOffsetTop - yOverflow - windowPadding, offset.top + yOffset + windowPadding + yDistance); 349 | } 350 | } 351 | if (popupW < selfWidth) { 352 | xOverflow = popupOffsetLeft + popupW + windowPadding + xDistance + xOffset - winScrollLeft - winWidth; 353 | if (xOverflow > 0) { 354 | popupOffsetLeft = Math.max(popupOffsetLeft - xOverflow + windowPadding, offset.left + xOffset + windowPadding - xDistance); 355 | } 356 | } 357 | if (options.hideTrigger) { 358 | trigger.stop(true).fadeTo(options.triggerAnimationSpeed, 0); 359 | } 360 | opacity = 0; 361 | if (!popupData[dataBeingShown] || options.cssAnimations.enabled) { 362 | popupOffsetTop -= yDistance; 363 | popupOffsetLeft += xDistance; 364 | xDistance = yDistance = 0; 365 | opacity = 1; 366 | } 367 | if (isFixed) { 368 | popupOffsetLeft -= winScrollLeft; 369 | popupOffsetTop -= winScrollTop; 370 | } 371 | popup.data(dataXDistance, xDistance).data(dataYDistance, yDistance).css({ 372 | top: popupOffsetTop, 373 | left: popupOffsetLeft, 374 | display: 'block', 375 | opacity: opacity 376 | }); 377 | _results.push(fadeInPopup(popup, { 378 | top: "-=" + yDistance, 379 | left: "+=" + xDistance, 380 | opacity: 1 381 | })); 382 | } 383 | return _results; 384 | }; 385 | forceRefreshPosition = function() { 386 | return refreshPosition(false); 387 | }; 388 | fadeInPopup = function(popup, animationTarget) { 389 | var options, _ref; 390 | options = ((_ref = getTrigger(popup.data(dataShown)).data(classBase)) != null ? _ref.options : void 0) || sip.defaults; 391 | if (options.cssAnimations.enabled) { 392 | popup.addClass(options.cssAnimations.show); 393 | return window.setTimeout(function() { 394 | return fadeInPopupFinished(popup, options); 395 | }, options.popupAnimationSpeed); 396 | } else { 397 | return popup.stop(true).animate(animationTarget, options.popupAnimationSpeed, options.funcEase, function() { 398 | return fadeInPopupFinished(popup, options); 399 | }); 400 | } 401 | }; 402 | fadeInPopupFinished = function(popup, options) { 403 | var popupData; 404 | popupData = popup.data(); 405 | if (popupData[dataBeingShown]) { 406 | popup.data(dataBeingShown, false); 407 | return typeof options.onAfterShow === "function" ? options.onAfterShow(getTrigger(popupData[dataShown])) : void 0; 408 | } 409 | }; 410 | showPopup = function(trigger, content) { 411 | var lastTrigger, lastTriggerOpt, popup, popupContent, popupPosition, shownId, tourOverlay, triggerData, triggerOptions; 412 | if (content == null) { 413 | content = ''; 414 | } 415 | triggerData = trigger.data(classBase); 416 | triggerOptions = triggerData.options; 417 | popup = triggerData.popupInstance; 418 | if (!popup.data(dataTriggerHovered)) { 419 | return; 420 | } 421 | shownId = popup.data(dataShown); 422 | if (shownId) { 423 | lastTrigger = getTrigger(shownId); 424 | if (lastTrigger.length) { 425 | lastTriggerOpt = lastTrigger.data(classBase).options || sip.defaults; 426 | if (lastTriggerOpt.hideTrigger) { 427 | lastTrigger.stop(true).fadeTo(lastTriggerOpt.fadeSpeed, 1); 428 | } 429 | } 430 | } 431 | if (triggerOptions.tourHighlight && triggerOptions.tourIndex) { 432 | tourOverlay = getOverlay().css({ 433 | backgroundColor: triggerOptions.tourHighlightColor, 434 | zIndex: triggerOptions.tourHighlightZIndex 435 | }); 436 | resetTourZIndices(); 437 | if (trigger.css('position') === 'static') { 438 | trigger.css('position', 'relative'); 439 | } 440 | if (!trigger.data(dataZIndex)) { 441 | trigger.data(dataZIndex, trigger.css('zIndex')); 442 | } 443 | trigger.css('zIndex', triggerOptions.tourHighlightZIndex + 1); 444 | tourOverlay.fadeTo(triggerOptions.tourHighlightFadeDuration, triggerOptions.tourHighlightOpacity); 445 | } else if ($overlay) { 446 | hideTourOverlay(triggerOptions); 447 | } 448 | popupContent = content || triggerData.hint; 449 | if (triggerOptions.referencedContent && !content) { 450 | popupContent = $(triggerOptions.referencedContent).clone(true, true) || popupContent; 451 | } 452 | popupPosition = isElementFixed(trigger) ? 'fixed' : 'absolute'; 453 | if (shownId !== triggerData.id) { 454 | popup.hide(0); 455 | } 456 | popup.data(dataBeingShown, true).data(dataShown, triggerData.id).data(dataPosition, popupPosition).find('.' + classContent).empty().append(popupContent); 457 | popup.css('position', popupPosition); 458 | return queueRefreshPosition(0); 459 | }; 460 | isElementFixed = function(element) { 461 | var elemToCheck; 462 | elemToCheck = element; 463 | while (elemToCheck.length && elemToCheck[0].nodeName.toUpperCase() !== 'HTML') { 464 | if (elemToCheck.css('position') === 'fixed') { 465 | return true; 466 | } 467 | elemToCheck = elemToCheck.parent(); 468 | } 469 | return false; 470 | }; 471 | triggerMouseover = function() { 472 | var isTrigger, popup, shownId, trigger, triggerData, _base; 473 | trigger = popup = $(this); 474 | isTrigger = trigger.hasClass(classInitialized); 475 | if (!isTrigger) { 476 | trigger = getTrigger(popup.data(dataShown)); 477 | } 478 | if (!trigger.length) { 479 | return; 480 | } 481 | triggerData = trigger.data(classBase); 482 | popup = triggerData.popupInstance.data((isTrigger ? dataTriggerHovered : dataPopupHovered), true); 483 | killTimers(popup); 484 | shownId = popup.data(dataShown); 485 | if (shownId !== triggerData.id || popup.css('opacity') === 0) { 486 | if (typeof (_base = triggerData.options).onBeforeShow === "function") { 487 | _base.onBeforeShow(trigger); 488 | } 489 | return popup.data(dataTimerShow, setTimeout(function() { 490 | return showPopup(trigger); 491 | }, triggerData.options.popupDelay)); 492 | } 493 | }; 494 | triggerMouseout = function() { 495 | var isTrigger, popup, popupData, trigger, triggerData, _base; 496 | trigger = popup = $(this); 497 | isTrigger = trigger.hasClass(classInitialized); 498 | if (!isTrigger) { 499 | trigger = getTrigger(popup.data(dataShown)); 500 | } 501 | if (!trigger.length) { 502 | return; 503 | } 504 | triggerData = trigger.data(classBase); 505 | popup = triggerData.popupInstance.data((isTrigger ? dataTriggerHovered : dataPopupHovered), false); 506 | killTimers(popup); 507 | popupData = popup.data(); 508 | if (!(popupData[dataPopupHovered] || popupData[dataTriggerHovered])) { 509 | if (typeof (_base = triggerData.options).onBeforeHide === "function") { 510 | _base.onBeforeHide(trigger); 511 | } 512 | return popup.data(dataTimerHide, setTimeout(function() { 513 | return hideSmallipop(popup); 514 | }, triggerData.options.hideDelay)); 515 | } 516 | }; 517 | onWindowScroll = function(e) { 518 | clearTimeout(scrollTimer); 519 | return scrollTimer = setTimeout(forceRefreshPosition, 250); 520 | }; 521 | setContent = function(trigger, content) { 522 | var partOfTour, popupContent, triggerData; 523 | if (!(trigger != null ? trigger.length : void 0)) { 524 | return; 525 | } 526 | triggerData = trigger.data(classBase); 527 | partOfTour = triggerData.tourTitle; 528 | if (partOfTour) { 529 | popupContent = triggerData.popupInstance.find('.' + classTourContent); 530 | } else { 531 | popupContent = triggerData.popupInstance.find('.' + classContent); 532 | } 533 | if (popupContent.html() !== content) { 534 | return popupContent.stop(true).fadeTo(triggerData.options.contentAnimationSpeed, 0, function() { 535 | $(this).html(content).fadeTo(triggerData.options.contentAnimationSpeed, 1); 536 | return refreshPosition(); 537 | }); 538 | } 539 | }; 540 | runTour = function(trigger, step) { 541 | var currentTourItems, i, tourTitle, triggerData, _i, _ref; 542 | triggerData = trigger.data(classBase); 543 | tourTitle = triggerData != null ? triggerData.tourTitle : void 0; 544 | if (!(tourTitle && tours[tourTitle])) { 545 | return; 546 | } 547 | tours[tourTitle].sort(function(a, b) { 548 | return a.index - b.index; 549 | }); 550 | if (!(typeof step === 'number' && step % 1 === 0)) { 551 | step = -1; 552 | } else { 553 | step -= 1; 554 | } 555 | currentTour = tourTitle; 556 | currentTourItems = tours[tourTitle]; 557 | for (i = _i = 0, _ref = currentTourItems.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { 558 | if ((step >= 0 && i === step) || (step < 0 && currentTourItems[i].id === triggerData.id)) { 559 | return tourShow(tourTitle, i); 560 | } 561 | } 562 | }; 563 | tourShow = function(title, index) { 564 | var $content, $trigger, currentTourItems, navigation, navigationEnabled, options, triggerData; 565 | currentTourItems = tours[title]; 566 | if (!currentTourItems) { 567 | return; 568 | } 569 | $trigger = currentTourItems[index].trigger; 570 | triggerData = $trigger.data(classBase); 571 | options = triggerData.options; 572 | navigationEnabled = options.tourNavigationEnabled; 573 | navigation = ''; 574 | if (navigationEnabled) { 575 | navigation += ("
") + ("" + (index + 1) + " " + options.labels.of + " " + currentTourItems.length + "
"); 576 | if (index > 0) { 577 | navigation += "" + options.labels.prev + ""; 578 | } 579 | if (index < currentTourItems.length - 1) { 580 | navigation += "" + options.labels.next + ""; 581 | } 582 | } 583 | if (!navigationEnabled || index === currentTourItems.length - 1) { 584 | navigation += "" + options.labels.close + ""; 585 | } 586 | $content = $(("
") + ("Χ") + ("
" + navigation + "
")); 587 | $content.eq(0).append(triggerData.hint); 588 | killTimers(triggerData.popupInstance); 589 | triggerData.popupInstance.data(dataTriggerHovered, true); 590 | return showWhenVisible($trigger, $content); 591 | }; 592 | showWhenVisible = function($trigger, content) { 593 | var offset, targetPosition, triggerOptions, windowHeight; 594 | targetPosition = $trigger.offset().top; 595 | offset = targetPosition - $document.scrollTop(); 596 | windowHeight = $window.height(); 597 | triggerOptions = $trigger.data(classBase).options; 598 | if (!isElementFixed($trigger) && (offset < triggerOptions.autoscrollPadding || offset > windowHeight - triggerOptions.autoscrollPadding)) { 599 | return $('html, body').animate({ 600 | scrollTop: targetPosition - windowHeight / 2 601 | }, 800, 'swing', function() { 602 | return showPopup($trigger, content); 603 | }); 604 | } else { 605 | return showPopup($trigger, content); 606 | } 607 | }; 608 | tourNext = function(e) { 609 | var $popup, currentTourItems, i, shownId, triggerOptions, _i, _ref; 610 | if (e != null) { 611 | e.preventDefault(); 612 | } 613 | currentTourItems = tours[currentTour]; 614 | if (!currentTourItems) { 615 | return; 616 | } 617 | $popup = currentTourItems[0].popupInstance; 618 | shownId = $popup.data(dataShown) || currentTourItems[0].id; 619 | for (i = _i = 0, _ref = currentTourItems.length - 2; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { 620 | if (!(currentTourItems[i].id === shownId)) { 621 | continue; 622 | } 623 | triggerOptions = currentTourItems[i].trigger.data(classBase).options; 624 | if (triggerOptions.tourNavigationEnabled) { 625 | if (typeof triggerOptions.onTourNext === "function") { 626 | triggerOptions.onTourNext(currentTourItems[i + 1].trigger); 627 | } 628 | return tourShow(currentTour, i + 1); 629 | } 630 | } 631 | }; 632 | tourPrev = function(e) { 633 | var $popup, currentTourItems, i, shownId, triggerOptions, _i, _ref; 634 | if (e != null) { 635 | e.preventDefault(); 636 | } 637 | currentTourItems = tours[currentTour]; 638 | if (!currentTourItems) { 639 | return; 640 | } 641 | $popup = currentTourItems[0].popupInstance; 642 | shownId = $popup.data(dataShown) || currentTourItems[0].id; 643 | for (i = _i = 1, _ref = currentTourItems.length - 1; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 644 | if (!(currentTourItems[i].id === shownId)) { 645 | continue; 646 | } 647 | triggerOptions = currentTourItems[i].trigger.data(classBase).options; 648 | if (triggerOptions.tourNavigationEnabled) { 649 | if (typeof triggerOptions.onTourPrev === "function") { 650 | triggerOptions.onTourPrev(currentTourItems[i - 1].trigger); 651 | } 652 | return tourShow(currentTour, i - 1); 653 | } 654 | } 655 | }; 656 | tourClose = function(e) { 657 | var $popup; 658 | if (e != null) { 659 | e.preventDefault(); 660 | } 661 | $popup = $(e.target).closest("." + classInstance); 662 | return hideSmallipop($popup); 663 | }; 664 | destroy = function(instances) { 665 | return instances.each(function() { 666 | var data, self; 667 | self = $(this); 668 | data = self.data(classBase); 669 | if (data) { 670 | return self.unbind("." + classBase).data(classBase, {}).removeClass(filterBaseClass); 671 | } 672 | }); 673 | }; 674 | onWindowKeyUp = function(e) { 675 | var popup, popupId, targetIsInput, _ref, _results; 676 | targetIsInput = (_ref = e != null ? e.target.tagName.toLowerCase() : void 0) === 'input' || _ref === 'textarea'; 677 | switch (e.which) { 678 | case 27: 679 | _results = []; 680 | for (popupId in instances) { 681 | popup = instances[popupId]; 682 | _results.push(hideSmallipop(popup)); 683 | } 684 | return _results; 685 | break; 686 | case 37: 687 | if (!targetIsInput) { 688 | return tourPrev(); 689 | } 690 | break; 691 | case 39: 692 | if (!targetIsInput) { 693 | return tourNext(); 694 | } 695 | } 696 | }; 697 | getInstance = function(id, isTour) { 698 | var instance; 699 | if (id == null) { 700 | id = 'default'; 701 | } 702 | if (isTour == null) { 703 | isTour = false; 704 | } 705 | if (instances[id]) { 706 | return instances[id]; 707 | } 708 | instance = $(popupTemplate).css('opacity', 0).attr('id', "" + (classBase + nextInstanceId++)).addClass(classInstance).data(dataXDistance, 0).data(dataYDistance, 0).data(dataIsTour, isTour).bind(eventMouseOver, triggerMouseover).bind(eventMouseOut, triggerMouseout); 709 | $('body').append(instance); 710 | if (isTour) { 711 | instance.delegate("." + classTourPrev, eventClick, tourPrev).delegate("." + classTourNext, eventClick, tourNext).delegate("." + classTourClose + ", ." + classTourCloseIcon, eventClick, tourClose); 712 | } else { 713 | instance.delegate('a', eventClick, hideSmallipop); 714 | } 715 | if (nextInstanceId === 2) { 716 | $document.bind("" + eventClick + " " + eventTouchEnd, hideSmallipop); 717 | $window.bind(eventResize, queueRefreshPosition).bind(eventScroll, onWindowScroll).bind(eventKeyUp, onWindowKeyUp); 718 | } 719 | return instances[id] = instance; 720 | }; 721 | return $.fn.smallipop = function(options, hint) { 722 | var $popup; 723 | if (options == null) { 724 | options = {}; 725 | } 726 | if (hint == null) { 727 | hint = ''; 728 | } 729 | if (typeof options === 'string') { 730 | switch (options.toLowerCase()) { 731 | case 'show': 732 | showSmallipop.call(this.first().get(0)); 733 | break; 734 | case 'hide': 735 | hideSmallipop(this.first().get(0)); 736 | break; 737 | case 'destroy': 738 | destroy(this); 739 | break; 740 | case 'tour': 741 | runTour(this.first(), hint); 742 | break; 743 | case 'update': 744 | setContent(this.first(), hint); 745 | } 746 | return this; 747 | } 748 | options = $.extend(true, {}, sip.defaults, options); 749 | if (!cssAnimationsEnabled) { 750 | options.cssAnimations.enabled = false; 751 | } 752 | $popup = getInstance(options.popupId); 753 | return this.each(function() { 754 | var $objInfo, $self, isFormElement, newId, objHint, option, optionName, tagName, touchTrigger, tourTitle, triggerData, triggerEvents, triggerOptions, triggerPopupInstance, type, value; 755 | $self = $(this); 756 | tagName = $self[0].tagName.toLowerCase(); 757 | type = $self.attr('type'); 758 | triggerData = $self.data(); 759 | objHint = hint || $self.attr('title'); 760 | $objInfo = $("> ." + options.infoClass + ":first", $self); 761 | if ($objInfo.length) { 762 | objHint = $objInfo.clone(true, true).removeClass(options.infoClass); 763 | } 764 | if (objHint && !$self.hasClass(classInitialized)) { 765 | newId = lastId++; 766 | triggerEvents = {}; 767 | triggerPopupInstance = $popup; 768 | triggerOptions = $.extend(true, {}, options); 769 | if (typeof triggerData[classBase] === 'object') { 770 | $.extend(true, triggerOptions, triggerData[classBase]); 771 | } 772 | for (option in triggerData) { 773 | value = triggerData[option]; 774 | if (!(option.indexOf(classBase) >= 0)) { 775 | continue; 776 | } 777 | optionName = option.replace(classBase, ''); 778 | if (optionName) { 779 | optionName = optionName.substr(0, 1).toLowerCase() + optionName.substr(1); 780 | triggerOptions[optionName] = value; 781 | } 782 | } 783 | isFormElement = triggerOptions.handleInputs && (tagName === 'input' || tagName === 'select' || tagName === 'textarea'); 784 | if (triggerOptions.tourIndex) { 785 | tourTitle = triggerOptions.tourTitle || 'defaultTour'; 786 | triggerOptions.hideOnTriggerClick = triggerOptions.hideOnPopupClick = false; 787 | triggerPopupInstance = getInstance(tourTitle, true); 788 | if (!tours[tourTitle]) { 789 | tours[tourTitle] = []; 790 | } 791 | tours[tourTitle].push({ 792 | index: triggerOptions.tourIndex || 0, 793 | id: newId, 794 | trigger: $self, 795 | popupInstance: triggerPopupInstance 796 | }); 797 | } else { 798 | touchTrigger = triggerOptions.touchSupport && touchEnabled; 799 | if (isFormElement) { 800 | triggerOptions.hideOnTriggerClick = false; 801 | triggerEvents[eventFocus] = triggerMouseover; 802 | triggerEvents[eventBlur] = triggerMouseout; 803 | } else if (!touchTrigger) { 804 | triggerEvents[eventMouseOut] = triggerMouseout; 805 | } 806 | if (triggerOptions.triggerOnClick || touchTrigger) { 807 | triggerEvents[eventClick] = showSmallipop; 808 | } else { 809 | triggerEvents[eventClick] = triggerMouseout; 810 | triggerEvents[eventMouseOver] = triggerMouseover; 811 | } 812 | } 813 | $self.addClass("" + classInitialized + " " + classBase + newId).attr('title', '').data(classBase, { 814 | id: newId, 815 | hint: objHint, 816 | options: triggerOptions, 817 | tagName: tagName, 818 | type: type, 819 | tourTitle: tourTitle, 820 | popupInstance: triggerPopupInstance, 821 | isFormElement: isFormElement 822 | }).bind(triggerEvents); 823 | if (!triggerOptions.hideOnTriggerClick) { 824 | return $self.delegate('a', eventClick, hideSmallipop); 825 | } 826 | } 827 | }); 828 | }; 829 | }); 830 | -------------------------------------------------------------------------------- /www/js/jquery-0.6.1.smallipop.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | jQuery smallipop plugin 3 | @name jquery.smallipop.js 4 | @author Sebastian Helzle (sebastian@helzle.net or @sebobo) 5 | @version 0.6.1 6 | @date 2013-07-12 7 | @category jQuery plugin 8 | @copyright (c) 2011-2013 Small Improvements (http://www.small-improvements.com) 9 | @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 10 | */ 11 | (function(t){return"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){var e,n,o,i,a,r,d,s,u,l,p,f,c,h,g,v,m,T,C,b,y,w,x,I,A,H,O,S,k,D,P,z,N,E,M,B,L,j,F,Q,Z,Y,q,R,W,X,G,J,K,U,V,$,_,te,ee,ne,oe,ie,ae,re,de,se,ue,le,pe,fe,ce,he,ge,ve,me,Te,Ce,be,ye,we,xe,Ie,Ae,He,Oe,Se,ke,De,Pe,ze,Ne;return r="smallipop",u=r+"-hint",p=r+"-instance",s=r+"-content",f=r+"-left",c=r+"-right",d=r+"-bottom",i=r+"-align-left",a=r+"-align-right",l=r+"-initialized",h=r+"-theme-",g=r+"-tour",T=g+"-content",y=g+"-overlay",C=g+"-footer",m=g+"-close-icon",x=g+"-progress",v=g+"-close",w=g+"-prev",b=g+"-next",Q="focus."+r,F="click."+r,j="blur."+r,Y="mouseout."+r,q="mouseover."+r,X="touchend."+r,R="resize."+r,W="scroll."+r,Z="keyup."+r,B=r+"OriginalZIndex",H=r+"BeingShown",P=r+"HideDelayTimer",z=r+"ShowDelayTimer",N=r+"TriggerHovered",S=r+"PopupHovered",D=r+"Shown",k=r+"Position",E=r+"XDistance",M=r+"YDistance",O=r+"IsTour",he=RegExp(r+"-(align|bottom)w*","g"),ge=RegExp(r+"w+","g"),e=t(document),o=t(window),n=null,ie={},Pe={},A=null,de=1,ue=1,be=null,se=0,me=null,fe="
",t.smallipop=Ae={version:"0.6.1",defaults:{autoscrollPadding:200,contentAnimationSpeed:150,cssAnimations:{enabled:!1,show:"animated fadeIn",hide:"animated fadeOut"},funcEase:"easeInOutQuad",handleInputs:!0,hideDelay:500,hideTrigger:!1,hideOnPopupClick:!0,hideOnTriggerClick:!0,infoClass:u,invertAnimation:!1,popupId:"",popupOffset:31,popupYOffset:0,popupDistance:20,popupDelay:100,popupAnimationSpeed:200,preferredPosition:"top",referencedContent:null,theme:"default",touchSupport:!0,tourHighlight:!1,tourHighlightColor:"#222",tourHighlightFadeDuration:200,tourHighlightOpacity:.5,tourHighlightZIndex:9997,tourNavigationEnabled:!0,triggerAnimationSpeed:150,triggerOnClick:!1,onAfterHide:null,onAfterShow:null,onBeforeHide:null,onBeforeShow:null,onTourClose:null,onTourNext:null,onTourPrev:null,windowPadding:30,labels:{prev:"Prev",next:"Next",close:"Close",of:"of"}}},t.easing.easeInOutQuad||(t.easing.easeInOutQuad=function(t,e,n,o,i){return 1>(e/=i/2)?o/2*e*e+n:-o/2*(--e*(e-2)-1)+n}),Te=function(){var t,e,n,o,i;i=[];for(n in Pe)e=Pe[n],i.push(function(){var n,i,a;for(a=[],n=0,i=e.length;i>n;n++)t=e[n],o=t.trigger,o.data(B)?a.push(o.css("zIndex",o.data(B))):a.push(void 0);return a}());return i},He="undefined"!=typeof Modernizr&&null!==Modernizr?Modernizr.touch:void 0,I="undefined"!=typeof Modernizr&&null!==Modernizr?Modernizr.cssanimations:void 0,ee=function(e){return t("."+(r+e))},te=function(){return n||(n=t("
").appendTo(t("body")).fadeOut(0)),n},oe=function(t){return te().fadeOut(t.tourHighlightFadeDuration),Te()},ne=function(e){var n,o,i,a,d,s,u,l,p,f,c,h,g,v,m,T,C,b;clearTimeout(be),l=(null!=e?e.target:void 0)?t(e.target):e,b=[];for(s in ie)a=ie[s],d=a.data(),(u=d[D])&&(p=ee(u),c=p.is(l),f=p.data(r),h=f.options||Ae.defaults,(!d[O]&&!f.isFormElement||a.is(l)||c&&a.is(h.popupInstance))&&(d[O]&&(A=null,null!=(T=p.data(r))&&"function"==typeof(m=T.options).onTourClose&&m.onTourClose(),oe(h)),i=!h.hideOnTriggerClick&&c,o=!h.hideOnPopupClick&&a.find(l).length,(!l||!p.length||"click"!==(C=null!=e?e.type:void 0)&&"touchend"!==C||!i&&!o)&&(u&&h.hideTrigger&&p.stop(!0).fadeTo(h.triggerAnimationSpeed,1),a.data(P,null).data(H,!1),h.cssAnimations.enabled?(a.removeClass(h.cssAnimations.show).addClass(h.cssAnimations.hide).data(D,""),h.onAfterHide?b.push(window.setTimeout(h.onAfterHide,h.popupAnimationSpeed)):b.push(void 0)):(n=h.invertAnimation?-1:1,g=d[E]*n,v=d[M]*n,b.push(a.stop(!0).animate({top:"-="+v,left:"+="+g,opacity:0},h.popupAnimationSpeed,h.funcEase,function(){var e;return e=t(this),e.data(H)||e.css("display","none").data(D,""),"function"==typeof h.onAfterHide?h.onAfterHide():void 0}))))));return b},xe=function(e){var n,o;return(n=t(this).data(r))?(n.popupInstance.data(D)===n.id||"checkbox"!==(o=!n.type)&&"radio"!==o||null!=e&&e.preventDefault(),Ne.call(this)):void 0},re=function(t){return clearTimeout(t.data(P)),clearTimeout(t.data(z))},ce=function(t){return null==t&&(t=50),clearTimeout(me),me=setTimeout(ve,t)},V=function(t,e){return t?(t.match(e)||[]).join(" "):void 0},K=function(t,e){return V(e,he)},U=function(t,e){return V(e,ge)},ve=function(e){var n,o,s,u,l,g,v,m,T,C,b,y,w,x,I,A,O,S,P,z,N,B,L,j,F,Q,Z,Y,q,R,W,X,J,U,V,$,_,te;null==e&&(e=!0),te=[];for(w in ie)l=ie[w],v=l.data(),B=v[D],B&&(j=ee(B),F=j.data(r),u=F.options,l.removeClass(K),e&&(L=h+u.theme.split(" ").join(" "+h),l.attr("class",""+p+" "+L)),Q=t(window),X=V=u.popupDistance,J=u.popupOffset,$=u.popupYOffset,n="fixed"===l.data(k),y=l.outerHeight(),A=l.outerWidth(),g=A/2,R=Q.width(),Z=Q.height(),q=Q.scrollTop(),Y=Q.scrollLeft(),W=u.windowPadding,o=j.offset(),z=j.outerWidth(),P=j.outerHeight(),N=o.top-q,x=o.left+z/2,I=o.top-y+$,O=y+u.popupDistance-$,b=N-O,m=Z-N-P-O,T=o.left-A-J,C=R-o.left-z-A,S=u.preferredPosition,"left"===S||"right"===S?(V=0,I+=P/2+y/2,"right"===S&&C>W||W>T?(l.addClass(c),x=o.left+z+J):(l.addClass(f),x=o.left-A-J,X=-X)):(X=0,x+g>R-W?(x-=2*g-J,l.addClass(i)):W>x-g?(x-=J,l.addClass(a)):x-=g,W>x&&(x=W),("bottom"===S&&m>W||W>b)&&(V=-V,I+=y+P-2*$,l.addClass(d))),P>y&&(_=I+y+W-V+$-q-Z,_>0&&(I=Math.max(I-_-W,o.top+$+W+V))),z>A&&(U=x+A+W+X+J-Y-R,U>0&&(x=Math.max(x-U+W,o.left+J+W-X))),u.hideTrigger&&j.stop(!0).fadeTo(u.triggerAnimationSpeed,0),s=0,(!v[H]||u.cssAnimations.enabled)&&(I-=V,x+=X,X=V=0,s=1),n&&(x-=Y,I-=q),l.data(E,X).data(M,V).css({top:I,left:x,display:"block",opacity:s}),te.push(G(l,{top:"-="+V,left:"+="+X,opacity:1})));return te},$=function(){return ve(!1)},G=function(t,e){var n,o;return n=(null!=(o=ee(t.data(D)).data(r))?o.options:void 0)||Ae.defaults,n.cssAnimations.enabled?(t.addClass(n.cssAnimations.show),window.setTimeout(function(){return J(t,n)},n.popupAnimationSpeed)):t.stop(!0).animate(e,n.popupAnimationSpeed,n.funcEase,function(){return J(t,n)})},J=function(t,e){var n;return n=t.data(),n[H]?(t.data(H,!1),"function"==typeof e.onAfterShow?e.onAfterShow(ee(n[D])):void 0):void 0},we=function(e,o){var i,a,d,u,l,p,f,c,h;return null==o&&(o=""),c=e.data(r),h=c.options,d=c.popupInstance,d.data(N)?(p=d.data(D),p&&(i=ee(p),i.length&&(a=i.data(r).options||Ae.defaults,a.hideTrigger&&i.stop(!0).fadeTo(a.fadeSpeed,1))),h.tourHighlight&&h.tourIndex?(f=te().css({backgroundColor:h.tourHighlightColor,zIndex:h.tourHighlightZIndex}),Te(),"static"===e.css("position")&&e.css("position","relative"),e.data(B)||e.data(B,e.css("zIndex")),e.css("zIndex",h.tourHighlightZIndex+1),f.fadeTo(h.tourHighlightFadeDuration,h.tourHighlightOpacity)):n&&oe(h),u=o||c.hint,h.referencedContent&&!o&&(u=t(h.referencedContent).clone(!0,!0)||u),l=ae(e)?"fixed":"absolute",p!==c.id&&d.hide(0),d.data(H,!0).data(D,c.id).data(k,l).find("."+s).empty().append(u),d.css("position",l),ce(0)):void 0},ae=function(t){var e;for(e=t;e.length&&"HTML"!==e[0].nodeName;){if("fixed"===e.css("position"))return!0;e=e.parent()}return!1},Ne=function(){var e,n,o,i,a,d;return i=n=t(this),e=i.hasClass(l),e||(i=ee(n.data(D))),i.length?(a=i.data(r),n=a.popupInstance.data(e?N:S,!0),re(n),o=n.data(D),o!==a.id||0===n.css("opacity")?("function"==typeof(d=a.options).onBeforeShow&&d.onBeforeShow(i),n.data(z,setTimeout(function(){return we(i)},a.options.popupDelay))):void 0):void 0},ze=function(){var e,n,o,i,a,d;return i=n=t(this),e=i.hasClass(l),e||(i=ee(n.data(D))),i.length?(a=i.data(r),n=a.popupInstance.data(e?N:S,!1),re(n),o=n.data(),o[S]||o[N]?void 0:("function"==typeof(d=a.options).onBeforeHide&&d.onBeforeHide(i),n.data(P,setTimeout(function(){return ne(n)},a.options.hideDelay)))):void 0},pe=function(){return clearTimeout(be),be=setTimeout($,250)},ye=function(e,n){var o,i,a;if(null!=e?e.length:void 0)return a=e.data(r),o=a.tourTitle,i=o?a.popupInstance.find("."+T):a.popupInstance.find("."+s),i.html()!==n?i.stop(!0).fadeTo(a.options.contentAnimationSpeed,0,function(){return t(this).html(n).fadeTo(a.options.contentAnimationSpeed,1),ve()}):void 0},Ce=function(t,e){var n,o,i,a,d,s;if(a=t.data(r),i=null!=a?a.tourTitle:void 0,i&&Pe[i])for(Pe[i].sort(function(t,e){return t.index-e.index}),"number"!=typeof e||0!==e%1?e=-1:e-=1,A=i,n=Pe[i],o=d=0,s=n.length-1;s>=0?s>=d:d>=s;o=s>=0?++d:--d)if(e>=0&&o===e||0>e&&n[o].id===a.id)return De(i,o)},De=function(e,n){var o,i,a,d,s,u,l;return(a=Pe[e])?(i=a[n].trigger,l=i.data(r),u=l.options,s=u.tourNavigationEnabled,d="",s&&(d+="
"+(""+(n+1)+" "+u.labels.of+" "+a.length+"
"),n>0&&(d+=""+u.labels.prev+""),a.length-1>n&&(d+=""+u.labels.next+"")),s&&n!==a.length-1||(d+=""+u.labels.close+""),o=t("
"+("Χ")+("
"+d+"
")),o.eq(0).append(l.hint),re(l.popupInstance),l.popupInstance.data(N,!0),Ie(i,o)):void 0},Ie=function(n,i){var a,d,s,u;return d=n.offset().top,a=d-e.scrollTop(),u=o.height(),s=n.data(r).options,!ae(n)&&(s.autoscrollPadding>a||a>u-s.autoscrollPadding)?t("html, body").animate({scrollTop:d-u/2},800,"swing",function(){return we(n,i)}):we(n,i)},Se=function(t){var e,n,o,i,a,d,s;if(null!=t&&t.preventDefault(),n=Pe[A])for(e=n[0].popupInstance,i=e.data(D)||n[0].id,o=d=0,s=n.length-2;s>=0?s>=d:d>=s;o=s>=0?++d:--d)if(n[o].id===i&&(a=n[o].trigger.data(r).options,a.tourNavigationEnabled))return"function"==typeof a.onTourNext&&a.onTourNext(n[o+1].trigger),De(A,o+1)},ke=function(t){var e,n,o,i,a,d,s;if(null!=t&&t.preventDefault(),n=Pe[A])for(e=n[0].popupInstance,i=e.data(D)||n[0].id,o=d=1,s=n.length-1;s>=1?s>=d:d>=s;o=s>=1?++d:--d)if(n[o].id===i&&(a=n[o].trigger.data(r).options,a.tourNavigationEnabled))return"function"==typeof a.onTourPrev&&a.onTourPrev(n[o-1].trigger),De(A,o-1)},Oe=function(e){var n;return null!=e&&e.preventDefault(),n=t(e.target).closest("."+p),ne(n)},L=function(e){return e.each(function(){var e,n;return n=t(this),e=n.data(r),e?n.unbind("."+r).data(r,{}).removeClass(U):void 0})},le=function(t){var e,n,o,i,a;switch(o="input"===(i=null!=t?t.target.tagName.toLowerCase():void 0)||"textarea"===i,t.which){case 27:a=[];for(n in ie)e=ie[n],a.push(ne(e));return a;case 37:if(!o)return ke();break;case 39:if(!o)return Se()}},_=function(n,i){var a;return null==n&&(n="default"),null==i&&(i=!1),ie[n]?ie[n]:(a=t(fe).css("opacity",0).attr("id",""+(r+ue++)).addClass(p).data(E,0).data(M,0).data(O,i).bind(q,Ne).bind(Y,ze),t("body").append(a),i?a.delegate("."+w,F,ke).delegate("."+b,F,Se).delegate("."+v+", ."+m,F,Oe):a.delegate("a",F,ne),2===ue&&(e.bind(""+F+" "+X,ne),o.bind(R,ce).bind(W,pe).bind(Z,le)),ie[n]=a)},t.fn.smallipop=function(e,n){var o;if(null==e&&(e={}),null==n&&(n=""),"string"==typeof e){switch(e.toLowerCase()){case"show":xe.call(this.first().get(0));break;case"hide":ne(this.first().get(0));break;case"destroy":L(this);break;case"tour":Ce(this.first(),n);break;case"update":ye(this.first(),n)}return this}return e=t.extend(!0,{},Ae.defaults,e),I||(e.cssAnimations.enabled=!1),o=_(e.popupId),this.each(function(){var i,a,d,s,u,p,f,c,h,g,v,m,T,C,b,y;if(a=t(this),c=a[0].tagName.toLowerCase(),b=a.attr("type"),v=a.data(),u=n||a.attr("title"),i=t("> ."+e.infoClass+":first",a),i.length&&(u=i.clone(!0,!0).removeClass(e.infoClass)),u&&!a.hasClass(l)){s=de++,m={},C=o,T=t.extend(!0,{},e),"object"==typeof v[r]&&t.extend(!0,T,v[r]);for(p in v)y=v[p],p.indexOf(r)>=0&&(f=p.replace(r,""),f&&(f=f.substr(0,1).toLowerCase()+f.substr(1),T[f]=y));if(d=T.handleInputs&&("input"===c||"select"===c||"textarea"===c),T.tourIndex?(g=T.tourTitle||"defaultTour",T.hideOnTriggerClick=T.hideOnPopupClick=!1,C=_(g,!0),Pe[g]||(Pe[g]=[]),Pe[g].push({index:T.tourIndex||0,id:s,trigger:a,popupInstance:C})):(h=T.touchSupport&&He,d?(T.hideOnTriggerClick=!1,m[Q]=Ne,m[j]=ze):h||(m[Y]=ze),T.triggerOnClick||h?m[F]=xe:(m[F]=ze,m[q]=Ne)),a.addClass(""+l+" "+r+s).attr("title","").data(r,{id:s,hint:u,options:T,tagName:c,type:b,tourTitle:g,popupInstance:C,isFormElement:d}).bind(m),!T.hideOnTriggerClick)return a.delegate("a",F,ne)}})}}); -------------------------------------------------------------------------------- /www/js/show-links.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Load it with: 3 | * s=document.createElement('script');s.src='http://stapibas.bogo/js/show-links.js';document.body.appendChild(s); 4 | */ 5 | var scripts = document.getElementsByTagName("script"); 6 | var thisScript = scripts[scripts.length-1]; 7 | var thisScriptsSrc = thisScript.src; 8 | var stapibasUrl = thisScriptsSrc.replace('js/show-links.js', ''); 9 | //var stapibasUrl = 'http://stapibas.bogo/'; 10 | 11 | var pageUrl = window.location.href; 12 | 13 | function loadScript(url, callback) 14 | { 15 | var script = document.createElement('script'); 16 | script.type = 'text/javascript'; 17 | script.src = url; 18 | script.onreadystatechange = callback; 19 | script.onload = callback; 20 | document.getElementsByTagName('head')[0].appendChild(script); 21 | } 22 | 23 | function loadData() 24 | { 25 | jQuery('head').append( 26 | '' 28 | + '' 30 | ); 31 | jQuery.ajax( 32 | stapibasUrl + 'api/links.php?url=' 33 | + encodeURIComponent(fixUrl(pageUrl)) 34 | ).done(function(data) {showData(data);}) 35 | .fail(function(data) {showError(data);}); 36 | } 37 | 38 | function showData(data) 39 | { 40 | var items = jQuery('.e-content a'); 41 | if (items.length == 0) { 42 | items = jQuery('a'); 43 | } 44 | 45 | //collect stats 46 | var stats = { 47 | links: 0 48 | }; 49 | $.each(data.links, function(key, link) { 50 | stats.links++; 51 | if (!stats[link.status]) { 52 | stats[link.status] = 1; 53 | } else { 54 | stats[link.status]++; 55 | } 56 | }); 57 | var statlist = ''; 58 | $.each(stats, function(status, count) { 59 | statlist = statlist + '
  • ' + count + ' ' + status + '
  • '; 60 | }); 61 | $('body').prepend( 62 | '
    ' 63 | + 'Linkback stats: ' 64 | + '
      ' + statlist + '
    ' 65 | + '
    ' 66 | ); 67 | 68 | //add link info 69 | items.each(function(key, elem) { 70 | if (!data.links[fixUrl(elem.href)]) { 71 | return; 72 | } 73 | var link = data.links[fixUrl(elem.href)]; 74 | $(elem).addClass('stapibas-link') 75 | .addClass('stapibas-status-' + link.status); 76 | $(elem).smallipop( 77 | { 78 | theme: 'white' 79 | }, 80 | '

    Linkback information

    ' 81 | + '
    ' 82 | + '
    Status
    ' + link.status + '
    ' 83 | + '
    Pinged
    ' + link.pinged + '
    ' 84 | + '
    Updated
    ' + link.updated + '
    ' 85 | + '
    Tries
    ' + link.tries + '
    ' 86 | + '
    Error code
    ' + link.error.code + '
    ' 87 | + '
    Error message
    ' + link.error.message + '
    ' 88 | + '
    ' 89 | ); 90 | }); 91 | } 92 | 93 | function fixUrl(url) 94 | { 95 | return url.replace(/www.bogo/, 'cweiske.de'); 96 | } 97 | 98 | function showError(data) 99 | { 100 | $('body').prepend( 101 | '
    ' 102 | + 'Error loading linkback data: ' 103 | + data.status + ' ' + data.statusText + '' 104 | + '
    ' 105 | ); 106 | } 107 | 108 | loadScript( 109 | stapibasUrl + 'js/jquery-2.1.0.js', 110 | function() { 111 | loadScript(stapibasUrl + 'js/jquery-0.6.1.smallipop.js', loadData); 112 | } 113 | ); 114 | -------------------------------------------------------------------------------- /www/render.php: -------------------------------------------------------------------------------- 1 | db = new PDO($dbdsn, $dbuser, $dbpass); 30 | $deps->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 31 | $deps->options = array( 32 | 'template_dir' => __DIR__ . '/../data/templates/default/' 33 | ); 34 | 35 | $r = new Renderer_Html($deps); 36 | echo $r->render($url); 37 | ?> 38 | -------------------------------------------------------------------------------- /www/request-feed-update.php: -------------------------------------------------------------------------------- 1 | query( 29 | 'SELECT f_id, f_needs_update FROM feeds WHERE f_url = ' . $db->quote($url) 30 | ); 31 | $row = $res->fetch(PDO::FETCH_OBJ); 32 | if ($row === false) { 33 | header('HTTP/1.0 404 Not Found'); 34 | echo "Feed URL could not be found in database\n"; 35 | exit(1); 36 | } 37 | if ($row->f_needs_update == 1) { 38 | header('HTTP/1.0 200 OK'); 39 | echo "Already in the queue\n"; 40 | exit(0); 41 | } 42 | 43 | $db->exec( 44 | 'UPDATE feeds SET f_needs_update = 1' 45 | . ' WHERE f_id = ' . $db->quote($row->f_id) 46 | ); 47 | 48 | header('HTTP/1.0 202 Accepted'); 49 | echo "Feed has been put into the queue\n"; 50 | exit(0); 51 | ?> 52 | -------------------------------------------------------------------------------- /www/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow / 3 | -------------------------------------------------------------------------------- /www/www-header.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 11 | ?> 12 | -------------------------------------------------------------------------------- /www/xmlrpc.php: -------------------------------------------------------------------------------- 1 | getRequest()->setConfig( 12 | array( 13 | 'ssl_verify_peer' => false, 14 | 'ssl_verify_host' => false 15 | ) 16 | ); 17 | $callbacks = array( 18 | $fs, 19 | new \PEAR2\Services\Linkback\Server\Callback\LinkExists(), 20 | new Linkback_DbStorage($db), 21 | new Linkback_Mailer() 22 | ); 23 | $s->setCallbacks($callbacks); 24 | $s->run(); 25 | ?> 26 | --------------------------------------------------------------------------------