From 7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7 Mon Sep 17 00:00:00 2001
From: Theo Chatzimichos
Date: Sun, 10 Mar 2013 12:10:26 +0100
Subject: update plugins
---
plugins/akismet/.htaccess | 6 +
plugins/akismet/admin.php | 27 +-
plugins/akismet/akismet.js | 12 +-
plugins/akismet/akismet.php | 39 +-
plugins/akismet/readme.txt | 13 +-
plugins/jetpack/_inc/gallery-settings.js | 19 +
.../jetpack/_inc/images/module-icons-sprite-2x.png | Bin 37559 -> 72735 bytes
.../jetpack/_inc/images/module-icons-sprite.png | Bin 11465 -> 31542 bytes
plugins/jetpack/_inc/images/publicize.png | Bin 0 -> 113219 bytes
.../jetpack/_inc/images/screenshots/carousel.png | Bin 0 -> 361672 bytes
.../jetpack/_inc/images/screenshots/custom-css.png | Bin 0 -> 43048 bytes
plugins/jetpack/_inc/images/screenshots/likes.png | Bin 0 -> 48953 bytes
.../screenshots/mobile-push-notifications.jpg | Bin 0 -> 29830 bytes
.../_inc/images/screenshots/mobile-theme.png | Bin 0 -> 37559 bytes
plugins/jetpack/_inc/images/screenshots/notes.png | Bin 0 -> 27450 bytes
.../_inc/images/screenshots/post-by-email.png | Bin 0 -> 28349 bytes
.../jetpack/_inc/images/screenshots/publicize.png | Bin 0 -> 113219 bytes
.../_inc/images/screenshots/tiled-gallery.png | Bin 0 -> 192182 bytes
plugins/jetpack/_inc/jetpack.css | 247 +-
plugins/jetpack/_inc/jetpack.js | 58 +-
plugins/jetpack/_inc/jquery.inview.js | 143 +
plugins/jetpack/_inc/jquery.jetpack-resize.js | 275 +
plugins/jetpack/_inc/jquery.spin.js | 86 +
plugins/jetpack/_inc/postmessage.js | 438 +
plugins/jetpack/_inc/spin.js | 301 +
plugins/jetpack/class.jetpack-ixr-client.php | 2 -
plugins/jetpack/class.jetpack-post-images.php | 440 +
plugins/jetpack/class.jetpack-signature.php | 24 +-
plugins/jetpack/class.jetpack-user-agent.php | 1331 +++
plugins/jetpack/class.jetpack-xmlrpc-server.php | 225 +-
plugins/jetpack/class.json-api-endpoints.php | 3912 ++++++++
plugins/jetpack/class.json-api.php | 443 +
plugins/jetpack/class.photon.php | 554 ++
plugins/jetpack/functions.compat.php | 15 +
plugins/jetpack/functions.gallery.php | 50 +
plugins/jetpack/functions.opengraph.php | 158 +
plugins/jetpack/functions.photon.php | 160 +
plugins/jetpack/jetpack.php | 2107 ++++-
plugins/jetpack/languages/jetpack-ar.mo | Bin 0 -> 14004 bytes
plugins/jetpack/languages/jetpack-az.mo | Bin 2398 -> 2886 bytes
plugins/jetpack/languages/jetpack-bs_BA.mo | Bin 59865 -> 76528 bytes
plugins/jetpack/languages/jetpack-ca.mo | Bin 52959 -> 76602 bytes
plugins/jetpack/languages/jetpack-cs_CZ.mo | Bin 5260 -> 8242 bytes
plugins/jetpack/languages/jetpack-da_DK.mo | Bin 48806 -> 44830 bytes
plugins/jetpack/languages/jetpack-de_DE.mo | Bin 43096 -> 117625 bytes
plugins/jetpack/languages/jetpack-el.mo | Bin 0 -> 8337 bytes
plugins/jetpack/languages/jetpack-es_ES.mo | Bin 45191 -> 49121 bytes
plugins/jetpack/languages/jetpack-fa_IR.mo | Bin 32814 -> 47862 bytes
plugins/jetpack/languages/jetpack-fi.mo | Bin 21934 -> 27526 bytes
plugins/jetpack/languages/jetpack-fr_FR.mo | Bin 69552 -> 113684 bytes
plugins/jetpack/languages/jetpack-gl_ES.mo | Bin 30784 -> 33955 bytes
plugins/jetpack/languages/jetpack-he_IL.mo | Bin 62301 -> 75590 bytes
plugins/jetpack/languages/jetpack-hr.mo | Bin 31130 -> 35895 bytes
plugins/jetpack/languages/jetpack-hu_HU.mo | Bin 30043 -> 77710 bytes
plugins/jetpack/languages/jetpack-id_ID.mo | Bin 49894 -> 52347 bytes
plugins/jetpack/languages/jetpack-it_IT.mo | Bin 33628 -> 50000 bytes
plugins/jetpack/languages/jetpack-ja.mo | Bin 67264 -> 116549 bytes
plugins/jetpack/languages/jetpack-ko_KR.mo | Bin 0 -> 110462 bytes
plugins/jetpack/languages/jetpack-lt_LT.mo | Bin 0 -> 12440 bytes
plugins/jetpack/languages/jetpack-mk_MK.mo | Bin 22392 -> 23594 bytes
plugins/jetpack/languages/jetpack-my_MM.mo | Bin 5741 -> 11071 bytes
plugins/jetpack/languages/jetpack-nb_NO.mo | Bin 64081 -> 88203 bytes
plugins/jetpack/languages/jetpack-nl_NL.mo | Bin 32682 -> 38043 bytes
plugins/jetpack/languages/jetpack-nn_NO.mo | Bin 9040 -> 14701 bytes
plugins/jetpack/languages/jetpack-pl_PL.mo | Bin 5948 -> 11997 bytes
plugins/jetpack/languages/jetpack-pt_BR.mo | Bin 62629 -> 107473 bytes
plugins/jetpack/languages/jetpack-pt_PT.mo | Bin 40066 -> 46165 bytes
plugins/jetpack/languages/jetpack-ro_RO.mo | Bin 6675 -> 10652 bytes
plugins/jetpack/languages/jetpack-ru_RU.mo | Bin 41905 -> 49300 bytes
plugins/jetpack/languages/jetpack-sa_IN.mo | Bin 1008 -> 1006 bytes
plugins/jetpack/languages/jetpack-sk_SK.mo | Bin 7567 -> 10517 bytes
plugins/jetpack/languages/jetpack-sq.mo | Bin 56036 -> 112583 bytes
plugins/jetpack/languages/jetpack-sr_RS.mo | Bin 36803 -> 44079 bytes
plugins/jetpack/languages/jetpack-sv_SE.mo | Bin 19356 -> 24534 bytes
plugins/jetpack/languages/jetpack-th.mo | Bin 0 -> 18421 bytes
plugins/jetpack/languages/jetpack-tr_TR.mo | Bin 23475 -> 35311 bytes
plugins/jetpack/languages/jetpack-zh_CN.mo | Bin 0 -> 7637 bytes
plugins/jetpack/languages/jetpack-zh_TW.mo | Bin 0 -> 93742 bytes
plugins/jetpack/locales.php | 139 +-
plugins/jetpack/modules/after-the-deadline.php | 2 +-
.../modules/after-the-deadline/config-options.php | 4 +-
.../modules/carousel/jetpack-carousel-ie8fix.css | 8 +
.../jetpack/modules/carousel/jetpack-carousel.css | 89 +-
.../jetpack/modules/carousel/jetpack-carousel.js | 344 +-
.../jetpack/modules/carousel/jetpack-carousel.php | 85 +-
.../modules/carousel/rtl/jetpack-carousel-rtl.css | 1106 +++
plugins/jetpack/modules/comments.php | 9 +
plugins/jetpack/modules/comments/base.php | 47 +-
plugins/jetpack/modules/comments/comments.php | 134 +
plugins/jetpack/modules/contact-form/admin.php | 245 +-
.../jetpack/modules/contact-form/css/grunion.css | 7 +-
.../modules/contact-form/grunion-contact-form.php | 1866 ++--
.../contact-form/images/grunion-menu-2x.png | Bin 541 -> 53439 bytes
.../contact-form/images/grunion-menu-hover-2x.png | Bin 697 -> 51427 bytes
.../images/grunion-remove-field-2x.png | Bin 201 -> 47832 bytes
.../images/grunion-remove-field-hover-2x.png | Bin 207 -> 47745 bytes
plugins/jetpack/modules/contact-form/js/grunion.js | 60 +-
plugins/jetpack/modules/custom-css.php | 26 +
.../modules/custom-css/csstidy/class.csstidy.php | 1241 +++
.../custom-css/csstidy/class.csstidy_ctype.php | 46 +
.../custom-css/csstidy/class.csstidy_optimise.php | 936 ++
.../custom-css/csstidy/class.csstidy_print.php | 408 +
.../modules/custom-css/csstidy/cssparse.css | 118 +
.../modules/custom-css/csstidy/cssparsed.css | 29 +
.../modules/custom-css/csstidy/data-wp.inc.php | 75 +
.../modules/custom-css/csstidy/data.inc.php | 661 ++
.../modules/custom-css/csstidy/lang.inc.php | 311 +
.../custom-css/csstidy/wordpress-standard.tpl | 10 +
plugins/jetpack/modules/custom-css/custom-css.php | 1468 +++
.../modules/custom-css/custom-css/blank.css | 1 +
.../modules/custom-css/custom-css/js/ace/ace.js | 11 +
.../custom-css/custom-css/js/ace/mode-css.js | 1 +
.../custom-css/custom-css/js/ace/readme.txt | 1 +
.../custom-css/custom-css/js/ace/theme-textmate.js | 1 +
.../custom-css/custom-css/js/ace/worker-css.js | 1 +
.../custom-css/custom-css/js/safecss-ace.js | 69 +
.../custom-css/custom-css/preprocessors.php | 57 +
.../custom-css/preprocessors/lessc.inc.php | 3359 +++++++
.../custom-css/preprocessors/scss.inc.php | 3759 ++++++++
plugins/jetpack/modules/enhanced-distribution.php | 23 +-
.../modules/featured-content/featured-content.php | 454 +
plugins/jetpack/modules/gravatar-hovercards.php | 17 +-
plugins/jetpack/modules/holiday-snow.php | 72 +
plugins/jetpack/modules/holiday-snow/snowstorm.js | 539 ++
plugins/jetpack/modules/infinite-scroll.php | 196 +
.../jetpack/modules/infinite-scroll/infinity.css | 137 +
.../jetpack/modules/infinite-scroll/infinity.js | 490 +
.../jetpack/modules/infinite-scroll/infinity.php | 945 ++
.../infinite-scroll/themes/twentyeleven.css | 45 +
.../infinite-scroll/themes/twentyeleven.php | 27 +
.../modules/infinite-scroll/themes/twentyten.css | 25 +
.../modules/infinite-scroll/themes/twentyten.php | 48 +
.../infinite-scroll/themes/twentytwelve.css | 33 +
.../infinite-scroll/themes/twentytwelve.php | 46 +
plugins/jetpack/modules/json-api.php | 19 +
plugins/jetpack/modules/latex.php | 6 +-
plugins/jetpack/modules/likes.php | 970 ++
plugins/jetpack/modules/likes/style.css | 189 +
plugins/jetpack/modules/minileven.php | 108 +
.../modules/minileven/images/wp-app-devices.png | Bin 0 -> 1865 bytes
plugins/jetpack/modules/minileven/minileven.php | 313 +
.../minileven/theme/pub/minileven/comments.php | 52 +
.../theme/pub/minileven/content-gallery.php | 78 +
.../minileven/theme/pub/minileven/content.php | 60 +
.../minileven/theme/pub/minileven/footer.php | 36 +
.../minileven/theme/pub/minileven/functions.php | 152 +
.../minileven/theme/pub/minileven/header.php | 49 +
.../minileven/theme/pub/minileven/image.php | 98 +
.../theme/pub/minileven/inc/custom-header.php | 101 +
.../theme/pub/minileven/inc/template-tags.php | 99 +
.../minileven/theme/pub/minileven/inc/tweaks.php | 94 +
.../minileven/theme/pub/minileven/index.php | 73 +
.../minileven/theme/pub/minileven/js/small-menu.js | 41 +
.../modules/minileven/theme/pub/minileven/page.php | 42 +
.../modules/minileven/theme/pub/minileven/rtl.css | 582 ++
.../minileven/theme/pub/minileven/screenshot.png | Bin 0 -> 59059 bytes
.../minileven/theme/pub/minileven/searchform.php | 12 +
.../minileven/theme/pub/minileven/sidebar.php | 12 +
.../minileven/theme/pub/minileven/style.css | 1437 +++
plugins/jetpack/modules/mobile-push.php | 11 +
plugins/jetpack/modules/module-extras.php | 58 +
plugins/jetpack/modules/module-info.php | 347 +-
plugins/jetpack/modules/notes.php | 195 +
plugins/jetpack/modules/photon.php | 9 +
plugins/jetpack/modules/photon/photon.js | 42 +
plugins/jetpack/modules/post-by-email.php | 240 +
.../modules/post-by-email/post-by-email.css | 6 +
.../jetpack/modules/post-by-email/post-by-email.js | 129 +
plugins/jetpack/modules/publicize.php | 283 +
.../jetpack/modules/publicize/assets/connected.gif | Bin 0 -> 1681 bytes
.../modules/publicize/assets/facebook-logo.png | Bin 0 -> 37624 bytes
.../modules/publicize/assets/linkedin-logo.png | Bin 0 -> 6882 bytes
.../jetpack/modules/publicize/assets/publicize.css | 175 +
.../jetpack/modules/publicize/assets/publicize.js | 108 +
.../modules/publicize/assets/rtl/publicize-rtl.css | 177 +
.../jetpack/modules/publicize/assets/spinner.gif | Bin 0 -> 457 bytes
.../modules/publicize/assets/tumblr-logo.png | Bin 0 -> 9001 bytes
.../modules/publicize/assets/twitter-logo.png | Bin 0 -> 4623 bytes
.../modules/publicize/assets/yahoo-logo.png | Bin 0 -> 9675 bytes
.../modules/publicize/publicize-jetpack.php | 578 ++
plugins/jetpack/modules/publicize/publicize.php | 328 +
plugins/jetpack/modules/publicize/ui.php | 545 ++
plugins/jetpack/modules/sharedaddy.php | 2 +-
.../jetpack/modules/sharedaddy/admin-sharing.css | 140 +-
.../modules/sharedaddy/images/icon-facebook-2x.png | Bin 0 -> 671 bytes
.../modules/sharedaddy/images/icon-facebook.png | Bin 0 -> 887 bytes
.../modules/sharedaddy/images/icon-twitter-2x.png | Bin 0 -> 1451 bytes
.../modules/sharedaddy/images/icon-twitter.png | Bin 0 -> 523 bytes
.../sharedaddy/images/icon-wordpress-2x.png | Bin 0 -> 657 bytes
.../modules/sharedaddy/images/icon-wordpress.png | Bin 0 -> 775 bytes
plugins/jetpack/modules/sharedaddy/sharedaddy.php | 57 +-
.../jetpack/modules/sharedaddy/sharing-service.php | 217 +-
.../jetpack/modules/sharedaddy/sharing-sources.php | 566 +-
plugins/jetpack/modules/sharedaddy/sharing.css | 142 +-
plugins/jetpack/modules/sharedaddy/sharing.js | 123 +-
plugins/jetpack/modules/sharedaddy/sharing.php | 128 +-
plugins/jetpack/modules/shortcodes/audio.php | 45 +-
.../shortcodes/css/rtl/slideshow-shortcode-rtl.css | 131 +
.../modules/shortcodes/css/slideshow-shortcode.css | 129 +
plugins/jetpack/modules/shortcodes/googlemaps.php | 2 +-
.../shortcodes/img/slideshow-controls-2x.png | Bin 0 -> 1944 bytes
.../modules/shortcodes/img/slideshow-controls.png | Bin 0 -> 1009 bytes
.../modules/shortcodes/img/slideshow-loader.gif | Bin 0 -> 13545 bytes
.../modules/shortcodes/js/audio-shortcode.js | 154 +
.../jetpack/modules/shortcodes/js/jquery.cycle.js | 1551 ++++
.../modules/shortcodes/js/slideshow-shortcode.js | 187 +
plugins/jetpack/modules/shortcodes/polldaddy.php | 6 +-
plugins/jetpack/modules/shortcodes/slideshow.php | 208 +
plugins/jetpack/modules/shortcodes/soundcloud.php | 246 +-
plugins/jetpack/modules/shortcodes/ted.php | 68 +
plugins/jetpack/modules/shortcodes/videopress.php | 51 +-
plugins/jetpack/modules/shortcodes/vimeo.php | 20 +-
plugins/jetpack/modules/shortcodes/youtube.php | 10 +-
plugins/jetpack/modules/stats.php | 172 +-
plugins/jetpack/modules/subscriptions.php | 289 +-
plugins/jetpack/modules/tiled-gallery.php | 25 +
.../math/class-constrained-array-rounding.php | 75 +
.../modules/tiled-gallery/tiled-gallery.php | 588 ++
.../tiled-gallery/rtl/tiled-gallery-rtl.css | 88 +
.../tiled-gallery/tiled-gallery/tiled-gallery.css | 85 +
.../tiled-gallery/tiled-gallery/tiled-gallery.js | 150 +
plugins/jetpack/modules/widgets.php | 21 +-
.../jetpack/modules/widgets/facebook-likebox.php | 57 +-
.../jetpack/modules/widgets/gravatar-profile.css | 15 +-
.../jetpack/modules/widgets/gravatar-profile.php | 128 +-
plugins/jetpack/modules/widgets/image-widget.php | 2 +-
plugins/jetpack/modules/widgets/readmill.php | 138 +
plugins/jetpack/modules/widgets/top-posts.php | 295 +
plugins/jetpack/modules/widgets/twitter.php | 407 +
.../modules/widgets/widget-grid-and-list.css | 110 +
plugins/jetpack/modules/widgets/widgets.css | 13 +
plugins/jetpack/readme.txt | 251 +-
plugins/openid/admin_panels.php | 3 -
plugins/openid/common.php | 8 +-
plugins/openid/consumer.php | 6 -
plugins/openid/lib/Auth/OpenID.php | 563 ++
plugins/openid/lib/Auth/OpenID/AX.php | 1022 +++
plugins/openid/lib/Auth/OpenID/Association.php | 610 ++
plugins/openid/lib/Auth/OpenID/BigMath.php | 451 +
plugins/openid/lib/Auth/OpenID/Consumer.php | 2236 +++++
plugins/openid/lib/Auth/OpenID/CryptUtil.php | 122 +
.../openid/lib/Auth/OpenID/DatabaseConnection.php | 130 +
plugins/openid/lib/Auth/OpenID/DiffieHellman.php | 113 +
plugins/openid/lib/Auth/OpenID/Discover.php | 606 ++
plugins/openid/lib/Auth/OpenID/DumbStore.php | 99 +
plugins/openid/lib/Auth/OpenID/Extension.php | 61 +
plugins/openid/lib/Auth/OpenID/FileStore.php | 618 ++
plugins/openid/lib/Auth/OpenID/HMAC.php | 105 +
plugins/openid/lib/Auth/OpenID/Interface.php | 196 +
plugins/openid/lib/Auth/OpenID/KVForm.php | 111 +
plugins/openid/lib/Auth/OpenID/MDB2Store.php | 413 +
plugins/openid/lib/Auth/OpenID/MemcachedStore.php | 207 +
plugins/openid/lib/Auth/OpenID/Message.php | 920 ++
plugins/openid/lib/Auth/OpenID/MySQLStore.php | 77 +
plugins/openid/lib/Auth/OpenID/Nonce.php | 108 +
plugins/openid/lib/Auth/OpenID/PAPE.php | 300 +
plugins/openid/lib/Auth/OpenID/Parse.php | 381 +
plugins/openid/lib/Auth/OpenID/PostgreSQLStore.php | 112 +
plugins/openid/lib/Auth/OpenID/SQLStore.php | 557 ++
plugins/openid/lib/Auth/OpenID/SQLiteStore.php | 70 +
plugins/openid/lib/Auth/OpenID/SReg.php | 521 ++
plugins/openid/lib/Auth/OpenID/Server.php | 1765 ++++
plugins/openid/lib/Auth/OpenID/ServerRequest.php | 36 +
plugins/openid/lib/Auth/OpenID/TrustRoot.php | 461 +
plugins/openid/lib/Auth/OpenID/URINorm.php | 249 +
plugins/openid/lib/Auth/Yadis/HTTPFetcher.php | 174 +
plugins/openid/lib/Auth/Yadis/Manager.php | 523 ++
plugins/openid/lib/Auth/Yadis/Misc.php | 58 +
.../openid/lib/Auth/Yadis/ParanoidHTTPFetcher.php | 273 +
plugins/openid/lib/Auth/Yadis/ParseHTML.php | 258 +
plugins/openid/lib/Auth/Yadis/PlainHTTPFetcher.php | 248 +
plugins/openid/lib/Auth/Yadis/XML.php | 352 +
plugins/openid/lib/Auth/Yadis/XRDS.php | 478 +
plugins/openid/lib/Auth/Yadis/XRI.php | 234 +
plugins/openid/lib/Auth/Yadis/XRIRes.php | 72 +
plugins/openid/lib/Auth/Yadis/Yadis.php | 382 +
plugins/openid/openid.php | 25 +-
plugins/openid/readme.txt | 9 +-
plugins/openid/server.php | 21 +-
plugins/wp-syntax/README.txt | 89 +-
plugins/wp-syntax/css/wp-syntax.css | 98 +
plugins/wp-syntax/geshi/geshi.php | 9534 ++++++++++----------
plugins/wp-syntax/geshi/geshi/4cs.php | 2 +-
plugins/wp-syntax/geshi/geshi/6502acme.php | 2 +-
plugins/wp-syntax/geshi/geshi/6502kickass.php | 2 +-
plugins/wp-syntax/geshi/geshi/6502tasm.php | 2 +-
plugins/wp-syntax/geshi/geshi/68000devpac.php | 2 +-
plugins/wp-syntax/geshi/geshi/abap.php | 2 +-
plugins/wp-syntax/geshi/geshi/actionscript.php | 2 +-
plugins/wp-syntax/geshi/geshi/actionscript3.php | 6 +-
plugins/wp-syntax/geshi/geshi/ada.php | 2 +-
plugins/wp-syntax/geshi/geshi/algol68.php | 287 +-
plugins/wp-syntax/geshi/geshi/apache.php | 5 +-
plugins/wp-syntax/geshi/geshi/applescript.php | 2 +-
plugins/wp-syntax/geshi/geshi/apt_sources.php | 12 +-
plugins/wp-syntax/geshi/geshi/arm.php | 3318 +++++++
plugins/wp-syntax/geshi/geshi/asm.php | 536 +-
plugins/wp-syntax/geshi/geshi/asp.php | 2 +-
plugins/wp-syntax/geshi/geshi/asymptote.php | 194 +
plugins/wp-syntax/geshi/geshi/autoconf.php | 2 +-
plugins/wp-syntax/geshi/geshi/autohotkey.php | 2 +-
plugins/wp-syntax/geshi/geshi/autoit.php | 2 +-
plugins/wp-syntax/geshi/geshi/avisynth.php | 2 +-
plugins/wp-syntax/geshi/geshi/awk.php | 2 +-
plugins/wp-syntax/geshi/geshi/bascomavr.php | 185 +
plugins/wp-syntax/geshi/geshi/bash.php | 141 +-
plugins/wp-syntax/geshi/geshi/basic4gl.php | 2 +-
plugins/wp-syntax/geshi/geshi/bf.php | 15 +-
plugins/wp-syntax/geshi/geshi/bibtex.php | 2 +-
plugins/wp-syntax/geshi/geshi/blitzbasic.php | 4 +-
plugins/wp-syntax/geshi/geshi/bnf.php | 2 +-
plugins/wp-syntax/geshi/geshi/boo.php | 2 +-
plugins/wp-syntax/geshi/geshi/c.php | 85 +-
plugins/wp-syntax/geshi/geshi/c_loadrunner.php | 323 +
plugins/wp-syntax/geshi/geshi/c_mac.php | 2 +-
plugins/wp-syntax/geshi/geshi/caddcl.php | 2 +-
plugins/wp-syntax/geshi/geshi/cadlisp.php | 2 +-
plugins/wp-syntax/geshi/geshi/cfdg.php | 2 +-
plugins/wp-syntax/geshi/geshi/cfm.php | 2 +-
plugins/wp-syntax/geshi/geshi/chaiscript.php | 4 +-
plugins/wp-syntax/geshi/geshi/cil.php | 2 +-
plugins/wp-syntax/geshi/geshi/clojure.php | 2 +-
plugins/wp-syntax/geshi/geshi/cmake.php | 2 +-
plugins/wp-syntax/geshi/geshi/cobol.php | 2 +-
plugins/wp-syntax/geshi/geshi/coffeescript.php | 146 +
plugins/wp-syntax/geshi/geshi/cpp-qt.php | 8 +-
plugins/wp-syntax/geshi/geshi/cpp.php | 4 +-
plugins/wp-syntax/geshi/geshi/csharp.php | 19 +-
plugins/wp-syntax/geshi/geshi/css.php | 22 +-
plugins/wp-syntax/geshi/geshi/cuesheet.php | 2 +-
plugins/wp-syntax/geshi/geshi/d.php | 68 +-
plugins/wp-syntax/geshi/geshi/dcl.php | 192 +
plugins/wp-syntax/geshi/geshi/dcpu16.php | 131 +
plugins/wp-syntax/geshi/geshi/dcs.php | 2 +-
plugins/wp-syntax/geshi/geshi/delphi.php | 46 +-
plugins/wp-syntax/geshi/geshi/diff.php | 2 +-
plugins/wp-syntax/geshi/geshi/div.php | 2 +-
plugins/wp-syntax/geshi/geshi/dos.php | 8 +-
plugins/wp-syntax/geshi/geshi/dot.php | 2 +-
plugins/wp-syntax/geshi/geshi/e.php | 2 +-
plugins/wp-syntax/geshi/geshi/ecmascript.php | 2 +-
plugins/wp-syntax/geshi/geshi/eiffel.php | 2 +-
plugins/wp-syntax/geshi/geshi/email.php | 26 +-
plugins/wp-syntax/geshi/geshi/epc.php | 154 +
plugins/wp-syntax/geshi/geshi/erlang.php | 10 +-
plugins/wp-syntax/geshi/geshi/euphoria.php | 140 +
plugins/wp-syntax/geshi/geshi/f1.php | 2 +-
plugins/wp-syntax/geshi/geshi/falcon.php | 218 +
plugins/wp-syntax/geshi/geshi/fo.php | 2 +-
plugins/wp-syntax/geshi/geshi/fortran.php | 2 +-
plugins/wp-syntax/geshi/geshi/freebasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/freeswitch.php | 168 +
plugins/wp-syntax/geshi/geshi/fsharp.php | 8 +-
plugins/wp-syntax/geshi/geshi/gambas.php | 2 +-
plugins/wp-syntax/geshi/geshi/gdb.php | 63 +-
plugins/wp-syntax/geshi/geshi/genero.php | 2 +-
plugins/wp-syntax/geshi/geshi/genie.php | 2 +-
plugins/wp-syntax/geshi/geshi/gettext.php | 2 +-
plugins/wp-syntax/geshi/geshi/glsl.php | 2 +-
plugins/wp-syntax/geshi/geshi/gml.php | 2 +-
plugins/wp-syntax/geshi/geshi/gnuplot.php | 2 +-
plugins/wp-syntax/geshi/geshi/go.php | 47 +-
plugins/wp-syntax/geshi/geshi/groovy.php | 18 +-
plugins/wp-syntax/geshi/geshi/gwbasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/haskell.php | 8 +-
plugins/wp-syntax/geshi/geshi/haxe.php | 161 +
plugins/wp-syntax/geshi/geshi/hicest.php | 2 +-
plugins/wp-syntax/geshi/geshi/hq9plus.php | 2 +-
plugins/wp-syntax/geshi/geshi/html4strict.php | 29 +-
plugins/wp-syntax/geshi/geshi/html5.php | 212 +
plugins/wp-syntax/geshi/geshi/icon.php | 2 +-
plugins/wp-syntax/geshi/geshi/idl.php | 2 +-
plugins/wp-syntax/geshi/geshi/ini.php | 2 +-
plugins/wp-syntax/geshi/geshi/inno.php | 2 +-
plugins/wp-syntax/geshi/geshi/intercal.php | 2 +-
plugins/wp-syntax/geshi/geshi/io.php | 2 +-
plugins/wp-syntax/geshi/geshi/j.php | 73 +-
plugins/wp-syntax/geshi/geshi/java.php | 4 +-
plugins/wp-syntax/geshi/geshi/java5.php | 328 +-
plugins/wp-syntax/geshi/geshi/javascript.php | 70 +-
plugins/wp-syntax/geshi/geshi/jquery.php | 2 +-
plugins/wp-syntax/geshi/geshi/kixtart.php | 2 +-
plugins/wp-syntax/geshi/geshi/klonec.php | 2 +-
plugins/wp-syntax/geshi/geshi/klonecpp.php | 2 +-
plugins/wp-syntax/geshi/geshi/latex.php | 8 +-
plugins/wp-syntax/geshi/geshi/lb.php | 16 +-
plugins/wp-syntax/geshi/geshi/ldif.php | 116 +
plugins/wp-syntax/geshi/geshi/lisp.php | 2 +-
plugins/wp-syntax/geshi/geshi/llvm.php | 385 +
plugins/wp-syntax/geshi/geshi/locobasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/logtalk.php | 35 +-
plugins/wp-syntax/geshi/geshi/lolcode.php | 2 +-
plugins/wp-syntax/geshi/geshi/lotusformulas.php | 2 +-
plugins/wp-syntax/geshi/geshi/lotusscript.php | 2 +-
plugins/wp-syntax/geshi/geshi/lscript.php | 2 +-
plugins/wp-syntax/geshi/geshi/lsl2.php | 2 +-
plugins/wp-syntax/geshi/geshi/lua.php | 62 +-
plugins/wp-syntax/geshi/geshi/m68k.php | 2 +-
plugins/wp-syntax/geshi/geshi/magiksf.php | 2 +-
plugins/wp-syntax/geshi/geshi/make.php | 2 +-
plugins/wp-syntax/geshi/geshi/mapbasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/matlab.php | 2 +-
plugins/wp-syntax/geshi/geshi/mirc.php | 2 +-
plugins/wp-syntax/geshi/geshi/mmix.php | 66 +-
plugins/wp-syntax/geshi/geshi/modula2.php | 2 +-
plugins/wp-syntax/geshi/geshi/modula3.php | 4 +-
plugins/wp-syntax/geshi/geshi/mpasm.php | 2 +-
plugins/wp-syntax/geshi/geshi/mxml.php | 2 +-
plugins/wp-syntax/geshi/geshi/mysql.php | 34 +-
plugins/wp-syntax/geshi/geshi/nagios.php | 225 +
plugins/wp-syntax/geshi/geshi/netrexx.php | 163 +
plugins/wp-syntax/geshi/geshi/newlisp.php | 2 +-
plugins/wp-syntax/geshi/geshi/nsis.php | 2 +-
plugins/wp-syntax/geshi/geshi/oberon2.php | 2 +-
plugins/wp-syntax/geshi/geshi/objc.php | 2 +-
plugins/wp-syntax/geshi/geshi/objeck.php | 10 +-
plugins/wp-syntax/geshi/geshi/ocaml-brief.php | 2 +-
plugins/wp-syntax/geshi/geshi/ocaml.php | 2 +-
plugins/wp-syntax/geshi/geshi/octave.php | 515 ++
plugins/wp-syntax/geshi/geshi/oobas.php | 2 +-
plugins/wp-syntax/geshi/geshi/oorexx.php | 171 +
plugins/wp-syntax/geshi/geshi/oracle11.php | 2 +-
plugins/wp-syntax/geshi/geshi/oracle8.php | 2 +-
plugins/wp-syntax/geshi/geshi/oxygene.php | 6 +-
plugins/wp-syntax/geshi/geshi/oz.php | 2 +-
plugins/wp-syntax/geshi/geshi/parasail.php | 133 +
plugins/wp-syntax/geshi/geshi/parigp.php | 277 +
plugins/wp-syntax/geshi/geshi/pascal.php | 53 +-
plugins/wp-syntax/geshi/geshi/pcre.php | 2 +-
plugins/wp-syntax/geshi/geshi/per.php | 2 +-
plugins/wp-syntax/geshi/geshi/perl.php | 2 +-
plugins/wp-syntax/geshi/geshi/perl6.php | 2 +-
plugins/wp-syntax/geshi/geshi/pf.php | 2 +-
plugins/wp-syntax/geshi/geshi/php-brief.php | 4 +-
plugins/wp-syntax/geshi/geshi/php.php | 21 +-
plugins/wp-syntax/geshi/geshi/pic16.php | 2 +-
plugins/wp-syntax/geshi/geshi/pike.php | 2 +-
plugins/wp-syntax/geshi/geshi/pixelbender.php | 2 +-
plugins/wp-syntax/geshi/geshi/pli.php | 200 +
plugins/wp-syntax/geshi/geshi/plsql.php | 2 +-
plugins/wp-syntax/geshi/geshi/postgresql.php | 2 +-
plugins/wp-syntax/geshi/geshi/povray.php | 2 +-
plugins/wp-syntax/geshi/geshi/powerbuilder.php | 2 +-
plugins/wp-syntax/geshi/geshi/powershell.php | 2 +-
plugins/wp-syntax/geshi/geshi/proftpd.php | 374 +
plugins/wp-syntax/geshi/geshi/progress.php | 2 +-
plugins/wp-syntax/geshi/geshi/prolog.php | 2 +-
plugins/wp-syntax/geshi/geshi/properties.php | 2 +-
plugins/wp-syntax/geshi/geshi/providex.php | 2 +-
plugins/wp-syntax/geshi/geshi/purebasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/pycon.php | 64 +
plugins/wp-syntax/geshi/geshi/pys60.php | 273 +
plugins/wp-syntax/geshi/geshi/python.php | 17 +-
plugins/wp-syntax/geshi/geshi/q.php | 2 +-
plugins/wp-syntax/geshi/geshi/qbasic.php | 6 +-
plugins/wp-syntax/geshi/geshi/rails.php | 2 +-
plugins/wp-syntax/geshi/geshi/rebol.php | 2 +-
plugins/wp-syntax/geshi/geshi/reg.php | 2 +-
plugins/wp-syntax/geshi/geshi/rexx.php | 162 +
plugins/wp-syntax/geshi/geshi/robots.php | 2 +-
plugins/wp-syntax/geshi/geshi/rpmspec.php | 2 +-
plugins/wp-syntax/geshi/geshi/rsplus.php | 20 +-
plugins/wp-syntax/geshi/geshi/ruby.php | 2 +-
plugins/wp-syntax/geshi/geshi/sas.php | 6 +-
plugins/wp-syntax/geshi/geshi/scala.php | 26 +-
plugins/wp-syntax/geshi/geshi/scheme.php | 2 +-
plugins/wp-syntax/geshi/geshi/scilab.php | 2 +-
plugins/wp-syntax/geshi/geshi/sdlbasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/smalltalk.php | 2 +-
plugins/wp-syntax/geshi/geshi/smarty.php | 2 +-
plugins/wp-syntax/geshi/geshi/spark.php | 132 +
plugins/wp-syntax/geshi/geshi/sparql.php | 155 +
plugins/wp-syntax/geshi/geshi/sql.php | 2 +-
plugins/wp-syntax/geshi/geshi/stonescript.php | 307 +
plugins/wp-syntax/geshi/geshi/systemverilog.php | 8 +-
plugins/wp-syntax/geshi/geshi/tcl.php | 2 +-
plugins/wp-syntax/geshi/geshi/teraterm.php | 147 +-
plugins/wp-syntax/geshi/geshi/text.php | 2 +-
plugins/wp-syntax/geshi/geshi/thinbasic.php | 2 +-
plugins/wp-syntax/geshi/geshi/tsql.php | 6 +-
plugins/wp-syntax/geshi/geshi/typoscript.php | 20 +-
plugins/wp-syntax/geshi/geshi/unicon.php | 2 +-
plugins/wp-syntax/geshi/geshi/upc.php | 270 +
plugins/wp-syntax/geshi/geshi/urbi.php | 200 +
plugins/wp-syntax/geshi/geshi/uscript.php | 299 +
plugins/wp-syntax/geshi/geshi/vala.php | 2 +-
plugins/wp-syntax/geshi/geshi/vb.php | 2 +-
plugins/wp-syntax/geshi/geshi/vbnet.php | 127 +-
plugins/wp-syntax/geshi/geshi/vedit.php | 103 +
plugins/wp-syntax/geshi/geshi/verilog.php | 4 +-
plugins/wp-syntax/geshi/geshi/vhdl.php | 105 +-
plugins/wp-syntax/geshi/geshi/vim.php | 2 +-
plugins/wp-syntax/geshi/geshi/visualfoxpro.php | 2 +-
plugins/wp-syntax/geshi/geshi/visualprolog.php | 2 +-
plugins/wp-syntax/geshi/geshi/whitespace.php | 2 +-
plugins/wp-syntax/geshi/geshi/whois.php | 2 +-
plugins/wp-syntax/geshi/geshi/winbatch.php | 2 +-
plugins/wp-syntax/geshi/geshi/xbasic.php | 3 +-
plugins/wp-syntax/geshi/geshi/xml.php | 2 +-
plugins/wp-syntax/geshi/geshi/xorg_conf.php | 2 +-
plugins/wp-syntax/geshi/geshi/xpp.php | 2 +-
plugins/wp-syntax/geshi/geshi/yaml.php | 150 +
plugins/wp-syntax/geshi/geshi/z80.php | 2 +-
plugins/wp-syntax/geshi/geshi/zxbasic.php | 2 +-
plugins/wp-syntax/js/wp-syntax.js | 34 +
plugins/wp-syntax/wp-syntax.php | 563 +-
506 files changed, 76912 insertions(+), 8864 deletions(-)
create mode 100644 plugins/akismet/.htaccess
create mode 100644 plugins/jetpack/_inc/gallery-settings.js
create mode 100644 plugins/jetpack/_inc/images/publicize.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/carousel.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/custom-css.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/likes.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg
create mode 100644 plugins/jetpack/_inc/images/screenshots/mobile-theme.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/notes.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/post-by-email.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/publicize.png
create mode 100644 plugins/jetpack/_inc/images/screenshots/tiled-gallery.png
create mode 100644 plugins/jetpack/_inc/jquery.inview.js
create mode 100644 plugins/jetpack/_inc/jquery.jetpack-resize.js
create mode 100644 plugins/jetpack/_inc/jquery.spin.js
create mode 100644 plugins/jetpack/_inc/postmessage.js
create mode 100644 plugins/jetpack/_inc/spin.js
create mode 100644 plugins/jetpack/class.jetpack-post-images.php
create mode 100644 plugins/jetpack/class.jetpack-user-agent.php
create mode 100644 plugins/jetpack/class.json-api-endpoints.php
create mode 100644 plugins/jetpack/class.json-api.php
create mode 100644 plugins/jetpack/class.photon.php
create mode 100644 plugins/jetpack/functions.compat.php
create mode 100644 plugins/jetpack/functions.gallery.php
create mode 100644 plugins/jetpack/functions.opengraph.php
create mode 100644 plugins/jetpack/functions.photon.php
create mode 100644 plugins/jetpack/languages/jetpack-ar.mo
create mode 100644 plugins/jetpack/languages/jetpack-el.mo
create mode 100644 plugins/jetpack/languages/jetpack-ko_KR.mo
create mode 100644 plugins/jetpack/languages/jetpack-lt_LT.mo
create mode 100644 plugins/jetpack/languages/jetpack-th.mo
create mode 100644 plugins/jetpack/languages/jetpack-zh_CN.mo
create mode 100644 plugins/jetpack/languages/jetpack-zh_TW.mo
create mode 100644 plugins/jetpack/modules/carousel/jetpack-carousel-ie8fix.css
create mode 100644 plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css
create mode 100644 plugins/jetpack/modules/custom-css.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/cssparse.css
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/cssparsed.css
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/data.inc.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/lang.inc.php
create mode 100644 plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl
create mode 100644 plugins/jetpack/modules/custom-css/custom-css.php
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/blank.css
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/ace/ace.js
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/ace/mode-css.js
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/ace/readme.txt
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/ace/theme-textmate.js
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/ace/worker-css.js
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/js/safecss-ace.js
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/preprocessors.php
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php
create mode 100644 plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php
create mode 100644 plugins/jetpack/modules/featured-content/featured-content.php
create mode 100644 plugins/jetpack/modules/holiday-snow.php
create mode 100644 plugins/jetpack/modules/holiday-snow/snowstorm.js
create mode 100644 plugins/jetpack/modules/infinite-scroll.php
create mode 100644 plugins/jetpack/modules/infinite-scroll/infinity.css
create mode 100644 plugins/jetpack/modules/infinite-scroll/infinity.js
create mode 100644 plugins/jetpack/modules/infinite-scroll/infinity.php
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.php
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentyten.css
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentyten.php
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.css
create mode 100644 plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.php
create mode 100644 plugins/jetpack/modules/json-api.php
create mode 100644 plugins/jetpack/modules/likes.php
create mode 100644 plugins/jetpack/modules/likes/style.css
create mode 100644 plugins/jetpack/modules/minileven.php
create mode 100644 plugins/jetpack/modules/minileven/images/wp-app-devices.png
create mode 100644 plugins/jetpack/modules/minileven/minileven.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/comments.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/content-gallery.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/content.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/header.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/image.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/inc/custom-header.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/inc/template-tags.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/inc/tweaks.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/index.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/js/small-menu.js
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/page.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/rtl.css
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/screenshot.png
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/searchform.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/sidebar.php
create mode 100644 plugins/jetpack/modules/minileven/theme/pub/minileven/style.css
create mode 100644 plugins/jetpack/modules/mobile-push.php
create mode 100644 plugins/jetpack/modules/module-extras.php
create mode 100644 plugins/jetpack/modules/notes.php
create mode 100644 plugins/jetpack/modules/photon.php
create mode 100644 plugins/jetpack/modules/photon/photon.js
create mode 100644 plugins/jetpack/modules/post-by-email.php
create mode 100644 plugins/jetpack/modules/post-by-email/post-by-email.css
create mode 100644 plugins/jetpack/modules/post-by-email/post-by-email.js
create mode 100644 plugins/jetpack/modules/publicize.php
create mode 100644 plugins/jetpack/modules/publicize/assets/connected.gif
create mode 100644 plugins/jetpack/modules/publicize/assets/facebook-logo.png
create mode 100644 plugins/jetpack/modules/publicize/assets/linkedin-logo.png
create mode 100644 plugins/jetpack/modules/publicize/assets/publicize.css
create mode 100644 plugins/jetpack/modules/publicize/assets/publicize.js
create mode 100644 plugins/jetpack/modules/publicize/assets/rtl/publicize-rtl.css
create mode 100644 plugins/jetpack/modules/publicize/assets/spinner.gif
create mode 100644 plugins/jetpack/modules/publicize/assets/tumblr-logo.png
create mode 100644 plugins/jetpack/modules/publicize/assets/twitter-logo.png
create mode 100644 plugins/jetpack/modules/publicize/assets/yahoo-logo.png
create mode 100644 plugins/jetpack/modules/publicize/publicize-jetpack.php
create mode 100644 plugins/jetpack/modules/publicize/publicize.php
create mode 100644 plugins/jetpack/modules/publicize/ui.php
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-facebook-2x.png
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-facebook.png
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-twitter-2x.png
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-twitter.png
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-wordpress-2x.png
create mode 100644 plugins/jetpack/modules/sharedaddy/images/icon-wordpress.png
create mode 100644 plugins/jetpack/modules/shortcodes/css/rtl/slideshow-shortcode-rtl.css
create mode 100644 plugins/jetpack/modules/shortcodes/css/slideshow-shortcode.css
create mode 100644 plugins/jetpack/modules/shortcodes/img/slideshow-controls-2x.png
create mode 100644 plugins/jetpack/modules/shortcodes/img/slideshow-controls.png
create mode 100644 plugins/jetpack/modules/shortcodes/img/slideshow-loader.gif
create mode 100644 plugins/jetpack/modules/shortcodes/js/audio-shortcode.js
create mode 100644 plugins/jetpack/modules/shortcodes/js/jquery.cycle.js
create mode 100644 plugins/jetpack/modules/shortcodes/js/slideshow-shortcode.js
create mode 100644 plugins/jetpack/modules/shortcodes/slideshow.php
create mode 100644 plugins/jetpack/modules/shortcodes/ted.php
create mode 100644 plugins/jetpack/modules/tiled-gallery.php
create mode 100644 plugins/jetpack/modules/tiled-gallery/math/class-constrained-array-rounding.php
create mode 100644 plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
create mode 100644 plugins/jetpack/modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css
create mode 100644 plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css
create mode 100644 plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.js
create mode 100644 plugins/jetpack/modules/widgets/readmill.php
create mode 100644 plugins/jetpack/modules/widgets/top-posts.php
create mode 100644 plugins/jetpack/modules/widgets/twitter.php
create mode 100644 plugins/jetpack/modules/widgets/widget-grid-and-list.css
create mode 100644 plugins/jetpack/modules/widgets/widgets.css
create mode 100644 plugins/openid/lib/Auth/OpenID.php
create mode 100644 plugins/openid/lib/Auth/OpenID/AX.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Association.php
create mode 100644 plugins/openid/lib/Auth/OpenID/BigMath.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Consumer.php
create mode 100644 plugins/openid/lib/Auth/OpenID/CryptUtil.php
create mode 100644 plugins/openid/lib/Auth/OpenID/DatabaseConnection.php
create mode 100644 plugins/openid/lib/Auth/OpenID/DiffieHellman.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Discover.php
create mode 100644 plugins/openid/lib/Auth/OpenID/DumbStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Extension.php
create mode 100644 plugins/openid/lib/Auth/OpenID/FileStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/HMAC.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Interface.php
create mode 100644 plugins/openid/lib/Auth/OpenID/KVForm.php
create mode 100644 plugins/openid/lib/Auth/OpenID/MDB2Store.php
create mode 100644 plugins/openid/lib/Auth/OpenID/MemcachedStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Message.php
create mode 100644 plugins/openid/lib/Auth/OpenID/MySQLStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Nonce.php
create mode 100644 plugins/openid/lib/Auth/OpenID/PAPE.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Parse.php
create mode 100644 plugins/openid/lib/Auth/OpenID/PostgreSQLStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/SQLStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/SQLiteStore.php
create mode 100644 plugins/openid/lib/Auth/OpenID/SReg.php
create mode 100644 plugins/openid/lib/Auth/OpenID/Server.php
create mode 100644 plugins/openid/lib/Auth/OpenID/ServerRequest.php
create mode 100644 plugins/openid/lib/Auth/OpenID/TrustRoot.php
create mode 100644 plugins/openid/lib/Auth/OpenID/URINorm.php
create mode 100644 plugins/openid/lib/Auth/Yadis/HTTPFetcher.php
create mode 100644 plugins/openid/lib/Auth/Yadis/Manager.php
create mode 100644 plugins/openid/lib/Auth/Yadis/Misc.php
create mode 100644 plugins/openid/lib/Auth/Yadis/ParanoidHTTPFetcher.php
create mode 100644 plugins/openid/lib/Auth/Yadis/ParseHTML.php
create mode 100644 plugins/openid/lib/Auth/Yadis/PlainHTTPFetcher.php
create mode 100644 plugins/openid/lib/Auth/Yadis/XML.php
create mode 100644 plugins/openid/lib/Auth/Yadis/XRDS.php
create mode 100644 plugins/openid/lib/Auth/Yadis/XRI.php
create mode 100644 plugins/openid/lib/Auth/Yadis/XRIRes.php
create mode 100644 plugins/openid/lib/Auth/Yadis/Yadis.php
create mode 100644 plugins/wp-syntax/css/wp-syntax.css
create mode 100644 plugins/wp-syntax/geshi/geshi/arm.php
create mode 100644 plugins/wp-syntax/geshi/geshi/asymptote.php
create mode 100644 plugins/wp-syntax/geshi/geshi/bascomavr.php
create mode 100644 plugins/wp-syntax/geshi/geshi/c_loadrunner.php
create mode 100644 plugins/wp-syntax/geshi/geshi/coffeescript.php
create mode 100644 plugins/wp-syntax/geshi/geshi/dcl.php
create mode 100644 plugins/wp-syntax/geshi/geshi/dcpu16.php
create mode 100644 plugins/wp-syntax/geshi/geshi/epc.php
create mode 100644 plugins/wp-syntax/geshi/geshi/euphoria.php
create mode 100644 plugins/wp-syntax/geshi/geshi/falcon.php
create mode 100644 plugins/wp-syntax/geshi/geshi/freeswitch.php
create mode 100644 plugins/wp-syntax/geshi/geshi/haxe.php
create mode 100644 plugins/wp-syntax/geshi/geshi/html5.php
create mode 100644 plugins/wp-syntax/geshi/geshi/ldif.php
create mode 100644 plugins/wp-syntax/geshi/geshi/llvm.php
create mode 100644 plugins/wp-syntax/geshi/geshi/nagios.php
create mode 100644 plugins/wp-syntax/geshi/geshi/netrexx.php
create mode 100644 plugins/wp-syntax/geshi/geshi/octave.php
create mode 100644 plugins/wp-syntax/geshi/geshi/oorexx.php
create mode 100644 plugins/wp-syntax/geshi/geshi/parasail.php
create mode 100644 plugins/wp-syntax/geshi/geshi/parigp.php
create mode 100644 plugins/wp-syntax/geshi/geshi/pli.php
create mode 100644 plugins/wp-syntax/geshi/geshi/proftpd.php
create mode 100644 plugins/wp-syntax/geshi/geshi/pycon.php
create mode 100644 plugins/wp-syntax/geshi/geshi/pys60.php
create mode 100644 plugins/wp-syntax/geshi/geshi/rexx.php
create mode 100644 plugins/wp-syntax/geshi/geshi/spark.php
create mode 100644 plugins/wp-syntax/geshi/geshi/sparql.php
create mode 100644 plugins/wp-syntax/geshi/geshi/stonescript.php
create mode 100644 plugins/wp-syntax/geshi/geshi/upc.php
create mode 100644 plugins/wp-syntax/geshi/geshi/urbi.php
create mode 100644 plugins/wp-syntax/geshi/geshi/uscript.php
create mode 100644 plugins/wp-syntax/geshi/geshi/vedit.php
create mode 100644 plugins/wp-syntax/geshi/geshi/yaml.php
create mode 100644 plugins/wp-syntax/js/wp-syntax.js
diff --git a/plugins/akismet/.htaccess b/plugins/akismet/.htaccess
new file mode 100644
index 00000000..18eed640
--- /dev/null
+++ b/plugins/akismet/.htaccess
@@ -0,0 +1,6 @@
+Order Deny,Allow
+Deny from all
+
+
+ Allow from all
+
\ No newline at end of file
diff --git a/plugins/akismet/admin.php b/plugins/akismet/admin.php
index aa30cde7..9d7673f5 100644
--- a/plugins/akismet/admin.php
+++ b/plugins/akismet/admin.php
@@ -23,7 +23,6 @@ function akismet_admin_init() {
$hook = get_plugin_page_hook( 'akismet-stats-display', 'index.php' );
else
$hook = 'dashboard_page_akismet-stats-display';
- add_action('admin_head-'.$hook, 'akismet_stats_script');
add_meta_box('akismet-status', __('Comment History'), 'akismet_comment_status_meta_box', 'comment', 'normal');
}
add_action('admin_init', 'akismet_admin_init');
@@ -56,7 +55,7 @@ $akismet_nonce = 'akismet-update-key';
function akismet_plugin_action_links( $links, $file ) {
if ( $file == plugin_basename( dirname(__FILE__).'/akismet.php' ) ) {
- $links[] = ''.__('Settings').' ';
+ $links[] = ''.__( 'Settings' ).' ';
}
return $links;
@@ -240,23 +239,6 @@ function akismet_conf() {
-
-
+
get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->commentmeta WHERE meta_key = 'akismet_error'" ) );
+ $waiting = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->commentmeta WHERE meta_key = 'akismet_error'" );
$next_check = wp_next_scheduled('akismet_schedule_cron_recheck');
if ( $waiting > 0 && $next_check > time() )
echo "
@@ -741,7 +723,8 @@ function akismet_recheck_queue() {
delete_comment_meta( $c['comment_ID'], 'akismet_rechecking' );
}
- wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
+ $redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url( 'edit-comments.php' );
+ wp_safe_redirect( $redirect_to );
exit;
}
diff --git a/plugins/akismet/akismet.js b/plugins/akismet/akismet.js
index 839fe6bc..8925c51f 100644
--- a/plugins/akismet/akismet.js
+++ b/plugins/akismet/akismet.js
@@ -74,6 +74,7 @@ jQuery(document).ready(function () {
return false;
});
jQuery('a[id^="author_comment_url"]').mouseover(function () {
+ var wpcomProtocol = ( 'https:' === location.protocol ) ? 'https://' : 'http://';
// Need to determine size of author column
var thisParentWidth = jQuery(this).parent().width();
// It changes based on if there is a gravatar present
@@ -83,12 +84,12 @@ jQuery(document).ready(function () {
jQuery('.widefat td').css('overflow', 'visible');
jQuery(this).css('position', 'relative');
var thisHref = jQuery.URLEncode(jQuery(this).attr('href'));
- jQuery(this).append('');
+ jQuery(this).append('');
setTimeout(function () {
- jQuery('.mshot-image_'+thisId).attr('src', 'http://s.wordpress.com/mshots/v1/'+thisHref+'?w=450&r=2');
+ jQuery('.mshot-image_'+thisId).attr('src', wpcomProtocol+'s0.wordpress.com/mshots/v1/'+thisHref+'?w=450&r=2');
}, 6000);
setTimeout(function () {
- jQuery('.mshot-image_'+thisId).attr('src', 'http://s.wordpress.com/mshots/v1/'+thisHref+'?w=450&r=3');
+ jQuery('.mshot-image_'+thisId).attr('src', wpcomProtocol+'s0.wordpress.com/mshots/v1/'+thisHref+'?w=450&r=3');
}, 12000);
} else {
jQuery(this).find('.mShot').css('left', thisParentWidth).show();
@@ -106,7 +107,8 @@ jQuery.extend({URLEncode:function(c){var o='';var x=0;c=c.toString();var r=/(^[a
});
// Preload mshot images after everything else has loaded
jQuery(window).load(function() {
+ var wpcomProtocol = ( 'https:' === location.protocol ) ? 'https://' : 'http://';
jQuery('a[id^="author_comment_url"]').each(function () {
- jQuery.get('http://s.wordpress.com/mshots/v1/'+jQuery.URLEncode(jQuery(this).attr('href'))+'?w=450');
+ jQuery.get(wpcomProtocol+'s0.wordpress.com/mshots/v1/'+jQuery.URLEncode(jQuery(this).attr('href'))+'?w=450');
});
-});
\ No newline at end of file
+});
diff --git a/plugins/akismet/akismet.php b/plugins/akismet/akismet.php
index 48fa3c3a..4c3aef71 100644
--- a/plugins/akismet/akismet.php
+++ b/plugins/akismet/akismet.php
@@ -5,8 +5,8 @@
/*
Plugin Name: Akismet
Plugin URI: http://akismet.com/?return=true
-Description: Used by millions, Akismet is quite possibly the best way in the world to protect your blog from comment and trackback spam . It keeps your site protected from spam even while you sleep. To get started: 1) Click the "Activate" link to the left of this description, 2) Sign up for an Akismet API key , and 3) Go to your Akismet configuration page, and save your API key.
-Version: 2.5.6
+Description: Used by millions, Akismet is quite possibly the best way in the world to protect your blog from comment and trackback spam . It keeps your site protected from spam even while you sleep. To get started: 1) Click the "Activate" link to the left of this description, 2) Sign up for an Akismet API key , and 3) Go to your Akismet configuration page, and save your API key.
+Version: 2.5.7
Author: Automattic
Author URI: http://automattic.com/wordpress-plugins/
License: GPLv2 or later
@@ -28,7 +28,13 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-define('AKISMET_VERSION', '2.5.6');
+// Make sure we don't expose any info if called directly
+if ( !function_exists( 'add_action' ) ) {
+ echo 'Hi there! I\'m just a plugin, not much I can do when called directly.';
+ exit;
+}
+
+define('AKISMET_VERSION', '2.5.7');
define('AKISMET_PLUGIN_URL', plugin_dir_url( __FILE__ ));
/** If you hardcode a WP.com API key here, all key config screens will be hidden */
@@ -38,12 +44,6 @@ else
$wpcom_api_key = '';
include '/var/www/blogs.gentoo.org/secrets/wp-apikey.php';
-// Make sure we don't expose any info if called directly
-if ( !function_exists( 'add_action' ) ) {
- echo "Hi there! I'm just a plugin, not much I can do when called directly.";
- exit;
-}
-
if ( isset($wp_db_version) && $wp_db_version <= 9872 )
include_once dirname( __FILE__ ) . '/legacy.php';
@@ -111,7 +111,7 @@ function akismet_test_mode() {
}
// return a comma-separated list of role names for the given user
-function akismet_get_user_roles($user_id ) {
+function akismet_get_user_roles( $user_id ) {
$roles = false;
if ( !class_exists('WP_User') )
@@ -278,10 +278,13 @@ function akismet_auto_check_update_meta( $id, $comment ) {
if ( !function_exists('add_comment_meta') )
return false;
+ if ( !isset( $akismet_last_comment['comment_author_email'] ) )
+ $akismet_last_comment['comment_author_email'] = '';
+
// wp_insert_comment() might be called in other contexts, so make sure this is the same comment
// as was checked by akismet_auto_check_comment
if ( is_object($comment) && !empty($akismet_last_comment) && is_array($akismet_last_comment) ) {
- if ( intval($akismet_last_comment['comment_post_ID']) == intval($comment->comment_post_ID)
+ if ( isset($akismet_last_comment['comment_post_ID']) && intval($akismet_last_comment['comment_post_ID']) == intval($comment->comment_post_ID)
&& $akismet_last_comment['comment_author'] == $comment->comment_author
&& $akismet_last_comment['comment_author_email'] == $comment->comment_author_email ) {
// normal result: true or false
@@ -320,15 +323,15 @@ function akismet_auto_check_comment( $commentdata ) {
$comment = $commentdata;
$comment['user_ip'] = $_SERVER['REMOTE_ADDR'];
- $comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
- $comment['referrer'] = $_SERVER['HTTP_REFERER'];
+ $comment['user_agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
+ $comment['referrer'] = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
$comment['blog'] = get_option('home');
$comment['blog_lang'] = get_locale();
$comment['blog_charset'] = get_option('blog_charset');
$comment['permalink'] = get_permalink($comment['comment_post_ID']);
if ( !empty( $comment['user_ID'] ) ) {
- $comment['user_role'] = akismet_get_user_roles($comment['user_ID']);
+ $comment['user_role'] = akismet_get_user_roles( $comment['user_ID'] );
}
$akismet_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) );
@@ -371,6 +374,7 @@ function akismet_auto_check_comment( $commentdata ) {
$commentdata['comment_as_submitted'] = $comment;
$response = akismet_http_post($query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port);
+ do_action( 'akismet_comment_check_response', $response );
akismet_update_alert( $response );
$commentdata['akismet_result'] = $response[1];
if ( 'true' == $response[1] ) {
@@ -387,7 +391,8 @@ function akismet_auto_check_comment( $commentdata ) {
// akismet_result_spam() won't be called so bump the counter here
if ( $incr = apply_filters('akismet_spam_count_incr', 1) )
update_option( 'akismet_spam_count', get_option('akismet_spam_count') + $incr );
- wp_safe_redirect( $_SERVER['HTTP_REFERER'] );
+ $redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : get_permalink( $post );
+ wp_safe_redirect( $redirect_to );
die();
}
}
@@ -499,7 +504,7 @@ function akismet_check_db_comment( $id, $recheck_reason = 'recheck_queue' ) {
$query_string .= $key . '=' . urlencode( stripslashes($data) ) . '&';
$response = akismet_http_post($query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port);
- return $response[1];
+ return ( is_array( $response ) && isset( $response[1] ) ) ? $response[1] : false;
}
function akismet_cron_recheck() {
@@ -566,7 +571,7 @@ function akismet_cron_recheck() {
delete_comment_meta( $comment_id, 'akismet_rechecking' );
}
- $remaining = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->commentmeta WHERE meta_key = 'akismet_error'" ) );
+ $remaining = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->commentmeta WHERE meta_key = 'akismet_error'" );
if ( $remaining && !wp_next_scheduled('akismet_schedule_cron_recheck') ) {
wp_schedule_single_event( time() + 1200, 'akismet_schedule_cron_recheck' );
}
diff --git a/plugins/akismet/readme.txt b/plugins/akismet/readme.txt
index 227fad95..4d61fe9d 100644
--- a/plugins/akismet/readme.txt
+++ b/plugins/akismet/readme.txt
@@ -2,8 +2,8 @@
Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, automattic
Tags: akismet, comments, spam
Requires at least: 3.0
-Tested up to: 3.4
-Stable tag: 2.5.6
+Tested up to: 3.5
+Stable tag: 2.5.7
License: GPLv2 or later
Akismet checks your comments against the Akismet web service to see if they look like spam or not.
@@ -31,6 +31,15 @@ Upload the Akismet plugin to your blog, Activate it, then enter your [Akismet.co
== Changelog ==
+= 2.5.7 =
+* FireFox Stats iframe preview bug
+* Fix mshots preview when using https
+* Add .htaccess to block direct access to files
+* Prevent some PHP notices
+* Fix Check For Spam return location when referrer is empty
+* Fix Settings links for network admins
+* Fix prepare() warnings in WP 3.5
+
= 2.5.6 =
* Prevent retry scheduling problems on sites where wp_cron is misbehaving
* Preload mshot previews
diff --git a/plugins/jetpack/_inc/gallery-settings.js b/plugins/jetpack/_inc/gallery-settings.js
new file mode 100644
index 00000000..0ce38f0b
--- /dev/null
+++ b/plugins/jetpack/_inc/gallery-settings.js
@@ -0,0 +1,19 @@
+/**
+ * Jetpack Gallery Settings
+ */
+(function($) {
+ var media = wp.media;
+
+ // Wrap the render() function to append controls.
+ media.view.Settings.Gallery = media.view.Settings.Gallery.extend({
+ render: function() {
+ media.view.Settings.prototype.render.apply( this, arguments );
+
+ // Append the type template and update the settings.
+ this.$el.append( media.template( 'jetpack-gallery-settings' ) );
+ media.gallery.defaults.type = 'default'; // lil hack that lets media know there's a type attribute.
+ this.update.apply( this, ['type'] );
+ return this;
+ }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/plugins/jetpack/_inc/images/module-icons-sprite-2x.png b/plugins/jetpack/_inc/images/module-icons-sprite-2x.png
index db87b2d5..11f42042 100644
Binary files a/plugins/jetpack/_inc/images/module-icons-sprite-2x.png and b/plugins/jetpack/_inc/images/module-icons-sprite-2x.png differ
diff --git a/plugins/jetpack/_inc/images/module-icons-sprite.png b/plugins/jetpack/_inc/images/module-icons-sprite.png
index 44de3b9e..c6979f67 100644
Binary files a/plugins/jetpack/_inc/images/module-icons-sprite.png and b/plugins/jetpack/_inc/images/module-icons-sprite.png differ
diff --git a/plugins/jetpack/_inc/images/publicize.png b/plugins/jetpack/_inc/images/publicize.png
new file mode 100644
index 00000000..428b886c
Binary files /dev/null and b/plugins/jetpack/_inc/images/publicize.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/carousel.png b/plugins/jetpack/_inc/images/screenshots/carousel.png
new file mode 100644
index 00000000..5bcc94cd
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/carousel.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/custom-css.png b/plugins/jetpack/_inc/images/screenshots/custom-css.png
new file mode 100644
index 00000000..4be5cb22
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/custom-css.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/likes.png b/plugins/jetpack/_inc/images/screenshots/likes.png
new file mode 100644
index 00000000..1c7670a3
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/likes.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg b/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg
new file mode 100644
index 00000000..94ca6dd6
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg differ
diff --git a/plugins/jetpack/_inc/images/screenshots/mobile-theme.png b/plugins/jetpack/_inc/images/screenshots/mobile-theme.png
new file mode 100644
index 00000000..88bad2d6
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/mobile-theme.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/notes.png b/plugins/jetpack/_inc/images/screenshots/notes.png
new file mode 100644
index 00000000..4506db17
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/notes.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/post-by-email.png b/plugins/jetpack/_inc/images/screenshots/post-by-email.png
new file mode 100644
index 00000000..b114088c
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/post-by-email.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/publicize.png b/plugins/jetpack/_inc/images/screenshots/publicize.png
new file mode 100644
index 00000000..428b886c
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/publicize.png differ
diff --git a/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png b/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png
new file mode 100644
index 00000000..8168590d
Binary files /dev/null and b/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png differ
diff --git a/plugins/jetpack/_inc/jetpack.css b/plugins/jetpack/_inc/jetpack.css
index 0b6c340e..b684f7c7 100644
--- a/plugins/jetpack/_inc/jetpack.css
+++ b/plugins/jetpack/_inc/jetpack.css
@@ -21,20 +21,27 @@
height: 70px;
}
- #jp-header #jp-clouds #jp-disconnect {
+
+ #jp-header #jp-clouds #jp-disconnectors {
font-size: 12px;
color: #fff;
float: right;
- margin: -35px 25px 0 0;
- text-align: right;
+ margin-top: -35px;
+ text-align: left;
+ position: relative;
+ left: -45px;
}
- #jp-header #jp-clouds #jp-disconnect a {
+ #jp-header #jp-clouds .jp-disconnect a {
background: #8caa46 url( images/status-light.png ) 3px 85% no-repeat;
display: inline-block;
- padding: 4px 10px 3px 30px;
+ position: relative;
+ width: 100%;
+ height: 1.7em;
+ overflow: hidden;
+ padding: 4px 0 3px 30px;
+ margin: 0 -20px 3px 0;
color: #fff;
- text-align: center;
text-decoration: none;
border: 1px solid #7a943d;
-moz-border-radius: 5px;
@@ -45,14 +52,23 @@
box-shadow: inset 0 0 2px rgba( 255, 255, 255, 0.4 );
text-shadow: 0px -1px 0px rgba( 0,0,0,0.3 );
}
- #jp-header #jp-clouds #jp-disconnect a:hover {
- background: #8caa46 url( images/status-light.png ) 3px 5% no-repeat;
+ #jp-header #jp-clouds .jp-disconnect a:hover {
+ background: #8caa46 url( images/status-light.png ) 3px -2% no-repeat;
background-color: #839f40;
border-color: #6a8037;
}
- #jp-header #jp-clouds #jp-disconnect span { display: none; }
-
+ #jp-header #jp-clouds .jp-disconnect div {
+ position: relative;
+ line-height: 1.7em;
+ height: 1.7em;
+ }
+
+ #jp-header #jp-clouds .jp-disconnect a:hover div,
+ #jp-header #jp-clouds .jp-disconnect a.clicked div {
+ top: -1.7em;
+ }
+
/* Retina Header Clouds & Status Light */
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
#jp-header #jp-clouds {
@@ -63,18 +79,18 @@
background: transparent url( images/header-clouds-small-2x.png ) -120px 100% repeat-x;
background-size:980px 140px;
}
-
- #jp-header #jp-clouds #jp-disconnect a {
+
+ #jp-header #jp-clouds .jp-disconnect a {
background: #8caa46 url( images/status-light-2x.png ) 3px 85% no-repeat;
background-size:25px 57px;
}
- #jp-header #jp-clouds #jp-disconnect a:hover {
- background: #8caa46 url( images/status-light-2x.png ) 3px 5% no-repeat;
+ #jp-header #jp-clouds .jp-disconnect a:hover {
+ background: #8caa46 url( images/status-light-2x.png ) 3px -2% no-repeat;
background-size:25px 57px;
}
}
-
-
+
+
#jp-header h3 {
position: relative;
background: transparent url( images/logo.png ) top left no-repeat;
@@ -93,7 +109,7 @@
height: 120px;
top: -35px;
}
-
+
/* Retina Logo */
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
#jp-header h3 {
@@ -105,9 +121,9 @@
background-size:150px 120px;
}
}
-
-
-
+
+
+
#jp-header p {
position: absolute;
left: 390px;
@@ -237,11 +253,11 @@
color: #fff;
text-decoration: underline;
}
-
+
.jetpack-message .squeezer a:hover {
color: #f0a000;
}
-
+
.jetpack-message code, .jetpack-err p {
background: rgba( 0,0,0,0.2 );
font-size: 14px;
@@ -309,7 +325,7 @@
-moz-box-shadow: inset 0 0 20px rgba(0,0,0,0.05), 0 1px 2px rgba( 0,0,0,0.1 );
box-shadow: inset 0 0 20px rgba(0,0,0,0.05), 0 1px 2px rgba( 0,0,0,0.1 );
}
-
+
/* Retina moreinfo bg clouds */
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
.more-info {
@@ -317,8 +333,8 @@
background-size:980px 140px;
}
}
-
-
+
+
.more-info h4 {
padding: 0;
background: none;
@@ -340,7 +356,7 @@
left: 0;
background: url( images/arrow.png ) top left no-repeat;
}
-
+
/* Retina module more info arrow */
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
.more-info .arrow {
@@ -534,13 +550,13 @@ p.jp-help {
border-bottom-left-radius: 3px;
background-repeat: no-repeat;
background-image: url( images/module-icons-sprite.png );
+ background-size: 2555px 50px; /* remember to update this every time a new module is added! */
}
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
.jetpack-module div.module-image {
background-image: url( images/module-icons-sprite-2x.png );
- background-size: 1450px 50px;
}
}
@@ -586,6 +602,40 @@ p.jp-help {
#carousel.jetpack-module div.module-image {
background-position: -1325px 5px;
}
+ #custom-css.jetpack-module div.module-image {
+ background-position: -1459px 5px;
+ }
+ #minileven.jetpack-module div.module-image {
+ background-position: -1570px 5px;
+ }
+ #notes.jetpack-module div.module-image {
+ background-position: -1806px 5px;
+ }
+ #json-api.jetpack-module div.module-image {
+ background-position: -1689px 5px;
+ }
+ #mobile-push.jetpack-module div.module-image {
+ background-position: -1925px 5px;
+ }
+ #publicize.jetpack-module div.module-image {
+ background-position: -2136px 5px;
+ }
+ #post-by-email.jetpack-module div.module-image {
+ background-position: -2025px 5px;
+ }
+ #infinite-scroll.jetpack-module div.module-image {
+ background-position: -2230px 5px;
+ }
+ #photon.jetpack-module div.module-image {
+ background-position: -2320px 5px;
+ }
+ #tiled-gallery.jetpack-module div.module-image {
+ background-position: -2400px 5px;
+ }
+
+ #likes.jetpack-module div.module-image {
+ background-position: -2471px 5px;
+ }
.jetpack-module div.module-image p {
background-color: #b4d278;
@@ -725,7 +775,7 @@ p.jp-help {
margin-right: 15px;
box-shadow: none;
}
-
+
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
.placeholder h3 {
background: transparent url(images/icon-comingsoon-2x.png) top center no-repeat;
@@ -822,34 +872,10 @@ p#news-sub {
margin-top: 15px;
}
-#jetpack-settings .button, #jetpack-settings .button-primary {
- -moz-border-radius: 5px !important;
- -webkit-border-radius: 5px !important;
- border-radius: 5px !important;
- padding: 5px 10px !important;
- -moz-box-shadow: inset 0 0 2px #fff, 0 0 3px rgba(0,0,0,0.1);
- -webkit-box-shadow: inset 0 0 2px #fff, 0 0 3px rgba(0,0,0,0.1);
- box-shadow: inset 0 0 2px #fff, 0 0 3px rgba(0,0,0,0.1);
-}
-
#jetpack-settings .button-primary {
- color: #bceaff !important;
-}
-
-#jetpack-settings .button-primary:hover {
color: #fff !important;
}
-#jetpack-settings .button:hover {
- color: #298cba !important;
- border-color: #69acce !important;
- -moz-box-shadow: 0 0 2px rgba(105,172,206,1);
- -webkit-box-shadow: 0 0 2px rgba(105,172,206,1);
- box-shadow: 0 0 2px rgba(105,172,206,1);
- -webkit-transition-duration: .3s;
- -moz-transition-duration: .3s;
-}
-
.jp-survey {
position: relative;
z-index: 100;
@@ -889,91 +915,6 @@ p#news-sub {
display: block;
}
-.jp-survey a {
- color: #000;
- text-decoration: underline;
- -webkit-transition-duration: .3s;
- -moz-transition-duration: .3s;
- -o-transition-duration: .3s;
- -ms-transition-duration: .3s;
- transition-duration: .3s;
-}
-
-.jp-survey a:hover {
- color: #555;
- text-decoration: none;
-}
-
-#jetpack-settings .jp-survey p a.button-primary {
- font-size: 16px !important;
- display: inline-block;
- padding: 8px 15px;
- color: #fff!important;
- text-align: center;
- font-size: 20px;
- text-decoration: none;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
- border-radius: 5px;
- border: 1px solid #2A8CBA;
- background: #6AAFCF;
- -moz-box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- -webkit-box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- text-shadow: 0px -1px 0px rgba( 0,0,0,0.3);
- -webkit-transition-duration: .3s;
- -moz-transition-duration: .3s;
- -o-transition-duration: .3s;
- -ms-transition-duration: .3s;
- transition-duration: .3s;
- cursor: pointer;
- font-family: "Helvetica Neue",Helvetica,Arial,"Lucida Grande",Verdana,"Bitstream Vera Sans",sans-serif;
-}
-
-#jetpack-settings .jp-survey p a.button-primary:hover, #jetpack-settings .jp-survey p a.button-primary:active {
- background-color: #f0a000;
- border-color: #c87800;
- -webkit-transition-duration: .3s;
- outline: none;
-}
-
-.jp-survey p a.button-secondary {
- font-size: 16px !important;
- display: inline-block;
- padding: 8px 15px;
- color: #fff;
- text-align: center;
- font-size: 20px;
- text-decoration: none;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
- border-radius: 5px;
- border: 1px solid #8caa46;
- background: #b4d278;
- -moz-box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- -webkit-box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- box-shadow: inset 0 0 2px rgba( 255,255,255,1), 0 1px 1px rgba( 0,0,0,0.1 );
- text-shadow: 0px -1px 0px rgba( 0,0,0,0.3);
- -webkit-transition-duration: .3s;
- -moz-transition-duration: .3s;
- -o-transition-duration: .3s;
- -ms-transition-duration: .3s;
- transition-duration: .3s;
- cursor: pointer;
- font-family: "Helvetica Neue",Helvetica,Arial,"Lucida Grande",Verdana,"Bitstream Vera Sans",sans-serif;
-}
-
-.jp-survey p a.button-secondary:hover, .jp-survey p a.button-secondary:active {
- background-color: #f0a000;
- border-color: #c87800;
- -webkit-transition-duration: .3s;
- -moz-transition-duration: .3s;
- -o-transition-duration: .3s;
- -ms-transition-duration: .3s;
- transition-duration: .3s;
- outline: none;
-}
-
.jp-survey-container {
overflow: hidden;
padding: 0 20px 8px 0;
@@ -1095,3 +1036,35 @@ p#news-sub {
box-shadow: inset 0 0 2px #fff, 0 1px 7spx rgba(240,160,0,0.5);
}
+.jetpack-inline-error, .jetpack-inline-message {
+ padding: .5em 1em .5em 1em;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ border-width: 1px;
+ border-style: solid;
+ color: #333;
+}
+
+.jetpack-inline-error {
+ background-color: #ffebe8;
+ border-color: #c00;
+}
+
+.jetpack-inline-message {
+ background-color: #ffffe0;
+ border-color: #e6db55;
+}
+
+.jetpack-targetable {
+ border-top: 28px solid transparent;
+ margin-top: -28px;
+}
+
+.jetpack-targetable:target {
+ background-color: #ffffe0;
+ background-clip: padding-box;
+ padding: 0 10px;
+ margin-left: -10px;
+ margin-right: -10px;
+}
diff --git a/plugins/jetpack/_inc/jetpack.js b/plugins/jetpack/_inc/jetpack.js
index 69a86c13..6ef9de8d 100644
--- a/plugins/jetpack/_inc/jetpack.js
+++ b/plugins/jetpack/_inc/jetpack.js
@@ -57,36 +57,23 @@ jetpack = {
});
var widerWidth = 0;
- jQuery( '#jp-disconnect' ).hover( function() {
- var t = jQuery( this ),
- a = t.find( 'a' ),
- width = t.width(),
- changeWidth = widerWidth == 0;
-
- if ( changeWidth && widerWidth < width ) {
- widerWidth = width;
- }
- jetpack.statusText = a.html();
- a.html( jQuery( '#jp-disconnect span' ).html() );
- width = t.width();
- if ( changeWidth && widerWidth < width ) {
- widerWidth = width + 15;
- }
- if ( changeWidth ) {
- t.width( widerWidth );
- }
- a.hide().fadeIn(100);
- }, function() {
- var a = jQuery( 'a', this );
- a.html( jetpack.statusText );
- a.hide().fadeIn(100);
- jetpack.statusText = null;
- } ).find( 'a' ).click( function() {
+ jQuery( '#jp-disconnect a' ).click( function() {
if ( confirm( jetpackL10n.ays_disconnect ) ) {
- jQuery( '#jp-disconnect' ).unbind( 'mouseenter mouseleave' );
+ jQuery( this ).addClass( 'clicked' ).css( {
+ "background-image": 'url( ' + userSettings.url + 'wp-admin/images/wpspin_light.gif )',
+ "background-position": '9px 5px',
+ "background-size": '16px 16px'
+ } ).unbind( 'click' ).click( function() { return false; } );
+ } else {
+ return false;
+ }
+ } );
+ jQuery( '#jp-unlink a' ).click( function() {
+ if ( confirm( jetpackL10n.ays_unlink ) ) {
jQuery( this ).css( {
- "background-image": 'url( ' + userSettings.url + 'wp-admin/images/wpspin_dark.gif )',
+ "background-image": 'url( ' + userSettings.url + 'wp-admin/images/wpspin_light.gif )',
"background-position": '9px 5px',
+ "background-size": '16px 16px'
} ).unbind( 'click' ).click( function() { return false; } );
} else {
return false;
@@ -108,7 +95,7 @@ jetpack = {
jQuery( 'div.placeholder' ).show();
var containerWidth = jetpack.container.width(),
- needed = 4 * parseInt( containerWidth / 242, 10 ) - jetpack.numModules
+ needed = 5 * parseInt( containerWidth / 242, 10 ) - jetpack.numModules
if ( jetpack.numModules * 242 > containerWidth )
jQuery( 'div.placeholder' ).slice( needed ).hide();
@@ -155,12 +142,15 @@ jetpack = {
jQuery( window ).scrollTo( ( jQuery( 'div.more-info' ).prev().offset().top ) - 70, 600, function() { if ( typeof callback == 'function' ) callback.call( this ); } );
} else {
jQuery( 'div.more-info div.jp-content' ).hide();
- jQuery( 'div.more-info' ).slideUp( 200, function() {
- jQuery(this).detach().insertAfter( el );
+ jQuery( 'div.more-info' ).css( { height: '230px', minHeight: 0 } ).slideUp( 200, function() {
+ var $this = jQuery(this);
+ $this.detach().insertAfter( el );
jQuery( 'div.more-info div.jp-content' ).hide();
jetpack.learn_more_content( jQuery(card).attr( 'id' ) );
- jQuery( 'div.more-info' ).slideDown( 300 );
- jQuery( window ).scrollTo( ( jQuery( 'div.more-info' ).prev().offset().top ) - 70, 600, function() { if ( typeof callback == 'function' ) callback.call( this ); } );
+ $this.css( { height: '230px', minHeight: 0 } ).slideDown( 300, function() {
+ $this.css( { height: 'auto', minHeight: '230px' } );
+ } );
+ jQuery( window ).scrollTo( ( $this.prev().offset().top ) - 70, 600, function() { if ( typeof callback == 'function' ) callback.call( this ); } );
} );
}
@@ -170,7 +160,9 @@ jetpack = {
jQuery( el ).after( '' );
// Show the box
+ jQuery( 'div.more-info' ).css( { height: '230px', minHeight: 0 } );
jQuery( 'div.more-info', 'div.module-container' ).hide().slideDown( 400, function() {
+ jQuery( 'div.more-info' ).css( { height: 'auto', minHeight: '230px' } );
// Load the content and scroll to it
jetpack.learn_more_content( jQuery(card).attr( 'id' ) );
jQuery( window ).scrollTo( ( jQuery( 'div.more-info' ).prev().offset().top ) - 70, 600 );
@@ -226,7 +218,7 @@ jetpack = {
close_learn_more: function( callback ) {
jQuery( 'div.more-info div.jp-content' ).hide();
- jQuery( 'div.more-info' ).slideUp( 200, function() {
+ jQuery( 'div.more-info' ).css( { height: '230px', minHeight: 0 } ).slideUp( 200, function() {
jQuery( this ).remove();
jQuery( 'a.jetpack-deactivate-button' ).hide();
jetpack.linkClicked.parents( 'div.jetpack-module' ).children( '.jetpack-module-actions' ).children( 'a.jetpack-configure-button' ).show();
diff --git a/plugins/jetpack/_inc/jquery.inview.js b/plugins/jetpack/_inc/jquery.inview.js
new file mode 100644
index 00000000..45f71c4c
--- /dev/null
+++ b/plugins/jetpack/_inc/jquery.inview.js
@@ -0,0 +1,143 @@
+/**
+ * author Christopher Blum
+ * - based on the idea of Remy Sharp, http://remysharp.com/2009/01/26/element-in-view-event-plugin/
+ * - forked from http://github.com/zuk/jquery.inview/
+ */
+(function ($) {
+ var inviewObjects = {}, viewportSize, viewportOffset,
+ d = document, w = window, documentElement = d.documentElement, expando = $.expando;
+
+ $.event.special.inview = {
+ add: function(data) {
+ inviewObjects[data.guid + "-" + this[expando]] = { data: data, $element: $(this) };
+ },
+
+ remove: function(data) {
+ try { delete inviewObjects[data.guid + "-" + this[expando]]; } catch(e) {}
+ }
+ };
+
+ function getViewportSize() {
+ var mode, domObject, size = { height: w.innerHeight, width: w.innerWidth };
+
+ // if this is correct then return it. iPad has compat Mode, so will
+ // go into check clientHeight/clientWidth (which has the wrong value).
+ if (!size.height) {
+ mode = d.compatMode;
+ if (mode || !$.support.boxModel) { // IE, Gecko
+ domObject = mode === 'CSS1Compat' ?
+ documentElement : // Standards
+ d.body; // Quirks
+ size = {
+ height: domObject.clientHeight,
+ width: domObject.clientWidth
+ };
+ }
+ }
+
+ return size;
+ }
+
+ function getViewportOffset() {
+ return {
+ top: w.pageYOffset || documentElement.scrollTop || d.body.scrollTop,
+ left: w.pageXOffset || documentElement.scrollLeft || d.body.scrollLeft
+ };
+ }
+
+ function checkInView() {
+ var $elements = $(), elementsLength, i = 0;
+
+ $.each(inviewObjects, function(i, inviewObject) {
+ var selector = inviewObject.data.selector,
+ $element = inviewObject.$element;
+ $elements = $elements.add(selector ? $element.find(selector) : $element);
+ });
+
+ elementsLength = $elements.length;
+ if (elementsLength) {
+ viewportSize = viewportSize || getViewportSize();
+ viewportOffset = viewportOffset || getViewportOffset();
+
+ for (; i= 0 && element.offsetHeight >= 0 && element.style.display != "none" &&
+ elementOffset.top + elementSize.height > viewportOffset.top &&
+ elementOffset.top < viewportOffset.top + viewportSize.height &&
+ elementOffset.left + elementSize.width > viewportOffset.left &&
+ elementOffset.left < viewportOffset.left + viewportSize.width) {
+ visiblePartX = (viewportOffset.left > elementOffset.left ?
+ 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ?
+ 'left' : 'both');
+ visiblePartY = (viewportOffset.top > elementOffset.top ?
+ 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ?
+ 'top' : 'both');
+ visiblePartsMerged = visiblePartX + "-" + visiblePartY;
+ if (!inView || inView !== visiblePartsMerged) {
+ $element.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]);
+ }
+ } else if (inView) {
+ $element.data('inview', false).trigger('inview', [false]);
+ }
+ }
+ }
+ }
+
+ $(w).bind("scroll resize", function() {
+ viewportSize = viewportOffset = null;
+ });
+
+ // IE < 9 scrolls to focused elements without firing the "scroll" event
+ if (!documentElement.addEventListener && documentElement.attachEvent) {
+ documentElement.attachEvent("onfocusin", function() {
+ viewportOffset = null;
+ });
+ }
+
+ // Use setInterval in order to also make sure this captures elements within
+ // "overflow:scroll" elements or elements that appeared in the dom tree due to
+ // dom manipulation and reflow
+ // old: $(window).scroll(checkInView);
+ //
+ // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays
+ // intervals while the user scrolls. Therefore the inview event might fire a bit late there
+ setInterval(checkInView, 250);
+})(jQuery);
\ No newline at end of file
diff --git a/plugins/jetpack/_inc/jquery.jetpack-resize.js b/plugins/jetpack/_inc/jquery.jetpack-resize.js
new file mode 100644
index 00000000..e1adb22d
--- /dev/null
+++ b/plugins/jetpack/_inc/jquery.jetpack-resize.js
@@ -0,0 +1,275 @@
+/**
+ * Resizeable Iframes.
+ *
+ * Start listening to resize postMessage events for selected iframes:
+ * $( selector ).Jetpack( 'resizeable' );
+ * - OR -
+ * Jetpack.resizeable( 'on', context );
+ *
+ * Resize selected iframes:
+ * $( selector ).Jetpack( 'resizeable', 'resize', { width: 100, height: 200 } );
+ * - OR -
+ * Jetpack.resizeable( 'resize', { width: 100, height: 200 }, context );
+ *
+ * Stop listening to resize postMessage events for selected iframes:
+ * $( selector ).Jetpack( 'resizeable', 'off' );
+ * - OR -
+ * Jetpack.resizeable( 'off', context );
+ *
+ * Stop listening to all resize postMessage events:
+ * Jetpack.resizeable( 'off' );
+ */
+(function($) {
+ var listening = false, // Are we listening for resize postMessage events
+ sourceOrigins = [], // What origins are allowed to send resize postMessage events
+ $sources = false, // What iframe elements are we tracking resize postMessage events from
+
+ URLtoOrigin, // Utility to convert URLs into origins
+ setupListener, // Binds global resize postMessage event handler
+ destroyListener, // Unbinds global resize postMessage event handler
+
+ methods; // Jetpack.resizeable methods
+
+ // Setup the Jetpack global
+ if ( 'undefined' === typeof window.Jetpack ) {
+ window.Jetpack = {
+ /**
+ * Handles the two different calling methods:
+ * $( selector ).Jetpack( 'namespace', 'method', context ) // here, context is optional and is used to filter the collection
+ * - vs. -
+ * Jetpack.namespace( 'method', context ) // here context defines the collection
+ *
+ * @internal
+ *
+ * Call as: Jetpack.getTarget.call( this, context )
+ *
+ * @param string context: jQuery selector
+ * @return jQuery|undefined object on which to perform operations or undefined when context cannot be determined
+ */
+ getTarget: function( context ) {
+ if ( this instanceof jQuery ) {
+ return context ? this.filter( context ) : this;
+ }
+
+ return context ? $( context ) : context;
+ }
+ };
+ }
+
+ // Setup the Jetpack jQuery method
+ if ( 'undefined' === typeof $.fn.Jetpack ) {
+ /**
+ * Dispatches calls to the correct namespace
+ *
+ * @param string namespace
+ * @param ...
+ * @return mixed|jQuery (chainable)
+ */
+ $.fn.Jetpack = function( namespace ) {
+ if ( 'function' === typeof Jetpack[namespace] ) {
+ // Send the call to the correct Jetpack.namespace
+ return Jetpack[namespace].apply( this, Array.prototype.slice.call( arguments, 1 ) );
+ } else {
+ $.error( 'Namespace "' + namespace + '" does not exist on jQuery.Jetpack' );
+ }
+ };
+ }
+
+ // Define Jetpack.resizeable() namespace to just always bail if no postMessage
+ if ( 'function' !== typeof window.postMessage ) {
+ $.extend( window.Jetpack, {
+ /**
+ * Defines the Jetpack.resizeable() namespace.
+ * See below for non-trivial definition for browsers with postMessage.
+ */
+ resizeable: function() {
+ $.error( 'Browser does not support window.postMessage' );
+ }
+ } );
+
+ return;
+ }
+
+ /**
+ * Utility to convert URLs into origins
+ *
+ * http://example.com:port/path?query#fragment -> http://example.com:port
+ *
+ * @param string URL
+ * @return string origin
+ */
+ URLtoOrigin = function( URL ) {
+ if ( ! URL.match( /^https?:\/\// ) ) {
+ URL = document.location.href;
+ }
+ return URL.split( '/' ).slice( 0, 3 ).join( '/' );
+ };
+
+ /**
+ * Binds global resize postMessage event handler
+ */
+ setupListener = function() {
+ listening = true;
+
+ $( window ).on( 'message.JetpackResizeableIframe', function( e ) {
+ var event = e.originalEvent,
+ data;
+
+ // Ensure origin is allowed
+ if ( -1 === $.inArray( event.origin, sourceOrigins ) ) {
+ return;
+ }
+
+ // Some browsers send structured data, some send JSON strings
+ if ( 'object' === typeof event.data ) {
+ data = event.data;
+ } else {
+ try {
+ data = JSON.parse( event.data );
+ } catch ( err ) {
+ data = false;
+ }
+ }
+
+ if ( !data ) {
+ return;
+ }
+
+ // Is it a resize event?
+ if ( 'undefined' === typeof data.action || 'resize' !== data.action ) {
+ return;
+ }
+
+ // Find the correct iframe and resize it
+ $sources.filter( function() {
+ if ( 'undefined' !== typeof data.name )
+ return this.name === data.name;
+ else
+ return event.source === this.contentWindow;
+ } ).first().Jetpack( 'resizeable', 'resize', data );
+ } );
+ };
+
+ /**
+ * Unbinds global resize postMessage event handler
+ */
+ destroyListener = function() {
+ listening = false;
+ $( window ).off( 'message.JetpackResizeableIframe' );
+
+ sourceOrigins = [];
+ $( '.jetpack-resizeable' ).removeClass( 'jetpack-resizeable' );
+ $sources = false;
+ };
+
+ // Methods for Jetpack.resizeable() namespace
+ methods = {
+ /**
+ * Start listening for resize postMessage events on the given iframes
+ *
+ * Call statically as: Jetpack.resizeable( 'on', context )
+ * Call as: $( selector ).Jetpack( 'resizeable', 'on', context ) // context optional: used to filter the collectino
+ *
+ * @param string context jQuery selector.
+ * @return jQuery (chainable)
+ */
+ on: function( context ) {
+ var target = Jetpack.getTarget.call( this, context );
+
+ if ( ! listening ) {
+ setupListener();
+ }
+
+ target.each( function() {
+ sourceOrigins.push( URLtoOrigin( $( this ).attr( 'src' ) ) );
+ } ).addClass( 'jetpack-resizeable' );
+
+ $sources = $( '.jetpack-resizeable' );
+
+ return target;
+ },
+
+ /**
+ * Stop listening for resize postMessage events on the given iframes
+ *
+ * Call statically as: Jetpack.resizeable( 'off', context )
+ * Call as: $( selector ).Jetpack( 'resizeable', 'off', context ) // context optional: used to filter the collectino
+ *
+ * @param string context jQuery selector
+ * @return jQuery (chainable)
+ */
+ off: function( context ) {
+ var target = Jetpack.getTarget.call( this, context );
+
+ if ( 'undefined' === typeof target ) {
+ destroyListener();
+
+ return target;
+ }
+
+ target.each( function() {
+ var origin = URLtoOrigin( $( this ).attr( 'src' ) ),
+ pos = $.inArray( origin, sourceOrigins );
+
+ if ( -1 !== pos ) {
+ sourceOrigins.splice( pos, 1 );
+ }
+ } ).removeClass( 'jetpack-resizeable' );
+
+ $sources = $( '.jetpack-resizeable' );
+
+ return target;
+ },
+
+ /**
+ * Resize the given iframes
+ *
+ * Call statically as: Jetpack.resizeable( 'resize', dimensions, context )
+ * Call as: $( selector ).Jetpack( 'resizeable', 'resize', dimensions, context ) // context optional: used to filter the collectino
+ *
+ * @param object dimensions in pixels: { width: (int), height: (int) }
+ * @param string context jQuery selector
+ * @return jQuery (chainable)
+ */
+ resize: function( dimensions, context ) {
+ var target = Jetpack.getTarget.call( this, context );
+
+ $.each( [ 'width', 'height' ], function( i, variable ) {
+ var value = 0;
+ if ( 'undefined' !== typeof dimensions[variable] ) {
+ value = parseInt( dimensions[variable], 10 );
+ }
+
+ if ( 0 !== value ) {
+ target[variable]( value );
+ }
+ } );
+
+ return target;
+ }
+ };
+
+ // Define Jetpack.resizeable() namespace
+ $.extend( window.Jetpack, {
+ /**
+ * Defines the Jetpack.resizeable() namespace.
+ * See above for trivial definition for browsers with no postMessage.
+ *
+ * @param string method
+ * @param ...
+ * @return mixed|jQuery (chainable)
+ */
+ resizeable: function( method ) {
+ if ( methods[method] ) {
+ // Send the call to the correct Jetpack.resizeable() method
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ) );
+ } else if ( ! method ) {
+ // By default, send to Jetpack.resizeable( 'on' ), which isn't useful in that form but is when called as
+ // jQuery( selector ).Jetpack( 'resizeable' )
+ return methods.on.apply( this );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on Jetpack.resizeable' );
+ }
+ }
+ } );
+})(jQuery);
diff --git a/plugins/jetpack/_inc/jquery.spin.js b/plugins/jetpack/_inc/jquery.spin.js
new file mode 100644
index 00000000..4642af13
--- /dev/null
+++ b/plugins/jetpack/_inc/jquery.spin.js
@@ -0,0 +1,86 @@
+/*
+ * Matt Husby https://github.com/matthusby/spin.js
+ * Based on the jquery plugin by Bradley Smith
+ * https://gist.github.com/1290439
+ */
+
+/*
+Add spin to the jQuery object
+If color is not passed the spinner will be black
+You can now create a spinner using any of the variants below:
+$("#el").spin(); // Produces default Spinner
+$("#el").spin("small"); // Produces a 'small' Spinner
+$("#el").spin("large", "white"); // Produces a 'large' Spinner in white (or any valid CSS color).
+$("#el").spin({ ... }); // Produces a Spinner using your custom settings.
+$("#el").spin("small-right"); // Pin the small spinner to the right edge
+$("#el").spin("{small, medium, large}-{left, right, top, bottom}"); // All options for where to pin
+$("#el").spin(false); // Kills the spinner.
+*/
+
+( function( $ ) {
+ $.fn.spin = function( opts, color ) {
+ var presets = {
+ "small": { lines: 8, length: 2, width: 2, radius: 3, trail: 60, speed: 1.3 },
+ "medium": { lines: 8, length: 4, width: 3, radius: 5, trail: 60, speed: 1.3 },
+ "large": { lines: 10, length: 6, width: 4, radius: 7, trail: 60, speed: 1.3 }
+ };
+ if ( Spinner ) {
+ return this.each( function() {
+ var $this = $( this ),
+ data = $this.data();
+
+ if ( data.spinner ) {
+ data.spinner.stop();
+ delete data.spinner;
+ }
+ if ( opts !== false ) {
+ var spinner_options;
+ if ( typeof opts === "string" ) {
+ var spinner_base = opts.indexOf( '-' );
+ if( spinner_base == -1 ) {
+ spinner_base = opts;
+ } else {
+ spinner_base = opts.substring( 0, spinner_base );
+ }
+ if ( spinner_base in presets ) {
+ spinner_options = presets[spinner_base];
+ } else {
+ spinner_options = {};
+ }
+ var padding;
+ if ( opts.indexOf( "-right" ) != -1 ) {
+ padding = jQuery( this ).css( 'padding-left' );
+ if( typeof padding === "undefined" ) {
+ padding = 0;
+ } else {
+ padding = padding.replace( 'px', '' );
+ }
+ spinner_options.left = jQuery( this ).outerWidth() - ( 2 * ( spinner_options.length + spinner_options.width + spinner_options.radius ) ) - padding - 5;
+ }
+ if ( opts.indexOf( '-left' ) != -1 ) {
+ spinner_options.left = 5;
+ }
+ if ( opts.indexOf( '-top' ) != -1 ) {
+ spinner_options.top = 5;
+ }
+ if ( opts.indexOf( '-bottom' ) != -1 ) {
+ padding = jQuery( this ).css( 'padding-top' );
+ if( typeof padding === "undefined" ) {
+ padding = 0;
+ } else {
+ padding = padding.replace( 'px', '' );
+ }
+ spinner_options.top = jQuery( this ).outerHeight() - ( 2 * ( spinner_options.length + spinner_options.width + spinner_options.radius ) ) - padding - 5;
+ }
+ }
+ if( color ){
+ spinner_options.color = color;
+ }
+ data.spinner = new Spinner( spinner_options ).spin( this );
+ }
+ });
+ } else {
+ throw "Spinner class not available.";
+ }
+ };
+})( jQuery );
\ No newline at end of file
diff --git a/plugins/jetpack/_inc/postmessage.js b/plugins/jetpack/_inc/postmessage.js
new file mode 100644
index 00000000..e8933bca
--- /dev/null
+++ b/plugins/jetpack/_inc/postmessage.js
@@ -0,0 +1,438 @@
+/**
+ The MIT License
+
+ Copyright (c) 2010 Daniel Park (http://metaweb.com, http://postmessage.freebaseapps.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ **/
+var NO_JQUERY = {};
+(function(window, $, undefined) {
+
+ if (!("console" in window)) {
+ var c = window.console = {};
+ c.log = c.warn = c.error = c.debug = function(){};
+ }
+
+ if ($ === NO_JQUERY) {
+ // jQuery is optional
+ $ = {
+ fn: {},
+ extend: function() {
+ var a = arguments[0];
+ for (var i=1,len=arguments.length; i> 1) : o.left+mid) + 'px',
+ top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : o.top+mid) + 'px'
+ });
+ }
+
+ el.setAttribute('aria-role', 'progressbar');
+ self.lines(el, self.opts);
+
+ if (!useCssAnimations) {
+ // No CSS animation support, use setTimeout() instead
+ var i = 0;
+ var fps = o.fps;
+ var f = fps/o.speed;
+ var ostep = (1-o.opacity)/(f*o.trail / 100);
+ var astep = f/o.lines;
+
+ !function anim() {
+ i++;
+ for (var s=o.lines; s; s--) {
+ var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity);
+ self.opacity(el, o.lines-s, alpha, o);
+ }
+ self.timeout = self.el && setTimeout(anim, ~~(1000/fps));
+ }();
+ }
+ return self;
+ },
+ stop: function() {
+ var el = this.el;
+ if (el) {
+ clearTimeout(this.timeout);
+ if (el.parentNode) el.parentNode.removeChild(el);
+ this.el = undefined;
+ }
+ return this;
+ },
+ lines: function(el, o) {
+ var i = 0;
+ var seg;
+
+ function fill(color, shadow) {
+ return css(createEl(), {
+ position: 'absolute',
+ width: (o.length+o.width) + 'px',
+ height: o.width + 'px',
+ background: color,
+ boxShadow: shadow,
+ transformOrigin: 'left',
+ transform: 'rotate(' + ~~(360/o.lines*i) + 'deg) translate(' + o.radius+'px' +',0)',
+ borderRadius: (o.width>>1) + 'px'
+ });
+ }
+ for (; i < o.lines; i++) {
+ seg = css(createEl(), {
+ position: 'absolute',
+ top: 1+~(o.width/2) + 'px',
+ transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
+ opacity: o.opacity,
+ animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite'
+ });
+ if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}));
+ ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')));
+ }
+ return el;
+ },
+ opacity: function(el, i, val) {
+ if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////
+ // VML rendering for IE
+ /////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Check and init VML support
+ */
+ !function() {
+ var s = css(createEl('group'), {behavior: 'url(#default#VML)'});
+ var i;
+
+ if (!vendor(s, 'transform') && s.adj) {
+
+ // VML support detected. Insert CSS rules ...
+ for (i=4; i--;) sheet.addRule(['group', 'roundrect', 'fill', 'stroke'][i], 'behavior:url(#default#VML)');
+
+ Spinner.prototype.lines = function(el, o) {
+ var r = o.length+o.width;
+ var s = 2*r;
+
+ function grp() {
+ return css(createEl('group', {coordsize: s +' '+s, coordorigin: -r +' '+-r}), {width: s, height: s});
+ }
+
+ var margin = -(o.width+o.length)*2+'px';
+ var g = css(grp(), {position: 'absolute', top: margin, left: margin});
+
+ var i;
+
+ function seg(i, dx, filter) {
+ ins(g,
+ ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
+ ins(css(createEl('roundrect', {arcsize: 1}), {
+ width: r,
+ height: o.width,
+ left: o.radius,
+ top: -o.width>>1,
+ filter: filter
+ }),
+ createEl('fill', {color: o.color, opacity: o.opacity}),
+ createEl('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
+ )
+ )
+ );
+ }
+
+ if (o.shadow) {
+ for (i = 1; i <= o.lines; i++) {
+ seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
+ }
+ }
+ for (i = 1; i <= o.lines; i++) seg(i);
+ return ins(el, g);
+ };
+ Spinner.prototype.opacity = function(el, i, val, o) {
+ var c = el.firstChild;
+ o = o.shadow && o.lines || 0;
+ if (c && i+o < c.childNodes.length) {
+ c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild;
+ if (c) c.opacity = val;
+ }
+ };
+ }
+ else {
+ useCssAnimations = vendor(s, 'animation');
+ }
+ }();
+
+ window.Spinner = Spinner;
+
+})(window, document);
\ No newline at end of file
diff --git a/plugins/jetpack/class.jetpack-ixr-client.php b/plugins/jetpack/class.jetpack-ixr-client.php
index 19f119c5..8d6e52fe 100644
--- a/plugins/jetpack/class.jetpack-ixr-client.php
+++ b/plugins/jetpack/class.jetpack-ixr-client.php
@@ -18,8 +18,6 @@ class Jetpack_IXR_Client extends IXR_Client {
$args = wp_parse_args( $args, $defaults );
- $args['user_id'] = (int) $args['user_id'];
-
$this->jetpack_args = $args;
$this->IXR_Client( $args['url'], $path, $port, $timeout );
diff --git a/plugins/jetpack/class.jetpack-post-images.php b/plugins/jetpack/class.jetpack-post-images.php
new file mode 100644
index 00000000..afdcacfa
--- /dev/null
+++ b/plugins/jetpack/class.jetpack-post-images.php
@@ -0,0 +1,440 @@
+post_content, '[slideshow' ) )
+ return false; // no slideshow - bail
+
+ $permalink = get_permalink( $post->ID );
+
+ $images = array();
+
+ // Mechanic: Somebody set us up the bomb
+ $old_post = $GLOBALS['post'];
+ $GLOBALS['post'] = $post;
+ $old_shortcodes = $GLOBALS['shortcode_tags'];
+ $GLOBALS['shortcode_tags'] = array( 'slideshow' => $old_shortcodes['slideshow'] );
+
+ // Find all the slideshows
+ preg_match_all( '/' . get_shortcode_regex() . '/sx', $post->post_content, $slideshow_matches, PREG_SET_ORDER );
+
+ ob_start(); // The slideshow shortcode handler calls wp_print_scripts and wp_print_styles... not too happy about that
+
+ foreach ( $slideshow_matches as $slideshow_match ) {
+ $slideshow = do_shortcode_tag( $slideshow_match );
+ if ( false === $pos = stripos( $slideshow, 'slideShow.images' ) ) // must be something wrong - or we changed the output format in which case none of the following will work
+ continue;
+ $start = strpos( $slideshow, '[', $pos );
+ $end = strpos( $slideshow, ']', $start );
+ $post_images = json_decode( str_replace( "'", '"', substr( $slideshow, $start, $end - $start + 1 ) ) ); // parse via JSON
+ foreach ( $post_images as $post_image ) {
+ if ( !$post_image_id = absint( $post_image->id ) )
+ continue;
+
+ $meta = wp_get_attachment_metadata( $post_image_id );
+
+ // Must be larger than 200x200 (or user-specified)
+ if ( !isset( $meta['width'] ) || $meta['width'] < $width )
+ continue;
+ if ( !isset( $meta['height'] ) || $meta['height'] < $height )
+ continue;
+
+ $url = wp_get_attachment_url( $post_image_id );
+
+ $images[] = array(
+ 'type' => 'image',
+ 'from' => 'slideshow',
+ 'src' => $url,
+ 'src_width' => $meta['width'],
+ 'src_height' => $meta['height'],
+ 'href' => $permalink,
+ );
+ }
+ }
+ ob_end_clean();
+
+ // Operator: Main screen turn on
+ $GLOBALS['shortcode_tags'] = $old_shortcodes;
+ $GLOBALS['post'] = $old_post;
+
+ return $images;
+ }
+
+ /**
+ * If a gallery is detected, then get all the images from it.
+ */
+ static function from_gallery( $post_id ) {
+ $post = get_post( $post_id );
+
+ if ( false === strpos( $post->post_content, '[gallery' ) )
+ return false; // no gallery - bail
+
+ $permalink = get_permalink( $post->ID );
+
+ $images = array();
+
+ // CATS: All your base are belong to us
+ $old_post = $GLOBALS['post'];
+ $GLOBALS['post'] = $post;
+ $old_shortcodes = $GLOBALS['shortcode_tags'];
+ $GLOBALS['shortcode_tags'] = array( 'gallery' => $old_shortcodes['gallery'] );
+
+ // Find all the galleries
+ preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $gallery_matches, PREG_SET_ORDER );
+
+ foreach ( $gallery_matches as $gallery_match ) {
+ $gallery = do_shortcode_tag( $gallery_match );
+
+ // Um... no images in the gallery - bail
+ if ( false === $pos = stripos( $gallery, ' ]*src=([\'"])([^\'"]*)\\1/', $gallery, $image_match, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE );
+
+ $a_pos = 0;
+ foreach ( $image_match[2] as $src ) {
+ list( $raw_src ) = explode( '?', $src[0] ); // pull off any Query string (?w=250)
+ $raw_src = wp_specialchars_decode( $raw_src ); // rawify it
+ $raw_src = esc_url_raw( $raw_src ); // clean it
+
+ $a_pos = strrpos( substr( $gallery, 0, $src[1] ), '?
+
+ if ( false !== $a_pos && preg_match( '/ ]*href=([\'"])([^\'"]*)\\1/', $gallery, $href_match, 0, $a_pos ) ) {
+ $href = wp_specialchars_decode( $href_match[2] );
+ $href = esc_url_raw( $href );
+ } else {
+ // CATS: You have no chance to survive make your time
+ $href = $raw_src;
+ }
+
+ $a_pos = $src[1];
+
+ $images[] = array(
+ 'type' => 'image',
+ 'from' => 'gallery',
+ 'src' => $raw_src,
+ 'href' => $permalink, // $href,
+ );
+ }
+ }
+
+ // Captain: For great justice
+ $GLOBALS['shortcode_tags'] = $old_shortcodes;
+ $GLOBALS['post'] = $old_post;
+
+ return $images;
+ }
+
+ /**
+ * Get attachment images for a specified post and return them. Also make sure
+ * their dimensions are at or above a required minimum.
+ */
+ static function from_attachment( $post_id, $width = 200, $height = 200 ) {
+
+ $post_images = get_posts( array(
+ 'post_parent' => $post_id, // Must be children of post
+ 'numberposts' => 5, // No more than 5
+ 'post_type' => 'attachment', // Must be attachments
+ 'post_mime_type' => 'image', // Must be images
+ ) );
+
+ if ( !$post_images )
+ return false;
+
+ $permalink = get_permalink( $post_id );
+
+ $images = array();
+
+ foreach ( $post_images as $post_image ) {
+ $meta = wp_get_attachment_metadata( $post_image->ID );
+ // Must be larger than 200x200
+ if ( !isset( $meta['width'] ) || $meta['width'] < $width )
+ continue;
+ if ( !isset( $meta['height'] ) || $meta['height'] < $height )
+ continue;
+
+ $url = wp_get_attachment_url( $post_image->ID );
+
+ $images[] = array(
+ 'type' => 'image',
+ 'from' => 'attachment',
+ 'src' => $url,
+ 'src_width' => $meta['width'],
+ 'src_height' => $meta['height'],
+ 'href' => $permalink,
+ );
+ }
+
+ /*
+ * We only want to pass back attached images that were actually inserted.
+ * We can load up all the images found in the HTML source and then
+ * compare URLs to see if an image is attached AND inserted.
+ */
+ $html_images = array();
+ $html_images = self::from_html( $post_id );
+ $inserted_images = array();
+
+ foreach( $html_images as $html_image ) {
+ $src = parse_url( $html_image['src'] );
+ $inserted_images[] = $src['scheme'] . '://' . $src['host'] . $src['path']; // strip off any query strings
+ }
+ foreach( $images as $i => $image ) {
+ if ( !in_array( $image['src'], $inserted_images ) )
+ unset( $images[$i] );
+ }
+
+ return $images;
+ }
+
+ /**
+ * Check if a Featured Image is set for this post, and return it in a similar
+ * format to the other images?_from_*() methods.
+ * @param int $post_id The post ID to check
+ * @return Array containing details of the Featured Image, or empty array if none.
+ */
+ static function from_thumbnail( $post_id, $width = 200, $height = 200 ) {
+ $images = array();
+
+ if ( !function_exists( 'get_post_thumbnail_id' ) ) {
+ return $images;
+ }
+
+ $thumb = get_post_thumbnail_id( $post_id );
+
+ if ( $thumb ) {
+ $meta = wp_get_attachment_metadata( $thumb );
+
+ // Must be larger than requested minimums
+ if ( !isset( $meta['width'] ) || $meta['width'] < $width )
+ return $images;
+ if ( !isset( $meta['height'] ) || $meta['height'] < $height )
+ return $images;
+
+ $url = wp_get_attachment_url( $thumb );
+ if ( stristr( $url, '?' ) )
+ $url = substr( $url, 0, strpos( $url, '?' ) );
+
+ $images = array( array( // Other methods below all return an array of arrays
+ 'type' => 'image',
+ 'from' => 'thumbnail',
+ 'src' => $url,
+ 'src_width' => $meta['width'],
+ 'src_height' => $meta['height'],
+ 'href' => get_permalink( $thumb ),
+ ) );
+ }
+ return $images;
+ }
+
+ /**
+ * Very raw -- just parse the HTML and pull out any/all img tags and return their src
+ * @param mixed $html_or_id The HTML string to parse for images, or a post id
+ * @return Array containing images
+ */
+ static function from_html( $html_or_id ) {
+ $images = array();
+
+ if ( is_numeric( $html_or_id ) ) {
+ $post = get_post( $html_or_id );
+
+ if ( !$post )
+ return $images;
+ $html = $post->post_content; // DO NOT apply the_content filters here, it will cause loops
+ }
+
+ if ( !$html )
+ return $images;
+
+ preg_match_all( '!!iUs', $html, $matches );
+ if ( !empty( $matches[1] ) ) {
+ foreach ( $matches[1] as $match ) {
+ if ( stristr( $match, '/smilies/' ) )
+ continue;
+
+ $images[] = array(
+ 'type' => 'image',
+ 'from' => 'html',
+ 'src' => html_entity_decode( $match ),
+ 'href' => '', // No link to apply to these. Might potentially parse for that as well, but not for now
+ );
+ }
+ }
+
+ return $images;
+ }
+
+ /**
+ * @param int $post_id The post ID to check
+ * @param int $size
+ * @return Array containing details of the image, or empty array if none.
+ */
+ static function from_blavatar( $post_id, $size = 96 ) {
+ if ( !function_exists( 'blavatar_domain' ) || !function_exists( 'blavatar_exists' ) || !function_exists( 'blavatar_url' ) ) {
+ return array();
+ }
+
+ $permalink = get_permalink( $post_id );
+ $domain = blavatar_domain( $permalink );
+
+ if ( !blavatar_exists( $domain ) ) {
+ return array();
+ }
+
+ $url = blavatar_url( $domain, 'img', $size );
+
+ return array( array(
+ 'type' => 'image',
+ 'from' => 'blavatar',
+ 'src' => $url,
+ 'src_width' => $size,
+ 'src_height' => $size,
+ 'href' => $permalink,
+ ) );
+ }
+
+ /**
+ * @param int $post_id The post ID to check
+ * @param int $size
+ * @param string $default The default image to use.
+ * @return Array containing details of the image, or empty array if none.
+ */
+ static function from_gravatar( $post_id, $size = 96, $default = false ) {
+ $post = get_post( $post_id );
+ $permalink = get_permalink( $post_id );
+
+ if ( function_exists( 'get_avatar_url' ) ) {
+ $url = get_avatar_url( $post->post_author, $size, $default, true );
+ if ( $url && is_array( $url ) ) {
+ $url = $url[0];
+ }
+ } else {
+ $has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
+ if ( !$has_filter ) {
+ add_filter( 'pre_option_show_avatars', '__return_true' );
+ }
+ $avatar = get_avatar( $post->post_author, $size, $default );
+ if ( !$has_filter ) {
+ remove_filter( 'pre_option_show_avatars', '__return_true' );
+ }
+
+ if ( !$avatar ) {
+ return array();
+ }
+
+ if ( !preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $matches ) ) {
+ return array();
+ }
+
+ $url = wp_specialchars_decode( $matches[1], ENT_QUOTES );
+ }
+
+ return array( array(
+ 'type' => 'image',
+ 'from' => 'gravatar',
+ 'src' => $url,
+ 'src_width' => $size,
+ 'src_height' => $size,
+ 'href' => $permalink,
+ ) );
+ }
+
+ /**
+ * Run through the different methods that we have available to try to find a single good
+ * display image for this post.
+ * @param int $post_id
+ * @param array $args Other arguments (currently width and height required for images where possible to determine)
+ * @return Array containing details of the best image to be used
+ */
+ static function get_image( $post_id, $args = array() ) {
+ $image = '';
+ do_action( 'jetpack_postimages_pre_get_image', $post_id );
+ $media = self::get_images( $post_id, $args );
+
+
+ if ( is_array( $media ) ) {
+ foreach ( $media as $item ) {
+ if ( 'image' == $item['type'] ) {
+ $image = $item;
+ break;
+ }
+ }
+ }
+
+ do_action( 'jetpack_postimages_post_get_image', $post_id );
+
+ return $image;
+ }
+
+ /**
+ * Get an array containing a collection of possible images for this post, stopping once we hit a method
+ * that returns something useful.
+ * @param int $post_id
+ * @param array $args Optional args, see defaults list for details
+ * @return Array containing images that would be good for representing this post
+ */
+ static function get_images( $post_id, $args = array() ) {
+ // Figure out which image to attach to this post.
+ $media = false;
+
+ $media = apply_filters( 'jetpack_images_pre_get_images', $media, $post_id, $args );
+ if ( $media )
+ return $media;
+
+ $defaults = array(
+ 'width' => 200, // Required minimum width (if possible to determine)
+ 'height' => 200, // Required minimum height (if possible to determine)
+
+ 'fallback_to_avatars' => false, // Optionally include Blavatar and Gravatar (in that order) in the image stack
+ 'avatar_size' => 96, // Used for both Grav and Blav
+ 'gravatar_default' => false, // Default image to use if we end up with no Gravatar
+
+ 'from_thumbnail' => true, // Use these flags to specifcy which methods to use to find an image
+ 'from_slideshow' => true,
+ 'from_gallery' => true,
+ 'from_attachment' => true,
+ 'from_html' => true,
+
+ 'html_content' => '' // HTML string to pass to from_html()
+ );
+ $args = wp_parse_args( $args, $defaults );
+
+ $media = false;
+ if ( $args['from_thumbnail'] )
+ $media = self::from_thumbnail( $post_id, $args['width'], $args['height'] );
+ if ( !$media && $args['from_slideshow'] )
+ $media = self::from_slideshow( $post_id, $args['width'], $args['height'] );
+ if ( !$media && $args['from_gallery'] )
+ $media = self::from_gallery( $post_id );
+ if ( !$media && $args['from_attachment'] )
+ $media = self::from_attachment( $post_id, $args['width'], $args['height'] );
+ if ( !$media && $args['from_html'] ) {
+ if ( empty( $args['html_content'] ) )
+ $media = self::from_html( $post_id ); // Use the post_id, which will load the content
+ else
+ $media = self::from_html( $args['html_content'] ); // If html_content is provided, use that
+ }
+
+ if ( !$media && $args['fallback_to_avatars'] ) {
+ $media = self::from_blavatar( $post_id, $args['avatar_size'] );
+ if ( !$media )
+ $media = self::from_gravatar( $post_id, $args['avatar_size'], $args['gravatar_default'] );
+ }
+
+ return apply_filters( 'jetpack_images_get_images', $media, $post_id, $args );
+ }
+}
diff --git a/plugins/jetpack/class.jetpack-signature.php b/plugins/jetpack/class.jetpack-signature.php
index b5718846..975375ab 100644
--- a/plugins/jetpack/class.jetpack-signature.php
+++ b/plugins/jetpack/class.jetpack-signature.php
@@ -17,21 +17,32 @@ class Jetpack_Signature {
$this->time_diff = $time_diff;
}
- function sign_current_request( $override = null ) {
+ function sign_current_request( $override = array() ) {
+ if ( isset( $override['scheme'] ) ) {
+ $scheme = $override['scheme'];
+ if ( !in_array( $scheme, array( 'http', 'https' ) ) ) {
+ return new Jetpack_Error( 'invalid_sheme', 'Invalid URL scheme' );
+ }
+ } else {
+ if ( is_ssl() ) {
+ $scheme = 'https';
+ } else {
+ $scheme = 'http';
+ }
+ }
+
if ( is_ssl() ) {
- $scheme = 'https';
$port = JETPACK_SIGNATURE__HTTPS_PORT == $_SERVER['SERVER_PORT'] ? '' : $_SERVER['SERVER_PORT'];
} else {
- $scheme = 'http';
$port = JETPACK_SIGNATURE__HTTP_PORT == $_SERVER['SERVER_PORT'] ? '' : $_SERVER['SERVER_PORT'];
}
$url = "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . stripslashes( $_SERVER['REQUEST_URI'] );
- if ( isset( $override['body'] ) && !is_null( $override['body'] ) ) {
+ if ( array_key_exists( 'body', $override ) && !is_null( $override['body'] ) ) {
$body = $override['body'];
} else if ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
- $body = $GLOBALS['HTTP_RAW_POST_DATA'];
+ $body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
} else {
$body = null;
}
@@ -45,7 +56,8 @@ class Jetpack_Signature {
}
}
- return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $_SERVER['REQUEST_METHOD'], $url, $body, true );
+ $method = isset( $override['method'] ) ? $override['method'] : $_SERVER['REQUEST_METHOD'];
+ return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $url, $body, true );
}
// body_hash v. body-hash is annoying. Refactor to accept an array?
diff --git a/plugins/jetpack/class.jetpack-user-agent.php b/plugins/jetpack/class.jetpack-user-agent.php
new file mode 100644
index 00000000..3ae1bb85
--- /dev/null
+++ b/plugins/jetpack/class.jetpack-user-agent.php
@@ -0,0 +1,1331 @@
+ false, 'dumb' => false, 'any' => false );
+ static $first_run = true;
+ static $matched_agent = '';
+
+ $ua_info = new Jetpack_User_Agent_Info();
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) || strpos( strtolower( $_SERVER['HTTP_USER_AGENT'] ), 'ipad' ) )
+ return false;
+
+ if( $ua_info->is_android_tablet() && $ua_info->is_kindle_touch() === false )
+ return false;
+
+ if( $ua_info->is_blackberry_tablet() )
+ return false;
+
+ if ( $first_run ) {
+ $first_run = false;
+
+ //checks for iPhoneTier devices & RichCSS devices
+ if ( $ua_info->isTierIphone() || $ua_info->isTierRichCSS() ) {
+ $kinds['smart'] = true;
+ $matched_agent = $ua_info->matched_agent;
+ }
+
+ if ( !$kinds['smart'] ) {
+ // if smart, we are not dumb so no need to check
+ $dumb_agents = $ua_info->dumb_agents;
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ foreach ( $dumb_agents as $dumb_agent ) {
+ if ( false !== strpos( $agent, $dumb_agent ) ) {
+ $kinds['dumb'] = true;
+ $matched_agent = $dumb_agent;
+ break;
+ }
+ }
+
+ if ( !$kinds['dumb'] ) {
+ if ( isset( $_SERVER['HTTP_X_WAP_PROFILE'] ) ) {
+ $kinds['dumb'] = true;
+ $matched_agent = 'http_x_wap_profile';
+ } elseif ( isset( $_SERVER['HTTP_ACCEPT']) && ( preg_match( '/wap\.|\.wap/i', $_SERVER['HTTP_ACCEPT'] ) || false !== strpos( strtolower( $_SERVER['HTTP_ACCEPT'] ), 'application/vnd.wap.xhtml+xml' ) ) ) {
+ $kinds['dumb'] = true;
+ $matched_agent = 'vnd.wap.xhtml+xml';
+ }
+ }
+ }
+
+ if ( $kinds['dumb'] || $kinds['smart'] )
+ $kinds['any'] = true;
+ }
+
+ if ( $return_matched_agent )
+ return $matched_agent;
+
+ return $kinds[$kind];
+}
+
+class Jetpack_User_Agent_Info {
+
+ var $useragent;
+ var $matched_agent;
+ var $isTierIphone; //Stores whether is the iPhone tier of devices.
+ var $isTierRichCss; //Stores whether the device can probably support Rich CSS, but JavaScript (jQuery) support is not assumed.
+ var $isTierGenericMobile; //Stores whether it is another mobile device, which cannot be assumed to support CSS or JS (eg, older BlackBerry, RAZR)
+
+ private $_platform = null; //Stores the device platform name
+ const PLATFORM_WINDOWS = 'windows';
+ const PLATFORM_IPHONE = 'iphone';
+ const PLATFORM_IPOD = 'ipod';
+ const PLATFORM_IPAD = 'ipad';
+ const PLATFORM_BLACKBERRY = 'blackberry';
+ const PLATFORM_BLACKBERRY_10 = 'blackberry_10';
+ const PLATFORM_SYMBIAN = 'symbian_series60';
+ const PLATFORM_SYMBIAN_S40 = 'symbian_series40';
+ const PLATFORM_J2ME_MIDP = 'j2me_midp';
+ const PLATFORM_ANDROID = 'android';
+ const PLATFORM_ANDROID_TABLET = 'android_tablet';
+
+ var $dumb_agents = array(
+ 'nokia', 'blackberry', 'philips', 'samsung', 'sanyo', 'sony', 'panasonic', 'webos',
+ 'ericsson', 'alcatel', 'palm',
+ 'windows ce', 'opera mini', 'series60', 'series40',
+ 'au-mic,', 'audiovox', 'avantgo', 'blazer',
+ 'danger', 'docomo', 'epoc',
+ 'ericy', 'i-mode', 'ipaq', 'midp-',
+ 'mot-', 'netfront', 'nitro',
+ 'palmsource', 'pocketpc', 'portalmmm',
+ 'rover', 'sie-',
+ 'symbian', 'cldc-', 'j2me',
+ 'smartphone', 'up.browser', 'up.link',
+ 'up.link', 'vodafone/', 'wap1.', 'wap2.', 'mobile', 'googlebot-mobile',
+ );
+
+ //The constructor. Initializes default variables.
+ function Jetpack_User_Agent_Info()
+ {
+ if ( !empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ $this->useragent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ }
+
+ /**
+ * This method detects the mobile User Agent name.
+ *
+ * @return string The matched User Agent name, false otherwise.
+ */
+ function get_mobile_user_agent_name() {
+ if( $this->is_chrome_for_iOS( ) ) //keep this check before the safari rule
+ return 'chrome-for-ios';
+ elseif ( $this->is_iphone_or_ipod( 'iphone-safari' ) )
+ return 'iphone';
+ elseif ( $this->is_ipad( 'ipad-safari' ) )
+ return 'ipad';
+ elseif ( $this->is_android_tablet() ) //keep this check before the android rule
+ return 'android_tablet';
+ elseif ( $this->is_android() )
+ return 'android';
+ elseif ( $this->is_blackberry_10() )
+ return 'blackberry_10';
+ elseif ( $this->is_blackbeberry() )
+ return 'blackberry';
+ elseif ( $this->is_WindowsPhone7() )
+ return 'win7';
+ elseif ( $this->is_windows_phone_8() )
+ return 'winphone8';
+ elseif ( $this->is_opera_mini() )
+ return 'opera-mini';
+ elseif ( $this->is_opera_mini_dumb() )
+ return 'opera-mini-dumb';
+ elseif ( $this->is_opera_mobile() )
+ return 'opera-mobi';
+ elseif ( $this->is_blackberry_tablet() )
+ return 'blackberry_tablet';
+ elseif ( $this->is_kindle_fire() )
+ return 'kindle-fire';
+ elseif ( $this->is_PalmWebOS() )
+ return 'webos';
+ elseif ( $this->is_S60_OSSBrowser() )
+ return 'series60';
+ elseif ( $this->is_firefox_mobile() )
+ return 'firefox_mobile';
+ elseif ( $this->is_MaemoTablet() )
+ return 'maemo';
+ elseif ( $this->is_MeeGo() )
+ return 'meego';
+ elseif( $this->is_TouchPad() )
+ return 'hp_tablet';
+ elseif ( $this->is_facebook_for_iphone() )
+ return 'facebook-for-iphone';
+ elseif ( $this->is_facebook_for_ipad() )
+ return 'facebook-for-ipad';
+ elseif ( $this->is_twitter_for_iphone() )
+ return 'twitter-for-iphone';
+ elseif ( $this->is_twitter_for_ipad() )
+ return 'twitter-for-ipad';
+ elseif ( $this->is_wordpress_for_ios() )
+ return 'ios-app';
+ elseif ( $this->is_iphone_or_ipod( 'iphone-not-safari' ) )
+ return 'iphone-unknown';
+ elseif ( $this->is_ipad( 'ipad-not-safari' ) )
+ return 'ipad-unknown';
+ else {
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $dumb_agents = $this->dumb_agents;
+ foreach ( $dumb_agents as $dumb_agent ) {
+ if ( false !== strpos( $agent, $dumb_agent ) ) {
+ return $dumb_agent;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This method detects the mobile device's platform. All return strings are from the class constants.
+ * Note that this function returns the platform name, not the UA name/type. You should use a different function
+ * if you need to test the UA capabilites.
+ *
+ * @return string Name of the platform, false otherwise.
+ */
+ public function get_platform() {
+ if ( isset( $this->_platform ) ) {
+ return $this->_platform;
+ }
+
+ if ( strpos( $this->useragent, 'windows phone' ) !== false ) {
+ $this->_platform = self::PLATFORM_WINDOWS;
+ }
+ elseif ( strpos( $this->useragent, 'windows ce' ) !== false ) {
+ $this->_platform = self::PLATFORM_WINDOWS;
+ }
+ elseif ( strpos( $this->useragent, 'ipad' ) !== false ) {
+ $this->_platform = self::PLATFORM_IPAD;
+ }
+ else if ( strpos( $this->useragent, 'ipod' ) !== false ) {
+ $this->_platform = self::PLATFORM_IPOD;
+ }
+ else if ( strpos( $this->useragent, 'iphone' ) !== false ) {
+ $this->_platform = self::PLATFORM_IPHONE;
+ }
+ elseif ( strpos( $this->useragent, 'android' ) !== false ) {
+ if ( $this->is_android_tablet() )
+ $this->_platform = self::PLATFORM_ANDROID_TABLET;
+ else
+ $this->_platform = self::PLATFORM_ANDROID;
+ }
+ elseif ( $this->is_kindle_fire() ) {
+ $this->_platform = self::PLATFORM_ANDROID_TABLET;
+ }
+ elseif ( $this->is_blackberry_10() ) {
+ $this->_platform = self::PLATFORM_BLACKBERRY_10;
+ }
+ elseif ( strpos( $this->useragent, 'blackberry' ) !== false ) {
+ $this->_platform = self::PLATFORM_BLACKBERRY;
+ }
+ elseif ( $this->is_blackberry_tablet() ) {
+ $this->_platform = self::PLATFORM_BLACKBERRY;
+ }
+ elseif ( $this->is_symbian_platform() ) {
+ $this->_platform = self::PLATFORM_SYMBIAN;
+ }
+ elseif ( $this->is_symbian_s40_platform() ) {
+ $this->_platform = self::PLATFORM_SYMBIAN_S40;
+ }
+ elseif ( $this->is_J2ME_platform() ) {
+ $this->_platform = self::PLATFORM_J2ME_MIDP;
+ }
+ else
+ $this->_platform = false;
+
+ return $this->_platform;
+ }
+
+ /*
+ * This method detects for UA which can display iPhone-optimized web content.
+ * Includes iPhone, iPod Touch, Android, WebOS, Fennec (Firefox mobile), etc.
+ *
+ */
+ function isTierIphone() {
+ if ( isset( $this->isTierIphone ) ) {
+ return $this->isTierIphone;
+ }
+ if ( $this->is_iphoneOrIpod() ) {
+ $this->matched_agent = 'iphone';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_android() ) {
+ $this->matched_agent = 'android';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_windows_phone_8() ) {
+ $this->matched_agent = 'winphone8';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_WindowsPhone7() ) {
+ $this->matched_agent = 'win7';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_blackberry_10() ) {
+ $this->matched_agent = 'blackberry-10';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_blackbeberry() && $this->detect_blackberry_browser_version() == 'blackberry-webkit' ) {
+ $this->matched_agent = 'blackberry-webkit';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_blackberry_tablet() ) {
+ $this->matched_agent = 'blackberry_tablet';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_PalmWebOS() ) {
+ $this->matched_agent = 'webos';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_TouchPad() ) {
+ $this->matched_agent = 'hp_tablet';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_firefox_mobile() ) {
+ $this->matched_agent = 'fennec';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_opera_mobile() ) {
+ $this->matched_agent = 'opera-mobi';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_MaemoTablet() ) {
+ $this->matched_agent = 'maemo';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_MeeGo() ) {
+ $this->matched_agent = 'meego';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_kindle_touch() ) {
+ $this->matched_agent = 'kindle-touch';
+ $this->isTierIphone = true;
+ $this->isTierRichCss = false;
+ $this->isTierGenericMobile = false;
+ }
+ else {
+ $this->isTierIphone = false;
+ }
+ return $this->isTierIphone;
+ }
+
+ /*
+ * This method detects for UA which are likely to be capable
+ * but may not necessarily support JavaScript.
+ * Excludes all iPhone Tier UA.
+ *
+ */
+ function isTierRichCss(){
+ if ( isset( $this->isTierRichCss ) ) {
+ return $this->isTierRichCss;
+ }
+ if ($this->isTierIphone())
+ return false;
+
+ //The following devices are explicitly ok.
+ if ( $this->is_S60_OSSBrowser() ) {
+ $this->matched_agent = 'series60';
+ $this->isTierIphone = false;
+ $this->isTierRichCss = true;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_opera_mini() ) {
+ $this->matched_agent = 'opera-mini';
+ $this->isTierIphone = false;
+ $this->isTierRichCss = true;
+ $this->isTierGenericMobile = false;
+ }
+ elseif ( $this->is_blackbeberry() ) {
+ $detectedDevice = $this->detect_blackberry_browser_version();
+ if ( $detectedDevice === 'blackberry-5' || $detectedDevice == 'blackberry-4.7' || $detectedDevice === 'blackberry-4.6' ) {
+ $this->matched_agent = $detectedDevice;
+ $this->isTierIphone = false;
+ $this->isTierRichCss = true;
+ $this->isTierGenericMobile = false;
+ }
+ }
+ else {
+ $this->isTierRichCss = false;
+ }
+
+ return $this->isTierRichCss;
+ }
+
+ // Detects if the user is using a tablet.
+ // props Corey Gilmore, BGR.com
+ function is_tablet() {
+ return ( 0 // never true, but makes it easier to manage our list of tablet conditions
+ || self::is_ipad()
+ || self::is_android_tablet()
+ || self::is_blackberry_tablet()
+ || self::is_kindle_fire()
+ || self::is_MaemoTablet()
+ || self::is_TouchPad()
+ );
+ }
+
+ /*
+ * Detects if the current UA is the default iPhone or iPod Touch Browser.
+ *
+ * DEPRECATED: use is_iphone_or_ipod
+ *
+ */
+ function is_iphoneOrIpod(){
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ if ( ( strpos( $ua, 'iphone' ) !== false ) || ( strpos( $ua,'ipod' ) !== false ) ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current UA is iPhone Mobile Safari or another iPhone or iPod Touch Browser.
+ *
+ * They type can check for any iPhone, an iPhone using Safari, or an iPhone using something other than Safari.
+ *
+ * Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPhone browser),
+ * you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
+ * Otherwise those browsers will be 'catched' by the iphone string.
+ *
+ */
+ function is_iphone_or_ipod( $type = 'iphone-any' ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $is_iphone = ( strpos( $ua, 'iphone' ) !== false ) || ( strpos( $ua,'ipod' ) !== false );
+ $is_safari = ( false !== strpos( $ua, 'safari' ) );
+
+ if ( 'iphone-safari' == $type )
+ return $is_iphone && $is_safari;
+ elseif ( 'iphone-not-safari' == $type )
+ return $is_iphone && !$is_safari;
+ else
+ return $is_iphone;
+ }
+
+
+ /*
+ * Detects if the current UA is Chrome for iOS
+ *
+ * The User-Agent string in Chrome for iOS is the same as the Mobile Safari User-Agent, with CriOS/ instead of Version/.
+ * - Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3
+ */
+ function is_chrome_for_iOS( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ if ( self::is_iphone_or_ipod( 'iphone-safari' ) === false ) return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'crios/' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current UA is Twitter for iPhone
+ *
+ * Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_5 like Mac OS X; nb-no) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8L1 Twitter for iPhone
+ * Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 Twitter for iPhone
+ *
+ */
+ function is_twitter_for_iphone( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'ipad' ) !== false )
+ return false;
+
+ if ( strpos( $ua, 'twitter for iphone' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Detects if the current UA is Twitter for iPad
+ *
+ * Old version 4.X - Mozilla/5.0 (iPad; U; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8L1 Twitter for iPad
+ * Ver 5.0 or Higher - Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 Twitter for iPhone
+ *
+ */
+ function is_twitter_for_ipad( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'twitter for ipad' ) !== false )
+ return true;
+ elseif( strpos( $ua, 'ipad' ) !== false && strpos( $ua, 'twitter for iphone' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current UA is Facebook for iPhone
+ * - Facebook 4020.0 (iPhone; iPhone OS 5.0.1; fr_FR)
+ * - Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_0 like Mac OS X; en_US) AppleWebKit (KHTML, like Gecko) Mobile [FBAN/FBForIPhone;FBAV/4.0.2;FBBV/4020.0;FBDV/iPhone3,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/5.0;FBSS/2; FBCR/O2;FBID/phone;FBLC/en_US;FBSF/2.0]
+ * - Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 [FBAN/FBIOS;FBAV/5.0;FBBV/47423;FBDV/iPhone3,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/5.1.1;FBSS/2; FBCR/3ITA;FBID/phone;FBLC/en_US]
+ */
+ function is_facebook_for_iphone( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if( strpos( $ua, 'iphone' ) === false )
+ return false;
+
+ if ( strpos( $ua, 'facebook' ) !== false && strpos( $ua, 'ipad' ) === false )
+ return true;
+ else if ( strpos( $ua, 'fbforiphone' ) !== false && strpos( $ua, 'tablet' ) === false )
+ return true;
+ else if ( strpos( $ua, 'fban/fbios;' ) !== false && strpos( $ua, 'tablet' ) === false ) //FB app v5.0 or higher
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Detects if the current UA is Facebook for iPad
+ * - Facebook 4020.0 (iPad; iPhone OS 5.0.1; en_US)
+ * - Mozilla/5.0 (iPad; U; CPU iPhone OS 5_0 like Mac OS X; en_US) AppleWebKit (KHTML, like Gecko) Mobile [FBAN/FBForIPhone;FBAV/4.0.2;FBBV/4020.0;FBDV/iPad2,1;FBMD/iPad;FBSN/iPhone OS;FBSV/5.0;FBSS/1; FBCR/;FBID/tablet;FBLC/en_US;FBSF/1.0]
+ * - Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10A403 [FBAN/FBIOS;FBAV/5.0;FBBV/47423;FBDV/iPad2,1;FBMD/iPad;FBSN/iPhone OS;FBSV/6.0;FBSS/1; FBCR/;FBID/tablet;FBLC/en_US]
+ */
+ function is_facebook_for_ipad( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'ipad' ) === false )
+ return false;
+
+ if ( strpos( $ua, 'facebook' ) !== false || strpos( $ua, 'fbforiphone' ) !== false || strpos( $ua, 'fban/fbios;' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Detects if the current UA is WordPress for iOS
+ */
+ function is_wordpress_for_ios( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ if ( strpos( $ua, 'wp-iphone' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Detects if the current device is an iPad.
+ * They type can check for any iPad, an iPad using Safari, or an iPad using something other than Safari.
+ *
+ * Note: If you want to check for Opera mini, Opera mobile or Firefox mobile (or any 3rd party iPad browser),
+ * you should put the check condition before the check for 'iphone-any' or 'iphone-not-safari'.
+ * Otherwise those browsers will be 'catched' by the ipad string.
+ *
+ */
+ function is_ipad( $type = 'ipad-any' ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $is_ipad = ( false !== strpos( $ua, 'ipad' ) );
+ $is_safari = ( false !== strpos( $ua, 'safari' ) );
+
+ if ( 'ipad-safari' == $type )
+ return $is_ipad && $is_safari;
+ elseif ( 'ipad-not-safari' == $type )
+ return $is_ipad && !$is_safari;
+ else
+ return $is_ipad;
+ }
+
+ /*
+ * Detects if the current browser is Firefox Mobile (Fennec)
+ *
+ * http://www.useragentstring.com/pages/Fennec/
+ * Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.1.1) Gecko/20110415 Firefox/4.0.2pre Fennec/4.0.1
+ * Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1b2pre) Gecko/20081015 Fennec/1.0a1
+ */
+ function is_firefox_mobile( ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'fennec' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current browser is Opera Mobile
+ *
+ * What is the difference between Opera Mobile and Opera Mini?
+ * - Opera Mobile is a full Internet browser for mobile devices.
+ * - Opera Mini always uses a transcoder to convert the page for a small display.
+ * (it uses Opera advanced server compression technology to compress web content before it gets to a device.
+ * The rendering engine is on Opera's server.)
+ *
+ * Opera/9.80 (Windows NT 6.1; Opera Mobi/14316; U; en) Presto/2.7.81 Version/11.00"
+ */
+ function is_opera_mobile( ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mobi' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current browser is Opera Mini
+ *
+ * Opera/8.01 (J2ME/MIDP; Opera Mini/3.0.6306/1528; en; U; ssr)
+ * Opera/9.80 (Android;Opera Mini/6.0.24212/24.746 U;en) Presto/2.5.25 Version/10.5454
+ * Opera/9.80 (iPhone; Opera Mini/5.0.019802/18.738; U; en) Presto/2.4.15
+ * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
+ * Opera/9.80 (J2ME/iPhone;Opera Mini/5.0.019802/886; U; ja) Presto/2.4.15
+ * Opera/9.80 (Series 60; Opera Mini/5.1.22783/23.334; U; en) Presto/2.5.25 Version/10.54
+ * Opera/9.80 (BlackBerry; Opera Mini/5.1.22303/22.387; U; en) Presto/2.5.25 Version/10.54
+ *
+ */
+ function is_opera_mini( ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'opera' ) !== false && strpos( $ua, 'mini' ) !== false )
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Detects if the current browser is Opera Mini, but not on a smart device OS(Android, iOS, etc)
+ * Used to send users on dumb devices to m.wor
+ */
+ function is_opera_mini_dumb( ) {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( self::is_opera_mini() ) {
+ if ( strpos( $ua, 'android' ) !== false || strpos( $ua, 'iphone' ) !== false || strpos( $ua, 'ipod' ) !== false
+ || strpos( $ua, 'ipad' ) !== false || strpos( $ua, 'blackberry' ) !== false)
+ return false;
+ else
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * Detects if the current browser is Opera Mobile or Mini.
+ * DEPRECATED: use is_opera_mobile or is_opera_mini
+ *
+ * Opera Mini 5 Beta: Opera/9.80 (J2ME/MIDP; Opera Mini/5.0.15650/756; U; en) Presto/2.2.0
+ * Opera Mini 8: Opera/8.01 (J2ME/MIDP; Opera Mini/3.0.6306/1528; en; U; ssr)
+ */
+ function is_OperaMobile() {
+ _deprecated_function( __FUNCTION__, 'always', 'is_opera_mini() or is_opera_mobile()' );
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'opera' ) !== false ) {
+ if ( ( strpos( $ua, 'mini' ) !== false ) || ( strpos( $ua,'mobi' ) !== false ) )
+ return true;
+ else
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * Detects if the current browser is a Windows Phone 7 device.
+ * ex: Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; LG; GW910)
+ */
+ function is_WindowsPhone7() {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'windows phone os 7' ) === false ) {
+ return false;
+ } else {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ }
+
+ /*
+ * Detects if the current browser is a Windows Phone 8 device.
+ * ex: Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; ARM; Touch; IEMobile/10.0; ; [;])
+ */
+ function is_windows_phone_8() {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ if ( strpos( $ua, 'windows phone 8' ) === false ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+ /*
+ * Detects if the current browser is on a Palm device running the new WebOS. This EXCLUDES TouchPad.
+ *
+ * ex1: Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.1
+ * ex2: Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pixi/1.1
+ *
+ */
+ function is_PalmWebOS() {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'webos' ) === false ) {
+ return false;
+ } else {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ }
+
+ /*
+ * Detects if the current browser is the HP TouchPad default browser. This excludes phones wt WebOS.
+ *
+ * TouchPad Emulator: Mozilla/5.0 (hp-desktop; Linux; hpwOS/2.0; U; it-IT) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 Desktop/1.0
+ * TouchPad: Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0
+ *
+ */
+ function is_TouchPad() {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $http_user_agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ if ( false !== strpos( $http_user_agent, 'hp-tablet' ) || false !== strpos( $http_user_agent, 'hpwos' ) || false !== strpos( $http_user_agent, 'touchpad' ) ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ /*
+ * Detects if the current browser is the Series 60 Open Source Browser.
+ *
+ * OSS Browser 3.2 on E75: Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaE75-1/110.48.125 Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413
+ *
+ * 7.0 Browser (Nokia 5800 XpressMusic (v21.0.025)) : Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Nokia5800d-1/21.0.025; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413
+ *
+ * Browser 7.1 (Nokia N97 (v12.0.024)) : Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/12.0.024; Profile/MIDP-2.1 Configuration/CLDC-1.1; en-us) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.12344
+ *
+ */
+ function is_S60_OSSBrowser() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+
+ $pos_webkit = strpos( $agent, 'webkit' );
+ if ( $pos_webkit !== false ) {
+ //First, test for WebKit, then make sure it's either Symbian or S60.
+ if ( strpos( $agent, 'symbian' ) !== false || strpos( $agent, 'series60' ) !== false ) {
+ return true;
+ } else
+ return false;
+ } elseif ( strpos( $agent, 'symbianos' ) !== false && strpos( $agent,'series60' ) !== false ) {
+ return true;
+ } elseif ( strpos( $agent, 'nokia' ) !== false && strpos( $agent,'series60' ) !== false ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ *
+ * Detects if the device platform is the Symbian Series 60.
+ *
+ */
+ function is_symbian_platform() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_webkit = strpos( $agent, 'webkit' );
+ if ( $pos_webkit !== false ) {
+ //First, test for WebKit, then make sure it's either Symbian or S60.
+ if ( strpos( $agent, 'symbian' ) !== false || strpos( $agent, 'series60' ) !== false ) {
+ return true;
+ } else
+ return false;
+ } elseif ( strpos( $agent, 'symbianos' ) !== false && strpos( $agent,'series60' ) !== false ) {
+ return true;
+ } elseif ( strpos( $agent, 'nokia' ) !== false && strpos( $agent,'series60' ) !== false ) {
+ return true;
+ } elseif ( strpos( $agent, 'opera mini' ) !== false ) {
+ if( strpos( $agent,'symbianos' ) !== false || strpos( $agent,'symbos' ) !== false || strpos( $agent,'series 60' ) !== false )
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ *
+ * Detects if the device platform is the Symbian Series 40.
+ * Nokia Browser for Series 40 is a proxy based browser, previously known as Ovi Browser.
+ * This browser will report 'NokiaBrowser' in the header, however some older version will also report 'OviBrowser'.
+ *
+ */
+ function is_symbian_s40_platform() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $agent, 'series40' ) !== false ) {
+ if( strpos( $agent,'nokia' ) !== false || strpos( $agent,'ovibrowser' ) !== false || strpos( $agent,'nokiabrowser' ) !== false )
+ return true;
+ }
+
+ return false;
+ }
+
+ function is_J2ME_platform() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $agent, 'j2me/midp' ) !== false ) {
+ return true;
+ } elseif ( strpos( $agent, 'midp' ) !== false && strpos( $agent, 'cldc' ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /*
+ * Detects if the current UA is on one of the Maemo-based Nokia Internet Tablets.
+ */
+ function is_MaemoTablet() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_maemo = strpos( $agent, 'maemo' );
+ if ( $pos_maemo === false ) return false;
+
+ //Must be Linux + Tablet, or else it could be something else.
+ if ( strpos( $agent, 'tablet' ) !== false && strpos( $agent, 'linux' ) !== false ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ } else
+ return false;
+ }
+
+ /*
+ * Detects if the current UA is a MeeGo device (Nokia Smartphone).
+ */
+ function is_MeeGo() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( strpos( $ua, 'meego' ) === false ) {
+ return false;
+ } else {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ }
+
+
+ /*
+ is_webkit() can be used to check the User Agent for an webkit generic browser
+ */
+ function is_webkit() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_webkit = strpos( $agent, 'webkit' );
+
+ if ( $pos_webkit !== false )
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Detects if the current browser is the Native Android browser.
+ * @return boolean true if the browser is Android otherwise false
+ */
+ function is_android() {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos_android = strpos( $agent, 'android' );
+ if ( $pos_android !== false ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ /**
+ * Detects if the current browser is the Native Android Tablet browser.
+ * Assumes 'Android' should be in the user agent, but not 'mobile'
+ *
+ * @return boolean true if the browser is Android and not 'mobile' otherwise false
+ */
+ function is_android_tablet( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_android = strpos( $agent, 'android' );
+ $pos_mobile = strpos( $agent, 'mobile' );
+ $post_android_app = strpos( $agent, 'wp-android' );
+
+ if ( $pos_android !== false && $pos_mobile === false && $post_android_app === false ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ } else
+ return false;
+ }
+
+ /**
+ * Detects if the current browser is the Kindle Fire Native browser.
+ *
+ * Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-84) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true
+ * Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-84) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=false
+ *
+ * @return boolean true if the browser is Kindle Fire Native browser otherwise false
+ */
+ function is_kindle_fire( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos_silk = strpos( $agent, 'silk/' );
+ $pos_silk_acc = strpos( $agent, 'silk-accelerated=' );
+ if ( $pos_silk !== false && $pos_silk_acc !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+/**
+ * Detects if the current browser is the Kindle Touch Native browser
+ *
+ * Mozilla/5.0 (X11; U; Linux armv7l like Android; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/533.2+ Kindle/3.0+
+ *
+ * @return boolean true if the browser is Kindle monochrome Native browser otherwise false
+ */
+ function is_kindle_touch( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos_kindle_touch = strpos( $agent, 'kindle/3.0+' );
+ if ( $pos_kindle_touch !== false && self::is_kindle_fire() === false )
+ return true;
+ else
+ return false;
+ }
+
+
+ // Detect if user agent is the WordPress.com Windows 8 app (used ONLY on the custom oauth stylesheet)
+ function is_windows8_auth( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos = strpos( $agent, 'msauthhost' );
+ if ( $pos !== false )
+ return true;
+ else
+ return false;
+ }
+
+ // Detect if user agent is the WordPress.com Windows 8 app.
+ function is_wordpress_for_win8( ) {
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos = strpos( $agent, 'wp-windows8' );
+ if ( $pos !== false )
+ return true;
+ else
+ return false;
+ }
+
+
+ /*
+ * is_blackberry_tablet() can be used to check the User Agent for a RIM blackberry tablet
+ * The user agent of the BlackBerry® Tablet OS follows a format similar to the following:
+ * Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+
+ *
+ */
+ function is_blackberry_tablet() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ $pos_playbook = stripos( $agent, 'PlayBook' );
+ $pos_rim_tablet = stripos( $agent, 'RIM Tablet' );
+
+ if ( ($pos_playbook === false) || ($pos_rim_tablet === false) )
+ {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /*
+ is_blackbeberry() can be used to check the User Agent for a blackberry device
+ Note that opera mini on BB matches this rule.
+ */
+ function is_blackbeberry() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_blackberry = strpos( $agent, 'blackberry' );
+ if ( $pos_blackberry !== false ) {
+ if ( self::is_opera_mini() || self::is_opera_mobile() || self::is_firefox_mobile() )
+ return false;
+ else
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ is_blackberry_10() can be used to check the User Agent for a BlackBerry 10 device.
+ */
+ function is_blackberry_10() {
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+ return ( strpos( $agent, 'bb10' ) !== false ) && ( strpos( $agent, 'mobile' ) !== false );
+ }
+
+ /**
+ * Retrieve the blackberry OS version.
+ *
+ * Return strings are from the following list:
+ * - blackberry-10
+ * - blackberry-7
+ * - blackberry-6
+ * - blackberry-torch //only the first edition. The 2nd edition has the OS7 onboard and doesn't need any special rule.
+ * - blackberry-5
+ * - blackberry-4.7
+ * - blackberry-4.6
+ * - blackberry-4.5
+ *
+ * @return string Version of the BB OS.
+ * If version is not found, get_blackbeberry_OS_version will return boolean false.
+ */
+ function get_blackbeberry_OS_version() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ if ( self::is_blackberry_10() )
+ return 'blackberry-10';
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $pos_blackberry = stripos( $agent, 'blackberry' );
+ if ( $pos_blackberry === false ) {
+ //not a blackberry device
+ return false;
+ }
+
+ //blackberry devices OS 6.0 or higher
+ //Mozilla/5.0 (BlackBerry; U; BlackBerry 9670; en) AppleWebKit/534.3+ (KHTML, like Gecko) Version/6.0.0.286 Mobile Safari/534.3+
+ //Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, Like Gecko) Version/6.0.0.141 Mobile Safari/534.1+
+ //Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0 Mobile Safari/534.11+
+ $pos_webkit = stripos( $agent, 'webkit' );
+ if ( $pos_webkit !== false ) {
+ //detected blackberry webkit browser
+ $pos_torch = stripos( $agent, 'BlackBerry 9800' );
+ if ( $pos_torch !== false ) {
+ return 'blackberry-torch'; //match the torch first edition. the 2nd edition should use the OS7 and doesn't need any special rule
+ } else {
+ //detecting the BB OS version for devices running OS 6.0 or higher
+ if ( preg_match( '#Version\/([\d\.]+)#i', $agent, $matches ) ) {
+ $version = $matches[1];
+ $version_num = explode( '.', $version );
+ if( is_array( $version_num ) === false || count( $version_num ) <= 1 )
+ return 'blackberry-6'; //not a BB device that match our rule.
+ else
+ return 'blackberry-'.$version_num[0];
+ } else {
+ //if doesn't match returns the minimun version with a webkit browser. we should never fall here.
+ return 'blackberry-6'; //not a BB device that match our rule.
+ }
+ }
+ }
+
+ //blackberry devices <= 5.XX
+ //BlackBerry9000/5.0.0.93 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/179
+ if ( preg_match( '#BlackBerry\w+\/([\d\.]+)#i', $agent, $matches ) ) {
+ $version = $matches[1];
+ } else {
+ return false; //not a BB device that match our rule.
+ }
+
+ $version_num = explode( '.', $version );
+
+ if( is_array( $version_num ) === false || count( $version_num ) <= 1 )
+ return false;
+ if ( $version_num[0] == 5 ) {
+ return 'blackberry-5';
+ } elseif ( $version_num[0] == 4 && $version_num[1] == 7 ) {
+ return 'blackberry-4.7';
+ } elseif ( $version_num[0] == 4 && $version_num[1] == 6 ) {
+ return 'blackberry-4.6';
+ } elseif ( $version_num[0] == 4 && $version_num[1] == 5 ) {
+ return 'blackberry-4.5';
+ } else {
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the blackberry browser version.
+ *
+ * Return string are from the following list:
+ * - blackberry-10
+ * - blackberry-webkit
+ * - blackberry-5
+ * - blackberry-4.7
+ * - blackberry-4.6
+ *
+ * @return string Type of the BB browser.
+ * If browser's version is not found, detect_blackbeberry_browser_version will return boolean false.
+ */
+ function detect_blackberry_browser_version() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( self::is_blackberry_10() )
+ return 'blackberry-10';
+
+ $pos_blackberry = strpos( $agent, 'blackberry' );
+ if ( $pos_blackberry === false ) {
+ //not a blackberry device
+ return false;
+ }
+
+ $pos_webkit = strpos( $agent, 'webkit' );
+
+ if ( ! ( $pos_webkit === false ) ) {
+ return 'blackberry-webkit';
+ } else {
+ if ( preg_match( '#BlackBerry\w+\/([\d\.]+)#i', $agent, $matches ) ) {
+ $version = $matches[1];
+ } else {
+ return false; //not a BB device that match our rule.
+ }
+
+ $version_num = explode( '.', $version );
+
+ if( is_array( $version_num ) === false || count( $version_num ) <= 1 )
+ return false;
+
+ if ( $version_num[0] == 5 ) {
+ return 'blackberry-5';
+ } elseif ( $version_num[0] == 4 && $version_num[1] == 7 ) {
+ return 'blackberry-4.7';
+ } elseif ( $version_num[0] == 4 && $version_num[1] == 6 ) {
+ return 'blackberry-4.6';
+ } else {
+ //A very old BB device is found or this is a BB device that doesn't match our rules.
+ return false;
+ }
+ }
+ return false;
+ }
+
+ //Checks if a visitor is coming from one of the WordPress mobile apps
+ function is_mobile_app() {
+
+ if ( empty( $_SERVER['HTTP_USER_AGENT'] ) )
+ return false;
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ if ( isset( $_SERVER['X_USER_AGENT'] ) && preg_match( '|wp-webos|', $_SERVER['X_USER_AGENT'] ) )
+ return true; //wp4webos 1.1 or higher
+
+ $app_agents = array( 'wp-android', 'wp-blackberry', 'wp-iphone', 'wp-nokia', 'wp-webos', 'wp-windowsphone' );
+ // the mobile reader on iOS has an incorrect UA when loading the reader
+ // currently it is the default one provided by the iOS framework which
+ // causes problems with 2-step-auth
+ // User-Agent WordPress/3.1.4 CFNetwork/609 Darwin/13.0.0
+ $app_agents[] = 'wordpress/3.1';
+
+ foreach ( $app_agents as $app_agent ) {
+ if ( false !== strpos( $agent, $app_agent ) )
+ return true;
+ }
+ return false;
+ }
+
+ static function is_bot() {
+ static $is_bot = false;
+ static $first_run = true;
+
+ if ( $first_run ) {
+ $first_run = false;
+
+ /*
+ $bot_ips = array( );
+
+ foreach ( $bot_ips as $bot_ip ) {
+ if ( $_SERVER['REMOTE_ADDR'] == $bot_ip )
+ $is_bot = true;
+ }
+ */
+
+ $agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
+
+ $bot_agents = array(
+ 'alexa', 'altavista', 'ask jeeves', 'attentio', 'baiduspider', 'bingbot', 'chtml generic', 'crawler', 'fastmobilecrawl',
+ 'feedfetcher-google', 'firefly', 'froogle', 'gigabot', 'googlebot', 'googlebot-mobile', 'heritrix', 'ia_archiver', 'irlbot',
+ 'infoseek', 'jumpbot', 'lycos', 'mediapartners', 'mediobot', 'motionbot', 'msnbot', 'mshots', 'openbot',
+ 'pythumbnail', 'scooter', 'slurp', 'snapbot', 'spider', 'surphace scout', 'taptubot', 'technoratisnoop',
+ 'teoma', 'twiceler', 'yahooseeker', 'yahooysmcm', 'yammybot',
+ );
+
+ foreach ( $bot_agents as $bot_agent ) {
+ if ( false !== strpos( $agent, $bot_agent ) )
+ $is_bot = true;
+ }
+ }
+
+ return $is_bot;
+ }
+}
diff --git a/plugins/jetpack/class.jetpack-xmlrpc-server.php b/plugins/jetpack/class.jetpack-xmlrpc-server.php
index cf01db52..3aa5adb1 100644
--- a/plugins/jetpack/class.jetpack-xmlrpc-server.php
+++ b/plugins/jetpack/class.jetpack-xmlrpc-server.php
@@ -10,22 +10,37 @@ class Jetpack_XMLRPC_Server {
var $error = null;
/**
- * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
+ * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
* user is not authenticated (->login()) then the methods are never added,
* so they will get a "does not exist" error.
*/
- function xmlrpc_methods() {
- if ( !$user = $this->login() ) {
- return array();
+ function xmlrpc_methods( $core_methods ) {
+ $jetpack_methods = array(
+ 'jetpack.jsonAPI' => array( $this, 'json_api' ),
+ 'jetpack.verifyAction' => array( $this, 'verify_action' ),
+ );
+
+ $user = $this->login();
+
+ if ( $user ) {
+ $jetpack_methods = array_merge( $jetpack_methods, array(
+ 'jetpack.testConnection' => array( $this, 'test_connection' ),
+ 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
+ 'jetpack.featuresAvailable' => array( $this, 'features_available' ),
+ 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ),
+ 'jetpack.getPost' => array( $this, 'get_post' ),
+ 'jetpack.getPosts' => array( $this, 'get_posts' ),
+ 'jetpack.getComment' => array( $this, 'get_comment' ),
+ 'jetpack.getComments' => array( $this, 'get_comments' ),
+ ) );
+
+ if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
+ $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
+ $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
+ }
}
- return apply_filters( 'jetpack_xmlrpc_methods', array(
- 'jetpack.testConnection' => array( $this, 'test_connection' ),
- 'jetpack.featuresAvailable' => array( $this, 'features_available' ),
- 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ),
- 'jetpack.getPost' => array( $this, 'get_post' ),
- 'jetpack.getComment' => array( $this, 'get_comment' ),
- ) );
+ return apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $user );
}
/**
@@ -34,12 +49,18 @@ class Jetpack_XMLRPC_Server {
function bootstrap_xmlrpc_methods() {
return array(
'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
+ 'jetpack.verifyAction' => array( $this, 'verify_action' ),
);
}
/**
- * Verifies that Jetpack.WordPress.com received a registration request from this site
- *
+ * Verifies that Jetpack.WordPress.com received a registration request from this site
+ */
+ function verify_registration( $verify_secret ) {
+ return $this->verify_action( array( 'register', $verify_secret ) );
+ }
+
+ /**
* @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
*
* Possible error_codes:
@@ -49,31 +70,34 @@ class Jetpack_XMLRPC_Server {
* verify_secrets_missing: No longer have verification secrets stored
* verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
*/
- function verify_registration( $verify_secret ) {
+ function verify_action( $params ) {
+ $action = $params[0];
+ $verify_secret = $params[1];
+
if ( empty( $verify_secret ) ) {
return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ) );
} else if ( !is_string( $verify_secret ) ) {
return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ) );
}
- $secrets = Jetpack::get_option( 'register' );
+ $secrets = Jetpack::get_option( $action );
if ( !$secrets || is_wp_error( $secrets ) ) {
- Jetpack::delete_option( 'register' );
+ Jetpack::delete_option( $action );
return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) );
}
@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets );
if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
- Jetpack::delete_option( 'register' );
+ Jetpack::delete_option( $action );
return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) );
}
if ( $verify_secret !== $secret_1 ) {
- Jetpack::delete_option( 'register' );
+ Jetpack::delete_option( $action );
return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ) );
}
- Jetpack::delete_option( 'register' );
+ Jetpack::delete_option( $action );
return $secret_2;
}
@@ -132,7 +156,50 @@ class Jetpack_XMLRPC_Server {
* @return bool|IXR_Error
*/
function test_connection() {
- return true;
+ return JETPACK__VERSION;
+ }
+
+ function test_api_user_code( $args ) {
+ $client_id = (int) $args[0];
+ $user_id = (int) $args[1];
+ $nonce = (string) $args[2];
+ $verify = (string) $args[3];
+
+ if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
+ return false;
+ }
+
+ $user = get_user_by( 'id', $user_id );
+ if ( !$user || is_wp_error( $user ) ) {
+ return false;
+ }
+
+ /* debugging
+ error_log( "CLIENT: $client_id" );
+ error_log( "USER: $user_id" );
+ error_log( "NONCE: $nonce" );
+ error_log( "VERIFY: $verify" );
+ */
+
+ $jetpack_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
+
+ $api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
+ if ( !$api_user_code ) {
+ return false;
+ }
+
+ $hmac = hash_hmac( 'md5', json_encode( (object) array(
+ 'client_id' => (int) $client_id,
+ 'user_id' => (int) $user_id,
+ 'nonce' => (string) $nonce,
+ 'code' => (string) $api_user_code,
+ ) ), $jetpack_token->secret );
+
+ if ( $hmac !== $verify ) {
+ return false;
+ }
+
+ return $user_id;
}
/**
@@ -164,35 +231,137 @@ class Jetpack_XMLRPC_Server {
return $modules;
}
-
+
function get_post( $id ) {
if ( !$id = (int) $id ) {
return false;
}
$jetpack = Jetpack::init();
- $post = $jetpack->get_post( $id );
- if ( $jetpack->is_post_public( $post ) )
- return $post;
+ $post = $jetpack->sync->get_post( $id );
+ return $post;
+ }
- return false;
+ function get_posts( $args ) {
+ list( $post_ids ) = $args;
+ $post_ids = array_map( 'intval', (array) $post_ids );
+ $jp = Jetpack::init();
+ $sync_data = $jp->sync->get_content( array( 'posts' => $post_ids ) );
+
+ return $sync_data;
}
-
+
function get_comment( $id ) {
if ( !$id = (int) $id ) {
return false;
}
$jetpack = Jetpack::init();
- $comment = $jetpack->get_comment( $id );
+ $comment = $jetpack->sync->get_comment( $id );
if ( !is_array( $comment ) )
return false;
- if ( !$this->get_post( $comment['comment_post_ID'] ) )
+ $post = $jetpack->sync->get_post( $comment['comment_post_ID'] );
+ if ( !$post ) {
return false;
+ }
return $comment;
}
+
+ function get_comments( $args ) {
+ list( $comment_ids ) = $args;
+ $comment_ids = array_map( 'intval', (array) $comment_ids );
+ $jp = Jetpack::init();
+ $sync_data = $jp->sync->get_content( array( 'comments' => $comment_ids ) );
+
+ return $sync_data;
+ }
+
+ function update_attachment_parent( $args ) {
+ $attachment_id = (int) $args[0];
+ $parent_id = (int) $args[1];
+
+ return wp_update_post( array(
+ 'ID' => $attachment_id,
+ 'post_parent' => $parent_id,
+ ) );
+ }
+
+ function json_api( $args = array() ) {
+ $json_api_args = $args[0];
+ $verify_api_user_args = $args[1];
+
+ $method = (string) $json_api_args[0];
+ $url = (string) $json_api_args[1];
+ $post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
+ $my_id = (int) $json_api_args[3];
+ $user_details = (array) $json_api_args[4];
+
+ if ( !$verify_api_user_args ) {
+ $user_id = 0;
+ } elseif ( 'internal' === $verify_api_user_args[0] ) {
+ $user_id = (int) $verify_api_user_args[1];
+ if ( $user_id ) {
+ $user = get_user_by( 'id', $user_id );
+ if ( !$user || is_wp_error( $user ) ) {
+ return false;
+ }
+ }
+ } else {
+ $user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
+ if ( !$user_id ) {
+ return false;
+ }
+ }
+
+ /* debugging
+ error_log( "-- begin json api via jetpack debugging -- " );
+ error_log( "METHOD: $method" );
+ error_log( "URL: $url" );
+ error_log( "POST BODY: $post_body" );
+ error_log( "MY JETPACK ID: $my_id" );
+ error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
+ error_log( "VERIFIED USER_ID: " . (int) $user_id );
+ error_log( "-- end json api via jetpack debugging -- " );
+ */
+
+ $old_user = wp_get_current_user();
+ wp_set_current_user( $user_id );
+
+ $token = Jetpack_Data::get_access_token( get_current_user_id() );
+ if ( !$token || is_wp_error( $token ) ) {
+ return false;
+ }
+
+ define( 'REST_API_REQUEST', true );
+ define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
+
+ // needed?
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
+
+ require_once dirname( __FILE__ ) . '/class.json-api.php';
+ $api = WPCOM_JSON_API::init( $method, $url, $post_body );
+ $api->token_details['user'] = $user_details;
+ require_once dirname( __FILE__ ) . '/class.json-api-endpoints.php';
+
+ $display_errors = ini_set( 'display_errors', 0 );
+ ob_start();
+ $content_type = $api->serve( false );
+ $output = ob_get_clean();
+ ini_set( 'display_errors', $display_errors );
+
+ $nonce = wp_generate_password( 10, false );
+ $hmac = hash_hmac( 'md5', $nonce . $output, $token->secret );
+
+ wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
+
+ return array(
+ (string) $output,
+ (string) $nonce,
+ (string) $hmac,
+ );
+ }
}
diff --git a/plugins/jetpack/class.json-api-endpoints.php b/plugins/jetpack/class.json-api-endpoints.php
new file mode 100644
index 00000000..6189d404
--- /dev/null
+++ b/plugins/jetpack/class.json-api-endpoints.php
@@ -0,0 +1,3912 @@
+ array(
+ // Default value => description
+ 'display' => 'Formats the output as HTML for display. Shortcodes are parsed, paragraph tags are added, etc..',
+ // Other possible values => description
+ 'edit' => 'Formats the output for editing. Shortcodes are left unparsed, significant whitespace is kept, etc..',
+ ),
+ 'http_envelope' => array(
+ 'false' => '',
+ 'true' => 'Some enviroments (like in-browser Javascript or Flash) block or divert responses with a non-200 HTTP status code. Setting this parameter will force the HTTP status code to always be 200. The JSON response is wrapped in an "envelope" containing the "real" HTTP status code and headers.',
+ ),
+ 'pretty' => array(
+ 'false' => '',
+ 'true' => 'Output pretty JSON',
+ ),
+ // Parameter name => description (default value is empty)
+ 'callback' => '(string) An optional JSONP callback function.',
+ );
+
+ // Response format
+ var $response_format = array();
+
+ // Request format
+ var $request_format = array();
+
+ // Is this endpoint still in testing phase? If so, not available to the public.
+ var $in_testing = false;
+
+ /**
+ * @var string Version of the API
+ */
+ var $version = '';
+
+ /**
+ * @var string Example request to make
+ */
+ var $example_request = '';
+
+ /**
+ * @var string Example request data (for POST methods)
+ */
+ var $example_request_data = '';
+
+ /**
+ * @var string Example response from $example_request
+ */
+ var $example_response = '';
+
+ function __construct( $args ) {
+ $defaults = array(
+ 'in_testing' => false,
+ 'description' => '',
+ 'group' => '',
+ 'method' => 'GET',
+ 'path' => '/',
+ 'force' => '',
+ 'jp_disabled' => false,
+ 'path_labels' => array(),
+ 'request_format' => array(),
+ 'response_format' => array(),
+ 'query_parameters' => array(),
+ 'version' => 'v1',
+ 'example_request' => '',
+ 'example_request_data' => '',
+ 'example_response' => '',
+
+ 'pass_wpcom_user_details' => false,
+ 'can_use_user_details_instead_of_blog_membership' => false,
+ );
+
+ $args = wp_parse_args( $args, $defaults );
+
+ $this->in_testing = $args['in_testing'];
+
+ $this->description = $args['description'];
+ $this->group = $args['group'];
+ $this->stat = $args['stat'];
+ $this->force = $args['force'];
+ $this->jp_disabled = $args['jp_disabled'];
+
+ $this->method = $args['method'];
+ $this->path = $args['path'];
+ $this->path_labels = $args['path_labels'];
+
+ $this->pass_wpcom_user_details = $args['pass_wpcom_user_details'];
+ $this->can_use_user_details_instead_of_blog_membership = $args['can_use_user_details_instead_of_blog_membership'];
+
+ $this->version = $args['version'];
+
+ if ( $this->request_format ) {
+ $this->request_format = array_filter( array_merge( $this->request_format, $args['request_format'] ) );
+ } else {
+ $this->request_format = $args['request_format'];
+ }
+
+ if ( $this->response_format ) {
+ $this->response_format = array_filter( array_merge( $this->response_format, $args['response_format'] ) );
+ } else {
+ $this->response_format = $args['response_format'];
+ }
+
+ if ( false === $args['query_parameters'] ) {
+ $this->query = array();
+ } elseif ( is_array( $args['query_parameters'] ) ) {
+ $this->query = array_filter( array_merge( $this->query, $args['query_parameters'] ) );
+ }
+
+ $this->api = WPCOM_JSON_API::init(); // Auto-add to WPCOM_JSON_API
+
+ /** Example Request/Response ******************************************/
+
+ // Examples for endpoint documentation request
+ $this->example_request = $args['example_request'];
+ $this->example_request_data = $args['example_request_data'];
+ $this->example_response = $args['example_response'];
+
+ $this->api->add( $this );
+ }
+
+ // Get all query args. Prefill with defaults
+ function query_args( $return_default_values = true, $cast_and_filter = true ) {
+ $args = array_intersect_key( $this->api->query, $this->query );
+
+ if ( !$cast_and_filter ) {
+ return $args;
+ }
+
+ return $this->cast_and_filter( $args, $this->query, $return_default_values );
+ }
+
+ // Get POST body data
+ function input( $return_default_values = true, $cast_and_filter = true ) {
+ $input = trim( $this->api->post_body );
+
+ switch ( $this->api->content_type ) {
+ case 'application/json' :
+ case 'application/x-javascript' :
+ case 'text/javascript' :
+ case 'text/x-javascript' :
+ case 'text/x-json' :
+ case 'text/json' :
+ $return = json_decode( $input );
+ if ( function_exists( 'json_last_error' ) ) {
+ if ( JSON_ERROR_NONE !== json_last_error() ) {
+ return null;
+ }
+ } else {
+ if ( is_null( $return ) && json_encode( null ) !== $input ) {
+ return null;
+ }
+ }
+
+ if ( is_object( $return ) ) {
+ $return = (array) $return;
+ }
+ break;
+ case 'multipart/form-data' :
+ $return = array_merge( stripslashes_deep( $_POST ), $_FILES );
+ break;
+ default :
+ wp_parse_str( $input, $return );
+ break;
+ }
+
+ if ( !$cast_and_filter ) {
+ return $return;
+ }
+
+ return $this->cast_and_filter( $return, $this->request_format, $return_default_values );
+ }
+
+ function cast_and_filter( $data, $documentation, $return_default_values = false, $for_output = false ) {
+ $return_as_object = false;
+ if ( is_object( $data ) ) {
+ $data = (array) $data;
+ $return_as_object = true;
+ } elseif ( !is_array( $data ) ) {
+ return $data;
+ }
+
+ $boolean_arg = array( 'false', 'true' );
+ $naeloob_arg = array( 'true', 'false' );
+
+ $return = array();
+
+ foreach ( $documentation as $key => $description ) {
+ if ( is_array( $description ) ) {
+ // String or boolean array keys only
+ $whitelist = array_keys( $description );
+ if ( isset( $data[$key] ) && isset( $description[$data[$key]] ) ) {
+ $return[$key] = (string) $data[$key];
+ } elseif ( $return_default_values ) {
+ $return[$key] = (string) current( $whitelist );
+ } else {
+ continue;
+ }
+
+ // Truthiness
+ if ( $whitelist === $boolean_arg || $whitelist === $naeloob_arg ) {
+ $return[$key] = (bool) WPCOM_JSON_API::is_truthy( $return[$key] );
+ }
+
+ continue;
+ }
+
+ $types = $this->parse_types( $description );
+ $type = array_shift( $types );
+
+ // Explicit default - string and int only for now. Always set these reguardless of $return_default_values
+ if ( isset( $type['default'] ) ) {
+ if ( !isset( $data[$key] ) ) {
+ $data[$key] = $type['default'];
+ }
+ }
+
+ if ( !isset( $data[$key] ) ) {
+ continue;
+ }
+
+ $this->cast_and_filter_item( $return, $type, $key, $data[$key], $types, $for_output );
+ }
+
+ if ( $return_as_object ) {
+ return (object) $return;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Casts $value according to $type.
+ * Handles fallbacks for certain values of $type when $value is not that $type
+ * Currently, only handles fallback between string <-> array (two way), from string -> false (one way), and from object -> false (one way)
+ *
+ * Handles "child types" - array:URL, object:category
+ * array:URL means an array of URLs
+ * object:category means a hash of categories
+ *
+ * Handles object typing - object>post means an object of type post
+ */
+ function cast_and_filter_item( &$return, $type, $key, $value, $types = array(), $for_output = false ) {
+ if ( is_string( $type ) ) {
+ $type = compact( 'type' );
+ }
+
+ switch ( $type['type'] ) {
+ case 'false' :
+ $return[$key] = false;
+ break;
+ case 'url' :
+ $return[$key] = (string) esc_url_raw( $value );
+ break;
+ case 'string' :
+ // Fallback string -> array
+ if ( is_array( $value ) ) {
+ if ( !empty( $types[0] ) ) {
+ $next_type = array_shift( $types );
+ return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
+ }
+ }
+
+ // Fallback string -> false
+ if ( !is_string( $value ) ) {
+ if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
+ $next_type = array_shift( $types );
+ return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
+ }
+ }
+ $return[$key] = (string) $value;
+ break;
+ case 'html' :
+ $return[$key] = (string) $value;
+ break;
+ case 'media' :
+ if ( is_array( $value ) ) {
+ if ( isset( $value['name'] ) ) {
+ // It's a $_FILES array
+ // Reformat into array of $_FILES items
+
+ $files = array();
+ foreach ( $value['name'] as $k => $v ) {
+ $files[$k] = array();
+ foreach ( array_keys( $value ) as $file_key ) {
+ $files[$k][$file_key] = $value[$file_key][$k];
+ }
+ }
+
+ $return[$key] = $files;
+ }
+ break;
+ } else {
+ // no break - treat as 'array'
+ }
+ // nobreak
+ case 'array' :
+ // Fallback array -> string
+ if ( is_string( $value ) ) {
+ if ( !empty( $types[0] ) ) {
+ $next_type = array_shift( $types );
+ return $this->cast_and_filter_item( $return, $next_type, $key, $value, $types, $for_output );
+ }
+ }
+
+ if ( isset( $type['children'] ) ) {
+ $children = array();
+ foreach ( (array) $value as $k => $child ) {
+ $this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
+ }
+ $return[$key] = (array) $children;
+ break;
+ }
+
+ $return[$key] = (array) $value;
+ break;
+ case 'iso 8601 datetime' :
+ case 'datetime' :
+ // (string)s
+ $dates = $this->parse_date( (string) $value );
+ if ( $for_output ) {
+ $return[$key] = $this->format_date( $dates[1], $dates[0] );
+ } else {
+ list( $return[$key], $return["{$key}_gmt"] ) = $dates;
+ }
+ break;
+ case 'float' :
+ $return[$key] = (float) $value;
+ break;
+ case 'int' :
+ case 'integer' :
+ $return[$key] = (int) $value;
+ break;
+ case 'bool' :
+ case 'boolean' :
+ $return[$key] = (bool) WPCOM_JSON_API::is_truthy( $value );
+ break;
+ case 'object' :
+ // Fallback object -> false
+ if ( is_scalar( $value ) || is_null( $value ) ) {
+ if ( !empty( $types[0] ) && 'false' === $types[0]['type'] ) {
+ return $this->cast_and_filter_item( $return, 'false', $key, $value, $types, $for_output );
+ }
+ }
+
+ if ( isset( $type['children'] ) ) {
+ $children = array();
+ foreach ( (array) $value as $k => $child ) {
+ $this->cast_and_filter_item( $children, $type['children'], $k, $child, array(), $for_output );
+ }
+ $return[$key] = (object) $children;
+ break;
+ }
+
+ if ( isset( $type['subtype'] ) ) {
+ return $this->cast_and_filter_item( $return, $type['subtype'], $key, $value, $types, $for_output );
+ }
+
+ $return[$key] = (object) $value;
+ break;
+ case 'post' :
+ $return[$key] = (object) $this->cast_and_filter( $value, $this->post_object_format, false, $for_output );
+ break;
+ case 'comment' :
+ $return[$key] = (object) $this->cast_and_filter( $value, $this->comment_object_format, false, $for_output );
+ break;
+ case 'tag' :
+ case 'category' :
+ $docs = array(
+ 'name' => '(string)',
+ 'slug' => '(string)',
+ 'description' => '(HTML)',
+ 'post_count' => '(int)',
+ 'meta' => '(object)',
+ );
+ if ( 'category' === $type ) {
+ $docs['parent'] = '(int)';
+ }
+ $return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
+ break;
+ case 'post_reference' :
+ case 'comment_reference' :
+ $docs = array(
+ 'ID' => '(int)',
+ 'type' => '(string)',
+ 'link' => '(URL)',
+ );
+ $return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
+ break;
+ case 'geo' :
+ $docs = array(
+ 'latitude' => '(float)',
+ 'longitude' => '(float)',
+ 'address' => '(string)',
+ );
+ $return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
+ break;
+ case 'author' :
+ $docs = array(
+ 'ID' => '(int)',
+ 'email' => '(string|false)',
+ 'name' => '(string)',
+ 'URL' => '(URL)',
+ 'avatar_URL' => '(URL)',
+ 'profile_URL' => '(URL)',
+ );
+ $return[$key] = (object) $this->cast_and_filter( $value, $docs, false, $for_output );
+ break;
+ case 'attachment' :
+ $docs = array(
+ 'ID' => '(int)',
+ 'URL' => '(URL)',
+ 'guid' => '(string)',
+ 'mime_type' => '(string)',
+ 'width' => '(int)',
+ 'height' => '(int)',
+ 'duration' => '(int)',
+ );
+ $return[$key] = (object) $this->cast_and_filter( $value, apply_filters( 'wpcom_json_api_attachment_cast_and_filter', $docs ), false, $for_output );
+ break;
+ default :
+ trigger_error( "Unknown API casting type {$type['type']}", E_USER_WARNING );
+ }
+ }
+
+ function parse_types( $text ) {
+ if ( !preg_match( '#^\(([^)]+)\)#', ltrim( $text ), $matches ) ) {
+ return 'none';
+ }
+
+ $types = explode( '|', strtolower( $matches[1] ) );
+ $return = array();
+ foreach ( $types as $type ) {
+ foreach ( array( ':' => 'children', '>' => 'subtype', '=' => 'default' ) as $operator => $meaning ) {
+ if ( false !== strpos( $type, $operator ) ) {
+ $item = explode( $operator, $type, 2 );
+ $return[] = array( 'type' => $item[0], $meaning => $item[1] );
+ continue 2;
+ }
+ }
+ $return[] = compact( 'type' );
+ }
+
+ return $return;
+ }
+
+ /**
+ * Auto generates documentation based on description, method, path, path_labels, and query parameters.
+ * Echoes HTML.
+ */
+ function document( $show_description = true ) {
+ $original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : 'unset';
+ unset( $GLOBALS['post'] );
+
+ $doc = $this->generate_documentation();
+
+ if ( $show_description ) :
+?>
+
+
+
+
+
+
+
+
+ Resource URL
+
+
+
+ Type
+ URL and Format
+
+
+
+
+
+ https://public-api.wordpress.com/rest/v1
+
+
+
+
+
+ 'Method Parameters',
+ 'query' => 'Query Parameters',
+ 'body' => 'Request Parameters',
+ 'response' => 'Response Parameters',
+ ) as $doc_section_key => $label ) :
+ $doc_section = 'response' == $doc_section_key ? $doc['response']['body'] : $doc['request'][$doc_section_key];
+ if ( !$doc_section ) {
+ continue;
+ }
+
+ $param_label = strtolower( str_replace( ' ', '-', $label ) );
+?>
+
+
+
+
+
+
+
+
+
+ Parameter
+ Type
+ Description
+
+
+
+
+ $item ) : ?>
+
+
+
+
+ generate_doc_description( $item['description'] );
+
+ ?>
+
+
+
+
+
+
+
+
+example_response ) ) {
+
+ // Examples for endpoint documentation response
+ $response_key = 'dev_response_' . $this->version . '_' . $this->method . '_' . sanitize_title( $this->path );
+ $response = get_option( $response_key );
+
+ // Response doesn't exist, so run the request
+ if ( empty( $response ) ) {
+
+ // Only trust GET request
+ if ( 'GET' == $this->method ) {
+ $response = wp_remote_get( $this->example_request );
+ }
+
+ // Set as false if it's an error
+ if ( is_wp_error( $response ) ) {
+ $response = false;
+ }
+
+ // Only update the option if there's a result
+ if ( !empty( $response ) ) {
+ $response = $response['body'];
+ update_option( $response_key, $response );
+ }
+ }
+
+ // Example response was passed into the constructor via params
+ } else {
+ $response = $this->example_response;
+ }
+
+ // Wrap the response in a sourcecode shortcode
+ if ( !empty( $response ) ) {
+ $response = '[sourcecode language="php" wraplines="false" light="true" autolink="false" htmlscript="false"]' . $response . '[/sourcecode]';
+ $response = apply_filters( 'the_content', $response );
+ $this->example_response = $response;
+ }
+
+ $curl = 'curl';
+
+ $php_opts = array( 'ignore_errors' => true );
+
+ if ( 'GET' !== $this->method ) {
+ $php_opts['method'] = $this->method;
+ }
+
+ if ( $this->example_request_data ) {
+ if ( isset( $this->example_request_data['headers'] ) && is_array( $this->example_request_data['headers'] ) ) {
+ $php_opts['header'] = array();
+ foreach ( $this->example_request_data['headers'] as $header => $value ) {
+ $curl .= " \\\n -H " . escapeshellarg( "$header: $value" );
+ $php_opts['header'][] = "$header: $value";
+ }
+ }
+
+ if ( isset( $this->example_request_data['body'] ) && is_array( $this->example_request_data['body'] ) ) {
+ $php_opts['content'] = $this->example_request_data['body'];
+ $php_opts['header'][] = 'Content-Type: application/x-www-form-urlencoded';
+ foreach ( $this->example_request_data['body'] as $key => $value ) {
+ $curl .= " \\\n --data-urlencode " . escapeshellarg( "$key=$value" );
+ }
+ }
+ }
+
+ if ( $php_opts ) {
+ $php_opts_exported = var_export( array( 'http' => $php_opts ), true );
+ if ( !empty( $php_opts['content'] ) ) {
+ $content_exported = preg_quote( var_export( $php_opts['content'], true ), '/' );
+ $content_exported = '\\s*' . str_replace( "\n", "\n\\s*", $content_exported ) . '\\s*';
+ $php_opts_exported = preg_replace_callback( "/$content_exported/", array( $this, 'add_http_build_query_to_php_content_example' ), $php_opts_exported );
+ }
+ $php = <<example_request',
+ false,
+ \$context
+);
+\$response = json_decode( \$response );
+
+?>
+EOPHP;
+ } else {
+ $php = <<example_request' );
+\$response = json_decode( \$response );
+
+?>
+EOPHP;
+ }
+
+ if ( false !== strpos( $curl, "\n" ) ) {
+ $curl .= " \\\n";
+ }
+
+ $curl .= ' ' . escapeshellarg( $this->example_request );
+
+ $curl = '[sourcecode language="bash" wraplines="false" light="true" autolink="false" htmlscript="false"]' . $curl . '[/sourcecode]';
+ $curl = apply_filters( 'the_content', $curl );
+
+ $php = '[sourcecode language="php" wraplines="false" light="true" autolink="false" htmlscript="false"]' . $php . '[/sourcecode]';
+ $php = apply_filters( 'the_content', $php );
+?>
+
+example_request ) || ! empty( $this->example_request_data ) || ! empty( $this->example_response ) ) : ?>
+
+
+ Example
+
+
+
+
+
+ example_response ) ) : ?>
+
+
+ Response Body
+ example_response; ?>
+
+
+
+
+
+
+
+
+'s to document item descriptions.
+ * Echoes HTML.
+ */
+ function generate_doc_description( $item ) {
+ if ( is_array( $item ) ) : ?>
+
+
+ $description_value ) : ?>
+
+
+ generate_doc_description( $description_value ); ?>
+
+
+
+
+
+path );
+ $path_labeled = vsprintf( $format, array_keys( $this->path_labels ) );
+ $boolean_arg = array( 'false', 'true' );
+ $naeloob_arg = array( 'true', 'false' );
+
+ $doc = array(
+ 'description' => $this->description,
+ 'method' => $this->method,
+ 'path_format' => $this->path,
+ 'path_labeled' => $path_labeled,
+ 'group' => $this->group,
+ 'request' => array(
+ 'path' => array(),
+ 'query' => array(),
+ 'body' => array(),
+ ),
+ 'response' => array(
+ 'body' => array(),
+ )
+ );
+
+ foreach ( array( 'path_labels' => 'path', 'query' => 'query', 'request_format' => 'body', 'response_format' => 'body' ) as $_property => $doc_item ) {
+ foreach ( $this->$_property as $key => $description ) {
+ if ( is_array( $description ) ) {
+ $description_keys = array_keys( $description );
+ if ( $boolean_arg === $description_keys || $naeloob_arg === $description_keys ) {
+ $type = '(bool)';
+ } else {
+ $type = '(string)';
+ }
+
+ if ( 'response_format' != $_property ) {
+ // hack - don't show "(default)" in response format
+ reset( $description );
+ $description_key = key( $description );
+ $description[$description_key] = "(default) {$description[$description_key]}";
+ }
+ } else {
+ $types = $this->parse_types( $description );
+ $type = array();
+ $default = '';
+
+ foreach ( $types as $type_array ) {
+ $type[] = $type_array['type'];
+ if ( isset( $type_array['default'] ) ) {
+ $default = $type_array['default'];
+ if ( 'string' === $type_array['type'] ) {
+ $default = "'$default'";
+ }
+ }
+ }
+ $type = '(' . join( '|', $type ) . ')';
+ $noop = ''; // skip an index in list below
+ list( $noop, $description ) = explode( ')', $description, 2 );
+ $description = trim( $description );
+ if ( $default ) {
+ $description .= " Default: $default.";
+ }
+ }
+
+ $item = compact( 'type', 'description' );
+
+ if ( 'response_format' == $_property ) {
+ $doc['response'][$doc_item][$key] = $item;
+ } else {
+ $doc['request'][$doc_item][$key] = $item;
+ }
+ }
+ }
+
+ return $doc;
+ }
+
+ function user_can_view_post( $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return false;
+ }
+
+ if ( 'inherit' == $post->post_status ) {
+ $parent_post = get_post( $post->post_parent );
+ $post_status_obj = get_post_status_object( $parent_post->post_status );
+ } else {
+ $post_status_obj = get_post_status_object( $post->post_status );
+ }
+
+ if ( !$post_status_obj->public ) {
+ if ( is_user_logged_in() ) {
+ if ( $post_status_obj->protected ) {
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+ } elseif ( $post_status_obj->private ) {
+ if ( !current_user_can( 'read_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+ } elseif ( 'trash' === $post->post_status ) {
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+ }
+
+ if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view post', 403 );
+ }
+
+ if ( strlen( $post->post_password ) && !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view password protected post', 403 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns author object.
+ *
+ * @param $author user ID, user row, WP_User object, comment row, post row
+ * @param $show_email output the author's email address?
+ *
+ * @return (object)
+ */
+ function get_author( $author, $show_email = false ) {
+ if ( isset( $author->comment_author_email ) && !$author->user_id ) {
+ $ID = 0;
+ $email = $author->comment_author_email;
+ $name = $author->comment_author;
+ $URL = $author->comment_author_url;
+ $profile_URL = 'http://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
+ } else {
+ if ( isset( $author->post_author ) ) {
+ $author = $author->post_author;
+ } elseif ( isset( $author->user_id ) && $author->user_id ) {
+ $author = $author->user_id;
+ } elseif ( isset( $author->user_email ) ) {
+ $author = $author->ID;
+ }
+
+ $user = get_user_by( 'id', $author );
+ if ( !$user || is_wp_error( $user ) ) {
+ trigger_error( 'Unknown user', E_USER_WARNING );
+ return null;
+ }
+
+ $ID = $user->ID;
+ $email = $user->user_email;
+ $name = $user->display_name;
+ $URL = $user->user_url;
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $profile_URL = "http://en.gravatar.com/{$user->user_login}";
+ } else {
+ $profile_URL = 'http://en.gravatar.com/' . md5( strtolower( trim( $email ) ) );
+ }
+ }
+
+ $avatar_URL = $this->api->get_avatar_url( $email );
+
+ $email = $show_email ? (string) $email : false;
+
+ return (object) array(
+ 'ID' => (int) $ID,
+ 'email' => $email, // (string|bool)
+ 'name' => (string) $name,
+ 'URL' => (string) esc_url_raw( $URL ),
+ 'avatar_URL' => (string) esc_url_raw( $avatar_URL ),
+ 'profile_URL' => (string) esc_url_raw( $profile_URL ),
+ );
+ }
+
+ function get_taxonomy( $taxonomy_id, $taxonomy_type, $context ) {
+
+ $taxonomy = get_term_by( 'slug', $taxonomy_id, $taxonomy_type );
+ /// keep updating this function
+ if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
+ }
+
+ // Permissions
+ switch ( $context ) {
+ case 'edit' :
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->edit_terms ) )
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+ break;
+ case 'display' :
+ if ( -1 == get_option( 'blog_public' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view taxonomy', 403 );
+ }
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $response = array();
+ $response['name'] = (string) $taxonomy->name;
+ $response['slug'] = (string) $taxonomy_id;
+ $response['description'] = (string) $taxonomy->description;
+ $response['post_count'] = (int) $taxonomy->count;
+
+ if ( 'category' == $taxonomy_type )
+ $response['parent'] = (int) $taxonomy->parent;
+
+ $response['meta'] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy_id, $taxonomy_type ),
+ 'help' => (string) $this->get_taxonomy_link( $this->api->get_blog_id_for_output(), $taxonomy_id, $taxonomy_type, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+ ),
+ );
+
+ return (object) $response;
+ }
+
+ /**
+ * Returns ISO 8601 formatted datetime: 2011-12-08T01:15:36-08:00
+ *
+ * @param $date_gmt (string) GMT datetime string.
+ * @param $date (string) Optional. Used to calculate the offset from GMT.
+ *
+ * @return string
+ */
+ function format_date( $date_gmt, $date = null ) {
+ $timestamp_gmt = strtotime( "$date_gmt+0000" );
+ if ( null === $date ) {
+ $timestamp = $timestamp_gmt;
+ $hours = $minutes = $west = 0;
+ } else {
+ $timestamp = strtotime( "$date+0000" );
+ $offset = $timestamp - $timestamp_gmt;
+ $west = $offset < 0;
+ $offset = abs( $offset );
+ $hours = (int) floor( $offset / 3600 );
+ $offset -= $hours * 3600;
+ $minutes = (int) floor( $offset / 60 );
+ }
+
+ return (string) gmdate( 'Y-m-d\\TH:i:s', $timestamp ) . sprintf( '%s%02d:%02d', $west ? '-' : '+', $hours, $minutes );
+ }
+
+ /**
+ * @param datetime string
+ *
+ * @return array( $local_time_string, $gmt_time_string )
+ */
+ function parse_date( $date_string ) {
+ $time = strtotime( $date_string );
+ if ( !$time ) {
+ $time = time();
+ }
+
+ $datetime = new DateTime( "@$time" );
+ $gmt = $datetime->format( 'Y-m-d H:i:s' );
+ $timezone_string = get_option( 'timezone_string' );
+ if ( $timezone_string ) {
+ $tz = timezone_open( $timezone_string );
+ if ( $tz ) {
+ $datetime->setTimezone( $tz );
+ $local = $datetime->format( 'Y-m-d H:i:s' );
+ return array( (string) $local, (string) $gmt );
+ }
+ }
+
+ $gmt_offset = get_option( 'gmt_offset' );
+ $local_time = $time + $gmt_offset * 3600;
+
+ $date = getdate( ( int ) $local_time );
+ $datetime->setDate( $date['year'], $date['mon'], $date['mday'] );
+ $datetime->setTime( $date['hours'], $date['minutes'], $date['seconds'] );
+
+ $local = $datetime->format( 'Y-m-d H:i:s' );
+ return array( (string) $local, (string) $gmt );
+ }
+
+ function get_link() {
+ $args = func_get_args();
+ $format = array_shift( $args );
+ array_unshift( $args, $this->api->public_api_scheme, WPCOM_JSON_API__BASE );
+ $path = array_pop( $args );
+ if ( $path ) {
+ $path = '/' . ltrim( $path, '/' );
+ }
+ $args[] = $path;
+
+ // http, WPCOM_JSON_API__BASE, ... , path
+ // %s , %s , $format, %s
+ return esc_url_raw( vsprintf( "%s://%s$format%s", $args ) );
+ }
+
+ function get_me_link( $path = '' ) {
+ return $this->get_link( '/me', $path );
+ }
+
+ function get_taxonomy_link( $blog_id, $taxonomy_id, $taxonomy_type, $path = '' ) {
+ if ( 'category' == $taxonomy_type )
+ return $this->get_link( '/sites/%d/categories/slug:%s', $blog_id, $taxonomy_id, $path );
+ else
+ return $this->get_link( '/sites/%d/tags/slug:%s', $blog_id, $taxonomy_id, $path );
+ }
+
+ function get_site_link( $blog_id, $path = '' ) {
+ return $this->get_link( '/sites/%d', $blog_id, $path );
+ }
+
+ function get_post_link( $blog_id, $post_id, $path = '' ) {
+ return $this->get_link( '/sites/%d/posts/%d', $blog_id, $post_id, $path );
+ }
+
+ function get_comment_link( $blog_id, $comment_id, $path = '' ) {
+ return $this->get_link( '/sites/%d/comments/%d', $blog_id, $comment_id, $path );
+ }
+
+ /**
+ * Return endpoint response
+ *
+ * @param ... determined by ->$path
+ *
+ * @return
+ * falsy: HTTP 500, no response body
+ * WP_Error( $error_code, $error_message, $http_status_code ): HTTP $status_code, json_encode( array( 'error' => $error_code, 'message' => $error_message ) ) response body
+ * $data: HTTP 200, json_encode( $data ) response body
+ */
+ abstract function callback( $path = '' );
+}
+
+abstract class WPCOM_JSON_API_Post_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $post_object_format = array(
+ // explicitly document and cast all output
+ 'ID' => '(int) The post ID.',
+ 'author' => '(object>author) The author of the post.',
+ 'date' => "(ISO 8601 datetime) The post's creation time.",
+ 'modified' => "(ISO 8601 datetime) The post's creation time.",
+ 'title' => '(HTML) context
dependent.',
+ 'URL' => '(URL) The full permalink URL to the post.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) context
dependent.',
+ 'excerpt' => '(HTML) context
dependent.',
+ 'slug' => '(string) The name (slug) for your post, used in URLs.',
+ 'status' => array(
+ 'publish' => 'The post is published.',
+ 'draft' => 'The post is saved as a draft.',
+ 'pending' => 'The post is pending editorial approval.',
+ 'future' => 'The post is scheduled for future publishing.',
+ 'trash' => 'The post is in the trash.',
+ ),
+ 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
+ 'parent' => "(object>post_reference|false) A reference to the post's parent, if it has one.",
+ 'type' => array(
+ 'post' => 'A blog post.',
+ 'page' => 'A page.',
+ ),
+ 'comments_open' => '(bool) Is the post open for comments?',
+ 'pings_open' => '(bool) Is the post open for pingbacks, trackbacks?',
+ 'comment_count' => '(int) The number of comments for this post.',
+ 'like_count' => '(int) The number of likes for this post.',
+ 'featured_image' => '(URL) The URL to the featured image for this post if it has one.',
+ 'format' => array(), // see constructor
+ 'geo' => '(object>geo|false)',
+ 'publicize_URLs' => '(array:URL) Array of Twitter and Facebook URLs published by this post.',
+ 'tags' => '(object:tag) Hash of tags (keyed by tag name) applied to the post.',
+ 'categories' => '(object:category) Hash of categories (keyed by category name) applied to the post.',
+ 'attachments' => '(object:attachment) Hash of post attachments (keyed by attachment ID).',
+ 'meta' => '(object) Meta data',
+ );
+
+ // var $response_format =& $this->post_object_format;
+
+ function __construct( $args ) {
+ if ( is_array( $this->post_object_format ) && isset( $this->post_object_format['format'] ) ) {
+ $this->post_object_format['format'] = get_post_format_strings();
+ }
+ if ( !$this->response_format ) {
+ $this->response_format =& $this->post_object_format;
+ }
+ parent::__construct( $args );
+ }
+
+ function the_password_form() {
+ return __( 'This post is password protected.', 'jetpack' );
+ }
+
+ function get_post_by( $field, $post_id, $context = 'display' ) {
+ global $blog_id;
+
+ if ( defined( 'GEO_LOCATION__CLASS' ) && class_exists( GEO_LOCATION__CLASS ) ) {
+ $geo = call_user_func( array( GEO_LOCATION__CLASS, 'init' ) );
+ } else {
+ $geo = false;
+ }
+
+ if ( 'display' == $context ) {
+ $args = $this->query_args();
+ if ( isset( $args['content_width'] ) && $args['content_width'] ) {
+ $GLOBALS['content_width'] = (int) $args['content_width'];
+ }
+ }
+
+ if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'wp-windows8' ) ) {
+ remove_shortcode( 'gallery', 'gallery_shortcode' );
+ add_shortcode( 'gallery', array( &$this, 'win8_gallery_shortcode' ) );
+ }
+
+ switch ( $field ) {
+ case 'name' :
+ $post_id = sanitize_title( $post_id );
+ if ( !$post_id ) {
+ return new WP_Error( 'invalid_post', 'Invalid post', 400 );
+ }
+
+ $posts = get_posts( array( 'name' => $post_id ) );
+ if ( !$posts || !isset( $posts[0]->ID ) || !$posts[0]->ID ) {
+ $page = get_page_by_path( $post_id );
+ if ( !$page )
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ $post_id = $page->ID;
+ } else {
+ $post_id = (int) $posts[0]->ID;
+ }
+ break;
+ default :
+ $post_id = (int) $post_id;
+ break;
+ }
+
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ $types = array( 'post', 'page' );
+ if ( !in_array( $post->post_type, $types ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ // Permissions
+ switch ( $context ) {
+ case 'edit' :
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+ break;
+ case 'display' :
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ // Re-get post according to the correct $context
+ $post = get_post( $post->ID, OBJECT, $context );
+ $GLOBALS['post'] = $post;
+
+ if ( 'display' == $context ) {
+ setup_postdata( $post );
+ }
+
+ $response = array();
+ foreach ( array_keys( $this->post_object_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ // explicitly cast all output
+ $response[$key] = (int) $post->ID;
+ break;
+ case 'author' :
+ $response[$key] = (object) $this->get_author( $post, 'edit' === $context && current_user_can( 'edit_post', $post->ID ) );
+ break;
+ case 'date' :
+ $response[$key] = (string) $this->format_date( $post->post_date_gmt, $post->post_date );
+ break;
+ case 'modified' :
+ $response[$key] = (string) $this->format_date( $post->post_modified_gmt, $post->post_modified );
+ break;
+ case 'title' :
+ if ( 'display' == $context ) {
+ $response[$key] = (string) get_the_title( $post->ID );
+ } else {
+ $response[$key] = (string) $post->post_title;
+ }
+ break;
+ case 'URL' :
+ $response[$key] = (string) esc_url_raw( get_permalink( $post->ID ) );
+ break;
+ case 'short_URL' :
+ $response[$key] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) );
+ break;
+ case 'content' :
+ if ( 'display' == $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ $response[$key] = (string) $this->get_the_post_content_for_display();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = (string) $post->post_content;
+ }
+ break;
+ case 'excerpt' :
+ if ( 'display' == $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ ob_start();
+ the_excerpt();
+ $response[$key] = (string) ob_get_clean();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = (string) $post->post_excerpt;
+ }
+ break;
+ case 'status' :
+ $response[$key] = (string) get_post_status( $post->ID );
+ break;
+ case 'slug' :
+ $response[$key] = (string) $post->post_name;
+ break;
+ case 'password' :
+ $response[$key] = (string) $post->post_password;
+ break;
+ case 'parent' : // (object|false)
+ if ( $post->post_parent ) {
+ $parent = get_post( $post->post_parent );
+ $response[$key] = (object) array(
+ 'ID' => (int) $parent->ID,
+ 'type' => (string) $parent->post_type,
+ 'link' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $parent->ID ),
+ );
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'type' :
+ $response[$key] = (string) $post->post_type;
+ break;
+ case 'comments_open' :
+ $response[$key] = (bool) comments_open( $post->ID );
+ break;
+ case 'pings_open' :
+ $response[$key] = (bool) pings_open( $post->ID );
+ break;
+ case 'comment_count' :
+ $response[$key] = (int) $post->comment_count;
+ break;
+ case 'like_count' :
+ $response[$key] = (int) $this->api->post_like_count( $blog_id, $post->ID );
+ break;
+ case 'featured_image' :
+ $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
+ if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) )
+ $response[$key] = (string) $image_attributes[0];
+ else
+ $response[$key] = '';
+ break;
+ case 'format' :
+ $response[$key] = (string) get_post_format( $post->ID );
+ if ( !$response[$key] ) {
+ $response[$key] = 'standard';
+ }
+ break;
+ case 'geo' : // (object|false)
+ if ( !$geo ) {
+ $response[$key] = false;
+ } else {
+ $geo_data = $geo->get_geo( 'post', $post->ID );
+ $response[$key] = false;
+ if ( $geo_data ) {
+ $geo_data = array_intersect_key( $geo_data, array( 'latitude' => true, 'longitude' => true, 'address' => true, 'public' => true ) );
+ if ( $geo_data ) {
+ $response[$key] = (object) array(
+ 'latitude' => isset( $geo_data['latitude'] ) ? (float) $geo_data['latitude'] : 0,
+ 'longitude' => isset( $geo_data['longitude'] ) ? (float) $geo_data['longitude'] : 0,
+ 'address' => isset( $geo_data['address'] ) ? (string) $geo_data['address'] : '',
+ );
+ } else {
+ $response[$key] = false;
+ }
+ // Private
+ if ( !isset( $geo_data['public'] ) || !$geo_data['public'] ) {
+ if ( 'edit' !== $context || !current_user_can( 'edit_post', $post->ID ) ) {
+ // user can't access
+ $response[$key] = false;
+ }
+ }
+ }
+ }
+ break;
+ case 'publicize_URLs' :
+ $publicize_URLs = array();
+ $publicize = get_post_meta( $post->ID, 'publicize_results', true );
+ if ( $publicize ) {
+ foreach ( $publicize as $service => $data ) {
+ switch ( $service ) {
+ case 'twitter' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://twitter.com/{$datum['user_id']}/status/{$datum['post_id']}" );
+ }
+ break;
+ case 'fb' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://www.facebook.com/permalink.php?story_fbid={$datum['post_id']}&id={$datum['user_id']}" );
+ }
+ break;
+ }
+ }
+ }
+ $response[$key] = (array) $publicize_URLs;
+ break;
+ case 'tags' :
+ $response[$key] = array();
+ $terms = wp_get_post_tags( $post->ID );
+ foreach ( $terms as $term ) {
+ if ( !empty( $term->name ) ) {
+ $response[$key][$term->name] = $this->get_taxonomy( $term->slug, 'post_tag', $context );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'categories':
+ $response[$key] = array();
+ $terms = wp_get_post_categories( $post->ID );
+ foreach ( $terms as $term ) {
+ $category = $taxonomy = get_term_by( 'id', $term, 'category' );
+ if ( !empty( $category->name ) ) {
+ $response[$key][$category->name] = $this->get_taxonomy( $category->slug, 'category', $context );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'attachments':
+ $response[$key] = array();
+ $_attachments = get_posts( array( 'post_parent' => $post->ID, 'post_status' => 'inherit', 'post_type' => 'attachment' ) );
+ foreach ( $_attachments as $attachment ) {
+ $response[$key][$attachment->ID] = $this->get_attachment( $attachment );
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
+ 'help' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+// 'author' => (string) $this->get_user_link( $post->post_author ),
+// 'via' => (string) $this->get_post_link( $reblog_origin_blog_id, $reblog_origin_post_id ),
+ 'replies' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'replies/' ),
+ 'likes' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'likes/' ),
+ ),
+ );
+ break;
+ }
+ }
+
+ unset( $GLOBALS['post'] );
+ return $response;
+ }
+
+ // No Blog ID parameter. No Post ID parameter. Depends on globals.
+ // Expects setup_postdata() to already have been run
+ function get_the_post_content_for_display() {
+ global $pages, $page;
+
+ $old_pages = $pages;
+ $old_page = $page;
+
+ $content = join( "\n\n", $pages );
+ $content = preg_replace( '//', '', $content );
+ $pages = array( $content );
+ $page = 1;
+
+ ob_start();
+ the_content();
+ $return = ob_get_clean();
+
+ $pages = $old_pages;
+ $page = $old_page;
+
+ return $return;
+ }
+
+ function get_blog_post( $blog_id, $post_id, $context = 'display' ) {
+ $blog_id = $this->api->get_blog_id( $blog_id );
+ if ( !$blog_id || is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+ switch_to_blog( $blog_id );
+ $post = $this->get_post_by( 'ID', $post_id, $context );
+ restore_current_blog();
+ return $post;
+ }
+
+ function win8_gallery_shortcode( $attr ) {
+ global $post;
+
+ static $instance = 0;
+ $instance++;
+
+ $output = '';
+
+ // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
+ if ( isset( $attr['orderby'] ) ) {
+ $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
+ if ( !$attr['orderby'] )
+ unset( $attr['orderby'] );
+ }
+
+ extract( shortcode_atts( array(
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order ID',
+ 'id' => $post->ID,
+ 'include' => '',
+ 'exclude' => '',
+ 'slideshow' => false
+ ), $attr ) );
+
+ // Custom image size and always use it
+ add_image_size( 'win8app-column', 480 );
+ $size = 'win8app-column';
+
+ $id = intval( $id );
+ if ( 'RAND' == $order )
+ $orderby = 'none';
+
+ if ( !empty( $include ) ) {
+ $include = preg_replace( '/[^0-9,]+/', '', $include );
+ $_attachments = get_posts( array( 'include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ $attachments = array();
+ foreach ( $_attachments as $key => $val ) {
+ $attachments[$val->ID] = $_attachments[$key];
+ }
+ } elseif ( !empty( $exclude ) ) {
+ $exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
+ $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ } else {
+ $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ }
+
+ if ( ! empty( $attachments ) ) {
+ foreach ( $attachments as $id => $attachment ) {
+ $link = isset( $attr['link'] ) && 'file' == $attr['link'] ? wp_get_attachment_link( $id, $size, false, false ) : wp_get_attachment_link( $id, $size, true, false );
+
+ if ( $captiontag && trim($attachment->post_excerpt) ) {
+ $output .= "$link
+
" . wptexturize($attachment->post_excerpt) . "
+
";
+ } else {
+ $output .= $link . ' ';
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns attachment object.
+ *
+ * @param $attachment attachment row
+ *
+ * @return (object)
+ */
+ function get_attachment( $attachment ) {
+ $metadata = wp_get_attachment_metadata( $attachment->ID );
+
+ $result = array(
+ 'ID' => (int) $attachment->ID,
+ 'URL' => (string) wp_get_attachment_url( $attachment->ID ),
+ 'guid' => (string) $attachment->guid,
+ 'mime_type' => (string) $attachment->post_mime_type,
+ 'width' => (int) $metadata['width'],
+ 'height' => (int) $metadata['height'],
+ );
+
+ if ( isset( $metadata['duration'] ) ) {
+ $result['duration'] = (int) $metadata['duration'];
+ }
+
+ return (object) apply_filters( 'get_attachment', $result );
+ }
+}
+
+class WPCOM_JSON_API_Get_Post_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/name:%s -> $blog_id, $post_id // not documented
+ // /sites/%s/posts/slug:%s -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( false === strpos( $path, '/posts/slug:' ) && false === strpos( $path, '/posts/name:' ) ) {
+ $get_by = 'ID';
+ } else {
+ $get_by = 'name';
+ }
+
+ $return = $this->get_post_by( $get_by, $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+}
+
+class WPCOM_JSON_API_List_Posts_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ var $date_range = array();
+
+ var $response_format = array(
+ 'found' => '(int) The total number of posts found that match the request (ignoring limits, offsets, and pagination).',
+ 'posts' => '(array:post) An array of post objects.',
+ );
+
+ // /sites/%s/posts/ -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ $query = array(
+ 'posts_per_page' => $args['number'],
+ 'order' => $args['order'],
+ 'orderby' => $args['order_by'],
+ 'post_type' => $args['type'],
+ 'post_status' => $args['status'],
+ 'author' => isset( $args['author'] ) && 0 < $args['author'] ? $args['author'] : null,
+ 's' => isset( $args['search'] ) ? $args['search'] : null,
+ );
+
+ if (
+ isset( $args['sticky'] )
+ &&
+ ( $sticky = get_option( 'sticky_posts' ) )
+ &&
+ is_array( $sticky )
+ ) {
+ if ( $args['sticky'] ) {
+ $query['post__in'] = $sticky;
+ } else {
+ $query['post__not_in'] = $sticky;
+ $query['ignore_sticky_posts'] = 1;
+ }
+ }
+
+ if ( isset( $args['category'] ) ) {
+ $category = get_term_by( 'slug', $args['category'], 'category' );
+ if ( $category === false) {
+ $query['category_name'] = $args['category'];
+ } else {
+ $query['cat'] = $category->term_id;
+ }
+ }
+
+ if ( isset( $args['tag'] ) ) {
+ $query['tag'] = $args['tag'];
+ }
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $query['paged'] = $args['page'];
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $query['offset'] = $args['offset'];
+ }
+
+ if ( isset( $args['before'] ) ) {
+ $this->date_range['before'] = $args['before'];
+ }
+ if ( isset( $args['after'] ) ) {
+ $this->date_range['after'] = $args['after'];
+ }
+
+ if ( $this->date_range ) {
+ add_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ }
+ $wp_query = new WP_Query( $query );
+ if ( $this->date_range ) {
+ remove_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ $this->date_range = array();
+ }
+
+ $return = array();
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $wp_query->found_posts;
+ break;
+ case 'posts' :
+ $posts = array();
+ foreach ( $wp_query->posts as $post ) {
+ $the_post = $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ if ( $the_post && !is_wp_error( $the_post ) ) {
+ $posts[] = $the_post;
+ }
+ }
+
+ if ( $posts ) {
+ do_action( 'wpcom_json_api_objects', 'posts', count( $posts ) );
+ }
+
+ $return[$key] = $posts;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ function handle_date_range( $where ) {
+ global $wpdb;
+
+ switch ( count( $this->date_range ) ) {
+ case 2 :
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date BETWEEN CAST( %s AS DATETIME ) AND CAST( %s AS DATETIME ) ",
+ $this->date_range['after'],
+ $this->date_range['before']
+ );
+ break;
+ case 1 :
+ if ( isset( $this->date_range['before'] ) ) {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date <= CAST( %s AS DATETIME ) ",
+ $this->date_range['before']
+ );
+ } else {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date >= CAST( %s AS DATETIME ) ",
+ $this->date_range['after']
+ );
+ }
+ break;
+ }
+
+ return $where;
+ }
+}
+
+class WPCOM_JSON_API_Update_Post_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( $this->api->ends_with( $this->path, '/delete' ) ) {
+ $this->post_object_format['status']['deleted'] = 'The post has been deleted permanently.';
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_post( $path, $blog_id, $post_id );
+ } else {
+ return $this->write_post( $path, $blog_id, $post_id );
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ function write_post( $path, $blog_id, $post_id ) {
+ $new = $this->api->ends_with( $path, '/new' );
+ $args = $this->query_args();
+
+ if ( $new ) {
+ $input = $this->input( true );
+
+ if ( !isset( $input['title'] ) && !isset( $input['content'] ) && !isset( $input['excerpt'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $post_type = get_post_type_object( $input['type'] );
+
+ if ( 'publish' === $input['status'] ) {
+ if ( !current_user_can( $post_type->cap->publish_posts ) ) {
+ if ( current_user_can( $post_type->cap->edit_posts ) ) {
+ $input['status'] = 'pending';
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
+ }
+ }
+ } else {
+ if ( !current_user_can( $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
+ }
+ }
+ } else {
+ $input = $this->input( false );
+
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+
+ if ( 'publish' === $input['status'] && 'publish' !== $post->post_status && !current_user_can( 'publish_post', $post->ID ) ) {
+ $input['status'] = 'pending';
+ }
+
+ $post_type = get_post_type_object( $post->post_type );
+ }
+
+ if ( !is_post_type_hierarchical( $post_type->name ) ) {
+ unset( $input['parent'] );
+ }
+
+ $categories = null;
+ $tags = null;
+
+ if ( !empty( $input['categories'] )) {
+ if ( is_array( $input['categories'] ) ) {
+ $categories = $input['categories'];
+ } else {
+ foreach ( explode( ',', $input['categories'] ) as $category ) {
+ $categories[] = $category;
+ }
+ }
+ }
+
+ if ( !empty( $input['tags'] ) ) {
+ if ( is_array( $input['tags'] ) ) {
+ $tags = $input['tags'];
+ } else {
+ foreach ( explode( ',', $input['tags'] ) as $tag ) {
+ $tags[] = $tag;
+ }
+ }
+ $tags_string = implode( ',', $tags );
+ }
+
+ unset( $input['tags'], $input['categories'] );
+
+ $insert = array();
+
+ if ( !empty( $input['slug'] ) ) {
+ $insert['post_name'] = $input['slug'];
+ unset( $input['slug'] );
+ }
+
+ if ( true === $input['comments_open'] )
+ $insert['comment_status'] = 'open';
+ else if ( false === $input['comments_open'] )
+ $insert['comment_status'] = 'closed';
+
+ if ( true === $input['pings_open'] )
+ $insert['ping_status'] = 'open';
+ else if ( false === $input['pings_open'] )
+ $insert['ping_status'] = 'closed';
+
+ unset( $input['comments_open'], $input['pings_open'] );
+
+ $publicize = $input['publicize'];
+ $publicize_custom_message = $input['publicize_message'];
+ unset( $input['publicize'], $input['publicize_message'] );
+
+ foreach ( $input as $key => $value ) {
+ $insert["post_$key"] = $value;
+ }
+
+ $has_media = isset( $input['media'] ) && $input['media'] ? count( $input['media'] ) : false;
+
+ if ( $new ) {
+ if ( false === strpos( $input['content'], '[gallery' ) && $has_media ) {
+ switch ( $has_media ) {
+ case 0 :
+ // No images - do nothing.
+ break;
+ case 1 :
+ // 1 image - make it big
+ $insert['post_content'] = $input['content'] = "[gallery size=full columns=1]\n\n" . $input['content'];
+ break;
+ default :
+ // Several images - 3 column gallery
+ $insert['post_content'] = $input['content'] = "[gallery]\n\n" . $input['content'];
+ break;
+ }
+ }
+
+ $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
+
+ if ( $has_media ) {
+ $this->api->trap_wp_die( 'upload_error' );
+ foreach ( $input['media'] as $media_item ) {
+ $_FILES['.api.media.item.'] = $media_item;
+ // check for WP_Error if we ever actually need $media_id
+ $media_id = media_handle_upload( '.api.media.item.', $post_id );
+ }
+ $this->api->trap_wp_die( null );
+
+ unset( $_FILES['.api.media.item.'] );
+ }
+ } else {
+ $insert['ID'] = $post->ID;
+ $post_id = wp_update_post( (object) $insert );
+ }
+
+ if ( !$post_id || is_wp_error( $post_id ) ) {
+ return null;
+ }
+
+ if ( $publicize === false ) {
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name, 1 );
+ }
+ } else if ( is_array( $publicize ) && ( count ( $publicize ) > 0 ) ) {
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ if ( !in_array( $name, $publicize ) ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name, 1 );
+ }
+ }
+ }
+
+ if ( !empty( $publicize_custom_message ) )
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
+
+ if ( is_array( $categories ) )
+ wp_set_object_terms( $post_id, $categories, 'category' );
+ if ( is_array( $tags ) )
+ wp_set_object_terms( $post_id, $tags, 'post_tag' );
+
+ set_post_format( $post_id, $insert['post_format'] );
+
+ $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ function delete_post( $path, $blog_id, $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'delete_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ wp_delete_post( $post->ID );
+
+ $status = get_post_status( $post->ID );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ return $return;
+ }
+
+ return $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ }
+}
+
+abstract class WPCOM_JSON_API_Taxonomy_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $category_object_format = array(
+ 'ID' => '(int) The category ID.',
+ 'name' => "(string) The name of the category.",
+ 'slug' => "(string) The slug of the category.",
+ 'description' => '(string) The description of the category.',
+ 'post_count' => "(int) The number of posts using this category.",
+ 'parent' => "(int) The parent ID for the category.",
+ 'meta' => '(object) Meta data',
+ );
+
+ var $tag_object_format = array(
+ 'ID' => '(int) The tag ID.',
+ 'name' => "(string) The name of the tag.",
+ 'slug' => "(string) The slug of the tag.",
+ 'description' => '(string) The description of the tag.',
+ 'post_count' => "(int) The number of posts using this t.",
+ 'meta' => '(object) Meta data',
+ );
+
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( preg_match( '#/tags/#i', $this->path ) )
+ $this->response_format =& $this->tag_object_format;
+ else
+ $this->response_format =& $this->category_object_format;
+ }
+}
+
+
+class WPCOM_JSON_API_Get_Taxonomy_Endpoint extends WPCOM_JSON_API_Taxonomy_Endpoint {
+ // /sites/%s/tags/slug:%s -> $blog_id, $tag_id
+ // /sites/%s/categories/slug:%s -> $blog_id, $tag_id
+ function callback( $path = '', $blog_id = 0, $taxonomy_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+ if ( preg_match( '#/tags/#i', $path ) ) {
+ $taxonomy_type = "post_tag";
+ } else {
+ $taxonomy_type = "category";
+ }
+
+ $return = $this->get_taxonomy( $taxonomy_id, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+
+ return $return;
+ }
+}
+
+
+class WPCOM_JSON_API_Update_Taxonomy_Endpoint extends WPCOM_JSON_API_Taxonomy_Endpoint {
+ // /sites/%s/tags|categories/new -> $blog_id
+ // /sites/%s/tags|categories/slug:%s -> $blog_id, $taxonomy_id
+ // /sites/%s/tags|categories/slug:%s/delete -> $blog_id, $taxonomy_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( preg_match( '#/tags/#i', $path ) ) {
+ $taxonomy_type = "post_tag";
+ } else {
+ $taxonomy_type = "category";
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_taxonomy( $path, $blog_id, $object_id, $taxonomy_type );
+ } elseif ( $this->api->ends_with( $path, '/new' ) ) {
+ return $this->new_taxonomy( $path, $blog_id, $taxonomy_type );
+ }
+
+ return $this->update_taxonomy( $path, $blog_id, $object_id, $taxonomy_type );
+ }
+
+ // /sites/%s/tags|categories/new -> $blog_id
+ function new_taxonomy( $path, $blog_id, $taxonomy_type ) {
+ $args = $this->query_args();
+ $input = $this->input();
+ if ( !is_array( $input ) || !$input || !strlen( $input['name'] ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown data passed', 404 );
+ }
+
+ $user = wp_get_current_user();
+ if ( !$user || is_wp_error( $user ) || !$user->ID ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to manage taxonomies.', 403 );
+ }
+
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->edit_terms ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+ }
+
+ if ( term_exists( $input['name'], $taxonomy_type ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'A taxonomy with that name already exists', 404 );
+ }
+
+ if ( 'category' != $taxonomy_type )
+ $input['parent'] = 0;
+
+ $data = wp_insert_term( addslashes( $input['name'] ), $taxonomy_type,
+ array(
+ 'description' => addslashes( $input['description'] ),
+ 'parent' => $input['parent']
+ )
+ );
+
+ $taxonomy = get_term_by( 'id', $data['term_id'], $taxonomy_type );
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+ return $return;
+ }
+
+ // /sites/%s/tags|categories/slug:%s -> $blog_id, $taxonomy_id
+ function update_taxonomy( $path, $blog_id, $object_id, $taxonomy_type ) {
+ $taxonomy = get_term_by( 'slug', $object_id, $taxonomy_type );
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->edit_terms ) )
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+
+ if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
+ }
+
+ if ( false === term_exists( $object_id, $taxonomy_type ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'That taxonomy does not exist', 404 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input( false );
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $update = array();
+ if ( 'category' == $taxonomy_type && !empty( $input['parent'] ) )
+ $update['parent'] = $input['parent'];
+
+ if ( !empty( $input['description'] ) )
+ $update['description'] = addslashes( $input['description'] );
+
+ if ( !empty( $input['name'] ) )
+ $update['name'] = addslashes( $input['name'] );
+
+
+ $data = wp_update_term( $taxonomy->term_id, $taxonomy_type, $update );
+ $taxonomy = get_term_by( 'id', $data['term_id'], $taxonomy_type );
+
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+ return $return;
+ }
+
+ // /sites/%s/tags|categories/%s/delete -> $blog_id, $taxonomy_id
+ function delete_taxonomy( $path, $blog_id, $object_id, $taxonomy_type ) {
+ $taxonomy = get_term_by( 'slug', $object_id, $taxonomy_type );
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->delete_terms ) )
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+
+ if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
+ }
+
+ if ( false === term_exists( $object_id, $taxonomy_type ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'That taxonomy does not exist', 404 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+
+ wp_delete_term( $taxonomy->term_id, $taxonomy_type );
+
+ return array(
+ 'slug' => (string) $taxonomy->slug,
+ 'success' => 'true',
+ );
+ }
+}
+
+abstract class WPCOM_JSON_API_Comment_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $comment_object_format = array(
+ // explicitly document and cast all output
+ 'ID' => '(int) The comment ID.',
+ 'post' => "(object>post_reference) A reference to the comment's post.",
+ 'author' => '(object>author) The author of the comment.',
+ 'date' => "(ISO 8601 datetime) The comment's creation time.",
+ 'URL' => '(URL) The full permalink URL to the comment.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) context
dependent.',
+ 'status' => array(
+ 'approved' => 'The comment has been approved.',
+ 'unapproved' => 'The comment has been held for review in the moderation queue.',
+ 'spam' => 'The comment has been marked as spam.',
+ 'trash' => 'The comment is in the trash.',
+ ),
+ 'parent' => "(object>comment_reference|false) A reference to the comment's parent, if it has one.",
+ 'type' => array(
+ 'comment' => 'The comment is a regular comment.',
+ 'trackback' => 'The comment is a trackback.',
+ 'pingback' => 'The comment is a pingback.',
+ ),
+ 'meta' => '(object) Meta data',
+ );
+
+ // var $response_format =& $this->comment_object_format;
+
+ function __construct( $args ) {
+ if ( !$this->response_format ) {
+ $this->response_format =& $this->comment_object_format;
+ }
+ parent::__construct( $args );
+ }
+
+ function get_comment( $comment_id, $context ) {
+ global $blog_id;
+
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $types = array( '', 'comment', 'pingback', 'trackback' );
+ if ( !in_array( $comment->comment_type, $types ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $post = get_post( $comment->comment_post_ID );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ $status = wp_get_comment_status( $comment->comment_ID );
+
+ // Permissions
+ switch ( $context ) {
+ case 'edit' :
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit comment', 403 );
+ }
+
+ $GLOBALS['post'] = $post;
+ $comment = get_comment_to_edit( $comment->comment_ID );
+ break;
+ case 'display' :
+ if ( 'approved' !== $status ) {
+ $current_user_id = get_current_user_id();
+ $user_can_read_coment = false;
+ if ( $current_user_id && $comment->user_id && $current_user_id == $comment->user_id ) {
+ $user_can_read_coment = true;
+ } elseif (
+ $comment->comment_author_email && $comment->comment_author
+ &&
+ isset( $this->api->token_details['user'] )
+ &&
+ $this->api->token_details['user']['user_email'] === $comment->comment_author_email
+ &&
+ $this->api->token_details['user']['display_name'] === $comment->comment_author
+ ) {
+ $user_can_read_coment = true;
+ } else {
+ $user_can_read_coment = current_user_can( 'edit_comment', $comment->comment_ID );
+ }
+
+ if ( !$user_can_read_coment ) {
+ return new WP_Error( 'unauthorized', 'User cannot read unapproved comment', 403 );
+ }
+ }
+
+ $GLOBALS['post'] = $post;
+ setup_postdata( $post );
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $GLOBALS['comment'] = $comment;
+ $response = array();
+
+ foreach ( array_keys( $this->comment_object_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ // explicitly cast all output
+ $response[$key] = (int) $comment->comment_ID;
+ break;
+ case 'post' :
+ $response[$key] = (object) array(
+ 'ID' => (int) $post->ID,
+ 'type' => (string) $post->post_type,
+ 'link' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
+ );
+ break;
+ case 'author' :
+ $response[$key] = (object) $this->get_author( $comment, 'edit' === $context && current_user_can( 'edit_comment', $comment->comment_ID ) );
+ break;
+ case 'date' :
+ $response[$key] = (string) $this->format_date( $comment->comment_date_gmt, $comment->comment_date );
+ break;
+ case 'URL' :
+ $response[$key] = (string) esc_url_raw( get_comment_link( $comment->comment_ID ) );
+ break;
+ case 'short_URL' :
+ // @todo - pagination
+ $response[$key] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) . "%23comment-{$comment->comment_ID}" );
+ break;
+ case 'content' :
+ if ( 'display' == $context ) {
+ ob_start();
+ comment_text();
+ $response[$key] = (string) ob_get_clean();
+ } else {
+ $response[$key] = (string) $comment->comment_content;
+ }
+ break;
+ case 'status' :
+ $response[$key] = (string) $status;
+ break;
+ case 'parent' : // (object|false)
+ if ( $comment->comment_parent ) {
+ $parent = get_comment( $comment->comment_parent );
+ $response[$key] = (object) array(
+ 'ID' => (int) $parent->comment_ID,
+ 'type' => (string) ( $parent->comment_type ? $parent->comment_type : 'comment' ),
+ 'link' => (string) $this->get_comment_link( $blog_id, $parent->comment_ID ),
+ );
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'type' :
+ $response[$key] = (string) ( $comment->comment_type ? $comment->comment_type : 'comment' );
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID ),
+ 'help' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+ 'post' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $comment->comment_post_ID ),
+ 'replies' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'replies/' ),
+// 'author' => (string) $this->get_user_link( $comment->user_id ),
+// 'via' => (string) $this->get_post_link( $ping_origin_blog_id, $ping_origin_post_id ), // Ping/trackbacks
+ ),
+ );
+ break;
+ }
+ }
+
+ unset( $GLOBALS['comment'], $GLOBALS['post'] );
+ return $response;
+ }
+}
+
+class WPCOM_JSON_API_Get_Comment_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $comment_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ $return = $this->get_comment( $comment_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+
+ return $return;
+ }
+}
+
+// @todo permissions
+class WPCOM_JSON_API_List_Comments_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ var $date_range = array();
+
+ var $response_format = array(
+ 'found' => '(int) The total number of comments found that match the request (ignoring limits, offsets, and pagination).',
+ 'comments' => '(array:comment) An array of comment objects.',
+ );
+
+ function __construct( $args ) {
+ parent::__construct( $args );
+ $this->query = array_merge( $this->query, array(
+ 'number' => '(int=20) The number of comments to return. Limit: 100.',
+ 'offset' => '(int=0) 0-indexed offset.',
+ 'page' => '(int) Return the Nth 1-indexed page of comments. Takes precedence over the offset
parameter.',
+ 'order' => array(
+ 'DESC' => 'Return comments in descending order from newest to oldest.',
+ 'ASC' => 'Return comments in ascending order from oldest to newest.',
+ ),
+ 'after' => '(ISO 8601 datetime) Return comments dated on or after the specified datetime.',
+ 'before' => '(ISO 8601 datetime) Return comments dated on or before the specified datetime.',
+ 'type' => array(
+ 'any' => 'Return all comments regardless of type.',
+ 'comment' => 'Return only regular comments.',
+ 'trackback' => 'Return only trackbacks.',
+ 'pingback' => 'Return only pingbacks.',
+ 'pings' => 'Return both trackbacks and pingbacks.',
+ ),
+ 'status' => array(
+ 'approved' => 'Return only approved comments.',
+ 'unapproved' => 'Return only comments in the moderation queue.',
+ 'spam' => 'Return only comments marked as spam.',
+ 'trash' => 'Return only comments in the trash.',
+ ),
+ ) );
+ }
+
+ // /sites/%s/comments/ -> $blog_id
+ // /sites/%s/posts/%d/replies/ -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/ -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( false !== strpos( $path, '/posts/' ) ) {
+ // We're looking for comments of a particular post
+ $post_id = $object_id;
+ $comment_id = 0;
+ } else {
+ // We're looking for comments for the whole blog, or replies to a single comment
+ $comment_id = $object_id;
+ $post_id = 0;
+ }
+
+ // We can't efficiently get the number of replies to a single comment
+ $count = false;
+ $found = -1;
+
+ if ( !$comment_id ) {
+ // We can get comment counts for the whole site or for a single post, but only for certain queries
+ if ( 'any' === $args['type'] && !isset( $args['after'] ) && !isset( $args['before'] ) ) {
+ $count = wp_count_comments( $post_id );
+ }
+ }
+
+ switch ( $args['status'] ) {
+ case 'approved' :
+ $status = 'approve';
+ if ( $count ) {
+ $found = $count->approved;
+ }
+ break;
+ default :
+ if ( !current_user_can( 'moderate_comments' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot read non-approved comments', 403 );
+ }
+ if ( 'unapproved' === $args['status'] ) {
+ $status = 'hold';
+ $count_status = 'moderated';
+ } else {
+ $status = $count_status = $args['status'];
+ }
+ if ( $count ) {
+ $found = $count->$count_status;
+ }
+ }
+
+ $query = array(
+ 'number' => $args['number'],
+ 'order' => $args['order'],
+ 'type' => 'any' === $args['type'] ? false : $args['type'],
+ 'status' => $status,
+ );
+
+ if ( $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+ $query['post_id'] = $post->ID;
+ if ( $this->api->ends_with( $this->path, '/replies' ) ) {
+ $query['parent'] = 0;
+ }
+ } elseif ( $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+ $query['parent'] = $comment_id;
+ }
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $query['offset'] = ( $args['page'] - 1 ) * $args['number'];
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $query['offset'] = $args['offset'];
+ }
+
+ if ( isset( $args['before_gmt'] ) ) {
+ $this->date_range['before_gmt'] = $args['before_gmt'];
+ }
+ if ( isset( $args['after_gmt'] ) ) {
+ $this->date_range['after_gmt'] = $args['after_gmt'];
+ }
+
+ if ( $this->date_range ) {
+ add_filter( 'comments_clauses', array( $this, 'handle_date_range' ) );
+ }
+ $comments = get_comments( $query );
+ if ( $this->date_range ) {
+ remove_filter( 'comments_clauses', array( $this, 'handle_date_range' ) );
+ $this->date_range = array();
+ }
+
+ $return = array();
+
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $found;
+ break;
+ case 'comments' :
+ $return_comments = array();
+ foreach ( $comments as $comment ) {
+ $the_comment = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( $the_comment && !is_wp_error( $the_comment ) ) {
+ $return_comments[] = $the_comment;
+ }
+ }
+
+ if ( $return_comments ) {
+ do_action( 'wpcom_json_api_objects', 'comments', count( $return_comments ) );
+ }
+
+ $return[$key] = $return_comments;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ function handle_date_range( $clauses ) {
+ global $wpdb;
+
+ switch ( count( $this->date_range ) ) {
+ case 2 :
+ $clauses['where'] .= $wpdb->prepare(
+ " AND `$wpdb->comments`.comment_date_gmt BETWEEN CAST( %s AS DATETIME ) AND CAST( %s AS DATETIME ) ",
+ $this->date_range['after_gmt'],
+ $this->date_range['before_gmt']
+ );
+ break;
+ case 1 :
+ if ( isset( $this->date_range['before_gmt'] ) ) {
+ $clauses['where'] .= $wpdb->prepare(
+ " AND `$wpdb->comments`.comment_date_gmt <= CAST( %s AS DATETIME ) ",
+ $this->date_range['before_gmt']
+ );
+ } else {
+ $clauses['where'] .= $wpdb->prepare(
+ " AND `$wpdb->comments`.comment_date_gmt >= CAST( %s AS DATETIME ) ",
+ $this->date_range['after_gmt']
+ );
+ }
+ break;
+ }
+
+ return $clauses;
+ }
+}
+
+class WPCOM_JSON_API_Update_Comment_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( $this->api->ends_with( $this->path, '/delete' ) ) {
+ $this->comment_object_format['status']['deleted'] = 'The comment has been deleted permanently.';
+ }
+ }
+
+ // /sites/%s/posts/%d/replies/new -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/new -> $blog_id, $comment_id
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ // /sites/%s/comments/%d/delete -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_comment( $path, $blog_id, $object_id );
+ } elseif ( $this->api->ends_with( $path, '/new' ) ) {
+ if ( false !== strpos( $path, '/posts/' ) ) {
+ return $this->new_comment( $path, $blog_id, $object_id, 0 );
+ } else {
+ return $this->new_comment( $path, $blog_id, 0, $object_id );
+ }
+ }
+
+ return $this->update_comment( $path, $blog_id, $object_id );
+ }
+
+ // /sites/%s/posts/%d/replies/new -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/new -> $blog_id, $comment_id
+ function new_comment( $path, $blog_id, $post_id, $comment_parent_id ) {
+ if ( !$post_id ) {
+ $comment_parent = get_comment( $comment_parent_id );
+ if ( !$comment_parent_id || !$comment_parent || is_wp_error( $comment_parent ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $post_id = $comment_parent->comment_post_ID;
+ }
+
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( -1 == get_option( 'blog_public' ) && ! is_user_member_of_blog() && ! is_super_admin() ) {
+ return new WP_Error( 'unauthorized', 'User cannot create comments', 403 );
+ }
+
+ if ( !comments_open( $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'Comments on this post are closed', 403 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $post_status = get_post_status_object( $post->post_status );
+ if ( !$post_status->public && !$post_status->private ) {
+ return new WP_Error( 'unauthorized', 'Comments on drafts are not allowed', 403 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input();
+ if ( !is_array( $input ) || !$input || !strlen( $input['content'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $user = wp_get_current_user();
+ if ( !$user || is_wp_error( $user ) || !$user->ID ) {
+ $auth_required = false;
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $auth_required = true;
+ } elseif ( isset( $this->api->token_details['user'] ) ) {
+ $user = (object) $this->api->token_details['user'];
+ foreach ( array( 'display_name', 'user_email', 'user_url' ) as $user_datum ) {
+ if ( !isset( $user->$user_datum ) ) {
+ $auth_required = true;
+ }
+ }
+ if ( !isset( $user->ID ) ) {
+ $user->ID = 0;
+ }
+ } else {
+ $auth_required = true;
+ }
+
+ if ( $auth_required ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to comment.', 403 );
+ }
+ }
+
+ $insert = array(
+ 'comment_post_ID' => $post->ID,
+ 'user_ID' => $user->ID,
+ 'comment_author' => $user->display_name,
+ 'comment_author_email' => $user->user_email,
+ 'comment_author_url' => $user->user_url,
+ 'comment_content' => $input['content'],
+ 'comment_parent' => $comment_parent_id,
+ 'comment_type' => '',
+ );
+
+ $this->api->trap_wp_die( 'comment_failure' );
+ $comment_id = wp_new_comment( add_magic_quotes( $insert ) );
+ $this->api->trap_wp_die( null );
+
+ $return = $this->get_comment( $comment_id, $args['context'] );
+ if ( !$return ) {
+ return new WP_Error( 400, __( 'Comment cache problem?', 'jetpack' ) );
+ }
+ if ( is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+ return $return;
+ }
+
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ function update_comment( $path, $blog_id, $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit comment', 403 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input( false );
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $update = array();
+ foreach ( $input as $key => $value ) {
+ $update["comment_$key"] = $value;
+ }
+
+ $comment_status = wp_get_comment_status( $comment->comment_ID );
+ if ( $comment_status !== $update['status'] && !current_user_can( 'moderate_comments' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot moderate comments', 403 );
+ }
+
+ if ( isset( $update['comment_status'] ) ) {
+ switch ( $update['comment_status'] ) {
+ case 'unapproved' :
+ $update['comment_approved'] = 0;
+ break;
+ case 'spam' :
+ if ( 'spam' != $comment_status ) {
+ wp_spam_comment( $comment->comment_ID );
+ }
+ break;
+ case 'unspam' :
+ if ( 'spam' == $comment_status ) {
+ wp_unspam_comment( $comment->comment_ID );
+ }
+ break;
+ case 'trash' :
+ if ( ! EMPTY_TRASH_DAYS ) {
+ return new WP_Error( 'trash_disabled', 'Cannot trash comment', 403 );
+ }
+
+ if ( 'trash' != $comment_status ) {
+ wp_trash_comment( $comment_id );
+ }
+ break;
+ case 'untrash' :
+ if ( 'trash' == $comment_status ) {
+ wp_untrash_comment( $comment->comment_ID );
+ }
+ break;
+ default:
+ $update['comment_approved'] = 1;
+ break;
+ }
+ unset( $update['comment_status'] );
+ }
+
+ $update['comment_ID'] = $comment->comment_ID;
+
+ wp_update_comment( add_magic_quotes( $update ) );
+
+ $return = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+ return $return;
+ }
+
+ // /sites/%s/comments/%d/delete -> $blog_id, $comment_id
+ function delete_comment( $path, $blog_id, $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) { // [sic] There is no delete_comment cap
+ return new WP_Error( 'unauthorized', 'User cannot delete comment', 403 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+
+ wp_delete_comment( $comment->comment_ID );
+ $status = wp_get_comment_status( $comment->comment_ID );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ return $return;
+ }
+
+ return $this->get_comment( $comment->comment_ID, $args['context'] );
+ }
+}
+
+class WPCOM_JSON_API_GET_Site_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/mine
+ // /sites/%s -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ global $wpdb;
+ if ( 'mine' === $blog_id ) {
+ $api = WPCOM_JSON_API::init();
+ if ( !$api->token_details || empty( $api->token_details['blog_id'] ) ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to query information about the current blog.', 403 );
+ }
+ $blog_id = $api->token_details['blog_id'];
+ }
+
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $is_user_logged_in = is_user_logged_in();
+
+ $response = array();
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ $response[$key] = (int) $this->api->get_blog_id_for_output();
+ break;
+ case 'name' :
+ $response[$key] = (string) get_bloginfo( 'name' );
+ break;
+ case 'description' :
+ $response[$key] = (string) get_bloginfo( 'description' );
+ break;
+ case 'URL' :
+ $response[$key] = (string) home_url();
+ break;
+ case 'jetpack' :
+ if ( $is_user_logged_in )
+ $response[$key] = false; // magic
+ break;
+ case 'post_count' :
+ if ( $is_user_logged_in )
+ $response[$key] = (int) $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status = 'publish'");
+ break;
+ case 'lang' :
+ if ( $is_user_logged_in )
+ $response[$key] = (string) get_bloginfo( 'language' );
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+ 'help' => (string) $this->get_site_link( $this->api->get_blog_id_for_output(), 'help' ),
+ 'posts' => (string) $this->get_site_link( $this->api->get_blog_id_for_output(), 'posts/' ),
+ 'comments' => (string) $this->get_site_link( $this->api->get_blog_id_for_output(), 'comments/' ),
+ ),
+ );
+ break;
+ }
+ }
+
+ do_action( 'wpcom_json_api_objects', 'sites' );
+
+ return $response;
+ }
+}
+
+/*
+ * Set up endpoints
+ */
+
+/*
+ * Site endpoints
+ */
+new WPCOM_JSON_API_GET_Site_Endpoint( array(
+ 'description' => 'Information about a site ID/domain',
+ 'group' => 'Sites',
+ 'stat' => 'sites:X',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'query_parameters' => array(
+ 'context' => false,
+ ),
+
+ 'response_format' => array(
+ 'ID' => '(int) Blog ID',
+ 'name' => '(string) Title of blog',
+ 'description' => '(string) Tagline or description of blog',
+ 'URL' => '(string) Full URL to the blog',
+ 'jetpack' => '(bool) Whether the blog is a Jetpack blog or not',
+ 'post_count' => '(int) The number of posts the blog has',
+ 'lang' => '(string) Primary language code of the blog',
+ 'meta' => '(object) Meta data',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/?pretty=1',
+) );
+
+
+/*
+ * Post endpoints
+ */
+new WPCOM_JSON_API_List_Posts_Endpoint( array(
+ 'description' => 'Return matching Posts',
+ 'group' => 'Posts',
+ 'stat' => 'posts',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/posts/',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'query_parameters' => array(
+ 'number' => '(int=20) The number of posts to return. Limit: 100.',
+ 'offset' => '(int=0) 0-indexed offset.',
+ 'page' => '(int) Return the Nth 1-indexed page of posts. Takes precedence over the offset
parameter.',
+ 'order' => array(
+ 'DESC' => 'Return posts in descending order. For dates, that means newest to oldest.',
+ 'ASC' => 'Return posts in ascending order. For dates, that means oldest to newest.',
+ ),
+ 'order_by' => array(
+ 'date' => 'Order by the created time of each post.',
+ 'modified' => 'Order by the modified time of each post.',
+ 'title' => "Order lexicographically by the posts' titles.",
+ 'comment_count' => 'Order by the number of comments for each post.',
+ ),
+ 'after' => '(ISO 8601 datetime) Return posts dated on or after the specified datetime.',
+ 'before' => '(ISO 8601 datetime) Return posts dated on or before the specified datetime.',
+ 'tag' => '(string) Specify the tag name or slug.',
+ 'category' => '(string) Specify the category name or slug.',
+ 'type' => array(
+ 'post' => 'Return only blog posts.',
+ 'page' => 'Return only pages.',
+ 'any' => 'Return both blog posts and pages.',
+ ),
+ 'status' => array(
+ 'publish' => 'Return only published posts.',
+ 'private' => 'Return only private posts.',
+ 'draft' => 'Return only draft posts.',
+ 'pending' => 'Return only posts pending editorial approval.',
+ 'future' => 'Return only posts scheduled for future publishing.',
+ 'trash' => 'Return only posts in the trash.',
+ 'any' => 'Return all posts regardless of status.',
+ ),
+ 'sticky' => '(bool) Specify the stickiness.',
+ 'author' => "(int) Author's user ID",
+ 'search' => '(string) Search query',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/?number=5&pretty=1'
+) );
+
+new WPCOM_JSON_API_Get_Post_Endpoint( array(
+ 'description' => 'Return a single Post (by ID)',
+ 'group' => 'Posts',
+ 'stat' => 'posts:1',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/posts/%d',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_ID' => '(int) The post ID',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7/?pretty=1'
+) );
+
+new WPCOM_JSON_API_Get_Post_Endpoint( array(
+ 'description' => 'Return a single Post (by name)',
+ 'group' => '__do_not_document',
+ 'stat' => 'posts:name',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/posts/name:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_name' => '(string) The post name (a.k.a. slug)',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/name:blogging-and-stuff?pretty=1',
+) );
+
+new WPCOM_JSON_API_Get_Post_Endpoint( array(
+ 'description' => 'Return a single Post (by slug)',
+ 'group' => 'Posts',
+ 'stat' => 'posts:slug',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/posts/slug:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_slug' => '(string) The post slug (a.k.a. sanitized name)',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/slug:blogging-and-stuff?pretty=1',
+) );
+
+new WPCOM_JSON_API_Update_Post_Endpoint( array(
+ 'description' => 'Create a Post',
+ 'group' => 'Posts',
+ 'stat' => 'posts:new',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/posts/new',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'request_format' => array(
+ // explicitly document all input
+ 'date' => "(ISO 8601 datetime) The post's creation time.",
+ 'title' => '(HTML) The post title.',
+ 'content' => '(HTML) The post content.',
+ 'excerpt' => '(HTML) An optional post excerpt.',
+ 'slug' => '(string) The name (slug) for your post, used in URLs.',
+ 'publicize' => '(array|bool) True or false if the post be publicized to external services. An array of services if we only want to publicize to a select few. Defaults to true.',
+ 'publicize_message' => '(string) Custom message to be publicized to external services.',
+ 'status' => array(
+ 'publish' => 'Publish the post.',
+ 'private' => 'Privately publish the post.',
+ 'draft' => 'Save the post as a draft.',
+ 'pending' => 'Mark the post as pending editorial approval.',
+ ),
+ 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
+ 'parent' => "(int) The post ID of the new post's parent.",
+ 'type' => array(
+ 'post' => 'Create a blog post.',
+ 'page' => 'Create a page.',
+ ),
+ 'categories' => "(array|string) Comma separated list or array of categories (name or id)",
+ 'tags' => "(array|string) Comma separated list or array of tags (name or id)",
+ 'format' => get_post_format_strings(),
+ 'media' => "(media) An array of images to attach to the post. To upload media, the entire request should be multipart/form-data encoded. Multiple media items will be displayed in a gallery. Accepts images (image/gif, image/jpeg, image/png) only.Example : " .
+ "curl \ --form 'title=Image' \ --form 'media[]=@/path/to/file.jpg' \ -H 'Authorization: BEARER your-token' \ 'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'
",
+ 'comments_open' => "(bool) Should the post be open to comments? Defaults to the blog's preference.",
+ 'pings_open' => "(bool) Should the post be open to comments? Defaults to the blog's preference.",
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/posts/new/',
+
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+
+ 'body' => array(
+ 'title' => 'Hello World',
+ 'content' => 'Hello. I am a test post. I was created by the API',
+ 'tags' => 'tests',
+ 'categories' => 'API'
+ )
+ ),
+
+ 'example_response' => '
+{
+ "ID": 1270,
+ "author": {
+ "ID": 18342963,
+ "email": false,
+ "name": "binarysmash",
+ "URL": "http:\/\/binarysmash.wordpress.com",
+ "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/binarysmash"
+ },
+ "date": "2012-04-11T19:42:44+00:00",
+ "modified": "2012-04-11T19:42:44+00:00",
+ "title": "Hello World",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2012\/04\/11\/hello-world-3\/",
+ "short_URL": "http:\/\/wp.me\/p23HjV-ku",
+ "content": "Hello. I am a test post. I was created by the API<\/p>\n",
+ "excerpt": "
Hello. I am a test post. I was created by the API<\/p>\n",
+ "status": "publish",
+ "password": "",
+ "parent": false,
+ "type": "post",
+ "comments_open": true,
+ "pings_open": true,
+ "comment_count": 0,
+ "like_count": 0,
+ "featured_image": "",
+ "format": "standard",
+ "geo": false,
+ "publicize_URLs": [
+
+ ],
+ "tags": {
+ "tests": {
+ "name": "tests",
+ "slug": "tests",
+ "description": "",
+ "post_count": 1,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "categories": {
+ "API": {
+ "name": "API",
+ "slug": "api",
+ "description": "",
+ "post_count": 1,
+ "parent": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1270",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1270\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1270\/replies\/",
+ "likes": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1270\/likes\/"
+ }
+ }
+}'
+) );
+
+new WPCOM_JSON_API_Update_Post_Endpoint( array(
+ 'description' => 'Edit a Post',
+ 'group' => 'Posts',
+ 'stat' => 'posts:1:POST',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/posts/%d',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_ID' => '(int) The post ID',
+ ),
+
+ 'request_format' => array(
+ 'date' => "(ISO 8601 datetime) The post's creation time.",
+ 'title' => '(HTML) The post title.',
+ 'content' => '(HTML) The post content.',
+ 'excerpt' => '(HTML) An optional post excerpt.',
+ 'slug' => '(string) The name (slug) for your post, used in URLs.',
+ 'publicize' => '(array|bool) True or false if the post be publicized to external services. An array of services if we only want to publicize to a select few. Defaults to true.',
+ 'publicize_message' => '(string) Custom message to be publicized to external services.',
+ 'status' => array(
+ 'publish' => 'Publish the post.',
+ 'private' => 'Privately publish the post.',
+ 'draft' => 'Save the post as a draft.',
+ 'pending' => 'Mark the post as pending editorial approval.',
+ ),
+ 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
+ 'parent' => "(int) The post ID of the new post's parent.",
+ 'categories' => "(string) Comma separated list of categories (name or id)",
+ 'tags' => "(string) Comma separated list of tags (name or id)",
+ 'format' => get_post_format_strings(),
+ 'comments_open' => '(bool) Should the post be open to comments?',
+ 'pings_open' => '(bool) Should the post be open to comments?',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/posts/1222/',
+
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+
+ 'body' => array(
+ 'title' => 'Hello World (Again)',
+ 'content' => 'Hello. I am an edited post. I was edited by the API',
+ 'tags' => 'tests',
+ 'categories' => 'API'
+ )
+ ),
+
+ 'example_response' => '
+{
+ "ID": 1222,
+ "author": {
+ "ID": 422,
+ "email": false,
+ "name": "Justin Shreve",
+ "URL": "http:\/\/justin.wordpress.com",
+ "avatar_URL": "http:\/\/1.gravatar.com\/avatar\/9ea5b460afb2859968095ad3afe4804b?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/justin"
+ },
+ "date": "2012-04-11T15:53:52+00:00",
+ "modified": "2012-04-11T19:44:35+00:00",
+ "title": "Hello World (Again)",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2012\/04\/11\/hello-world-2\/",
+ "short_URL": "http:\/\/wp.me\/p23HjV-jI",
+ "content": "
Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "excerpt": "
Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "status": "publish",
+ "password": "",
+ "parent": false,
+ "type": "post",
+ "comments_open": true,
+ "pings_open": true,
+ "comment_count": 5,
+ "like_count": 0,
+ "featured_image": "",
+ "format": "standard",
+ "geo": false,
+ "publicize_URLs": [
+
+ ],
+ "tags": {
+ "tests": {
+ "name": "tests",
+ "slug": "tests",
+ "description": "",
+ "post_count": 2,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "categories": {
+ "API": {
+ "name": "API",
+ "slug": "api",
+ "description": "",
+ "post_count": 2,
+ "parent": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/replies\/",
+ "likes": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/likes\/"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Post_Endpoint( array(
+ 'description' => 'Delete a Post',
+ 'group' => 'Posts',
+ 'stat' => 'posts:1:delete',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/posts/%d/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_ID' => '(int) The post ID',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/posts/1222/delete/',
+
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ )
+ ),
+
+ 'example_response' => '
+{
+ "ID": 1222,
+ "author": {
+ "ID": 422,
+ "email": false,
+ "name": "Justin Shreve",
+ "URL": "http:\/\/justin.wordpress.com",
+ "avatar_URL": "http:\/\/1.gravatar.com\/avatar\/9ea5b460afb2859968095ad3afe4804b?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/justin"
+ },
+ "date": "2012-04-11T15:53:52+00:00",
+ "modified": "2012-04-11T19:49:42+00:00",
+ "title": "Hello World (Again)",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2012\/04\/11\/hello-world-2\/",
+ "short_URL": "http:\/\/wp.me\/p23HjV-jI",
+ "content": "
Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "excerpt": "
Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "status": "trash",
+ "password": "",
+ "parent": false,
+ "type": "post",
+ "comments_open": true,
+ "pings_open": true,
+ "comment_count": 5,
+ "like_count": 0,
+ "featured_image": "",
+ "format": "standard",
+ "geo": false,
+ "publicize_URLs": [
+
+ ],
+ "tags": {
+ "tests": {
+ "name": "tests",
+ "slug": "tests",
+ "description": "",
+ "post_count": 1,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/tests\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "categories": {
+ "API": {
+ "name": "API",
+ "slug": "api",
+ "description": "",
+ "post_count": 1,
+ "parent": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/api\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+ }
+ },
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/replies\/",
+ "likes": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222\/likes\/"
+ }
+ }
+}'
+
+) );
+
+/*
+ * Comment endpoints
+ */
+new WPCOM_JSON_API_List_Comments_Endpoint( array(
+ 'description' => 'Return recent Comments',
+ 'group' => 'Comments',
+ 'stat' => 'comments',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/comments/',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/comments/?number=5&pretty=1'
+) );
+
+new WPCOM_JSON_API_List_Comments_Endpoint( array(
+ 'description' => 'Return recent Comments for a Post',
+ 'group' => 'Comments',
+ 'stat' => 'posts:1:replies',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/posts/%d/replies/',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_ID' => '(int) The post ID',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7/replies/?number=5&pretty=1'
+) );
+
+new WPCOM_JSON_API_Get_Comment_Endpoint( array(
+ 'description' => 'Return a single Comment',
+ 'group' => 'Comments',
+ 'stat' => 'comments:1',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/comments/%d',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$comment_ID' => '(int) The comment ID'
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/comments/11/?pretty=1'
+) );
+
+new WPCOM_JSON_API_Update_Comment_Endpoint( array(
+ 'description' => 'Create a Comment on a Post',
+ 'group' => 'Comments',
+ 'stat' => 'posts:1:replies:new',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/posts/%d/replies/new',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$post_ID' => '(int) The post ID'
+ ),
+
+ 'request_format' => array(
+ // explicitly document all input
+ 'content' => '(HTML) The comment text.',
+// @todo Should we open this up to unauthenticated requests too?
+// 'author' => '(author object) The author of the comment.',
+ ),
+
+ 'pass_wpcom_user_details' => true,
+ 'can_use_user_details_instead_of_blog_membership' => true,
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/posts/1222/replies/new/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'content' => 'Your reply is very interesting. This is a reply.'
+ )
+ ),
+
+ 'example_response' => '
+{
+ "ID": 9,
+ "post": {
+ "ID": 1222,
+ "type": "post",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222"
+ },
+ "author": {
+ "ID": 18342963,
+ "email": false,
+ "name": "binarysmash",
+ "URL": "http:\/\/binarysmash.wordpress.com",
+ "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/binarysmash"
+ },
+ "date": "2012-04-11T18:09:41+00:00",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2012\/04\/11\/hello-world-2\/#comment-9",
+ "short_URL": "http:\/\/wp.me\/p23HjV-jI%23comment-9",
+ "content": "
Your reply is very interesting. This is a reply.<\/p>\n",
+ "status": "approved",
+ "parent": {
+ "ID":8,
+ "type": "comment",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/8"
+ },
+ "type": "comment",
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/9",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/9\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "post": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1222",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/9\/replies\/"
+ }
+ }
+}',
+) );
+
+new WPCOM_JSON_API_Update_Comment_Endpoint( array(
+ 'description' => 'Create a Comment as a reply to another Comment',
+ 'group' => 'Comments',
+ 'stat' => 'comments:1:replies:new',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/comments/%d/replies/new',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$comment_ID' => '(int) The comment ID'
+ ),
+
+ 'request_format' => array(
+ 'content' => '(HTML) The comment text.',
+// @todo Should we open this up to unauthenticated requests too?
+// 'author' => '(author object) The author of the comment.',
+ ),
+
+ 'pass_wpcom_user_details' => true,
+ 'can_use_user_details_instead_of_blog_membership' => true,
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/comments/8/replies/new/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'content' => 'This reply is very interesting. This is editing a comment reply via the API.',
+ )
+ ),
+ 'example_response' => '
+{
+ "ID": 13,
+ "post": {
+ "ID": 1,
+ "type": "post",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1"
+ },
+ "author": {
+ "ID": 18342963,
+ "email": false,
+ "name": "binarysmash",
+ "URL": "http:\/\/binarysmash.wordpress.com",
+ "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/binarysmash"
+ },
+ "date": "2012-04-11T20:16:28+00:00",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2011\/12\/13\/hello-world\/#comment-13",
+ "short_URL": "http:\/\/wp.me\/p23HjV-1%23comment-13",
+ "content": "
This reply is very interesting. This is editing a comment reply via the API.<\/p>\n",
+ "status": "approved",
+ "parent": {
+ "ID": 1,
+ "type": "comment",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/1"
+ },
+ "type": "comment",
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "post": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/replies\/"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Comment_Endpoint( array(
+ 'description' => 'Edit a Comment',
+ 'group' => 'Comments',
+ 'stat' => 'comments:1:POST',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/comments/%d',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$comment_ID' => '(int) The comment ID'
+ ),
+
+ 'request_format' => array(
+ 'date' => "(ISO 8601 datetime) The comment's creation time.",
+ 'content' => '(HTML) The comment text.',
+ 'status' => array(
+ 'approved' => 'Approve the comment.',
+ 'unapproved' => 'Remove the comment from public view and send it to the moderation queue.',
+ 'spam' => 'Mark the comment as spam.',
+ 'unspam' => 'Unmark the comment as spam. Will attempt to set it to the previous status.',
+ 'trash' => 'Send a comment to the trash if trashing is enabled (see constant: EMPTY_TRASH_DAYS).',
+ 'untrash' => 'Untrash a comment. Only works when the comment is in the trash.',
+ ),
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/comments/8/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'content' => 'This reply is now edited via the API.',
+ 'status' => 'approved',
+ )
+ ),
+ 'example_response' => '
+{
+ "ID": 13,
+ "post": {
+ "ID": 1,
+ "type": "post",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1"
+ },
+ "author": {
+ "ID": 18342963,
+ "email": false,
+ "name": "binarysmash",
+ "URL": "http:\/\/binarysmash.wordpress.com",
+ "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/binarysmash"
+ },
+ "date": "2012-04-11T20:16:28+00:00",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2011\/12\/13\/hello-world\/#comment-13",
+ "short_URL": "http:\/\/wp.me\/p23HjV-1%23comment-13",
+ "content": "
This reply is very interesting. This is editing a comment reply via the API.<\/p>\n",
+ "status": "approved",
+ "parent": {
+ "ID": 1,
+ "type": "comment",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/1"
+ },
+ "type": "comment",
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "post": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/replies\/"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Comment_Endpoint( array(
+ 'description' => 'Delete a Comment',
+ 'group' => 'Comments',
+ 'stat' => 'comments:1:delete',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/comments/%d/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$comment_ID' => '(int) The comment ID'
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/comments/8/delete/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ )
+ ),
+
+ 'example_response' => '
+{
+ "ID": 13,
+ "post": {
+ "ID": 1,
+ "type": "post",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1"
+ },
+ "author": {
+ "ID": 18342963,
+ "email": false,
+ "name": "binarysmash",
+ "URL": "http:\/\/binarysmash.wordpress.com",
+ "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G",
+ "profile_URL": "http:\/\/en.gravatar.com\/binarysmash"
+ },
+ "date": "2012-04-11T20:16:28+00:00",
+ "URL": "http:\/\/opossumapi.wordpress.com\/2011\/12\/13\/hello-world\/#comment-13",
+ "short_URL": "http:\/\/wp.me\/p23HjV-1%23comment-13",
+ "content": "
This reply is very interesting. This is editing a comment reply via the API.<\/p>\n",
+ "status": "deleted",
+ "parent": {
+ "ID": 1,
+ "type": "comment",
+ "link": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/1"
+ },
+ "type": "comment",
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183",
+ "post": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/posts\/1",
+ "replies": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/comments\/13\/replies\/"
+ }
+ }
+}'
+
+) );
+
+/**
+ * Taxonomy Management Endpoints
+ */
+new WPCOM_JSON_API_Get_Taxonomy_Endpoint( array(
+ 'description' => 'Returns information on a single Category',
+ 'group' => 'Taxonomy',
+ 'stat' => 'categories:1',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/categories/slug:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$category' => '(string) The category slug'
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/categories/slug:community?pretty=1'
+) );
+
+new WPCOM_JSON_API_Get_Taxonomy_Endpoint( array(
+ 'description' => 'Returns information on a single Tag',
+ 'group' => 'Taxonomy',
+ 'stat' => 'tags:1',
+
+ 'method' => 'GET',
+ 'path' => '/sites/%s/tags/slug:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$tag' => '(string) The tag slug'
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/tags/slug:wordpresscom?pretty=1'
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Create a new Category',
+ 'group' => 'Taxonomy',
+ 'stat' => 'categories:new',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/categories/new',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'request_format' => array(
+ 'name' => '(string) Name of the category',
+ 'description' => '(string) A description of the category',
+ 'parent' => '(id) ID of the parent category',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/categories/new/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'name' => 'Puppies',
+ )
+ ),
+ 'example_response' => '
+{
+ "name": "Puppies",
+ "slug": "puppies",
+ "description": "",
+ "post_count": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/puppies",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/puppies\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Create a new Tag',
+ 'group' => 'Taxonomy',
+ 'stat' => 'tags:new',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/tags/new',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+
+ 'request_format' => array(
+ 'name' => '(string) Name of the tag',
+ 'description' => '(string) A description of the tag',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/tags/new/',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'name' => 'Kitties'
+ )
+ ),
+ 'example_response' => '
+{
+ "name": "Kitties",
+ "slug": "kitties",
+ "description": "",
+ "post_count": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/kitties",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/kitties\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Edit a Tag',
+ 'group' => 'Taxonomy',
+ 'stat' => 'tags:1:POST',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/tags/slug:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$tag' => '(string) The tag slug',
+ ),
+
+ 'request_format' => array(
+ 'name' => '(string) Name of the tag',
+ 'description' => '(string) A description of the tag',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/tags/slug:testing-tag',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'description' => 'Kitties are awesome!'
+ )
+ ),
+ 'example_response' => '
+{
+ "name": "testing tag",
+ "slug": "testing-tag",
+ "description": "Kitties are awesome!",
+ "post_count": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/testing-tag",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/tags\/testing-tag\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Edit a Category',
+ 'group' => 'Taxonomy',
+ 'stat' => 'categories:1:POST',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/categories/slug:%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$category' => '(string) The category slug',
+ ),
+
+ 'request_format' => array(
+ 'name' => '(string) Name of the category',
+ 'description' => '(string) A description of the category',
+ 'parent' => '(id) ID of the parent category',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/categories/slug:testing-category',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'description' => 'Puppies are great!'
+ )
+ ),
+ 'example_response' => '
+{
+ "name": "testing category",
+ "slug": "testing-category",
+ "description": "Puppies are great!",
+ "post_count": 0,
+ "parent": 0,
+ "meta": {
+ "links": {
+ "self": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/testing-category",
+ "help": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183\/categories\/testing-category\/help",
+ "site": "https:\/\/public-api.wordpress.com\/rest\/v1\/sites\/30434183"
+ }
+ }
+}'
+
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Delete a Category',
+ 'group' => 'Taxonomy',
+ 'stat' => 'categories:1:delete',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/categories/slug:%s/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$category' => '(string) The category slug',
+ ),
+ 'response_format' => array(
+ 'slug' => '(string) The slug of the deleted category',
+ 'success' => '(bool) Was the operation successful?',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/categories/slug:some-category-name/delete',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_response' => '{
+ "slug": "some-category-name",
+ "success": "true"
+}'
+) );
+
+new WPCOM_JSON_API_Update_Taxonomy_Endpoint( array(
+ 'description' => 'Delete a Tag',
+ 'group' => 'Taxonomy',
+ 'stat' => 'tags:1:delete',
+
+ 'method' => 'POST',
+ 'path' => '/sites/%s/tags/slug:%s/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$tag' => '(string) The tag slug',
+ ),
+ 'response_format' => array(
+ 'slug' => '(string) The slug of the deleted tag',
+ 'success' => '(bool) Was the operation successful?',
+ ),
+
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/30434183/tags/slug:some-tag-name/delete',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_response' => '{
+ "slug": "some-tag-name",
+ "success": "true"
+}'
+) );
diff --git a/plugins/jetpack/class.json-api.php b/plugins/jetpack/class.json-api.php
new file mode 100644
index 00000000..5a322eb6
--- /dev/null
+++ b/plugins/jetpack/class.json-api.php
@@ -0,0 +1,443 @@
+endpoints[$endpoint->path] ) ) {
+ $this->endpoints[$endpoint->path] = array();
+ }
+ $this->endpoints[$endpoint->path][$endpoint->method] = $endpoint;
+ }
+
+ static function is_truthy( $value ) {
+ switch ( strtolower( (string) $value ) ) {
+ case '1' :
+ case 't' :
+ case 'true' :
+ return true;
+ }
+
+ return false;
+ }
+
+ function __construct() {
+ $args = func_get_args();
+ call_user_func_array( array( $this, 'setup_inputs' ), $args );
+ }
+
+ function setup_inputs( $method = null, $url = null, $post_body = null ) {
+ if ( is_null( $method ) ) {
+ $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
+ } else {
+ $this->method = strtoupper( $method );
+ }
+ if ( is_null( $url ) ) {
+ $this->url = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ } else {
+ $this->url = $url;
+ }
+
+ $parsed = parse_url( $this->url );
+ $this->path = $parsed['path'];
+
+ if ( !empty( $parsed['query'] ) ) {
+ wp_parse_str( $parsed['query'], $this->query );
+ }
+
+ if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
+ $this->accept = $_SERVER['HTTP_ACCEPT'];
+ }
+
+ if ( 'POST' == $this->method ) {
+ if ( is_null( $post_body ) ) {
+ $this->post_body = file_get_contents( 'php://input' );
+
+ if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
+ $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
+ } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
+ $this->content_type = $_SERVER['CONTENT_TYPE'] ;
+ } elseif ( '{' === $this->post_body[0] ) {
+ $this->content_type = 'application/json';
+ } else {
+ $this->content_type = 'application/x-www-form-urlencoded';
+ }
+
+ if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
+ $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
+ $this->files = $_FILES;
+ $this->content_type = 'multipart/form-data';
+ }
+ } else {
+ $this->post_body = $post_body;
+ $this->content_type = '{' === $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
+ }
+ } else {
+ $this->post_body = null;
+ $this->content_type = null;
+ }
+
+ $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
+ }
+
+ function initialize() {
+ $this->token_details['blog_id'] = Jetpack::get_option( 'id' );
+ }
+
+ function serve( $exit = true ) {
+ $this->exit = (bool) $exit;
+
+ add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
+
+ add_filter( 'user_can_richedit', '__return_true' );
+
+ add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
+
+ $this->initialize();
+
+ // Normalize path
+ $this->path = untrailingslashit( $this->path );
+ $this->path = preg_replace( '#^/rest/v1#', '', $this->path );
+
+ $allowed_methods = array( 'GET', 'POST' );
+ $four_oh_five = false;
+
+ $is_help = preg_match( '#/help/?$#i', $this->path );
+ $matching_endpoints = array();
+
+ if ( $is_help ) {
+ $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
+ // Show help for all matching endpoints regardless of method
+ $methods = $allowed_methods;
+ $find_all_matching_endpoints = true;
+ // How deep to truncate each endpoint's path to see if it matches this help request
+ $depth = substr_count( $this->path, '/' ) + 1;
+ if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
+ $help_content_type = 'json';
+ } else {
+ $help_content_type = 'html';
+ }
+ } else {
+ if ( in_array( $this->method, $allowed_methods ) ) {
+ // Only serve requested method
+ $methods = array( $this->method );
+ $find_all_matching_endpoints = false;
+ } else {
+ // We don't allow this requested method - find matching endpoints and send 405
+ $methods = $allowed_methods;
+ $find_all_matching_endpoints = true;
+ $four_oh_five = true;
+ }
+ }
+
+ // Find which endpoint to serve
+ $found = false;
+ foreach ( $this->endpoints as $endpoint_path => $endpoints_by_method ) {
+ foreach ( $methods as $method ) {
+ if ( !isset( $endpoints_by_method[$method] ) ) {
+ continue;
+ }
+
+ // Normalize
+ $endpoint_path = untrailingslashit( $endpoint_path );
+ if ( $is_help ) {
+ // Truncate path at help depth
+ $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
+ }
+
+ // Generate regular expression from sprintf()
+ $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
+
+ if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
+ // This endpoint does not match the requested path.
+ continue;
+ }
+
+ $found = true;
+
+ if ( $find_all_matching_endpoints ) {
+ $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
+ } else {
+ // The method parameters are now in $path_pieces
+ $endpoint = $endpoints_by_method[$method];
+ break 2;
+ }
+ }
+ }
+
+ if ( !$found ) {
+ return $this->output( 404, '', 'text/plain' );
+ }
+
+ if ( $four_oh_five ) {
+ $allowed_methods = array();
+ foreach ( $matching_endpoints as $matching_endpoint ) {
+ $allowed_methods[] = $matching_endpoint[0]->method;
+ }
+
+ header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
+ return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
+ }
+
+ if ( $is_help ) {
+ do_action( 'wpcom_json_api_output', 'help' );
+ if ( 'json' === $help_content_type ) {
+ $docs = array();
+ foreach ( $matching_endpoints as $matching_endpoint ) {
+ if ( !$matching_endpoint[0]->in_testing || WPCOM_JSON_API__DEBUG )
+ $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
+ }
+ return $this->output( 200, $docs );
+ } else {
+ status_header( 200 );
+ foreach ( $matching_endpoints as $matching_endpoint ) {
+ if ( !$matching_endpoint[0]->in_testing || WPCOM_JSON_API__DEBUG )
+ call_user_func( array( $matching_endpoint[0], 'document' ) );
+ }
+ }
+ exit;
+ }
+
+ if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
+ return $this->output( 404, '', 'text/plain' );
+ }
+
+ do_action( 'wpcom_json_api_output', $endpoint->stat );
+
+ $response = $this->process_request( $endpoint, $path_pieces );
+
+ if ( !$response ) {
+ return $this->output( 500, '', 'text/plain' );
+ } elseif ( is_wp_error( $response ) ) {
+ $status_code = $response->get_error_data();
+ if ( !$status_code ) {
+ $status_code = 400;
+ }
+ $response = array(
+ 'error' => $response->get_error_code(),
+ 'message' => $response->get_error_message(),
+ );
+ return $this->output( $status_code, $response );
+ }
+
+ return $this->output( 200, $response );
+ }
+
+ function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
+ return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
+ }
+
+ function output( $status_code, $response = null, $content_type = 'application/json' ) {
+ if ( is_null( $response ) ) {
+ $response = new stdClass;
+ }
+
+ if ( 'text/plain' === $content_type ) {
+ status_header( (int) $status_code );
+ header( 'Content-Type: text/plain' );
+ echo $response;
+ if ( $this->exit ) {
+ exit;
+ }
+
+ return $content_type;
+ }
+
+ if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
+ $response = array(
+ 'code' => (int) $status_code,
+ 'headers' => array(
+ array(
+ 'name' => 'Content-Type',
+ 'value' => $content_type,
+ ),
+ ),
+ 'body' => $response,
+ );
+ $status_code = 200;
+ $content_type = 'application/json';
+ }
+
+ status_header( (int) $status_code );
+ header( "Content-Type: $content_type" );
+ if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
+ $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
+ } else {
+ $callback = false;
+ }
+
+ if ( $callback ) {
+ echo "$callback(";
+ }
+ echo $this->json_encode( $response );
+ if ( $callback ) {
+ echo ");";
+ }
+
+ if ( $this->exit ) {
+ exit;
+ }
+
+ return $content_type;
+ }
+
+ function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
+ if ( $original_scheme ) {
+ return $url;
+ }
+
+ return preg_replace( '#^https:#', 'http:', $url );
+ }
+
+ function comment_edit_pre( $comment_content ) {
+ return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
+ }
+
+ function json_encode( $data ) {
+ return json_encode( $data );
+ }
+
+ function ends_with( $haystack, $needle ) {
+ return $needle === substr( $haystack, -strlen( $needle ) );
+ }
+
+ // Returns the site's blog_id in the WP.com ecosystem
+ function get_blog_id_for_output() {
+ return $this->token_details['blog_id'];
+ }
+
+ // Returns the site's local blog_id
+ function get_blog_id( $blog_id ) {
+ return $GLOBALS['blog_id'];
+ }
+
+ function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
+ if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
+ }
+
+ return $blog_id;
+ }
+
+ function post_like_count( $blog_id, $post_id ) {
+ return 0;
+ }
+
+ function get_avatar_url( $email ) {
+ add_filter( 'pre_option_show_avatars', '__return_true', 999 );
+ $_SERVER['HTTPS'] = 'off';
+
+ $avatar_img_element = get_avatar( $email, 96, '' );
+
+ if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) {
+ $return = '';
+ } elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) {
+ $return = '';
+ } else {
+ $return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) );
+ }
+
+ remove_filter( 'pre_option_show_avatars', '__return_true', 999 );
+ if ( '--UNset--' === $this->_server_https ) {
+ unset( $_SERVER['HTTPS'] );
+ } else {
+ $_SERVER['HTTPS'] = $this->_server_https;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Traps `wp_die()` calls and outputs a JSON response instead.
+ * The result is always output, never returned.
+ *
+ * @param string|null $error_code. Call with string to start the trapping. Call with null to stop.
+ */
+ function trap_wp_die( $error_code = null ) {
+ // Stop trapping
+ if ( is_null( $error_code ) ) {
+ $this->trapped_error = null;
+ remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
+ return;
+ }
+
+ // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
+ return;
+ }
+ } else {
+ if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
+ return;
+ }
+ }
+
+ // Start trapping
+ $this->trapped_error = array(
+ 'status' => 500,
+ 'code' => $error_code,
+ 'message' => '',
+ );
+
+ add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
+ }
+
+ function wp_die_handler_callback() {
+ return array( $this, 'wp_die_handler' );
+ }
+
+ function wp_die_handler( $message, $title = '', $args = array() ) {
+ $args = wp_parse_args( $args, array(
+ 'response' => 500,
+ ) );
+
+ if ( $title ) {
+ $message = "$title: $message";
+ }
+
+ $this->trapped_error['status'] = $args['response'];
+ $this->trapped_error['message'] = wp_kses( $message, array() );
+
+ // We still want to exit so that code execution stops where it should.
+ // Attach the JSON output to WordPress' shutdown handler
+ add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
+ exit;
+ }
+
+ function output_trapped_error() {
+ $this->exit = false; // We're already exiting once. Don't do it twice.
+ $this->output( $this->trapped_error['status'], (object) array(
+ 'error' => $this->trapped_error['code'],
+ 'message' => $this->trapped_error['message'],
+ ) );
+ }
+}
diff --git a/plugins/jetpack/class.photon.php b/plugins/jetpack/class.photon.php
new file mode 100644
index 00000000..c0456885
--- /dev/null
+++ b/plugins/jetpack/class.photon.php
@@ -0,0 +1,554 @@
+setup();
+ }
+
+ return self::$__instance;
+ }
+
+ /**
+ * Silence is golden.
+ */
+ private function __construct() {}
+
+ /**
+ * Register actions and filters, but only if basic Photon functions are available.
+ * The basic functions are found in ./functions.photon.php.
+ *
+ * @uses add_action, add_filter
+ * @return null
+ */
+ private function setup() {
+ // Display warning if site is private
+ add_action( 'jetpack_activate_module_photon', array( $this, 'action_jetpack_activate_module_photon' ) );
+
+ if ( ! function_exists( 'jetpack_photon_url' ) )
+ return;
+
+ // Images in post content
+ add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 );
+
+ // Core image retrieval
+ add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
+
+ // og:image URL
+ add_filter( 'jetpack_open_graph_tags', array( $this, 'filter_open_graph_tags' ), 10, 2 );
+
+ // Helpers for maniuplated images
+ add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
+ }
+
+ /**
+ * Check if site is private and warn user if it is
+ *
+ * @uses Jetpack::check_privacy
+ * @action jetpack_activate_module_photon
+ * @return null
+ */
+ public function action_jetpack_activate_module_photon() {
+ Jetpack::check_privacy( __FILE__ );
+ }
+
+ /**
+ ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
+ **/
+
+ /**
+ * Match all images and any relevant tags in a block of HTML.
+ *
+ * @param string $content Some HTML.
+ * @return array An array of $images matches, where $images[0] is
+ * an array of full matches, and the link_url, img_tag,
+ * and img_url keys are arrays of those matches.
+ */
+ public static function parse_images_from_html( $content ) {
+ $images = array();
+
+ if ( preg_match_all( '#(?: ]+?href=["|\'](?P[^\s]+?)["|\'][^>]*?>\s*)?(?P ]+?src=["|\'](?P[^\s]+?)["|\'].*?>){1}(?:\s* )?#is', $content, $images ) ) {
+ foreach ( $images as $key => $unused ) {
+ // Simplify the output as much as possible, mostly for confirming test results.
+ if ( is_numeric( $key ) && $key > 0 )
+ unset( $images[$key] );
+ }
+
+ return $images;
+ }
+
+ return array();
+ }
+
+ /**
+ * Try to determine height and width from strings WP appends to resized image filenames.
+ *
+ * @param string $src The image URL.
+ * @return array An array consisting of width and height.
+ */
+ public static function parse_dimensions_from_filename( $src ) {
+ $width_height_string = array();
+
+ if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
+ $width = (int) $width_height_string[1];
+ $height = (int) $width_height_string[2];
+
+ if ( $width && $height )
+ return array( $width, $height );
+ }
+
+ return array( false, false );
+ }
+
+ /**
+ * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
+ *
+ * @param string $content
+ * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
+ * @filter the_content
+ * @return string
+ */
+ public static function filter_the_content( $content ) {
+ $images = Jetpack_Photon::parse_images_from_html( $content );
+
+ if ( ! empty( $images ) ) {
+ global $content_width;
+
+ $image_sizes = self::image_sizes();
+ $upload_dir = wp_upload_dir();
+
+ foreach ( $images[0] as $index => $tag ) {
+ // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
+ $transform = 'resize';
+
+ // Start with a clean attachment ID each time
+ $attachment_id = false;
+
+ // Flag if we need to munge a fullsize URL
+ $fullsize_url = false;
+
+ // Identify image source
+ $src = $src_orig = $images['img_url'][ $index ];
+
+ // Allow specific images to be skipped
+ if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
+ continue;
+
+ // Support Automattic's Lazy Load plugin
+ // Can't modify $tag yet as we need unadulterated version later
+ if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
+ $placeholder_src = $placeholder_src_orig = $src;
+ $src = $src_orig = $lazy_load_src[1];
+ }
+
+ // Check if image URL should be used with Photon
+ if ( self::validate_image_url( $src ) ) {
+ // Find the width and height attributes
+ $width = $height = false;
+
+ // First, check the image tag
+ if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
+ $width = $width_string[1];
+
+ if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
+ $height = $height_string[1];
+
+ // Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
+ if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
+ $width = $height = false;
+
+ // Detect WP registered image size from HTML class
+ if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
+ $size = array_pop( $size );
+
+ if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
+ $width = (int) $image_sizes[ $size ]['width'];
+ $height = (int) $image_sizes[ $size ]['height'];
+ $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
+ }
+ } else {
+ unset( $size );
+ }
+
+ // WP Attachment ID, if uploaded to this site
+ if ( preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) && ( 0 === strpos( $src, $upload_dir['baseurl'] ) || apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) ) ) ) {
+ $attachment_id = intval( array_pop( $attachment_id ) );
+
+ if ( $attachment_id ) {
+ $attachment = get_post( $attachment_id );
+
+ // Basic check on returned post object
+ if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
+ $src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
+
+ if ( self::validate_image_url( $src_per_wp[0] ) ) {
+ $src = $src_per_wp[0];
+ $fullsize_url = true;
+
+ // Prevent image distortion if a detected dimension exceeds the image's natural dimensions
+ if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
+ $width = false == $width ? false : min( $width, $src_per_wp[1] );
+ $height = false == $height ? false : min( $height, $src_per_wp[2] );
+ }
+
+ // If no width and height are found, max out at source image's natural dimensions
+ // Otherwise, respect registered image sizes' cropping setting
+ if ( false == $width && false == $height ) {
+ $width = $src_per_wp[1];
+ $height = $src_per_wp[2];
+ $transform = 'fit';
+ } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
+ $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
+ }
+ }
+ } else {
+ unset( $attachment_id );
+ unset( $attachment );
+ }
+ }
+ }
+
+ // If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
+ if ( false === $width && false === $height ) {
+ list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
+ }
+
+ // If width is available, constrain to $content_width
+ if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
+ if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
+ $height = round( ( $content_width * $height ) / $width );
+ $width = $content_width;
+ } elseif ( $width > $content_width ) {
+ $width = $content_width;
+ }
+ }
+
+ // Set a width if none is found and $content_width is available
+ // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
+ if ( false === $width && is_numeric( $content_width ) ) {
+ $width = (int) $content_width;
+
+ if ( false !== $height )
+ $transform = 'fit';
+ }
+
+ // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
+ if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
+ $fullsize_url = true;
+
+ // Build URL, first removing WP's resized string so we pass the original image to Photon
+ if ( ! $fullsize_url && preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) )
+ $src = str_replace( $src_parts[1], '', $src );
+
+ // Build array of Photon args and expose to filter before passing to Photon URL function
+ $args = array();
+
+ if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
+ $args[ $transform ] = $width . ',' . $height;
+ elseif ( false !== $width )
+ $args['w'] = $width;
+ elseif ( false !== $height )
+ $args['h'] = $height;
+
+ $args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
+
+ $photon_url = jetpack_photon_url( $src, $args );
+
+ // Modify image tag if Photon function provides a URL
+ // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version.
+ if ( $src != $photon_url ) {
+ $new_tag = $tag;
+
+ // If present, replace the link href with a Photoned URL for the full-size image.
+ if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
+ $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
+
+ // Supplant the original source value with our Photon URL
+ $photon_url = esc_url( $photon_url );
+ $new_tag = str_replace( $src_orig, $photon_url, $new_tag );
+
+ // If Lazy Load is in use, pass placeholder image through Photon
+ if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
+ $placeholder_src = jetpack_photon_url( $placeholder_src );
+
+ if ( $placeholder_src != $placeholder_src_orig )
+ $new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag );
+
+ unset( $placeholder_src );
+ }
+
+ // Remove the width and height arguments from the tag to prevent distortion
+ $new_tag = preg_replace( '#(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
+
+ // Tag an image for dimension checking
+ $new_tag = preg_replace( '#(\s?/)?>(
)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
+
+ // Replace original tag with modified version
+ $content = str_replace( $tag, $new_tag, $content );
+ }
+ }
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ ** CORE IMAGE RETRIEVAL
+ **/
+
+ /**
+ * Filter post thumbnail image retrieval, passing images through Photon
+ *
+ * @param string|bool $image
+ * @param int $attachment_id
+ * @param string|array $size
+ * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
+ * @filter image_downsize
+ * @return string|bool
+ */
+ public function filter_image_downsize( $image, $attachment_id, $size ) {
+ // Don't foul up the admin side of things, and provide plugins a way of preventing Photon from being applied to images.
+ if ( is_admin() || apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) )
+ return $image;
+
+ // Get the image URL and proceed with Photon-ification if successful
+ $image_url = wp_get_attachment_url( $attachment_id );
+
+ if ( $image_url ) {
+ // Check if image URL should be used with Photon
+ if ( ! self::validate_image_url( $image_url ) )
+ return $image;
+
+ // If an image is requested with a size known to WordPress, use that size's settings with Photon
+ if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
+ $image_args = self::image_sizes();
+ $image_args = $image_args[ $size ];
+
+ $photon_args = array();
+
+ // `full` is a special case in WP
+ // To ensure filter receives consistent data regardless of requested size, `$image_args` is overridden with dimensions of original image.
+ if ( 'full' == $size ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+
+ // 'crop' is true so Photon's `resize` method is used
+ $image_args = array(
+ 'width' => $image_meta['width'],
+ 'height' => $image_meta['height'],
+ 'crop' => true
+ );
+ }
+
+ // Expose determined arguments to a filter before passing to Photon
+ $transform = $image_args['crop'] ? 'resize' : 'fit';
+
+ // Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
+ if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
+ if ( 0 == $image_args['width'] && 0 < $image_args['height'] )
+ $photon_args['h'] = $image_args['height'];
+ elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] )
+ $photon_args['w'] = $image_args['width'];
+ } else {
+ $photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
+ }
+
+ $photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
+
+ // Generate Photon URL
+ $image = array(
+ jetpack_photon_url( $image_url, $photon_args ),
+ false,
+ false
+ );
+ } elseif ( is_array( $size ) ) {
+ // Pull width and height values from the provided array, if possible
+ $width = isset( $size[0] ) ? (int) $size[0] : false;
+ $height = isset( $size[1] ) ? (int) $size[1] : false;
+
+ // Don't bother if necessary parameters aren't passed.
+ if ( ! $width || ! $height )
+ return $image;
+
+ // Expose arguments to a filter before passing to Photon
+ $photon_args = array(
+ 'fit' => $width . ',' . $height
+ );
+
+ $photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
+
+ // Generate Photon URL
+ $image = array(
+ jetpack_photon_url( $image_url, $photon_args ),
+ false,
+ false
+ );
+ }
+ }
+
+ return $image;
+ }
+
+ /**
+ ** GENERAL FUNCTIONS
+ **/
+
+ /**
+ * Ensure image URL is valid for Photon.
+ * Though Photon functions address some of the URL issues, we should avoid unnecessary processing if we know early on that the image isn't supported.
+ *
+ * @param string $url
+ * @uses wp_parse_args
+ * @return bool
+ */
+ protected static function validate_image_url( $url ) {
+ $parsed_url = @parse_url( $url );
+
+ if ( ! $parsed_url )
+ return false;
+
+ // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
+ $url_info = wp_parse_args( $parsed_url, array(
+ 'scheme' => null,
+ 'host' => null,
+ 'port' => null,
+ 'path' => null
+ ) );
+
+ // Bail if scheme isn't http or port is set that isn't port 80
+ if ( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) )
+ return false;
+
+ // Bail if no host is found
+ if ( is_null( $url_info['host'] ) )
+ return false;
+
+ // Bail if the image alredy went through Photon
+ if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
+ return false;
+
+ // Bail if no path is found
+ if ( is_null( $url_info['path'] ) )
+ return false;
+
+ // Ensure image extension is acceptable
+ if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
+ return false;
+
+ // If we got this far, we should have an acceptable image URL
+ return true;
+ }
+
+ /**
+ * Provide an array of available image sizes and corresponding dimensions.
+ * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
+ *
+ * @global $wp_additional_image_sizes
+ * @uses get_option
+ * @return array
+ */
+ protected static function image_sizes() {
+ if ( null == self::$image_sizes ) {
+ global $_wp_additional_image_sizes;
+
+ // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
+ $images = array(
+ 'thumb' => array(
+ 'width' => intval( get_option( 'thumbnail_size_w' ) ),
+ 'height' => intval( get_option( 'thumbnail_size_h' ) ),
+ 'crop' => (bool) get_option( 'thumbnail_crop' )
+ ),
+ 'medium' => array(
+ 'width' => intval( get_option( 'medium_size_w' ) ),
+ 'height' => intval( get_option( 'medium_size_h' ) ),
+ 'crop' => false
+ ),
+ 'large' => array(
+ 'width' => intval( get_option( 'large_size_w' ) ),
+ 'height' => intval( get_option( 'large_size_h' ) ),
+ 'crop' => false
+ ),
+ 'full' => array(
+ 'width' => null,
+ 'height' => null,
+ 'crop' => false
+ )
+ );
+
+ // Compatibility mapping as found in wp-includes/media.php
+ $images['thumbnail'] = $images['thumb'];
+
+ // Update class variable, merging in $_wp_additional_image_sizes if any are set
+ if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
+ self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
+ else
+ self::$image_sizes = $images;
+ }
+
+ return is_array( self::$image_sizes ) ? self::$image_sizes : array();
+ }
+
+ /**
+ * Pass og:image URLs through Photon
+ *
+ * @param array $tags
+ * @param array $parameters
+ * @uses jetpack_photon_url
+ * @return array
+ */
+ function filter_open_graph_tags( $tags, $parameters ) {
+ if ( empty( $tags['og:image'] ) ) {
+ return $tags;
+ }
+
+ $photon_args = array(
+ 'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
+ );
+
+ if ( is_array( $tags['og:image'] ) ) {
+ $images = array();
+ foreach ( $tags['og:image'] as $image ) {
+ $images[] = jetpack_photon_url( $image, $photon_args );
+ }
+ $tags['og:image'] = $images;
+ } else {
+ $tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Enqueue Photon helper script
+ *
+ * @uses wp_enqueue_script, plugins_url
+ * @action wp_enqueue_script
+ * @return null
+ */
+ public function action_wp_enqueue_scripts() {
+ wp_enqueue_script( 'jetpack-photon', plugins_url( 'modules/photon/photon.js', __FILE__ ), array( 'jquery' ), 20130122, true );
+ }
+}
diff --git a/plugins/jetpack/functions.compat.php b/plugins/jetpack/functions.compat.php
new file mode 100644
index 00000000..5e776c07
--- /dev/null
+++ b/plugins/jetpack/functions.compat.php
@@ -0,0 +1,15 @@
+gallery_types = apply_filters( 'jetpack_gallery_types', array() );
+
+ // Enqueue the media UI only if needed.
+ if ( ! empty( $this->gallery_types ) ) {
+ add_action( 'wp_enqueue_media', array( $this, 'wp_enqueue_media' ) );
+ add_action( 'print_media_templates', array( $this, 'print_media_templates' ) );
+ }
+ }
+
+ /**
+ * Registers/enqueues the gallery settings admin js.
+ */
+ function wp_enqueue_media() {
+ if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) )
+ wp_register_script( 'jetpack-gallery-settings', plugins_url( 'gallery-settings/gallery-settings.js', __FILE__ ), array( 'media-views' ), '20121225' );
+
+ wp_enqueue_script( 'jetpack-gallery-settings' );
+ }
+
+ /**
+ * Outputs a view template which can be used with wp.media.template
+ */
+ function print_media_templates() {
+ ?>
+
+ \n";
+ $tags = array();
+
+ $image_width = absint( apply_filters( 'jetpack_open_graph_image_width', 200 ) );
+ $image_height = absint( apply_filters( 'jetpack_open_graph_image_height', 200 ) );
+ $description_length = 197;
+
+ if ( is_home() || is_front_page() ) {
+ $site_type = get_option( 'open_graph_protocol_site_type' );
+ $tags['og:type'] = ! empty( $site_type ) ? $site_type : 'blog';
+ $tags['og:title'] = get_bloginfo( 'name' );
+ $tags['og:description'] = get_bloginfo( 'description' );
+
+ $front_page_id = get_option( 'page_for_posts' );
+ if ( $front_page_id && is_home() )
+ $tags['og:url'] = get_permalink( $front_page_id );
+ else
+ $tags['og:url'] = home_url( '/' );
+
+ // Associate a blog's root path with one or more Facebook accounts
+ $facebook_admins = get_option( 'facebook_admins' );
+ if ( ! empty( $facebook_admins ) )
+ $tags['fb:admins'] = $facebook_admins;
+
+ } else if ( is_author() ) {
+ $tags['og:type'] = 'author';
+
+ $author = get_queried_object();
+
+ $tags['og:title'] = $author->display_name;
+ $tags['og:url'] = get_author_posts_url( $author->ID );
+ $tags['og:description'] = $author->description;
+
+ } else if ( is_singular() ) {
+ global $post;
+ $data = $post; // so that we don't accidentally explode the global
+
+ $tags['og:type'] = 'article';
+ $tags['og:title'] = empty( $data->post_title ) ? ' ' : wp_kses( $data->post_title, array() ) ;
+ $tags['og:url'] = get_permalink( $data->ID );
+ if ( !post_password_required() )
+ $tags['og:description'] = ! empty( $data->post_excerpt ) ? strip_shortcodes( wp_kses( $data->post_excerpt, array() ) ) : wp_trim_words( strip_shortcodes( wp_kses( $data->post_content, array() ) ) );
+ $tags['og:description'] = empty( $tags['og:description'] ) ? ' ' : $tags['og:description'];
+ }
+
+ // Re-enable widont if we had disabled it
+ if ( $disable_widont )
+ add_filter( 'the_title', 'widont' );
+
+ if ( empty( $tags ) )
+ return;
+
+ $tags['og:site_name'] = get_bloginfo( 'name' );
+ $tags['og:image'] = jetpack_og_get_image( $image_width, $image_height );
+
+ // Facebook whines if you give it an empty title
+ if ( empty( $tags['og:title'] ) )
+ $tags['og:title'] = __( '(no title)', 'jetpack' );
+
+ // Shorten the description if it's too long
+ $tags['og:description'] = strlen( $tags['og:description'] ) > $description_length ? mb_substr( $tags['og:description'], 0, $description_length ) . '...' : $tags['og:description'];
+
+ // Add any additional tags here, or modify what we've come up with
+ $tags = apply_filters( 'jetpack_open_graph_tags', $tags, compact( 'image_width', 'image_height' ) );
+
+ foreach ( (array) $tags as $tag_property => $tag_content ) {
+ // to accomodate multiple images
+ $tag_content = (array) $tag_content;
+ $tag_content = array_unique( $tag_content );
+
+ foreach ( $tag_content as $tag_content_single ) {
+ if ( empty( $tag_content_single ) )
+ continue; // Don't ever output empty tags
+ $og_tag = sprintf( ' ', esc_attr( $tag_property ), esc_attr( $tag_content_single ) );
+ $og_output .= apply_filters( 'jetpack_open_graph_output', $og_tag );
+ $og_output .= "\n";
+ }
+ }
+
+ echo $og_output;
+}
+
+function jetpack_og_get_image( $width = 50, $height = 50, $max_images = 4 ) { // Facebook requires thumbnails to be a minimum of 50x50
+ $image = '';
+
+ if ( is_singular() && !is_home() && !is_front_page() ) {
+ global $post;
+ $image = '';
+
+ // Attempt to find something good for this post using our generalized PostImages code
+ if ( class_exists( 'Jetpack_PostImages' ) ) {
+ $post_images = Jetpack_PostImages::get_images( $post->ID, array( 'width' => $width, 'height' => $height ) );
+ if ( $post_images && !is_wp_error( $post_images ) ) {
+ $image = array();
+ foreach ( (array) $post_images as $post_image ) {
+ $image[] = $post_image['src'];
+ }
+ }
+ }
+ } else if ( is_author() ) {
+ $author = get_queried_object();
+ if ( function_exists( 'get_avatar_url' ) ) {
+ $avatar = get_avatar_url( $author->user_email, $width );
+
+ if ( ! empty( $avatar ) ) {
+ if ( is_array( $avatar ) )
+ $image = $avatar[0];
+ else
+ $image = $avatar;
+ }
+ }
+ else {
+ $has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
+ if ( !$has_filter ) {
+ add_filter( 'pre_option_show_avatars', '__return_true' );
+ }
+ $avatar = get_avatar( $author->user_email, $width );
+ if ( !$has_filter ) {
+ remove_filter( 'pre_option_show_avatars', '__return_true' );
+ }
+
+ if ( !empty( $avatar ) && !is_wp_error( $avatar ) ) {
+ if ( preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $matches ) );
+ $image = wp_specialchars_decode( $matches[1], ENT_QUOTES );
+ }
+ }
+ }
+
+ // Fallback to Blavatar if available
+ if ( function_exists( 'blavatar_domain' ) ) {
+ $blavatar_domain = blavatar_domain( site_url() );
+ if ( empty( $image ) && blavatar_exists( $blavatar_domain ) )
+ $image = blavatar_url( $blavatar_domain, 'img', $width );
+ }
+
+ return $image;
+}
diff --git a/plugins/jetpack/functions.photon.php b/plugins/jetpack/functions.photon.php
new file mode 100644
index 00000000..fc276b31
--- /dev/null
+++ b/plugins/jetpack/functions.photon.php
@@ -0,0 +1,160 @@
+ '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456)
+ * @return string The raw final URL. You should run this through esc_url() before displaying it.
+ */
+function jetpack_photon_url( $image_url, $args = array(), $scheme = null ) {
+ $image_url = trim( $image_url );
+
+ $image_url = apply_filters( 'jetpack_photon_pre_image_url', $image_url, $args, $scheme );
+ $args = apply_filters( 'jetpack_photon_pre_args', $args, $image_url, $scheme );
+
+ if ( empty( $image_url ) )
+ return $image_url;
+
+ $image_url_parts = @parse_url( $image_url );
+
+ // Unable to parse
+ if ( ! is_array( $image_url_parts ) || empty( $image_url_parts['host'] ) || empty( $image_url_parts['path'] ) )
+ return $image_url;
+
+ if ( is_array( $args ) ){
+ // Convert values that are arrays into strings
+ foreach ( $args as $arg => $value ) {
+ if ( is_array( $value ) ) {
+ $args[$arg] = implode( ',', $value );
+ }
+ }
+
+ // Encode values
+ // See http://core.trac.wordpress.org/ticket/17923
+ $args = rawurlencode_deep( $args );
+ }
+
+ // You can't run a Photon URL through Photon again because query strings are stripped.
+ // So if the image is already a Photon URL, append the new arguments to the existing URL.
+ if ( in_array( $image_url_parts['host'], array( 'i0.wp.com', 'i1.wp.com', 'i2.wp.com' ) ) ) {
+ $photon_url = add_query_arg( $args, $image_url );
+
+ return jetpack_photon_url_scheme( $photon_url, $scheme );
+ }
+
+ // This setting is Photon Server dependent
+ if ( ! apply_filters( 'jetpack_photon_any_extension_for_domain', false, $image_url_parts['host'] ) ) {
+ // Photon doesn't support query strings so we ignore them and look only at the path.
+ // However some source images are served via PHP so check the no-query-string extension.
+ // For future proofing, this is a blacklist of common issues rather than a whitelist.
+ $extension = pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION );
+ if ( empty( $extension ) || in_array( $extension, array( 'php' ) ) )
+ return $image_url;
+ }
+
+ $image_host_path = $image_url_parts['host'] . $image_url_parts['path'];
+
+ // Figure out which CDN subdomain to use
+ srand( crc32( $image_host_path ) );
+ $subdomain = rand( 0, 2 );
+ srand();
+
+ $photon_url = "http://i{$subdomain}.wp.com/$image_host_path";
+
+ // This setting is Photon Server dependent
+ if ( isset( $image_url_parts['query'] ) && apply_filters( 'jetpack_photon_add_query_string_to_domain', false, $image_url_parts['host'] ) ) {
+ $photon_url .= '?q=' . rawurlencode( $image_url_parts['query'] );
+ }
+
+ if ( $args ) {
+ if ( is_array( $args ) ) {
+ $photon_url = add_query_arg( $args, $photon_url );
+ } else {
+ // You can pass a query string for complicated requests but where you still want CDN subdomain help, etc.
+ $photon_url .= '?' . $args;
+ }
+ }
+
+ return jetpack_photon_url_scheme( $photon_url, $scheme );
+}
+
+
+/**
+ * WordPress.com
+ *
+ * If a cropped WP.com-hosted image is the source image, have Photon replicate the crop.
+ */
+add_filter( 'jetpack_photon_pre_args', 'jetpack_photon_parse_wpcom_query_args', 10, 2 );
+
+function jetpack_photon_parse_wpcom_query_args( $args, $image_url ) {
+ $parsed_url = @parse_url( $image_url );
+
+ if ( ! $parsed_url )
+ return $args;
+
+ $image_url_parts = wp_parse_args( $parsed_url, array(
+ 'host' => '',
+ 'query' => ''
+ ) );
+
+ if ( '.files.wordpress.com' != substr( $image_url_parts['host'], -20 ) )
+ return $args;
+
+ if ( empty( $image_url_parts['query'] ) )
+ return $args;
+
+ $wpcom_args = wp_parse_args( $image_url_parts['query'] );
+
+ if ( empty( $wpcom_args['w'] ) || empty( $wpcom_args['h'] ) )
+ return $args;
+
+ // Keep the crop by using "resize"
+ if ( ! empty( $wpcom_args['crop'] ) ) {
+ if ( is_array( $args ) ) {
+ $args = array_merge( array( 'resize' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
+ } else {
+ $args = 'resize=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
+ }
+ } else {
+ if ( is_array( $args ) ) {
+ $args = array_merge( array( 'fit' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
+ } else {
+ $args = 'fit=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
+ }
+ }
+
+ return $args;
+}
+
+
+/**
+ * Facebook
+ */
+add_filter( 'jetpack_photon_add_query_string_to_domain', 'jetpack_photon_allow_facebook_graph_domain', 10, 2 );
+add_filter( 'jetpack_photon_any_extension_for_domain', 'jetpack_photon_allow_facebook_graph_domain', 10, 2 );
+
+function jetpack_photon_url_scheme( $url, $scheme ) {
+ if ( ! in_array( $scheme, array( 'http', 'https', 'network_path' ) ) ) {
+ $scheme = is_ssl() ? 'https' : 'http';
+ }
+
+ if ( 'network_path' == $scheme ) {
+ $scheme_slashes = '//';
+ } else {
+ $scheme_slashes = "$scheme://";
+ }
+
+ return preg_replace( '#^[a-z:]+//#i', $scheme_slashes, $url );
+}
+
+function jetpack_photon_allow_facebook_graph_domain( $allow = false, $domain ) {
+ switch ( $domain ) {
+ case 'graph.facebook.com' :
+ return true;
+ }
+
+ return $allow;
+}
\ No newline at end of file
diff --git a/plugins/jetpack/jetpack.php b/plugins/jetpack/jetpack.php
index 68d7aaed..80b6a3ce 100644
--- a/plugins/jetpack/jetpack.php
+++ b/plugins/jetpack/jetpack.php
@@ -5,7 +5,7 @@
* Plugin URI: http://wordpress.org/extend/plugins/jetpack/
* Description: Bring the power of the WordPress.com cloud to your self-hosted WordPress. Jetpack enables you to connect your blog to a WordPress.com account to use the powerful features normally only available to WordPress.com users.
* Author: Automattic
- * Version: 1.6.1
+ * Version: 2.2
* Author URI: http://jetpack.me
* License: GPL2+
* Text Domain: jetpack
@@ -14,11 +14,15 @@
defined( 'JETPACK__API_BASE' ) or define( 'JETPACK__API_BASE', 'https://jetpack.wordpress.com/jetpack.' );
define( 'JETPACK__API_VERSION', 1 );
-define( 'JETPACK__MINIMUM_WP_VERSION', '3.2' );
+define( 'JETPACK__MINIMUM_WP_VERSION', '3.3' );
defined( 'JETPACK_CLIENT__AUTH_LOCATION' ) or define( 'JETPACK_CLIENT__AUTH_LOCATION', 'header' );
defined( 'JETPACK_CLIENT__HTTPS' ) or define( 'JETPACK_CLIENT__HTTPS', 'AUTO' );
-define( 'JETPACK__VERSION', '1.6.1' );
+define( 'JETPACK__VERSION', '2.2' );
define( 'JETPACK__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) or define( 'JETPACK__GLOTPRESS_LOCALES_PATH', JETPACK__PLUGIN_DIR . 'locales.php' );
+
+define( 'JETPACK_MASTER_USER', true );
+
/*
Options:
jetpack_options (array)
@@ -53,13 +57,15 @@ class Jetpack {
'twitter-widget' => array( 'wickett-twitter-widget/wickett-twitter-widget.php', 'Wickett Twitter Widget' ),
'after-the-deadline' => array( 'after-the-deadline/after-the-deadline.php', 'After The Deadline' ),
'contact-form' => array( 'grunion-contact-form/grunion-contact-form.php', 'Grunion Contact Form' ),
+ 'custom-css' => array( 'safecss/safecss.php', 'WordPress.com Custom CSS' ),
);
var $capability_translations = array(
'administrator' => 'manage_options',
-// 'editor' => 'edit_others_posts',
-// 'author' => 'publish_posts',
-// 'contributor' => 'edit_posts',
+ 'editor' => 'edit_others_posts',
+ 'author' => 'publish_posts',
+ 'contributor' => 'edit_posts',
+ 'subscriber' => 'read',
);
/**
@@ -74,6 +80,12 @@ class Jetpack {
*/
var $error = '';
+ /**
+ * Modules that need more privacy description.
+ * @var string
+ */
+ var $privacy_checks = '';
+
/**
* Stats to record once the page loads
*
@@ -86,11 +98,16 @@ class Jetpack {
*/
var $sync;
+ /**
+ * Verified data for JSON authorization request
+ */
+ var $json_api_authorization_request = array();
+
/**
* Singleton
* @static
*/
- function init() {
+ public static function init() {
static $instance = false;
if ( !$instance ) {
@@ -132,7 +149,21 @@ class Jetpack {
}
}
- // Future: switch on version? If so, think twice before updating version/old_version.
+ // Upgrade from a single user token to a user_id-indexed array and a master_user ID
+ if ( !Jetpack::get_option( 'user_tokens' ) ) {
+ if ( $user_token = Jetpack::get_option( 'user_token' ) ) {
+ $token_parts = explode( '.', $user_token );
+ if ( isset( $token_parts[2] ) ) {
+ $master_user = $token_parts[2];
+ $user_tokens = array( $master_user => $user_token );
+ Jetpack::update_options( compact( 'master_user', 'user_tokens' ) );
+ Jetpack::delete_option( 'user_token' );
+ } else {
+ // @todo: is this even possible?
+ trigger_error( sprintf( 'Jetpack::plugin_upgrade found no user_id in user_token "%s"', $user_token ), E_USER_WARNING );
+ }
+ }
+ }
}
/**
@@ -141,19 +172,25 @@ class Jetpack {
function Jetpack() {
$this->sync = new Jetpack_Sync;
+ // Modules should do Jetpack_Sync::sync_options( __FILE__, $option, ... ); instead
+ // We access the "internal" method here only because the Jetpack object isn't instantiated yet
+ $this->sync->options( __FILE__,
+ 'home',
+ 'siteurl',
+ 'blogname',
+ 'gmt_offset',
+ 'timezone_string'
+ );
+
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && isset( $_GET['for'] ) && 'jetpack' == $_GET['for'] ) {
@ini_set( 'display_errors', false ); // Display errors can cause the XML to be not well formed.
require_once dirname( __FILE__ ) . '/class.jetpack-xmlrpc-server.php';
$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
- // Don't let anyone authenticate
- remove_all_filters( 'authenticate' );
-
- if ( $this->is_active() ) {
- // Allow Jetpack authentication
- add_filter( 'authenticate', array( $this, 'authenticate_xml_rpc' ), 10, 3 );
+ $this->require_jetpack_authentication();
+ if ( Jetpack::is_active() ) {
// Hack to preserve $HTTP_RAW_POST_DATA
add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
@@ -166,9 +203,21 @@ class Jetpack {
// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
+ } elseif ( is_admin() && isset( $_POST['action'] ) && 'jetpack_upload_file' == $_POST['action'] ) {
+ $this->require_jetpack_authentication();
+ $this->add_remote_request_handlers();
+ } else {
+ if ( Jetpack::is_active() ) {
+ add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
+ }
+ }
+
+ add_action( 'jetpack_clean_nonces', array( 'Jetpack', 'clean_nonces' ) );
+ if ( !wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
+ wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
}
- add_action( 'jetpack_clean_nonces', array( $this, 'clean_nonces' ) );
+ add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ) );
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
add_action( 'admin_init', array( $this, 'admin_init' ) );
@@ -177,9 +226,43 @@ class Jetpack {
add_action( 'wp_ajax_jetpack-check-news-subscription', array( $this, 'check_news_subscription' ) );
add_action( 'wp_ajax_jetpack-subscribe-to-news', array( $this, 'subscribe_to_news' ) );
+ add_action( 'wp_loaded', array( $this, 'register_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'devicepx' ) );
add_action( 'customize_controls_enqueue_scripts', array( $this, 'devicepx' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'devicepx' ) );
+
+ add_action( 'jetpack_activate_module', array( $this, 'activate_module_actions' ) );
+
+ add_action( 'plugins_loaded', array( $this, 'check_open_graph' ), 999 );
+ }
+
+ function require_jetpack_authentication() {
+ // Don't let anyone authenticate
+ $_COOKIE = array();
+ remove_all_filters( 'authenticate' );
+
+ if ( Jetpack::is_active() ) {
+ // Allow Jetpack authentication
+ add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
+ }
+ }
+
+ /**
+ * Register assets for use in various modules and the Jetpack admin page.
+ *
+ * @uses wp_script_is, wp_register_script, plugins_url
+ * @action wp_loaded
+ * @return null
+ */
+ public function register_assets() {
+ if ( ! wp_script_is( 'spin', 'registered' ) )
+ wp_register_script( 'spin', plugins_url( '_inc/spin.js', __FILE__ ), false, '1.2.4' );
+
+ if ( ! wp_script_is( 'jquery.spin', 'registered' ) )
+ wp_register_script( 'jquery.spin', plugins_url( '_inc/jquery.spin.js', __FILE__ ) , array( 'jquery', 'spin' ) );
+
+ if ( ! wp_script_is( 'jetpack-gallery-settings', 'registered' ) )
+ wp_register_script( 'jetpack-gallery-settings', plugins_url( '_inc/gallery-settings.js', __FILE__ ), array( 'media-views' ), '20121225' );
}
/**
@@ -193,19 +276,77 @@ class Jetpack {
/**
* Is Jetpack active?
*/
- function is_active() {
- return (bool) Jetpack_Data::get_access_token( 1 ); // 1 just means user token
+ public static function is_active() {
+ return (bool) Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
+ }
+
+ /**
+ * Is a given user (or the current user if none is specified) linked to a WordPress.com user?
+ */
+ public static function is_user_connected( $user_id = false ) {
+ $user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
+ if ( !$user_id ) {
+ return false;
+ }
+ return (bool) Jetpack_Data::get_access_token( $user_id );
}
function current_user_is_connection_owner() {
- $user_token = Jetpack_Data::get_access_token( 1 );
+ $user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && get_current_user_id() === $user_token->external_user_id;
}
+ /**
+ * Synchronize connected user role changes
+ */
+ function user_role_change( $user_id ) {
+ if ( Jetpack::is_active() && Jetpack::is_user_connected( $user_id ) ) {
+
+ $current_user_id = get_current_user_id();
+ wp_set_current_user( $user_id );
+ $role = $this->translate_current_user_to_role();
+ $signed_role = $this->sign_role( $role );
+ wp_set_current_user( $current_user_id );
+
+ $master_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
+ $master_user_id = absint( $master_token->external_user_id );
+
+ if ( !$master_user_id )
+ return; // this shouldn't happen
+
+ Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role );
+ //@todo retry on failure
+
+ //try to choose a new master if we're demoting the current one
+ if ( $user_id == $master_user_id && 'administrator' != $role ) {
+ $query = new WP_User_Query( array(
+ 'fields' => array( 'id' ),
+ 'role' => 'administrator',
+ 'orderby' => 'id',
+ 'exclude' => array( $master_user_id ),
+ )
+ );
+ $new_master = false;
+ foreach ( $query->results as $result ) {
+ $uid = absint( $result->id );
+ if ( $uid && Jetpack::is_user_connected( $uid ) ) {
+ $new_master = $uid;
+ break;
+ }
+ }
+
+ if ( $new_master ) {
+ Jetpack::update_option( 'master_user', $new_master );
+ }
+ // else disconnect..?
+ }
+ }
+ }
+
/**
* Loads the currently active modules.
*/
- function load_modules() {
+ public static function load_modules() {
if ( !Jetpack::is_active() ) {
return;
}
@@ -247,11 +388,61 @@ class Jetpack {
}
do_action( 'jetpack_modules_loaded' );
+
+ // Load module-specific code that is needed even when a module isn't active. Loaded here because code contained therein may need actions such as setup_theme.
+ require_once( dirname( __FILE__ ) . '/modules/module-extras.php' );
+ }
+
+ /**
+ * Check if Jetpack's Open Graph tags should be used.
+ * If certain plugins are active, Jetpack's og tags are suppressed.
+ *
+ * @uses Jetpack::get_active_modules, add_filter, get_option, apply_filters
+ * @action plugins_loaded
+ * @return null
+ */
+ public function check_open_graph() {
+ if ( in_array( 'publicize', Jetpack::get_active_modules() ) || in_array( 'sharedaddy', Jetpack::get_active_modules() ) )
+ add_filter( 'jetpack_enable_open_graph', '__return_true', 0 );
+
+ $active_plugins = get_option( 'active_plugins', array() );
+
+ $conflicting_plugins = array(
+ 'facebook/facebook.php', // Official Facebook plugin
+ 'wordpress-seo/wp-seo.php', // WordPress SEO by Yoast
+ 'add-link-to-facebook/add-link-to-facebook.php', // Add Link to Facebook
+ 'facebook-awd/AWD_facebook.php', // Facebook AWD All in one
+ 'header-footer/plugin.php', // Header and Footer
+ 'nextgen-facebook/nextgen-facebook.php', // NextGEN Facebook OG
+ 'seo-facebook-comments/seofacebook.php', // SEO Facebook Comments
+ 'seo-ultimate/seo-ultimate.php', // SEO Ultimate
+ 'sexybookmarks/sexy-bookmarks.php', // Shareaholic
+ 'shareaholic/sexy-bookmarks.php', // Shareaholic
+ 'social-discussions/social-discussions.php', // Social Discussions
+ 'social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.php', // NextScripts SNAP
+ 'wordbooker/wordbooker.php', // Wordbooker
+ 'socialize/socialize.php', // Socialize
+ 'simple-facebook-connect/sfc.php', // Simple Facebook Connect
+ 'social-sharing-toolkit/social_sharing_toolkit.php', // Social Sharing Toolkit
+ 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol
+ 'opengraph/opengraph.php', // Open Graph
+ 'sharepress/sharepress.php', // SharePress
+ );
+
+ foreach ( $conflicting_plugins as $plugin ) {
+ if ( in_array( $plugin, $active_plugins ) ) {
+ add_filter( 'jetpack_enable_open_graph', '__return_false', 99 );
+ break;
+ }
+ }
+
+ if ( apply_filters( 'jetpack_enable_open_graph', false ) )
+ require_once dirname( __FILE__ ) . '/functions.opengraph.php';
}
/* Jetpack Options API */
- function get_option_names( $type = 'compact' ) {
+ public static function get_option_names( $type = 'compact' ) {
switch ( $type ) {
case 'non-compact' :
case 'non_compact' :
@@ -260,17 +451,23 @@ class Jetpack {
'activated',
'active_modules',
'do_activate',
+ 'publicize',
+ 'widget_twitter',
);
}
return array(
'id', // (int) The Client ID/WP.com Blog ID of this site.
'blog_token', // (string) The Client Secret/Blog Token of this site.
- 'user_token', // (string) The User Token of this site.
+ 'user_token', // (string) The User Token of this site. (deprecated)
+ 'publicize_connections', // (array) An array of Publicize connections from WordPress.com
+ 'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
+ 'user_tokens', // (array) User Tokens for each user of this site who has connected to jetpack.wordpress.com.
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time
'old_version', // (string) Used to determine which modules are the most recently added. previous_version:time
'fallback_no_verify_ssl_certs', // (int) Flag for determining if this host must skip SSL Certificate verification due to misconfigured SSL.
'time_diff', // (int) Offset between Jetpack server's clocks and this server's clocks. Jetpack Server Time = time() + (int) Jetpack::get_option( 'time_diff' )
+ 'public', // (int|bool) If we think this site is public or not (1, 0), false if we haven't yet tried to figure it out.
);
}
@@ -280,7 +477,7 @@ class Jetpack {
* @param string $name Option name
* @param mixed $default (optional)
*/
- function get_option( $name, $default = false ) {
+ public static function get_option( $name, $default = false ) {
if ( in_array( $name, Jetpack::get_option_names( 'non_compact' ) ) ) {
return get_option( "jetpack_$name" );
} else if ( !in_array( $name, Jetpack::get_option_names() ) ) {
@@ -297,141 +494,23 @@ class Jetpack {
}
/**
- * Get a post and associated data in the standard JP format.
- * Cannot be called statically
- *
- * @param int $id Post ID
- * @param bool|array $columns Columns/fields to get.
- * @return Array containing full post details
- */
- function get_post( $id, $columns = true ) {
- $post_obj = get_post( $id );
- if ( !$post_obj )
- return false;
- $post = get_object_vars( $post_obj );
-
- // Only send specific columns if requested
- if ( is_array( $columns ) ) {
- $keys = array_keys( $post );
- foreach ( $keys as $column ) {
- if ( !in_array( $column, $columns ) ) {
- unset( $post[$column] );
- }
- }
- if ( in_array( '_jetpack_backfill', $columns ) ) {
- $post['_jetpack_backfill'] = true;
- }
- }
-
- if ( true === $columns || in_array( 'tax', $columns ) ) {
- $tax = array();
- $taxonomies = get_object_taxonomies( $post_obj );
- foreach ( $taxonomies as $taxonomy ) {
- $t = get_taxonomy( $taxonomy );
- $terms = get_object_term_cache( $post_obj->ID, $taxonomy );
- if ( empty( $terms ) )
- $terms = wp_get_object_terms( $post_obj->ID, $taxonomy );
- $term_names = array();
- foreach ( $terms as $term ) {
- $term_names[] = $term->name;
- }
- $tax[$taxonomy] = $term_names;
- }
- $post['tax'] = $tax;
- }
-
- // Include all postmeta for requests that specifically ask for it, or ask for everything
- if ( true == $columns || in_array( 'meta', $columns ) ) {
- $meta = get_post_meta( $post_obj->ID, false );
- $post['meta'] = array();
- foreach ( $meta as $key => $value ) {
- $post['meta'][$key] = array_map( 'maybe_unserialize', $value );
- }
- }
+ * Stores two secrets and a timestamp so WordPress.com can make a request back and verify an action
+ * Does some extra verification so urls (such as those to public-api, register, etc) cant just be crafted
+ * $name must be a registered option name.
+ */
+ public static function create_nonce( $name ) {
+ $secret = wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 );
+
+ Jetpack::update_option( $name, $secret );
+ @list( $secret_1, $secret_2, $eol ) = explode( ':', Jetpack::get_option( $name ) );
+ if ( empty( $secret_1 ) || empty( $secret_2 ) || $eol < time() )
+ return new Jetpack_Error( 'missing_secrets' );
- $post['extra'] = array(
- 'author' => get_the_author_meta( 'display_name', $post_obj->post_author ),
- 'author_email' => get_the_author_meta( 'email', $post_obj->post_author ),
+ return array(
+ 'secret_1' => $secret_1,
+ 'secret_2' => $secret_2,
+ 'eol' => $eol,
);
-
- $post['permalink'] = get_permalink( $post_obj->ID );
- return $post;
- }
-
- /**
- * Decide whether a post/page/attachment is visible to the public.
- *
- * @param array $post
- * @return bool
- */
- function is_post_public( $post ) {
- if ( ! is_array( $post ) )
- return false;
- if ( ! empty( $post['post_password'] ) )
- return false;
- if ( ! in_array( $post['post_type'], get_post_types( array( 'public' => true ) ) ) )
- return false;
- $post_status = get_post_status( $post['ID'] ); // Inherited status is resolved here.
- if ( ! in_array( $post_status, get_post_stati( array( 'public' => true ) ) ) )
- return false;
- return true;
- }
-
- /**
- * Get a comment and associated data in the standard JP format.
- * Cannot be called statically
- *
- * @param int $id Comment ID
- * @param array $columns Columns/fields to get.
- * @return Array containing full comment details
- */
- function get_comment( $id, $columns = true ) {
- $comment_obj = get_comment( $id );
- if ( !$comment_obj )
- return false;
- $comment = get_object_vars( $comment_obj );
-
- // Only send specific columns if requested
- if ( is_array( $columns ) ) {
- $keys = array_keys( $comment );
- foreach ( $keys as $column ) {
- if ( !in_array( $column, $columns ) ) {
- unset( $comment[$column] );
- }
- }
- }
-
- // Include all commentmeta for requests that specifically ask for it, or ask for everything
- if ( isset( $columns['meta'] ) || true == $columns ) {
- $meta = get_comment_meta( $id, false );
- $comment['meta'] = array();
- foreach ( $meta as $key => $value ) {
- $comment['meta'][$key] = array_map( 'maybe_unserialize', $value );
- }
- }
-
- return $comment;
- }
-
- function get_taxonomy( $id, $columns = true, $type ) {
- $taxonomy_obj = get_term_by( 'slug', $id, $type );
-
- if ( !$taxonomy_obj )
- return false;
- $taxonomy = get_object_vars( $taxonomy_obj );
-
- // Only send specific columns if requested
- if ( is_array( $columns ) ) {
- $keys = array_keys( $taxonomy );
- foreach ( $keys as $column ) {
- if ( !in_array( $column, $columns ) ) {
- unset( $taxonomy[$column] );
- }
- }
- }
-
- $taxonomy['type'] = $type;
- return $taxonomy;
}
/**
@@ -440,7 +519,7 @@ class Jetpack {
* @param string $name Option name
* @param mixed $value Option value
*/
- function update_option( $name, $value ) {
+ public static function update_option( $name, $value ) {
if ( in_array( $name, Jetpack::get_option_names( 'non_compact' ) ) ) {
return update_option( "jetpack_$name", $value );
} else if ( !in_array( $name, Jetpack::get_option_names() ) ) {
@@ -463,7 +542,7 @@ class Jetpack {
*
* @param array $array array( option name => option value, ... )
*/
- function update_options( $array ) {
+ public static function update_options( $array ) {
$names = array_keys( $array );
foreach ( array_diff( $names, Jetpack::get_option_names(), Jetpack::get_option_names( 'non_compact' ) ) as $unknown_name ) {
@@ -490,7 +569,7 @@ class Jetpack {
*
* @param string|array $names
*/
- function delete_option( $names ) {
+ public static function delete_option( $names ) {
$names = (array) $names;
foreach ( array_diff( $names, Jetpack::get_option_names(), Jetpack::get_option_names( 'non_compact' ) ) as $unknown_name ) {
@@ -512,12 +591,34 @@ class Jetpack {
unset( $options[$name] );
}
- return update_option( 'jetpack_options', $options );;
+ return update_option( 'jetpack_options', $options );
}
return true;
}
+ /**
+ * Enters a user token into the user_tokens option
+ *
+ * @param int $user_id
+ * @param string $token
+ * return bool
+ */
+ public static function update_user_token( $user_id, $token, $is_master_user ) {
+ // not designed for concurrent updates
+ $user_tokens = Jetpack::get_option( 'user_tokens' );
+ if ( ! is_array( $user_tokens ) )
+ $user_tokens = array();
+ $user_tokens[$user_id] = $token;
+ if ( $is_master_user ) {
+ $master_user = $user_id;
+ $options = compact('user_tokens', 'master_user');
+ } else {
+ $options = compact('user_tokens');
+ }
+ return Jetpack::update_options( $options );
+ }
+
/**
* Returns an array of all PHP files in the specified absolute path.
* Equivalent to glob( "$absolute_path/*.php" ).
@@ -525,7 +626,7 @@ class Jetpack {
* @param string $absolute_path The absolute path of the directory to search.
* @return array Array of absolute paths to the PHP files.
*/
- function glob_php( $absolute_path ) {
+ public static function glob_php( $absolute_path ) {
$absolute_path = untrailingslashit( $absolute_path );
$files = array();
if ( !$dir = @opendir( $absolute_path ) ) {
@@ -551,8 +652,8 @@ class Jetpack {
return $files;
}
- function activate_new_modules() {
- if ( !$this->is_active() ) {
+ public function activate_new_modules() {
+ if ( ! Jetpack::is_active() ) {
return;
}
@@ -584,6 +685,10 @@ class Jetpack {
Jetpack::deactivate_module( $active_module );
}
+ if ( version_compare( $jetpack_version, '1.9.2', '<' ) && version_compare( '1.9-something', JETPACK__VERSION, '<' ) ) {
+ add_action( 'jetpack_activate_default_modules', array( $this->sync, 'sync_all_registered_options' ), 1000 );
+ }
+
Jetpack::update_options( array(
'version' => JETPACK__VERSION . ':' . time(),
'old_version' => $jetpack_old_version,
@@ -599,7 +704,7 @@ class Jetpack {
* List available Jetpack modules. Simply lists .php files in /modules/.
* Make sure to tuck away module "library" files in a sub-directory.
*/
- function get_available_modules( $min_version = false, $max_version = false ) {
+ public static function get_available_modules( $min_version = false, $max_version = false ) {
static $modules = null;
if ( !isset( $modules ) ) {
@@ -639,15 +744,31 @@ class Jetpack {
/**
* Default modules loaded on activation.
*/
- function get_default_modules( $min_version = false, $max_version = false ) {
+ public static function get_default_modules( $min_version = false, $max_version = false ) {
$return = array();
foreach ( Jetpack::get_available_modules( $min_version, $max_version ) as $module ) {
// Add special cases here for modules to avoid auto-activation
switch ( $module ) {
+
+ // These modules are default off: they change things blog-side
case 'comments' :
case 'carousel' :
- continue;
+ case 'minileven':
+ case 'infinite-scroll' :
+ case 'photon' :
+ case 'tiled-gallery' :
+ case 'likes' :
+ break;
+
+ // These modules are default off if we think the site is a private one
+ case 'enhanced-distribution' :
+ case 'json-api' :
+ if ( !Jetpack::get_option( 'public' ) ) {
+ break;
+ }
+ // else no break
+ // The rest are default on
default :
$return[] = $module;
}
@@ -659,14 +780,14 @@ class Jetpack {
/**
* Extract a module's slug from its full path.
*/
- function get_module_slug( $file ) {
+ public static function get_module_slug( $file ) {
return str_replace( '.php', '', basename( $file ) );
}
/**
* Generate a module's path from its slug.
*/
- function get_module_path( $slug ) {
+ public static function get_module_path( $slug ) {
return dirname( __FILE__ ) . "/modules/$slug.php";
}
@@ -675,7 +796,7 @@ class Jetpack {
* plugin headers to avoid them being identified as standalone
* plugins on the WordPress plugins page.
*/
- function get_module( $module ) {
+ public static function get_module( $module ) {
$headers = array(
'name' => 'Module Name',
'description' => 'Module Description',
@@ -706,7 +827,7 @@ class Jetpack {
/**
* Get a list of activated modules as an array of module slugs.
*/
- function get_active_modules() {
+ public static function get_active_modules() {
$active = Jetpack::get_option( 'active_modules' );
if ( !is_array( $active ) )
$active = array();
@@ -718,7 +839,7 @@ class Jetpack {
return array_unique( $active );
}
- function is_module( $module ) {
+ public static function is_module( $module ) {
return !empty( $module ) && !validate_file( $module, Jetpack::get_available_modules() );
}
@@ -729,7 +850,7 @@ class Jetpack {
*
* @static
*/
- function catch_errors( $catch ) {
+ public static function catch_errors( $catch ) {
static $display_errors, $error_reporting;
if ( $catch ) {
@@ -746,11 +867,11 @@ class Jetpack {
/**
* Saves any generated PHP errors in ::state( 'php_errors', {errors} )
*/
- function catch_errors_on_shutdown() {
+ public static function catch_errors_on_shutdown() {
Jetpack::state( 'php_errors', ob_get_clean() );
}
- function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array() ) {
+ public static function activate_default_modules( $min_version = false, $max_version = false, $other_modules = array() ) {
$jetpack = Jetpack::init();
$modules = Jetpack::get_default_modules( $min_version, $max_version );
@@ -784,6 +905,8 @@ class Jetpack {
exit;
}
+ do_action( 'jetpack_before_activate_default_modules', $min_version, $max_version, $other_modules );
+
// Check each module for fatal errors, a la wp-admin/plugins.php::activate before activating
$redirect = menu_page_url( 'jetpack', false );
Jetpack::restate();
@@ -816,6 +939,7 @@ class Jetpack {
Jetpack::state( 'module', $module );
ob_start();
require $file;
+ do_action( 'jetpack_activate_module', $module );
$active[] = $module;
$state = in_array( $module, $other_modules ) ? 'reactivated_modules' : 'activated_modules';
if ( $active_state = Jetpack::state( $state ) ) {
@@ -831,9 +955,10 @@ class Jetpack {
Jetpack::state( 'error', false );
Jetpack::state( 'module', false );
Jetpack::catch_errors( false );
+ do_action( 'jetpack_activate_default_modules', $min_version, $max_version, $other_modules );
}
- function activate_module( $module ) {
+ public static function activate_module( $module ) {
$jetpack = Jetpack::init();
if ( !Jetpack::is_active() )
@@ -872,7 +997,7 @@ class Jetpack {
Jetpack::catch_errors( true );
ob_start();
require Jetpack::get_module_path( $module );
- do_action( "jetpack_activate_module_$module" );
+ do_action( 'jetpack_activate_module', $module );
$active[] = $module;
Jetpack::update_option( 'active_modules', array_unique( $active ) );
Jetpack::state( 'error', false ); // the override
@@ -883,7 +1008,13 @@ class Jetpack {
exit;
}
- function deactivate_module( $module ) {
+ function activate_module_actions( $module ) {
+ do_action( "jetpack_activate_module_$module" );
+
+ $this->sync->sync_all_module_options( $module );
+ }
+
+ public static function deactivate_module( $module ) {
$active = Jetpack::get_active_modules();
$new = array();
foreach ( $active as $check ) {
@@ -895,34 +1026,34 @@ class Jetpack {
return Jetpack::update_option( 'active_modules', array_unique( $new ) );
}
- function enable_module_configurable( $module ) {
+ public static function enable_module_configurable( $module ) {
$module = Jetpack::get_module_slug( $module );
add_filter( 'jetpack_module_configurable_' . $module, '__return_true' );
}
- function module_configuration_url( $module ) {
+ public static function module_configuration_url( $module ) {
$module = Jetpack::get_module_slug( $module );
return Jetpack::admin_url( array( 'configure' => $module ) );
}
- function module_configuration_load( $module, $method ) {
+ public static function module_configuration_load( $module, $method ) {
$module = Jetpack::get_module_slug( $module );
add_action( 'jetpack_module_configuration_load_' . $module, $method );
}
- function module_configuration_head( $module, $method ) {
+ public static function module_configuration_head( $module, $method ) {
$module = Jetpack::get_module_slug( $module );
add_action( 'jetpack_module_configuration_head_' . $module, $method );
}
- function module_configuration_screen( $module, $method ) {
+ public static function module_configuration_screen( $module, $method ) {
$module = Jetpack::get_module_slug( $module );
add_action( 'jetpack_module_configuration_screen_' . $module, $method );
}
/* Installation */
- function bail_on_activation( $message, $deactivate = true ) {
+ public static function bail_on_activation( $message, $deactivate = true ) {
?>
@@ -967,7 +1098,7 @@ p {
* Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
* @static
*/
- function plugin_activation( $network_wide ) {
+ public static function plugin_activation( $network_wide ) {
Jetpack::update_option( 'activated', 1 );
if ( version_compare( $GLOBALS['wp_version'], JETPACK__MINIMUM_WP_VERSION, '<' ) ) {
@@ -984,7 +1115,7 @@ p {
* Sets the internal version number and activation state.
* @static
*/
- function plugin_initialize() {
+ public static function plugin_initialize() {
if ( !Jetpack::get_option( 'activated' ) ) {
Jetpack::update_option( 'activated', 2 );
}
@@ -1003,7 +1134,7 @@ p {
* Removes all connection options
* @static
*/
- function plugin_deactivation( $network_wide ) {
+ public static function plugin_deactivation( $network_wide ) {
Jetpack::disconnect( false );
}
@@ -1012,7 +1143,7 @@ p {
* Forgets all connection details and tells the Jetpack servers to do the same.
* @static
*/
- function disconnect( $update_activated_state = true ) {
+ public static function disconnect( $update_activated_state = true ) {
wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
Jetpack::clean_nonces( true );
@@ -1024,6 +1155,8 @@ p {
'register',
'blog_token',
'user_token',
+ 'user_tokens',
+ 'master_user',
'time_diff',
'fallback_no_verify_ssl_certs',
) );
@@ -1033,11 +1166,36 @@ p {
}
}
+ /**
+ * Unlinks the current user from the linked WordPress.com user
+ */
+ function unlink_user() {
+ if ( !$tokens = Jetpack::get_option( 'user_tokens' ) )
+ return false;
+
+ $user_id = get_current_user_id();
+
+ if ( Jetpack::get_option( 'master_user' ) == $user_id )
+ return false;
+
+ if ( !isset( $tokens[$user_id] ) )
+ return false;
+
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( compact( 'user_id' ) );
+ $xml->query( 'jetpack.unlink_user', $user_id );
+
+ unset( $tokens[$user_id] );
+
+ Jetpack::update_option( 'user_tokens', $tokens );
+
+ return true;
+ }
+
/**
* Attempts Jetpack registration. If it fail, a state flag is set: @see ::admin_page_load()
- * @static
*/
- function try_registration() {
+ public static function try_registration() {
$result = Jetpack::register();
// If there was an error with registration and the site was not registered, record this so we can show a message.
@@ -1062,9 +1220,7 @@ p {
Jetpack::plugin_initialize();
}
- $is_active = Jetpack::is_active();
-
- if ( !$is_active ) {
+ if ( !Jetpack::is_active() ) {
if ( 4 != Jetpack::get_option( 'activated' ) ) {
// Show connect notice on dashboard and plugins pages
add_action( 'load-index.php', array( $this, 'prepare_connect_notice' ) );
@@ -1087,10 +1243,13 @@ p {
add_action( 'wp_ajax_jetpack_debug', array( $this, 'ajax_debug' ) );
- if ( $is_active ) {
+ if ( Jetpack::is_active() ) {
// Artificially throw errors in certain whitelisted cases during plugin activation
add_action( 'activate_plugin', array( $this, 'throw_error_on_activate_plugin' ) );
+ // Kick off synchronization of user role when it changes
+ add_action( 'set_user_role', array( $this, 'user_role_change' ) );
+
// Add retina images hotfix to admin
global $wp_db_version;
if ( $wp_db_version > 19470 ) {
@@ -1154,7 +1313,7 @@ p {
foreach ( $this->plugins_to_deactivate as $module => $deactivate_me ) {
if ( "plugin-activation-error_{$deactivate_me[0]}" == $action ) {
- $this->bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old “%1$s” plugin.', 'jetpack' ), $deactivate_me[1] ), false );
+ Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old “%1$s” plugin.', 'jetpack' ), $deactivate_me[1] ), false );
}
}
}
@@ -1172,7 +1331,7 @@ p {
&&
( $new_modules_count = count( $new_modules ) )
&&
- $this->is_active()
+ Jetpack::is_active()
) {
$new_modules_count_i18n = number_format_i18n( $new_modules_count );
$span_title = esc_attr( sprintf( _n( 'One New Jetpack Module', '%s New Jetpack Modules', $new_modules_count, 'jetpack' ), $new_modules_count_i18n ) );
@@ -1181,7 +1340,7 @@ p {
$title = __( 'Jetpack', 'jetpack' );
}
- $hook = add_menu_page( 'Jetpack', $title, 'manage_options', 'jetpack', array( $this, 'admin_page' ), 'div' );
+ $hook = add_menu_page( 'Jetpack', $title, 'read', 'jetpack', array( $this, 'admin_page' ), 'div' );
add_action( "load-$hook", array( $this, 'admin_page_load' ) );
@@ -1203,6 +1362,126 @@ p {
do_action( 'jetpack_admin_menu' );
}
+ function add_remote_request_handlers() {
+ add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) );
+ }
+
+ function remote_request_handlers() {
+ switch ( current_filter() ) {
+ case 'wp_ajax_nopriv_jetpack_upload_file' :
+ $response = $this->upload_handler();
+ break;
+ default :
+ $response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 );
+ break;
+ }
+
+ if ( !$response ) {
+ $response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ $status_code = $response->get_error_data();
+ $error = $response->get_error_code();
+ $error_description = $response->get_error_message();
+
+ if ( !is_int( $status_code ) ) {
+ $status_code = 400;
+ }
+
+ status_header( $status_code );
+ die( json_encode( (object) compact( 'error', 'error_description' ) ) );
+ }
+
+ status_header( 200 );
+ if ( true === $response ) {
+ exit;
+ }
+
+ die( json_encode( (object) $response ) );
+ }
+
+ function upload_handler() {
+ if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+ return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 );
+ }
+
+ $user = wp_authenticate( '', '' );
+ if ( !$user || is_wp_error( $user ) ) {
+ return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 );
+ }
+
+ wp_set_current_user( $user->ID );
+
+ if ( !current_user_can( 'upload_files' ) ) {
+ return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 );
+ }
+
+ if ( empty( $_FILES ) ) {
+ return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 );
+ }
+
+ foreach ( array_keys( $_FILES ) as $files_key ) {
+ if ( !isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) {
+ return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 );
+ }
+ }
+
+ $media_keys = array_keys( $_FILES['media'] );
+
+ $token = Jetpack_Data::get_access_token( get_current_user_id() );
+ if ( !$token || is_wp_error( $token ) ) {
+ return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 );
+ }
+
+ $uploaded_files = array();
+ $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
+ unset( $GLOBALS['post'] );
+ foreach ( $_FILES['media']['name'] as $index => $name ) {
+ $file = array();
+ foreach ( $media_keys as $media_key ) {
+ $file[$media_key] = $_FILES['media'][$media_key][$index];
+ }
+
+ list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] );
+
+ $hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret );
+ if ( $hmac_provided !== $hmac_file ) {
+ $uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' );
+ continue;
+ }
+
+ $_FILES['.jetpack.upload.'] = $file;
+ $post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0;
+ if ( !current_user_can( 'edit_post', $post_id ) ) {
+ $post_id = 0;
+ }
+ $attachment_id = media_handle_upload( '.jetpack.upload.', $post_id, array(), array(
+ 'action' => 'jetpack_upload_file',
+ ) );
+
+ if ( !$attachment_id ) {
+ $uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' );
+ } elseif ( is_wp_error( $attachment_id ) ) {
+ $uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() );
+ } else {
+ $attachment = get_post( $attachment_id );
+ $uploaded_files[$index] = (object) array(
+ 'id' => (string) $attachment_id,
+ 'file' => $attachment->post_title,
+ 'url' => wp_get_attachment_url( $attachment_id ),
+ 'type' => $attachment->post_mime_type,
+ 'meta' => wp_get_attachment_metadata( $attachment_id ),
+ );
+ }
+ }
+ if ( !is_null( $global_post ) ) {
+ $GLOBALS['post'] = $global_post;
+ }
+
+ return $uploaded_files;
+ }
+
/**
* Add help to the Jetpack page
*
@@ -1240,19 +1519,21 @@ p {
) );
// Screen Content
- $current_screen->add_help_tab( array(
- 'id' => 'modules',
- 'title' => __( 'Modules', 'jetpack' ),
- 'content' =>
- '' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '
' .
- '' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '
' .
- '' .
- '' . __( 'Find the component you want to manage', 'jetpack' ) . ' ' .
- '' . __( 'Click on Learn More', 'jetpack' ) . ' ' .
- '' . __( 'An Activate or Deactivate button will appear', 'jetpack' ) . ' ' .
- '' . __( 'If additional settings are available, a link to them will appear', 'jetpack' ) . ' ' .
- ' '
- ) );
+ if ( current_user_can( 'manage_options' ) ) {
+ $current_screen->add_help_tab( array(
+ 'id' => 'modules',
+ 'title' => __( 'Modules', 'jetpack' ),
+ 'content' =>
+ '' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '
' .
+ '' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '
' .
+ '' .
+ '' . __( 'Find the component you want to manage', 'jetpack' ) . ' ' .
+ '' . __( 'Click on Learn More', 'jetpack' ) . ' ' .
+ '' . __( 'An Activate or Deactivate button will appear', 'jetpack' ) . ' ' .
+ '' . __( 'If additional settings are available, a link to them will appear', 'jetpack' ) . ' ' .
+ ' '
+ ) );
+ }
// Help Sidebar
$current_screen->set_help_sidebar(
@@ -1264,20 +1545,20 @@ p {
function admin_menu_css() { ?>
add_data( 'jetpack', 'rtl', true );
}
function admin_scripts() {
- wp_enqueue_script( 'jetpack-js', plugins_url( basename( dirname( __FILE__ ) ) ) . '/_inc/jetpack.js', array( 'jquery' ), JETPACK__VERSION . '-20111115' );
+ wp_enqueue_script( 'jetpack-js', plugins_url( basename( dirname( __FILE__ ) ) ) . '/_inc/jetpack.js', array( 'jquery' ), JETPACK__VERSION . '-20121111' );
wp_localize_script( 'jetpack-js', 'jetpackL10n', array(
'ays_disconnect' => "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?",
+ 'ays_unlink' => "This will prevent user-specific modules such as Publicize, Notifications and Post By Email from working.\nAre you sure you want to unlink?",
'ays_dismiss' => "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?",
) );
add_action( 'admin_footer', array( $this, 'do_stats' ) );
@@ -1349,7 +1631,7 @@ p {
- Your Jetpack is almost ready – A connection to WordPress.com is needed to enabled features like Comments, Stats, Contact Forms, and Subscriptions. Connect now to get fueled up!', 'jetpack' ); ?>
+
Your Jetpack is almost ready – A connection to WordPress.com is needed to enable features like Stats, Contact Forms, and Subscriptions. Connect now to get fueled up!', 'jetpack' ); ?>
Jetpack is installed and ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?>
@@ -1377,8 +1659,8 @@ p {
' . sprintf(
+ return ' ' . sprintf(
__( 'Jetpack now includes Jetpack Comments, which enables your visitors to use their WordPress.com, Twitter, or Facebook accounts when commenting on your site. To activate Jetpack Comments, %s .', 'jetpack' ),
wp_nonce_url(
Jetpack::admin_url( array(
@@ -1423,17 +1705,17 @@ p {
* xmlrpc.php?for=jetpack: RPC method: jetpack.verifyRegistration, Parameters: secret_1
* - The XML-RPC request verifies secret_1, deletes both secrets and responds with: secret_2
* - https://jetpack.wordpress.com/jetpack.register/1/ verifies that XML-RPC response (secret_2) then finally responds itself with
- * jetpack_id, jetpack_secret
+ * jetpack_id, jetpack_secret, jetpack_public
* - ::register() then stores jetpack_options: id => jetpack_id, blog_token => jetpack_secret
* 4 - redirect to https://jetpack.wordpress.com/jetpack.authorize/1/
* 5 - user logs in with WP.com account
* 6 - redirect to this site's wp-admin/index.php?page=jetpack&action=authorize with
- * code <-- OAuth2 style authorization code
+ * code <-- OAuth2 style authorization code
* 7 - ::admin_page_load() action=authorize
* 8 - Jetpack_Client_Server::authorize()
* 9 - Jetpack_Client_Server::get_token()
* 10- GET https://jetpack.wordpress.com/jetpack.token/1/ with
- * client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email
+ * client_id, client_secret, grant_type, code, redirect_uri:action=authorize, state, scope, user_email, user_login
* 11- which responds with
* access_token, token_type, scope
* 12- Jetpack_Client_Server::authorize() stores jetpack_options: user_token => access_token.$user_id
@@ -1456,10 +1738,25 @@ p {
Jetpack::restate();
}
+ if ( isset( $_GET['connect_url_redirect'] ) ) {
+ // User clicked in the iframe to link their accounts
+ if ( ! Jetpack::is_user_connected() ) {
+ $connect_url = $this->build_connect_url( true );
+ if ( isset( $_GET['notes_iframe'] ) )
+ $connect_url .= '¬es_iframe';
+ wp_redirect( $connect_url );
+ exit;
+ } else {
+ Jetpack::state( 'message', 'already_authorized' );
+ wp_safe_redirect( Jetpack::admin_url() );
+ exit;
+ }
+ }
+
if ( isset( $_GET['action'] ) ) {
switch ( $_GET['action'] ) {
case 'authorize' :
- if ( Jetpack::is_active() ) {
+ if ( Jetpack::is_active() && Jetpack::is_user_connected() ) {
Jetpack::state( 'message', 'already_authorized' );
wp_safe_redirect( Jetpack::admin_url() );
exit;
@@ -1495,22 +1792,30 @@ p {
exit;
case 'disconnect' :
check_admin_referer( 'jetpack-disconnect' );
- $this->disconnect();
+ Jetpack::disconnect();
wp_safe_redirect( Jetpack::admin_url() );
exit;
case 'deactivate' :
- $module = stripslashes( $_GET['module'] );
- check_admin_referer( "jetpack_deactivate-$module" );
- Jetpack::deactivate_module( $module );
- Jetpack::state( 'message', 'module_deactivated' );
- Jetpack::state( 'module', $module );
+ $modules = stripslashes( $_GET['module'] );
+ check_admin_referer( "jetpack_deactivate-$modules" );
+ foreach ( explode( ',', $modules ) as $module ) {
+ Jetpack::deactivate_module( $module );
+ Jetpack::state( 'message', 'module_deactivated' );
+ }
+ Jetpack::state( 'module', $modules );
+ wp_safe_redirect( Jetpack::admin_url() );
+ exit;
+ case 'unlink' :
+ check_admin_referer( 'jetpack-unlink' );
+ $this->unlink_user();
+ Jetpack::state( 'message', 'unlinked' );
wp_safe_redirect( Jetpack::admin_url() );
exit;
}
}
if ( !$error = $error ? $error : Jetpack::state( 'error' ) ) {
- Jetpack::activate_new_modules();
+ $this->activate_new_modules();
}
switch ( $error ) {
@@ -1663,10 +1968,35 @@ p {
break;
case 'module_deactivated' :
- if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) {
- $this->message = sprintf( __( '%s Deactivated! You can activate it again at any time using the activate button on the module card.', 'jetpack' ), $module['name'] );
- $this->stat( 'module-deactivated', Jetpack::state( 'module' ) );
+ $modules = Jetpack::state( 'module' );
+ if ( !$modules ) {
+ break;
+ }
+
+ $module_names = array();
+ foreach ( explode( ',', $modules ) as $module_slug ) {
+ $module = Jetpack::get_module( $module_slug );
+ if ( $module ) {
+ $module_names[] = $module['name'];
+ }
+
+ $this->stat( 'module-deactivated', $module_slug );
+ }
+
+ if ( !$module_names ) {
+ break;
}
+
+ $this->message = wp_sprintf(
+ _nx(
+ '%l Deactivated! You can activate it again at any time using the activate button on the module card.',
+ '%l Deactivated! You can activate them again at any time using the activate buttons on their module cards.',
+ count( $module_names ),
+ '%l = list of Jetpack module/feature names',
+ 'jetpack'
+ ),
+ $module_names
+ );
break;
case 'module_configured' :
@@ -1683,6 +2013,16 @@ p {
$this->message .= __( 'The features below are now active. Click the learn more buttons to explore each feature.', 'jetpack' );
$this->message .= Jetpack::jetpack_comment_notice();
break;
+
+ case 'linked' :
+ $this->message = __( "You’re fueled up and ready to go. ", 'jetpack' );
+ $this->message .= Jetpack::jetpack_comment_notice();
+ break;
+
+ case 'unlinked' :
+ $user = wp_get_current_user();
+ $this->message = sprintf( __( 'You have unlinked your account (%s) from WordPress.com. ', 'jetpack' ), $user->user_login );
+ break;
}
$deactivated_plugins = Jetpack::state( 'deactivated_plugins' );
@@ -1721,11 +2061,13 @@ p {
}
}
- if ( $this->message || $this->error ) {
+ $this->privacy_checks = Jetpack::state( 'privacy_checks' );
+
+ if ( $this->message || $this->error || $this->privacy_checks ) {
add_action( 'jetpack_notices', array( $this, 'admin_notices' ) );
}
- if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) ) {
+ if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
do_action( 'jetpack_module_configuration_load_' . $_GET['configure'] );
}
@@ -1755,7 +2097,64 @@ p {
privacy_checks ) :
+ $module_names = $module_slugs = array();
+
+ $privacy_checks = explode( ',', $this->privacy_checks );
+ foreach ( $privacy_checks as $module_slug ) {
+ $module = Jetpack::get_module( $module_slug );
+ if ( !$module ) {
+ continue;
+ }
+
+ $module_slugs[] = $module_slug;
+ $module_names[] = "{$module['name']} ";
+ }
+
+ $module_slugs = join( ',', $module_slugs );
+?>
+
+
+
+
true ) );
+
+ echo "\n \n";
+
+ echo wp_kses( sprintf(
+ _nx(
+ 'If your site is not publicly accessible, consider deactivating this feature .',
+ 'If your site is not publicly accessible, consider deactivating these features .',
+ count( $privacy_checks ),
+ '%1$s = deactivation URL, %2$s = "Deactivate {list of Jetpack module/feature names}',
+ 'jetpack'
+ ),
+ wp_nonce_url(
+ Jetpack::admin_url( array(
+ 'action' => 'deactivate',
+ 'module' => urlencode( $module_slugs ),
+ ) ),
+ "jetpack_deactivate-$module_slugs"
+ ),
+ esc_attr( wp_kses( wp_sprintf( _x( 'Deactivate %l', '%l = list of Jetpack module/feature names', 'jetpack' ), $module_names ), array() ) )
+ ), array( 'a' => array( 'href' => true, 'title' => true ) ) );
+ ?>
+
+
+secret );
}
- function build_connect_url( $raw = false ) {
+ function build_connect_url( $raw = false, $redirect = false ) {
if ( !Jetpack::get_option( 'blog_token' ) ) {
$url = wp_nonce_url( add_query_arg( 'action', 'register', menu_page_url( 'jetpack', false ) ), 'jetpack-register' );
} else {
@@ -1819,16 +2218,21 @@ p {
$user = wp_get_current_user();
+ $redirect = $redirect ? esc_url_raw( $redirect ) : '';
+
$args = urlencode_deep( array(
'response_type' => 'code',
'client_id' => Jetpack::get_option( 'id' ),
'redirect_uri' => add_query_arg( array(
'action' => 'authorize',
- '_wpnonce' => wp_create_nonce( "jetpack-authorize_$role" ),
+ '_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
+ 'redirect' => $redirect ? urlencode( $redirect ) : false,
), menu_page_url( 'jetpack', false ) ),
'state' => $user->ID,
'scope' => $signed_role,
'user_email' => $user->user_email,
+ 'user_login' => $user->user_login,
+ 'is_active' => Jetpack::is_active(),
) );
$url = add_query_arg( $args, Jetpack::api_url( 'authorize' ) );
@@ -1837,8 +2241,8 @@ p {
return $raw ? $url : esc_url( $url );
}
- function admin_url( $args = null ) {
- $url = menu_page_url( 'jetpack', false );
+ public static function admin_url( $args = null ) {
+ $url = admin_url( 'admin.php?page=jetpack' );
if ( is_array( $args ) )
$url = add_query_arg( $args, $url );
return $url;
@@ -1860,6 +2264,9 @@ p {
$role = $this->translate_current_user_to_role();
$is_connected = Jetpack::is_active();
+ $user_token = Jetpack_Data::get_access_token($current_user->ID);
+ $is_user_connected = $user_token && !is_wp_error($user_token);
+ $is_master_user = $current_user->ID == Jetpack::get_option( 'master_user' );
$module = false;
?>
@@ -1869,9 +2276,17 @@ p {
-
+
'id',
'BLOG_TOKEN' => 'blog_token',
- 'USER_TOKEN' => 'user_token',
+ 'MASTER_USER' => 'master_user',
'CERT' => 'fallback_no_verify_ssl_certs',
'TIME_DIFF' => 'time_diff',
'VERSION' => 'version',
'OLD_VERSION' => 'old_version',
+ 'PUBLIC' => 'public',
) as $label => $option_name ) :
?>
:
+ USER_ID:
+ USER_TOKEN:
PHP_VERSION:
WORDPRESS_VERSION:
@@ -2017,7 +2466,7 @@ p {
}
function admin_screen_configure_module( $module_id ) {
- if ( !in_array( $module_id, $this->get_active_modules() ) )
+ if ( !in_array( $module_id, Jetpack::get_active_modules() ) || !current_user_can( 'manage_options' ) )
return false; ?>
@@ -2033,7 +2482,7 @@ p {
-
-
+
+
' . __( 'Configure', 'jetpack' ) . '';
+ if ( current_user_can( 'manage_options' ) && apply_filters( 'jetpack_module_configurable_' . $module, false ) ) {
+ echo '' . __( 'Configure', 'jetpack' ) . ' ';
}
- ?>
+ ?>
@@ -2197,9 +2646,9 @@ p {
exit;
}
- $this->load_xml_rpc_client();
+ Jetpack::load_xml_rpc_client();
$xml = new Jetpack_IXR_Client( array(
- 'user_id' => $GLOBALS['current_user']->ID
+ 'user_id' => JETPACK_MASTER_USER,
) );
$xml->query( 'jetpack.checkNewsSubscription' );
if ( $xml->isError() ) {
@@ -2215,9 +2664,9 @@ p {
exit;
}
- $this->load_xml_rpc_client();
+ Jetpack::load_xml_rpc_client();
$xml = new Jetpack_IXR_Client( array(
- 'user_id' => $GLOBALS['current_user']->ID
+ 'user_id' => JETPACK_MASTER_USER,
) );
$xml->query( 'jetpack.subscribeToNews' );
if ( $xml->isError() ) {
@@ -2233,17 +2682,16 @@ p {
/**
* Returns the requested Jetpack API URL
*
- * @static
* @return string
*/
- function api_url( $relative_url ) {
+ public static function api_url( $relative_url ) {
return trailingslashit( JETPACK__API_BASE . $relative_url ) . JETPACK__API_VERSION . '/';
}
/**
* Some hosts disable the OpenSSL extension and so cannot make outgoing HTTPS requsets
*/
- function fix_url_for_bad_hosts( $url, &$args ) {
+ public static function fix_url_for_bad_hosts( $url, &$args ) {
if ( 0 !== strpos( $url, 'https://' ) ) {
return $url;
}
@@ -2270,19 +2718,17 @@ p {
/**
* Returns the Jetpack XML-RPC API
*
- * @static
* @return string
*/
- function xmlrpc_api_url() {
+ public static function xmlrpc_api_url() {
$base = preg_replace( '#(https?://[^?/]+)(/?.*)?$#', '\\1', JETPACK__API_BASE );
return untrailingslashit( $base ) . '/xmlrpc.php';
}
/**
- * @static
* @return bool|WP_Error
*/
- function register() {
+ public static function register() {
Jetpack::update_option( 'register', wp_generate_password( 32, false ) . ':' . wp_generate_password( 32, false ) . ':' . ( time() + 600 ) );
@list( $secret_1, $secret_2, $secret_eol ) = explode( ':', Jetpack::get_option( 'register' ) );
@@ -2337,24 +2783,38 @@ p {
$code_type = intval( $code / 100 );
if ( 5 == $code_type ) {
- return new Jetpack_error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
+ return new Jetpack_Error( 'wpcom_5??', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
} elseif ( 408 == $code ) {
- return new Jetpack_error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
+ return new Jetpack_Error( 'wpcom_408', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
} elseif ( !empty( $json->error ) ) {
$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
return new Jetpack_Error( (string) $json->error, $error_description, $code );
} elseif ( 200 != $code ) {
- return new Jetpack_error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
+ return new Jetpack_Error( 'wpcom_bad_response', sprintf( __( 'Error Details: %s', 'jetpack' ), $code ), $code );
+ }
+
+ // Jetpack ID error block
+ if ( empty( $json->jetpack_id ) ) {
+ return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ), $entity );
+ } elseif ( ! is_scalar( $json->jetpack_id ) ) {
+ return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID is not a scalar. Do not publicly post this error message! %s', 'jetpack' ) , $entity ), $entity );
+ } elseif ( preg_match( '/[^0-9]/', $json->jetpack_id ) ) {
+ return new Jetpack_Error( 'jetpack_id', sprintf( __( 'Error Details: Jetpack ID begins with a numeral. Do not publicly post this error message! %s', 'jetpack' ) , $entity ), $entity);
}
- if ( empty( $json->jetpack_id ) || !is_scalar( $json->jetpack_id ) || preg_match( '/[^0-9]/', $json->jetpack_id ) )
- return new Jetpack_Error( 'jetpack_id', '', $code );
if ( empty( $json->jetpack_secret ) || !is_string( $json->jetpack_secret ) )
return new Jetpack_Error( 'jetpack_secret', '', $code );
+ if ( isset( $json->jetpack_public ) ) {
+ $jetpack_public = (int) $json->jetpack_public;
+ } else {
+ $jetpack_public = false;
+ }
+
Jetpack::update_options( array(
'id' => (int) $json->jetpack_id,
'blog_token' => (string) $json->jetpack_secret,
+ 'public' => $jetpack_public,
) );
return true;
@@ -2366,23 +2826,21 @@ p {
/**
* Loads the Jetpack XML-RPC client
*/
- function load_xml_rpc_client() {
+ public static function load_xml_rpc_client() {
require_once ABSPATH . WPINC . '/class-IXR.php';
require_once dirname( __FILE__ ) . '/class.jetpack-ixr-client.php';
}
/**
- * Authenticates XML-RPC requests from the Jetpack Server
- *
- * We don't actually know who the real user is; we set it to the account that created the connection.
+ * Authenticates XML-RPC and other requests from the Jetpack Server
*/
- function authenticate_xml_rpc( $user, $username, $password ) {
+ function authenticate_jetpack( $user, $username, $password ) {
if ( is_a( $user, 'WP_User' ) ) {
return $user;
}
// It's not for us
- if ( !isset( $_GET['for'] ) || 'jetpack' != $_GET['for'] || !isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
+ if ( !isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
return $user;
}
@@ -2409,7 +2867,34 @@ p {
require_once dirname( __FILE__ ) . '/class.jetpack-signature.php';
$jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack::get_option( 'time_diff' ) );
- $signature = $jetpack_signature->sign_current_request( array( 'body' => $this->HTTP_RAW_POST_DATA ) );
+ if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
+ $post_data = $_POST;
+ $file_hashes = array();
+ foreach ( $post_data as $post_data_key => $post_data_value ) {
+ if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
+ continue;
+ }
+ $post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
+ $file_hashes[$post_data_key] = $post_data_value;
+ }
+
+ foreach ( $file_hashes as $post_data_key => $post_data_value ) {
+ unset( $post_data["_jetpack_file_hmac_{$post_data_key}"] );
+ $post_data[$post_data_key] = $post_data_value;
+ }
+
+ ksort( $post_data );
+
+ $body = http_build_query( stripslashes_deep( $post_data ) );
+ } elseif ( is_null( $this->HTTP_RAW_POST_DATA ) ) {
+ $body = file_get_contents( 'php://input' );
+ } else {
+ $body = null;
+ }
+ $signature = $jetpack_signature->sign_current_request( array(
+ 'body' => is_null( $body ) ? $this->HTTP_RAW_POST_DATA : $body
+ ) );
+
if ( !$signature ) {
return $user;
} else if ( is_wp_error( $signature ) ) {
@@ -2418,7 +2903,10 @@ p {
return $user;
}
- if ( !$this->add_nonce( $_GET['timestamp'], $_GET['nonce'] ) ) {
+ $timestamp = (int) $_GET['timestamp'];
+ $nonce = stripslashes( (string) $_GET['nonce'] );
+
+ if ( !$this->add_nonce( $timestamp, $nonce ) ) {
return $user;
}
@@ -2429,8 +2917,15 @@ p {
function add_nonce( $timestamp, $nonce ) {
global $wpdb;
+ static $nonces_used_this_request = array();
+
+ if ( isset( $nonces_used_this_request["$timestamp:$nonce"] ) ) {
+ return $nonces_used_this_request["$timestamp:$nonce"];
+ }
// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce
+ $timestamp = (int) $timestamp;
+ $nonce = $wpdb->escape( $nonce );
// Raw query so we can avoid races: add_option will also update
$show_errors = $wpdb->show_errors( false );
@@ -2441,6 +2936,9 @@ p {
'no'
) );
$wpdb->show_errors( $show_errors );
+
+ $nonces_used_this_request["$timestamp:$nonce"] = $return;
+
return $return;
}
@@ -2453,7 +2951,22 @@ p {
return $methods;
}
- function clean_nonces( $all = false ) {
+ function xmlrpc_options( $options ) {
+ $options['jetpack_version'] = array(
+ 'desc' => __( 'Jetpack Plugin Version' , 'jetpack'),
+ 'readonly' => true,
+ 'value' => JETPACK__VERSION,
+ );
+
+ $options['jetpack_client_id'] = array(
+ 'desc' => __( 'The Client ID/WP.com Blog ID of this site' , 'jetpack'),
+ 'readonly' => true,
+ 'value' => Jetpack::get_option( 'id' ),
+ );
+ return $options;
+ }
+
+ public static function clean_nonces( $all = false ) {
global $wpdb;
$sql = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
@@ -2464,7 +2977,15 @@ p {
$sql_args[] = time() - 3600;
}
- $wpdb->query( $wpdb->prepare( $sql, $sql_args ) );
+ $sql .= ' LIMIT 100';
+
+ $sql = $wpdb->prepare( $sql, $sql_args );
+
+ for ( $i = 0; $i < 1000; $i++ ) {
+ if ( !$wpdb->query( $sql ) ) {
+ break;
+ }
+ }
}
/**
@@ -2475,10 +2996,8 @@ p {
* @param string $key
* @param string $value
* @param bool $restate private
- *
- * @static
*/
- function state( $key = null, $value = null, $restate = false ) {
+ public static function state( $key = null, $value = null, $restate = false ) {
static $state = array();
static $path, $domain;
if ( !isset( $path ) ) {
@@ -2526,17 +3045,50 @@ p {
}
}
- /**
- * @static
- */
- function restate() {
+ public static function restate() {
Jetpack::state( null, null, true );
}
+ public static function check_privacy( $file ) {
+ static $is_site_publicly_accessible = null;
+
+ if ( is_null( $is_site_publicly_accessible ) ) {
+ $is_site_publicly_accessible = false;
+
+ Jetpack::load_xml_rpc_client();
+ $rpc = new Jetpack_IXR_Client();
+
+ $success = $rpc->query( 'jetpack.isSitePubliclyAccessible', home_url() );
+ if ( $success ) {
+ $response = $rpc->getResponse();
+ if ( $response ) {
+ $is_site_publicly_accessible = true;
+ }
+ }
+
+ Jetpack::update_option( 'public', (int) $is_site_publicly_accessible );
+ }
+
+ if ( $is_site_publicly_accessible ) {
+ return;
+ }
+
+ $module_slug = self::get_module_slug( $file );
+
+ $privacy_checks = Jetpack::state( 'privacy_checks' );
+ if ( !$privacy_checks ) {
+ $privacy_checks = $module_slug;
+ } else {
+ $privacy_checks .= ",$module_slug";
+ }
+
+ Jetpack::state( 'privacy_checks', $privacy_checks );
+ }
+
/**
* Helper method for multicall XMLRPC.
*/
- function xmlrpc_async_call() {
+ public static function xmlrpc_async_call() {
global $blog_id;
static $clients = array();
@@ -2545,7 +3097,7 @@ p {
if ( !isset( $clients[$client_blog_id] ) ) {
Jetpack::load_xml_rpc_client();
$clients[$client_blog_id] = new Jetpack_IXR_ClientMulticall( array(
- 'user_id' => get_current_user_id()
+ 'user_id' => JETPACK_MASTER_USER,
) );
ignore_user_abort( true );
add_action( 'shutdown', array( 'Jetpack', 'xmlrpc_async_call' ) );
@@ -2579,7 +3131,12 @@ p {
}
}
- function staticize_subdomain( $url ) {
+ public static function staticize_subdomain( $url ) {
+ $host = parse_url( $url, PHP_URL_HOST );
+ if ( !preg_match( '/.?(?:wordpress|wp)\.com$/', $host ) ) {
+ return $url;
+ }
+
if ( is_ssl() ) {
return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
}
@@ -2590,16 +3147,149 @@ p {
return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
}
+
+/* JSON API Authorization */
+
+ /**
+ * Handles the login action for Authorizing the JSON API
+ */
+ function login_form_json_api_authorization() {
+ $this->verify_json_api_authorization_request();
+
+ add_action( 'wp_login', array( &$this, 'store_json_api_authorization_token' ), 10, 2 );
+
+ add_action( 'login_message', array( &$this, 'login_message_json_api_authorization' ) );
+ add_action( 'login_form', array( &$this, 'preserve_action_in_login_form_for_json_api_authorization' ) );
+ add_filter( 'site_url', array( &$this, 'post_login_form_to_signed_url' ), 10, 3 );
+ }
+
+ // Make sure the login form is POSTed to the signed URL so we can reverify the request
+ function post_login_form_to_signed_url( $url, $path, $scheme ) {
+ if ( 'wp-login.php' !== $path || 'login_post' !== $scheme ) {
+ return $url;
+ }
+
+ return "$url?{$_SERVER['QUERY_STRING']}";
+ }
+
+ // Make sure the POSTed request is handled by the same action
+ function preserve_action_in_login_form_for_json_api_authorization() {
+ echo " \n";
+ }
+
+ // If someone logs in to approve API access, store the Access Code in usermeta
+ function store_json_api_authorization_token( $user_login, $user ) {
+ add_filter( 'login_redirect', array( &$this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
+ add_filter( 'allowed_redirect_hosts', array( &$this, 'allow_wpcom_public_api_domain' ) );
+ $token = wp_generate_password( 32, false );
+ update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
+ }
+
+ // Add public-api.wordpress.com to the safe redirect whitelist - only added when someone allows API access
+ function allow_wpcom_public_api_domain( $domains ) {
+ $domains[] = 'public-api.wordpress.com';
+ return $domains;
+ }
+
+ // Add the Access Code details to the public-api.wordpress.com redirect
+ function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
+ return add_query_arg( urlencode_deep( array(
+ 'jetpack-code' => get_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], true ),
+ 'jetpack-user-id' => (int) $user->ID,
+ 'jetpack-state' => $this->json_api_authorization_request['state'],
+ ) ), $redirect_to );
+ }
+
+ // Verifies the request by checking the signature
+ function verify_json_api_authorization_request() {
+ require_once dirname( __FILE__ ) . '/class.jetpack-signature.php';
+
+ $token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
+ if ( !$token || empty( $token->secret ) ) {
+ wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.' , 'jetpack') );
+ }
+
+ $die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack' );
+
+ $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack::get_option( 'time_diff' ) );
+ $signature = $jetpack_signature->sign_current_request( array( 'body' => null, 'method' => 'GET' ) );
+ if ( !$signature ) {
+ wp_die( $die_error );
+ } else if ( is_wp_error( $signature ) ) {
+ wp_die( $die_error );
+ } else if ( $signature !== $_GET['signature'] ) {
+ if ( is_ssl() ) {
+ // If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well
+ $signature = $jetpack_signature->sign_current_request( array( 'scheme' => 'http', 'body' => null, 'method' => 'GET' ) );
+ if ( !$signature || is_wp_error( $signature ) || $signature !== $_GET['signature'] ) {
+ wp_die( $die_error );
+ }
+ } else {
+ wp_die( $die_error );
+ }
+ }
+
+ $timestamp = (int) $_GET['timestamp'];
+ $nonce = stripslashes( (string) $_GET['nonce'] );
+
+ if ( !$this->add_nonce( $timestamp, $nonce ) ) {
+ // De-nonce the nonce, at least for 5 minutes.
+ // We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed)
+ $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
+ if ( $old_nonce_time < time() - 300 ) {
+ wp_die( __( 'The authorization process expired. Please go back and try again.' , 'jetpack') );
+ }
+ }
+
+ $data = json_decode( base64_decode( stripslashes( $_GET['data'] ) ) );
+ $data_filters = array(
+ 'state' => 'opaque',
+ 'client_id' => 'int',
+ 'client_title' => 'string',
+ 'client_image' => 'url',
+ );
+
+ foreach ( $data_filters as $key => $sanitation ) {
+ if ( !isset( $data->$key ) ) {
+ wp_die( $die_error );
+ }
+
+ switch ( $sanitation ) {
+ case 'int' :
+ $this->json_api_authorization_request[$key] = (int) $data->$key;
+ break;
+ case 'opaque' :
+ $this->json_api_authorization_request[$key] = (string) $data->$key;
+ break;
+ case 'string' :
+ $this->json_api_authorization_request[$key] = wp_kses( (string) $data->$key, array() );
+ break;
+ case 'url' :
+ $this->json_api_authorization_request[$key] = esc_url_raw( (string) $data->$key );
+ break;
+ }
+ }
+
+ if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
+ wp_die( $die_error );
+ }
+ }
+
+ function login_message_json_api_authorization( $message ) {
+ return '' . sprintf(
+ esc_html__( '%s wants to access your site’s data. Log in to authorize that access.' , 'jetpack'),
+ '' . esc_html( $this->json_api_authorization_request['client_title'] ) . ' '
+ ) . '
';
+ }
}
class Jetpack_Client {
/**
* Makes an authorized remote request using Jetpack_Signature
*
- * @static
* @return array|WP_Error WP HTTP response on success
*/
- function remote_request( $args, $body = null ) {
+ public static function remote_request( $args, $body = null ) {
$defaults = array(
'url' => '',
'user_id' => 0,
@@ -2612,14 +3302,13 @@ class Jetpack_Client {
$args = wp_parse_args( $args, $defaults );
- $args['user_id'] = (int) $args['user_id'];
$args['blog_id'] = (int) $args['blog_id'];
if ( 'header' != $args['auth_location'] ) {
$args['auth_location'] = 'query_string';
}
- $token = Jetpack_Data::get_access_token( $args );
+ $token = Jetpack_Data::get_access_token( $args['user_id'] );
if ( !$token ) {
return new Jetpack_Error( 'missing_token' );
}
@@ -2715,10 +3404,9 @@ class Jetpack_Client {
* @todo: Better fallbacks (bundled certs?), feedback, UI, ....
* @see Jetpack::fix_url_for_bad_hosts()
*
- * @static
* @return array|WP_Error WP HTTP response on success
*/
- function _wp_remote_request( $url, $args, $set_fallback = false ) {
+ public static function _wp_remote_request( $url, $args, $set_fallback = false ) {
$fallback = Jetpack::get_option( 'fallback_no_verify_ssl_certs' );
if ( false === $fallback ) {
Jetpack::update_option( 'fallback_no_verify_ssl_certs', 0 );
@@ -2777,7 +3465,7 @@ class Jetpack_Client {
return $response;
}
- function set_time_diff( &$response, $force_set = false ) {
+ public static function set_time_diff( &$response, $force_set = false ) {
$code = wp_remote_retrieve_response_code( $response );
// Only trust the Date header on some responses
@@ -2810,23 +3498,28 @@ class Jetpack_Data {
/**
* Gets locally stored token
*
- * @static
* @return object|false
*/
- function get_access_token( $args ) {
- if ( is_numeric( $args ) ) {
- $args = array( 'user_id' => $args );
- }
-
- if ( $args['user_id'] ) {
- if ( !$token = Jetpack::get_option( 'user_token' ) ) {
+ public static function get_access_token( $user_id = false ) {
+ if ( $user_id ) {
+ if ( !$tokens = Jetpack::get_option( 'user_tokens' ) ) {
+ return false;
+ }
+ if ( $user_id === JETPACK_MASTER_USER ) {
+ if ( !$user_id = Jetpack::get_option( 'master_user' ) ) {
+ return false;
+ }
+ }
+ if ( !isset( $tokens[$user_id] ) || !$token = $tokens[$user_id] ) {
return false;
}
$token_chunks = explode( '.', $token );
if ( empty( $token_chunks[1] ) || empty( $token_chunks[2] ) ) {
return false;
}
- $args['user_id'] = $token_chunks[2];
+ if ( $user_id != $token_chunks[2] ) {
+ return false;
+ }
$token = "{$token_chunks[0]}.{$token_chunks[1]}";
} else {
$token = Jetpack::get_option( 'blog_token' );
@@ -2837,7 +3530,7 @@ class Jetpack_Data {
return (object) array(
'secret' => $token,
- 'external_user_id' => (int) $args['user_id'],
+ 'external_user_id' => (int) $user_id,
);
}
}
@@ -2854,6 +3547,8 @@ class Jetpack_Client_Server {
$args = array();
+ $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
+
do {
$jetpack = Jetpack::init();
$role = $jetpack->translate_current_user_to_role();
@@ -2868,7 +3563,7 @@ class Jetpack_Client_Server {
break;
}
- check_admin_referer( "jetpack-authorize_$role" );
+ check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
if ( !empty( $data['error'] ) ) {
Jetpack::state( 'error', $data['error'] );
@@ -2914,8 +3609,18 @@ class Jetpack_Client_Server {
break;
}
- Jetpack::update_option( 'user_token', sprintf( '%s.%d', $token, $current_user_id ), true );
- Jetpack::state( 'message', 'authorized' );
+ $is_master_user = ! Jetpack::is_active();
+
+ Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_master_user );
+
+
+ if ( $is_master_user ) {
+ Jetpack::state( 'message', 'authorized' );
+ } else {
+ Jetpack::state( 'message', 'linked' );
+ // Don't activate anything since we are just connecting a user.
+ break;
+ }
if ( $active_modules = Jetpack::get_option( 'active_modules' ) ) {
Jetpack::delete_option( 'active_modules' );
@@ -2925,16 +3630,23 @@ class Jetpack_Client_Server {
Jetpack::activate_default_modules();
}
+ $jetpack->sync->register( 'noop' ); // Spawn a sync to make sure the Jetpack Servers know what modules are active.
+
// Start nonce cleaner
wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
} while ( false );
- wp_safe_redirect( Jetpack::admin_url() );
+ if ( wp_validate_redirect( $redirect ) ) {
+ wp_safe_redirect( $redirect );
+ } else {
+ wp_safe_redirect( Jetpack::admin_url() );
+ }
+
exit;
}
- function deactivate_plugin( $probable_file, $probable_title ) {
+ public static function deactivate_plugin( $probable_file, $probable_title ) {
if ( is_plugin_active( $probable_file ) ) {
deactivate_plugins( $probable_file );
return 1;
@@ -2964,11 +3676,13 @@ class Jetpack_Client_Server {
return new Jetpack_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) );
}
- $client_secret = Jetpack_Data::get_access_token( 0 );
+ $client_secret = Jetpack_Data::get_access_token();
if ( !$client_secret ) {
return new Jetpack_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) );
}
+ $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
+
$body = array(
'client_id' => Jetpack::get_option( 'id' ),
'client_secret' => $client_secret->secret,
@@ -2976,7 +3690,8 @@ class Jetpack_Client_Server {
'code' => $data['code'],
'redirect_uri' => add_query_arg( array(
'action' => 'authorize',
- '_wpnonce' => wp_create_nonce( "jetpack-authorize_$role" ),
+ '_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
+ 'redirect' => $redirect ? urlencode( $redirect ) : false,
), menu_page_url( 'jetpack', false ) ),
);
@@ -3044,64 +3759,119 @@ class Jetpack_Client_Server {
* Jetpack server for remote processing/notifications/etc
*/
class Jetpack_Sync {
- var $sync = array();
+ // What modules want to sync what content
+ var $sync_conditions = array( 'posts' => array(), 'comments' => array() );
+
+ // We keep track of all the options registered for sync so that we can sync them all if needed
+ var $sync_options = array();
+
+ // Keep trac of status transitions, which we wouldn't always know about on the Jetpack Servers but are important when deciding what to do with the sync.
var $post_transitions = array();
+ var $comment_transitions = array();
+
+ // Objects to sync
+ var $sync = array();
- function Jetpack_Sync() {
- add_action( 'transition_post_status', array( $this, 'track_post_transition' ), 1, 3 );
+ function __construct() {
+ // WP Cron action. Only used on upgrade
+ add_action( 'jetpack_sync_all_registered_options', array( $this, 'sync_all_registered_options' ) );
}
- function track_post_transition( $new_status, $old_status, $post ) {
- if ( empty( $post->ID ) ) {
- return;
- }
+/* Static Methods for Modules */
- if ( isset( $this->post_transitions[$post->ID] ) ) {
- $this->post_transitions[$post->ID][0] = $new_status;
- return;
- }
+ /**
+ * @param string $file __FILE__
+ * @param array settings:
+ * post_types => array( post_type slugs ): The post types to sync. Default: post, page
+ * post_stati => array( post_status slugs ): The post stati to sync. Default: publish
+ */
+ static function sync_posts( $file, array $settings = null ) {
+ $jetpack = Jetpack::init();
+ $args = func_get_args();
+ return call_user_func_array( array( $jetpack->sync, 'posts' ), $args );
+ }
- $this->post_transitions[$post->ID] = array( $new_status, $old_status );
+ /**
+ * @param string $file __FILE__
+ * @param array settings:
+ * post_types => array( post_type slugs ): The post types to sync. Default: post, page
+ * post_stati => array( post_status slugs ): The post stati to sync. Default: publish
+ * comment_types => array( comment_type slugs ): The comment types to sync. Default: '', comment, trackback, pingback
+ * comment_stati => array( comment_status slugs ): The comment stati to sync. Default: approved
+ */
+ static function sync_comments( $file, array $settings = null ) {
+ $jetpack = Jetpack::init();
+ $args = func_get_args();
+ return call_user_func_array( array( $jetpack->sync, 'comments' ), $args );
}
+ /**
+ * @param string $file __FILE__
+ * @param string $option, Option name to sync
+ * @param string $option ...
+ */
+ static function sync_options( $file, $option /*, $option, ... */ ) {
+ $jetpack = Jetpack::init();
+ $args = func_get_args();
+ return call_user_func_array( array( $jetpack->sync, 'options' ), $args );
+ }
+
+/* Internal Methods */
+
/**
* Create a sync object/request
*
- * @param string $object Type of object to sync -- [ post | comment ]
+ * @param string $object Type of object to sync -- [ post | comment | option ]
* @param int $id Unique identifier
- * @param array $specifics Specific fields/elements of that object to sync. Defaults to syncing all data for the $object
+ * @param array $settings
*/
- function register( $object, $id = false, $specifics = true ) {
+ function register( $object, $id = false, array $settings = null ) {
// Since we've registered something for sync, hook it up to execute on shutdown if we haven't already
if ( !$this->sync ) {
ignore_user_abort( true );
add_action( 'shutdown', array( $this, 'sync' ), 9 ); // Right before async XML-RPC
}
- $this->add_to_array( $this->sync, $object, $id, $specifics );
- return true;
- }
+ $defaults = array(
+ 'on_behalf_of' => array(), // What modules want this data
+ );
+ $settings = wp_parse_args( $settings, $defaults );
+
+ if ( !isset( $this->sync[$object] ) ) {
+ $this->sync[$object] = array();
+ }
- function add_to_array( &$array, $object, $id, $data ) {
- if ( !isset( $array[$object] ) ) {
- $array[$object] = array( $id => $data );
- } else if ( !isset( $array[$object][$id] ) ) {
- $array[$object][$id] = $data;
+ // Store the settings for this object
+ if (
+ // First time for this object
+ !isset( $this->sync[$object][$id] )
+ ) {
+ // Easy: store the current settings
+ $this->sync[$object][$id] = $settings;
} else {
- if ( true === $array[$object][$id] || true === $data )
- $array[$object][$id] = true;
- else
- $array[$object][$id] = array_merge( $array[$object][$id], $data );
+ // Not as easy: we have to manually merge the settings from previous runs for this object with the settings for this run
+
+ $this->sync[$object][$id]['on_behalf_of'] = array_unique( array_merge( $this->sync[$object][$id]['on_behalf_of'], $settings['on_behalf_of'] ) );
}
- }
- /**
- * Set up all the data and queue it for the outgoing XML-RPC request
- */
- function sync() {
- global $wpdb;
- $jetpack = Jetpack::init();
+ $delete_prefix = 'delete_';
+ if ( 0 === strpos( $object, $delete_prefix ) ) {
+ $unset_object = substr( $object, strlen( $delete_prefix ) );
+ } else {
+ $unset_object = "{$delete_prefix}{$object}";
+ }
+
+ // Ensure post ... delete_post yields a delete operation
+ // Ensure delete_post ... post yields a sync post operation
+ // Ensure update_option() ... delete_option() ends up as a delete
+ // Ensure delete_option() ... update_option() ends up as an update
+ // Etc.
+ unset( $this->sync[$unset_object][$id] );
+ return true;
+ }
+
+ function get_common_sync_data() {
$available_modules = Jetpack::get_available_modules();
$active_modules = Jetpack::get_active_modules();
$modules = array();
@@ -3110,174 +3880,599 @@ class Jetpack_Sync {
}
$modules['vaultpress'] = class_exists( 'VaultPress' ) || function_exists( 'vaultpress_contact_service' );
- $sync_data = compact( 'modules' );
-
- if ( count( $this->sync ) ) {
- foreach ( $this->sync as $obj => $data ) {
- switch ( $obj ) {
- case 'post':
- $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
- $GLOBALS['post'] = null;
- foreach ( $data as $post => $columns ) {
- $sync_data['post'][$post] = $jetpack->get_post( $post, $columns );
- if ( isset( $this->post_transitions[$post] ) ) {
- $sync_data['post'][$post]['transitions'] = $this->post_transitions[$post];
- } else {
- $sync_data['post'][$post]['transitions'] = array( false, false );
- }
- }
- $GLOBALS['post'] = $global_post;
- unset( $global_post );
- break;
+ $sync_data = array(
+ 'modules' => $modules,
+ 'version' => JETPACK__VERSION,
+ );
- case 'delete_post':
- foreach ( $data as $post => $true ) {
- $sync_data['delete_post'][$post] = true;
- }
- break;
+ return $sync_data;
+ }
- case 'comment':
- $global_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null;
- unset( $GLOBALS['comment'] );
- foreach ( $data as $comment => $columns ) {
- $sync_data['comment'][$comment] = $jetpack->get_comment( $comment, $columns );
- }
- $GLOBALS['comment'] = $global_comment;
- unset( $global_comment );
- break;
+ /**
+ * Set up all the data and queue it for the outgoing XML-RPC request
+ */
+ function sync() {
+ if ( !$this->sync ) {
+ return false;
+ }
- case 'delete_comment':
- foreach ( $data as $comment => $true ) {
- $sync_data['delete_comment'][$comment] = true;
- }
- break;
+ $sync_data = $this->get_common_sync_data();
- case 'tag':
- foreach ( $data as $taxonomy => $columns ) {
- $sync_data['tag'][$taxonomy] = $jetpack->get_taxonomy( $taxonomy, $columns, 'post_tag' );
- }
- break;
+ $wp_importing = defined( 'WP_IMPORTING' ) && WP_IMPORTING;
- case 'delete_tag':
- foreach ( $data as $taxonomy => $columns ) {
- $sync_data['delete_tag'][$taxonomy] = $columns;
- }
+ foreach ( $this->sync as $sync_operation_type => $sync_operations ) {
+ switch ( $sync_operation_type ) {
+ case 'post':
+ if ( $wp_importing ) {
break;
+ }
- case 'category':
- foreach ( $data as $taxonomy => $columns ) {
- $sync_data['category'][$taxonomy] = $jetpack->get_taxonomy( $taxonomy, $columns, 'category' );
+ $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null;
+ $GLOBALS['post'] = null;
+ foreach ( $sync_operations as $post_id => $settings ) {
+ $sync_data['post'][$post_id] = $this->get_post( $post_id );
+ if ( isset( $this->post_transitions[$post_id] ) ) {
+ $sync_data['post'][$post_id]['transitions'] = $this->post_transitions[$post_id];
+ } else {
+ $sync_data['post'][$post_id]['transitions'] = array( false, false );
}
+ $sync_data['post'][$post_id]['on_behalf_of'] = $settings['on_behalf_of'];
+ }
+ $GLOBALS['post'] = $global_post;
+ unset( $global_post );
+ break;
+ case 'comment':
+ if ( $wp_importing ) {
break;
+ }
- case 'delete_category':
- foreach ( $data as $taxonomy => $columns ) {
- $sync_data['delete_category'][$taxonomy] = $columns;
+ $global_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null;
+ unset( $GLOBALS['comment'] );
+ foreach ( $sync_operations as $comment_id => $settings ) {
+ $sync_data['comment'][$comment_id] = $this->get_comment( $comment_id );
+ if ( isset( $this->comment_transitions[$comment_id] ) ) {
+ $sync_data['comment'][$comment_id]['transitions'] = $this->comment_transitions[$comment_id];
+ } else {
+ $sync_data['comment'][$comment_id]['transitions'] = array( false, false );
}
- break;
+ $sync_data['comment'][$comment_id]['on_behalf_of'] = $settings['on_behalf_of'];
}
- }
+ $GLOBALS['comment'] = $global_comment;
+ unset( $global_comment );
+ break;
+ case 'option' :
+ foreach ( $sync_operations as $option => $settings ) {
+ $sync_data['option'][$option] = array( 'value' => get_option( $option ) );
+ }
+ break;
- Jetpack::xmlrpc_async_call( 'jetpack.syncContent', $sync_data );
+ case 'delete_post':
+ case 'delete_comment':
+ foreach ( $sync_operations as $object_id => $settings ) {
+ $sync_data[$sync_operation_type][$object_id] = array( 'on_behalf_of' => $settings['on_behalf_of'] );
+ }
+ break;
+ case 'delete_option' :
+ foreach ( $sync_operations as $object_id => $settings ) {
+ $sync_data[$sync_operation_type][$object_id] = true;
+ }
+ break;
+ }
}
+
+ Jetpack::xmlrpc_async_call( 'jetpack.syncContent', $sync_data );
}
- function taxonomy( $slug, $fields = true, $type ) {
- if ( !get_term_by( 'slug', $slug, $type ) ) {
- return false;
+ /**
+ * Format and return content data from a direct xmlrpc request for it.
+ *
+ * @param array $content_ids: array( 'posts' => array of ids, 'comments' => array of ids, 'options' => array of options )
+ */
+ function get_content( $content_ids ) {
+ $sync_data = $this->get_common_sync_data();
+
+ if ( isset( $content_ids['posts'] ) ) {
+ foreach ( $content_ids['posts'] as $id ) {
+ $sync_data['post'][$id] = $this->get_post( $id );
+ }
}
- if ( 'post_tag' == $type )
- return $this->register( 'tag', $slug, $fields );
- else
- return $this->register( 'category', $slug, $fields );
+ if ( isset( $content_ids['comments'] ) ) {
+ foreach ( $content_ids['comments'] as $id ) {
+ $sync_data['comment'][$id] = $this->get_post( $id );
+ }
+ }
+
+ if ( isset( $content_ids['options'] ) ) {
+ foreach ( $content_ids['options'] as $option ) {
+ $sync_data['option'][$option] = array( 'value' => get_option( $option ) );
+ }
+ }
+
+ return $sync_data;
}
/**
- * Request that a post be deleted remotely
+ * Helper method for registering a post for sync
*
- * @param int $id The post_ID
+ * @param int $id wp_posts.ID
+ * @param array $settings Sync data
*/
- function delete_taxonomy( $slugs, $type ) {
- if ( 'post_tag' == $type )
- return $this->register( 'delete_tag', 1, $slugs );
- else
- return $this->register( 'delete_category', 1, $slugs );
+ function register_post( $id, array $settings = null ) {
+ $id = (int) $id;
+ if ( !$id ) {
+ return false;
+ }
+
+ $post = get_post( $id );
+ if ( !$post ) {
+ return false;
+ }
+
+ $settings = wp_parse_args( $settings, array(
+ 'on_behalf_of' => array(),
+ ) );
+
+ return $this->register( 'post', $id, $settings );
}
/**
- * Helper method for easily requesting a sync of a post.
+ * Helper method for registering a comment for sync
*
- * @param int $id wp_posts.ID
- * @param array $fields Array containing field/column names to sync (optional, defaults to all fields)
+ * @param int $id wp_comments.comment_ID
+ * @param array $settings Sync data
*/
- function post( $id, $fields = true ) {
- if ( !$id = (int) $id ) {
+ function register_comment( $id, array $settings = null ) {
+ $id = (int) $id;
+ if ( !$id ) {
return false;
}
- if ( false === $fields ) {
- $fields = array( '_jetpack_backfill' );
- }
- if ( is_array( $fields ) ) {
- $fields = array_merge( $fields, array( 'ID', 'post_title', 'post_name', 'guid', 'post_date', 'post_date_gmt', 'post_parent', 'post_type', 'post_status' ) );
+ $comment = get_comment( $id );
+ if ( !$comment || empty( $comment->comment_post_ID ) ) {
+ return false;
}
- if ( !$post = get_post( $id ) ) {
+ $post = get_post( $comment->comment_post_ID );
+ if ( !$post ) {
return false;
}
- if (
- !empty( $post->post_password )
- ||
- !in_array( $post->post_type, get_post_types( array( 'public' => true ) ) )
- ||
- !in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) )
- ) {
+ $settings = wp_parse_args( $settings, array(
+ 'on_behalf_of' => array(),
+ ) );
+
+ return $this->register( 'comment', $id, $settings );
+ }
+
+/* Posts Sync */
+
+ function posts( $file, array $settings = null ) {
+ $module_slug = Jetpack::get_module_slug( $file );
+
+ $defaults = array(
+ 'post_types' => array( 'post', 'page' ),
+ 'post_stati' => array( 'publish' ),
+ );
+
+ $this->sync_conditions['posts'][$module_slug] = wp_parse_args( $settings, $defaults );
+
+ add_action( 'transition_post_status', array( $this, 'transition_post_status_action' ), 10, 3 );
+ add_action( 'delete_post', array( $this, 'delete_post_action' ) );
+ }
+
+ function delete_post_action( $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post ) {
+ return $this->register( 'delete_post', (int) $post_id );
+ }
+
+ $this->transition_post_status_action( 'delete', $post->post_status, $post );
+ }
+
+ function transition_post_status_action( $new_status, $old_status, $post ) {
+ $sync = $this->get_post_sync_operation( $new_status, $old_status, $post, $this->sync_conditions['posts'] );
+ if ( !$sync ) {
+ // No module wants to sync this post
return false;
}
- return $this->register( 'post', (int) $id, $fields );
+ // Track post transitions
+ if ( isset( $this->post_transitions[$post->ID] ) ) {
+ // status changed more than once - keep tha most recent $new_status
+ $this->post_transitions[$post->ID][0] = $new_status;
+ } else {
+ $this->post_transitions[$post->ID] = array( $new_status, $old_status );
+ }
+
+ $operation = $sync['operation'];
+ unset( $sync['operation'] );
+
+ switch ( $operation ) {
+ case 'delete' :
+ return $this->register( 'delete_post', (int) $post->ID, $sync );
+ case 'submit' :
+ return $this->register_post( (int) $post->ID, $sync );
+ }
+ }
+
+ function get_post_sync_operation( $new_status, $old_status, $post, $module_conditions ) {
+ $delete_on_behalf_of = array();
+ $submit_on_behalf_of = array();
+ $delete_stati = array( 'delete' );
+
+ foreach ( $module_conditions as $module => $conditions ) {
+ if ( !in_array( $post->post_type, $conditions['post_types'] ) ) {
+ continue;
+ }
+
+ $deleted_post = in_array( $new_status, $delete_stati );
+
+ if ( $deleted_post ) {
+ $delete_on_behalf_of[] = $module;
+ } else {
+ clean_post_cache( $post->ID );
+ $new_status = get_post_status( $post->ID ); // Inherited status is resolved here
+ }
+
+ $old_status_in_stati = in_array( $old_status, $conditions['post_stati'] );
+ $new_status_in_stati = in_array( $new_status, $conditions['post_stati'] );
+
+ if ( $old_status_in_stati && !$new_status_in_stati ) {
+ // Jetpack no longer needs the post
+ if ( !$deleted_post ) {
+ $delete_on_behalf_of[] = $module;
+ } // else, we've already flagged it above
+ continue;
+ }
+
+ if ( !$new_status_in_stati ) {
+ continue;
+ }
+
+ // At this point, we know we want to sync the post, not delete it
+ $submit_on_behalf_of[] = $module;
+ }
+
+ if ( !empty( $submit_on_behalf_of ) ) {
+ return array( 'operation' => 'submit', 'on_behalf_of' => $submit_on_behalf_of );
+ }
+
+ if ( !empty( $delete_on_behalf_of ) ) {
+ return array( 'operation' => 'delete', 'on_behalf_of' => $delete_on_behalf_of );
+ }
+
+ return false;
}
/**
- * Request that a post be deleted remotely
+ * Get a post and associated data in the standard JP format.
+ * Cannot be called statically
*
- * @param int $id The post_ID
+ * @param int $id Post ID
+ * @return Array containing full post details
*/
- function delete_post( $id ) {
- return $this->register( 'delete_post', (int) $id, true );
+ function get_post( $id ) {
+ $post_obj = get_post( $id );
+ if ( !$post_obj )
+ return false;
+
+ if ( is_callable( $post_obj, 'to_array' ) ) {
+ // WP >= 3.5
+ $post = $post_obj->to_array();
+ } else {
+ // WP < 3.5
+ $post = get_object_vars( $post_obj );
+ }
+
+ if ( 0 < strlen( $post['post_password'] ) ) {
+ $post['post_password'] = 'auto-' . wp_generate_password( 10, false ); // We don't want the real password. Just pass something random.
+ }
+
+ // local optimizations
+ unset(
+ $post['filter'],
+ $post['ancestors'],
+ $post['post_content_filtered'],
+ $post['to_ping'],
+ $post['pinged']
+ );
+
+ if ( $this->is_post_public( $post ) ) {
+ $post['post_is_public'] = Jetpack::get_option( 'public' );
+ } else {
+ //obscure content
+ $post['post_content'] = '';
+ $post['post_excerpt'] = '';
+ $post['post_is_public'] = false;
+ }
+ $post_type_obj = get_post_type_object( $post['post_type'] );
+ $post['post_is_excluded_from_search'] = $post_type_obj->exclude_from_search;
+
+ $post['tax'] = array();
+ $taxonomies = get_object_taxonomies( $post_obj );
+ foreach ( $taxonomies as $taxonomy ) {
+ $terms = get_object_term_cache( $post_obj->ID, $taxonomy );
+ if ( empty( $terms ) )
+ $terms = wp_get_object_terms( $post_obj->ID, $taxonomy );
+ $term_names = array();
+ foreach ( $terms as $term ) {
+ $term_names[] = $term->name;
+ }
+ $post['tax'][$taxonomy] = $term_names;
+ }
+
+ $meta = get_post_meta( $post_obj->ID, false );
+ $post['meta'] = array();
+ foreach ( $meta as $key => $value ) {
+ $post['meta'][$key] = array_map( 'maybe_unserialize', $value );
+ }
+
+ $post['extra'] = array(
+ 'author' => get_the_author_meta( 'display_name', $post_obj->post_author ),
+ 'author_email' => get_the_author_meta( 'email', $post_obj->post_author ),
+ );
+
+ if ( $fid = get_post_thumbnail_id( $id ) ) {
+ $feature = wp_get_attachment_image_src( $fid, 'large' );
+ if ( !empty( $feature[0] ) )
+ $post['extra']['featured_image'] = $feature[0];
+ }
+
+ $post['permalink'] = get_permalink( $post_obj->ID );
+ $post['shortlink'] = wp_get_shortlink( $post_obj->ID );
+ return $post;
}
/**
- * Helper method for easily requesting a sync of a comment.
+ * Decide whether a post/page/attachment is visible to the public.
*
- * @param int $id wp_comments.ID
- * @param array $fields Array containing field/column names to sync (optional, defaults to all fields). Should always use default.
+ * @param array $post
+ * @return bool
*/
- function comment( $id, $fields = true ) {
- if ( !$comment = get_comment( $id ) ) {
+ function is_post_public( $post ) {
+ if ( !is_array( $post ) ) {
+ $post = (array) $post;
+ }
+
+ if ( 0 < strlen( $post['post_password'] ) )
+ return false;
+ if ( ! in_array( $post['post_type'], get_post_types( array( 'public' => true ) ) ) )
+ return false;
+ $post_status = get_post_status( $post['ID'] ); // Inherited status is resolved here.
+ if ( ! in_array( $post_status, get_post_stati( array( 'public' => true ) ) ) )
return false;
+ return true;
+ }
+
+/* Comments Sync */
+
+ function comments( $file, array $settings = null ) {
+ $module_slug = Jetpack::get_module_slug( $file );
+
+ $defaults = array(
+ 'post_types' => array( 'post', 'page' ), // For what post types will we sync comments?
+ 'post_stati' => array( 'publish' ), // For what post stati will we sync comments?
+ 'comment_types' => array( '', 'comment', 'trackback', 'pingback' ), // What comment types will we sync?
+ 'comment_stati' => array( 'approved' ), // What comment stati will we sync?
+ );
+
+ $settings = wp_parse_args( $settings, $defaults );
+
+ $this->sync_conditions['comments'][$module_slug] = $settings;
+
+ add_action( 'wp_insert_comment', array( $this, 'wp_insert_comment_action' ), 10, 2 );
+ add_action( 'transition_comment_status', array( $this, 'transition_comment_status_action' ), 10, 3 );
+ add_action( 'edit_comment', array( $this, 'edit_comment_action' ) );
+ }
+
+ /*
+ * This is really annoying. If you edit a comment, but don't change the status, WordPress doesn't fire the transition_comment_status hook.
+ * That means we have to catch these comments on the edit_comment hook, but ignore comments on that hook when the transition_comment_status does fire.
+ */
+ function edit_comment_action( $comment_id ) {
+ $comment = get_comment( $comment_id );
+ $new_status = $this->translate_comment_status( $comment->comment_approved );
+ add_action( "comment_{$new_status}_{$comment->comment_type}", array( $this, 'transition_comment_status_for_comments_whose_status_does_not_change' ), 10, 2 );
+ }
+
+ function wp_insert_comment_action( $comment_id, $comment ) {
+ $this->transition_comment_status_action( $comment->comment_approved, 'new', $comment );
+ }
+
+ function transition_comment_status_for_comments_whose_status_does_not_change( $comment_id, $comment ) {
+ if ( isset( $this->comment_transitions[$comment_id] ) ) {
+ return $this->transition_comment_status_action( $comment->comment_approved, $this->comment_transitions[$comment_id][1], $comment );
+ }
+
+ return $this->transition_comment_status_action( $comment->comment_approved, $comment->comment_approved, $comment );
+ }
+
+ function translate_comment_status( $status ) {
+ switch ( (string) $status ) {
+ case '0' :
+ case 'hold' :
+ return 'unapproved';
+ case '1' :
+ case 'approve' :
+ return 'approved';
}
- if ( !$comment->comment_post_ID ) {
+
+ return $status;
+ }
+
+ function transition_comment_status_action( $new_status, $old_status, $comment ) {
+ $post = get_post( $comment->comment_post_ID );
+ if ( !$post ) {
return false;
}
- if ( !$this->post( $comment->comment_post_ID, false ) ) {
+
+ foreach ( array( 'new_status', 'old_status' ) as $_status ) {
+ $$_status = $this->translate_comment_status( $$_status );
+ }
+
+ // Track comment transitions
+ if ( isset( $this->comment_transitions[$comment->comment_ID] ) ) {
+ // status changed more than once - keep tha most recent $new_status
+ $this->comment_transitions[$comment->comment_ID][0] = $new_status;
+ } else {
+ $this->comment_transitions[$comment->comment_ID] = array( $new_status, $old_status );
+ }
+
+ $post_sync = $this->get_post_sync_operation( $post->post_status, '_jetpack_test_sync', $post, $this->sync_conditions['comments'] );
+
+ if ( !$post_sync ) {
+ // No module wants to sync this comment because its post doesn't match any sync conditions
return false;
}
- return $this->register( 'comment', (int) $id, $fields );
+
+ if ( 'delete' == $post_sync['operation'] ) {
+ // Had we been looking at post sync operations (instead of comment sync operations),
+ // this comment's post would have been deleted. Don't sync the comment.
+ return false;
+ }
+
+ $delete_on_behalf_of = array();
+ $submit_on_behalf_of = array();
+ $delete_stati = array( 'delete' );
+
+ foreach ( $this->sync_conditions['comments'] as $module => $conditions ) {
+ if ( !in_array( $comment->comment_type, $conditions['comment_types'] ) ) {
+ continue;
+ }
+
+ $deleted_comment = in_array( $new_status, $delete_stati );
+
+ if ( $deleted_comment ) {
+ $delete_on_behalf_of[] = $module;
+ }
+
+ $old_status_in_stati = in_array( $old_status, $conditions['comment_stati'] );
+ $new_status_in_stati = in_array( $new_status, $conditions['comment_stati'] );
+
+ if ( $old_status_in_stati && !$new_status_in_stati ) {
+ // Jetpack no longer needs the comment
+ if ( !$deleted_comment ) {
+ $delete_on_behalf_of[] = $module;
+ } // else, we've already flagged it above
+ continue;
+ }
+
+ if ( !$new_status_in_stati ) {
+ continue;
+ }
+
+ // At this point, we know we want to sync the comment, not delete it
+ $submit_on_behalf_of[] = $module;
+ }
+
+ if ( ! empty( $submit_on_behalf_of ) ) {
+ $this->register_post( $comment->comment_post_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) );
+ return $this->register_comment( $comment->comment_ID, array( 'on_behalf_of' => $submit_on_behalf_of ) );
+ }
+
+ if ( !empty( $delete_on_behalf_of ) ) {
+ return $this->register( 'delete_comment', $comment->comment_ID, array( 'on_behalf_of' => $delete_on_behalf_of ) );
+ }
+
+ return false;
}
/**
- * Request that a comment be deleted remotely
+ * Get a comment and associated data in the standard JP format.
+ * Cannot be called statically
*
- * @param int $id The comment_ID
+ * @param int $id Comment ID
+ * @return Array containing full comment details
*/
- function delete_comment( $id ) {
- return $this->register( 'delete_comment', (int) $id, true );
+ function get_comment( $id ) {
+ $comment_obj = get_comment( $id );
+ if ( !$comment_obj )
+ return false;
+ $comment = get_object_vars( $comment_obj );
+
+ $meta = get_comment_meta( $id, false );
+ $comment['meta'] = array();
+ foreach ( $meta as $key => $value ) {
+ $comment['meta'][$key] = array_map( 'maybe_unserialize', $value );
+ }
+
+ return $comment;
+ }
+
+/* Options Sync */
+
+ /* Ah... so much simpler than Posts and Comments :) */
+ function options( $file, $option /*, $option, ... */ ) {
+ $options = func_get_args();
+ $file = array_shift( $options );
+
+ $module_slug = Jetpack::get_module_slug( $file );
+
+ if ( !isset( $this->sync_options[$module_slug] ) ) {
+ $this->sync_options[$module_slug] = array();
+ }
+
+ foreach ( $options as $option ) {
+ $this->sync_options[$module_slug][] = $option;
+ add_action( "delete_option_{$option}", array( $this, 'deleted_option_action' ) );
+ add_action( "update_option_{$option}", array( $this, 'updated_option_action' ) );
+ add_action( "add_option_{$option}", array( $this, 'added_option_action' ) );
+ }
+
+ $this->sync_options[$module_slug] = array_unique( $this->sync_options[$module_slug] );
+ }
+
+ function deleted_option_action( $option ) {
+ $this->register( 'delete_option', $option );
+ }
+
+ function updated_option_action( $old_value ) {
+ // The value of $option isn't passed to the filter
+ // Calculate it
+ $option = current_filter();
+ $prefix = 'update_option_';
+ if ( 0 !== strpos( $option, $prefix ) ) {
+ return;
+ }
+ $option = substr( $option, strlen( $prefix ) );
+
+ $this->added_option_action( $option );
+ }
+
+ function added_option_action( $option ) {
+ $this->register( 'option', $option );
+ }
+
+ function sync_all_module_options( $module_slug ) {
+ if ( empty( $this->sync_options[$module_slug] ) ) {
+ return;
+ }
+
+ foreach ( $this->sync_options[$module_slug] as $option ) {
+ $this->added_option_action( $option );
+ }
+ }
+
+ function sync_all_registered_options( $options = array() ) {
+ if ( 'jetpack_sync_all_registered_options' == current_filter() ) {
+ $all_registered_options = array_unique( call_user_func_array( 'array_merge', $this->sync_options ) );
+ foreach ( $all_registered_options as $option ) {
+ $this->added_option_action( $option );
+ }
+ } else {
+ wp_schedule_single_event( time(), 'jetpack_sync_all_registered_options', array( $this->sync_options ) );
+ }
}
}
+require_once dirname( __FILE__ ) . '/class.jetpack-user-agent.php';
+require_once dirname( __FILE__ ) . '/class.jetpack-post-images.php';
+require_once dirname( __FILE__ ) . '/class.photon.php';
+require dirname( __FILE__ ) . '/functions.photon.php';
+require dirname( __FILE__ ) . '/functions.compat.php';
+require dirname( __FILE__ ) . '/functions.gallery.php';
+
class Jetpack_Error extends WP_Error {}
register_activation_hook( __FILE__, array( 'Jetpack', 'plugin_activation' ) );
@@ -3286,3 +4481,5 @@ register_deactivation_hook( __FILE__, array( 'Jetpack', 'plugin_deactivation' )
add_action( 'init', array( 'Jetpack', 'init' ) );
add_action( 'plugins_loaded', array( 'Jetpack', 'load_modules' ), 100 );
add_filter( 'jetpack_static_url', array( 'Jetpack', 'staticize_subdomain' ) );
+
+Jetpack_Sync::sync_options( __FILE__, 'widget_twitter' );
diff --git a/plugins/jetpack/languages/jetpack-ar.mo b/plugins/jetpack/languages/jetpack-ar.mo
new file mode 100644
index 00000000..254be8e6
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-ar.mo differ
diff --git a/plugins/jetpack/languages/jetpack-az.mo b/plugins/jetpack/languages/jetpack-az.mo
index 40f5771b..033f8593 100644
Binary files a/plugins/jetpack/languages/jetpack-az.mo and b/plugins/jetpack/languages/jetpack-az.mo differ
diff --git a/plugins/jetpack/languages/jetpack-bs_BA.mo b/plugins/jetpack/languages/jetpack-bs_BA.mo
index e53b9559..67cd4468 100644
Binary files a/plugins/jetpack/languages/jetpack-bs_BA.mo and b/plugins/jetpack/languages/jetpack-bs_BA.mo differ
diff --git a/plugins/jetpack/languages/jetpack-ca.mo b/plugins/jetpack/languages/jetpack-ca.mo
index 9a25543b..2daa9e1c 100644
Binary files a/plugins/jetpack/languages/jetpack-ca.mo and b/plugins/jetpack/languages/jetpack-ca.mo differ
diff --git a/plugins/jetpack/languages/jetpack-cs_CZ.mo b/plugins/jetpack/languages/jetpack-cs_CZ.mo
index 0977a8b9..5e088773 100644
Binary files a/plugins/jetpack/languages/jetpack-cs_CZ.mo and b/plugins/jetpack/languages/jetpack-cs_CZ.mo differ
diff --git a/plugins/jetpack/languages/jetpack-da_DK.mo b/plugins/jetpack/languages/jetpack-da_DK.mo
index dd4a5ae2..773e5b51 100644
Binary files a/plugins/jetpack/languages/jetpack-da_DK.mo and b/plugins/jetpack/languages/jetpack-da_DK.mo differ
diff --git a/plugins/jetpack/languages/jetpack-de_DE.mo b/plugins/jetpack/languages/jetpack-de_DE.mo
index c21d78b2..a93c1c7c 100644
Binary files a/plugins/jetpack/languages/jetpack-de_DE.mo and b/plugins/jetpack/languages/jetpack-de_DE.mo differ
diff --git a/plugins/jetpack/languages/jetpack-el.mo b/plugins/jetpack/languages/jetpack-el.mo
new file mode 100644
index 00000000..8538e68c
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-el.mo differ
diff --git a/plugins/jetpack/languages/jetpack-es_ES.mo b/plugins/jetpack/languages/jetpack-es_ES.mo
index 9102fc81..3cc9243e 100644
Binary files a/plugins/jetpack/languages/jetpack-es_ES.mo and b/plugins/jetpack/languages/jetpack-es_ES.mo differ
diff --git a/plugins/jetpack/languages/jetpack-fa_IR.mo b/plugins/jetpack/languages/jetpack-fa_IR.mo
index 864aa9bd..f34c74e6 100644
Binary files a/plugins/jetpack/languages/jetpack-fa_IR.mo and b/plugins/jetpack/languages/jetpack-fa_IR.mo differ
diff --git a/plugins/jetpack/languages/jetpack-fi.mo b/plugins/jetpack/languages/jetpack-fi.mo
index fae02a3e..591c0472 100644
Binary files a/plugins/jetpack/languages/jetpack-fi.mo and b/plugins/jetpack/languages/jetpack-fi.mo differ
diff --git a/plugins/jetpack/languages/jetpack-fr_FR.mo b/plugins/jetpack/languages/jetpack-fr_FR.mo
index 15385ae5..165c2b9e 100644
Binary files a/plugins/jetpack/languages/jetpack-fr_FR.mo and b/plugins/jetpack/languages/jetpack-fr_FR.mo differ
diff --git a/plugins/jetpack/languages/jetpack-gl_ES.mo b/plugins/jetpack/languages/jetpack-gl_ES.mo
index 1fb3e4c4..e5f4c09a 100644
Binary files a/plugins/jetpack/languages/jetpack-gl_ES.mo and b/plugins/jetpack/languages/jetpack-gl_ES.mo differ
diff --git a/plugins/jetpack/languages/jetpack-he_IL.mo b/plugins/jetpack/languages/jetpack-he_IL.mo
index 3bbb699d..90d820c7 100644
Binary files a/plugins/jetpack/languages/jetpack-he_IL.mo and b/plugins/jetpack/languages/jetpack-he_IL.mo differ
diff --git a/plugins/jetpack/languages/jetpack-hr.mo b/plugins/jetpack/languages/jetpack-hr.mo
index 6dfab5c0..8765f205 100644
Binary files a/plugins/jetpack/languages/jetpack-hr.mo and b/plugins/jetpack/languages/jetpack-hr.mo differ
diff --git a/plugins/jetpack/languages/jetpack-hu_HU.mo b/plugins/jetpack/languages/jetpack-hu_HU.mo
index f9e1b630..11715656 100644
Binary files a/plugins/jetpack/languages/jetpack-hu_HU.mo and b/plugins/jetpack/languages/jetpack-hu_HU.mo differ
diff --git a/plugins/jetpack/languages/jetpack-id_ID.mo b/plugins/jetpack/languages/jetpack-id_ID.mo
index 3e16a67d..8d3427dd 100644
Binary files a/plugins/jetpack/languages/jetpack-id_ID.mo and b/plugins/jetpack/languages/jetpack-id_ID.mo differ
diff --git a/plugins/jetpack/languages/jetpack-it_IT.mo b/plugins/jetpack/languages/jetpack-it_IT.mo
index 1aa7c72a..7165de10 100644
Binary files a/plugins/jetpack/languages/jetpack-it_IT.mo and b/plugins/jetpack/languages/jetpack-it_IT.mo differ
diff --git a/plugins/jetpack/languages/jetpack-ja.mo b/plugins/jetpack/languages/jetpack-ja.mo
index 82c715ef..f715cc91 100644
Binary files a/plugins/jetpack/languages/jetpack-ja.mo and b/plugins/jetpack/languages/jetpack-ja.mo differ
diff --git a/plugins/jetpack/languages/jetpack-ko_KR.mo b/plugins/jetpack/languages/jetpack-ko_KR.mo
new file mode 100644
index 00000000..3a7a5295
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-ko_KR.mo differ
diff --git a/plugins/jetpack/languages/jetpack-lt_LT.mo b/plugins/jetpack/languages/jetpack-lt_LT.mo
new file mode 100644
index 00000000..55f190a5
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-lt_LT.mo differ
diff --git a/plugins/jetpack/languages/jetpack-mk_MK.mo b/plugins/jetpack/languages/jetpack-mk_MK.mo
index a567c77d..804e8a3b 100644
Binary files a/plugins/jetpack/languages/jetpack-mk_MK.mo and b/plugins/jetpack/languages/jetpack-mk_MK.mo differ
diff --git a/plugins/jetpack/languages/jetpack-my_MM.mo b/plugins/jetpack/languages/jetpack-my_MM.mo
index ba1e6945..1d1604b7 100644
Binary files a/plugins/jetpack/languages/jetpack-my_MM.mo and b/plugins/jetpack/languages/jetpack-my_MM.mo differ
diff --git a/plugins/jetpack/languages/jetpack-nb_NO.mo b/plugins/jetpack/languages/jetpack-nb_NO.mo
index 7c6bf66c..5600cf1d 100644
Binary files a/plugins/jetpack/languages/jetpack-nb_NO.mo and b/plugins/jetpack/languages/jetpack-nb_NO.mo differ
diff --git a/plugins/jetpack/languages/jetpack-nl_NL.mo b/plugins/jetpack/languages/jetpack-nl_NL.mo
index 9bd9ffb3..a125514d 100644
Binary files a/plugins/jetpack/languages/jetpack-nl_NL.mo and b/plugins/jetpack/languages/jetpack-nl_NL.mo differ
diff --git a/plugins/jetpack/languages/jetpack-nn_NO.mo b/plugins/jetpack/languages/jetpack-nn_NO.mo
index f5061e15..1157c955 100644
Binary files a/plugins/jetpack/languages/jetpack-nn_NO.mo and b/plugins/jetpack/languages/jetpack-nn_NO.mo differ
diff --git a/plugins/jetpack/languages/jetpack-pl_PL.mo b/plugins/jetpack/languages/jetpack-pl_PL.mo
index c8ba1dd6..3a5df93c 100644
Binary files a/plugins/jetpack/languages/jetpack-pl_PL.mo and b/plugins/jetpack/languages/jetpack-pl_PL.mo differ
diff --git a/plugins/jetpack/languages/jetpack-pt_BR.mo b/plugins/jetpack/languages/jetpack-pt_BR.mo
index b32d48f8..da51355f 100644
Binary files a/plugins/jetpack/languages/jetpack-pt_BR.mo and b/plugins/jetpack/languages/jetpack-pt_BR.mo differ
diff --git a/plugins/jetpack/languages/jetpack-pt_PT.mo b/plugins/jetpack/languages/jetpack-pt_PT.mo
index 226b63d2..62d6331b 100644
Binary files a/plugins/jetpack/languages/jetpack-pt_PT.mo and b/plugins/jetpack/languages/jetpack-pt_PT.mo differ
diff --git a/plugins/jetpack/languages/jetpack-ro_RO.mo b/plugins/jetpack/languages/jetpack-ro_RO.mo
index a3100506..6a76116e 100644
Binary files a/plugins/jetpack/languages/jetpack-ro_RO.mo and b/plugins/jetpack/languages/jetpack-ro_RO.mo differ
diff --git a/plugins/jetpack/languages/jetpack-ru_RU.mo b/plugins/jetpack/languages/jetpack-ru_RU.mo
index 7c3dc2dd..392de6d0 100644
Binary files a/plugins/jetpack/languages/jetpack-ru_RU.mo and b/plugins/jetpack/languages/jetpack-ru_RU.mo differ
diff --git a/plugins/jetpack/languages/jetpack-sa_IN.mo b/plugins/jetpack/languages/jetpack-sa_IN.mo
index 10f549d0..f7bfee62 100644
Binary files a/plugins/jetpack/languages/jetpack-sa_IN.mo and b/plugins/jetpack/languages/jetpack-sa_IN.mo differ
diff --git a/plugins/jetpack/languages/jetpack-sk_SK.mo b/plugins/jetpack/languages/jetpack-sk_SK.mo
index b9360173..0f6a8ea8 100644
Binary files a/plugins/jetpack/languages/jetpack-sk_SK.mo and b/plugins/jetpack/languages/jetpack-sk_SK.mo differ
diff --git a/plugins/jetpack/languages/jetpack-sq.mo b/plugins/jetpack/languages/jetpack-sq.mo
index 39c037cf..795f5abb 100644
Binary files a/plugins/jetpack/languages/jetpack-sq.mo and b/plugins/jetpack/languages/jetpack-sq.mo differ
diff --git a/plugins/jetpack/languages/jetpack-sr_RS.mo b/plugins/jetpack/languages/jetpack-sr_RS.mo
index 9e7db3c6..62d2959a 100644
Binary files a/plugins/jetpack/languages/jetpack-sr_RS.mo and b/plugins/jetpack/languages/jetpack-sr_RS.mo differ
diff --git a/plugins/jetpack/languages/jetpack-sv_SE.mo b/plugins/jetpack/languages/jetpack-sv_SE.mo
index ca682a7c..382cbc3e 100644
Binary files a/plugins/jetpack/languages/jetpack-sv_SE.mo and b/plugins/jetpack/languages/jetpack-sv_SE.mo differ
diff --git a/plugins/jetpack/languages/jetpack-th.mo b/plugins/jetpack/languages/jetpack-th.mo
new file mode 100644
index 00000000..13499189
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-th.mo differ
diff --git a/plugins/jetpack/languages/jetpack-tr_TR.mo b/plugins/jetpack/languages/jetpack-tr_TR.mo
index 83a6b84e..24eeccec 100644
Binary files a/plugins/jetpack/languages/jetpack-tr_TR.mo and b/plugins/jetpack/languages/jetpack-tr_TR.mo differ
diff --git a/plugins/jetpack/languages/jetpack-zh_CN.mo b/plugins/jetpack/languages/jetpack-zh_CN.mo
new file mode 100644
index 00000000..17900fb1
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-zh_CN.mo differ
diff --git a/plugins/jetpack/languages/jetpack-zh_TW.mo b/plugins/jetpack/languages/jetpack-zh_TW.mo
new file mode 100644
index 00000000..38662136
Binary files /dev/null and b/plugins/jetpack/languages/jetpack-zh_TW.mo differ
diff --git a/plugins/jetpack/locales.php b/plugins/jetpack/locales.php
index 41075cdd..e910fbfc 100644
--- a/plugins/jetpack/locales.php
+++ b/plugins/jetpack/locales.php
@@ -13,22 +13,22 @@ class GP_Locale {
var $preferred_sans_serif_font_family = null;
var $facebook_locale = null;
// TODO: days, months, decimals, quotes
-
+
function GP_Locale( $args = array() ) {
foreach( $args as $key => $value ) {
$this->$key = $value;
}
}
-
+
static function __set_state( $state ) {
return new GP_Locale( $state );
}
-
+
function combined_name() {
/* translators: combined name for locales: 1: name in English, 2: native name */
- return sprintf( _x( '%1$s/%2$s', 'locales' ), $this->english_name, $this->native_name );
+ return sprintf( _x( '%1$s/%2$s', 'locales', 'jetpack' ), $this->english_name, $this->native_name );
}
-
+
function numbers_for_index( $index, $how_many = 3, $test_up_to = 1000 ) {
$numbers = array();
for( $number = 0; $number < $test_up_to; ++$number ) {
@@ -39,7 +39,7 @@ class GP_Locale {
}
return $numbers;
}
-
+
function index_for_number( $number ) {
if ( !isset( $this->_index_for_number ) ) {
$expression = Gettext_Translations::parenthesize_plural_exression( $this->plural_expression );
@@ -51,9 +51,9 @@ class GP_Locale {
}
class GP_Locales {
-
+
var $locales = array();
-
+
function GP_Locales() {
$aa = new GP_Locale();
$aa->english_name = 'Afar';
@@ -144,7 +144,7 @@ class GP_Locales {
$av->native_name = 'авар мацӀ';
$av->lang_code_iso_639_1 = 'av';
$av->lang_code_iso_639_2 = 'ava';
- $av->country_code = '';
+ $av->country_code = '';
$av->slug = 'av';
$ay = new GP_Locale();
@@ -167,7 +167,7 @@ class GP_Locales {
$az->slug = 'az';
$az->google_code = 'az';
$az->facebook_locale = 'az_AZ';
-
+
$az_tr = new GP_Locale();
$az_tr->english_name = 'Azerbaijani (Turkey)';
$az_tr->native_name = 'Azərbaycan Türkcəsi';
@@ -398,7 +398,7 @@ class GP_Locales {
$da->slug = 'da';
$da->google_code = 'da';
$da->facebook_locale = 'da_DK';
-
+
$de = new GP_Locale();
$de->english_name = 'German';
$de->native_name = 'Deutsch';
@@ -408,7 +408,7 @@ class GP_Locales {
$de->slug = 'de';
$de->google_code = 'de';
$de->facebook_locale = 'de_DE';
-
+
$dv = new GP_Locale();
$dv->english_name = 'Divehi';
$dv->native_name = 'Þ‹Þ¨ÞˆÞ¬Þ€Þ¨';
@@ -419,7 +419,7 @@ class GP_Locales {
$dv->slug = 'dv';
$dv->google_code = 'dv';
$dv->rtl = true;
-
+
$dz = new GP_Locale();
$dz->english_name = 'Dzongkha';
$dz->native_name = 'རྫོང་à½';
@@ -429,7 +429,7 @@ class GP_Locales {
$dz->slug = 'dz';
$dz->nplurals = 1;
$dz->plural_expression = '0';
-
+
$ee = new GP_Locale();
$ee->english_name = 'Ewe';
$ee->native_name = 'EÊ‹egbe';
@@ -456,7 +456,7 @@ class GP_Locales {
$el->slug = 'el';
$el->google_code = 'el';
$el->facebook_locale = 'el_GR';
-
+
$en = new GP_Locale();
$en->english_name = 'English';
$en->native_name = 'English';
@@ -466,28 +466,28 @@ class GP_Locales {
$en->slug = 'en';
$en->google_code = 'en';
$en->facebook_locale = 'en_US';
-
+
$en_ca = new GP_Locale();
$en_ca->english_name = 'English (Canada)';
$en_ca->native_name = 'English (Canada)';
$en_ca->lang_code_iso_639_1 = 'en';
- $en_ca->lang_code_iso_639_2 = 'eng';
+ $en_ca->lang_code_iso_639_2 = 'eng';
$en_ca->lang_code_iso_639_3 = 'eng';
$en_ca->country_code = 'ca';
$en_ca->wp_locale = 'en_CA';
$en_ca->slug = 'en-ca';
$en_ca->google_code = 'en';
-
+
$en_gb = new GP_Locale();
$en_gb->english_name = 'English (UK)';
$en_gb->native_name = 'English (UK)';
$en_gb->lang_code_iso_639_1 = 'en';
- $en_gb->lang_code_iso_639_2 = 'eng';
+ $en_gb->lang_code_iso_639_2 = 'eng';
$en_gb->lang_code_iso_639_3 = 'eng';
$en_gb->country_code = 'gb';
$en_gb->wp_locale = 'en_GB';
$en_gb->slug = 'en-gb';
- $en_gb->google_code = 'en';
+ $en_gb->google_code = 'en';
$en_gb->facebook_locale = 'en_GB';
$eo = new GP_Locale();
@@ -533,7 +533,7 @@ class GP_Locales {
$es_pr->slug = 'es-pr';
$es_pr->google_code = 'es';
$es_pr->facebook_locale = 'es_LA';
-
+
$es_ve = new GP_Locale();
$es_ve->english_name = 'Spanish (Venezuela)';
$es_ve->native_name = 'Español de Venezuela';
@@ -565,7 +565,7 @@ class GP_Locales {
$es->slug = 'es';
$es->google_code = 'es';
$es->facebook_locale = 'es_ES';
-
+
$et = new GP_Locale();
$et->english_name = 'Estonian';
$et->native_name = 'Eesti';
@@ -601,7 +601,7 @@ class GP_Locales {
$fa->nplurals = 1;
$fa->plural_expression = '0';
$fa->rtl = true;
-
+
$fa_af = new GP_Locale();
$fa_af->english_name = 'Persian (Afghanistan)';
$fa_af->native_name = '(Ùارسی (اÙغانستان';
@@ -614,7 +614,7 @@ class GP_Locales {
$fa_af->nplurals = 1;
$fa_af->plural_expression = '0';
$fa_af->rtl = true;
-
+
$fi = new GP_Locale();
$fi->english_name = 'Finnish';
$fi->native_name = 'Suomi';
@@ -643,7 +643,7 @@ class GP_Locales {
$fo->wp_locale = 'fo';
$fo->slug = 'fo';
$fo->facebook_locale = 'fo_FO';
-
+
$fr = new GP_Locale();
$fr->english_name = 'French (France)';
$fr->native_name = 'Français';
@@ -691,7 +691,7 @@ class GP_Locales {
$fy->facebook_locale = 'fy_NL';
$fy->slug = 'fy';
$fy->wp_locale = 'fy';
-
+
$ga = new GP_Locale();
$ga->english_name = 'Irish';
$ga->native_name = 'Gaelige';
@@ -703,7 +703,7 @@ class GP_Locales {
$ga->facebook_locale = 'ga_IE';
$ga->nplurals = 5;
$ga->plural_expression = 'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4';
-
+
$gd = new GP_Locale();
$gd->english_name = 'Scottish Gaelic';
$gd->native_name = 'GÃ idhlig';
@@ -715,7 +715,7 @@ class GP_Locales {
$gd->slug = 'gd';
$gd->google_code = 'gd';
$gd->nplurals = 4;
- $gd->plural_expression = '(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3';
+ $gd->plural_expression = '(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3';
$gl = new GP_Locale();
$gl->english_name = 'Galician';
@@ -755,7 +755,7 @@ class GP_Locales {
$ha->country_code = '';
$ha->slug = 'ha';
$ha->rtl = true;
-
+
$haw = new GP_Locale();
$haw->english_name = 'Hawaiian';
$haw->native_name = 'Ōlelo Hawaiʻi';
@@ -1007,18 +1007,18 @@ class GP_Locales {
$lb->country_code = 'lu';
$lb->wp_locale = 'lb_LU';
$lb->slug = 'lb';
-
+
$li = new GP_Locale();
$li->english_name = 'Limburgish';
$li->native_name = 'Limburgs';
$li->lang_code_iso_639_1 = 'li';
$li->lang_code_iso_639_2 = 'lim';
- $li->lang_code_iso_639_3 = 'lim';
+ $li->lang_code_iso_639_3 = 'lim';
$li->country_code = 'nl';
$li->wp_locale = 'li';
$li->slug = 'li';
$li->google_code = 'li';
-
+
$lo = new GP_Locale();
$lo->english_name = 'Lao';
$lo->native_name = 'ພາສາລາວ';
@@ -1037,6 +1037,7 @@ class GP_Locales {
$lt->lang_code_iso_639_1 = 'lt';
$lt->lang_code_iso_639_2 = 'lit';
$lt->country_code = 'lt';
+ $lt->wp_locale = 'lt_LT';
$lt->slug = 'lt';
$lt->google_code = 'lt';
$lt->facebook_locale = 'lt_LT';
@@ -1055,7 +1056,7 @@ class GP_Locales {
$lv->facebook_locale = 'lv_LV';
$lv->nplurals = 3;
$lv->plural_expression = '(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)';
-
+
$me = new GP_Locale();
$me->english_name = 'Montenegrin';
$me->native_name = 'Crnogorski jezik';
@@ -1064,7 +1065,9 @@ class GP_Locales {
$me->wp_locale = 'me_ME';
$me->google_code = 'srp';
$me->slug = 'me';
-
+ $me->nplurals = 3;
+ $me->plural_expression = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)';
+
$mg = new GP_Locale();
$mg->english_name = 'Malagasy';
$mg->native_name = 'Malagasy';
@@ -1073,7 +1076,7 @@ class GP_Locales {
$mg->country_code = 'mg';
$mg->wp_locale = 'mg_MG';
$mg->slug = 'mg';
-
+
$mhr = new GP_Locale();
$mhr->english_name = 'Mari (Meadow)';
$mhr->native_name = 'олык марий';
@@ -1082,8 +1085,8 @@ class GP_Locales {
$mhr->lang_code_iso_639_3 = 'mhr';
$mhr->country_code = 'ru';
$mhr->slug = 'mhr';
- $mhr->google_code = 'chm';
-
+ $mhr->google_code = 'chm';
+
$mk = new GP_Locale();
$mk->english_name = 'Macedonian';
$mk->native_name = 'македонÑки јазик';
@@ -1125,7 +1128,7 @@ class GP_Locales {
$mr->country_code = '';
$mr->slug = 'mr';
$mr->google_code = 'mr';
-
+
$mrj = new GP_Locale();
$mrj->english_name = 'Mari (Hill)';
$mrj->native_name = 'кырык мары';
@@ -1134,7 +1137,7 @@ class GP_Locales {
$mrj->lang_code_iso_639_3 = 'mrj';
$mrj->country_code = 'ru';
$mrj->slug = 'mrj';
- $mrj->google_code = 'chm';
+ $mrj->google_code = 'chm';
$ms = new GP_Locale();
$ms->english_name = 'Malay';
@@ -1208,7 +1211,7 @@ class GP_Locales {
$nl_be->wp_locale = 'nl_BE';
$nl_be->slug = 'nl-be';
$nl_be->google_code = 'nl';
-
+
$nn = new GP_Locale();
$nn->english_name = 'Norwegian (Nynorsk)';
$nn->native_name = 'Norsk nynorsk';
@@ -1244,7 +1247,7 @@ class GP_Locales {
$os->wp_locale = 'os';
$os->country_code = '';
$os->slug = 'os';
-
+
$pa = new GP_Locale();
$pa->english_name = 'Punjabi';
$pa->native_name = 'ਪੰਜਾਬੀ';
@@ -1281,7 +1284,7 @@ class GP_Locales {
$pt_br->facebook_locale = 'pt_BR';
$pt_br->nplurals = 2;
$pt_br->plural_expression = '(n > 1)';
-
+
$pt = new GP_Locale();
$pt->english_name = 'Portuguese (Portugal)';
$pt->native_name = 'Português';
@@ -1291,7 +1294,7 @@ class GP_Locales {
$pt->slug = 'pt';
$pt->google_code = 'pt-PT';
$pt->facebook_locale = 'pt_PT';
-
+
$ps = new GP_Locale();
$ps->english_name = 'Pashto';
$ps->native_name = 'پښتو';
@@ -1301,7 +1304,7 @@ class GP_Locales {
$ps->slug = 'ps';
$ps->google_code = 'ps';
$ps->facebook_locale = 'ps_AF';
- $ps->rtl = true;
+ $ps->rtl = true;
$ro = new GP_Locale();
$ro->english_name = 'Romanian';
@@ -1340,7 +1343,7 @@ class GP_Locales {
$ru_ua->google_code = 'ru';
$ru_ua->nplurals = 3;
$ru_ua->plural_expression = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)';
-
+
$rue = new GP_Locale();
$rue->english_name = 'Rusyn';
$rue->native_name = 'РуÑиньÑкый';
@@ -1351,8 +1354,8 @@ class GP_Locales {
$rue->wp_locale = 'rue';
$rue->slug = 'rue';
$rue->nplurals = 3;
- $rue->plural_expression = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)';
-
+ $rue->plural_expression = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)';
+
$rup = new GP_Locale();
$rup->english_name = 'Aromanian';
$rup->native_name = 'Armãneashce';
@@ -1427,18 +1430,18 @@ class GP_Locales {
$sl->facebook_locale = 'sl_SI';
$sl->nplurals = 4;
$sl->plural_expression = '(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)';
-
+
$so = new GP_Locale();
$so->english_name = 'Somali';
$so->native_name = 'Afsoomaali';
$so->lang_code_iso_639_1 = 'so';
$so->lang_code_iso_639_2 = 'som';
- $so->lang_code_iso_639_3 = 'som';
+ $so->lang_code_iso_639_3 = 'som';
$so->country_code = 'so';
$so->wp_locale = 'so_SO';
$so->slug = 'so';
$so->google_code = 'so';
-
+
$sq = new GP_Locale();
$sq->english_name = 'Albanian';
$sq->native_name = 'Shqip';
@@ -1515,7 +1518,7 @@ class GP_Locales {
$ta->slug = 'ta';
$ta->google_code = 'ta';
$ta->facebook_locale = 'ta_IN';
-
+
$ta_lk = new GP_Locale();
$ta_lk->english_name = 'Tamil (Sri Lanka)';
$ta_lk->native_name = 'தமிழà¯';
@@ -1537,6 +1540,18 @@ class GP_Locales {
$te->google_code = 'te';
$te->facebook_locale = 'te_IN';
+ $tg = new GP_Locale();
+ $tg->english_name = 'Tajik';
+ $tg->native_name = 'тоҷикӣ';
+ $tg->lang_code_iso_639_1 = 'tg';
+ $tg->lang_code_iso_639_2 = 'tgk';
+ $tg->country_code = '';
+ $tg->wp_locale = 'tg';
+ $tg->slug = 'tg';
+ $tg->google_code = 'tg';
+ $tg->nplurals = 2;
+ $tg->plural_expression = 'n != 1;';
+
$th = new GP_Locale();
$th->english_name = 'Thai';
$th->native_name = 'ไทย';
@@ -1549,7 +1564,7 @@ class GP_Locales {
$th->facebook_locale = 'th_TH';
$th->nplurals = 1;
$th->plural_expression = '0';
-
+
$tlh = new GP_Locale();
$tlh->english_name = 'Klingon';
$tlh->native_name = 'TlhIngan';
@@ -1686,6 +1701,14 @@ class GP_Locales {
$yi->slug = 'yi';
$yi->google_code = 'yi';
$yi->rtl = true;
+
+ $yo = new GP_Locale();
+ $yo->english_name = 'Yorùbá';
+ $yo->native_name = 'èdè Yorùbá';
+ $yo->lang_code_iso_639_1 = 'yo';
+ $yo->lang_code_iso_639_2 = 'yor';
+ $yo->country_code = '';
+ $yo->slug = 'yo';
$zh_cn = new GP_Locale();
$zh_cn->english_name = 'Chinese (China)';
@@ -1744,28 +1767,28 @@ class GP_Locales {
$zh->slug = 'zh';
$zh->nplurals = 1;
$zh->plural_expression = '0';
-
+
foreach( get_defined_vars() as $locale ) {
$this->locales[$locale->slug] = $locale;
}
}
-
+
function &instance() {
if ( !isset( $GLOBALS['gp_locales'] ) )
- $GLOBALS['gp_locales'] = &new GP_Locales();
+ $GLOBALS['gp_locales'] = new GP_Locales;
return $GLOBALS['gp_locales'];
}
-
+
function locales() {
$instance = GP_Locales::instance();
return $instance->locales;
}
-
+
function exists( $slug ) {
$instance = GP_Locales::instance();
return isset( $instance->locales[$slug] );
}
-
+
function by_slug( $slug ) {
$instance = GP_Locales::instance();
return isset( $instance->locales[$slug] )? $instance->locales[$slug] : null;
diff --git a/plugins/jetpack/modules/after-the-deadline.php b/plugins/jetpack/modules/after-the-deadline.php
index b187de35..606f6488 100644
--- a/plugins/jetpack/modules/after-the-deadline.php
+++ b/plugins/jetpack/modules/after-the-deadline.php
@@ -14,7 +14,7 @@ function AtD_load() {
}
function AtD_configuration_load() {
- wp_safe_redirect( admin_url( 'profile.php#atd' ) );
+ wp_safe_redirect( get_edit_profile_url( get_current_user_id() ) . '#atd' );
exit;
}
diff --git a/plugins/jetpack/modules/after-the-deadline/config-options.php b/plugins/jetpack/modules/after-the-deadline/config-options.php
index 31cc4da3..097b062d 100644
--- a/plugins/jetpack/modules/after-the-deadline/config-options.php
+++ b/plugins/jetpack/modules/after-the-deadline/config-options.php
@@ -45,7 +45,7 @@ function AtD_display_options_form() {
?>
-
+
@@ -86,7 +86,7 @@ function AtD_display_options_form() {
%2%s value is the default proofreading language.', '%1$s = http://codex.wordpress.org/Installing_WordPress_in_Your_Language, %2$s = WPLANG', 'jetpack' ),
+ _x( 'The proofreader supports English, French, German, Portuguese, and Spanish. Your %2$s value is the default proofreading language.', '%1$s = http://codex.wordpress.org/Installing_WordPress_in_Your_Language, %2$s = WPLANG', 'jetpack' ),
'http://codex.wordpress.org/Installing_WordPress_in_Your_Language',
'WPLANG'
); ?>
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel-ie8fix.css b/plugins/jetpack/modules/carousel/jetpack-carousel-ie8fix.css
new file mode 100644
index 00000000..9f2f6cff
--- /dev/null
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel-ie8fix.css
@@ -0,0 +1,8 @@
+.jp-carousel .jp-carousel-slide {
+ display: none !important;
+}
+
+.jp-carousel .selected {
+ margin: 0 auto;
+ display: block !important;
+}
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.css b/plugins/jetpack/modules/carousel/jetpack-carousel.css
index aa2b1d0f..49bdad8e 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.css
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.css
@@ -11,7 +11,7 @@ div.jp-carousel-fadeaway {
background: -webkit-gradient(linear, left bottom, left top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0)));
position: fixed;
bottom: 0;
- z-index: 99999;
+ z-index: 2147483647;
width: 100%;
height: 15px;
}
@@ -199,13 +199,11 @@ div.jp-carousel-buttons a:hover {
.jp-carousel-close-hint {
color: #999;
cursor: default;
- font: 16px/1 "Helvetica Neue", sans-serif !important;
- font-weight: 600 !important;
letter-spacing: 0 !important;
- padding:0.55em 0 0;
- text-align: left;
- width: 100%;
+ padding:0.35em 0 0;
position: absolute;
+ text-align: left;
+ width: 90%;
-webkit-transition: color 200ms linear;
-moz-transition: color 200ms linear;
-o-transition: color 200ms linear;
@@ -213,16 +211,17 @@ div.jp-carousel-buttons a:hover {
}
.jp-carousel-close-hint span {
- cursor:pointer;
+ cursor: pointer;
background-color: black;
background-color: rgba(0,0,0,0.8);
- height: 26px;
- width: 26px;
display: block;
- text-align: center;
- vertical-align: middle;
+ height: 22px;
+ font: 400 24px/1 "Helvetica Neue", sans-serif !important;
line-height: 22px;
margin: 0 0 0 0.4em;
+ text-align: center;
+ vertical-align: middle;
+ width: 22px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
@@ -482,7 +481,10 @@ div#carousel-reblog-box {
display: none;
}
-h1:before, h1:after {
+.jp-carousel-photo-info h1:before,
+.jp-carousel-photo-info h1:after,
+.jp-carousel-left-column-wrapper h1:before,
+.jp-carousel-left-column-wrapper h1:after {
content:none !important;
}
/** Title and Desc End **/
@@ -631,6 +633,8 @@ a.jp-carousel-image-download:hover {
width:auto;
display: inline;
float:none;
+ border:none;
+ margin:0;
}
.jp-carousel-comment .comment-author a {
@@ -644,6 +648,7 @@ a.jp-carousel-image-download:hover {
.jp-carousel-comment .comment-content {
border:none;
margin-left:85px;
+ padding: 0;
}
.jp-carousel-comment .avatar {
@@ -1037,3 +1042,63 @@ textarea#jp-carousel-comment-form-comment-field:focus::-webkit-input-placeholder
background: -moz-linear-gradient(bottom, rgba(255,255,255,0.75), rgba(255,255,255,0));
background: -webkit-gradient(linear, left bottom, left top, from(rgba(255,255,255,0.75)), to(rgba(255,255,255,0)));
}
+
+/* Small screens */
+@media only screen and (max-width: 760px) {
+
+ .jp-carousel-info {
+ margin: 0 10px !important;
+ }
+
+ .jp-carousel-next-button, .jp-carousel-previous-button {
+ display: none !important;
+ }
+
+ .jp-carousel-buttons {
+ display: none !important;
+ }
+
+ .jp-carousel-image-meta {
+ float: none !important;
+ width: 100% !important;
+ -moz-box-sizing:border-box;
+ -webkit-box-sizing:border-box;
+ box-sizing: border-box;
+ }
+
+ .jp-carousel-close-hint {
+ font-weight: 800 !important;
+ font-size: 26px !important;
+ position: fixed !important;
+ top: -10px;
+ }
+
+ .jp-carousel-slide img {
+ filter: alpha(opacity=100);
+ opacity: 1;
+ }
+
+ .jp-carousel-wrap {
+ background-color: #000;
+ }
+
+ .jp-carousel-fadeaway {
+ display: none;
+ }
+
+ #jp-carousel-comment-form-container {
+ display: none !important;
+ }
+
+ .jp-carousel-titleanddesc {
+ padding-top: 0 !important;
+ border: none !important;
+ }
+ .jp-carousel-titleanddesc-title {
+ font-size: 1em !important;
+ }
+
+ .jp-carousel-left-column-wrapper {
+ padding: 0;
+ }
+}
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.js b/plugins/jetpack/modules/carousel/jetpack-carousel.js
index 5760811e..43f1d1f7 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.js
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.js
@@ -4,7 +4,12 @@ jQuery(document).ready(function($) {
// gallery faded layer and container elements
var overlay, comments, gallery, container, nextButton, previousButton, info, title,
caption, resizeTimeout, mouseTimeout, photo_info, close_hint, commentInterval, buttons,
- screenPadding = 110, originalOverflow = $('body').css('overflow'), proportion = 85;
+ screenPadding = 110, originalOverflow = $('body').css('overflow'), originalHOverflow = $('html').css('overflow'), proportion = 85, isMobile;
+
+ isMobile = /Android|iPhone|iPod/i.test(navigator.userAgent);
+
+ if (isMobile)
+ screenPadding = 0;
var keyListener = function(e){
switch(e.which){
@@ -64,7 +69,7 @@ jQuery(document).ready(function($) {
buttons = '';
buttons = $('' + buttons + '
');
-
+
caption = $(' ');
photo_info = $('
').append(caption);
@@ -127,7 +132,7 @@ jQuery(document).ready(function($) {
'bottom' : '10px',
'margin-top' : '20px'
});
-
+
leftWidth = ( $(window).width() - ( screenPadding * 2 ) ) - (imageMeta.width() + 40);
if ( $.browser.mozilla )
leftWidth -= 55;
@@ -135,6 +140,9 @@ jQuery(document).ready(function($) {
leftWidth -= 20;
leftWidth += 'px';
+ if (isMobile)
+ leftWidth = '100%';
+
leftColWrapper = $('
')
.addClass('jp-carousel-left-column-wrapper')
.css({
@@ -147,7 +155,7 @@ jQuery(document).ready(function($) {
fadeaway = $('
')
.addClass('jp-carousel-fadeaway');
-
+
info = $('
')
.addClass('jp-carousel-info')
.css({
@@ -159,6 +167,11 @@ jQuery(document).ready(function($) {
.append(imageMeta)
.append(leftColWrapper);
+ if (isMobile)
+ info.prepend(leftColWrapper);
+ else
+ info.append(leftColWrapper);
+
targetBottomPos = ( $(window).height() - parseInt( info.css('top'), 10 ) ) + 'px';
nextButton = $("
")
@@ -166,7 +179,7 @@ jQuery(document).ready(function($) {
.css({
'position' : 'fixed',
'top' : 0,
- 'right' : 0,
+ 'right' : '15px',
'bottom' : 0,
'width' : screenPadding
});
@@ -175,7 +188,7 @@ jQuery(document).ready(function($) {
'top' : '40px',
'bottom' : targetBottomPos
});
-
+
previousButton = $("
")
.addClass('jp-carousel-previous-button')
.css({
@@ -190,7 +203,7 @@ jQuery(document).ready(function($) {
'top' : '40px',
'bottom' : targetBottomPos
});
-
+
gallery = $('
')
.addClass('jp-carousel')
.css({
@@ -205,20 +218,20 @@ jQuery(document).ready(function($) {
.css({
position : 'fixed'
});
-
+
container = $("
")
.addClass('jp-carousel-wrap');
-
+
if ( 'white' == jetpackCarouselStrings.background_color )
container.addClass('jp-carousel-light');
-
+
container.css({
'position' : 'fixed',
'top' : 0,
'right' : 0,
'bottom' : 0,
'left' : 0,
- 'z-index' : 999999,
+ 'z-index' : 2147483647,
'overflow-x' : 'hidden',
'overflow-y' : 'auto',
'direction' : 'ltr'
@@ -315,7 +328,7 @@ jQuery(document).ready(function($) {
return;
}
}
-
+
$.ajax({
type: 'POST',
url: jetpackCarouselStrings.ajaxurl,
@@ -349,6 +362,7 @@ jQuery(document).ready(function($) {
.bind('jp_carousel.afterOpen', function(){
$(window).bind('keydown', keyListener);
$(window).bind('resize', resizeListener);
+ gallery.opened = true;
})
.bind('jp_carousel.beforeClose', function(){
var scroll = $(window).scrollTop();
@@ -357,6 +371,15 @@ jQuery(document).ready(function($) {
$(window).unbind('resize', resizeListener);
document.location.hash = '';
$(window).scrollTop(scroll);
+ gallery.opened = false;
+ });
+
+ $('.jp-carousel').touchwipe({
+ wipeLeft: function() { gallery.jp_carousel('next'); },
+ wipeRight: function() { gallery.jp_carousel('previous'); },
+ min_move_x: 20,
+ min_move_y: 20,
+ preventDefaultEvents: true
});
nextButton.add(previousButton).click(function(e){
@@ -372,9 +395,22 @@ jQuery(document).ready(function($) {
};
var methods = {
+ testForData: function(gallery) {
+ gallery = $( gallery ); // make sure we have it as a jQuery object.
+ if ( ! gallery.length || undefined == gallery.data( 'carousel-extra' ) )
+ return false;
+ return true;
+ },
+
+ testIfOpened: function() {
+ if ( 'undefined' != typeof(gallery) && 'undefined' != typeof(gallery.opened) && true == gallery.opened )
+ return true;
+ return false;
+ },
+
open: function(options) {
var settings = {
- 'items_selector' : ".gallery-item [data-attachment-id]",
+ 'items_selector' : ".gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id]",
'start_index': 0
},
data = $(this).data('carousel-extra');
@@ -382,12 +418,19 @@ jQuery(document).ready(function($) {
if ( !data )
return; // don't run if the default gallery functions weren't used
+ prepareGallery();
+
+ if ( gallery.jp_carousel( 'testIfOpened' ) )
+ return; // don't open if already opened
+
// make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
// infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
originalOverflow = $('body').css('overflow');
$('body').css('overflow', 'hidden');
-
- prepareGallery();
+ // prevent html from overflowing on some of the new themes.
+ originalHOverflow = $('html').css('overflow');
+ $('html').css('overflow', 'hidden');
+
container.data('carousel-extra', data);
return this.each(function() {
@@ -415,7 +458,7 @@ jQuery(document).ready(function($) {
if ( 0 === selected.length )
selected = slides.eq(0);
-
+
gallery.jp_carousel('selectSlide', selected, false);
return this;
},
@@ -423,6 +466,7 @@ jQuery(document).ready(function($) {
close : function(){
// make sure to let the page scroll again
$('body').css('overflow', originalOverflow);
+ $('html').css('overflow', originalHOverflow);
return container
.trigger('jp_carousel.beforeClose')
.fadeOut('fast', function(){
@@ -473,29 +517,21 @@ jQuery(document).ready(function($) {
gallery.jp_carousel('selectedSlide').removeClass('selected').css({'position': 'fixed'});
if (reverse !== true ) {
last = slides.last();
- slides.first().nextAll().not(last).css({'left':gallery.width()+slides.first().width()}).hide();
- last.css({
- 'left' : -last.width()
- });
- last.prev().css({
- 'left' : -last.width() - last.prev().width()
- });
- slides.first().css({'left':gallery.width()});
+ slides.first().nextAll().not(last).jp_carousel('setSlidePosition', gallery.width()+slides.first().width()).hide();
+ last.jp_carousel('setSlidePosition', -last.width());
+ last.prev().jp_carousel('setSlidePosition', -last.width() - last.prev().width());
+ slides.first().jp_carousel('setSlidePosition', gallery.width());
setTimeout(function(){
gallery.jp_carousel('selectSlide', slides.show().first());
}, 400);
} else {
first = slides.first();
- first.css({
- 'left':gallery.width()
- });
- first.next().css({
- 'left':gallery.width() + first.width()
- });
- first.next().nextAll().hide().css({'left':-slides.last().width()});
- slides.last().css({'left':-slides.last().width()});
- slides.last().prevAll().not(first, first.next()).hide().css({'left':-slides.last().width()-slides.last().prev().width()});
+ first.jp_carousel('setSlidePosition', gallery.width());
+ first.next().jp_carousel('setSlidePosition', gallery.width() + first.width());
+ first.next().nextAll().hide().jp_carousel('setSlidePosition', -slides.last().width());
+ slides.last().jp_carousel('setSlidePosition', -slides.last().width());
+ slides.last().prevAll().not(first, first.next()).hide().jp_carousel('setSlidePosition', -slides.last().width()-slides.last().prev().width());
setTimeout(function(){
gallery.jp_carousel('selectSlide', slides.show().last());
}, 400);
@@ -507,6 +543,16 @@ jQuery(document).ready(function($) {
return this.find('.selected');
},
+ setSlidePosition : function(x) {
+ return this.css({
+ '-webkit-transform':'translate3d(' + x + 'px,0,0)',
+ '-moz-transform':'translate3d(' + x + 'px,0,0)',
+ '-ms-transform':'translate(' + x + 'px,0)',
+ '-o-transform':'translate(' + x + 'px,0)',
+ 'transform':'translate3d(' + x + 'px,0,0)'
+ });
+ },
+
selectSlide : function(slide, animate){
var last = this.find('.selected').removeClass('selected'),
slides = gallery.jp_carousel('slides').css({'position': 'fixed'}),
@@ -521,7 +567,7 @@ jQuery(document).ready(function($) {
animated,
info_min;
// center the main image
-
+
caption.hide();
method = 'css';
@@ -534,7 +580,7 @@ jQuery(document).ready(function($) {
// slide the whole view to the x we want
slides.not(animated).hide();
- current[method]({left:left}).show();
+ current.jp_carousel('setSlidePosition', left).show();
// minimum width
gallery.jp_carousel('fitInfo', animate);
@@ -542,24 +588,24 @@ jQuery(document).ready(function($) {
// prep the slides
var direction = last.is(current.prevAll()) ? 1 : -1;
if ( 1 == direction ) {
- next_next.css({'left':gallery.width() + next.width()}).show();
- next.hide().css({'left':gallery.width() + current.width()}).show();
- previous_previous.css({'left':-previous_previous.width() - current.width()});
+ next_next.jp_carousel('setSlidePosition', gallery.width() + next.width()).show();
+ next.hide().jp_carousel('setSlidePosition', gallery.width() + current.width()).show();
+ previous_previous.jp_carousel('setSlidePosition', -previous_previous.width() - current.width()).show();
} else {
- previous.css({'left':-previous.width() - current.width()});
- next_next.css({'left':gallery.width() + current.width()});
+ previous.jp_carousel('setSlidePosition', -previous.width() - current.width()).show();
+ next_next.jp_carousel('setSlidePosition', gallery.width() + current.width()).show();
}
-
- // if advancing prepare the slide that will enter the screen
- previous[method]({left:-previous.width() + (screenPadding * 0.75) }).show();
- next[method]({left:gallery.width() - (screenPadding * 0.75) }).show();
+ // if advancing prepare the slide that will enter the screen
+ previous.jp_carousel('setSlidePosition', -previous.width() + (screenPadding * 0.75)).show();
+ next.jp_carousel('setSlidePosition', gallery.width() - (screenPadding * 0.75)).show();
+ next.css({'position': ''});
document.location.href = document.location.href.replace(/#.*/, '') + '#jp-carousel-' + current.data('attachment-id');
gallery.jp_carousel('resetButtons', current);
container.trigger('jp_carousel.selectSlide', [current]);
$( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ).html('');
-
+
gallery.jp_carousel('getTitleDesc', { title: current.data('title'), desc: current.data('desc') } );
gallery.jp_carousel('getMeta', current.data('image-meta'));
gallery.jp_carousel('getFullSizeLink', current);
@@ -568,7 +614,7 @@ jQuery(document).ready(function($) {
gallery.jp_carousel('getComments', {'attachment_id': current.data('attachment-id'), 'offset': 0, 'clear': true});
$('#jp-carousel-comment-post-results').slideUp();
-
+
// $('
').html(sometext).text() is a trick to go to HTML to plain text (including HTML emntities decode, etc)
if ( current.data('caption') ) {
if ( $('
').html(current.data('caption')).text() == $('
').html(current.data('title')).text() )
@@ -593,7 +639,7 @@ jQuery(document).ready(function($) {
};
},
- loadSlide : function(){
+ loadSlide : function() {
return this.each(function(){
var slide = $(this);
slide.find('img')
@@ -642,13 +688,19 @@ jQuery(document).ready(function($) {
'left' : (info.width() - size.width) * 0.5,
'width' : size.width
});
+
+ if (isMobile){
+ photo_info.css('left', '0px');
+ photo_info.css('top', '-20px');
+ }
+
return this;
},
fitMeta : function(animated){
var newInfoTop = { top: ( $(window).height() / 100 * proportion + 5 ) + 'px' };
var newLeftWidth = { width: ( info.width() - (imageMeta.width() + 80) ) + 'px' };
-
+
if (animated) {
info.animate(newInfoTop);
leftColWrapper.animate(newLeftWidth);
@@ -666,27 +718,14 @@ jQuery(document).ready(function($) {
method = 'css',
max = gallery.jp_carousel('slideDimensions');
- if ( 0 === selected.length ) {
- dimensions.left = $(window).width();
- } else if ($this.is(selected)) {
- dimensions.left = ($(window).width() - dimensions.width) * 0.5;
- } else if ($this.is(selected.next())) {
- dimensions.left = gallery.width() - ( screenPadding * 0.75 );
- } else if ($this.is(selected.prev())) {
- dimensions.left = -dimensions.width + screenPadding * 0.75;
- } else {
- if ($this.is(selected.nextAll())) {
- dimensions.left = $(window).width();
- } else {
- dimensions.left = -dimensions.width;
- }
- }
+ dimensions.left = 0;
dimensions.top = ( (max.height - dimensions.height) * 0.5 ) + 40;
$this[method](dimensions);
});
},
texturize : function(text) {
+ text = new String(text); // make sure we get a string. Title "1" came in as int 1, for example, which did not support .replace().
text = text.replace(/'/g, '’').replace(/'/g, '’').replace(/[\u2019]/g, '’');
text = text.replace(/"/g, '”').replace(/"/g, '”').replace(/"/g, '”').replace(/[\u201D]/g, '”');
text = text.replace(/([\w]+)=[\d]+;(.+?)[\d]+;/g, '$1="$2"'); // untexturize allowed HTML tags params double-quotes
@@ -700,7 +739,7 @@ jQuery(document).ready(function($) {
// Calculate the new src.
items.each(function(i){
var src_item = $(this),
- orig_size = src_item.data('orig-size') || 0,
+ orig_size = src_item.data('orig-size') || '',
max = gallery.jp_carousel('slideDimensions'),
parts = orig_size.split(',');
orig_size = {width: parseInt(parts[0], 10), height: parseInt(parts[1], 10)},
@@ -708,7 +747,7 @@ jQuery(document).ready(function($) {
large_file = src_item.data('large-file') || '';
src = src_item.data('orig-file');
-
+
src = gallery.jp_carousel('selectBestImageSize', {
orig_file : src,
orig_width : orig_size.width,
@@ -718,7 +757,7 @@ jQuery(document).ready(function($) {
medium_file : medium_file,
large_file : large_file
});
-
+
// Set the final src
$(this).data( 'gallery-src', src );
});
@@ -733,45 +772,49 @@ jQuery(document).ready(function($) {
attachment_id = src_item.data('attachment-id') || 0,
comments_opened = src_item.data('comments-opened') || 0,
image_meta = src_item.data('image-meta') || {},
- orig_size = src_item.data('orig-size') || 0,
- title = src_item.attr('title') || '',
+ orig_size = src_item.data('orig-size') || '',
+ title = src_item.data('image-title') || '',
description = src_item.data('image-description') || '',
caption = src_item.parents('dl').find('dd.gallery-caption').html() || '',
- src = src_item.data('gallery-src') || '',
+ src = src_item.data('gallery-src') || '',
medium_file = src_item.data('medium-file') || '',
- large_file = src_item.data('large-file') || '';
-
- if ( !attachment_id || !orig_size )
- return false; // break the loop if we are missing the data-* attributes
-
- title = gallery.jp_carousel('texturize', title);
- description = gallery.jp_carousel('texturize', description);
- caption = gallery.jp_carousel('texturize', caption);
-
- var slide = $('
')
- .hide()
- .css({
- 'position' : 'fixed',
- 'left' : i < start_index ? -1000 : gallery.width()
- })
- .append($(' '))
- .appendTo(gallery)
- .data('src', src )
- .data('title', title)
- .data('desc', description)
- .data('caption', caption)
- .data('attachment-id', attachment_id)
- .data('permalink', src_item.parents('a').attr('href'))
- .data('orig-size', orig_size)
- .data('comments-opened', comments_opened)
- .data('image-meta', image_meta)
- .data('medium-file', medium_file)
- .data('large-file', large_file)
- .jp_carousel('fitSlide', false);
-
-
- // Preloading all images
- slide.find('img').first().attr('src', src );
+ large_file = src_item.data('large-file') || '',
+ orig_file = src_item.data('orig-file') || '';
+
+ var tiledCaption = src_item.parents('div.tiled-gallery-item').find('div.tiled-gallery-caption').html();
+ if ( tiledCaption )
+ caption = tiledCaption;
+
+ if ( attachment_id && orig_size.length ) {
+ title = gallery.jp_carousel('texturize', title);
+ description = gallery.jp_carousel('texturize', description);
+ caption = gallery.jp_carousel('texturize', caption);
+
+ var slide = $('
')
+ .hide()
+ .css({
+ //'position' : 'fixed',
+ 'left' : i < start_index ? -1000 : gallery.width()
+ })
+ .append($(' '))
+ .appendTo(gallery)
+ .data('src', src )
+ .data('title', title)
+ .data('desc', description)
+ .data('caption', caption)
+ .data('attachment-id', attachment_id)
+ .data('permalink', src_item.parents('a').attr('href'))
+ .data('orig-size', orig_size)
+ .data('comments-opened', comments_opened)
+ .data('image-meta', image_meta)
+ .data('medium-file', medium_file)
+ .data('large-file', large_file)
+ .data('orig-file', orig_file)
+ .jp_carousel('fitSlide', false);
+
+ // Preloading all images
+ slide.find('img').first().attr('src', src );
+ }
});
return this;
},
@@ -779,13 +822,13 @@ jQuery(document).ready(function($) {
selectBestImageSize: function(args) {
if ( 'object' != typeof args )
args = {};
-
+
if ( 'undefined' == typeof args.orig_file )
return '';
-
+
if ( 'undefined' == typeof args.orig_width || 'undefined' == typeof args.max_width )
return args.orig_file;
-
+
if ( 'undefined' == typeof args.medium_file || 'undefined' == typeof args.large_file )
return args.orig_file;
@@ -797,19 +840,19 @@ jQuery(document).ready(function($) {
large_size_parts = (large_size != args.large_file) ? large_size.split('x') : [args.orig_width, 0],
large_width = parseInt( large_size_parts[0], 10 ),
large_height = parseInt( large_size_parts[1], 10 );
-
+
// Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc)
if ('undefined' != typeof window.devicePixelRatio && window.devicePixelRatio > 1) {
args.max_width = args.max_width * window.devicePixelRatio;
args.max_height = args.max_height * window.devicePixelRatio;
}
- if ( medium_width >= args.max_width || medium_height >= args.max_height )
- return args.medium_file;
-
if ( large_width >= args.max_width || large_height >= args.max_height )
return args.large_file;
+ if ( medium_width >= args.max_width || medium_height >= args.max_height )
+ return args.medium_file;
+
return args.orig_file;
},
@@ -826,14 +869,14 @@ jQuery(document).ready(function($) {
return;
if ( ! args.replacements || 'undefined' == typeof args.replacements )
return args.text;
- return args.text.replace(/{(\d+)}/g, function(match, number) {
+ return args.text.replace(/{(\d+)}/g, function(match, number) {
return typeof args.replacements[number] != 'undefined' ? args.replacements[number] : match;
});
},
shutterSpeed: function(d) {
if (d >= 1)
- Math.round(d) + 's';
+ return Math.round(d) + 's';
var df = 1, top = 1, bot = 1;
var limit = 1e5; //Increase for greater precision.
while (df != d && limit-- > 0) {
@@ -887,16 +930,16 @@ jQuery(document).ready(function($) {
});
return value;
},
-
+
getTitleDesc: function( data ) {
var title ='', desc = '', markup = '', target, commentWrappere;
-
+
target = $( 'div.jp-carousel-titleanddesc', 'div.jp-carousel-wrap' );
target.hide();
-
+
title = gallery.jp_carousel('parseTitleDesc', data.title) || '';
desc = gallery.jp_carousel('parseTitleDesc', data.desc) || '';
-
+
if ( title.length || desc.length ) {
// $('
').html(sometext).text() is a trick to go to HTML to plain text (including HTML emntities decode, etc)
if ( $('
').html(title).text() == $('
').html(desc).text() )
@@ -911,16 +954,16 @@ jQuery(document).ready(function($) {
$( 'div#jp-carousel-comment-form-container' ).css('margin-top', '20px');
$( 'div#jp-carousel-comments-loading' ).css('margin-top', '20px');
},
-
+
getMeta: function( meta ) {
if ( !meta || 1 != jetpackCarouselStrings.display_exif )
return false;
-
+
var $ul = $( '' );
$.each( meta, function( key, val ) {
if ( 0 === parseFloat(val) || !val.length || -1 === $.inArray( key, [ 'camera', 'aperture', 'shutter_speed', 'focal_length' ] ) )
return;
-
+
switch( key ) {
case 'focal_length':
val = val + 'mm';
@@ -935,7 +978,7 @@ jQuery(document).ready(function($) {
// making jslint happy
break;
}
-
+
$ul.append( '' + jetpackCarouselStrings[key] + ' ' + val + ' ' );
});
@@ -949,23 +992,23 @@ jQuery(document).ready(function($) {
getFullSizeLink: function(current) {
if(!current || !current.data)
return false;
- var original = current.data('src').replace(/\?.+$/, ''),
+ var original = current.data('orig-file').replace(/\?.+$/, ''),
origSize = current.data('orig-size').split(','),
permalink = $( ''+gallery.jp_carousel('format', {'text': jetpackCarouselStrings.download_original, 'replacements': origSize})+' ' )
.addClass( 'jp-carousel-image-download' )
.attr( 'href', original )
.attr( 'target', '_blank' );
-
+
$( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' )
.append( permalink );
},
-
+
getMap: function( meta ) {
if ( !meta.latitude || !meta.longitude || 1 != jetpackCarouselStrings.display_geo )
return;
-
- var latitude = meta.latitude,
- longitude = meta.longitude,
+
+ var latitude = meta.latitude,
+ longitude = meta.longitude,
$metabox = $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ),
$mapbox = $( '
' ),
style = '&scale=2&style=feature:all|element:all|invert_lightness:true|hue:0x0077FF|saturation:-50|lightness:-5|gamma:0.91';
@@ -1003,23 +1046,23 @@ jQuery(document).ready(function($) {
getComments: function( args ) {
if ( 'object' != typeof args )
args = {};
-
+
if ( ! args.attachment_id || 'undefined' == typeof args.attachment_id )
return;
-
+
if ( ! args.offset || 'undefined' == typeof args.offset || args.offset < 1 )
args.offset = 0;
-
+
var comments = $('.jp-carousel-comments'),
commentsLoading = $('#jp-carousel-comments-loading');
-
+
commentsLoading.show();
-
+
if ( args.clear ) {
comments.hide();
comments.empty();
}
-
+
$.ajax({
type: 'GET',
url: jetpackCarouselStrings.ajaxurl,
@@ -1056,7 +1099,7 @@ jQuery(document).ready(function($) {
+ ''
);
comments.append(comment);
-
+
// Set the interval to check for a new page of comments.
clearInterval( commentInterval );
commentInterval = setInterval( function() {
@@ -1066,7 +1109,7 @@ jQuery(document).ready(function($) {
}
}, 150 );
});
-
+
// Verify (late) that the user didn't repeatldy click the arrows really fast, in which case the requested
// attachment id might no longer match the current attachment id by the time we get the data back or a now
// registered infiniscroll event kicks in, so we don't ever display comments for the wrong image by mistake.
@@ -1079,7 +1122,7 @@ jQuery(document).ready(function($) {
// Increase the height of the background, semi-transparent overlay to match the new length of the comments list.
$('.jp-carousel-overlay').height( $(window).height() + titleAndDescription.height() + commentForm.height() + ( (comments.height() > 0) ? comments.height() : imageMeta.height() ) + 200 );
-
+
comments.show();
commentsLoading.hide();
},
@@ -1130,18 +1173,28 @@ jQuery(document).ready(function($) {
};
- // register the event listener for staring the gallery
- $( document.body ).on( 'click', 'div.gallery', function(e) {
+ // register the event listener for starting the gallery
+ $( document.body ).on( 'click', 'div.gallery,div.tiled-gallery', function(e) {
+ if ( ! $(this).jp_carousel( 'testForData', e.currentTarget ) )
+ return;
if ( $(e.target).parent().hasClass('gallery-caption') )
return;
e.preventDefault();
- $(this).jp_carousel('open', {start_index: $(this).find('.gallery-item').index($(e.target).parents('.gallery-item'))});
+ $(this).jp_carousel('open', {start_index: $(this).find('.gallery-item, .tiled-gallery-item').index($(e.target).parents('.gallery-item, .tiled-gallery-item'))});
});
- // start on page load if hash exists
- if ( document.location.hash && document.location.hash.match(/jp-carousel-(\d+)/) ) {
- $(document).ready(function(){
- var gallery = $('div.gallery'), index = -1, n = document.location.hash.match(/jp-carousel-(\d+)/);
+ // Set an interval on page load to load the carousel if hash exists and not already opened.
+ // Makes carousel work on page load and when back button leads to same URL with carousel hash (ie: no actual document.ready trigger)
+ $(document).ready(function(){
+ var jp_carousel_open_interval = window.setInterval(function(){
+ // We should have a URL hash by now.
+ if ( ! document.location.hash || ! document.location.hash.match(/jp-carousel-(\d+)/) )
+ return;
+
+ var gallery = $('div.gallery, div.tiled-gallery'), index = -1, n = document.location.hash.match(/jp-carousel-(\d+)/);
+
+ if ( ! $(this).jp_carousel( 'testForData', gallery ) )
+ return;
n = parseInt(n[1], 10);
@@ -1153,7 +1206,10 @@ jQuery(document).ready(function($) {
});
if ( index != -1 )
- gallery.jp_carousel('open', {start_index: index});
- });
- }
+ gallery.jp_carousel('open', {start_index: index}); // open method checks if already opened
+ }, 1000);
+ });
});
+
+// Swipe gesture detection
+(function($){$.fn.touchwipe=function(settings){var config={min_move_x:20,min_move_y:20,wipeLeft:function(){},wipeRight:function(){},wipeUp:function(){},wipeDown:function(){},preventDefaultEvents:true};if(settings)$.extend(config,settings);this.each(function(){var startX;var startY;var isMoving=false;function cancelTouch(){this.removeEventListener('touchmove',onTouchMove);startX=null;isMoving=false}function onTouchMove(e){if(config.preventDefaultEvents){e.preventDefault()}if(isMoving){var x=e.touches[0].pageX;var y=e.touches[0].pageY;var dx=startX-x;var dy=startY-y;if(Math.abs(dx)>=config.min_move_x){cancelTouch();if(dx>0){config.wipeLeft()}else{config.wipeRight()}}else if(Math.abs(dy)>=config.min_move_y){cancelTouch();if(dy>0){config.wipeDown()}else{config.wipeUp()}}}}function onTouchStart(e){if(e.touches.length==1){startX=e.touches[0].pageX;startY=e.touches[0].pageY;isMoving=true;this.addEventListener('touchmove',onTouchMove,false)}}if('ontouchstart'in document.documentElement){this.addEventListener('touchstart',onTouchStart,false)}});return this}})(jQuery);
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.php b/plugins/jetpack/modules/carousel/jetpack-carousel.php
index 3d48681b..314dd7b2 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.php
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.php
@@ -76,7 +76,7 @@ class Jetpack_Carousel {
}
function enqueue_assets( $output ) {
- if ( ! empty( $output ) ) {
+ if ( ! empty( $output ) && ! apply_filters( 'jp_carousel_force_enable', false ) ) {
// Bail because someone is overriding the [gallery] shortcode.
remove_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
remove_filter( 'wp_get_attachment_link', array( $this, 'add_data_to_images' ) );
@@ -86,12 +86,7 @@ class Jetpack_Carousel {
do_action( 'jp_carousel_thumbnails_shown' );
if ( $this->first_run ) {
- if ( ! has_action( 'wp_enqueue_scripts', 'register_spin_scripts' ) ) {
- wp_enqueue_script( 'spin', plugins_url( 'spin.js', __FILE__ ), false, '1.2.4' );
- wp_enqueue_script( 'jquery.spin', plugins_url( 'jquery.spin.js', __FILE__ ) , array( 'jquery', 'spin' ) );
- }
-
- wp_enqueue_script( 'jetpack-carousel', plugins_url( 'jetpack-carousel.js', __FILE__ ), array( 'jquery' ), $this->asset_version( '20120629' ), true );
+ wp_enqueue_script( 'jetpack-carousel', plugins_url( 'jetpack-carousel.js', __FILE__ ), array( 'jquery.spin' ), $this->asset_version( '20130109' ), true );
// Note: using home_url() instead of admin_url() for ajaxurl to be sure to get same domain on wpcom when using mapped domains (also works on self-hosted)
// Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context.
@@ -140,7 +135,14 @@ class Jetpack_Carousel {
$localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings );
wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings );
wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( '20120629' ) );
-
+ global $is_IE;
+ if( $is_IE )
+ {
+ $msie = strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) + 4;
+ $version = (float) substr( $_SERVER['HTTP_USER_AGENT'], $msie, strpos( $_SERVER['HTTP_USER_AGENT'], ';', $msie ) - $msie );
+ if( $version < 9 )
+ wp_enqueue_style( 'jetpack-carousel-ie8fix', plugins_url( 'jetpack-carousel-ie8fix.css', __FILE__ ), array(), $this->asset_version( '20121024' ) );
+ }
do_action( 'jp_carousel_enqueue_assets', $this->first_run, $localize_strings );
$this->first_run = false;
@@ -154,10 +156,11 @@ class Jetpack_Carousel {
return $html;
$attachment_id = intval( $attachment_id );
- $orig_file = wp_get_attachment_url( $attachment_id );
+ $orig_file = wp_get_attachment_image_src( $attachment_id, 'full' );
+ $orig_file = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id );
$meta = wp_get_attachment_metadata( $attachment_id );
$size = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : '';
- $img_meta = $meta['image_meta'];
+ $img_meta = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array();
$comments_opened = intval( comments_open( $attachment_id ) );
/*
@@ -167,10 +170,10 @@ class Jetpack_Carousel {
* $content_width has no filter we could temporarily de-register, run wp_get_attachment_image_src(), then
* re-register. So using returned file URL instead, which we can define the sizes from through filename
* parsing in the JS, as this is a failsafe file reference.
- *
+ *
* EG with Twenty Eleven activated:
* array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(584) [2]=> int(435) [3]=> bool(true) }
- *
+ *
* EG with Twenty Ten activated:
* array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(640) [2]=> int(477) [3]=> bool(true) }
*/
@@ -178,11 +181,12 @@ class Jetpack_Carousel {
$medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' );
$medium_file = isset( $medium_file_info[0] ) ? $medium_file_info[0] : '';
- $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
- $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
+ $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
+ $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
- $attachment = get_post( $attachment_id );
- $attachment_desc = wpautop( wptexturize( $attachment->post_content ) );
+ $attachment = get_post( $attachment_id );
+ $attachment_title = wptexturize( $attachment->post_title );
+ $attachment_desc = wpautop( wptexturize( $attachment->post_content ) );
// Not yet providing geo-data, need to "fuzzify" for privacy
if ( ! empty( $img_meta ) ) {
@@ -192,17 +196,18 @@ class Jetpack_Carousel {
}
}
- $img_meta = json_encode( $img_meta );
+ $img_meta = json_encode( array_map( 'strval', $img_meta ) );
$html = str_replace(
' 'approve',
'order' => ( 'asc' == get_option('comment_order') ) ? 'ASC' : 'DESC',
@@ -255,9 +260,9 @@ class Jetpack_Carousel {
'offset' => $offset,
'post_id' => $attachment_id,
) );
-
+
$out = array();
-
+
// Can't just send the results, they contain the commenter's email address.
foreach ( $comments as $comment ) {
$author_markup = '' . esc_html( $comment->comment_author ) . ' ';
@@ -270,42 +275,42 @@ class Jetpack_Carousel {
'content' => wpautop($comment->comment_content),
);
}
-
+
die( json_encode( $out ) );
}
function post_attachment_comment() {
if ( ! headers_sent() )
header('Content-type: text/javascript');
-
+
if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce($_POST['nonce'], 'carousel_nonce') )
die( json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) );
-
+
$_blog_id = (int) $_POST['blog_id'];
$_post_id = (int) $_POST['id'];
$comment = $_POST['comment'];
-
+
if ( empty( $_blog_id ) )
die( json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) );
-
+
if ( empty( $_post_id ) )
die( json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) );
-
+
if ( empty( $comment ) )
die( json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) );
// Used in context like NewDash
$switched = false;
- if ( $_blog_id != get_current_blog_id() ) {
+ if ( is_multisite() && $_blog_id != get_current_blog_id() ) {
switch_to_blog( $_blog_id );
$switched = true;
}
-
+
do_action('jp_carousel_check_blog_user_privileges');
if ( ! comments_open( $_post_id ) )
die( json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) );
-
+
if ( is_user_logged_in() ) {
$user = wp_get_current_user();
$user_id = $user->ID;
@@ -353,10 +358,10 @@ class Jetpack_Carousel {
die( json_encode( array( 'comment_id' => $comment_id, 'comment_status' => $comment_status ) ) );
}
-
+
function register_settings() {
add_settings_section('carousel_section', __( 'Image Gallery Carousel', 'jetpack' ), array( $this, 'carousel_section_callback' ), 'media');
-
+
if ( ! $this->in_jetpack ) {
add_settings_field('carousel_enable_it', __( 'Enable carousel', 'jetpack' ), array( $this, 'carousel_enable_it_callback' ), 'media', 'carousel_section' );
register_setting( 'media', 'carousel_enable_it', array( $this, 'carousel_enable_it_sanitize' ) );
@@ -364,7 +369,7 @@ class Jetpack_Carousel {
add_settings_field('carousel_background_color', __( 'Background color', 'jetpack' ), array( $this, 'carousel_background_color_callback' ), 'media', 'carousel_section' );
register_setting( 'media', 'carousel_background_color', array( $this, 'carousel_background_color_sanitize' ) );
-
+
add_settings_field('carousel_display_exif', __( 'Metadata', 'jetpack'), array( $this, 'carousel_display_exif_callback' ), 'media', 'carousel_section' );
register_setting( 'media', 'carousel_display_exif', array( $this, 'carousel_display_exif_sanitize' ) );
@@ -386,7 +391,7 @@ class Jetpack_Carousel {
}
return ( 1 == $value ) ? 1 : 0;
}
-
+
function sanitize_1or0_option( $value ) {
return ( 1 == $value ) ? 1 : 0;
}
@@ -435,7 +440,7 @@ class Jetpack_Carousel {
function carousel_display_geo_sanitize( $value ) {
return $this->sanitize_1or0_option( $value );
- }
+ }
function carousel_background_color_callback() {
$this->settings_select( 'carousel_background_color', array( 'black' => __( 'Black', 'jetpack' ), 'white' => __( 'White', 'jetpack', 'jetpack' ) ) );
diff --git a/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css b/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css
new file mode 100644
index 00000000..0d010eb5
--- /dev/null
+++ b/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css
@@ -0,0 +1,1106 @@
+/* This file was automatically generated on Jan 29 2013 22:51:19 */
+
+* {
+ line-height:inherit; /* prevent declarations of line-height in the universal selector */
+}
+
+.jp-carousel-overlay {
+ background: #000;
+}
+
+div.jp-carousel-fadeaway {
+ background: -moz-linear-gradient(bottom, rgba(0,0,0,0.5), rgba(0,0,0,0));
+ background: -webkit-gradient(linear, right bottom, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0)));
+ position: fixed;
+ bottom: 0;
+ z-index: 2147483647;
+ width: 100%;
+ height: 15px;
+}
+
+.jp-carousel-next-button span,
+.jp-carousel-previous-button span {
+ background: url(.././images/arrows.png) no-repeat center center;
+ background-size: 200px 126px;
+}
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 1.5),
+only screen and (-o-min-device-pixel-ratio: 3/2),
+only screen and (min--moz-device-pixel-ratio: 1.5),
+only screen and (min-device-pixel-ratio: 1.5) {
+ .jp-carousel-next-button span,
+ .jp-carousel-previous-button span {
+ background-image: url(.././images/arrows-2x.png);
+ }
+}
+
+.jp-carousel-wrap {
+ font-family: "Helvetica Neue", sans-serif !important;
+}
+
+.jp-carousel-info {
+ position: absolute;
+ bottom: 0;
+ text-align: right !important;
+ -webkit-font-smoothing: subpixel-antialiased !important;
+}
+
+.jp-carousel-info ::selection {
+ background: #68c9e8; /* Safari */
+ color: #fff;
+ }
+
+.jp-carousel-info ::-moz-selection {
+ background: #68c9e8; /* Firefox */
+ color: #fff;
+}
+
+.jp-carousel-photo-info {
+ position: relative;
+ -webkit-transition: 400ms ease-out;
+ -moz-transition: 400ms ease-out;
+ -o-transition: 400ms ease-out;
+ transition: 400ms ease-out;
+ right: 25%;
+ width: 50%;
+}
+
+.jp-carousel-info h2 {
+ background: none !important;
+ border: none !important;
+ color: #999;
+ display: block !important;
+ font: normal 13px/1.25em "Helvetica Neue", sans-serif !important;
+ letter-spacing: 0 !important;
+ margin: 7px 0 0 0 !important;
+ padding: 10px 0 0 !important;
+ overflow: hidden;
+ text-align: right;
+ text-shadow: none !important;
+ text-transform: none !important;
+ -webkit-font-smoothing: subpixel-antialiased;
+}
+
+.jp-carousel-next-button,
+.jp-carousel-previous-button {
+ text-indent: -9999px;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.jp-carousel-next-button span,
+.jp-carousel-previous-button span {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 82px;
+ zoom: 1;
+ filter: alpha(opacity=20);
+ opacity: 0.2;
+ -webkit-transition: 500ms opacity ease-out;
+ -moz-transition: 500ms opacity ease-out;
+ -o-transition: 500ms opacity ease-out;
+ transition: 500ms opacity ease-out;
+}
+
+.jp-carousel-next-button:hover span,
+.jp-carousel-previous-button:hover span {
+ filter: alpha(opacity=60);
+ opacity: 0.6;
+}
+.jp-carousel-next-button span {
+ background-position: -110px center;
+ left: 0;
+}
+
+.jp-carousel-previous-button span {
+ background-position: -10px center;
+ right:0;
+}
+
+.jp-carousel-buttons {
+ margin:-18px -20px 15px;
+ padding:8px 10px;
+ border-bottom:1px solid #222;
+ background: #222;
+ text-align: center;
+}
+
+div.jp-carousel-buttons a {
+ border: none !important;
+ color: #999;
+ font: normal 11px/1.2em "Helvetica Neue", sans-serif !important;
+ letter-spacing: 0 !important;
+ padding: 5px 0 5px 2px;
+ text-decoration: none !important;
+ text-shadow: none !important;
+ vertical-align: baseline !important;
+ -webkit-font-smoothing: subpixel-antialiased;
+}
+
+div.jp-carousel-buttons a:hover {
+ color: #68c9e8;
+ border: none !important;
+ -webkit-transition: none !important;
+ -moz-transition: none !important;
+ -o-transition: none !important;
+ transition: none !important;
+}
+
+.jp-carousel-slide, .jp-carousel-slide img, .jp-carousel-next-button,
+.jp-carousel-previous-button {
+ -webkit-transform:translate3d(0, 0, 0);
+ -moz-transform:translate3d(0, 0, 0);
+ -o-transform:translate3d(0, 0, 0);
+ -ms-transform:translate3d(0, 0, 0);
+}
+
+.jp-carousel-slide {
+ position:absolute;
+ width:0;
+ bottom:0;
+ background-color:#000;
+ border-radius:2px;
+ -webkit-border-radius:2px;
+ -moz-border-radius:2px;
+ -ms-border-radius:2px;
+ -o-border-radius:2px;
+ -webkit-transition: 400ms ease-out;
+ -moz-transition: 400ms ease-out;
+ -o-transition: 400ms ease-out;
+ transition: 400ms ease-out;
+}
+
+.jp-carousel-slide img {
+ display: block;
+ width: 100% !important;
+ height: 100% !important;
+ max-width: 100% !important;
+ max-height: 100% !important;
+ background: none !important;
+ border: none !important;
+ padding: 0 !important;
+ -webkit-box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ -moz-box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ zoom: 1;
+ filter: alpha(opacity=25);
+ opacity: 0.25;
+ -webkit-transition: opacity 400ms linear;
+ -moz-transition: opacity 400ms linear;
+ -o-transition: opacity 400ms linear;
+ transition: opacity 400ms linear;
+}
+
+.jp-carousel-slide.selected img {
+ filter: alpha(opacity=100);
+ opacity: 1;
+}
+
+.jp-carousel-close-hint {
+ color: #999;
+ cursor: default;
+ letter-spacing: 0 !important;
+ padding:0.35em 0 0;
+ position: absolute;
+ text-align: right;
+ width: 90%;
+ -webkit-transition: color 200ms linear;
+ -moz-transition: color 200ms linear;
+ -o-transition: color 200ms linear;
+ transition: color 200ms linear;
+}
+
+.jp-carousel-close-hint span {
+ cursor: pointer;
+ background-color: black;
+ background-color: rgba(0,0,0,0.8);
+ display: block;
+ height: 22px;
+ font: 400 24px/1 "Helvetica Neue", sans-serif !important;
+ line-height: 22px;
+ margin: 0 0.4em 0 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 22px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-transition: border-color 200ms linear;
+ -moz-transition: border-color 200ms linear;
+ -o-transition: border-color 200ms linear;
+ transition: border-color 200ms linear;
+}
+
+.jp-carousel-close-hint:hover {
+ cursor: default;
+ color: #fff;
+}
+
+.jp-carousel-close-hint:hover span {
+ border-color: #fff;
+}
+
+div.jp-carousel-buttons a.jp-carousel-like,
+div.jp-carousel-buttons a.jp-carousel-reblog,
+div.jp-carousel-buttons a.jp-carousel-commentlink,
+a.jp-carousel-image-download {
+ background: url(.././images/carousel-sprite.png?4) no-repeat;
+ background-size: 16px 160px;
+}
+
+div.jp-carousel-buttons a.jp-carousel-reblog,
+div.jp-carousel-buttons a.jp-carousel-commentlink {
+ margin:0 0 0 14px !important;
+}
+
+div.jp-carousel-buttons a.jp-carousel-reblog.reblogged,
+div.jp-carousel-buttons a.jp-carousel-like.liked {
+ background-color: #303030;
+ padding-left: 8px !important;
+ border-radius: 2px;
+ border-radius:2px;
+ -webkit-border-radius:2px;
+ -moz-border-radius:2px;
+ -ms-border-radius:2px;
+ -o-border-radius:2px;
+}
+
+div.jp-carousel-buttons a.jp-carousel-reblog.reblogged {
+ margin:0 -12px 0 2px !important;
+}
+
+
+div.jp-carousel-buttons a.jp-carousel-reblog,
+div.jp-carousel-buttons a.jp-carousel-reblog.reblogged:hover {
+ background-position: 6px -36px;
+ padding-right: 26px !important;
+ color: #999;
+}
+
+div.jp-carousel-buttons a.jp-carousel-commentlink {
+ background-position: 0px -116px;
+ padding-right: 19px !important;
+}
+
+div.jp-carousel-buttons a.jp-carousel-reblog.reblogged:hover {
+ cursor: default;
+}
+
+div.jp-carousel-buttons a.jp-carousel-reblog:hover {
+ background-position: 6px -56px;
+ color: #68c9e8;
+}
+
+div.jp-carousel-buttons a.jp-carousel-like {
+ background-position: 5px 5px;
+ padding-right: 24px !important;
+}
+
+div.jp-carousel-buttons a.jp-carousel-like:hover {
+ background-position: 5px -15px;
+}
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 1.5),
+only screen and (-o-min-device-pixel-ratio: 3/2),
+only screen and (min--moz-device-pixel-ratio: 1.5),
+only screen and (min-device-pixel-ratio: 1.5) {
+ div.jp-carousel-buttons a.jp-carousel-like,
+ div.jp-carousel-buttons a.jp-carousel-reblog,
+ div.jp-carousel-buttons a.jp-carousel-commentlink,
+ a.jp-carousel-image-download {
+ background-image: url(.././images/carousel-sprite-2x.png?4);
+ }
+}
+
+/* reblog */
+div#carousel-reblog-box {
+ background: #222;
+ background: -moz-linear-gradient(bottom, #222, #333);
+ background: -webkit-gradient(linear, right bottom, right top, from(#222), to(#333));
+ padding: 3px 0 0;
+ display: none;
+ margin: 5px auto 0;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+ -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.9);
+ -moz-box-shadow: 0 0 20px rgba(0,0,0,0.9);
+ box-shadow: 0 0 20px rgba(0,0,0,0.9);
+ height: 74px;
+ width: 565px;
+}
+
+#carousel-reblog-box textarea {
+ background: #999;
+ font: 13px/1.4 "Helvetica Neue", sans-serif !important;
+ color: #444;
+ padding: 3px 6px;
+ width: 370px;
+ height: 48px;
+ float: right;
+ margin: 6px 9px 0 9px;
+ border: 1px solid #666;
+ -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+}
+
+#carousel-reblog-box textarea:focus {
+ background: #ccc;
+ color: #222;
+}
+
+#carousel-reblog-box label {
+ color: #aaa;
+ font-size: 11px;
+ padding-left: 2px;
+ padding-right: 2px;
+ display: inline;
+ font-weight: normal;
+}
+
+#carousel-reblog-box select {
+ width: 110px;
+ padding: 0;
+ font-size: 12px;
+ font-family: "Helvetica Neue", sans-serif !important;
+ background: #333;
+ color: #eee;
+ border: 1px solid #444;
+ margin-top:5px;
+}
+
+#carousel-reblog-box .submit,
+#wrapper #carousel-reblog-box p.response {
+ float: right;
+ width: 154px;
+ padding-top: 0;
+ padding-right: 1px;
+ overflow: hidden;
+ height: 34px;
+ margin:3px 2px 0 0 !important;
+}
+
+#wrapper #carousel-reblog-box p.response {
+ font-size: 13px;
+ clear: none;
+ padding-right: 2px;
+ height: 34px;
+ color: #aaa;
+}
+
+#carousel-reblog-box input#carousel-reblog-submit, #jp-carousel-comment-form-button-submit {
+ font: 13px/24px "Helvetica Neue", sans-serif !important;
+ margin-top: 8px;
+ padding: 0 10px !important;
+ border-radius: 1em;
+ height: 24px;
+ color: #333;
+ cursor:pointer;
+ font-weight: normal;
+ background: #aaa;
+ background: -moz-linear-gradient(bottom, #aaa, #ccc);
+ background: -webkit-gradient(linear, right bottom, right top, from(#aaa), to(#ccc));
+ border: 1px solid #444;
+}
+
+#carousel-reblog-box input#carousel-reblog-submit:hover, #jp-carousel-comment-form-button-submit:hover {
+ background: #ccc;
+ background: -moz-linear-gradient(bottom, #ccc, #eee);
+ background: -webkit-gradient(linear, right bottom, right top, from(#ccc), to(#eee));
+}
+
+#carousel-reblog-box .canceltext {
+ color: #aaa;
+ font-size: 11px;
+ line-height: 24px;
+}
+
+#carousel-reblog-box .canceltext a {
+ color: #fff;
+}
+/* reblog end */
+
+
+/** Title and Desc Start **/
+.jp-carousel-titleanddesc {
+ border-top: 1px solid #222;
+ color: #999;
+ font-size: 15px;
+ padding-top: 24px;
+ margin-bottom: 20px;
+ font-weight:400;
+}
+.jp-carousel-titleanddesc-title {
+ font: 300 1.5em/1.1 "Helvetica Neue", sans-serif !important;
+ text-transform: none !important; /* prevents uppercase from leaking through */
+ color: #fff;
+ margin: 0 0 15px;
+ padding:0;
+}
+
+.jp-carousel-titleanddesc-desc p {
+ color: #999;
+ line-height:1.4;
+ margin-bottom: 0.75em;
+}
+
+.jp-carousel-titleanddesc p a,
+.jp-carousel-comments p a,
+.jp-carousel-info h2 a {
+ color: #fff !important;
+ border: none !important;
+ text-decoration: underline !important;
+ font-weight: normal !important;
+ font-style: normal !important;
+}
+
+.jp-carousel-titleanddesc p strong,
+.jp-carousel-titleanddesc p b {
+ font-weight: bold;
+ color: #999;
+}
+
+.jp-carousel-titleanddesc p em,
+.jp-carousel-titleanddesc p i {
+ font-style: italic;
+ color: #999;
+}
+
+
+.jp-carousel-titleanddesc p a:hover,
+.jp-carousel-comments p a:hover,
+.jp-carousel-info h2 a:hover {
+ color: #68c9e8 !important;
+}
+
+.jp-carousel-titleanddesc p:empty {
+ display: none;
+}
+
+.jp-carousel-photo-info h1:before,
+.jp-carousel-photo-info h1:after,
+.jp-carousel-left-column-wrapper h1:before,
+.jp-carousel-left-column-wrapper h1:after {
+ content:none !important;
+}
+/** Title and Desc End **/
+
+/** Meta Box Start **/
+.jp-carousel-image-meta {
+ background: #111;
+ border: 1px solid #222;
+ color: #fff;
+ font-size: 13px;
+ font: 12px/1.4 "Helvetica Neue", sans-serif !important;
+ overflow: hidden;
+ padding: 18px 20px;
+ width: 209px !important;
+}
+
+.jp-carousel-image-meta li,
+.jp-carousel-image-meta h5 {
+ font-family: "Helvetica Neue", sans-serif !important;
+ position: inherit !important;
+ top: auto !important;
+ left: auto !important;
+ right: auto !important;
+ bottom: auto !important;
+ background: none !important;
+ border: none !important;
+ font-weight: 400 !important;
+ line-height: 1.3em !important;
+}
+
+.jp-carousel-image-meta ul {
+ margin: 0 !important;
+ padding: 0 !important;
+ list-style: none !important;
+}
+
+.jp-carousel-image-meta li {
+ width: 48% !important;
+ float: right !important;
+ margin: 0 0 15px 2% !important;
+ color: #fff !important;
+ font-size:13px !important;
+}
+
+.jp-carousel-image-meta h5 {
+ color: #999 !important;
+ text-transform: uppercase !important;
+ font-size:10px !important;
+ margin:0 0 2px !important;
+ letter-spacing: 0.1em !important;
+}
+
+a.jp-carousel-image-download {
+ padding-right: 23px;
+ display: inline-block;
+ clear: both;
+ color: #999;
+ line-height: 1;
+ font-weight: 400;
+ font-size: 13px;
+ text-decoration: none;
+ background-position: 0 -82px;
+}
+
+a.jp-carousel-image-download span.photo-size {
+ font-size: 11px;
+ border-radius: 1em;
+ margin-right: 2px;
+ display: inline-block;
+}
+
+a.jp-carousel-image-download span.photo-size-times {
+ padding: 0 2px 0 1px;
+}
+
+a.jp-carousel-image-download:hover {
+ background-position: 0 -102px;
+ color: #68c9e8;
+ border: none !important;
+}
+
+/** Meta Box End **/
+
+/** GPS Map Start **/
+.jp-carousel-image-map {
+ position: relative;
+ margin: -20px -20px 20px;
+ border-bottom: 1px solid rgba( 255, 255, 255, 0.17 );
+ height: 154px;
+}
+
+.jp-carousel-image-map img.gmap-main {
+ -moz-border-radius-topleft: 6px;
+ border-top-right-radius: 6px;
+ border-left: 1px solid rgba( 255, 255, 255, 0.17 );
+}
+.jp-carousel-image-map div.gmap-topright {
+ width: 94px;
+ height: 154px;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+.jp-carousel-image-map div.imgclip {
+ overflow: hidden;
+ -moz-border-radius-topright: 6px;
+ border-top-left-radius: 6px;
+}
+.jp-carousel-image-map div.gmap-topright img {
+ margin-right: -40px;
+}
+.jp-carousel-image-map img.gmap-bottomright {
+ position: absolute;
+ top: 96px;
+ left: 0;
+}
+
+/** Comments Start **/
+.jp-carousel-comments {
+ font: 15px/1.7 "Helvetica Neue", sans-serif !important;
+ font-weight: 400;
+ background:none transparent;
+}
+
+.jp-carousel-comments p a:hover, .jp-carousel-comments p a:focus, .jp-carousel-comments p a:active {
+ color: #68c9e8 !important;
+}
+
+.jp-carousel-comment {
+ background:none transparent;
+ color: #999;
+ margin-bottom: 20px;
+ clear:right;
+ overflow: auto;
+ width: 100%
+}
+
+.jp-carousel-comment p {
+ color: #999 !important;
+}
+
+.jp-carousel-comment .comment-author {
+ font-size: 13px;
+ font-weight:400;
+ padding:0;
+ width:auto;
+ display: inline;
+ float:none;
+ border:none;
+ margin:0;
+}
+
+.jp-carousel-comment .comment-author a {
+ color: #fff;
+}
+
+.jp-carousel-comment .comment-gravatar {
+ float:right;
+}
+
+.jp-carousel-comment .comment-content {
+ border:none;
+ margin-right:85px;
+ padding: 0;
+}
+
+.jp-carousel-comment .avatar {
+ margin:0 0 0 20px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ border: none !important;
+ padding: 0 !important;
+ background-color: transparent !important;
+}
+
+.jp-carousel-comment .comment-date {
+ color:#999;
+ margin-top: 4px;
+ font-size:11px;
+ display: inline;
+ float: left;
+ /*clear: right;*/
+}
+
+#jp-carousel-comment-form {
+ margin:0 0 10px !important;
+ float: right;
+ width: 100%;
+}
+
+textarea#jp-carousel-comment-form-comment-field {
+ background: rgba(34,34,34,0.9);
+ border: 1px solid #3a3a3a;
+ color: #aaa;
+ font: 15px/1.4 "Helvetica Neue", sans-serif !important;
+ width: 100%;
+ padding: 10px 10px 5px;
+ margin: 0;
+ float: none;
+ height: 147px;
+ -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ overflow: hidden;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+textarea#jp-carousel-comment-form-comment-field::-webkit-input-placeholder {
+ color: #555;
+}
+
+textarea#jp-carousel-comment-form-comment-field:focus {
+ background: #ccc;
+ color: #222;
+}
+
+textarea#jp-carousel-comment-form-comment-field:focus::-webkit-input-placeholder {
+ color: #aaa;
+}
+
+#jp-carousel-comment-form-spinner {
+ color: #fff;
+ margin:22px 10px 0 0;
+ display: block;
+ width: 20px;
+ height: 20px;
+ float: right;
+}
+
+#jp-carousel-comment-form-submit-and-info-wrapper {
+ display: none;
+ /*margin-bottom:15px;*/
+ overflow: hidden;
+ width: 100%
+}
+
+#jp-carousel-comment-form-commenting-as {
+}
+
+#jp-carousel-comment-form-commenting-as input {
+ background: rgba(34,34,34,0.9);
+ border: 1px solid #3a3a3a;
+ color: #aaa;
+ font: 13px/1.4 "Helvetica Neue", sans-serif !important;
+ padding: 3px 6px;
+ float: right;
+ -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+ width:285px;
+}
+
+#jp-carousel-comment-form-commenting-as input:focus {
+ background: #ccc;
+ color: #222;
+}
+
+#jp-carousel-comment-form-commenting-as p {
+ font: 400 13px/1.7 "Helvetica Neue", sans-serif !important;
+ margin:22px 0 0;
+ float: right;
+}
+
+#jp-carousel-comment-form-commenting-as fieldset {
+ float:right;
+ border:none;
+ margin:20px 0 0 0;
+ padding:0;
+}
+
+#jp-carousel-comment-form-commenting-as fieldset {
+ clear: both;
+}
+
+#jp-carousel-comment-form-commenting-as label {
+ font: 400 13px/1.7 "Helvetica Neue", sans-serif !important;
+ margin:0 0 3px 20px;
+ float:right;
+ width:100px;
+}
+
+#jp-carousel-comment-form-button-submit {
+ margin-top: 20px;
+ float:left;
+}
+
+#js-carousel-comment-form-container {
+ margin-bottom:15px;
+ overflow: auto;
+ width: 100%;
+}
+
+#jp-carousel-comment-form-container {
+ margin-bottom:15px;
+ overflow: auto;
+ width: 100%;
+}
+
+#jp-carousel-comment-post-results {
+ display: none;
+ overflow:auto;
+ width:100%;
+}
+
+#jp-carousel-comment-post-results span {
+ display:block;
+ text-align: center;
+ margin-top:20px;
+ width: 100%;
+ overflow: auto;
+ padding: 1em 0;
+ box-sizing: border-box;
+ background: rgba( 0, 0, 0, 0.7 );
+ border-radius: 2px;
+ font: 13px/1.4 "Helvetica Neue", sans-serif !important;
+ border: 1px solid rgba( 255, 255, 255, 0.17 );
+ -webkit-box-shadow: inset 0px 5px 5px 0px rgba(0, 0, 0, 1);
+ box-shadow: inset 0px 5px 5px 0px rgba(0, 0, 0, 1);
+}
+
+.jp-carousel-comment-post-error {
+ color:#DF4926;
+}
+
+.jp-carousel-comment-post-success {
+ /*color:#21759B;*/
+}
+
+#jp-carousel-comments-closed {
+ display: none;
+ color: #999;
+}
+
+#jp-carousel-comments-loading {
+ font: 444 15px/1.7 "Helvetica Neue", sans-serif !important;
+ display: none;
+ color: #999;
+ text-align: right;
+ margin-bottom: 20px;
+}
+
+
+/* ----- Light variant ----- */
+
+.jp-carousel-light .jp-carousel-overlay {
+ background: #fff;
+}
+
+.jp-carousel-light .jp-carousel-next-button:hover span,
+.jp-carousel-light .jp-carousel-previous-button:hover span {
+ opacity: 0.8;
+}
+
+.jp-carousel-light .jp-carousel-close-hint:hover,
+.jp-carousel-light .jp-carousel-titleanddesc div {
+ color: #000 !important;
+}
+
+.jp-carousel-light .jp-carousel-comments p a,
+.jp-carousel-light .jp-carousel-comment .comment-author a,
+.jp-carousel-light .jp-carousel-titleanddesc p a,
+.jp-carousel-light .jp-carousel-titleanddesc p a,
+.jp-carousel-light .jp-carousel-comments p a,
+.jp-carousel-light .jp-carousel-info h2 a {
+ color: #1e8cbe !important;
+}
+
+.jp-carousel-light .jp-carousel-comments p a:hover,
+.jp-carousel-light .jp-carousel-comment .comment-author a:hover,
+.jp-carousel-light .jp-carousel-titleanddesc p a:hover,
+.jp-carousel-light .jp-carousel-titleanddesc p a:hover,
+.jp-carousel-light .jp-carousel-comments p a:hover,
+.jp-carousel-light .jp-carousel-info h2 a:hover {
+ color: #f1831e !important;
+}
+
+.jp-carousel-light .jp-carousel-info h2,
+.jp-carousel-light .jp-carousel-titleanddesc,
+.jp-carousel-light .jp-carousel-titleanddesc p,
+.jp-carousel-light .jp-carousel-comment,
+.jp-carousel-light .jp-carousel-comment p,
+.jp-carousel-light div.jp-carousel-buttons a,
+.jp-carousel-light .jp-carousel-titleanddesc p strong,
+.jp-carousel-light .jp-carousel-titleanddesc p b,
+.jp-carousel-light .jp-carousel-titleanddesc p em,
+.jp-carousel-light .jp-carousel-titleanddesc p i {
+ color: #666;
+}
+
+.jp-carousel-light .jp-carousel-buttons {
+ border-bottom-color: #f0f0f0;
+ background: #f5f5f5;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a:hover {
+ text-decoration: none;
+ color: #f1831e;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog,
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog:hover {
+ background-position: 4px -56px;
+ padding-right: 24px !important;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog.reblogged,
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like.liked {
+ background-color: #2ea2cc;
+ color: #fff;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-commentlink {
+ background-position: 0px -136px;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like,
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like:hover {
+ background-position: 5px -15px;
+ padding-right: 23px !important;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog.reblogged {
+ background-position: 5px -36px;
+}
+
+.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like.liked {
+ background-position: 5px 5px;
+}
+
+.jp-carousel-light div#carousel-reblog-box {
+ background: #eee;
+ background: -moz-linear-gradient(bottom, #ececec, #f7f7f7);
+ background: -webkit-gradient(linear, right bottom, right top, from(#ececec), to(#f7f7f7));
+ -webkit-box-shadow: 0 2px 6px rgba(0,0,0,0.1);
+ -moz-box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ border:1px solid #ddd;
+}
+
+.jp-carousel-light #carousel-reblog-box textarea {
+ border: 1px inset #ccc;
+ color: #666;
+ border: 1px solid #cfcfcf;
+ background: #fff;
+}
+
+.jp-carousel-light #carousel-reblog-box .canceltext {
+ color: #888;
+}
+
+.jp-carousel-light #carousel-reblog-box .canceltext a {
+ color: #666;
+}
+
+.jp-carousel-light #carousel-reblog-box select {
+ background: #eee;
+ color: #333;
+ border: 1px solid #aaa;
+}
+
+.jp-carousel-light #carousel-reblog-box input#carousel-reblog-submit, #jp-carousel-comment-form-button-submit {
+ color: #333;
+ background: #fff;
+ background: -moz-linear-gradient(bottom, #ddd, #fff);
+ background: -webkit-gradient(linear, right bottom, right top, from(#ddd), to(#fff));
+ border: 1px solid #aaa;
+}
+
+.jp-carousel-light .jp-carousel-image-meta {
+ background: #fafafa;
+ border: 1px solid #eee;
+ border-top-color: #f5f5f5;
+ border-right-color: #f5f5f5;
+ color: #333;
+}
+
+.jp-carousel-light .jp-carousel-image-meta li {
+ color: #000 !important;
+}
+
+.jp-carousel-light .jp-carousel-close-hint {
+ color: #ccc;
+}
+
+.jp-carousel-light .jp-carousel-close-hint span {
+ background-color: white;
+ border-color: #ccc;
+}
+
+.jp-carousel-light #jp-carousel-comment-form-comment-field::-webkit-input-placeholder {
+ color: #aaa;
+}
+
+.jp-carousel-light #jp-carousel-comment-form-comment-field:focus {
+ color: #333;
+}
+
+.jp-carousel-light #jp-carousel-comment-form-comment-field:focus::-webkit-input-placeholder {
+ color: #ddd;
+}
+
+.jp-carousel-light a.jp-carousel-image-download {
+ background-position: 0 -102px;
+}
+
+.jp-carousel-light a.jp-carousel-image-download:hover {
+ background-position: 0 -102px;
+ color: #f1831e;
+}
+
+.jp-carousel-light textarea#jp-carousel-comment-form-comment-field {
+ background: #fbfbfb;
+ color: #333;
+ border: 1px solid #dfdfdf;
+ -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1);
+ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1);
+}
+
+.jp-carousel-light #jp-carousel-comment-form-commenting-as input {
+ background: #fbfbfb;
+ border: 1px solid #dfdfdf;
+ color: #333;
+ -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1);
+ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1);
+}
+
+.jp-carousel-light #jp-carousel-comment-form-commenting-as input:focus {
+ background: #fbfbfb;
+ color: #333;
+}
+
+.jp-carousel-light #jp-carousel-comment-post-results span {
+ background: #f7f7f7;
+ border:1px solid #dfdfdf;
+ -webkit-box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.05);
+}
+
+.jp-carousel-light .jp-carousel-slide {
+ background-color:#fff;
+}
+
+.jp-carousel-light .jp-carousel-titleanddesc {
+ border-top: 1px solid #eee;
+}
+
+.jp-carousel-light .jp-carousel-fadeaway {
+ background: -moz-linear-gradient(bottom, rgba(255,255,255,0.75), rgba(255,255,255,0));
+ background: -webkit-gradient(linear, right bottom, right top, from(rgba(255,255,255,0.75)), to(rgba(255,255,255,0)));
+}
+
+/* Small screens */
+@media only screen and (max-width: 760px) {
+
+ .jp-carousel-info {
+ margin: 0 10px !important;
+ }
+
+ .jp-carousel-next-button, .jp-carousel-previous-button {
+ display: none !important;
+ }
+
+ .jp-carousel-buttons {
+ display: none !important;
+ }
+
+ .jp-carousel-image-meta {
+ float: none !important;
+ width: 100% !important;
+ -moz-box-sizing:border-box;
+ -webkit-box-sizing:border-box;
+ box-sizing: border-box;
+ }
+
+ .jp-carousel-close-hint {
+ font-weight: 800 !important;
+ font-size: 26px !important;
+ position: fixed !important;
+ top: -10px;
+ }
+
+ .jp-carousel-slide img {
+ filter: alpha(opacity=100);
+ opacity: 1;
+ }
+
+ .jp-carousel-wrap {
+ background-color: #000;
+ }
+
+ .jp-carousel-fadeaway {
+ display: none;
+ }
+
+ #jp-carousel-comment-form-container {
+ display: none !important;
+ }
+
+ .jp-carousel-titleanddesc {
+ padding-top: 0 !important;
+ border: none !important;
+ }
+ .jp-carousel-titleanddesc-title {
+ font-size: 1em !important;
+ }
+
+ .jp-carousel-left-column-wrapper {
+ padding: 0;
+ }
+}
diff --git a/plugins/jetpack/modules/comments.php b/plugins/jetpack/modules/comments.php
index b30ccd73..d5d4da4d 100644
--- a/plugins/jetpack/modules/comments.php
+++ b/plugins/jetpack/modules/comments.php
@@ -13,6 +13,15 @@ if ( is_admin() ) {
require dirname( __FILE__ ) . '/comments/admin.php';
}
+Jetpack_Sync::sync_options( __FILE__,
+ 'comment_registration',
+ 'require_name_email',
+ 'show_avatars',
+ 'avatar_default',
+ 'highlander_comment_form_prompt',
+ 'jetpack_comment_form_color_scheme'
+);
+
function jetpack_comments_load() {
Jetpack::enable_module_configurable( __FILE__ );
Jetpack::module_configuration_load( __FILE__, 'jetpack_comments_configuration_load' );
diff --git a/plugins/jetpack/modules/comments/base.php b/plugins/jetpack/modules/comments/base.php
index 2835313f..f1b5440e 100644
--- a/plugins/jetpack/modules/comments/base.php
+++ b/plugins/jetpack/modules/comments/base.php
@@ -35,7 +35,6 @@ class Highlander_Comments_Base {
protected function setup_filters() {
add_filter( 'comments_array', array( $this, 'comments_array' ) );
add_filter( 'preprocess_comment', array( $this, 'allow_logged_in_user_to_comment_as_guest' ), 0 );
- add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 4 );
}
/**
@@ -273,56 +272,16 @@ class Highlander_Comments_Base {
}
/**
- * Get the comment avatar from Gravatar, Twitter, or Facebook
- *
- * @since JetpackComments (1.4)
- * @param string $avatar Current avatar URL
- * @param string $comment Comment for the avatar
- * @param int $size Size of the avatar
- * @param string $default Not used
- * @return string New avatar
- */
- public function get_avatar( $avatar, $comment, $size, $default ) {
- if ( ! isset( $comment->comment_post_ID ) || ! isset( $comment->comment_ID ) ) {
- // it's not a comment - bail
- return $avatar;
- }
-
- if ( false === strpos( $comment->comment_author_url, '/www.facebook.com/' ) && false === strpos( $comment->comment_author_url, '/twitter.com/' ) ) {
- // It's neither FB nor Twitter - bail
- return $avatar;
- }
-
- // It's a FB or Twitter avatar
- $foreign_avatar = get_comment_meta( $comment->comment_ID, 'hc_avatar', true );
- if ( empty( $foreign_avatar ) ) {
- // Can't find the avatar details - bail
- return $avatar;
- }
-
- // Return the FB or Twitter avatar
- return preg_replace( '#src=([\'"])[^\'"]+\\1#', 'src=\\1' . esc_url( $this->imgpress_avatar( $foreign_avatar, $size ) ) . '\\1', $avatar );
- }
-
- /**
- * Get an avatar from Imgpress
+ * Get an avatar from Photon
*
* @since JetpackComments (1.4)
* @param string $url
* @param int $size
* @return string
*/
- protected function imgpress_avatar( $url, $size ) {
+ protected function photon_avatar( $url, $size ) {
$size = (int) $size;
- $args = urlencode_deep( array(
- 'url' => $url,
- 'resize' => "$size,$size",
- ) );
-
- $url = apply_filters( 'jetpack_static_url', ( is_ssl() ? 'https://s-ssl.wordpress.com' : 'http://s.wordpress.com' ) . '/imgpress' );
- $url = add_query_arg( $args, $url );
-
- return $url;
+ return jetpack_photon_url( $url, array( 'resize' => "$size,$size" ) );
}
}
diff --git a/plugins/jetpack/modules/comments/comments.php b/plugins/jetpack/modules/comments/comments.php
index d33ad240..8dae39b7 100644
--- a/plugins/jetpack/modules/comments/comments.php
+++ b/plugins/jetpack/modules/comments/comments.php
@@ -115,6 +115,49 @@ class Jetpack_Comments extends Highlander_Comments_Base {
add_action( 'comment_post', array( $this, 'add_comment_meta' ) );
}
+ /**
+ * Setup filters for methods in this class
+ * @since 1.6.2
+ */
+ protected function setup_filters() {
+ parent::setup_filters();
+
+ add_filter( 'comment_post_redirect', array( $this, 'capture_comment_post_redirect_to_reload_parent_frame' ), 100 );
+ add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 4 );
+ }
+
+ /**
+ * Get the comment avatar from Gravatar, Twitter, or Facebook
+ *
+ * @since JetpackComments (1.4)
+ * @param string $avatar Current avatar URL
+ * @param string $comment Comment for the avatar
+ * @param int $size Size of the avatar
+ * @param string $default Not used
+ * @return string New avatar
+ */
+ public function get_avatar( $avatar, $comment, $size, $default ) {
+ if ( ! isset( $comment->comment_post_ID ) || ! isset( $comment->comment_ID ) ) {
+ // it's not a comment - bail
+ return $avatar;
+ }
+
+ if ( false === strpos( $comment->comment_author_url, '/www.facebook.com/' ) && false === strpos( $comment->comment_author_url, '/twitter.com/' ) ) {
+ // It's neither FB nor Twitter - bail
+ return $avatar;
+ }
+
+ // It's a FB or Twitter avatar
+ $foreign_avatar = get_comment_meta( $comment->comment_ID, 'hc_avatar', true );
+ if ( empty( $foreign_avatar ) ) {
+ // Can't find the avatar details - bail
+ return $avatar;
+ }
+
+ // Return the FB or Twitter avatar
+ return preg_replace( '#src=([\'"])[^\'"]+\\1#', 'src=\\1' . esc_url( $this->photon_avatar( $foreign_avatar, $size ) ) . '\\1', $avatar );
+ }
+
/** Output Methods ********************************************************/
/**
@@ -168,6 +211,7 @@ class Jetpack_Comments extends Highlander_Comments_Base {
'greeting' => get_option( 'highlander_comment_form_prompt', __( 'Leave a Reply', 'jetpack' ) ),
'color_scheme' => get_option( 'jetpack_comment_form_color_scheme', $this->default_color_scheme ),
'lang' => get_bloginfo( 'language' ),
+ 'jetpack_version' => JETPACK__VERSION,
);
// Extra parameters for logged in user
@@ -223,6 +267,14 @@ class Jetpack_Comments extends Highlander_Comments_Base {
$url_origin = ( is_ssl() ? 'https' : 'http' ) . '://jetpack.wordpress.com';
?>
+
+
+