summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheo Chatzimichos <tampakrap@gentoo.org>2013-03-10 12:10:26 +0100
committerTheo Chatzimichos <tampakrap@gentoo.org>2013-03-10 12:10:26 +0100
commit7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7 (patch)
tree68c852c654cef340592f1001b6310e33827b130c
parentMake the script more silent (diff)
downloadblogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.tar.gz
blogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.tar.bz2
blogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.zip
update plugins
-rw-r--r--plugins/akismet/.htaccess6
-rw-r--r--plugins/akismet/admin.php27
-rw-r--r--plugins/akismet/akismet.js12
-rw-r--r--plugins/akismet/akismet.php39
-rw-r--r--plugins/akismet/readme.txt13
-rw-r--r--plugins/jetpack/_inc/gallery-settings.js19
-rw-r--r--plugins/jetpack/_inc/images/module-icons-sprite-2x.pngbin37559 -> 72735 bytes
-rw-r--r--plugins/jetpack/_inc/images/module-icons-sprite.pngbin11465 -> 31542 bytes
-rw-r--r--plugins/jetpack/_inc/images/publicize.pngbin0 -> 113219 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/carousel.pngbin0 -> 361672 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/custom-css.pngbin0 -> 43048 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/likes.pngbin0 -> 48953 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpgbin0 -> 29830 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/mobile-theme.pngbin0 -> 37559 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/notes.pngbin0 -> 27450 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/post-by-email.pngbin0 -> 28349 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/publicize.pngbin0 -> 113219 bytes
-rw-r--r--plugins/jetpack/_inc/images/screenshots/tiled-gallery.pngbin0 -> 192182 bytes
-rw-r--r--plugins/jetpack/_inc/jetpack.css247
-rw-r--r--plugins/jetpack/_inc/jetpack.js58
-rw-r--r--plugins/jetpack/_inc/jquery.inview.js143
-rw-r--r--plugins/jetpack/_inc/jquery.jetpack-resize.js275
-rw-r--r--plugins/jetpack/_inc/jquery.spin.js86
-rw-r--r--plugins/jetpack/_inc/postmessage.js438
-rw-r--r--plugins/jetpack/_inc/spin.js301
-rw-r--r--plugins/jetpack/class.jetpack-ixr-client.php2
-rw-r--r--plugins/jetpack/class.jetpack-post-images.php440
-rw-r--r--plugins/jetpack/class.jetpack-signature.php24
-rw-r--r--plugins/jetpack/class.jetpack-user-agent.php1331
-rw-r--r--plugins/jetpack/class.jetpack-xmlrpc-server.php225
-rw-r--r--plugins/jetpack/class.json-api-endpoints.php3912
-rw-r--r--plugins/jetpack/class.json-api.php443
-rw-r--r--plugins/jetpack/class.photon.php554
-rw-r--r--plugins/jetpack/functions.compat.php15
-rw-r--r--plugins/jetpack/functions.gallery.php50
-rw-r--r--plugins/jetpack/functions.opengraph.php158
-rw-r--r--plugins/jetpack/functions.photon.php160
-rw-r--r--plugins/jetpack/jetpack.php2107
-rw-r--r--plugins/jetpack/languages/jetpack-ar.mobin0 -> 14004 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-az.mobin2398 -> 2886 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-bs_BA.mobin59865 -> 76528 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-ca.mobin52959 -> 76602 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-cs_CZ.mobin5260 -> 8242 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-da_DK.mobin48806 -> 44830 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-de_DE.mobin43096 -> 117625 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-el.mobin0 -> 8337 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-es_ES.mobin45191 -> 49121 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-fa_IR.mobin32814 -> 47862 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-fi.mobin21934 -> 27526 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-fr_FR.mobin69552 -> 113684 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-gl_ES.mobin30784 -> 33955 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-he_IL.mobin62301 -> 75590 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-hr.mobin31130 -> 35895 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-hu_HU.mobin30043 -> 77710 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-id_ID.mobin49894 -> 52347 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-it_IT.mobin33628 -> 50000 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-ja.mobin67264 -> 116549 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-ko_KR.mobin0 -> 110462 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-lt_LT.mobin0 -> 12440 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-mk_MK.mobin22392 -> 23594 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-my_MM.mobin5741 -> 11071 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-nb_NO.mobin64081 -> 88203 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-nl_NL.mobin32682 -> 38043 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-nn_NO.mobin9040 -> 14701 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-pl_PL.mobin5948 -> 11997 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-pt_BR.mobin62629 -> 107473 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-pt_PT.mobin40066 -> 46165 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-ro_RO.mobin6675 -> 10652 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-ru_RU.mobin41905 -> 49300 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-sa_IN.mobin1008 -> 1006 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-sk_SK.mobin7567 -> 10517 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-sq.mobin56036 -> 112583 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-sr_RS.mobin36803 -> 44079 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-sv_SE.mobin19356 -> 24534 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-th.mobin0 -> 18421 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-tr_TR.mobin23475 -> 35311 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-zh_CN.mobin0 -> 7637 bytes
-rw-r--r--plugins/jetpack/languages/jetpack-zh_TW.mobin0 -> 93742 bytes
-rw-r--r--plugins/jetpack/locales.php139
-rw-r--r--plugins/jetpack/modules/after-the-deadline.php2
-rw-r--r--plugins/jetpack/modules/after-the-deadline/config-options.php4
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel-ie8fix.css8
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.css89
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.js344
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.php85
-rw-r--r--plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css1106
-rw-r--r--plugins/jetpack/modules/comments.php9
-rw-r--r--plugins/jetpack/modules/comments/base.php47
-rw-r--r--plugins/jetpack/modules/comments/comments.php134
-rw-r--r--plugins/jetpack/modules/contact-form/admin.php245
-rw-r--r--plugins/jetpack/modules/contact-form/css/grunion.css7
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-contact-form.php1866
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-menu-2x.pngbin541 -> 53439 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-menu-hover-2x.pngbin697 -> 51427 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.pngbin201 -> 47832 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.pngbin207 -> 47745 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/js/grunion.js60
-rw-r--r--plugins/jetpack/modules/custom-css.php26
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php1241
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php46
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php936
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php408
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparse.css118
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparsed.css29
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php75
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/data.inc.php661
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/lang.inc.php311
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl10
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css.php1468
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/blank.css1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/ace/ace.js11
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/ace/mode-css.js1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/ace/readme.txt1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/ace/theme-textmate.js1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/ace/worker-css.js1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/safecss-ace.js69
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors.php57
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php3359
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php3759
-rw-r--r--plugins/jetpack/modules/enhanced-distribution.php23
-rw-r--r--plugins/jetpack/modules/featured-content/featured-content.php454
-rw-r--r--plugins/jetpack/modules/gravatar-hovercards.php17
-rw-r--r--plugins/jetpack/modules/holiday-snow.php72
-rw-r--r--plugins/jetpack/modules/holiday-snow/snowstorm.js539
-rw-r--r--plugins/jetpack/modules/infinite-scroll.php196
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.css137
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.js490
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.php945
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css45
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.php27
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyten.css25
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyten.php48
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.css33
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.php46
-rw-r--r--plugins/jetpack/modules/json-api.php19
-rw-r--r--plugins/jetpack/modules/latex.php6
-rw-r--r--plugins/jetpack/modules/likes.php970
-rw-r--r--plugins/jetpack/modules/likes/style.css189
-rw-r--r--plugins/jetpack/modules/minileven.php108
-rw-r--r--plugins/jetpack/modules/minileven/images/wp-app-devices.pngbin0 -> 1865 bytes
-rw-r--r--plugins/jetpack/modules/minileven/minileven.php313
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/comments.php52
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/content-gallery.php78
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/content.php60
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php36
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php152
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/header.php49
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/image.php98
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/inc/custom-header.php101
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/inc/template-tags.php99
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/inc/tweaks.php94
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/index.php73
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/js/small-menu.js41
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/page.php42
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/rtl.css582
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/screenshot.pngbin0 -> 59059 bytes
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/searchform.php12
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/sidebar.php12
-rw-r--r--plugins/jetpack/modules/minileven/theme/pub/minileven/style.css1437
-rw-r--r--plugins/jetpack/modules/mobile-push.php11
-rw-r--r--plugins/jetpack/modules/module-extras.php58
-rw-r--r--plugins/jetpack/modules/module-info.php347
-rw-r--r--plugins/jetpack/modules/notes.php195
-rw-r--r--plugins/jetpack/modules/photon.php9
-rw-r--r--plugins/jetpack/modules/photon/photon.js42
-rw-r--r--plugins/jetpack/modules/post-by-email.php240
-rw-r--r--plugins/jetpack/modules/post-by-email/post-by-email.css6
-rw-r--r--plugins/jetpack/modules/post-by-email/post-by-email.js129
-rw-r--r--plugins/jetpack/modules/publicize.php283
-rw-r--r--plugins/jetpack/modules/publicize/assets/connected.gifbin0 -> 1681 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/facebook-logo.pngbin0 -> 37624 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/linkedin-logo.pngbin0 -> 6882 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/publicize.css175
-rw-r--r--plugins/jetpack/modules/publicize/assets/publicize.js108
-rw-r--r--plugins/jetpack/modules/publicize/assets/rtl/publicize-rtl.css177
-rw-r--r--plugins/jetpack/modules/publicize/assets/spinner.gifbin0 -> 457 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/tumblr-logo.pngbin0 -> 9001 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/twitter-logo.pngbin0 -> 4623 bytes
-rw-r--r--plugins/jetpack/modules/publicize/assets/yahoo-logo.pngbin0 -> 9675 bytes
-rw-r--r--plugins/jetpack/modules/publicize/publicize-jetpack.php578
-rw-r--r--plugins/jetpack/modules/publicize/publicize.php328
-rw-r--r--plugins/jetpack/modules/publicize/ui.php545
-rw-r--r--plugins/jetpack/modules/sharedaddy.php2
-rw-r--r--plugins/jetpack/modules/sharedaddy/admin-sharing.css140
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-facebook-2x.pngbin0 -> 671 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-facebook.pngbin0 -> 887 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-twitter-2x.pngbin0 -> 1451 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-twitter.pngbin0 -> 523 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-wordpress-2x.pngbin0 -> 657 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/images/icon-wordpress.pngbin0 -> 775 bytes
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharedaddy.php57
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing-service.php217
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing-sources.php566
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.css142
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.js123
-rw-r--r--plugins/jetpack/modules/sharedaddy/sharing.php128
-rw-r--r--plugins/jetpack/modules/shortcodes/audio.php45
-rw-r--r--plugins/jetpack/modules/shortcodes/css/rtl/slideshow-shortcode-rtl.css131
-rw-r--r--plugins/jetpack/modules/shortcodes/css/slideshow-shortcode.css129
-rw-r--r--plugins/jetpack/modules/shortcodes/googlemaps.php2
-rw-r--r--plugins/jetpack/modules/shortcodes/img/slideshow-controls-2x.pngbin0 -> 1944 bytes
-rw-r--r--plugins/jetpack/modules/shortcodes/img/slideshow-controls.pngbin0 -> 1009 bytes
-rw-r--r--plugins/jetpack/modules/shortcodes/img/slideshow-loader.gifbin0 -> 13545 bytes
-rw-r--r--plugins/jetpack/modules/shortcodes/js/audio-shortcode.js154
-rw-r--r--plugins/jetpack/modules/shortcodes/js/jquery.cycle.js1551
-rw-r--r--plugins/jetpack/modules/shortcodes/js/slideshow-shortcode.js187
-rw-r--r--plugins/jetpack/modules/shortcodes/polldaddy.php6
-rw-r--r--plugins/jetpack/modules/shortcodes/slideshow.php208
-rw-r--r--plugins/jetpack/modules/shortcodes/soundcloud.php246
-rw-r--r--plugins/jetpack/modules/shortcodes/ted.php68
-rw-r--r--plugins/jetpack/modules/shortcodes/videopress.php51
-rw-r--r--plugins/jetpack/modules/shortcodes/vimeo.php20
-rw-r--r--plugins/jetpack/modules/shortcodes/youtube.php10
-rw-r--r--plugins/jetpack/modules/stats.php172
-rw-r--r--plugins/jetpack/modules/subscriptions.php289
-rw-r--r--plugins/jetpack/modules/tiled-gallery.php25
-rw-r--r--plugins/jetpack/modules/tiled-gallery/math/class-constrained-array-rounding.php75
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery.php588
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css88
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css85
-rw-r--r--plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.js150
-rw-r--r--plugins/jetpack/modules/widgets.php21
-rw-r--r--plugins/jetpack/modules/widgets/facebook-likebox.php57
-rw-r--r--plugins/jetpack/modules/widgets/gravatar-profile.css15
-rw-r--r--plugins/jetpack/modules/widgets/gravatar-profile.php128
-rw-r--r--plugins/jetpack/modules/widgets/image-widget.php2
-rw-r--r--plugins/jetpack/modules/widgets/readmill.php138
-rw-r--r--plugins/jetpack/modules/widgets/top-posts.php295
-rw-r--r--plugins/jetpack/modules/widgets/twitter.php407
-rw-r--r--plugins/jetpack/modules/widgets/widget-grid-and-list.css110
-rw-r--r--plugins/jetpack/modules/widgets/widgets.css13
-rw-r--r--plugins/jetpack/readme.txt251
-rw-r--r--plugins/openid/admin_panels.php3
-rw-r--r--plugins/openid/common.php8
-rw-r--r--plugins/openid/consumer.php6
-rw-r--r--plugins/openid/lib/Auth/OpenID.php563
-rw-r--r--plugins/openid/lib/Auth/OpenID/AX.php1022
-rw-r--r--plugins/openid/lib/Auth/OpenID/Association.php610
-rw-r--r--plugins/openid/lib/Auth/OpenID/BigMath.php451
-rw-r--r--plugins/openid/lib/Auth/OpenID/Consumer.php2236
-rw-r--r--plugins/openid/lib/Auth/OpenID/CryptUtil.php122
-rw-r--r--plugins/openid/lib/Auth/OpenID/DatabaseConnection.php130
-rw-r--r--plugins/openid/lib/Auth/OpenID/DiffieHellman.php113
-rw-r--r--plugins/openid/lib/Auth/OpenID/Discover.php606
-rw-r--r--plugins/openid/lib/Auth/OpenID/DumbStore.php99
-rw-r--r--plugins/openid/lib/Auth/OpenID/Extension.php61
-rw-r--r--plugins/openid/lib/Auth/OpenID/FileStore.php618
-rw-r--r--plugins/openid/lib/Auth/OpenID/HMAC.php105
-rw-r--r--plugins/openid/lib/Auth/OpenID/Interface.php196
-rw-r--r--plugins/openid/lib/Auth/OpenID/KVForm.php111
-rw-r--r--plugins/openid/lib/Auth/OpenID/MDB2Store.php413
-rw-r--r--plugins/openid/lib/Auth/OpenID/MemcachedStore.php207
-rw-r--r--plugins/openid/lib/Auth/OpenID/Message.php920
-rw-r--r--plugins/openid/lib/Auth/OpenID/MySQLStore.php77
-rw-r--r--plugins/openid/lib/Auth/OpenID/Nonce.php108
-rw-r--r--plugins/openid/lib/Auth/OpenID/PAPE.php300
-rw-r--r--plugins/openid/lib/Auth/OpenID/Parse.php381
-rw-r--r--plugins/openid/lib/Auth/OpenID/PostgreSQLStore.php112
-rw-r--r--plugins/openid/lib/Auth/OpenID/SQLStore.php557
-rw-r--r--plugins/openid/lib/Auth/OpenID/SQLiteStore.php70
-rw-r--r--plugins/openid/lib/Auth/OpenID/SReg.php521
-rw-r--r--plugins/openid/lib/Auth/OpenID/Server.php1765
-rw-r--r--plugins/openid/lib/Auth/OpenID/ServerRequest.php36
-rw-r--r--plugins/openid/lib/Auth/OpenID/TrustRoot.php461
-rw-r--r--plugins/openid/lib/Auth/OpenID/URINorm.php249
-rw-r--r--plugins/openid/lib/Auth/Yadis/HTTPFetcher.php174
-rw-r--r--plugins/openid/lib/Auth/Yadis/Manager.php523
-rw-r--r--plugins/openid/lib/Auth/Yadis/Misc.php58
-rw-r--r--plugins/openid/lib/Auth/Yadis/ParanoidHTTPFetcher.php273
-rw-r--r--plugins/openid/lib/Auth/Yadis/ParseHTML.php258
-rw-r--r--plugins/openid/lib/Auth/Yadis/PlainHTTPFetcher.php248
-rw-r--r--plugins/openid/lib/Auth/Yadis/XML.php352
-rw-r--r--plugins/openid/lib/Auth/Yadis/XRDS.php478
-rw-r--r--plugins/openid/lib/Auth/Yadis/XRI.php234
-rw-r--r--plugins/openid/lib/Auth/Yadis/XRIRes.php72
-rw-r--r--plugins/openid/lib/Auth/Yadis/Yadis.php382
-rw-r--r--plugins/openid/openid.php25
-rw-r--r--plugins/openid/readme.txt9
-rw-r--r--plugins/openid/server.php21
-rw-r--r--plugins/wp-syntax/README.txt89
-rw-r--r--plugins/wp-syntax/css/wp-syntax.css98
-rw-r--r--plugins/wp-syntax/geshi/geshi.php9534
-rw-r--r--plugins/wp-syntax/geshi/geshi/4cs.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/6502acme.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/6502kickass.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/6502tasm.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/68000devpac.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/abap.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/actionscript.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/actionscript3.php6
-rw-r--r--plugins/wp-syntax/geshi/geshi/ada.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/algol68.php287
-rw-r--r--plugins/wp-syntax/geshi/geshi/apache.php5
-rw-r--r--plugins/wp-syntax/geshi/geshi/applescript.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/apt_sources.php12
-rw-r--r--plugins/wp-syntax/geshi/geshi/arm.php3318
-rw-r--r--plugins/wp-syntax/geshi/geshi/asm.php536
-rw-r--r--plugins/wp-syntax/geshi/geshi/asp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/asymptote.php194
-rw-r--r--plugins/wp-syntax/geshi/geshi/autoconf.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/autohotkey.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/autoit.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/avisynth.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/awk.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/bascomavr.php185
-rw-r--r--plugins/wp-syntax/geshi/geshi/bash.php141
-rw-r--r--plugins/wp-syntax/geshi/geshi/basic4gl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/bf.php15
-rw-r--r--plugins/wp-syntax/geshi/geshi/bibtex.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/blitzbasic.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/bnf.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/boo.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/c.php85
-rw-r--r--plugins/wp-syntax/geshi/geshi/c_loadrunner.php323
-rw-r--r--plugins/wp-syntax/geshi/geshi/c_mac.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/caddcl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/cadlisp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/cfdg.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/cfm.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/chaiscript.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/cil.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/clojure.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/cmake.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/cobol.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/coffeescript.php146
-rw-r--r--plugins/wp-syntax/geshi/geshi/cpp-qt.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/cpp.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/csharp.php19
-rw-r--r--plugins/wp-syntax/geshi/geshi/css.php22
-rw-r--r--plugins/wp-syntax/geshi/geshi/cuesheet.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/d.php68
-rw-r--r--plugins/wp-syntax/geshi/geshi/dcl.php192
-rw-r--r--plugins/wp-syntax/geshi/geshi/dcpu16.php131
-rw-r--r--plugins/wp-syntax/geshi/geshi/dcs.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/delphi.php46
-rw-r--r--plugins/wp-syntax/geshi/geshi/diff.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/div.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/dos.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/dot.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/e.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/ecmascript.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/eiffel.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/email.php26
-rw-r--r--plugins/wp-syntax/geshi/geshi/epc.php154
-rw-r--r--plugins/wp-syntax/geshi/geshi/erlang.php10
-rw-r--r--plugins/wp-syntax/geshi/geshi/euphoria.php140
-rw-r--r--plugins/wp-syntax/geshi/geshi/f1.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/falcon.php218
-rw-r--r--plugins/wp-syntax/geshi/geshi/fo.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/fortran.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/freebasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/freeswitch.php168
-rw-r--r--plugins/wp-syntax/geshi/geshi/fsharp.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/gambas.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/gdb.php63
-rw-r--r--plugins/wp-syntax/geshi/geshi/genero.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/genie.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/gettext.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/glsl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/gml.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/gnuplot.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/go.php47
-rw-r--r--plugins/wp-syntax/geshi/geshi/groovy.php18
-rw-r--r--plugins/wp-syntax/geshi/geshi/gwbasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/haskell.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/haxe.php161
-rw-r--r--plugins/wp-syntax/geshi/geshi/hicest.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/hq9plus.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/html4strict.php29
-rw-r--r--plugins/wp-syntax/geshi/geshi/html5.php212
-rw-r--r--plugins/wp-syntax/geshi/geshi/icon.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/idl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/ini.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/inno.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/intercal.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/io.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/j.php73
-rw-r--r--plugins/wp-syntax/geshi/geshi/java.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/java5.php328
-rw-r--r--plugins/wp-syntax/geshi/geshi/javascript.php70
-rw-r--r--plugins/wp-syntax/geshi/geshi/jquery.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/kixtart.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/klonec.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/klonecpp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/latex.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/lb.php16
-rw-r--r--plugins/wp-syntax/geshi/geshi/ldif.php116
-rw-r--r--plugins/wp-syntax/geshi/geshi/lisp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/llvm.php385
-rw-r--r--plugins/wp-syntax/geshi/geshi/locobasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/logtalk.php35
-rw-r--r--plugins/wp-syntax/geshi/geshi/lolcode.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/lotusformulas.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/lotusscript.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/lscript.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/lsl2.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/lua.php62
-rw-r--r--plugins/wp-syntax/geshi/geshi/m68k.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/magiksf.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/make.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/mapbasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/matlab.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/mirc.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/mmix.php66
-rw-r--r--plugins/wp-syntax/geshi/geshi/modula2.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/modula3.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/mpasm.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/mxml.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/mysql.php34
-rw-r--r--plugins/wp-syntax/geshi/geshi/nagios.php225
-rw-r--r--plugins/wp-syntax/geshi/geshi/netrexx.php163
-rw-r--r--plugins/wp-syntax/geshi/geshi/newlisp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/nsis.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/oberon2.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/objc.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/objeck.php10
-rw-r--r--plugins/wp-syntax/geshi/geshi/ocaml-brief.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/ocaml.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/octave.php515
-rw-r--r--plugins/wp-syntax/geshi/geshi/oobas.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/oorexx.php171
-rw-r--r--plugins/wp-syntax/geshi/geshi/oracle11.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/oracle8.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/oxygene.php6
-rw-r--r--plugins/wp-syntax/geshi/geshi/oz.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/parasail.php133
-rw-r--r--plugins/wp-syntax/geshi/geshi/parigp.php277
-rw-r--r--plugins/wp-syntax/geshi/geshi/pascal.php53
-rw-r--r--plugins/wp-syntax/geshi/geshi/pcre.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/per.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/perl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/perl6.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/pf.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/php-brief.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/php.php21
-rw-r--r--plugins/wp-syntax/geshi/geshi/pic16.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/pike.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/pixelbender.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/pli.php200
-rw-r--r--plugins/wp-syntax/geshi/geshi/plsql.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/postgresql.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/povray.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/powerbuilder.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/powershell.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/proftpd.php374
-rw-r--r--plugins/wp-syntax/geshi/geshi/progress.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/prolog.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/properties.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/providex.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/purebasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/pycon.php64
-rw-r--r--plugins/wp-syntax/geshi/geshi/pys60.php273
-rw-r--r--plugins/wp-syntax/geshi/geshi/python.php17
-rw-r--r--plugins/wp-syntax/geshi/geshi/q.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/qbasic.php6
-rw-r--r--plugins/wp-syntax/geshi/geshi/rails.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/rebol.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/reg.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/rexx.php162
-rw-r--r--plugins/wp-syntax/geshi/geshi/robots.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/rpmspec.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/rsplus.php20
-rw-r--r--plugins/wp-syntax/geshi/geshi/ruby.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/sas.php6
-rw-r--r--plugins/wp-syntax/geshi/geshi/scala.php26
-rw-r--r--plugins/wp-syntax/geshi/geshi/scheme.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/scilab.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/sdlbasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/smalltalk.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/smarty.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/spark.php132
-rw-r--r--plugins/wp-syntax/geshi/geshi/sparql.php155
-rw-r--r--plugins/wp-syntax/geshi/geshi/sql.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/stonescript.php307
-rw-r--r--plugins/wp-syntax/geshi/geshi/systemverilog.php8
-rw-r--r--plugins/wp-syntax/geshi/geshi/tcl.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/teraterm.php147
-rw-r--r--plugins/wp-syntax/geshi/geshi/text.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/thinbasic.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/tsql.php6
-rw-r--r--plugins/wp-syntax/geshi/geshi/typoscript.php20
-rw-r--r--plugins/wp-syntax/geshi/geshi/unicon.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/upc.php270
-rw-r--r--plugins/wp-syntax/geshi/geshi/urbi.php200
-rw-r--r--plugins/wp-syntax/geshi/geshi/uscript.php299
-rw-r--r--plugins/wp-syntax/geshi/geshi/vala.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/vb.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/vbnet.php127
-rw-r--r--plugins/wp-syntax/geshi/geshi/vedit.php103
-rw-r--r--plugins/wp-syntax/geshi/geshi/verilog.php4
-rw-r--r--plugins/wp-syntax/geshi/geshi/vhdl.php105
-rw-r--r--plugins/wp-syntax/geshi/geshi/vim.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/visualfoxpro.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/visualprolog.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/whitespace.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/whois.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/winbatch.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/xbasic.php3
-rw-r--r--plugins/wp-syntax/geshi/geshi/xml.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/xorg_conf.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/xpp.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/yaml.php150
-rw-r--r--plugins/wp-syntax/geshi/geshi/z80.php2
-rw-r--r--plugins/wp-syntax/geshi/geshi/zxbasic.php2
-rw-r--r--plugins/wp-syntax/js/wp-syntax.js34
-rw-r--r--plugins/wp-syntax/wp-syntax.php563
506 files changed, 76912 insertions, 8864 deletions
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
+
+<FilesMatch "^akismet\.(css|js)$">
+ Allow from all
+</FilesMatch> \ 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[] = '<a href="admin.php?page=akismet-key-config">'.__('Settings').'</a>';
+ $links[] = '<a href="' . admin_url( 'admin.php?page=akismet-key-config' ) . '">'.__( 'Settings' ).'</a>';
}
return $links;
@@ -240,23 +239,6 @@ function akismet_conf() {
<?php
}
-function akismet_stats_script() {
- ?>
-<script type="text/javascript">
-function resizeIframe() {
-
- document.getElementById('akismet-stats-frame').style.height = "2500px";
-
-};
-function resizeIframeInit() {
- document.getElementById('akismet-stats-frame').onload = resizeIframe;
- window.onresize = resizeIframe;
-}
-addLoadEvent(resizeIframeInit);
-</script><?php
-}
-
-
function akismet_stats_display() {
global $akismet_api_host, $akismet_api_port, $wpcom_api_key;
$blog = urlencode( get_bloginfo('url') );
@@ -269,7 +251,7 @@ function akismet_stats_display() {
$url .= "?blog={$blog}&api_key=" . akismet_get_key();
?>
<div class="wrap">
- <iframe src="<?php echo $url; ?>" width="100%" height="100%" frameborder="0" id="akismet-stats-frame"></iframe>
+ <iframe src="<?php echo $url; ?>" width="100%" height="2500px" frameborder="0" id="akismet-stats-frame"></iframe>
</div>
<?php
}
@@ -329,7 +311,7 @@ function akismet_admin_warnings() {
function akismet_warning() {
global $wpdb;
akismet_fix_scheduled_recheck();
- $waiting = $wpdb->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('<div class="mShot mshot-container" style="left: '+thisParentWidth+'"><div class="mshot-arrow"></div><img src="http://s.wordpress.com/mshots/v1/'+thisHref+'?w=450" width="450" class="mshot-image_'+thisId+'" style="margin: 0;" /></div>');
+ jQuery(this).append('<div class="mShot mshot-container" style="left: '+thisParentWidth+'"><div class="mshot-arrow"></div><img src="'+wpcomProtocol+'s0.wordpress.com/mshots/v1/'+thisHref+'?w=450" width="450" class="mshot-image_'+thisId+'" style="margin: 0;" /></div>');
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 <strong>protect your blog from comment and trackback spam</strong>. 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) <a href="http://akismet.com/get/?return=true">Sign up for an Akismet API key</a>, and 3) Go to your <a href="admin.php?page=akismet-key-config">Akismet configuration</a> 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 <strong>protect your blog from comment and trackback spam</strong>. 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) <a href="http://akismet.com/get/?return=true">Sign up for an Akismet API key</a>, 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
--- a/plugins/jetpack/_inc/images/module-icons-sprite-2x.png
+++ b/plugins/jetpack/_inc/images/module-icons-sprite-2x.png
Binary files 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
--- a/plugins/jetpack/_inc/images/module-icons-sprite.png
+++ b/plugins/jetpack/_inc/images/module-icons-sprite.png
Binary files differ
diff --git a/plugins/jetpack/_inc/images/publicize.png b/plugins/jetpack/_inc/images/publicize.png
new file mode 100644
index 00000000..428b886c
--- /dev/null
+++ b/plugins/jetpack/_inc/images/publicize.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/carousel.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/custom-css.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/likes.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/mobile-theme.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/notes.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/post-by-email.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/publicize.png
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png
Binary files 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( '<div id="message" class="more-info jetpack-message"><div class="arrow"></div><div class="jp-content"></div><div class="jp-close">&times;</div><div class="clear"></div></div>' );
// 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<elementsLength; i++) {
+ // Ignore elements that are not in the DOM tree
+ if (!$.contains(documentElement, $elements[i])) {
+ continue;
+ }
+
+ var element = $elements[i],
+ $element = $(element),
+ elementSize = {},
+ elementOffset = {},
+ inView = $element.data('inview'),
+ visiblePartX,
+ visiblePartY,
+ visiblePartsMerged;
+
+ // for the case where 'display:none' is used in place of 'visibility:hidden'
+ // count and sum the above items to get and move closer to the correct values
+ // IMPORTANT :: insert element into container empty
+ if($element.css('display') == 'none')
+ {
+ var parentElement = $element.parent();
+
+ elementOffset.top = parentElement.offset().top;
+ elementOffset.left = parentElement.offset().left;
+ elementSize.height = parentElement.height();
+ elementSize.width = parentElement.width();
+ } else {
+ elementSize = { height: $element.height(), width: $element.width() }
+ elementOffset = $element.offset();
+ }
+
+ // Don't ask me why because I haven't figured out yet:
+ // viewportOffset and viewportSize are sometimes suddenly null in Firefox 5.
+ // Even though it sounds weird:
+ // It seems that the execution of this function is interferred by the onresize/onscroll event
+ // where viewportOffset and viewportSize are unset
+ if (!viewportOffset || !viewportSize) {
+ return;
+ }
+
+ if (element.offsetWidth >= 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<len; i++) {
+ var b = arguments[i];
+ for (var prop in b) {
+ a[prop] = b[prop];
+ }
+ }
+ return a;
+ }
+ };
+ }
+
+ $.fn.pm = function() {
+ console.log("usage: \nto send: $.pm(options)\nto receive: $.pm.bind(type, fn, [origin])");
+ return this;
+ };
+
+ // send postmessage
+ $.pm = window.pm = function(options) {
+ pm.send(options);
+ };
+
+ // bind postmessage handler
+ $.pm.bind = window.pm.bind = function(type, fn, origin, hash, async_reply) {
+ pm.bind(type, fn, origin, hash, async_reply === true);
+ };
+
+ // unbind postmessage handler
+ $.pm.unbind = window.pm.unbind = function(type, fn) {
+ pm.unbind(type, fn);
+ };
+
+ // default postmessage origin on bind
+ $.pm.origin = window.pm.origin = null;
+
+ // default postmessage polling if using location hash to pass postmessages
+ $.pm.poll = window.pm.poll = 200;
+
+ var pm = {
+
+ send: function(options) {
+ var o = $.extend({}, pm.defaults, options),
+ target = o.target;
+ if (!o.target) {
+ console.warn("postmessage target window required");
+ return;
+ }
+ if (!o.type) {
+ console.warn("postmessage type required");
+ return;
+ }
+ var msg = {data:o.data, type:o.type};
+ if (o.success) {
+ msg.callback = pm._callback(o.success);
+ }
+ if (o.error) {
+ msg.errback = pm._callback(o.error);
+ }
+ if (("postMessage" in target) && !o.hash) {
+ pm._bind();
+ target.postMessage(JSON.stringify(msg), o.origin || '*');
+ }
+ else {
+ pm.hash._bind();
+ pm.hash.send(o, msg);
+ }
+ },
+
+ bind: function(type, fn, origin, hash, async_reply) {
+ pm._replyBind ( type, fn, origin, hash, async_reply );
+ },
+
+ _replyBind: function(type, fn, origin, hash, isCallback) {
+ if (("postMessage" in window) && !hash) {
+ pm._bind();
+ }
+ else {
+ pm.hash._bind();
+ }
+ var l = pm.data("listeners.postmessage");
+ if (!l) {
+ l = {};
+ pm.data("listeners.postmessage", l);
+ }
+ var fns = l[type];
+ if (!fns) {
+ fns = [];
+ l[type] = fns;
+ }
+ fns.push({fn:fn, callback: isCallback, origin:origin || $.pm.origin});
+ },
+
+ unbind: function(type, fn) {
+ var l = pm.data("listeners.postmessage");
+ if (l) {
+ if (type) {
+ if (fn) {
+ // remove specific listener
+ var fns = l[type];
+ if (fns) {
+ var m = [];
+ for (var i=0,len=fns.length; i<len; i++) {
+ var o = fns[i];
+ if (o.fn !== fn) {
+ m.push(o);
+ }
+ }
+ l[type] = m;
+ }
+ }
+ else {
+ // remove all listeners by type
+ delete l[type];
+ }
+ }
+ else {
+ // unbind all listeners of all type
+ for (var i in l) {
+ delete l[i];
+ }
+ }
+ }
+ },
+
+ data: function(k, v) {
+ if (v === undefined) {
+ return pm._data[k];
+ }
+ pm._data[k] = v;
+ return v;
+ },
+
+ _data: {},
+
+ _CHARS: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''),
+
+ _random: function() {
+ var r = [];
+ for (var i=0; i<32; i++) {
+ r[i] = pm._CHARS[0 | Math.random() * 32];
+ };
+ return r.join("");
+ },
+
+ _callback: function(fn) {
+ var cbs = pm.data("callbacks.postmessage");
+ if (!cbs) {
+ cbs = {};
+ pm.data("callbacks.postmessage", cbs);
+ }
+ var r = pm._random();
+ cbs[r] = fn;
+ return r;
+ },
+
+ _bind: function() {
+ // are we already listening to message events on this w?
+ if (!pm.data("listening.postmessage")) {
+ if (window.addEventListener) {
+ window.addEventListener("message", pm._dispatch, false);
+ }
+ else if (window.attachEvent) {
+ window.attachEvent("onmessage", pm._dispatch);
+ }
+ pm.data("listening.postmessage", 1);
+ }
+ },
+
+ _dispatch: function(e) {
+ //console.log("$.pm.dispatch", e, this);
+ try {
+ var msg = JSON.parse(e.data);
+ }
+ catch (ex) {
+ //console.warn("postmessage data invalid json: ", ex); //message wasn't meant for pm
+ return;
+ }
+ if (!msg.type) {
+ //console.warn("postmessage message type required"); //message wasn't meant for pm
+ return;
+ }
+ var cbs = pm.data("callbacks.postmessage") || {},
+ cb = cbs[msg.type];
+ if (cb) {
+ cb(msg.data);
+ }
+ else {
+ var l = pm.data("listeners.postmessage") || {};
+ var fns = l[msg.type] || [];
+ for (var i=0,len=fns.length; i<len; i++) {
+ var o = fns[i];
+ if (o.origin && o.origin !== '*' && e.origin !== o.origin) {
+ console.warn("postmessage message origin mismatch", e.origin, o.origin);
+ if (msg.errback) {
+ // notify post message errback
+ var error = {
+ message: "postmessage origin mismatch",
+ origin: [e.origin, o.origin]
+ };
+ pm.send({target:e.source, data:error, type:msg.errback});
+ }
+ continue;
+ }
+
+ function sendReply ( data ) {
+ if (msg.callback) {
+ pm.send({target:e.source, data:data, type:msg.callback});
+ }
+ }
+
+ try {
+ if ( o.callback ) {
+ o.fn(msg.data, sendReply, e);
+ } else {
+ sendReply ( o.fn(msg.data, e) );
+ }
+ }
+ catch (ex) {
+ if (msg.errback) {
+ // notify post message errback
+ pm.send({target:e.source, data:ex, type:msg.errback});
+ } else {
+ throw ex;
+ }
+ }
+ };
+ }
+ }
+ };
+
+ // location hash polling
+ pm.hash = {
+
+ send: function(options, msg) {
+ //console.log("hash.send", target_window, options, msg);
+ var target_window = options.target,
+ target_url = options.url;
+ if (!target_url) {
+ console.warn("postmessage target window url is required");
+ return;
+ }
+ target_url = pm.hash._url(target_url);
+ var source_window,
+ source_url = pm.hash._url(window.location.href);
+ if (window == target_window.parent) {
+ source_window = "parent";
+ }
+ else {
+ try {
+ for (var i=0,len=parent.frames.length; i<len; i++) {
+ var f = parent.frames[i];
+ if (f == window) {
+ source_window = i;
+ break;
+ }
+ };
+ }
+ catch(ex) {
+ // Opera: security error trying to access parent.frames x-origin
+ // juse use window.name
+ source_window = window.name;
+ }
+ }
+ if (source_window == null) {
+ console.warn("postmessage windows must be direct parent/child windows and the child must be available through the parent window.frames list");
+ return;
+ }
+ var hashmessage = {
+ "x-requested-with": "postmessage",
+ source: {
+ name: source_window,
+ url: source_url
+ },
+ postmessage: msg
+ };
+ var hash_id = "#x-postmessage-id=" + pm._random();
+ target_window.location = target_url + hash_id + encodeURIComponent(JSON.stringify(hashmessage));
+ },
+
+ _regex: /^\#x\-postmessage\-id\=(\w{32})/,
+
+ _regex_len: "#x-postmessage-id=".length + 32,
+
+ _bind: function() {
+ // are we already listening to message events on this w?
+ if (!pm.data("polling.postmessage")) {
+ setInterval(function() {
+ var hash = "" + window.location.hash,
+ m = pm.hash._regex.exec(hash);
+ if (m) {
+ var id = m[1];
+ if (pm.hash._last !== id) {
+ pm.hash._last = id;
+ pm.hash._dispatch(hash.substring(pm.hash._regex_len));
+ }
+ }
+ }, $.pm.poll || 200);
+ pm.data("polling.postmessage", 1);
+ }
+ },
+
+ _dispatch: function(hash) {
+ if (!hash) {
+ return;
+ }
+ try {
+ hash = JSON.parse(decodeURIComponent(hash));
+ if (!(hash['x-requested-with'] === 'postmessage' &&
+ hash.source && hash.source.name != null && hash.source.url && hash.postmessage)) {
+ // ignore since hash could've come from somewhere else
+ return;
+ }
+ }
+ catch (ex) {
+ // ignore since hash could've come from somewhere else
+ return;
+ }
+ var msg = hash.postmessage,
+ cbs = pm.data("callbacks.postmessage") || {},
+ cb = cbs[msg.type];
+ if (cb) {
+ cb(msg.data);
+ }
+ else {
+ var source_window;
+ if (hash.source.name === "parent") {
+ source_window = window.parent;
+ }
+ else {
+ source_window = window.frames[hash.source.name];
+ }
+ var l = pm.data("listeners.postmessage") || {};
+ var fns = l[msg.type] || [];
+ for (var i=0,len=fns.length; i<len; i++) {
+ var o = fns[i];
+ if (o.origin) {
+ var origin = /https?\:\/\/[^\/]*/.exec(hash.source.url)[0];
+ if (o.origin !== '*' && origin !== o.origin) {
+ console.warn("postmessage message origin mismatch", origin, o.origin);
+ if (msg.errback) {
+ // notify post message errback
+ var error = {
+ message: "postmessage origin mismatch",
+ origin: [origin, o.origin]
+ };
+ pm.send({target:source_window, data:error, type:msg.errback, hash:true, url:hash.source.url});
+ }
+ continue;
+ }
+ }
+
+ function sendReply ( data ) {
+ if (msg.callback) {
+ pm.send({target:source_window, data:data, type:msg.callback, hash:true, url:hash.source.url});
+ }
+ }
+
+ try {
+ if ( o.callback ) {
+ o.fn(msg.data, sendReply);
+ } else {
+ sendReply ( o.fn(msg.data) );
+ }
+ }
+ catch (ex) {
+ if (msg.errback) {
+ // notify post message errback
+ pm.send({target:source_window, data:ex, type:msg.errback, hash:true, url:hash.source.url});
+ } else {
+ throw ex;
+ }
+ }
+ };
+ }
+ },
+
+ _url: function(url) {
+ // url minus hash part
+ return (""+url).replace(/#.*$/, "");
+ }
+
+ };
+
+ $.extend(pm, {
+ defaults: {
+ target: null, /* target window (required) */
+ url: null, /* target window url (required if no window.postMessage or hash == true) */
+ type: null, /* message type (required) */
+ data: null, /* message data (required) */
+ success: null, /* success callback (optional) */
+ error: null, /* error callback (optional) */
+ origin: "*", /* postmessage origin (optional) */
+ hash: false /* use location hash for message passing (optional) */
+ }
+ });
+
+ })(this, typeof jQuery === "undefined" ? NO_JQUERY : jQuery);
+
+/**
+ * http://www.JSON.org/json2.js
+ **/
+if (! ("JSON" in window && window.JSON)){JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z"};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());
diff --git a/plugins/jetpack/_inc/spin.js b/plugins/jetpack/_inc/spin.js
new file mode 100644
index 00000000..f506cd2b
--- /dev/null
+++ b/plugins/jetpack/_inc/spin.js
@@ -0,0 +1,301 @@
+//fgnass.github.com/spin.js#v1.2.4
+(function(window, document, undefined) {
+
+/**
+ * Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de]
+ * Licensed under the MIT license
+ */
+
+ var prefixes = ['webkit', 'Moz', 'ms', 'O']; /* Vendor prefixes */
+ var animations = {}; /* Animation rules keyed by their name */
+ var useCssAnimations;
+
+ /**
+ * Utility function to create elements. If no tag name is given,
+ * a DIV is created. Optionally properties can be passed.
+ */
+ function createEl(tag, prop) {
+ var el = document.createElement(tag || 'div');
+ var n;
+
+ for(n in prop) {
+ el[n] = prop[n];
+ }
+ return el;
+ }
+
+ /**
+ * Appends children and returns the parent.
+ */
+ function ins(parent /* child1, child2, ...*/) {
+ for (var i=1, n=arguments.length; i<n; i++) {
+ parent.appendChild(arguments[i]);
+ }
+ return parent;
+ }
+
+ /**
+ * Insert a new stylesheet to hold the @keyframe or VML rules.
+ */
+ var sheet = function() {
+ var el = createEl('style');
+ ins(document.getElementsByTagName('head')[0], el);
+ return el.sheet || el.styleSheet;
+ }();
+
+ /**
+ * Creates an opacity keyframe animation rule and returns its name.
+ * Since most mobile Webkits have timing issues with animation-delay,
+ * we create separate rules for each line/segment.
+ */
+ function addAnimation(alpha, trail, i, lines) {
+ var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-');
+ var start = 0.01 + i/lines*100;
+ var z = Math.max(1-(1-alpha)/trail*(100-start) , alpha);
+ var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase();
+ var pre = prefix && '-'+prefix+'-' || '';
+
+ if (!animations[name]) {
+ sheet.insertRule(
+ '@' + pre + 'keyframes ' + name + '{' +
+ '0%{opacity:'+z+'}' +
+ start + '%{opacity:'+ alpha + '}' +
+ (start+0.01) + '%{opacity:1}' +
+ (start+trail)%100 + '%{opacity:'+ alpha + '}' +
+ '100%{opacity:'+ z + '}' +
+ '}', 0);
+ animations[name] = 1;
+ }
+ return name;
+ }
+
+ /**
+ * Tries various vendor prefixes and returns the first supported property.
+ **/
+ function vendor(el, prop) {
+ var s = el.style;
+ var pp;
+ var i;
+
+ if(s[prop] !== undefined) return prop;
+ prop = prop.charAt(0).toUpperCase() + prop.slice(1);
+ for(i=0; i<prefixes.length; i++) {
+ pp = prefixes[i]+prop;
+ if(s[pp] !== undefined) return pp;
+ }
+ }
+
+ /**
+ * Sets multiple style properties at once.
+ */
+ function css(el, prop) {
+ for (var n in prop) {
+ el.style[vendor(el, n)||n] = prop[n];
+ }
+ return el;
+ }
+
+ /**
+ * Fills in default values.
+ */
+ function merge(obj) {
+ for (var i=1; i < arguments.length; i++) {
+ var def = arguments[i];
+ for (var n in def) {
+ if (obj[n] === undefined) obj[n] = def[n];
+ }
+ }
+ return obj;
+ }
+
+ /**
+ * Returns the absolute page-offset of the given element.
+ */
+ function pos(el) {
+ var o = {x:el.offsetLeft, y:el.offsetTop};
+ while((el = el.offsetParent)) {
+ o.x+=el.offsetLeft;
+ o.y+=el.offsetTop;
+ }
+ return o;
+ }
+
+ var defaults = {
+ lines: 12, // The number of lines to draw
+ length: 7, // The length of each line
+ width: 5, // The line thickness
+ radius: 10, // The radius of the inner circle
+ color: '#000', // #rgb or #rrggbb
+ speed: 1, // Rounds per second
+ trail: 100, // Afterglow percentage
+ opacity: 1/4, // Opacity of the lines
+ fps: 20, // Frames per second when using setTimeout()
+ zIndex: 2e9, // Use a high z-index by default
+ className: 'spinner', // CSS class to assign to the element
+ top: 'auto', // center vertically
+ left: 'auto' // center horizontally
+ };
+
+ /** The constructor */
+ var Spinner = function Spinner(o) {
+ if (!this.spin) return new Spinner(o);
+ this.opts = merge(o || {}, Spinner.defaults, defaults);
+ };
+
+ Spinner.defaults = {};
+ Spinner.prototype = {
+ spin: function(target) {
+ this.stop();
+ var self = this;
+ var o = self.opts;
+ var el = self.el = css(createEl(0, {className: o.className}), {position: 'relative', zIndex: o.zIndex});
+ var mid = o.radius+o.length+o.width;
+ var ep; // element position
+ var tp; // target position
+
+ if (target) {
+ target.insertBefore(el, target.firstChild||null);
+ tp = pos(target);
+ ep = pos(el);
+ css(el, {
+ left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 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 @@
+<?php
+
+/**
+ * Useful for finding an image to display alongside/in representation of a specific post.
+ *
+ * Includes a few different methods, all of which return a similar-format array containing
+ * details of any images found. Everything can (should) be called statically, it's just a
+ * function-bucket. You can also call Jetpack_PostImages::get_image() to cycle through all of the methods until
+ * one of them finds something useful.
+ *
+ * This file is included verbatim in Jetpack
+ */
+class Jetpack_PostImages {
+ /**
+ * If a slideshow is embedded within a post, then parse out the images involved and return them
+ */
+ static function from_slideshow( $post_id, $width = 200, $height = 200 ) {
+ $post = get_post( $post_id );
+
+ if ( false === strpos( $post->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, '<img' ) )
+ continue;
+
+ preg_match_all( '/<img\s+[^>]*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] ), '<a', $a_pos ); // is there surrounding <a>?
+
+ if ( false !== $a_pos && preg_match( '/<a\s+[^>]*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( '!<img.*src="([^"]+)".*/?>!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 @@
+<?php
+
+function jetpack_is_mobile( $kind = 'any', $return_matched_agent = false ) {
+ static $kinds = array( 'smart' => 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/<ChromeRevision> instead of Version/<VersionNum>.
+ * - 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; <Manufacturer>; <Device> [;<Operator>])
+ */
+ 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 @@
+<?php
+
+// Endpoint
+abstract class WPCOM_JSON_API_Endpoint {
+ // The API Object
+ var $api;
+
+ var $pass_wpcom_user_details = false;
+ var $can_use_user_details_instead_of_blog_membership = false;
+
+ // One liner.
+ var $description;
+
+ // Object Grouping For Documentation (Users, Posts, Comments)
+ var $group;
+
+ // Stats extra value to bump
+ var $stat;
+
+ // HTTP Method
+ var $method = 'GET';
+
+ // Path at which to serve this endpoint: sprintf() format.
+ var $path = '';
+
+ // Identifiers to fill sprintf() formatted $path
+ var $path_labels = array();
+
+ // Accepted query parameters
+ var $query = array(
+ // Parameter name
+ 'context' => 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 ) :
+?>
+<caption>
+ <h1><?php echo wp_kses_post( $doc['method'] ); ?> <?php echo wp_kses_post( $doc['path_labeled'] ); ?></h1>
+ <p><?php echo wp_kses_post( $doc['description'] ); ?></p>
+</caption>
+
+<?php endif; ?>
+
+<section class="resource-url">
+ <h2 id="apidoc-resource-url">Resource URL</h2>
+ <table class="api-doc api-doc-resource-parameters api-doc-resource">
+ <thead>
+ <tr>
+ <th class="api-index-title" scope="column">Type</th>
+ <th class="api-index-title" scope="column">URL and Format</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="api-index-item">
+ <th scope="row" class="parameter api-index-item-title"><?php echo wp_kses_post( $doc['method'] ); ?></th>
+ <td class="type api-index-item-title" style="white-space: nowrap;">https://public-api.wordpress.com/rest/v1<?php echo wp_kses_post( $doc['path_labeled'] ); ?></td>
+ </tr>
+ </tbody>
+ </table>
+</section>
+
+<?php
+
+ foreach ( array(
+ 'path' => '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 ) );
+?>
+
+<section class="<?php echo $param_label; ?>">
+
+<h2 id="apidoc-<?php echo esc_attr( $doc_section_key ); ?>"><?php echo wp_kses_post( $label ); ?></h2>
+
+<table class="api-doc api-doc-<?php echo $param_label; ?>-parameters api-doc-<?php echo strtolower( str_replace( ' ', '-', $doc['group'] ) ); ?>">
+
+<thead>
+ <tr>
+ <th class="api-index-title" scope="column">Parameter</th>
+ <th class="api-index-title" scope="column">Type</th>
+ <th class="api-index-title" scope="column">Description</th>
+ </tr>
+</thead>
+<tbody>
+
+<?php foreach ( $doc_section as $key => $item ) : ?>
+
+ <tr class="api-index-item">
+ <th scope="row" class="parameter api-index-item-title"><?php echo wp_kses_post( $key ); ?></th>
+ <td class="type api-index-item-title"><?php echo wp_kses_post( $item['type'] ); // @todo auto-link? ?></td>
+ <td class="description api-index-item-body"><?php
+
+ $this->generate_doc_description( $item['description'] );
+
+ ?></td>
+ </tr>
+
+<?php endforeach; ?>
+</tbody>
+</table>
+</section>
+<?php endforeach; ?>
+
+<?php
+ // If no example was hardcoded in the doc, try to get some
+ if ( empty( $this->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 = <<<EOPHP
+<?php
+
+\$options = $php_opts_exported;
+
+\$context = stream_context_create( \$options );
+\$response = file_get_contents(
+ '$this->example_request',
+ false,
+ \$context
+);
+\$response = json_decode( \$response );
+
+?>
+EOPHP;
+ } else {
+ $php = <<<EOPHP
+<?php
+
+\$response = file_get_contents( '$this->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 );
+?>
+
+<?php if ( ! empty( $this->example_request ) || ! empty( $this->example_request_data ) || ! empty( $this->example_response ) ) : ?>
+
+ <section class="example-response">
+ <h2 id="apidoc-example">Example</h2>
+
+ <section>
+ <h3>cURL</h3>
+ <?php echo wp_kses_post( $curl ); ?>
+ </section>
+
+ <section>
+ <h3>PHP</h3>
+ <?php echo wp_kses_post( $php ); ?>
+ </section>
+
+ <?php if ( ! empty( $this->example_response ) ) : ?>
+
+ <section>
+ <h3>Response Body</h3>
+ <?php echo $this->example_response; ?>
+ </section>
+
+ <?php endif; ?>
+
+ </section>
+
+<?php endif; ?>
+
+<?php
+ if ( 'unset' !== $original_post ) {
+ $GLOBALS['post'] = $original_post;
+ }
+ }
+
+ function add_http_build_query_to_php_content_example( $matches ) {
+ $trimmed_match = ltrim( $matches[0] );
+ $pad = substr( $matches[0], 0, -1 * strlen( $trimmed_match ) );
+ $pad = ltrim( $pad, ' ' );
+ $return = ' ' . str_replace( "\n", "\n ", $matches[0] );
+ return " http_build_query({$return}{$pad})";
+ }
+
+ /**
+ * Recursively generates the <dl>'s to document item descriptions.
+ * Echoes HTML.
+ */
+ function generate_doc_description( $item ) {
+ if ( is_array( $item ) ) : ?>
+
+ <dl>
+<?php foreach ( $item as $description_key => $description_value ) : ?>
+
+ <dt><?php echo wp_kses_post( $description_key . ':' ); ?></dt>
+ <dd><?php $this->generate_doc_description( $description_value ); ?></dd>
+
+<?php endforeach; ?>
+
+ </dl>
+
+<?php
+ else :
+ echo wp_kses_post( $item );
+ endif;
+ }
+
+ /**
+ * Auto generates documentation based on description, method, path, path_labels, and query parameters.
+ * Echoes HTML.
+ */
+ function generate_documentation() {
+ $format = str_replace( '%d', '%s', $this->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) <code>context</code> dependent.',
+ 'URL' => '(URL) The full permalink URL to the post.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) <code>context</code> dependent.',
+ 'excerpt' => '(HTML) <code>context</code> 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( '/<!--more(.*?)?-->/', '', $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 .= "<div class='wp-caption aligncenter'>$link
+ <p class='wp-caption-text'>" . wptexturize($attachment->post_excerpt) . "</p>
+ </div>";
+ } 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) <code>context</code> 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 <code>offset</code> 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 <code>offset</code> 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.<br /><br /><strong>Example</strong>:<br />" .
+ "<code>curl \<br />--form 'title=Image' \<br />--form 'media[]=@/path/to/file.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>",
+ '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": "<p>Hello. I am a test post. I was created by the API<\/p>\n",
+ "excerpt": "<p>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": "<p>Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "excerpt": "<p>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": "<p>Hello. I am an edited post. I was edited by the API<\/p>\n",
+ "excerpt": "<p>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": "<p>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": "<p>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": "<p>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": "<p>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 @@
+<?php
+
+defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
+
+class WPCOM_JSON_API {
+ static $self = null;
+
+ var $endpoints = array();
+
+ var $token_details = array();
+
+ var $method = '';
+ var $url = '';
+ var $path = '';
+ var $query = array();
+ var $post_body = null;
+ var $files = null;
+ var $content_type = null;
+ var $accept = '';
+
+ var $_server_https;
+ var $exit = true;
+ var $public_api_scheme = 'https';
+
+ var $trapped_error = null;
+
+ static function init( $method = null, $url = null, $post_body = null ) {
+ if ( !self::$self ) {
+ $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
+ self::$self = new $class( $method, $url, $post_body );
+ }
+ return self::$self;
+ }
+
+ function add( WPCOM_JSON_API_Endpoint $endpoint ) {
+ if ( !isset( $this->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 @@
+<?php
+
+class Jetpack_Photon {
+ /**
+ * Class variables
+ */
+ // Oh look, a singleton
+ private static $__instance = null;
+
+ // Allowed extensions must match http://code.trac.wordpress.org/browser/photon/index.php#L31
+ protected static $extensions = array(
+ 'gif',
+ 'jpg',
+ 'jpeg',
+ 'png'
+ );
+
+ // Don't access this directly. Instead, use self::image_sizes() so it's actually populated with something.
+ protected static $image_sizes = null;
+
+ /**
+ * Singleton implementation
+ *
+ * @return object
+ */
+ public static function instance() {
+ if ( ! is_a( self::$__instance, 'Jetpack_Photon' ) ) {
+ self::$__instance = new Jetpack_Photon;
+ self::$__instance->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 <a> 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( '#(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag><img[^>]+?src=["|\'](?P<img_url>[^\s]+?)["|\'].*?>){1}(?:\s*</a>)?#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?/)?>(</a>)?$#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 @@
+<?php
+
+if ( !function_exists( 'rawurlencode_deep' ) ) :
+/**
+ * Navigates through an array and raw encodes the values to be used in a URL.
+ *
+ * @since WordPress 3.4.0
+ *
+ * @param array|string $value The array or string to be encoded.
+ * @return array|string $value The encoded array (or string from the callback).
+ */
+function rawurlencode_deep( $value ) {
+ return is_array( $value ) ? array_map( 'rawurlencode_deep', $value ) : rawurlencode( $value );
+}
+endif;
diff --git a/plugins/jetpack/functions.gallery.php b/plugins/jetpack/functions.gallery.php
new file mode 100644
index 00000000..9a9e71b5
--- /dev/null
+++ b/plugins/jetpack/functions.gallery.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Renders extra controls in the Gallery Settings section of the new media UI.
+ */
+class Jetpack_Gallery_Settings {
+ function __construct() {
+ add_action( 'admin_init', array( $this, 'admin_init' ) );
+ }
+
+ function admin_init() {
+ $this->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() {
+ ?>
+ <script type="text/html" id="tmpl-jetpack-gallery-settings">
+ <label class="setting">
+ <span><?php _e( 'Type', 'jetpack' ); ?></span>
+ <select class="type" name="type" data-setting="type">
+ <option value="default" <?php selected( true ); ?>><?php _e( 'Default', 'jetpack' ); ?></option>
+ <?php foreach ( $this->gallery_types as $value => $caption ) : ?>
+ <option value="<?php echo esc_attr( $value ); ?>"><?php echo esc_html( $caption ); ?></option>
+ <?php endforeach; ?>
+ </select>
+ </label>
+ </script>
+ <?php
+ }
+}
+new Jetpack_Gallery_Settings;
diff --git a/plugins/jetpack/functions.opengraph.php b/plugins/jetpack/functions.opengraph.php
new file mode 100644
index 00000000..542d01f1
--- /dev/null
+++ b/plugins/jetpack/functions.opengraph.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Open Graph Tags
+ *
+ * Add Open Graph tags so that Facebook (and any other service that supports them)
+ * can crawl the site better and we provide a better sharing experience.
+ *
+ * @link http://ogp.me/
+ * @link http://developers.facebook.com/docs/opengraph/
+ */
+add_action( 'wp_head', 'jetpack_og_tags' );
+
+function jetpack_og_tags() {
+ if ( false === apply_filters( 'jetpack_enable_opengraph', true ) ) {
+ _deprecated_function( 'jetpack_enable_opengraph', '2.0.3', 'jetpack_enable_open_graph' );
+ return;
+ }
+
+ // Disable the widont filter on WP.com to avoid stray &nbsps
+ $disable_widont = remove_filter( 'the_title', 'widont' );
+
+ $og_output = "\n<!-- Jetpack Open Graph Tags -->\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( '<meta property="%s" content="%s" />', 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 @@
+<?php
+
+/**
+ * Generates a Photon URL.
+ *
+ * @see http://developer.wordpress.com/docs/photon/
+ *
+ * @param string $image_url URL to the publicly accessible image you want to manipulate
+ * @param array|string $args An array of arguments, i.e. array( 'w' => '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',
);
/**
@@ -75,6 +81,12 @@ class Jetpack {
var $error = '';
/**
+ * Modules that need more privacy description.
+ * @var string
+ */
+ var $privacy_checks = '';
+
+ /**
* Stats to record once the page loads
*
* @var array
@@ -87,10 +99,15 @@ 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,20 +591,42 @@ 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" ).
*
* @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 ) {
?>
<!doctype html>
<html>
@@ -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',
) );
@@ -1034,10 +1167,35 @@ 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 &#8220;%1$s&#8221; plugin.', 'jetpack' ), $deactivate_me[1] ), false );
+ Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old &#8220;%1$s&#8221; 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' =>
- '<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
- '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
- '<ol>' .
- '<li>' . __( 'Find the component you want to manage', 'jetpack' ) . '</li>' .
- '<li>' . __( 'Click on Learn More', 'jetpack' ) . '</li>' .
- '<li>' . __( 'An Activate or Deactivate button will appear', 'jetpack' ) . '</li>' .
- '<li>' . __( 'If additional settings are available, a link to them will appear', 'jetpack' ) . '</li>' .
- '</ol>'
- ) );
+ if ( current_user_can( 'manage_options' ) ) {
+ $current_screen->add_help_tab( array(
+ 'id' => 'modules',
+ 'title' => __( 'Modules', 'jetpack' ),
+ 'content' =>
+ '<p><strong>' . __( 'Jetpack by WordPress.com', 'jetpack' ) . '</strong></p>' .
+ '<p>' . __( 'You can activate or deactivate individual Jetpack modules to suit your needs.', 'jetpack' ) . '</p>' .
+ '<ol>' .
+ '<li>' . __( 'Find the component you want to manage', 'jetpack' ) . '</li>' .
+ '<li>' . __( 'Click on Learn More', 'jetpack' ) . '</li>' .
+ '<li>' . __( 'An Activate or Deactivate button will appear', 'jetpack' ) . '</li>' .
+ '<li>' . __( 'If additional settings are available, a link to them will appear', 'jetpack' ) . '</li>' .
+ '</ol>'
+ ) );
+ }
// Help Sidebar
$current_screen->set_help_sidebar(
@@ -1264,20 +1545,20 @@ p {
function admin_menu_css() { ?>
<style type="text/css" id="jetpack-menu-css">
- #toplevel_page_jetpack .wp-menu-image {
- background: url( <?php echo plugins_url( basename( dirname( __FILE__ ) ) . '/_inc/images/menuicon-sprite.png' ) ?> ) 0 90% no-repeat;
+ #toplevel_page_jetpack .wp-menu-image {
+ background: url( <?php echo plugins_url( basename( dirname( __FILE__ ) ) . '/_inc/images/menuicon-sprite.png' ) ?> ) 0 90% no-repeat;
}
/* Retina Jetpack Menu Icon */
@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) {
- #toplevel_page_jetpack .wp-menu-image {
+ #toplevel_page_jetpack .wp-menu-image {
background: url( <?php echo plugins_url( basename( dirname( __FILE__ ) ) . '/_inc/images/menuicon-sprite-2x.png' ) ?> ) 0 90% no-repeat;
background-size:30px 64px;
}
}
- #toplevel_page_jetpack.current .wp-menu-image,
- #toplevel_page_jetpack.wp-has-current-submenu .wp-menu-image,
- #toplevel_page_jetpack:hover .wp-menu-image {
- background-position: top left;
+ #toplevel_page_jetpack.current .wp-menu-image,
+ #toplevel_page_jetpack.wp-has-current-submenu .wp-menu-image,
+ #toplevel_page_jetpack:hover .wp-menu-image {
+ background-position: top left;
}
</style><?php
}
@@ -1301,20 +1582,21 @@ p {
}
function admin_head() {
- 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_head_' . $_GET['configure'] );
}
function admin_styles() {
global $wp_styles;
- wp_enqueue_style( 'jetpack', plugins_url( basename( dirname( __FILE__ ) ) . '/_inc/jetpack.css' ), false, JETPACK__VERSION . '-20120701' );
+ wp_enqueue_style( 'jetpack', plugins_url( basename( dirname( __FILE__ ) ) . '/_inc/jetpack.css' ), false, JETPACK__VERSION . '-20121016' );
$wp_styles->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 {
<div class="jetpack-text-container">
<h4>
<?php if ( 1 == Jetpack::get_option( 'activated' ) ) : ?>
- <p><?php _e( '<strong>Your Jetpack is almost ready</strong> &#8211; A connection to WordPress.com is needed to enabled features like Comments, Stats, Contact Forms, and Subscriptions. Connect now to get fueled up!', 'jetpack' ); ?></p>
+ <p><?php _e( '<strong>Your Jetpack is almost ready</strong> &#8211; A connection to WordPress.com is needed to enable features like Stats, Contact Forms, and Subscriptions. Connect now to get fueled up!', 'jetpack' ); ?></p>
<?php else : ?>
<p><?php _e( '<strong>Jetpack is installed</strong> and ready to bring awesome, WordPress.com cloud-powered features to your site.', 'jetpack' ) ?></p>
<?php endif; ?>
@@ -1377,8 +1659,8 @@ p {
</div>
<?php
}
-
- function jetpack_comment_notice() {
+
+ public static function jetpack_comment_notice() {
if ( in_array( 'comments', Jetpack::get_active_modules() ) ) {
return '';
}
@@ -1398,7 +1680,7 @@ p {
}
}
- return '<br /><br />' . sprintf(
+ return '<br /><br />' . 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, <a href="%s">%s</a>.', '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 .= '&notes_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( __( '<strong>%s Deactivated!</strong> 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(
+ '<strong>%l Deactivated!</strong> You can activate it again at any time using the activate button on the module card.',
+ '<strong>%l Deactivated!</strong> 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 = __( "<strong>You&#8217;re fueled up and ready to go.</strong> ", 'jetpack' );
+ $this->message .= Jetpack::jetpack_comment_notice();
+ break;
+
+ case 'unlinked' :
+ $user = wp_get_current_user();
+ $this->message = sprintf( __( '<strong>You have unlinked your account (%s) from WordPress.com.</strong>', '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 {
</div>
</div>
<?php
+
}
+
+ if ( $this->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[] = "<strong>{$module['name']}</strong>";
+ }
+
+ $module_slugs = join( ',', $module_slugs );
+?>
+<div id="message" class="jetpack-message jetpack-err">
+ <div class="squeezer">
+ <h4><strong><?php esc_html_e( 'Is this site private?', 'jetpack' ); ?></strong></h4><br />
+ <p><?php
+ echo wp_kses( wptexturize( wp_sprintf(
+ _nx(
+ "Like your site's RSS feeds, %l allows access to your posts and other content to third parties.",
+ "Like your site's RSS feeds, %l allow access to your posts and other content to third parties.",
+ count( $privacy_checks ),
+ '%l = list of Jetpack module/feature names',
+ 'jetpack'
+ ),
+ $module_names
+ ) ), array( 'strong' => true ) );
+
+ echo "\n<br />\n";
+
+ echo wp_kses( sprintf(
+ _nx(
+ 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating this feature</a>.',
+ 'If your site is not publicly accessible, consider <a href="%1$s" title="%2$s">deactivating these features</a>.',
+ 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 ) ) );
+ ?></p>
+ </div>
+</div>
+<?php
+ endif;
}
/**
@@ -1802,7 +2201,7 @@ p {
return false;
}
- $token = Jetpack_Data::get_access_token( 0 );
+ $token = Jetpack_Data::get_access_token();
if ( !$token || is_wp_error( $token ) ) {
return false;
}
@@ -1810,7 +2209,7 @@ p {
return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->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;
?>
<div class="wrap" id="jetpack-settings">
@@ -1869,9 +2276,17 @@ p {
<div id="jp-header"<?php if ( $is_connected ) : ?> class="small"<?php endif; ?>>
<div id="jp-clouds">
<?php if ( $is_connected ) : ?>
- <div id="jp-disconnect">
- <a href="<?php echo wp_nonce_url( Jetpack::admin_url( array( 'action' => 'disconnect' ) ), 'jetpack-disconnect' ); ?>"><?php _e( 'Connected to WordPress.com', 'jetpack' ); ?></a>
- <span><?php _e( 'Disconnect from WordPress.com', 'jetpack' ) ?></span>
+ <div id="jp-disconnectors">
+ <?php if ( current_user_can( 'manage_options' ) ) : ?>
+ <div id="jp-disconnect" class="jp-disconnect">
+ <a href="<?php echo wp_nonce_url( Jetpack::admin_url( array( 'action' => 'disconnect' ) ), 'jetpack-disconnect' ); ?>"><div class="deftext"><?php _e( 'Connected to WordPress.com', 'jetpack' ); ?></div><div class="hovertext"><?php _e( 'Disconnect from WordPress.com', 'jetpack' ) ?></div></a>
+ </div>
+ <?php endif; ?>
+ <?php if ( $is_user_connected && !$is_master_user ) : ?>
+ <div id="jp-unlink" class="jp-disconnect">
+ <a href="<?php echo wp_nonce_url( Jetpack::admin_url( array( 'action' => 'unlink' ) ), 'jetpack-unlink' ); ?>"><div class="deftext"><?php _e( 'User linked to WordPress.com', 'jetpack' ); ?></div><div class="hovertext"><?php _e( 'Unlink user from WordPress.com', 'jetpack' ) ?></div></a>
+ </div>
+ <?php endif; ?>
</div>
<?php endif; ?>
<h3><?php _e( 'Jetpack by WordPress.com', 'jetpack' ) ?></h3>
@@ -1910,11 +2325,28 @@ p {
</div>
</div>
+ <?php elseif ( ! $is_user_connected ) : ?>
+
+ <div id="message" class="updated jetpack-message jp-connect">
+ <div class="jetpack-wrap-container">
+ <div class="jetpack-text-container">
+ <h4>
+ <p><?php _e( "To enable all of the Jetpack features you&#8217;ll need to link your account here to your WordPress.com account using the button to the right.", 'jetpack' ) ?></p>
+ </h4>
+ </div>
+ <div class="jetpack-install-container">
+ <p class="submit"><a href="<?php echo $this->build_connect_url() ?>" class="button-connector" id="wpcom-connect"><?php _e( 'Link account with WordPress.com', 'jetpack' ); ?></a></p>
+ </div>
+ </div>
+ </div>
+
+ <?php else /* blog and user are connected */ : ?>
+ <?php /* TODO: if not master user, show user disconnect button? */ ?>
<?php endif; ?>
<?php
// If we select the configure option for a module, show the configuration screen.
- if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) ) :
+ if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) :
$this->admin_screen_configure_module( $_GET['configure'] );
// List all the available modules.
@@ -1975,19 +2407,23 @@ p {
<a href="http://jetpack.me/" target="_blank">Jetpack <?php echo esc_html( JETPACK__VERSION ); ?></a> |
<a href="http://automattic.com/privacy/" target="_blank"><?php _e( 'Privacy Policy', 'jetpack' ); ?></a> |
<a href="http://wordpress.com/tos/" target="_blank"><?php _e( 'Terms of Service', 'jetpack' ); ?></a> |
+<?php if ( current_user_can( 'manage_options' ) ) : ?>
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-ajax.php?action=jetpack_debug' ), 'jetpack_debug' ) ); ?>" id="jp-debug"><?php _e( 'Debug', 'jetpack' ); ?></a> |
+<?php endif; ?>
<a href="http://jetpack.me/support/" target="_blank"><?php _e( 'Support', 'jetpack' ); ?></a>
</p>
</div>
<div id="jetpack-configuration" style="display:none;">
- <p><img src="<?php echo esc_url( admin_url( 'images/wpspin_dark.gif' ) ); ?>" alt="Loading ..." /></p>
+ <p><img width="16" src="<?php echo esc_url( plugins_url( '_inc/images/wpspin_light-2x.gif', __FILE__ ) ); ?>" alt="Loading ..." /></p>
</div>
</div>
<?php
}
function ajax_debug() {
+ nocache_headers();
+
check_ajax_referer( 'jetpack_debug' );
if ( !current_user_can( 'manage_options' ) ) {
@@ -1997,18 +2433,31 @@ p {
<p><?php esc_html_e( 'This is sensitive information. Please do not post your BLOG_TOKEN or USER_TOKEN publicly; they are like passwords.', 'jetpack' ); ?></p>
<ul>
<?php
+ // Extract the current_user's token
+ $user_id = get_current_user_id();
+ $user_tokens = Jetpack::get_option( 'user_tokens' );
+ if ( is_array( $user_tokens ) && array_key_exists( $user_id, $user_tokens ) ) {
+ $user_token = $user_tokens[$user_id];
+ } else {
+ $user_token = '[this user has no token]';
+ }
+ unset( $user_tokens );
+
foreach ( array(
'CLIENT_ID' => '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 ) :
?>
<li><?php echo esc_html( $label ); ?>: <code><?php echo esc_html( Jetpack::get_option( $option_name ) ); ?></code></li>
<?php endforeach; ?>
+ <li>USER_ID: <code><?php echo esc_html( $user_id ); ?></code></li>
+ <li>USER_TOKEN: <code><?php echo esc_html( $user_token ); ?></code></li>
<li>PHP_VERSION: <code><?php echo esc_html( PHP_VERSION ); ?></code></li>
<li>WORDPRESS_VERSION: <code><?php echo esc_html( $GLOBALS['wp_version'] ); ?></code></li>
</ul>
@@ -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; ?>
<div id="jp-settings-screen" style="position: relative">
@@ -2033,7 +2482,7 @@ p {
</div><?php
}
- function sort_modules( $a, $b ) {
+ public static function sort_modules( $a, $b ) {
if ( $a['sort'] == $b['sort'] )
return 0;
@@ -2142,17 +2591,17 @@ p {
<div class="jetpack-module-actions">
<?php if ( $jetpack_connected ) : ?>
- <?php if ( !$activated ) : ?>
- <a href="<?php echo esc_url( $toggle_url ); ?>" class="jetpack-toggle-button<?php echo ( 'inactive' == $css ? ' button-primary' : ' button' ); ?>"><?php echo $toggle; ?></a>&nbsp;
+ <?php if ( !$activated && current_user_can( 'manage_options' ) && apply_filters( 'jetpack_can_activate_' . $module, true ) ) : ?>
+ <a href="<?php echo esc_url( $toggle_url ); ?>" class="<?php echo ( 'inactive' == $css ? ' button-primary' : ' button-secondary' ); ?>"><?php echo $toggle; ?></a>&nbsp;
<?php endif; ?>
<?php do_action( 'jetpack_learn_more_button_' . $module ) ?>
<?php
- if ( apply_filters( 'jetpack_module_configurable_' . $module, false ) ) {
- echo '<a href="' . esc_attr( Jetpack::module_configuration_url( $module ) ) . '" class="jetpack-configure-button button">' . __( 'Configure', 'jetpack' ) . '</a>';
+ if ( current_user_can( 'manage_options' ) && apply_filters( 'jetpack_module_configurable_' . $module, false ) ) {
+ echo '<a href="' . esc_attr( Jetpack::module_configuration_url( $module ) ) . '" class="jetpack-configure-button button-secondary">' . __( 'Configure', 'jetpack' ) . '</a>';
}
- ?><?php if ( $activated && $module_data['deactivate'] ) : ?><a style="display: none;" href="<?php echo esc_url( $toggle_url ); ?>" class="jetpack-deactivate-button button"><?php echo $toggle; ?></a>&nbsp;<?php endif; ?>
+ ?><?php if ( $activated && $module_data['deactivate'] && current_user_can( 'manage_options' ) ) : ?><a style="display: none;" href="<?php echo esc_url( $toggle_url ); ?>" class="jetpack-deactivate-button button-secondary"><?php echo $toggle; ?></a>&nbsp;<?php endif; ?>
<?php else : ?>
<?php do_action( 'jetpack_learn_more_button_' . $module ) ?>
@@ -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 "<input type='hidden' name='action' value='jetpack_json_api_authorization' />\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 '<p class="message">' . sprintf(
+ esc_html__( '%s wants to access your site&#8217;s data. Log in to authorize that access.' , 'jetpack'),
+ '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
+ ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
+ }
}
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 );
- 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;
+ if ( !isset( $this->sync[$object] ) ) {
+ $this->sync[$object] = array();
+ }
+
+ // 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 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 comment( $id, $fields = true ) {
- if ( !$comment = get_comment( $id ) ) {
+ 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';
+ }
+
+ return $status;
+ }
+
+ function transition_comment_status_action( $new_status, $old_status, $comment ) {
+ $post = get_post( $comment->comment_post_ID );
+ if ( !$post ) {
return false;
}
- if ( !$comment->comment_post_ID ) {
+
+ 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;
}
- if ( !$this->post( $comment->comment_post_ID, false ) ) {
+
+ 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;
}
- return $this->register( 'comment', (int) $id, $fields );
+
+ $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
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-ar.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-az.mo b/plugins/jetpack/languages/jetpack-az.mo
index 40f5771b..033f8593 100644
--- a/plugins/jetpack/languages/jetpack-az.mo
+++ b/plugins/jetpack/languages/jetpack-az.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-bs_BA.mo b/plugins/jetpack/languages/jetpack-bs_BA.mo
index e53b9559..67cd4468 100644
--- a/plugins/jetpack/languages/jetpack-bs_BA.mo
+++ b/plugins/jetpack/languages/jetpack-bs_BA.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-ca.mo b/plugins/jetpack/languages/jetpack-ca.mo
index 9a25543b..2daa9e1c 100644
--- a/plugins/jetpack/languages/jetpack-ca.mo
+++ b/plugins/jetpack/languages/jetpack-ca.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-cs_CZ.mo b/plugins/jetpack/languages/jetpack-cs_CZ.mo
index 0977a8b9..5e088773 100644
--- a/plugins/jetpack/languages/jetpack-cs_CZ.mo
+++ b/plugins/jetpack/languages/jetpack-cs_CZ.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-da_DK.mo b/plugins/jetpack/languages/jetpack-da_DK.mo
index dd4a5ae2..773e5b51 100644
--- a/plugins/jetpack/languages/jetpack-da_DK.mo
+++ b/plugins/jetpack/languages/jetpack-da_DK.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-de_DE.mo b/plugins/jetpack/languages/jetpack-de_DE.mo
index c21d78b2..a93c1c7c 100644
--- a/plugins/jetpack/languages/jetpack-de_DE.mo
+++ b/plugins/jetpack/languages/jetpack-de_DE.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-el.mo b/plugins/jetpack/languages/jetpack-el.mo
new file mode 100644
index 00000000..8538e68c
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-el.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-es_ES.mo b/plugins/jetpack/languages/jetpack-es_ES.mo
index 9102fc81..3cc9243e 100644
--- a/plugins/jetpack/languages/jetpack-es_ES.mo
+++ b/plugins/jetpack/languages/jetpack-es_ES.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-fa_IR.mo b/plugins/jetpack/languages/jetpack-fa_IR.mo
index 864aa9bd..f34c74e6 100644
--- a/plugins/jetpack/languages/jetpack-fa_IR.mo
+++ b/plugins/jetpack/languages/jetpack-fa_IR.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-fi.mo b/plugins/jetpack/languages/jetpack-fi.mo
index fae02a3e..591c0472 100644
--- a/plugins/jetpack/languages/jetpack-fi.mo
+++ b/plugins/jetpack/languages/jetpack-fi.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-fr_FR.mo b/plugins/jetpack/languages/jetpack-fr_FR.mo
index 15385ae5..165c2b9e 100644
--- a/plugins/jetpack/languages/jetpack-fr_FR.mo
+++ b/plugins/jetpack/languages/jetpack-fr_FR.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-gl_ES.mo b/plugins/jetpack/languages/jetpack-gl_ES.mo
index 1fb3e4c4..e5f4c09a 100644
--- a/plugins/jetpack/languages/jetpack-gl_ES.mo
+++ b/plugins/jetpack/languages/jetpack-gl_ES.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-he_IL.mo b/plugins/jetpack/languages/jetpack-he_IL.mo
index 3bbb699d..90d820c7 100644
--- a/plugins/jetpack/languages/jetpack-he_IL.mo
+++ b/plugins/jetpack/languages/jetpack-he_IL.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-hr.mo b/plugins/jetpack/languages/jetpack-hr.mo
index 6dfab5c0..8765f205 100644
--- a/plugins/jetpack/languages/jetpack-hr.mo
+++ b/plugins/jetpack/languages/jetpack-hr.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-hu_HU.mo b/plugins/jetpack/languages/jetpack-hu_HU.mo
index f9e1b630..11715656 100644
--- a/plugins/jetpack/languages/jetpack-hu_HU.mo
+++ b/plugins/jetpack/languages/jetpack-hu_HU.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-id_ID.mo b/plugins/jetpack/languages/jetpack-id_ID.mo
index 3e16a67d..8d3427dd 100644
--- a/plugins/jetpack/languages/jetpack-id_ID.mo
+++ b/plugins/jetpack/languages/jetpack-id_ID.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-it_IT.mo b/plugins/jetpack/languages/jetpack-it_IT.mo
index 1aa7c72a..7165de10 100644
--- a/plugins/jetpack/languages/jetpack-it_IT.mo
+++ b/plugins/jetpack/languages/jetpack-it_IT.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-ja.mo b/plugins/jetpack/languages/jetpack-ja.mo
index 82c715ef..f715cc91 100644
--- a/plugins/jetpack/languages/jetpack-ja.mo
+++ b/plugins/jetpack/languages/jetpack-ja.mo
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-ko_KR.mo
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-lt_LT.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-mk_MK.mo b/plugins/jetpack/languages/jetpack-mk_MK.mo
index a567c77d..804e8a3b 100644
--- a/plugins/jetpack/languages/jetpack-mk_MK.mo
+++ b/plugins/jetpack/languages/jetpack-mk_MK.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-my_MM.mo b/plugins/jetpack/languages/jetpack-my_MM.mo
index ba1e6945..1d1604b7 100644
--- a/plugins/jetpack/languages/jetpack-my_MM.mo
+++ b/plugins/jetpack/languages/jetpack-my_MM.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-nb_NO.mo b/plugins/jetpack/languages/jetpack-nb_NO.mo
index 7c6bf66c..5600cf1d 100644
--- a/plugins/jetpack/languages/jetpack-nb_NO.mo
+++ b/plugins/jetpack/languages/jetpack-nb_NO.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-nl_NL.mo b/plugins/jetpack/languages/jetpack-nl_NL.mo
index 9bd9ffb3..a125514d 100644
--- a/plugins/jetpack/languages/jetpack-nl_NL.mo
+++ b/plugins/jetpack/languages/jetpack-nl_NL.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-nn_NO.mo b/plugins/jetpack/languages/jetpack-nn_NO.mo
index f5061e15..1157c955 100644
--- a/plugins/jetpack/languages/jetpack-nn_NO.mo
+++ b/plugins/jetpack/languages/jetpack-nn_NO.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-pl_PL.mo b/plugins/jetpack/languages/jetpack-pl_PL.mo
index c8ba1dd6..3a5df93c 100644
--- a/plugins/jetpack/languages/jetpack-pl_PL.mo
+++ b/plugins/jetpack/languages/jetpack-pl_PL.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-pt_BR.mo b/plugins/jetpack/languages/jetpack-pt_BR.mo
index b32d48f8..da51355f 100644
--- a/plugins/jetpack/languages/jetpack-pt_BR.mo
+++ b/plugins/jetpack/languages/jetpack-pt_BR.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-pt_PT.mo b/plugins/jetpack/languages/jetpack-pt_PT.mo
index 226b63d2..62d6331b 100644
--- a/plugins/jetpack/languages/jetpack-pt_PT.mo
+++ b/plugins/jetpack/languages/jetpack-pt_PT.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-ro_RO.mo b/plugins/jetpack/languages/jetpack-ro_RO.mo
index a3100506..6a76116e 100644
--- a/plugins/jetpack/languages/jetpack-ro_RO.mo
+++ b/plugins/jetpack/languages/jetpack-ro_RO.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-ru_RU.mo b/plugins/jetpack/languages/jetpack-ru_RU.mo
index 7c3dc2dd..392de6d0 100644
--- a/plugins/jetpack/languages/jetpack-ru_RU.mo
+++ b/plugins/jetpack/languages/jetpack-ru_RU.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-sa_IN.mo b/plugins/jetpack/languages/jetpack-sa_IN.mo
index 10f549d0..f7bfee62 100644
--- a/plugins/jetpack/languages/jetpack-sa_IN.mo
+++ b/plugins/jetpack/languages/jetpack-sa_IN.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-sk_SK.mo b/plugins/jetpack/languages/jetpack-sk_SK.mo
index b9360173..0f6a8ea8 100644
--- a/plugins/jetpack/languages/jetpack-sk_SK.mo
+++ b/plugins/jetpack/languages/jetpack-sk_SK.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-sq.mo b/plugins/jetpack/languages/jetpack-sq.mo
index 39c037cf..795f5abb 100644
--- a/plugins/jetpack/languages/jetpack-sq.mo
+++ b/plugins/jetpack/languages/jetpack-sq.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-sr_RS.mo b/plugins/jetpack/languages/jetpack-sr_RS.mo
index 9e7db3c6..62d2959a 100644
--- a/plugins/jetpack/languages/jetpack-sr_RS.mo
+++ b/plugins/jetpack/languages/jetpack-sr_RS.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-sv_SE.mo b/plugins/jetpack/languages/jetpack-sv_SE.mo
index ca682a7c..382cbc3e 100644
--- a/plugins/jetpack/languages/jetpack-sv_SE.mo
+++ b/plugins/jetpack/languages/jetpack-sv_SE.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-th.mo b/plugins/jetpack/languages/jetpack-th.mo
new file mode 100644
index 00000000..13499189
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-th.mo
Binary files differ
diff --git a/plugins/jetpack/languages/jetpack-tr_TR.mo b/plugins/jetpack/languages/jetpack-tr_TR.mo
index 83a6b84e..24eeccec 100644
--- a/plugins/jetpack/languages/jetpack-tr_TR.mo
+++ b/plugins/jetpack/languages/jetpack-tr_TR.mo
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-zh_CN.mo
Binary files 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
--- /dev/null
+++ b/plugins/jetpack/languages/jetpack-zh_TW.mo
Binary files 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() {
?>
<table class="form-table">
<tr valign="top">
- <th scope="row"> <a name="atd"></a> <?php _e( 'Proofreading', 'jetpack' ); ?></th>
+ <th scope="row"> <a id="atd"></a> <?php _e( 'Proofreading', 'jetpack' ); ?></th>
<td>
<p><?php _e( 'Automatically proofread content when:', 'jetpack' ); ?>
@@ -86,7 +86,7 @@ function AtD_display_options_form() {
<p style="font-weight: bold"><?php _e( 'Language', 'jetpack' ); ?></font>
<p><?php printf(
- _x( 'The proofreader supports English, French, German, Portuguese, and Spanish. Your <a href="%1$s">%2%s</a> 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 <a href="%1$s">%2$s</a> 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'
); ?></p>
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 = '<a class="jp-carousel-commentlink" href="#">' + jetpackCarouselStrings.comment + '</a>';
buttons = $('<div class="jp-carousel-buttons">' + buttons + '</div>');
-
+
caption = $('<h2></h2>');
photo_info = $('<div class="jp-carousel-photo-info"></div>').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 = $('<div></div>')
.addClass('jp-carousel-left-column-wrapper')
.css({
@@ -147,7 +155,7 @@ jQuery(document).ready(function($) {
fadeaway = $('<div></div>')
.addClass('jp-carousel-fadeaway');
-
+
info = $('<div></div>')
.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 = $("<div><span></span></div>")
@@ -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 = $("<div><span></span></div>")
.addClass('jp-carousel-previous-button')
.css({
@@ -190,7 +203,7 @@ jQuery(document).ready(function($) {
'top' : '40px',
'bottom' : targetBottomPos
});
-
+
gallery = $('<div></div>')
.addClass('jp-carousel')
.css({
@@ -205,20 +218,20 @@ jQuery(document).ready(function($) {
.css({
position : 'fixed'
});
-
+
container = $("<div></div>")
.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();