diff options
author | 2013-03-10 12:10:26 +0100 | |
---|---|---|
committer | 2013-03-10 12:10:26 +0100 | |
commit | 7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7 (patch) | |
tree | 68c852c654cef340592f1001b6310e33827b130c /plugins/jetpack | |
parent | Make the script more silent (diff) | |
download | blogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.tar.gz blogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.tar.bz2 blogs-gentoo-7aea9fc04bd42e2ac02a1925d3a02a76d391c3e7.zip |
update plugins
Diffstat (limited to 'plugins/jetpack')
227 files changed, 43057 insertions, 2708 deletions
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 Binary files differindex db87b2d5..11f42042 100644 --- a/plugins/jetpack/_inc/images/module-icons-sprite-2x.png +++ b/plugins/jetpack/_inc/images/module-icons-sprite-2x.png diff --git a/plugins/jetpack/_inc/images/module-icons-sprite.png b/plugins/jetpack/_inc/images/module-icons-sprite.png Binary files differindex 44de3b9e..c6979f67 100644 --- a/plugins/jetpack/_inc/images/module-icons-sprite.png +++ b/plugins/jetpack/_inc/images/module-icons-sprite.png diff --git a/plugins/jetpack/_inc/images/publicize.png b/plugins/jetpack/_inc/images/publicize.png Binary files differnew file mode 100644 index 00000000..428b886c --- /dev/null +++ b/plugins/jetpack/_inc/images/publicize.png diff --git a/plugins/jetpack/_inc/images/screenshots/carousel.png b/plugins/jetpack/_inc/images/screenshots/carousel.png Binary files differnew file mode 100644 index 00000000..5bcc94cd --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/carousel.png diff --git a/plugins/jetpack/_inc/images/screenshots/custom-css.png b/plugins/jetpack/_inc/images/screenshots/custom-css.png Binary files differnew file mode 100644 index 00000000..4be5cb22 --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/custom-css.png diff --git a/plugins/jetpack/_inc/images/screenshots/likes.png b/plugins/jetpack/_inc/images/screenshots/likes.png Binary files differnew file mode 100644 index 00000000..1c7670a3 --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/likes.png diff --git a/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg b/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg Binary files differnew file mode 100644 index 00000000..94ca6dd6 --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/mobile-push-notifications.jpg diff --git a/plugins/jetpack/_inc/images/screenshots/mobile-theme.png b/plugins/jetpack/_inc/images/screenshots/mobile-theme.png Binary files differnew file mode 100644 index 00000000..88bad2d6 --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/mobile-theme.png diff --git a/plugins/jetpack/_inc/images/screenshots/notes.png b/plugins/jetpack/_inc/images/screenshots/notes.png Binary files differnew file mode 100644 index 00000000..4506db17 --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/notes.png diff --git a/plugins/jetpack/_inc/images/screenshots/post-by-email.png b/plugins/jetpack/_inc/images/screenshots/post-by-email.png Binary files differnew file mode 100644 index 00000000..b114088c --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/post-by-email.png diff --git a/plugins/jetpack/_inc/images/screenshots/publicize.png b/plugins/jetpack/_inc/images/screenshots/publicize.png Binary files differnew file mode 100644 index 00000000..428b886c --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/publicize.png diff --git a/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png b/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png Binary files differnew file mode 100644 index 00000000..8168590d --- /dev/null +++ b/plugins/jetpack/_inc/images/screenshots/tiled-gallery.png 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">×</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  s + $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 “%1$s” plugin.', 'jetpack' ), $deactivate_me[1] ), false ); + Jetpack::bail_on_activation( sprintf( __( 'Jetpack contains the most recent version of the old “%1$s” plugin.', 'jetpack' ), $deactivate_me[1] ), false ); } } } @@ -1172,7 +1331,7 @@ p { && ( $new_modules_count = count( $new_modules ) ) && - $this->is_active() + Jetpack::is_active() ) { $new_modules_count_i18n = number_format_i18n( $new_modules_count ); $span_title = esc_attr( sprintf( _n( 'One New Jetpack Module', '%s New Jetpack Modules', $new_modules_count, 'jetpack' ), $new_modules_count_i18n ) ); @@ -1181,7 +1340,7 @@ p { $title = __( 'Jetpack', 'jetpack' ); } - $hook = add_menu_page( 'Jetpack', $title, 'manage_options', 'jetpack', array( $this, 'admin_page' ), 'div' ); + $hook = add_menu_page( 'Jetpack', $title, 'read', 'jetpack', array( $this, 'admin_page' ), 'div' ); add_action( "load-$hook", array( $this, 'admin_page_load' ) ); @@ -1203,6 +1362,126 @@ p { do_action( 'jetpack_admin_menu' ); } + function add_remote_request_handlers() { + add_action( 'wp_ajax_nopriv_jetpack_upload_file', array( $this, 'remote_request_handlers' ) ); + } + + function remote_request_handlers() { + switch ( current_filter() ) { + case 'wp_ajax_nopriv_jetpack_upload_file' : + $response = $this->upload_handler(); + break; + default : + $response = new Jetpack_Error( 'unknown_handler', 'Unknown Handler', 400 ); + break; + } + + if ( !$response ) { + $response = new Jetpack_Error( 'unknown_error', 'Unknown Error', 400 ); + } + + if ( is_wp_error( $response ) ) { + $status_code = $response->get_error_data(); + $error = $response->get_error_code(); + $error_description = $response->get_error_message(); + + if ( !is_int( $status_code ) ) { + $status_code = 400; + } + + status_header( $status_code ); + die( json_encode( (object) compact( 'error', 'error_description' ) ) ); + } + + status_header( 200 ); + if ( true === $response ) { + exit; + } + + die( json_encode( (object) $response ) ); + } + + function upload_handler() { + if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { + return new Jetpack_Error( 405, get_status_header_desc( 405 ), 405 ); + } + + $user = wp_authenticate( '', '' ); + if ( !$user || is_wp_error( $user ) ) { + return new Jetpack_Error( 403, get_status_header_desc( 403 ), 403 ); + } + + wp_set_current_user( $user->ID ); + + if ( !current_user_can( 'upload_files' ) ) { + return new Jetpack_Error( 'cannot_upload_files', 'User does not have permission to upload files', 403 ); + } + + if ( empty( $_FILES ) ) { + return new Jetpack_Error( 'no_files_uploaded', 'No files were uploaded: nothing to process', 400 ); + } + + foreach ( array_keys( $_FILES ) as $files_key ) { + if ( !isset( $_POST["_jetpack_file_hmac_{$files_key}"] ) ) { + return new Jetpack_Error( 'missing_hmac', 'An HMAC for one or more files is missing', 400 ); + } + } + + $media_keys = array_keys( $_FILES['media'] ); + + $token = Jetpack_Data::get_access_token( get_current_user_id() ); + if ( !$token || is_wp_error( $token ) ) { + return new Jetpack_Error( 'unknown_token', 'Unknown Jetpack token', 403 ); + } + + $uploaded_files = array(); + $global_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; + unset( $GLOBALS['post'] ); + foreach ( $_FILES['media']['name'] as $index => $name ) { + $file = array(); + foreach ( $media_keys as $media_key ) { + $file[$media_key] = $_FILES['media'][$media_key][$index]; + } + + list( $hmac_provided, $salt ) = explode( ':', $_POST['_jetpack_file_hmac_media'][$index] ); + + $hmac_file = hash_hmac_file( 'sha1', $file['tmp_name'], $salt . $token->secret ); + if ( $hmac_provided !== $hmac_file ) { + $uploaded_files[$index] = (object) array( 'error' => 'invalid_hmac', 'error_description' => 'The corresponding HMAC for this file does not match' ); + continue; + } + + $_FILES['.jetpack.upload.'] = $file; + $post_id = isset( $_POST['post_id'][$index] ) ? absint( $_POST['post_id'][$index] ) : 0; + if ( !current_user_can( 'edit_post', $post_id ) ) { + $post_id = 0; + } + $attachment_id = media_handle_upload( '.jetpack.upload.', $post_id, array(), array( + 'action' => 'jetpack_upload_file', + ) ); + + if ( !$attachment_id ) { + $uploaded_files[$index] = (object) array( 'error' => 'unknown', 'error_description' => 'An unknown problem occurred processing the upload on the Jetpack site' ); + } elseif ( is_wp_error( $attachment_id ) ) { + $uploaded_files[$index] = (object) array( 'error' => 'attachment_' . $attachment_id->get_error_code(), 'error_description' => $attachment_id->get_error_message() ); + } else { + $attachment = get_post( $attachment_id ); + $uploaded_files[$index] = (object) array( + 'id' => (string) $attachment_id, + 'file' => $attachment->post_title, + 'url' => wp_get_attachment_url( $attachment_id ), + 'type' => $attachment->post_mime_type, + 'meta' => wp_get_attachment_metadata( $attachment_id ), + ); + } + } + if ( !is_null( $global_post ) ) { + $GLOBALS['post'] = $global_post; + } + + return $uploaded_files; + } + /** * Add help to the Jetpack page * @@ -1240,19 +1519,21 @@ p { ) ); // Screen Content - $current_screen->add_help_tab( array( - 'id' => 'modules', - 'title' => __( 'Modules', 'jetpack' ), - 'content' => - '<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> – 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> – 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 .= '¬es_iframe'; + wp_redirect( $connect_url ); + exit; + } else { + Jetpack::state( 'message', 'already_authorized' ); + wp_safe_redirect( Jetpack::admin_url() ); + exit; + } + } + if ( isset( $_GET['action'] ) ) { switch ( $_GET['action'] ) { case 'authorize' : - if ( Jetpack::is_active() ) { + if ( Jetpack::is_active() && Jetpack::is_user_connected() ) { Jetpack::state( 'message', 'already_authorized' ); wp_safe_redirect( Jetpack::admin_url() ); exit; @@ -1495,22 +1792,30 @@ p { exit; case 'disconnect' : check_admin_referer( 'jetpack-disconnect' ); - $this->disconnect(); + Jetpack::disconnect(); wp_safe_redirect( Jetpack::admin_url() ); exit; case 'deactivate' : - $module = stripslashes( $_GET['module'] ); - check_admin_referer( "jetpack_deactivate-$module" ); - Jetpack::deactivate_module( $module ); - Jetpack::state( 'message', 'module_deactivated' ); - Jetpack::state( 'module', $module ); + $modules = stripslashes( $_GET['module'] ); + check_admin_referer( "jetpack_deactivate-$modules" ); + foreach ( explode( ',', $modules ) as $module ) { + Jetpack::deactivate_module( $module ); + Jetpack::state( 'message', 'module_deactivated' ); + } + Jetpack::state( 'module', $modules ); + wp_safe_redirect( Jetpack::admin_url() ); + exit; + case 'unlink' : + check_admin_referer( 'jetpack-unlink' ); + $this->unlink_user(); + Jetpack::state( 'message', 'unlinked' ); wp_safe_redirect( Jetpack::admin_url() ); exit; } } if ( !$error = $error ? $error : Jetpack::state( 'error' ) ) { - Jetpack::activate_new_modules(); + $this->activate_new_modules(); } switch ( $error ) { @@ -1663,10 +1968,35 @@ p { break; case 'module_deactivated' : - if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) { - $this->message = sprintf( __( '<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’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’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> + <?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> <?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> <?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> <?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’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 Binary files differnew file mode 100644 index 00000000..254be8e6 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-ar.mo diff --git a/plugins/jetpack/languages/jetpack-az.mo b/plugins/jetpack/languages/jetpack-az.mo Binary files differindex 40f5771b..033f8593 100644 --- a/plugins/jetpack/languages/jetpack-az.mo +++ b/plugins/jetpack/languages/jetpack-az.mo diff --git a/plugins/jetpack/languages/jetpack-bs_BA.mo b/plugins/jetpack/languages/jetpack-bs_BA.mo Binary files differindex e53b9559..67cd4468 100644 --- a/plugins/jetpack/languages/jetpack-bs_BA.mo +++ b/plugins/jetpack/languages/jetpack-bs_BA.mo diff --git a/plugins/jetpack/languages/jetpack-ca.mo b/plugins/jetpack/languages/jetpack-ca.mo Binary files differindex 9a25543b..2daa9e1c 100644 --- a/plugins/jetpack/languages/jetpack-ca.mo +++ b/plugins/jetpack/languages/jetpack-ca.mo diff --git a/plugins/jetpack/languages/jetpack-cs_CZ.mo b/plugins/jetpack/languages/jetpack-cs_CZ.mo Binary files differindex 0977a8b9..5e088773 100644 --- a/plugins/jetpack/languages/jetpack-cs_CZ.mo +++ b/plugins/jetpack/languages/jetpack-cs_CZ.mo diff --git a/plugins/jetpack/languages/jetpack-da_DK.mo b/plugins/jetpack/languages/jetpack-da_DK.mo Binary files differindex dd4a5ae2..773e5b51 100644 --- a/plugins/jetpack/languages/jetpack-da_DK.mo +++ b/plugins/jetpack/languages/jetpack-da_DK.mo diff --git a/plugins/jetpack/languages/jetpack-de_DE.mo b/plugins/jetpack/languages/jetpack-de_DE.mo Binary files differindex c21d78b2..a93c1c7c 100644 --- a/plugins/jetpack/languages/jetpack-de_DE.mo +++ b/plugins/jetpack/languages/jetpack-de_DE.mo diff --git a/plugins/jetpack/languages/jetpack-el.mo b/plugins/jetpack/languages/jetpack-el.mo Binary files differnew file mode 100644 index 00000000..8538e68c --- /dev/null +++ b/plugins/jetpack/languages/jetpack-el.mo diff --git a/plugins/jetpack/languages/jetpack-es_ES.mo b/plugins/jetpack/languages/jetpack-es_ES.mo Binary files differindex 9102fc81..3cc9243e 100644 --- a/plugins/jetpack/languages/jetpack-es_ES.mo +++ b/plugins/jetpack/languages/jetpack-es_ES.mo diff --git a/plugins/jetpack/languages/jetpack-fa_IR.mo b/plugins/jetpack/languages/jetpack-fa_IR.mo Binary files differindex 864aa9bd..f34c74e6 100644 --- a/plugins/jetpack/languages/jetpack-fa_IR.mo +++ b/plugins/jetpack/languages/jetpack-fa_IR.mo diff --git a/plugins/jetpack/languages/jetpack-fi.mo b/plugins/jetpack/languages/jetpack-fi.mo Binary files differindex fae02a3e..591c0472 100644 --- a/plugins/jetpack/languages/jetpack-fi.mo +++ b/plugins/jetpack/languages/jetpack-fi.mo diff --git a/plugins/jetpack/languages/jetpack-fr_FR.mo b/plugins/jetpack/languages/jetpack-fr_FR.mo Binary files differindex 15385ae5..165c2b9e 100644 --- a/plugins/jetpack/languages/jetpack-fr_FR.mo +++ b/plugins/jetpack/languages/jetpack-fr_FR.mo diff --git a/plugins/jetpack/languages/jetpack-gl_ES.mo b/plugins/jetpack/languages/jetpack-gl_ES.mo Binary files differindex 1fb3e4c4..e5f4c09a 100644 --- a/plugins/jetpack/languages/jetpack-gl_ES.mo +++ b/plugins/jetpack/languages/jetpack-gl_ES.mo diff --git a/plugins/jetpack/languages/jetpack-he_IL.mo b/plugins/jetpack/languages/jetpack-he_IL.mo Binary files differindex 3bbb699d..90d820c7 100644 --- a/plugins/jetpack/languages/jetpack-he_IL.mo +++ b/plugins/jetpack/languages/jetpack-he_IL.mo diff --git a/plugins/jetpack/languages/jetpack-hr.mo b/plugins/jetpack/languages/jetpack-hr.mo Binary files differindex 6dfab5c0..8765f205 100644 --- a/plugins/jetpack/languages/jetpack-hr.mo +++ b/plugins/jetpack/languages/jetpack-hr.mo diff --git a/plugins/jetpack/languages/jetpack-hu_HU.mo b/plugins/jetpack/languages/jetpack-hu_HU.mo Binary files differindex f9e1b630..11715656 100644 --- a/plugins/jetpack/languages/jetpack-hu_HU.mo +++ b/plugins/jetpack/languages/jetpack-hu_HU.mo diff --git a/plugins/jetpack/languages/jetpack-id_ID.mo b/plugins/jetpack/languages/jetpack-id_ID.mo Binary files differindex 3e16a67d..8d3427dd 100644 --- a/plugins/jetpack/languages/jetpack-id_ID.mo +++ b/plugins/jetpack/languages/jetpack-id_ID.mo diff --git a/plugins/jetpack/languages/jetpack-it_IT.mo b/plugins/jetpack/languages/jetpack-it_IT.mo Binary files differindex 1aa7c72a..7165de10 100644 --- a/plugins/jetpack/languages/jetpack-it_IT.mo +++ b/plugins/jetpack/languages/jetpack-it_IT.mo diff --git a/plugins/jetpack/languages/jetpack-ja.mo b/plugins/jetpack/languages/jetpack-ja.mo Binary files differindex 82c715ef..f715cc91 100644 --- a/plugins/jetpack/languages/jetpack-ja.mo +++ b/plugins/jetpack/languages/jetpack-ja.mo diff --git a/plugins/jetpack/languages/jetpack-ko_KR.mo b/plugins/jetpack/languages/jetpack-ko_KR.mo Binary files differnew file mode 100644 index 00000000..3a7a5295 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-ko_KR.mo diff --git a/plugins/jetpack/languages/jetpack-lt_LT.mo b/plugins/jetpack/languages/jetpack-lt_LT.mo Binary files differnew file mode 100644 index 00000000..55f190a5 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-lt_LT.mo diff --git a/plugins/jetpack/languages/jetpack-mk_MK.mo b/plugins/jetpack/languages/jetpack-mk_MK.mo Binary files differindex a567c77d..804e8a3b 100644 --- a/plugins/jetpack/languages/jetpack-mk_MK.mo +++ b/plugins/jetpack/languages/jetpack-mk_MK.mo diff --git a/plugins/jetpack/languages/jetpack-my_MM.mo b/plugins/jetpack/languages/jetpack-my_MM.mo Binary files differindex ba1e6945..1d1604b7 100644 --- a/plugins/jetpack/languages/jetpack-my_MM.mo +++ b/plugins/jetpack/languages/jetpack-my_MM.mo diff --git a/plugins/jetpack/languages/jetpack-nb_NO.mo b/plugins/jetpack/languages/jetpack-nb_NO.mo Binary files differindex 7c6bf66c..5600cf1d 100644 --- a/plugins/jetpack/languages/jetpack-nb_NO.mo +++ b/plugins/jetpack/languages/jetpack-nb_NO.mo diff --git a/plugins/jetpack/languages/jetpack-nl_NL.mo b/plugins/jetpack/languages/jetpack-nl_NL.mo Binary files differindex 9bd9ffb3..a125514d 100644 --- a/plugins/jetpack/languages/jetpack-nl_NL.mo +++ b/plugins/jetpack/languages/jetpack-nl_NL.mo diff --git a/plugins/jetpack/languages/jetpack-nn_NO.mo b/plugins/jetpack/languages/jetpack-nn_NO.mo Binary files differindex f5061e15..1157c955 100644 --- a/plugins/jetpack/languages/jetpack-nn_NO.mo +++ b/plugins/jetpack/languages/jetpack-nn_NO.mo diff --git a/plugins/jetpack/languages/jetpack-pl_PL.mo b/plugins/jetpack/languages/jetpack-pl_PL.mo Binary files differindex c8ba1dd6..3a5df93c 100644 --- a/plugins/jetpack/languages/jetpack-pl_PL.mo +++ b/plugins/jetpack/languages/jetpack-pl_PL.mo diff --git a/plugins/jetpack/languages/jetpack-pt_BR.mo b/plugins/jetpack/languages/jetpack-pt_BR.mo Binary files differindex b32d48f8..da51355f 100644 --- a/plugins/jetpack/languages/jetpack-pt_BR.mo +++ b/plugins/jetpack/languages/jetpack-pt_BR.mo diff --git a/plugins/jetpack/languages/jetpack-pt_PT.mo b/plugins/jetpack/languages/jetpack-pt_PT.mo Binary files differindex 226b63d2..62d6331b 100644 --- a/plugins/jetpack/languages/jetpack-pt_PT.mo +++ b/plugins/jetpack/languages/jetpack-pt_PT.mo diff --git a/plugins/jetpack/languages/jetpack-ro_RO.mo b/plugins/jetpack/languages/jetpack-ro_RO.mo Binary files differindex a3100506..6a76116e 100644 --- a/plugins/jetpack/languages/jetpack-ro_RO.mo +++ b/plugins/jetpack/languages/jetpack-ro_RO.mo diff --git a/plugins/jetpack/languages/jetpack-ru_RU.mo b/plugins/jetpack/languages/jetpack-ru_RU.mo Binary files differindex 7c3dc2dd..392de6d0 100644 --- a/plugins/jetpack/languages/jetpack-ru_RU.mo +++ b/plugins/jetpack/languages/jetpack-ru_RU.mo diff --git a/plugins/jetpack/languages/jetpack-sa_IN.mo b/plugins/jetpack/languages/jetpack-sa_IN.mo Binary files differindex 10f549d0..f7bfee62 100644 --- a/plugins/jetpack/languages/jetpack-sa_IN.mo +++ b/plugins/jetpack/languages/jetpack-sa_IN.mo diff --git a/plugins/jetpack/languages/jetpack-sk_SK.mo b/plugins/jetpack/languages/jetpack-sk_SK.mo Binary files differindex b9360173..0f6a8ea8 100644 --- a/plugins/jetpack/languages/jetpack-sk_SK.mo +++ b/plugins/jetpack/languages/jetpack-sk_SK.mo diff --git a/plugins/jetpack/languages/jetpack-sq.mo b/plugins/jetpack/languages/jetpack-sq.mo Binary files differindex 39c037cf..795f5abb 100644 --- a/plugins/jetpack/languages/jetpack-sq.mo +++ b/plugins/jetpack/languages/jetpack-sq.mo diff --git a/plugins/jetpack/languages/jetpack-sr_RS.mo b/plugins/jetpack/languages/jetpack-sr_RS.mo Binary files differindex 9e7db3c6..62d2959a 100644 --- a/plugins/jetpack/languages/jetpack-sr_RS.mo +++ b/plugins/jetpack/languages/jetpack-sr_RS.mo diff --git a/plugins/jetpack/languages/jetpack-sv_SE.mo b/plugins/jetpack/languages/jetpack-sv_SE.mo Binary files differindex ca682a7c..382cbc3e 100644 --- a/plugins/jetpack/languages/jetpack-sv_SE.mo +++ b/plugins/jetpack/languages/jetpack-sv_SE.mo diff --git a/plugins/jetpack/languages/jetpack-th.mo b/plugins/jetpack/languages/jetpack-th.mo Binary files differnew file mode 100644 index 00000000..13499189 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-th.mo diff --git a/plugins/jetpack/languages/jetpack-tr_TR.mo b/plugins/jetpack/languages/jetpack-tr_TR.mo Binary files differindex 83a6b84e..24eeccec 100644 --- a/plugins/jetpack/languages/jetpack-tr_TR.mo +++ b/plugins/jetpack/languages/jetpack-tr_TR.mo diff --git a/plugins/jetpack/languages/jetpack-zh_CN.mo b/plugins/jetpack/languages/jetpack-zh_CN.mo Binary files differnew file mode 100644 index 00000000..17900fb1 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-zh_CN.mo diff --git a/plugins/jetpack/languages/jetpack-zh_TW.mo b/plugins/jetpack/languages/jetpack-zh_TW.mo Binary files differnew file mode 100644 index 00000000..38662136 --- /dev/null +++ b/plugins/jetpack/languages/jetpack-zh_TW.mo 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(); + last.jp_carousel('setSlidePosition', -last.width()); + last.prev().jp_carousel('setSlidePosition', -last.width() - last.prev().width()); + slides.first().jp_carousel('setSlidePosition', gallery.width()); setTimeout(function(){ gallery.jp_carousel('selectSlide', slides.show().first()); }, 400); } else { first = slides.first(); - first.css({ - 'left':gallery.width() - }); - first.next().css({ - 'left':gallery.width() + first.width() - }); - first.next().nextAll().hide().css({'left':-slides.last().width()}); - slides.last().css({'left':-slides.last().width()}); - slides.last().prevAll().not(first, first.next()).hide().css({'left':-slides.last().width()-slides.last().prev().width()}); + first.jp_carousel('setSlidePosition', gallery.width()); + first.next().jp_carousel('setSlidePosition', gallery.width() + first.width()); + first.next().nextAll().hide().jp_carousel('setSlidePosition', -slides.last().width()); + slides.last().jp_carousel('setSlidePosition', -slides.last().width()); + slides.last().prevAll().not(first, first.next()).hide().jp_carousel('setSlidePosition', -slides.last().width()-slides.last().prev().width()); setTimeout(function(){ gallery.jp_carousel('selectSlide', slides.show().last()); }, 400); @@ -507,6 +543,16 @@ jQuery(document).ready(function($) { return this.find('.selected'); }, + setSlidePosition : function(x) { + return this.css({ + '-webkit-transform':'translate3d(' + x + 'px,0,0)', + '-moz-transform':'translate3d(' + x + 'px,0,0)', + '-ms-transform':'translate(' + x + 'px,0)', + '-o-transform':'translate(' + x + 'px,0)', + 'transform':'translate3d(' + x + 'px,0,0)' + }); + }, + selectSlide : function(slide, animate){ var last = this.find('.selected').removeClass('selected'), slides = gallery.jp_carousel('slides').css({'position': 'fixed'}), @@ -521,7 +567,7 @@ jQuery(document).ready(function($) { animated, info_min; // center the main image - + caption.hide(); method = 'css'; @@ -534,7 +580,7 @@ jQuery(document).ready(function($) { // slide the whole view to the x we want slides.not(animated).hide(); - current[method]({left:left}).show(); + current.jp_carousel('setSlidePosition', left).show(); // minimum width gallery.jp_carousel('fitInfo', animate); @@ -542,24 +588,24 @@ jQuery(document).ready(function($) { // prep the slides var direction = last.is(current.prevAll()) ? 1 : -1; if ( 1 == direction ) { - next_next.css({'left':gallery.width() + next.width()}).show(); - next.hide().css({'left':gallery.width() + current.width()}).show(); - previous_previous.css({'left':-previous_previous.width() - current.width()}); + next_next.jp_carousel('setSlidePosition', gallery.width() + next.width()).show(); + next.hide().jp_carousel('setSlidePosition', gallery.width() + current.width()).show(); + previous_previous.jp_carousel('setSlidePosition', -previous_previous.width() - current.width()).show(); } else { - previous.css({'left':-previous.width() - current.width()}); - next_next.css({'left':gallery.width() + current.width()}); + previous.jp_carousel('setSlidePosition', -previous.width() - current.width()).show(); + next_next.jp_carousel('setSlidePosition', gallery.width() + current.width()).show(); } - - // if advancing prepare the slide that will enter the screen - previous[method]({left:-previous.width() + (screenPadding * 0.75) }).show(); - next[method]({left:gallery.width() - (screenPadding * 0.75) }).show(); + // if advancing prepare the slide that will enter the screen + previous.jp_carousel('setSlidePosition', -previous.width() + (screenPadding * 0.75)).show(); + next.jp_carousel('setSlidePosition', gallery.width() - (screenPadding * 0.75)).show(); + next.css({'position': ''}); document.location.href = document.location.href.replace(/#.*/, '') + '#jp-carousel-' + current.data('attachment-id'); gallery.jp_carousel('resetButtons', current); container.trigger('jp_carousel.selectSlide', [current]); $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ).html(''); - + gallery.jp_carousel('getTitleDesc', { title: current.data('title'), desc: current.data('desc') } ); gallery.jp_carousel('getMeta', current.data('image-meta')); gallery.jp_carousel('getFullSizeLink', current); @@ -568,7 +614,7 @@ jQuery(document).ready(function($) { gallery.jp_carousel('getComments', {'attachment_id': current.data('attachment-id'), 'offset': 0, 'clear': true}); $('#jp-carousel-comment-post-results').slideUp(); - + // $('<div />').html(sometext).text() is a trick to go to HTML to plain text (including HTML emntities decode, etc) if ( current.data('caption') ) { if ( $('<div />').html(current.data('caption')).text() == $('<div />').html(current.data('title')).text() ) @@ -593,7 +639,7 @@ jQuery(document).ready(function($) { }; }, - loadSlide : function(){ + loadSlide : function() { return this.each(function(){ var slide = $(this); slide.find('img') @@ -642,13 +688,19 @@ jQuery(document).ready(function($) { 'left' : (info.width() - size.width) * 0.5, 'width' : size.width }); + + if (isMobile){ + photo_info.css('left', '0px'); + photo_info.css('top', '-20px'); + } + return this; }, fitMeta : function(animated){ var newInfoTop = { top: ( $(window).height() / 100 * proportion + 5 ) + 'px' }; var newLeftWidth = { width: ( info.width() - (imageMeta.width() + 80) ) + 'px' }; - + if (animated) { info.animate(newInfoTop); leftColWrapper.animate(newLeftWidth); @@ -666,27 +718,14 @@ jQuery(document).ready(function($) { method = 'css', max = gallery.jp_carousel('slideDimensions'); - if ( 0 === selected.length ) { - dimensions.left = $(window).width(); - } else if ($this.is(selected)) { - dimensions.left = ($(window).width() - dimensions.width) * 0.5; - } else if ($this.is(selected.next())) { - dimensions.left = gallery.width() - ( screenPadding * 0.75 ); - } else if ($this.is(selected.prev())) { - dimensions.left = -dimensions.width + screenPadding * 0.75; - } else { - if ($this.is(selected.nextAll())) { - dimensions.left = $(window).width(); - } else { - dimensions.left = -dimensions.width; - } - } + dimensions.left = 0; dimensions.top = ( (max.height - dimensions.height) * 0.5 ) + 40; $this[method](dimensions); }); }, texturize : function(text) { + text = new String(text); // make sure we get a string. Title "1" came in as int 1, for example, which did not support .replace(). text = text.replace(/'/g, '’').replace(/'/g, '’').replace(/[\u2019]/g, '’'); text = text.replace(/"/g, '”').replace(/"/g, '”').replace(/"/g, '”').replace(/[\u201D]/g, '”'); text = text.replace(/([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"'); // untexturize allowed HTML tags params double-quotes @@ -700,7 +739,7 @@ jQuery(document).ready(function($) { // Calculate the new src. items.each(function(i){ var src_item = $(this), - orig_size = src_item.data('orig-size') || 0, + orig_size = src_item.data('orig-size') || '', max = gallery.jp_carousel('slideDimensions'), parts = orig_size.split(','); orig_size = {width: parseInt(parts[0], 10), height: parseInt(parts[1], 10)}, @@ -708,7 +747,7 @@ jQuery(document).ready(function($) { large_file = src_item.data('large-file') || ''; src = src_item.data('orig-file'); - + src = gallery.jp_carousel('selectBestImageSize', { orig_file : src, orig_width : orig_size.width, @@ -718,7 +757,7 @@ jQuery(document).ready(function($) { medium_file : medium_file, large_file : large_file }); - + // Set the final src $(this).data( 'gallery-src', src ); }); @@ -733,45 +772,49 @@ jQuery(document).ready(function($) { attachment_id = src_item.data('attachment-id') || 0, comments_opened = src_item.data('comments-opened') || 0, image_meta = src_item.data('image-meta') || {}, - orig_size = src_item.data('orig-size') || 0, - title = src_item.attr('title') || '', + orig_size = src_item.data('orig-size') || '', + title = src_item.data('image-title') || '', description = src_item.data('image-description') || '', caption = src_item.parents('dl').find('dd.gallery-caption').html() || '', - src = src_item.data('gallery-src') || '', + src = src_item.data('gallery-src') || '', medium_file = src_item.data('medium-file') || '', - large_file = src_item.data('large-file') || ''; - - if ( !attachment_id || !orig_size ) - return false; // break the loop if we are missing the data-* attributes - - title = gallery.jp_carousel('texturize', title); - description = gallery.jp_carousel('texturize', description); - caption = gallery.jp_carousel('texturize', caption); - - var slide = $('<div class="jp-carousel-slide"></div>') - .hide() - .css({ - 'position' : 'fixed', - 'left' : i < start_index ? -1000 : gallery.width() - }) - .append($('<img>')) - .appendTo(gallery) - .data('src', src ) - .data('title', title) - .data('desc', description) - .data('caption', caption) - .data('attachment-id', attachment_id) - .data('permalink', src_item.parents('a').attr('href')) - .data('orig-size', orig_size) - .data('comments-opened', comments_opened) - .data('image-meta', image_meta) - .data('medium-file', medium_file) - .data('large-file', large_file) - .jp_carousel('fitSlide', false); - - - // Preloading all images - slide.find('img').first().attr('src', src ); + large_file = src_item.data('large-file') || '', + orig_file = src_item.data('orig-file') || ''; + + var tiledCaption = src_item.parents('div.tiled-gallery-item').find('div.tiled-gallery-caption').html(); + if ( tiledCaption ) + caption = tiledCaption; + + if ( attachment_id && orig_size.length ) { + title = gallery.jp_carousel('texturize', title); + description = gallery.jp_carousel('texturize', description); + caption = gallery.jp_carousel('texturize', caption); + + var slide = $('<div class="jp-carousel-slide"></div>') + .hide() + .css({ + //'position' : 'fixed', + 'left' : i < start_index ? -1000 : gallery.width() + }) + .append($('<img>')) + .appendTo(gallery) + .data('src', src ) + .data('title', title) + .data('desc', description) + .data('caption', caption) + .data('attachment-id', attachment_id) + .data('permalink', src_item.parents('a').attr('href')) + .data('orig-size', orig_size) + .data('comments-opened', comments_opened) + .data('image-meta', image_meta) + .data('medium-file', medium_file) + .data('large-file', large_file) + .data('orig-file', orig_file) + .jp_carousel('fitSlide', false); + + // Preloading all images + slide.find('img').first().attr('src', src ); + } }); return this; }, @@ -779,13 +822,13 @@ jQuery(document).ready(function($) { selectBestImageSize: function(args) { if ( 'object' != typeof args ) args = {}; - + if ( 'undefined' == typeof args.orig_file ) return ''; - + if ( 'undefined' == typeof args.orig_width || 'undefined' == typeof args.max_width ) return args.orig_file; - + if ( 'undefined' == typeof args.medium_file || 'undefined' == typeof args.large_file ) return args.orig_file; @@ -797,19 +840,19 @@ jQuery(document).ready(function($) { large_size_parts = (large_size != args.large_file) ? large_size.split('x') : [args.orig_width, 0], large_width = parseInt( large_size_parts[0], 10 ), large_height = parseInt( large_size_parts[1], 10 ); - + // Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc) if ('undefined' != typeof window.devicePixelRatio && window.devicePixelRatio > 1) { args.max_width = args.max_width * window.devicePixelRatio; args.max_height = args.max_height * window.devicePixelRatio; } - if ( medium_width >= args.max_width || medium_height >= args.max_height ) - return args.medium_file; - if ( large_width >= args.max_width || large_height >= args.max_height ) return args.large_file; + if ( medium_width >= args.max_width || medium_height >= args.max_height ) + return args.medium_file; + return args.orig_file; }, @@ -826,14 +869,14 @@ jQuery(document).ready(function($) { return; if ( ! args.replacements || 'undefined' == typeof args.replacements ) return args.text; - return args.text.replace(/{(\d+)}/g, function(match, number) { + return args.text.replace(/{(\d+)}/g, function(match, number) { return typeof args.replacements[number] != 'undefined' ? args.replacements[number] : match; }); }, shutterSpeed: function(d) { if (d >= 1) - Math.round(d) + 's'; + return Math.round(d) + 's'; var df = 1, top = 1, bot = 1; var limit = 1e5; //Increase for greater precision. while (df != d && limit-- > 0) { @@ -887,16 +930,16 @@ jQuery(document).ready(function($) { }); return value; }, - + getTitleDesc: function( data ) { var title ='', desc = '', markup = '', target, commentWrappere; - + target = $( 'div.jp-carousel-titleanddesc', 'div.jp-carousel-wrap' ); target.hide(); - + title = gallery.jp_carousel('parseTitleDesc', data.title) || ''; desc = gallery.jp_carousel('parseTitleDesc', data.desc) || ''; - + if ( title.length || desc.length ) { // $('<div />').html(sometext).text() is a trick to go to HTML to plain text (including HTML emntities decode, etc) if ( $('<div />').html(title).text() == $('<div />').html(desc).text() ) @@ -911,16 +954,16 @@ jQuery(document).ready(function($) { $( 'div#jp-carousel-comment-form-container' ).css('margin-top', '20px'); $( 'div#jp-carousel-comments-loading' ).css('margin-top', '20px'); }, - + getMeta: function( meta ) { if ( !meta || 1 != jetpackCarouselStrings.display_exif ) return false; - + var $ul = $( '<ul></ul>' ); $.each( meta, function( key, val ) { if ( 0 === parseFloat(val) || !val.length || -1 === $.inArray( key, [ 'camera', 'aperture', 'shutter_speed', 'focal_length' ] ) ) return; - + switch( key ) { case 'focal_length': val = val + 'mm'; @@ -935,7 +978,7 @@ jQuery(document).ready(function($) { // making jslint happy break; } - + $ul.append( '<li><h5>' + jetpackCarouselStrings[key] + '</h5>' + val + '</li>' ); }); @@ -949,23 +992,23 @@ jQuery(document).ready(function($) { getFullSizeLink: function(current) { if(!current || !current.data) return false; - var original = current.data('src').replace(/\?.+$/, ''), + var original = current.data('orig-file').replace(/\?.+$/, ''), origSize = current.data('orig-size').split(','), permalink = $( '<a>'+gallery.jp_carousel('format', {'text': jetpackCarouselStrings.download_original, 'replacements': origSize})+'</a>' ) .addClass( 'jp-carousel-image-download' ) .attr( 'href', original ) .attr( 'target', '_blank' ); - + $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ) .append( permalink ); }, - + getMap: function( meta ) { if ( !meta.latitude || !meta.longitude || 1 != jetpackCarouselStrings.display_geo ) return; - - var latitude = meta.latitude, - longitude = meta.longitude, + + var latitude = meta.latitude, + longitude = meta.longitude, $metabox = $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ), $mapbox = $( '<div></div>' ), style = '&scale=2&style=feature:all|element:all|invert_lightness:true|hue:0x0077FF|saturation:-50|lightness:-5|gamma:0.91'; @@ -1003,23 +1046,23 @@ jQuery(document).ready(function($) { getComments: function( args ) { if ( 'object' != typeof args ) args = {}; - + if ( ! args.attachment_id || 'undefined' == typeof args.attachment_id ) return; - + if ( ! args.offset || 'undefined' == typeof args.offset || args.offset < 1 ) args.offset = 0; - + var comments = $('.jp-carousel-comments'), commentsLoading = $('#jp-carousel-comments-loading'); - + commentsLoading.show(); - + if ( args.clear ) { comments.hide(); comments.empty(); } - + $.ajax({ type: 'GET', url: jetpackCarouselStrings.ajaxurl, @@ -1056,7 +1099,7 @@ jQuery(document).ready(function($) { + '</div>' ); comments.append(comment); - + // Set the interval to check for a new page of comments. clearInterval( commentInterval ); commentInterval = setInterval( function() { @@ -1066,7 +1109,7 @@ jQuery(document).ready(function($) { } }, 150 ); }); - + // Verify (late) that the user didn't repeatldy click the arrows really fast, in which case the requested // attachment id might no longer match the current attachment id by the time we get the data back or a now // registered infiniscroll event kicks in, so we don't ever display comments for the wrong image by mistake. @@ -1079,7 +1122,7 @@ jQuery(document).ready(function($) { // Increase the height of the background, semi-transparent overlay to match the new length of the comments list. $('.jp-carousel-overlay').height( $(window).height() + titleAndDescription.height() + commentForm.height() + ( (comments.height() > 0) ? comments.height() : imageMeta.height() ) + 200 ); - + comments.show(); commentsLoading.hide(); }, @@ -1130,18 +1173,28 @@ jQuery(document).ready(function($) { }; - // register the event listener for staring the gallery - $( document.body ).on( 'click', 'div.gallery', function(e) { + // register the event listener for starting the gallery + $( document.body ).on( 'click', 'div.gallery,div.tiled-gallery', function(e) { + if ( ! $(this).jp_carousel( 'testForData', e.currentTarget ) ) + return; if ( $(e.target).parent().hasClass('gallery-caption') ) return; e.preventDefault(); - $(this).jp_carousel('open', {start_index: $(this).find('.gallery-item').index($(e.target).parents('.gallery-item'))}); + $(this).jp_carousel('open', {start_index: $(this).find('.gallery-item, .tiled-gallery-item').index($(e.target).parents('.gallery-item, .tiled-gallery-item'))}); }); - // start on page load if hash exists - if ( document.location.hash && document.location.hash.match(/jp-carousel-(\d+)/) ) { - $(document).ready(function(){ - var gallery = $('div.gallery'), index = -1, n = document.location.hash.match(/jp-carousel-(\d+)/); + // Set an interval on page load to load the carousel if hash exists and not already opened. + // Makes carousel work on page load and when back button leads to same URL with carousel hash (ie: no actual document.ready trigger) + $(document).ready(function(){ + var jp_carousel_open_interval = window.setInterval(function(){ + // We should have a URL hash by now. + if ( ! document.location.hash || ! document.location.hash.match(/jp-carousel-(\d+)/) ) + return; + + var gallery = $('div.gallery, div.tiled-gallery'), index = -1, n = document.location.hash.match(/jp-carousel-(\d+)/); + + if ( ! $(this).jp_carousel( 'testForData', gallery ) ) + return; n = parseInt(n[1], 10); @@ -1153,7 +1206,10 @@ jQuery(document).ready(function($) { }); if ( index != -1 ) - gallery.jp_carousel('open', {start_index: index}); - }); - } + gallery.jp_carousel('open', {start_index: index}); // open method checks if already opened + }, 1000); + }); }); + +// Swipe gesture detection +(function($){$.fn.touchwipe=function(settings){var config={min_move_x:20,min_move_y:20,wipeLeft:function(){},wipeRight:function(){},wipeUp:function(){},wipeDown:function(){},preventDefaultEvents:true};if(settings)$.extend(config,settings);this.each(function(){var startX;var startY;var isMoving=false;function cancelTouch(){this.removeEventListener('touchmove',onTouchMove);startX=null;isMoving=false}function onTouchMove(e){if(config.preventDefaultEvents){e.preventDefault()}if(isMoving){var x=e.touches[0].pageX;var y=e.touches[0].pageY;var dx=startX-x;var dy=startY-y;if(Math.abs(dx)>=config.min_move_x){cancelTouch();if(dx>0){config.wipeLeft()}else{config.wipeRight()}}else if(Math.abs(dy)>=config.min_move_y){cancelTouch();if(dy>0){config.wipeDown()}else{config.wipeUp()}}}}function onTouchStart(e){if(e.touches.length==1){startX=e.touches[0].pageX;startY=e.touches[0].pageY;isMoving=true;this.addEventListener('touchmove',onTouchMove,false)}}if('ontouchstart'in document.documentElement){this.addEventListener('touchstart',onTouchStart,false)}});return this}})(jQuery); diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.php b/plugins/jetpack/modules/carousel/jetpack-carousel.php index 3d48681b..314dd7b2 100644 --- a/plugins/jetpack/modules/carousel/jetpack-carousel.php +++ b/plugins/jetpack/modules/carousel/jetpack-carousel.php @@ -76,7 +76,7 @@ class Jetpack_Carousel { } function enqueue_assets( $output ) { - if ( ! empty( $output ) ) { + if ( ! empty( $output ) && ! apply_filters( 'jp_carousel_force_enable', false ) ) { // Bail because someone is overriding the [gallery] shortcode. remove_filter( 'gallery_style', array( $this, 'add_data_to_container' ) ); remove_filter( 'wp_get_attachment_link', array( $this, 'add_data_to_images' ) ); @@ -86,12 +86,7 @@ class Jetpack_Carousel { do_action( 'jp_carousel_thumbnails_shown' ); if ( $this->first_run ) { - if ( ! has_action( 'wp_enqueue_scripts', 'register_spin_scripts' ) ) { - wp_enqueue_script( 'spin', plugins_url( 'spin.js', __FILE__ ), false, '1.2.4' ); - wp_enqueue_script( 'jquery.spin', plugins_url( 'jquery.spin.js', __FILE__ ) , array( 'jquery', 'spin' ) ); - } - - wp_enqueue_script( 'jetpack-carousel', plugins_url( 'jetpack-carousel.js', __FILE__ ), array( 'jquery' ), $this->asset_version( '20120629' ), true ); + wp_enqueue_script( 'jetpack-carousel', plugins_url( 'jetpack-carousel.js', __FILE__ ), array( 'jquery.spin' ), $this->asset_version( '20130109' ), true ); // Note: using home_url() instead of admin_url() for ajaxurl to be sure to get same domain on wpcom when using mapped domains (also works on self-hosted) // Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context. @@ -140,7 +135,14 @@ class Jetpack_Carousel { $localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings ); wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings ); wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( '20120629' ) ); - + global $is_IE; + if( $is_IE ) + { + $msie = strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) + 4; + $version = (float) substr( $_SERVER['HTTP_USER_AGENT'], $msie, strpos( $_SERVER['HTTP_USER_AGENT'], ';', $msie ) - $msie ); + if( $version < 9 ) + wp_enqueue_style( 'jetpack-carousel-ie8fix', plugins_url( 'jetpack-carousel-ie8fix.css', __FILE__ ), array(), $this->asset_version( '20121024' ) ); + } do_action( 'jp_carousel_enqueue_assets', $this->first_run, $localize_strings ); $this->first_run = false; @@ -154,10 +156,11 @@ class Jetpack_Carousel { return $html; $attachment_id = intval( $attachment_id ); - $orig_file = wp_get_attachment_url( $attachment_id ); + $orig_file = wp_get_attachment_image_src( $attachment_id, 'full' ); + $orig_file = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id ); $meta = wp_get_attachment_metadata( $attachment_id ); $size = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : ''; - $img_meta = $meta['image_meta']; + $img_meta = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array(); $comments_opened = intval( comments_open( $attachment_id ) ); /* @@ -167,10 +170,10 @@ class Jetpack_Carousel { * $content_width has no filter we could temporarily de-register, run wp_get_attachment_image_src(), then * re-register. So using returned file URL instead, which we can define the sizes from through filename * parsing in the JS, as this is a failsafe file reference. - * + * * EG with Twenty Eleven activated: * array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(584) [2]=> int(435) [3]=> bool(true) } - * + * * EG with Twenty Ten activated: * array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(640) [2]=> int(477) [3]=> bool(true) } */ @@ -178,11 +181,12 @@ class Jetpack_Carousel { $medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' ); $medium_file = isset( $medium_file_info[0] ) ? $medium_file_info[0] : ''; - $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' ); - $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : ''; + $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' ); + $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : ''; - $attachment = get_post( $attachment_id ); - $attachment_desc = wpautop( wptexturize( $attachment->post_content ) ); + $attachment = get_post( $attachment_id ); + $attachment_title = wptexturize( $attachment->post_title ); + $attachment_desc = wpautop( wptexturize( $attachment->post_content ) ); // Not yet providing geo-data, need to "fuzzify" for privacy if ( ! empty( $img_meta ) ) { @@ -192,17 +196,18 @@ class Jetpack_Carousel { } } - $img_meta = json_encode( $img_meta ); + $img_meta = json_encode( array_map( 'strval', $img_meta ) ); $html = str_replace( '<img ', sprintf( - '<img data-attachment-id="%1$d" data-orig-file="%2$s" data-orig-size="%3$s" data-comments-opened="%4$s" data-image-meta="%5$s" data-image-description="%6$s" data-medium-file="%7$s" data-large-file="%8$s" ', + '<img data-attachment-id="%1$d" data-orig-file="%2$s" data-orig-size="%3$s" data-comments-opened="%4$s" data-image-meta="%5$s" data-image-title="%6$s" data-image-description="%7$s" data-medium-file="%8$s" data-large-file="%9$s" ', $attachment_id, esc_attr( $orig_file ), $size, $comments_opened, esc_attr( $img_meta ), + esc_attr( $attachment_title ), esc_attr( $attachment_desc ), esc_attr( $medium_file ), esc_attr( $large_file ) @@ -230,24 +235,24 @@ class Jetpack_Carousel { return $html; } - + function get_attachment_comments() { if ( ! headers_sent() ) header('Content-type: text/javascript'); - + do_action('jp_carousel_check_blog_user_privileges'); - + $attachment_id = ( isset( $_REQUEST['id'] ) ) ? (int) $_REQUEST['id'] : 0; $offset = ( isset( $_REQUEST['offset'] ) ) ? (int) $_REQUEST['offset'] : 0; - + if ( ! $attachment_id ) { echo json_encode( __( 'Missing attachment ID.', 'jetpack' ) ); die(); } - + if ( $offset < 1 ) $offset = 0; - + $comments = get_comments( array( 'status' => 'approve', 'order' => ( 'asc' == get_option('comment_order') ) ? 'ASC' : 'DESC', @@ -255,9 +260,9 @@ class Jetpack_Carousel { 'offset' => $offset, 'post_id' => $attachment_id, ) ); - + $out = array(); - + // Can't just send the results, they contain the commenter's email address. foreach ( $comments as $comment ) { $author_markup = '<a href="' . esc_url( $comment->comment_author_url ) . '">' . esc_html( $comment->comment_author ) . '</a>'; @@ -270,42 +275,42 @@ class Jetpack_Carousel { 'content' => wpautop($comment->comment_content), ); } - + die( json_encode( $out ) ); } function post_attachment_comment() { if ( ! headers_sent() ) header('Content-type: text/javascript'); - + if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce($_POST['nonce'], 'carousel_nonce') ) die( json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) ); - + $_blog_id = (int) $_POST['blog_id']; $_post_id = (int) $_POST['id']; $comment = $_POST['comment']; - + if ( empty( $_blog_id ) ) die( json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) ); - + if ( empty( $_post_id ) ) die( json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) ); - + if ( empty( $comment ) ) die( json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) ); // Used in context like NewDash $switched = false; - if ( $_blog_id != get_current_blog_id() ) { + if ( is_multisite() && $_blog_id != get_current_blog_id() ) { switch_to_blog( $_blog_id ); $switched = true; } - + do_action('jp_carousel_check_blog_user_privileges'); if ( ! comments_open( $_post_id ) ) die( json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) ); - + if ( is_user_logged_in() ) { $user = wp_get_current_user(); $user_id = $user->ID; @@ -353,10 +358,10 @@ class Jetpack_Carousel { die( json_encode( array( 'comment_id' => $comment_id, 'comment_status' => $comment_status ) ) ); } - + function register_settings() { add_settings_section('carousel_section', __( 'Image Gallery Carousel', 'jetpack' ), array( $this, 'carousel_section_callback' ), 'media'); - + if ( ! $this->in_jetpack ) { add_settings_field('carousel_enable_it', __( 'Enable carousel', 'jetpack' ), array( $this, 'carousel_enable_it_callback' ), 'media', 'carousel_section' ); register_setting( 'media', 'carousel_enable_it', array( $this, 'carousel_enable_it_sanitize' ) ); @@ -364,7 +369,7 @@ class Jetpack_Carousel { add_settings_field('carousel_background_color', __( 'Background color', 'jetpack' ), array( $this, 'carousel_background_color_callback' ), 'media', 'carousel_section' ); register_setting( 'media', 'carousel_background_color', array( $this, 'carousel_background_color_sanitize' ) ); - + add_settings_field('carousel_display_exif', __( 'Metadata', 'jetpack'), array( $this, 'carousel_display_exif_callback' ), 'media', 'carousel_section' ); register_setting( 'media', 'carousel_display_exif', array( $this, 'carousel_display_exif_sanitize' ) ); @@ -386,7 +391,7 @@ class Jetpack_Carousel { } return ( 1 == $value ) ? 1 : 0; } - + function sanitize_1or0_option( $value ) { return ( 1 == $value ) ? 1 : 0; } @@ -435,7 +440,7 @@ class Jetpack_Carousel { function carousel_display_geo_sanitize( $value ) { return $this->sanitize_1or0_option( $value ); - } + } function carousel_background_color_callback() { $this->settings_select( 'carousel_background_color', array( 'black' => __( 'Black', 'jetpack' ), 'white' => __( 'White', 'jetpack', 'jetpack' ) ) ); diff --git a/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css b/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css new file mode 100644 index 00000000..0d010eb5 --- /dev/null +++ b/plugins/jetpack/modules/carousel/rtl/jetpack-carousel-rtl.css @@ -0,0 +1,1106 @@ +/* This file was automatically generated on Jan 29 2013 22:51:19 */ + +* { + line-height:inherit; /* prevent declarations of line-height in the universal selector */ +} + +.jp-carousel-overlay { + background: #000; +} + +div.jp-carousel-fadeaway { + background: -moz-linear-gradient(bottom, rgba(0,0,0,0.5), rgba(0,0,0,0)); + background: -webkit-gradient(linear, right bottom, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0))); + position: fixed; + bottom: 0; + z-index: 2147483647; + width: 100%; + height: 15px; +} + +.jp-carousel-next-button span, +.jp-carousel-previous-button span { + background: url(.././images/arrows.png) no-repeat center center; + background-size: 200px 126px; +} + +@media +only screen and (-webkit-min-device-pixel-ratio: 1.5), +only screen and (-o-min-device-pixel-ratio: 3/2), +only screen and (min--moz-device-pixel-ratio: 1.5), +only screen and (min-device-pixel-ratio: 1.5) { + .jp-carousel-next-button span, + .jp-carousel-previous-button span { + background-image: url(.././images/arrows-2x.png); + } +} + +.jp-carousel-wrap { + font-family: "Helvetica Neue", sans-serif !important; +} + +.jp-carousel-info { + position: absolute; + bottom: 0; + text-align: right !important; + -webkit-font-smoothing: subpixel-antialiased !important; +} + +.jp-carousel-info ::selection { + background: #68c9e8; /* Safari */ + color: #fff; + } + +.jp-carousel-info ::-moz-selection { + background: #68c9e8; /* Firefox */ + color: #fff; +} + +.jp-carousel-photo-info { + position: relative; + -webkit-transition: 400ms ease-out; + -moz-transition: 400ms ease-out; + -o-transition: 400ms ease-out; + transition: 400ms ease-out; + right: 25%; + width: 50%; +} + +.jp-carousel-info h2 { + background: none !important; + border: none !important; + color: #999; + display: block !important; + font: normal 13px/1.25em "Helvetica Neue", sans-serif !important; + letter-spacing: 0 !important; + margin: 7px 0 0 0 !important; + padding: 10px 0 0 !important; + overflow: hidden; + text-align: right; + text-shadow: none !important; + text-transform: none !important; + -webkit-font-smoothing: subpixel-antialiased; +} + +.jp-carousel-next-button, +.jp-carousel-previous-button { + text-indent: -9999px; + overflow: hidden; + cursor: pointer; +} + +.jp-carousel-next-button span, +.jp-carousel-previous-button span { + position: absolute; + top: 0; + bottom: 0; + width: 82px; + zoom: 1; + filter: alpha(opacity=20); + opacity: 0.2; + -webkit-transition: 500ms opacity ease-out; + -moz-transition: 500ms opacity ease-out; + -o-transition: 500ms opacity ease-out; + transition: 500ms opacity ease-out; +} + +.jp-carousel-next-button:hover span, +.jp-carousel-previous-button:hover span { + filter: alpha(opacity=60); + opacity: 0.6; +} +.jp-carousel-next-button span { + background-position: -110px center; + left: 0; +} + +.jp-carousel-previous-button span { + background-position: -10px center; + right:0; +} + +.jp-carousel-buttons { + margin:-18px -20px 15px; + padding:8px 10px; + border-bottom:1px solid #222; + background: #222; + text-align: center; +} + +div.jp-carousel-buttons a { + border: none !important; + color: #999; + font: normal 11px/1.2em "Helvetica Neue", sans-serif !important; + letter-spacing: 0 !important; + padding: 5px 0 5px 2px; + text-decoration: none !important; + text-shadow: none !important; + vertical-align: baseline !important; + -webkit-font-smoothing: subpixel-antialiased; +} + +div.jp-carousel-buttons a:hover { + color: #68c9e8; + border: none !important; + -webkit-transition: none !important; + -moz-transition: none !important; + -o-transition: none !important; + transition: none !important; +} + +.jp-carousel-slide, .jp-carousel-slide img, .jp-carousel-next-button, +.jp-carousel-previous-button { + -webkit-transform:translate3d(0, 0, 0); + -moz-transform:translate3d(0, 0, 0); + -o-transform:translate3d(0, 0, 0); + -ms-transform:translate3d(0, 0, 0); +} + +.jp-carousel-slide { + position:absolute; + width:0; + bottom:0; + background-color:#000; + border-radius:2px; + -webkit-border-radius:2px; + -moz-border-radius:2px; + -ms-border-radius:2px; + -o-border-radius:2px; + -webkit-transition: 400ms ease-out; + -moz-transition: 400ms ease-out; + -o-transition: 400ms ease-out; + transition: 400ms ease-out; +} + +.jp-carousel-slide img { + display: block; + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + background: none !important; + border: none !important; + padding: 0 !important; + -webkit-box-shadow: 0 2px 8px rgba(0,0,0,0.1); + -moz-box-shadow: 0 2px 8px rgba(0,0,0,0.1); + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + zoom: 1; + filter: alpha(opacity=25); + opacity: 0.25; + -webkit-transition: opacity 400ms linear; + -moz-transition: opacity 400ms linear; + -o-transition: opacity 400ms linear; + transition: opacity 400ms linear; +} + +.jp-carousel-slide.selected img { + filter: alpha(opacity=100); + opacity: 1; +} + +.jp-carousel-close-hint { + color: #999; + cursor: default; + letter-spacing: 0 !important; + padding:0.35em 0 0; + position: absolute; + text-align: right; + width: 90%; + -webkit-transition: color 200ms linear; + -moz-transition: color 200ms linear; + -o-transition: color 200ms linear; + transition: color 200ms linear; +} + +.jp-carousel-close-hint span { + cursor: pointer; + background-color: black; + background-color: rgba(0,0,0,0.8); + display: block; + height: 22px; + font: 400 24px/1 "Helvetica Neue", sans-serif !important; + line-height: 22px; + margin: 0 0.4em 0 0; + text-align: center; + vertical-align: middle; + width: 22px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-transition: border-color 200ms linear; + -moz-transition: border-color 200ms linear; + -o-transition: border-color 200ms linear; + transition: border-color 200ms linear; +} + +.jp-carousel-close-hint:hover { + cursor: default; + color: #fff; +} + +.jp-carousel-close-hint:hover span { + border-color: #fff; +} + +div.jp-carousel-buttons a.jp-carousel-like, +div.jp-carousel-buttons a.jp-carousel-reblog, +div.jp-carousel-buttons a.jp-carousel-commentlink, +a.jp-carousel-image-download { + background: url(.././images/carousel-sprite.png?4) no-repeat; + background-size: 16px 160px; +} + +div.jp-carousel-buttons a.jp-carousel-reblog, +div.jp-carousel-buttons a.jp-carousel-commentlink { + margin:0 0 0 14px !important; +} + +div.jp-carousel-buttons a.jp-carousel-reblog.reblogged, +div.jp-carousel-buttons a.jp-carousel-like.liked { + background-color: #303030; + padding-left: 8px !important; + border-radius: 2px; + border-radius:2px; + -webkit-border-radius:2px; + -moz-border-radius:2px; + -ms-border-radius:2px; + -o-border-radius:2px; +} + +div.jp-carousel-buttons a.jp-carousel-reblog.reblogged { + margin:0 -12px 0 2px !important; +} + + +div.jp-carousel-buttons a.jp-carousel-reblog, +div.jp-carousel-buttons a.jp-carousel-reblog.reblogged:hover { + background-position: 6px -36px; + padding-right: 26px !important; + color: #999; +} + +div.jp-carousel-buttons a.jp-carousel-commentlink { + background-position: 0px -116px; + padding-right: 19px !important; +} + +div.jp-carousel-buttons a.jp-carousel-reblog.reblogged:hover { + cursor: default; +} + +div.jp-carousel-buttons a.jp-carousel-reblog:hover { + background-position: 6px -56px; + color: #68c9e8; +} + +div.jp-carousel-buttons a.jp-carousel-like { + background-position: 5px 5px; + padding-right: 24px !important; +} + +div.jp-carousel-buttons a.jp-carousel-like:hover { + background-position: 5px -15px; +} + +@media +only screen and (-webkit-min-device-pixel-ratio: 1.5), +only screen and (-o-min-device-pixel-ratio: 3/2), +only screen and (min--moz-device-pixel-ratio: 1.5), +only screen and (min-device-pixel-ratio: 1.5) { + div.jp-carousel-buttons a.jp-carousel-like, + div.jp-carousel-buttons a.jp-carousel-reblog, + div.jp-carousel-buttons a.jp-carousel-commentlink, + a.jp-carousel-image-download { + background-image: url(.././images/carousel-sprite-2x.png?4); + } +} + +/* reblog */ +div#carousel-reblog-box { + background: #222; + background: -moz-linear-gradient(bottom, #222, #333); + background: -webkit-gradient(linear, right bottom, right top, from(#222), to(#333)); + padding: 3px 0 0; + display: none; + margin: 5px auto 0; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.9); + -moz-box-shadow: 0 0 20px rgba(0,0,0,0.9); + box-shadow: 0 0 20px rgba(0,0,0,0.9); + height: 74px; + width: 565px; +} + +#carousel-reblog-box textarea { + background: #999; + font: 13px/1.4 "Helvetica Neue", sans-serif !important; + color: #444; + padding: 3px 6px; + width: 370px; + height: 48px; + float: right; + margin: 6px 9px 0 9px; + border: 1px solid #666; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +#carousel-reblog-box textarea:focus { + background: #ccc; + color: #222; +} + +#carousel-reblog-box label { + color: #aaa; + font-size: 11px; + padding-left: 2px; + padding-right: 2px; + display: inline; + font-weight: normal; +} + +#carousel-reblog-box select { + width: 110px; + padding: 0; + font-size: 12px; + font-family: "Helvetica Neue", sans-serif !important; + background: #333; + color: #eee; + border: 1px solid #444; + margin-top:5px; +} + +#carousel-reblog-box .submit, +#wrapper #carousel-reblog-box p.response { + float: right; + width: 154px; + padding-top: 0; + padding-right: 1px; + overflow: hidden; + height: 34px; + margin:3px 2px 0 0 !important; +} + +#wrapper #carousel-reblog-box p.response { + font-size: 13px; + clear: none; + padding-right: 2px; + height: 34px; + color: #aaa; +} + +#carousel-reblog-box input#carousel-reblog-submit, #jp-carousel-comment-form-button-submit { + font: 13px/24px "Helvetica Neue", sans-serif !important; + margin-top: 8px; + padding: 0 10px !important; + border-radius: 1em; + height: 24px; + color: #333; + cursor:pointer; + font-weight: normal; + background: #aaa; + background: -moz-linear-gradient(bottom, #aaa, #ccc); + background: -webkit-gradient(linear, right bottom, right top, from(#aaa), to(#ccc)); + border: 1px solid #444; +} + +#carousel-reblog-box input#carousel-reblog-submit:hover, #jp-carousel-comment-form-button-submit:hover { + background: #ccc; + background: -moz-linear-gradient(bottom, #ccc, #eee); + background: -webkit-gradient(linear, right bottom, right top, from(#ccc), to(#eee)); +} + +#carousel-reblog-box .canceltext { + color: #aaa; + font-size: 11px; + line-height: 24px; +} + +#carousel-reblog-box .canceltext a { + color: #fff; +} +/* reblog end */ + + +/** Title and Desc Start **/ +.jp-carousel-titleanddesc { + border-top: 1px solid #222; + color: #999; + font-size: 15px; + padding-top: 24px; + margin-bottom: 20px; + font-weight:400; +} +.jp-carousel-titleanddesc-title { + font: 300 1.5em/1.1 "Helvetica Neue", sans-serif !important; + text-transform: none !important; /* prevents uppercase from leaking through */ + color: #fff; + margin: 0 0 15px; + padding:0; +} + +.jp-carousel-titleanddesc-desc p { + color: #999; + line-height:1.4; + margin-bottom: 0.75em; +} + +.jp-carousel-titleanddesc p a, +.jp-carousel-comments p a, +.jp-carousel-info h2 a { + color: #fff !important; + border: none !important; + text-decoration: underline !important; + font-weight: normal !important; + font-style: normal !important; +} + +.jp-carousel-titleanddesc p strong, +.jp-carousel-titleanddesc p b { + font-weight: bold; + color: #999; +} + +.jp-carousel-titleanddesc p em, +.jp-carousel-titleanddesc p i { + font-style: italic; + color: #999; +} + + +.jp-carousel-titleanddesc p a:hover, +.jp-carousel-comments p a:hover, +.jp-carousel-info h2 a:hover { + color: #68c9e8 !important; +} + +.jp-carousel-titleanddesc p:empty { + display: none; +} + +.jp-carousel-photo-info h1:before, +.jp-carousel-photo-info h1:after, +.jp-carousel-left-column-wrapper h1:before, +.jp-carousel-left-column-wrapper h1:after { + content:none !important; +} +/** Title and Desc End **/ + +/** Meta Box Start **/ +.jp-carousel-image-meta { + background: #111; + border: 1px solid #222; + color: #fff; + font-size: 13px; + font: 12px/1.4 "Helvetica Neue", sans-serif !important; + overflow: hidden; + padding: 18px 20px; + width: 209px !important; +} + +.jp-carousel-image-meta li, +.jp-carousel-image-meta h5 { + font-family: "Helvetica Neue", sans-serif !important; + position: inherit !important; + top: auto !important; + left: auto !important; + right: auto !important; + bottom: auto !important; + background: none !important; + border: none !important; + font-weight: 400 !important; + line-height: 1.3em !important; +} + +.jp-carousel-image-meta ul { + margin: 0 !important; + padding: 0 !important; + list-style: none !important; +} + +.jp-carousel-image-meta li { + width: 48% !important; + float: right !important; + margin: 0 0 15px 2% !important; + color: #fff !important; + font-size:13px !important; +} + +.jp-carousel-image-meta h5 { + color: #999 !important; + text-transform: uppercase !important; + font-size:10px !important; + margin:0 0 2px !important; + letter-spacing: 0.1em !important; +} + +a.jp-carousel-image-download { + padding-right: 23px; + display: inline-block; + clear: both; + color: #999; + line-height: 1; + font-weight: 400; + font-size: 13px; + text-decoration: none; + background-position: 0 -82px; +} + +a.jp-carousel-image-download span.photo-size { + font-size: 11px; + border-radius: 1em; + margin-right: 2px; + display: inline-block; +} + +a.jp-carousel-image-download span.photo-size-times { + padding: 0 2px 0 1px; +} + +a.jp-carousel-image-download:hover { + background-position: 0 -102px; + color: #68c9e8; + border: none !important; +} + +/** Meta Box End **/ + +/** GPS Map Start **/ +.jp-carousel-image-map { + position: relative; + margin: -20px -20px 20px; + border-bottom: 1px solid rgba( 255, 255, 255, 0.17 ); + height: 154px; +} + +.jp-carousel-image-map img.gmap-main { + -moz-border-radius-topleft: 6px; + border-top-right-radius: 6px; + border-left: 1px solid rgba( 255, 255, 255, 0.17 ); +} +.jp-carousel-image-map div.gmap-topright { + width: 94px; + height: 154px; + position: absolute; + top: 0; + left: 0; +} +.jp-carousel-image-map div.imgclip { + overflow: hidden; + -moz-border-radius-topright: 6px; + border-top-left-radius: 6px; +} +.jp-carousel-image-map div.gmap-topright img { + margin-right: -40px; +} +.jp-carousel-image-map img.gmap-bottomright { + position: absolute; + top: 96px; + left: 0; +} + +/** Comments Start **/ +.jp-carousel-comments { + font: 15px/1.7 "Helvetica Neue", sans-serif !important; + font-weight: 400; + background:none transparent; +} + +.jp-carousel-comments p a:hover, .jp-carousel-comments p a:focus, .jp-carousel-comments p a:active { + color: #68c9e8 !important; +} + +.jp-carousel-comment { + background:none transparent; + color: #999; + margin-bottom: 20px; + clear:right; + overflow: auto; + width: 100% +} + +.jp-carousel-comment p { + color: #999 !important; +} + +.jp-carousel-comment .comment-author { + font-size: 13px; + font-weight:400; + padding:0; + width:auto; + display: inline; + float:none; + border:none; + margin:0; +} + +.jp-carousel-comment .comment-author a { + color: #fff; +} + +.jp-carousel-comment .comment-gravatar { + float:right; +} + +.jp-carousel-comment .comment-content { + border:none; + margin-right:85px; + padding: 0; +} + +.jp-carousel-comment .avatar { + margin:0 0 0 20px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + border: none !important; + padding: 0 !important; + background-color: transparent !important; +} + +.jp-carousel-comment .comment-date { + color:#999; + margin-top: 4px; + font-size:11px; + display: inline; + float: left; + /*clear: right;*/ +} + +#jp-carousel-comment-form { + margin:0 0 10px !important; + float: right; + width: 100%; +} + +textarea#jp-carousel-comment-form-comment-field { + background: rgba(34,34,34,0.9); + border: 1px solid #3a3a3a; + color: #aaa; + font: 15px/1.4 "Helvetica Neue", sans-serif !important; + width: 100%; + padding: 10px 10px 5px; + margin: 0; + float: none; + height: 147px; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + overflow: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +textarea#jp-carousel-comment-form-comment-field::-webkit-input-placeholder { + color: #555; +} + +textarea#jp-carousel-comment-form-comment-field:focus { + background: #ccc; + color: #222; +} + +textarea#jp-carousel-comment-form-comment-field:focus::-webkit-input-placeholder { + color: #aaa; +} + +#jp-carousel-comment-form-spinner { + color: #fff; + margin:22px 10px 0 0; + display: block; + width: 20px; + height: 20px; + float: right; +} + +#jp-carousel-comment-form-submit-and-info-wrapper { + display: none; + /*margin-bottom:15px;*/ + overflow: hidden; + width: 100% +} + +#jp-carousel-comment-form-commenting-as { +} + +#jp-carousel-comment-form-commenting-as input { + background: rgba(34,34,34,0.9); + border: 1px solid #3a3a3a; + color: #aaa; + font: 13px/1.4 "Helvetica Neue", sans-serif !important; + padding: 3px 6px; + float: right; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2); + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + width:285px; +} + +#jp-carousel-comment-form-commenting-as input:focus { + background: #ccc; + color: #222; +} + +#jp-carousel-comment-form-commenting-as p { + font: 400 13px/1.7 "Helvetica Neue", sans-serif !important; + margin:22px 0 0; + float: right; +} + +#jp-carousel-comment-form-commenting-as fieldset { + float:right; + border:none; + margin:20px 0 0 0; + padding:0; +} + +#jp-carousel-comment-form-commenting-as fieldset { + clear: both; +} + +#jp-carousel-comment-form-commenting-as label { + font: 400 13px/1.7 "Helvetica Neue", sans-serif !important; + margin:0 0 3px 20px; + float:right; + width:100px; +} + +#jp-carousel-comment-form-button-submit { + margin-top: 20px; + float:left; +} + +#js-carousel-comment-form-container { + margin-bottom:15px; + overflow: auto; + width: 100%; +} + +#jp-carousel-comment-form-container { + margin-bottom:15px; + overflow: auto; + width: 100%; +} + +#jp-carousel-comment-post-results { + display: none; + overflow:auto; + width:100%; +} + +#jp-carousel-comment-post-results span { + display:block; + text-align: center; + margin-top:20px; + width: 100%; + overflow: auto; + padding: 1em 0; + box-sizing: border-box; + background: rgba( 0, 0, 0, 0.7 ); + border-radius: 2px; + font: 13px/1.4 "Helvetica Neue", sans-serif !important; + border: 1px solid rgba( 255, 255, 255, 0.17 ); + -webkit-box-shadow: inset 0px 5px 5px 0px rgba(0, 0, 0, 1); + box-shadow: inset 0px 5px 5px 0px rgba(0, 0, 0, 1); +} + +.jp-carousel-comment-post-error { + color:#DF4926; +} + +.jp-carousel-comment-post-success { + /*color:#21759B;*/ +} + +#jp-carousel-comments-closed { + display: none; + color: #999; +} + +#jp-carousel-comments-loading { + font: 444 15px/1.7 "Helvetica Neue", sans-serif !important; + display: none; + color: #999; + text-align: right; + margin-bottom: 20px; +} + + +/* ----- Light variant ----- */ + +.jp-carousel-light .jp-carousel-overlay { + background: #fff; +} + +.jp-carousel-light .jp-carousel-next-button:hover span, +.jp-carousel-light .jp-carousel-previous-button:hover span { + opacity: 0.8; +} + +.jp-carousel-light .jp-carousel-close-hint:hover, +.jp-carousel-light .jp-carousel-titleanddesc div { + color: #000 !important; +} + +.jp-carousel-light .jp-carousel-comments p a, +.jp-carousel-light .jp-carousel-comment .comment-author a, +.jp-carousel-light .jp-carousel-titleanddesc p a, +.jp-carousel-light .jp-carousel-titleanddesc p a, +.jp-carousel-light .jp-carousel-comments p a, +.jp-carousel-light .jp-carousel-info h2 a { + color: #1e8cbe !important; +} + +.jp-carousel-light .jp-carousel-comments p a:hover, +.jp-carousel-light .jp-carousel-comment .comment-author a:hover, +.jp-carousel-light .jp-carousel-titleanddesc p a:hover, +.jp-carousel-light .jp-carousel-titleanddesc p a:hover, +.jp-carousel-light .jp-carousel-comments p a:hover, +.jp-carousel-light .jp-carousel-info h2 a:hover { + color: #f1831e !important; +} + +.jp-carousel-light .jp-carousel-info h2, +.jp-carousel-light .jp-carousel-titleanddesc, +.jp-carousel-light .jp-carousel-titleanddesc p, +.jp-carousel-light .jp-carousel-comment, +.jp-carousel-light .jp-carousel-comment p, +.jp-carousel-light div.jp-carousel-buttons a, +.jp-carousel-light .jp-carousel-titleanddesc p strong, +.jp-carousel-light .jp-carousel-titleanddesc p b, +.jp-carousel-light .jp-carousel-titleanddesc p em, +.jp-carousel-light .jp-carousel-titleanddesc p i { + color: #666; +} + +.jp-carousel-light .jp-carousel-buttons { + border-bottom-color: #f0f0f0; + background: #f5f5f5; +} + +.jp-carousel-light div.jp-carousel-buttons a:hover { + text-decoration: none; + color: #f1831e; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog, +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog:hover { + background-position: 4px -56px; + padding-right: 24px !important; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog.reblogged, +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like.liked { + background-color: #2ea2cc; + color: #fff; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-commentlink { + background-position: 0px -136px; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like, +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like:hover { + background-position: 5px -15px; + padding-right: 23px !important; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-reblog.reblogged { + background-position: 5px -36px; +} + +.jp-carousel-light div.jp-carousel-buttons a.jp-carousel-like.liked { + background-position: 5px 5px; +} + +.jp-carousel-light div#carousel-reblog-box { + background: #eee; + background: -moz-linear-gradient(bottom, #ececec, #f7f7f7); + background: -webkit-gradient(linear, right bottom, right top, from(#ececec), to(#f7f7f7)); + -webkit-box-shadow: 0 2px 6px rgba(0,0,0,0.1); + -moz-box-shadow: 0 2px 10px rgba(0,0,0,0.1); + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + border:1px solid #ddd; +} + +.jp-carousel-light #carousel-reblog-box textarea { + border: 1px inset #ccc; + color: #666; + border: 1px solid #cfcfcf; + background: #fff; +} + +.jp-carousel-light #carousel-reblog-box .canceltext { + color: #888; +} + +.jp-carousel-light #carousel-reblog-box .canceltext a { + color: #666; +} + +.jp-carousel-light #carousel-reblog-box select { + background: #eee; + color: #333; + border: 1px solid #aaa; +} + +.jp-carousel-light #carousel-reblog-box input#carousel-reblog-submit, #jp-carousel-comment-form-button-submit { + color: #333; + background: #fff; + background: -moz-linear-gradient(bottom, #ddd, #fff); + background: -webkit-gradient(linear, right bottom, right top, from(#ddd), to(#fff)); + border: 1px solid #aaa; +} + +.jp-carousel-light .jp-carousel-image-meta { + background: #fafafa; + border: 1px solid #eee; + border-top-color: #f5f5f5; + border-right-color: #f5f5f5; + color: #333; +} + +.jp-carousel-light .jp-carousel-image-meta li { + color: #000 !important; +} + +.jp-carousel-light .jp-carousel-close-hint { + color: #ccc; +} + +.jp-carousel-light .jp-carousel-close-hint span { + background-color: white; + border-color: #ccc; +} + +.jp-carousel-light #jp-carousel-comment-form-comment-field::-webkit-input-placeholder { + color: #aaa; +} + +.jp-carousel-light #jp-carousel-comment-form-comment-field:focus { + color: #333; +} + +.jp-carousel-light #jp-carousel-comment-form-comment-field:focus::-webkit-input-placeholder { + color: #ddd; +} + +.jp-carousel-light a.jp-carousel-image-download { + background-position: 0 -102px; +} + +.jp-carousel-light a.jp-carousel-image-download:hover { + background-position: 0 -102px; + color: #f1831e; +} + +.jp-carousel-light textarea#jp-carousel-comment-form-comment-field { + background: #fbfbfb; + color: #333; + border: 1px solid #dfdfdf; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1); +} + +.jp-carousel-light #jp-carousel-comment-form-commenting-as input { + background: #fbfbfb; + border: 1px solid #dfdfdf; + color: #333; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.1); +} + +.jp-carousel-light #jp-carousel-comment-form-commenting-as input:focus { + background: #fbfbfb; + color: #333; +} + +.jp-carousel-light #jp-carousel-comment-post-results span { + background: #f7f7f7; + border:1px solid #dfdfdf; + -webkit-box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.05); + box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.05); +} + +.jp-carousel-light .jp-carousel-slide { + background-color:#fff; +} + +.jp-carousel-light .jp-carousel-titleanddesc { + border-top: 1px solid #eee; +} + +.jp-carousel-light .jp-carousel-fadeaway { + background: -moz-linear-gradient(bottom, rgba(255,255,255,0.75), rgba(255,255,255,0)); + background: -webkit-gradient(linear, right bottom, right top, from(rgba(255,255,255,0.75)), to(rgba(255,255,255,0))); +} + +/* Small screens */ +@media only screen and (max-width: 760px) { + + .jp-carousel-info { + margin: 0 10px !important; + } + + .jp-carousel-next-button, .jp-carousel-previous-button { + display: none !important; + } + + .jp-carousel-buttons { + display: none !important; + } + + .jp-carousel-image-meta { + float: none !important; + width: 100% !important; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + box-sizing: border-box; + } + + .jp-carousel-close-hint { + font-weight: 800 !important; + font-size: 26px !important; + position: fixed !important; + top: -10px; + } + + .jp-carousel-slide img { + filter: alpha(opacity=100); + opacity: 1; + } + + .jp-carousel-wrap { + background-color: #000; + } + + .jp-carousel-fadeaway { + display: none; + } + + #jp-carousel-comment-form-container { + display: none !important; + } + + .jp-carousel-titleanddesc { + padding-top: 0 !important; + border: none !important; + } + .jp-carousel-titleanddesc-title { + font-size: 1em !important; + } + + .jp-carousel-left-column-wrapper { + padding: 0; + } +} diff --git a/plugins/jetpack/modules/comments.php b/plugins/jetpack/modules/comments.php index b30ccd73..d5d4da4d 100644 --- a/plugins/jetpack/modules/comments.php +++ b/plugins/jetpack/modules/comments.php @@ -13,6 +13,15 @@ if ( is_admin() ) { require dirname( __FILE__ ) . '/comments/admin.php'; } +Jetpack_Sync::sync_options( __FILE__, + 'comment_registration', + 'require_name_email', + 'show_avatars', + 'avatar_default', + 'highlander_comment_form_prompt', + 'jetpack_comment_form_color_scheme' +); + function jetpack_comments_load() { Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, 'jetpack_comments_configuration_load' ); diff --git a/plugins/jetpack/modules/comments/base.php b/plugins/jetpack/modules/comments/base.php index 2835313f..f1b5440e 100644 --- a/plugins/jetpack/modules/comments/base.php +++ b/plugins/jetpack/modules/comments/base.php @@ -35,7 +35,6 @@ class Highlander_Comments_Base { protected function setup_filters() { add_filter( 'comments_array', array( $this, 'comments_array' ) ); add_filter( 'preprocess_comment', array( $this, 'allow_logged_in_user_to_comment_as_guest' ), 0 ); - add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 4 ); } /** @@ -273,56 +272,16 @@ class Highlander_Comments_Base { } /** - * Get the comment avatar from Gravatar, Twitter, or Facebook - * - * @since JetpackComments (1.4) - * @param string $avatar Current avatar URL - * @param string $comment Comment for the avatar - * @param int $size Size of the avatar - * @param string $default Not used - * @return string New avatar - */ - public function get_avatar( $avatar, $comment, $size, $default ) { - if ( ! isset( $comment->comment_post_ID ) || ! isset( $comment->comment_ID ) ) { - // it's not a comment - bail - return $avatar; - } - - if ( false === strpos( $comment->comment_author_url, '/www.facebook.com/' ) && false === strpos( $comment->comment_author_url, '/twitter.com/' ) ) { - // It's neither FB nor Twitter - bail - return $avatar; - } - - // It's a FB or Twitter avatar - $foreign_avatar = get_comment_meta( $comment->comment_ID, 'hc_avatar', true ); - if ( empty( $foreign_avatar ) ) { - // Can't find the avatar details - bail - return $avatar; - } - - // Return the FB or Twitter avatar - return preg_replace( '#src=([\'"])[^\'"]+\\1#', 'src=\\1' . esc_url( $this->imgpress_avatar( $foreign_avatar, $size ) ) . '\\1', $avatar ); - } - - /** - * Get an avatar from Imgpress + * Get an avatar from Photon * * @since JetpackComments (1.4) * @param string $url * @param int $size * @return string */ - protected function imgpress_avatar( $url, $size ) { + protected function photon_avatar( $url, $size ) { $size = (int) $size; - $args = urlencode_deep( array( - 'url' => $url, - 'resize' => "$size,$size", - ) ); - - $url = apply_filters( 'jetpack_static_url', ( is_ssl() ? 'https://s-ssl.wordpress.com' : 'http://s.wordpress.com' ) . '/imgpress' ); - $url = add_query_arg( $args, $url ); - - return $url; + return jetpack_photon_url( $url, array( 'resize' => "$size,$size" ) ); } } diff --git a/plugins/jetpack/modules/comments/comments.php b/plugins/jetpack/modules/comments/comments.php index d33ad240..8dae39b7 100644 --- a/plugins/jetpack/modules/comments/comments.php +++ b/plugins/jetpack/modules/comments/comments.php @@ -115,6 +115,49 @@ class Jetpack_Comments extends Highlander_Comments_Base { add_action( 'comment_post', array( $this, 'add_comment_meta' ) ); } + /** + * Setup filters for methods in this class + * @since 1.6.2 + */ + protected function setup_filters() { + parent::setup_filters(); + + add_filter( 'comment_post_redirect', array( $this, 'capture_comment_post_redirect_to_reload_parent_frame' ), 100 ); + add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 4 ); + } + + /** + * Get the comment avatar from Gravatar, Twitter, or Facebook + * + * @since JetpackComments (1.4) + * @param string $avatar Current avatar URL + * @param string $comment Comment for the avatar + * @param int $size Size of the avatar + * @param string $default Not used + * @return string New avatar + */ + public function get_avatar( $avatar, $comment, $size, $default ) { + if ( ! isset( $comment->comment_post_ID ) || ! isset( $comment->comment_ID ) ) { + // it's not a comment - bail + return $avatar; + } + + if ( false === strpos( $comment->comment_author_url, '/www.facebook.com/' ) && false === strpos( $comment->comment_author_url, '/twitter.com/' ) ) { + // It's neither FB nor Twitter - bail + return $avatar; + } + + // It's a FB or Twitter avatar + $foreign_avatar = get_comment_meta( $comment->comment_ID, 'hc_avatar', true ); + if ( empty( $foreign_avatar ) ) { + // Can't find the avatar details - bail + return $avatar; + } + + // Return the FB or Twitter avatar + return preg_replace( '#src=([\'"])[^\'"]+\\1#', 'src=\\1' . esc_url( $this->photon_avatar( $foreign_avatar, $size ) ) . '\\1', $avatar ); + } + /** Output Methods ********************************************************/ /** @@ -168,6 +211,7 @@ class Jetpack_Comments extends Highlander_Comments_Base { 'greeting' => get_option( 'highlander_comment_form_prompt', __( 'Leave a Reply', 'jetpack' ) ), 'color_scheme' => get_option( 'jetpack_comment_form_color_scheme', $this->default_color_scheme ), 'lang' => get_bloginfo( 'language' ), + 'jetpack_version' => JETPACK__VERSION, ); // Extra parameters for logged in user @@ -223,6 +267,14 @@ class Jetpack_Comments extends Highlander_Comments_Base { $url_origin = ( is_ssl() ? 'https' : 'http' ) . '://jetpack.wordpress.com'; ?> + <!--[if IE]> + <script type="text/javascript"> + if ( 0 === window.location.hash.indexOf( '#comment-' ) ) { + // window.location.reload() doesn't respect the Hash in IE + window.location.hash = window.location.hash; + } + </script> + <![endif]--> <script type="text/javascript"> var comm_par_el = document.getElementById( 'comment_parent' ), comm_par = (comm_par_el && comm_par_el.value) ? comm_par_el.value : '', @@ -361,6 +413,7 @@ class Jetpack_Comments extends Highlander_Comments_Base { $comment_meta['hc_post_as'] = 'wordpress'; $comment_meta['hc_avatar'] = stripslashes( $_POST['hc_avatar'] ); $comment_meta['hc_foreign_user_id'] = stripslashes( $_POST['hc_userid'] ); + $comment_meta['hc_wpcom_id_sig'] = stripslashes( $_POST['hc_wpcom_id_sig'] ); //since 1.9 break; case 'jetpack' : @@ -379,6 +432,87 @@ class Jetpack_Comments extends Highlander_Comments_Base { foreach ( $comment_meta as $key => $value ) add_comment_meta( $comment_id, $key, $value, true ); } + function capture_comment_post_redirect_to_reload_parent_frame( $url ) { + if ( !isset( $_GET['for'] ) || 'jetpack' != $_GET['for'] ) { + return $url; + } +?> +<!DOCTYPE html> +<html <?php language_attributes(); ?>> +<!--<![endif]--> +<head> +<meta charset="<?php bloginfo( 'charset' ); ?>" /> +<title><?php printf( __( 'Submitting Comment%s', 'jetpack' ), '…' ); ?></title> +<style type="text/css"> +body { + display: table; + width: 100%; + height: 60%; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + color: #333; +} + +h1 { + text-align: center; + margin: 0; + padding: 0; + display: table-cell; + vertical-align: middle; + font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", sans-serif; + font-weight: normal; +} + +.hidden { + opacity: 0; +} + +h1 span { + -moz-transition-property: opacity; + -moz-transition-duration: 1s; + -moz-transition-timing-function: ease-in-out; + + -webkit-transition-property: opacity; + -webkit-transition-duration: 1s; + -webbit-transition-timing-function: ease-in-out; + + -o-transition-property: opacity; + -o-transition-duration: 1s; + -o-transition-timing-function: ease-in-out; + + -ms-transition-property: opacity; + -ms-transition-duration: 1s; + -ms-transition-timing-function: ease-in-out; + + transition-property: opacity; + transition-duration: 1s; + transition-timing-function: ease-in-out; +} +</style> +</head> +<body> + <h1><?php printf( __( 'Submitting Comment%s', 'jetpack' ), '<span id="ellipsis" class="hidden">…</span>' ); ?></h1> +<script type="text/javascript"> +try { + window.parent.location = <?php echo json_encode( $url ); ?>; + window.parent.location.reload( true ); +} catch ( e ) { + window.location = <?php echo json_encode( $url ); ?>; + window.location.reload( true ); +} +ellipsis = document.getElementById( 'ellipsis' ); +function toggleEllipsis() { + ellipsis.className = ellipsis.className ? '' : 'hidden'; +} +setInterval( toggleEllipsis, 1200 ); +</script> +</body> +</html> +<?php + exit; + } } Jetpack_Comments::init(); diff --git a/plugins/jetpack/modules/contact-form/admin.php b/plugins/jetpack/modules/contact-form/admin.php index b5fcadd9..d3efefa0 100644 --- a/plugins/jetpack/modules/contact-form/admin.php +++ b/plugins/jetpack/modules/contact-form/admin.php @@ -1,5 +1,43 @@ <?php +function menu_alter() { + echo ' + <style> + #menu-posts-feedback .wp-menu-image img { display: none; } + #adminmenu .menu-icon-feedback:hover div.wp-menu-image, #adminmenu .menu-icon-feedback.wp-has-current-submenu div.wp-menu-image, #adminmenu .menu-icon-feedback.current div.wp-menu-image { background: url("' .GRUNION_PLUGIN_URL . 'images/grunion-menu-hover.png") no-repeat 7px 7px !important; } + #adminmenu .menu-icon-feedback div.wp-menu-image, #adminmenu .menu-icon-feedback div.wp-menu-image, #adminmenu .menu-icon-feedback div.wp-menu-image { background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-menu.png") no-repeat 7px 7px !important; } + .grunion-menu-button { background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-form.png") no-repeat; width: 13px; height: 13px; display: inline-block; vertical-align: middle; ) } + @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) { + #adminmenu .menu-icon-feedback:hover div.wp-menu-image, #adminmenu .menu-icon-feedback.wp-has-current-submenu div.wp-menu-image, #adminmenu .menu-icon-feedback.current div.wp-menu-image { background: url("' .GRUNION_PLUGIN_URL . 'images/grunion-menu-hover-2x.png") no-repeat 7px 7px !important; background-size: 15px 16px !important; } + #adminmenu .menu-icon-feedback div.wp-menu-image, #adminmenu .menu-icon-feedback div.wp-menu-image, #adminmenu .menu-icon-feedback div.wp-menu-image { background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-menu-2x.png") no-repeat 7px 7px !important; background-size: 15px 16px !important; } + .grunion-menu-button { background-image: url("' . GRUNION_PLUGIN_URL . 'images/grunion-form-2x.png"); background-size: 13px 12px !important; vertical-align: bottom; } + } + </style>'; +} + +add_action('admin_head', 'menu_alter'); + +/** + * Add a contact form button to the post composition screen + */ +add_action( 'media_buttons', 'grunion_media_button', 999 ); +function grunion_media_button( ) { + global $post_ID, $temp_ID; + $iframe_post_id = (int) (0 == $post_ID ? $temp_ID : $post_ID); + $title = esc_attr( __( 'Add a custom form', 'jetpack' ) ); + $plugin_url = esc_url( GRUNION_PLUGIN_URL ); + $site_url = esc_url( admin_url( "/admin-ajax.php?post_id={$iframe_post_id}&action=grunion_form_builder&TB_iframe=true&width=768" ) ); + + echo '<a href="' . $site_url . '&id=add_form" class="thickbox" title="' . $title . '"><div class="grunion-menu-button" alt="' . $title . '"></div></a>'; +} + +add_action( 'wp_ajax_grunion_form_builder', 'display_form_view' ); + +function display_form_view() { + require_once GRUNION_PLUGIN_DIR . 'grunion-form-view.php'; + exit; +} + // feedback specific css items add_action( 'admin_print_styles', 'grunion_admin_css' ); function grunion_admin_css() { @@ -40,17 +78,91 @@ function grunion_admin_css() { color: #D98500; } -#icon-edit { background-position: -432px -5px; } - -#icon-edit, #icon-post { background: url("<?php echo GRUNION_PLUGIN_URL; ?>images/grunion-menu-big.png") no-repeat !important; } +#icon-edit.icon32-posts-feedback, #icon-post.icon32-posts-feedback { background: url("<?php echo GRUNION_PLUGIN_URL; ?>images/grunion-menu-big.png") no-repeat !important; } @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) { - #icon-edit, #icon-post { background: url("<?php echo GRUNION_PLUGIN_URL; ?>images/grunion-menu-big-2x.png") no-repeat !important; background-size: 30px 31px !important; } + #icon-edit.icon32-posts-feedback, #icon-post.icon32-posts-feedback { background: url("<?php echo GRUNION_PLUGIN_URL; ?>images/grunion-menu-big-2x.png") no-repeat !important; background-size: 30px 31px !important; } } + +#icon-edit.icon32-posts-feedback { background-position: 2px 2px !important; } + </style> <?php } +/** + * Hack a 'Bulk Spam' option for bulk edit + * There isn't a better way to do this until + * http://core.trac.wordpress.org/changeset/17297 is resolved + */ +add_action( 'admin_head', 'grunion_add_bulk_edit_option' ); +function grunion_add_bulk_edit_option() { + + $screen = get_current_screen(); + + if ( 'edit-feedback' != $screen->id + || ( ! empty( $_GET['post_status'] ) && 'spam' == $_GET['post_status'] ) ) + return; + + $spam_text = __( 'Mark Spam', 'jetpack' ); + ?> + <script type="text/javascript"> + jQuery(document).ready(function($) { + $('#posts-filter .actions select[name=action] option:first-child').after('<option value="spam"><?php echo esc_attr( $spam_text ); ?></option>' ); + }) + </script> + <?php +} + +/** + * Handle a bulk spam report + */ +add_action( 'admin_init', 'grunion_handle_bulk_spam' ); +function grunion_handle_bulk_spam() { + global $pagenow; + + if ( 'edit.php' != $pagenow + || ( empty( $_REQUEST['post_type'] ) || 'feedback' != $_REQUEST['post_type'] ) ) + return; + + // Slip in a success message + if ( ! empty( $_REQUEST['message'] ) && 'marked-spam' == $_REQUEST['message'] ) + add_action( 'admin_notices', 'grunion_message_bulk_spam' ); + + if ( empty( $_REQUEST['action'] ) || 'spam' != $_REQUEST['action'] ) + return; + + check_admin_referer('bulk-posts'); + + if ( empty( $_REQUEST['post'] ) ) { + wp_safe_redirect( wp_get_referer() ); + exit; + } + + $post_ids = array_map( 'intval', $_REQUEST['post'] ); + + foreach( $post_ids as $post_id ) { + if ( ! current_user_can( "edit_page", $post_id ) ) + wp_die( __( 'You are not allowed to manage this item.', 'jetpack' ) ); + + $post = array( + 'ID' => $post_id, + 'post_status' => 'spam', + ); + $akismet_values = get_post_meta( $post_id, '_feedback_akismet_values', true ); + wp_update_post( $post ); + do_action( 'contact_form_akismet', 'spam', $akismet_values ); + } + + $redirect_url = add_query_arg( 'message', 'marked-spam', wp_get_referer() ); + wp_safe_redirect( $redirect_url ); + exit; +} + +function grunion_message_bulk_spam() { + echo '<div class="updated"><p>' . __( 'Feedback(s) marked as spam', 'jetpack' ) . '</p></div>'; +} + // remove admin UI parts that we don't support in feedback management add_action( 'admin_menu', 'grunion_admin_menu' ); function grunion_admin_menu() { @@ -320,48 +432,41 @@ function grunion_sort_objects( $a, $b ) { // returns both the shortcode form, and HTML markup representing a preview of the form function grunion_ajax_shortcode() { check_ajax_referer( 'grunion_shortcode' ); - - $atts = ''; - if ( trim( $_POST['subject'] ) ) - $atts .= ' subject="'.grunion_esc_attr($_POST['subject']).'"'; - if ( trim( $_POST['to'] ) ) - $atts .= ' to="'.grunion_esc_attr($_POST['to']).'"'; - - $shortcode = '[contact-form'.$atts.']'; - $shortcode .= "\n"; + + $attributes = array(); + + foreach ( array( 'subject', 'to' ) as $attribute ) { + if ( isset( $_POST[$attribute] ) && strlen( $_POST[$attribute] ) ) { + $attributes[$attribute] = stripslashes( $_POST[$attribute] ); + } + } + if ( is_array( $_POST['fields'] ) ) { - usort( $_POST['fields'], 'grunion_sort_objects' ); - foreach ( $_POST['fields'] as $field ) { - $req = $opts = ''; - if ( $field['required'] == 'true' ) - $req = ' required="true"'; - if ( isset( $field['options'] ) && $field['options'] ) { - $opts = ' options="'; - foreach ( $field['options'] as $option ) { - $option = wp_kses( $option, array() ); - $option = grunion_esc_attr( $option ); - - # we need to be very specific about how we - # encode these values - $option = str_replace( ',', ',', $option ); - $option = str_replace( '"', '"', $option ); - $option = str_replace( "'", ''', $option ); - $option = str_replace( '&', '&', $option ); - - $opts .= $option . ','; - } - $opts = rtrim( $opts, ',' ) . '"'; + $fields = stripslashes_deep( $_POST['fields'] ); + usort( $fields, 'grunion_sort_objects' ); + + $field_shortcodes = array(); + + foreach ( $fields as $field ) { + $field_attributes = array(); + + if ( isset( $field['required'] ) && 'true' === $field['required'] ) { + $field_attributes['required'] = 'true'; } - $field['label'] = wp_kses( $field['label'], array() ); - $field['label'] = str_replace( '"', '"', $field['label'] ); + foreach ( array( 'options', 'label', 'type' ) as $attribute ) { + if ( isset( $field[$attribute] ) ) { + $field_attributes[$attribute] = $field[$attribute]; + } + } - $shortcode .= '[contact-field label="'. $field['label'] .'" type="'.grunion_esc_attr($field['type']).'"' . $req . $opts .' /]'."\n"; + $field_shortcodes[] = new Grunion_Contact_Form_Field( $field_attributes ); } } - $shortcode .= '[/contact-form]'; - - die( "\n$shortcode\n" ); + + $grunion = new Grunion_Contact_Form( $attributes, $field_shortcodes ); + + die( "\n$grunion\n" ); } // takes a post_id, extracts the contact-form shortcode from that post (if there is one), parses it, @@ -370,33 +475,45 @@ function grunion_ajax_shortcode_to_json() { global $post, $grunion_form; check_ajax_referer( 'grunion_shortcode_to_json' ); - if ( isset( $_POST['content'] ) && is_numeric( $_POST['post_id'] ) ) { - $content = stripslashes( $_POST['content'] ); - $post = get_post( $_POST['post_id'] ); - // does it look like a post with a [contact-form] already? - if ( strpos( $content, '[contact-form' ) !== false ) { - $out = do_shortcode($content); - global $contact_form_fields; - if ( is_array($contact_form_fields) && !empty($contact_form_fields) ) { - foreach ( $contact_form_fields as $field_id => $field ) { - # need to dig deeper on select field options - if ( preg_match( "|^(.*)\-select$|", $field_id ) ) { - foreach ( (array) $field['options'] as $opt_i => $opt ) { - $contact_form_fields[$field_id]['options'][$opt_i] = html_entity_decode( $opt ); - } - } - $contact_form_fields[$field_id]['label'] = html_entity_decode( $contact_form_fields[$field_id]['label'] ); - $contact_form_fields[$field_id]['label'] = wp_kses( $contact_form_fields[$field_id]['label'], array() ); - } - $out = array( 'fields' => $contact_form_fields, 'to' => $grunion_form->to, 'subject' => $grunion_form->subject ); - die( json_encode( $out ) ); - } - } + if ( !isset( $_POST['content'] ) || !is_numeric( $_POST['post_id'] ) ) { + die( '-1' ); + } + + $content = stripslashes( $_POST['content'] ); + + // doesn't look like a post with a [contact-form] already. + if ( false === strpos( $content, '[contact-form' ) ) { die( '' ); } - - die( -1 ); + + $post = get_post( $_POST['post_id'] ); + + do_shortcode( $content ); + + $grunion = Grunion_Contact_Form::$last; + + $out = array( + 'to' => '', + 'subject' => '', + 'fields' => array(), + ); + + foreach ( $grunion->fields as $field ) { + $out['fields'][$field->get_attribute( 'id' )] = $field->attributes; + } + + $to = $grunion->get_attribute( 'to' ); + $subject = $grunion->get_attribute( 'subject' ); + foreach ( array( 'to', 'subject' ) as $attribute ) { + $value = $grunion->get_attribute( $attribute ); + if ( isset( $grunion->defaults[$attribute] ) && $value == $grunion->defaults[$attribute] ) { + $value = ''; + } + $out[$attribute] = $value; + } + + die( json_encode( $out ) ); } diff --git a/plugins/jetpack/modules/contact-form/css/grunion.css b/plugins/jetpack/modules/contact-form/css/grunion.css index a8f1651d..00035180 100644 --- a/plugins/jetpack/modules/contact-form/css/grunion.css +++ b/plugins/jetpack/modules/contact-form/css/grunion.css @@ -1,9 +1,10 @@ -.textwidget input[type='text'], .textwidget textarea { width: 100% !important; } .contact-form .clear-form { clear: both; } -.contact-form input[type='text'] { width: 300px; margin-bottom: 13px; } +.contact-form input[type='text'], .contact-form input[type='email'] { width: 300px; margin-bottom: 13px; } .contact-form select { margin-bottom: 13px; } .contact-form textarea { height: 200px; width: 80%; float: none; margin-bottom: 13px; } .contact-form input[type='radio'], .contact-form input[type='checkbox'] { float: none; margin-bottom: 13px; } .contact-form label { margin-bottom: 3px; float: none; font-weight: bold; display: block; } .contact-form label.checkbox, .contact-form label.radio { margin-bottom: 3px; float: none; font-weight: bold; display: inline-block; } -.contact-form label span { color: #AAA; margin-left: 4px; font-weight: normal; }
\ No newline at end of file +.contact-form label span { color: #AAA; margin-left: 4px; font-weight: normal; } +.form-errors .form-error-message { color: red; } +.textwidget input[type='text'], .textwidget input[type='email'], .textwidget textarea { width: 250px; } diff --git a/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/plugins/jetpack/modules/contact-form/grunion-contact-form.php index bbf2ba54..5600febb 100644 --- a/plugins/jetpack/modules/contact-form/grunion-contact-form.php +++ b/plugins/jetpack/modules/contact-form/grunion-contact-form.php @@ -6,7 +6,7 @@ Description: Add a contact form to any post, page or text widget. Emails will b Plugin URI: http://automattic.com/# AUthor: Automattic, Inc. Author URI: http://automattic.com/ -Version: 2.3 +Version: 2.4 License: GPLv2 or later */ @@ -16,782 +16,1338 @@ define( 'GRUNION_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); if ( is_admin() ) require_once GRUNION_PLUGIN_DIR . '/admin.php'; -// take the content of a contact-form shortcode and parse it into a list of field types -function contact_form_parse( $content ) { - // first parse all the contact-field shortcodes into an array - global $contact_form_fields, $grunion_form; - $contact_form_fields = array(); +/** + * Sets up various actions, filters, post types, post statuses, shortcodes. + */ +class Grunion_Contact_Form_Plugin { + /** + * @var string The Widget ID of the widget currently being processed. Used to build the unique contact-form ID for forms embedded in widgets. + */ + var $current_widget_id; - if ( empty( $_REQUEST['action'] ) || $_REQUEST['action'] != 'grunion_shortcode_to_json' ) { - wp_print_styles( 'grunion.css' ); - } - - $out = do_shortcode( $content ); - - if ( empty($contact_form_fields) || !is_array($contact_form_fields) ) { - // default form: same as the original Grunion form - $default_form = ' - [contact-field label="'.__( 'Name', 'jetpack' ).'" type="name" required="true" /] - [contact-field label="'.__( 'Email', 'jetpack' ).'" type="email" required="true" /] - [contact-field label="'.__( 'Website', 'jetpack' ).'" type="url" /]'; - if ( 'yes' == strtolower($grunion_form->show_subject) ) { - $default_form .= ' - [contact-field label="'.__( 'Subject', 'jetpack' ).'" type="subject" /]'; + static function init() { + static $instance = false; + + if ( !$instance ) { + $instance = new Grunion_Contact_Form_Plugin; } - $default_form .= ' - [contact-field label="'.__( 'Message', 'jetpack' ).'" type="textarea" /]'; - $out = do_shortcode( $default_form ); + return $instance; } - return $out; -} - -function contact_form_render_field( $field ) { - global $contact_form_last_id, $contact_form_errors, $contact_form_fields, $current_user, $user_identity; - - $r = ''; - - $field_id = $field['id']; - if ( isset($_POST[ $field_id ]) ) { - $field_value = stripslashes( $_POST[ $field_id ] ); - } elseif ( is_user_logged_in() ) { - // Special defaults for logged-in users - if ( $field['type'] == 'email' ) - $field_value = $current_user->data->user_email; - elseif ( $field['type'] == 'name' ) - $field_value = $user_identity; - elseif ( $field['type'] == 'url' ) - $field_value = $current_user->data->user_url; - else - $field_value = $field['default']; - } else { - $field_value = $field['default']; + /** + * Strips HTML tags from input. Output is NOT HTML safe. + * + * @param string $string + * @return string + */ + static function strip_tags( $string ) { + $string = wp_kses( $string, array() ); + return str_replace( '&', '&', $string ); // undo damage done by wp_kses_normalize_entities() } - - $field_value = wp_kses($field_value, array()); - - $field['label'] = html_entity_decode( $field['label'] ); - $field['label'] = wp_kses( $field['label'], array() ); - - if ( $field['type'] == 'email' ) { - $r .= "\n<div>\n"; - $r .= "\t\t<label for='".esc_attr($field_id)."' class='grunion-field-label ".esc_attr($field['type']) . ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "'>" . htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - $r .= "\t\t<input type='text' name='".esc_attr($field_id)."' id='".esc_attr($field_id)."' value='".esc_attr($field_value)."' class='".esc_attr($field['type'])."'/>\n"; - $r .= "\t</div>\n"; - } elseif ( $field['type'] == 'textarea' ) { - $r .= "\n<div>\n"; - $r .= "\t\t<label class='".esc_attr($field['type']) . ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "' for='contact-form-comment-" . esc_attr( $field_id ) . "'>" . htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - $r .= "\t\t<textarea name='".esc_attr($field_id)."' id='contact-form-comment-".esc_attr($field_id)."' rows='20'>".htmlspecialchars($field_value)."</textarea>\n"; - $r .= "\t</div>\n"; - } elseif ( $field['type'] == 'radio' ) { - $r .= "\t<div><label class='". ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "'>" . htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - foreach ( $field['options'] as $option ) { - $r .= "\t\t<label class='" . esc_attr( $field['type'] ) . ( contact_form_is_error( $field_id ) ? ' form-error' : '' ) . "'>"; - $r .= "<input type='radio' name='".esc_attr($field_id)."' value='".esc_attr($option)."' class='".esc_attr($field['type'])."' ".( $option == $field_value ? "checked='checked' " : "")." /> "; - $r .= htmlspecialchars( $option ) . "</label>\n"; - $r .= "\t\t<div class='clear-form'></div>\n"; + + function __construct() { + $this->add_shortcode(); + + // While generating the output of a text widget with a contact-form shortcode, we need to know its widget ID. + add_action( 'dynamic_sidebar', array( $this, 'track_current_widget' ) ); + + // Add a "widget" shortcode attribute to all contact-form shortcodes embedded in widgets + add_filter( 'widget_text', array( $this, 'widget_atts' ), 0 ); + + // If Text Widgets don't get shortcode processed, hack ours into place. + if ( !has_filter( 'widget_text', 'do_shortcode' ) ) + add_filter( 'widget_text', array( $this, 'widget_shortcode_hack' ), 5 ); + + // Akismet to the rescue + if ( function_exists( 'akismet_http_post' ) ) { + add_filter( 'contact_form_is_spam', array( $this, 'is_spam_akismet' ), 10 ); + add_action( 'contact_form_akismet', array( $this, 'akismet_submit' ), 10, 2 ); } - $r .= "\t\t</div>\n"; - } elseif ( $field['type'] == 'checkbox' ) { - $r .= "\t<div>\n"; - $r .= "\t\t<label class='".esc_attr($field['type']) . ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "'>\n"; - $r .= "\t\t<input type='checkbox' name='".esc_attr($field_id)."' value='".__( 'Yes', 'jetpack' )."' class='".esc_attr($field['type'])."' ".( $field_value ? "checked='checked' " : "")." /> \n"; - $r .= "\t\t". htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - $r .= "\t\t<div class='clear-form'></div>\n"; - $r .= "\t</div>\n"; - } elseif ( $field['type'] == 'select' ) { - $r .= "\n<div>\n"; - $r .= "\t\t<label for='".esc_attr($field_id)."' class='".esc_attr($field['type']) . ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "'>" . htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - $r .= "\t<select name='".esc_attr($field_id)."' id='".esc_attr($field_id)."' value='".esc_attr($field_value)."' class='".esc_attr($field['type'])."'/>\n"; - foreach ( $field['options'] as $option ) { - $option = html_entity_decode( $option ); - $option = wp_kses( $option, array() ); - $r .= "\t\t<option".( $option == $field_value ? " selected='selected'" : "").">". esc_html( $option ) ."</option>\n"; - } - $r .= "\t</select>\n"; - $r .= "\t</div>\n"; - } else { - // default: text field - // note that any unknown types will produce a text input, so we can use arbitrary type names to handle - // input fields like name, email, url that require special validation or handling at POST - $r .= "\n<div>\n"; - $r .= "\t\t<label for='".esc_attr($field_id)."' class='".esc_attr($field['type']) . ( contact_form_is_error($field_id) ? ' form-error' : '' ) . "'>" . htmlspecialchars( $field['label'] ) . ( $field['required'] ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; - $r .= "\t\t<input type='text' name='".esc_attr($field_id)."' id='".esc_attr($field_id)."' value='".esc_attr($field_value)."' class='".esc_attr($field['type'])."'/>\n"; - $r .= "\t</div>\n"; + + add_action( 'loop_start', array( 'Grunion_Contact_Form', '_style_on' ) ); + + // custom post type we'll use to keep copies of the feedback items + register_post_type( 'feedback', array( + 'labels' => array( + 'name' => __( 'Feedbacks', 'jetpack' ), + 'singular_name' => __( 'Feedback', 'jetpack' ), + 'search_items' => __( 'Search Feedback', 'jetpack' ), + 'not_found' => __( 'No feedback found', 'jetpack' ), + 'not_found_in_trash' => __( 'No feedback found', 'jetpack' ) + ), + 'menu_icon' => GRUNION_PLUGIN_URL . '/images/grunion-menu.png', + 'show_ui' => TRUE, + 'show_in_admin_bar' => FALSE, + 'public' => FALSE, + 'rewrite' => FALSE, + 'query_var' => FALSE, + 'capability_type' => 'page' + ) ); + + // Add "spam" as a post status + register_post_status( 'spam', array( + 'label' => 'Spam', + 'public' => FALSE, + 'exclude_from_search' => TRUE, + 'show_in_admin_all_list' => FALSE, + 'label_count' => _n_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'jetpack' ), + 'protected' => TRUE, + '_builtin' => FALSE + ) ); + + // POST handler + if ( + 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) + && + isset( $_POST['action'] ) && 'grunion-contact-form' == $_POST['action'] + && + isset( $_POST['contact-form-id'] ) + ) { + add_action( 'template_redirect', array( $this, 'process_form_submission' ) ); + } + + /* Can be dequeued by placing the following in wp-content/themes/yourtheme/functions.php + * + * function remove_grunion_style() { + * wp_deregister_style('grunion.css'); + * } + * add_action('wp_print_styles', 'remove_grunion_style'); + */ + + wp_register_style( 'grunion.css', GRUNION_PLUGIN_URL . 'css/grunion.css', array(), JETPACK__VERSION ); } - - return $r; -} -function contact_form_validate_field( $field ) { - global $contact_form_last_id, $contact_form_errors, $contact_form_values; + /** + * Handles all contact-form POST submissions + * + * Conditionally attached to `template_redirect` + */ + function process_form_submission() { + $id = stripslashes( $_POST['contact-form-id'] ); + + check_admin_referer( "contact-form_{$id}" ); + + $is_widget = 0 === strpos( $id, 'widget-' ); + + $form = false; - $field_id = $field['id']; - $field_value = isset($_POST[ $field_id ]) ? stripslashes($_POST[ $field_id ]) : ''; + if ( $is_widget ) { + // It's a form embedded in a text widget - # pay special attention to required email fields - if ( $field['required'] && $field['type'] == 'email' ) { - if ( !is_email( $field_value ) ) { - if ( !is_wp_error( $contact_form_errors ) ) { - $contact_form_errors = new WP_Error(); + $this->current_widget_id = substr( $id, 7 ); // remove "widget-" + + // Is the widget active? + $sidebar = is_active_widget( false, $this->current_widget_id, 'text' ); + + // This is lame - no core API for getting a widget by ID + $widget = isset( $GLOBALS['wp_registered_widgets'][$this->current_widget_id] ) ? $GLOBALS['wp_registered_widgets'][$this->current_widget_id] : false; + + if ( $sidebar && $widget && isset( $widget['callback'] ) ) { + // This is lamer - no API for outputting a given widget by ID + ob_start(); + // Process the widget to populate Grunion_Contact_Form::$last + call_user_func( $widget['callback'], array(), $widget['params'][0] ); + ob_end_clean(); } + } else { + // It's a form embedded in a post + + $post = get_post( $id ); - $contact_form_errors->add( $field_id, sprintf( __( '%s requires a valid email address', 'jetpack' ), $field['label'] ) ); + // Process the content to populate Grunion_Contact_Form::$last + apply_filters( 'the_content', $post->post_content ); } - } elseif ( $field['required'] && !trim($field_value) ) { - if ( !is_wp_error($contact_form_errors) ) { - $contact_form_errors = new WP_Error(); + + $form = Grunion_Contact_Form::$last; + + if ( !$form || ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) ) { + return; } - $contact_form_errors->add( $field_id, sprintf( __( '%s is required', 'jetpack' ), $field['label'] ) ); + // Process the form + $form->process_submission(); } - - $contact_form_values[ $field_id ] = $field_value; -} -function contact_form_is_error( $field_id ) { - global $contact_form_errors; - - return ( is_wp_error( $contact_form_errors ) && $contact_form_errors->get_error_message( $field_id ) ); -} + /** + * Ensure the post author is always zero for contact-form feedbacks + * Attached to `wp_insert_post_data` + * + * @see Grunion_Contact_Form::process_submission() + * + * @param array $data the data to insert + * @param array $postarr the data sent to wp_insert_post() + * @return array The filtered $data to insert + */ + function insert_feedback_filter( $data, $postarr ) { + if ( $data['post_type'] == 'feedback' && $postarr['post_type'] == 'feedback' ) { + $data['post_author'] = 0; + } -// generic shortcode that handles all of the major input types -// this parses the field attributes into an array that is used by other functions for rendering, validation etc -function contact_form_field( $atts, $content, $tag ) { - global $contact_form_fields, $contact_form_last_id, $grunion_form; - - $field = shortcode_atts( array( - 'label' => null, - 'type' => 'text', - 'required' => false, - 'options' => array(), - 'id' => null, - 'default' => null, - ), $atts); - - // special default for subject field - if ( $field['type'] == 'subject' && is_null($field['default']) ) - $field['default'] = $grunion_form->subject; - - // allow required=1 or required=true - if ( $field['required'] == '1' || strtolower($field['required']) == 'true' ) - $field['required'] = true; - else - $field['required'] = false; - - // parse out comma-separated options list - if ( !empty($field['options']) && is_string($field['options']) ) - $field['options'] = array_map('trim', explode(',', $field['options'])); - - // make a unique field ID based on the label, with an incrementing number if needed to avoid clashes - $id = $field['id']; - if ( empty($id) ) { - $id = sanitize_title_with_dashes( $contact_form_last_id . '-' . $field['label'] ); - $i = 0; - $max_tries = 12; - while ( isset( $contact_form_fields[ $id ] ) ) { - $i++; - $id = sanitize_title_with_dashes( $contact_form_last_id . '-' . $field['label'] . '-' . $i ); - - if ( $i > $max_tries ) { - break; - } + return $data; + } + + /* + * Adds our contact-form shortcode + * The "child" contact-field shortcode is added as needed by the contact-form shortcode handler + */ + function add_shortcode() { + add_shortcode( 'contact-form', array( 'Grunion_Contact_Form', 'parse' ) ); + } + + /** + * Tracks the widget currently being processed. + * Attached to `dynamic_sidebar` + * + * @see $current_widget_id + * + * @param array $widget The widget data + */ + function track_current_widget( $widget ) { + $this->current_widget_id = $widget['id']; + } + + /** + * Adds a "widget" attribute to every contact-form embedded in a text widget. + * Used to tell the difference between post-embedded contact-forms and widget-embedded contact-forms + * Attached to `widget_text` + * + * @param string $text The widget text + * @return string The filtered widget text + */ + function widget_atts( $text ) { + Grunion_Contact_Form::style( true ); + + return preg_replace( '/\[contact-form([^a-zA-Z_-])/', '[contact-form widget="' . $this->current_widget_id . '"\\1', $text ); + } + + /** + * For sites where text widgets are not processed for shortcodes, we add this hack to process just our shortcode + * Attached to `widget_text` + * + * @param string $text The widget text + * @return string The contact-form filtered widget text + */ + function widget_shortcode_hack( $text ) { + if ( !preg_match( '/\[contact-form([^a-zA-Z_-])/', $text ) ) { + return $text; } - $field['id'] = $id; + + $old = $GLOBALS['shortcode_tags']; + remove_all_shortcodes(); + $this->add_shortcode(); + + $text = do_shortcode( $text ); + + $GLOBALS['shortcode_tags'] = $old; + + return $text; } + + /** + * Populate an array with all values necessary to submit a NEW contact-form feedback to Akismet. + * Note that this includes the current user_ip etc, so this should only be called when accepting a new item via $_POST + * + * @param array $form Contact form feedback array + * @return array feedback array with additional data ready for submission to Akismet + */ + function prepare_for_akismet( $form ) { + $form['comment_type'] = 'contact_form'; + $form['user_ip'] = preg_replace( '/[^0-9., ]/', '', $_SERVER['REMOTE_ADDR'] ); + $form['user_agent'] = $_SERVER['HTTP_USER_AGENT']; + $form['referrer'] = $_SERVER['HTTP_REFERER']; + $form['blog'] = get_option( 'home' ); + + $ignore = array( 'HTTP_COOKIE' ); + + foreach ( $_SERVER as $k => $value ) + if ( !in_array( $k, $ignore ) && is_string( $value ) ) + $form["$k"] = $value; + + return $form; + } + + /** + * Submit contact-form data to Akismet to check for spam. + * If you're accepting a new item via $_POST, run it Grunion_Contact_Form_Plugin::prepare_for_akismet() first + * Attached to `contact_form_is_spam` + * + * @param array $form + * @return bool|WP_Error TRUE => spam, FALSE => not spam, WP_Error => stop processing entirely + */ + function is_spam_akismet( $form ) { + global $akismet_api_host, $akismet_api_port; - $contact_form_fields[ $id ] = $field; - - if ( isset( $_POST['contact-form-id'] ) && $_POST['contact-form-id'] == $contact_form_last_id ) - contact_form_validate_field( $field ); - - return contact_form_render_field( $field ); + if ( !function_exists( 'akismet_http_post' ) ) + return false; + + $query_string = http_build_query( $form ); + + $response = akismet_http_post( $query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port ); + $result = false; + if ( 'true' == trim( $response[1] ) ) // 'true' is spam + $result = true; + return apply_filters( 'contact_form_is_spam_akismet', $result, $form ); + } + + /** + * Submit a feedback as either spam or ham + * + * @param string $as Either 'spam' or 'ham'. + * @param array $form the contact-form data + */ + function akismet_submit( $as, $form ) { + global $akismet_api_host, $akismet_api_port; + + if ( !in_array( $as, array( 'ham', 'spam' ) ) ) + return false; + + $query_string = http_build_query( $form ); + + $response = akismet_http_post( $query_string, $akismet_api_host, "/1.1/submit-{$as}", $akismet_api_port ); + return trim( $response[1] ); + } } -add_shortcode('contact-field', 'contact_form_field'); +/** + * Generic shortcode class. + * Does nothing other than store structured data and output the shortcode as a string + * + * Not very general - specific to Grunion. + */ +class Crunion_Contact_Form_Shortcode { + /** + * @var string the name of the shortcode: [$shortcode_name /] + */ + var $shortcode_name; + + /** + * @var array key => value pairs for the shortcode's attributes: [$shortcode_name key="value" ... /] + */ + var $attributes; + + /** + * @var array key => value pair for attribute defaults + */ + var $defaults = array(); + + /** + * @var null|string Null for selfclosing shortcodes. Hhe inner content of otherwise: [$shortcode_name]$content[/$shortcode_name] + */ + var $content; + + /** + * @var array Associative array of inner "child" shortcodes equivalent to the $content: [$shortcode_name][child 1/][child 2/][/$shortcode_name] + */ + var $fields; + + /** + * @var null|string The HTML of the parsed inner "child" shortcodes". Null for selfclosing shortcodes. + */ + var $body; + + /** + * @param array $attributes An associative array of shortcode attributes. @see shortcode_atts() + * @param null|string $content Null for selfclosing shortcodes. The inner content otherwise. + */ + function __construct( $attributes, $content = null ) { + $this->attributes = $this->unesc_attr( $attributes ); + if ( is_array( $content ) ) { + $string_content = ''; + foreach ( $content as $field ) { + $string_content .= (string) $field; + } + + $this->content = $string_content; + } else { + $this->content = $content; + } + $this->parse_content( $content ); + } -function contact_form_shortcode( $atts, $content ) { - global $post; + /** + * Processes the shortcode's inner content for "child" shortcodes + * + * @param string $content The shortcode's inner content: [shortcode]$content[/shortcode] + */ + function parse_content( $content ) { + if ( is_null( $content ) ) { + $this->body = null; + } - $default_to = get_option( 'admin_email' ); - $default_subject = "[" . get_option( 'blogname' ) . "]"; + $this->body = do_shortcode( $content ); + } - if ( !empty( $atts['widget'] ) && $atts['widget'] ) { - $default_subject .= " Sidebar"; - } elseif ( $post->ID ) { - $default_subject .= " ". wp_kses( $post->post_title, array() ); - $post_author = get_userdata( $post->post_author ); - $default_to = $post_author->user_email; + /** + * Returns the value of the requested attribute. + * + * @param string $key The attribute to retrieve + * @return mixed + */ + function get_attribute( $key ) { + return isset( $this->attributes[$key] ) ? $this->attributes[$key] : null; } - extract( shortcode_atts( array( - 'to' => $default_to, - 'subject' => $default_subject, - 'show_subject' => 'no', // only used in back-compat mode - 'widget' => 0 //This is not exposed to the user. Works with contact_form_widget_atts - ), $atts ) ); + function esc_attr( $value ) { + if ( is_array( $value ) ) { + return array_map( array( $this, 'esc_attr' ), $value ); + } - $widget = esc_attr( $widget ); + $value = Grunion_Contact_Form_Plugin::strip_tags( $value ); + $value = _wp_specialchars( $value, ENT_QUOTES, false, true ); - if ( ( function_exists( 'faux_faux' ) && faux_faux() ) || is_feed() ) - return '[contact-form]'; + // Shortcode attributes can't contain "]" + $value = str_replace( ']', '', $value ); + $value = str_replace( ',', ',', $value ); // store commas encoded + $value = strtr( $value, array( '%' => '%25', '&' => '%26' ) ); - global $wp_query, $grunion_form, $contact_form_errors, $contact_form_values, $user_identity, $contact_form_last_id, $contact_form_message; - - // used to store attributes, configuration etc for access by contact-field shortcodes - $grunion_form = new stdClass(); - $grunion_form->to = $to; - $grunion_form->subject = $subject; - $grunion_form->show_subject = $show_subject; - - if ( $widget ) - $id = 'widget-' . $widget; - elseif ( is_singular() ) - $id = $wp_query->get_queried_object_id(); - else - $id = $GLOBALS['post']->ID; - if ( !$id ) // something terrible has happened - return '[contact-form]'; - - if ( $id == $contact_form_last_id ) - return; - else - $contact_form_last_id = $id; - - ob_start(); - wp_nonce_field( 'contact-form_' . $id ); - $nonce = ob_get_contents(); - ob_end_clean(); - - - $body = contact_form_parse( $content ); - - $r = "<div id='contact-form-$id'>\n"; - - $errors = array(); - if ( is_wp_error( $contact_form_errors ) && $errors = (array) $contact_form_errors->get_error_codes() ) { - $r .= "<div class='form-error'>\n<h3>" . __( 'Error!', 'jetpack' ) . "</h3>\n<ul class='form-errors'>\n"; - foreach ( $contact_form_errors->get_error_messages() as $message ) - $r .= "\t<li class='form-error-message' style='color: red;'>$message</li>\n"; - $r .= "</ul>\n</div>\n\n"; + // shortcode_parse_atts() does stripcslashes() + $value = addslashes( $value ); + return $value; } - - $action = apply_filters( 'grunion_contact_form_form_action', get_permalink( $post->ID ) . "#contact-form-$id", $post, $id ); - $r .= "<form action='" . esc_url( $action ) . "' method='post' class='contact-form commentsblock'>\n"; - $r .= $body; - $r .= "\t<p class='contact-submit'>\n"; - $r .= "\t\t<input type='submit' value='" . __( "Submit »", 'jetpack' ) . "' class='pushbutton-wide'/>\n"; - $r .= "\t\t$nonce\n"; - $r .= "\t\t<input type='hidden' name='contact-form-id' value='$id' />\n"; - $r .= "\t</p>\n"; - $r .= "</form>\n</div>"; - - if ( !isset( $_POST['contact-form-id'] ) || $_POST['contact-form-id'] != $contact_form_last_id ) - return $r; + function unesc_attr( $value ) { + if ( is_array( $value ) ) { + return array_map( array( $this, 'unesc_attr' ), $value ); + } - if ( is_wp_error($contact_form_errors) ) - return $r; + // For back-compat with old Grunion encoding + // Also, unencode commas + $value = strtr( $value, array( '%26' => '&', '%25' => '%' ) ); + $value = preg_replace( array( '/�*22;/i', '/�*27;/i', '/�*26;/i', '/�*2c;/i' ), array( '"', "'", '&', ',' ), $value ); + $value = htmlspecialchars_decode( $value, ENT_QUOTES ); + $value = Grunion_Contact_Form_Plugin::strip_tags( $value ); - - $emails = str_replace( ' ', '', $to ); - $emails = explode( ',', $emails ); - foreach ( (array) $emails as $email ) { - if ( is_email( $email ) && ( !function_exists( 'is_email_address_unsafe' ) || !is_email_address_unsafe( $email ) ) ) - $valid_emails[] = $email; + return $value; } - $to = ( $valid_emails ) ? $valid_emails : $default_to; + /** + * Generates the shortcode + */ + function __toString() { + $r = "[{$this->shortcode_name} "; - $message_sent = contact_form_send_message( $to, $subject, $widget ); + foreach ( $this->attributes as $key => $value ) { + if ( !$value ) { + continue; + } - if ( is_array( $contact_form_values ) ) - extract( $contact_form_values ); + if ( isset( $this->defaults[$key] ) && $this->defaults[$key] == $value ) { + continue; + } - if ( !isset( $comment_content ) ) - $comment_content = ''; - else - $comment_content = wp_kses( $comment_content, array() ); + if ( 'id' == $key ) { + continue; + } + $value = $this->esc_attr( $value ); - $r = "<div id='contact-form-$id'>\n"; + if ( is_array( $value ) ) { + $value = join( ',', $value ); + } - $errors = array(); - if ( is_wp_error( $contact_form_errors ) && $errors = (array) $contact_form_errors->get_error_codes() ) : - $r .= "<div class='form-error'>\n<h3>" . __( 'Error!', 'jetpack' ) . "</h3>\n<p>\n"; - foreach ( $contact_form_errors->get_error_messages() as $message ) - $r .= "\t$message<br />\n"; - $r .= "</p>\n</div>\n\n"; - else : - $r_success_message = "<h3>" . __( 'Message Sent', 'jetpack' ) . "</h3>\n\n"; - $r_success_message .= wp_kses($contact_form_message, array('br' => array(), 'blockquote' => array())); + if ( false === strpos( $value, "'" ) ) { + $value = "'$value'"; + } elseif ( false === strpos( $value, '"' ) ) { + $value = '"' . $value . '"'; + } else { + // Shortcodes can't contain both '"' and "'". Strip one. + $value = str_replace( "'", '', $value ); + $value = "'$value'"; + } - $r .= apply_filters( 'grunion_contact_form_success_message', $r_success_message ); + $r .= "{$key}={$value} "; + } - $r .= "</div>"; - - // Reset for multiple contact forms. Hacky - $contact_form_values['comment_content'] = ''; + $r = rtrim( $r ); - return $r; - endif; + if ( $this->fields ) { + $r .= ']'; - return $r; -} -add_shortcode( 'contact-form', 'contact_form_shortcode' ); + foreach ( $this->fields as $field ) { + $r .= (string) $field; + } -function contact_form_send_message( $to, $subject, $widget ) { - global $post; - - if ( !isset( $_POST['contact-form-id'] ) ) - return; - - if ( ( $widget && 'widget-' . $widget != $_POST['contact-form-id'] ) || ( !$widget && $post->ID != $_POST['contact-form-id'] ) ) - return; - - if ( $widget ) - check_admin_referer( 'contact-form_widget-' . $widget ); - else - check_admin_referer( 'contact-form_' . $post->ID ); - - global $contact_form_values, $contact_form_errors, $current_user, $user_identity; - global $contact_form_fields, $contact_form_message; - - // compact the fields and values into an array of Label => Value pairs - // also find values for comment_author_email and other significant fields - $all_values = $extra_values = array(); - - foreach ( $contact_form_fields as $id => $field ) { - if ( $field['type'] == 'email' && !isset( $comment_author_email ) ) { - $comment_author_email = $contact_form_values[ $id ]; - $comment_author_email_label = $field['label']; - } elseif ( $field['type'] == 'name' && !isset( $comment_author ) ) { - $comment_author = $contact_form_values[ $id ]; - $comment_author_label = $field['label']; - } elseif ( $field['type'] == 'url' && !isset( $comment_author_url ) ) { - $comment_author_url = $contact_form_values[ $id ]; - $comment_author_url_label = $field['label']; - } elseif ( $field['type'] == 'subject' && !isset( $contact_form_subject ) ) { - $contact_form_subject = $contact_form_values[$id]; - $contact_form_subject_label = $field['label']; - } elseif ( $field['type'] == 'textarea' && !isset( $comment_content ) ) { - $comment_content = $contact_form_values[ $id ]; - $comment_content_label = $field['label']; + $r .= "[/{$this->shortcode_name}]"; } else { - $extra_values[ $field['label'] ] = $contact_form_values[ $id ]; + $r .= '/]'; } - - $all_values[ $field['label'] ] = $contact_form_values[ $id ]; + + return $r; } +} -/* - $contact_form_values = array(); - $contact_form_errors = new WP_Error(); +/** + * Class for the contact-form shortcode. + * Parses shortcode to output the contact form as HTML + * Sends email and stores the contact form response (a.k.a. "feedback") + */ +class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { + var $shortcode_name = 'contact-form'; - list($comment_author, $comment_author_email, $comment_author_url) = is_user_logged_in() ? - add_magic_quotes( array( $user_identity, $current_user->data->user_email, $current_user->data->user_url ) ) : - array( $_POST['comment_author'], $_POST['comment_author_email'], $_POST['comment_author_url'] ); -*/ + /** + * @var WP_Error stores form submission errors + */ + var $errors; - $comment_author = stripslashes( apply_filters( 'pre_comment_author_name', $comment_author ) ); + /** + * @var Grunion_Contact_Form The most recent (inclusive) contact-form shortcode processed + */ + static $last; - if ( !empty( $comment_author_email ) ) { - $comment_author_email = stripslashes( apply_filters( 'pre_comment_author_email', $comment_author_email ) ); - } else { - $comment_author_email = ''; - $comment_author_email_label = ''; - } + /** + * @var bool Whether to print the grunion.css style when processing the contact-form shortcode + */ + static $style = false; + + function __construct( $attributes, $content = null ) { + global $post; + + // Set up the default subject and recipient for this form + $default_to = get_option( 'admin_email' ); + $default_subject = "[" . get_option( 'blogname' ) . "]"; - if ( !empty( $comment_author_url ) ) { - $comment_author_url = stripslashes( apply_filters( 'pre_comment_author_url', $comment_author_url ) ); - if ( 'http://' == $comment_author_url ) { - $comment_author_url = ''; + if ( !empty( $attributes['widget'] ) && $attributes['widget'] ) { + $attributes['id'] = 'widget-' . $attributes['widget']; + + $default_subject = sprintf( _x( '%1$s Sidebar', '%1$s = blog name', 'jetpack' ), $default_subject ); + } else if ( $post ) { + $attributes['id'] = $post->ID; + $default_subject = sprintf( _x( '%1$s %2$s', '%1$s = blog name, %2$s = post title', 'jetpack' ), $default_subject, Grunion_Contact_Form_Plugin::strip_tags( $post->post_title ) ); + $post_author = get_userdata( $post->post_author ); + $default_to = $post_author->user_email; } - } else { - $comment_author_url = ''; - $comment_author_url_label = ''; - } - $comment_content = stripslashes( $comment_content ); - $comment_content = trim( wp_kses( $comment_content, array() ) ); + $this->defaults = array( + 'to' => $default_to, + 'subject' => $default_subject, + 'show_subject' => 'no', // only used in back-compat mode + 'widget' => 0, // Not exposed to the user. Works with Grunion_Contact_Form_Plugin::widget_atts() + 'id' => null, // Not exposed to the user. Set above. + ); + + $attributes = shortcode_atts( $this->defaults, $attributes ); - if ( empty( $contact_form_subject ) ) - $contact_form_subject = trim( wp_kses( $subject, array() ) ); - else - $contact_form_subject = trim( wp_kses( $contact_form_subject, array() ) ); - - $comment_author_IP = $_SERVER['REMOTE_ADDR']; + // We only add the contact-field shortcode temporarily while processing the contact-form shortcode + add_shortcode( 'contact-field', array( $this, 'parse_contact_field' ) ); - $vars = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'contact_form_subject', 'comment_author_IP' ); - foreach ( $vars as $var ) - $$var = str_replace( array("\n", "\r" ), '', $$var ); // I don't know if it's possible to inject this - $vars[] = 'comment_content'; + parent::__construct( $attributes, $content ); - $contact_form_values = compact( $vars ); + // There were no fields in the contact form. The form was probably just [contact-form /]. Build a default form. + if ( empty( $this->fields ) ) { + // same as the original Grunion v1 form + $default_form = ' + [contact-field label="' . __( 'Name', 'jetpack' ) . '" type="name" required="true" /] + [contact-field label="' . __( 'Email', 'jetpack' ) . '" type="email" required="true" /] + [contact-field label="' . __( 'Website', 'jetpack' ) . '" type="url" /]'; - $spam = ''; - $akismet_values = contact_form_prepare_for_akismet( $contact_form_values ); - $is_spam = apply_filters( 'contact_form_is_spam', $akismet_values ); - if ( is_wp_error( $is_spam ) ) - return; // abort - else if ( $is_spam === TRUE ) - $spam = '***SPAM*** '; + if ( 'yes' == strtolower( $this->get_attribute( 'show_subject' ) ) ) { + $default_form .= ' + [contact-field label="' . __( 'Subject', 'jetpack' ) . '" type="subject" /]'; + } - if ( !$comment_author ) - $comment_author = $comment_author_email; + $default_form .= ' + [contact-field label="' . __( 'Message', 'jetpack' ) . '" type="textarea" /]'; - $to = apply_filters( 'contact_form_to', $to ); - foreach ( (array) $to as $to_key => $to_value ) { - $to[$to_key] = wp_kses( $to_value, array() ); + $this->parse_content( $default_form ); + } + + // $this->body and $this->fields have been setup. We no longer need the contact-field shortcode. + remove_shortcode( 'contact-field' ); + } + + /** + * Toggle for printing the grunion.css stylesheet + * + * @param bool $style + */ + static function style( $style ) { + $previous_style = self::$style; + self::$style = (bool) $style; + return $previous_style; } - $from_email_addr = $to[0]; - if ( !empty( $comment_author_email ) ) { - $from_email_addr = $comment_author_email; + /** + * Turn on printing of grunion.css stylesheet + * @see ::style() + * @internal + * @param bool $style + */ + static function _style_on() { + return self::style( true ); } - $headers = 'From: ' . wp_kses( $comment_author, array() ) . - ' <' . wp_kses( $from_email_addr, array() ) . ">\r\n" . - 'Reply-To: ' . wp_kses( $from_email_addr, array() ) . "\r\n" . - "Content-Type: text/plain; charset=\"" . get_option('blog_charset') . "\""; - $subject = apply_filters( 'contact_form_subject', $contact_form_subject ); - $subject = wp_kses( $subject, array() ); + /** + * The contact-form shortcode processor + * + * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts() + * @param string|null $content The shortcode's inner content: [contact-form]$content[/contact-form] + * @return string HTML for the concat form. + */ + static function parse( $attributes, $content ) { + // Create a new Grunion_Contact_Form object (this class) + $form = new Grunion_Contact_Form( $attributes, $content ); + + $id = $form->get_attribute( 'id' ); - $time = date_i18n( __( 'l F j, Y \a\t g:i a', 'jetpack' ), current_time( 'timestamp' ) ); - - $extra_content = ''; - $extra_content_br = ''; + if ( !$id ) { // something terrible has happened + return '[contact-form]'; + } + + if ( apply_filters( 'jetpack_bail_on_shortcode', false, 'contact-form' ) || is_feed() ) { + return '[contact-form]'; + } + + // Only allow one contact form per post/widget + if ( self::$last && $id == self::$last->get_attribute( 'id' ) ) { + // We're processing the same post + + if ( self::$last->attributes != $form->attributes || self::$last->content != $form->content ) { + // And we're processing a different shortcode; + return ''; + } // else, we're processing the same shortcode - probably a separate run of do_shortcode() - let it through + + } else { + self::$last = $form; + } + + // Enqueue the grunion.css stylesheet if self::$style allows it + if ( self::$style && ( empty( $_REQUEST['action'] ) || $_REQUEST['action'] != 'grunion_shortcode_to_json' ) ) { + // Enqueue the style here instead of printing it, because if some other plugin has run the_post()+rewind_posts(), + // (like VideoPress does), the style tag gets "printed" the first time and discarded, leaving the contact form unstyled. + // when WordPress does the real loop. + wp_enqueue_style( 'grunion.css' ); + } + + $r = ''; + $r .= "<div id='contact-form-$id'>\n"; - foreach ( $extra_values as $label => $value ) { - $extra_content .= $label . ': ' . trim($value) . "\n"; - $extra_content_br .= wp_kses( $label, array() ) . ': ' . wp_kses( trim($value), array() ) . "<br />"; - } - - $message = "$comment_author_label: $comment_author\n"; - if ( !empty( $comment_author_email ) ) { - $message .= "$comment_author_email_label: $comment_author_email\n"; - } - if ( !empty( $comment_author_url ) ) { - $message .= "$comment_author_url_label: $comment_author_url\n"; - } - $message .= "$comment_content_label: $comment_content\n"; - $message .= $extra_content . "\n"; - - $message .= __( "Time:", 'jetpack' ) . " " . $time . "\n"; - $message .= __( "IP Address:", 'jetpack' ) . " " . $comment_author_IP . "\n"; - $message .= __( "Contact Form URL:", 'jetpack' ) . " " . get_permalink( $post->ID ) . "\n"; - - - // Construct message that is returned to user - $contact_form_message = "<blockquote>"; - if (isset($comment_author_label)) - $contact_form_message .= wp_kses( $comment_author_label, array() ) . ": " . wp_kses( $comment_author, array() ) . "<br />"; - if ( !empty( $comment_author_email ) ) - $contact_form_message .= wp_kses( $comment_author_email_label, array() ) . ": " . wp_kses( $comment_author_email, array() ) . "<br />"; - if ( !empty( $comment_author_url ) ) - $contact_form_message .= wp_kses( $comment_author_url_label, array() ) . ": " . wp_kses( $comment_author_url, array() ) . "<br />"; - if ( !empty( $contact_form_subject_label ) ) { - $contact_form_message .= wp_kses( $contact_form_subject_label, array() ) . ": " . wp_kses( $contact_form_subject, array() ) . "<br />"; - } - if (isset($comment_content_label)) - $contact_form_message .= wp_kses( $comment_content_label, array() ) . ": " . wp_kses( $comment_content, array() ) . "<br />"; - if (isset($extra_content_br)) - $contact_form_message .= $extra_content_br; - $contact_form_message .= "</blockquote><br /><br />"; - - if ( is_user_logged_in() ) { - $message .= "\n"; - $message .= sprintf( - __( 'Sent by a verified %s user.', 'jetpack' ), - isset( $GLOBALS['current_site']->site_name ) && $GLOBALS['current_site']->site_name ? $GLOBALS['current_site']->site_name : '"' . get_option( 'blogname' ) . '"' - ); - } else { - $message .= __( "Sent by an unverified visitor to your site.", 'jetpack' ); - } + if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) { + // There are errors. Display them + $r .= "<div class='form-error'>\n<h3>" . __( 'Error!', 'jetpack' ) . "</h3>\n<ul class='form-errors'>\n"; + foreach ( $form->errors->get_error_messages() as $message ) + $r .= "\t<li class='form-error-message'>" . esc_html( $message ) . "</li>\n"; + $r .= "</ul>\n</div>\n\n"; + } + + if ( isset( $_GET['contact-form-id'] ) && $_GET['contact-form-id'] == self::$last->get_attribute( 'id' ) && isset( $_GET['contact-form-sent'] ) ) { + // The contact form was submitted. Show the success message/results + + $feedback_id = (int) $_GET['contact-form-sent']; + + $back_url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', '_wpnonce' ) ); + + $r_success_message = + "<h3>" . __( 'Message Sent', 'jetpack' ) . + ' (<a href="' . esc_url( $back_url ) . '">' . esc_html__( 'go back', 'jetpack' ) . '</a>)' . + "</h3>\n\n"; + + // Don't show the feedback details unless the nonce matches + if ( $feedback_id && wp_verify_nonce( stripslashes( $_GET['_wpnonce'] ), "contact-form-sent-{$feedback_id}" ) ) { + $feedback = get_post( $feedback_id ); + + $field_ids = $form->get_field_ids(); + + // Maps field_ids to post_meta keys + $field_value_map = array( + 'name' => 'author', + 'email' => 'author_email', + 'url' => 'author_url', + 'subject' => 'subject', + 'textarea' => false, // not a post_meta key. This is stored in post_content + ); + + $contact_form_message = "<blockquote>\n"; + + // "Standard" field whitelist + foreach ( $field_value_map as $type => $meta_key ) { + if ( isset( $field_ids[$type] ) ) { + $field = $form->fields[$field_ids[$type]]; + + if ( $meta_key ) { + $value = get_post_meta( $feedback_id, "_feedback_{$meta_key}", true ); + } else { + // The feedback content is stored as the first "half" of post_content + $value = $feedback->post_content; + list( $value ) = explode( '<!--more-->', $value ); + $value = trim( $value ); + } + + $contact_form_message .= sprintf( + _x( '%1$s: %2$s', '%1$s = form field label, %2$s = form field value', 'jetpack' ), + wp_kses( $field->get_attribute( 'label' ), array() ), + wp_kses( $value, array() ) + ) . '<br />'; + } + } + + // "Non-standard" fields + if ( $field_ids['extra'] ) { + // array indexed by field label (not field id) + $extra_fields = get_post_meta( $feedback_id, '_feedback_extra_fields', true ); + + foreach ( $field_ids['extra'] as $field_id ) { + $field = $form->fields[$field_id]; + $label = $field->get_attribute( 'label' ); + $contact_form_message .= sprintf( + _x( '%1$s: %2$s', '%1$s = form field label, %2$s = form field value', 'jetpack' ), + wp_kses( $label, array() ), + wp_kses( $extra_fields[$label], array() ) + ) . '<br />'; + } + } + + $contact_form_message .= "</blockquote><br /><br />"; + + $r_success_message .= wp_kses( $contact_form_message, array( 'br' => array(), 'blockquote' => array() ) ); + } - $message = apply_filters( 'contact_form_message', $message ); - $message = wp_kses( $message, array() ); + $r .= apply_filters( 'grunion_contact_form_success_message', $r_success_message ); + } else { + // Nothing special - show the normal contact form + + if ( $form->get_attribute( 'widget' ) ) { + // Submit form to the current URL + $url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', 'action', '_wpnonce' ) ); + } else { + // Submit form to the post permalink + $url = get_permalink(); + } + + // May eventually want to send this to admin-post.php... + $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id ); + + $r .= "<form action='" . esc_url( $url ) . "' method='post' class='contact-form commentsblock'>\n"; + $r .= $form->body; + $r .= "\t<p class='contact-submit'>\n"; + $r .= "\t\t<input type='submit' value='" . esc_attr__( 'Submit »', 'jetpack' ) . "' class='pushbutton-wide'/>\n"; + $r .= "\t\t" . wp_nonce_field( 'contact-form_' . $id, '_wpnonce', true, false ) . "\n"; // nonce and referer + $r .= "\t\t<input type='hidden' name='contact-form-id' value='$id' />\n"; + $r .= "\t\t<input type='hidden' name='action' value='grunion-contact-form' />\n"; + $r .= "\t</p>\n"; + $r .= "</form>\n"; + } - // keep a copy of the feedback as a custom post type - $feedback_mysql_time = current_time( 'mysql' ); - $feedback_title = "{$comment_author} - {$feedback_mysql_time}"; - $feedback_status = 'publish'; - if ( $is_spam === TRUE ) - $feedback_status = 'spam'; + $r .= "</div>"; - foreach ( (array) $akismet_values as $av_key => $av_value ) { - $akismet_values[$av_key] = wp_kses( $av_value, array() ); + return $r; } - foreach ( (array) $all_values as $all_key => $all_value ) { - $all_values[$all_key] = wp_kses( $all_value, array() ); + /** + * The contact-field shortcode processor + * We use an object method here instead of a static Grunion_Contact_Form_Field class method to parse contact-field shortcodes so that we can tie them to the contact-form object. + * + * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts() + * @param string|null $content The shortcode's inner content: [contact-field]$content[/contact-field] + * @return HTML for the contact form field + */ + function parse_contact_field( $attributes, $content ) { + $field = new Grunion_Contact_Form_Field( $attributes, $content, $this ); + + $field_id = $field->get_attribute( 'id' ); + if ( $field_id ) { + $this->fields[$field_id] = $field; + } else { + $this->fields[] = $field; + } + + if ( + isset( $_POST['action'] ) && 'grunion-contact-form' === $_POST['action'] + && + isset( $_POST['contact-form-id'] ) && $this->get_attribute( 'id' ) == $_POST['contact-form-id'] + ) { + // If we're processing a POST submission for this contact form, validate the field value so we can show errors as necessary. + $field->validate(); + } + + // Output HTML + return $field->render(); } - foreach ( (array) $extra_values as $ev_key => $ev_value ) { - $ev_values[$ev_key] = wp_kses( $ev_value, array() ); + /** + * Loops through $this->fields to generate a (structured) list of field IDs + * @return array + */ + function get_field_ids() { + $field_ids = array( + 'all' => array(), // array of all field_ids + 'extra' => array(), // array of all non-whitelisted field IDs + + // Whitelisted "standard" field IDs: + // 'email' => field_id, + // 'name' => field_id, + // 'url' => field_id, + // 'subject' => field_id, + // 'textarea' => field_id, + ); + + foreach ( $this->fields as $id => $field ) { + $field_ids['all'][] = $id; + + $type = $field->get_attribute( 'type' ); + if ( isset( $field_ids[$type] ) ) { + // This type of field is already present in our whitelist of "standard" fields for this form + // Put it in extra + $field_ids['extra'][] = $id; + continue; + } + + switch ( $type ) { + case 'email' : + case 'name' : + case 'url' : + case 'subject' : + case 'textarea' : + $field_ids[$type] = $id; + break; + default : + // Put everything else in extra + $field_ids['extra'][] = $id; + } + } + + return $field_ids; } - # We need to make sure that the post author is always zero for contact - # form submissions. This prevents export/import from trying to create - # new users based on form submissions from people who were logged in - # at the time. - # - # Unfortunately wp_insert_post() tries very hard to make sure the post - # author gets the currently logged in user id. That is how we ended up - # with this work around. - global $do_grunion_insert; - $do_grunion_insert = TRUE; - add_filter( 'wp_insert_post_data', 'grunion_insert_filter', 10, 2 ); + /** + * Process the contact form's POST submission + * Stores feedback. Sends email. + */ + function process_submission() { + global $post; - $post_id = wp_insert_post( array( - 'post_date' => $feedback_mysql_time, - 'post_type' => 'feedback', - 'post_status' => $feedback_status, - 'post_parent' => $post->ID, - 'post_title' => wp_kses( $feedback_title, array() ), - 'post_content' => wp_kses($comment_content . "\n<!--more-->\n" . "AUTHOR: {$comment_author}\nAUTHOR EMAIL: {$comment_author_email}\nAUTHOR URL: {$comment_author_url}\nSUBJECT: {$contact_form_subject}\nIP: {$comment_author_IP}\n" . print_r( $all_values, TRUE ), array()), // so that search will pick up this data - 'post_name' => md5( $feedback_title ) - ) ); + $plugin = Grunion_Contact_Form_Plugin::init(); - # once insert has finished we don't need this filter any more - remove_filter( 'wp_insert_post_data', 'grunion_insert_filter' ); - $do_grunion_insert = FALSE; + $id = $this->get_attribute( 'id' ); + $to = $this->get_attribute( 'to' ); + $widget = $this->get_attribute( 'widget' ); - update_post_meta( $post_id, '_feedback_author', wp_kses( $comment_author, array() ) ); - update_post_meta( $post_id, '_feedback_author_email', wp_kses( $comment_author_email, array() ) ); - update_post_meta( $post_id, '_feedback_author_url', wp_kses( $comment_author_url, array() ) ); - update_post_meta( $post_id, '_feedback_subject', wp_kses( $contact_form_subject, array() ) ); - update_post_meta( $post_id, '_feedback_ip', wp_kses( $comment_author_IP, array() ) ); - update_post_meta( $post_id, '_feedback_contact_form_url', wp_kses( get_permalink( $post->ID ), array() ) ); - update_post_meta( $post_id, '_feedback_all_fields', $all_values ); - update_post_meta( $post_id, '_feedback_extra_fields', $extra_values ); - update_post_meta( $post_id, '_feedback_akismet_values', $akismet_values ); - update_post_meta( $post_id, '_feedback_email', array( 'to' => $to, 'subject' => $subject, 'message' => $message, 'headers' => $headers ) ); + $contact_form_subject = $this->get_attribute( 'subject' ); - do_action( 'grunion_pre_message_sent', $post_id, $all_values, $extra_values ); + $to = str_replace( ' ', '', $to ); + $emails = explode( ',', $to ); - # schedule deletes of old spam feedbacks - if ( !wp_next_scheduled( 'grunion_scheduled_delete' ) ) { - wp_schedule_event( time() + 250, 'daily', 'grunion_scheduled_delete' ); - } + $valid_emails = array(); - if ( $is_spam !== TRUE ) - return wp_mail( $to, "{$spam}{$subject}", $message, $headers ); - elseif ( apply_filters( 'grunion_still_email_spam', FALSE ) == TRUE ) - return wp_mail( $to, "{$spam}{$subject}", $message, $headers ); + foreach ( (array) $emails as $email ) { + if ( !is_email( $email ) ) { + continue; + } -} + if ( function_exists( 'is_email_address_unsafe' ) && is_email_address_unsafe( $email ) ) { + continue; + } -// populate an array with all values necessary to submit a NEW comment to Akismet -// note that this includes the current user_ip etc, so this should only be called when accepting a new item via $_POST -function contact_form_prepare_for_akismet( $form ) { + $valid_emails[] = $email; + } - $form['comment_type'] = 'contact_form'; - $form['user_ip'] = preg_replace( '/[^0-9., ]/', '', $_SERVER['REMOTE_ADDR'] ); - $form['user_agent'] = $_SERVER['HTTP_USER_AGENT']; - $form['referrer'] = $_SERVER['HTTP_REFERER']; - $form['blog'] = get_option( 'home' ); + // No one to send it to :( + if ( !$valid_emails ) { + return; + } - $ignore = array( 'HTTP_COOKIE' ); + $to = $valid_emails; - foreach ( $_SERVER as $k => $value ) - if ( !in_array( $k, $ignore ) && is_string( $value ) ) - $form["$k"] = $value; - - return $form; -} + // Make sure we're processing the form we think we're processing... probably a redundant check. + if ( $widget ) { + if ( 'widget-' . $widget != $_POST['contact-form-id'] ) { + return; + } + } else { + if ( $post->ID != $_POST['contact-form-id'] ) { + return; + } + } -// submit an array to Akismet. If you're accepting a new item via $_POST, run it through contact_form_prepare_for_akismet() first -function contact_form_is_spam_akismet( $form ) { - if ( !function_exists( 'akismet_http_post' ) ) - return false; - - global $akismet_api_host, $akismet_api_port; - - $query_string = ''; - foreach ( array_keys( $form ) as $k ) - $query_string .= $k . '=' . urlencode( $form[$k] ) . '&'; - - $response = akismet_http_post( $query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port ); - $result = false; - if ( 'true' == trim( $response[1] ) ) // 'true' is spam - $result = true; - return apply_filters( 'contact_form_is_spam_akismet', $result, $form ); -} + $field_ids = $this->get_field_ids(); -// submit a comment as either spam or ham -// $as should be a string (either 'spam' or 'ham'), $form should be the comment array -function contact_form_akismet_submit( $as, $form ) { - global $akismet_api_host, $akismet_api_port; - - if ( !in_array( $as, array( 'ham', 'spam' ) ) ) - return false; + // Initialize all these "standard" fields to null + $comment_author_email = $comment_author_email_label = // v + $comment_author = $comment_author_label = // v + $comment_author_url = $comment_author_url_label = // v + $comment_content = $comment_content_label = null; - $query_string = ''; - foreach ( array_keys( $form ) as $k ) - $query_string .= $k . '=' . urlencode( $form[$k] ) . '&'; + // For each of the "standard" fields, grab their field label and value. - $response = akismet_http_post( $query_string, $akismet_api_host, '/1.1/submit-'.$as, $akismet_api_port ); - return trim( $response[1] ); -} + if ( isset( $field_ids['name'] ) ) { + $field = $this->fields[$field_ids['name']]; + $comment_author = Grunion_Contact_Form_Plugin::strip_tags( stripslashes( apply_filters( 'pre_comment_author_name', addslashes( $field->value ) ) ) ); + $comment_author_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); + } -function contact_form_widget_atts( $text ) { - static $widget = 0; - - $widget++; + if ( isset( $field_ids['email'] ) ) { + $field = $this->fields[$field_ids['email']]; + $comment_author_email = Grunion_Contact_Form_Plugin::strip_tags( stripslashes( apply_filters( 'pre_comment_author_email', addslashes( $field->value ) ) ) ); + $comment_author_email_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); + } - return preg_replace( '/\[contact-form([^a-zA-Z_-])/', '[contact-form widget="' . $widget . '"\\1', $text ); -} -add_filter( 'widget_text', 'contact_form_widget_atts', 0 ); + if ( isset( $field_ids['url'] ) ) { + $field = $this->fields[$field_ids['url']]; + $comment_author_url = Grunion_Contact_Form_Plugin::strip_tags( stripslashes( apply_filters( 'pre_comment_author_url', addslashes( $field->value ) ) ) ); + if ( 'http://' == $comment_author_url ) { + $comment_author_url = ''; + } + $comment_author_url_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); + } -function contact_form_widget_shortcode_hack( $text ) { - if ( !preg_match( '/\[contact-form([^a-zA-Z_-])/', $text ) ) { - return $text; - } + if ( isset( $field_ids['textarea'] ) ) { + $field = $this->fields[$field_ids['textarea']]; + $comment_content = trim( Grunion_Contact_Form_Plugin::strip_tags( $field->value ) ); + $comment_content_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); + } - $old = $GLOBALS['shortcode_tags']; - remove_all_shortcodes(); - add_shortcode( 'contact-form', 'contact_form_shortcode' ); - add_shortcode( 'contact-field', 'contact_form_field' ); - $text = do_shortcode( $text ); - $GLOBALS['shortcode_tags'] = $old; - return $text; -} + if ( isset( $field_ids['subject'] ) ) { + $field = $this->fields[$field_ids['subject']]; + if ( $field->value ) { + $contact_form_subject = Grunion_Contact_Form_Plugin::strip_tags( $field->value ); + } + } + + $all_values = $extra_values = array(); + + // For all fields, grab label and value + foreach ( $field_ids['all'] as $field_id ) { + $field = $this->fields[$field_id]; + $label = $field->get_attribute( 'label' ); + $value = $field->value; + $all_values[$label] = $value; + } + + // For the "non-standard" fields, grab label and value + foreach ( $field_ids['extra'] as $field_id ) { + $field = $this->fields[$field_id]; + $label = $field->get_attribute( 'label' ); + $value = $field->value; + $extra_values[$label] = $value; + } + + $contact_form_subject = trim( $contact_form_subject ); + + $comment_author_IP = Grunion_Contact_Form_Plugin::strip_tags( $_SERVER['REMOTE_ADDR'] ); + + $vars = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'contact_form_subject', 'comment_author_IP' ); + foreach ( $vars as $var ) + $$var = str_replace( array( "\n", "\r" ), '', $$var ); + $vars[] = 'comment_content'; + + $spam = ''; + $akismet_values = $plugin->prepare_for_akismet( compact( $vars ) ); + + // Is it spam? + $is_spam = apply_filters( 'contact_form_is_spam', $akismet_values ); + if ( is_wp_error( $is_spam ) ) // WP_Error to abort + return; // abort + else if ( $is_spam === TRUE ) // TRUE to flag a spam + $spam = '***SPAM*** '; + + if ( !$comment_author ) + $comment_author = $comment_author_email; + + $to = (array) apply_filters( 'contact_form_to', $to ); + foreach ( $to as $to_key => $to_value ) { + $to[$to_key] = Grunion_Contact_Form_Plugin::strip_tags( $to_value ); + } -function contact_form_init() { - if ( function_exists( 'akismet_http_post' ) ) { - add_filter( 'contact_form_is_spam', 'contact_form_is_spam_akismet', 10 ); - add_action( 'contact_form_akismet', 'contact_form_akismet_submit', 10, 2 ); - } - if ( !has_filter( 'widget_text', 'do_shortcode' ) ) - add_filter( 'widget_text', 'contact_form_widget_shortcode_hack', 5 ); - - // custom post type we'll use to keep copies of the feedback items - register_post_type( 'feedback', array( - 'labels' => array( - 'name' => __( 'Feedbacks', 'jetpack' ), - 'singular_name' => __( 'Feedback', 'jetpack' ), - 'search_items' => __( 'Search Feedback', 'jetpack' ), - 'not_found' => __( 'No feedback found', 'jetpack' ), - 'not_found_in_trash' => __( 'No feedback found', 'jetpack' ) - ), - 'menu_icon' => GRUNION_PLUGIN_URL . 'images/grunion-menu.png', - 'show_ui' => TRUE, - 'show_in_admin_bar' => FALSE, - 'public' => FALSE, - 'rewrite' => FALSE, - 'query_var' => FALSE, - 'capability_type' => 'page' - ) ); - - register_post_status( 'spam', array( - 'label' => 'Spam', - 'public' => FALSE, - 'exclude_from_search' => TRUE, - 'show_in_admin_all_list' => FALSE, - 'label_count' => _n_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'jetpack' ), - 'protected' => TRUE, - '_builtin' => FALSE - ) ); + $blog_url = parse_url( site_url() ); + $from_email_addr = 'wordpress@' . $blog_url['host']; + + $reply_to_addr = $to[0]; + if ( ! empty( $comment_author_email ) ) { + $reply_to_addr = $comment_author_email; + } + + $headers = 'From: ' . $comment_author .' <' . $from_email_addr . ">\r\n" . + 'Reply-To: ' . $comment_author . ' <' . $reply_to_addr . ">\r\n" . + "Content-Type: text/plain; charset=\"" . get_option('blog_charset') . "\""; + + $subject = apply_filters( 'contact_form_subject', $contact_form_subject ); + + $time = date_i18n( __( 'l F j, Y \a\t g:i a', 'jetpack' ), current_time( 'timestamp' ) ); - /* Can be dequeued by placing the following in wp-content/themes/yourtheme/functions.php - * - * function remove_grunion_style() { - * wp_deregister_style('grunion.css'); - * } - * add_action('wp_print_styles', 'remove_grunion_style'); - */ + $extra_content = ''; - wp_register_style('grunion.css', GRUNION_PLUGIN_URL . 'css/grunion.css'); + foreach ( $extra_values as $label => $value ) { + $extra_content .= $label . ': ' . trim( $value ) . "\n"; + } + + $message = "$comment_author_label: $comment_author\n"; + if ( !empty( $comment_author_email ) ) { + $message .= "$comment_author_email_label: $comment_author_email\n"; + } + if ( !empty( $comment_author_url ) ) { + $message .= "$comment_author_url_label: $comment_author_url\n"; + } + if ( !empty( $comment_content_label ) ) { + $message .= "$comment_content_label: $comment_content\n"; + } + $message .= $extra_content . "\n"; + + $message .= __( 'Time:', 'jetpack' ) . ' ' . $time . "\n"; + $message .= __( 'IP Address:', 'jetpack' ) . ' ' . $comment_author_IP . "\n"; + + if ( $widget ) { + $url = home_url( '/' ); + } else { + $url = get_permalink( $post->ID ); + } + + $message .= __( 'Contact Form URL:', 'jetpack' ) . " $url\n"; + + if ( is_user_logged_in() ) { + $message .= "\n"; + $message .= sprintf( + __( 'Sent by a verified %s user.', 'jetpack' ), + isset( $GLOBALS['current_site']->site_name ) && $GLOBALS['current_site']->site_name ? $GLOBALS['current_site']->site_name : '"' . get_option( 'blogname' ) . '"' + ); + } else { + $message .= __( 'Sent by an unverified visitor to your site.', 'jetpack' ); + } + + $message = apply_filters( 'contact_form_message', $message ); + $message = Grunion_Contact_Form_Plugin::strip_tags( $message ); + + // keep a copy of the feedback as a custom post type + $feedback_mysql_time = current_time( 'mysql' ); + $feedback_title = "{$comment_author} - {$feedback_mysql_time}"; + $feedback_status = 'publish'; + if ( $is_spam === TRUE ) + $feedback_status = 'spam'; + + foreach ( (array) $akismet_values as $av_key => $av_value ) { + $akismet_values[$av_key] = Grunion_Contact_Form_Plugin::strip_tags( $av_value ); + } + + foreach ( (array) $all_values as $all_key => $all_value ) { + $all_values[$all_key] = Grunion_Contact_Form_Plugin::strip_tags( $all_value ); + } + + foreach ( (array) $extra_values as $ev_key => $ev_value ) { + $extra_values[$ev_key] = Grunion_Contact_Form_Plugin::strip_tags( $ev_value ); + } + + /* We need to make sure that the post author is always zero for contact + * form submissions. This prevents export/import from trying to create + * new users based on form submissions from people who were logged in + * at the time. + * + * Unfortunately wp_insert_post() tries very hard to make sure the post + * author gets the currently logged in user id. That is how we ended up + * with this work around. */ + add_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10, 2 ); + + $post_id = wp_insert_post( array( + 'post_date' => addslashes( $feedback_mysql_time ), + 'post_type' => 'feedback', + 'post_status' => addslashes( $feedback_status ), + 'post_parent' => (int) $post->ID, + 'post_title' => addslashes( wp_kses( $feedback_title, array() ) ), + 'post_content' => addslashes( wp_kses( $comment_content . "\n<!--more-->\n" . "AUTHOR: {$comment_author}\nAUTHOR EMAIL: {$comment_author_email}\nAUTHOR URL: {$comment_author_url}\nSUBJECT: {$contact_form_subject}\nIP: {$comment_author_IP}\n" . print_r( $all_values, TRUE ), array() ) ), // so that search will pick up this data + 'post_name' => md5( $feedback_title ), + ) ); + + // once insert has finished we don't need this filter any more + remove_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10, 2 ); + + update_post_meta( $post_id, '_feedback_author', addslashes( $comment_author ) ); + update_post_meta( $post_id, '_feedback_author_email', addslashes( $comment_author_email ) ); + update_post_meta( $post_id, '_feedback_author_url', addslashes( $comment_author_url ) ); + update_post_meta( $post_id, '_feedback_subject', addslashes( $contact_form_subject ) ); + update_post_meta( $post_id, '_feedback_ip', addslashes( $comment_author_IP ) ); + update_post_meta( $post_id, '_feedback_contact_form_url', addslashes( get_permalink( $post->ID ) ) ); + update_post_meta( $post_id, '_feedback_all_fields', $this->addslashes_deep( $all_values ) ); + update_post_meta( $post_id, '_feedback_extra_fields', $this->addslashes_deep( $extra_values ) ); + update_post_meta( $post_id, '_feedback_akismet_values', $this->addslashes_deep( $akismet_values ) ); + update_post_meta( $post_id, '_feedback_email', $this->addslashes_deep( array( 'to' => $to, 'subject' => $subject, 'message' => $message, 'headers' => $headers ) ) ); + + do_action( 'grunion_pre_message_sent', $post_id, $all_values, $extra_values ); + + // schedule deletes of old spam feedbacks + if ( !wp_next_scheduled( 'grunion_scheduled_delete' ) ) { + wp_schedule_event( time() + 250, 'daily', 'grunion_scheduled_delete' ); + } + + if ( $is_spam !== TRUE ) + wp_mail( $to, "{$spam}{$subject}", $message, $headers ); + elseif ( apply_filters( 'grunion_still_email_spam', FALSE ) == TRUE ) // don't send spam by default. Filterable. + wp_mail( $to, "{$spam}{$subject}", $message, $headers ); + + $redirect = wp_get_referer(); + if ( !$redirect ) { // wp_get_referer() returns false if the referer is the same as the current page + $redirect = $_SERVER['REQUEST_URI']; + } + + $redirect = add_query_arg( urlencode_deep( array( + 'contact-form-id' => $id, + 'contact-form-sent' => $post_id, + '_wpnonce' => wp_create_nonce( "contact-form-sent-{$post_id}" ), // wp_nonce_url HTMLencodes :( + ) ), $redirect ); + + $redirect = apply_filters( 'grunion_contact_form_redirect_url', $redirect, $id, $post_id ); + + wp_safe_redirect( $redirect ); + exit; + } + + function addslashes_deep( $value ) { + if ( is_array( $value ) ) { + return array_map( array( $this, 'addslashes_deep' ), $value ); + } elseif ( is_object( $value ) ) { + $vars = get_object_vars( $value ); + foreach ( $vars as $key => $data ) { + $value->{$key} = $this->addslashes_deep( $data ); + } + return $value; + } + + return addslashes( $value ); + } } -add_action( 'init', 'contact_form_init' ); /** - * Add a contact form button to the post composition screen + * Class for the contact-field shortcode. + * Parses shortcode to output the contact form field as HTML. + * Validates input. */ -add_action( 'media_buttons', 'grunion_media_button', 999 ); -function grunion_media_button( ) { - global $post_ID, $temp_ID; - $iframe_post_id = (int) (0 == $post_ID ? $temp_ID : $post_ID); - $title = esc_attr( __( 'Add a custom form', 'jetpack' ) ); - $plugin_url = esc_url( GRUNION_PLUGIN_URL ); - $site_url = admin_url( "/admin-ajax.php?post_id=$iframe_post_id&grunion=form-builder&action=grunion_form_builder&TB_iframe=true&width=768" ); - - echo '<a href="' . $site_url . '&id=add_form" class="thickbox" title="' . $title . '"><div class="grunion-menu-button" alt="' . $title . '"></div></a>'; -} +class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { + var $shortcode_name = 'contact-field'; + /** + * @var Grunion_Contact_Form parent form + */ + var $form; -if ( !empty( $_GET['grunion'] ) && $_GET['grunion'] == 'form-builder' ) { - add_action( 'parse_request', 'parse_wp_request' ); - add_action( 'wp_ajax_grunion_form_builder', 'parse_wp_request' ); -} + /** + * @var string default or POSTed value + */ + var $value; -function parse_wp_request( $wp ) { - display_form_view( ); - exit; -} + /** + * @var bool Is the input invalid? + */ + var $error = false; -function display_form_view( ) { - require_once GRUNION_PLUGIN_DIR . 'grunion-form-view.php'; -} + /** + * @param array $attributes An associative array of shortcode attributes. @see shortcode_atts() + * @param null|string $content Null for selfclosing shortcodes. The inner content otherwise. + * @param Grunion_Contact_Form $form The parent form + */ + function __construct( $attributes, $content = null, $form = null ) { + $attributes = shortcode_atts( array( + 'label' => null, + 'type' => 'text', + 'required' => false, + 'options' => array(), + 'id' => null, + 'default' => null, + ), $attributes ); + + // special default for subject field + if ( 'subject' == $attributes['type'] && is_null( $attributes['default'] ) && !is_null( $form ) ) { + $attributes['default'] = $form->get_attribute( 'subject' ); + } -function menu_alter() { - echo ' - <style> - #menu-posts-feedback .wp-menu-image img { display: none; } - #adminmenu .menu-icon-feedback:hover div.wp-menu-image, - #adminmenu .menu-icon-feedback.wp-has-current-submenu div.wp-menu-image, - #adminmenu .menu-icon-feedback.current div.wp-menu-image { - background: url("' .GRUNION_PLUGIN_URL . 'images/grunion-menu-hover.png") no-repeat 6px 7px; - } - #adminmenu .menu-icon-feedback div.wp-menu-image { - background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-menu.png") no-repeat 6px 7px; - } - .grunion-menu-button { - background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-form.png") no-repeat; - width: 13px; - height: 12px; - display: inline-block; - } - @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) { - #adminmenu .menu-icon-feedback:hover div.wp-menu-image, - #adminmenu .menu-icon-feedback.wp-has-current-submenu div.wp-menu-image, - #adminmenu .menu-icon-feedback.current div.wp-menu-image { - background: url("' .GRUNION_PLUGIN_URL . 'images/grunion-menu-hover-2x.png") no-repeat 6px 7px; - background-size: 15px 16px; - } - #adminmenu .menu-icon-feedback div.wp-menu-image { - background: url("' . GRUNION_PLUGIN_URL . 'images/grunion-menu-2x.png") no-repeat 6px 7px; - background-size: 15px 16px; - } - .grunion-menu-button { - background-image: url("' . GRUNION_PLUGIN_URL . 'images/grunion-form-2x.png"); - background-size: 13px 12px; - vertical-align: bottom; - } - } - </style>'; -} + // allow required=1 or required=true + if ( '1' == $attributes['required'] || 'true' == strtolower( $attributes['required'] ) ) + $attributes['required'] = true; + else + $attributes['required'] = false; -add_action('admin_head', 'menu_alter'); + // parse out comma-separated options list (for selects and radios) + if ( !empty( $attributes['options'] ) && is_string( $attributes['options'] ) ) { + $attributes['options'] = array_map( 'trim', explode( ',', $attributes['options'] ) ); + } -function grunion_insert_filter( $data, $postarr ) { - global $do_grunion_insert; + if ( $form ) { + // make a unique field ID based on the label, with an incrementing number if needed to avoid clashes + $form_id = $form->get_attribute( 'id' ); + $id = isset( $attributes['id'] ) ? $attributes['id'] : false; + + $unescaped_label = $this->unesc_attr( $attributes['label'] ); + $unescaped_label = str_replace( '%', '-', $unescaped_label ); // jQuery doesn't like % in IDs? + $unescaped_label = preg_replace( '/[^a-zA-Z0-9.-_:]/', '', $unescaped_label ); + + if ( empty( $id ) ) { + $id = sanitize_title_with_dashes( 'g' + $form_id . '-' . $unescaped_label ); + $i = 0; + $max_tries = 24; + while ( isset( $form->fields[$id] ) ) { + $i++; + $id = sanitize_title_with_dashes( 'g' + $form_id . '-' . $unescaped_label . '-' . $i ); + + if ( $i > $max_tries ) { + break; + } + } + } - if ( $do_grunion_insert === TRUE ) { - if ( $data['post_type'] == 'feedback' ) { - if ( $postarr['post_type'] == 'feedback' ) { - $data['post_author'] = 0; + $attributes['id'] = $id; + } + + parent::__construct( $attributes, $content ); + + // Store parent form + $this->form = $form; + } + + /** + * This field's input is invalid. Flag as invalid and add an error to the parent form + * + * @param string $message The error message to display on the form. + */ + function add_error( $message ) { + $this->is_error = true; + + if ( !is_wp_error( $this->form->errors ) ) { + $this->form->errors = new WP_Error; + } + + $this->form->errors->add( $this->get_attribute( 'id' ), $message ); + } + + /** + * Is the field input invalid? + * + * @see $error + * + * @return bool + */ + function is_error() { + return $this->error; + } + + /** + * Validates the form input + */ + function validate() { + // If it's not required, there's nothing to validate + if ( !$this->get_attribute( 'required' ) ) { + return; + } + + $field_id = $this->get_attribute( 'id' ); + $field_type = $this->get_attribute( 'type' ); + $field_label = $this->get_attribute( 'label' ); + + $field_value = isset( $_POST[$field_id] ) ? stripslashes( $_POST[$field_id] ) : ''; + + switch ( $field_type ) { + case 'email' : + // Make sure the email address is valid + if ( !is_email( $field_value ) ) { + $this->add_error( sprintf( __( '%s requires a valid email address', 'jetpack' ), $field_label ) ); + } + break; + default : + // Just check for presence of any text + if ( !strlen( trim( $field_value ) ) ) { + $this->add_error( sprintf( __( '%s is required', 'jetpack' ), $field_label ) ); } } } - return $data; + /** + * Outputs the HTML for this form field + * + * @return string HTML + */ + function render() { + global $current_user, $user_identity; + + $r = ''; + + $field_id = $this->get_attribute( 'id' ); + $field_type = $this->get_attribute( 'type' ); + $field_label = $this->get_attribute( 'label' ); + $field_required = $this->get_attribute( 'required' ); + + if ( isset( $_POST[$field_id] ) ) { + $this->value = stripslashes( (string) $_POST[$field_id] ); + } elseif ( is_user_logged_in() ) { + // Special defaults for logged-in users + switch ( $this->get_attribute( 'type' ) ) { + case 'email'; + $this->value = $current_user->data->user_email; + break; + case 'name' : + $this->value = $user_identity; + break; + case 'url' : + $this->value = $current_user->data->user_url; + break; + default : + $this->value = $this->get_attribute( 'default' ); + } + } else { + $this->value = $this->get_attribute( 'default' ); + } + + $field_value = Grunion_Contact_Form_Plugin::strip_tags( $this->value ); + $field_label = Grunion_Contact_Form_Plugin::strip_tags( $field_label ); + + switch ( $field_type ) { + case 'email' : + $r .= "\n<div>\n"; + $r .= "\t\t<label for='" . esc_attr( $field_id ) . "' class='grunion-field-label email" . ( $this->is_error() ? ' form-error' : '' ) . "'>" . esc_html( $field_label ) . ( $field_required ? '<span>' . __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + $r .= "\t\t<input type='email' name='" . esc_attr( $field_id ) . "' id='" . esc_attr( $field_id ) . "' value='" . esc_attr( $field_value ) . "' class='email' />\n"; + $r .= "\t</div>\n"; + break; + case 'textarea' : + $r .= "\n<div>\n"; + $r .= "\t\t<label for='contact-form-comment-" . esc_attr( $field_id ) . "' class='grunion-field-label textarea" . ( $this->is_error() ? ' form-error' : '' ) . "'>" . esc_html( $field_label ) . ( $field_required ? '<span>' . __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + $r .= "\t\t<textarea name='" . esc_attr( $field_id ) . "' id='contact-form-comment-" . esc_attr( $field_id ) . "' rows='20'>" . esc_textarea( $field_value ) . "</textarea>\n"; + $r .= "\t</div>\n"; + break; + case 'radio' : + $r .= "\t<div><label class='grunion-field-label" . ( $this->is_error() ? ' form-error' : '' ) . "'>" . esc_html( $field_label ) . ( $field_required ? '<span>' . __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + foreach ( $this->get_attribute( 'options' ) as $option ) { + $option = Grunion_Contact_Form_Plugin::strip_tags( $option ); + $r .= "\t\t<label class='grunion-radio-label radio" . ( $this->is_error() ? ' form-error' : '' ) . "'>"; + $r .= "<input type='radio' name='" . esc_attr( $field_id ) . "' value='" . esc_attr( $option ) . "' class='radio' " . checked( $option, $field_value, false ) . " /> "; + $r .= esc_html( $option ) . "</label>\n"; + $r .= "\t\t<div class='clear-form'></div>\n"; + } + $r .= "\t\t</div>\n"; + break; + case 'checkbox' : + $r .= "\t<div>\n"; + $r .= "\t\t<label class='grunion-field-label checkbox" . ( $this->is_error() ? ' form-error' : '' ) . "'>\n"; + $r .= "\t\t<input type='checkbox' name='" . esc_attr( $field_id ) . "' value='" . esc_attr__( 'Yes', 'jetpack' ) . "' class='checkbox' " . checked( (bool) $field_value, true, false ) . " /> \n"; + $r .= "\t\t" . esc_html( $field_label ) . ( $field_required ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + $r .= "\t\t<div class='clear-form'></div>\n"; + $r .= "\t</div>\n"; + break; + case 'select' : + $r .= "\n<div>\n"; + $r .= "\t\t<label for='" . esc_attr( $field_id ) . "' class='grunion-field-label select" . ( $this->is_error() ? ' form-error' : '' ) . "'>" . esc_html( $field_label ) . ( $field_required ? '<span>'. __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + $r .= "\t<select name='" . esc_attr( $field_id ) . "' id='" . esc_attr( $field_id ) . "' class='select' />\n"; + foreach ( $this->get_attribute( 'options' ) as $option ) { + $option = Grunion_Contact_Form_Plugin::strip_tags( $option ); + $r .= "\t\t<option" . selected( $option, $field_value, false ) . ">" . esc_html( $option ) . "</option>\n"; + } + $r .= "\t</select>\n"; + $r .= "\t</div>\n"; + break; + default : // text field + // note that any unknown types will produce a text input, so we can use arbitrary type names to handle + // input fields like name, email, url that require special validation or handling at POST + $r .= "\n<div>\n"; + $r .= "\t\t<label for='" . esc_attr( $field_id ) . "' class='grunion-field-label " . esc_attr( $field_type ) . ( $this->is_error() ? ' form-error' : '' ) . "'>" . esc_html( $field_label ) . ( $field_required ? '<span>' . __( "(required)", 'jetpack' ) . '</span>' : '' ) . "</label>\n"; + $r .= "\t\t<input type='text' name='" . esc_attr( $field_id ) . "' id='" . esc_attr( $field_id ) . "' value='" . esc_attr( $field_value ) . "' class='" . esc_attr( $field_type ) . "'/>\n"; + $r .= "\t</div>\n"; + } + + return $r; + } } +add_action( 'init', array( 'Grunion_Contact_Form_Plugin', 'init' ) ); + add_action( 'grunion_scheduled_delete', 'grunion_delete_old_spam' ); + +/** + * Deletes old spam feedbacks to keep the posts table size under control + */ function grunion_delete_old_spam() { global $wpdb; diff --git a/plugins/jetpack/modules/contact-form/images/grunion-menu-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-menu-2x.png Binary files differindex 04fd8be5..7c197441 100644 --- a/plugins/jetpack/modules/contact-form/images/grunion-menu-2x.png +++ b/plugins/jetpack/modules/contact-form/images/grunion-menu-2x.png diff --git a/plugins/jetpack/modules/contact-form/images/grunion-menu-hover-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-menu-hover-2x.png Binary files differindex f4656a98..21840d17 100644 --- a/plugins/jetpack/modules/contact-form/images/grunion-menu-hover-2x.png +++ b/plugins/jetpack/modules/contact-form/images/grunion-menu-hover-2x.png diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png Binary files differindex 49553746..244c102c 100644 --- a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png +++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png Binary files differindex 561382fa..11fc10ed 100644 --- a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png +++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png diff --git a/plugins/jetpack/modules/contact-form/js/grunion.js b/plugins/jetpack/modules/contact-form/js/grunion.js index 4fe391bf..835f7f80 100644 --- a/plugins/jetpack/modules/contact-form/js/grunion.js +++ b/plugins/jetpack/modules/contact-form/js/grunion.js @@ -22,6 +22,16 @@ GrunionFB_i18n = jQuery.extend( { GrunionFB_i18n.moveInstructions = GrunionFB_i18n.moveInstructions.replace( "\n", '<br />' ); +FB.span = jQuery( '<span>' ); +FB.esc_html = function( string ) { + return FB.span.text( string ).html(); +}; + +FB.esc_attr = function( string ) { + string = FB.esc_html( string ); + return string.replace( '"', '"' ).replace( "'", ''' ); +}; + FB.ContactForm = function() { var fbForm = { // Main object that generated shortcode via AJAX call 'action' : 'grunion_shortcode', @@ -31,28 +41,28 @@ FB.ContactForm = function() { 'fields' : {} }; var defaultFields = { - 'name': { + 'name': { 'label' : GrunionFB_i18n.nameLabel, 'type' : 'name', 'required' : true, 'options' : [], 'order' : '1' - }, - 'email': { + }, + 'email': { 'label' : GrunionFB_i18n.emailLabel, 'type' : 'email', 'required' : true, 'options' : [], 'order' : '2' - }, - 'url': { + }, + 'url': { 'label' : GrunionFB_i18n.urlLabel, 'type' : 'url', 'required' : false, 'options' : [], 'order' : '3' - }, - 'comment': { + }, + 'comment': { 'label' : GrunionFB_i18n.commentLabel, 'type' : 'textarea', 'required' : true, @@ -66,7 +76,7 @@ FB.ContactForm = function() { var optionsCache = {}; var optionsCount = 0; // increment for options var shortcode; - + function addField () { try { grunionNewCount++; @@ -103,7 +113,7 @@ FB.ContactForm = function() { } } function addOption () { - try { + try { optionsCount++; var thisId = jQuery('#fb-field-id').val(); var thisType = jQuery('#fb-new-type').val(); @@ -157,9 +167,9 @@ FB.ContactForm = function() { for (i=0; i<optionsCache[id].options.length; i++) { if (optionsCache[id].options[i] !== undefined) { if (thisType === "radio") { - thisOptions = thisOptions + '<div id="fb-radio-' + id + '-' + i + '"><input type="radio" id="fb-field' + id + '" name="radio-' + id + '" /><span>' + optionsCache[id].options[i] + '</span><div class="clear"></div></div>'; + thisOptions = thisOptions + '<div id="fb-radio-' + id + '-' + i + '"><input type="radio" id="fb-field' + id + '" name="radio-' + id + '" /><span>' + FB.esc_html( optionsCache[id].options[i] ) + '</span><div class="clear"></div></div>'; } else { - thisOptions = thisOptions + '<option id="fb-' + id + '-' + i + '" value="' + id + '-' + i + '">' + optionsCache[id].options[i] + '</option>'; + thisOptions = thisOptions + '<option id="fb-' + id + '-' + i + '" value="' + id + '-' + i + '">' + FB.esc_html( optionsCache[id].options[i] ) + '</option>'; } } } @@ -277,9 +287,9 @@ FB.ContactForm = function() { for (i=0; i<thisOptions.length; i++) { if (thisOptions[i] !== undefined) { if (thisType === "radio") { - jQuery('#fb-new-options').append('<div id="fb-option-box-' + i + '" class="fb-new-fields"><span optionid="' + i + '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' + i + '" optionid="' + i + '" value="' + fbForm.fields[id].options[i] + '" class="fb-options" /><div>'); + jQuery('#fb-new-options').append('<div id="fb-option-box-' + i + '" class="fb-new-fields"><span optionid="' + i + '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' + i + '" optionid="' + i + '" value="' + FB.esc_attr( fbForm.fields[id].options[i] ) + '" class="fb-options" /><div>'); } else { - jQuery('#fb-new-options').append('<div id="fb-option-box-' + i + '" class="fb-new-fields"><span optionid="' + i + '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' + i + '" optionid="' + i + '" value="' + fbForm.fields[id].options[i] + '" class="fb-options" /><div>'); + jQuery('#fb-new-options').append('<div id="fb-option-box-' + i + '" class="fb-new-fields"><span optionid="' + i + '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' + i + '" optionid="' + i + '" value="' + FB.esc_attr( fbForm.fields[id].options[i] ) + '" class="fb-options" /><div>'); } } } @@ -301,6 +311,8 @@ FB.ContactForm = function() { fbForm.fields = defaultFields; } else { jQuery.each(data.fields, function(index, value) { + if ( 1 == value.required ) + value.required = 'true'; fbForm.fields[index] = value; }); fbForm.to = data.to; @@ -373,10 +385,10 @@ FB.ContactForm = function() { } } var regexp = new RegExp("\\[contact-form\\b.*?\\/?\\](?:[\\s\\S]+?\\[\\/contact-form\\])?"); - + // Remove new lines that cause BR tags to show up response = response.replace(/\n/g,' '); - + // Add new shortcode if (currentCode.match(regexp)) { if (isVisual) { @@ -526,11 +538,11 @@ FB.ContactForm = function() { var isLoaded = thisType; var thisId = jQuery('#fb-field-id').val(); if (!thisType) { var thisType = jQuery('#fb-new-type').val(); } - if (!thisLabelText) { var thisLabelText = jQuery('#fb-new-field' + thisId + ' label').html(); } + if (!thisLabelText) { var thisLabelText = jQuery('#fb-new-field' + thisId + ' .label-text').html(); } var isRequired = (thisRequired) ? '<span class="label-required">' + GrunionFB_i18n.requiredLabel + '</span>' : ''; - var thisLabel = '<label fieldid="' + thisId + '" for="fb-field' + thisId + '"><span class="label-text">' + thisLabelText + '</span>' + isRequired + '</label>'; + var thisLabel = '<label fieldid="' + thisId + '" for="fb-field' + thisId + '"><span class="label-text">' + FB.esc_html( thisLabelText ) + '</span>' + isRequired + '</label>'; var thisRadio = '<input type="radio" name="radio-' + thisId + '" id="fb-field' + thisId + ' "disabled="disabled" />'; - var thisRadioLabel = '<label fieldid="' + thisId + '" for="fb-field' + thisId + '" class="fb-radio-label"><span class="label-text">' + thisLabelText + '</span>' + isRequired + '</label>'; + var thisRadioLabel = '<label fieldid="' + thisId + '" for="fb-field' + thisId + '" class="fb-radio-label"><span class="label-text">' + FB.esc_html( thisLabelText ) + '</span>' + isRequired + '</label>'; var thisRadioRemove = '<div class="fb-remove fb-remove-small" id="' + thisId + '"></div>'; var thisRemove = '<div class="fb-remove" id="' + thisId + '"></div>'; var thisCheckbox = '<input type="checkbox" id="fb-field' + thisId + '" "disabled="disabled" />'; @@ -545,11 +557,11 @@ FB.ContactForm = function() { break; case "email": removeOptions(); - jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); + jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); break; case "name": removeOptions(); - jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); + jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); break; case "radio": jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisLabel + thisRadioRemove + '<div fieldid="' + thisId + '" id="fb-custom-radio' + thisId + '"></div>'); @@ -578,7 +590,7 @@ FB.ContactForm = function() { break; case "text": removeOptions(); - jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); + jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); break; case "textarea": removeOptions(); @@ -586,7 +598,7 @@ FB.ContactForm = function() { break; case "url": removeOptions(); - jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); + jQuery('#fb-new-field' + thisId + ' .fb-fields').html(thisRemove + thisLabel + thisText); break; } // update object @@ -604,7 +616,7 @@ FB.ContactForm = function() { var totalWidth = jQuery('body', window.parent.document).width(); var totalHeight = jQuery('body', window.parent.document).height(); var isIE6 = typeof document.body.style.maxHeight === "undefined"; - + jQuery('#TB_window, #TB_iframeContent', window.parent.document).css('width', '768px'); jQuery('#TB_window', window.parent.document).css({ left: (totalWidth-768)/2 + 'px', top: '23px', position: 'absolute', marginLeft: '0' }); if ( ! isIE6 ) { // take away IE6 @@ -632,7 +644,7 @@ FB.ContactForm = function() { post_id: postId, content: contentSource }; - + jQuery.post(ajaxurl, data, function(response) { // Setup fbForm parseShortcode(jQuery.parseJSON(response)); diff --git a/plugins/jetpack/modules/custom-css.php b/plugins/jetpack/modules/custom-css.php new file mode 100644 index 00000000..2b5e6a34 --- /dev/null +++ b/plugins/jetpack/modules/custom-css.php @@ -0,0 +1,26 @@ +<?php + +/** + * Module Name: Custom CSS + * Module Description: Customize the appearance of your site using CSS but without modifying your theme. + * Sort Order: 11 + * First Introduced: 1.7 + */ + +function jetpack_load_custom_css() { + include dirname( __FILE__ ) . "/custom-css/custom-css.php"; +} + +add_action( 'jetpack_modules_loaded', 'custom_css_loaded' ); + +function custom_css_loaded() { + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, 'custom_css_configuration_load' ); +} + +function custom_css_configuration_load() { + wp_safe_redirect( admin_url( 'themes.php?page=editcss#settingsdiv' ) ); + exit; +} + +jetpack_load_custom_css();
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php new file mode 100644 index 00000000..f458a46e --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php @@ -0,0 +1,1241 @@ +<?php + +/** + * CSSTidy - CSS Parser and Optimiser + * + * CSS Parser class + * + * Copyright 2005, 2006, 2007 Florian Schmitz + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 + * @author Cedric Morin (cedric at yterium dot com) 2010 + */ +/** + * Defines ctype functions if required + * + * @version 1.0 + */ +require_once('class.csstidy_ctype.php'); + +/** + * Various CSS data needed for correct optimisations etc. + * + * @version 1.3 + */ +require('data.inc.php'); + +/** + * Contains a class for printing CSS code + * + * @version 1.0 + */ +require('class.csstidy_print.php'); + +/** + * Contains a class for optimising CSS code + * + * @version 1.0 + */ +require('class.csstidy_optimise.php'); + +/** + * CSS Parser class + * + + * This class represents a CSS parser which reads CSS code and saves it in an array. + * In opposite to most other CSS parsers, it does not use regular expressions and + * thus has full CSS2 support and a higher reliability. + * Additional to that it applies some optimisations and fixes to the CSS code. + * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.3.1 + */ +class csstidy { + + /** + * Saves the parsed CSS. This array is empty if preserve_css is on. + * @var array + * @access public + */ + var $css = array(); + /** + * Saves the parsed CSS (raw) + * @var array + * @access private + */ + var $tokens = array(); + /** + * Printer class + * @see csstidy_print + * @var object + * @access public + */ + var $print; + /** + * Optimiser class + * @see csstidy_optimise + * @var object + * @access private + */ + var $optimise; + /** + * Saves the CSS charset (@charset) + * @var string + * @access private + */ + var $charset = ''; + /** + * Saves all @import URLs + * @var array + * @access private + */ + var $import = array(); + /** + * Saves the namespace + * @var string + * @access private + */ + var $namespace = ''; + /** + * Contains the version of csstidy + * @var string + * @access private + */ + var $version = '1.3'; + /** + * Stores the settings + * @var array + * @access private + */ + var $settings = array(); + /** + * Saves the parser-status. + * + * Possible values: + * - is = in selector + * - ip = in property + * - iv = in value + * - instr = in string (started at " or ' or ( ) + * - ic = in comment (ignore everything) + * - at = in @-block + * + * @var string + * @access private + */ + var $status = 'is'; + /** + * Saves the current at rule (@media) + * @var string + * @access private + */ + var $at = ''; + /** + * Saves the current selector + * @var string + * @access private + */ + var $selector = ''; + /** + * Saves the current property + * @var string + * @access private + */ + var $property = ''; + /** + * Saves the position of , in selectors + * @var array + * @access private + */ + var $sel_separate = array(); + /** + * Saves the current value + * @var string + * @access private + */ + var $value = ''; + /** + * Saves the current sub-value + * + * Example for a subvalue: + * background:url(foo.png) red no-repeat; + * "url(foo.png)", "red", and "no-repeat" are subvalues, + * seperated by whitespace + * @var string + * @access private + */ + var $sub_value = ''; + /** + * Array which saves all subvalues for a property. + * @var array + * @see sub_value + * @access private + */ + var $sub_value_arr = array(); + /** + * Saves the stack of characters that opened the current strings + * @var array + * @access private + */ + var $str_char = array(); + var $cur_string = array(); + /** + * Status from which the parser switched to ic or instr + * @var array + * @access private + */ + var $from = array(); + /** + /** + * =true if in invalid at-rule + * @var bool + * @access private + */ + var $invalid_at = false; + /** + * =true if something has been added to the current selector + * @var bool + * @access private + */ + var $added = false; + /** + * Array which saves the message log + * @var array + * @access private + */ + var $log = array(); + /** + * Saves the line number + * @var integer + * @access private + */ + var $line = 1; + /** + * Marks if we need to leave quotes for a string + * @var array + * @access private + */ + var $quoted_string = array(); + + /** + * List of tokens + * @var string + */ + var $tokens_list = ""; + /** + * Loads standard template and sets default settings + * @access private + * @version 1.3 + */ + function csstidy() { + $this->settings['remove_bslash'] = true; + $this->settings['compress_colors'] = true; + $this->settings['compress_font-weight'] = true; + $this->settings['lowercase_s'] = false; + /* + 1 common shorthands optimization + 2 + font property optimization + 3 + background property optimization + */ + $this->settings['optimise_shorthands'] = 1; + $this->settings['remove_last_;'] = true; + /* rewrite all properties with low case, better for later gzip OK, safe*/ + $this->settings['case_properties'] = 1; + /* sort properties in alpabetic order, better for later gzip + * but can cause trouble in case of overiding same propertie or using hack + */ + $this->settings['sort_properties'] = false; + /* + 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{} + 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{} + preserve order by default cause it can break functionnality + */ + $this->settings['sort_selectors'] = 0; + /* is dangeroues to be used: CSS is broken sometimes */ + $this->settings['merge_selectors'] = 0; + /* preserve or not browser hacks */ + $this->settings['discard_invalid_selectors'] = false; + $this->settings['discard_invalid_properties'] = false; + $this->settings['css_level'] = 'CSS2.1'; + $this->settings['preserve_css'] = false; + $this->settings['timestamp'] = false; + $this->settings['template'] = ''; // say that propertie exist + $this->set_cfg('template','default'); // call load_template + $this->optimise = new csstidy_optimise($this); + + $this->tokens_list = & $GLOBALS['csstidy']['tokens']; + } + + /** + * Get the value of a setting. + * @param string $setting + * @access public + * @return mixed + * @version 1.0 + */ + function get_cfg($setting) { + if (isset($this->settings[$setting])) { + return $this->settings[$setting]; + } + return false; + } + + /** + * Load a template + * @param string $template used by set_cfg to load a template via a configuration setting + * @access private + * @version 1.4 + */ + function _load_template($template) { + switch ($template) { + case 'default': + $this->load_template('default'); + break; + + case 'highest': + $this->load_template('highest_compression'); + break; + + case 'high': + $this->load_template('high_compression'); + break; + + case 'low': + $this->load_template('low_compression'); + break; + + default: + $this->load_template($template); + break; + } + } + + /** + * Set the value of a setting. + * @param string $setting + * @param mixed $value + * @access public + * @return bool + * @version 1.0 + */ + function set_cfg($setting, $value=null) { + if (is_array($setting) && $value === null) { + foreach ($setting as $setprop => $setval) { + $this->settings[$setprop] = $setval; + } + if (array_key_exists('template', $setting)) { + $this->_load_template($this->settings['template']); + } + return true; + } else if (isset($this->settings[$setting]) && $value !== '') { + $this->settings[$setting] = $value; + if ($setting === 'template') { + $this->_load_template($this->settings['template']); + } + return true; + } + return false; + } + + /** + * Adds a token to $this->tokens + * @param mixed $type + * @param string $data + * @param bool $do add a token even if preserve_css is off + * @access private + * @version 1.0 + */ + function _add_token($type, $data, $do = false) { + if ($this->get_cfg('preserve_css') || $do) { + $this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data)); + } + } + + /** + * Add a message to the message log + * @param string $message + * @param string $type + * @param integer $line + * @access private + * @version 1.0 + */ + function log($message, $type, $line = -1) { + if ($line === -1) { + $line = $this->line; + } + $line = intval($line); + $add = array('m' => $message, 't' => $type); + if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) { + $this->log[$line][] = $add; + } + } + + /** + * Parse unicode notations and find a replacement character + * @param string $string + * @param integer $i + * @access private + * @return string + * @version 1.2 + */ + function _unicode(&$string, &$i) { + ++$i; + $add = ''; + $replaced = false; + + while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) { + $add .= $string{$i}; + + if (ctype_space($string{$i})) { + break; + } + $i++; + } + + if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) { + $this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information'); + $add = chr(hexdec($add)); + $replaced = true; + } else { + $add = trim('\\' . $add); + } + + if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i}) + && !$replaced || !ctype_space($string{$i})) { + $i--; + } + + if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) { + return $add; + } + + if ($add === '\\') { + $this->log('Removed unnecessary backslash', 'Information'); + } + return ''; + } + + /** + * Write formatted output to a file + * @param string $filename + * @param string $doctype when printing formatted, is a shorthand for the document type + * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet + * @param string $title when printing formatted, is the title to be added in the head of the document + * @param string $lang when printing formatted, gives a two-letter language code to be added to the output + * @access public + * @version 1.4 + */ + function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') { + $this->write($filename, true); + } + + /** + * Write plain output to a file + * @param string $filename + * @param bool $formatted whether to print formatted or not + * @param string $doctype when printing formatted, is a shorthand for the document type + * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet + * @param string $title when printing formatted, is the title to be added in the head of the document + * @param string $lang when printing formatted, gives a two-letter language code to be added to the output + * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates) + * @access public + * @version 1.4 + */ + function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) { + $filename .= ( $formatted) ? '.xhtml' : '.css'; + + if (!is_dir('temp')) { + $madedir = mkdir('temp'); + if (!$madedir) { + print 'Could not make directory "temp" in ' . dirname(__FILE__); + exit; + } + } + $handle = fopen('temp/' . $filename, 'w'); + if ($handle) { + if (!$formatted) { + fwrite($handle, $this->print->plain()); + } else { + fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code)); + } + } + fclose($handle); + } + + /** + * Loads a new template + * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default" + * @param bool $from_file uses $content as filename if true + * @access public + * @version 1.1 + * @see http://csstidy.sourceforge.net/templates.php + */ + function load_template($content, $from_file=true) { + $predefined_templates = & $GLOBALS['csstidy']['predefined_templates']; + if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') { + $this->template = $predefined_templates[$content]; + return; + } + + + if ($from_file) { + $content = strip_tags(file_get_contents($content), '<span>'); + } + $content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n) + $template = explode('|', $content); + + for ($i = 0; $i < count($template); $i++) { + $this->template[$i] = $template[$i]; + } + } + + /** + * Starts parsing from URL + * @param string $url + * @access public + * @version 1.0 + */ + function parse_from_url($url) { + return $this->parse(@file_get_contents($url)); + } + + /** + * Checks if there is a token at the current position + * @param string $string + * @param integer $i + * @access public + * @version 1.11 + */ + function is_token(&$string, $i) { + return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i)); + } + + /** + * Parses CSS in $string. The code is saved as array in $this->css + * @param string $string the CSS code + * @access public + * @return bool + * @version 1.1 + */ + function parse($string) { + // Temporarily set locale to en_US in order to handle floats properly + $old = @setlocale(LC_ALL, 0); + @setlocale(LC_ALL, 'C'); + + // PHP bug? Settings need to be refreshed in PHP4 + $this->print = new csstidy_print($this); + //$this->optimise = new csstidy_optimise($this); + + $all_properties = & $GLOBALS['csstidy']['all_properties']; + $at_rules = & $GLOBALS['csstidy']['at_rules']; + $quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties']; + + $this->css = array(); + $this->print->input_css = $string; + $string = str_replace("\r\n", "\n", $string) . ' '; + $cur_comment = ''; + + for ($i = 0, $size = strlen($string); $i < $size; $i++) { + if ($string{$i} === "\n" || $string{$i} === "\r") { + ++$this->line; + } + + switch ($this->status) { + /* Case in at-block */ + case 'at': + if (csstidy::is_token($string, $i)) { + if ($string{$i} === '/' && @$string{$i + 1} === '*') { + $this->status = 'ic'; + ++$i; + $this->from[] = 'at'; + } elseif ($string{$i} === '{') { + $this->status = 'is'; + $this->at = $this->css_new_media_section($this->at); + $this->_add_token(AT_START, $this->at); + } elseif ($string{$i} === ',') { + $this->at = trim($this->at) . ','; + } elseif ($string{$i} === '\\') { + $this->at .= $this->_unicode($string, $i); + } + // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5) + // '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2) + elseif (in_array($string{$i}, array('(', ')', ':', '.', '/'))) { + $this->at .= $string{$i}; + } + } else { + $lastpos = strlen($this->at) - 1; + if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) { + $this->at .= $string{$i}; + } + } + break; + + /* Case in-selector */ + case 'is': + if (csstidy::is_token($string, $i)) { + if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') { + $this->status = 'ic'; + ++$i; + $this->from[] = 'is'; + } elseif ($string{$i} === '@' && trim($this->selector) == '') { + // Check for at-rule + $this->invalid_at = true; + foreach ($at_rules as $name => $type) { + if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) { + ($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name; + $this->status = $type; + $i += strlen($name); + $this->invalid_at = false; + } + } + + if ($this->invalid_at) { + $this->selector = '@'; + $invalid_at_name = ''; + for ($j = $i + 1; $j < $size; ++$j) { + if (!ctype_alpha($string{$j})) { + break; + } + $invalid_at_name .= $string{$j}; + } + $this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning'); + } + } elseif (($string{$i} === '"' || $string{$i} === "'")) { + $this->cur_string[] = $string{$i}; + $this->status = 'instr'; + $this->str_char[] = $string{$i}; + $this->from[] = 'is'; + /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */ + $this->quoted_string[] = ($string{$i - 1} == '=' ); + } elseif ($this->invalid_at && $string{$i} === ';') { + $this->invalid_at = false; + $this->status = 'is'; + } elseif ($string{$i} === '{') { + $this->status = 'ip'; + if($this->at == '') { + $this->at = $this->css_new_media_section(DEFAULT_AT); + } + $this->selector = $this->css_new_selector($this->at,$this->selector); + $this->_add_token(SEL_START, $this->selector); + $this->added = false; + } elseif ($string{$i} === '}') { + $this->_add_token(AT_END, $this->at); + $this->at = ''; + $this->selector = ''; + $this->sel_separate = array(); + } elseif ($string{$i} === ',') { + $this->selector = trim($this->selector) . ','; + $this->sel_separate[] = strlen($this->selector); + } elseif ($string{$i} === '\\') { + $this->selector .= $this->_unicode($string, $i); + } elseif ($string{$i} === '*' && @in_array($string{$i + 1}, array('.', '#', '[', ':'))) { + // remove unnecessary universal selector, FS#147 + } else { + $this->selector .= $string{$i}; + } + } else { + $lastpos = strlen($this->selector) - 1; + if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) { + $this->selector .= $string{$i}; + } + else if (ctype_space($string{$i}) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) { + $this->selector .= $string{$i}; + } + } + break; + + /* Case in-property */ + case 'ip': + if (csstidy::is_token($string, $i)) { + if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') { + $this->status = 'iv'; + if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) { + $this->property = $this->css_new_property($this->at,$this->selector,$this->property); + $this->_add_token(PROPERTY, $this->property); + } + } elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') { + $this->status = 'ic'; + ++$i; + $this->from[] = 'ip'; + } elseif ($string{$i} === '}') { + $this->explode_selectors(); + $this->status = 'is'; + $this->invalid_at = false; + $this->_add_token(SEL_END, $this->selector); + $this->selector = ''; + $this->property = ''; + } elseif ($string{$i} === ';') { + $this->property = ''; + } elseif ($string{$i} === '\\') { + $this->property .= $this->_unicode($string, $i); + } + // else this is dumb IE a hack, keep it + elseif ($this->property=='' AND !ctype_space($string{$i})) { + $this->property .= $string{$i}; + } + } + elseif (!ctype_space($string{$i})) { + $this->property .= $string{$i}; + } + break; + + /* Case in-value */ + case 'iv': + $pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1); + if ((csstidy::is_token($string, $i) || $pn) && (!($string{$i} == ',' && !ctype_space($string{$i+1})))) { + if ($string{$i} === '/' && @$string{$i + 1} === '*') { + $this->status = 'ic'; + ++$i; + $this->from[] = 'iv'; + } elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) { + $this->cur_string[] = $string{$i}; + $this->str_char[] = ($string{$i} === '(') ? ')' : $string{$i}; + $this->status = 'instr'; + $this->from[] = 'iv'; + $this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties); + } elseif ($string{$i} === ',') { + $this->sub_value = trim($this->sub_value) . ','; + } elseif ($string{$i} === '\\') { + $this->sub_value .= $this->_unicode($string, $i); + } elseif ($string{$i} === ';' || $pn) { + if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') { + $this->status = 'is'; + + switch ($this->selector) { + case '@charset': + /* Add quotes to charset */ + $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"'; + $this->charset = $this->sub_value_arr[0]; + break; + case '@namespace': + /* Add quotes to namespace */ + $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"'; + $this->namespace = implode(' ', $this->sub_value_arr); + break; + case '@import': + $this->sub_value = trim($this->sub_value); + + if (empty($this->sub_value_arr)) { + // Quote URLs in imports only if they're not already inside url() and not already quoted. + if (substr($this->sub_value, 0, 4) != 'url(') { + if (!($this->sub_value{0} == substr($this->sub_value, -1) && in_array($this->sub_value{0}, array("'", '"')))) { + $this->sub_value = '"' . $this->sub_value . '"'; + } + } + } + + $this->sub_value_arr[] = $this->sub_value; + $this->import[] = implode(' ', $this->sub_value_arr); + break; + } + + $this->sub_value_arr = array(); + $this->sub_value = ''; + $this->selector = ''; + $this->sel_separate = array(); + } else { + $this->status = 'ip'; + } + } elseif ($string{$i} !== '}') { + $this->sub_value .= $string{$i}; + } + if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) { + if ($this->at == '') { + $this->at = $this->css_new_media_section(DEFAULT_AT); + } + + // case settings + if ($this->get_cfg('lowercase_s')) { + $this->selector = strtolower($this->selector); + } + $this->property = strtolower($this->property); + + $this->optimise->subvalue(); + if ($this->sub_value != '') { + if (substr($this->sub_value, 0, 6) == 'format') { + $format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1)); + if (!$format_strings) { + $this->sub_value = ""; + } + else { + $this->sub_value = "format("; + + foreach ($format_strings as $format_string) { + $this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",'; + } + + $this->sub_value = substr($this->sub_value, 0, -1) . ")"; + } + } + if ($this->sub_value != '') { + $this->sub_value_arr[] = $this->sub_value; + } + $this->sub_value = ''; + } + + $this->value = array_shift($this->sub_value_arr); + while(count($this->sub_value_arr)){ + //$this->value .= (substr($this->value,-1,1)==','?'':' ').array_shift($this->sub_value_arr); + $this->value .= ' '.array_shift($this->sub_value_arr); + } + + $this->optimise->value(); + + $valid = csstidy::property_is_valid($this->property); + if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) { + $this->css_add_property($this->at, $this->selector, $this->property, $this->value); + $this->_add_token(VALUE, $this->value); + $this->optimise->shorthands(); + } + if (!$valid) { + if ($this->get_cfg('discard_invalid_properties')) { + $this->log('Removed invalid property: ' . $this->property, 'Warning'); + } else { + $this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning'); + } + } + + $this->property = ''; + $this->sub_value_arr = array(); + $this->value = ''; + } + if ($string{$i} === '}') { + $this->explode_selectors(); + $this->_add_token(SEL_END, $this->selector); + $this->status = 'is'; + $this->invalid_at = false; + $this->selector = ''; + } + } elseif (!$pn) { + $this->sub_value .= $string{$i}; + + if (ctype_space($string{$i}) || $string{$i} == ',') { + $this->optimise->subvalue(); + if ($this->sub_value != '') { + $this->sub_value_arr[] = $this->sub_value; + $this->sub_value = ''; + } + } + } + break; + + /* Case in string */ + case 'instr': + $_str_char = $this->str_char[count($this->str_char)-1]; + $_cur_string = $this->cur_string[count($this->cur_string)-1]; + $temp_add = $string{$i}; + + // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but + // parentheticals can be nested more than once. + if ($_str_char === ")" && ($string{$i} === "(" || $string{$i} === '"' || $string{$i} === '\'') && !csstidy::escaped($string, $i)) { + $this->cur_string[] = $string{$i}; + $this->str_char[] = $string{$i} == "(" ? ")" : $string{$i}; + $this->from[] = 'instr'; + $this->quoted_string[] = !($string{$i} === "("); + continue; + } + + if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) { + $temp_add = "\\A"; + $this->log('Fixed incorrect newline in string', 'Warning'); + } + + $_cur_string .= $temp_add; + + if ($string{$i} === $_str_char && !csstidy::escaped($string, $i)) { + $_quoted_string = array_pop($this->quoted_string); + + $this->status = array_pop($this->from); + + if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') { + if (!$_quoted_string) { + if ($_str_char !== ')') { + // Convert properties like + // font-family: 'Arial'; + // to + // font-family: Arial; + // or + // url("abc") + // to + // url(abc) + $_cur_string = substr($_cur_string, 1, -1); + } + } else { + $_quoted_string = false; + } + } + + array_pop($this->cur_string); + array_pop($this->str_char); + + if ($_str_char === ")") { + $_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")"; + } + + if ($this->status === 'iv') { + if (!$_quoted_string){ + if (strpos($_cur_string,',')!==false) + // we can on only remove space next to ',' + $_cur_string = implode(',',array_map('trim',explode(',',$_cur_string))); + // and multiple spaces (too expensive) + if (strpos($_cur_string,' ')!==false) + $_cur_string = preg_replace(",\s+,"," ",$_cur_string); + } + $this->sub_value .= $_cur_string; + } elseif ($this->status === 'is') { + $this->selector .= $_cur_string; + } elseif ($this->status === 'instr') { + $this->cur_string[count($this->cur_string)-1] .= $_cur_string; + } + } + else { + $this->cur_string[count($this->cur_string)-1] = $_cur_string; + } + break; + + /* Case in-comment */ + case 'ic': + if ($string{$i} === '*' && $string{$i + 1} === '/') { + $this->status = array_pop($this->from); + $i++; + $this->_add_token(COMMENT, $cur_comment); + $cur_comment = ''; + } else { + $cur_comment .= $string{$i}; + } + break; + } + } + + $this->optimise->postparse(); + + $this->print->_reset(); + + @setlocale(LC_ALL, $old); // Set locale back to original setting + + return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace)); + } + + /** + * Explodes selectors + * @access private + * @version 1.0 + */ + function explode_selectors() { + // Explode multiple selectors + if ($this->get_cfg('merge_selectors') === 1) { + $new_sels = array(); + $lastpos = 0; + $this->sel_separate[] = strlen($this->selector); + foreach ($this->sel_separate as $num => $pos) { + if ($num == count($this->sel_separate) - 1) { + $pos += 1; + } + + $new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1); + $lastpos = $pos; + } + + if (count($new_sels) > 1) { + foreach ($new_sels as $selector) { + if (isset($this->css[$this->at][$this->selector])) { + $this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]); + } + } + unset($this->css[$this->at][$this->selector]); + } + } + $this->sel_separate = array(); + } + + /** + * Checks if a character is escaped (and returns true if it is) + * @param string $string + * @param integer $pos + * @access public + * @return bool + * @version 1.02 + */ + static function escaped(&$string, $pos) { + return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1)); + } + + /** + * Adds a property with value to the existing CSS code + * @param string $media + * @param string $selector + * @param string $property + * @param string $new_val + * @access private + * @version 1.2 + */ + function css_add_property($media, $selector, $property, $new_val) { + if ($this->get_cfg('preserve_css') || trim($new_val) == '') { + return; + } + + $this->added = true; + if (isset($this->css[$media][$selector][$property])) { + if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) { + $this->css[$media][$selector][$property] = trim($new_val); + } + } else { + $this->css[$media][$selector][$property] = trim($new_val); + } + } + + /** + * Start a new media section. + * Check if the media is not already known, + * else rename it with extra spaces + * to avoid merging + * + * @param string $media + * @return string + */ + function css_new_media_section($media){ + if($this->get_cfg('preserve_css')) { + return $media; + } + + // if the last @media is the same as this + // keep it + if (!$this->css OR !is_array($this->css) OR empty($this->css)){ + return $media; + } + end($this->css); + list($at,) = each($this->css); + if ($at == $media){ + return $media; + } + while (isset($this->css[$media])) + if (is_numeric($media)) + $media++; + else + $media .= " "; + return $media; + } + + /** + * Start a new selector. + * If already referenced in this media section, + * rename it with extra space to avoid merging + * except if merging is required, + * or last selector is the same (merge siblings) + * + * never merge @font-face + * + * @param string $media + * @param string $selector + * @return string + */ + function css_new_selector($media,$selector){ + if($this->get_cfg('preserve_css')) { + return $selector; + } + $selector = trim($selector); + if (strncmp($selector,"@font-face",10)!=0){ + if ($this->settings['merge_selectors'] != false) + return $selector; + + if (!$this->css OR !isset($this->css[$media]) OR !$this->css[$media]) + return $selector; + + // if last is the same, keep it + end($this->css[$media]); + list($sel,) = each($this->css[$media]); + if ($sel == $selector){ + return $selector; + } + } + + while (isset($this->css[$media][$selector])) + $selector .= " "; + return $selector; + } + + /** + * Start a new propertie. + * If already references in this selector, + * rename it with extra space to avoid override + * + * @param string $media + * @param string $selector + * @param string $property + * @return string + */ + function css_new_property($media, $selector, $property){ + if($this->get_cfg('preserve_css')) { + return $property; + } + if (!$this->css OR !isset($this->css[$media][$selector]) OR !$this->css[$media][$selector]) + return $property; + + while (isset($this->css[$media][$selector][$property])) + $property .= " "; + + return $property; + } + + /** + * Adds CSS to an existing media/selector + * @param string $media + * @param string $selector + * @param array $css_add + * @access private + * @version 1.1 + */ + function merge_css_blocks($media, $selector, $css_add) { + foreach ($css_add as $property => $value) { + $this->css_add_property($media, $selector, $property, $value, false); + } + } + + /** + * Checks if $value is !important. + * @param string $value + * @return bool + * @access public + * @version 1.0 + */ + static function is_important(&$value) { + return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important')); + } + + /** + * Returns a value without !important + * @param string $value + * @return string + * @access public + * @version 1.0 + */ + static function gvw_important($value) { + if (csstidy::is_important($value)) { + $value = trim($value); + $value = substr($value, 0, -9); + $value = trim($value); + $value = substr($value, 0, -1); + $value = trim($value); + return $value; + } + return $value; + } + + /** + * Checks if the next word in a string from pos is a CSS property + * @param string $istring + * @param integer $pos + * @return bool + * @access private + * @version 1.2 + */ + function property_is_next($istring, $pos) { + $all_properties = & $GLOBALS['csstidy']['all_properties']; + $istring = substr($istring, $pos, strlen($istring) - $pos); + $pos = strpos($istring, ':'); + if ($pos === false) { + return false; + } + $istring = strtolower(trim(substr($istring, 0, $pos))); + if (isset($all_properties[$istring])) { + $this->log('Added semicolon to the end of declaration', 'Warning'); + return true; + } + return false; + } + + /** + * Checks if a property is valid + * @param string $property + * @return bool; + * @access public + * @version 1.0 + */ + function property_is_valid($property) { + if (in_array(trim($property), $GLOBALS['csstidy']['multiple_properties'])) $property = trim($property); + $all_properties = & $GLOBALS['csstidy']['all_properties']; + return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false ); + } + + /** + * Accepts a list of strings (e.g., the argument to format() in a @font-face src property) + * and returns a list of the strings. Converts things like: + * + * format(abc) => format("abc") + * format(abc def) => format("abc","def") + * format(abc "def") => format("abc","def") + * format(abc, def, ghi) => format("abc","def","ghi") + * format("abc",'def') => format("abc","def") + * format("abc, def, ghi") => format("abc, def, ghi") + * + * @param string + * @return array + */ + + function parse_string_list($value) { + $value = trim($value); + + // Case: empty + if (!$value) return array(); + + $strings = array(); + + $in_str = false; + $current_string = ""; + + for ($i = 0, $_len = strlen($value); $i < $_len; $i++) { + if (($value{$i} == "," || $value{$i} === " ") && $in_str === true) { + $in_str = false; + $strings[] = $current_string; + $current_string = ""; + } + else if ($value{$i} == '"' || $value{$i} == "'"){ + if ($in_str === $value{$i}) { + $strings[] = $current_string; + $in_str = false; + $current_string = ""; + continue; + } + else if (!$in_str) { + $in_str = $value{$i}; + } + } + else { + if ($in_str){ + $current_string .= $value{$i}; + } + else { + if (!preg_match("/[\s,]/", $value{$i})) { + $in_str = true; + $current_string = $value{$i}; + } + } + } + } + + if ($current_string) { + $strings[] = $current_string; + } + + return $strings; + } +} diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php new file mode 100644 index 00000000..bc5accc5 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php @@ -0,0 +1,46 @@ +<?php + +/** + * CSSTidy - CSS Parser and Optimiser + * + * CSS ctype functions + * Defines some functions that can be not defined. + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CSSTidy; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + * @package csstidy + * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 + * @version 1.0 + */ +/* ctype_space Check for whitespace character(s) */ +if (!function_exists('ctype_space')) { + + function ctype_space($text) { + return!preg_match("/[^\s\r\n\t\f]/", $text); + } + +} +/* ctype_alpha Check for alphabetic character(s) */ +if (!function_exists('ctype_alpha')) { + + function ctype_alpha($text) { + return preg_match("/[a-zA-Z]/", $text); + } + +} +?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php new file mode 100644 index 00000000..364573a3 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php @@ -0,0 +1,936 @@ +<?php + +/** + * CSSTidy - CSS Parser and Optimiser + * + * CSS Optimising Class + * This class optimises CSS data generated by csstidy. + * + * Copyright 2005, 2006, 2007 Florian Schmitz + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 + */ + +/** + * CSS Optimising Class + * + * This class optimises CSS data generated by csstidy. + * + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.0 + */ +class csstidy_optimise { + + /** + * Constructor + * @param array $css contains the class csstidy + * @access private + * @version 1.0 + */ + function csstidy_optimise(&$css) { + $this->parser = & $css; + $this->css = & $css->css; + $this->sub_value = & $css->sub_value; + $this->at = & $css->at; + $this->selector = & $css->selector; + $this->property = & $css->property; + $this->value = & $css->value; + } + + /** + * Optimises $css after parsing + * @access public + * @version 1.0 + */ + function postparse() { + if ($this->parser->get_cfg('preserve_css')) { + return; + } + + if ($this->parser->get_cfg('merge_selectors') === 2) { + foreach ($this->css as $medium => $value) { + $this->merge_selectors($this->css[$medium]); + } + } + + if ($this->parser->get_cfg('discard_invalid_selectors')) { + foreach ($this->css as $medium => $value) { + $this->discard_invalid_selectors($this->css[$medium]); + } + } + + if ($this->parser->get_cfg('optimise_shorthands') > 0) { + foreach ($this->css as $medium => $value) { + foreach ($value as $selector => $value1) { + $this->css[$medium][$selector] = csstidy_optimise::merge_4value_shorthands($this->css[$medium][$selector]); + + if ($this->parser->get_cfg('optimise_shorthands') < 2) { + continue; + } + + $this->css[$medium][$selector] = csstidy_optimise::merge_font($this->css[$medium][$selector]); + + if ($this->parser->get_cfg('optimise_shorthands') < 3) { + continue; + } + + $this->css[$medium][$selector] = csstidy_optimise::merge_bg($this->css[$medium][$selector]); + if (empty($this->css[$medium][$selector])) { + unset($this->css[$medium][$selector]); + } + } + } + } + } + + /** + * Optimises values + * @access public + * @version 1.0 + */ + function value() { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + // optimise shorthand properties + if (isset($shorthands[$this->property])) { + $temp = csstidy_optimise::shorthand($this->value); // FIXME - move + if ($temp != $this->value) { + $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information'); + } + $this->value = $temp; + } + + // Remove whitespace at ! important + if ($this->value != $this->compress_important($this->value)) { + $this->parser->log('Optimised !important', 'Information'); + } + } + + /** + * Optimises shorthands + * @access public + * @version 1.0 + */ + function shorthands() { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) { + return; + } + + if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) { + $this->css[$this->at][$this->selector]['font']=''; + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_font($this->value)); + } + if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) { + $this->css[$this->at][$this->selector]['background']=''; + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_bg($this->value)); + } + if (isset($shorthands[$this->property])) { + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_4value_shorthands($this->property, $this->value)); + if (is_array($shorthands[$this->property])) { + $this->css[$this->at][$this->selector][$this->property] = ''; + } + } + } + + /** + * Optimises a sub-value + * @access public + * @version 1.0 + */ + function subvalue() { + $replace_colors = & $GLOBALS['csstidy']['replace_colors']; + + $this->sub_value = trim($this->sub_value); + if ($this->sub_value == '') { // caution : '0' + return; + } + + $important = ''; + if (csstidy::is_important($this->sub_value)) { + $important = '!important'; + } + $this->sub_value = csstidy::gvw_important($this->sub_value); + + // Compress font-weight + if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) { + if ($this->sub_value === 'bold') { + $this->sub_value = '700'; + $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information'); + } else if ($this->sub_value === 'normal') { + $this->sub_value = '400'; + $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information'); + } + } + + $temp = $this->compress_numbers($this->sub_value); + if (strcasecmp($temp, $this->sub_value) !== 0) { + if (strlen($temp) > strlen($this->sub_value)) { + $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); + } else { + $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); + } + $this->sub_value = $temp; + } + if ($this->parser->get_cfg('compress_colors')) { + $temp = $this->cut_color($this->sub_value); + if ($temp !== $this->sub_value) { + if (isset($replace_colors[$this->sub_value])) { + $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); + } else { + $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); + } + $this->sub_value = $temp; + } + } + $this->sub_value .= $important; + } + + /** + * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px + * @param string $value + * @access public + * @return string + * @version 1.0 + */ + function shorthand($value) { + $important = ''; + if (csstidy::is_important($value)) { + $values = csstidy::gvw_important($value); + $important = '!important'; + } + else + $values = $value; + + $values = explode(' ', $values); + switch (count($values)) { + case 4: + if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) { + return $values[0] . $important; + } elseif ($values[1] == $values[3] && $values[0] == $values[2]) { + return $values[0] . ' ' . $values[1] . $important; + } elseif ($values[1] == $values[3]) { + return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important; + } + break; + + case 3: + if ($values[0] == $values[1] && $values[0] == $values[2]) { + return $values[0] . $important; + } elseif ($values[0] == $values[2]) { + return $values[0] . ' ' . $values[1] . $important; + } + break; + + case 2: + if ($values[0] == $values[1]) { + return $values[0] . $important; + } + break; + } + + return $value; + } + + /** + * Removes unnecessary whitespace in ! important + * @param string $string + * @return string + * @access public + * @version 1.1 + */ + function compress_important(&$string) { + if (csstidy::is_important($string)) { + $string = csstidy::gvw_important($string) . '!important'; + } + return $string; + } + + /** + * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. + * @param string $color + * @return string + * @version 1.1 + */ + function cut_color($color) { + $replace_colors = & $GLOBALS['csstidy']['replace_colors']; + + // rgb(0,0,0) -> #000000 (or #000 in this case later) + if (strtolower(substr($color, 0, 4)) === 'rgb(') { + $color_tmp = substr($color, 4, strlen($color) - 5); + $color_tmp = explode(',', $color_tmp); + for ($i = 0; $i < count($color_tmp); $i++) { + $color_tmp[$i] = trim($color_tmp[$i]); + if (substr($color_tmp[$i], -1) === '%') { + $color_tmp[$i] = round((255 * $color_tmp[$i]) / 100); + } + if ($color_tmp[$i] > 255) + $color_tmp[$i] = 255; + } + $color = '#'; + for ($i = 0; $i < 3; $i++) { + if ($color_tmp[$i] < 16) { + $color .= '0' . dechex($color_tmp[$i]); + } else { + $color .= dechex($color_tmp[$i]); + } + } + } + + // Fix bad color names + if (isset($replace_colors[strtolower($color)])) { + $color = $replace_colors[strtolower($color)]; + } + + // #aabbcc -> #abc + if (strlen($color) == 7) { + $color_temp = strtolower($color); + if ($color_temp{0} === '#' && $color_temp{1} == $color_temp{2} && $color_temp{3} == $color_temp{4} && $color_temp{5} == $color_temp{6}) { + $color = '#' . $color{1} . $color{3} . $color{5}; + } + } + + switch (strtolower($color)) { + /* color name -> hex code */ + case 'black': return '#000'; + case 'fuchsia': return '#f0f'; + case 'white': return '#fff'; + case 'yellow': return '#ff0'; + + /* hex code -> color name */ + case '#800000': return 'maroon'; + case '#ffa500': return 'orange'; + case '#808000': return 'olive'; + case '#800080': return 'purple'; + case '#008000': return 'green'; + case '#000080': return 'navy'; + case '#008080': return 'teal'; + case '#c0c0c0': return 'silver'; + case '#808080': return 'gray'; + case '#f00': return 'red'; + } + + return $color; + } + + /** + * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 ) + * @param string $subvalue + * @return string + * @version 1.2 + */ + function compress_numbers($subvalue) { + $unit_values = & $GLOBALS['csstidy']['unit_values']; + $color_values = & $GLOBALS['csstidy']['color_values']; + + // for font:1em/1em sans-serif...; + if ($this->property === 'font') { + $temp = explode('/', $subvalue); + } else { + $temp = array($subvalue); + } + for ($l = 0; $l < count($temp); $l++) { + // if we are not dealing with a number at this point, do not optimise anything + $number = $this->AnalyseCssNumber($temp[$l]); + if ($number === false) { + return $subvalue; + } + + // Fix bad colors + if (in_array($this->property, $color_values)) { + if (strlen($temp[$l]) == 3 || strlen($temp[$l]) == 6) { + $temp[$l] = '#' . $temp[$l]; + } + else { + $temp[$l] = "0"; + } + continue; + } + + if (abs($number[0]) > 0) { + if ($number[1] == '' && in_array($this->property, $unit_values, true)) { + $number[1] = 'px'; + } + } else { + $number[1] = ''; + } + + $temp[$l] = $number[0] . $number[1]; + } + + return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]); + } + + /** + * Checks if a given string is a CSS valid number. If it is, + * an array containing the value and unit is returned + * @param string $string + * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number + */ + function AnalyseCssNumber($string) { + // most simple checks first + if (strlen($string) == 0 || ctype_alpha($string{0})) { + return false; + } + + $units = & $GLOBALS['csstidy']['units']; + $return = array(0, ''); + + $return[0] = floatval($string); + if (abs($return[0]) > 0 && abs($return[0]) < 1) { + if ($return[0] < 0) { + $return[0] = '-' . ltrim(substr($return[0], 1), '0'); + } else { + $return[0] = ltrim($return[0], '0'); + } + } + + // Look for unit and split from value if exists + foreach ($units as $unit) { + $expectUnitAt = strlen($string) - strlen($unit); + if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false" + continue; + } + $actualPosition = strpos($string, $unitInString); + if ($expectUnitAt === $actualPosition) { + $return[1] = $unit; + $string = substr($string, 0, - strlen($unit)); + break; + } + } + if (!is_numeric($string)) { + return false; + } + return $return; + } + + /** + * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} + * Very basic and has at least one bug. Hopefully there is a replacement soon. + * @param array $array + * @return array + * @access public + * @version 1.2 + */ + function merge_selectors(&$array) { + $css = $array; + foreach ($css as $key => $value) { + if (!isset($css[$key])) { + continue; + } + $newsel = ''; + + // Check if properties also exist in another selector + $keys = array(); + // PHP bug (?) without $css = $array; here + foreach ($css as $selector => $vali) { + if ($selector == $key) { + continue; + } + + if ($css[$key] === $vali) { + $keys[] = $selector; + } + } + + if (!empty($keys)) { + $newsel = $key; + unset($css[$key]); + foreach ($keys as $selector) { + unset($css[$selector]); + $newsel .= ',' . $selector; + } + $css[$newsel] = $value; + } + } + $array = $css; + } + + /** + * Removes invalid selectors and their corresponding rule-sets as + * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check + * and should be replaced by a full-blown parsing algorithm or + * regular expression + * @version 1.4 + */ + function discard_invalid_selectors(&$array) { + $invalid = array('+' => true, '~' => true, ',' => true, '>' => true); + foreach ($array as $selector => $decls) { + $ok = true; + $selectors = array_map('trim', explode(',', $selector)); + foreach ($selectors as $s) { + $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s); + foreach ($simple_selectors as $ss) { + if ($ss === '') + $ok = false; + // could also check $ss for internal structure, + // but that probably would be too slow + } + } + if (!$ok) + unset($array[$selector]); + } + } + + /** + * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... + * @param string $property + * @param string $value + * @return array + * @version 1.0 + * @see merge_4value_shorthands() + */ + function dissolve_4value_shorthands($property, $value) { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + if (!is_array($shorthands[$property])) { + $return[$property] = $value; + return $return; + } + + $important = ''; + if (csstidy::is_important($value)) { + $value = csstidy::gvw_important($value); + $important = '!important'; + } + $values = explode(' ', $value); + + + $return = array(); + if (count($values) == 4) { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = $values[$i] . $important; + } + } elseif (count($values) == 3) { + $return[$shorthands[$property][0]] = $values[0] . $important; + $return[$shorthands[$property][1]] = $values[1] . $important; + $return[$shorthands[$property][3]] = $values[1] . $important; + $return[$shorthands[$property][2]] = $values[2] . $important; + } elseif (count($values) == 2) { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important; + } + } else { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = $values[0] . $important; + } + } + + return $return; + } + + /** + * Explodes a string as explode() does, however, not if $sep is escaped or within a string. + * @param string $sep seperator + * @param string $string + * @return array + * @version 1.0 + */ + function explode_ws($sep, $string) { + $status = 'st'; + $to = ''; + + $output = array(); + $num = 0; + for ($i = 0, $len = strlen($string); $i < $len; $i++) { + switch ($status) { + case 'st': + if ($string{$i} == $sep && !csstidy::escaped($string, $i)) { + ++$num; + } elseif ($string{$i} === '"' || $string{$i} === '\'' || $string{$i} === '(' && !csstidy::escaped($string, $i)) { + $status = 'str'; + $to = ($string{$i} === '(') ? ')' : $string{$i}; + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + } else { + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + } + break; + + case 'str': + if ($string{$i} == $to && !csstidy::escaped($string, $i)) { + $status = 'st'; + } + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + break; + } + } + + if (isset($output[0])) { + return $output; + } else { + return array($output); + } + } + + /** + * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() + * @param array $array + * @return array + * @version 1.2 + * @see dissolve_4value_shorthands() + */ + function merge_4value_shorthands($array) { + $return = $array; + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + foreach ($shorthands as $key => $value) { + if (isset($array[$value[0]]) && isset($array[$value[1]]) + && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) { + $return[$key] = ''; + + $important = ''; + for ($i = 0; $i < 4; $i++) { + $val = $array[$value[$i]]; + if (csstidy::is_important($val)) { + $important = '!important'; + $return[$key] .= csstidy::gvw_important($val) . ' '; + } else { + $return[$key] .= $val . ' '; + } + unset($return[$value[$i]]); + } + $return[$key] = csstidy_optimise::shorthand(trim($return[$key] . $important)); + } + } + return $return; + } + + /** + * Dissolve background property + * @param string $str_value + * @return array + * @version 1.0 + * @see merge_bg() + * @todo full CSS 3 compliance + */ + function dissolve_short_bg($str_value) { + // don't try to explose background gradient ! + if (stripos($str_value, "gradient(")!==FALSE) + return array('background'=>$str_value); + + $background_prop_default = & $GLOBALS['csstidy']['background_prop_default']; + $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space'); + $attachment = array('scroll', 'fixed', 'local'); + $clip = array('border', 'padding'); + $origin = array('border', 'padding', 'content'); + $pos = array('top', 'center', 'bottom', 'left', 'right'); + $important = ''; + $return = array('background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null); + + if (csstidy::is_important($str_value)) { + $important = ' !important'; + $str_value = csstidy::gvw_important($str_value); + } + + $str_value = csstidy_optimise::explode_ws(',', $str_value); + for ($i = 0; $i < count($str_value); $i++) { + $have['clip'] = false; + $have['pos'] = false; + $have['color'] = false; + $have['bg'] = false; + + if (is_array($str_value[$i])) { + $str_value[$i] = $str_value[$i][0]; + } + $str_value[$i] = csstidy_optimise::explode_ws(' ', trim($str_value[$i])); + + for ($j = 0; $j < count($str_value[$i]); $j++) { + if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) { + $return['background-image'] .= $str_value[$i][$j] . ','; + $have['bg'] = true; + } elseif (in_array($str_value[$i][$j], $repeat, true)) { + $return['background-repeat'] .= $str_value[$i][$j] . ','; + } elseif (in_array($str_value[$i][$j], $attachment, true)) { + $return['background-attachment'] .= $str_value[$i][$j] . ','; + } elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) { + $return['background-clip'] .= $str_value[$i][$j] . ','; + $have['clip'] = true; + } elseif (in_array($str_value[$i][$j], $origin, true)) { + $return['background-origin'] .= $str_value[$i][$j] . ','; + } elseif ($str_value[$i][$j]{0} === '(') { + $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ','; + } elseif (in_array($str_value[$i][$j], $pos, true) || is_numeric($str_value[$i][$j]{0}) || $str_value[$i][$j]{0} === null || $str_value[$i][$j]{0} === '-' || $str_value[$i][$j]{0} === '.') { + $return['background-position'] .= $str_value[$i][$j]; + if (!$have['pos']) + $return['background-position'] .= ' '; else + $return['background-position'].= ','; + $have['pos'] = true; + } + elseif (!$have['color']) { + $return['background-color'] .= $str_value[$i][$j] . ','; + $have['color'] = true; + } + } + } + + foreach ($background_prop_default as $bg_prop => $default_value) { + if ($return[$bg_prop] !== null) { + $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important; + } + else + $return[$bg_prop] = $default_value . $important; + } + return $return; + } + + /** + * Merges all background properties + * @param array $input_css + * @return array + * @version 1.0 + * @see dissolve_short_bg() + * @todo full CSS 3 compliance + */ + function merge_bg($input_css) { + $background_prop_default = & $GLOBALS['csstidy']['background_prop_default']; + // Max number of background images. CSS3 not yet fully implemented + $number_of_values = @max(count(csstidy_optimise::explode_ws(',', $input_css['background-image'])), count(csstidy_optimise::explode_ws(',', $input_css['background-color'])), 1); + // Array with background images to check if BG image exists + $bg_img_array = @csstidy_optimise::explode_ws(',', csstidy::gvw_important($input_css['background-image'])); + $new_bg_value = ''; + $important = ''; + + // if background properties is here and not empty, don't try anything + if (isset($input_css['background']) AND $input_css['background']) + return $input_css; + + for ($i = 0; $i < $number_of_values; $i++) { + foreach ($background_prop_default as $bg_property => $default_value) { + // Skip if property does not exist + if (!isset($input_css[$bg_property])) { + continue; + } + + $cur_value = $input_css[$bg_property]; + // skip all optimisation if gradient() somewhere + if (stripos($cur_value, "gradient(")!==FALSE) + return $input_css; + + // Skip some properties if there is no background image + if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none') + && ($bg_property === 'background-size' || $bg_property === 'background-position' + || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')) { + continue; + } + + // Remove !important + if (csstidy::is_important($cur_value)) { + $important = ' !important'; + $cur_value = csstidy::gvw_important($cur_value); + } + + // Do not add default values + if ($cur_value === $default_value) { + continue; + } + + $temp = csstidy_optimise::explode_ws(',', $cur_value); + + if (isset($temp[$i])) { + if ($bg_property === 'background-size') { + $new_bg_value .= '(' . $temp[$i] . ') '; + } else { + $new_bg_value .= $temp[$i] . ' '; + } + } + } + + $new_bg_value = trim($new_bg_value); + if ($i != $number_of_values - 1) + $new_bg_value .= ','; + } + + // Delete all background-properties + foreach ($background_prop_default as $bg_property => $default_value) { + unset($input_css[$bg_property]); + } + + // Add new background property + if ($new_bg_value !== '') + $input_css['background'] = $new_bg_value . $important; + elseif(isset ($input_css['background'])) + $input_css['background'] = 'none'; + + return $input_css; + } + + /** + * Dissolve font property + * @param string $str_value + * @return array + * @version 1.3 + * @see merge_font() + */ + function dissolve_short_font($str_value) { + $font_prop_default = & $GLOBALS['csstidy']['font_prop_default']; + $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900); + $font_variant = array('normal', 'small-caps'); + $font_style = array('normal', 'italic', 'oblique'); + $important = ''; + $return = array('font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null); + + if (csstidy::is_important($str_value)) { + $important = '!important'; + $str_value = csstidy::gvw_important($str_value); + } + + $have['style'] = false; + $have['variant'] = false; + $have['weight'] = false; + $have['size'] = false; + // Detects if font-family consists of several words w/o quotes + $multiwords = false; + + // Workaround with multiple font-family + $str_value = csstidy_optimise::explode_ws(',', trim($str_value)); + + $str_value[0] = csstidy_optimise::explode_ws(' ', trim($str_value[0])); + + for ($j = 0; $j < count($str_value[0]); $j++) { + if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) { + $return['font-weight'] = $str_value[0][$j]; + $have['weight'] = true; + } elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) { + $return['font-variant'] = $str_value[0][$j]; + $have['variant'] = true; + } elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) { + $return['font-style'] = $str_value[0][$j]; + $have['style'] = true; + } elseif ($have['size'] === false && (is_numeric($str_value[0][$j]{0}) || $str_value[0][$j]{0} === null || $str_value[0][$j]{0} === '.')) { + $size = csstidy_optimise::explode_ws('/', trim($str_value[0][$j])); + $return['font-size'] = $size[0]; + if (isset($size[1])) { + $return['line-height'] = $size[1]; + } else { + $return['line-height'] = ''; // don't add 'normal' ! + } + $have['size'] = true; + } else { + if (isset($return['font-family'])) { + $return['font-family'] .= ' ' . $str_value[0][$j]; + $multiwords = true; + } else { + $return['font-family'] = $str_value[0][$j]; + } + } + } + // add quotes if we have several qords in font-family + if ($multiwords !== false) { + $return['font-family'] = '"' . $return['font-family'] . '"'; + } + $i = 1; + while (isset($str_value[$i])) { + $return['font-family'] .= ',' . trim($str_value[$i]); + $i++; + } + + // Fix for 100 and more font-size + if ($have['size'] === false && isset($return['font-weight']) && + is_numeric($return['font-weight']{0})) { + $return['font-size'] = $return['font-weight']; + unset($return['font-weight']); + } + + foreach ($font_prop_default as $font_prop => $default_value) { + if ($return[$font_prop] !== null) { + $return[$font_prop] = $return[$font_prop] . $important; + } + else + $return[$font_prop] = $default_value . $important; + } + return $return; + } + + /** + * Merges all fonts properties + * @param array $input_css + * @return array + * @version 1.3 + * @see dissolve_short_font() + */ + function merge_font($input_css) { + $font_prop_default = & $GLOBALS['csstidy']['font_prop_default']; + $new_font_value = ''; + $important = ''; + // Skip if not font-family and font-size set + if (isset($input_css['font-family']) && isset($input_css['font-size'])) { + // fix several words in font-family - add quotes + if (isset($input_css['font-family'])) { + $families = explode(",", $input_css['font-family']); + $result_families = array(); + foreach ($families as $family) { + $family = trim($family); + $len = strlen($family); + if (strpos($family, " ") && + !(($family{0} == '"' && $family{$len - 1} == '"') || + ($family{0} == "'" && $family{$len - 1} == "'"))) { + $family = '"' . $family . '"'; + } + $result_families[] = $family; + } + $input_css['font-family'] = implode(",", $result_families); + } + foreach ($font_prop_default as $font_property => $default_value) { + + // Skip if property does not exist + if (!isset($input_css[$font_property])) { + continue; + } + + $cur_value = $input_css[$font_property]; + + // Skip if default value is used + if ($cur_value === $default_value) { + continue; + } + + // Remove !important + if (csstidy::is_important($cur_value)) { + $important = '!important'; + $cur_value = csstidy::gvw_important($cur_value); + } + + $new_font_value .= $cur_value; + // Add delimiter + $new_font_value .= ( $font_property === 'font-size' && + isset($input_css['line-height'])) ? '/' : ' '; + } + + $new_font_value = trim($new_font_value); + + // Delete all font-properties + foreach ($font_prop_default as $font_property => $default_value) { + if ($font_property!=='font' OR !$new_font_value) + unset($input_css[$font_property]); + } + + // Add new font property + if ($new_font_value !== '') { + $input_css['font'] = $new_font_value . $important; + } + } + + return $input_css; + } + +} diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php new file mode 100644 index 00000000..00a1956b --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php @@ -0,0 +1,408 @@ +<?php + +/** + * CSSTidy - CSS Parser and Optimiser + * + * CSS Printing class + * This class prints CSS data generated by csstidy. + * + * Copyright 2005, 2006, 2007 Florian Schmitz + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Cedric Morin (cedric at yterium dot com) 2010 + */ + +/** + * CSS Printing class + * + * This class prints CSS data generated by csstidy. + * + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.0.1 + */ +class csstidy_print { + + /** + * Saves the input CSS string + * @var string + * @access private + */ + var $input_css = ''; + /** + * Saves the formatted CSS string + * @var string + * @access public + */ + var $output_css = ''; + /** + * Saves the formatted CSS string (plain text) + * @var string + * @access public + */ + var $output_css_plain = ''; + + /** + * Constructor + * @param array $css contains the class csstidy + * @access private + * @version 1.0 + */ + function csstidy_print(&$css) { + $this->parser = & $css; + $this->css = & $css->css; + $this->template = & $css->template; + $this->tokens = & $css->tokens; + $this->charset = & $css->charset; + $this->import = & $css->import; + $this->namespace = & $css->namespace; + } + + /** + * Resets output_css and output_css_plain (new css code) + * @access private + * @version 1.0 + */ + function _reset() { + $this->output_css = ''; + $this->output_css_plain = ''; + } + + /** + * Returns the CSS code as plain text + * @param string $default_media default @media to add to selectors without any @media + * @return string + * @access public + * @version 1.0 + */ + function plain($default_media='') { + $this->_print(true, $default_media); + return $this->output_css_plain; + } + + /** + * Returns the formatted CSS code + * @param string $default_media default @media to add to selectors without any @media + * @return string + * @access public + * @version 1.0 + */ + function formatted($default_media='') { + $this->_print(false, $default_media); + return $this->output_css; + } + + /** + * Returns the formatted CSS code to make a complete webpage + * @param string $doctype shorthand for the document type + * @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet + * @param string $title title to be added in the head of the document + * @param string $lang two-letter language code to be added to the output + * @return string + * @access public + * @version 1.4 + */ + function formatted_page($doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') { + switch ($doctype) { + case 'xhtml1.0strict': + $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; + break; + case 'xhtml1.1': + default: + $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'; + break; + } + + $output = $cssparsed = ''; + $this->output_css_plain = & $output; + + $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"'; + $output .= ( $doctype === 'xhtml1.1') ? '>' : ' lang="' . $lang . '">'; + $output .= "\n<head>\n <title>$title</title>"; + + if ($externalcss) { + $output .= "\n <style type=\"text/css\">\n"; + $cssparsed = file_get_contents('cssparsed.css'); + $output .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php + $output .= "\n</style>"; + } else { + $output .= "\n" . ' <link rel="stylesheet" type="text/css" href="cssparsed.css" />'; +// } + } + $output .= "\n</head>\n<body><code id=\"copytext\">"; + $output .= $this->formatted(); + $output .= '</code>' . "\n" . '</body></html>'; + return $this->output_css_plain; + } + + /** + * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain + * @param bool $plain plain text or not + * @param string $default_media default @media to add to selectors without any @media + * @access private + * @version 2.0 + */ + function _print($plain = false, $default_media='') { + if ($this->output_css && $this->output_css_plain) { + return; + } + + $output = ''; + if (!$this->parser->get_cfg('preserve_css')) { + $this->_convert_raw_css($default_media); + } + + $template = & $this->template; + + if ($plain) { + $template = array_map('strip_tags', $template); + } + + if ($this->parser->get_cfg('timestamp')) { + array_unshift($this->tokens, array(COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date('r') . ' ')); + } + + if (!empty($this->charset)) { + $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6]; + } + + if (!empty($this->import)) { + for ($i = 0, $size = count($this->import); $i < $size; $i++) { + $import_components = explode(' ', $this->import[$i]); + if (substr($import_components[0], 0, 4) === 'url(' && substr($import_components[0], -1, 1) === ')') { + $import_components[0] = '\'' . trim(substr($import_components[0], 4, -1), "'\"") . '\''; + $this->import[$i] = implode(' ', $import_components); + $this->parser->log('Optimised @import : Removed "url("', 'Information'); + } + $output .= $template[0] . '@import ' . $template[5] . $this->import[$i] . $template[6]; + } + } + + if (!empty($this->namespace)) { + if (substr($this->namespace, 0, 4) === 'url(' && substr($this->namespace, -1, 1) === ')') { + $this->namespace = '\'' . substr($this->namespace, 4, -1) . '\''; + $this->parser->log('Optimised @namespace : Removed "url("', 'Information'); + } + $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6]; + } + + $output .= $template[13]; + $in_at_out = ''; + $out = & $output; + + foreach ($this->tokens as $key => $token) { + switch ($token[0]) { + case AT_START: + $out .= $template[0] . $this->_htmlsp($token[1], $plain) . $template[1]; + $out = & $in_at_out; + break; + + case SEL_START: + if ($this->parser->get_cfg('lowercase_s')) + $token[1] = strtolower($token[1]); + $out .= ( $token[1]{0} !== '@') ? $template[2] . $this->_htmlsp($token[1], $plain) : $template[0] . $this->_htmlsp($token[1], $plain); + $out .= $template[3]; + break; + + case PROPERTY: + if ($this->parser->get_cfg('case_properties') === 2) { + $token[1] = strtoupper($token[1]); + } elseif ($this->parser->get_cfg('case_properties') === 1) { + $token[1] = strtolower($token[1]); + } + $out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5]; + break; + + case VALUE: + $out .= $this->_htmlsp($token[1], $plain); + if ($this->_seeknocomment($key, 1) == SEL_END && $this->parser->get_cfg('remove_last_;')) { + $out .= str_replace(';', '', $template[6]); + } else { + $out .= $template[6]; + } + break; + + case SEL_END: + $out .= $template[7]; + if ($this->_seeknocomment($key, 1) != AT_END) + $out .= $template[8]; + break; + + case AT_END: + $out = & $output; + $out .= $template[10] . str_replace("\n", "\n" . $template[10], $in_at_out); + $in_at_out = ''; + $out .= $template[9]; + break; + + case COMMENT: + $out .= $template[11] . '/*' . $this->_htmlsp($token[1], $plain) . '*/' . $template[12]; + break; + } + } + + $output = trim($output); + + if (!$plain) { + $this->output_css = $output; + $this->_print(true); + } else { + // If using spaces in the template, don't want these to appear in the plain output + $this->output_css_plain = str_replace(' ', '', $output); + } + } + + /** + * Gets the next token type which is $move away from $key, excluding comments + * @param integer $key current position + * @param integer $move move this far + * @return mixed a token type + * @access private + * @version 1.0 + */ + function _seeknocomment($key, $move) { + $go = ($move > 0) ? 1 : -1; + for ($i = $key + 1; abs($key - $i) - 1 < abs($move); $i += $go) { + if (!isset($this->tokens[$i])) { + return; + } + if ($this->tokens[$i][0] == COMMENT) { + $move += 1; + continue; + } + return $this->tokens[$i][0]; + } + } + + /** + * Converts $this->css array to a raw array ($this->tokens) + * @param string $default_media default @media to add to selectors without any @media + * @access private + * @version 1.0 + */ + function _convert_raw_css($default_media='') { + $this->tokens = array(); + + foreach ($this->css as $medium => $val) { + if ($this->parser->get_cfg('sort_selectors')) + ksort($val); + if (intval($medium) < DEFAULT_AT) { + $this->parser->_add_token(AT_START, $medium, true); + } + elseif ($default_media) { + $this->parser->_add_token(AT_START, $default_media, true); + } + + foreach ($val as $selector => $vali) { + if ($this->parser->get_cfg('sort_properties')) + ksort($vali); + $this->parser->_add_token(SEL_START, $selector, true); + + foreach ($vali as $property => $valj) { + $this->parser->_add_token(PROPERTY, $property, true); + $this->parser->_add_token(VALUE, $valj, true); + } + + $this->parser->_add_token(SEL_END, $selector, true); + } + + if (intval($medium) < DEFAULT_AT) { + $this->parser->_add_token(AT_END, $medium, true); + } + elseif ($default_media) { + $this->parser->_add_token(AT_END, $default_media, true); + } + } + } + + /** + * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner. + * @param string $string + * @param bool $plain + * @return string + * @see csstidy_print::_print() + * @access private + * @version 1.0 + */ + function _htmlsp($string, $plain) { + if (!$plain) { + return htmlspecialchars($string, ENT_QUOTES, 'utf-8'); + } + return $string; + } + + /** + * Get compression ratio + * @access public + * @return float + * @version 1.2 + */ + function get_ratio() { + if (!$this->output_css_plain) { + $this->formatted(); + } + return round((strlen($this->input_css) - strlen($this->output_css_plain)) / strlen($this->input_css), 3) * 100; + } + + /** + * Get difference between the old and new code in bytes and prints the code if necessary. + * @access public + * @return string + * @version 1.1 + */ + function get_diff() { + if (!$this->output_css_plain) { + $this->formatted(); + } + + $diff = strlen($this->output_css_plain) - strlen($this->input_css); + + if ($diff > 0) { + return '+' . $diff; + } elseif ($diff == 0) { + return '+-' . $diff; + } + + return $diff; + } + + /** + * Get the size of either input or output CSS in KB + * @param string $loc default is "output" + * @access public + * @return integer + * @version 1.0 + */ + function size($loc = 'output') { + if ($loc === 'output' && !$this->output_css) { + $this->formatted(); + } + + if ($loc === 'input') { + return (strlen($this->input_css) / 1000); + } else { + return (strlen($this->output_css_plain) / 1000); + } + } + +} diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparse.css b/plugins/jetpack/modules/custom-css/csstidy/cssparse.css new file mode 100644 index 00000000..38fc40b4 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/cssparse.css @@ -0,0 +1,118 @@ +@import url("cssparsed.css"); + +html, body { +font:0.8em Verdana,Helvetica,sans-serif; +background:#F8F8F6; +} + +code { +font-size:1.2em; +} + +div#rightcol { +padding-left:32em; +} + +fieldset { +display:block; +margin:0.5em 0; +padding:1em; +border:solid #7284AB 2px; +} +fieldset.code_output { +display:inline; +} + +h1 { +font-size:2em; +} + +small { +font-size:0.7em; +} + +fieldset#field_input { +float:left; +margin:0 0.5em 1em 0; +} + +fieldset#options,fieldset#code_layout { +width:31em; +} + +input#submit { +clear:both; +display:block; +margin:1em; +} + +select { +margin:2px 0 0; +} + +label.block { +display:block; +} + +legend { +background:#c4E1C3; +padding:2px 4px; +border:dashed 1px; +} + +textarea#css_text { +width:27em; +height:370px; +display:block; +margin-right:1em; +} + +.help { +cursor:help; +} + +p.important { +border:solid 1px red; +font-weight:bold; +padding:1em; +background:white; +} + +p { +margin:1em 0; +} + +dl { +padding-left:0.5em; +} + +dt { +font-weight:bold; +margin:0; +float:left; +clear:both; +height:1.5em; +} + +dd { +margin:0 0 0 4em; +height:1.5em; +} + +fieldset#messages { +background:white; +padding:0 0 0 1em; +} + +fieldset#messages div { +height:10em; +overflow:auto; +} + +dd.Warning { +color:orange; +} + +dd.Information { +color:green; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css new file mode 100644 index 00000000..5aaf2bb1 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css @@ -0,0 +1,29 @@ +code#copytext { + white-space: pre; + font-family: Verdana; +} + +.at { +color:darkblue; +} + +.format { +color:gray; +} + +.property { +color:green; +} + +.selector { +color:blue; +} + +.value { +color:red; +left: 500px; +} + +.comment { +color:orange; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php b/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php new file mode 100644 index 00000000..f39ecbf8 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php @@ -0,0 +1,75 @@ +<?php + +unset( $GLOBALS['csstidy']['all_properties']['binding'] ); + +$GLOBALS['csstidy']['all_properties']['text-size-adjust'] = 'CSS3.0'; + +// Support browser prefixes for properties only in the latest CSS draft +foreach ( $GLOBALS['csstidy']['all_properties'] as $property => $levels ) { + if ( strpos( $levels, "," ) === false ) { + $GLOBALS['csstidy']['all_properties']['-moz-' . $property] = $levels; + $GLOBALS['csstidy']['all_properties']['-webkit-' . $property] = $levels; + $GLOBALS['csstidy']['all_properties']['-ms-' . $property] = $levels; + $GLOBALS['csstidy']['all_properties']['-o-' . $property] = $levels; + $GLOBALS['csstidy']['all_properties']['-khtml-' . $property] = $levels; + + if ( in_array( $property, $GLOBALS['csstidy']['unit_values'] ) ) { + $GLOBALS['csstidy']['unit_values'][] = '-moz-' . $property; + $GLOBALS['csstidy']['unit_values'][] = '-webkit-' . $property; + $GLOBALS['csstidy']['unit_values'][] = '-ms-' . $property; + $GLOBALS['csstidy']['unit_values'][] = '-o-' . $property; + $GLOBALS['csstidy']['unit_values'][] = '-khtml-' . $property; + } + + if ( in_array( $property, $GLOBALS['csstidy']['color_values'] ) ) { + $GLOBALS['csstidy']['color_values'][] = '-moz-' . $property; + $GLOBALS['csstidy']['color_values'][] = '-webkit-' . $property; + $GLOBALS['csstidy']['color_values'][] = '-ms-' . $property; + $GLOBALS['csstidy']['color_values'][] = '-o-' . $property; + $GLOBALS['csstidy']['color_values'][] = '-khtml-' . $property; + } + } +} + +foreach ( $GLOBALS['csstidy']['multiple_properties'] as $property ) { + if ( '-' != $property[0] ) { + $GLOBALS['csstidy']['multiple_properties'][] = '-o-' . $property; + $GLOBALS['csstidy']['multiple_properties'][] = '-ms-' . $property; + $GLOBALS['csstidy']['multiple_properties'][] = '-webkit-' . $property; + $GLOBALS['csstidy']['multiple_properties'][] = '-moz-' . $property; + $GLOBALS['csstidy']['multiple_properties'][] = '-khtml-' . $property; + } +} + +/** + * CSS Animation + * + * @see https://developer.mozilla.org/en/CSS/CSS_animations + */ +$GLOBALS['csstidy']['at_rules']['-webkit-keyframes'] = 'at'; +$GLOBALS['csstidy']['at_rules']['-moz-keyframes'] = 'at'; +$GLOBALS['csstidy']['at_rules']['-ms-keyframes'] = 'at'; + +/** + * Non-standard CSS properties. They're not part of any spec, but we say + * they're in all of them so that we can support them. + */ +$GLOBALS['csstidy']['all_properties']['-webkit-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-moz-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-ms-filter'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['filter'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['scrollbar-face-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-ms-interpolation-mode'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-rendering'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-x'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-y'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-z'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-webkit-font-smoothing'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-font-smooth'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-o-object-fit'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['object-fit'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['-o-object-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['object-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-overflow'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['zoom'] = 'CSS3.0'; + diff --git a/plugins/jetpack/modules/custom-css/csstidy/data.inc.php b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php new file mode 100644 index 00000000..c0855681 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php @@ -0,0 +1,661 @@ +<?php +/** + * Various CSS Data for CSSTidy + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CSSTidy; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005 + * @author Nikolay Matsievsky (speed at webo dot name) 2010 + */ + +define('AT_START', 1); +define('AT_END', 2); +define('SEL_START', 3); +define('SEL_END', 4); +define('PROPERTY', 5); +define('VALUE', 6); +define('COMMENT', 7); +define('DEFAULT_AT', 41); + +/** + * All whitespace allowed in CSS + * + * @global array $GLOBALS['csstidy']['whitespace'] + * @version 1.0 + */ +$GLOBALS['csstidy']['whitespace'] = array(' ',"\n","\t","\r","\x0B"); + +/** + * All CSS tokens used by csstidy + * + * @global string $GLOBALS['csstidy']['tokens'] + * @version 1.0 + */ +$GLOBALS['csstidy']['tokens'] = '/@}{;:=\'"(,\\!$%&)*+.<>?[]^`|~'; + +/** + * All CSS units (CSS 3 units included) + * + * @see compress_numbers() + * @global array $GLOBALS['csstidy']['units'] + * @version 1.0 + */ +$GLOBALS['csstidy']['units'] = array('in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz'); + +/** + * Available at-rules + * + * @global array $GLOBALS['csstidy']['at_rules'] + * @version 1.0 + */ +$GLOBALS['csstidy']['at_rules'] = array('page' => 'is','font-face' => 'is','charset' => 'iv', 'import' => 'iv','namespace' => 'iv','media' => 'at','keyframes' => 'at'); + + /** + * Properties that need a value with unit + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['unit_values'] + * @version 1.2 + */ +$GLOBALS['csstidy']['unit_values'] = array ('background', 'background-position', 'background-size', 'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-width', + 'border-top-width', 'border-right-width', 'border-left-width', 'border-bottom-width', 'bottom', 'border-spacing', 'column-gap', 'column-width', + 'font-size', 'height', 'left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'max-height', + 'max-width', 'min-height', 'min-width', 'outline', 'outline-width', 'padding', 'padding-top', 'padding-right', + 'padding-bottom', 'padding-left', 'perspective', 'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width'); + +/** + * Properties that allow <color> as value + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['color_values'] + * @version 1.0 + */ +$GLOBALS['csstidy']['color_values'] = array(); +$GLOBALS['csstidy']['color_values'][] = 'background-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-top-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-right-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-bottom-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-left-color'; +$GLOBALS['csstidy']['color_values'][] = 'color'; +$GLOBALS['csstidy']['color_values'][] = 'outline-color'; +$GLOBALS['csstidy']['color_values'][] = 'column-rule-color'; + +/** + * Default values for the background properties + * + * @todo Possibly property names will change during CSS3 development + * @global array $GLOBALS['csstidy']['background_prop_default'] + * @see dissolve_short_bg() + * @see merge_bg() + * @version 1.0 + */ +$GLOBALS['csstidy']['background_prop_default'] = array(); +$GLOBALS['csstidy']['background_prop_default']['background-image'] = 'none'; +$GLOBALS['csstidy']['background_prop_default']['background-size'] = 'auto'; +$GLOBALS['csstidy']['background_prop_default']['background-repeat'] = 'repeat'; +$GLOBALS['csstidy']['background_prop_default']['background-position'] = '0 0'; +$GLOBALS['csstidy']['background_prop_default']['background-attachment'] = 'scroll'; +$GLOBALS['csstidy']['background_prop_default']['background-clip'] = 'border'; +$GLOBALS['csstidy']['background_prop_default']['background-origin'] = 'padding'; +$GLOBALS['csstidy']['background_prop_default']['background-color'] = 'transparent'; + +/** + * Default values for the font properties + * + * @global array $GLOBALS['csstidy']['font_prop_default'] + * @see merge_fonts() + * @version 1.3 + */ +$GLOBALS['csstidy']['font_prop_default'] = array(); +$GLOBALS['csstidy']['font_prop_default']['font-style'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-variant'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-weight'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-size'] = ''; +$GLOBALS['csstidy']['font_prop_default']['line-height'] = ''; +$GLOBALS['csstidy']['font_prop_default']['font-family'] = ''; + +/** + * A list of non-W3C color names which get replaced by their hex-codes + * + * @global array $GLOBALS['csstidy']['replace_colors'] + * @see cut_color() + * @version 1.0 + */ +$GLOBALS['csstidy']['replace_colors'] = array(); +$GLOBALS['csstidy']['replace_colors']['aliceblue'] = '#f0f8ff'; +$GLOBALS['csstidy']['replace_colors']['antiquewhite'] = '#faebd7'; +$GLOBALS['csstidy']['replace_colors']['aquamarine'] = '#7fffd4'; +$GLOBALS['csstidy']['replace_colors']['azure'] = '#f0ffff'; +$GLOBALS['csstidy']['replace_colors']['beige'] = '#f5f5dc'; +$GLOBALS['csstidy']['replace_colors']['bisque'] = '#ffe4c4'; +$GLOBALS['csstidy']['replace_colors']['blanchedalmond'] = '#ffebcd'; +$GLOBALS['csstidy']['replace_colors']['blueviolet'] = '#8a2be2'; +$GLOBALS['csstidy']['replace_colors']['brown'] = '#a52a2a'; +$GLOBALS['csstidy']['replace_colors']['burlywood'] = '#deb887'; +$GLOBALS['csstidy']['replace_colors']['cadetblue'] = '#5f9ea0'; +$GLOBALS['csstidy']['replace_colors']['chartreuse'] = '#7fff00'; +$GLOBALS['csstidy']['replace_colors']['chocolate'] = '#d2691e'; +$GLOBALS['csstidy']['replace_colors']['coral'] = '#ff7f50'; +$GLOBALS['csstidy']['replace_colors']['cornflowerblue'] = '#6495ed'; +$GLOBALS['csstidy']['replace_colors']['cornsilk'] = '#fff8dc'; +$GLOBALS['csstidy']['replace_colors']['crimson'] = '#dc143c'; +$GLOBALS['csstidy']['replace_colors']['cyan'] = '#00ffff'; +$GLOBALS['csstidy']['replace_colors']['darkblue'] = '#00008b'; +$GLOBALS['csstidy']['replace_colors']['darkcyan'] = '#008b8b'; +$GLOBALS['csstidy']['replace_colors']['darkgoldenrod'] = '#b8860b'; +$GLOBALS['csstidy']['replace_colors']['darkgray'] = '#a9a9a9'; +$GLOBALS['csstidy']['replace_colors']['darkgreen'] = '#006400'; +$GLOBALS['csstidy']['replace_colors']['darkkhaki'] = '#bdb76b'; +$GLOBALS['csstidy']['replace_colors']['darkmagenta'] = '#8b008b'; +$GLOBALS['csstidy']['replace_colors']['darkolivegreen'] = '#556b2f'; +$GLOBALS['csstidy']['replace_colors']['darkorange'] = '#ff8c00'; +$GLOBALS['csstidy']['replace_colors']['darkorchid'] = '#9932cc'; +$GLOBALS['csstidy']['replace_colors']['darkred'] = '#8b0000'; +$GLOBALS['csstidy']['replace_colors']['darksalmon'] = '#e9967a'; +$GLOBALS['csstidy']['replace_colors']['darkseagreen'] = '#8fbc8f'; +$GLOBALS['csstidy']['replace_colors']['darkslateblue'] = '#483d8b'; +$GLOBALS['csstidy']['replace_colors']['darkslategray'] = '#2f4f4f'; +$GLOBALS['csstidy']['replace_colors']['darkturquoise'] = '#00ced1'; +$GLOBALS['csstidy']['replace_colors']['darkviolet'] = '#9400d3'; +$GLOBALS['csstidy']['replace_colors']['deeppink'] = '#ff1493'; +$GLOBALS['csstidy']['replace_colors']['deepskyblue'] = '#00bfff'; +$GLOBALS['csstidy']['replace_colors']['dimgray'] = '#696969'; +$GLOBALS['csstidy']['replace_colors']['dodgerblue'] = '#1e90ff'; +$GLOBALS['csstidy']['replace_colors']['feldspar'] = '#d19275'; +$GLOBALS['csstidy']['replace_colors']['firebrick'] = '#b22222'; +$GLOBALS['csstidy']['replace_colors']['floralwhite'] = '#fffaf0'; +$GLOBALS['csstidy']['replace_colors']['forestgreen'] = '#228b22'; +$GLOBALS['csstidy']['replace_colors']['gainsboro'] = '#dcdcdc'; +$GLOBALS['csstidy']['replace_colors']['ghostwhite'] = '#f8f8ff'; +$GLOBALS['csstidy']['replace_colors']['gold'] = '#ffd700'; +$GLOBALS['csstidy']['replace_colors']['goldenrod'] = '#daa520'; +$GLOBALS['csstidy']['replace_colors']['greenyellow'] = '#adff2f'; +$GLOBALS['csstidy']['replace_colors']['honeydew'] = '#f0fff0'; +$GLOBALS['csstidy']['replace_colors']['hotpink'] = '#ff69b4'; +$GLOBALS['csstidy']['replace_colors']['indianred'] = '#cd5c5c'; +$GLOBALS['csstidy']['replace_colors']['indigo'] = '#4b0082'; +$GLOBALS['csstidy']['replace_colors']['ivory'] = '#fffff0'; +$GLOBALS['csstidy']['replace_colors']['khaki'] = '#f0e68c'; +$GLOBALS['csstidy']['replace_colors']['lavender'] = '#e6e6fa'; +$GLOBALS['csstidy']['replace_colors']['lavenderblush'] = '#fff0f5'; +$GLOBALS['csstidy']['replace_colors']['lawngreen'] = '#7cfc00'; +$GLOBALS['csstidy']['replace_colors']['lemonchiffon'] = '#fffacd'; +$GLOBALS['csstidy']['replace_colors']['lightblue'] = '#add8e6'; +$GLOBALS['csstidy']['replace_colors']['lightcoral'] = '#f08080'; +$GLOBALS['csstidy']['replace_colors']['lightcyan'] = '#e0ffff'; +$GLOBALS['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#fafad2'; +$GLOBALS['csstidy']['replace_colors']['lightgrey'] = '#d3d3d3'; +$GLOBALS['csstidy']['replace_colors']['lightgreen'] = '#90ee90'; +$GLOBALS['csstidy']['replace_colors']['lightpink'] = '#ffb6c1'; +$GLOBALS['csstidy']['replace_colors']['lightsalmon'] = '#ffa07a'; +$GLOBALS['csstidy']['replace_colors']['lightseagreen'] = '#20b2aa'; +$GLOBALS['csstidy']['replace_colors']['lightskyblue'] = '#87cefa'; +$GLOBALS['csstidy']['replace_colors']['lightslateblue'] = '#8470ff'; +$GLOBALS['csstidy']['replace_colors']['lightslategray'] = '#778899'; +$GLOBALS['csstidy']['replace_colors']['lightsteelblue'] = '#b0c4de'; +$GLOBALS['csstidy']['replace_colors']['lightyellow'] = '#ffffe0'; +$GLOBALS['csstidy']['replace_colors']['limegreen'] = '#32cd32'; +$GLOBALS['csstidy']['replace_colors']['linen'] = '#faf0e6'; +$GLOBALS['csstidy']['replace_colors']['magenta'] = '#ff00ff'; +$GLOBALS['csstidy']['replace_colors']['mediumaquamarine'] = '#66cdaa'; +$GLOBALS['csstidy']['replace_colors']['mediumblue'] = '#0000cd'; +$GLOBALS['csstidy']['replace_colors']['mediumorchid'] = '#ba55d3'; +$GLOBALS['csstidy']['replace_colors']['mediumpurple'] = '#9370d8'; +$GLOBALS['csstidy']['replace_colors']['mediumseagreen'] = '#3cb371'; +$GLOBALS['csstidy']['replace_colors']['mediumslateblue'] = '#7b68ee'; +$GLOBALS['csstidy']['replace_colors']['mediumspringgreen'] = '#00fa9a'; +$GLOBALS['csstidy']['replace_colors']['mediumturquoise'] = '#48d1cc'; +$GLOBALS['csstidy']['replace_colors']['mediumvioletred'] = '#c71585'; +$GLOBALS['csstidy']['replace_colors']['midnightblue'] = '#191970'; +$GLOBALS['csstidy']['replace_colors']['mintcream'] = '#f5fffa'; +$GLOBALS['csstidy']['replace_colors']['mistyrose'] = '#ffe4e1'; +$GLOBALS['csstidy']['replace_colors']['moccasin'] = '#ffe4b5'; +$GLOBALS['csstidy']['replace_colors']['navajowhite'] = '#ffdead'; +$GLOBALS['csstidy']['replace_colors']['oldlace'] = '#fdf5e6'; +$GLOBALS['csstidy']['replace_colors']['olivedrab'] = '#6b8e23'; +$GLOBALS['csstidy']['replace_colors']['orangered'] = '#ff4500'; +$GLOBALS['csstidy']['replace_colors']['orchid'] = '#da70d6'; +$GLOBALS['csstidy']['replace_colors']['palegoldenrod'] = '#eee8aa'; +$GLOBALS['csstidy']['replace_colors']['palegreen'] = '#98fb98'; +$GLOBALS['csstidy']['replace_colors']['paleturquoise'] = '#afeeee'; +$GLOBALS['csstidy']['replace_colors']['palevioletred'] = '#d87093'; +$GLOBALS['csstidy']['replace_colors']['papayawhip'] = '#ffefd5'; +$GLOBALS['csstidy']['replace_colors']['peachpuff'] = '#ffdab9'; +$GLOBALS['csstidy']['replace_colors']['peru'] = '#cd853f'; +$GLOBALS['csstidy']['replace_colors']['pink'] = '#ffc0cb'; +$GLOBALS['csstidy']['replace_colors']['plum'] = '#dda0dd'; +$GLOBALS['csstidy']['replace_colors']['powderblue'] = '#b0e0e6'; +$GLOBALS['csstidy']['replace_colors']['rosybrown'] = '#bc8f8f'; +$GLOBALS['csstidy']['replace_colors']['royalblue'] = '#4169e1'; +$GLOBALS['csstidy']['replace_colors']['saddlebrown'] = '#8b4513'; +$GLOBALS['csstidy']['replace_colors']['salmon'] = '#fa8072'; +$GLOBALS['csstidy']['replace_colors']['sandybrown'] = '#f4a460'; +$GLOBALS['csstidy']['replace_colors']['seagreen'] = '#2e8b57'; +$GLOBALS['csstidy']['replace_colors']['seashell'] = '#fff5ee'; +$GLOBALS['csstidy']['replace_colors']['sienna'] = '#a0522d'; +$GLOBALS['csstidy']['replace_colors']['skyblue'] = '#87ceeb'; +$GLOBALS['csstidy']['replace_colors']['slateblue'] = '#6a5acd'; +$GLOBALS['csstidy']['replace_colors']['slategray'] = '#708090'; +$GLOBALS['csstidy']['replace_colors']['snow'] = '#fffafa'; +$GLOBALS['csstidy']['replace_colors']['springgreen'] = '#00ff7f'; +$GLOBALS['csstidy']['replace_colors']['steelblue'] = '#4682b4'; +$GLOBALS['csstidy']['replace_colors']['tan'] = '#d2b48c'; +$GLOBALS['csstidy']['replace_colors']['thistle'] = '#d8bfd8'; +$GLOBALS['csstidy']['replace_colors']['tomato'] = '#ff6347'; +$GLOBALS['csstidy']['replace_colors']['turquoise'] = '#40e0d0'; +$GLOBALS['csstidy']['replace_colors']['violet'] = '#ee82ee'; +$GLOBALS['csstidy']['replace_colors']['violetred'] = '#d02090'; +$GLOBALS['csstidy']['replace_colors']['wheat'] = '#f5deb3'; +$GLOBALS['csstidy']['replace_colors']['whitesmoke'] = '#f5f5f5'; +$GLOBALS['csstidy']['replace_colors']['yellowgreen'] = '#9acd32'; + +/** + * A list of all shorthand properties that are devided into four properties and/or have four subvalues + * + * @global array $GLOBALS['csstidy']['shorthands'] + * @todo Are there new ones in CSS3? + * @see dissolve_4value_shorthands() + * @see merge_4value_shorthands() + * @version 1.0 + */ +$GLOBALS['csstidy']['shorthands'] = array(); +$GLOBALS['csstidy']['shorthands']['border-color'] = array('border-top-color','border-right-color','border-bottom-color','border-left-color'); +$GLOBALS['csstidy']['shorthands']['border-style'] = array('border-top-style','border-right-style','border-bottom-style','border-left-style'); +$GLOBALS['csstidy']['shorthands']['border-width'] = array('border-top-width','border-right-width','border-bottom-width','border-left-width'); +$GLOBALS['csstidy']['shorthands']['margin'] = array('margin-top','margin-right','margin-bottom','margin-left'); +$GLOBALS['csstidy']['shorthands']['padding'] = array('padding-top','padding-right','padding-bottom','padding-left'); +$GLOBALS['csstidy']['shorthands']['-moz-border-radius'] = 0; + +/** + * All CSS Properties. Needed for csstidy::property_is_next() + * + * @global array $GLOBALS['csstidy']['all_properties'] + * @todo Add CSS3 properties + * @version 1.0 + * @see csstidy::property_is_next() + */ +$GLOBALS['csstidy']['all_properties']['alignment-adjust'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['alignment-baseline'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-delay'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-direction'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-duration'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-iteration-count'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-name'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-play-state'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['animation-timing-function'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['appearance'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['backface-visibility'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-clip'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-origin'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['background-size'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['baseline-shift'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['binding'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bleed'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bookmark-label'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bookmark-level'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bookmark-state'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bookmark-target'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom-left-radius'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom-right-radius'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image-outset'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image-repeat'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image-slice'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image-source'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-image-width'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-radius'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top-left-radius'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top-right-radius'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['box-decoration-break'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['box-shadow'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['box-sizing'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['break-after'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['break-before'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['break-inside'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['clip'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['color-profile'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-count'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-fill'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-gap'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-rule'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-rule-color'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-rule-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-rule-width'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-span'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['column-width'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['columns'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['crop'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['dominant-baseline'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-after-adjust'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-after-align'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-before-adjust'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-before-align'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-size'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['drop-initial-value'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['fit'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['fit-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['flex-align'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['flex-flow'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['flex-line-pack'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['flex-order'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['flex-pack'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['float-offset'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['grid-columns'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['grid-rows'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hanging-punctuation'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphenate-after'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphenate-before'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphenate-character'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphenate-lines'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphenate-resource'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['hyphens'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['icon'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['image-orientation'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['image-rendering'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['image-resolution'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['inline-box-align'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['left'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-break'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-stacking'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-stacking-ruby'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-stacking-shift'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['line-stacking-strategy'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marker-offset'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marks'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marquee-direction'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marquee-loop'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marquee-play-count'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marquee-speed'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['marquee-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['move-to'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['nav-down'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['nav-index'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['nav-left'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['nav-right'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['nav-up'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['opacity'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['outline-offset'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['overflow'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['overflow-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['overflow-wrap'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['overflow-x'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['overflow-y'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['page'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['page-break-after'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['page-break-before'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['page-policy'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['perspective'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['perspective-origin'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['phonemes'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['position'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['presentation-level'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['punctuation-trim'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rendering-intent'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['resize'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rest'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rest-after'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rest-before'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rotation'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['rotation-point'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['ruby-align'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['ruby-overhang'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['ruby-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['ruby-span'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['size'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['src'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['string-set'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['tab-size'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['target'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['target-name'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['target-new'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['target-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-align-last'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-decoration-color'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-decoration-line'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-decoration-skip'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-decoration-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-emphasis'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-emphasis-color'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-emphasis-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-emphasis-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-height'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-justify'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-outline'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-shadow'] = 'CSS2.0,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-space-collapse'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-underline-position'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['text-wrap'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['top'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transform'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transform-origin'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transform-style'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transition'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transition-delay'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transition-duration'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transition-property'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['transition-timing-function'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['visibility'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-balance'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-duration'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-pitch'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-pitch-range'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-rate'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-stress'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['voice-volume'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['word-break'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0'; +$GLOBALS['csstidy']['all_properties']['word-wrap'] = 'CSS3.0'; +$GLOBALS['csstidy']['all_properties']['z-index'] = 'CSS2.0,CSS2.1,CSS3.0'; + +/** + * An array containing all properties that can accept a quoted string as a value. + * + * @global array $GLOBALS['csstidy']['quoted_string_properties'] + */ +$GLOBALS['csstidy']['quoted_string_properties'] = array('content', 'font', 'font-family', 'quotes'); + +/** + * An array containing all properties that can be defined multiple times without being overwritten. + * All unit values are included so that units like rem can be supported with fallbacks to px or em. + * + * @global array $GLOBALS['csstidy']['quoted_string_properties'] + */ +$GLOBALS['csstidy']['multiple_properties'] = array_merge( $GLOBALS['csstidy']['unit_values'], array('background', 'background-image', 'transition') ); + +/** + * An array containing all predefined templates. + * + * @global array $GLOBALS['csstidy']['predefined_templates'] + * @version 1.0 + * @see csstidy::load_template() + */ +$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="at">'; //string before @rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>'."\n"; //bracket after @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="selector">'; //string before selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>'."\n"; //bracket after selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="property">'; //string before property +$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="value">'; //string after property+before value +$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="format">;</span>'."\n"; //string after value +$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="format">}</span>'; //closing bracket - selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n\n"; //space between blocks {...} +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n".'<span class="format">}</span>'. "\n\n"; //closing bracket @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //indent in @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="comment">'; // before comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span>'."\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n"; // after last line @-rule + +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="at">'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span> <span class="format">{</span>'."\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="selector">'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">{</span>'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="property">'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="value">'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">;</span>'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="format">}</span>'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n". '<span class="format">}'."\n".'</span>'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="comment">'; // before comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span>'; // after comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; + +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="at">'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="selector">'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="property">'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="value">'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">;</span>'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="comment">'; // before comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span>'; // after comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; + +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="at">'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span> <span class="format">{</span>'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="selector">'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>'."\n".'<span class="format">{</span>'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' <span class="property">'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="value">'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="format">;</span>'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="format">}</span>'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n".'<span class="format">}</span>'."\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' '; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="comment">'; // before comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>'."\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n"; + +require dirname( __FILE__ ) . '/data-wp.inc.php'; + +?> diff --git a/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php b/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php new file mode 100644 index 00000000..9e6b24b0 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php @@ -0,0 +1,311 @@ +<?php + +/** + * Localization of CSS Optimiser Interface of CSSTidy + * + * Copyright 2005, 2006, 2007 Florian Schmitz + * + * This file is part of CSSTidy. + * + * CSSTidy is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * CSSTidy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + */ + + +if(isset($_GET['lang'])) { + $l = $_GET['lang']; +} +else if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $l = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + $l = strtolower(substr($l, 0, 2)); +} +else { + $l = ''; +} + +$l = (in_array($l, array('de', 'fr', 'zh'))) ? $l : 'en'; + +// note 5 in all but French, and 40 in all are orphaned + +$lang = array(); +$lang['en'][0] = 'CSS Formatter and Optimiser/Optimizer (based on CSSTidy '; +$lang['en'][1] = 'CSS Formatter and Optimiser'; +$lang['en'][2] = '(based on'; +$lang['en'][3] = '(plaintext)'; +$lang['en'][4] = 'Important Note:'; +$lang['en'][6] = 'Your code should be well-formed. This is <strong>not a validator</strong> which points out errors in your CSS code. To make sure that your code is valid, use the <a href="http://jigsaw.w3.org/css-validator/">W3C Validator</a>.'; +$lang['en'][7] = 'all comments are removed'; +$lang['en'][8] = 'CSS Input:'; +$lang['en'][9] = 'CSS-Code:'; +$lang['en'][10] = 'CSS from URL:'; +$lang['en'][11] = 'Code Layout:'; +$lang['en'][12] = 'Compression (code layout):'; +$lang['en'][13] = 'Highest (no readability, smallest size)'; +$lang['en'][14] = 'High (moderate readability, smaller size)'; +$lang['en'][15] = 'Standard (balance between readability and size)'; +$lang['en'][16] = 'Low (higher readability)'; +$lang['en'][17] = 'Custom (enter below)'; +$lang['en'][18] = 'Custom <a href="http://csstidy.sourceforge.net/templates.php">template</a>'; +$lang['en'][19] = 'Options'; +$lang['en'][20] = 'Sort Selectors (caution)'; +$lang['en'][21] = 'Sort Properties'; +$lang['en'][22] = 'Regroup selectors'; +$lang['en'][23] = 'Optimise shorthands'; +$lang['en'][24] = 'Compress colors'; +$lang['en'][25] = 'Lowercase selectors'; +$lang['en'][26] = 'Case for properties:'; +$lang['en'][27] = 'Lowercase'; +$lang['en'][28] = 'No or invalid CSS input or wrong URL!'; +$lang['en'][29] = 'Uppercase'; +$lang['en'][30] = 'lowercase elementnames needed for XHTML'; +$lang['en'][31] = 'Remove unnecessary backslashes'; +$lang['en'][32] = 'convert !important-hack'; +$lang['en'][33] = 'Output as file'; +$lang['en'][34] = 'Bigger compression because of smaller newlines (copy & paste doesn\'t work)'; +$lang['en'][35] = 'Process CSS'; +$lang['en'][36] = 'Compression Ratio'; +$lang['en'][37] = 'Input'; +$lang['en'][38] = 'Output'; +$lang['en'][39] = 'Language'; +$lang['en'][41] = 'Attention: This may change the behaviour of your CSS Code!'; +$lang['en'][42] = 'Remove last ;'; +$lang['en'][43] = 'Discard invalid properties'; +$lang['en'][44] = 'Only safe optimisations'; +$lang['en'][45] = 'Compress font-weight'; +$lang['en'][46] = 'Save comments'; +$lang['en'][47] = 'Do not change anything'; +$lang['en'][48] = 'Only seperate selectors (split at ,)'; +$lang['en'][49] = 'Merge selectors with the same properties (fast)'; +$lang['en'][50] = 'Merge selectors intelligently (slow)'; +$lang['en'][51] = 'Preserve CSS'; +$lang['en'][52] = 'Save comments, hacks, etc. Most optimisations can *not* be applied if this is enabled.'; +$lang['en'][53] = 'None'; +$lang['en'][54] = 'Don\'t optimise'; +$lang['en'][55] = 'Safe optimisations'; +$lang['en'][56] = 'All optimisations'; +$lang['en'][57] = 'Add timestamp'; +$lang['en'][58] = 'Copy to clipboard'; +$lang['en'][59] = 'Back to top'; +$lang['en'][60] = 'Your browser doesn\'t support copy to clipboard.'; +$lang['en'][61] = 'For bugs and suggestions feel free to'; +$lang['en'][62] = 'contact me'; +$lang['en'][63] = 'Output CSS code as complete HTML document'; +$lang['en'][64] = 'Code'; +$lang['en'][65] = 'CSS to style CSS output'; +$lang['en'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.'; + + +$lang['de'][0] = 'CSS Formatierer und Optimierer (basierend auf CSSTidy '; +$lang['de'][1] = 'CSS Formatierer und Optimierer'; +$lang['de'][2] = '(basierend auf'; +$lang['de'][3] = '(Textversion)'; +$lang['de'][4] = 'Wichtiger Hinweis:'; +$lang['de'][6] = 'Der CSS Code sollte wohlgeformt sein. Der CSS Code wird <strong>nicht auf Gültigkeit überprüft</strong>. Um sicherzugehen dass dein Code valide ist, benutze den <a href="http://jigsaw.w3.org/css-validator/">W3C Validierungsservice</a>.'; +$lang['de'][7] = 'alle Kommentare werden entfernt'; +$lang['de'][8] = 'CSS Eingabe:'; +$lang['de'][9] = 'CSS-Code:'; +$lang['de'][10] = 'CSS von URL:'; +$lang['de'][11] = 'Code Layout:'; +$lang['de'][12] = 'Komprimierung (Code Layout):'; +$lang['de'][13] = 'Höchste (keine Lesbarkeit, niedrigste Größe)'; +$lang['de'][14] = 'Hoch (mittelmäßige Lesbarkeit, geringe Größe)'; +$lang['de'][15] = 'Standard (Kompromiss zwischen Lesbarkeit und Größe)'; +$lang['de'][16] = 'Niedrig (höhere Lesbarkeit)'; +$lang['de'][17] = 'Benutzerdefiniert (unten eingeben)'; +$lang['de'][18] = 'Benutzerdefinierte <a href="http://csstidy.sourceforge.net/templates.php">Vorlage</a>'; +$lang['de'][19] = 'Optionen'; +$lang['de'][20] = 'Selektoren sortieren (Vorsicht)'; +$lang['de'][21] = 'Eigenschaften sortieren'; +$lang['de'][22] = 'Selektoren umgruppieren'; +$lang['de'][23] = 'Shorthands optimieren'; +$lang['de'][24] = 'Farben komprimieren'; +$lang['de'][25] = 'Selektoren in Kleinbuchstaben'; +$lang['de'][26] = 'Groß-/Kleinschreibung für Eigenschaften'; +$lang['de'][27] = 'Kleinbuchstaben'; +$lang['de'][28] = 'Keine oder ungültige CSS Eingabe oder falsche URL!'; +$lang['de'][29] = 'Großbuchstaben'; +$lang['de'][30] = 'kleingeschriebene Elementnamen benötigt für XHTML'; +$lang['de'][31] = 'Unnötige Backslashes entfernen'; +$lang['de'][32] = '!important-Hack konvertieren'; +$lang['de'][33] = 'Als Datei ausgeben'; +$lang['de'][34] = 'Größere Komprimierung augrund von kleineren Neuezeile-Zeichen'; +$lang['de'][35] = 'CSS verarbeiten'; +$lang['de'][36] = 'Komprimierungsrate'; +$lang['de'][37] = 'Eingabe'; +$lang['de'][38] = 'Ausgabe'; +$lang['de'][39] = 'Sprache'; +$lang['de'][41] = 'Achtung: Dies könnte das Verhalten ihres CSS-Codes verändern!'; +$lang['de'][42] = 'Letztes ; entfernen'; +$lang['de'][43] = 'Ungültige Eigenschaften entfernen'; +$lang['de'][44] = 'Nur sichere Optimierungen'; +$lang['de'][45] = 'font-weight komprimieren'; +$lang['de'][46] = 'Kommentare beibehalten'; +$lang['de'][47] = 'Nichts ändern'; +$lang['de'][48] = 'Selektoren nur trennen (am Komma)'; +$lang['de'][49] = 'Selektoren mit gleichen Eigenschaften zusammenfassen (schnell)'; +$lang['de'][50] = 'Selektoren intelligent zusammenfassen (langsam!)'; +$lang['de'][51] = 'CSS erhalten'; +$lang['de'][52] = 'Kommentare, Hacks, etc. speichern. Viele Optimierungen sind dann aber nicht mehr möglich.'; +$lang['de'][53] = 'Keine'; +$lang['de'][54] = 'Nicht optimieren'; +$lang['de'][55] = 'Sichere Optimierungen'; +$lang['de'][56] = 'Alle Optimierungen'; +$lang['de'][57] = 'Zeitstempel hinzufügen'; +$lang['de'][58] = 'Copy to clipboard'; +$lang['de'][59] = 'Back to top'; +$lang['de'][60] = 'Your browser doesn\'t support copy to clipboard.'; +$lang['de'][61] = 'For bugs and suggestions feel free to'; +$lang['de'][62] = 'contact me'; +$lang['de'][63] = 'Output CSS code as complete HTML document'; +$lang['de'][64] = 'Code'; +$lang['de'][65] = 'CSS to style CSS output'; +$lang['de'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.'; + + +$lang['fr'][0] = 'CSS Formatteur et Optimiseur (basé sur CSSTidy '; +$lang['fr'][1] = 'CSS Formatteur et Optimiseur'; +$lang['fr'][2] = '(basé sur '; +$lang['fr'][3] = '(Version texte)'; +$lang['fr'][4] = 'Note Importante :'; +$lang['fr'][6] = 'Votre code doit être valide. Ce n’est <strong>pas un validateur</strong> qui signale les erreurs dans votre code CSS. Pour être sûr que votre code est correct, utilisez le validateur : <a href="http://jigsaw.w3.org/css-validator/">W3C Validator</a>.'; +$lang['fr'][7] = 'tous les commentaires sont enlevés'; +$lang['fr'][8] = 'Champ CSS :'; +$lang['fr'][9] = 'Code CSS :'; +$lang['fr'][10] = 'CSS en provenance d’une URL :<br />'; +$lang['fr'][11] = 'Mise en page du code :'; +$lang['fr'][12] = 'Compression (mise en page du code) :'; +$lang['fr'][13] = 'La plus élevée (aucune lisibilité, taille minimale)'; +$lang['fr'][14] = 'Élevée (lisibilité modérée, petite taille)'; +$lang['fr'][15] = 'Normale (équilibre entre lisibilité et taille)'; +$lang['fr'][16] = 'Faible (lisibilité élevée)'; +$lang['fr'][17] = 'Sur mesure (entrer ci-dessous)'; +$lang['fr'][18] = '<a href="http://csstidy.sourceforge.net/templates.php">Gabarit</a> sur mesure'; +$lang['fr'][19] = 'Options'; +$lang['fr'][20] = 'Trier les sélecteurs (attention)'; +$lang['fr'][21] = 'Trier les propriétés'; +$lang['fr'][22] = 'Regrouper les sélecteurs'; +$lang['fr'][23] = 'Propriétés raccourcies'; +$lang['fr'][24] = 'Compresser les couleurs'; +$lang['fr'][25] = 'Sélecteurs en minuscules'; +$lang['fr'][26] = 'Case pour les propriétés :'; +$lang['fr'][27] = 'Minuscule'; +$lang['fr'][28] = 'CSS non valide ou URL incorrecte !'; +$lang['fr'][29] = 'Majuscule'; +$lang['fr'][30] = 'les noms des éléments en minuscules (indispensables pour XHTML)'; +$lang['fr'][31] = 'enlever les antislashs inutiles'; +$lang['fr'][32] = 'convertir !important-hack'; +$lang['fr'][33] = 'Sauver en tant que fichier'; +$lang['fr'][34] = 'Meilleure compression grâce aux caractères de saut de ligne plus petits (copier & coller ne marche pas)'; +$lang['fr'][35] = 'Compresser la CSS'; +$lang['fr'][36] = 'Facteur de Compression'; +$lang['fr'][37] = 'Entrée'; +$lang['fr'][38] = 'Sortie'; +$lang['fr'][39] = 'Langue'; +$lang['fr'][41] = 'Attention : ceci peut changer le comportement de votre code CSS !'; +$lang['fr'][42] = 'Enlever le dernier ;'; +$lang['fr'][43] = 'Supprimer les propriétés non valide'; +$lang['fr'][44] = 'Seulement les optimisations sûres'; +$lang['fr'][45] = 'Compresser font-weight'; +$lang['fr'][46] = 'Sauvegarder les commentaires '; +$lang['fr'][47] = 'Ne rien changer'; +$lang['fr'][48] = 'Sépare les sélecteurs (sépare au niveau de ,)'; +$lang['fr'][49] = 'Fusionne les sélecteurs avec les mêmes propriétés (rapide)'; +$lang['fr'][50] = 'Fusionne les sélecteurs intelligemment (lent)'; +$lang['fr'][51] = 'Préserver la CSS'; +$lang['fr'][52] = 'Sauvegarder les commentaires, hacks, etc. La plupart des optimisations ne peuvent *pas* être appliquées si cela est activé.'; +$lang['fr'][53] = 'Aucun'; +$lang['fr'][54] = 'Ne pas optimiser'; +$lang['fr'][55] = 'Optimisations sûres'; +$lang['fr'][56] = 'Toutes les optimisations'; +$lang['fr'][57] = 'Ajouter un timestamp'; +$lang['fr'][58] = 'Copier dans le presse-papiers'; +$lang['fr'][59] = 'Retour en haut'; +$lang['fr'][60] = 'Votre navigateur ne suporte pas la copie vers le presse-papiers.'; +$lang['fr'][61] = 'Pour signaler des bugs ou pour des suggestions,'; +$lang['fr'][62] = 'contactez-moi'; +$lang['fr'][63] = 'Sauver le code CSS comme document complet HTML'; +$lang['fr'][64] = 'Code'; +$lang['fr'][65] = 'CSS pour colorier la sortie CSS'; +$lang['fr'][66] = 'Vous devez aller dans about:config dans votre barre d’adresse, selectionner \'signed.applets.codebase_principal_support\' dans le champ Filtre et attribuez-lui la valeur \'true\' pour utiliser cette fonctionnalité; toutefois, soyez conscient que cela augmente les risques de sécurité.'; + + +$lang['zh'][0] = 'CSS整形與最佳化工具(使用 CSSTidy '; +$lang['zh'][1] = 'CSS整形與最佳化工具'; +$lang['zh'][2] = '(使用'; +$lang['zh'][3] = '(純文字)'; +$lang['zh'][4] = '重要事項:'; +$lang['zh'][6] = '你的原始碼必須是良構的(well-formed). 這個工具<strong>沒有內建驗證器(validator)</strong>. 驗證器能夠指出你CSS原始碼裡的錯誤. 請使用 <a href="http://jigsaw.w3.org/css-validator/">W3C 驗證器</a>, 確保你的原始碼合乎規範.'; +$lang['zh'][7] = '所有註解都移除了'; +$lang['zh'][8] = 'CSS 輸入:'; +$lang['zh'][9] = 'CSS 原始碼:'; +$lang['zh'][10] = 'CSS 檔案網址(URL):'; +$lang['zh'][11] = '原始碼規劃:'; +$lang['zh'][12] = '壓縮程度(原始碼規劃):'; +$lang['zh'][13] = '最高 (沒有辦法讀, 檔案最小)'; +$lang['zh'][14] = '高 (適度的可讀性, 檔案小)'; +$lang['zh'][15] = '標準 (兼顧可讀性與檔案大小)'; +$lang['zh'][16] = '低 (注重可讀性)'; +$lang['zh'][17] = '自訂 (在下方設定)'; +$lang['zh'][18] = '自訂<a href="http://csstidy.sourceforge.net/templates.php">樣板</a>'; +$lang['zh'][19] = '選項'; +$lang['zh'][20] = '整理選擇符(請謹慎使用)'; +$lang['zh'][21] = '整理屬性'; +$lang['zh'][22] = '重組選擇符'; +$lang['zh'][23] = '速記法(shorthand)最佳化'; +$lang['zh'][24] = '壓縮色彩語法'; +$lang['zh'][25] = '改用小寫選擇符'; +$lang['zh'][26] = '屬性的字形:'; +$lang['zh'][27] = '小寫'; +$lang['zh'][28] = '沒有輸入CSS, 語法不符合規定, 或是網址錯誤!'; +$lang['zh'][29] = '大寫'; +$lang['zh'][30] = 'XHTML必須使用小寫的元素名稱'; +$lang['zh'][31] = '移除不必要的反斜線'; +$lang['zh'][32] = '轉換 !important-hack'; +$lang['zh'][33] = '輸出成檔案形式'; +$lang['zh'][34] = '由於比較少換行字元, 會有更大的壓縮比率(複製&貼上沒有用)'; +$lang['zh'][35] = '執行'; +$lang['zh'][36] = '壓縮比率'; +$lang['zh'][37] = '輸入'; +$lang['zh'][38] = '輸出'; +$lang['zh'][39] = '語言'; +$lang['zh'][41] = '注意: 這或許會變更你CSS原始碼的行為!'; +$lang['zh'][42] = '除去最後一個分號'; +$lang['zh'][43] = '拋棄不符合規定的屬性'; +$lang['zh'][44] = '只安全地最佳化'; +$lang['zh'][45] = '壓縮 font-weight'; +$lang['zh'][46] = '保留註解'; +$lang['zh'][47] = '什麼都不要改'; +$lang['zh'][48] = '只分開原本用逗號分隔的選擇符'; +$lang['zh'][49] = '合併有相同屬性的選擇符(快速)'; +$lang['zh'][50] = '聰明地合併選擇符(慢速)'; +$lang['zh'][51] = '保護CSS'; +$lang['zh'][52] = '保留註解與 hack 等等. 如果啟用這個選項, 大多數的最佳化程序都不會執行.'; +$lang['zh'][53] = '不改變'; +$lang['zh'][54] = '不做最佳化'; +$lang['zh'][55] = '安全地最佳化'; +$lang['zh'][56] = '全部最佳化'; +$lang['zh'][57] = '加上時間戳記'; +$lang['zh'][58] = '复制到剪贴板'; +$lang['zh'][59] = '回到页面上方'; +$lang['zh'][60] = '你的浏览器不支持复制到剪贴板。'; +$lang['zh'][61] = '如果程序有错误或你有建议,欢迎'; +$lang['zh'][62] = '和我联系'; +$lang['zh'][63] = 'Output CSS code as complete HTML document'; +$lang['zh'][64] = '代码'; +$lang['zh'][65] = 'CSS to style CSS output'; +$lang['zh'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.'; diff --git a/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl b/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl new file mode 100644 index 00000000..9499e839 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl @@ -0,0 +1,10 @@ +| { +|| { +| | |; +|}| + +| +} + +| || +| diff --git a/plugins/jetpack/modules/custom-css/custom-css.php b/plugins/jetpack/modules/custom-css/custom-css.php new file mode 100644 index 00000000..9bc0c211 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css.php @@ -0,0 +1,1468 @@ +<?php + +class Jetpack_Custom_CSS { + static function init() { + add_action( 'switch_theme', array( __CLASS__, 'reset' ) ); + add_action( 'wp_restore_post_revision', array( __CLASS__, 'restore_revision' ), 10, 2 ); + + // Save revisions for posts of type safecss. + add_filter( 'revision_redirect', array( __CLASS__, 'revision_redirect' ) ); + + // Override the edit link, the default link causes a redirect loop + add_filter( 'get_edit_post_link', array( __CLASS__, 'revision_post_link' ), 10, 3 ); + + if ( ! is_admin() ) + add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) ); + + define( 'SAFECSS_USE_ACE', ! jetpack_is_mobile() && ! Jetpack_User_Agent_Info::is_ipad() && apply_filters( 'safecss_use_ace', true ) ); + + // Register safecss as a custom post_type + // Explicit capability definitions are largely unnecessary because the posts are manipulated in code via an options page, managing CSS revisions does check the capabilities, so let's ensure that the proper caps are checked. + register_post_type( 'safecss', array( + // These are the defaults + // 'exclude_from_search' => true, + // 'public' => false, + // 'publicly_queryable' => false, + // 'show_ui' => false, + 'supports' => array( 'revisions' ), + 'label' => 'Custom CSS', + 'can_export' => false, + 'rewrite' => false, + 'capabilities' => array( + 'edit_post' => 'edit_theme_options', + 'read_post' => 'read', + 'delete_post' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'publish_posts' => 'edit_theme_options', + 'read_private_posts' => 'read' + ) + ) ); + + // Short-circuit WP if this is a CSS stylesheet request + if ( isset( $_GET['custom-css'] ) ) { + header( 'Content-Type: text/css', true, 200 ); + header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000) . ' GMT' ); // 1 year + Jetpack_Custom_CSS::print_css(); + exit; + } + + if ( isset( $_GET['page'] ) && 'editcss' == $_GET['page'] && is_admin() ) { + // Do migration routine if necessary + Jetpack_Custom_CSS::upgrade(); + + do_action( 'safecss_migrate_post' ); + } + + add_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 ); + + if ( !current_user_can( 'switch_themes' ) && !is_super_admin() ) + return; + + add_action( 'admin_menu', array( 'Jetpack_Custom_CSS', 'menu' ) ); + + if ( isset( $_POST['safecss'] ) && false == strstr( $_SERVER[ 'REQUEST_URI' ], 'options.php' ) ) { + check_admin_referer( 'safecss' ); + + $save_result = self::save( array( + 'css' => $_POST['safecss'], + 'is_preview' => isset( $_POST['action'] ) && $_POST['action'] == 'preview', + 'preprocessor' => isset( $_POST['custom_css_preprocessor'] ) ? $_POST['custom_css_preprocessor'] : '', + 'add_to_existing' => isset( $_POST['add_to_existing'] ) ? $_POST['add_to_existing'] == 'true' : true, + 'content_width' => isset( $_POST['custom_content_width'] ) ? $_POST['custom_content_width'] : false, + ) ); + + if ( $_POST['action'] == 'preview' ) { + wp_safe_redirect( add_query_arg( 'csspreview', 'true', get_option( 'home' ) ) ); + exit; + } + + if ( $save_result ) + add_action( 'admin_notices', array( 'Jetpack_Custom_CSS', 'saved_message' ) ); + } + + // Modify all internal links so that preview state persists + if ( Jetpack_Custom_CSS::is_preview() ) + ob_start( array( 'Jetpack_Custom_CSS', 'buffer' ) ); + } + + /** + * Save new custom CSS. This should be the entry point for any third-party code using Jetpack_Custom_CSS + * to save CSS. + * + * @param array $args Array of arguments: + * string $css The CSS (or LESS or Sass) + * bool $is_preview Whether this CSS is preview or published + * bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it. + * int $content_width A custom $content_width to go along with this CSS. + * @return int The post ID of the saved Custom CSS post. + */ + public static function save( $args = array() ) { + $defaults = array( + 'css' => '', + 'is_preview' => false, + 'preprocessor' => '', + 'add_to_existing' => true, + 'content_width' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + + // Remove wp_filter_post_kses, this causes CSS escaping issues + remove_filter( 'content_save_pre', 'wp_filter_post_kses' ); + remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' ); + remove_all_filters( 'content_save_pre' ); + + do_action( 'safecss_save_pre', $args ); + + $warnings = array(); + + safecss_class(); + $csstidy = new csstidy(); + $csstidy->optimise = new safecss( $csstidy ); + + $csstidy->set_cfg( 'remove_bslash', false ); + $csstidy->set_cfg( 'compress_colors', false ); + $csstidy->set_cfg( 'compress_font-weight', false ); + $csstidy->set_cfg( 'optimise_shorthands', 0 ); + $csstidy->set_cfg( 'remove_last_;', false ); + $csstidy->set_cfg( 'case_properties', false ); + $csstidy->set_cfg( 'discard_invalid_properties', true ); + $csstidy->set_cfg( 'css_level', 'CSS3.0' ); + $csstidy->set_cfg( 'preserve_css', true ); + $csstidy->set_cfg( 'template', dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' ); + + $css = $orig = stripslashes( $args['css'] ); + + $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $prev = $css ); + + if ( $css != $prev ) + $warnings[] = 'preg_replace found stuff'; + + // Some people put weird stuff in their CSS, KSES tends to be greedy + $css = str_replace( '<=', '<=', $css ); + // Why KSES instead of strip_tags? Who knows? + $css = wp_kses_split( $prev = $css, array(), array() ); + $css = str_replace( '>', '>', $css ); // kses replaces lone '>' with > + // Why both KSES and strip_tags? Because we just added some '>'. + $css = strip_tags( $css ); + + if ( $css != $prev ) + $warnings[] = 'kses found stuff'; + + // if we're not using a preprocessor + if ( ! $args['preprocessor'] ) { + do_action( 'safecss_parse_pre', $csstidy, $css, $args ); + + $csstidy->parse( $css ); + + do_action( 'safecss_parse_post', $csstidy, $warnings, $args ); + + $css = $csstidy->print->plain(); + } + + if ( $args['content_width'] && intval( $args['content_width']) > 0 && ( ! isset( $GLOBALS['content_width'] ) || $args['content_width'] != $GLOBALS['content_width'] ) ) + $custom_content_width = intval( $args['content_width'] ); + else + $custom_content_width = false; + + if ( $args['add_to_existing'] ) + $add_to_existing = 'yes'; + else + $add_to_existing = 'no'; + + if ( $args['is_preview'] || Jetpack_Custom_CSS::is_freetrial() ) { + // Save the CSS + $safecss_revision_id = Jetpack_Custom_CSS::save_revision( $css, true, $args['preprocessor'] ); + + // Cache Buster + update_option( 'safecss_preview_rev', intval( get_option( 'safecss_preview_rev' ) ) + 1); + + update_metadata( 'post', $safecss_revision_id, 'custom_css_add', $add_to_existing ); + update_metadata( 'post', $safecss_revision_id, 'content_width', $custom_content_width ); + update_metadata( 'post', $safecss_revision_id, 'custom_css_preprocessor', $args['preprocessor'] ); + + if ( $args['is_preview'] ) { + return $safecss_revision_id; + } + + // Freetrial only. + do_action( 'safecss_save_preview_post' ); + } + + // Save the CSS + $safecss_post_id = Jetpack_Custom_CSS::save_revision( $css, false, $args['preprocessor'] ); + + $safecss_post_revision = Jetpack_Custom_CSS::get_current_revision(); + + update_option( 'safecss_rev', intval( get_option( 'safecss_rev' ) ) + 1 ); + + update_post_meta( $safecss_post_id, 'custom_css_add', $add_to_existing ); + update_post_meta( $safecss_post_id, 'content_width', $custom_content_width ); + update_post_meta( $safecss_post_id, 'custom_css_preprocessor', $args['preprocessor'] ); + update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_add', $add_to_existing ); + update_metadata( 'post', $safecss_post_revision['ID'], 'content_width', $custom_content_width ); + update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_preprocessor', $args['preprocessor'] ); + + return $safecss_post_id; + } + + /** + * Get the published custom CSS post. + * + * @return array + */ + static function get_post() { + $custom_css_post_id = Jetpack_Custom_CSS::post_id(); + + if ( $custom_css_post_id ) + return get_post( $custom_css_post_id, ARRAY_A ); + + return array(); + } + + /** + * Get the post ID of the published custom CSS post. + * + * @return int|bool The post ID if it exists; false otherwise. + */ + static function post_id() { + $custom_css_post_id = wp_cache_get( 'custom_css_post_id' ); + + if ( false === $custom_css_post_id ) { + $custom_css_post = array_shift( get_posts( array( + 'posts_per_page' => 1, + 'post_type' => 'safecss', + 'post_status' => 'publish', + 'orderby' => 'date', + 'order' => 'DESC' + ) ) ); + + if ( $custom_css_post ) + $custom_css_post_id = $custom_css_post->ID; + else + $custom_css_post_id = 0; + + // Save post_id=0 to note that no safecss post exists. + wp_cache_set( 'custom_css_post_id', $custom_css_post_id ); + } + + if ( ! $custom_css_post_id ) + return false; + + return $custom_css_post_id; + } + + /** + * Get the current revision of the original safecss record + * + * @return object + */ + static function get_current_revision() { + $safecss_post = Jetpack_Custom_CSS::get_post(); + + if ( empty( $safecss_post ) ) { + return false; + } + + $revisions = wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC' ) ); + + // Empty array if no revisions exist + if ( empty( $revisions ) ) { + // Return original post + return $safecss_post; + } else { + // Return the first entry in $revisions, this will be the current revision + $current_revision = get_object_vars( array_shift( $revisions ) ); + return $current_revision; + } + } + + /** + * Save new revision of CSS + * Checks to see if content was modified before really saving + * + * @param string $css + * @param bool $is_preview + * @return bool|int If nothing was saved, returns false. If a post + * or revision was saved, returns the post ID. + */ + static function save_revision( $css, $is_preview = false, $preprocessor = '' ) { + $safecss_post = Jetpack_Custom_CSS::get_post(); + + $compressed_css = Jetpack_Custom_CSS::minify( $css, $preprocessor ); + + // If null, there was no original safecss record, so create one + if ( null == $safecss_post ) { + if ( ! $css ) + return false; + + $post = array(); + $post['post_content'] = $css; + $post['post_title'] = 'safecss'; + $post['post_status'] = 'publish'; + $post['post_type'] = 'safecss'; + $post['post_content_filtered'] = $compressed_css; + + // Set excerpt to current theme, for display in revisions list + if ( function_exists( 'wp_get_theme' ) ) { + $current_theme = wp_get_theme(); + $post['post_excerpt'] = $current_theme->Name; + } + else { + $post['post_excerpt'] = get_current_theme(); + } + + // Insert the CSS into wp_posts + $post_id = wp_insert_post( $post ); + wp_cache_set( 'custom_css_post_id', $post_id ); + return $post_id; + } + + // Update CSS in post array with new value passed to this function + $safecss_post['post_content'] = $css; + $safecss_post['post_content_filtered'] = $compressed_css; + + // Set excerpt to current theme, for display in revisions list + if ( function_exists( 'wp_get_theme' ) ) { + $current_theme = wp_get_theme(); + $safecss_post['post_excerpt'] = $current_theme->Name; + } + else { + $safecss_post['post_excerpt'] = get_current_theme(); + } + + // Don't carry over last revision's timestamps, otherwise revisions all have matching timestamps + unset( $safecss_post['post_date'] ); + unset( $safecss_post['post_date_gmt'] ); + unset( $safecss_post['post_modified'] ); + unset( $safecss_post['post_modified_gmt'] ); + + // Do not update post if we are only saving a preview + if ( false === $is_preview ) { + $post_id = wp_update_post( $safecss_post ); + wp_cache_set( 'custom_css_post_id', $post_id ); + return $post_id; + } + else if ( ! defined( 'DOING_MIGRATE' ) ) { + return _wp_put_post_revision( $safecss_post ); + } + } + + static function skip_stylesheet() { + if ( Jetpack_Custom_CSS::is_customizer_preview() ) { + return false; + } + else { + if ( Jetpack_Custom_CSS::is_preview() ) { + $safecss_post = Jetpack_Custom_CSS::get_current_revision(); + + return (bool) ( get_option( 'safecss_preview_add' ) == 'no' || get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) == 'no' ); + } + else { + $custom_css_post_id = Jetpack_Custom_CSS::post_id(); + + return (bool) ( get_option( 'safecss_add' ) == 'no' || ( $custom_css_post_id && get_post_meta( $custom_css_post_id, 'custom_css_add', true ) == 'no' ) ); + } + } + } + + static function is_preview() { + return isset( $_GET['csspreview'] ) && $_GET['csspreview'] === 'true'; + } + + /* + * False when the site has the Custom Design upgrade. + * Used only on WordPress.com. + */ + static function is_freetrial() { + return apply_filters( 'safecss_is_freetrial', false ); + } + + static function get_css( $compressed = false ) { + $default_css = apply_filters( 'safecss_get_css_error', false ); + + if ( $default_css !== false ) + return $default_css; + + $option = ( Jetpack_Custom_CSS::is_preview() || Jetpack_Custom_CSS::is_freetrial() ) ? 'safecss_preview' : 'safecss'; + + if ( 'safecss' == $option ) { + if ( get_option( 'safecss_revision_migrated' ) ) { + $safecss_post = Jetpack_Custom_CSS::get_post(); + $css = ( $compressed && $safecss_post['post_content_filtered'] ) ? $safecss_post['post_content_filtered'] : $safecss_post['post_content']; + } else { + $current_revision = Jetpack_Custom_CSS::get_current_revision(); + if ( false === $current_revision ) { + $css = ''; + } else { + $css = ( $compressed && $current_revision['post_content_filtered'] ) ? $current_revision['post_content_filtered'] : $current_revision['post_content']; + } + } + + // Fix for un-migrated Custom CSS + if ( empty( $safecss_post ) ) { + $_css = get_option( 'safecss' ); + if ( !empty( $_css ) ) { + $css = $_css; + } + } + } + else if ( 'safecss_preview' == $option ) { + $safecss_post = Jetpack_Custom_CSS::get_current_revision(); + $css = $safecss_post['post_content']; + $css = stripslashes( $css ); + $css = Jetpack_Custom_CSS::minify( $css, get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ) ); + } + + $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css ); + + if ( empty( $css ) ) { + $css = "/*\n" + . wordwrap( + apply_filters( + 'safecss_default_css', + __( + "Welcome to Custom CSS!\n\nCSS (Cascading Style Sheets) is a kind of code that tells the browser how to render a web page. You may delete these comments and get started with your customizations.\n\nBy default, your stylesheet will be loaded after the theme stylesheets, which means that your rules can take precedence and override the theme CSS rules. Just write here what you want to change, you don't need to copy all your theme's stylesheet content.", + 'jetpack' + ) + ) + ) + . "\n*/"; + } + + $css = apply_filters( 'safecss_css', $css ); + + return $css; + } + + static function print_css() { + do_action( 'safecss_print_pre' ); + + echo Jetpack_Custom_CSS::get_css( true ); + } + + static function link_tag() { + global $blog_id, $current_blog; + + if ( apply_filters( 'safecss_style_error', false ) ) + return; + + if ( ! is_super_admin() && isset( $current_blog ) && ( 1 == $current_blog->spam || 1 == $current_blog->deleted ) ) + return; + + if ( Jetpack_Custom_CSS::is_customizer_preview() ) + return; + + $css = ''; + $option = Jetpack_Custom_CSS::is_preview() ? 'safecss_preview' : 'safecss'; + + if ( 'safecss' == $option ) { + if ( get_option( 'safecss_revision_migrated' ) ) { + $safecss_post = Jetpack_Custom_CSS::get_post(); + + if ( ! empty( $safecss_post['post_content'] ) ) { + $css = $safecss_post['post_content']; + } + } else { + $current_revision = Jetpack_Custom_CSS::get_current_revision(); + + if ( ! empty( $current_revision['post_content'] ) ) { + $css = $current_revision['post_content']; + } + } + + // Fix for un-migrated Custom CSS + if ( empty( $safecss_post ) ) { + $_css = get_option( 'safecss' ); + if ( !empty( $_css ) ) { + $css = $_css; + } + } + } + + if ( 'safecss_preview' == $option ) { + $safecss_post = Jetpack_Custom_CSS::get_current_revision(); + + if ( !empty( $safecss_post['post_content'] ) ) { + $css = $safecss_post['post_content']; + } + } + + $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css ); + + if ( $css == '' ) + return; + + $href = trailingslashit( site_url() ); + $href = add_query_arg( 'custom-css', 1, $href ); + $href = add_query_arg( 'csblog', $blog_id, $href ); + $href = add_query_arg( 'cscache', 6, $href ); + $href = add_query_arg( 'csrev', (int) get_option( $option . '_rev' ), $href ); + + $href = apply_filters( 'safecss_href', $href, $blog_id ); + + if ( Jetpack_Custom_CSS::is_preview() ) + $href = add_query_arg( 'csspreview', 'true', $href ); + + ?> + <link rel="stylesheet" id="custom-css-css" type="text/css" href="<?php echo esc_url( $href ); ?>" /> + <?php + } + + static function style_filter( $current ) { + if ( Jetpack_Custom_CSS::is_freetrial() && ( ! Jetpack_Custom_CSS::is_preview() || ! current_user_can( 'switch_themes' ) ) ) + return $current; + else if ( Jetpack_Custom_CSS::skip_stylesheet() ) + return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/blank.css', __FILE__ ) ); + + return $current; + } + + static function buffer( $html ) { + $html = str_replace( '</body>', Jetpack_Custom_CSS::preview_flag(), $html ); + return preg_replace_callback( '!href=([\'"])(.*?)\\1!', array( 'Jetpack_Custom_CSS', 'preview_links' ), $html ); + } + + static function preview_links( $matches ) { + if ( 0 !== strpos( $matches[2], get_option( 'home' ) ) ) + return $matches[0]; + + $link = wp_specialchars_decode( $matches[2] ); + $link = add_query_arg( 'csspreview', 'true', $link ); + $link = esc_url( $link ); + return "href={$matches[1]}$link{$matches[1]}"; + } + + /** + * Places a black bar above every preview page + */ + static function preview_flag() { + if ( is_admin() ) + return; + + $message = esc_html__( 'Preview: changes must be saved or they will be lost', 'jetpack' ); + $message = apply_filters( 'safecss_preview_message', $message ); + + $preview_flag_js = "var flag = document.createElement('div'); + flag.innerHTML = " . json_encode( $message ) . "; + flag.style.background = 'black'; + flag.style.color = 'white'; + flag.style.textAlign = 'center'; + flag.style.fontSize = '15px'; + flag.style.padding = '1px'; + document.body.style.paddingTop = '32px'; + document.body.insertBefore(flag, document.body.childNodes[0]); + "; + + $preview_flag_js = apply_filters( 'safecss_preview_flag_js', $preview_flag_js ); + if ( $preview_flag_js ) { + $preview_flag_js = '<script type="text/javascript"> + // <![CDATA[ + ' . $preview_flag_js . ' + // ]]> + </script>'; + } + + return $preview_flag_js; + } + + static function menu() { + $parent = 'themes.php'; + $title = __( 'Edit CSS', 'jetpack' ); + $hook = add_theme_page( $title, $title, 'edit_theme_options', 'editcss', array( 'Jetpack_Custom_CSS', 'admin' ) ); + add_action( "admin_print_scripts-$hook", array( 'Jetpack_Custom_CSS', 'enqueue_scripts' ) ); + add_action( "admin_head-$hook", array( 'Jetpack_Custom_CSS', 'admin_head' ) ); + add_action( "load-revision.php", array( 'Jetpack_Custom_CSS', 'prettify_post_revisions' ) ); + add_action( "load-$hook", array( 'Jetpack_Custom_CSS', 'update_title' ) ); + } + + /** + * Adds a menu item in the appearance section for this plugin's administration + * page. Also adds hooks to enqueue the CSS and JS for the admin page. + */ + static function update_title() { + global $title; + $title = __( 'CSS', 'jetpack' ); + } + + static function prettify_post_revisions() { + add_filter( 'the_title', array( 'Jetpack_Custom_CSS', 'post_title' ), 10, 2 ); + add_action( 'admin_head', array( 'Jetpack_Custom_CSS', 'remove_title_excerpt_from_revisions' ) ); + } + + static function remove_title_excerpt_from_revisions() { + global $post; + + if ( !$post ) { + return; + } + + if ( 'safecss' != $post->post_type ) { + return; + } + ?> + <style type="text/css"> + #revision-field-post_title, #revision-field-post_excerpt { + display: none; + } + </style> + <?php + } + + static function post_title( $title, $post_id ) { + if ( !$post_id = (int) $post_id ) { + return $title; + } + + if ( !$post = get_post( $post_id ) ) { + return $title; + } + + if ( 'safecss' != $post->post_type ) { + return $title; + } + + return __( 'Custom CSS Stylesheet', 'jetpack' ); + } + + static function enqueue_scripts() { + wp_enqueue_script( 'postbox' ); + + if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { + $url = plugins_url( 'custom-css/js/', __FILE__ ); + wp_enqueue_script( 'jquery.spin' ); + wp_enqueue_script( 'safecss-ace', $url . 'ace/ace.js', array(), '20130213', true ); + wp_enqueue_script( 'safecss-ace-css', $url . 'ace/mode-css.js', array( 'safecss-ace' ), '20130213', true ); + wp_enqueue_script( 'safecss-ace-use', $url . 'safecss-ace.js', array( 'jquery', 'safecss-ace-css' ), '20130213', true ); + } + } + + static function admin_head() { + ?> + <style type="text/css"> + #safecssform { + position: relative; + } + + #poststuff { + padding-top: 0; + } + + #safecss { + min-height: 250px; + width: 100%; + } + + .misc-pub-section > span { + font-weight: bold; + } + + .misc-pub-section > div { + margin-top: 3px; + } + + <?php + + if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { + + ?> + #safecss-container { + position: relative; + width: 99.5%; + height: 400px; + border: 1px solid #dfdfdf; + border-radius: 3px; + } + + #safecss-container .ace_editor { + font-family: Consolas, Monaco, Courier, monospace; + } + + #safecss-ace { + width: 100%; + height: 100%; + display: none; /* Hide on load otherwise it looks weird */ + } + + #safecss-ace.ace_editor { + display: block; + } + + #safecss-container .ace-tm .ace_gutter { + background-color: #ededed; + } + <?php + } + + ?> + </style> + <script type="text/javascript"> + /*<![CDATA[*/ + var safecssResize, safecssInit; + + <?php + + if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { + ?>var safecssAceSrcPath = <?php echo json_encode( parse_url( plugins_url( 'custom-css/js/ace/', __FILE__ ), PHP_URL_PATH ) ); ?>;<?php + } + + ?> + + ( function ( $ ) { + var safe, win; + + safecssResize = function () { + safe.height( win.height() - safe.offset().top - 250 ); + }; + + safecssInit = function() { + safe = $('#safecss'); + win = $(window); + + postboxes.add_postbox_toggles('editcss'); + safecssResize(); + var button = document.getElementById( 'preview' ); + button.onclick = function ( event ) { + <?php + + // hack for now for previewing. + // TODO: move all of this JS into its own file. + if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { echo "\t\taceSyncCSS();\n"; } + + ?> + document.forms["safecssform"].target = "csspreview"; + document.forms["safecssform"].action.value = 'preview'; + document.forms["safecssform"].submit(); + document.forms["safecssform"].target = ""; + document.forms["safecssform"].action.value = 'save'; + + event = event || window.event; + + if ( event.preventDefault ) + event.preventDefault(); + + return false; + } + }; + + window.onresize = safecssResize; + addLoadEvent( safecssInit ); + } )( jQuery ); + + jQuery( function ( $ ) { + $( '.edit-preprocessor' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#preprocessor-select' ).slideDown(); + $( this ).hide(); + } ); + + $( '.cancel-preprocessor' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#preprocessor-select' ).slideUp( function () { + $( '.edit-preprocessor' ).show(); + $( '#preprocessor_choices' ).val( $( '#custom_css_preprocessor' ).val() ); + } ); + } ); + + $( '.save-preprocessor' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#preprocessor-select' ).slideUp(); + $( '#preprocessor-display' ).text( $( '#preprocessor_choices option:selected' ).text() ); + $( '#custom_css_preprocessor' ).val( $( '#preprocessor_choices' ).val() ); + $( '.edit-preprocessor' ).show(); + } ); + } ); + /*]]>*/ + </script> + <?php + } + + static function saved_message() { + echo '<div id="message" class="updated fade"><p><strong>' . __( 'Stylesheet saved.', 'jetpack' ) . '</strong></p></div>'; + } + + static function admin() { + add_meta_box( 'submitdiv', __( 'Publish', 'jetpack' ), array( __CLASS__, 'publish_box' ), 'editcss', 'side' ); + + $safecss_post = Jetpack_Custom_CSS::get_post(); + + if ( ! empty( $safecss_post ) && 0 < $safecss_post['ID'] && wp_get_post_revisions( $safecss_post['ID'] ) ) + add_meta_box( 'revisionsdiv', __( 'CSS Revisions', 'jetpack' ), array( __CLASS__, 'revisions_meta_box' ), 'editcss', 'side' ); + + ?> + <div class="wrap columns-2"> + <?php do_action( 'custom_design_header' ); ?> + <h2><?php _e( 'CSS Stylesheet Editor', 'jetpack' ); ?></h2> + <form id="safecssform" action="" method="post"> + <?php wp_nonce_field( 'safecss' ) ?> + <?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?> + <?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?> + <input type="hidden" name="action" value="save" /> + <div id="poststuff" class="metabox-holder has-right-sidebar"> + <p class="css-support"><?php echo apply_filters( 'safecss_intro_text', __( 'New to CSS? Start with a <a href="http://www.htmldog.com/guides/cssbeginner/">beginner tutorial</a>. Questions? + Ask in the <a href="http://wordpress.org/support/forum/themes-and-templates">Themes and Templates forum</a>.', 'jetpack' ) ); ?></p> + <div id="postbox-container-1" class="inner-sidebar"> + <?php do_meta_boxes( 'editcss', 'side', $safecss_post ); ?> + </div> + <div id="post-body"> + <div id="post-body-content"> + <div class="postarea"> + <?php if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { ?> + <div id="safecss-container"> + <div id="safecss-ace"></div> + </div> + <script type="text/javascript"> + jQuery.fn.spin && jQuery("#safecss-container").spin( 'large' ); + </script> + <textarea id="safecss" name="safecss" class="hide-if-js"><?php echo esc_textarea( Jetpack_Custom_CSS::get_css() ); ?></textarea> + <div class="clear"></div> + <?php } else { ?> + <p><textarea id="safecss" name="safecss"><?php echo str_replace('</textarea>', '</textarea>', Jetpack_Custom_CSS::get_css()); ?></textarea></p> + <?php } ?> + </div> + </div> + </div> + <br class="clear" /> + </div> + </form> + </div> + <?php + } + + static function publish_box() { + ?> + <div id="minor-publishing"> + <div id="misc-publishing-actions"> + <?php + + $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() ); + + if ( ! empty( $preprocessors ) ) { + $safecss_post = Jetpack_Custom_CSS::get_current_revision(); + $selected_preprocessor_key = get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ); + $selected_preprocessor = isset( $preprocessors[$selected_preprocessor_key] ) ? $preprocessors[$selected_preprocessor_key] : null; + + ?> + <div class="misc-pub-section"> + <label><?php esc_html_e( 'Preprocessor:', 'jetpack' ); ?></label> + <span id="preprocessor-display"><?php echo esc_html( $selected_preprocessor ? $selected_preprocessor['name'] : __( 'None', 'jetpack' ) ); ?></span> + <a class="edit-preprocessor hide-if-no-js" href="#preprocessor"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a> + <div id="preprocessor-select" class="hide-if-js"> + <input type="hidden" name="custom_css_preprocessor" id="custom_css_preprocessor" value="<?php echo esc_attr( $selected_preprocessor_key ); ?>" /> + <select id="preprocessor_choices"> + <option value=""><?php esc_html_e( 'None', 'jetpack' ); ?></option> + <?php + + foreach ( $preprocessors as $preprocessor_key => $preprocessor ) { + ?> + <option value="<?php echo esc_attr( $preprocessor_key ); ?>" <?php selected( $selected_preprocessor_key, $preprocessor_key ); ?>><?php echo esc_html( $preprocessor['name'] ); ?></option> + <?php + } + + ?> + </select> + <a class="save-preprocessor hide-if-no-js button" href="#preprocessor"><?php esc_html_e( 'OK', 'jetpack' ); ?></a> + <a class="cancel-preprocessor hide-if-no-js" href="#preprocessor"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a> + </div> + </div> + <?php + } + + $safecss_post = Jetpack_Custom_CSS::get_current_revision(); + + $add_css = ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) != 'no' ); + + ?> + <div class="misc-pub-section"> + <label><?php esc_html_e( 'Mode:', 'jetpack' ); ?></label> + <span id="css-mode-display"><?php echo esc_html( $add_css ? __( 'Add-on', 'jetpack' ) : __( 'Replacement', 'jetpack' ) ); ?></span> + <a class="edit-css-mode hide-if-no-js" href="#css-mode"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a> + <div id="css-mode-select" class="hide-if-js"> + <input type="hidden" name="add_to_existing" id="add_to_existing" value="<?php echo $add_css ? 'true' : 'false'; ?>" /> + <p> + <label> + <input type="radio" name="add_to_existing_display" value="true" <?php checked( $add_css ); ?>/> + <?php _e( 'Add-on CSS <b>(Recommended)</b>', 'jetpack' ); ?> + </label> + <br /> + <label> + <input type="radio" name="add_to_existing_display" value="false" <?php checked( ! $add_css ); ?>/> + <?php printf( __( 'Replace <a href="%s">theme\'s CSS</a> <b>(Advanced)</b>', 'jetpack' ), apply_filters( 'safecss_theme_stylesheet_url', get_stylesheet_uri() ) ); ?> + </label> + </p> + <a class="save-css-mode hide-if-no-js button" href="#css-mode"><?php esc_html_e( 'OK', 'jetpack' ); ?></a> + <a class="cancel-css-mode hide-if-no-js" href="#css-mode"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a> + </div> + <script type="text/javascript"> + jQuery( function ( $ ) { + $( '.edit-css-mode' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#css-mode-select' ).slideDown(); + $( this ).hide(); + } ); + + $( '.cancel-css-mode' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#css-mode-select' ).slideUp( function () { + $( '.edit-css-mode' ).show(); + $( 'input[name=add_to_existing_display][value=' + $( '#add_to_existing' ).val() + ']' ).attr( 'checked', true ); + } ); + } ); + + $( '.save-css-mode' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#css-mode-select' ).slideUp(); + $( '#css-mode-display' ).text( $( 'input[name=add_to_existing_display]:checked' ).val() == 'true' ? 'Add-on' : 'Replacement' ); + $( '#add_to_existing' ).val( $( 'input[name=add_to_existing_display]:checked' ).val() ); + $( '.edit-css-mode' ).show(); + } ); + } ); + </script> + </div> + <?php do_action( 'custom_css_submitbox_misc_actions' ); ?> + </div> + </div> + <div id="major-publishing-actions"> + <input type="button" class="button" id="preview" name="preview" value="<?php esc_attr_e( 'Preview', 'jetpack' ) ?>" /> + <div id="publishing-action"> + <input type="submit" class="button-primary" id="save" name="save" value="<?php ( Jetpack_Custom_CSS::is_freetrial() ) ? esc_attr_e( 'Save & Buy Upgrade', 'jetpack' ) : esc_attr_e( 'Save Stylesheet', 'jetpack' ); ?>" /> + </div> + </div> + <?php + } + + /** + * Render metabox listing CSS revisions and the themes that correspond to the revisions. + * Called by afecss_admin * + * @param array $safecss_post + * @global $post + * @uses WP_Query, wp_post_revision_title, esc_html, add_query_arg, menu_page_url, wp_reset_query + * @return string + */ + static function revisions_meta_box( $safecss_post ) { + $max_revisions = defined( 'WP_POST_REVISIONS' ) && is_numeric( WP_POST_REVISIONS ) ? (int) WP_POST_REVISIONS : 25; + $posts_per_page = isset( $_GET['show_all_rev'] ) ? $max_revisions : 6; + + $revisions = new WP_Query( array( + 'posts_per_page' => $posts_per_page, + 'post_type' => 'revision', + 'post_status' => 'inherit', + 'post_parent' => $safecss_post['ID'], + 'orderby' => 'date', + 'order' => 'DESC' + ) ); + + if ( $revisions->have_posts() ) { ?> + <ul class="post-revisions"><?php + + global $post; + + while ( $revisions->have_posts() ) : + $revisions->the_post(); + + ?><li> + <?php + echo wp_post_revision_title( $post ); + + if ( ! empty( $post->post_excerpt ) ) + echo ' (' . esc_html( $post->post_excerpt ) . ')'; + ?> + </li><?php + + endwhile; + + ?></ul><?php + + if ( $revisions->found_posts > 6 ) { + ?> + <br> + <a href="<?php echo add_query_arg( 'show_all_rev', 'true', menu_page_url( 'editcss', false ) ); ?>"><?php esc_html_e( 'Show more', 'jetpack' ); ?></a> + <?php + } + } + + wp_reset_query(); + } + + /** + * Hook in init at priority 11 to disable custom CSS. + */ + static function disable() { + remove_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 ); + remove_filter( 'stylesheet_uri', array( 'Jetpack_Custom_CSS', 'style_filter' ) ); + } + + /** + * Reset all aspects of Custom CSS on a theme switch so that changing + * themes is a sure-fire way to get a clean start. + */ + static function reset() { + $safecss_post_id = Jetpack_Custom_CSS::save_revision( '' ); + $safecss_revision = Jetpack_Custom_CSS::get_current_revision(); + + update_option( 'safecss_rev', intval( get_option( 'safecss_rev' ) ) + 1 ); + + update_post_meta( $safecss_post_id, 'custom_css_add', 'yes' ); + update_post_meta( $safecss_post_id, 'content_width', false ); + update_post_meta( $safecss_post_id, 'custom_css_preprocessor', '' ); + update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', 'yes' ); + update_metadata( 'post', $safecss_revision['ID'], 'content_width', false ); + update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', '' ); + } + + static function is_customizer_preview() { + if ( isset ( $GLOBALS['wp_customize'] ) ) + return ! $GLOBALS['wp_customize']->is_theme_active(); + + return false; + } + + static function minify( $css, $preprocessor = '' ) { + if ( ! $css ) + return ''; + + if ( $preprocessor ) { + $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() ); + + if ( isset( $preprocessors[$preprocessor] ) ) { + $css = call_user_func( $preprocessors[$preprocessor]['callback'], $css ); + } + } + + safecss_class(); + $csstidy = new csstidy(); + $csstidy->optimise = new safecss( $csstidy ); + + $csstidy->set_cfg( 'remove_bslash', false ); + $csstidy->set_cfg( 'compress_colors', true ); + $csstidy->set_cfg( 'compress_font-weight', true ); + $csstidy->set_cfg( 'remove_last_;', true ); + $csstidy->set_cfg( 'case_properties', true ); + $csstidy->set_cfg( 'discard_invalid_properties', true ); + $csstidy->set_cfg( 'css_level', 'CSS3.0' ); + $csstidy->set_cfg( 'template', 'highest'); + $csstidy->parse( $css ); + + return $csstidy->print->plain(); + } + + /** + * When restoring a SafeCSS post revision, also copy over the + * content_width and custom_css_add post metadata. + */ + static function restore_revision( $_post_id, $_revision_id ) { + $_post = get_post( $_post_id ); + + if ( 'safecss' != $_post->post_type ) + return; + + $safecss_revision = Jetpack_Custom_CSS::get_current_revision(); + + $content_width = get_post_meta( $_revision_id, 'content_width', true ); + $custom_css_add = get_post_meta( $_revision_id, 'custom_css_add', true ); + $preprocessor = get_post_meta( $_revision_id, 'custom_css_preprocessor', true ); + + update_metadata( 'post', $safecss_revision['ID'], 'content_width', $content_width ); + update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', $custom_css_add ); + update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', $preprocessor ); + update_post_meta( $_post->ID, 'content_width', $content_width ); + update_post_meta( $_post->ID, 'custom_css_add', $custom_css_add ); + update_post_meta( $_post->ID, 'custom_css_preprocessor', $preprocessor ); + } + + /** + * Migration routine for moving safecss from wp_options to wp_posts to support revisions + * + * @return void + */ + static function upgrade() { + $css = get_option( 'safecss' ); + + // Check if CSS is stored in wp_options + if ( $css ) { + // Remove the async actions from publish_post + remove_action( 'publish_post', 'queue_publish_post' ); + + $post = array(); + $post['post_content'] = $css; + $post['post_title'] = 'safecss'; + $post['post_status'] = 'publish'; + $post['post_type'] = 'safecss'; + + // Insert the CSS into wp_posts + $post_id = wp_insert_post( $post ); + // Check for errors + if ( !$post_id or is_wp_error( $post_id ) ) + die( $post_id->get_error_message() ); + + // Delete safecss option + delete_option( 'safecss' ); + } + + unset( $css ); + + // Check if we have already done this + if ( !get_option( 'safecss_revision_migrated' ) ) { + define( 'DOING_MIGRATE', true ); + + // Get hashes of safecss post and current revision + $safecss_post = Jetpack_Custom_CSS::get_post(); + + if ( empty( $safecss_post ) ) + return; + + $safecss_post_hash = md5( $safecss_post['post_content'] ); + $current_revision = Jetpack_Custom_CSS::get_current_revision(); + + if ( null == $current_revision ) + return; + + $current_revision_hash = md5( $current_revision['post_content'] ); + + // If hashes are not equal, set safecss post with content from current revision + if ( $safecss_post_hash !== $current_revision_hash ) { + Jetpack_Custom_CSS::save_revision( $current_revision['post_content'] ); + // Reset post_content to display the migrated revsion + $safecss_post['post_content'] = $current_revision['post_content']; + } + + // Set option so that we dont keep doing this + update_option( 'safecss_revision_migrated', time() ); + } + + $newest_safecss_post = Jetpack_Custom_CSS::get_current_revision(); + + if ( $newest_safecss_post ) { + if ( get_option( 'safecss_content_width' ) ) { + // Add the meta to the post and the latest revision. + update_post_meta( $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) ); + update_metadata( 'post', $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) ); + + delete_option( 'safecss_content_width' ); + } + + if ( get_option( 'safecss_add' ) ) { + update_post_meta( $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) ); + update_metadata( 'post', $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) ); + + delete_option( 'safecss_add' ); + } + } + } + + static function revision_redirect( $redirect ) { + global $post; + + if ( 'safecss' == $post->post_type ) { + if ( strstr( $redirect, 'action=edit' ) ) { + return 'themes.php?page=editcss'; + } + + if ( 'edit.php' == $redirect ) { + return ''; + } + } + + return $redirect; + } + + static function revision_post_link( $post_link, $post_id, $context ) { + if ( !$post_id = (int) $post_id ) { + return $post_link; + } + + if ( !$post = get_post( $post_id ) ) { + return $post_link; + } + + if ( 'safecss' != $post->post_type ) { + return $post_link; + } + + $post_link = admin_url( 'themes.php?page=editcss' ); + + if ( 'display' == $context ) { + return esc_url( $post_link ); + } + + return esc_url_raw( $post_link ); + } +} + +class Jetpack_Safe_CSS { + static function filter_attr( $css, $element = 'div' ) { + safecss_class(); + + $css = $element . ' {' . $css . '}'; + + $csstidy = new csstidy(); + $csstidy->optimise = new safecss( $csstidy ); + $csstidy->set_cfg( 'remove_bslash', false ); + $csstidy->set_cfg( 'compress_colors', false ); + $csstidy->set_cfg( 'compress_font-weight', false ); + $csstidy->set_cfg( 'discard_invalid_properties', true ); + $csstidy->set_cfg( 'merge_selectors', false ); + $csstidy->set_cfg( 'remove_last_;', false ); + $csstidy->set_cfg( 'css_level', 'CSS3.0' ); + + $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css ); + $css = wp_kses_split( $css, array(), array() ); + $csstidy->parse( $css ); + + $css = $csstidy->print->plain(); + + $css = str_replace( array( "\n","\r","\t" ), '', $css ); + + preg_match( "/^{$element}\s*{(.*)}\s*$/", $css, $matches ); + + if ( empty( $matches[1] ) ) + return ''; + + return $matches[1]; + } +} + +function migrate() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::upgrade()' ); + + return Jetpack_Custom_CSS::upgrade(); +} + +function safecss_revision_redirect( $redirect ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revision_redirect()' ); + + return Jetpack_Custom_CSS::revision_redirect( $redirect ); +} + +function safecss_revision_post_link( $post_link, $post_id, $context ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revision_post_link()' ); + + return Jetpack_Custom_CSS::revision_post_link( $post_link, $post_id, $context ); +} + +function get_safecss_post() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_post()' ); + + return Jetpack_Custom_CSS::get_post(); +} + +function custom_css_post_id() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::post_id()' ); + + return Jetpack_Custom_CSS::post_id(); +} + +function get_current_revision() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_current_revision()' ); + + return Jetpack_Custom_CSS::get_current_revision(); +} + +function save_revision( $css, $is_preview = false, $preprocessor = '' ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::save_revision()' ); + + return Jetpack_Custom_CSS::save_revision( $css, $is_preview, $preprocessor ); +} + +function safecss_skip_stylesheet() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::skip_stylesheet()' ); + + return Jetpack_Custom_CSS::skip_stylesheet(); +} + +function safecss_init() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::init()' ); + + return Jetpack_Custom_CSS::init(); +} + +function safecss_is_preview() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_preview()' ); + + return Jetpack_Custom_CSS::is_preview(); +} + +function safecss_is_freetrial() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_freetrial()' ); + + return Jetpack_Custom_CSS::is_freetrial(); +} + +function safecss( $compressed = false ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_css()' ); + + return Jetpack_Custom_CSS::get_css( $compressed ); +} + +function safecss_print() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::print_css()' ); + + return Jetpack_Custom_CSS::print_css(); +} + +function safecss_style() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::link_tag()' ); + + return Jetpack_Custom_CSS::link_tag(); +} + +function safecss_style_filter( $current ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::style_filter()' ); + + return Jetpack_Custom_CSS::style_filter( $current ); +} + +function safecss_buffer( $html ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::buffer()' ); + + return Jetpack_Custom_CSS::buffer( $html ); +} + +function safecss_preview_links( $matches ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::preview_links()' ); + + return Jetpack_Custom_CSS::preview_links( $matches ); +} + +function safecss_preview_flag() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::preview_flag()' ); + + return Jetpack_Custom_CSS::preview_flag(); +} + +function safecss_menu() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::menu()' ); + + return Jetpack_Custom_CSS::menu(); +} + +function update_title() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::update_title()' ); + + return Jetpack_Custom_CSS::update_title(); +} + +function safecss_prettify_post_revisions() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::prettify_post_revisions()' ); + + return Jetpack_Custom_CSS::prettify_post_revisions(); +} + +function safecss_remove_title_excerpt_from_revisions() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::remove_title_excerpt_from_revisions()' ); + + return Jetpack_Custom_CSS::remove_title_excerpt_from_revisions(); +} + +function safecss_post_title( $title, $post_id ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::post_title()' ); + + return Jetpack_Custom_CSS::post_title( $title, $post_id ); +} + +function safe_css_enqueue_scripts() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::enqueue_scripts()' ); + + return Jetpack_Custom_CSS::enqueue_scripts(); +} + +function safecss_admin_head() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::admin_head()' ); + + return Jetpack_Custom_CSS::admin_head(); +} + +function safecss_saved() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::saved_message()' ); + + return Jetpack_Custom_CSS::saved_message(); +} + +function safecss_admin() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::admin()' ); + + return Jetpack_Custom_CSS::admin(); +} + +function custom_css_meta_box() { + _deprecated_function( __FUNCTION__, '2.1', 'add_meta_box( $id, $title, $callback, \'editcss\', \'side\' )' ); +} + +function custom_css_post_revisions_meta_box( $safecss_post ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revisions_meta_box()' ); + + return Jetpack_Custom_CSS::revisions_meta_box( $safecss_post ); +} + +function disable_safecss_style() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::disable()' ); + + return Jetpack_Custom_CSS::disable(); +} + +function custom_css_reset() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::reset()' ); + + return Jetpack_Custom_CSS::reset(); +} + +function custom_css_is_customizer_preview() { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_customizer_preview()' ); + + return Jetpack_Custom_CSS::is_customizer_preview(); +} + +function custom_css_minify( $css, $preprocessor = '' ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::minify()' ); + + return Jetpack_Custom_CSS::minify( $css, $preprocessor ); +} + +function custom_css_restore_revision( $_post_id, $_revision_id ) { + _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::restore_revision()' ); + + return Jetpack_Custom_CSS::restore_revision( $_post_id, $_revision_id );; +} + +function safecss_class() { + // Wrapped so we don't need the parent class just to load the plugin + if ( class_exists('safecss') ) + return; + + require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' ); + + class safecss extends csstidy_optimise { + function safecss( &$css ) { + return $this->csstidy_optimise( $css ); + } + + function postparse() { + do_action( 'csstidy_optimize_postparse', $this ); + + return parent::postparse(); + } + + function subvalue() { + do_action( 'csstidy_optimize_subvalue', $this ); + + return parent::subvalue(); + } + } +} + +if ( ! function_exists( 'safecss_filter_attr' ) ) { + function safecss_filter_attr( $css, $element = 'div' ) { + return Jetpack_Safe_CSS::filter_attr( $css, $element ); + } +} + +add_action( 'init', array( 'Jetpack_Custom_CSS', 'init' ) ); + +include dirname( __FILE__ ) . '/custom-css/preprocessors.php';
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/blank.css b/plugins/jetpack/modules/custom-css/custom-css/blank.css new file mode 100644 index 00000000..79a9626b --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/blank.css @@ -0,0 +1 @@ +/* */ diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/ace/ace.js b/plugins/jetpack/modules/custom-css/custom-css/js/ace/ace.js new file mode 100644 index 00000000..520b8fca --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/ace/ace.js @@ -0,0 +1,11 @@ +(function(){function o(e){var i=function(e,t){return r("",e,t)},s=t;e&&(t[e]||(t[e]={}),s=t[e]);if(!s.define||!s.define.packaged)n.original=s.define,s.define=n,s.define.packaged=!0;if(!s.require||!s.require.packaged)r.original=s.require,s.require=i,s.require.packaged=!0}var e="ace",t=function(){return this}();if(!e&&typeof requirejs!="undefined")return;var n=function(e,t,r){if(typeof e!="string"){n.original?n.original.apply(window,arguments):(console.error("dropping module because define wasn't a string."),console.trace());return}arguments.length==2&&(r=t),n.modules||(n.modules={}),n.modules[e]=r},r=function(e,t,n){if(Object.prototype.toString.call(t)==="[object Array]"){var i=[];for(var o=0,u=t.length;o<u;++o){var a=s(e,t[o]);if(!a&&r.original)return r.original.apply(window,arguments);i.push(a)}n&&n.apply(null,i)}else{if(typeof t=="string"){var f=s(e,t);return!f&&r.original?r.original.apply(window,arguments):(n&&n(),f)}if(r.original)return r.original.apply(window,arguments)}},i=function(e,t){if(t.indexOf("!")!==-1){var n=t.split("!");return i(e,n[0])+"!"+i(e,n[1])}if(t.charAt(0)=="."){var r=e.split("/").slice(0,-1).join("/");t=r+"/"+t;while(t.indexOf(".")!==-1&&s!=t){var s=t;t=t.replace(/\/\.\//,"/").replace(/[^\/]+\/\.\.\//,"")}}return t},s=function(e,t){t=i(e,t);var s=n.modules[t];if(!s)return null;if(typeof s=="function"){var o={},u={id:t,uri:"",exports:o,packaged:!0},a=function(e,n){return r(t,e,n)},f=s(a,o,u);return o=f||u.exports,n.modules[t]=o,o}return s};o(e)})(),ace.define("ace/ace",["require","exports","module","ace/lib/fixoldbrowsers","ace/lib/dom","ace/lib/event","ace/editor","ace/edit_session","ace/undomanager","ace/virtual_renderer","ace/multi_select","ace/worker/worker_client","ace/keyboard/hash_handler","ace/placeholder","ace/mode/folding/fold_mode","ace/config"],function(e,t,n){e("./lib/fixoldbrowsers");var r=e("./lib/dom"),i=e("./lib/event"),s=e("./editor").Editor,o=e("./edit_session").EditSession,u=e("./undomanager").UndoManager,a=e("./virtual_renderer").VirtualRenderer,f=e("./multi_select").MultiSelect;e("./worker/worker_client"),e("./keyboard/hash_handler"),e("./placeholder"),e("./mode/folding/fold_mode"),t.config=e("./config"),t.require=e,t.edit=function(e){if(typeof e=="string"){var n=e,e=document.getElementById(n);if(!e)throw"ace.edit can't find div #"+n}if(e.env&&e.env.editor instanceof s)return e.env.editor;var o=t.createEditSession(r.getInnerText(e));e.innerHTML="";var u=new s(new a(e));new f(u),u.setSession(o);var l={document:o,editor:u,onResize:u.resize.bind(u)};return i.addListener(window,"resize",l.onResize),e.env=u.env=l,u},t.createEditSession=function(e,t){var n=new o(e,n);return n.setUndoManager(new u),n},t.EditSession=o,t.UndoManager=u}),ace.define("ace/lib/fixoldbrowsers",["require","exports","module","ace/lib/regexp","ace/lib/es5-shim"],function(e,t,n){e("./regexp"),e("./es5-shim")}),ace.define("ace/lib/regexp",["require","exports","module"],function(e,t,n){function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.extended?"x":"")+(e.sticky?"y":"")}function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(var r=n||0;r<e.length;r++)if(e[r]===t)return r;return-1}var r={exec:RegExp.prototype.exec,test:RegExp.prototype.test,match:String.prototype.match,replace:String.prototype.replace,split:String.prototype.split},i=r.exec.call(/()??/,"")[1]===undefined,s=function(){var e=/^/g;return r.test.call(e,""),!e.lastIndex}();if(s&&i)return;RegExp.prototype.exec=function(e){var t=r.exec.apply(this,arguments),n,a;if(typeof e=="string"&&t){!i&&t.length>1&&u(t,"")>-1&&(a=RegExp(this.source,r.replace.call(o(this),"g","")),r.replace.call(e.slice(t.index),a,function(){for(var e=1;e<arguments.length-2;e++)arguments[e]===undefined&&(t[e]=undefined)}));if(this._xregexp&&this._xregexp.captureNames)for(var f=1;f<t.length;f++)n=this._xregexp.captureNames[f-1],n&&(t[n]=t[f]);!s&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--}return t},s||(RegExp.prototype.test=function(e){var t=r.exec.call(this,e);return t&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--,!!t})}),ace.define("ace/lib/es5-shim",["require","exports","module"],function(e,t,n){function m(e){try{return Object.defineProperty(e,"sentinel",{}),"sentinel"in e}catch(t){}}Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=o.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,u=n.apply(s,r.concat(o.call(arguments)));return u!==null&&Object(u)===u?u:s}return n.apply(t,r.concat(o.call(arguments)))};return i});var r=Function.prototype.call,i=Array.prototype,s=Object.prototype,o=i.slice,u=r.bind(s.toString),a=r.bind(s.hasOwnProperty),f,l,c,h,p;if(p=a(s,"__defineGetter__"))f=r.bind(s.__defineGetter__),l=r.bind(s.__defineSetter__),c=r.bind(s.__lookupGetter__),h=r.bind(s.__lookupSetter__);Array.isArray||(Array.isArray=function(t){return u(t)=="[object Array]"}),Array.prototype.forEach||(Array.prototype.forEach=function(t){var n=D(this),r=arguments[1],i=0,s=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;while(i<s)i in n&&t.call(r,n[i],i,n),i++}),Array.prototype.map||(Array.prototype.map=function(t){var n=D(this),r=n.length>>>0,i=Array(r),s=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var o=0;o<r;o++)o in n&&(i[o]=t.call(s,n[o],o,n));return i}),Array.prototype.filter||(Array.prototype.filter=function(t){var n=D(this),r=n.length>>>0,i=[],s=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var o=0;o<r;o++)o in n&&t.call(s,n[o],o,n)&&i.push(n[o]);return i}),Array.prototype.every||(Array.prototype.every=function(t){var n=D(this),r=n.length>>>0,i=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var s=0;s<r;s++)if(s in n&&!t.call(i,n[s],s,n))return!1;return!0}),Array.prototype.some||(Array.prototype.some=function(t){var n=D(this),r=n.length>>>0,i=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var s=0;s<r;s++)if(s in n&&t.call(i,n[s],s,n))return!0;return!1}),Array.prototype.reduce||(Array.prototype.reduce=function(t){var n=D(this),r=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;if(!r&&arguments.length==1)throw new TypeError;var i=0,s;if(arguments.length>=2)s=arguments[1];else do{if(i in n){s=n[i++];break}if(++i>=r)throw new TypeError}while(!0);for(;i<r;i++)i in n&&(s=t.call(void 0,s,n[i],i,n));return s}),Array.prototype.reduceRight||(Array.prototype.reduceRight=function(t){var n=D(this),r=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;if(!r&&arguments.length==1)throw new TypeError;var i,s=r-1;if(arguments.length>=2)i=arguments[1];else do{if(s in n){i=n[s--];break}if(--s<0)throw new TypeError}while(!0);do s in this&&(i=t.call(void 0,i,n[s],s,n));while(s--);return i}),Array.prototype.indexOf||(Array.prototype.indexOf=function(t){var n=D(this),r=n.length>>>0;if(!r)return-1;var i=0;arguments.length>1&&(i=M(arguments[1])),i=i>=0?i:Math.max(0,r+i);for(;i<r;i++)if(i in n&&n[i]===t)return i;return-1}),Array.prototype.lastIndexOf||(Array.prototype.lastIndexOf=function(t){var n=D(this),r=n.length>>>0;if(!r)return-1;var i=r-1;arguments.length>1&&(i=Math.min(i,M(arguments[1]))),i=i>=0?i:r-Math.abs(i);for(;i>=0;i--)if(i in n&&t===n[i])return i;return-1}),Object.getPrototypeOf||(Object.getPrototypeOf=function(t){return t.__proto__||(t.constructor?t.constructor.prototype:s)});if(!Object.getOwnPropertyDescriptor){var d="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(t,n){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(d+t);if(!a(t,n))return;var r,i,o;r={enumerable:!0,configurable:!0};if(p){var u=t.__proto__;t.__proto__=s;var i=c(t,n),o=h(t,n);t.__proto__=u;if(i||o)return i&&(r.get=i),o&&(r.set=o),r}return r.value=t[n],r}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(t){return Object.keys(t)});if(!Object.create){var v;Object.prototype.__proto__===null?v=function(){return{__proto__:null}}:v=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(t,n){var r;if(t===null)r=v();else{if(typeof t!="object")throw new TypeError("typeof prototype["+typeof t+"] != 'object'");var i=function(){};i.prototype=t,r=new i,r.__proto__=t}return n!==void 0&&Object.defineProperties(r,n),r}}if(Object.defineProperty){var g=m({}),y=typeof document=="undefined"||m(document.createElement("div"));if(!g||!y)var b=Object.defineProperty}if(!Object.defineProperty||b){var w="Property description must be an object: ",E="Object.defineProperty called on non-object: ",S="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(t,n,r){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(E+t);if(typeof r!="object"&&typeof r!="function"||r===null)throw new TypeError(w+r);if(b)try{return b.call(Object,t,n,r)}catch(i){}if(a(r,"value"))if(p&&(c(t,n)||h(t,n))){var o=t.__proto__;t.__proto__=s,delete t[n],t[n]=r.value,t.__proto__=o}else t[n]=r.value;else{if(!p)throw new TypeError(S);a(r,"get")&&f(t,n,r.get),a(r,"set")&&l(t,n,r.set)}return t}}Object.defineProperties||(Object.defineProperties=function(t,n){for(var r in n)a(n,r)&&Object.defineProperty(t,r,n[r]);return t}),Object.seal||(Object.seal=function(t){return t}),Object.freeze||(Object.freeze=function(t){return t});try{Object.freeze(function(){})}catch(x){Object.freeze=function(t){return function(n){return typeof n=="function"?n:t(n)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(t){return t}),Object.isSealed||(Object.isSealed=function(t){return!1}),Object.isFrozen||(Object.isFrozen=function(t){return!1}),Object.isExtensible||(Object.isExtensible=function(t){if(Object(t)===t)throw new TypeError;var n="";while(a(t,n))n+="?";t[n]=!0;var r=a(t,n);return delete t[n],r});if(!Object.keys){var T=!0,N=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],C=N.length;for(var k in{toString:null})T=!1;Object.keys=function P(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var P=[];for(var t in e)a(e,t)&&P.push(t);if(T)for(var n=0,r=C;n<r;n++){var i=N[n];a(e,i)&&P.push(i)}return P}}if(!Date.prototype.toISOString||(new Date(-621987552e5)).toISOString().indexOf("-000001")===-1)Date.prototype.toISOString=function(){var t,n,r,i;if(!isFinite(this))throw new RangeError;t=[this.getUTCMonth()+1,this.getUTCDate(),this.getUTCHours(),this.getUTCMinutes(),this.getUTCSeconds()],i=this.getUTCFullYear(),i=(i<0?"-":i>9999?"+":"")+("00000"+Math.abs(i)).slice(0<=i&&i<=9999?-4:-6),n=t.length;while(n--)r=t[n],r<10&&(t[n]="0"+r);return i+"-"+t.slice(0,2).join("-")+"T"+t.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"};Date.now||(Date.now=function(){return(new Date).getTime()}),Date.prototype.toJSON||(Date.prototype.toJSON=function(t){if(typeof this.toISOString!="function")throw new TypeError;return this.toISOString()}),Date.parse("+275760-09-13T00:00:00.000Z")!==864e13&&(Date=function(e){var t=function i(t,n,r,s,o,u,a){var f=arguments.length;if(this instanceof e){var l=f==1&&String(t)===t?new e(i.parse(t)):f>=7?new e(t,n,r,s,o,u,a):f>=6?new e(t,n,r,s,o,u):f>=5?new e(t,n,r,s,o):f>=4?new e(t,n,r,s):f>=3?new e(t,n,r):f>=2?new e(t,n):f>=1?new e(t):new e;return l.constructor=i,l}return e.apply(this,arguments)},n=new RegExp("^(\\d{4}|[+-]\\d{6})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3}))?)?(?:Z|(?:([-+])(\\d{2}):(\\d{2})))?)?)?)?$");for(var r in e)t[r]=e[r];return t.now=e.now,t.UTC=e.UTC,t.prototype=e.prototype,t.prototype.constructor=t,t.parse=function(r){var i=n.exec(r);if(i){i.shift();for(var s=1;s<7;s++)i[s]=+(i[s]||(s<3?1:0)),s==1&&i[s]--;var o=+i.pop(),u=+i.pop(),a=i.pop(),f=0;if(a){if(u>23||o>59)return NaN;f=(u*60+o)*6e4*(a=="+"?-1:1)}var l=+i[0];return 0<=l&&l<=99?(i[0]=l+400,e.UTC.apply(this,i)+f-126227808e5):e.UTC.apply(this,i)+f}return e.parse.apply(this,arguments)},t}(Date));var L=" \n\f\r \u2028\u2029";if(!String.prototype.trim||L.trim()){L="["+L+"]";var A=new RegExp("^"+L+L+"*"),O=new RegExp(L+L+"*$");String.prototype.trim=function(){return String(this).replace(A,"").replace(O,"")}}var M=function(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-1)*Math.floor(Math.abs(e))),e},_="a"[0]!="a",D=function(e){if(e==null)throw new TypeError;return _&&typeof e=="string"&&e?e.split(""):Object(e)}}),ace.define("ace/lib/dom",["require","exports","module"],function(e,t,n){var r="http://www.w3.org/1999/xhtml";t.createElement=function(e,t){return document.createElementNS?document.createElementNS(t||r,e):document.createElement(e)},t.setText=function(e,t){e.innerText!==undefined&&(e.innerText=t),e.textContent!==undefined&&(e.textContent=t)},t.hasCssClass=function(e,t){var n=e.className.split(/\s+/g);return n.indexOf(t)!==-1},t.addCssClass=function(e,n){t.hasCssClass(e,n)||(e.className+=" "+n)},t.removeCssClass=function(e,t){var n=e.className.split(/\s+/g);for(;;){var r=n.indexOf(t);if(r==-1)break;n.splice(r,1)}e.className=n.join(" ")},t.toggleCssClass=function(e,t){var n=e.className.split(/\s+/g),r=!0;for(;;){var i=n.indexOf(t);if(i==-1)break;r=!1,n.splice(i,1)}return r&&n.push(t),e.className=n.join(" "),r},t.setCssClass=function(e,n,r){r?t.addCssClass(e,n):t.removeCssClass(e,n)},t.hasCssString=function(e,t){var n=0,r;t=t||document;if(t.createStyleSheet&&(r=t.styleSheets)){while(n<r.length)if(r[n++].owningElement.id===e)return!0}else if(r=t.getElementsByTagName("style"))while(n<r.length)if(r[n++].id===e)return!0;return!1},t.importCssString=function(n,i,s){s=s||document;if(i&&t.hasCssString(i,s))return null;var o;if(s.createStyleSheet)o=s.createStyleSheet(),o.cssText=n,i&&(o.owningElement.id=i);else{o=s.createElementNS?s.createElementNS(r,"style"):s.createElement("style"),o.appendChild(s.createTextNode(n)),i&&(o.id=i);var u=s.getElementsByTagName("head")[0]||s.documentElement;u.appendChild(o)}},t.importCssStylsheet=function(e,n){if(n.createStyleSheet)n.createStyleSheet(e);else{var r=t.createElement("link");r.rel="stylesheet",r.href=e;var i=n.getElementsByTagName("head")[0]||n.documentElement;i.appendChild(r)}},t.getInnerWidth=function(e){return parseInt(t.computedStyle(e,"paddingLeft"),10)+parseInt(t.computedStyle(e,"paddingRight"),10)+e.clientWidth},t.getInnerHeight=function(e){return parseInt(t.computedStyle(e,"paddingTop"),10)+parseInt(t.computedStyle(e,"paddingBottom"),10)+e.clientHeight},window.pageYOffset!==undefined?(t.getPageScrollTop=function(){return window.pageYOffset},t.getPageScrollLeft=function(){return window.pageXOffset}):(t.getPageScrollTop=function(){return document.body.scrollTop},t.getPageScrollLeft=function(){return document.body.scrollLeft}),window.getComputedStyle?t.computedStyle=function(e,t){return t?(window.getComputedStyle(e,"")||{})[t]||"":window.getComputedStyle(e,"")||{}}:t.computedStyle=function(e,t){return t?e.currentStyle[t]:e.currentStyle},t.scrollbarWidth=function(e){var n=t.createElement("p");n.style.width="100%",n.style.minWidth="0px",n.style.height="200px";var r=t.createElement("div"),i=r.style;i.position="absolute",i.left="-10000px",i.overflow="hidden",i.width="200px",i.minWidth="0px",i.height="150px",r.appendChild(n);var s=e.body||e.documentElement;s.appendChild(r);var o=n.offsetWidth;i.overflow="scroll";var u=n.offsetWidth;return o==u&&(u=r.clientWidth),s.removeChild(r),o-u},t.setInnerHtml=function(e,t){var n=e.cloneNode(!1);return n.innerHTML=t,e.parentNode.replaceChild(n,e),n},t.setInnerText=function(e,t){var n=e.ownerDocument;n.body&&"textContent"in n.body?e.textContent=t:e.innerText=t},t.getInnerText=function(e){var t=e.ownerDocument;return t.body&&"textContent"in t.body?e.textContent:e.innerText||e.textContent||""},t.getParentWindow=function(e){return e.defaultView||e.parentWindow}}),ace.define("ace/lib/event",["require","exports","module","ace/lib/keys","ace/lib/useragent","ace/lib/dom"],function(e,t,n){function o(e,t,n){var s=0;!i.isOpera||"KeyboardEvent"in window||!i.isMac?s=0|(t.ctrlKey?1:0)|(t.altKey?2:0)|(t.shiftKey?4:0)|(t.metaKey?8:0):s=0|(t.metaKey?1:0)|(t.altKey?2:0)|(t.shiftKey?4:0)|(t.ctrlKey?8:0);if(n in r.MODIFIER_KEYS){switch(r.MODIFIER_KEYS[n]){case"Alt":s=2;break;case"Shift":s=4;break;case"Ctrl":s=1;break;default:s=8}n=0}return s&8&&(n==91||n==93)&&(n=0),!!s||n in r.FUNCTION_KEYS||n in r.PRINTABLE_KEYS?e(t,s,n):!1}var r=e("./keys"),i=e("./useragent"),s=e("./dom");t.addListener=function(e,t,n){if(e.addEventListener)return e.addEventListener(t,n,!1);if(e.attachEvent){var r=function(){n(window.event)};n._wrapper=r,e.attachEvent("on"+t,r)}},t.removeListener=function(e,t,n){if(e.removeEventListener)return e.removeEventListener(t,n,!1);e.detachEvent&&e.detachEvent("on"+t,n._wrapper||n)},t.stopEvent=function(e){return t.stopPropagation(e),t.preventDefault(e),!1},t.stopPropagation=function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},t.preventDefault=function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},t.getButton=function(e){return e.type=="dblclick"?0:e.type=="contextmenu"||e.ctrlKey&&i.isMac?2:e.preventDefault?e.button:{1:0,2:2,4:1}[e.button]},document.documentElement.setCapture?t.capture=function(e,n,r){function s(o){n(o),i||(i=!0,r(o)),t.removeListener(e,"mousemove",n),t.removeListener(e,"mouseup",s),t.removeListener(e,"losecapture",s),e.releaseCapture()}var i=!1;t.addListener(e,"mousemove",n),t.addListener(e,"mouseup",s),t.addListener(e,"losecapture",s),e.setCapture()}:t.capture=function(e,t,n){function r(e){t&&t(e),n&&n(e),document.removeEventListener("mousemove",t,!0),document.removeEventListener("mouseup",r,!0),e.stopPropagation()}document.addEventListener("mousemove",t,!0),document.addEventListener("mouseup",r,!0)},t.addMouseWheelListener=function(e,n){var r=8,i=function(e){e.wheelDelta!==undefined?e.wheelDeltaX!==undefined?(e.wheelX=-e.wheelDeltaX/r,e.wheelY=-e.wheelDeltaY/r):(e.wheelX=0,e.wheelY=-e.wheelDelta/r):e.axis&&e.axis==e.HORIZONTAL_AXIS?(e.wheelX=(e.detail||0)*5,e.wheelY=0):(e.wheelX=0,e.wheelY=(e.detail||0)*5),n(e)};t.addListener(e,"DOMMouseScroll",i),t.addListener(e,"mousewheel",i)},t.addMultiMouseDownListener=function(e,n,r,s){var o=0,u,a,f,l={2:"dblclick",3:"tripleclick",4:"quadclick"};t.addListener(e,"mousedown",function(e){if(t.getButton(e)!=0)o=0;else{var i=Math.abs(e.clientX-u)>5||Math.abs(e.clientY-a)>5;if(!f||i)o=0;o+=1,f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o-1]||600)}o==1&&(u=e.clientX,a=e.clientY),r[s]("mousedown",e);if(o>4)o=0;else if(o>1)return r[s](l[o],e)}),i.isOldIE&&t.addListener(e,"dblclick",function(e){o=2,f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o-1]||600),r[s]("mousedown",e),r[s](l[o],e)})},t.addCommandKeyListener=function(e,n){var r=t.addListener;if(i.isOldGecko||i.isOpera&&!("KeyboardEvent"in window)){var s=null;r(e,"keydown",function(e){s=e.keyCode}),r(e,"keypress",function(e){return o(n,e,s)})}else{var u=null;r(e,"keydown",function(e){return u=e.keyIdentifier||e.keyCode,o(n,e,e.keyCode)})}};if(window.postMessage&&!i.isOldIE){var u=1;t.nextTick=function(e,n){n=n||window;var r="zero-timeout-message-"+u;t.addListener(n,"message",function i(s){s.data==r&&(t.stopPropagation(s),t.removeListener(n,"message",i),e())}),n.postMessage(r,"*")}}t.nextFrame=window.requestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame,t.nextFrame?t.nextFrame=t.nextFrame.bind(window):t.nextFrame=function(e){setTimeout(e,17)}}),ace.define("ace/lib/keys",["require","exports","module","ace/lib/oop"],function(e,t,n){var r=e("./oop"),i=function(){var e={MODIFIER_KEYS:{16:"Shift",17:"Ctrl",18:"Alt",224:"Meta"},KEY_MODS:{ctrl:1,alt:2,option:2,shift:4,meta:8,command:8},FUNCTION_KEYS:{8:"Backspace",9:"Tab",13:"Return",19:"Pause",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"Print",45:"Insert",46:"Delete",96:"Numpad0",97:"Numpad1",98:"Numpad2",99:"Numpad3",100:"Numpad4",101:"Numpad5",102:"Numpad6",103:"Numpad7",104:"Numpad8",105:"Numpad9",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Numlock",145:"Scrolllock"},PRINTABLE_KEYS:{32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",107:"+",109:"-",110:".",188:",",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}};for(var t in e.FUNCTION_KEYS){var n=e.FUNCTION_KEYS[t].toLowerCase();e[n]=parseInt(t,10)}return r.mixin(e,e.MODIFIER_KEYS),r.mixin(e,e.PRINTABLE_KEYS),r.mixin(e,e.FUNCTION_KEYS),e.enter=e["return"],e.escape=e.esc,e.del=e["delete"],e[173]="-",e}();r.mixin(t,i),t.keyCodeToString=function(e){return(i[e]||String.fromCharCode(e)).toLowerCase()}}),ace.define("ace/lib/oop",["require","exports","module"],function(e,t,n){t.inherits=function(){var e=function(){};return function(t,n){e.prototype=n.prototype,t.super_=n.prototype,t.prototype=new e,t.prototype.constructor=t}}(),t.mixin=function(e,t){for(var n in t)e[n]=t[n]},t.implement=function(e,n){t.mixin(e,n)}}),ace.define("ace/lib/useragent",["require","exports","module"],function(e,t,n){t.OS={LINUX:"LINUX",MAC:"MAC",WINDOWS:"WINDOWS"},t.getOS=function(){return t.isMac?t.OS.MAC:t.isLinux?t.OS.LINUX:t.OS.WINDOWS};if(typeof navigator!="object")return;var r=(navigator.platform.match(/mac|win|linux/i)||["other"])[0].toLowerCase(),i=navigator.userAgent;t.isWin=r=="win",t.isMac=r=="mac",t.isLinux=r=="linux",t.isIE=navigator.appName=="Microsoft Internet Explorer"&&parseFloat(navigator.userAgent.match(/MSIE ([0-9]+[\.0-9]+)/)[1]),t.isOldIE=t.isIE&&t.isIE<9,t.isGecko=t.isMozilla=window.controllers&&window.navigator.product==="Gecko",t.isOldGecko=t.isGecko&&parseInt((navigator.userAgent.match(/rv\:(\d+)/)||[])[1],10)<4,t.isOpera=window.opera&&Object.prototype.toString.call(window.opera)=="[object Opera]",t.isWebKit=parseFloat(i.split("WebKit/")[1])||undefined,t.isChrome=parseFloat(i.split(" Chrome/")[1])||undefined,t.isAIR=i.indexOf("AdobeAIR")>=0,t.isIPad=i.indexOf("iPad")>=0,t.isTouchPad=i.indexOf("TouchPad")>=0}),ace.define("ace/editor",["require","exports","module","ace/lib/fixoldbrowsers","ace/lib/oop","ace/lib/lang","ace/lib/useragent","ace/keyboard/textinput","ace/mouse/mouse_handler","ace/mouse/fold_handler","ace/keyboard/keybinding","ace/edit_session","ace/search","ace/range","ace/lib/event_emitter","ace/commands/command_manager","ace/commands/default_commands"],function(e,t,n){e("./lib/fixoldbrowsers");var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/useragent"),o=e("./keyboard/textinput").TextInput,u=e("./mouse/mouse_handler").MouseHandler,a=e("./mouse/fold_handler").FoldHandler,f=e("./keyboard/keybinding").KeyBinding,l=e("./edit_session").EditSession,c=e("./search").Search,h=e("./range").Range,p=e("./lib/event_emitter").EventEmitter,d=e("./commands/command_manager").CommandManager,v=e("./commands/default_commands").commands,m=function(e,t){var n=e.getContainerElement();this.container=n,this.renderer=e,this.commands=new d(s.isMac?"mac":"win",v),this.textInput=new o(e.getTextAreaContainer(),this),this.renderer.textarea=this.textInput.getElement(),this.keyBinding=new f(this),this.$mouseHandler=new u(this),new a(this),this.$blockScrolling=0,this.$search=(new c).set({wrap:!0}),this.setSession(t||new l(""))};(function(){r.implement(this,p),this.setKeyboardHandler=function(e){this.keyBinding.setKeyboardHandler(e)},this.getKeyboardHandler=function(){return this.keyBinding.getKeyboardHandler()},this.setSession=function(e){if(this.session==e)return;if(this.session){var t=this.session;this.session.removeEventListener("change",this.$onDocumentChange),this.session.removeEventListener("changeMode",this.$onChangeMode),this.session.removeEventListener("tokenizerUpdate",this.$onTokenizerUpdate),this.session.removeEventListener("changeTabSize",this.$onChangeTabSize),this.session.removeEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.session.removeEventListener("changeWrapMode",this.$onChangeWrapMode),this.session.removeEventListener("onChangeFold",this.$onChangeFold),this.session.removeEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.session.removeEventListener("changeBackMarker",this.$onChangeBackMarker),this.session.removeEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.session.removeEventListener("changeAnnotation",this.$onChangeAnnotation),this.session.removeEventListener("changeOverwrite",this.$onCursorChange),this.session.removeEventListener("changeScrollTop",this.$onScrollTopChange),this.session.removeEventListener("changeLeftTop",this.$onScrollLeftChange);var n=this.session.getSelection();n.removeEventListener("changeCursor",this.$onCursorChange),n.removeEventListener("changeSelection",this.$onSelectionChange)}this.session=e,this.$onDocumentChange=this.onDocumentChange.bind(this),e.addEventListener("change",this.$onDocumentChange),this.renderer.setSession(e),this.$onChangeMode=this.onChangeMode.bind(this),e.addEventListener("changeMode",this.$onChangeMode),this.$onTokenizerUpdate=this.onTokenizerUpdate.bind(this),e.addEventListener("tokenizerUpdate",this.$onTokenizerUpdate),this.$onChangeTabSize=this.renderer.onChangeTabSize.bind(this.renderer),e.addEventListener("changeTabSize",this.$onChangeTabSize),this.$onChangeWrapLimit=this.onChangeWrapLimit.bind(this),e.addEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.$onChangeWrapMode=this.onChangeWrapMode.bind(this),e.addEventListener("changeWrapMode",this.$onChangeWrapMode),this.$onChangeFold=this.onChangeFold.bind(this),e.addEventListener("changeFold",this.$onChangeFold),this.$onChangeFrontMarker=this.onChangeFrontMarker.bind(this),this.session.addEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.$onChangeBackMarker=this.onChangeBackMarker.bind(this),this.session.addEventListener("changeBackMarker",this.$onChangeBackMarker),this.$onChangeBreakpoint=this.onChangeBreakpoint.bind(this),this.session.addEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.$onChangeAnnotation=this.onChangeAnnotation.bind(this),this.session.addEventListener("changeAnnotation",this.$onChangeAnnotation),this.$onCursorChange=this.onCursorChange.bind(this),this.session.addEventListener("changeOverwrite",this.$onCursorChange),this.$onScrollTopChange=this.onScrollTopChange.bind(this),this.session.addEventListener("changeScrollTop",this.$onScrollTopChange),this.$onScrollLeftChange=this.onScrollLeftChange.bind(this),this.session.addEventListener("changeScrollLeft",this.$onScrollLeftChange),this.selection=e.getSelection(),this.selection.addEventListener("changeCursor",this.$onCursorChange),this.$onSelectionChange=this.onSelectionChange.bind(this),this.selection.addEventListener("changeSelection",this.$onSelectionChange),this.onChangeMode(),this.$blockScrolling+=1,this.onCursorChange(),this.$blockScrolling-=1,this.onScrollTopChange(),this.onScrollLeftChange(),this.onSelectionChange(),this.onChangeFrontMarker(),this.onChangeBackMarker(),this.onChangeBreakpoint(),this.onChangeAnnotation(),this.session.getUseWrapMode()&&this.renderer.adjustWrapLimit(),this.renderer.updateFull(),this._emit("changeSession",{session:e,oldSession:t})},this.getSession=function(){return this.session},this.setValue=function(e,t){return this.session.doc.setValue(e),t?t==1?this.navigateFileEnd():t==-1&&this.navigateFileStart():this.selectAll(),e},this.getValue=function(){return this.session.getValue()},this.getSelection=function(){return this.selection},this.resize=function(e){this.renderer.onResize(e)},this.setTheme=function(e){this.renderer.setTheme(e)},this.getTheme=function(){return this.renderer.getTheme()},this.setStyle=function(e){this.renderer.setStyle(e)},this.unsetStyle=function(e){this.renderer.unsetStyle(e)},this.setFontSize=function(e){this.container.style.fontSize=e,this.renderer.updateFontSize()},this.$highlightBrackets=function(){this.session.$bracketHighlight&&(this.session.removeMarker(this.session.$bracketHighlight),this.session.$bracketHighlight=null);if(this.$highlightPending)return;var e=this;this.$highlightPending=!0,setTimeout(function(){e.$highlightPending=!1;var t=e.session.findMatchingBracket(e.getCursorPosition());if(t){var n=new h(t.row,t.column,t.row,t.column+1);e.session.$bracketHighlight=e.session.addMarker(n,"ace_bracket","text")}},50)},this.focus=function(){var e=this;setTimeout(function(){e.textInput.focus()}),this.textInput.focus()},this.isFocused=function(){return this.textInput.isFocused()},this.blur=function(){this.textInput.blur()},this.onFocus=function(){if(this.$isFocused)return;this.$isFocused=!0,this.renderer.showCursor(),this.renderer.visualizeFocus(),this._emit("focus")},this.onBlur=function(){if(!this.$isFocused)return;this.$isFocused=!1,this.renderer.hideCursor(),this.renderer.visualizeBlur(),this._emit("blur")},this.$cursorChange=function(){this.renderer.updateCursor()},this.onDocumentChange=function(e){var t=e.data,n=t.range,r;n.start.row==n.end.row&&t.action!="insertLines"&&t.action!="removeLines"?r=n.end.row:r=Infinity,this.renderer.updateLines(n.start.row,r),this._emit("change",e),this.$cursorChange()},this.onTokenizerUpdate=function(e){var t=e.data;this.renderer.updateLines(t.first,t.last)},this.onScrollTopChange=function(){this.renderer.scrollToY(this.session.getScrollTop())},this.onScrollLeftChange=function(){this.renderer.scrollToX(this.session.getScrollLeft())},this.onCursorChange=function(){this.$cursorChange(),this.$blockScrolling||this.renderer.scrollCursorIntoView(),this.$highlightBrackets(),this.$updateHighlightActiveLine(),this._emit("changeSelection")},this.$updateHighlightActiveLine=function(){var e=this.getSession(),t;this.$highlightActiveLine&&(this.$selectionStyle!="line"||!this.selection.isMultiLine())&&(t=this.getCursorPosition()),e.$highlightLineMarker&&!t?(e.removeMarker(e.$highlightLineMarker.id),e.$highlightLineMarker=null):!e.$highlightLineMarker&&t?e.$highlightLineMarker=e.highlightLines(t.row,t.row,"ace_active-line"):t&&(e.$highlightLineMarker.start.row=t.row,e.$highlightLineMarker.end.row=t.row,e._emit("changeBackMarker"))},this.onSelectionChange=function(e){var t=this.session;t.$selectionMarker&&t.removeMarker(t.$selectionMarker),t.$selectionMarker=null;if(!this.selection.isEmpty()){var n=this.selection.getRange(),r=this.getSelectionStyle();t.$selectionMarker=t.addMarker(n,"ace_selection",r)}else this.$updateHighlightActiveLine();var i=this.$highlightSelectedWord&&this.$getSelectionHighLightRegexp();this.session.highlight(i),this._emit("changeSelection")},this.$getSelectionHighLightRegexp=function(){var e=this.session,t=this.getSelectionRange();if(t.isEmpty()||t.isMultiLine())return;var n=t.start.column-1,r=t.end.column+1,i=e.getLine(t.start.row),s=i.length,o=i.substring(Math.max(n,0),Math.min(r,s));if(n>=0&&/^[\w\d]/.test(o)||r<=s&&/[\w\d]$/.test(o))return;o=i.substring(t.start.column,t.end.column);if(!/^[\w\d]+$/.test(o))return;var u=this.$search.$assembleRegExp({wholeWord:!0,caseSensitive:!0,needle:o});return u},this.onChangeFrontMarker=function(){this.renderer.updateFrontMarkers()},this.onChangeBackMarker=function(){this.renderer.updateBackMarkers()},this.onChangeBreakpoint=function(){this.renderer.updateBreakpoints()},this.onChangeAnnotation=function(){this.renderer.setAnnotations(this.session.getAnnotations())},this.onChangeMode=function(){this.renderer.updateText()},this.onChangeWrapLimit=function(){this.renderer.updateFull()},this.onChangeWrapMode=function(){this.renderer.onResize(!0)},this.onChangeFold=function(){this.$updateHighlightActiveLine(),this.renderer.updateFull()},this.getCopyText=function(){var e="";return this.selection.isEmpty()||(e=this.session.getTextRange(this.getSelectionRange())),this._emit("copy",e),e},this.onCopy=function(){this.commands.exec("copy",this)},this.onCut=function(){this.commands.exec("cut",this)},this.onPaste=function(e){if(this.$readOnly)return;this._emit("paste",e),this.insert(e)},this.execCommand=function(e,t){this.commands.exec(e,this,t)},this.insert=function(e){var t=this.session,n=t.getMode(),r=this.getCursorPosition();if(this.getBehavioursEnabled()){var i=n.transformAction(t.getState(r.row),"insertion",this,t,e);i&&(e=i.text)}e=e.replace(" ",this.session.getTabString());if(!this.selection.isEmpty())r=this.session.remove(this.getSelectionRange()),this.clearSelection();else if(this.session.getOverwrite()){var s=new h.fromPoints(r,r);s.end.column+=e.length,this.session.remove(s)}this.clearSelection();var o=r.column,u=t.getState(r.row),a=t.getLine(r.row),f=n.checkOutdent(u,a,e),l=t.insert(r,e);i&&i.selection&&(i.selection.length==2?this.selection.setSelectionRange(new h(r.row,o+i.selection[0],r.row,o+i.selection[1])):this.selection.setSelectionRange(new h(r.row+i.selection[0],i.selection[1],r.row+i.selection[2],i.selection[3])));if(t.getDocument().isNewLine(e)){var c=n.getNextLineIndent(u,a.slice(0,r.column),t.getTabString());this.moveCursorTo(r.row+1,0);var p=t.getTabSize(),d=Number.MAX_VALUE;for(var v=r.row+1;v<=l.row;++v){var m=0;a=t.getLine(v);for(var g=0;g<a.length;++g)if(a.charAt(g)==" ")m+=p;else{if(a.charAt(g)!=" ")break;m+=1}/[^\s]/.test(a)&&(d=Math.min(m,d))}for(var v=r.row+1;v<=l.row;++v){var y=d;a=t.getLine(v);for(var g=0;g<a.length&&y>0;++g)a.charAt(g)==" "?y-=p:a.charAt(g)==" "&&(y-=1);t.remove(new h(v,0,v,g))}t.indentRows(r.row+1,l.row,c)}f&&n.autoOutdent(u,t,r.row)},this.onTextInput=function(e){this.keyBinding.onTextInput(e)},this.onCommandKey=function(e,t,n){this.keyBinding.onCommandKey(e,t,n)},this.setOverwrite=function(e){this.session.setOverwrite(e)},this.getOverwrite=function(){return this.session.getOverwrite()},this.toggleOverwrite=function(){this.session.toggleOverwrite()},this.setScrollSpeed=function(e){this.$mouseHandler.setScrollSpeed(e)},this.getScrollSpeed=function(){return this.$mouseHandler.getScrollSpeed()},this.setDragDelay=function(e){this.$mouseHandler.setDragDelay(e)},this.getDragDelay=function(){return this.$mouseHandler.getDragDelay()},this.$selectionStyle="line",this.setSelectionStyle=function(e){if(this.$selectionStyle==e)return;this.$selectionStyle=e,this.onSelectionChange(),this._emit("changeSelectionStyle",{data:e})},this.getSelectionStyle=function(){return this.$selectionStyle},this.$highlightActiveLine=!0,this.setHighlightActiveLine=function(e){if(this.$highlightActiveLine==e)return;this.$highlightActiveLine=e,this.$updateHighlightActiveLine()},this.getHighlightActiveLine=function(){return this.$highlightActiveLine},this.$highlightGutterLine=!0,this.setHighlightGutterLine=function(e){if(this.$highlightGutterLine==e)return;this.renderer.setHighlightGutterLine(e),this.$highlightGutterLine=e},this.getHighlightGutterLine=function(){return this.$highlightGutterLine},this.$highlightSelectedWord=!0,this.setHighlightSelectedWord=function(e){if(this.$highlightSelectedWord==e)return;this.$highlightSelectedWord=e,this.$onSelectionChange()},this.getHighlightSelectedWord=function(){return this.$highlightSelectedWord},this.setAnimatedScroll=function(e){this.renderer.setAnimatedScroll(e)},this.getAnimatedScroll=function(){return this.renderer.getAnimatedScroll()},this.setShowInvisibles=function(e){this.renderer.setShowInvisibles(e)},this.getShowInvisibles=function(){return this.renderer.getShowInvisibles()},this.setDisplayIndentGuides=function(e){this.renderer.setDisplayIndentGuides(e)},this.getDisplayIndentGuides=function(){return this.renderer.getDisplayIndentGuides()},this.setShowPrintMargin=function(e){this.renderer.setShowPrintMargin(e)},this.getShowPrintMargin=function(){return this.renderer.getShowPrintMargin()},this.setPrintMarginColumn=function(e){this.renderer.setPrintMarginColumn(e)},this.getPrintMarginColumn=function(){return this.renderer.getPrintMarginColumn()},this.$readOnly=!1,this.setReadOnly=function(e){this.$readOnly=e},this.getReadOnly=function(){return this.$readOnly},this.$modeBehaviours=!0,this.setBehavioursEnabled=function(e){this.$modeBehaviours=e},this.getBehavioursEnabled=function(){return this.$modeBehaviours},this.$modeWrapBehaviours=!0,this.setWrapBehavioursEnabled=function(e){this.$modeWrapBehaviours=e},this.getWrapBehavioursEnabled=function(){return this.$modeWrapBehaviours},this.setShowFoldWidgets=function(e){var t=this.renderer.$gutterLayer;if(t.getShowFoldWidgets()==e)return;this.renderer.$gutterLayer.setShowFoldWidgets(e),this.$showFoldWidgets=e,this.renderer.updateFull()},this.getShowFoldWidgets=function(){return this.renderer.$gutterLayer.getShowFoldWidgets()},this.setFadeFoldWidgets=function(e){this.renderer.setFadeFoldWidgets(e)},this.getFadeFoldWidgets=function(){return this.renderer.getFadeFoldWidgets()},this.remove=function(e){this.selection.isEmpty()&&(e=="left"?this.selection.selectLeft():this.selection.selectRight());var t=this.getSelectionRange();if(this.getBehavioursEnabled()){var n=this.session,r=n.getState(t.start.row),i=n.getMode().transformAction(r,"deletion",this,n,t);i&&(t=i)}this.session.remove(t),this.clearSelection()},this.removeWordRight=function(){this.selection.isEmpty()&&this.selection.selectWordRight(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeWordLeft=function(){this.selection.isEmpty()&&this.selection.selectWordLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineStart=function(){this.selection.isEmpty()&&this.selection.selectLineStart(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineEnd=function(){this.selection.isEmpty()&&this.selection.selectLineEnd();var e=this.getSelectionRange();e.start.column==e.end.column&&e.start.row==e.end.row&&(e.end.column=0,e.end.row++),this.session.remove(e),this.clearSelection()},this.splitLine=function(){this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection());var e=this.getCursorPosition();this.insert("\n"),this.moveCursorToPosition(e)},this.transposeLetters=function(){if(!this.selection.isEmpty())return;var e=this.getCursorPosition(),t=e.column;if(t===0)return;var n=this.session.getLine(e.row),r,i;t<n.length?(r=n.charAt(t)+n.charAt(t-1),i=new h(e.row,t-1,e.row,t+1)):(r=n.charAt(t-1)+n.charAt(t-2),i=new h(e.row,t-2,e.row,t)),this.session.replace(i,r)},this.toLowerCase=function(){var e=this.getSelectionRange();this.selection.isEmpty()&&this.selection.selectWord();var t=this.getSelectionRange(),n=this.session.getTextRange(t);this.session.replace(t,n.toLowerCase()),this.selection.setSelectionRange(e)},this.toUpperCase=function(){var e=this.getSelectionRange();this.selection.isEmpty()&&this.selection.selectWord();var t=this.getSelectionRange(),n=this.session.getTextRange(t);this.session.replace(t,n.toUpperCase()),this.selection.setSelectionRange(e)},this.indent=function(){var e=this.session,t=this.getSelectionRange();if(!(t.start.row<t.end.row||t.start.column<t.end.column)){var r;if(this.session.getUseSoftTabs()){var s=e.getTabSize(),o=this.getCursorPosition(),u=e.documentToScreenColumn(o.row,o.column),a=s-u%s;r=i.stringRepeat(" ",a)}else r=" ";return this.insert(r)}var n=this.$getSelectedRows();e.indentRows(n.first,n.last," ")},this.blockOutdent=function(){var e=this.session.getSelection();this.session.outdentRows(e.getRange())},this.sortLines=function(){var e=this.$getSelectedRows(),t=this.session,n=[];for(i=e.first;i<=e.last;i++)n.push(t.getLine(i));n.sort(function(e,t){return e.toLowerCase()<t.toLowerCase()?-1:e.toLowerCase()>t.toLowerCase()?1:0});var r=new h(0,0,0,0);for(var i=e.first;i<=e.last;i++){var s=t.getLine(i);r.start.row=i,r.end.row=i,r.end.column=s.length,t.replace(r,n[i-e.first])}},this.toggleCommentLines=function(){var e=this.session.getState(this.getCursorPosition().row),t=this.$getSelectedRows();this.session.getMode().toggleCommentLines(e,this.session,t.first,t.last)},this.getNumberAt=function(e,t){var n=/[\-]?[0-9]+(?:\.[0-9]+)?/g;n.lastIndex=0;var r=this.session.getLine(e);while(n.lastIndex<t-1){var i=n.exec(r);if(i.index<=t&&i.index+i[0].length>=t){var s={value:i[0],start:i.index,end:i.index+i[0].length};return s}}return null},this.modifyNumber=function(e){var t=this.selection.getCursor().row,n=this.selection.getCursor().column,r=new h(t,n-1,t,n),i=this.session.getTextRange(r);if(!isNaN(parseFloat(i))&&isFinite(i)){var s=this.getNumberAt(t,n);if(s){var o=s.value.indexOf(".")>=0?s.start+s.value.indexOf(".")+1:s.end,u=s.start+s.value.length-o,a=parseFloat(s.value);a*=Math.pow(10,u),o!==s.end&&n<o?e*=Math.pow(10,s.end-n-1):e*=Math.pow(10,s.end-n),a+=e,a/=Math.pow(10,u);var f=a.toFixed(u),l=new h(t,s.start,t,s.end);this.session.replace(l,f),this.moveCursorTo(t,Math.max(s.start+1,n+f.length-s.value.length))}}},this.removeLines=function(){var e=this.$getSelectedRows(),t;e.first===0||e.last+1<this.session.getLength()?t=new h(e.first,0,e.last+1,0):t=new h(e.first-1,this.session.getLine(e.first-1).length,e.last,this.session.getLine(e.last).length),this.session.remove(t),this.clearSelection()},this.duplicateSelection=function(){var e=this.selection,t=this.session,n=e.getRange();if(n.isEmpty()){var r=n.start.row;t.duplicateLines(r,r)}else{var i=e.isBackwards(),s=e.isBackwards()?n.start:n.end,o=t.insert(s,t.getTextRange(n),!1);n.start=s,n.end=o,e.setSelectionRange(n,i)}},this.moveLinesDown=function(){this.$moveLines(function(e,t){return this.session.moveLinesDown(e,t)})},this.moveLinesUp=function(){this.$moveLines(function(e,t){return this.session.moveLinesUp(e,t)})},this.moveText=function(e,t){return this.$readOnly?null:this.session.moveText(e,t)},this.copyLinesUp=function(){this.$moveLines(function(e,t){return this.session.duplicateLines(e,t),0})},this.copyLinesDown=function(){this.$moveLines(function(e,t){return this.session.duplicateLines(e,t)})},this.$moveLines=function(e){var t=this.$getSelectedRows(),n=this.selection;if(!n.isMultiLine())var r=n.getRange(),i=n.isBackwards();var s=e.call(this,t.first,t.last);r?(r.start.row+=s,r.end.row+=s,n.setSelectionRange(r,i)):(n.setSelectionAnchor(t.last+s+1,0),n.$moveSelection(function(){n.moveCursorTo(t.first+s,0)}))},this.$getSelectedRows=function(){var e=this.getSelectionRange().collapseRows();return{first:e.start.row,last:e.end.row}},this.onCompositionStart=function(e){this.renderer.showComposition(this.getCursorPosition())},this.onCompositionUpdate=function(e){this.renderer.setCompositionText(e)},this.onCompositionEnd=function(){this.renderer.hideComposition()},this.getFirstVisibleRow=function(){return this.renderer.getFirstVisibleRow()},this.getLastVisibleRow=function(){return this.renderer.getLastVisibleRow()},this.isRowVisible=function(e){return e>=this.getFirstVisibleRow()&&e<=this.getLastVisibleRow()},this.isRowFullyVisible=function(e){return e>=this.renderer.getFirstFullyVisibleRow()&&e<=this.renderer.getLastFullyVisibleRow()},this.$getVisibleRowCount=function(){return this.renderer.getScrollBottomRow()-this.renderer.getScrollTopRow()+1},this.$moveByPage=function(e,t){var n=this.renderer,r=this.renderer.layerConfig,i=e*Math.floor(r.height/r.lineHeight);this.$blockScrolling++,t==1?this.selection.$moveSelection(function(){this.moveCursorBy(i,0)}):t==0&&(this.selection.moveCursorBy(i,0),this.selection.clearSelection()),this.$blockScrolling--;var s=n.scrollTop;n.scrollBy(0,i*r.lineHeight),t!=null&&n.scrollCursorIntoView(null,.5),n.animateScrolling(s)},this.selectPageDown=function(){this.$moveByPage(1,!0)},this.selectPageUp=function(){this.$moveByPage(-1,!0)},this.gotoPageDown=function(){this.$moveByPage(1,!1)},this.gotoPageUp=function(){this.$moveByPage(-1,!1)},this.scrollPageDown=function(){this.$moveByPage(1)},this.scrollPageUp=function(){this.$moveByPage(-1)},this.scrollToRow=function(e){this.renderer.scrollToRow(e)},this.scrollToLine=function(e,t,n,r){this.renderer.scrollToLine(e,t,n,r)},this.centerSelection=function(){var e=this.getSelectionRange(),t={row:Math.floor(e.start.row+(e.end.row-e.start.row)/2),column:Math.floor(e.start.column+(e.end.column-e.start.column)/2)};this.renderer.alignCursor(t,.5)},this.getCursorPosition=function(){return this.selection.getCursor()},this.getCursorPositionScreen=function(){return this.session.documentToScreenPosition(this.getCursorPosition())},this.getSelectionRange=function(){return this.selection.getRange()},this.selectAll=function(){this.$blockScrolling+=1,this.selection.selectAll(),this.$blockScrolling-=1},this.clearSelection=function(){this.selection.clearSelection()},this.moveCursorTo=function(e,t){this.selection.moveCursorTo(e,t)},this.moveCursorToPosition=function(e){this.selection.moveCursorToPosition(e)},this.jumpToMatching=function(e){var t=this.getCursorPosition(),n=this.session.getBracketRange(t);if(!n){n=this.find({needle:/[{}()\[\]]/g,preventScroll:!0,start:{row:t.row,column:t.column-1}});if(!n)return;var r=n.start;r.row==t.row&&Math.abs(r.column-t.column)<2&&(n=this.session.getBracketRange(r))}r=n&&n.cursor||r,r&&(e?n&&n.isEqual(this.getSelectionRange())?this.clearSelection():this.selection.selectTo(r.row,r.column):(this.clearSelection(),this.moveCursorTo(r.row,r.column)))},this.gotoLine=function(e,t,n){this.selection.clearSelection(),this.session.unfold({row:e-1,column:t||0}),this.$blockScrolling+=1,this.moveCursorTo(e-1,t||0),this.$blockScrolling-=1,this.isRowFullyVisible(e-1)||this.scrollToLine(e-1,!0,n)},this.navigateTo=function(e,t){this.clearSelection(),this.moveCursorTo(e,t)},this.navigateUp=function(e){this.selection.clearSelection(),e=e||1,this.selection.moveCursorBy(-e,0)},this.navigateDown=function(e){this.selection.clearSelection(),e=e||1,this.selection.moveCursorBy(e,0)},this.navigateLeft=function(e){if(!this.selection.isEmpty()){var t=this.getSelectionRange().start;this.moveCursorToPosition(t)}else{e=e||1;while(e--)this.selection.moveCursorLeft()}this.clearSelection()},this.navigateRight=function(e){if(!this.selection.isEmpty()){var t=this.getSelectionRange().end;this.moveCursorToPosition(t)}else{e=e||1;while(e--)this.selection.moveCursorRight()}this.clearSelection()},this.navigateLineStart=function(){this.selection.moveCursorLineStart(),this.clearSelection()},this.navigateLineEnd=function(){this.selection.moveCursorLineEnd(),this.clearSelection()},this.navigateFileEnd=function(){var e=this.renderer.scrollTop;this.selection.moveCursorFileEnd(),this.clearSelection(),this.renderer.animateScrolling(e)},this.navigateFileStart=function(){var e=this.renderer.scrollTop;this.selection.moveCursorFileStart(),this.clearSelection(),this.renderer.animateScrolling(e)},this.navigateWordRight=function(){this.selection.moveCursorWordRight(),this.clearSelection()},this.navigateWordLeft=function(){this.selection.moveCursorWordLeft(),this.clearSelection()},this.replace=function(e,t){t&&this.$search.set(t);var n=this.$search.find(this.session),r=0;return n?(this.$tryReplace(n,e)&&(r=1),n!==null&&(this.selection.setSelectionRange(n),this.renderer.scrollSelectionIntoView(n.start,n.end)),r):r},this.replaceAll=function(e,t){t&&this.$search.set(t);var n=this.$search.findAll(this.session),r=0;if(!n.length)return r;this.$blockScrolling+=1;var i=this.getSelectionRange();this.clearSelection(),this.selection.moveCursorTo(0,0);for(var s=n.length-1;s>=0;--s)this.$tryReplace(n[s],e)&&r++;return this.selection.setSelectionRange(i),this.$blockScrolling-=1,r},this.$tryReplace=function(e,t){var n=this.session.getTextRange(e);return t=this.$search.replace(n,t),t!==null?(e.end=this.session.replace(e,t),e):null},this.getLastSearchOptions=function(){return this.$search.getOptions()},this.find=function(e,t,n){t||(t={}),typeof e=="string"||e instanceof RegExp?t.needle=e:typeof e=="object"&&r.mixin(t,e);var i=this.selection.getRange();t.needle==null&&(e=this.session.getTextRange(i)||this.$search.$options.needle,e||(i=this.session.getWordRange(i.start.row,i.start.column),e=this.session.getTextRange(i)),this.$search.set({needle:e})),this.$search.set(t),t.start||this.$search.set({start:i});var s=this.$search.find(this.session);if(t.preventScroll)return s;if(s)return this.revealRange(s,n),s;t.backwards?i.start=i.end:i.end=i.start,this.selection.setRange(i)},this.findNext=function(e,t){this.find({skipCurrent:!0,backwards:!1},e,t)},this.findPrevious=function(e,t){this.find(e,{skipCurrent:!0,backwards:!0},t)},this.revealRange=function(e,t){this.$blockScrolling+=1,this.session.unfold(e),this.selection.setSelectionRange(e),this.$blockScrolling-=1;var n=this.renderer.scrollTop;this.renderer.scrollSelectionIntoView(e.start,e.end,.5),t!=0&&this.renderer.animateScrolling(n)},this.undo=function(){this.$blockScrolling++,this.session.getUndoManager().undo(),this.$blockScrolling--,this.renderer.scrollCursorIntoView(null,.5)},this.redo=function(){this.$blockScrolling++,this.session.getUndoManager().redo(),this.$blockScrolling--,this.renderer.scrollCursorIntoView(null,.5)},this.destroy=function(){this.renderer.destroy()}}).call(m.prototype),t.Editor=m}),ace.define("ace/lib/lang",["require","exports","module"],function(e,t,n){t.stringReverse=function(e){return e.split("").reverse().join("")},t.stringRepeat=function(e,t){return(new Array(t+1)).join(e)};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n<r;n++)e[n]&&typeof e[n]=="object"?t[n]=this.copyObject(e[n]):t[n]=e[n];return t},t.deepCopy=function(e){if(typeof e!="object")return e;var t=e.constructor();for(var n in e)typeof e[n]=="object"?t[n]=this.deepCopy(e[n]):t[n]=e[n];return t},t.arrayToMap=function(e){var t={};for(var n=0;n<e.length;n++)t[e[n]]=1;return t},t.createMap=function(e){var t=Object.create(null);for(var n in e)t[n]=e[n];return t},t.arrayRemove=function(e,t){for(var n=0;n<=e.length;n++)t===e[n]&&e.splice(n,1)},t.escapeRegExp=function(e){return e.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")},t.escapeHTML=function(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<")},t.getMatchOffsets=function(e,t){var n=[];return e.replace(t,function(e){n.push({offset:arguments[arguments.length-2],length:e.length})}),n},t.deferredCall=function(e){var t=null,n=function(){t=null,e()},r=function(e){return r.cancel(),t=setTimeout(n,e||0),r};return r.schedule=r,r.call=function(){return this.cancel(),e(),r},r.cancel=function(){return clearTimeout(t),t=null,r},r},t.delayedCall=function(e,t){var n=null,r=function(){n=null,e()},i=function(e){n&&clearTimeout(n),n=setTimeout(r,e||t)};return i.delay=i,i.schedule=function(e){n==null&&(n=setTimeout(r,e||0))},i.call=function(){this.cancel(),e()},i.cancel=function(){n&&clearTimeout(n),n=null},i.isPending=function(){return n},i}}),ace.define("ace/keyboard/textinput",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/lib/dom","ace/lib/lang"],function(e,t,n){var r=e("../lib/event"),i=e("../lib/useragent"),s=e("../lib/dom"),o=e("../lib/lang"),u=function(e,t){function g(e){if(c)return;var t=e?2:1,r=2;try{n.setSelectionRange(t,r)}catch(i){}}function y(){if(c)return;n.value=u,i.isWebKit&&m.schedule()}function D(){setTimeout(function(){h&&(n.style.cssText=h,h=""),t.renderer.$keepTextAreaAtCursor==null&&(t.renderer.$keepTextAreaAtCursor=!0,t.renderer.$moveTextAreaToCursor())},0)}var n=s.createElement("textarea");n.className="ace_text-input",i.isTouchPad&&n.setAttribute("x-palm-disable-auto-cap",!0),n.wrap="off",n.autocorrect="off",n.autocapitalize="off",n.spellcheck=!1,n.style.top="-2em",e.insertBefore(n,e.firstChild);var u="",a=!1,f=!1,l=!1,c=!1,h="",p=!0,d=document.activeElement===n;r.addListener(n,"blur",function(){t.onBlur(),d=!1}),r.addListener(n,"focus",function(){d=!0,t.onFocus(),g()}),this.focus=function(){n.focus()},this.blur=function(){n.blur()},this.isFocused=function(){return d};var v=o.delayedCall(function(){d&&g(p)}),m=o.delayedCall(function(){c||(n.value=u,d&&g())});i.isWebKit||t.addEventListener("changeSelection",function(){t.selection.isEmpty()!=p&&(p=!p,v.schedule())}),y(),d&&t.onFocus();var b=function(e){return e.selectionStart===0&&e.selectionEnd===e.value.length};!n.setSelectionRange&&n.createTextRange&&(n.setSelectionRange=function(e,t){var n=this.createTextRange();n.collapse(!0),n.moveStart("character",e),n.moveEnd("character",t),n.select()},b=function(e){try{var t=e.ownerDocument.selection.createRange()}catch(n){}return!t||t.parentElement()!=e?!1:t.text==e.value});if(i.isOldIE){var w=!1,E=function(e){if(w)return;var t=n.value;if(c||!t||t==u)return;if(e&&t==u[0])return S.schedule();N(t),w=!0,y(),w=!1},S=o.delayedCall(E);r.addListener(n,"propertychange",E);var x={13:1,27:1};r.addListener(n,"keyup",function(e){c&&(!n.value||x[e.keyCode])&&setTimeout(_,0);if((n.value.charCodeAt(0)||0)<129)return;c?M():O()})}var T=function(e){if(a){a=!1;return}if(f){f=!1;return}b(n)&&(t.selectAll(),g())},N=function(e){l?(g(),e&&t.onPaste(e),l=!1):e==u[0]?t.execCommand("del",{source:"ace"}):(e.substring(0,2)==u?e=e.substr(2):e[0]==u[0]?e=e.substr(1):e[e.length-1]==u[0]&&(e=e.slice(0,-1)),e[e.length-1]==u[0]&&(e=e.slice(0,-1)),e&&t.onTextInput(e))},C=function(e){if(c)return;var t=n.value;y(),N(t)},k=function(e){var i=t.getCopyText();if(!i){r.preventDefault(e);return}var s=e.clipboardData||window.clipboardData;if(s){var o=s.setData("Text",i);o&&(t.onCut(),r.preventDefault(e))}o||(a=!0,n.value=i,n.select(),setTimeout(function(){a=!1,y(),g(),t.onCut()}))},L=function(e){var i=t.getCopyText();if(!i){r.preventDefault(e);return}var s=e.clipboardData||window.clipboardData;if(s){var o=s.setData("Text",i);o&&(t.onCopy(),r.preventDefault(e))}o||(f=!0,n.value=i,n.select(),setTimeout(function(){f=!1,y(),g(),t.onCopy()}))},A=function(e){var s=e.clipboardData||window.clipboardData;if(s){var o=s.getData("Text");o&&t.onPaste(o),i.isIE&&setTimeout(g),r.preventDefault(e)}else n.value="",l=!0};r.addCommandKeyListener(n,t.onCommandKey.bind(t)),r.addListener(n,"select",T),r.addListener(n,"input",C),r.addListener(n,"cut",k),r.addListener(n,"copy",L),r.addListener(n,"paste",A),(!("oncut"in n)||!("oncopy"in n)||!("onpaste"in n))&&r.addListener(e,"keydown",function(e){if(i.isMac&&!e.metaKey||!e.ctrlKey)return;switch(e.keyCode){case 67:L(e);break;case 86:A(e);break;case 88:k(e)}});var O=function(e){c=!0,t.onCompositionStart(),setTimeout(M,0)},M=function(){if(!c)return;t.onCompositionUpdate(n.value)},_=function(e){c=!1,t.onCompositionEnd()};r.addListener(n,"compositionstart",O),i.isGecko?r.addListener(n,"text",M):r.addListener(n,"keyup",M),r.addListener(n,"compositionend",_),this.getElement=function(){return n},this.onContextMenu=function(e){h||(h=n.style.cssText),n.style.cssText="z-index:100000;"+(i.isIE?"opacity:0.1;":""),g(t.selection.isEmpty()),t._emit("nativecontextmenu",{target:t});var s=t.container.getBoundingClientRect(),o=function(e){n.style.left=e.clientX-s.left-2+"px",n.style.top=e.clientY-s.top-2+"px"};o(e);if(e.type!="mousedown")return;t.renderer.$keepTextAreaAtCursor&&(t.renderer.$keepTextAreaAtCursor=null),i.isWin&&r.capture(t.container,o,D)},this.onContextMenuClose=D,i.isGecko||r.addListener(n,"contextmenu",function(e){t.textInput.onContextMenu(e),D()})};t.TextInput=u}),ace.define("ace/mouse/mouse_handler",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/mouse/default_handlers","ace/mouse/default_gutter_handler","ace/mouse/mouse_event","ace/mouse/dragdrop"],function(e,t,n){var r=e("../lib/event"),i=e("../lib/useragent"),s=e("./default_handlers").DefaultHandlers,o=e("./default_gutter_handler").GutterHandler,u=e("./mouse_event").MouseEvent,a=e("./dragdrop").DragdropHandler,f=function(e){this.editor=e,new s(this),new o(this),new a(this),r.addListener(e.container,"mousedown",function(t){return e.focus(),r.preventDefault(t)});var t=e.renderer.getMouseEventTarget();r.addListener(t,"click",this.onMouseEvent.bind(this,"click")),r.addListener(t,"mousemove",this.onMouseMove.bind(this,"mousemove")),r.addMultiMouseDownListener(t,[300,300,250],this,"onMouseEvent"),r.addMouseWheelListener(e.container,this.onMouseWheel.bind(this,"mousewheel"));var n=e.renderer.$gutter;r.addListener(n,"mousedown",this.onMouseEvent.bind(this,"guttermousedown")),r.addListener(n,"click",this.onMouseEvent.bind(this,"gutterclick")),r.addListener(n,"dblclick",this.onMouseEvent.bind(this,"gutterdblclick")),r.addListener(n,"mousemove",this.onMouseEvent.bind(this,"guttermousemove"))};(function(){this.$scrollSpeed=1,this.setScrollSpeed=function(e){this.$scrollSpeed=e},this.getScrollSpeed=function(){return this.$scrollSpeed},this.onMouseEvent=function(e,t){this.editor._emit(e,new u(t,this.editor))},this.$dragDelay=250,this.setDragDelay=function(e){this.$dragDelay=e},this.getDragDelay=function(){return this.$dragDelay},this.onMouseMove=function(e,t){var n=this.editor._eventRegistry&&this.editor._eventRegistry.mousemove;if(!n||!n.length)return;this.editor._emit(e,new u(t,this.editor))},this.onMouseWheel=function(e,t){var n=new u(t,this.editor);n.speed=this.$scrollSpeed*2,n.wheelX=t.wheelX,n.wheelY=t.wheelY,this.editor._emit(e,n)},this.setState=function(e){this.state=e},this.captureMouse=function(e,t){t&&this.setState(t),this.x=e.x,this.y=e.y,this.isMousePressed=!0;var n=this.editor.renderer;n.$keepTextAreaAtCursor&&(n.$keepTextAreaAtCursor=null);var s=this,o=function(e){s.x=e.clientX,s.y=e.clientY},u=function(e){clearInterval(f),s[s.state+"End"]&&s[s.state+"End"](e),s.$clickSelection=null,n.$keepTextAreaAtCursor==null&&(n.$keepTextAreaAtCursor=!0,n.$moveTextAreaToCursor()),s.isMousePressed=!1},a=function(){s[s.state]&&s[s.state]()};if(i.isOldIE&&e.domEvent.type=="dblclick"){setTimeout(function(){a(),u(e.domEvent)});return}r.capture(this.editor.container,o,u);var f=setInterval(a,20)}}).call(f.prototype),t.MouseHandler=f}),ace.define("ace/mouse/default_handlers",["require","exports","module","ace/lib/dom","ace/lib/useragent"],function(e,t,n){function o(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler("mousedown",this.onMouseDown.bind(e)),t.setDefaultHandler("dblclick",this.onDoubleClick.bind(e)),t.setDefaultHandler("tripleclick",this.onTripleClick.bind(e)),t.setDefaultHandler("quadclick",this.onQuadClick.bind(e)),t.setDefaultHandler("mousewheel",this.onMouseWheel.bind(e));var n=["select","startSelect","drag","dragEnd","dragWait","dragWaitEnd","startDrag","focusWait"];n.forEach(function(t){e[t]=this[t]},this),e.selectByLines=this.extendSelectionBy.bind(e,"getLineRange"),e.selectByWords=this.extendSelectionBy.bind(e,"getWordRange"),e.$focusWaitTimout=250}function u(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}function a(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.column-e.end.column;else var n=2*t.row-e.start.row-e.end.row;return n<0?{cursor:e.start,anchor:e.end}:{cursor:e.end,anchor:e.start}}var r=e("../lib/dom"),i=e("../lib/useragent"),s=5;(function(){this.onMouseDown=function(e){var t=e.inSelection(),n=e.getDocumentPosition();this.mousedownEvent=e;var r=this.editor,i=e.getButton();if(i!==0){var s=r.getSelectionRange(),o=s.isEmpty();o&&(r.moveCursorToPosition(n),r.selection.clearSelection()),r.textInput.onContextMenu(e.domEvent);return}if(t&&!r.isFocused()){r.focus();if(this.$focusWaitTimout&&!this.$clickSelection)return this.setState("focusWait"),this.captureMouse(e),e.preventDefault()}return!t||this.$clickSelection||e.getShiftKey()?this.startSelect(n):t&&(this.mousedownEvent.time=(new Date).getTime(),this.setState("dragWait")),this.captureMouse(e),e.preventDefault()},this.startSelect=function(e){e=e||this.editor.renderer.screenToTextCoordinates(this.x,this.y),this.mousedownEvent.getShiftKey()?this.editor.selection.selectToPosition(e):this.$clickSelection||(this.editor.moveCursorToPosition(e),this.editor.selection.clearSelection()),this.setState("select")},this.select=function(){var e,t=this.editor,n=t.renderer.screenToTextCoordinates(this.x,this.y);if(this.$clickSelection){var r=this.$clickSelection.comparePoint(n);if(r==-1)e=this.$clickSelection.end;else if(r==1)e=this.$clickSelection.start;else{var i=a(this.$clickSelection,n);n=i.cursor,e=i.anchor}t.selection.setSelectionAnchor(e.row,e.column)}t.selection.selectToPosition(n),t.renderer.scrollCursorIntoView()},this.extendSelectionBy=function(e){var t,n=this.editor,r=n.renderer.screenToTextCoordinates(this.x,this.y),i=n.selection[e](r.row,r.column);if(this.$clickSelection){var s=this.$clickSelection.comparePoint(i.start),o=this.$clickSelection.comparePoint(i.end);if(s==-1&&o<=0){t=this.$clickSelection.end;if(i.end.row!=r.row||i.end.column!=r.column)r=i.start}else if(o==1&&s>=0){t=this.$clickSelection.start;if(i.start.row!=r.row||i.start.column!=r.column)r=i.end}else if(s==-1&&o==1)r=i.end,t=i.start;else{var u=a(this.$clickSelection,r);r=u.cursor,t=u.anchor}n.selection.setSelectionAnchor(t.row,t.column)}n.selection.selectToPosition(r),n.renderer.scrollCursorIntoView()},this.startDrag=function(){var e=this.editor;this.setState("drag"),this.dragRange=e.getSelectionRange();var t=e.getSelectionStyle();this.dragSelectionMarker=e.session.addMarker(this.dragRange,"ace_selection",t),e.clearSelection(),r.addCssClass(e.container,"ace_dragging"),this.$dragKeybinding||(this.$dragKeybinding={handleKeyboard:function(e,t,n,r){if(n=="esc")return{command:this.command}},command:{exec:function(e){var t=e.$mouseHandler;t.dragCursor=null,t.dragEnd(),t.startSelect()}}}),e.keyBinding.addKeyboardHandler(this.$dragKeybinding)},this.focusWait=function(){var e=u(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y),t=(new Date).getTime();(e>s||t-this.mousedownEvent.time>this.$focusWaitTimout)&&this.startSelect()},this.dragWait=function(e){var t=u(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y),n=(new Date).getTime(),r=this.editor;t>s?this.startSelect(this.mousedownEvent.getDocumentPosition()):n-this.mousedownEvent.time>r.getDragDelay()&&this.startDrag()},this.dragWaitEnd=function(e){this.mousedownEvent.domEvent=e,this.startSelect()},this.drag=function(){var e=this.editor;this.dragCursor=e.renderer.screenToTextCoordinates(this.x,this.y),e.moveCursorToPosition(this.dragCursor),e.renderer.scrollCursorIntoView()},this.dragEnd=function(e){var t=this.editor,n=this.dragCursor,i=this.dragRange;r.removeCssClass(t.container,"ace_dragging"),t.session.removeMarker(this.dragSelectionMarker),t.keyBinding.removeKeyboardHandler(this.$dragKeybinding);if(!n)return;t.clearSelection();if(e&&(e.ctrlKey||e.altKey)){var s=t.session,o=i;o.end=s.insert(n,s.getTextRange(i)),o.start=n}else{if(i.contains(n.row,n.column))return;var o=t.moveText(i,n)}if(!o)return;t.selection.setSelectionRange(o)},this.onDoubleClick=function(e){var t=e.getDocumentPosition(),n=this.editor,r=n.session,i=r.getBracketRange(t);if(i){i.isEmpty()&&(i.start.column--,i.end.column++),this.$clickSelection=i,this.setState("select");return}this.$clickSelection=n.selection.getWordRange(t.row,t.column),this.setState("selectByWords")},this.onTripleClick=function(e){var t=e.getDocumentPosition(),n=this.editor;this.setState("selectByLines"),this.$clickSelection=n.selection.getLineRange(t.row)},this.onQuadClick=function(e){var t=this.editor;t.selectAll(),this.$clickSelection=t.getSelectionRange(),this.setState("null")},this.onMouseWheel=function(e){if(e.getShiftKey()||e.getAccelKey())return;var t=this.editor,n=t.renderer.isScrollableBy(e.wheelX*e.speed,e.wheelY*e.speed);if(n)this.$passScrollEvent=!1;else{if(this.$passScrollEvent)return;if(!this.$scrollStopTimeout){var r=this;this.$scrollStopTimeout=setTimeout(function(){r.$passScrollEvent=!0,r.$scrollStopTimeout=null},200)}}return t.renderer.scrollBy(e.wheelX*e.speed,e.wheelY*e.speed),e.preventDefault()}}).call(o.prototype),t.DefaultHandlers=o}),ace.define("ace/mouse/default_gutter_handler",["require","exports","module","ace/lib/dom","ace/lib/event"],function(e,t,n){function s(e){function f(){u=r.createElement("div"),u.className="ace_gutter-tooltip",u.style.maxWidth="500px",u.style.display="none",t.container.appendChild(u)}function l(){u||f();var e=o.getDocumentPosition().row,r=n.$annotations[e];if(!r)return c();var i=t.session.getLength();if(e==i){var s=t.renderer.pixelToScreenCoordinates(0,o.y).row,l=o.$pos;if(s>t.session.documentToScreenRow(l.row,l.column))return c()}if(a==r)return;a=r.text.join("<br/>"),u.style.display="block",u.innerHTML=a,t.on("mousewheel",c),h(o)}function c(){s&&(s=clearTimeout(s)),a&&(u.style.display="none",a=null,t.removeEventListener("mousewheel",c))}function h(e){var n=t.renderer.$gutter.getBoundingClientRect();u.style.left=e.x-n.left+15+"px",e.y+3*t.renderer.lineHeight+15<n.bottom?(u.style.bottom="",u.style.top=e.y-n.top+15+"px"):(u.style.top="",u.style.bottom=n.bottom-e.y+5+"px")}var t=e.editor,n=t.renderer.$gutterLayer;e.editor.setDefaultHandler("guttermousedown",function(r){if(!t.isFocused())return;var i=n.getRegion(r);if(i)return;var s=r.getDocumentPosition().row,o=t.session.selection;if(r.getShiftKey())o.selectTo(s,0);else{if(r.domEvent.detail==2)return t.selectAll(),r.preventDefault();e.$clickSelection=t.selection.getLineRange(s)}return e.captureMouse(r,"selectByLines"),r.preventDefault()});var s,o,u,a;e.editor.setDefaultHandler("guttermousemove",function(t){var n=t.domEvent.target||t.domEvent.srcElement;if(r.hasCssClass(n,"ace_fold-widget"))return c();a&&h(t),o=t;if(s)return;s=setTimeout(function(){s=null,o&&!e.isMousePressed?l():c()},50)}),i.addListener(t.renderer.$gutter,"mouseout",function(e){o=null;if(!a||s)return;s=setTimeout(function(){s=null,c()},50)})}var r=e("../lib/dom"),i=e("../lib/event");t.GutterHandler=s}),ace.define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"],function(e,t,n){var r=e("../lib/event"),i=e("../lib/useragent"),s=t.MouseEvent=function(e,t){this.domEvent=e,this.editor=t,this.x=this.clientX=e.clientX,this.y=this.clientY=e.clientY,this.$pos=null,this.$inSelection=null,this.propagationStopped=!1,this.defaultPrevented=!1};(function(){this.stopPropagation=function(){r.stopPropagation(this.domEvent),this.propagationStopped=!0},this.preventDefault=function(){r.preventDefault(this.domEvent),this.defaultPrevented=!0},this.stop=function(){this.stopPropagation(),this.preventDefault()},this.getDocumentPosition=function(){return this.$pos?this.$pos:(this.$pos=this.editor.renderer.screenToTextCoordinates(this.clientX,this.clientY),this.$pos)},this.inSelection=function(){if(this.$inSelection!==null)return this.$inSelection;var e=this.editor;if(e.getReadOnly())this.$inSelection=!1;else{var t=e.getSelectionRange();if(t.isEmpty())this.$inSelection=!1;else{var n=this.getDocumentPosition();this.$inSelection=t.contains(n.row,n.column)}}return this.$inSelection},this.getButton=function(){return r.getButton(this.domEvent)},this.getShiftKey=function(){return this.domEvent.shiftKey},this.getAccelKey=i.isMac?function(){return this.domEvent.metaKey}:function(){return this.domEvent.ctrlKey}}).call(s.prototype)}),ace.define("ace/mouse/dragdrop",["require","exports","module","ace/lib/event"],function(e,t,n){var r=e("../lib/event"),i=function(e){var t=e.editor,n,i,s,o,u,a,f,l=0,c=t.container;r.addListener(c,"dragenter",function(e){l++;if(!n){u=t.getSelectionRange(),a=t.selection.isBackwards();var i=t.getSelectionStyle();n=t.session.addMarker(u,"ace_selection",i),t.clearSelection(),clearInterval(o),o=setInterval(h,20)}return r.preventDefault(e)}),r.addListener(c,"dragover",function(e){return i=e.clientX,s=e.clientY,r.preventDefault(e)});var h=function(){f=t.renderer.screenToTextCoordinates(i,s),t.moveCursorToPosition(f),t.renderer.scrollCursorIntoView()};r.addListener(c,"dragleave",function(e){l--;if(l>0)return;return console.log(e.type,l,e.target),clearInterval(o),t.session.removeMarker(n),n=null,t.selection.setSelectionRange(u,a),r.preventDefault(e)}),r.addListener(c,"drop",function(e){return console.log(e.type,l,e.target),l=0,clearInterval(o),t.session.removeMarker(n),n=null,u.end=t.session.insert(f,e.dataTransfer.getData("Text")),u.start=f,t.focus(),t.selection.setSelectionRange(u),r.preventDefault(e)})};t.DragdropHandler=i}),ace.define("ace/mouse/fold_handler",["require","exports","module"],function(e,t,n){function r(e){e.on("click",function(t){var n=t.getDocumentPosition(),r=e.session,i=r.getFoldAt(n.row,n.column,1);i&&(t.getAccelKey()?r.removeFold(i):r.expandFold(i),t.stop())}),e.on("guttermousedown",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session;i.foldWidgets&&i.foldWidgets[r]&&e.session.onFoldWidgetClick(r,t),t.stop()}}),e.on("gutterdblclick",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session,s=i.foldWidgets;if(!s||s[r])return;var o=r-1,u;while(o>=0){var a=s[o];a==null&&(a=s[o]=i.getFoldWidget());if(a=="start"){var f=i.getFoldWidgetRange(o);u||(u=f);if(f&&f.end.row>=r)break}o--}o==-1&&(f=u);if(f){var r=f.start.row,l=i.getFoldAt(r,i.getLine(r).length,1);l?i.removeFold(l):(i.addFold("...",f),e.renderer.scrollCursorIntoView({row:f.start.row,column:0}))}t.stop()}})}t.FoldHandler=r}),ace.define("ace/keyboard/keybinding",["require","exports","module","ace/lib/keys","ace/lib/event"],function(e,t,n){var r=e("../lib/keys"),i=e("../lib/event"),s=function(e){this.$editor=e,this.$data={},this.$handlers=[],this.setDefaultHandler(e.commands)};(function(){this.setDefaultHandler=function(e){this.removeKeyboardHandler(this.$defaultHandler),this.$defaultHandler=e,this.addKeyboardHandler(e,0),this.$data={editor:this.$editor}},this.setKeyboardHandler=function(e){if(this.$handlers[this.$handlers.length-1]==e)return;while(this.$handlers[1])this.removeKeyboardHandler(this.$handlers[1]);this.addKeyboardHandler(e,1)},this.addKeyboardHandler=function(e,t){if(!e)return;var n=this.$handlers.indexOf(e);n!=-1&&this.$handlers.splice(n,1),t==undefined?this.$handlers.push(e):this.$handlers.splice(t,0,e),n==-1&&e.attach&&e.attach(this.$editor)},this.removeKeyboardHandler=function(e){var t=this.$handlers.indexOf(e);return t==-1?!1:(this.$handlers.splice(t,1),e.detach&&e.detach(this.$editor),!0)},this.getKeyboardHandler=function(){return this.$handlers[this.$handlers.length-1]},this.$callKeyboardHandlers=function(e,t,n,r){var s;for(var o=this.$handlers.length;o--;){s=this.$handlers[o].handleKeyboard(this.$data,e,t,n,r);if(s&&s.command)break}if(!s||!s.command)return!1;var u=!1,a=this.$editor.commands;return s.command!="null"?u=a.exec(s.command,this.$editor,s.args,r):u=s.passEvent!=1,u&&r&&e!=-1&&i.stopEvent(r),u},this.onCommandKey=function(e,t,n){var i=r.keyCodeToString(n);this.$callKeyboardHandlers(t,i,n,e)},this.onTextInput=function(e){var t=this.$callKeyboardHandlers(-1,e);t||this.$editor.commands.exec("insertstring",this.$editor,e)}}).call(s.prototype),t.KeyBinding=s}),ace.define("ace/edit_session",["require","exports","module","ace/config","ace/lib/oop","ace/lib/lang","ace/lib/net","ace/lib/event_emitter","ace/selection","ace/mode/text","ace/range","ace/document","ace/background_tokenizer","ace/search_highlight","ace/edit_session/folding","ace/edit_session/bracket_match"],function(e,t,n){var r=e("./config"),i=e("./lib/oop"),s=e("./lib/lang"),o=e("./lib/net"),u=e("./lib/event_emitter").EventEmitter,a=e("./selection").Selection,f=e("./mode/text").Mode,l=e("./range").Range,c=e("./document").Document,h=e("./background_tokenizer").BackgroundTokenizer,p=e("./search_highlight").SearchHighlight,d=function(e,t){this.$breakpoints=[],this.$decorations=[],this.$frontMarkers={},this.$backMarkers={},this.$markerId=1,this.$undoSelect=!0,this.$foldData=[],this.$foldData.toString=function(){var e="";return this.forEach(function(t){e+="\n"+t.toString()}),e},this.on("changeFold",this.onChangeFold.bind(this)),this.$onChange=this.onChange.bind(this);if(typeof e!="object"||!e.getLine)e=new c(e);this.setDocument(e),this.selection=new a(this),this.setMode(t)};(function(){function y(e){return e<4352?!1:e>=4352&&e<=4447||e>=4515&&e<=4519||e>=4602&&e<=4607||e>=9001&&e<=9002||e>=11904&&e<=11929||e>=11931&&e<=12019||e>=12032&&e<=12245||e>=12272&&e<=12283||e>=12288&&e<=12350||e>=12353&&e<=12438||e>=12441&&e<=12543||e>=12549&&e<=12589||e>=12593&&e<=12686||e>=12688&&e<=12730||e>=12736&&e<=12771||e>=12784&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=13054||e>=13056&&e<=19903||e>=19968&&e<=42124||e>=42128&&e<=42182||e>=43360&&e<=43388||e>=44032&&e<=55203||e>=55216&&e<=55238||e>=55243&&e<=55291||e>=63744&&e<=64255||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=65281&&e<=65376||e>=65504&&e<=65510}i.implement(this,u),this.setDocument=function(e){this.doc&&this.doc.removeListener("change",this.$onChange),this.doc=e,e.on("change",this.$onChange),this.bgTokenizer&&this.bgTokenizer.setDocument(this.getDocument()),this.resetCaches()},this.getDocument=function(){return this.doc},this.$resetRowCache=function(e){if(!e){this.$docRowCache=[],this.$screenRowCache=[];return}var t=this.$getRowCacheIndex(this.$docRowCache,e)+1,n=this.$docRowCache.length;this.$docRowCache.splice(t,n),this.$screenRowCache.splice(t,n)},this.$getRowCacheIndex=function(e,t){var n=0,r=e.length-1;while(n<=r){var i=n+r>>1,s=e[i];if(t>s)n=i+1;else{if(!(t<s))return i;r=i-1}}return n-1},this.resetCaches=function(){this.$modified=!0,this.$wrapData=[],this.$rowLengthCache=[],this.$resetRowCache(0),this.bgTokenizer&&this.bgTokenizer.start(0)},this.onChangeFold=function(e){var t=e.data;this.$resetRowCache(t.start.row)},this.onChange=function(e){var t=e.data;this.$modified=!0,this.$resetRowCache(t.range.start.row);var n=this.$updateInternalDataOnChange(e);!this.$fromUndo&&this.$undoManager&&!t.ignore&&(this.$deltasDoc.push(t),n&&n.length!=0&&this.$deltasFold.push({action:"removeFolds",folds:n}),this.$informUndoManager.schedule()),this.bgTokenizer.$updateOnChange(t),this._emit("change",e)},this.setValue=function(e){this.doc.setValue(e),this.selection.moveCursorTo(0,0),this.selection.clearSelection(),this.$resetRowCache(0),this.$deltas=[],this.$deltasDoc=[],this.$deltasFold=[],this.getUndoManager().reset()},this.getValue=this.toString=function(){return this.doc.getValue()},this.getSelection=function(){return this.selection},this.getState=function(e){return this.bgTokenizer.getState(e)},this.getTokens=function(e){return this.bgTokenizer.getTokens(e)},this.getTokenAt=function(e,t){var n=this.bgTokenizer.getTokens(e),r,i=0;if(t==null)s=n.length-1,i=this.getLine(e).length;else for(var s=0;s<n.length;s++){i+=n[s].value.length;if(i>=t)break}return r=n[s],r?(r.index=s,r.start=i-r.value.length,r):null},this.setUndoManager=function(e){this.$undoManager=e,this.$deltas=[],this.$deltasDoc=[],this.$deltasFold=[],this.$informUndoManager&&this.$informUndoManager.cancel();if(e){var t=this;this.$syncInformUndoManager=function(){t.$informUndoManager.cancel(),t.$deltasFold.length&&(t.$deltas.push({group:"fold",deltas:t.$deltasFold}),t.$deltasFold=[]),t.$deltasDoc.length&&(t.$deltas.push({group:"doc",deltas:t.$deltasDoc}),t.$deltasDoc=[]),t.$deltas.length>0&&e.execute({action:"aceupdate",args:[t.$deltas,t]}),t.$deltas=[]},this.$informUndoManager=s.deferredCall(this.$syncInformUndoManager)}},this.$defaultUndoManager={undo:function(){},redo:function(){},reset:function(){}},this.getUndoManager=function(){return this.$undoManager||this.$defaultUndoManager},this.getTabString=function(){return this.getUseSoftTabs()?s.stringRepeat(" ",this.getTabSize()):" "},this.$useSoftTabs=!0,this.setUseSoftTabs=function(e){if(this.$useSoftTabs===e)return;this.$useSoftTabs=e},this.getUseSoftTabs=function(){return this.$useSoftTabs},this.$tabSize=4,this.setTabSize=function(e){if(isNaN(e)||this.$tabSize===e)return;this.$modified=!0,this.$rowLengthCache=[],this.$tabSize=e,this._emit("changeTabSize")},this.getTabSize=function(){return this.$tabSize},this.isTabStop=function(e){return this.$useSoftTabs&&e.column%this.$tabSize==0},this.$overwrite=!1,this.setOverwrite=function(e){if(this.$overwrite==e)return;this.$overwrite=e,this._emit("changeOverwrite")},this.getOverwrite=function(){return this.$overwrite},this.toggleOverwrite=function(){this.setOverwrite(!this.$overwrite)},this.addGutterDecoration=function(e,t){this.$decorations[e]||(this.$decorations[e]=""),this.$decorations[e]+=" "+t,this._emit("changeBreakpoint",{})},this.removeGutterDecoration=function(e,t){this.$decorations[e]=(this.$decorations[e]||"").replace(" "+t,""),this._emit("changeBreakpoint",{})},this.getBreakpoints=function(){return this.$breakpoints},this.setBreakpoints=function(e){this.$breakpoints=[];for(var t=0;t<e.length;t++)this.$breakpoints[e[t]]="ace_breakpoint";this._emit("changeBreakpoint",{})},this.clearBreakpoints=function(){this.$breakpoints=[],this._emit("changeBreakpoint",{})},this.setBreakpoint=function(e,t){t===undefined&&(t="ace_breakpoint"),t?this.$breakpoints[e]=t:delete this.$breakpoints[e],this._emit("changeBreakpoint",{})},this.clearBreakpoint=function(e){delete this.$breakpoints[e],this._emit("changeBreakpoint",{})},this.addMarker=function(e,t,n,r){var i=this.$markerId++,s={range:e,type:n||"line",renderer:typeof n=="function"?n:null,clazz:t,inFront:!!r,id:i};return r?(this.$frontMarkers[i]=s,this._emit("changeFrontMarker")):(this.$backMarkers[i]=s,this._emit("changeBackMarker")),i},this.addDynamicMarker=function(e,t){if(!e.update)return;var n=this.$markerId++;return e.id=n,e.inFront=!!t,t?(this.$frontMarkers[n]=e,this._emit("changeFrontMarker")):(this.$backMarkers[n]=e,this._emit("changeBackMarker")),e},this.removeMarker=function(e){var t=this.$frontMarkers[e]||this.$backMarkers[e];if(!t)return;var n=t.inFront?this.$frontMarkers:this.$backMarkers;t&&(delete n[e],this._emit(t.inFront?"changeFrontMarker":"changeBackMarker"))},this.getMarkers=function(e){return e?this.$frontMarkers:this.$backMarkers},this.highlight=function(e){if(!this.$searchHighlight){var t=new p(null,"ace_selected-word","text");this.$searchHighlight=this.addDynamicMarker(t)}this.$searchHighlight.setRegexp(e)},this.highlightLines=function(e,t,n,r){typeof t!="number"&&(n=t,t=e),n||(n="ace_step");var i=new l(e,0,t,Infinity),s=this.addMarker(i,n,"fullLine",r);return i.id=s,i},this.setAnnotations=function(e){this.$annotations=e,this._emit("changeAnnotation",{})},this.getAnnotations=function(){return this.$annotations||[]},this.clearAnnotations=function(){this.$annotations={},this._emit("changeAnnotation",{})},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r?\n)/m);t?this.$autoNewLine=t[1]:this.$autoNewLine="\n"},this.getWordRange=function(e,t){var n=this.getLine(e),r=!1;t>0&&(r=!!n.charAt(t-1).match(this.tokenRe)),r||(r=!!n.charAt(t).match(this.tokenRe));if(r)var i=this.tokenRe;else if(/^\s+$/.test(n.slice(t-1,t+1)))var i=/\s/;else var i=this.nonTokenRe;var s=t;if(s>0){do s--;while(s>=0&&n.charAt(s).match(i));s++}var o=t;while(o<n.length&&n.charAt(o).match(i))o++;return new l(e,s,e,o)},this.getAWordRange=function(e,t){var n=this.getWordRange(e,t),r=this.getLine(n.end.row);while(r.charAt(n.end.column).match(/[ \t]/))n.end.column+=1;return n},this.setNewLineMode=function(e){this.doc.setNewLineMode(e)},this.getNewLineMode=function(){return this.doc.getNewLineMode()},this.$useWorker=!0,this.setUseWorker=function(e){if(this.$useWorker==e)return;this.$useWorker=e,this.$stopWorker(),e&&this.$startWorker()},this.getUseWorker=function(){return this.$useWorker},this.onReloadTokenizer=function(e){var t=e.data;this.bgTokenizer.start(t.first),this._emit("tokenizerUpdate",e)},this.$modes={},this._loadMode=function(t,n){function a(e){if(i.$modes[t])return n(i.$modes[t]);i.$modes[t]=new e.Mode,i.$modes[t].$id=t,i._emit("loadmode",{name:t,mode:i.$modes[t]}),n(i.$modes[t])}function l(e,t){if(!r.get("packaged"))return t();o.loadScript(r.moduleUrl(e,"mode"),t)}this.$modes["null"]||(this.$modes["null"]=this.$modes["ace/mode/text"]=new f);if(this.$modes[t])return n(this.$modes[t]);var i=this,s;try{s=e(t)}catch(u){}if(s&&s.Mode)return a(s);this.$mode||this.$setModePlaceholder(),l(t,function(){e([t],a)})},this.$setModePlaceholder=function(){this.$mode=this.$modes["null"];var e=this.$mode.getTokenizer();if(!this.bgTokenizer){this.bgTokenizer=new h(e);var t=this;this.bgTokenizer.addEventListener("update",function(e){t._emit("tokenizerUpdate",e)})}else this.bgTokenizer.setTokenizer(e);this.bgTokenizer.setDocument(this.getDocument()),this.tokenRe=this.$mode.tokenRe,this.nonTokenRe=this.$mode.nonTokenRe},this.$mode=null,this.$modeId=null,this.setMode=function(e){e=e||"null";if(typeof e=="string"){if(this.$modeId==e)return;this.$modeId=e;var t=this;this._loadMode(e,function(n){if(t.$modeId!==e)return;t.setMode(n)});return}if(this.$mode===e)return;this.$mode=e,this.$modeId=e.$id,this.$stopWorker(),this.$useWorker&&this.$startWorker();var n=e.getTokenizer();if(n.addEventListener!==undefined){var r=this.onReloadTokenizer.bind(this);n.addEventListener("update",r)}if(!this.bgTokenizer){this.bgTokenizer=new h(n);var t=this;this.bgTokenizer.addEventListener("update",function(e){t._emit("tokenizerUpdate",e)})}else this.bgTokenizer.setTokenizer(n);this.bgTokenizer.setDocument(this.getDocument()),this.bgTokenizer.start(0),this.tokenRe=e.tokenRe,this.nonTokenRe=e.nonTokenRe,this.$setFolding(e.foldingRules),this._emit("changeMode")},this.$stopWorker=function(){this.$worker&&this.$worker.terminate(),this.$worker=null},this.$startWorker=function(){if(typeof Worker!="undefined"&&!e.noWorker)try{this.$worker=this.$mode.createWorker(this)}catch(t){console.log("Could not load worker"),console.log(t),this.$worker=null}else this.$worker=null},this.getMode=function(){return this.$mode},this.$scrollTop=0,this.setScrollTop=function(e){e=Math.round(Math.max(0,e));if(this.$scrollTop===e)return;this.$scrollTop=e,this._emit("changeScrollTop",e)},this.getScrollTop=function(){return this.$scrollTop},this.$scrollLeft=0,this.setScrollLeft=function(e){e=Math.round(Math.max(0,e));if(this.$scrollLeft===e)return;this.$scrollLeft=e,this._emit("changeScrollLeft",e)},this.getScrollLeft=function(){return this.$scrollLeft},this.getScreenWidth=function(){return this.$computeWidth(),this.screenWidth},this.$computeWidth=function(e){if(this.$modified||e){this.$modified=!1;if(this.$useWrapMode)return this.screenWidth=this.$wrapLimit;var t=this.doc.getAllLines(),n=this.$rowLengthCache,r=0,i=0,s=this.$foldData[i],o=s?s.start.row:Infinity,u=t.length;for(var a=0;a<u;a++){if(a>o){a=s.end.row+1;if(a>=u)break;s=this.$foldData[i++],o=s?s.start.row:Infinity}n[a]==null&&(n[a]=this.$getStringScreenWidth(t[a])[0]),n[a]>r&&(r=n[a])}this.screenWidth=r}},this.getLine=function(e){return this.doc.getLine(e)},this.getLines=function(e,t){return this.doc.getLines(e,t)},this.getLength=function(){return this.doc.getLength()},this.getTextRange=function(e){return this.doc.getTextRange(e||this.selection.getRange())},this.insert=function(e,t){return this.doc.insert(e,t)},this.remove=function(e){return this.doc.remove(e)},this.undoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;var n=null;for(var r=e.length-1;r!=-1;r--){var i=e[r];i.group=="doc"?(this.doc.revertDeltas(i.deltas),n=this.$getUndoSelection(i.deltas,!0,n)):i.deltas.forEach(function(e){this.addFolds(e.folds)},this)}return this.$fromUndo=!1,n&&this.$undoSelect&&!t&&this.selection.setSelectionRange(n),n},this.redoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;var n=null;for(var r=0;r<e.length;r++){var i=e[r];i.group=="doc"&&(this.doc.applyDeltas(i.deltas),n=this.$getUndoSelection(i.deltas,!1,n))}return this.$fromUndo=!1,n&&this.$undoSelect&&!t&&this.selection.setSelectionRange(n),n},this.setUndoSelect=function(e){this.$undoSelect=e},this.$getUndoSelection=function(e,t,n){function r(e){var n=e.action=="insertText"||e.action=="insertLines";return t?!n:n}var i=e[0],s,o,u=!1;r(i)?(s=i.range.clone(),u=!0):(s=l.fromPoints(i.range.start,i.range.start),u=!1);for(var a=1;a<e.length;a++)i=e[a],r(i)?(o=i.range.start,s.compare(o.row,o.column)==-1&&s.setStart(i.range.start),o=i.range.end,s.compare(o.row,o.column)==1&&s.setEnd(i.range.end),u=!0):(o=i.range.start,s.compare(o.row,o.column)==-1&&(s=l.fromPoints(i.range.start,i.range.start)),u=!1);if(n!=null){var f=n.compareRange(s);f==1?s.setStart(n.start):f==-1&&s.setEnd(n.end)}return s},this.replace=function(e,t){return this.doc.replace(e,t)},this.moveText=function(e,t){var n=this.getTextRange(e);this.remove(e);var r=t.row,i=t.column;!e.isMultiLine()&&e.start.row==r&&e.end.column<i&&(i-=n.length);if(e.isMultiLine()&&e.end.row<r){var s=this.doc.$split(n);r-=s.length-1}var o=r+e.end.row-e.start.row,u=e.isMultiLine()?e.end.column:i+e.end.column-e.start.column,a=new l(r,i,o,u);return this.insert(a.start,n),a},this.indentRows=function(e,t,n){n=n.replace(/\t/g,this.getTabString());for(var r=e;r<=t;r++)this.insert({row:r,column:0},n)},this.outdentRows=function(e){var t=e.collapseRows(),n=new l(0,0,0,0),r=this.getTabSize();for(var i=t.start.row;i<=t.end.row;++i){var s=this.getLine(i);n.start.row=i,n.end.row=i;for(var o=0;o<r;++o)if(s.charAt(o)!=" ")break;o<r&&s.charAt(o)==" "?(n.start.column=o,n.end.column=o+1):(n.start.column=0,n.end.column=o),this.remove(n)}},this.moveLinesUp=function(e,t){if(e<=0)return 0;var n=this.doc.removeLines(e,t);return this.doc.insertLines(e-1,n),-1},this.moveLinesDown=function(e,t){if(t>=this.doc.getLength()-1)return 0;var n=this.doc.removeLines(e,t);return this.doc.insertLines(e+1,n),1},this.duplicateLines=function(e,t){var e=this.$clipRowToDocument(e),t=this.$clipRowToDocument(t),n=this.getLines(e,t);this.doc.insertLines(e,n);var r=t-e+1;return r},this.$clipRowToDocument=function(e){return Math.max(0,Math.min(e,this.doc.getLength()-1))},this.$clipColumnToRow=function(e,t){return t<0?0:Math.min(this.doc.getLine(e).length,t)},this.$clipPositionToDocument=function(e,t){t=Math.max(0,t);if(e<0)e=0,t=0;else{var n=this.doc.getLength();e>=n?(e=n-1,t=this.doc.getLine(n-1).length):t=Math.min(this.doc.getLine(e).length,t)}return{row:e,column:t}},this.$clipRangeToDocument=function(e){e.start.row<0?(e.start.row=0,e.start.column=0):e.start.column=this.$clipColumnToRow(e.start.row,e.start.column);var t=this.doc.getLength()-1;return e.end.row>t?(e.end.row=t,e.end.column=this.doc.getLine(t).length):e.end.column=this.$clipColumnToRow(e.end.row,e.end.column),e},this.$wrapLimit=80,this.$useWrapMode=!1,this.$wrapLimitRange={min:null,max:null},this.setUseWrapMode=function(e){if(e!=this.$useWrapMode){this.$useWrapMode=e,this.$modified=!0,this.$resetRowCache(0);if(e){var t=this.getLength();this.$wrapData=[];for(var n=0;n<t;n++)this.$wrapData.push([]);this.$updateWrapData(0,t-1)}this._emit("changeWrapMode")}},this.getUseWrapMode=function(){return this.$useWrapMode},this.setWrapLimitRange=function(e,t){if(this.$wrapLimitRange.min!==e||this.$wrapLimitRange.max!==t)this.$wrapLimitRange.min=e,this.$wrapLimitRange.max=t,this.$modified=!0,this._emit("changeWrapMode")},this.adjustWrapLimit=function(e){var t=this.$constrainWrapLimit(e);return t!=this.$wrapLimit&&t>0?(this.$wrapLimit=t,this.$modified=!0,this.$useWrapMode&&(this.$updateWrapData(0,this.getLength()-1),this.$resetRowCache(0),this._emit("changeWrapLimit")),!0):!1},this.$constrainWrapLimit=function(e){var t=this.$wrapLimitRange.min;t&&(e=Math.max(t,e));var n=this.$wrapLimitRange.max;return n&&(e=Math.min(n,e)),Math.max(1,e)},this.getWrapLimit=function(){return this.$wrapLimit},this.getWrapLimitRange=function(){return{min:this.$wrapLimitRange.min,max:this.$wrapLimitRange.max}},this.$updateInternalDataOnChange=function(e){var t=this.$useWrapMode,n,r=e.data.action,i=e.data.range.start.row,s=e.data.range.end.row,o=e.data.range.start,u=e.data.range.end,a=null;r.indexOf("Lines")!=-1?(r=="insertLines"?s=i+e.data.lines.length:s=i,n=e.data.lines?e.data.lines.length:s-i):n=s-i;if(n!=0)if(r.indexOf("remove")!=-1){this[t?"$wrapData":"$rowLengthCache"].splice(i,n);var f=this.$foldData;a=this.getFoldsInRange(e.data.range),this.removeFolds(a);var l=this.getFoldLine(u.row),c=0;if(l){l.addRemoveChars(u.row,u.column,o.column-u.column),l.shiftRow(-n);var h=this.getFoldLine(i);h&&h!==l&&(h.merge(l),l=h),c=f.indexOf(l)+1}for(c;c<f.length;c++){var l=f[c];l.start.row>=u.row&&l.shiftRow(-n)}s=i}else{var p;if(t){p=[i,0];for(var d=0;d<n;d++)p.push([]);this.$wrapData.splice.apply(this.$wrapData,p)}else p=Array(n),p.unshift(i,0),this.$rowLengthCache.splice.apply(this.$rowLengthCache,p);var f=this.$foldData,l=this.getFoldLine(i),c=0;if(l){var v=l.range.compareInside(o.row,o.column);v==0?(l=l.split(o.row,o.column),l.shiftRow(n),l.addRemoveChars(s,0,u.column-o.column)):v==-1&&(l.addRemoveChars(i,0,u.column-o.column),l.shiftRow(n)),c=f.indexOf(l)+1}for(c;c<f.length;c++){var l=f[c];l.start.row>=i&&l.shiftRow(n)}}else{n=Math.abs(e.data.range.start.column-e.data.range.end.column),r.indexOf("remove")!=-1&&(a=this.getFoldsInRange(e.data.range),this.removeFolds(a),n=-n);var l=this.getFoldLine(i);l&&l.addRemoveChars(i,o.column,n)}return t&&this.$wrapData.length!=this.doc.getLength()&&console.error("doc.getLength() and $wrapData.length have to be the same!"),t?this.$updateWrapData(i,s):this.$updateRowLengthCache(i,s),a},this.$updateRowLengthCache=function(e,t,n){this.$rowLengthCache[e]=null,this.$rowLengthCache[t]=null},this.$updateWrapData=function(e,t){var n=this.doc.getAllLines(),r=this.getTabSize(),i=this.$wrapData,o=this.$wrapLimit,u,f,l=e;t=Math.min(t,n.length-1);while(l<=t){f=this.getFoldLine(l,f);if(!f)u=this.$getDisplayTokens(s.stringTrimRight(n[l])),i[l]=this.$computeWrapSplits(u,o,r),l++;else{u=[],f.walk(function(e,t,r,i){var s;if(e!=null){s=this.$getDisplayTokens(e,u.length),s[0]=a;for(var o=1;o<s.length;o++)s[o]=c}else s=this.$getDisplayTokens(n[t].substring(i,r),u.length);u=u.concat(s)}.bind(this),f.end.row,n[f.end.row].length+1);while(u.length!=0&&u[u.length-1]>=v)u.pop();i[f.start.row]=this.$computeWrapSplits(u,o,r),l=f.end.row+1}}};var t=1,n=2,a=3,c=4,d=9,v=10,m=11,g=12;this.$computeWrapSplits=function(e,t){function o(t){var r=e.slice(i,t),o=r.length;r.join("").replace(/12/g,function(){o-=1}).replace(/2/g,function(){o-=1}),s+=o,n.push(s),i=t}if(e.length==0)return[];var n=[],r=e.length,i=0,s=0;while(r-i>t){var u=i+t;if(e[u]>=v){while(e[u]>=v)u++;o(u);continue}if(e[u]==a||e[u]==c){for(u;u!=i-1;u--)if(e[u]==a)break;if(u>i){o(u);continue}u=i+t;for(u;u<e.length;u++)if(e[u]!=c)break;if(u==e.length)break;o(u);continue}var f=Math.max(u-10,i-1);while(u>f&&e[u]<a)u--;while(u>f&&e[u]==d)u--;if(u>f){o(++u);continue}u=i+t,o(u)}return n},this.$getDisplayTokens=function(e,r){var i=[],s;r=r||0;for(var o=0;o<e.length;o++){var u=e.charCodeAt(o);if(u==9){s=this.getScreenTabSize(i.length+r),i.push(m);for(var a=1;a<s;a++)i.push(g)}else u==32?i.push(v):u>39&&u<48||u>57&&u<64?i.push(d):u>=4352&&y(u)?i.push(t,n):i.push(t)}return i},this.$getStringScreenWidth=function(e,t,n){if(t==0)return[0,0];t==null&&(t=Infinity),n=n||0;var r,i;for(i=0;i<e.length;i++){r=e.charCodeAt(i),r==9?n+=this.getScreenTabSize(n):r>=4352&&y(r)?n+=2:n+=1;if(n>t)break}return[n,i]},this.getRowLength=function(e){return!this.$useWrapMode||!this.$wrapData[e]?1:this.$wrapData[e].length+1},this.getScreenLastRowColumn=function(e){var t=this.screenToDocumentPosition(e,Number.MAX_VALUE);return this.documentToScreenColumn(t.row,t.column)},this.getDocumentLastRowColumn=function(e,t){var n=this.documentToScreenRow(e,t);return this.getScreenLastRowColumn(n)},this.getDocumentLastRowColumnPosition=function(e,t){var n=this.documentToScreenRow(e,t);return this.screenToDocumentPosition(n,Number.MAX_VALUE/10)},this.getRowSplitData=function(e){return this.$useWrapMode?this.$wrapData[e]:undefined},this.getScreenTabSize=function(e){return this.$tabSize-e%this.$tabSize},this.screenToDocumentRow=function(e,t){return this.screenToDocumentPosition(e,t).row},this.screenToDocumentColumn=function(e,t){return this.screenToDocumentPosition(e,t).column},this.screenToDocumentPosition=function(e,t){if(e<0)return{row:0,column:0};var n,r=0,i=0,s,o=0,u=0,a=this.$screenRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var o=a[f],r=this.$docRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getLength()-1,p=this.getNextFoldLine(r),d=p?p.start.row:Infinity;while(o<=e){u=this.getRowLength(r);if(o+u-1>=e||r>=h)break;o+=u,r++,r>d&&(r=p.end.row+1,p=this.getNextFoldLine(r,p),d=p?p.start.row:Infinity),c&&(this.$docRowCache.push(r),this.$screenRowCache.push(o))}if(p&&p.start.row<=r)n=this.getFoldDisplayLine(p),r=p.start.row;else{if(o+u<=e||r>h)return{row:h,column:this.getLine(h).length};n=this.getLine(r),p=null}if(this.$useWrapMode){var v=this.$wrapData[r];v&&(s=v[e-o],e>o&&v.length&&(i=v[e-o-1]||v[v.length-1],n=n.substring(i)))}return i+=this.$getStringScreenWidth(n,t)[1],this.$useWrapMode&&i>=s&&(i=s-1),p?p.idxToPosition(i):{row:r,column:i}},this.documentToScreenPosition=function(e,t){if(typeof t=="undefined")var n=this.$clipPositionToDocument(e.row,e.column);else n=this.$clipPositionToDocument(e,t);e=n.row,t=n.column;var r=0,i=null,s=null;s=this.getFoldAt(e,t,1),s&&(e=s.start.row,t=s.start.column);var o,u=0,a=this.$docRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var u=a[f],r=this.$screenRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getNextFoldLine(u),p=h?h.start.row:Infinity;while(u<e){if(u>=p){o=h.end.row+1;if(o>e)break;h=this.getNextFoldLine(o,h),p=h?h.start.row:Infinity}else o=u+1;r+=this.getRowLength(u),u=o,c&&(this.$docRowCache.push(u),this.$screenRowCache.push(r))}var d="";h&&u>=p?(d=this.getFoldDisplayLine(h,e,t),i=h.start.row):(d=this.getLine(e).substring(0,t),i=e);if(this.$useWrapMode){var v=this.$wrapData[i],m=0;while(d.length>=v[m])r++,m++;d=d.substring(v[m-1]||0,d.length)}return{row:r,column:this.$getStringScreenWidth(d)[0]}},this.documentToScreenColumn=function(e,t){return this.documentToScreenPosition(e,t).column},this.documentToScreenRow=function(e,t){return this.documentToScreenPosition(e,t).row},this.getScreenLength=function(){var e=0,t=null;if(!this.$useWrapMode){e=this.getLength();var n=this.$foldData;for(var r=0;r<n.length;r++)t=n[r],e-=t.end.row-t.start.row}else{var i=this.$wrapData.length,s=0,r=0,t=this.$foldData[r++],o=t?t.start.row:Infinity;while(s<i)e+=this.$wrapData[s].length+1,s++,s>o&&(s=t.end.row+1,t=this.$foldData[r++],o=t?t.start.row:Infinity)}return e}}).call(d.prototype),e("./edit_session/folding").Folding.call(d.prototype),e("./edit_session/bracket_match").BracketMatch.call(d.prototype),t.EditSession=d}),ace.define("ace/config",["require","exports","module","ace/lib/lang"],function(e,t,n){"no use strict";function o(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCase()})}var r=e("./lib/lang"),i=function(){return this}(),s={packaged:!1,workerPath:null,modePath:null,themePath:null,basePath:"",suffix:".js",$moduleUrls:{}};t.get=function(e){if(!s.hasOwnProperty(e))throw new Error("Unknown config key: "+e);return s[e]},t.set=function(e,t){if(!s.hasOwnProperty(e))throw new Error("Unknown config key: "+e);s[e]=t},t.all=function(){return r.copyObject(s)},t.moduleUrl=function(e,t){if(s.$moduleUrls[e])return s.$moduleUrls[e];var n=e.split("/");t=t||n[n.length-2]||"";var r=n[n.length-1].replace(t,"").replace(/(^[\-_])|([\-_]$)/,"");!r&&n.length>1&&(r=n[n.length-2]);var i=s[t+"Path"];return i==null&&(i=s.basePath),i&&i.slice(-1)!="/"&&(i+="/"),i+t+"-"+r+this.get("suffix")},t.setModuleUrl=function(e,t){return s.$moduleUrls[e]=t},t.init=function(){s.packaged=e.packaged||n.packaged||i.define&&define.packaged;if(!i.document)return"";var r={},u="",a=document.getElementsByTagName("script");for(var f=0;f<a.length;f++){var l=a[f],c=l.src||l.getAttribute("src");if(!c)continue;var h=l.attributes;for(var p=0,d=h.length;p<d;p++){var v=h[p];v.name.indexOf("data-ace-")===0&&(r[o(v.name.replace(/^data-ace-/,""))]=v.value)}var m=c.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/);m&&(u=m[1])}u&&(r.base=r.base||u,r.packaged=!0),r.workerPath=r.workerPath||r.base,r.modePath=r.modePath||r.base,r.themePath=r.themePath||r.base,delete r.base;for(var g in r)typeof r[g]!="undefined"&&t.set(g,r[g])}}),ace.define("ace/lib/net",["require","exports","module","ace/lib/useragent"],function(e,t,n){var r=e("./useragent");t.get=function(e,n){var r=t.createXhr();r.open("GET",e,!0),r.onreadystatechange=function(e){r.readyState===4&&n(r.responseText)},r.send(null)};var i=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"];t.createXhr=function(){var e,t,n;if(typeof XMLHttpRequest!="undefined")return new XMLHttpRequest;for(t=0;t<3;t++){n=i[t];try{e=new ActiveXObject(n)}catch(r){}if(e){i=[n];break}}if(!e)throw new Error("createXhr(): XMLHttpRequest not available");return e},t.loadScript=function(e,t){var n=document.getElementsByTagName("head")[0],i=document.createElement("script");i.src=e,n.appendChild(i),r.isOldIE?i.onreadystatechange=function(){this.readyState=="loaded"&&t()}:i.onload=t}}),ace.define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){var r={};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry=this._eventRegistry||{},this._defaultHandlers=this._defaultHandlers||{};var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=function(){this.propagationStopped=!0}),t.preventDefault||(t.preventDefault=function(){this.defaultPrevented=!0});for(var i=0;i<n.length;i++){n[i](t);if(t.propagationStopped)break}if(r&&!t.defaultPrevented)return r(t)},r.setDefaultHandler=function(e,t){this._defaultHandlers=this._defaultHandlers||{};if(this._defaultHandlers[e])throw new Error("The default handler for '"+e+"' is already set");this._defaultHandlers[e]=t},r.on=r.addEventListener=function(e,t){this._eventRegistry=this._eventRegistry||{};var n=this._eventRegistry[e];n||(n=this._eventRegistry[e]=[]),n.indexOf(t)==-1&&n.push(t)},r.removeListener=r.removeEventListener=function(e,t){this._eventRegistry=this._eventRegistry||{};var n=this._eventRegistry[e];if(!n)return;var r=n.indexOf(t);r!==-1&&n.splice(r,1)},r.removeAllListeners=function(e){this._eventRegistry&&(this._eventRegistry[e]=[])},t.EventEmitter=r}),ace.define("ace/selection",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter","ace/range"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=function(e){this.session=e,this.doc=e.getDocument(),this.clearSelection(),this.lead=this.selectionLead=this.doc.createAnchor(0,0),this.anchor=this.selectionAnchor=this.doc.createAnchor(0,0);var t=this;this.lead.on("change",function(e){t._emit("changeCursor"),t.$isEmpty||t._emit("changeSelection"),!t.$keepDesiredColumnOnChange&&e.old.column!=e.value.column&&(t.$desiredColumn=null)}),this.selectionAnchor.on("change",function(){t.$isEmpty||t._emit("changeSelection")})};(function(){r.implement(this,s),this.isEmpty=function(){return this.$isEmpty||this.anchor.row==this.lead.row&&this.anchor.column==this.lead.column},this.isMultiLine=function(){return this.isEmpty()?!1:this.getRange().isMultiLine()},this.getCursor=function(){return this.lead.getPosition()},this.setSelectionAnchor=function(e,t){this.anchor.setPosition(e,t),this.$isEmpty&&(this.$isEmpty=!1,this._emit("changeSelection"))},this.getSelectionAnchor=function(){return this.$isEmpty?this.getSelectionLead():this.anchor.getPosition()},this.getSelectionLead=function(){return this.lead.getPosition()},this.shiftSelection=function(e){if(this.$isEmpty){this.moveCursorTo(this.lead.row,this.lead.column+e);return}var t=this.getSelectionAnchor(),n=this.getSelectionLead(),r=this.isBackwards();(!r||t.column!==0)&&this.setSelectionAnchor(t.row,t.column+e),(r||n.column!==0)&&this.$moveSelection(function(){this.moveCursorTo(n.row,n.column+e)})},this.isBackwards=function(){var e=this.anchor,t=this.lead;return e.row>t.row||e.row==t.row&&e.column>t.column},this.getRange=function(){var e=this.anchor,t=this.lead;return this.isEmpty()?o.fromPoints(t,t):this.isBackwards()?o.fromPoints(t,e):o.fromPoints(e,t)},this.clearSelection=function(){this.$isEmpty||(this.$isEmpty=!0,this._emit("changeSelection"))},this.selectAll=function(){var e=this.doc.getLength()-1;this.setSelectionAnchor(0,0),this.moveCursorTo(e,this.doc.getLine(e).length)},this.setRange=this.setSelectionRange=function(e,t){t?(this.setSelectionAnchor(e.end.row,e.end.column),this.selectTo(e.start.row,e.start.column)):(this.setSelectionAnchor(e.start.row,e.start.column),this.selectTo(e.end.row,e.end.column)),this.$desiredColumn=null},this.$moveSelection=function(e){var t=this.lead;this.$isEmpty&&this.setSelectionAnchor(t.row,t.column),e.call(this)},this.selectTo=function(e,t){this.$moveSelection(function(){this.moveCursorTo(e,t)})},this.selectToPosition=function(e){this.$moveSelection(function(){this.moveCursorToPosition(e)})},this.selectUp=function(){this.$moveSelection(this.moveCursorUp)},this.selectDown=function(){this.$moveSelection(this.moveCursorDown)},this.selectRight=function(){this.$moveSelection(this.moveCursorRight)},this.selectLeft=function(){this.$moveSelection(this.moveCursorLeft)},this.selectLineStart=function(){this.$moveSelection(this.moveCursorLineStart)},this.selectLineEnd=function(){this.$moveSelection(this.moveCursorLineEnd)},this.selectFileEnd=function(){this.$moveSelection(this.moveCursorFileEnd)},this.selectFileStart=function(){this.$moveSelection(this.moveCursorFileStart)},this.selectWordRight=function(){this.$moveSelection(this.moveCursorWordRight)},this.selectWordLeft=function(){this.$moveSelection(this.moveCursorWordLeft)},this.getWordRange=function(e,t){if(typeof t=="undefined"){var n=e||this.lead;e=n.row,t=n.column}return this.session.getWordRange(e,t)},this.selectWord=function(){this.setSelectionRange(this.getWordRange())},this.selectAWord=function(){var e=this.getCursor(),t=this.session.getAWordRange(e.row,e.column);this.setSelectionRange(t)},this.getLineRange=function(e,t){var n=typeof e=="number"?e:this.lead.row,r,i=this.session.getFoldLine(n);return i?(n=i.start.row,r=i.end.row):r=n,t?new o(n,0,r,this.session.getLine(r).length):new o(n,0,r+1,0)},this.selectLine=function(){this.setSelectionRange(this.getLineRange())},this.moveCursorUp=function(){this.moveCursorBy(-1,0)},this.moveCursorDown=function(){this.moveCursorBy(1,0)},this.moveCursorLeft=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,-1))this.moveCursorTo(t.start.row,t.start.column);else if(e.column==0)e.row>0&&this.moveCursorTo(e.row-1,this.doc.getLine(e.row-1).length);else{var n=this.session.getTabSize();this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(e.column-n,e.column).split(" ").length-1==n?this.moveCursorBy(0,-n):this.moveCursorBy(0,-1)}},this.moveCursorRight=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,1))this.moveCursorTo(t.end.row,t.end.column);else if(this.lead.column==this.doc.getLine(this.lead.row).length)this.lead.row<this.doc.getLength()-1&&this.moveCursorTo(this.lead.row+1,0);else{var n=this.session.getTabSize(),e=this.lead;this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(e.column,e.column+n).split(" ").length-1==n?this.moveCursorBy(0,n):this.moveCursorBy(0,1)}},this.moveCursorLineStart=function(){var e=this.lead.row,t=this.lead.column,n=this.session.documentToScreenRow(e,t),r=this.session.screenToDocumentPosition(n,0),i=this.session.getDisplayLine(e,null,r.row,r.column),s=i.match(/^\s*/);s[0].length==t?this.moveCursorTo(r.row,r.column):this.moveCursorTo(r.row,r.column+s[0].length)},this.moveCursorLineEnd=function(){var e=this.lead,t=this.session.getDocumentLastRowColumnPosition(e.row,e.column);if(this.lead.column==t.column){var n=this.session.getLine(t.row);if(t.column==n.length){var r=n.search(/\s+$/);r>0&&(t.column=r)}}this.moveCursorTo(t.row,t.column)},this.moveCursorFileEnd=function(){var e=this.doc.getLength()-1,t=this.doc.getLine(e).length;this.moveCursorTo(e,t)},this.moveCursorFileStart=function(){this.moveCursorTo(0,0)},this.moveCursorLongWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;var s=this.session.getFoldAt(e,t,1);if(s){this.moveCursorTo(s.end.row,s.end.column);return}if(i=this.session.nonTokenRe.exec(r))t+=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0,r=n.substring(t);if(t>=n.length){this.moveCursorTo(e,n.length),this.moveCursorRight(),e<this.doc.getLength()-1&&this.moveCursorWordRight();return}if(i=this.session.tokenRe.exec(r))t+=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(e,t)},this.moveCursorLongWordLeft=function(){var e=this.lead.row,t=this.lead.column,n;if(n=this.session.getFoldAt(e,t,-1)){this.moveCursorTo(n.start.row,n.start.column);return}var r=this.session.getFoldStringAt(e,t,-1);r==null&&(r=this.doc.getLine(e).substring(0,t));var s=i.stringReverse(r),o;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;if(o=this.session.nonTokenRe.exec(s))t-=this.session.nonTokenRe.lastIndex,s=s.slice(this.session.nonTokenRe.lastIndex),this.session.nonTokenRe.lastIndex=0;if(t<=0){this.moveCursorTo(e,0),this.moveCursorLeft(),e>0&&this.moveCursorWordLeft();return}if(o=this.session.tokenRe.exec(s))t-=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(e,t)},this.$shortWordEndIndex=function(e){var t,n=0,r,i=/\s/,s=this.session.tokenRe;s.lastIndex=0;if(t=this.session.tokenRe.exec(e))n=this.session.tokenRe.lastIndex;else{while((r=e[n])&&i.test(r))n++;if(n<=1){s.lastIndex=0;while((r=e[n])&&!s.test(r)){s.lastIndex=0,n++;if(i.test(r)){if(n>2){n--;break}while((r=e[n])&&i.test(r))n++;if(n>2)break}}}}return s.lastIndex=0,n},this.moveCursorShortWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i=this.session.getFoldAt(e,t,1);if(i)return this.moveCursorTo(i.end.row,i.end.column);if(t==n.length){var s=this.doc.getLength();do e++,r=this.doc.getLine(e);while(e<s&&/^\s*$/.test(r));/^\s+/.test(r)||(r=""),t=0}var o=this.$shortWordEndIndex(r);this.moveCursorTo(e,t+o)},this.moveCursorShortWordLeft=function(){var e=this.lead.row,t=this.lead.column,n;if(n=this.session.getFoldAt(e,t,-1))return this.moveCursorTo(n.start.row,n.start.column);var r=this.session.getLine(e).substring(0,t);if(t==0){do e--,r=this.doc.getLine(e);while(e>0&&/^\s*$/.test(r));t=r.length,/\s+$/.test(r)||(r="")}var s=i.stringReverse(r),o=this.$shortWordEndIndex(s);return this.moveCursorTo(e,t-o)},this.moveCursorWordRight=function(){this.session.$selectLongWords?this.moveCursorLongWordRight():this.moveCursorShortWordRight()},this.moveCursorWordLeft=function(){this.session.$selectLongWords?this.moveCursorLongWordLeft():this.moveCursorShortWordLeft()},this.moveCursorBy=function(e,t){var n=this.session.documentToScreenPosition(this.lead.row,this.lead.column);t===0&&(this.$desiredColumn?n.column=this.$desiredColumn:this.$desiredColumn=n.column);var r=this.session.screenToDocumentPosition(n.row+e,n.column);this.moveCursorTo(r.row,r.column+t,t===0)},this.moveCursorToPosition=function(e){this.moveCursorTo(e.row,e.column)},this.moveCursorTo=function(e,t,n){var r=this.session.getFoldAt(e,t,1);r&&(e=r.start.row,t=r.start.column),this.$keepDesiredColumnOnChange=!0,this.lead.setPosition(e,t),this.$keepDesiredColumnOnChange=!1,n||(this.$desiredColumn=null)},this.moveCursorToScreen=function(e,t,n){var r=this.session.screenToDocumentPosition(e,t);this.moveCursorTo(r.row,r.column,n)},this.detach=function(){this.lead.detach(),this.anchor.detach(),this.session=this.doc=null},this.fromOrientedRange=function(e){this.setSelectionRange(e,e.cursor==e.start),this.$desiredColumn=e.desiredColumn||this.$desiredColumn},this.toOrientedRange=function(e){var t=this.getRange();return e?(e.start.column=t.start.column,e.start.row=t.start.row,e.end.column=t.end.column,e.end.row=t.end.row):e=t,e.cursor=this.isBackwards()?e.start:e.end,e.desiredColumn=this.$desiredColumn,e}}).call(u.prototype),t.Selection=u}),ace.define("ace/range",["require","exports","module"],function(e,t,n){var r=function(e,t,n,r){this.start={row:e,column:t},this.end={row:n,column:r}};(function(){this.isEqual=function(e){return this.start.row==e.start.row&&this.end.row==e.end.row&&this.start.column==e.start.column&&this.end.column==e.end.column},this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?t<this.start.column?-1:t>this.end.column?1:0:e<this.start.row?-1:e>this.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};if(this.start.row>t)var i={row:t+1,column:0};if(this.start.row<e)var i={row:e,column:0};if(this.end.row<e)var n={row:e,column:0};return r.fromPoints(i||this.start,n||this.end)},this.extend=function(e,t){var n=this.compare(e,t);if(n==0)return this;if(n==-1)var i={row:e,column:t};else var s={row:e,column:t};return r.fromPoints(i||this.start,s||this.end)},this.isEmpty=function(){return this.start.row==this.end.row&&this.start.column==this.end.column},this.isMultiLine=function(){return this.start.row!==this.end.row},this.clone=function(){return r.fromPoints(this.start,this.end)},this.collapseRows=function(){return this.end.column==0?new r(this.start.row,0,Math.max(this.start.row,this.end.row-1),0):new r(this.start.row,0,this.end.row,0)},this.toScreenRange=function(e){var t=e.documentToScreenPosition(this.start),n=e.documentToScreenPosition(this.end);return new r(t.row,t.column,n.row,n.column)}}).call(r.prototype),r.fromPoints=function(e,t){return new r(e.row,e.column,t.row,t.column)},t.Range=r}),ace.define("ace/mode/text",["require","exports","module","ace/tokenizer","ace/mode/text_highlight_rules","ace/mode/behaviour","ace/unicode"],function(e,t,n){var r=e("../tokenizer").Tokenizer,i=e("./text_highlight_rules").TextHighlightRules,s=e("./behaviour").Behaviour,o=e("../unicode"),u=function(){this.$tokenizer=new r((new i).getRules()),this.$behaviour=new s};(function(){this.tokenRe=new RegExp("^["+o.packages.L+o.packages.Mn+o.packages.Mc+o.packages.Nd+o.packages.Pc+"\\$_]+","g"),this.nonTokenRe=new RegExp("^(?:[^"+o.packages.L+o.packages.Mn+o.packages.Mc+o.packages.Nd+o.packages.Pc+"\\$_]|s])+","g"),this.getTokenizer=function(){return this.$tokenizer},this.toggleCommentLines=function(e,t,n,r){},this.getNextLineIndent=function(e,t,n){return""},this.checkOutdent=function(e,t,n){return!1},this.autoOutdent=function(e,t,n){},this.$getIndent=function(e){var t=e.match(/^(\s+)/);return t?t[1]:""},this.createWorker=function(e){return null},this.createModeDelegates=function(e){if(!this.$embeds)return;this.$modes={};for(var t=0;t<this.$embeds.length;t++)e[this.$embeds[t]]&&(this.$modes[this.$embeds[t]]=new e[this.$embeds[t]]);var n=["toggleCommentLines","getNextLineIndent","checkOutdent","autoOutdent","transformAction"];for(var t=0;t<n.length;t++)(function(e){var r=n[t],i=e[r];e[n[t]]=function(){return this.$delegator(r,arguments,i)}})(this)},this.$delegator=function(e,t,n){var r=t[0];for(var i=0;i<this.$embeds.length;i++){if(!this.$modes[this.$embeds[i]])continue;var s=r.split(this.$embeds[i]);if(!s[0]&&s[1]){t[0]=s[1];var o=this.$modes[this.$embeds[i]];return o[e].apply(o,t)}}var u=n.apply(this,t);return n?u:undefined},this.transformAction=function(e,t,n,r,i){if(this.$behaviour){var s=this.$behaviour.getBehaviours();for(var o in s)if(s[o][t]){var u=s[o][t].apply(this,arguments);if(u)return u}}}}).call(u.prototype),t.Mode=u}),ace.define("ace/tokenizer",["require","exports","module"],function(e,t,n){var r=function(e,t){t=t?"g"+t:"g",this.rules=e,this.regExps={},this.matchMappings={};for(var n in this.rules){var r=this.rules[n],i=r,s=[],o=0,u=this.matchMappings[n]={};for(var a=0;a<i.length;a++){i[a].regex instanceof RegExp&&(i[a].regex=i[a].regex.toString().slice(1,-1));var f=(new RegExp("(?:("+i[a].regex+")|(.))")).exec("a").length-2,l=i[a].regex.replace(/\\([0-9]+)/g,function(e,t){return"\\"+(parseInt(t,10)+o+1)});if(f>1&&i[a].token.length!==f-1)throw new Error("For "+i[a].regex+" the matching groups ("+(f-1)+") and length of the token array ("+i[a].token.length+") don't match (rule #"+a+" of state "+n+")");u[o]={rule:a,len:f},o+=f,s.push(l)}this.regExps[n]=new RegExp("(?:("+s.join(")|(")+")|(.))",t)}};(function(){this.getLineTokens=function(e,t){var n=t||"start",r=this.rules[n],i=this.matchMappings[n],s=this.regExps[n];s.lastIndex=0;var o,u=[],a=0,f={type:null,value:""};while(o=s.exec(e)){var l="text",c=null,h=[o[0]];for(var p=0;p<o.length-2;p++){if(o[p+1]===undefined)continue;c=r[i[p].rule],i[p].len>1&&(h=o.slice(p+2,p+1+i[p].len)),typeof c.token=="function"?l=c.token.apply(this,h):l=c.token;if(c.next){n=c.next,r=this.rules[n],i=this.matchMappings[n],a=s.lastIndex,s=this.regExps[n];if(s===undefined)throw new Error("You indicated a state of "+c.next+" to go to, but it doesn't exist!");s.lastIndex=a}break}if(h[0]){typeof l=="string"&&(h=[h.join("")],l=[l]);for(var p=0;p<h.length;p++){if(!h[p])continue;(!c||c.merge||l[p]==="text")&&f.type===l[p]?f.value+=h[p]:(f.type&&u.push(f),f={type:l[p],value:h[p]})}}if(a==e.length)break;a=s.lastIndex}return f.type&&u.push(f),{tokens:u,state:n}}}).call(r.prototype),t.Tokenizer=r}),ace.define("ace/mode/text_highlight_rules",["require","exports","module","ace/lib/lang"],function(e,t,n){var r=e("../lib/lang"),i=function(){this.$rules={start:[{token:"empty_line",regex:"^$"},{token:"text",regex:".+"}]}};(function(){this.addRules=function(e,t){for(var n in e){var r=e[n];for(var i=0;i<r.length;i++){var s=r[i];s.next&&(s.next=t+s.next)}this.$rules[t+n]=r}},this.getRules=function(){return this.$rules},this.embedRules=function(e,t,n,i,s){var o=(new e).getRules();if(i)for(var u=0;u<i.length;u++)i[u]=t+i[u];else{i=[];for(var a in o)i.push(t+a)}this.addRules(o,t);var f=Array.prototype[s?"push":"unshift"];for(var u=0;u<i.length;u++)f.apply(this.$rules[i[u]],r.deepCopy(n));this.$embeds||(this.$embeds=[]),this.$embeds.push(t)},this.getEmbeds=function(){return this.$embeds},this.createKeywordMapper=function(e,t,n,r){var i=Object.create(null);return Object.keys(e).forEach(function(t){var s=e[t];n&&(s=s.toLowerCase());var o=s.split(r||"|");for(var u=o.length;u--;)i[o[u]]=t}),e=null,n?function(e){return i[e.toLowerCase()]||t}:function(e){return i[e]||t}},this.getKeywords=function(){return this.$keywords}}).call(i.prototype),t.TextHighlightRules=i}),ace.define("ace/mode/behaviour",["require","exports","module"],function(e,t,n){var r=function(){this.$behaviours={}};(function(){this.add=function(e,t,n){switch(undefined){case this.$behaviours:this.$behaviours={};case this.$behaviours[e]:this.$behaviours[e]={}}this.$behaviours[e][t]=n},this.addBehaviours=function(e){for(var t in e)for(var n in e[t])this.add(t,n,e[t][n])},this.remove=function(e){this.$behaviours&&this.$behaviours[e]&&delete this.$behaviours[e]},this.inherit=function(e,t){if(typeof e=="function")var n=(new e).getBehaviours(t);else var n=e.getBehaviours(t);this.addBehaviours(n)},this.getBehaviours=function(e){if(!e)return this.$behaviours;var t={};for(var n=0;n<e.length;n++)this.$behaviours[e[n]]&&(t[e[n]]=this.$behaviours[e[n]]);return t}}).call(r.prototype),t.Behaviour=r}),ace.define("ace/unicode",["require","exports","module"],function(e,t,n){function r(e){var n=/\w{4}/g;for(var r in e)t.packages[r]=e[r].replace(n,"\\u$&")}t.packages={},r({L:"0041-005A0061-007A00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05250531-055605590561-058705D0-05EA05F0-05F20621-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280904-0939093D09500958-0961097109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10D05-0D0C0D0E-0D100D12-0D280D2A-0D390D3D0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E460E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EC60EDC0EDD0F000F40-0F470F49-0F6C0F88-0F8B1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10A0-10C510D0-10FA10FC1100-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317D717DC1820-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541AA71B05-1B331B45-1B4B1B83-1BA01BAE1BAF1C00-1C231C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF11D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209421022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E218321842C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2D00-2D252D30-2D652D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2E2F300530063031-3035303B303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31B731F0-31FF3400-4DB54E00-9FCBA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A65FA662-A66EA67F-A697A6A0-A6E5A717-A71FA722-A788A78BA78CA7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2A9CFAA00-AA28AA40-AA42AA44-AA4BAA60-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADB-AADDABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA2DFA30-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",Ll:"0061-007A00AA00B500BA00DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02AF037103730377037B-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F0521052305250561-05871D00-1D2B1D62-1D771D79-1D9A1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF7210A210E210F2113212F21342139213C213D2146-2149214E21842C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7C2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2D00-2D25A641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76FA771-A778A77AA77CA77FA781A783A785A787A78CFB00-FB06FB13-FB17FF41-FF5A",Lu:"0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E0520052205240531-055610A0-10C51E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F214521832C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CEDA640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BFF21-FF3A",Lt:"01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC",Lm:"02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D611D781D9B-1DBF2071207F2090-20942C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A9CFAA70AADDFF70FF9EFF9F",Lo:"01BB01C0-01C3029405D0-05EA05F0-05F20621-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150904-0939093D09500958-096109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10D05-0D0C0D0E-0D100D12-0D280D2A-0D390D3D0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E450E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EDC0EDD0F000F40-0F470F49-0F6C0F88-0F8B1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10D0-10FA1100-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317DC1820-18421844-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541B05-1B331B45-1B4B1B83-1BA01BAE1BAF1C00-1C231C4D-1C4F1C5A-1C771CE9-1CEC1CEE-1CF12135-21382D30-2D652D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE3006303C3041-3096309F30A1-30FA30FF3105-312D3131-318E31A0-31B731F0-31FF3400-4DB54E00-9FCBA000-A014A016-A48CA4D0-A4F7A500-A60BA610-A61FA62AA62BA66EA6A0-A6E5A7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2AA00-AA28AA40-AA42AA44-AA4BAA60-AA6FAA71-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADBAADCABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA2DFA30-FA6DFA70-FAD9FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF66-FF6FFF71-FF9DFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",M:"0300-036F0483-04890591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DE-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0903093C093E-094E0951-0955096209630981-098309BC09BE-09C409C709C809CB-09CD09D709E209E30A01-0A030A3C0A3E-0A420A470A480A4B-0A4D0A510A700A710A750A81-0A830ABC0ABE-0AC50AC7-0AC90ACB-0ACD0AE20AE30B01-0B030B3C0B3E-0B440B470B480B4B-0B4D0B560B570B620B630B820BBE-0BC20BC6-0BC80BCA-0BCD0BD70C01-0C030C3E-0C440C46-0C480C4A-0C4D0C550C560C620C630C820C830CBC0CBE-0CC40CC6-0CC80CCA-0CCD0CD50CD60CE20CE30D020D030D3E-0D440D46-0D480D4A-0D4D0D570D620D630D820D830DCA0DCF-0DD40DD60DD8-0DDF0DF20DF30E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F3E0F3F0F71-0F840F860F870F90-0F970F99-0FBC0FC6102B-103E1056-1059105E-10601062-10641067-106D1071-10741082-108D108F109A-109D135F1712-17141732-1734175217531772177317B6-17D317DD180B-180D18A91920-192B1930-193B19B0-19C019C819C91A17-1A1B1A55-1A5E1A60-1A7C1A7F1B00-1B041B34-1B441B6B-1B731B80-1B821BA1-1BAA1C24-1C371CD0-1CD21CD4-1CE81CED1CF21DC0-1DE61DFD-1DFF20D0-20F02CEF-2CF12DE0-2DFF302A-302F3099309AA66F-A672A67CA67DA6F0A6F1A802A806A80BA823-A827A880A881A8B4-A8C4A8E0-A8F1A926-A92DA947-A953A980-A983A9B3-A9C0AA29-AA36AA43AA4CAA4DAA7BAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1ABE3-ABEAABECABEDFB1EFE00-FE0FFE20-FE26",Mn:"0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0902093C0941-0948094D0951-095509620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630DCA0DD2-0DD40DD60E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F71-0F7E0F80-0F840F860F870F90-0F970F99-0FBC0FC6102D-10301032-10371039103A103D103E10581059105E-10601071-1074108210851086108D109D135F1712-17141732-1734175217531772177317B7-17BD17C617C9-17D317DD180B-180D18A91920-19221927192819321939-193B1A171A181A561A58-1A5E1A601A621A65-1A6C1A73-1A7C1A7F1B00-1B031B341B36-1B3A1B3C1B421B6B-1B731B801B811BA2-1BA51BA81BA91C2C-1C331C361C371CD0-1CD21CD4-1CE01CE2-1CE81CED1DC0-1DE61DFD-1DFF20D0-20DC20E120E5-20F02CEF-2CF12DE0-2DFF302A-302F3099309AA66FA67CA67DA6F0A6F1A802A806A80BA825A826A8C4A8E0-A8F1A926-A92DA947-A951A980-A982A9B3A9B6-A9B9A9BCAA29-AA2EAA31AA32AA35AA36AA43AA4CAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1ABE5ABE8ABEDFB1EFE00-FE0FFE20-FE26",Mc:"0903093E-09400949-094C094E0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A1B1A551A571A611A631A641A6D-1A721B041B351B3B1B3D-1B411B431B441B821BA11BA61BA71BAA1C24-1C2B1C341C351CE11CF2A823A824A827A880A881A8B4-A8C3A952A953A983A9B4A9B5A9BAA9BBA9BD-A9C0AA2FAA30AA33AA34AA4DAA7BABE3ABE4ABE6ABE7ABE9ABEAABEC",Me:"0488048906DE20DD-20E020E2-20E4A670-A672",N:"0030-003900B200B300B900BC-00BE0660-066906F0-06F907C0-07C90966-096F09E6-09EF09F4-09F90A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BF20C66-0C6F0C78-0C7E0CE6-0CEF0D66-0D750E50-0E590ED0-0ED90F20-0F331040-10491090-10991369-137C16EE-16F017E0-17E917F0-17F91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C5920702074-20792080-20892150-21822185-21892460-249B24EA-24FF2776-27932CFD30073021-30293038-303A3192-31953220-32293251-325F3280-328932B1-32BFA620-A629A6E6-A6EFA830-A835A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",Nd:"0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",Nl:"16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF",No:"00B200B300B900BC-00BE09F4-09F90BF0-0BF20C78-0C7E0D70-0D750F2A-0F331369-137C17F0-17F920702074-20792080-20892150-215F21892460-249B24EA-24FF2776-27932CFD3192-31953220-32293251-325F3280-328932B1-32BFA830-A835",P:"0021-00230025-002A002C-002F003A003B003F0040005B-005D005F007B007D00A100AB00B700BB00BF037E0387055A-055F0589058A05BE05C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F3A-0F3D0F850FD0-0FD4104A-104F10FB1361-13681400166D166E169B169C16EB-16ED1735173617D4-17D617D8-17DA1800-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD32010-20272030-20432045-20512053-205E207D207E208D208E2329232A2768-277527C527C627E6-27EF2983-299829D8-29DB29FC29FD2CF9-2CFC2CFE2CFF2E00-2E2E2E302E313001-30033008-30113014-301F3030303D30A030FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFABEBFD3EFD3FFE10-FE19FE30-FE52FE54-FE61FE63FE68FE6AFE6BFF01-FF03FF05-FF0AFF0C-FF0FFF1AFF1BFF1FFF20FF3B-FF3DFF3FFF5BFF5DFF5F-FF65",Pd:"002D058A05BE140018062010-20152E172E1A301C303030A0FE31FE32FE58FE63FF0D",Ps:"0028005B007B0F3A0F3C169B201A201E2045207D208D23292768276A276C276E27702772277427C527E627E827EA27EC27EE2983298529872989298B298D298F299129932995299729D829DA29FC2E222E242E262E283008300A300C300E3010301430163018301A301DFD3EFE17FE35FE37FE39FE3BFE3DFE3FFE41FE43FE47FE59FE5BFE5DFF08FF3BFF5BFF5FFF62",Pe:"0029005D007D0F3B0F3D169C2046207E208E232A2769276B276D276F27712773277527C627E727E927EB27ED27EF298429862988298A298C298E2990299229942996299829D929DB29FD2E232E252E272E293009300B300D300F3011301530173019301B301E301FFD3FFE18FE36FE38FE3AFE3CFE3EFE40FE42FE44FE48FE5AFE5CFE5EFF09FF3DFF5DFF60FF63",Pi:"00AB2018201B201C201F20392E022E042E092E0C2E1C2E20",Pf:"00BB2019201D203A2E032E052E0A2E0D2E1D2E21",Pc:"005F203F20402054FE33FE34FE4D-FE4FFF3F",Po:"0021-00230025-0027002A002C002E002F003A003B003F0040005C00A100B700BF037E0387055A-055F058905C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F850FD0-0FD4104A-104F10FB1361-1368166D166E16EB-16ED1735173617D4-17D617D8-17DA1800-18051807-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD3201620172020-20272030-2038203B-203E2041-20432047-205120532055-205E2CF9-2CFC2CFE2CFF2E002E012E06-2E082E0B2E0E-2E162E182E192E1B2E1E2E1F2E2A-2E2E2E302E313001-3003303D30FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFABEBFE10-FE16FE19FE30FE45FE46FE49-FE4CFE50-FE52FE54-FE57FE5F-FE61FE68FE6AFE6BFF01-FF03FF05-FF07FF0AFF0CFF0EFF0FFF1AFF1BFF1FFF20FF3CFF61FF64FF65",S:"0024002B003C-003E005E0060007C007E00A2-00A900AC00AE-00B100B400B600B800D700F702C2-02C502D2-02DF02E5-02EB02ED02EF-02FF03750384038503F604820606-0608060B060E060F06E906FD06FE07F609F209F309FA09FB0AF10B700BF3-0BFA0C7F0CF10CF20D790E3F0F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-139917DB194019E0-19FF1B61-1B6A1B74-1B7C1FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE20442052207A-207C208A-208C20A0-20B8210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B2140-2144214A-214D214F2190-2328232B-23E82400-24262440-244A249C-24E92500-26CD26CF-26E126E326E8-26FF2701-27042706-2709270C-27272729-274B274D274F-27522756-275E2761-276727942798-27AF27B1-27BE27C0-27C427C7-27CA27CC27D0-27E527F0-29822999-29D729DC-29FB29FE-2B4C2B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F309B309C319031913196-319F31C0-31E33200-321E322A-32503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A700-A716A720A721A789A78AA828-A82BA836-A839AA77-AA79FB29FDFCFDFDFE62FE64-FE66FE69FF04FF0BFF1C-FF1EFF3EFF40FF5CFF5EFFE0-FFE6FFE8-FFEEFFFCFFFD",Sm:"002B003C-003E007C007E00AC00B100D700F703F60606-060820442052207A-207C208A-208C2140-2144214B2190-2194219A219B21A021A321A621AE21CE21CF21D221D421F4-22FF2308-230B23202321237C239B-23B323DC-23E125B725C125F8-25FF266F27C0-27C427C7-27CA27CC27D0-27E527F0-27FF2900-29822999-29D729DC-29FB29FE-2AFF2B30-2B442B47-2B4CFB29FE62FE64-FE66FF0BFF1C-FF1EFF5CFF5EFFE2FFE9-FFEC",Sc:"002400A2-00A5060B09F209F309FB0AF10BF90E3F17DB20A0-20B8A838FDFCFE69FF04FFE0FFE1FFE5FFE6",Sk:"005E006000A800AF00B400B802C2-02C502D2-02DF02E5-02EB02ED02EF-02FF0375038403851FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE309B309CA700-A716A720A721A789A78AFF3EFF40FFE3",So:"00A600A700A900AE00B000B60482060E060F06E906FD06FE07F609FA0B700BF3-0BF80BFA0C7F0CF10CF20D790F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-1399194019E0-19FF1B61-1B6A1B74-1B7C210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B214A214C214D214F2195-2199219C-219F21A121A221A421A521A7-21AD21AF-21CD21D021D121D321D5-21F32300-2307230C-231F2322-2328232B-237B237D-239A23B4-23DB23E2-23E82400-24262440-244A249C-24E92500-25B625B8-25C025C2-25F72600-266E2670-26CD26CF-26E126E326E8-26FF2701-27042706-2709270C-27272729-274B274D274F-27522756-275E2761-276727942798-27AF27B1-27BE2800-28FF2B00-2B2F2B452B462B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F319031913196-319F31C0-31E33200-321E322A-32503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A828-A82BA836A837A839AA77-AA79FDFDFFE4FFE8FFEDFFEEFFFCFFFD",Z:"002000A01680180E2000-200A20282029202F205F3000",Zs:"002000A01680180E2000-200A202F205F3000",Zl:"2028",Zp:"2029",C:"0000-001F007F-009F00AD03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-0605061C061D0620065F06DD070E070F074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF00AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B72-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D290D3A-0D3C0D450D490D4E-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EDE-0EFF0F480F6D-0F700F8C-0F8F0F980FBD0FCD0FD9-0FFF10C6-10CF10FD-10FF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B-135E137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17B417B517DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BAB-1BAD1BBA-1BFF1C38-1C3A1C4A-1C4C1C80-1CCF1CF3-1CFF1DE7-1DFC1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF200B-200F202A-202E2060-206F20722073208F2095-209F20B9-20CF20F1-20FF218A-218F23E9-23FF2427-243F244B-245F26CE26E226E4-26E727002705270A270B2728274C274E2753-2755275F27602795-279727B027BF27CB27CD-27CF2B4D-2B4F2B5A-2BFF2C2F2C5F2CF2-2CF82D26-2D2F2D66-2D6E2D70-2D7F2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E32-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31B8-31BF31E4-31EF321F32FF4DB6-4DBF9FCC-9FFFA48D-A48FA4C7-A4CFA62C-A63FA660A661A674-A67BA698-A69FA6F8-A6FFA78D-A7FAA82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAE0-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-F8FFFA2EFA2FFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBB2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFD-FF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFFBFFFEFFFF",Cc:"0000-001F007F-009F",Cf:"00AD0600-060306DD070F17B417B5200B-200F202A-202E2060-2064206A-206FFEFFFFF9-FFFB",Co:"E000-F8FF",Cs:"D800-DFFF",Cn:"03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-05FF06040605061C061D0620065F070E074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF00AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B72-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D290D3A-0D3C0D450D490D4E-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EDE-0EFF0F480F6D-0F700F8C-0F8F0F980FBD0FCD0FD9-0FFF10C6-10CF10FD-10FF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B-135E137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BAB-1BAD1BBA-1BFF1C38-1C3A1C4A-1C4C1C80-1CCF1CF3-1CFF1DE7-1DFC1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF2065-206920722073208F2095-209F20B9-20CF20F1-20FF218A-218F23E9-23FF2427-243F244B-245F26CE26E226E4-26E727002705270A270B2728274C274E2753-2755275F27602795-279727B027BF27CB27CD-27CF2B4D-2B4F2B5A-2BFF2C2F2C5F2CF2-2CF82D26-2D2F2D66-2D6E2D70-2D7F2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E32-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31B8-31BF31E4-31EF321F32FF4DB6-4DBF9FCC-9FFFA48D-A48FA4C7-A4CFA62C-A63FA660A661A674-A67BA698-A69FA6F8-A6FFA78D-A7FAA82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAE0-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-D7FFFA2EFA2FFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBB2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFDFEFEFF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFF8FFFEFFFF"})}),ace.define("ace/document",["require","exports","module","ace/lib/oop","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=e("./range").Range,o=e("./anchor").Anchor,u=function(e){this.$lines=[],e.length==0?this.$lines=[""]:Array.isArray(e)?this.insertLines(0,e):this.insert({row:0,column:0},e)};(function(){r.implement(this,i),this.setValue=function(e){var t=this.getLength();this.remove(new s(0,0,t,this.getLine(t-1).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new o(this,e,t)},"aaa".split(/a/).length==0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);t?this.$autoNewLine=t[1]:this.$autoNewLine="\n"},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";case"auto":return this.$autoNewLine}},this.$autoNewLine="\n",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){if(e.start.row==e.end.row)return this.$lines[e.start.row].substring(e.start.column,e.end.column);var t=this.getLines(e.start.row+1,e.end.row-1);return t.unshift((this.$lines[e.start.row]||"").substring(e.start.column)),t.push((this.$lines[e.end.row]||"").substring(0,e.end.column)),t.join(this.getNewLineCharacter())},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t&&(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length),e},this.insert=function(e,t){if(!t||t.length===0)return e;e=this.$clipPosition(e),this.getLength()<=1&&this.$detectNewLine(t);var n=this.$split(t),r=n.splice(0,1)[0],i=n.length==0?null:n.splice(n.length-1,1)[0];return e=this.insertInLine(e,r),i!==null&&(e=this.insertNewLine(e),e=this.insertLines(e.row,n),e=this.insertInLine(e,i||"")),e},this.insertLines=function(e,t){if(t.length==0)return{row:e,column:0};if(t.length>65535){var n=this.insertLines(e,t.slice(65535));t=t.slice(0,65535)}var r=[e,0];r.push.apply(r,t),this.$lines.splice.apply(this.$lines,r);var i=new s(e,0,e+t.length,0),o={action:"insertLines",range:i,lines:t};return this._emit("change",{data:o}),n||i.end},this.insertNewLine=function(e){e=this.$clipPosition(e);var t=this.$lines[e.row]||"";this.$lines[e.row]=t.substring(0,e.column),this.$lines.splice(e.row+1,0,t.substring(e.column,t.length));var n={row:e.row+1,column:0},r={action:"insertText",range:s.fromPoints(e,n),text:this.getNewLineCharacter()};return this._emit("change",{data:r}),n},this.insertInLine=function(e,t){if(t.length==0)return e;var n=this.$lines[e.row]||"";this.$lines[e.row]=n.substring(0,e.column)+t+n.substring(e.column);var r={row:e.row,column:e.column+t.length},i={action:"insertText",range:s.fromPoints(e,r),text:t};return this._emit("change",{data:i}),r},this.remove=function(e){e.start=this.$clipPosition(e.start),e.end=this.$clipPosition(e.end);if(e.isEmpty())return e.start;var t=e.start.row,n=e.end.row;if(e.isMultiLine()){var r=e.start.column==0?t:t+1,i=n-1;e.end.column>0&&this.removeInLine(n,0,e.end.column),i>=r&&this.removeLines(r,i),r!=t&&(this.removeInLine(t,e.start.column,this.getLine(t).length),this.removeNewLine(e.start.row))}else this.removeInLine(t,e.start.column,e.end.column);return e.start},this.removeInLine=function(e,t,n){if(t==n)return;var r=new s(e,t,e,n),i=this.getLine(e),o=i.substring(t,n),u=i.substring(0,t)+i.substring(n,i.length);this.$lines.splice(e,1,u);var a={action:"removeText",range:r,text:o};return this._emit("change",{data:a}),r.start},this.removeLines=function(e,t){var n=new s(e,0,t+1,0),r=this.$lines.splice(e,t-e+1),i={action:"removeLines",range:n,nl:this.getNewLineCharacter(),lines:r};return this._emit("change",{data:i}),r},this.removeNewLine=function(e){var t=this.getLine(e),n=this.getLine(e+1),r=new s(e,t.length,e+1,0),i=t+n;this.$lines.splice(e,2,i);var o={action:"removeText",range:r,text:this.getNewLineCharacter()};this._emit("change",{data:o})},this.replace=function(e,t){if(t.length==0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);if(t)var n=this.insert(e.start,t);else n=e.start;return n},this.applyDeltas=function(e){for(var t=0;t<e.length;t++){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this.insertLines(r.start.row,n.lines):n.action=="insertText"?this.insert(r.start,n.text):n.action=="removeLines"?this.removeLines(r.start.row,r.end.row-1):n.action=="removeText"&&this.remove(r)}},this.revertDeltas=function(e){for(var t=e.length-1;t>=0;t--){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this.removeLines(r.start.row,r.end.row-1):n.action=="insertText"?this.remove(r):n.action=="removeLines"?this.insertLines(r.start.row,n.lines):n.action=="removeText"&&this.insert(r.start,n.text)}}}).call(u.prototype),t.Document=u}),ace.define("ace/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=t.Anchor=function(e,t,n){this.document=e,typeof n=="undefined"?this.setPosition(t.row,t.column):this.setPosition(t,n),this.$onChange=this.onChange.bind(this),e.on("change",this.$onChange)};(function(){r.implement(this,i),this.getPosition=function(){return this.$clipPositionToDocument(this.row,this.column)},this.getDocument=function(){return this.document},this.onChange=function(e){var t=e.data,n=t.range;if(n.start.row==n.end.row&&n.start.row!=this.row)return;if(n.start.row>this.row)return;if(n.start.row==this.row&&n.start.column>this.column)return;var r=this.row,i=this.column;t.action==="insertText"?n.start.row===r&&n.start.column<=i?n.start.row===n.end.row?i+=n.end.column-n.start.column:(i-=n.start.column,r+=n.end.row-n.start.row):n.start.row!==n.end.row&&n.start.row<r&&(r+=n.end.row-n.start.row):t.action==="insertLines"?n.start.row<=r&&(r+=n.end.row-n.start.row):t.action=="removeText"?n.start.row==r&&n.start.column<i?n.end.column>=i?i=n.start.column:i=Math.max(0,i-(n.end.column-n.start.column)):n.start.row!==n.end.row&&n.start.row<r?(n.end.row==r&&(i=Math.max(0,i-n.end.column)+n.start.column),r-=n.end.row-n.start.row):n.end.row==r&&(r-=n.end.row-n.start.row,i=Math.max(0,i-n.end.column)+n.start.column):t.action=="removeLines"&&n.start.row<=r&&(n.end.row<=r?r-=n.end.row-n.start.row:(r=n.start.row,i=0)),this.setPosition(r,i,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._emit("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),ace.define("ace/background_tokenizer",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=5e3,o=function(e,t){this.running=!1,this.lines=[],this.states=[],this.currentLine=0,this.tokenizer=e;var n=this;this.$worker=function(){if(!n.running)return;var e=new Date,t=n.currentLine,r=n.doc,i=0,s=r.getLength();while(n.currentLine<s){n.$tokenizeRow(n.currentLine);while(n.lines[n.currentLine])n.currentLine++;i++;if(i%5==0&&new Date-e>20){n.fireUpdateEvent(t,n.currentLine-1),n.running=setTimeout(n.$worker,20);return}}n.running=!1,n.fireUpdateEvent(t,s-1)}};(function(){r.implement(this,i),this.setTokenizer=function(e){this.tokenizer=e,this.lines=[],this.states=[],this.start(0)},this.setDocument=function(e){this.doc=e,this.lines=[],this.states=[],this.stop()},this.fireUpdateEvent=function(e,t){var n={first:e,last:t};this._emit("update",{data:n})},this.start=function(e){this.currentLine=Math.min(e||0,this.currentLine,this.doc.getLength()),this.lines.splice(this.currentLine,this.lines.length),this.states.splice(this.currentLine,this.states.length),this.stop(),this.running=setTimeout(this.$worker,700)},this.$updateOnChange=function(e){var t=e.range,n=t.start.row,r=t.end.row-n;if(r===0)this.lines[n]=null;else if(e.action=="removeText"||e.action=="removeLines")this.lines.splice(n,r+1,null),this.states.splice(n,r+1,null);else{var i=Array(r+1);i.unshift(n,1),this.lines.splice.apply(this.lines,i),this.states.splice.apply(this.states,i)}this.currentLine=Math.min(n,this.currentLine,this.doc.getLength()),this.stop(),this.running=setTimeout(this.$worker,700)},this.stop=function(){this.running&&clearTimeout(this.running),this.running=!1},this.getTokens=function(e){return this.lines[e]||this.$tokenizeRow(e)},this.getState=function(e){return this.currentLine==e&&this.$tokenizeRow(e),this.states[e]||"start"},this.$tokenizeRow=function(e){var t=this.doc.getLine(e),n=this.states[e-1];if(t.length>s){var r={value:t.substr(s),type:"text"};t=t.slice(0,s)}var i=this.tokenizer.getLineTokens(t,n);return r&&(i.tokens.push(r),i.state="start"),this.states[e]!==i.state?(this.states[e]=i.state,this.lines[e+1]=null,this.currentLine>e+1&&(this.currentLine=e+1)):this.currentLine==e&&(this.currentLine=e+1),this.lines[e]=i.tokens}}).call(o.prototype),t.BackgroundTokenizer=o}),ace.define("ace/search_highlight",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(e,t,n){this.setRegexp(e),this.clazz=t,this.type=n||"text"};(function(){this.MAX_RANGES=500,this.setRegexp=function(e){if(this.regExp+""==e+"")return;this.regExp=e,this.cache=[]},this.update=function(e,t,n,i){if(!this.regExp)return;var o=i.firstRow,u=i.lastRow;for(var a=o;a<=u;a++){var f=this.cache[a];f==null&&(f=r.getMatchOffsets(n.getLine(a),this.regExp),f.length>this.MAX_RANGES&&(f=f.slice(0,this.MAX_RANGES)),f=f.map(function(e){return new s(a,e.offset,a,e.offset+e.length)}),this.cache[a]=f.length?f:"");for(var l=f.length;l--;)t.drawSingleLineMarker(e,f[l].toScreenRange(n),this.clazz,i,null,this.type)}}}).call(o.prototype),t.SearchHighlight=o}),ace.define("ace/edit_session/folding",["require","exports","module","ace/range","ace/edit_session/fold_line","ace/edit_session/fold","ace/token_iterator"],function(e,t,n){function u(){this.getFoldAt=function(e,t,n){var r=this.getFoldLine(e);if(!r)return null;var i=r.folds;for(var s=0;s<i.length;s++){var o=i[s];if(o.range.contains(e,t)){if(n==1&&o.range.isEnd(e,t))continue;if(n==-1&&o.range.isStart(e,t))continue;return o}}},this.getFoldsInRange=function(e){e=e.clone();var t=e.start,n=e.end,r=this.$foldData,i=[];t.column+=1,n.column-=1;for(var s=0;s<r.length;s++){var o=r[s].range.compareRange(e);if(o==2)continue;if(o==-2)break;var u=r[s].folds;for(var a=0;a<u.length;a++){var f=u[a];o=f.range.compareRange(e);if(o==-2)break;if(o==2)continue;if(o==42)break;i.push(f)}}return i},this.getAllFolds=function(){function n(t){e.push(t);if(!t.subFolds)return;for(var r=0;r<t.subFolds.length;r++)n(t.subFolds[r])}var e=[],t=this.$foldData;for(var r=0;r<t.length;r++)for(var i=0;i<t[r].folds.length;i++)n(t[r].folds[i]);return e},this.getFoldStringAt=function(e,t,n,r){r=r||this.getFoldLine(e);if(!r)return null;var i={end:{column:0}},s,o;for(var u=0;u<r.folds.length;u++){o=r.folds[u];var a=o.range.compareEnd(e,t);if(a==-1){s=this.getLine(o.start.row).substring(i.end.column,o.start.column);break}if(a===0)return null;i=o}return s||(s=this.getLine(o.start.row).substring(i.end.column)),n==-1?s.substring(0,t-i.end.column):n==1?s.substring(t-i.end.column):s},this.getFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r<n.length;r++){var i=n[r];if(i.start.row<=e&&i.end.row>=e)return i;if(i.end.row>e)return null}return null},this.getNextFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r<n.length;r++){var i=n[r];if(i.end.row>=e)return i}return null},this.getFoldedRowCount=function(e,t){var n=this.$foldData,r=t-e+1;for(var i=0;i<n.length;i++){var s=n[i],o=s.end.row,u=s.start.row;if(o>=t){u<t&&(u>=e?r-=t-u:r=0);break}o>=e&&(u>=e?r-=o-u:r-=o-e+1)}return r},this.$addFoldLine=function(e){return this.$foldData.push(e),this.$foldData.sort(function(e,t){return e.start.row-t.start.row}),e},this.addFold=function(e,t){var n=this.$foldData,r=!1,o;e instanceof s?o=e:o=new s(t,e),this.$clipRangeToDocument(o.range);var u=o.start.row,a=o.start.column,f=o.end.row,l=o.end.column;if(u==f&&l-a<2)throw"The range has to be at least 2 characters width";var c=this.getFoldAt(u,a,1),h=this.getFoldAt(f,l,-1);if(c&&h==c)return c.addSubFold(o);if(c&&!c.range.isStart(u,a)||h&&!h.range.isEnd(f,l))throw"A fold can't intersect already existing fold"+o.range+c.range;var p=this.getFoldsInRange(o.range);p.length>0&&(this.removeFolds(p),o.subFolds=p);for(var d=0;d<n.length;d++){var v=n[d];if(f==v.start.row){v.addFold(o),r=!0;break}if(u==v.end.row){v.addFold(o),r=!0;if(!o.sameRow){var m=n[d+1];if(m&&m.start.row==f){v.merge(m);break}}break}if(f<=v.start.row)break}return r||(v=this.$addFoldLine(new i(this.$foldData,o))),this.$useWrapMode?this.$updateWrapData(v.start.row,v.start.row):this.$updateRowLengthCache(v.start.row,v.start.row),this.$modified=!0,this._emit("changeFold",{data:o}),o},this.addFolds=function(e){e.forEach(function(e){this.addFold(e)},this)},this.removeFold=function(e){var t=e.foldLine,n=t.start.row,r=t.end.row,i=this.$foldData,s=t.folds;if(s.length==1)i.splice(i.indexOf(t),1);else if(t.range.isEnd(e.end.row,e.end.column))s.pop(),t.end.row=s[s.length-1].end.row,t.end.column=s[s.length-1].end.column;else if(t.range.isStart(e.start.row,e.start.column))s.shift(),t.start.row=s[0].start.row,t.start.column=s[0].start.column;else if(e.sameRow)s.splice(s.indexOf(e),1);else{var o=t.split(e.start.row,e.start.column);s=o.folds,s.shift(),o.start.row=s[0].start.row,o.start.column=s[0].start.column}this.$useWrapMode?this.$updateWrapData(n,r):this.$updateRowLengthCache(n,r),this.$modified=!0,this._emit("changeFold",{data:e})},this.removeFolds=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(e[n]);t.forEach(function(e){this.removeFold(e)},this),this.$modified=!0},this.expandFold=function(e){this.removeFold(e),e.subFolds.forEach(function(e){this.addFold(e)},this),e.subFolds=[]},this.expandFolds=function(e){e.forEach(function(e){this.expandFold(e)},this)},this.unfold=function(e,t){var n,i;e==null?n=new r(0,0,this.getLength(),0):typeof e=="number"?n=new r(e,0,e,this.getLine(e).length):"row"in e?n=r.fromPoints(e,e):n=e,i=this.getFoldsInRange(n);if(t)this.removeFolds(i);else while(i.length)this.expandFolds(i),i=this.getFoldsInRange(n)},this.isRowFolded=function(e,t){return!!this.getFoldLine(e,t)},this.getRowFoldEnd=function(e,t){var n=this.getFoldLine(e,t);return n?n.end.row:e},this.getFoldDisplayLine=function(e,t,n,r,i){r==null&&(r=e.start.row,i=0),t==null&&(t=e.end.row,n=this.getLine(t).length);var s=this.doc,o="";return e.walk(function(e,t,n,u){if(t<r)return;if(t==r){if(n<i)return;u=Math.max(i,u)}e!=null?o+=e:o+=s.getLine(t).substring(u,n)}.bind(this),t,n),o},this.getDisplayLine=function(e,t,n,r){var i=this.getFoldLine(e);if(!i){var s;return s=this.doc.getLine(e),s.substring(r||0,t||s.length)}return this.getFoldDisplayLine(i,e,t,n,r)},this.$cloneFoldData=function(){var e=[];return e=this.$foldData.map(function(t){var n=t.folds.map(function(e){return e.clone()});return new i(e,n)}),e},this.toggleFold=function(e){var t=this.selection,n=t.getRange(),r,i;if(n.isEmpty()){var s=n.start;r=this.getFoldAt(s.row,s.column);if(r){this.expandFold(r);return}(i=this.findMatchingBracket(s))?n.comparePoint(i)==1?n.end=i:(n.start=i,n.start.column++,n.end.column--):(i=this.findMatchingBracket({row:s.row,column:s.column+1}))?(n.comparePoint(i)==1?n.end=i:n.start=i,n.start.column++):n=this.getCommentFoldRange(s.row,s.column)||n}else{var o=this.getFoldsInRange(n);if(e&&o.length){this.expandFolds(o);return}o.length==1&&(r=o[0])}r||(r=this.getFoldAt(n.start.row,n.start.column));if(r&&r.range.toString()==n.toString()){this.expandFold(r);return}var u="...";if(!n.isMultiLine()){u=this.getTextRange(n);if(u.length<4)return;u=u.trim().substring(0,2)+".."}this.addFold(u,n)},this.getCommentFoldRange=function(e,t,n){var i=new o(this,e,t),s=i.getCurrentToken();if(s&&/^comment|string/.test(s.type)){var u=new r,a=new RegExp(s.type.replace(/\..*/,"\\."));if(n!=1){do s=i.stepBackward();while(s&&a.test(s.type));i.stepForward()}u.start.row=i.getCurrentTokenRow(),u.start.column=i.getCurrentTokenColumn()+2,i=new o(this,e,t);if(n!=-1){do s=i.stepForward();while(s&&a.test(s.type));s=i.stepBackward()}else s=i.getCurrentToken();return u.end.row=i.getCurrentTokenRow(),u.end.column=i.getCurrentTokenColumn()+s.value.length-2,u}},this.foldAll=function(e,t){var n=this.foldWidgets;t=t||this.getLength();for(var r=e||0;r<t;r++){n[r]==null&&(n[r]=this.getFoldWidget(r));if(n[r]!="start")continue;var i=this.getFoldWidgetRange(r);if(i&&i.end.row<=t)try{this.addFold("...",i)}catch(s){}}},this.$foldStyles={manual:1,markbegin:1,markbeginend:1},this.$foldStyle="markbegin",this.setFoldStyle=function(e){if(!this.$foldStyles[e])throw new Error("invalid fold style: "+e+"["+Object.keys(this.$foldStyles).join(", ")+"]");if(this.$foldStyle==e)return;this.$foldStyle=e,e=="manual"&&this.unfold();var t=this.$foldMode;this.$setFolding(null),this.$setFolding(t)},this.$setFolding=function(e){if(this.$foldMode==e)return;this.$foldMode=e,this.removeListener("change",this.$updateFoldWidgets),this._emit("changeAnnotation");if(!e||this.$foldStyle=="manual"){this.foldWidgets=null;return}this.foldWidgets=[],this.getFoldWidget=e.getFoldWidget.bind(e,this,this.$foldStyle),this.getFoldWidgetRange=e.getFoldWidgetRange.bind(e,this,this.$foldStyle),this.$updateFoldWidgets=this.updateFoldWidgets.bind(this),this.on("change",this.$updateFoldWidgets)},this.onFoldWidgetClick=function(e,t){t=t.domEvent;var n=this.getFoldWidget(e),r=this.getLine(e),i=t.shiftKey,s=i||t.ctrlKey||t.altKey||t.metaKey,o;n=="end"?o=this.getFoldAt(e,0,-1):o=this.getFoldAt(e,r.length,1);if(o){s?this.removeFold(o):this.expandFold(o);return}var u=this.getFoldWidgetRange(e);if(u){if(!u.isMultiLine()){o=this.getFoldAt(u.start.row,u.start.column,1);if(o&&u.isEqual(o.range)){this.removeFold(o);return}}i||this.addFold("...",u),s&&this.foldAll(u.start.row+1,u.end.row)}else s&&this.foldAll(e+1,this.getLength()),(t.target||t.srcElement).className+=" ace_invalid"},this.updateFoldWidgets=function(e){var t=e.data,n=t.range,r=n.start.row,i=n.end.row-r;if(i===0)this.foldWidgets[r]=null;else if(t.action=="removeText"||t.action=="removeLines")this.foldWidgets.splice(r,i+1,null);else{var s=Array(i+1);s.unshift(r,1),this.foldWidgets.splice.apply(this.foldWidgets,s)}}}var r=e("../range").Range,i=e("./fold_line").FoldLine,s=e("./fold").Fold,o=e("../token_iterator").TokenIterator;t.Folding=u}),ace.define("ace/edit_session/fold_line",["require","exports","module","ace/range"],function(e,t,n){function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.folds=[t];var n=t[t.length-1];this.range=new r(t[0].start.row,t[0].start.column,n.end.row,n.end.column),this.start=this.range.start,this.end=this.range.end,this.folds.forEach(function(e){e.setFoldLine(this)},this)}var r=e("../range").Range;(function(){this.shiftRow=function(e){this.start.row+=e,this.end.row+=e,this.folds.forEach(function(t){t.start.row+=e,t.end.row+=e})},this.addFold=function(e){if(e.sameRow){if(e.start.row<this.startRow||e.endRow>this.endRow)throw"Can't add a fold to this FoldLine as it has no connection";this.folds.push(e),this.folds.sort(function(e,t){return-e.range.compareEnd(t.start.row,t.start.column)}),this.range.compareEnd(e.start.row,e.start.column)>0?(this.end.row=e.end.row,this.end.column=e.end.column):this.range.compareStart(e.end.row,e.end.column)<0&&(this.start.row=e.start.row,this.start.column=e.start.column)}else if(e.start.row==this.end.row)this.folds.push(e),this.end.row=e.end.row,this.end.column=e.end.column;else{if(e.end.row!=this.start.row)throw"Trying to add fold to FoldRow that doesn't have a matching row";this.folds.unshift(e),this.start.row=e.start.row,this.start.column=e.start.column}e.foldLine=this},this.containsRow=function(e){return e>=this.start.row&&e<=this.end.row},this.walk=function(e,t,n){var r=0,i=this.folds,s,o,u,a=!0;t==null&&(t=this.end.row,n=this.end.column);for(var f=0;f<i.length;f++){s=i[f],o=s.range.compareStart(t,n);if(o==-1){e(null,t,n,r,a);return}u=e(null,s.start.row,s.start.column,r,a),u=!u&&e(s.placeholder,s.start.row,s.start.column,r);if(u||o==0)return;a=!s.sameRow,r=s.end.column}e(null,t,n,r,a)},this.getNextFoldTo=function(e,t){var n,r;for(var i=0;i<this.folds.length;i++){n=this.folds[i],r=n.range.compareEnd(e,t);if(r==-1)return{fold:n,kind:"after"};if(r==0)return{fold:n,kind:"inside"}}return null},this.addRemoveChars=function(e,t,n){var r=this.getNextFoldTo(e,t),i,s;if(r){i=r.fold;if(r.kind=="inside"&&i.start.column!=t&&i.start.row!=e)window.console&&window.console.log(e,t,i);else if(i.start.row==e){s=this.folds;var o=s.indexOf(i);o==0&&(this.start.column+=n);for(o;o<s.length;o++){i=s[o],i.start.column+=n;if(!i.sameRow)return;i.end.column+=n}this.end.column+=n}}},this.split=function(e,t){var n=this.getNextFoldTo(e,t).fold,r=this.folds,s=this.foldData;if(!n)return null;var o=r.indexOf(n),u=r[o-1];this.end.row=u.end.row,this.end.column=u.end.column,r=r.splice(o,r.length-o);var a=new i(s,r);return s.splice(s.indexOf(this)+1,0,a),a},this.merge=function(e){var t=e.folds;for(var n=0;n<t.length;n++)this.addFold(t[n]);var r=this.foldData;r.splice(r.indexOf(e),1)},this.toString=function(){var e=[this.range.toString()+": ["];return this.folds.forEach(function(t){e.push(" "+t.toString())}),e.push("]"),e.join("\n")},this.idxToPosition=function(e){var t=0,n;for(var r=0;r<this.folds.length;r++){var n=this.folds[r];e-=n.start.column-t;if(e<0)return{row:n.start.row,column:n.start.column+e};e-=n.placeholder.length;if(e<0)return n.start;t=n.end.column}return{row:this.end.row,column:this.end.column+e}}}).call(i.prototype),t.FoldLine=i}),ace.define("ace/edit_session/fold",["require","exports","module"],function(e,t,n){var r=t.Fold=function(e,t){this.foldLine=null,this.placeholder=t,this.range=e,this.start=e.start,this.end=e.end,this.sameRow=e.start.row==e.end.row,this.subFolds=[]};(function(){this.toString=function(){return'"'+this.placeholder+'" '+this.range.toString()},this.setFoldLine=function(e){this.foldLine=e,this.subFolds.forEach(function(t){t.setFoldLine(e)})},this.clone=function(){var e=this.range.clone(),t=new r(e,this.placeholder);return this.subFolds.forEach(function(e){t.subFolds.push(e.clone())}),t},this.addSubFold=function(e){if(this.range.isEqual(e))return this;if(!this.range.containsRange(e))throw"A fold can't intersect already existing fold"+e.range+this.range;var t=e.range.start.row,n=e.range.start.column;for(var r=0,i=-1;r<this.subFolds.length;r++){i=this.subFolds[r].range.compare(t,n);if(i!=1)break}var s=this.subFolds[r];if(i==0)return s.addSubFold(e);var t=e.range.end.row,n=e.range.end.column;for(var o=r,i=-1;o<this.subFolds.length;o++){i=this.subFolds[o].range.compare(t,n);if(i!=1)break}var u=this.subFolds[o];if(i==0)throw"A fold can't intersect already existing fold"+e.range+this.range;var a=this.subFolds.splice(r,o-r,e);return e.setFoldLine(this.foldLine),e}}).call(r.prototype)}),ace.define("ace/token_iterator",["require","exports","module"],function(e,t,n){var r=function(e,t,n){this.$session=e,this.$row=t,this.$rowTokens=e.getTokens(t);var r=e.getTokenAt(t,n);this.$tokenIndex=r?r.index:-1};(function(){this.stepBackward=function(){this.$tokenIndex-=1;while(this.$tokenIndex<0){this.$row-=1;if(this.$row<0)return this.$row=0,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=this.$rowTokens.length-1}return this.$rowTokens[this.$tokenIndex]},this.stepForward=function(){var e=this.$session.getLength();this.$tokenIndex+=1;while(this.$tokenIndex>=this.$rowTokens.length){this.$row+=1;if(this.$row>=e)return this.$row=e-1,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=0}return this.$rowTokens[this.$tokenIndex]},this.getCurrentToken=function(){return this.$rowTokens[this.$tokenIndex]},this.getCurrentTokenRow=function(){return this.$row},this.getCurrentTokenColumn=function(){var e=this.$rowTokens,t=this.$tokenIndex,n=e[t].start;if(n!==undefined)return n;n=0;while(t>0)t-=1,n+=e[t].value.length;return n}}).call(r.prototype),t.TokenIterator=r}),ace.define("ace/edit_session/bracket_match",["require","exports","module","ace/token_iterator","ace/range"],function(e,t,n){function s(){this.findMatchingBracket=function(e,t){if(e.column==0)return null;var n=t||this.getLine(e.row).charAt(e.column-1);if(n=="")return null;var r=n.match(/([\(\[\{])|([\)\]\}])/);return r?r[1]?this.$findClosingBracket(r[1],e):this.$findOpeningBracket(r[2],e):null},this.getBracketRange=function(e){var t=this.getLine(e.row),n=!0,r,s=t.charAt(e.column-1),o=s&&s.match(/([\(\[\{])|([\)\]\}])/);o||(s=t.charAt(e.column),e={row:e.row,column:e.column+1},o=s&&s.match(/([\(\[\{])|([\)\]\}])/),n=!1);if(!o)return null;if(o[1]){var u=this.$findClosingBracket(o[1],e);if(!u)return null;r=i.fromPoints(e,u),n||(r.end.column++,r.start.column--),r.cursor=r.end}else{var u=this.$findOpeningBracket(o[2],e);if(!u)return null;r=i.fromPoints(u,e),n||(r.start.column++,r.end.column--),r.cursor=r.start}return r},this.$brackets={")":"(","(":")","]":"[","[":"]","{":"}","}":"{"},this.$findOpeningBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("rparen",".paren")+")+"));var a=t.column-o.getCurrentTokenColumn()-2,f=u.value;for(;;){while(a>=0){var l=f.charAt(a);if(l==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else l==e&&(s+=1);a-=1}do u=o.stepBackward();while(u&&!n.test(u.type));if(u==null)break;f=u.value,a=f.length-1}return null},this.$findClosingBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("lparen",".paren")+")+"));var a=t.column-o.getCurrentTokenColumn();for(;;){var f=u.value,l=f.length;while(a<l){var c=f.charAt(a);if(c==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else c==e&&(s+=1);a+=1}do u=o.stepForward();while(u&&!n.test(u.type));if(u==null)break;a=0}return null}}var r=e("../token_iterator").TokenIterator,i=e("../range").Range;t.BracketMatch=s}),ace.define("ace/search",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(){this.$options={}};(function(){this.set=function(e){return i.mixin(this.$options,e),this},this.getOptions=function(){return r.copyObject(this.$options)},this.setOptions=function(e){this.$options=e},this.find=function(e){var t=this.$matchIterator(e,this.$options);if(!t)return!1;var n=null;return t.forEach(function(e,t,r){if(!e.start){var i=e.offset+(r||0);n=new s(t,i,t,i+e.length)}else n=e;return!0}),n},this.findAll=function(e){var t=this.$options;if(!t.needle)return[];this.$assembleRegExp(t);var n=t.range,i=n?e.getLines(n.start.row,n.end.row):e.doc.getAllLines(),o=[],u=t.re;if(t.$isMultiLine){var a=u.length,f=i.length-a;for(var l=u.offset||0;l<=f;l++){for(var c=0;c<a;c++)if(i[l+c].search(u[c])==-1)break;var h=i[l],p=i[l+a-1],d=h.match(u[0])[0].length,v=p.match(u[a-1])[0].length;o.push(new s(l,h.length-d,l+a-1,v))}}else for(var m=0;m<i.length;m++){var g=r.getMatchOffsets(i[m],u);for(var c=0;c<g.length;c++){var y=g[c];o.push(new s(m,y.offset,m,y.offset+y.length))}}if(n){var b=n.start.column,w=n.start.column,m=0,c=o.length-1;while(m<c&&o[m].start.column<b&&o[m].start.row==n.start.row)m++;while(m<c&&o[c].end.column>w&&o[c].end.row==n.end.row)c--;return o.slice(m,c+1)}return o},this.replace=function(e,t){var n=this.$options,r=this.$assembleRegExp(n);if(n.$isMultiLine)return t;if(!r)return;var i=r.exec(e);if(!i||i[0].length!=e.length)return null;t=e.replace(r,t);if(n.preserveCase){t=t.split("");for(var s=Math.min(e.length,e.length);s--;){var o=e[s];o&&o.toLowerCase()!=o?t[s]=t[s].toUpperCase():t[s]=t[s].toLowerCase()}t=t.join("")}return t},this.$matchIterator=function(e,t){var n=this.$assembleRegExp(t);if(!n)return!1;var i=this,o,u=t.backwards;if(t.$isMultiLine)var a=n.length,f=function(t,r,i){var u=t.search(n[0]);if(u==-1)return;for(var f=1;f<a;f++){t=e.getLine(r+f);if(t.search(n[f])==-1)return}var l=t.match(n[a-1])[0].length,c=new s(r,u,r+a-1,l);n.offset==1?(c.start.row--,c.start.column=Number.MAX_VALUE):i&&(c.start.column+=i);if(o(c))return!0};else if(u)var f=function(e,t,i){var s=r.getMatchOffsets(e,n);for(var u=s.length-1;u>=0;u--)if(o(s[u],t,i))return!0};else var f=function(e,t,i){var s=r.getMatchOffsets(e,n);for(var u=0;u<s.length;u++)if(o(s[u],t,i))return!0};return{forEach:function(n){o=n,i.$lineIterator(e,t).forEach(f)}}},this.$assembleRegExp=function(e){if(e.needle instanceof RegExp)return e.re=e.needle;var t=e.needle;if(!e.needle)return e.re=!1;e.regExp||(t=r.escapeRegExp(t)),e.wholeWord&&(t="\\b"+t+"\\b");var n=e.caseSensitive?"g":"gi";e.$isMultiLine=/[\n\r]/.test(t);if(e.$isMultiLine)return e.re=this.$assembleMultilineRegExp(t,n);try{var i=new RegExp(t,n)}catch(s){i=!1}return e.re=i},this.$assembleMultilineRegExp=function(e,t){var n=e.replace(/\r\n|\r|\n/g,"$\n^").split("\n"),r=[];for(var i=0;i<n.length;i++)try{r.push(new RegExp(n[i],t))}catch(s){return!1}return n[0]==""?(r.shift(),r.offset=1):r.offset=0,r},this.$lineIterator=function(e,t){var n=t.backwards==1,r=t.skipCurrent!=0,i=t.range,s=t.start;s||(s=i?i[n?"end":"start"]:e.selection.getRange()),s.start&&(s=s[r!=n?"end":"start"]);var o=i?i.start.row:0,u=i?i.end.row:e.getLength()-1,a=n?function(n){var r=s.row,i=e.getLine(r).substring(0,s.column);if(n(i,r))return;for(r--;r>=o;r--)if(n(e.getLine(r),r))return;if(t.wrap==0)return;for(r=u,o=s.row;r>=o;r--)if(n(e.getLine(r),r))return}:function(n){var r=s.row,i=e.getLine(r).substr(s.column);if(n(i,r,s.column))return;for(r+=1;r<=u;r++)if(n(e.getLine(r),r))return;if(t.wrap==0)return;for(r=o,u=s.row;r<=u;r++)if(n(e.getLine(r),r))return};return{forEach:a}}}).call(o.prototype),t.Search=o}),ace.define("ace/commands/command_manager",["require","exports","module","ace/lib/oop","ace/keyboard/hash_handler","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/oop"),i=e("../keyboard/hash_handler").HashHandler,s=e("../lib/event_emitter").EventEmitter,o=function(e,t){this.platform=e,this.commands=this.byName={},this.commmandKeyBinding={},this.addCommands(t),this.setDefaultHandler("exec",function(e){return e.command.exec(e.editor,e.args||{})})};r.inherits(o,i),function(){r.implement(this,s),this.exec=function(e,t,n){typeof e=="string"&&(e=this.commands[e]);if(!e)return!1;if(t&&t.$readOnly&&!e.readOnly)return!1;var r=this._emit("exec",{editor:t,command:e,args:n});return r===!1?!1:!0},this.toggleRecording=function(e){if(this.$inReplay)return;return e&&e._emit("changeStatus"),this.recording?(this.macro.pop(),this.removeEventListener("exec",this.$addCommandToMacro),this.macro.length||(this.macro=this.oldMacro),this.recording=!1):(this.$addCommandToMacro||(this.$addCommandToMacro=function(e){this.macro.push([e.command,e.args])}.bind(this)),this.oldMacro=this.macro,this.macro=[],this.on("exec",this.$addCommandToMacro),this.recording=!0)},this.replay=function(e){if(this.$inReplay||!this.macro)return;if(this.recording)return this.toggleRecording(e);try{this.$inReplay=!0,this.macro.forEach(function(t){typeof t=="string"?this.exec(t,e):this.exec(t[0],e,t[1])},this)}finally{this.$inReplay=!1}},this.trimMacro=function(e){return e.map(function(e){return typeof e[0]!="string"&&(e[0]=e[0].name),e[1]||(e=e[0]),e})}}.call(o.prototype),t.CommandManager=o}),ace.define("ace/keyboard/hash_handler",["require","exports","module","ace/lib/keys"],function(e,t,n){function i(e,t){this.platform=t,this.commands={},this.commmandKeyBinding={},this.addCommands(e)}var r=e("../lib/keys");(function(){this.addCommand=function(e){this.commands[e.name]&&this.removeCommand(e),this.commands[e.name]=e,e.bindKey&&this._buildKeyHash(e)},this.removeCommand=function(e){var t=typeof e=="string"?e:e.name;e=this.commands[t],delete this.commands[t];var n=this.commmandKeyBinding;for(var r in n)for(var i in n[r])n[r][i]==e&&delete n[r][i]},this.bindKey=function(e,t){if(!e)return;if(typeof t=="function"){this.addCommand({exec:t,bindKey:e,name:e});return}var n=this.commmandKeyBinding;e.split("|").forEach(function(e){var r=this.parseKeys(e,t),i=r.hashId;(n[i]||(n[i]={}))[r.key]=t},this)},this.addCommands=function(e){e&&Object.keys(e).forEach(function(t){var n=e[t];if(typeof n=="string")return this.bindKey(n,t);typeof n=="function"&&(n={exec:n}),n.name||(n.name=t),this.addCommand(n)},this)},this.removeCommands=function(e){Object.keys(e).forEach(function(t){this.removeCommand(e[t])},this)},this.bindKeys=function(e){Object.keys(e).forEach(function(t){this.bindKey(t,e[t])},this)},this._buildKeyHash=function(e){var t=e.bindKey;if(!t)return;var n=typeof t=="string"?t:t[this.platform];this.bindKey(n,e)},this.parseKeys=function(e){var t=e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(e){return e}),n=t.pop(),i=r[n];if(r.FUNCTION_KEYS[i])n=r.FUNCTION_KEYS[i].toLowerCase();else{if(!t.length)return{key:n,hashId:-1};if(t.length==1&&t[0]=="shift")return{key:n.toUpperCase(),hashId:-1}}var s=0;for(var o=t.length;o--;){var u=r.KEY_MODS[t[o]];if(u==null)throw"invalid modifier "+t[o]+" in "+e;s|=u}return{key:n,hashId:s}},this.findKeyCommand=function(t,n){var r=this.commmandKeyBinding;return r[t]&&r[t][n]},this.handleKeyboard=function(e,t,n,r){return{command:this.findKeyCommand(t,n)}}}).call(i.prototype),t.HashHandler=i}),ace.define("ace/commands/default_commands",["require","exports","module","ace/lib/lang"],function(e,t,n){function i(e,t){return{win:e,mac:t}}var r=e("../lib/lang");t.commands=[{name:"selectall",bindKey:i("Ctrl-A","Command-A"),exec:function(e){e.selectAll()},readOnly:!0},{name:"centerselection",bindKey:i(null,"Ctrl-L"),exec:function(e){e.centerSelection()},readOnly:!0},{name:"gotoline",bindKey:i("Ctrl-L","Command-L"),exec:function(e){var t=parseInt(prompt("Enter line number:"),10);isNaN(t)||e.gotoLine(t)},readOnly:!0},{name:"fold",bindKey:i("Alt-L|Ctrl-F1","Command-Alt-L|Command-F1"),exec:function(e){e.session.toggleFold(!1)},readOnly:!0},{name:"unfold",bindKey:i("Alt-Shift-L|Ctrl-Shift-F1","Command-Alt-Shift-L|Command-Shift-F1"),exec:function(e){e.session.toggleFold(!0)},readOnly:!0},{name:"foldall",bindKey:i("Alt-0","Command-Option-0"),exec:function(e){e.session.foldAll()},readOnly:!0},{name:"unfoldall",bindKey:i("Alt-Shift-0","Command-Option-Shift-0"),exec:function(e){e.session.unfold()},readOnly:!0},{name:"findnext",bindKey:i("Ctrl-K","Command-G"),exec:function(e){e.findNext()},readOnly:!0},{name:"findprevious",bindKey:i("Ctrl-Shift-K","Command-Shift-G"),exec:function(e){e.findPrevious()},readOnly:!0},{name:"find",bindKey:i("Ctrl-F","Command-F"),exec:function(e){var t=prompt("Find:",e.getCopyText());e.find(t)},readOnly:!0},{name:"overwrite",bindKey:"Insert",exec:function(e){e.toggleOverwrite()},readOnly:!0},{name:"selecttostart",bindKey:i("Ctrl-Shift-Home","Command-Shift-Up"),exec:function(e){e.getSelection().selectFileStart()},multiSelectAction:"forEach",readOnly:!0},{name:"gotostart",bindKey:i("Ctrl-Home","Command-Home|Command-Up"),exec:function(e){e.navigateFileStart()},multiSelectAction:"forEach",readOnly:!0},{name:"selectup",bindKey:i("Shift-Up","Shift-Up"),exec:function(e){e.getSelection().selectUp()},multiSelectAction:"forEach",readOnly:!0},{name:"golineup",bindKey:i("Up","Up|Ctrl-P"),exec:function(e,t){e.navigateUp(t.times)},multiSelectAction:"forEach",readOnly:!0},{name:"selecttoend",bindKey:i("Ctrl-Shift-End","Command-Shift-Down"),exec:function(e){e.getSelection().selectFileEnd()},multiSelectAction:"forEach",readOnly:!0},{name:"gotoend",bindKey:i("Ctrl-End","Command-End|Command-Down"),exec:function(e){e.navigateFileEnd()},multiSelectAction:"forEach",readOnly:!0},{name:"selectdown",bindKey:i("Shift-Down","Shift-Down"),exec:function(e){e.getSelection().selectDown()},multiSelectAction:"forEach",readOnly:!0},{name:"golinedown",bindKey:i("Down","Down|Ctrl-N"),exec:function(e,t){e.navigateDown(t.times)},multiSelectAction:"forEach",readOnly:!0},{name:"selectwordleft",bindKey:i("Ctrl-Shift-Left","Option-Shift-Left"),exec:function(e){e.getSelection().selectWordLeft()},multiSelectAction:"forEach",readOnly:!0},{name:"gotowordleft",bindKey:i("Ctrl-Left","Option-Left"),exec:function(e){e.navigateWordLeft()},multiSelectAction:"forEach",readOnly:!0},{name:"selecttolinestart",bindKey:i("Alt-Shift-Left","Command-Shift-Left"),exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",readOnly:!0},{name:"gotolinestart",bindKey:i("Alt-Left|Home","Command-Left|Home|Ctrl-A"),exec:function(e){e.navigateLineStart()},multiSelectAction:"forEach",readOnly:!0},{name:"selectleft",bindKey:i("Shift-Left","Shift-Left"),exec:function(e){e.getSelection().selectLeft()},multiSelectAction:"forEach",readOnly:!0},{name:"gotoleft",bindKey:i("Left","Left|Ctrl-B"),exec:function(e,t){e.navigateLeft(t.times)},multiSelectAction:"forEach",readOnly:!0},{name:"selectwordright",bindKey:i("Ctrl-Shift-Right","Option-Shift-Right"),exec:function(e){e.getSelection().selectWordRight()},multiSelectAction:"forEach",readOnly:!0},{name:"gotowordright",bindKey:i("Ctrl-Right","Option-Right"),exec:function(e){e.navigateWordRight()},multiSelectAction:"forEach",readOnly:!0},{name:"selecttolineend",bindKey:i("Alt-Shift-Right","Command-Shift-Right"),exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",readOnly:!0},{name:"gotolineend",bindKey:i("Alt-Right|End","Command-Right|End|Ctrl-E"),exec:function(e){e.navigateLineEnd()},multiSelectAction:"forEach",readOnly:!0},{name:"selectright",bindKey:i("Shift-Right","Shift-Right"),exec:function(e){e.getSelection().selectRight()},multiSelectAction:"forEach",readOnly:!0},{name:"gotoright",bindKey:i("Right","Right|Ctrl-F"),exec:function(e,t){e.navigateRight(t.times)},multiSelectAction:"forEach",readOnly:!0},{name:"selectpagedown",bindKey:"Shift-PageDown",exec:function(e){e.selectPageDown()},readOnly:!0},{name:"pagedown",bindKey:i(null,"Option-PageDown"),exec:function(e){e.scrollPageDown()},readOnly:!0},{name:"gotopagedown",bindKey:i("PageDown","PageDown|Ctrl-V"),exec:function(e){e.gotoPageDown()},readOnly:!0},{name:"selectpageup",bindKey:"Shift-PageUp",exec:function(e){e.selectPageUp()},readOnly:!0},{name:"pageup",bindKey:i(null,"Option-PageUp"),exec:function(e){e.scrollPageUp()},readOnly:!0},{name:"gotopageup",bindKey:"PageUp",exec:function(e){e.gotoPageUp()},readOnly:!0},{name:"scrollup",bindKey:i("Ctrl-Up",null),exec:function(e){e.renderer.scrollBy(0,-2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"scrolldown",bindKey:i("Ctrl-Down",null),exec:function(e){e.renderer.scrollBy(0,2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"selectlinestart",bindKey:"Shift-Home",exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",readOnly:!0},{name:"selectlineend",bindKey:"Shift-End",exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",readOnly:!0},{name:"togglerecording",bindKey:i("Ctrl-Alt-E","Command-Option-E"),exec:function(e){e.commands.toggleRecording(e)},readOnly:!0},{name:"replaymacro",bindKey:i("Ctrl-Shift-E","Command-Shift-E"),exec:function(e){e.commands.replay(e)},readOnly:!0},{name:"jumptomatching",bindKey:i("Ctrl-P","Ctrl-Shift-P"),exec:function(e){e.jumpToMatching()},multiSelectAction:"forEach",readOnly:!0},{name:"selecttomatching",bindKey:i("Ctrl-Shift-P",null),exec:function(e){e.jumpToMatching(!0)},readOnly:!0},{name:"cut",exec:function(e){var t=e.getSelectionRange();e._emit("cut",t),e.selection.isEmpty()||(e.session.remove(t),e.clearSelection())},multiSelectAction:"forEach"},{name:"removeline",bindKey:i("Ctrl-D","Command-D"),exec:function(e){e.removeLines()},multiSelectAction:"forEach"},{name:"duplicateSelection",bindKey:i("Ctrl-Shift-D","Command-Shift-D"),exec:function(e){e.duplicateSelection()},multiSelectAction:"forEach"},{name:"sortlines",bindKey:i("Ctrl-Alt-S","Command-Alt-S"),exec:function(e){e.sortLines()},multiSelectAction:"forEach"},{name:"togglecomment",bindKey:i("Ctrl-/","Command-/"),exec:function(e){e.toggleCommentLines()},multiSelectAction:"forEach"},{name:"modifyNumberUp",bindKey:i("Ctrl-Shift-Up","Alt-Shift-Up"),exec:function(e){e.modifyNumber(1)},multiSelectAction:"forEach"},{name:"modifyNumberDown",bindKey:i("Ctrl-Shift-Down","Alt-Shift-Down"),exec:function(e){e.modifyNumber(-1)},multiSelectAction:"forEach"},{name:"replace",bindKey:i("Ctrl-R","Command-Option-F"),exec:function(e){var t=prompt("Find:",e.getCopyText());if(!t)return;var n=prompt("Replacement:");if(!n)return;e.replace(n,{needle:t})}},{name:"replaceall",bindKey:i("Ctrl-Shift-R","Command-Shift-Option-F"),exec:function(e){var t=prompt("Find:");if(!t)return;var n=prompt("Replacement:");if(!n)return;e.replaceAll(n,{needle:t})}},{name:"undo",bindKey:i("Ctrl-Z","Command-Z"),exec:function(e){e.undo()}},{name:"redo",bindKey:i("Ctrl-Shift-Z|Ctrl-Y","Command-Shift-Z|Command-Y"),exec:function(e){e.redo()}},{name:"copylinesup",bindKey:i("Alt-Shift-Up","Command-Option-Up"),exec:function(e){e.copyLinesUp()}},{name:"movelinesup",bindKey:i("Alt-Up","Option-Up"),exec:function(e){e.moveLinesUp()}},{name:"copylinesdown",bindKey:i("Alt-Shift-Down","Command-Option-Down"),exec:function(e){e.copyLinesDown()}},{name:"movelinesdown",bindKey:i("Alt-Down","Option-Down"),exec:function(e){e.moveLinesDown()}},{name:"del",bindKey:i("Delete","Delete|Ctrl-D"),exec:function(e){e.remove("right")},multiSelectAction:"forEach"},{name:"backspace",bindKey:i("Command-Backspace|Option-Backspace|Shift-Backspace|Backspace","Ctrl-Backspace|Command-Backspace|Shift-Backspace|Backspace|Ctrl-H"),exec:function(e){e.remove("left")},multiSelectAction:"forEach"},{name:"removetolinestart",bindKey:i("Alt-Backspace","Command-Backspace"),exec:function(e){e.removeToLineStart()},multiSelectAction:"forEach"},{name:"removetolineend",bindKey:i("Alt-Delete","Ctrl-K"),exec:function(e){e.removeToLineEnd()},multiSelectAction:"forEach"},{name:"removewordleft",bindKey:i("Ctrl-Backspace","Alt-Backspace|Ctrl-Alt-Backspace"),exec:function(e){e.removeWordLeft()},multiSelectAction:"forEach"},{name:"removewordright",bindKey:i("Ctrl-Delete","Alt-Delete"),exec:function(e){e.removeWordRight()},multiSelectAction:"forEach"},{name:"outdent",bindKey:i("Shift-Tab","Shift-Tab"),exec:function(e){e.blockOutdent()},multiSelectAction:"forEach"},{name:"indent",bindKey:i("Tab","Tab"),exec:function(e){e.indent()},multiSelectAction:"forEach"},{name:"insertstring",exec:function(e,t){e.insert(t)},multiSelectAction:"forEach"},{name:"inserttext",exec:function(e,t){e.insert(r.stringRepeat(t.text||"",t.times||1))},multiSelectAction:"forEach"},{name:"splitline",bindKey:i(null,"Ctrl-O"),exec:function(e){e.splitLine()},multiSelectAction:"forEach"},{name:"transposeletters",bindKey:i("Ctrl-T","Ctrl-T"),exec:function(e){e.transposeLetters()},multiSelectAction:function(e){e.transposeSelections(1)}},{name:"touppercase",bindKey:i("Ctrl-U","Ctrl-U"),exec:function(e){e.toUpperCase()},multiSelectAction:"forEach"},{name:"tolowercase",bindKey:i("Ctrl-Shift-U","Ctrl-Shift-U"),exec:function(e){e.toLowerCase()},multiSelectAction:"forEach"}]}),ace.define("ace/undomanager",["require","exports","module"],function(e,t,n){var r=function(){this.reset()};(function(){this.execute=function(e){var t=e.args[0];this.$doc=e.args[1],this.$undoStack.push(t),this.$redoStack=[]},this.undo=function(e){var t=this.$undoStack.pop(),n=null;return t&&(n=this.$doc.undoChanges(t,e),this.$redoStack.push(t)),n},this.redo=function(e){var t=this.$redoStack.pop(),n=null;return t&&(n=this.$doc.redoChanges(t,e),this.$undoStack.push(t)),n},this.reset=function(){this.$undoStack=[],this.$redoStack=[]},this.hasUndo=function(){return this.$undoStack.length>0},this.hasRedo=function(){return this.$redoStack.length>0}}).call(r.prototype),t.UndoManager=r}),ace.define("ace/virtual_renderer",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/useragent","ace/config","ace/lib/net","ace/layer/gutter","ace/layer/marker","ace/layer/text","ace/layer/cursor","ace/scrollbar","ace/renderloop","ace/lib/event_emitter"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/event"),o=e("./lib/useragent"),u=e("./config"),a=e("./lib/net"),f=e("./layer/gutter").Gutter,l=e("./layer/marker").Marker,c=e("./layer/text").Text,h=e("./layer/cursor").Cursor,p=e("./scrollbar").ScrollBar,d=e("./renderloop").RenderLoop,v=e("./lib/event_emitter").EventEmitter,m=".ace_editor {position: absolute;overflow: hidden;font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;font-size: 12px;}.ace_scroller {position: absolute;overflow: hidden;}.ace_content {position: absolute;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;cursor: text;}.ace_gutter {position: absolute;overflow : hidden;height: 100%;width: auto;cursor: default;z-index: 4;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUM2OEZDQTQ4RTU0MTFFMUEzM0VFRTM2RUY1M0RBMjYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUM2OEZDQTU4RTU0MTFFMUEzM0VFRTM2RUY1M0RBMjYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQzY4RkNBMjhFNTQxMUUxQTMzRUVFMzZFRjUzREEyNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQzY4RkNBMzhFNTQxMUUxQTMzRUVFMzZFRjUzREEyNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkgXxbAAAAJbSURBVHjapFNNaBNBFH4zs5vdZLP5sQmNpT82QY209heh1ioWisaDRcSKF0WKJ0GQnrzrxasHsR6EnlrwD0TagxJabaVEpFYxLWlLSS822tr87m66ccfd2GKyVhA6MMybgfe97/vmPUQphd0sZjto9XIn9OOsvlu2nkqRzVU+6vvlzPf8W6bk8dxQ0NPbxAALgCgg2JkaQuhzQau/El0zbmUA7U0Es8v2CiYmKQJHGO1QICCLoqilMhkmurDAyapKgqItezi/USRdJqEYY4D5jCy03ht2yMkkvL91jTTX10qzyyu2hruPRN7jgbH+EOsXcMLgYiThEgAMhABW85oqy1DXdRIdvP1AHJ2acQXvDIrVHcdQNrEKNYSVMSZGMjEzIIAwDXIo+6G/FxcGnzkC3T2oMhLjre49sBB+RRcHLqdafK6sYdE/GGBwU1VpFNj0aN8pJbe+BkZyevUrvLl6Xmm0W9IuTc0DxrDNAJd5oEvI/KRsNC3bQyNjPO9yQ1YHcfj2QvfQc/5TUhJTBc2iM0U7AWDQtc1nJHvD/cfO2s7jaGkiTEfa/Ep8coLu7zmNmh8+dc5lZDuUeFAGUNA/OY6JVaypQ0vjr7XYjUvJM37vt+j1vuTK5DgVfVUoTjVe+y3/LxMxY2GgU+CSLy4cpfsYorRXuXIOi0Vt40h67uZFTdIo6nLaZcwUJWAzwNS0tBnqqKzQDnjdG/iPyZxo46HaKUpbvYkj8qYRTZsBhge+JHhZyh0x9b95JqjVJkT084kZIPwu/mPWqPgfQ5jXh2+92Ay7HedfAgwA6KDWafb4w3cAAAAASUVORK5CYII=\");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUM2OEZDQTg4RTU0MTFFMUEzM0VFRTM2RUY1M0RBMjYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUM2OEZDQTk4RTU0MTFFMUEzM0VFRTM2RUY1M0RBMjYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQzY4RkNBNjhFNTQxMUUxQTMzRUVFMzZFRjUzREEyNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQzY4RkNBNzhFNTQxMUUxQTMzRUVFMzZFRjUzREEyNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pgd7PfIAAAGmSURBVHjaYvr//z8DJZiJgUIANoCRkREb9gLiSVAaQx4OQM7AAkwd7XU2/v++/rOttdYGEB9dASEvOMydGKfH8Gv/p4XTkvRBfLxeQAP+1cUhXopyvzhP7P/IoSj7g7Mw09cNKO6J1QQ0L4gICPIv/veg/8W+JdFvQNLHVsW9/nmn9zk7B+cCkDwhL7gt6knSZnx9/LuCEOcvkIAMP+cvto9nfqyZmmUAksfnBUtbM60gX/3/kgyv3/xSFOL5DZT+L8vP+Yfh5cvfPvp/xUHyQHXGyAYwgpwBjZYFT3Y1OEl/OfCH4ffv3wzc4iwMvNIsDJ+f/mH4+vIPAxsb631WW0Yln6ZpQLXdMK/DXGDflh+sIv37EivD5x//Gb7+YWT4y86sl7BCCkSD+Z++/1dkvsFRl+HnD1Rvje4F8whjMXmGj58YGf5zsDMwcnAwfPvKcml62DsQDeaDxN+/Y0qwlpEHqrdB94IRNIDUgfgfKJChGK4OikEW3gTiXUB950ASLFAF54AC94A0G9QAfOnmF9DCDzABFqS08IHYDIScdijOjQABBgC+/9awBH96jwAAAABJRU5ErkJggg==\");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url(\"data:image/gif;base64,R0lGODlhEAAQAMQAAAAAAEFBQVJSUl5eXmRkZGtra39/f4WFhYmJiZGRkaampry8vMPDw8zMzNXV1dzc3OTk5Orq6vDw8P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABQALAAAAAAQABAAAAUuICWOZGmeaBml5XGwFCQSBGyXRSAwtqQIiRuiwIM5BoYVbEFIyGCQoeJGrVptIQA7\");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRTk5MTVGREIxNDkxMUUxOTc5Q0FFREQyMTNGMjBFQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRTk5MTVGRUIxNDkxMUUxOTc5Q0FFREQyMTNGMjBFQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkZFOTkxNUZCQjE0OTExRTE5NzlDQUVERDIxM0YyMEVDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFOTkxNUZDQjE0OTExRTE5NzlDQUVERDIxM0YyMEVDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+SIDkjAAAAJ1JREFUeNpi/P//PwMlgImBQkB7A6qrq/+DMC55FkIGKCoq4pVnpFkgTp069f/+/fv/r1u37r+tre1/kg0A+ptn9uzZYLaRkRHpLvjw4cNXWVlZhufPnzOcO3eOdAO0tbVPAjHDmzdvGA4fPsxIsgGSkpJmv379Ynj37h2DjIyMCMkG3LhxQ/T27dsMampqDHZ2dq/pH41DxwCAAAMAFdc68dUsFZgAAAAASUVORK5CYII=\");}.ace_scrollbar {position: absolute;overflow-x: hidden;overflow-y: scroll;right: 0;}.ace_scrollbar-inner {position: absolute;width: 1px;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;}.ace_text-input.ace_composition {background: #fff;color: #000;z-index: 1000;opacity: 1;border: solid lightgray 1px;margin: -1px}.ace_layer {z-index: 1;position: absolute;overflow: hidden;white-space: nowrap;height: 100%;width: 100%;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;/* setting pointer-events: auto; on node under the mouse, which changesduring scroll, will break mouse wheel scrolling in Safari */pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;}.ace_text-layer {color: black;font: inherit !important;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_smooth-blinking .ace_cursor {-moz-transition: opacity 0.18s;-webkit-transition: opacity 0.18s;-o-transition: opacity 0.18s;-ms-transition: opacity 0.18s;transition: opacity 0.18s;}.ace_cursor[style*=\"opacity: 0\"]{-ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";}.ace_editor.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_line {white-space: nowrap;}.ace_marker-layer .ace_step {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}.ace_line .ace_fold {-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%11%00%00%00%09%08%06%00%00%00%D4%E8%C7%0C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C6lt%EEo%DF%AB%FEc%D5%9A%95%0C%11%F0%1C%20%BE%945%C4%22%E1Y%A0i%5C%D4t%13%E0%D6%89%EF%9D15%C2%CDLsX%A7%04%09%1Fg8oc%81%E1%8C%8D%23%96f45%40%9A%09%C2%07%C5B%3AK%B8%408%98i%E0%F3%0D%D8%CE%81%14%E4'%26%A9%92.%8B%3C%ABER%2F%E5dE%B2%0C%F6%F0%1Fs%83%F2_%B0%A8%94%E9%9B%AD%E7%10%8Dm%9A%19N%D1%7C%8A%DE%1F9%7Dp%8C%E6%00%D5%C1%3F_%18%BDA%B8%9DpX6%E3%A35~B%CD%24%AE%11%26%BD%E7%EEti%98%EDe%9A%97Y)%12%25%1C%24%BCbT%AE3li%E6%0B%03%89%9A%E6%D3%ED%F4P%92%B0%9F4%BF43Y%F3%E3%EDP%95%04%EB1%C5%F5%F6KF%F4%BA%BD%D7%DB%91%93%07%E35%3E%A7)%D6%7F%40%FE%BD%F7%F5r%8A%E5y%92%F0%EB%B4%1E%8D%D5%F4%5B%92%3AV%DB%DB%E4%CD%A6%23%C3%C4wQ%3F%03HB%82%8E%1Cd(%E0%91B%0Ca%9Ac%C4%AA%F8L%16%19%22J%A4%D2itTy%B28%D6%3B(%93%96%ED%1CGx%C9_%0E%B8%5E%16%F5%5B%B2%B8%F6%E0%FB%9E%DD%25%D7%8E%BC%15%85%C5%B7%A3%D8Q%ED%B5%81%E9%BA%B2%13%9A%1B%7Fua%A5%A3n%E17%B9%E5%9B%1Bm%AB%0B%08Q%FE%8A%E5%B1H%5Ee%CAO%82Q%D7u6%E6%90S%97%FCu%0B%CF2%94%EE%25v%12X%0C%BA%AC%F0%5E%F8*l%0AO%85%17%C2%97%BF%D4%C8%CE%DE%AD%11%CB%80q%2C%3E%AB%9ES%CD%C6%EC%25%D2L%D2%EBd%B8%BF%8A%F5B%C6%18%F9%901CZ%9D%BE%24M%9C%8A9%F2%DAP%0B'%06w%82%EB%E6%E2%5C%2F%D7%07%9E%BB%CC%5D%E1%FA%B9%08%AD.r%23%8E%C2%17%F5E%7C!%F0%BE3%BE%3E_%B7o%88a%A7%DB%BE%D3d%EB%A31Z%EB%BB%D3%91%BA%A2%B1z%94%8F%DB'%F6%3D%8E%AA%13%19%B2%B1%BE%B1~V%08%2B%B4%A2cjJ%B3tO%00%03%25mN%97%F3%05%93%EF%11%84%0B%7C%88%AE-%89%8F%ABbW%90O%2B%0Ao%99%0C%5E%97%0CI%AFH%D9.%B0%3B%8F%ED%03%B6S%D6%5D%E6i_s9%F3*p%E9%1B%FD%C3%EB.7U%06%5E%19%C0%D1s.%17%A03u%E4%09%B0%7C%5E%2C%EB%15%DB%1F%3C%9E%B7%80%91%3B%DBc%AD%3Dma%BA%8B%3EV%AB%DBt.%5B%1E%01%BB%0F%AB%D5%9F%CF%AA%D5%DD%E7%E4%7F%0Bx%A3%FC%06%A9%23%0A%D6%C2%A1_2%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%B5IDAT(%15%A5%91%3D%0E%02!%10%85ac%E1%05%D6%CE%D6%C6%CE%D2%E8%ED%CD%DE%C0%C6%D6N.%E0V%F8%3D%9Ca%891XH%C2%BE%D9y%3F%90!%E6%9C%C3%BFk%E5%011%C6-%F5%C8N%04%DF%BD%FF%89%DFt%83DN%60%3E%F3%AB%A0%DE%1A%5Dg%BE%10Q%97%1B%40%9C%A8o%10%8F%5E%828%B4%1B%60%87%F6%02%26%85%1Ch%1E%C1%2B%5Bk%FF%86%EE%B7j%09%9A%DA%9B%ACe%A3%F9%EC%DA!9%B4%D5%A6%81%86%86%98%CC%3C%5B%40%FA%81%B3%E9%CB%23%94%C16Azo%05%D4%E1%C1%95a%3B%8A'%A0%E8%CC%17%22%85%1D%BA%00%A2%FA%DC%0A%94%D1%D1%8D%8B%3A%84%17B%C7%60%1A%25Z%FC%8D%00%00%00%00IEND%AEB%60%82\"),url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%007%08%06%00%00%00%C4%DD%80C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C6lt%EEo%DF%AB%FEc%D5%9A%95%0C%11%F0%1C%20%BE%945%C4%22%E1Y%A0i%5C%D4t%13%E0%D6%89%EF%9D15%C2%CDLsX%A7%04%09%1Fg8oc%81%E1%8C%8D%23%96f45%40%9A%09%C2%07%C5B%3AK%B8%408%98i%E0%F3%0D%D8%CE%81%14%E4'%26%A9%92.%8B%3C%ABER%2F%E5dE%B2%0C%F6%F0%1Fs%83%F2_%B0%A8%94%E9%9B%AD%E7%10%8Dm%9A%19N%D1%7C%8A%DE%1F9%7Dp%8C%E6%00%D5%C1%3F_%18%BDA%B8%9DpX6%E3%A35~B%CD%24%AE%11%26%BD%E7%EEti%98%EDe%9A%97Y)%12%25%1C%24%BCbT%AE3li%E6%0B%03%89%9A%E6%D3%ED%F4P%92%B0%9F4%BF43Y%F3%E3%EDP%95%04%EB1%C5%F5%F6KF%F4%BA%BD%D7%DB%91%93%07%E35%3E%A7)%D6%7F%40%FE%BD%F7%F5r%8A%E5y%92%F0%EB%B4%1E%8D%D5%F4%5B%92%3AV%DB%DB%E4%CD%A6%23%C3%C4wQ%3F%03HB%82%8E%1Cd(%E0%91B%0Ca%9Ac%C4%AA%F8L%16%19%22J%A4%D2itTy%B28%D6%3B(%93%96%ED%1CGx%C9_%0E%B8%5E%16%F5%5B%B2%B8%F6%E0%FB%9E%DD%25%D7%8E%BC%15%85%C5%B7%A3%D8Q%ED%B5%81%E9%BA%B2%13%9A%1B%7Fua%A5%A3n%E17%B9%E5%9B%1Bm%AB%0B%08Q%FE%8A%E5%B1H%5Ee%CAO%82Q%D7u6%E6%90S%97%FCu%0B%CF2%94%EE%25v%12X%0C%BA%AC%F0%5E%F8*l%0AO%85%17%C2%97%BF%D4%C8%CE%DE%AD%11%CB%80q%2C%3E%AB%9ES%CD%C6%EC%25%D2L%D2%EBd%B8%BF%8A%F5B%C6%18%F9%901CZ%9D%BE%24M%9C%8A9%F2%DAP%0B'%06w%82%EB%E6%E2%5C%2F%D7%07%9E%BB%CC%5D%E1%FA%B9%08%AD.r%23%8E%C2%17%F5E%7C!%F0%BE3%BE%3E_%B7o%88a%A7%DB%BE%D3d%EB%A31Z%EB%BB%D3%91%BA%A2%B1z%94%8F%DB'%F6%3D%8E%AA%13%19%B2%B1%BE%B1~V%08%2B%B4%A2cjJ%B3tO%00%03%25mN%97%F3%05%93%EF%11%84%0B%7C%88%AE-%89%8F%ABbW%90O%2B%0Ao%99%0C%5E%97%0CI%AFH%D9.%B0%3B%8F%ED%03%B6S%D6%5D%E6i_s9%F3*p%E9%1B%FD%C3%EB.7U%06%5E%19%C0%D1s.%17%A03u%E4%09%B0%7C%5E%2C%EB%15%DB%1F%3C%9E%B7%80%91%3B%DBc%AD%3Dma%BA%8B%3EV%AB%DBt.%5B%1E%01%BB%0F%AB%D5%9F%CF%AA%D5%DD%E7%E4%7F%0Bx%A3%FC%06%A9%23%0A%D6%C2%A1_2%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%3AIDAT8%11c%FC%FF%FF%7F%18%03%1A%60%01%F2%3F%A0%891%80%04%FF%11-%F8%17%9BJ%E2%05%B1ZD%81v%26t%E7%80%F8%A3%82h%A12%1A%20%A3%01%02%0F%01%BA%25%06%00%19%C0%0D%AEF%D5%3ES%00%00%00%00IEND%AEB%60%82\");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%11%00%00%00%09%08%06%00%00%00%D4%E8%C7%0C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C6lt%EEo%DF%AB%FEc%D5%9A%95%0C%11%F0%1C%20%BE%945%C4%22%E1Y%A0i%5C%D4t%13%E0%D6%89%EF%9D15%C2%CDLsX%A7%04%09%1Fg8oc%81%E1%8C%8D%23%96f45%40%9A%09%C2%07%C5B%3AK%B8%408%98i%E0%F3%0D%D8%CE%81%14%E4'%26%A9%92.%8B%3C%ABER%2F%E5dE%B2%0C%F6%F0%1Fs%83%F2_%B0%A8%94%E9%9B%AD%E7%10%8Dm%9A%19N%D1%7C%8A%DE%1F9%7Dp%8C%E6%00%D5%C1%3F_%18%BDA%B8%9DpX6%E3%A35~B%CD%24%AE%11%26%BD%E7%EEti%98%EDe%9A%97Y)%12%25%1C%24%BCbT%AE3li%E6%0B%03%89%9A%E6%D3%ED%F4P%92%B0%9F4%BF43Y%F3%E3%EDP%95%04%EB1%C5%F5%F6KF%F4%BA%BD%D7%DB%91%93%07%E35%3E%A7)%D6%7F%40%FE%BD%F7%F5r%8A%E5y%92%F0%EB%B4%1E%8D%D5%F4%5B%92%3AV%DB%DB%E4%CD%A6%23%C3%C4wQ%3F%03HB%82%8E%1Cd(%E0%91B%0Ca%9Ac%C4%AA%F8L%16%19%22J%A4%D2itTy%B28%D6%3B(%93%96%ED%1CGx%C9_%0E%B8%5E%16%F5%5B%B2%B8%F6%E0%FB%9E%DD%25%D7%8E%BC%15%85%C5%B7%A3%D8Q%ED%B5%81%E9%BA%B2%13%9A%1B%7Fua%A5%A3n%E17%B9%E5%9B%1Bm%AB%0B%08Q%FE%8A%E5%B1H%5Ee%CAO%82Q%D7u6%E6%90S%97%FCu%0B%CF2%94%EE%25v%12X%0C%BA%AC%F0%5E%F8*l%0AO%85%17%C2%97%BF%D4%C8%CE%DE%AD%11%CB%80q%2C%3E%AB%9ES%CD%C6%EC%25%D2L%D2%EBd%B8%BF%8A%F5B%C6%18%F9%901CZ%9D%BE%24M%9C%8A9%F2%DAP%0B'%06w%82%EB%E6%E2%5C%2F%D7%07%9E%BB%CC%5D%E1%FA%B9%08%AD.r%23%8E%C2%17%F5E%7C!%F0%BE3%BE%3E_%B7o%88a%A7%DB%BE%D3d%EB%A31Z%EB%BB%D3%91%BA%A2%B1z%94%8F%DB'%F6%3D%8E%AA%13%19%B2%B1%BE%B1~V%08%2B%B4%A2cjJ%B3tO%00%03%25mN%97%F3%05%93%EF%11%84%0B%7C%88%AE-%89%8F%ABbW%90O%2B%0Ao%99%0C%5E%97%0CI%AFH%D9.%B0%3B%8F%ED%03%B6S%D6%5D%E6i_s9%F3*p%E9%1B%FD%C3%EB.7U%06%5E%19%C0%D1s.%17%A03u%E4%09%B0%7C%5E%2C%EB%15%DB%1F%3C%9E%B7%80%91%3B%DBc%AD%3Dma%BA%8B%3EV%AB%DBt.%5B%1E%01%BB%0F%AB%D5%9F%CF%AA%D5%DD%E7%E4%7F%0Bx%A3%FC%06%A9%23%0A%D6%C2%A1_2%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%B5IDAT(%15%A5%91%3D%0E%02!%10%85ac%E1%05%D6%CE%D6%C6%CE%D2%E8%ED%CD%DE%C0%C6%D6N.%E0V%F8%3D%9Ca%891XH%C2%BE%D9y%3F%90!%E6%9C%C3%BFk%E5%011%C6-%F5%C8N%04%DF%BD%FF%89%DFt%83DN%60%3E%F3%AB%A0%DE%1A%5Dg%BE%10Q%97%1B%40%9C%A8o%10%8F%5E%828%B4%1B%60%87%F6%02%26%85%1Ch%1E%C1%2B%5Bk%FF%86%EE%B7j%09%9A%DA%9B%ACe%A3%F9%EC%DA!9%B4%D5%A6%81%86%86%98%CC%3C%5B%40%FA%81%B3%E9%CB%23%94%C16Azo%05%D4%E1%C1%95a%3B%8A'%A0%E8%CC%17%22%85%1D%BA%00%A2%FA%DC%0A%94%D1%D1%8D%8B%3A%84%17B%C7%60%1A%25Z%FC%8D%00%00%00%00IEND%AEB%60%82\"),url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%007%08%06%00%00%00%C4%DD%80C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C6lt%EEo%DF%AB%FEc%D5%9A%95%0C%11%F0%1C%20%BE%945%C4%22%E1Y%A0i%5C%D4t%13%E0%D6%89%EF%9D15%C2%CDLsX%A7%04%09%1Fg8oc%81%E1%8C%8D%23%96f45%40%9A%09%C2%07%C5B%3AK%B8%408%98i%E0%F3%0D%D8%CE%81%14%E4'%26%A9%92.%8B%3C%ABER%2F%E5dE%B2%0C%F6%F0%1Fs%83%F2_%B0%A8%94%E9%9B%AD%E7%10%8Dm%9A%19N%D1%7C%8A%DE%1F9%7Dp%8C%E6%00%D5%C1%3F_%18%BDA%B8%9DpX6%E3%A35~B%CD%24%AE%11%26%BD%E7%EEti%98%EDe%9A%97Y)%12%25%1C%24%BCbT%AE3li%E6%0B%03%89%9A%E6%D3%ED%F4P%92%B0%9F4%BF43Y%F3%E3%EDP%95%04%EB1%C5%F5%F6KF%F4%BA%BD%D7%DB%91%93%07%E35%3E%A7)%D6%7F%40%FE%BD%F7%F5r%8A%E5y%92%F0%EB%B4%1E%8D%D5%F4%5B%92%3AV%DB%DB%E4%CD%A6%23%C3%C4wQ%3F%03HB%82%8E%1Cd(%E0%91B%0Ca%9Ac%C4%AA%F8L%16%19%22J%A4%D2itTy%B28%D6%3B(%93%96%ED%1CGx%C9_%0E%B8%5E%16%F5%5B%B2%B8%F6%E0%FB%9E%DD%25%D7%8E%BC%15%85%C5%B7%A3%D8Q%ED%B5%81%E9%BA%B2%13%9A%1B%7Fua%A5%A3n%E17%B9%E5%9B%1Bm%AB%0B%08Q%FE%8A%E5%B1H%5Ee%CAO%82Q%D7u6%E6%90S%97%FCu%0B%CF2%94%EE%25v%12X%0C%BA%AC%F0%5E%F8*l%0AO%85%17%C2%97%BF%D4%C8%CE%DE%AD%11%CB%80q%2C%3E%AB%9ES%CD%C6%EC%25%D2L%D2%EBd%B8%BF%8A%F5B%C6%18%F9%901CZ%9D%BE%24M%9C%8A9%F2%DAP%0B'%06w%82%EB%E6%E2%5C%2F%D7%07%9E%BB%CC%5D%E1%FA%B9%08%AD.r%23%8E%C2%17%F5E%7C!%F0%BE3%BE%3E_%B7o%88a%A7%DB%BE%D3d%EB%A31Z%EB%BB%D3%91%BA%A2%B1z%94%8F%DB'%F6%3D%8E%AA%13%19%B2%B1%BE%B1~V%08%2B%B4%A2cjJ%B3tO%00%03%25mN%97%F3%05%93%EF%11%84%0B%7C%88%AE-%89%8F%ABbW%90O%2B%0Ao%99%0C%5E%97%0CI%AFH%D9.%B0%3B%8F%ED%03%B6S%D6%5D%E6i_s9%F3*p%E9%1B%FD%C3%EB.7U%06%5E%19%C0%D1s.%17%A03u%E4%09%B0%7C%5E%2C%EB%15%DB%1F%3C%9E%B7%80%91%3B%DBc%AD%3Dma%BA%8B%3EV%AB%DBt.%5B%1E%01%BB%0F%AB%D5%9F%CF%AA%D5%DD%E7%E4%7F%0Bx%A3%FC%06%A9%23%0A%D6%C2%A1_2%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%003IDAT8%11c%FC%FF%FF%7F%3E%03%1A%60%01%F2%3F%A3%891%80%04%FFQ%26%F8w%C0%B43%A1%DB%0C%E2%8F%0A%A2%85%CAh%80%8C%06%08%3C%04%E8%96%18%00%A3S%0D%CD%CF%D8%C1%9D%00%00%00%00IEND%AEB%60%82\");background-repeat: no-repeat, repeat-x;background-position: center center, top left;}.ace_editor.ace_dragging .ace_content {cursor: move;}.ace_gutter-tooltip {background-color: #FFFFD5;border: 1px solid gray;box-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);color: black;display: inline-block;padding: 4px;position: absolute;z-index: 300;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;cursor: default;white-space: pre-line;word-wrap: break-word;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;margin: 0 -12px 0 1px;display: inline-block;width: 11px;vertical-align: top;background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%00%05%08%06%00%00%00%8Do%26%E5%00%00%004IDATx%DAe%8A%B1%0D%000%0C%C2%F2%2CK%96%BC%D0%8F9%81%88H%E9%D0%0E%96%C0%10%92%3E%02%80%5E%82%E4%A9*-%EEsw%C8%CC%11%EE%96w%D8%DC%E9*Eh%0C%151(%00%00%00%00IEND%AEB%60%82\");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;}.ace_fold-widget.ace_end {background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%00%05%08%06%00%00%00%8Do%26%E5%00%00%004IDATx%DAm%C7%C1%09%000%08C%D1%8C%ECE%C8E(%8E%EC%02)%1EZJ%F1%C1'%04%07I%E1%E5%EE%CAL%F5%A2%99%99%22%E2%D6%1FU%B5%FE0%D9x%A7%26Wz5%0E%D5%00%00%00%00IEND%AEB%60%82\");}.ace_fold-widget.ace_closed {background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%03%00%00%00%06%08%06%00%00%00%06%E5%24%0C%00%00%009IDATx%DA5%CA%C1%09%000%08%03%C0%AC*(%3E%04%C1%0D%BA%B1%23%A4Uh%E0%20%81%C0%CC%F8%82%81%AA%A2%AArGfr%88%08%11%11%1C%DD%7D%E0%EE%5B%F6%F6%CB%B8%05Q%2F%E9tai%D9%00%00%00%00IEND%AEB%60%82\");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}/*** Dark version for fold widgets*/.ace_dark .ace_fold-widget {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC\");}.ace_dark .ace_fold-widget.ace_end {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==\");}.ace_dark .ace_fold-widget.ace_closed {background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==\");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {-moz-transition: opacity 0.4s ease 0.05s;-webkit-transition: opacity 0.4s ease 0.05s;-o-transition: opacity 0.4s ease 0.05s;-ms-transition: opacity 0.4s ease 0.05s;transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {-moz-transition: opacity 0.05s ease 0.05s;-webkit-transition: opacity 0.05s ease 0.05s;-o-transition: opacity 0.05s ease 0.05s;-ms-transition: opacity 0.05s ease 0.05s;transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}";i.importCssString(m,"ace_editor");var g=function(e,t){var n=this;this.container=e,this.$keepTextAreaAtCursor=!o.isIE,i.addCssClass(e,"ace_editor"),this.setTheme(t),this.$gutter=i.createElement("div"),this.$gutter.className="ace_gutter",this.container.appendChild(this.$gutter),this.scroller=i.createElement("div"),this.scroller.className="ace_scroller",this.container.appendChild(this.scroller),this.content=i.createElement("div"),this.content.className="ace_content",this.scroller.appendChild(this.content),this.setHighlightGutterLine(!0),this.$gutterLayer=new f(this.$gutter),this.$gutterLayer.on("changeGutterWidth",this.onGutterResize.bind(this)),this.$markerBack=new l(this.content);var r=this.$textLayer=new c(this.content);this.canvas=r.element,this.$markerFront=new l(this.content),this.$cursorLayer=new h(this.content),this.$horizScroll=!1,this.$horizScrollAlwaysVisible=!1,this.$animatedScroll=!1,this.scrollBar=new p(e),this.scrollBar.addEventListener("scroll",function(e){n.$inScrollAnimation||n.session.setScrollTop(e.data)}),this.scrollTop=0,this.scrollLeft=0,s.addListener(this.scroller,"scroll",function(){var e=n.scroller.scrollLeft;n.scrollLeft=e,n.session.setScrollLeft(e)}),this.cursorPos={row:0,column:0},this.$textLayer.addEventListener("changeCharacterSize",function(){n.updateCharacterSize(),n.onResize(!0)}),this.$size={width:0,height:0,scrollerHeight:0,scrollerWidth:0},this.layerConfig={width:1,padding:0,firstRow:0,firstRowScreen:0,lastRow:0,lineHeight:1,characterWidth:1,minHeight:1,maxHeight:1,offset:0,height:1},this.$loop=new d(this.$renderChanges.bind(this),this.container.ownerDocument.defaultView),this.$loop.schedule(this.CHANGE_FULL),this.updateCharacterSize(),this.setPadding(4)};(function(){this.showGutter=!0,this.CHANGE_CURSOR=1,this.CHANGE_MARKER=2,this.CHANGE_GUTTER=4,this.CHANGE_SCROLL=8,this.CHANGE_LINES=16,this.CHANGE_TEXT=32,this.CHANGE_SIZE=64,this.CHANGE_MARKER_BACK=128,this.CHANGE_MARKER_FRONT=256,this.CHANGE_FULL=512,this.CHANGE_H_SCROLL=1024,r.implement(this,v),this.updateCharacterSize=function(){this.$textLayer.allowBoldFonts!=this.$allowBoldFonts&&(this.$allowBoldFonts=this.$textLayer.allowBoldFonts,this.setStyle("ace_nobold",!this.$allowBoldFonts)),this.characterWidth=this.$textLayer.getCharacterWidth(),this.lineHeight=this.$textLayer.getLineHeight(),this.$updatePrintMargin()},this.setSession=function(e){this.session=e,this.scroller.className="ace_scroller",this.$cursorLayer.setSession(e),this.$markerBack.setSession(e),this.$markerFront.setSession(e),this.$gutterLayer.setSession(e),this.$textLayer.setSession(e),this.$loop.schedule(this.CHANGE_FULL)},this.updateLines=function(e,t){t===undefined&&(t=Infinity),this.$changedLines?(this.$changedLines.firstRow>e&&(this.$changedLines.firstRow=e),this.$changedLines.lastRow<t&&(this.$changedLines.lastRow=t)):this.$changedLines={firstRow:e,lastRow:t};if(this.$changedLines.firstRow>this.layerConfig.lastRow||this.$changedLines.lastRow<this.layerConfig.firstRow)return;this.$loop.schedule(this.CHANGE_LINES)},this.onChangeTabSize=function(){this.$loop.schedule(this.CHANGE_TEXT|this.CHANGE_MARKER),this.$textLayer.onChangeTabSize()},this.updateText=function(){this.$loop.schedule(this.CHANGE_TEXT)},this.updateFull=function(e){e?this.$renderChanges(this.CHANGE_FULL,!0):this.$loop.schedule(this.CHANGE_FULL)},this.updateFontSize=function(){this.$textLayer.checkForSizeChanges()},this.onResize=function(e,t,n,r){var s=this.CHANGE_SIZE,o=this.$size;if(this.resizing>2)return;this.resizing>1?this.resizing++:this.resizing=e?1:0,r||(r=i.getInnerHeight(this.container));if(e||o.height!=r)o.height=r,this.scroller.style.height=r+"px",o.scrollerHeight=this.scroller.clientHeight,this.scrollBar.setHeight(o.scrollerHeight),this.session&&(this.session.setScrollTop(this.getScrollTop()),s|=this.CHANGE_FULL);n||(n=i.getInnerWidth(this.container));if(e||this.resizing>1||o.width!=n){o.width=n;var t=this.showGutter?this.$gutter.offsetWidth:0;this.scroller.style.left=t+"px",o.scrollerWidth=Math.max(0,n-t-this.scrollBar.getWidth()),this.scroller.style.right=this.scrollBar.getWidth()+"px";if(this.session.getUseWrapMode()&&this.adjustWrapLimit()||e)s|=this.CHANGE_FULL}e?this.$renderChanges(s,!0):this.$loop.schedule(s),e&&delete this.resizing},this.onGutterResize=function(){var e=this.$size.width,t=this.showGutter?this.$gutter.offsetWidth:0;this.scroller.style.left=t+"px",this.$size.scrollerWidth=Math.max(0,e-t-this.scrollBar.getWidth()),this.session.getUseWrapMode()&&this.adjustWrapLimit()&&this.$loop.schedule(this.CHANGE_FULL)},this.adjustWrapLimit=function(){var e=this.$size.scrollerWidth-this.$padding*2,t=Math.floor(e/this.characterWidth);return this.session.adjustWrapLimit(t)},this.setAnimatedScroll=function(e){this.$animatedScroll=e},this.getAnimatedScroll=function(){return this.$animatedScroll},this.setShowInvisibles=function(e){this.$textLayer.setShowInvisibles(e)&&this.$loop.schedule(this.CHANGE_TEXT)},this.getShowInvisibles=function(){return this.$textLayer.showInvisibles},this.getDisplayIndentGuides=function(){return this.$textLayer.displayIndentGuides},this.setDisplayIndentGuides=function(e){this.$textLayer.setDisplayIndentGuides(e)&&this.$loop.schedule(this.CHANGE_TEXT)},this.$showPrintMargin=!0,this.setShowPrintMargin=function(e){this.$showPrintMargin=e,this.$updatePrintMargin()},this.getShowPrintMargin=function(){return this.$showPrintMargin},this.$printMarginColumn=80,this.setPrintMarginColumn=function(e){this.$printMarginColumn=e,this.$updatePrintMargin()},this.getPrintMarginColumn=function(){return this.$printMarginColumn},this.getShowGutter=function(){return this.showGutter},this.setShowGutter=function(e){if(this.showGutter===e)return;this.$gutter.style.display=e?"block":"none",this.showGutter=e,this.onResize(!0)},this.getFadeFoldWidgets=function(){return i.hasCssClass(this.$gutter,"ace_fade-fold-widgets")},this.setFadeFoldWidgets=function(e){e?i.addCssClass(this.$gutter,"ace_fade-fold-widgets"):i.removeCssClass(this.$gutter,"ace_fade-fold-widgets")},this.$highlightGutterLine=!1,this.setHighlightGutterLine=function(e){if(this.$highlightGutterLine==e)return;this.$highlightGutterLine=e;if(!this.$gutterLineHighlight){this.$gutterLineHighlight=i.createElement("div"),this.$gutterLineHighlight.className="ace_gutter-active-line",this.$gutter.appendChild(this.$gutterLineHighlight);return}this.$gutterLineHighlight.style.display=e?"":"none",this.$cursorLayer.$pixelPos&&this.$updateGutterLineHighlight()},this.getHighlightGutterLine=function(){return this.$highlightGutterLine},this.$updateGutterLineHighlight=function(){this.$gutterLineHighlight.style.top=this.$cursorLayer.$pixelPos.top-this.layerConfig.offset+"px",this.$gutterLineHighlight.style.height=this.layerConfig.lineHeight+"px"},this.$updatePrintMargin=function(){if(!this.$showPrintMargin&&!this.$printMarginEl)return;if(!this.$printMarginEl){var e=i.createElement("div");e.className="ace_layer ace_print-margin-layer",this.$printMarginEl=i.createElement("div"),this.$printMarginEl.className="ace_print-margin",e.appendChild(this.$printMarginEl),this.content.insertBefore(e,this.content.firstChild)}var t=this.$printMarginEl.style;t.left=this.characterWidth*this.$printMarginColumn+this.$padding+"px",t.visibility=this.$showPrintMargin?"visible":"hidden"},this.getContainerElement=function(){return this.container},this.getMouseEventTarget=function(){return this.content},this.getTextAreaContainer=function(){return this.container},this.$moveTextAreaToCursor=function(){if(!this.$keepTextAreaAtCursor)return;var e=this.$cursorLayer.$pixelPos.top,t=this.$cursorLayer.$pixelPos.left;e-=this.layerConfig.offset;if(e<0||e>this.layerConfig.height-this.lineHeight)return;var n=this.characterWidth;this.$composition&&(n+=this.textarea.scrollWidth),t-=this.scrollLeft,t>this.$size.scrollerWidth-n&&(t=this.$size.scrollerWidth-n),this.showGutter&&(t+=this.$gutterLayer.gutterWidth),this.textarea.style.height=this.lineHeight+"px",this.textarea.style.width=n+"px",this.textarea.style.left=t+"px",this.textarea.style.top=e-1+"px"},this.getFirstVisibleRow=function(){return this.layerConfig.firstRow},this.getFirstFullyVisibleRow=function(){return this.layerConfig.firstRow+(this.layerConfig.offset===0?0:1)},this.getLastFullyVisibleRow=function(){var e=Math.floor((this.layerConfig.height+this.layerConfig.offset)/this.layerConfig.lineHeight);return this.layerConfig.firstRow-1+e},this.getLastVisibleRow=function(){return this.layerConfig.lastRow},this.$padding=null,this.setPadding=function(e){this.$padding=e,this.$textLayer.setPadding(e),this.$cursorLayer.setPadding(e),this.$markerFront.setPadding(e),this.$markerBack.setPadding(e),this.$loop.schedule(this.CHANGE_FULL),this.$updatePrintMargin()},this.getHScrollBarAlwaysVisible=function(){return this.$horizScrollAlwaysVisible},this.setHScrollBarAlwaysVisible=function(e){this.$horizScrollAlwaysVisible!=e&&(this.$horizScrollAlwaysVisible=e,(!this.$horizScrollAlwaysVisible||!this.$horizScroll)&&this.$loop.schedule(this.CHANGE_SCROLL))},this.$updateScrollBar=function(){this.scrollBar.setInnerHeight(this.layerConfig.maxHeight),this.scrollBar.setScrollTop(this.scrollTop)},this.$renderChanges=function(e,t){if(!t&&(!e||!this.session||!this.container.offsetWidth))return;(e&this.CHANGE_FULL||e&this.CHANGE_SIZE||e&this.CHANGE_TEXT||e&this.CHANGE_LINES||e&this.CHANGE_SCROLL)&&this.$computeLayerConfig();if(e&this.CHANGE_H_SCROLL){this.scroller.scrollLeft=this.scrollLeft;var n=this.scroller.scrollLeft;this.scrollLeft=n,this.session.setScrollLeft(n),this.scroller.className=this.scrollLeft==0?"ace_scroller":"ace_scroller ace_scroll-left"}if(e&this.CHANGE_FULL){this.$textLayer.checkForSizeChanges(),this.$updateScrollBar(),this.$textLayer.update(this.layerConfig),this.showGutter&&this.$gutterLayer.update(this.layerConfig),this.$markerBack.update(this.layerConfig),this.$markerFront.update(this.layerConfig),this.$cursorLayer.update(this.layerConfig),this.$moveTextAreaToCursor(),this.$highlightGutterLine&&this.$updateGutterLineHighlight();return}if(e&this.CHANGE_SCROLL){this.$updateScrollBar(),e&this.CHANGE_TEXT||e&this.CHANGE_LINES?this.$textLayer.update(this.layerConfig):this.$textLayer.scrollLines(this.layerConfig),this.showGutter&&this.$gutterLayer.update(this.layerConfig),this.$markerBack.update(this.layerConfig),this.$markerFront.update(this.layerConfig),this.$cursorLayer.update(this.layerConfig),this.$moveTextAreaToCursor(),this.$highlightGutterLine&&this.$updateGutterLineHighlight();return}e&this.CHANGE_TEXT?(this.$textLayer.update(this.layerConfig),this.showGutter&&this.$gutterLayer.update(this.layerConfig)):e&this.CHANGE_LINES?(this.$updateLines()||e&this.CHANGE_GUTTER&&this.showGutter)&&this.$gutterLayer.update(this.layerConfig):(e&this.CHANGE_TEXT||e&this.CHANGE_GUTTER)&&this.showGutter&&this.$gutterLayer.update(this.layerConfig),e&this.CHANGE_CURSOR&&(this.$cursorLayer.update(this.layerConfig),this.$moveTextAreaToCursor(),this.$highlightGutterLine&&this.$updateGutterLineHighlight()),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_FRONT)&&this.$markerFront.update(this.layerConfig),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_BACK)&&this.$markerBack.update(this.layerConfig),e&this.CHANGE_SIZE&&this.$updateScrollBar()},this.$computeLayerConfig=function(){var e=this.session,t=this.scrollTop%this.lineHeight,n=this.$size.scrollerHeight+this.lineHeight,r=this.$getLongestLine(),i=this.$horizScrollAlwaysVisible||this.$size.scrollerWidth-r<0,s=this.$horizScroll!==i;this.$horizScroll=i,s&&(this.scroller.style.overflowX=i?"scroll":"hidden",i||this.session.setScrollLeft(0));var o=this.session.getScreenLength()*this.lineHeight;this.session.setScrollTop(Math.max(0,Math.min(this.scrollTop,o-this.$size.scrollerHeight)));var u=Math.ceil(n/this.lineHeight)-1,a=Math.max(0,Math.round((this.scrollTop-t)/this.lineHeight)),f=a+u,l,c,h=this.lineHeight;a=e.screenToDocumentRow(a,0);var p=e.getFoldLine(a);p&&(a=p.start.row),l=e.documentToScreenRow(a,0),c=e.getRowLength(a)*h,f=Math.min(e.screenToDocumentRow(f,0),e.getLength()-1),n=this.$size.scrollerHeight+e.getRowLength(f)*h+c,t=this.scrollTop-l*h,this.layerConfig={width:r,padding:this.$padding,firstRow:a,firstRowScreen:l,lastRow:f,lineHeight:h,characterWidth:this.characterWidth,minHeight:n,maxHeight:o,offset:t,height:this.$size.scrollerHeight},this.$gutterLayer.element.style.marginTop=-t+"px",this.content.style.marginTop=-t+"px",this.content.style.width=r+2*this.$padding+"px",this.content.style.height=n+"px",s&&this.onResize(!0)},this.$updateLines=function(){var e=this.$changedLines.firstRow,t=this.$changedLines.lastRow;this.$changedLines=null;var n=this.layerConfig;if(e>n.lastRow+1)return;if(t<n.firstRow)return;if(t===Infinity){this.showGutter&&this.$gutterLayer.update(n),this.$textLayer.update(n);return}return this.$textLayer.updateLines(n,e,t),!0},this.$getLongestLine=function(){var e=this.session.getScreenWidth();return this.$textLayer.showInvisibles&&(e+=1),Math.max(this.$size.scrollerWidth-2*this.$padding,Math.round(e*this.characterWidth))},this.updateFrontMarkers=function(){this.$markerFront.setMarkers(this.session.getMarkers(!0)),this.$loop.schedule(this.CHANGE_MARKER_FRONT)},this.updateBackMarkers=function(){this.$markerBack.setMarkers(this.session.getMarkers()),this.$loop.schedule(this.CHANGE_MARKER_BACK)},this.addGutterDecoration=function(e,t){this.$gutterLayer.addGutterDecoration(e,t)},this.removeGutterDecoration=function(e,t){this.$gutterLayer.removeGutterDecoration(e,t)},this.updateBreakpoints=function(e){this.$loop.schedule(this.CHANGE_GUTTER)},this.setAnnotations=function(e){this.$gutterLayer.setAnnotations(e),this.$loop.schedule(this.CHANGE_GUTTER)},this.updateCursor=function(){this.$loop.schedule(this.CHANGE_CURSOR)},this.hideCursor=function(){this.$cursorLayer.hideCursor()},this.showCursor=function(){this.$cursorLayer.showCursor()},this.scrollSelectionIntoView=function(e,t,n){this.scrollCursorIntoView(e,n),this.scrollCursorIntoView(t,n)},this.scrollCursorIntoView=function(e,t){if(this.$size.scrollerHeight===0)return;var n=this.$cursorLayer.getPixelPosition(e),r=n.left,i=n.top;this.scrollTop>i?(t&&(i-=t*this.$size.scrollerHeight),this.session.setScrollTop(i)):this.scrollTop+this.$size.scrollerHeight<i+this.lineHeight&&(t&&(i+=t*this.$size.scrollerHeight),this.session.setScrollTop(i+this.lineHeight-this.$size.scrollerHeight));var s=this.scrollLeft;s>r?(r<this.$padding+2*this.layerConfig.characterWidth&&(r=0),this.session.setScrollLeft(r)):s+this.$size.scrollerWidth<r+this.characterWidth&&this.session.setScrollLeft(Math.round(r+this.characterWidth-this.$size.scrollerWidth))},this.getScrollTop=function(){return this.session.getScrollTop()},this.getScrollLeft=function(){return this.session.getScrollLeft()},this.getScrollTopRow=function(){return this.scrollTop/this.lineHeight},this.getScrollBottomRow=function(){return Math.max(0,Math.floor((this.scrollTop+this.$size.scrollerHeight)/this.lineHeight)-1)},this.scrollToRow=function(e){this.session.setScrollTop(e*this.lineHeight)},this.alignCursor=function(e,t){typeof e=="number"&&(e={row:e,column:0});var n=this.$cursorLayer.getPixelPosition(e),r=this.$size.scrollerHeight-this.lineHeight,i=n.top-r*(t||0);return this.session.setScrollTop(i),i},this.STEPS=8,this.$calcSteps=function(e,t){var n=0,r=this.STEPS,i=[],s=function(e,t,n){return n*(Math.pow(e-1,3)+1)+t};for(n=0;n<r;++n)i.push(s(n/this.STEPS,e,t-e));return i},this.scrollToLine=function(e,t,n,r){var i=this.$cursorLayer.getPixelPosition({row:e,column:0}),s=i.top;t&&(s-=this.$size.scrollerHeight/2);var o=this.scrollTop;this.session.setScrollTop(s),n!==!1&&this.animateScrolling(o,r)},this.animateScrolling=function(e,t){var n=this.scrollTop;if(this.$animatedScroll&&Math.abs(e-n)<1e5){var r=this,i=r.$calcSteps(e,n);this.$inScrollAnimation=!0,clearInterval(this.$timer),r.session.setScrollTop(i.shift()),this.$timer=setInterval(function(){i.length?(r.session.setScrollTop(i.shift()),r.session.$scrollTop=n):n!=null?(r.session.$scrollTop=-1,r.session.setScrollTop(n),n=null):(r.$timer=clearInterval(r.$timer),r.$inScrollAnimation=!1,t&&t())},10)}},this.scrollToY=function(e){this.scrollTop!==e&&(this.$loop.schedule(this.CHANGE_SCROLL),this.scrollTop=e)},this.scrollToX=function(e){e<0&&(e=0),this.scrollLeft!==e&&(this.scrollLeft=e),this.$loop.schedule(this.CHANGE_H_SCROLL)},this.scrollBy=function(e,t){t&&this.session.setScrollTop(this.session.getScrollTop()+t),e&&this.session.setScrollLeft(this.session.getScrollLeft()+e)},this.isScrollableBy=function(e,t){if(t<0&&this.session.getScrollTop()>0)return!0;if(t>0&&this.session.getScrollTop()+this.$size.scrollerHeight<this.layerConfig.maxHeight)return!0},this.pixelToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=(e+this.scrollLeft-n.left-this.$padding)/this.characterWidth,i=Math.floor((t+this.scrollTop-n.top)/this.lineHeight),s=Math.round(r);return{row:i,column:s,side:r-s>0?1:-1}},this.screenToTextCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=Math.round((e+this.scrollLeft-n.left-this.$padding)/this.characterWidth),i=Math.floor((t+this.scrollTop-n.top)/this.lineHeight);return this.session.screenToDocumentPosition(i,Math.max(r,0))},this.textToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=this.session.documentToScreenPosition(e,t),i=this.$padding+Math.round(r.column*this.characterWidth),s=r.row*this.lineHeight;return{pageX:n.left+i-this.scrollLeft,pageY:n.top+s-this.scrollTop}},this.visualizeFocus=function(){i.addCssClass(this.container,"ace_focus")},this.visualizeBlur=function(){i.removeCssClass(this.container,"ace_focus")},this.showComposition=function(e){this.$composition||(this.$composition={keepTextAreaAtCursor:this.$keepTextAreaAtCursor,cssText:this.textarea.style.cssText}),this.$keepTextAreaAtCursor=!0,i.addCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText="",this.$moveTextAreaToCursor()},this.setCompositionText=function(e){this.$moveTextAreaToCursor()},this.hideComposition=function(){if(!this.$composition)return;i.removeCssClass(this.textarea,"ace_composition"),this.$keepTextAreaAtCursor=this.$composition.keepTextAreaAtCursor,this.textarea.style.cssText=this.$composition.cssText,this.$composition=null},this._loadTheme=function(e,t){if(!u.get("packaged"))return t();a.loadScript(u.moduleUrl(e,"theme"),t)},this.setTheme=function(t){function u(e){i.importCssString(e.cssText,e.cssClass,n.container.ownerDocument),n.theme&&i.removeCssClass(n.container,n.theme.cssClass),n.$theme=e.cssClass,n.theme=e,i.addCssClass(n.container,e.cssClass),i.setCssClass(n.container,"ace_dark",e.isDark);var t=e.padding||4;n.$padding&&t!=n.$padding&&n.setPadding(t),n.$size&&(n.$size.width=0,n.onResize()),n._dispatchEvent("themeLoaded",{theme:e})}var n=this;this.$themeValue=t,n._dispatchEvent("themeChange",{theme:t});if(!t||typeof t=="string"){var r=t||"ace/theme/textmate",s;try{s=e(r)}catch(o){}if(s)return u(s);n._loadTheme(r,function(){e([r],function(e){if(n.$themeValue!==t)return;u(e)})})}else u(t)},this.getTheme=function(){return this.$themeValue},this.setStyle=function(t,n){i.setCssClass(this.container,t,n!=0)},this.unsetStyle=function(t){i.removeCssClass(this.container,t)},this.destroy=function(){this.$textLayer.destroy(),this.$cursorLayer.destroy()}}).call(g.prototype),t.VirtualRenderer=g}),ace.define("ace/layer/gutter",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/dom"),i=e("../lib/oop"),s=e("../lib/lang"),o=e("../lib/event_emitter").EventEmitter,u=function(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_gutter-layer",e.appendChild(this.element),this.setShowFoldWidgets(this.$showFoldWidgets),this.gutterWidth=0,this.$annotations=[],this.$updateAnnotations=this.$updateAnnotations.bind(this)};(function(){i.implement(this,o),this.setSession=function(e){this.session&&this.session.removeEventListener("change",this.$updateAnnotations),this.session=e,e.on("change",this.$updateAnnotations)},this.addGutterDecoration=function(e,t){window.console&&console.warn&&console.warn("deprecated use session.addGutterDecoration"),this.session.addGutterDecoration(e,t)},this.removeGutterDecoration=function(e,t){window.console&&console.warn&&console.warn("deprecated use session.removeGutterDecoration"),this.session.removeGutterDecoration(e,t)},this.setAnnotations=function(e){this.$annotations=[];var t,n;for(var r=0;r<e.length;r++){var i=e[r],n=i.row,t=this.$annotations[n];t||(t=this.$annotations[n]={text:[]});var o=i.text;o=o?s.escapeHTML(o):i.html||"",t.text.indexOf(o)===-1&&t.text.push(o);var u=i.type;u=="error"?t.className=" ace_error":u=="warning"&&t.className!=" ace_error"?t.className=" ace_warning":u=="info"&&!t.className&&(t.className=" ace_info")}},this.$updateAnnotations=function(e){if(!this.$annotations.length)return;var t=e.data,n=t.range,r=n.start.row,i=n.end.row-r;if(i!==0)if(t.action=="removeText"||t.action=="removeLines")this.$annotations.splice(r,i+1,null);else{var s=Array(i+1);s.unshift(r,1),this.$annotations.splice.apply(this.$annotations,s)}},this.update=function(e){var t={className:""},n=[],i=e.firstRow,s=e.lastRow,o=this.session.getNextFoldLine(i),u=o?o.start.row:Infinity,a=this.$showFoldWidgets&&this.session.foldWidgets,f=this.session.$breakpoints,l=this.session.$decorations,c=0;for(;;){i>u&&(i=o.end.row+1,o=this.session.getNextFoldLine(i,o),u=o?o.start.row:Infinity);if(i>s)break;var h=this.$annotations[i]||t;n.push("<div class='ace_gutter-cell ",f[i]||"",l[i]||"",h.className,"' style='height:",this.session.getRowLength(i)*e.lineHeight,"px;'>",c=i+1);if(a){var p=a[i];p==null&&(p=a[i]=this.session.getFoldWidget(i)),p&&n.push("<span class='ace_fold-widget ace_",p,p=="start"&&i==u&&i<o.end.row?" ace_closed":" ace_open","' style='height:",e.lineHeight,"px","'></span>")}n.push("</div>"),i++}this.element=r.setInnerHtml(this.element,n.join("")),this.element.style.height=e.minHeight+"px",this.session.$useWrapMode&&(c=this.session.getLength());var d=(""+c).length*e.characterWidth,v=this.$padding||this.$computePadding();d+=v.left+v.right,d!==this.gutterWidth&&(this.gutterWidth=d,this.element.style.width=Math.ceil(this.gutterWidth)+"px",this._emit("changeGutterWidth",d))},this.$showFoldWidgets=!0,this.setShowFoldWidgets=function(e){e?r.addCssClass(this.element,"ace_folding-enabled"):r.removeCssClass(this.element,"ace_folding-enabled"),this.$showFoldWidgets=e,this.$padding=null},this.getShowFoldWidgets=function(){return this.$showFoldWidgets},this.$computePadding=function(){if(!this.element.firstChild)return{left:0,right:0};var e=r.computedStyle(this.element.firstChild);return this.$padding={},this.$padding.left=parseInt(e.paddingLeft)+1,this.$padding.right=parseInt(e.paddingRight),this.$padding},this.getRegion=function(e){var t=this.$padding||this.$computePadding(),n=this.element.getBoundingClientRect();if(e.x<t.left+n.left)return"markers";if(this.$showFoldWidgets&&e.x>n.right-t.right)return"foldWidgets"}}).call(u.prototype),t.Gutter=u}),ace.define("ace/layer/marker",["require","exports","module","ace/range","ace/lib/dom"],function(e,t,n){var r=e("../range").Range,i=e("../lib/dom"),s=function(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_marker-layer",e.appendChild(this.element)};(function(){this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setMarkers=function(e){this.markers=e},this.update=function(e){var e=e||this.config;if(!e)return;this.config=e;var t=[];for(var n in this.markers){var r=this.markers[n];if(!r.range){r.update(t,this,this.session,e);continue}var s=r.range.clipRows(e.firstRow,e.lastRow);if(s.isEmpty())continue;s=s.toScreenRange(this.session);if(r.renderer){var o=this.$getTop(s.start.row,e),u=this.$padding+s.start.column*e.characterWidth;r.renderer(t,s,u,o,e)}else r.type=="fullLine"?this.drawFullLineMarker(t,s,r.clazz,e):s.isMultiLine()?r.type=="text"?this.drawTextMarker(t,s,r.clazz,e):this.drawMultiLineMarker(t,s,r.clazz,e):this.drawSingleLineMarker(t,s,r.clazz+" ace_start",e)}this.element=i.setInnerHtml(this.element,t.join(""))},this.$getTop=function(e,t){return(e-t.firstRowScreen)*t.lineHeight},this.drawTextMarker=function(e,t,n,i){var s=t.start.row,o=new r(s,t.start.column,s,this.session.getScreenLastRowColumn(s));this.drawSingleLineMarker(e,o,n+" ace_start",i,1,"text"),s=t.end.row,o=new r(s,0,s,t.end.column),this.drawSingleLineMarker(e,o,n,i,0,"text");for(s=t.start.row+1;s<t.end.row;s++)o.start.row=s,o.end.row=s,o.end.column=this.session.getScreenLastRowColumn(s),this.drawSingleLineMarker(e,o,n,i,1,"text")},this.drawMultiLineMarker=function(e,t,n,r,i){var s=this.$padding,o=r.lineHeight,u=this.$getTop(t.start.row,r),a=s+t.start.column*r.characterWidth;e.push("<div class='",n," ace_start' style='","height:",o,"px;","right:0;","top:",u,"px;","left:",a,"px;'></div>"),u=this.$getTop(t.end.row,r);var f=t.end.column*r.characterWidth;e.push("<div class='",n,"' style='","height:",o,"px;","width:",f,"px;","top:",u,"px;","left:",s,"px;'></div>"),o=(t.end.row-t.start.row-1)*r.lineHeight;if(o<0)return;u=this.$getTop(t.start.row+1,r),e.push("<div class='",n,"' style='","height:",o,"px;","right:0;","top:",u,"px;","left:",s,"px;'></div>")},this.drawSingleLineMarker=function(e,t,n,r,i){var s=r.lineHeight,o=(t.end.column+(i||0)-t.start.column)*r.characterWidth,u=this.$getTop(t.start.row,r),a=this.$padding+t.start.column*r.characterWidth;e.push("<div class='",n,"' style='","height:",s,"px;","width:",o,"px;","top:",u,"px;","left:",a,"px;'></div>")},this.drawFullLineMarker=function(e,t,n,r){var i=this.$getTop(t.start.row,r),s=r.lineHeight;t.start.row!=t.end.row&&(s+=this.$getTop(t.end.row,r)-i),e.push("<div class='",n,"' style='","height:",s,"px;","top:",i,"px;","left:0;right:0;'></div>")}}).call(s.prototype),t.Marker=s}),ace.define("ace/layer/text",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/useragent","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("../lib/useragent"),u=e("../lib/event_emitter").EventEmitter,a=function(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_text-layer",e.appendChild(this.element),this.$characterSize={width:0,height:0},this.checkForSizeChanges(),this.$pollSizeChanges()};(function(){r.implement(this,u),this.EOF_CHAR="¶",this.EOL_CHAR="¬",this.TAB_CHAR="→",this.SPACE_CHAR="·",this.$padding=0,this.setPadding=function(e){this.$padding=e,this.element.style.padding="0 "+e+"px"},this.getLineHeight=function(){return this.$characterSize.height||1},this.getCharacterWidth=function(){return this.$characterSize.width||1},this.checkForSizeChanges=function(){var e=this.$measureSizes();if(e&&(this.$characterSize.width!==e.width||this.$characterSize.height!==e.height)){this.$measureNode.style.fontWeight="bold";var t=this.$measureSizes();this.$measureNode.style.fontWeight="",this.$characterSize=e,this.allowBoldFonts=t&&t.width===e.width&&t.height===e.height,this._emit("changeCharacterSize",{data:e})}},this.$pollSizeChanges=function(){var e=this;this.$pollSizeChangesTimer=setInterval(function(){e.checkForSizeChanges()},500)},this.$fontStyles={fontFamily:1,fontSize:1,fontWeight:1,fontStyle:1,lineHeight:1},this.$measureSizes=o.isIE||o.isOldGecko?function(){var e=1e3;if(!this.$measureNode){var t=this.$measureNode=i.createElement("div"),n=t.style;n.width=n.height="auto",n.left=n.top=-e*40+"px",n.visibility="hidden",n.position="fixed",n.overflow="visible",n.whiteSpace="nowrap",t.innerHTML=s.stringRepeat("Xy",e);if(this.element.ownerDocument.body)this.element.ownerDocument.body.appendChild(t);else{var r=this.element.parentNode;while(!i.hasCssClass(r,"ace_editor"))r=r.parentNode;r.appendChild(t)}}if(!this.element.offsetWidth)return null;var n=this.$measureNode.style,o=i.computedStyle(this.element);for(var u in this.$fontStyles)n[u]=o[u];var a={height:this.$measureNode.offsetHeight,width:this.$measureNode.offsetWidth/(e*2)};return a.width==0||a.height==0?null:a}:function(){if(!this.$measureNode){var e=this.$measureNode=i.createElement("div"),t=e.style;t.width=t.height="auto",t.left=t.top="-100px",t.visibility="hidden",t.position="fixed",t.overflow="visible",t.whiteSpace="nowrap",e.innerHTML="X";var n=this.element.parentNode;while(n&&!i.hasCssClass(n,"ace_editor"))n=n.parentNode;if(!n)return this.$measureNode=null;n.appendChild(e)}var r=this.$measureNode.getBoundingClientRect(),s={height:r.height,width:r.width};return s.width==0||s.height==0?null:s},this.setSession=function(e){this.session=e,this.$computeTabString()},this.showInvisibles=!1,this.setShowInvisibles=function(e){return this.showInvisibles==e?!1:(this.showInvisibles=e,this.$computeTabString(),!0)},this.displayIndentGuides=!0,this.setDisplayIndentGuides=function(e){return this.displayIndentGuides==e?!1:(this.displayIndentGuides=e,this.$computeTabString(),!0)},this.$tabStrings=[],this.onChangeTabSize=this.$computeTabString=function(){var e=this.session.getTabSize();this.tabSize=e;var t=this.$tabStrings=[0];for(var n=1;n<e+1;n++)this.showInvisibles?t.push("<span class='ace_invisible'>"+this.TAB_CHAR+Array(n).join(" ")+"</span>"):t.push((new Array(n+1)).join(" "));if(this.displayIndentGuides){this.$indentGuideRe=/\s\S| \t|\t |\s$/;var r="ace_indent-guide",i=Array(this.tabSize+1).join(" "),s=i;this.showInvisibles&&(r+=" ace_invisible",s=this.TAB_CHAR+i.substr(6)),this.$tabStrings[" "]="<span class='"+r+"'>"+i+"</span>",this.$tabStrings[" "]="<span class='"+r+"'>"+s+"</span>"}},this.updateLines=function(e,t,n){(this.config.lastRow!=e.lastRow||this.config.firstRow!=e.firstRow)&&this.scrollLines(e),this.config=e;var r=Math.max(t,e.firstRow),s=Math.min(n,e.lastRow),o=this.element.childNodes,u=0;for(var a=e.firstRow;a<r;a++){var f=this.session.getFoldLine(a);if(f){if(f.containsRow(r)){r=f.start.row;break}a=f.end.row}u++}var a=r,f=this.session.getNextFoldLine(a),l=f?f.start.row:Infinity;for(;;){a>l&&(a=f.end.row+1,f=this.session.getNextFoldLine(a,f),l=f?f.start.row:Infinity);if(a>s)break;var c=o[u++];if(c){var h=[];this.$renderLine(h,a,!this.$useLineGroups(),a==l?f:!1),i.setInnerHtml(c,h.join(""))}a++}},this.scrollLines=function(e){var t=this.config;this.config=e;if(!t||t.lastRow<e.firstRow)return this.update(e);if(e.lastRow<t.firstRow)return this.update(e);var n=this.element;if(t.firstRow<e.firstRow)for(var r=this.session.getFoldedRowCount(t.firstRow,e.firstRow-1);r>0;r--)n.removeChild(n.firstChild);if(t.lastRow>e.lastRow)for(var r=this.session.getFoldedRowCount(e.lastRow+1,t.lastRow);r>0;r--)n.removeChild(n.lastChild);if(e.firstRow<t.firstRow){var i=this.$renderLinesFragment(e,e.firstRow,t.firstRow-1);n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i)}if(e.lastRow>t.lastRow){var i=this.$renderLinesFragment(e,t.lastRow+1,e.lastRow);n.appendChild(i)}},this.$renderLinesFragment=function(e,t,n){var r=this.element.ownerDocument.createDocumentFragment(),s=t,o=this.session.getNextFoldLine(s),u=o?o.start.row:Infinity;for(;;){s>u&&(s=o.end.row+1,o=this.session.getNextFoldLine(s,o),u=o?o.start.row:Infinity);if(s>n)break;var a=i.createElement("div"),f=[];this.$renderLine(f,s,!1,s==u?o:!1),a.innerHTML=f.join("");if(this.$useLineGroups())a.className="ace_line_group",r.appendChild(a);else{var l=a.childNodes;while(l.length)r.appendChild(l[0])}s++}return r},this.update=function(e){this.config=e;var t=[],n=e.firstRow,r=e.lastRow,s=n,o=this.session.getNextFoldLine(s),u=o?o.start.row:Infinity;for(;;){s>u&&(s=o.end.row+1,o=this.session.getNextFoldLine(s,o),u=o?o.start.row:Infinity);if(s>r)break;this.$useLineGroups()&&t.push("<div class='ace_line_group'>"),this.$renderLine(t,s,!1,s==u?o:!1),this.$useLineGroups()&&t.push("</div>"),s++}this.element=i.setInnerHtml(this.element,t.join(""))},this.$textToken={text:!0,rparen:!0,lparen:!0},this.$renderToken=function(e,t,n,r){var i=this,s=/\t|&|<|( +)|([\x00-\x1f\x80-\xa0\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g,o=function(e,n,r,s,o){if(n)return(new Array(e.length+1)).join(" ");if(e=="&")return"&";if(e=="<")return"<";if(e==" "){var u=i.session.getScreenTabSize(t+s);return t+=u-1,i.$tabStrings[u]}if(e==" "){var a=i.showInvisibles?"ace_cjk ace_invisible":"ace_cjk",f=i.showInvisibles?i.SPACE_CHAR:"";return t+=1,"<span class='"+a+"' style='width:"+i.config.characterWidth*2+"px'>"+f+"</span>"}return r?"<span class='ace_invisible ace_invalid'>"+i.SPACE_CHAR+"</span>":(t+=1,"<span class='ace_cjk' style='width:"+i.config.characterWidth*2+"px'>"+e+"</span>")},u=r.replace(s,o);if(!this.$textToken[n.type]){var a="ace_"+n.type.replace(/\./g," ace_"),f="";n.type=="fold"&&(f=" style='width:"+n.value.length*this.config.characterWidth+"px;' "),e.push("<span class='",a,"'",f,">",u,"</span>")}else e.push(u);return t+r.length},this.renderIndentGuide=function(e,t){var n=t.search(this.$indentGuideRe);return n<=0?t:t[0]==" "?(n-=n%this.tabSize,e.push(Array(n/this.tabSize+1).join(this.$tabStrings[" "])),t.substr(n)):t[0]==" "?(e.push(Array(n+1).join(this.$tabStrings[" "])),t.substr(n)):t},this.$renderWrappedLine=function(e,t,n,r){var i=0,s=0,o=n[0],u=0;for(var a=0;a<t.length;a++){var f=t[a],l=f.value;if(a==0&&this.displayIndentGuides){i=l.length,l=this.renderIndentGuide(e,l);if(!l)continue;i-=l.length}if(i+l.length<o)u=this.$renderToken(e,u,f,l),i+=l.length;else{while(i+l.length>=o)u=this.$renderToken(e,u,f,l.substring(0,o-i)),l=l.substring(o-i),i=o,r||e.push("</div>","<div class='ace_line' style='height:",this.config.lineHeight,"px'>"),s++,u=0,o=n[s]||Number.MAX_VALUE;l.length!=0&&(i+=l.length,u=this.$renderToken(e,u,f,l))}}},this.$renderSimpleLine=function(e,t){var n=0,r=t[0],i=r.value;this.displayIndentGuides&&(i=this.renderIndentGuide(e,i)),i&&(n=this.$renderToken(e,n,r,i));for(var s=1;s<t.length;s++)r=t[s],i=r.value,n=this.$renderToken(e,n,r,i)},this.$renderLine=function(e,t,n,r){!r&&r!=0&&(r=this.session.getFoldLine(t));if(r)var i=this.$getFoldLineTokens(t,r);else var i=this.session.getTokens(t);n||e.push("<div class='ace_line' style='height:",this.config.lineHeight,"px'>");if(i.length){var s=this.session.getRowSplitData(t);s&&s.length?this.$renderWrappedLine(e,i,s,n):this.$renderSimpleLine(e,i)}this.showInvisibles&&(r&&(t=r.end.row),e.push("<span class='ace_invisible'>",t==this.session.getLength()-1?this.EOF_CHAR:this.EOL_CHAR,"</span>")),n||e.push("</div>")},this.$getFoldLineTokens=function(e,t){function i(e,t,n){var i=0,s=0;while(s+e[i].value.length<t){s+=e[i].value.length,i++;if(i==e.length)return}if(s!=t){var o=e[i].value.substring(t-s);o.length>n-t&&(o=o.substring(0,n-t)),r.push({type:e[i].type,value:o}),s=t+o.length,i+=1}while(s<n&&i<e.length){var o=e[i].value;o.length+s>n?r.push({type:e[i].type,value:o.substring(0,n-s)}):r.push(e[i]),s+=o.length,i+=1}}var n=this.session,r=[],s=n.getTokens(e);return t.walk(function(e,t,o,u,a){e!=null?r.push({type:"fold",value:e}):(a&&(s=n.getTokens(t)),s.length&&i(s,u,o))},t.end.row,this.session.getLine(t.end.row).length),r},this.$useLineGroups=function(){return this.session.getUseWrapMode()},this.destroy=function(){clearInterval(this.$pollSizeChangesTimer),this.$measureNode&&this.$measureNode.parentNode.removeChild(this.$measureNode),delete this.$measureNode}}).call(a.prototype),t.Text=a}),ace.define("ace/layer/cursor",["require","exports","module","ace/lib/dom"],function(e,t,n){var r=e("../lib/dom"),i=function(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_cursor-layer",e.appendChild(this.element),this.isVisible=!1,this.isBlinking=!0,this.blinkInterval=1e3,this.smoothBlinking=!1,this.cursors=[],this.cursor=this.addCursor(),r.addCssClass(this.element,"ace_hidden-cursors")};(function(){this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setBlinking=function(e){e!=this.isBlinking&&(this.isBlinking=e,this.restartTimer())},this.setBlinkInterval=function(e){e!=this.blinkInterval&&(this.blinkInterval=e,this.restartTimer())},this.setSmoothBlinking=function(e){e!=this.smoothBlinking&&(this.smoothBlinking=e,e?r.addCssClass(this.element,"ace_smooth-blinking"):r.removeCssClass(this.element,"ace_smooth-blinking"),this.restartTimer())},this.addCursor=function(){var e=r.createElement("div");return e.className="ace_cursor",this.element.appendChild(e),this.cursors.push(e),e},this.removeCursor=function(){if(this.cursors.length>1){var e=this.cursors.pop();return e.parentNode.removeChild(e),e}},this.hideCursor=function(){this.isVisible=!1,r.addCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.showCursor=function(){this.isVisible=!0,r.removeCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.restartTimer=function(){clearInterval(this.intervalId),clearTimeout(this.timeoutId),this.smoothBlinking&&r.removeCssClass(this.element,"ace_smooth-blinking");for(var e=this.cursors.length;e--;)this.cursors[e].style.opacity="";if(!this.isBlinking||!this.blinkInterval||!this.isVisible)return;this.smoothBlinking&&setTimeout(function(){r.addCssClass(this.element,"ace_smooth-blinking")}.bind(this));var t=function(){this.timeoutId=setTimeout(function(){for(var e=this.cursors.length;e--;)this.cursors[e].style.opacity=0}.bind(this),.6*this.blinkInterval)}.bind(this);this.intervalId=setInterval(function(){for(var e=this.cursors.length;e--;)this.cursors[e].style.opacity="";t()}.bind(this),this.blinkInterval),t()},this.getPixelPosition=function(e,t){if(!this.config||!this.session)return{left:0,top:0};e||(e=this.session.selection.getCursor());var n=this.session.documentToScreenPosition(e),r=this.$padding+n.column*this.config.characterWidth,i=(n.row-(t?this.config.firstRowScreen:0))*this.config.lineHeight;return{left:r,top:i}},this.update=function(e){this.config=e;var t=this.session.$selectionMarkers,n=0,r=0;if(t===undefined||t.length===0)t=[{cursor:null}];for(var n=t.length;n--;){var i=this.getPixelPosition(t[n].cursor,!0);if((i.top>e.height+e.offset||i.top<-e.offset)&&n>1)continue;var s=(this.cursors[r++]||this.addCursor()).style;s.left=i.left+"px",s.top=i.top+"px",s.width=e.characterWidth+"px",s.height=e.lineHeight+"px"}while(this.cursors.length>r)this.removeCursor();var o=this.session.getOverwrite();this.$setOverwrite(o),this.$pixelPos=i,this.restartTimer()},this.$setOverwrite=function(e){e!=this.overwrite&&(this.overwrite=e,e?r.addCssClass(this.element,"ace_overwrite-cursors"):r.removeCssClass(this.element,"ace_overwrite-cursors"))},this.destroy=function(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)}}).call(i.prototype),t.Cursor=i}),ace.define("ace/scrollbar",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/event_emitter"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/event"),o=e("./lib/event_emitter").EventEmitter,u=function(e){this.element=i.createElement("div"),this.element.className="ace_scrollbar",this.inner=i.createElement("div"),this.inner.className="ace_scrollbar-inner",this.element.appendChild(this.inner),e.appendChild(this.element),this.width=i.scrollbarWidth(e.ownerDocument),this.element.style.width=(this.width||15)+5+"px",s.addListener(this.element,"scroll",this.onScroll.bind(this))};(function(){r.implement(this,o),this.onScroll=function(){this.skipEvent||(this.scrollTop=this.element.scrollTop,this._emit("scroll",{data:this.scrollTop})),this.skipEvent=!1},this.getWidth=function(){return this.width},this.setHeight=function(e){this.element.style.height=e+"px"},this.setInnerHeight=function(e){this.inner.style.height=e+"px"},this.setScrollTop=function(e){this.scrollTop!=e&&(this.skipEvent=!0,this.scrollTop=this.element.scrollTop=e)}}).call(u.prototype),t.ScrollBar=u}),ace.define("ace/renderloop",["require","exports","module","ace/lib/event"],function(e,t,n){var r=e("./lib/event"),i=function(e,t){this.onRender=e,this.pending=!1,this.changes=0,this.window=t||window};(function(){this.schedule=function(e){this.changes=this.changes|e;if(!this.pending){this.pending=!0;var t=this;r.nextFrame(function(){t.pending=!1;var e;while(e=t.changes)t.changes=0,t.onRender(e)},this.window)}}}).call(i.prototype),t.RenderLoop=i}),ace.define("ace/multi_select",["require","exports","module","ace/range_list","ace/range","ace/selection","ace/mouse/multi_select_handler","ace/lib/event","ace/lib/lang","ace/commands/multi_select_commands","ace/search","ace/edit_session","ace/editor"],function(e,t,n){function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$options.backwards=n==-1,c.find(e)}function v(e,t){return e.row==t.row&&e.column==t.column}function m(e){e.$onAddRange=e.$onAddRange.bind(e),e.$onRemoveRange=e.$onRemoveRange.bind(e),e.$onMultiSelect=e.$onMultiSelect.bind(e),e.$onSingleSelect=e.$onSingleSelect.bind(e),t.onSessionChange.call(e,e),e.on("changeSession",t.onSessionChange.bind(e)),e.on("mousedown",o),e.commands.addCommands(f.defaultCommands),g(e)}function g(e){function i(){n&&(r.style.cursor="",n=!1)}var t=e.textInput.getElement(),n=!1,r=e.renderer.content;u.addListener(t,"keydown",function(e){e.keyCode==18&&!(e.ctrlKey||e.shiftKey||e.metaKey)?n||(r.style.cursor="crosshair",n=!0):n&&(r.style.cursor="")}),u.addListener(t,"keyup",i),u.addListener(t,"blur",i)}var r=e("./range_list").RangeList,i=e("./range").Range,s=e("./selection").Selection,o=e("./mouse/multi_select_handler").onMouseDown,u=e("./lib/event"),a=e("./lib/lang"),f=e("./commands/multi_select_commands");t.commands=f.defaultCommands.concat(f.multiSelectCommands);var l=e("./search").Search,c=new l,p=e("./edit_session").EditSession;(function(){this.getSelectionMarkers=function(){return this.$selectionMarkers}}).call(p.prototype),function(){this.ranges=null,this.rangeList=null,this.addRange=function(e,t){if(!e)return;if(!this.inMultiSelectMode&&this.rangeCount==0){var n=this.toOrientedRange();if(e.intersects(n))return t||this.fromOrientedRange(e);this.rangeList.add(n),this.$onAddRange(n)}e.cursor||(e.cursor=e.end);var r=this.rangeList.add(e);return this.$onAddRange(e),r.length&&this.$onRemoveRange(r),this.rangeCount>1&&!this.inMultiSelectMode&&(this._emit("multiSelect"),this.inMultiSelectMode=!0,this.session.$undoSelect=!1,this.rangeList.attach(this.session)),t||this.fromOrientedRange(e)},this.toSingleRange=function(e){e=e||this.ranges[0];var t=this.rangeList.removeAll();t.length&&this.$onRemoveRange(t),e&&this.fromOrientedRange(e)},this.substractPoint=function(e){var t=this.rangeList.substractPoint(e);if(t)return this.$onRemoveRange(t),t[0]},this.mergeOverlappingRanges=function(){var e=this.rangeList.merge();e.length?this.$onRemoveRange(e):this.ranges[0]&&this.fromOrientedRange(this.ranges[0])},this.$onAddRange=function(e){this.rangeCount=this.rangeList.ranges.length,this.ranges.unshift(e),this._emit("addRange",{range:e})},this.$onRemoveRange=function(e){this.rangeCount=this.rangeList.ranges.length;if(this.rangeCount==1&&this.inMultiSelectMode){var t=this.rangeList.ranges.pop();e.push(t),this.rangeCount=0}for(var n=e.length;n--;){var r=this.ranges.indexOf(e[n]);this.ranges.splice(r,1)}this._emit("removeRange",{ranges:e}),this.rangeCount==0&&this.inMultiSelectMode&&(this.inMultiSelectMode=!1,this._emit("singleSelect"),this.session.$undoSelect=!0,this.rangeList.detach(this.session)),t=t||this.ranges[0],t&&!t.isEqual(this.getRange())&&this.fromOrientedRange(t)},this.$initRangeList=function(){if(this.rangeList)return;this.rangeList=new r,this.ranges=[],this.rangeCount=0},this.getAllRanges=function(){return this.rangeList.ranges.concat()},this.splitIntoLines=function(){if(this.rangeCount>1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var n=this.getRange(),r=this.isBackwards(),s=n.start.row,o=n.end.row;if(s==o){if(r)var u=n.end,a=n.start;else var u=n.start,a=n.end;this.addRange(i.fromPoints(a,a)),this.addRange(i.fromPoints(u,u));return}var f=[],l=this.getLineRange(s,!0);l.start.column=n.start.column,f.push(l);for(var c=s+1;c<o;c++)f.push(this.getLineRange(c,!0));l=this.getLineRange(o,!0),l.end.column=n.end.column,f.push(l),f.forEach(this.addRange,this)}},this.toggleBlockSelection=function(){if(this.rangeCount>1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var r=this.session.documentToScreenPosition(this.selectionLead),s=this.session.documentToScreenPosition(this.selectionAnchor),o=this.rectangularRangeBlock(r,s);o.forEach(this.addRange,this)}},this.rectangularRangeBlock=function(e,t,n){var r=[],s=e.column<t.column;if(s)var o=e.column,u=t.column;else var o=t.column,u=e.column;var a=e.row<t.row;if(a)var f=e.row,l=t.row;else var f=t.row,l=e.row;o<0&&(o=0),f<0&&(f=0),f==l&&(n=!0);for(var c=f;c<=l;c++){var h=i.fromPoints(this.session.screenToDocumentPosition(c,o),this.session.screenToDocumentPosition(c,u));if(h.isEmpty()){if(p&&v(h.end,p))break;var p=h.end}h.cursor=s?h.start:h.end,r.push(h)}a&&r.reverse();if(!n){var d=r.length-1;while(r[d].isEmpty()&&d>0)d--;if(d>0){var m=0;while(r[m].isEmpty())m++}for(var g=d;g>=m;g--)r[g].isEmpty()&&r.splice(g,1)}return r}}.call(s.prototype);var d=e("./editor").Editor;(function(){this.updateSelectionMarkers=function(){this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.addSelectionMarker=function(e){e.cursor||(e.cursor=e.end);var t=this.getSelectionStyle();return e.marker=this.session.addMarker(e,"ace_selection",t),this.session.$selectionMarkers.push(e),this.session.selectionMarkerCount=this.session.$selectionMarkers.length,e},this.removeSelectionMarker=function(e){if(!e.marker)return;this.session.removeMarker(e.marker);var t=this.session.$selectionMarkers.indexOf(e);t!=-1&&this.session.$selectionMarkers.splice(t,1),this.session.selectionMarkerCount=this.session.$selectionMarkers.length},this.removeSelectionMarkers=function(e){var t=this.session.$selectionMarkers;for(var n=e.length;n--;){var r=e[n];if(!r.marker)continue;this.session.removeMarker(r.marker);var i=t.indexOf(r);i!=-1&&t.splice(i,1)}this.session.selectionMarkerCount=t.length},this.$onAddRange=function(e){this.addSelectionMarker(e.range),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onRemoveRange=function(e){this.removeSelectionMarkers(e.ranges),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelect=function(e){if(this.inMultiSelectMode)return;this.inMultiSelectMode=!0,this.setStyle("ace_multiselect"),this.keyBinding.addKeyboardHandler(f.keyboardHandler),this.commands.on("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onSingleSelect=function(e){if(this.session.multiSelect.inVirtualMode)return;this.inMultiSelectMode=!1,this.unsetStyle("ace_multiselect"),this.keyBinding.removeKeyboardHandler(f.keyboardHandler),this.commands.removeEventListener("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelectExec=function(e){var t=e.command,n=e.editor;if(!n.multiSelect)return;t.multiSelectAction?t.multiSelectAction=="forEach"?n.forEachSelection(t,e.args):t.multiSelectAction=="single"?(n.exitMultiSelectMode(),t.exec(n,e.args||{})):t.multiSelectAction(n,e.args||{}):(t.exec(n,e.args||{}),n.multiSelect.addRange(n.multiSelect.toOrientedRange()),n.multiSelect.mergeOverlappingRanges()),e.preventDefault()},this.forEachSelection=function(e,t){if(this.inVirtualSelectionMode)return;var n=this.session,r=this.selection,i=r.rangeList,o=r._eventRegistry;r._eventRegistry={};var u=new s(n);this.inVirtualSelectionMode=!0;for(var a=i.ranges.length;a--;)u.fromOrientedRange(i.ranges[a]),this.selection=n.selection=u,e.exec(this,t||{}),u.toOrientedRange(i.ranges[a]);u.detach(),this.selection=n.selection=r,this.inVirtualSelectionMode=!1,r._eventRegistry=o,r.mergeOverlappingRanges(),this.onCursorChange(),this.onSelectionChange()},this.exitMultiSelectMode=function(){if(this.inVirtualSelectionMode)return;this.multiSelect.toSingleRange()},this.getCopyText=function(){var e="";if(this.inMultiSelectMode){var t=this.multiSelect.rangeList.ranges;e=[];for(var n=0;n<t.length;n++)e.push(this.session.getTextRange(t[n]));e=e.join(this.session.getDocument().getNewLineCharacter())}else this.selection.isEmpty()||(e=this.session.getTextRange(this.getSelectionRange()));return e},this.onPaste=function(e){if(this.$readOnly)return;this._emit("paste",e);if(!this.inMultiSelectMode)return this.insert(e);var t=e.split(/\r\n|\r|\n/),n=this.selection.rangeList.ranges;if(t.length>n.length||t.length<=2||!t[1])return this.commands.exec("insertstring",this,e);for(var r=n.length;r--;){var i=n[r];i.isEmpty()||this.session.remove(i),this.session.insert(i.start,t[r])}},this.findAll=function(e,t,n){t=t||{},t.needle=e||t.needle,this.$search.set(t);var r=this.$search.findAll(this.session);if(!r.length)return 0;this.$blockScrolling+=1;var i=this.multiSelect;n||i.toSingleRange(r[0]);for(var s=r.length;s--;)i.addRange(r[s],!0);return this.$blockScrolling-=1,r.length},this.selectMoreLines=function(e,t){var n=this.selection.toOrientedRange(),r=n.cursor==n.end,s=this.session.documentToScreenPosition(n.cursor);this.selection.$desiredColumn&&(s.column=this.selection.$desiredColumn);var o=this.session.screenToDocumentPosition(s.row+e,s.column);if(!n.isEmpty())var u=this.session.documentToScreenPosition(r?n.end:n.start),a=this.session.screenToDocumentPosition(u.row+e,u.column);else var a=o;if(r){var f=i.fromPoints(o,a);f.cursor=f.start}else{var f=i.fromPoints(a,o);f.cursor=f.end}f.desiredColumn=s.column;if(!this.selection.inMultiSelectMode)this.selection.addRange(n);else if(t)var l=n.cursor;this.selection.addRange(f),l&&this.selection.substractPoint(l)},this.transposeSelections=function(e){var t=this.session,n=t.multiSelect,r=n.ranges;for(var i=r.length;i--;){var s=r[i];if(s.isEmpty()){var o=t.getWordRange(s.start.row,s.start.column);s.start.row=o.start.row,s.start.column=o.start.column,s.end.row=o.end.row,s.end.column=o.end.column}}n.mergeOverlappingRanges();var u=[];for(var i=r.length;i--;){var s=r[i];u.unshift(t.getTextRange(s))}e<0?u.unshift(u.pop()):u.push(u.shift());for(var i=r.length;i--;){var s=r[i],o=s.clone();t.replace(s,u[i]),s.start.row=o.start.row,s.start.column=o.start.column}},this.selectMore=function(e,t){var n=this.session,r=n.multiSelect,i=r.toOrientedRange();if(i.isEmpty()){var i=n.getWordRange(i.start.row,i.start.column);i.cursor=i.end,this.multiSelect.addRange(i)}var s=n.getTextRange(i),o=h(n,s,e);o&&(o.cursor=e==-1?o.start:o.end,this.multiSelect.addRange(o)),t&&this.multiSelect.substractPoint(i.cursor)},this.alignCursors=function(){var e=this.session,t=e.multiSelect,n=t.ranges;if(!n.length){var r=this.selection.getRange(),s=r.start.row,o=r.end.row,u=this.session.doc.removeLines(s,o);u=this.$reAlignText(u),this.session.doc.insertLines(s,u),r.start.column=0,r.end.column=u[u.length-1].length,this.selection.setRange(r)}else{var f=-1,l=n.filter(function(e){if(e.cursor.row==f)return!0;f=e.cursor.row});t.$onRemoveRange(l);var c=0,h=Infinity,p=n.map(function(t){var n=t.cursor,r=e.getLine(n.row),i=r.substr(n.column).search(/\S/g);return i==-1&&(i=0),n.column>c&&(c=n.column),i<h&&(h=i),i});n.forEach(function(t,n){var r=t.cursor,s=c-r.column,o=p[n]-h;s>o?e.insert(r,a.stringRepeat(" ",s-o)):e.remove(new i(r.row,r.column,r.row,r.column-s+o)),t.start.column=t.end.column=c,t.start.row=t.end.row=r.row,t.cursor=t.end}),t.fromOrientedRange(n[0]),this.renderer.updateCursor(),this.renderer.updateBackMarkers()}},this.$reAlignText=function(e){function o(e,t){return Array(e+1).join(t)}function u(e){return e[2]?o(r," ")+e[2]+o(i-e[2].length+s," ")+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function a(e){return e[2]?o(r+i-e[2].length," ")+e[2]+o(s," ")+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function f(e){return e[2]?o(r," ")+e[2]+o(s," ")+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}var t=!0,n=!0,r,i,s;return e.map(function(e){var o=e.match(/(\s*)(.*?)(\s*)([=:].*)/);return o?r==null?(r=o[1].length,i=o[2].length,s=o[3].length,o):(r+i+s!=o[1].length+o[2].length+o[3].length&&(n=!1),r!=o[1].length&&(t=!1),r>o[1].length&&(r=o[1].length),i<o[2].length&&(i=o[2].length),s>o[3].length&&(s=o[3].length),o):[e]}).map(t?n?a:u:f)}}).call(d.prototype),t.onSessionChange=function(e){var t=e.session;t.multiSelect||(t.$selectionMarkers=[],t.selection.$initRangeList(),t.multiSelect=t.selection),this.multiSelect=t.multiSelect;var n=e.oldSession;n&&(n.multiSelect&&n.multiSelect.editor==this&&(n.multiSelect.editor=null),t.multiSelect.removeEventListener("addRange",this.$onAddRange),t.multiSelect.removeEventListener("removeRange",this.$onRemoveRange),t.multiSelect.removeEventListener("multiSelect",this.$onMultiSelect),t.multiSelect.removeEventListener("singleSelect",this.$onSingleSelect)),t.multiSelect.on("addRange",this.$onAddRange),t.multiSelect.on("removeRange",this.$onRemoveRange),t.multiSelect.on("multiSelect",this.$onMultiSelect),t.multiSelect.on("singleSelect",this.$onSingleSelect),this.inMultiSelectMode!=t.selection.inMultiSelectMode&&(t.selection.inMultiSelectMode?this.$onMultiSelect():this.$onSingleSelect())},t.MultiSelect=m}),ace.define("ace/range_list",["require","exports","module"],function(e,t,n){var r=function(){this.ranges=[]};(function(){this.comparePoints=function(e,t){return e.row-t.row||e.column-t.column},this.pointIndex=function(e,t){var n=this.ranges;for(var r=t||0;r<n.length;r++){var i=n[r],s=this.comparePoints(e,i.end);if(s>0)continue;return s==0?r:(s=this.comparePoints(e,i.start),s>=0?r:-r-1)}return-r-1},this.add=function(e){var t=this.pointIndex(e.start);t<0&&(t=-t-1);var n=this.pointIndex(e.end,t);return n<0?n=-n-1:n++,this.ranges.splice(t,n-t,e)},this.addList=function(e){var t=[];for(var n=e.length;n--;)t.push.call(t,this.add(e[n]));return t},this.substractPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges.splice(t,1)},this.merge=function(){var e=[],t=this.ranges,n=t[0],r;for(var i=1;i<t.length;i++){r=n,n=t[i];var s=this.comparePoints(r.end,n.start);if(s<0)continue;if(s==0&&!r.isEmpty()&&!n.isEmpty())continue;this.comparePoints(r.end,n.end)<0&&(r.end.row=n.end.row,r.end.column=n.end.column),t.splice(i,1),e.push(n),n=r,i--}return e},this.contains=function(e,t){return this.pointIndex({row:e,column:t})>=0},this.containsPoint=function(e){return this.pointIndex(e)>=0},this.rangeAtPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges[t]},this.clipRows=function(e,t){var n=this.ranges;if(n[0].start.row>t||n[n.length-1].start.row<e)return[];var r=this.pointIndex({row:e,column:0});r<0&&(r=-r-1);var i=this.pointIndex({row:t,column:0},r);i<0&&(i=-i-1);var s=[];for(var o=r;o<i;o++)s.push(n[o]);return s},this.removeAll=function(){return this.ranges.splice(0,this.ranges.length)},this.attach=function(e){this.session&&this.detach(),this.session=e,this.onChange=this.$onChange.bind(this),this.session.on("change",this.onChange)},this.detach=function(){if(!this.session)return;this.session.removeListener("change",this.onChange),this.session=null},this.$onChange=function(e){var t=e.data.range;if(e.data.action[0]=="i")var n=t.start,r=t.end;else var r=t.start,n=t.end;var i=n.row,s=r.row,o=s-i,u=-n.column+r.column,a=this.ranges;for(var f=0,l=a.length;f<l;f++){var c=a[f];if(c.end.row<i)continue;if(c.start.row>i)break;c.start.row==i&&c.start.column>=n.column&&(c.start.column+=u,c.start.row+=o),c.end.row==i&&c.end.column>=n.column&&(c.end.column+=u,c.end.row+=o)}if(o!=0&&f<l)for(;f<l;f++){var c=a[f];c.start.row+=o,c.end.row+=o}}}).call(r.prototype),t.RangeList=r}),ace.define("ace/mouse/multi_select_handler",["require","exports","module","ace/lib/event"],function(e,t,n){function i(e,t){return e.row==t.row&&e.column==t.column}function s(e){var t=e.domEvent,n=t.altKey,s=t.shiftKey,o=e.getAccelKey(),u=e.getButton();if(e.editor.inMultiSelectMode&&u==2){e.editor.textInput.onContextMenu(e.domEvent);return}if(!o&&!n){u==0&&e.editor.inMultiSelectMode&&e.editor.exitMultiSelectMode();return}var a=e.editor,f=a.selection,l=a.inMultiSelectMode,c=e.getDocumentPosition(),h=f.getCursor(),p=e.inSelection()||f.isEmpty()&&i(c,h),d=e.x,v=e.y,m=function(e){d=e.clientX,v=e.clientY},g=function(){var e=a.renderer.pixelToScreenCoordinates(d,v),t=y.screenToDocumentPosition(e.row,e.column);if(i(w,e)&&i(t,f.selectionLead))return;w=e,a.selection.moveCursorToPosition(t),a.selection.clearSelection(),a.renderer.scrollCursorIntoView(),a.removeSelectionMarkers(x),x=f.rectangularRangeBlock(w,b),x.forEach(a.addSelectionMarker,a),a.updateSelectionMarkers()},y=a.session,b=a.renderer.pixelToScreenCoordinates(d,v),w=b;if(o&&!s&&!n&&u==0){if(!l&&p)return;if(!l){var E=f.toOrientedRange();a.addSelectionMarker(E)}var S=f.rangeList.rangeAtPoint(c);r.capture(a.container,function(){},function(){var e=f.toOrientedRange();S&&e.isEmpty()&&i(S.cursor,e.cursor)?f.substractPoint(e.cursor):(E&&(a.removeSelectionMarker(E),f.addRange(E)),f.addRange(e))})}else if(!s&&n&&u==0){e.stop(),l&&!o?f.toSingleRange():!l&&o&&f.addRange(),f.moveCursorToPosition(c),f.clearSelection();var x=[],T=function(e){clearInterval(C),a.removeSelectionMarkers(x);for(var t=0;t<x.length;t++)f.addRange(x[t])},N=g;r.capture(a.container,m,T);var C=setInterval(function(){N()},20);return e.preventDefault()}}var r=e("../lib/event");t.onMouseDown=s}),ace.define("ace/commands/multi_select_commands",["require","exports","module","ace/keyboard/hash_handler"],function(e,t,n){t.defaultCommands=[{name:"addCursorAbove",exec:function(e){e.selectMoreLines(-1)},bindKey:{win:"Ctrl-Alt-Up",mac:"Ctrl-Alt-Up"},readonly:!0},{name:"addCursorBelow",exec:function(e){e.selectMoreLines(1)},bindKey:{win:"Ctrl-Alt-Down",mac:"Ctrl-Alt-Down"},readonly:!0},{name:"addCursorAboveSkipCurrent",exec:function(e){e.selectMoreLines(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Up",mac:"Ctrl-Alt-Shift-Up"},readonly:!0},{name:"addCursorBelowSkipCurrent",exec:function(e){e.selectMoreLines(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Down",mac:"Ctrl-Alt-Shift-Down"},readonly:!0},{name:"selectMoreBefore",exec:function(e){e.selectMore(-1)},bindKey:{win:"Ctrl-Alt-Left",mac:"Ctrl-Alt-Left"},readonly:!0},{name:"selectMoreAfter",exec:function(e){e.selectMore(1)},bindKey:{win:"Ctrl-Alt-Right",mac:"Ctrl-Alt-Right"},readonly:!0},{name:"selectNextBefore",exec:function(e){e.selectMore(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Left",mac:"Ctrl-Alt-Shift-Left"},readonly:!0},{name:"selectNextAfter",exec:function(e){e.selectMore(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Right",mac:"Ctrl-Alt-Shift-Right"},readonly:!0},{name:"splitIntoLines",exec:function(e){e.multiSelect.splitIntoLines()},bindKey:{win:"Ctrl-Alt-L",mac:"Ctrl-Alt-L"},readonly:!0},{name:"alignCursors",exec:function(e){e.alignCursors()},bindKey:{win:"Ctrl-Alt-A",mac:"Ctrl-Alt-A"}}],t.multiSelectCommands=[{name:"singleSelection",bindKey:"esc",exec:function(e){e.exitMultiSelectMode()},readonly:!0,isAvailable:function(e){return e&&e.inMultiSelectMode}}];var r=e("../keyboard/hash_handler").HashHandler;t.keyboardHandler=new r(t.multiSelectCommands)}),ace.define("ace/worker/worker_client",["require","exports","module","ace/lib/oop","ace/lib/event_emitter","ace/config"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/event_emitter").EventEmitter,s=e("../config"),o=function(t,n,r){this.changeListener=this.changeListener.bind(this),this.onMessage=this.onMessage.bind(this),this.onError=this.onError.bind(this);var i;if(s.get("packaged"))i=s.moduleUrl(n,"worker");else{var o=this.$normalizePath;typeof e.supports!="undefined"&&e.supports.indexOf("ucjs2-pinf-0")>=0?i=e.nameToUrl("ace/worker/worker_sourcemint"):(e.nameToUrl&&!e.toUrl&&(e.toUrl=e.nameToUrl),i=o(e.toUrl("ace/worker/worker",null,"_")));var u={};t.forEach(function(t){u[t]=o(e.toUrl(t,null,"_").replace(/.js(\?.*)?$/,""))})}this.$worker=new Worker(i),this.$worker.postMessage({init:!0,tlns:u,module:n,classname:r}),this.callbackId=1,this.callbacks={},this.$worker.onerror=this.onError,this.$worker.onmessage=this.onMessage};(function(){r.implement(this,i),this.onError=function(e){throw window.console&&console.log&&console.log(e),e},this.onMessage=function(e){var t=e.data;switch(t.type){case"log":window.console&&console.log&&console.log.apply(console,t.data);break;case"event":this._emit(t.name,{data:t.data});break;case"call":var n=this.callbacks[t.id];n&&(n(t.data),delete this.callbacks[t.id])}},this.$normalizePath=function(e){return location.host?(e=e.replace(/^[a-z]+:\/\/[^\/]+/,""),e=location.protocol+"//"+location.host+(e.charAt(0)=="/"?"":location.pathname.replace(/\/[^\/]*$/,""))+"/"+e.replace(/^[\/]+/,""),e):e},this.terminate=function(){this._emit("terminate",{}),this.$worker.terminate(),this.$worker=null,this.$doc.removeEventListener("change",this.changeListener),this.$doc=null},this.send=function(e,t){this.$worker.postMessage({command:e,args:t})},this.call=function(e,t,n){if(n){var r=this.callbackId++;this.callbacks[r]=n,t.push(r)}this.send(e,t)},this.emit=function(e,t){try{this.$worker.postMessage({event:e,data:{data:t.data}})}catch(n){}},this.attachToDocument=function(e){this.$doc&&this.terminate(),this.$doc=e,this.call("setValue",[e.getValue()]),e.on("change",this.changeListener)},this.changeListener=function(e){e.range={start:e.data.range.start,end:e.data.range.end},this.emit("change",e)}}).call(o.prototype);var u=function(t,n,r){this.changeListener=this.changeListener.bind(this),this.callbackId=1,this.callbacks={},this.messageBuffer=[];var s=null,o=Object.create(i),u=this;this.$worker={},this.$worker.postMessage=function(e){u.messageBuffer.push(e),s&&setTimeout(a)};var a=function(){var e=u.messageBuffer.shift();e.command?s[e.command].apply(s,e.args):e.event&&o._emit(e.event,e.data)};o.postMessage=function(e){u.onMessage({data:e})},o.callback=function(e,t){this.postMessage({type:"call",id:t,data:e})},o.emit=function(e,t){this.postMessage({type:"event",name:e,data:t})},e([n],function(e){s=new e[r](o);while(u.messageBuffer.length)a()})};u.prototype=o.prototype,t.UIWorkerClient=u,t.WorkerClient=o}),ace.define("ace/placeholder",["require","exports","module","ace/range","ace/lib/event_emitter","ace/lib/oop"],function(e,t,n){var r=e("./range").Range,i=e("./lib/event_emitter").EventEmitter,s=e("./lib/oop"),o=function(e,t,n,r,i,s){var o=this;this.length=t,this.session=e,this.doc=e.getDocument(),this.mainClass=i,this.othersClass=s,this.$onUpdate=this.onUpdate.bind(this),this.doc.on("change",this.$onUpdate),this.$others=r,this.$onCursorChange=function(){setTimeout(function(){o.onCursorChange()})},this.$pos=n;var u=e.getUndoManager().$undoStack||e.getUndoManager().$undostack||{length:-1};this.$undoStackDepth=u.length,this.setup(),e.selection.on("changeCursor",this.$onCursorChange)};(function(){s.implement(this,i),this.setup=function(){var e=this,t=this.doc,n=this.session,i=this.$pos;this.pos=t.createAnchor(i.row,i.column),this.markerId=n.addMarker(new r(i.row,i.column,i.row,i.column+this.length),this.mainClass,null,!1),this.pos.on("change",function(t){n.removeMarker(e.markerId),e.markerId=n.addMarker(new r(t.value.row,t.value.column,t.value.row,t.value.column+e.length),e.mainClass,null,!1)}),this.others=[],this.$others.forEach(function(n){var r=t.createAnchor(n.row,n.column);e.others.push(r)}),n.setUndoSelect(!1)},this.showOtherMarkers=function(){if(this.othersActive)return;var e=this.session,t=this;this.othersActive=!0,this.others.forEach(function(n){n.markerId=e.addMarker(new r(n.row,n.column,n.row,n.column+t.length),t.othersClass,null,!1),n.on("change",function(i){e.removeMarker(n.markerId),n.markerId=e.addMarker(new r(i.value.row,i.value.column,i.value.row,i.value.column+t.length),t.othersClass,null,!1)})})},this.hideOtherMarkers=function(){if(!this.othersActive)return;this.othersActive=!1;for(var e=0;e<this.others.length;e++)this.session.removeMarker(this.others[e].markerId)},this.onUpdate=function(e){var t=e.data,n=t.range;if(n.start.row!==n.end.row)return;if(n.start.row!==this.pos.row)return;if(this.$updating)return;this.$updating=!0;var i=t.action==="insertText"?n.end.column-n.start.column:n.start.column-n.end.column;if(n.start.column>=this.pos.column&&n.start.column<=this.pos.column+this.length+1){var s=n.start.column-this.pos.column;this.length+=i;if(!this.session.$fromUndo){if(t.action==="insertText")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};u.row===n.start.row&&n.start.column<u.column&&(a.column+=i),this.doc.insert(a,t.text)}else if(t.action==="removeText")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};u.row===n.start.row&&n.start.column<u.column&&(a.column+=i),this.doc.remove(new r(a.row,a.column,a.row,a.column-i))}n.start.column===this.pos.column&&t.action==="insertText"?setTimeout(function(){this.pos.setPosition(this.pos.row,this.pos.column-i);for(var e=0;e<this.others.length;e++){var t=this.others[e],r={row:t.row,column:t.column-i};t.row===n.start.row&&n.start.column<t.column&&(r.column+=i),t.setPosition(r.row,r.column)}}.bind(this),0):n.start.column===this.pos.column&&t.action==="removeText"&&setTimeout(function(){for(var e=0;e<this.others.length;e++){var t=this.others[e];t.row===n.start.row&&n.start.column<t.column&&t.setPosition(t.row,t.column-i)}}.bind(this),0)}this.pos._emit("change",{value:this.pos});for(var o=0;o<this.others.length;o++)this.others[o]._emit("change",{value:this.others[o]})}this.$updating=!1},this.onCursorChange=function(e){if(this.$updating)return;var t=this.session.selection.getCursor();t.row===this.pos.row&&t.column>=this.pos.column&&t.column<=this.pos.column+this.length?(this.showOtherMarkers(),this._emit("cursorEnter",e)):(this.hideOtherMarkers(),this._emit("cursorLeave",e))},this.detach=function(){this.session.removeMarker(this.markerId),this.hideOtherMarkers(),this.doc.removeEventListener("change",this.$onUpdate),this.session.selection.removeEventListener("changeCursor",this.$onCursorChange),this.pos.detach();for(var e=0;e<this.others.length;e++)this.others[e].detach();this.session.setUndoSelect(!0)},this.cancel=function(){if(this.$undoStackDepth===-1)throw Error("Canceling placeholders only supported with undo manager attached to session.");var e=this.session.getUndoManager(),t=(e.$undoStack||e.$undostack).length-this.$undoStackDepth;for(var n=0;n<t;n++)e.undo(!0)}}).call(o.prototype),t.PlaceHolder=o}),ace.define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(e,t,n){var r=e("../../range").Range,i=t.FoldMode=function(){};(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);return this.foldingStartMarker.test(r)?"start":t=="markbeginend"&&this.foldingStopMarker&&this.foldingStopMarker.test(r)?"end":""},this.getFoldWidgetRange=function(e,t,n){return null},this.indentationBlock=function(e,t,n){var i=/\S/,s=e.getLine(t),o=s.search(i);if(o==-1)return;var u=n||s.length,a=e.getLength(),f=t,l=t;while(++t<a){var c=e.getLine(t).search(i);if(c==-1)continue;if(c<=o)break;l=t}if(l>f){var h=e.getLine(l).length;return new r(f,u,l,h)}},this.openingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i+1},u=e.$findClosingBracket(t,o,s);if(!u)return;var a=e.foldWidgets[u.row];return a==null&&(a=this.getFoldWidget(e,u.row)),a=="start"&&u.row>o.row&&(u.row--,u.column=e.getLine(u.row).length),r.fromPoints(o,u)},this.closingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i},u=e.$findOpeningBracket(t,o);if(!u)return;return u.column++,o.column--,r.fromPoints(u,o)}}).call(i.prototype)}); + (function() { + ace.require(["ace/ace"], function(a) { + a && a.config.init(); + if (!window.ace) + window.ace = {}; + for (var key in a) if (a.hasOwnProperty(key)) + ace[key] = a[key]; + }); + })(); +
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/ace/mode-css.js b/plugins/jetpack/modules/custom-css/custom-css/js/ace/mode-css.js new file mode 100644 index 00000000..20155551 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/ace/mode-css.js @@ -0,0 +1 @@ +ace.define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/tokenizer","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){var r=e("../lib/oop"),i=e("./text").Mode,s=e("../tokenizer").Tokenizer,o=e("./css_highlight_rules").CssHighlightRules,u=e("./matching_brace_outdent").MatchingBraceOutdent,a=e("../worker/worker_client").WorkerClient,f=e("./behaviour/cstyle").CstyleBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.$tokenizer=new s((new o).getRules(),"i"),this.$outdent=new u,this.$behaviour=new f,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.$tokenizer.getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new a(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("csslint",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t}}.call(c.prototype),t.Mode=c}),ace.define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="animation-fill-mode|alignment-adjust|alignment-baseline|animation-delay|animation-direction|animation-duration|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|animation|appearance|azimuth|backface-visibility|background-attachment|background-break|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|background|baseline-shift|binding|bleed|bookmark-label|bookmark-level|bookmark-state|bookmark-target|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|border|bottom|box-align|box-decoration-break|box-direction|box-flex-group|box-flex|box-lines|box-ordinal-group|box-orient|box-pack|box-shadow|box-sizing|break-after|break-before|break-inside|caption-side|clear|clip|color-profile|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|crop|cue-after|cue-before|cue|cursor|direction|display|dominant-baseline|drop-initial-after-adjust|drop-initial-after-align|drop-initial-before-adjust|drop-initial-before-align|drop-initial-size|drop-initial-value|elevation|empty-cells|fit|fit-position|float-offset|float|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|font|grid-columns|grid-rows|hanging-punctuation|height|hyphenate-after|hyphenate-before|hyphenate-character|hyphenate-lines|hyphenate-resource|hyphens|icon|image-orientation|image-rendering|image-resolution|inline-box-align|left|letter-spacing|line-height|line-stacking-ruby|line-stacking-shift|line-stacking-strategy|line-stacking|list-style-image|list-style-position|list-style-type|list-style|margin-bottom|margin-left|margin-right|margin-top|margin|mark-after|mark-before|mark|marks|marquee-direction|marquee-play-count|marquee-speed|marquee-style|max-height|max-width|min-height|min-width|move-to|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|orphans|outline-color|outline-offset|outline-style|outline-width|outline|overflow-style|overflow-x|overflow-y|overflow|padding-bottom|padding-left|padding-right|padding-top|padding|page-break-after|page-break-before|page-break-inside|page-policy|page|pause-after|pause-before|pause|perspective-origin|perspective|phonemes|pitch-range|pitch|play-during|position|presentation-level|punctuation-trim|quotes|rendering-intent|resize|rest-after|rest-before|rest|richness|right|rotation-point|rotation|ruby-align|ruby-overhang|ruby-position|ruby-span|size|speak-header|speak-numeral|speak-punctuation|speak|speech-rate|stress|string-set|table-layout|target-name|target-new|target-position|target|text-align-last|text-align|text-decoration|text-emphasis|text-height|text-indent|text-justify|text-outline|text-shadow|text-transform|text-wrap|top|transform-origin|transform-style|transform|transition-delay|transition-duration|transition-property|transition-timing-function|transition|unicode-bidi|vertical-align|visibility|voice-balance|voice-duration|voice-family|voice-pitch-range|voice-pitch|voice-rate|voice-stress|voice-volume|volume|white-space-collapse|white-space|widows|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero",f=t.supportConstantColor="aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow",l=t.supportConstantFonts="arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+)|(?:[0-9]*\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0),t=[{token:"comment",merge:!0,regex:"\\/\\*",next:"ruleset_comment"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"}],n=i.copyArray(t);n.unshift({token:"paren.rparen",regex:"\\}",next:"start"});var r=i.copyArray(t);r.unshift({token:"paren.rparen",regex:"\\}",next:"media"});var s=[{token:"comment",merge:!0,regex:".+"}],d=i.copyArray(s);d.unshift({token:"comment",regex:".*?\\*\\/",next:"start"});var v=i.copyArray(s);v.unshift({token:"comment",regex:".*?\\*\\/",next:"media"});var m=i.copyArray(s);m.unshift({token:"comment",regex:".*?\\*\\/",next:"ruleset"}),this.$rules={start:[{token:"comment",merge:!0,regex:"\\/\\*",next:"comment"},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"string",regex:"@.*?{",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant",regex:"[a-z0-9-_]+"}],media:[{token:"comment",merge:!0,regex:"\\/\\*",next:"media_comment"},{token:"paren.lparen",regex:"\\{",next:"media_ruleset"},{token:"string",regex:"\\}",next:"start"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant",regex:"[a-z0-9-_]+"}],comment:d,ruleset:n,ruleset_comment:m,media_ruleset:r,media_comment:v}};r.inherits(d,s),t.CssHighlightRules=d}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){var t=e.match(/^(\s+)/);return t?t[1]:""}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),u=["text","paren.rparen","punctuation.operator"],a=["text","paren.rparen","punctuation.operator","comment"],f=0,l=-1,c="",h=0,p=-1,d="",v="",m=function(){m.isSaneInsertion=function(e,t){var n=e.getCursorPosition(),r=new s(t,n.row,n.column);if(!this.$matchTokenType(r.getCurrentToken()||"text",u)){var i=new s(t,n.row,n.column+1);if(!this.$matchTokenType(i.getCurrentToken()||"text",u))return!1}return r.stepForward(),r.getCurrentTokenRow()!==n.row||this.$matchTokenType(r.getCurrentToken()||"text",a)},m.$matchTokenType=function(e,t){return t.indexOf(e.type||e)>-1},m.recordAutoInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isAutoInsertedClosing(r,i,c[0])||(f=0),l=r.row,c=n+i.substr(r.column),f++},m.recordMaybeInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isMaybeInsertedClosing(r,i)||(h=0),p=r.row,d=i.substr(0,r.column)+n,v=i.substr(r.column),h++},m.isAutoInsertedClosing=function(e,t,n){return f>0&&e.row===l&&n===c[0]&&t.substr(e.column)===c},m.isMaybeInsertedClosing=function(e,t){return h>0&&e.row===p&&t.substr(e.column)===v&&t.substr(0,e.column)==d},m.popAutoInsertedClosing=function(){c=c.substr(1),f--},m.clearMaybeInsertedClosing=function(){h=0,p=-1},this.add("braces","insertion",function(e,t,n,r,i){var s=n.getCursorPosition(),u=r.doc.getLine(s.row);if(i=="{"){var a=n.getSelectionRange(),f=r.doc.getTextRange(a);if(f!==""&&f!=="{"&&n.getWrapBehavioursEnabled())return{text:"{"+f+"}",selection:!1};if(m.isSaneInsertion(n,r))return/[\]\}\)]/.test(u[s.column])?(m.recordAutoInsert(n,r,"}"),{text:"{}",selection:[1,1]}):(m.recordMaybeInsert(n,r,"{"),{text:"{",selection:[1,1]})}else if(i=="}"){var l=u.substring(s.column,s.column+1);if(l=="}"){var c=r.$findOpeningBracket("}",{column:s.column+1,row:s.row});if(c!==null&&m.isAutoInsertedClosing(s,u,i))return m.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}else if(i=="\n"||i=="\r\n"){var p="";m.isMaybeInsertedClosing(s,u)&&(p=o.stringRepeat("}",h),m.clearMaybeInsertedClosing());var l=u.substring(s.column,s.column+1);if(l=="}"||p!==""){var d=r.findMatchingBracket({row:s.row,column:s.column},"}");if(!d)return null;var v=this.getNextLineIndent(e,u.substring(0,s.column),r.getTabString()),g=this.$getIndent(u);return{text:"\n"+v+"\n"+g+p,selection:[1,v.length,1,v.length]}}}}),this.add("braces","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="{"){var o=r.doc.getLine(i.start.row),u=o.substring(i.end.column,i.end.column+1);if(u=="}")return i.end.column++,i;h--}}),this.add("parens","insertion",function(e,t,n,r,i){if(i=="("){var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return{text:"("+o+")",selection:!1};if(m.isSaneInsertion(n,r))return m.recordAutoInsert(n,r,")"),{text:"()",selection:[1,1]}}else if(i==")"){var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f==")"){var l=r.$findOpeningBracket(")",{column:u.column+1,row:u.row});if(l!==null&&m.isAutoInsertedClosing(u,a,i))return m.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="("){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==")")return i.end.column++,i}}),this.add("brackets","insertion",function(e,t,n,r,i){if(i=="["){var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return{text:"["+o+"]",selection:!1};if(m.isSaneInsertion(n,r))return m.recordAutoInsert(n,r,"]"),{text:"[]",selection:[1,1]}}else if(i=="]"){var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f=="]"){var l=r.$findOpeningBracket("]",{column:u.column+1,row:u.row});if(l!==null&&m.isAutoInsertedClosing(u,a,i))return m.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="["){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=="]")return i.end.column++,i}}),this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){var s=i,o=n.getSelectionRange(),u=r.doc.getTextRange(o);if(u!==""&&u!=="'"&&u!='"'&&n.getWrapBehavioursEnabled())return{text:s+u+s,selection:!1};var a=n.getCursorPosition(),f=r.doc.getLine(a.row),l=f.substring(a.column-1,a.column);if(l=="\\")return null;var c=r.getTokens(o.start.row),h=0,p,d=-1;for(var v=0;v<c.length;v++){p=c[v],p.type=="string"?d=-1:d<0&&(d=p.value.indexOf(s));if(p.value.length+h>o.start.column)break;h+=c[v].value.length}if(!p||d<0&&p.type!=="comment"&&(p.type!=="string"||o.start.column!==p.value.length+h-1&&p.value.lastIndexOf(s)===p.value.length-1)){if(!m.isSaneInsertion(n,r))return;return{text:s+s,selection:[1,1]}}if(p&&p.type==="string"){var g=f.substring(a.column,a.column+1);if(g==s)return{text:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=='"')return i.end.column++,i}})};r.inherits(m,i),t.CstyleBehaviour=m}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(){};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.getFoldWidgetRange=function(e,t,n){var r=e.getLine(n),i=r.match(this.foldingStartMarker);if(i){var s=i.index;return i[1]?this.openingBracketBlock(e,i[1],n,s):e.getCommentFoldRange(n,s+i[0].length,1)}if(t!=="markbeginend")return;var i=r.match(this.foldingStopMarker);if(i){var s=i.index+i[0].length;return i[1]?this.closingBracketBlock(e,i[1],n,s):e.getCommentFoldRange(n,s,-1)}}}.call(o.prototype)})
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/ace/readme.txt b/plugins/jetpack/modules/custom-css/custom-css/js/ace/readme.txt new file mode 100644 index 00000000..6ef94f82 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/ace/readme.txt @@ -0,0 +1 @@ +The ACE editor files in this directory were pulled from https://github.com/ajaxorg/ace-builds/tree/master/src-min-noconflict, package 12.17.2012.
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/ace/theme-textmate.js b/plugins/jetpack/modules/custom-css/custom-css/js/ace/theme-textmate.js new file mode 100644 index 00000000..d8ef4710 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/ace/theme-textmate.js @@ -0,0 +1 @@ +ace.define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm .ace_scroller {background-color: #FFFFFF;}.ace-tm .ace_cursor {border-left: 2px solid black;}.ace-tm .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_markup.ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_markup.ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/ace/worker-css.js b/plugins/jetpack/modules/custom-css/custom-css/js/ace/worker-css.js new file mode 100644 index 00000000..59ceeaf5 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/ace/worker-css.js @@ -0,0 +1 @@ +"no use strict";function initBaseUrls(e){require.tlns=e}function initSender(){var e=require(null,"ace/lib/event_emitter").EventEmitter,t=require(null,"ace/lib/oop"),n=function(){};return function(){t.implement(this,e),this.callback=function(e,t){postMessage({type:"call",id:t,data:e})},this.emit=function(e,t){postMessage({type:"event",name:e,data:t})}}.call(n.prototype),new n}if(typeof window!="undefined"&&window.document)throw"atempt to load ace worker into main window instead of webWorker";var console={log:function(){var e=Array.prototype.slice.call(arguments,0);postMessage({type:"log",data:e})},error:function(){var e=Array.prototype.slice.call(arguments,0);postMessage({type:"log",data:e})}},window={console:console},normalizeModule=function(e,t){if(t.indexOf("!")!==-1){var n=t.split("!");return normalizeModule(e,n[0])+"!"+normalizeModule(e,n[1])}if(t.charAt(0)=="."){var r=e.split("/").slice(0,-1).join("/"),t=r+"/"+t;while(t.indexOf(".")!==-1&&i!=t)var i=t,t=t.replace(/\/\.\//,"/").replace(/[^\/]+\/\.\.\//,"")}return t},require=function(e,t){if(!t.charAt)throw new Error("worker.js require() accepts only (parentId, id) as arguments");var t=normalizeModule(e,t),n=require.modules[t];if(n)return n.initialized||(n.initialized=!0,n.exports=n.factory().exports),n.exports;var r=t.split("/");r[0]=require.tlns[r[0]]||r[0];var i=r.join("/")+".js";return require.id=t,importScripts(i),require(e,t)};require.modules={},require.tlns={};var define=function(e,t,n){arguments.length==2?(n=t,typeof e!="string"&&(t=e,e=require.id)):arguments.length==1&&(n=e,e=require.id);if(e.indexOf("text!")===0)return;var r=function(t,n){return require(e,t,n)};require.modules[e]={factory:function(){var e={exports:{}},t=n(r,e.exports,e);return t&&(e.exports=t),e}}},main,sender;onmessage=function(e){var t=e.data;if(t.command){if(!main[t.command])throw new Error("Unknown command:"+t.command);main[t.command].apply(main,t.args)}else if(t.init){initBaseUrls(t.tlns),require(null,"ace/lib/fixoldbrowsers"),sender=initSender();var n=require(null,t.module)[t.classname];main=new n(sender)}else t.event&&sender&&sender._emit(t.event,t.data)},define("ace/lib/fixoldbrowsers",["require","exports","module","ace/lib/regexp","ace/lib/es5-shim"],function(e,t,n){e("./regexp"),e("./es5-shim")}),define("ace/lib/regexp",["require","exports","module"],function(e,t,n){function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.extended?"x":"")+(e.sticky?"y":"")}function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(var r=n||0;r<e.length;r++)if(e[r]===t)return r;return-1}var r={exec:RegExp.prototype.exec,test:RegExp.prototype.test,match:String.prototype.match,replace:String.prototype.replace,split:String.prototype.split},i=r.exec.call(/()??/,"")[1]===undefined,s=function(){var e=/^/g;return r.test.call(e,""),!e.lastIndex}();if(s&&i)return;RegExp.prototype.exec=function(e){var t=r.exec.apply(this,arguments),n,a;if(typeof e=="string"&&t){!i&&t.length>1&&u(t,"")>-1&&(a=RegExp(this.source,r.replace.call(o(this),"g","")),r.replace.call(e.slice(t.index),a,function(){for(var e=1;e<arguments.length-2;e++)arguments[e]===undefined&&(t[e]=undefined)}));if(this._xregexp&&this._xregexp.captureNames)for(var f=1;f<t.length;f++)n=this._xregexp.captureNames[f-1],n&&(t[n]=t[f]);!s&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--}return t},s||(RegExp.prototype.test=function(e){var t=r.exec.call(this,e);return t&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--,!!t})}),define("ace/lib/es5-shim",["require","exports","module"],function(e,t,n){function m(e){try{return Object.defineProperty(e,"sentinel",{}),"sentinel"in e}catch(t){}}Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=o.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,u=n.apply(s,r.concat(o.call(arguments)));return u!==null&&Object(u)===u?u:s}return n.apply(t,r.concat(o.call(arguments)))};return i});var r=Function.prototype.call,i=Array.prototype,s=Object.prototype,o=i.slice,u=r.bind(s.toString),a=r.bind(s.hasOwnProperty),f,l,c,h,p;if(p=a(s,"__defineGetter__"))f=r.bind(s.__defineGetter__),l=r.bind(s.__defineSetter__),c=r.bind(s.__lookupGetter__),h=r.bind(s.__lookupSetter__);Array.isArray||(Array.isArray=function(t){return u(t)=="[object Array]"}),Array.prototype.forEach||(Array.prototype.forEach=function(t){var n=D(this),r=arguments[1],i=0,s=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;while(i<s)i in n&&t.call(r,n[i],i,n),i++}),Array.prototype.map||(Array.prototype.map=function(t){var n=D(this),r=n.length>>>0,i=Array(r),s=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var o=0;o<r;o++)o in n&&(i[o]=t.call(s,n[o],o,n));return i}),Array.prototype.filter||(Array.prototype.filter=function(t){var n=D(this),r=n.length>>>0,i=[],s=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var o=0;o<r;o++)o in n&&t.call(s,n[o],o,n)&&i.push(n[o]);return i}),Array.prototype.every||(Array.prototype.every=function(t){var n=D(this),r=n.length>>>0,i=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var s=0;s<r;s++)if(s in n&&!t.call(i,n[s],s,n))return!1;return!0}),Array.prototype.some||(Array.prototype.some=function(t){var n=D(this),r=n.length>>>0,i=arguments[1];if(u(t)!="[object Function]")throw new TypeError;for(var s=0;s<r;s++)if(s in n&&t.call(i,n[s],s,n))return!0;return!1}),Array.prototype.reduce||(Array.prototype.reduce=function(t){var n=D(this),r=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;if(!r&&arguments.length==1)throw new TypeError;var i=0,s;if(arguments.length>=2)s=arguments[1];else do{if(i in n){s=n[i++];break}if(++i>=r)throw new TypeError}while(!0);for(;i<r;i++)i in n&&(s=t.call(void 0,s,n[i],i,n));return s}),Array.prototype.reduceRight||(Array.prototype.reduceRight=function(t){var n=D(this),r=n.length>>>0;if(u(t)!="[object Function]")throw new TypeError;if(!r&&arguments.length==1)throw new TypeError;var i,s=r-1;if(arguments.length>=2)i=arguments[1];else do{if(s in n){i=n[s--];break}if(--s<0)throw new TypeError}while(!0);do s in this&&(i=t.call(void 0,i,n[s],s,n));while(s--);return i}),Array.prototype.indexOf||(Array.prototype.indexOf=function(t){var n=D(this),r=n.length>>>0;if(!r)return-1;var i=0;arguments.length>1&&(i=M(arguments[1])),i=i>=0?i:Math.max(0,r+i);for(;i<r;i++)if(i in n&&n[i]===t)return i;return-1}),Array.prototype.lastIndexOf||(Array.prototype.lastIndexOf=function(t){var n=D(this),r=n.length>>>0;if(!r)return-1;var i=r-1;arguments.length>1&&(i=Math.min(i,M(arguments[1]))),i=i>=0?i:r-Math.abs(i);for(;i>=0;i--)if(i in n&&t===n[i])return i;return-1}),Object.getPrototypeOf||(Object.getPrototypeOf=function(t){return t.__proto__||(t.constructor?t.constructor.prototype:s)});if(!Object.getOwnPropertyDescriptor){var d="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(t,n){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(d+t);if(!a(t,n))return;var r,i,o;r={enumerable:!0,configurable:!0};if(p){var u=t.__proto__;t.__proto__=s;var i=c(t,n),o=h(t,n);t.__proto__=u;if(i||o)return i&&(r.get=i),o&&(r.set=o),r}return r.value=t[n],r}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(t){return Object.keys(t)});if(!Object.create){var v;Object.prototype.__proto__===null?v=function(){return{__proto__:null}}:v=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(t,n){var r;if(t===null)r=v();else{if(typeof t!="object")throw new TypeError("typeof prototype["+typeof t+"] != 'object'");var i=function(){};i.prototype=t,r=new i,r.__proto__=t}return n!==void 0&&Object.defineProperties(r,n),r}}if(Object.defineProperty){var g=m({}),y=typeof document=="undefined"||m(document.createElement("div"));if(!g||!y)var b=Object.defineProperty}if(!Object.defineProperty||b){var w="Property description must be an object: ",E="Object.defineProperty called on non-object: ",S="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(t,n,r){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(E+t);if(typeof r!="object"&&typeof r!="function"||r===null)throw new TypeError(w+r);if(b)try{return b.call(Object,t,n,r)}catch(i){}if(a(r,"value"))if(p&&(c(t,n)||h(t,n))){var o=t.__proto__;t.__proto__=s,delete t[n],t[n]=r.value,t.__proto__=o}else t[n]=r.value;else{if(!p)throw new TypeError(S);a(r,"get")&&f(t,n,r.get),a(r,"set")&&l(t,n,r.set)}return t}}Object.defineProperties||(Object.defineProperties=function(t,n){for(var r in n)a(n,r)&&Object.defineProperty(t,r,n[r]);return t}),Object.seal||(Object.seal=function(t){return t}),Object.freeze||(Object.freeze=function(t){return t});try{Object.freeze(function(){})}catch(x){Object.freeze=function(t){return function(n){return typeof n=="function"?n:t(n)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(t){return t}),Object.isSealed||(Object.isSealed=function(t){return!1}),Object.isFrozen||(Object.isFrozen=function(t){return!1}),Object.isExtensible||(Object.isExtensible=function(t){if(Object(t)===t)throw new TypeError;var n="";while(a(t,n))n+="?";t[n]=!0;var r=a(t,n);return delete t[n],r});if(!Object.keys){var T=!0,N=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],C=N.length;for(var k in{toString:null})T=!1;Object.keys=function P(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var P=[];for(var t in e)a(e,t)&&P.push(t);if(T)for(var n=0,r=C;n<r;n++){var i=N[n];a(e,i)&&P.push(i)}return P}}if(!Date.prototype.toISOString||(new Date(-621987552e5)).toISOString().indexOf("-000001")===-1)Date.prototype.toISOString=function(){var t,n,r,i;if(!isFinite(this))throw new RangeError;t=[this.getUTCMonth()+1,this.getUTCDate(),this.getUTCHours(),this.getUTCMinutes(),this.getUTCSeconds()],i=this.getUTCFullYear(),i=(i<0?"-":i>9999?"+":"")+("00000"+Math.abs(i)).slice(0<=i&&i<=9999?-4:-6),n=t.length;while(n--)r=t[n],r<10&&(t[n]="0"+r);return i+"-"+t.slice(0,2).join("-")+"T"+t.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"};Date.now||(Date.now=function(){return(new Date).getTime()}),Date.prototype.toJSON||(Date.prototype.toJSON=function(t){if(typeof this.toISOString!="function")throw new TypeError;return this.toISOString()}),Date.parse("+275760-09-13T00:00:00.000Z")!==864e13&&(Date=function(e){var t=function i(t,n,r,s,o,u,a){var f=arguments.length;if(this instanceof e){var l=f==1&&String(t)===t?new e(i.parse(t)):f>=7?new e(t,n,r,s,o,u,a):f>=6?new e(t,n,r,s,o,u):f>=5?new e(t,n,r,s,o):f>=4?new e(t,n,r,s):f>=3?new e(t,n,r):f>=2?new e(t,n):f>=1?new e(t):new e;return l.constructor=i,l}return e.apply(this,arguments)},n=new RegExp("^(\\d{4}|[+-]\\d{6})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3}))?)?(?:Z|(?:([-+])(\\d{2}):(\\d{2})))?)?)?)?$");for(var r in e)t[r]=e[r];return t.now=e.now,t.UTC=e.UTC,t.prototype=e.prototype,t.prototype.constructor=t,t.parse=function(r){var i=n.exec(r);if(i){i.shift();for(var s=1;s<7;s++)i[s]=+(i[s]||(s<3?1:0)),s==1&&i[s]--;var o=+i.pop(),u=+i.pop(),a=i.pop(),f=0;if(a){if(u>23||o>59)return NaN;f=(u*60+o)*6e4*(a=="+"?-1:1)}var l=+i[0];return 0<=l&&l<=99?(i[0]=l+400,e.UTC.apply(this,i)+f-126227808e5):e.UTC.apply(this,i)+f}return e.parse.apply(this,arguments)},t}(Date));var L=" \n\f\r \u2028\u2029";if(!String.prototype.trim||L.trim()){L="["+L+"]";var A=new RegExp("^"+L+L+"*"),O=new RegExp(L+L+"*$");String.prototype.trim=function(){return String(this).replace(A,"").replace(O,"")}}var M=function(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-1)*Math.floor(Math.abs(e))),e},_="a"[0]!="a",D=function(e){if(e==null)throw new TypeError;return _&&typeof e=="string"&&e?e.split(""):Object(e)}}),define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){var r={};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry=this._eventRegistry||{},this._defaultHandlers=this._defaultHandlers||{};var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=function(){this.propagationStopped=!0}),t.preventDefault||(t.preventDefault=function(){this.defaultPrevented=!0});for(var i=0;i<n.length;i++){n[i](t);if(t.propagationStopped)break}if(r&&!t.defaultPrevented)return r(t)},r.setDefaultHandler=function(e,t){this._defaultHandlers=this._defaultHandlers||{};if(this._defaultHandlers[e])throw new Error("The default handler for '"+e+"' is already set");this._defaultHandlers[e]=t},r.on=r.addEventListener=function(e,t){this._eventRegistry=this._eventRegistry||{};var n=this._eventRegistry[e];n||(n=this._eventRegistry[e]=[]),n.indexOf(t)==-1&&n.push(t)},r.removeListener=r.removeEventListener=function(e,t){this._eventRegistry=this._eventRegistry||{};var n=this._eventRegistry[e];if(!n)return;var r=n.indexOf(t);r!==-1&&n.splice(r,1)},r.removeAllListeners=function(e){this._eventRegistry&&(this._eventRegistry[e]=[])},t.EventEmitter=r}),define("ace/lib/oop",["require","exports","module"],function(e,t,n){t.inherits=function(){var e=function(){};return function(t,n){e.prototype=n.prototype,t.super_=n.prototype,t.prototype=new e,t.prototype.constructor=t}}(),t.mixin=function(e,t){for(var n in t)e[n]=t[n]},t.implement=function(e,n){t.mixin(e,n)}}),define("ace/mode/css_worker",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/worker/mirror","ace/mode/css/csslint"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/lang"),s=e("../worker/mirror").Mirror,o=e("./css/csslint").CSSLint,u=t.Worker=function(e){s.call(this,e),this.setTimeout(400),this.ruleset=null,this.setDisabledRules("ids"),this.setInfoRules("adjoining-classes|qualified-headings|zero-units|gradients|import|outline-none")};r.inherits(u,s),function(){this.setInfoRules=function(e){typeof e=="string"&&(e=e.split("|")),this.infoRules=i.arrayToMap(e),this.doc.getValue()&&this.deferredUpdate.schedule(100)},this.setDisabledRules=function(e){if(!e)this.ruleset=null;else{typeof e=="string"&&(e=e.split("|"));var t={};o.getRules().forEach(function(e){t[e.id]=!0}),e.forEach(function(e){delete t[e]}),console.log(t),this.ruleset=t}this.doc.getValue()&&this.deferredUpdate.schedule(100)},this.onUpdate=function(){var e=this.doc.getValue(),t=this.infoRules,n=o.verify(e,this.ruleset);this.sender.emit("csslint",n.messages.map(function(e){return{row:e.line-1,column:e.col-1,text:e.message,type:t[e.rule.id]?"info":e.type}}))}}.call(u.prototype)}),define("ace/lib/lang",["require","exports","module"],function(e,t,n){t.stringReverse=function(e){return e.split("").reverse().join("")},t.stringRepeat=function(e,t){return(new Array(t+1)).join(e)};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n<r;n++)e[n]&&typeof e[n]=="object"?t[n]=this.copyObject(e[n]):t[n]=e[n];return t},t.deepCopy=function(e){if(typeof e!="object")return e;var t=e.constructor();for(var n in e)typeof e[n]=="object"?t[n]=this.deepCopy(e[n]):t[n]=e[n];return t},t.arrayToMap=function(e){var t={};for(var n=0;n<e.length;n++)t[e[n]]=1;return t},t.createMap=function(e){var t=Object.create(null);for(var n in e)t[n]=e[n];return t},t.arrayRemove=function(e,t){for(var n=0;n<=e.length;n++)t===e[n]&&e.splice(n,1)},t.escapeRegExp=function(e){return e.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")},t.escapeHTML=function(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<")},t.getMatchOffsets=function(e,t){var n=[];return e.replace(t,function(e){n.push({offset:arguments[arguments.length-2],length:e.length})}),n},t.deferredCall=function(e){var t=null,n=function(){t=null,e()},r=function(e){return r.cancel(),t=setTimeout(n,e||0),r};return r.schedule=r,r.call=function(){return this.cancel(),e(),r},r.cancel=function(){return clearTimeout(t),t=null,r},r},t.delayedCall=function(e,t){var n=null,r=function(){n=null,e()},i=function(e){n&&clearTimeout(n),n=setTimeout(r,e||t)};return i.delay=i,i.schedule=function(e){n==null&&(n=setTimeout(r,e||0))},i.call=function(){this.cancel(),e()},i.cancel=function(){n&&clearTimeout(n),n=null},i.isPending=function(){return n},i}}),define("ace/worker/mirror",["require","exports","module","ace/document","ace/lib/lang"],function(e,t,n){var r=e("../document").Document,i=e("../lib/lang"),s=t.Mirror=function(e){this.sender=e;var t=this.doc=new r(""),n=this.deferredUpdate=i.deferredCall(this.onUpdate.bind(this)),s=this;e.on("change",function(e){t.applyDeltas([e.data]),n.schedule(s.$timeout)})};(function(){this.$timeout=500,this.setTimeout=function(e){this.$timeout=e},this.setValue=function(e){this.doc.setValue(e),this.deferredUpdate.schedule(this.$timeout)},this.getValue=function(e){this.sender.callback(this.doc.getValue(),e)},this.onUpdate=function(){}}).call(s.prototype)}),define("ace/document",["require","exports","module","ace/lib/oop","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=e("./range").Range,o=e("./anchor").Anchor,u=function(e){this.$lines=[],e.length==0?this.$lines=[""]:Array.isArray(e)?this.insertLines(0,e):this.insert({row:0,column:0},e)};(function(){r.implement(this,i),this.setValue=function(e){var t=this.getLength();this.remove(new s(0,0,t,this.getLine(t-1).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new o(this,e,t)},"aaa".split(/a/).length==0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);t?this.$autoNewLine=t[1]:this.$autoNewLine="\n"},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";case"auto":return this.$autoNewLine}},this.$autoNewLine="\n",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){if(e.start.row==e.end.row)return this.$lines[e.start.row].substring(e.start.column,e.end.column);var t=this.getLines(e.start.row+1,e.end.row-1);return t.unshift((this.$lines[e.start.row]||"").substring(e.start.column)),t.push((this.$lines[e.end.row]||"").substring(0,e.end.column)),t.join(this.getNewLineCharacter())},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t&&(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length),e},this.insert=function(e,t){if(!t||t.length===0)return e;e=this.$clipPosition(e),this.getLength()<=1&&this.$detectNewLine(t);var n=this.$split(t),r=n.splice(0,1)[0],i=n.length==0?null:n.splice(n.length-1,1)[0];return e=this.insertInLine(e,r),i!==null&&(e=this.insertNewLine(e),e=this.insertLines(e.row,n),e=this.insertInLine(e,i||"")),e},this.insertLines=function(e,t){if(t.length==0)return{row:e,column:0};if(t.length>65535){var n=this.insertLines(e,t.slice(65535));t=t.slice(0,65535)}var r=[e,0];r.push.apply(r,t),this.$lines.splice.apply(this.$lines,r);var i=new s(e,0,e+t.length,0),o={action:"insertLines",range:i,lines:t};return this._emit("change",{data:o}),n||i.end},this.insertNewLine=function(e){e=this.$clipPosition(e);var t=this.$lines[e.row]||"";this.$lines[e.row]=t.substring(0,e.column),this.$lines.splice(e.row+1,0,t.substring(e.column,t.length));var n={row:e.row+1,column:0},r={action:"insertText",range:s.fromPoints(e,n),text:this.getNewLineCharacter()};return this._emit("change",{data:r}),n},this.insertInLine=function(e,t){if(t.length==0)return e;var n=this.$lines[e.row]||"";this.$lines[e.row]=n.substring(0,e.column)+t+n.substring(e.column);var r={row:e.row,column:e.column+t.length},i={action:"insertText",range:s.fromPoints(e,r),text:t};return this._emit("change",{data:i}),r},this.remove=function(e){e.start=this.$clipPosition(e.start),e.end=this.$clipPosition(e.end);if(e.isEmpty())return e.start;var t=e.start.row,n=e.end.row;if(e.isMultiLine()){var r=e.start.column==0?t:t+1,i=n-1;e.end.column>0&&this.removeInLine(n,0,e.end.column),i>=r&&this.removeLines(r,i),r!=t&&(this.removeInLine(t,e.start.column,this.getLine(t).length),this.removeNewLine(e.start.row))}else this.removeInLine(t,e.start.column,e.end.column);return e.start},this.removeInLine=function(e,t,n){if(t==n)return;var r=new s(e,t,e,n),i=this.getLine(e),o=i.substring(t,n),u=i.substring(0,t)+i.substring(n,i.length);this.$lines.splice(e,1,u);var a={action:"removeText",range:r,text:o};return this._emit("change",{data:a}),r.start},this.removeLines=function(e,t){var n=new s(e,0,t+1,0),r=this.$lines.splice(e,t-e+1),i={action:"removeLines",range:n,nl:this.getNewLineCharacter(),lines:r};return this._emit("change",{data:i}),r},this.removeNewLine=function(e){var t=this.getLine(e),n=this.getLine(e+1),r=new s(e,t.length,e+1,0),i=t+n;this.$lines.splice(e,2,i);var o={action:"removeText",range:r,text:this.getNewLineCharacter()};this._emit("change",{data:o})},this.replace=function(e,t){if(t.length==0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);if(t)var n=this.insert(e.start,t);else n=e.start;return n},this.applyDeltas=function(e){for(var t=0;t<e.length;t++){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this.insertLines(r.start.row,n.lines):n.action=="insertText"?this.insert(r.start,n.text):n.action=="removeLines"?this.removeLines(r.start.row,r.end.row-1):n.action=="removeText"&&this.remove(r)}},this.revertDeltas=function(e){for(var t=e.length-1;t>=0;t--){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this.removeLines(r.start.row,r.end.row-1):n.action=="insertText"?this.remove(r):n.action=="removeLines"?this.insertLines(r.start.row,n.lines):n.action=="removeText"&&this.insert(r.start,n.text)}}}).call(u.prototype),t.Document=u}),define("ace/range",["require","exports","module"],function(e,t,n){var r=function(e,t,n,r){this.start={row:e,column:t},this.end={row:n,column:r}};(function(){this.isEqual=function(e){return this.start.row==e.start.row&&this.end.row==e.end.row&&this.start.column==e.start.column&&this.end.column==e.end.column},this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?t<this.start.column?-1:t>this.end.column?1:0:e<this.start.row?-1:e>this.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};if(this.start.row>t)var i={row:t+1,column:0};if(this.start.row<e)var i={row:e,column:0};if(this.end.row<e)var n={row:e,column:0};return r.fromPoints(i||this.start,n||this.end)},this.extend=function(e,t){var n=this.compare(e,t);if(n==0)return this;if(n==-1)var i={row:e,column:t};else var s={row:e,column:t};return r.fromPoints(i||this.start,s||this.end)},this.isEmpty=function(){return this.start.row==this.end.row&&this.start.column==this.end.column},this.isMultiLine=function(){return this.start.row!==this.end.row},this.clone=function(){return r.fromPoints(this.start,this.end)},this.collapseRows=function(){return this.end.column==0?new r(this.start.row,0,Math.max(this.start.row,this.end.row-1),0):new r(this.start.row,0,this.end.row,0)},this.toScreenRange=function(e){var t=e.documentToScreenPosition(this.start),n=e.documentToScreenPosition(this.end);return new r(t.row,t.column,n.row,n.column)}}).call(r.prototype),r.fromPoints=function(e,t){return new r(e.row,e.column,t.row,t.column)},t.Range=r}),define("ace/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=t.Anchor=function(e,t,n){this.document=e,typeof n=="undefined"?this.setPosition(t.row,t.column):this.setPosition(t,n),this.$onChange=this.onChange.bind(this),e.on("change",this.$onChange)};(function(){r.implement(this,i),this.getPosition=function(){return this.$clipPositionToDocument(this.row,this.column)},this.getDocument=function(){return this.document},this.onChange=function(e){var t=e.data,n=t.range;if(n.start.row==n.end.row&&n.start.row!=this.row)return;if(n.start.row>this.row)return;if(n.start.row==this.row&&n.start.column>this.column)return;var r=this.row,i=this.column;t.action==="insertText"?n.start.row===r&&n.start.column<=i?n.start.row===n.end.row?i+=n.end.column-n.start.column:(i-=n.start.column,r+=n.end.row-n.start.row):n.start.row!==n.end.row&&n.start.row<r&&(r+=n.end.row-n.start.row):t.action==="insertLines"?n.start.row<=r&&(r+=n.end.row-n.start.row):t.action=="removeText"?n.start.row==r&&n.start.column<i?n.end.column>=i?i=n.start.column:i=Math.max(0,i-(n.end.column-n.start.column)):n.start.row!==n.end.row&&n.start.row<r?(n.end.row==r&&(i=Math.max(0,i-n.end.column)+n.start.column),r-=n.end.row-n.start.row):n.end.row==r&&(r-=n.end.row-n.start.row,i=Math.max(0,i-n.end.column)+n.start.column):t.action=="removeLines"&&n.start.row<=r&&(n.end.row<=r?r-=n.end.row-n.start.row:(r=n.start.row,i=0)),this.setPosition(r,i,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._emit("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),define("ace/mode/css/csslint",["require","exports","module"],function(require,exports,module){function Reporter(e,t){this.messages=[],this.stats=[],this.lines=e,this.ruleset=t}var parserlib={};(function(){function e(){this._listeners={}}function t(e){this._input=e.replace(/\n\r?/g,"\n"),this._line=1,this._col=1,this._cursor=0}function n(e,t,n){this.col=n,this.line=t,this.message=e}function r(e,t,n,r){this.col=n,this.line=t,this.text=e,this.type=r}function i(e,n){this._reader=e?new t(e.toString()):null,this._token=null,this._tokenData=n,this._lt=[],this._ltIndex=0,this._ltIndexCache=[]}e.prototype={constructor:e,addListener:function(e,t){this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t)},fire:function(e){typeof e=="string"&&(e={type:e}),typeof e.target!="undefined"&&(e.target=this);if(typeof e.type=="undefined")throw new Error("Event object missing 'type' property.");if(this._listeners[e.type]){var t=this._listeners[e.type].concat();for(var n=0,r=t.length;n<r;n++)t[n].call(this,e)}},removeListener:function(e,t){if(this._listeners[e]){var n=this._listeners[e];for(var r=0,i=n.length;r<i;r++)if(n[r]===t){n.splice(r,1);break}}}},t.prototype={constructor:t,getCol:function(){return this._col},getLine:function(){return this._line},eof:function(){return this._cursor==this._input.length},peek:function(e){var t=null;return e=typeof e=="undefined"?1:e,this._cursor<this._input.length&&(t=this._input.charAt(this._cursor+e-1)),t},read:function(){var e=null;return this._cursor<this._input.length&&(this._input.charAt(this._cursor)=="\n"?(this._line++,this._col=1):this._col++,e=this._input.charAt(this._cursor++)),e},mark:function(){this._bookmark={cursor:this._cursor,line:this._line,col:this._col}},reset:function(){this._bookmark&&(this._cursor=this._bookmark.cursor,this._line=this._bookmark.line,this._col=this._bookmark.col,delete this._bookmark)},readTo:function(e){var t="",n;while(t.length<e.length||t.lastIndexOf(e)!=t.length-e.length){n=this.read();if(!n)throw new Error('Expected "'+e+'" at line '+this._line+", col "+this._col+".");t+=n}return t},readWhile:function(e){var t="",n=this.read();while(n!==null&&e(n))t+=n,n=this.read();return t},readMatch:function(e){var t=this._input.substring(this._cursor),n=null;return typeof e=="string"?t.indexOf(e)===0&&(n=this.readCount(e.length)):e instanceof RegExp&&e.test(t)&&(n=this.readCount(RegExp.lastMatch.length)),n},readCount:function(e){var t="";while(e--)t+=this.read();return t}},n.prototype=new Error,r.fromToken=function(e){return new r(e.value,e.startLine,e.startCol)},r.prototype={constructor:r,valueOf:function(){return this.toString()},toString:function(){return this.text}},i.createTokenData=function(e){var t=[],n={},r=e.concat([]),i=0,s=r.length+1;r.UNKNOWN=-1,r.unshift({name:"EOF"});for(;i<s;i++)t.push(r[i].name),r[r[i].name]=i,r[i].text&&(n[r[i].text]=i);return r.name=function(e){return t[e]},r.type=function(e){return n[e]},r},i.prototype={constructor:i,match:function(e,t){e instanceof Array||(e=[e]);var n=this.get(t),r=0,i=e.length;while(r<i)if(n==e[r++])return!0;return this.unget(),!1},mustMatch:function(e,t){var r;e instanceof Array||(e=[e]);if(!this.match.apply(this,arguments))throw r=this.LT(1),new n("Expected "+this._tokenData[e[0]].name+" at line "+r.startLine+", col "+r.startCol+".",r.startLine,r.startCol)},advance:function(e,t){while(this.LA(0)!==0&&!this.match(e,t))this.get();return this.LA(0)},get:function(e){var t=this._tokenData,n=this._reader,r,i=0,s=t.length,o=!1,u,a;if(this._lt.length&&this._ltIndex>=0&&this._ltIndex<this._lt.length){i++,this._token=this._lt[this._ltIndex++],a=t[this._token.type];while(a.channel!==undefined&&e!==a.channel&&this._ltIndex<this._lt.length)this._token=this._lt[this._ltIndex++],a=t[this._token.type],i++;if((a.channel===undefined||e===a.channel)&&this._ltIndex<=this._lt.length)return this._ltIndexCache.push(i),this._token.type}return u=this._getToken(),u.type>-1&&!t[u.type].hide&&(u.channel=t[u.type].channel,this._token=u,this._lt.push(u),this._ltIndexCache.push(this._lt.length-this._ltIndex+i),this._lt.length>5&&this._lt.shift(),this._ltIndexCache.length>5&&this._ltIndexCache.shift(),this._ltIndex=this._lt.length),a=t[u.type],a&&(a.hide||a.channel!==undefined&&e!==a.channel)?this.get(e):u.type},LA:function(e){var t=e,n;if(e>0){if(e>5)throw new Error("Too much lookahead.");while(t)n=this.get(),t--;while(t<e)this.unget(),t++}else if(e<0){if(!this._lt[this._ltIndex+e])throw new Error("Too much lookbehind.");n=this._lt[this._ltIndex+e].type}else n=this._token.type;return n},LT:function(e){return this.LA(e),this._lt[this._ltIndex+e-1]},peek:function(){return this.LA(1)},token:function(){return this._token},tokenName:function(e){return e<0||e>this._tokenData.length?"UNKNOWN_TOKEN":this._tokenData[e].name},tokenType:function(e){return this._tokenData[e]||-1},unget:function(){if(!this._ltIndexCache.length)throw new Error("Too much lookahead.");this._ltIndex-=this._ltIndexCache.pop(),this._token=this._lt[this._ltIndex-1]}},parserlib.util={StringReader:t,SyntaxError:n,SyntaxUnit:r,EventTarget:e,TokenStreamBase:i}})(),function(){function Combinator(e,t,n){SyntaxUnit.call(this,e,t,n,Parser.COMBINATOR_TYPE),this.type="unknown",/^\s+$/.test(e)?this.type="descendant":e==">"?this.type="child":e=="+"?this.type="adjacent-sibling":e=="~"&&(this.type="sibling")}function MediaFeature(e,t){SyntaxUnit.call(this,"("+e+(t!==null?":"+t:"")+")",e.startLine,e.startCol,Parser.MEDIA_FEATURE_TYPE),this.name=e,this.value=t}function MediaQuery(e,t,n,r,i){SyntaxUnit.call(this,(e?e+" ":"")+(t?t+" ":"")+n.join(" and "),r,i,Parser.MEDIA_QUERY_TYPE),this.modifier=e,this.mediaType=t,this.features=n}function Parser(e){EventTarget.call(this),this.options=e||{},this._tokenStream=null}function PropertyName(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.PROPERTY_NAME_TYPE),this.hack=t}function PropertyValue(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parser.PROPERTY_VALUE_TYPE),this.parts=e}function PropertyValueIterator(e){this._i=0,this._parts=e.parts,this._marks=[],this.value=e}function PropertyValuePart(text,line,col){SyntaxUnit.call(this,text,line,col,Parser.PROPERTY_VALUE_PART_TYPE),this.type="unknown";var temp;if(/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){this.type="dimension",this.value=+RegExp.$1,this.units=RegExp.$2;switch(this.units.toLowerCase()){case"em":case"rem":case"ex":case"px":case"cm":case"mm":case"in":case"pt":case"pc":case"ch":this.type="length";break;case"deg":case"rad":case"grad":this.type="angle";break;case"ms":case"s":this.type="time";break;case"hz":case"khz":this.type="frequency";break;case"dpi":case"dpcm":this.type="resolution"}}else/^([+\-]?[\d\.]+)%$/i.test(text)?(this.type="percentage",this.value=+RegExp.$1):/^([+\-]?[\d\.]+)%$/i.test(text)?(this.type="percentage",this.value=+RegExp.$1):/^([+\-]?\d+)$/i.test(text)?(this.type="integer",this.value=+RegExp.$1):/^([+\-]?[\d\.]+)$/i.test(text)?(this.type="number",this.value=+RegExp.$1):/^#([a-f0-9]{3,6})/i.test(text)?(this.type="color",temp=RegExp.$1,temp.length==3?(this.red=parseInt(temp.charAt(0)+temp.charAt(0),16),this.green=parseInt(temp.charAt(1)+temp.charAt(1),16),this.blue=parseInt(temp.charAt(2)+temp.charAt(2),16)):(this.red=parseInt(temp.substring(0,2),16),this.green=parseInt(temp.substring(2,4),16),this.blue=parseInt(temp.substring(4,6),16))):/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1,this.green=+RegExp.$2,this.blue=+RegExp.$3):/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1*255/100,this.green=+RegExp.$2*255/100,this.blue=+RegExp.$3*255/100):/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1,this.green=+RegExp.$2,this.blue=+RegExp.$3,this.alpha=+RegExp.$4):/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1*255/100,this.green=+RegExp.$2*255/100,this.blue=+RegExp.$3*255/100,this.alpha=+RegExp.$4):/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)?(this.type="color",this.hue=+RegExp.$1,this.saturation=+RegExp.$2/100,this.lightness=+RegExp.$3/100):/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.hue=+RegExp.$1,this.saturation=+RegExp.$2/100,this.lightness=+RegExp.$3/100,this.alpha=+RegExp.$4):/^url\(["']?([^\)"']+)["']?\)/i.test(text)?(this.type="uri",this.uri=RegExp.$1):/^([^\(]+)\(/i.test(text)?(this.type="function",this.name=RegExp.$1,this.value=text):/^["'][^"']*["']/.test(text)?(this.type="string",this.value=eval(text)):Colors[text.toLowerCase()]?(this.type="color",temp=Colors[text.toLowerCase()].substring(1),this.red=parseInt(temp.substring(0,2),16),this.green=parseInt(temp.substring(2,4),16),this.blue=parseInt(temp.substring(4,6),16)):/^[\,\/]$/.test(text)?(this.type="operator",this.value=text):/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)&&(this.type="identifier",this.value=text)}function Selector(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parser.SELECTOR_TYPE),this.parts=e,this.specificity=Specificity.calculate(this)}function SelectorPart(e,t,n,r,i){SyntaxUnit.call(this,n,r,i,Parser.SELECTOR_PART_TYPE),this.elementName=e,this.modifiers=t}function SelectorSubPart(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.SELECTOR_SUB_PART_TYPE),this.type=t,this.args=[]}function Specificity(e,t,n,r){this.a=e,this.b=t,this.c=n,this.d=r}function isHexDigit(e){return e!==null&&h.test(e)}function isDigit(e){return e!==null&&/\d/.test(e)}function isWhitespace(e){return e!==null&&/\s/.test(e)}function isNewLine(e){return e!==null&&nl.test(e)}function isNameStart(e){return e!==null&&/[a-z_\u0080-\uFFFF\\]/i.test(e)}function isNameChar(e){return e!==null&&(isNameStart(e)||/[0-9\-\\]/.test(e))}function isIdentStart(e){return e!==null&&(isNameStart(e)||/\-\\/.test(e))}function mix(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function TokenStream(e){TokenStreamBase.call(this,e,Tokens)}function ValidationError(e,t,n){this.col=n,this.line=t,this.message=e}var EventTarget=parserlib.util.EventTarget,TokenStreamBase=parserlib.util.TokenStreamBase,StringReader=parserlib.util.StringReader,SyntaxError=parserlib.util.SyntaxError,SyntaxUnit=parserlib.util.SyntaxUnit,Colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};Combinator.prototype=new SyntaxUnit,Combinator.prototype.constructor=Combinator,MediaFeature.prototype=new SyntaxUnit,MediaFeature.prototype.constructor=MediaFeature,MediaQuery.prototype=new SyntaxUnit,MediaQuery.prototype.constructor=MediaQuery,Parser.DEFAULT_TYPE=0,Parser.COMBINATOR_TYPE=1,Parser.MEDIA_FEATURE_TYPE=2,Parser.MEDIA_QUERY_TYPE=3,Parser.PROPERTY_NAME_TYPE=4,Parser.PROPERTY_VALUE_TYPE=5,Parser.PROPERTY_VALUE_PART_TYPE=6,Parser.SELECTOR_TYPE=7,Parser.SELECTOR_PART_TYPE=8,Parser.SELECTOR_SUB_PART_TYPE=9,Parser.prototype=function(){var e=new EventTarget,t,n={constructor:Parser,DEFAULT_TYPE:0,COMBINATOR_TYPE:1,MEDIA_FEATURE_TYPE:2,MEDIA_QUERY_TYPE:3,PROPERTY_NAME_TYPE:4,PROPERTY_VALUE_TYPE:5,PROPERTY_VALUE_PART_TYPE:6,SELECTOR_TYPE:7,SELECTOR_PART_TYPE:8,SELECTOR_SUB_PART_TYPE:9,_stylesheet:function(){var e=this._tokenStream,t=null,n,r,i;this.fire("startstylesheet"),this._charset(),this._skipCruft();while(e.peek()==Tokens.IMPORT_SYM)this._import(),this._skipCruft();while(e.peek()==Tokens.NAMESPACE_SYM)this._namespace(),this._skipCruft();i=e.peek();while(i>Tokens.EOF){try{switch(i){case Tokens.MEDIA_SYM:this._media(),this._skipCruft();break;case Tokens.PAGE_SYM:this._page(),this._skipCruft();break;case Tokens.FONT_FACE_SYM:this._font_face(),this._skipCruft();break;case Tokens.KEYFRAMES_SYM:this._keyframes(),this._skipCruft();break;case Tokens.UNKNOWN_SYM:e.get();if(!!this.options.strict)throw new SyntaxError("Unknown @ rule.",e.LT(0).startLine,e.LT(0).startCol);this.fire({type:"error",error:null,message:"Unknown @ rule: "+e.LT(0).value+".",line:e.LT(0).startLine,col:e.LT(0).startCol}),n=0;while(e.advance([Tokens.LBRACE,Tokens.RBRACE])==Tokens.LBRACE)n++;while(n)e.advance([Tokens.RBRACE]),n--;break;case Tokens.S:this._readWhitespace();break;default:if(!this._ruleset())switch(i){case Tokens.CHARSET_SYM:throw r=e.LT(1),this._charset(!1),new SyntaxError("@charset not allowed here.",r.startLine,r.startCol);case Tokens.IMPORT_SYM:throw r=e.LT(1),this._import(!1),new SyntaxError("@import not allowed here.",r.startLine,r.startCol);case Tokens.NAMESPACE_SYM:throw r=e.LT(1),this._namespace(!1),new SyntaxError("@namespace not allowed here.",r.startLine,r.startCol);default:e.get(),this._unexpectedToken(e.token())}}}catch(s){if(!(s instanceof SyntaxError&&!this.options.strict))throw s;this.fire({type:"error",error:s,message:s.message,line:s.line,col:s.col})}i=e.peek()}i!=Tokens.EOF&&this._unexpectedToken(e.token()),this.fire("endstylesheet")},_charset:function(e){var t=this._tokenStream,n,r,i,s;t.match(Tokens.CHARSET_SYM)&&(i=t.token().startLine,s=t.token().startCol,this._readWhitespace(),t.mustMatch(Tokens.STRING),r=t.token(),n=r.value,this._readWhitespace(),t.mustMatch(Tokens.SEMICOLON),e!==!1&&this.fire({type:"charset",charset:n,line:i,col:s}))},_import:function(e){var t=this._tokenStream,n,r,i,s=[];t.mustMatch(Tokens.IMPORT_SYM),i=t.token(),this._readWhitespace(),t.mustMatch([Tokens.STRING,Tokens.URI]),r=t.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/,"$1"),this._readWhitespace(),s=this._media_query_list(),t.mustMatch(Tokens.SEMICOLON),this._readWhitespace(),e!==!1&&this.fire({type:"import",uri:r,media:s,line:i.startLine,col:i.startCol})},_namespace:function(e){var t=this._tokenStream,n,r,i,s;t.mustMatch(Tokens.NAMESPACE_SYM),n=t.token().startLine,r=t.token().startCol,this._readWhitespace(),t.match(Tokens.IDENT)&&(i=t.token().value,this._readWhitespace()),t.mustMatch([Tokens.STRING,Tokens.URI]),s=t.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/,"$1"),this._readWhitespace(),t.mustMatch(Tokens.SEMICOLON),this._readWhitespace(),e!==!1&&this.fire({type:"namespace",prefix:i,uri:s,line:n,col:r})},_media:function(){var e=this._tokenStream,t,n,r;e.mustMatch(Tokens.MEDIA_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),r=this._media_query_list(),e.mustMatch(Tokens.LBRACE),this._readWhitespace(),this.fire({type:"startmedia",media:r,line:t,col:n});for(;;)if(e.peek()==Tokens.PAGE_SYM)this._page();else if(!this._ruleset())break;e.mustMatch(Tokens.RBRACE),this._readWhitespace(),this.fire({type:"endmedia",media:r,line:t,col:n})},_media_query_list:function(){var e=this._tokenStream,t=[];this._readWhitespace(),(e.peek()==Tokens.IDENT||e.peek()==Tokens.LPAREN)&&t.push(this._media_query());while(e.match(Tokens.COMMA))this._readWhitespace(),t.push(this._media_query());return t},_media_query:function(){var e=this._tokenStream,t=null,n=null,r=null,i=[];e.match(Tokens.IDENT)&&(n=e.token().value.toLowerCase(),n!="only"&&n!="not"?(e.unget(),n=null):r=e.token()),this._readWhitespace(),e.peek()==Tokens.IDENT?(t=this._media_type(),r===null&&(r=e.token())):e.peek()==Tokens.LPAREN&&(r===null&&(r=e.LT(1)),i.push(this._media_expression()));if(t===null&&i.length===0)return null;this._readWhitespace();while(e.match(Tokens.IDENT))e.token().value.toLowerCase()!="and"&&this._unexpectedToken(e.token()),this._readWhitespace(),i.push(this._media_expression());return new MediaQuery(n,t,i,r.startLine,r.startCol)},_media_type:function(){return this._media_feature()},_media_expression:function(){var e=this._tokenStream,t=null,n,r=null;return e.mustMatch(Tokens.LPAREN),t=this._media_feature(),this._readWhitespace(),e.match(Tokens.COLON)&&(this._readWhitespace(),n=e.LT(1),r=this._expression()),e.mustMatch(Tokens.RPAREN),this._readWhitespace(),new MediaFeature(t,r?new SyntaxUnit(r,n.startLine,n.startCol):null)},_media_feature:function(){var e=this._tokenStream;return e.mustMatch(Tokens.IDENT),SyntaxUnit.fromToken(e.token())},_page:function(){var e=this._tokenStream,t,n,r=null,i=null;e.mustMatch(Tokens.PAGE_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),e.match(Tokens.IDENT)&&(r=e.token().value,r.toLowerCase()==="auto"&&this._unexpectedToken(e.token())),e.peek()==Tokens.COLON&&(i=this._pseudo_page()),this._readWhitespace(),this.fire({type:"startpage",id:r,pseudo:i,line:t,col:n}),this._readDeclarations(!0,!0),this.fire({type:"endpage",id:r,pseudo:i,line:t,col:n})},_margin:function(){var e=this._tokenStream,t,n,r=this._margin_sym();return r?(t=e.token().startLine,n=e.token().startCol,this.fire({type:"startpagemargin",margin:r,line:t,col:n}),this._readDeclarations(!0),this.fire({type:"endpagemargin",margin:r,line:t,col:n}),!0):!1},_margin_sym:function(){var e=this._tokenStream;return e.match([Tokens.TOPLEFTCORNER_SYM,Tokens.TOPLEFT_SYM,Tokens.TOPCENTER_SYM,Tokens.TOPRIGHT_SYM,Tokens.TOPRIGHTCORNER_SYM,Tokens.BOTTOMLEFTCORNER_SYM,Tokens.BOTTOMLEFT_SYM,Tokens.BOTTOMCENTER_SYM,Tokens.BOTTOMRIGHT_SYM,Tokens.BOTTOMRIGHTCORNER_SYM,Tokens.LEFTTOP_SYM,Tokens.LEFTMIDDLE_SYM,Tokens.LEFTBOTTOM_SYM,Tokens.RIGHTTOP_SYM,Tokens.RIGHTMIDDLE_SYM,Tokens.RIGHTBOTTOM_SYM])?SyntaxUnit.fromToken(e.token()):null},_pseudo_page:function(){var e=this._tokenStream;return e.mustMatch(Tokens.COLON),e.mustMatch(Tokens.IDENT),e.token().value},_font_face:function(){var e=this._tokenStream,t,n;e.mustMatch(Tokens.FONT_FACE_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),this.fire({type:"startfontface",line:t,col:n}),this._readDeclarations(!0),this.fire({type:"endfontface",line:t,col:n})},_operator:function(){var e=this._tokenStream,t=null;return e.match([Tokens.SLASH,Tokens.COMMA])&&(t=e.token(),this._readWhitespace()),t?PropertyValuePart.fromToken(t):null},_combinator:function(){var e=this._tokenStream,t=null,n;return e.match([Tokens.PLUS,Tokens.GREATER,Tokens.TILDE])&&(n=e.token(),t=new Combinator(n.value,n.startLine,n.startCol),this._readWhitespace()),t},_unary_operator:function(){var e=this._tokenStream;return e.match([Tokens.MINUS,Tokens.PLUS])?e.token().value:null},_property:function(){var e=this._tokenStream,t=null,n=null,r,i,s,o;return e.peek()==Tokens.STAR&&this.options.starHack&&(e.get(),i=e.token(),n=i.value,s=i.startLine,o=i.startCol),e.match(Tokens.IDENT)&&(i=e.token(),r=i.value,r.charAt(0)=="_"&&this.options.underscoreHack&&(n="_",r=r.substring(1)),t=new PropertyName(r,n,s||i.startLine,o||i.startCol),this._readWhitespace()),t},_ruleset:function(){var e=this._tokenStream,t,n;try{n=this._selectors_group()}catch(r){if(r instanceof SyntaxError&&!this.options.strict){this.fire({type:"error",error:r,message:r.message,line:r.line,col:r.col}),t=e.advance([Tokens.RBRACE]);if(t!=Tokens.RBRACE)throw r;return!0}throw r}return n&&(this.fire({type:"startrule",selectors:n,line:n[0].line,col:n[0].col}),this._readDeclarations(!0),this.fire({type:"endrule",selectors:n,line:n[0].line,col:n[0].col})),n},_selectors_group:function(){var e=this._tokenStream,t=[],n;n=this._selector();if(n!==null){t.push(n);while(e.match(Tokens.COMMA))this._readWhitespace(),n=this._selector(),n!==null?t.push(n):this._unexpectedToken(e.LT(1))}return t.length?t:null},_selector:function(){var e=this._tokenStream,t=[],n=null,r=null,i=null;n=this._simple_selector_sequence();if(n===null)return null;t.push(n);do{r=this._combinator();if(r!==null)t.push(r),n=this._simple_selector_sequence(),n===null?this._unexpectedToken(e.LT(1)):t.push(n);else{if(!this._readWhitespace())break;i=new Combinator(e.token().value,e.token().startLine,e.token().startCol),r=this._combinator(),n=this._simple_selector_sequence(),n===null?r!==null&&this._unexpectedToken(e.LT(1)):(r!==null?t.push(r):t.push(i),t.push(n))}}while(!0);return new Selector(t,t[0].line,t[0].col)},_simple_selector_sequence:function(){var e=this._tokenStream,t=null,n=[],r="",i=[function(){return e.match(Tokens.HASH)?new SelectorSubPart(e.token().value,"id",e.token().startLine,e.token().startCol):null},this._class,this._attrib,this._pseudo,this._negation],s=0,o=i.length,u=null,a=!1,f,l;f=e.LT(1).startLine,l=e.LT(1).startCol,t=this._type_selector(),t||(t=this._universal()),t!==null&&(r+=t);for(;;){if(e.peek()===Tokens.S)break;while(s<o&&u===null)u=i[s++].call(this);if(u===null){if(r==="")return null;break}s=0,n.push(u),r+=u.toString(),u=null}return r!==""?new SelectorPart(t,n,r,f,l):null},_type_selector:function(){var e=this._tokenStream,t=this._namespace_prefix(),n=this._element_name();return n?(t&&(n.text=t+n.text,n.col-=t.length),n):(t&&(e.unget(),t.length>1&&e.unget()),null)},_class:function(){var e=this._tokenStream,t;return e.match(Tokens.DOT)?(e.mustMatch(Tokens.IDENT),t=e.token(),new SelectorSubPart("."+t.value,"class",t.startLine,t.startCol-1)):null},_element_name:function(){var e=this._tokenStream,t;return e.match(Tokens.IDENT)?(t=e.token(),new SelectorSubPart(t.value,"elementName",t.startLine,t.startCol)):null},_namespace_prefix:function(){var e=this._tokenStream,t="";if(e.LA(1)===Tokens.PIPE||e.LA(2)===Tokens.PIPE)e.match([Tokens.IDENT,Tokens.STAR])&&(t+=e.token().value),e.mustMatch(Tokens.PIPE),t+="|";return t.length?t:null},_universal:function(){var e=this._tokenStream,t="",n;return n=this._namespace_prefix(),n&&(t+=n),e.match(Tokens.STAR)&&(t+="*"),t.length?t:null},_attrib:function(){var e=this._tokenStream,t=null,n,r;return e.match(Tokens.LBRACKET)?(r=e.token(),t=r.value,t+=this._readWhitespace(),n=this._namespace_prefix(),n&&(t+=n),e.mustMatch(Tokens.IDENT),t+=e.token().value,t+=this._readWhitespace(),e.match([Tokens.PREFIXMATCH,Tokens.SUFFIXMATCH,Tokens.SUBSTRINGMATCH,Tokens.EQUALS,Tokens.INCLUDES,Tokens.DASHMATCH])&&(t+=e.token().value,t+=this._readWhitespace(),e.mustMatch([Tokens.IDENT,Tokens.STRING]),t+=e.token().value,t+=this._readWhitespace()),e.mustMatch(Tokens.RBRACKET),new SelectorSubPart(t+"]","attribute",r.startLine,r.startCol)):null},_pseudo:function(){var e=this._tokenStream,t=null,n=":",r,i;return e.match(Tokens.COLON)&&(e.match(Tokens.COLON)&&(n+=":"),e.match(Tokens.IDENT)?(t=e.token().value,r=e.token().startLine,i=e.token().startCol-n.length):e.peek()==Tokens.FUNCTION&&(r=e.LT(1).startLine,i=e.LT(1).startCol-n.length,t=this._functional_pseudo()),t&&(t=new SelectorSubPart(n+t,"pseudo",r,i))),t},_functional_pseudo:function(){var e=this._tokenStream,t=null;return e.match(Tokens.FUNCTION)&&(t=e.token().value,t+=this._readWhitespace(),t+=this._expression(),e.mustMatch(Tokens.RPAREN),t+=")"),t},_expression:function(){var e=this._tokenStream,t="";while(e.match([Tokens.PLUS,Tokens.MINUS,Tokens.DIMENSION,Tokens.NUMBER,Tokens.STRING,Tokens.IDENT,Tokens.LENGTH,Tokens.FREQ,Tokens.ANGLE,Tokens.TIME,Tokens.RESOLUTION]))t+=e.token().value,t+=this._readWhitespace();return t.length?t:null},_negation:function(){var e=this._tokenStream,t,n,r="",i,s=null;return e.match(Tokens.NOT)&&(r=e.token().value,t=e.token().startLine,n=e.token().startCol,r+=this._readWhitespace(),i=this._negation_arg(),r+=i,r+=this._readWhitespace(),e.match(Tokens.RPAREN),r+=e.token().value,s=new SelectorSubPart(r,"not",t,n),s.args.push(i)),s},_negation_arg:function(){var e=this._tokenStream,t=[this._type_selector,this._universal,function(){return e.match(Tokens.HASH)?new SelectorSubPart(e.token().value,"id",e.token().startLine,e.token().startCol):null},this._class,this._attrib,this._pseudo],n=null,r=0,i=t.length,s,o,u,a;o=e.LT(1).startLine,u=e.LT(1).startCol;while(r<i&&n===null)n=t[r].call(this),r++;return n===null&&this._unexpectedToken(e.LT(1)),n.type=="elementName"?a=new SelectorPart(n,[],n.toString(),o,u):a=new SelectorPart(null,[n],n.toString(),o,u),a},_declaration:function(){var e=this._tokenStream,t=null,n=null,r=null,i=null,s=null,o="";t=this._property();if(t!==null){e.mustMatch(Tokens.COLON),this._readWhitespace(),n=this._expr(),(!n||n.length===0)&&this._unexpectedToken(e.LT(1)),r=this._prio(),o=t.toString();if(this.options.starHack&&t.hack=="*"||this.options.underscoreHack&&t.hack=="_")o=t.text;try{this._validateProperty(o,n)}catch(u){s=u}return this.fire({type:"property",property:t,value:n,important:r,line:t.line,col:t.col,invalid:s}),!0}return!1},_prio:function(){var e=this._tokenStream,t=e.match(Tokens.IMPORTANT_SYM);return this._readWhitespace(),t},_expr:function(){var e=this._tokenStream,t=[],n=null,r=null;n=this._term();if(n!==null){t.push(n);do{r=this._operator(),r&&t.push(r),n=this._term();if(n===null)break;t.push(n)}while(!0)}return t.length>0?new PropertyValue(t,t[0].line,t[0].col):null},_term:function(){var e=this._tokenStream,t=null,n=null,r,i,s;return t=this._unary_operator(),t!==null&&(i=e.token().startLine,s=e.token().startCol),e.peek()==Tokens.IE_FUNCTION&&this.options.ieFilters?(n=this._ie_function(),t===null&&(i=e.token().startLine,s=e.token().startCol)):e.match([Tokens.NUMBER,Tokens.PERCENTAGE,Tokens.LENGTH,Tokens.ANGLE,Tokens.TIME,Tokens.FREQ,Tokens.STRING,Tokens.IDENT,Tokens.URI,Tokens.UNICODE_RANGE])?(n=e.token().value,t===null&&(i=e.token().startLine,s=e.token().startCol),this._readWhitespace()):(r=this._hexcolor(),r===null?(t===null&&(i=e.LT(1).startLine,s=e.LT(1).startCol),n===null&&(e.LA(3)==Tokens.EQUALS&&this.options.ieFilters?n=this._ie_function():n=this._function())):(n=r.value,t===null&&(i=r.startLine,s=r.startCol))),n!==null?new PropertyValuePart(t!==null?t+n:n,i,s):null},_function:function(){var e=this._tokenStream,t=null,n=null,r;if(e.match(Tokens.FUNCTION)){t=e.token().value,this._readWhitespace(),n=this._expr(),t+=n;if(this.options.ieFilters&&e.peek()==Tokens.EQUALS)do{this._readWhitespace()&&(t+=e.token().value),e.LA(0)==Tokens.COMMA&&(t+=e.token().value),e.match(Tokens.IDENT),t+=e.token().value,e.match(Tokens.EQUALS),t+=e.token().value,r=e.peek();while(r!=Tokens.COMMA&&r!=Tokens.S&&r!=Tokens.RPAREN)e.get(),t+=e.token().value,r=e.peek()}while(e.match([Tokens.COMMA,Tokens.S]));e.match(Tokens.RPAREN),t+=")",this._readWhitespace()}return t},_ie_function:function(){var e=this._tokenStream,t=null,n=null,r;if(e.match([Tokens.IE_FUNCTION,Tokens.FUNCTION])){t=e.token().value;do{this._readWhitespace()&&(t+=e.token().value),e.LA(0)==Tokens.COMMA&&(t+=e.token().value),e.match(Tokens.IDENT),t+=e.token().value,e.match(Tokens.EQUALS),t+=e.token().value,r=e.peek();while(r!=Tokens.COMMA&&r!=Tokens.S&&r!=Tokens.RPAREN)e.get(),t+=e.token().value,r=e.peek()}while(e.match([Tokens.COMMA,Tokens.S]));e.match(Tokens.RPAREN),t+=")",this._readWhitespace()}return t},_hexcolor:function(){var e=this._tokenStream,t=null,n;if(e.match(Tokens.HASH)){t=e.token(),n=t.value;if(!/#[a-f0-9]{3,6}/i.test(n))throw new SyntaxError("Expected a hex color but found '"+n+"' at line "+t.startLine+", col "+t.startCol+".",t.startLine,t.startCol);this._readWhitespace()}return t},_keyframes:function(){var e=this._tokenStream,t,n,r,i="";e.mustMatch(Tokens.KEYFRAMES_SYM),t=e.token(),/^@\-([^\-]+)\-/.test(t.value)&&(i=RegExp.$1),this._readWhitespace(),r=this._keyframe_name(),this._readWhitespace(),e.mustMatch(Tokens.LBRACE),this.fire({type:"startkeyframes",name:r,prefix:i,line:t.startLine,col:t.startCol}),this._readWhitespace(),n=e.peek();while(n==Tokens.IDENT||n==Tokens.PERCENTAGE)this._keyframe_rule(),this._readWhitespace(),n=e.peek();this.fire({type:"endkeyframes",name:r,prefix:i,line:t.startLine,col:t.startCol}),this._readWhitespace(),e.mustMatch(Tokens.RBRACE)},_keyframe_name:function(){var e=this._tokenStream,t;return e.mustMatch([Tokens.IDENT,Tokens.STRING]),SyntaxUnit.fromToken(e.token())},_keyframe_rule:function(){var e=this._tokenStream,t,n=this._key_list();this.fire({type:"startkeyframerule",keys:n,line:n[0].line,col:n[0].col}),this._readDeclarations(!0),this.fire({type:"endkeyframerule",keys:n,line:n[0].line,col:n[0].col})},_key_list:function(){var e=this._tokenStream,t,n,r=[];r.push(this._key()),this._readWhitespace();while(e.match(Tokens.COMMA))this._readWhitespace(),r.push(this._key()),this._readWhitespace();return r},_key:function(){var e=this._tokenStream,t;if(e.match(Tokens.PERCENTAGE))return SyntaxUnit.fromToken(e.token());if(e.match(Tokens.IDENT)){t=e.token();if(/from|to/i.test(t.value))return SyntaxUnit.fromToken(t);e.unget()}this._unexpectedToken(e.LT(1))},_skipCruft:function(){while(this._tokenStream.match([Tokens.S,Tokens.CDO,Tokens.CDC]));},_readDeclarations:function(e,t){var n=this._tokenStream,r;this._readWhitespace(),e&&n.mustMatch(Tokens.LBRACE),this._readWhitespace();try{for(;;){if(!(n.match(Tokens.SEMICOLON)||t&&this._margin())){if(!this._declaration())break;if(!n.match(Tokens.SEMICOLON))break}this._readWhitespace()}n.mustMatch(Tokens.RBRACE),this._readWhitespace()}catch(i){if(!(i instanceof SyntaxError&&!this.options.strict))throw i;this.fire({type:"error",error:i,message:i.message,line:i.line,col:i.col}),r=n.advance([Tokens.SEMICOLON,Tokens.RBRACE]);if(r==Tokens.SEMICOLON)this._readDeclarations(!1,t);else if(r!=Tokens.RBRACE)throw i}},_readWhitespace:function(){var e=this._tokenStream,t="";while(e.match(Tokens.S))t+=e.token().value;return t},_unexpectedToken:function(e){throw new SyntaxError("Unexpected token '"+e.value+"' at line "+e.startLine+", col "+e.startCol+".",e.startLine,e.startCol)},_verifyEnd:function(){this._tokenStream.LA(1)!=Tokens.EOF&&this._unexpectedToken(this._tokenStream.LT(1))},_validateProperty:function(e,t){Validation.validate(e,t)},parse:function(e){this._tokenStream=new TokenStream(e,Tokens),this._stylesheet()},parseStyleSheet:function(e){return this.parse(e)},parseMediaQuery:function(e){this._tokenStream=new TokenStream(e,Tokens);var t=this._media_query();return this._verifyEnd(),t},parsePropertyValue:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._expr();return this._readWhitespace(),this._verifyEnd(),t},parseRule:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._ruleset();return this._readWhitespace(),this._verifyEnd(),t},parseSelector:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._selector();return this._readWhitespace(),this._verifyEnd(),t},parseStyleAttribute:function(e){e+="}",this._tokenStream=new TokenStream(e,Tokens),this._readDeclarations()}};for(t in n)n.hasOwnProperty(t)&&(e[t]=n[t]);return e}();var Properties={"alignment-adjust":"auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>","alignment-baseline":"baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",animation:1,"animation-delay":{multi:"<time>",comma:!0},"animation-direction":{multi:"normal | alternate",comma:!0},"animation-duration":{multi:"<time>",comma:!0},"animation-iteration-count":{multi:"<number> | infinite",comma:!0},"animation-name":{multi:"none | <ident>",comma:!0},"animation-play-state":{multi:"running | paused",comma:!0},"animation-timing-function":1,"-moz-animation-delay":{multi:"<time>",comma:!0},"-moz-animation-direction":{multi:"normal | alternate",comma:!0},"-moz-animation-duration":{multi:"<time>",comma:!0},"-moz-animation-iteration-count":{multi:"<number> | infinite",comma:!0},"-moz-animation-name":{multi:"none | <ident>",comma:!0},"-moz-animation-play-state":{multi:"running | paused",comma:!0},"-ms-animation-delay":{multi:"<time>",comma:!0},"-ms-animation-direction":{multi:"normal | alternate",comma:!0},"-ms-animation-duration":{multi:"<time>",comma:!0},"-ms-animation-iteration-count":{multi:"<number> | infinite",comma:!0},"-ms-animation-name":{multi:"none | <ident>",comma:!0},"-ms-animation-play-state":{multi:"running | paused",comma:!0},"-webkit-animation-delay":{multi:"<time>",comma:!0},"-webkit-animation-direction":{multi:"normal | alternate",comma:!0},"-webkit-animation-duration":{multi:"<time>",comma:!0},"-webkit-animation-iteration-count":{multi:"<number> | infinite",comma:!0},"-webkit-animation-name":{multi:"none | <ident>",comma:!0},"-webkit-animation-play-state":{multi:"running | paused",comma:!0},"-o-animation-delay":{multi:"<time>",comma:!0},"-o-animation-direction":{multi:"normal | alternate",comma:!0},"-o-animation-duration":{multi:"<time>",comma:!0},"-o-animation-iteration-count":{multi:"<number> | infinite",comma:!0},"-o-animation-name":{multi:"none | <ident>",comma:!0},"-o-animation-play-state":{multi:"running | paused",comma:!0},appearance:"icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | inherit",azimuth:function(e){var t="<angle> | leftwards | rightwards | inherit",n="left-side | far-left | left | center-left | center | center-right | right | far-right | right-side",r=!1,i=!1,s;ValidationTypes.isAny(e,t)||(ValidationTypes.isAny(e,"behind")&&(r=!0,i=!0),ValidationTypes.isAny(e,n)&&(i=!0,r||ValidationTypes.isAny(e,"behind")));if(e.hasNext())throw s=e.next(),i?new ValidationError("Expected end of value but found '"+s+"'.",s.line,s.col):new ValidationError("Expected (<'azimuth'>) but found '"+s+"'.",s.line,s.col)},"backface-visibility":"visible | hidden",background:1,"background-attachment":{multi:"<attachment>",comma:!0},"background-clip":{multi:"<box>",comma:!0},"background-color":"<color> | inherit","background-image":{multi:"<bg-image>",comma:!0},"background-origin":{multi:"<box>",comma:!0},"background-position":{multi:"<bg-position>",comma:!0},"background-repeat":{multi:"<repeat-style>"},"background-size":{multi:"<bg-size>",comma:!0},"baseline-shift":"baseline | sub | super | <percentage> | <length>",behavior:1,binding:1,bleed:"<length>","bookmark-label":"<content> | <attr> | <string>","bookmark-level":"none | <integer>","bookmark-state":"open | closed","bookmark-target":"none | <uri> | <attr>",border:"<border-width> || <border-style> || <color>","border-bottom":"<border-width> || <border-style> || <color>","border-bottom-color":"<color>","border-bottom-left-radius":"<x-one-radius>","border-bottom-right-radius":"<x-one-radius>","border-bottom-style":"<border-style>","border-bottom-width":"<border-width>","border-collapse":"collapse | separate | inherit","border-color":{multi:"<color> | inherit",max:4},"border-image":1,"border-image-outset":{multi:"<length> | <number>",max:4},"border-image-repeat":{multi:"stretch | repeat | round",max:2},"border-image-slice":function(e){var t=!1,n="<number> | <percentage>",r=!1,i=0,s=4,o;ValidationTypes.isAny(e,"fill")&&(r=!0,t=!0);while(e.hasNext()&&i<s){t=ValidationTypes.isAny(e,n);if(!t)break;i++}r?t=!0:ValidationTypes.isAny(e,"fill");if(e.hasNext())throw o=e.next(),t?new ValidationError("Expected end of value but found '"+o+"'.",o.line,o.col):new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '"+o+"'.",o.line,o.col)},"border-image-source":"<image> | none","border-image-width":{multi:"<length> | <percentage> | <number> | auto",max:4},"border-left":"<border-width> || <border-style> || <color>","border-left-color":"<color> | inherit","border-left-style":"<border-style>","border-left-width":"<border-width>","border-radius":function(e){var t=!1,n="<length> | <percentage>",r=!1,i=!1,s=0,o=8,u;while(e.hasNext()&&s<o){t=ValidationTypes.isAny(e,n);if(!t){if(!(e.peek()=="/"&&s>1&&!r))break;r=!0,o=s+5,e.next()}s++}if(e.hasNext())throw u=e.next(),t?new ValidationError("Expected end of value but found '"+u+"'.",u.line,u.col):new ValidationError("Expected (<'border-radius'>) but found '"+u+"'.",u.line,u.col)},"border-right":"<border-width> || <border-style> || <color>","border-right-color":"<color> | inherit","border-right-style":"<border-style>","border-right-width":"<border-width>","border-spacing":{multi:"<length> | inherit",max:2},"border-style":{multi:"<border-style>",max:4},"border-top":"<border-width> || <border-style> || <color>","border-top-color":"<color> | inherit","border-top-left-radius":"<x-one-radius>","border-top-right-radius":"<x-one-radius>","border-top-style":"<border-style>","border-top-width":"<border-width>","border-width":{multi:"<border-width>",max:4},bottom:"<margin-width> | inherit","box-align":"start | end | center | baseline | stretch","box-decoration-break":"slice |clone","box-direction":"normal | reverse | inherit","box-flex":"<number>","box-flex-group":"<integer>","box-lines":"single | multiple","box-ordinal-group":"<integer>","box-orient":"horizontal | vertical | inline-axis | block-axis | inherit","box-pack":"start | end | center | justify","box-shadow":function(e){var t=!1,n;if(!ValidationTypes.isAny(e,"none"))Validation.multiProperty("<shadow>",e,!0,Infinity);else if(e.hasNext())throw n=e.next(),new ValidationError("Expected end of value but found '"+n+"'.",n.line,n.col)},"box-sizing":"content-box | border-box | inherit","break-after":"auto | always | avoid | left | right | page | column | avoid-page | avoid-column","break-before":"auto | always | avoid | left | right | page | column | avoid-page | avoid-column","break-inside":"auto | avoid | avoid-page | avoid-column","caption-side":"top | bottom | inherit",clear:"none | right | left | both | inherit",clip:1,color:"<color> | inherit","color-profile":1,"column-count":"<integer> | auto","column-fill":"auto | balance","column-gap":"<length> | normal","column-rule":"<border-width> || <border-style> || <color>","column-rule-color":"<color>","column-rule-style":"<border-style>","column-rule-width":"<border-width>","column-span":"none | all","column-width":"<length> | auto",columns:1,content:1,"counter-increment":1,"counter-reset":1,crop:"<shape> | auto",cue:"cue-after | cue-before | inherit","cue-after":1,"cue-before":1,cursor:1,direction:"ltr | rtl | inherit",display:"inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit","dominant-baseline":1,"drop-initial-after-adjust":"central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>","drop-initial-after-align":"baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical","drop-initial-before-adjust":"before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>","drop-initial-before-align":"caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical","drop-initial-size":"auto | line | <length> | <percentage>","drop-initial-value":"initial | <integer>",elevation:"<angle> | below | level | above | higher | lower | inherit","empty-cells":"show | hide | inherit",filter:1,fit:"fill | hidden | meet | slice","fit-position":1,"float":"left | right | none | inherit","float-offset":1,font:1,"font-family":1,"font-size":"<absolute-size> | <relative-size> | <length> | <percentage> | inherit","font-size-adjust":"<number> | none | inherit","font-stretch":"normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit","font-style":"normal | italic | oblique | inherit","font-variant":"normal | small-caps | inherit","font-weight":"normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit","grid-cell-stacking":"columns | rows | layer","grid-column":1,"grid-columns":1,"grid-column-align":"start | end | center | stretch","grid-column-sizing":1,"grid-column-span":"<integer>","grid-flow":"none | rows | columns","grid-layer":"<integer>","grid-row":1,"grid-rows":1,"grid-row-align":"start | end | center | stretch","grid-row-span":"<integer>","grid-row-sizing":1,"hanging-punctuation":1,height:"<margin-width> | inherit","hyphenate-after":"<integer> | auto","hyphenate-before":"<integer> | auto","hyphenate-character":"<string> | auto","hyphenate-lines":"no-limit | <integer>","hyphenate-resource":1,hyphens:"none | manual | auto",icon:1,"image-orientation":"angle | auto","image-rendering":1,"image-resolution":1,"inline-box-align":"initial | last | <integer>",left:"<margin-width> | inherit","letter-spacing":"<length> | normal | inherit","line-height":"<number> | <length> | <percentage> | normal | inherit","line-break":"auto | loose | normal | strict","line-stacking":1,"line-stacking-ruby":"exclude-ruby | include-ruby","line-stacking-shift":"consider-shifts | disregard-shifts","line-stacking-strategy":"inline-line-height | block-line-height | max-height | grid-height","list-style":1,"list-style-image":"<uri> | none | inherit","list-style-position":"inside | outside | inherit","list-style-type":"disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit",margin:{multi:"<margin-width> | inherit",max:4},"margin-bottom":"<margin-width> | inherit","margin-left":"<margin-width> | inherit","margin-right":"<margin-width> | inherit","margin-top":"<margin-width> | inherit",mark:1,"mark-after":1,"mark-before":1,marks:1,"marquee-direction":1,"marquee-play-count":1,"marquee-speed":1,"marquee-style":1,"max-height":"<length> | <percentage> | none | inherit","max-width":"<length> | <percentage> | none | inherit","min-height":"<length> | <percentage> | inherit","min-width":"<length> | <percentage> | inherit","move-to":1,"nav-down":1,"nav-index":1,"nav-left":1,"nav-right":1,"nav-up":1,opacity:"<number> | inherit",orphans:"<integer> | inherit",outline:1,"outline-color":"<color> | invert | inherit","outline-offset":1,"outline-style":"<border-style> | inherit","outline-width":"<border-width> | inherit",overflow:"visible | hidden | scroll | auto | inherit","overflow-style":1,"overflow-x":1,"overflow-y":1,padding:{multi:"<padding-width> | inherit",max:4},"padding-bottom":"<padding-width> | inherit","padding-left":"<padding-width> | inherit","padding-right":"<padding-width> | inherit","padding-top":"<padding-width> | inherit",page:1,"page-break-after":"auto | always | avoid | left | right | inherit","page-break-before":"auto | always | avoid | left | right | inherit","page-break-inside":"auto | avoid | inherit","page-policy":1,pause:1,"pause-after":1,"pause-before":1,perspective:1,"perspective-origin":1,phonemes:1,pitch:1,"pitch-range":1,"play-during":1,"pointer-events":"auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit",position:"static | relative | absolute | fixed | inherit","presentation-level":1,"punctuation-trim":1,quotes:1,"rendering-intent":1,resize:1,rest:1,"rest-after":1,"rest-before":1,richness:1,right:"<margin-width> | inherit",rotation:1,"rotation-point":1,"ruby-align":1,"ruby-overhang":1,"ruby-position":1,"ruby-span":1,size:1,speak:"normal | none | spell-out | inherit","speak-header":"once | always | inherit","speak-numeral":"digits | continuous | inherit","speak-punctuation":"code | none | inherit","speech-rate":1,src:1,stress:1,"string-set":1,"table-layout":"auto | fixed | inherit","tab-size":"<integer> | <length>",target:1,"target-name":1,"target-new":1,"target-position":1,"text-align":"left | right | center | justify | inherit","text-align-last":1,"text-decoration":1,"text-emphasis":1,"text-height":1,"text-indent":"<length> | <percentage> | inherit","text-justify":"auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida","text-outline":1,"text-overflow":1,"text-rendering":"auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit","text-shadow":1,"text-transform":"capitalize | uppercase | lowercase | none | inherit","text-wrap":"normal | none | avoid",top:"<margin-width> | inherit",transform:1,"transform-origin":1,"transform-style":1,transition:1,"transition-delay":1,"transition-duration":1,"transition-property":1,"transition-timing-function":1,"unicode-bidi":"normal | embed | bidi-override | inherit","user-modify":"read-only | read-write | write-only | inherit","user-select":"none | text | toggle | element | elements | all | inherit","vertical-align":"<percentage> | <length> | baseline | sub | super | top | text-top | middle | bottom | text-bottom | inherit",visibility:"visible | hidden | collapse | inherit","voice-balance":1,"voice-duration":1,"voice-family":1,"voice-pitch":1,"voice-pitch-range":1,"voice-rate":1,"voice-stress":1,"voice-volume":1,volume:1,"white-space":"normal | pre | nowrap | pre-wrap | pre-line | inherit","white-space-collapse":1,widows:"<integer> | inherit",width:"<length> | <percentage> | auto | inherit","word-break":"normal | keep-all | break-all","word-spacing":"<length> | normal | inherit","word-wrap":1,"z-index":"<integer> | auto | inherit",zoom:"<number> | <percentage> | normal"};PropertyName.prototype=new SyntaxUnit,PropertyName.prototype.constructor=PropertyName,PropertyName.prototype.toString=function(){return(this.hack?this.hack:"")+this.text},PropertyValue.prototype=new SyntaxUnit,PropertyValue.prototype.constructor=PropertyValue,PropertyValueIterator.prototype.count=function(){return this._parts.length},PropertyValueIterator.prototype.isFirst=function(){return this._i===0},PropertyValueIterator.prototype.hasNext=function(){return this._i<this._parts.length},PropertyValueIterator.prototype.mark=function(){this._marks.push(this._i)},PropertyValueIterator.prototype.peek=function(e){return this.hasNext()?this._parts[this._i+(e||0)]:null},PropertyValueIterator.prototype.next=function(){return this.hasNext()?this._parts[this._i++]:null},PropertyValueIterator.prototype.previous=function(){return this._i>0?this._parts[--this._i]:null},PropertyValueIterator.prototype.restore=function(){this._marks.length&&(this._i=this._marks.pop())},PropertyValuePart.prototype=new SyntaxUnit,PropertyValuePart.prototype.constructor=PropertyValuePart,PropertyValuePart.fromToken=function(e){return new PropertyValuePart(e.value,e.startLine,e.startCol)};var Pseudos={":first-letter":1,":first-line":1,":before":1,":after":1};Pseudos.ELEMENT=1,Pseudos.CLASS=2,Pseudos.isElement=function(e){return e.indexOf("::")===0||Pseudos[e.toLowerCase()]==Pseudos.ELEMENT},Selector.prototype=new SyntaxUnit,Selector.prototype.constructor=Selector,SelectorPart.prototype=new SyntaxUnit,SelectorPart.prototype.constructor=SelectorPart,SelectorSubPart.prototype=new SyntaxUnit,SelectorSubPart.prototype.constructor=SelectorSubPart,Specificity.prototype={constructor:Specificity,compare:function(e){var t=["a","b","c","d"],n,r;for(n=0,r=t.length;n<r;n++){if(this[t[n]]<e[t[n]])return-1;if(this[t[n]]>e[t[n]])return 1}return 0},valueOf:function(){return this.a*1e3+this.b*100+this.c*10+this.d},toString:function(){return this.a+","+this.b+","+this.c+","+this.d}},Specificity.calculate=function(e){function u(e){var t,n,r,a,f=e.elementName?e.elementName.text:"",l;f&&f.charAt(f.length-1)!="*"&&o++;for(t=0,r=e.modifiers.length;t<r;t++){l=e.modifiers[t];switch(l.type){case"class":case"attribute":s++;break;case"id":i++;break;case"pseudo":Pseudos.isElement(l.text)?o++:s++;break;case"not":for(n=0,a=l.args.length;n<a;n++)u(l.args[n])}}}var t,n,r,i=0,s=0,o=0;for(t=0,n=e.parts.length;t<n;t++)r=e.parts[t],r instanceof SelectorPart&&u(r);return new Specificity(0,i,s,o)};var h=/^[0-9a-fA-F]$/,nonascii=/^[\u0080-\uFFFF]$/,nl=/\n|\r\n|\r|\f/;TokenStream.prototype=mix(new TokenStreamBase,{_getToken:function(e){var t,n=this._reader,r=null,i=n.getLine(),s=n.getCol();t=n.read();while(t){switch(t){case"/":n.peek()=="*"?r=this.commentToken(t,i,s):r=this.charToken(t,i,s);break;case"|":case"~":case"^":case"$":case"*":n.peek()=="="?r=this.comparisonToken(t,i,s):r=this.charToken(t,i,s);break;case'"':case"'":r=this.stringToken(t,i,s);break;case"#":isNameChar(n.peek())?r=this.hashToken(t,i,s):r=this.charToken(t,i,s);break;case".":isDigit(n.peek())?r=this.numberToken(t,i,s):r=this.charToken(t,i,s);break;case"-":n.peek()=="-"?r=this.htmlCommentEndToken(t,i,s):isNameStart(n.peek())?r=this.identOrFunctionToken(t,i,s):r=this.charToken(t,i,s);break;case"!":r=this.importantToken(t,i,s);break;case"@":r=this.atRuleToken(t,i,s);break;case":":r=this.notToken(t,i,s);break;case"<":r=this.htmlCommentStartToken(t,i,s);break;case"U":case"u":if(n.peek()=="+"){r=this.unicodeRangeToken(t,i,s);break};default:isDigit(t)?r=this.numberToken(t,i,s):isWhitespace(t)?r=this.whitespaceToken(t,i,s):isIdentStart(t)?r=this.identOrFunctionToken(t,i,s):r=this.charToken(t,i,s)}break}return!r&&t===null&&(r=this.createToken(Tokens.EOF,null,i,s)),r},createToken:function(e,t,n,r,i){var s=this._reader;return i=i||{},{value:t,type:e,channel:i.channel,hide:i.hide||!1,startLine:n,startCol:r,endLine:s.getLine(),endCol:s.getCol()}},atRuleToken:function(e,t,n){var r=e,i=this._reader,s=Tokens.CHAR,o=!1,u,a;i.mark(),u=this.readName(),r=e+u,s=Tokens.type(r.toLowerCase());if(s==Tokens.CHAR||s==Tokens.UNKNOWN)r.length>1?s=Tokens.UNKNOWN_SYM:(s=Tokens.CHAR,r=e,i.reset());return this.createToken(s,r,t,n)},charToken:function(e,t,n){var r=Tokens.type(e);return r==-1&&(r=Tokens.CHAR),this.createToken(r,e,t,n)},commentToken:function(e,t,n){var r=this._reader,i=this.readComment(e);return this.createToken(Tokens.COMMENT,i,t,n)},comparisonToken:function(e,t,n){var r=this._reader,i=e+r.read(),s=Tokens.type(i)||Tokens.CHAR;return this.createToken(s,i,t,n)},hashToken:function(e,t,n){var r=this._reader,i=this.readName(e);return this.createToken(Tokens.HASH,i,t,n)},htmlCommentStartToken:function(e,t,n){var r=this._reader,i=e;return r.mark(),i+=r.readCount(3),i=="<!--"?this.createToken(Tokens.CDO,i,t,n):(r.reset(),this.charToken(e,t,n))},htmlCommentEndToken:function(e,t,n){var r=this._reader,i=e;return r.mark(),i+=r.readCount(2),i=="-->"?this.createToken(Tokens.CDC,i,t,n):(r.reset(),this.charToken(e,t,n))},identOrFunctionToken:function(e,t,n){var r=this._reader,i=this.readName(e),s=Tokens.IDENT;return r.peek()=="("?(i+=r.read(),i.toLowerCase()=="url("?(s=Tokens.URI,i=this.readURI(i),i.toLowerCase()=="url("&&(s=Tokens.FUNCTION)):s=Tokens.FUNCTION):r.peek()==":"&&i.toLowerCase()=="progid"&&(i+=r.readTo("("),s=Tokens.IE_FUNCTION),this.createToken(s,i,t,n)},importantToken:function(e,t,n){var r=this._reader,i=e,s=Tokens.CHAR,o,u;r.mark(),u=r.read();while(u){if(u=="/"){if(r.peek()!="*")break;o=this.readComment(u);if(o==="")break}else{if(!isWhitespace(u)){if(/i/i.test(u)){o=r.readCount(8),/mportant/i.test(o)&&(i+=u+o,s=Tokens.IMPORTANT_SYM);break}break}i+=u+this.readWhitespace()}u=r.read()}return s==Tokens.CHAR?(r.reset(),this.charToken(e,t,n)):this.createToken(s,i,t,n)},notToken:function(e,t,n){var r=this._reader,i=e;return r.mark(),i+=r.readCount(4),i.toLowerCase()==":not("?this.createToken(Tokens.NOT,i,t,n):(r.reset(),this.charToken(e,t,n))},numberToken:function(e,t,n){var r=this._reader,i=this.readNumber(e),s,o=Tokens.NUMBER,u=r.peek();return isIdentStart(u)?(s=this.readName(r.read()),i+=s,/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(s)?o=Tokens.LENGTH:/^deg|^rad$|^grad$/i.test(s)?o=Tokens.ANGLE:/^ms$|^s$/i.test(s)?o=Tokens.TIME:/^hz$|^khz$/i.test(s)?o=Tokens.FREQ:/^dpi$|^dpcm$/i.test(s)?o=Tokens.RESOLUTION:o=Tokens.DIMENSION):u=="%"&&(i+=r.read(),o=Tokens.PERCENTAGE),this.createToken(o,i,t,n)},stringToken:function(e,t,n){var r=e,i=e,s=this._reader,o=e,u=Tokens.STRING,a=s.read();while(a){i+=a;if(a==r&&o!="\\")break;if(isNewLine(s.peek())&&a!="\\"){u=Tokens.INVALID;break}o=a,a=s.read()}return a===null&&(u=Tokens.INVALID),this.createToken(u,i,t,n)},unicodeRangeToken:function(e,t,n){var r=this._reader,i=e,s,o=Tokens.CHAR;return r.peek()=="+"&&(r.mark(),i+=r.read(),i+=this.readUnicodeRangePart(!0),i.length==2?r.reset():(o=Tokens.UNICODE_RANGE,i.indexOf("?")==-1&&r.peek()=="-"&&(r.mark(),s=r.read(),s+=this.readUnicodeRangePart(!1),s.length==1?r.reset():i+=s))),this.createToken(o,i,t,n)},whitespaceToken:function(e,t,n){var r=this._reader,i=e+this.readWhitespace();return this.createToken(Tokens.S,i,t,n)},readUnicodeRangePart:function(e){var t=this._reader,n="",r=t.peek();while(isHexDigit(r)&&n.length<6)t.read(),n+=r,r=t.peek();if(e)while(r=="?"&&n.length<6)t.read(),n+=r,r=t.peek();return n},readWhitespace:function(){var e=this._reader,t="",n=e.peek();while(isWhitespace(n))e.read(),t+=n,n=e.peek();return t},readNumber:function(e){var t=this._reader,n=e,r=e==".",i=t.peek();while(i){if(isDigit(i))n+=t.read();else{if(i!=".")break;if(r)break;r=!0,n+=t.read()}i=t.peek()}return n},readString:function(){var e=this._reader,t=e.read(),n=t,r=t,i=e.peek();while(i){i=e.read(),n+=i;if(i==t&&r!="\\")break;if(isNewLine(e.peek())&&i!="\\"){n="";break}r=i,i=e.peek()}return i===null&&(n=""),n},readURI:function(e){var t=this._reader,n=e,r="",i=t.peek();t.mark();while(i&&isWhitespace(i))t.read(),i=t.peek();i=="'"||i=='"'?r=this.readString():r=this.readURL(),i=t.peek();while(i&&isWhitespace(i))t.read(),i=t.peek();return r===""||i!=")"?(n=e,t.reset()):n+=r+t.read(),n},readURL:function(){var e=this._reader,t="",n=e.peek();while(/^[!#$%&\\*-~]$/.test(n))t+=e.read(),n=e.peek();return t},readName:function(e){var t=this._reader,n=e||"",r=t.peek();for(;;)if(r=="\\")n+=this.readEscape(t.read()),r=t.peek();else{if(!r||!isNameChar(r))break;n+=t.read(),r=t.peek()}return n},readEscape:function(e){var t=this._reader,n=e||"",r=0,i=t.peek();if(isHexDigit(i))do n+=t.read(),i=t.peek();while(i&&isHexDigit(i)&&++r<6);return n.length==3&&/\s/.test(i)||n.length==7||n.length==1?t.read():i="",n+i},readComment:function(e){var t=this._reader,n=e||"",r=t.read();if(r=="*"){while(r){n+=r;if(n.length>2&&r=="*"&&t.peek()=="/"){n+=t.read();break}r=t.read()}return n}return""}});var Tokens=[{name:"CDO"},{name:"CDC"},{name:"S",whitespace:!0},{name:"COMMENT",comment:!0,hide:!0,channel:"comment"},{name:"INCLUDES",text:"~="},{name:"DASHMATCH",text:"|="},{name:"PREFIXMATCH",text:"^="},{name:"SUFFIXMATCH",text:"$="},{name:"SUBSTRINGMATCH",text:"*="},{name:"STRING"},{name:"IDENT"},{name:"HASH"},{name:"IMPORT_SYM",text:"@import"},{name:"PAGE_SYM",text:"@page"},{name:"MEDIA_SYM",text:"@media"},{name:"FONT_FACE_SYM",text:"@font-face"},{name:"CHARSET_SYM",text:"@charset"},{name:"NAMESPACE_SYM",text:"@namespace"},{name:"UNKNOWN_SYM"},{name:"KEYFRAMES_SYM",text:["@keyframes","@-webkit-keyframes","@-moz-keyframes","@-o-keyframes"]},{name:"IMPORTANT_SYM"},{name:"LENGTH"},{name:"ANGLE"},{name:"TIME"},{name:"FREQ"},{name:"DIMENSION"},{name:"PERCENTAGE"},{name:"NUMBER"},{name:"URI"},{name:"FUNCTION"},{name:"UNICODE_RANGE"},{name:"INVALID"},{name:"PLUS",text:"+"},{name:"GREATER",text:">"},{name:"COMMA",text:","},{name:"TILDE",text:"~"},{name:"NOT"},{name:"TOPLEFTCORNER_SYM",text:"@top-left-corner"},{name:"TOPLEFT_SYM",text:"@top-left"},{name:"TOPCENTER_SYM",text:"@top-center"},{name:"TOPRIGHT_SYM",text:"@top-right"},{name:"TOPRIGHTCORNER_SYM",text:"@top-right-corner"},{name:"BOTTOMLEFTCORNER_SYM",text:"@bottom-left-corner"},{name:"BOTTOMLEFT_SYM",text:"@bottom-left"},{name:"BOTTOMCENTER_SYM",text:"@bottom-center"},{name:"BOTTOMRIGHT_SYM",text:"@bottom-right"},{name:"BOTTOMRIGHTCORNER_SYM",text:"@bottom-right-corner"},{name:"LEFTTOP_SYM",text:"@left-top"},{name:"LEFTMIDDLE_SYM",text:"@left-middle"},{name:"LEFTBOTTOM_SYM",text:"@left-bottom"},{name:"RIGHTTOP_SYM",text:"@right-top"},{name:"RIGHTMIDDLE_SYM",text:"@right-middle"},{name:"RIGHTBOTTOM_SYM",text:"@right-bottom"},{name:"RESOLUTION",state:"media"},{name:"IE_FUNCTION"},{name:"CHAR"},{name:"PIPE",text:"|"},{name:"SLASH",text:"/"},{name:"MINUS",text:"-"},{name:"STAR",text:"*"},{name:"LBRACE",text:"{"},{name:"RBRACE",text:"}"},{name:"LBRACKET",text:"["},{name:"RBRACKET",text:"]"},{name:"EQUALS",text:"="},{name:"COLON",text:":"},{name:"SEMICOLON",text:";"},{name:"LPAREN",text:"("},{name:"RPAREN",text:")"},{name:"DOT",text:"."}];(function(){var e=[],t={};Tokens.UNKNOWN=-1,Tokens.unshift({name:"EOF"});for(var n=0,r=Tokens.length;n<r;n++){e.push(Tokens[n].name),Tokens[Tokens[n].name]=n;if(Tokens[n].text)if(Tokens[n].text instanceof Array)for(var i=0;i<Tokens[n].text.length;i++)t[Tokens[n].text[i]]=n;else t[Tokens[n].text]=n}Tokens.name=function(t){return e[t]},Tokens.type=function(e){return t[e]||-1}})();var Validation={validate:function(e,t){var n=e.toString().toLowerCase(),r=t.parts,i=new PropertyValueIterator(t),s=Properties[n],o,u,a,f,l,c,h,p,d,v,m;if(!s){if(n.indexOf("-")!==0)throw new ValidationError("Unknown property '"+e+"'.",e.line,e.col)}else typeof s!="number"&&(typeof s=="string"?s.indexOf("||")>-1?this.groupProperty(s,i):this.singleProperty(s,i,1):s.multi?this.multiProperty(s.multi,i,s.comma,s.max||Infinity):typeof s=="function"&&s(i))},singleProperty:function(e,t,n,r){var i=!1,s=t.value,o=0,u;while(t.hasNext()&&o<n){i=ValidationTypes.isAny(t,e);if(!i)break;o++}if(!i)throw t.hasNext()&&!t.isFirst()?(u=t.peek(),new ValidationError("Expected end of value but found '"+u+"'.",u.line,u.col)):new ValidationError("Expected ("+e+") but found '"+s+"'.",s.line,s.col);if(t.hasNext())throw u=t.next(),new ValidationError("Expected end of value but found '"+u+"'.",u.line,u.col)},multiProperty:function(e,t,n,r){var i=!1,s=t.value,o=0,u=!1,a;while(t.hasNext()&&!i&&o<r){if(!ValidationTypes.isAny(t,e))break;o++;if(!t.hasNext())i=!0;else if(n){if(t.peek()!=",")break;a=t.next()}}if(!i)throw t.hasNext()&&!t.isFirst()?(a=t.peek(),new ValidationError("Expected end of value but found '"+a+"'.",a.line,a.col)):(a=t.previous(),n&&a==","?new ValidationError("Expected end of value but found '"+a+"'.",a.line,a.col):new ValidationError("Expected ("+e+") but found '"+s+"'.",s.line,s.col));if(t.hasNext())throw a=t.next(),new ValidationError("Expected end of value but found '"+a+"'.",a.line,a.col)},groupProperty:function(e,t,n){var r=!1,i=t.value,s=e.split("||").length,o={count:0},u=!1,a,f;while(t.hasNext()&&!r){a=ValidationTypes.isAnyOfGroup(t,e);if(!a)break;if(o[a])break;o[a]=1,o.count++,u=!0;if(o.count==s||!t.hasNext())r=!0}if(!r)throw u&&t.hasNext()?(f=t.peek(),new ValidationError("Expected end of value but found '"+f+"'.",f.line,f.col)):new ValidationError("Expected ("+e+") but found '"+i+"'.",i.line,i.col);if(t.hasNext())throw f=t.next(),new ValidationError("Expected end of value but found '"+f+"'.",f.line,f.col)}};ValidationError.prototype=new Error;var ValidationTypes={isLiteral:function(e,t){var n=e.text.toString().toLowerCase(),r=t.split(" | "),i,s,o=!1;for(i=0,s=r.length;i<s&&!o;i++)n==r[i].toLowerCase()&&(o=!0);return o},isSimple:function(e){return!!this.simple[e]},isComplex:function(e){return!!this.complex[e]},isAny:function(e,t){var n=t.split(" | "),r,i,s=!1;for(r=0,i=n.length;r<i&&!s&&e.hasNext();r++)s=this.isType(e,n[r]);return s},isAnyOfGroup:function(e,t){var n=t.split(" || "),r,i,s=!1;for(r=0,i=n.length;r<i&&!s;r++)s=this.isType(e,n[r]);return s?n[r-1]:!1},isType:function(e,t){var n=e.peek(),r=!1;return t.charAt(0)!="<"?(r=this.isLiteral(n,t),r&&e.next()):this.simple[t]?(r=this.simple[t](n),r&&e.next()):r=this.complex[t](e),r},simple:{"<absolute-size>":function(e){return ValidationTypes.isLiteral(e,"xx-small | x-small | small | medium | large | x-large | xx-large")},"<attachment>":function(e){return ValidationTypes.isLiteral(e,"scroll | fixed | local")},"<attr>":function(e){return e.type=="function"&&e.name=="attr"},"<bg-image>":function(e){return this["<image>"](e)||this["<gradient>"](e)||e=="none"},"<gradient>":function(e){return e.type=="function"&&/^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(e)},"<box>":function(e){return ValidationTypes.isLiteral(e,"padding-box | border-box | content-box")},"<content>":function(e){return e.type=="function"&&e.name=="content"},"<relative-size>":function(e){return ValidationTypes.isLiteral(e,"smaller | larger")},"<ident>":function(e){return e.type=="identifier"},"<length>":function(e){return e.type=="length"||e.type=="number"||e.type=="integer"||e=="0"},"<color>":function(e){return e.type=="color"||e=="transparent"},"<number>":function(e){return e.type=="number"||this["<integer>"](e)},"<integer>":function(e){return e.type=="integer"},"<line>":function(e){return e.type=="integer"},"<angle>":function(e){return e.type=="angle"},"<uri>":function(e){return e.type=="uri"},"<image>":function(e){return this["<uri>"](e)},"<percentage>":function(e){return e.type=="percentage"||e=="0"},"<border-width>":function(e){return this["<length>"](e)||ValidationTypes.isLiteral(e,"thin | medium | thick")},"<border-style>":function(e){return ValidationTypes.isLiteral(e,"none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset")},"<margin-width>":function(e){return this["<length>"](e)||this["<percentage>"](e)||ValidationTypes.isLiteral(e,"auto")},"<padding-width>":function(e){return this["<length>"](e)||this["<percentage>"](e)},"<shape>":function(e){return e.type=="function"&&(e.name=="rect"||e.name=="inset-rect")},"<time>":function(e){return e.type=="time"}},complex:{"<bg-position>":function(e){var t=this,n=!1,r="<percentage> | <length>",i="left | center | right",s="top | center | bottom",o,u,a;return ValidationTypes.isAny(e,"top | bottom")?n=!0:ValidationTypes.isAny(e,r)?e.hasNext()&&(n=ValidationTypes.isAny(e,r+" | "+s)):ValidationTypes.isAny(e,i)&&e.hasNext()&&(ValidationTypes.isAny(e,s)?(n=!0,ValidationTypes.isAny(e,r)):ValidationTypes.isAny(e,r)&&(ValidationTypes.isAny(e,s)&&ValidationTypes.isAny(e,r),n=!0)),n},"<bg-size>":function(e){var t=this,n=!1,r="<percentage> | <length> | auto",i,s,o;return ValidationTypes.isAny(e,"cover | contain")?n=!0:ValidationTypes.isAny(e,r)&&(n=!0,ValidationTypes.isAny(e,r)),n},"<repeat-style>":function(e){var t=!1,n="repeat | space | round | no-repeat",r;return e.hasNext()&&(r=e.next(),ValidationTypes.isLiteral(r,"repeat-x | repeat-y")?t=!0:ValidationTypes.isLiteral(r,n)&&(t=!0,e.hasNext()&&ValidationTypes.isLiteral(e.peek(),n)&&e.next())),t},"<shadow>":function(e){var t=!1,n=0,r=!1,i=!1,s;if(e.hasNext()){ValidationTypes.isAny(e,"inset")&&(r=!0),ValidationTypes.isAny(e,"<color>")&&(i=!0);while(ValidationTypes.isAny(e,"<length>")&&n<4)n++;e.hasNext()&&(i||ValidationTypes.isAny(e,"<color>"),r||ValidationTypes.isAny(e,"inset")),t=n>=2&&n<=4}return t},"<x-one-radius>":function(e){var t=!1,n=0,r="<length> | <percentage>",i;return ValidationTypes.isAny(e,r)&&(t=!0,ValidationTypes.isAny(e,r)),t}}};parserlib.css={Colors:Colors,Combinator:Combinator,Parser:Parser,PropertyName:PropertyName,PropertyValue:PropertyValue,PropertyValuePart:PropertyValuePart,MediaFeature:MediaFeature,MediaQuery:MediaQuery,Selector:Selector,SelectorPart:SelectorPart,SelectorSubPart:SelectorSubPart,Specificity:Specificity,TokenStream:TokenStream,Tokens:Tokens,ValidationError:ValidationError}}();var CSSLint=function(){var e=[],t=[],n=new parserlib.util.EventTarget;return n.version="0.9.9",n.addRule=function(t){e.push(t),e[t.id]=t},n.clearRules=function(){e=[]},n.getRules=function(){return[].concat(e).sort(function(e,t){return e.id>t.id?1:0})},n.getRuleset=function(){var t={},n=0,r=e.length;while(n<r)t[e[n++].id]=1;return t},n.addFormatter=function(e){t[e.id]=e},n.getFormatter=function(e){return t[e]},n.format=function(e,t,n,r){var i=this.getFormatter(n),s=null;return i&&(s=i.startFormat(),s+=i.formatResults(e,t,r||{}),s+=i.endFormat()),s},n.hasFormat=function(e){return t.hasOwnProperty(e)},n.verify=function(t,n){var r=0,i=e.length,s,o,u,a=new parserlib.css.Parser({starHack:!0,ieFilters:!0,underscoreHack:!0,strict:!1});o=t.replace(/\n\r?/g,"$split$").split("$split$"),n||(n=this.getRuleset()),s=new Reporter(o,n),n.errors=2;for(r in n)n.hasOwnProperty(r)&&e[r]&&e[r].init(a,s);try{a.parse(t)}catch(f){s.error("Fatal error, cannot continue: "+f.message,f.line,f.col,{})}return u={messages:s.messages,stats:s.stats},u.messages.sort(function(e,t){return e.rollup&&!t.rollup?1:!e.rollup&&t.rollup?-1:e.line-t.line}),u},n}();Reporter.prototype={constructor:Reporter,error:function(e,t,n,r){this.messages.push({type:"error",line:t,col:n,message:e,evidence:this.lines[t-1],rule:r||{}})},warn:function(e,t,n,r){this.report(e,t,n,r)},report:function(e,t,n,r){this.messages.push({type:this.ruleset[r.id]==2?"error":"warning",line:t,col:n,message:e,evidence:this.lines[t-1],rule:r})},info:function(e,t,n,r){this.messages.push({type:"info",line:t,col:n,message:e,evidence:this.lines[t-1],rule:r})},rollupError:function(e,t){this.messages.push({type:"error",rollup:!0,message:e,rule:t})},rollupWarn:function(e,t){this.messages.push({type:"warning",rollup:!0,message:e,rule:t})},stat:function(e,t){this.stats[e]=t}},CSSLint._Reporter=Reporter,CSSLint.Util={mix:function(e,t){var n;for(n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return n},indexOf:function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},forEach:function(e,t){if(e.forEach)return e.forEach(t);for(var n=0,r=e.length;n<r;n++)t(e[n],n,e)}},CSSLint.addRule({id:"adjoining-classes",name:"Disallow adjoining classes",desc:"Don't use adjoining classes.",browsers:"IE6",init:function(e,t){var n=this;e.addListener("startrule",function(r){var i=r.selectors,s,o,u,a,f,l,c;for(f=0;f<i.length;f++){s=i[f];for(l=0;l<s.parts.length;l++){o=s.parts[l];if(o.type==e.SELECTOR_PART_TYPE){a=0;for(c=0;c<o.modifiers.length;c++)u=o.modifiers[c],u.type=="class"&&a++,a>1&&t.report("Don't use adjoining classes.",o.line,o.col,n)}}}})}}),CSSLint.addRule({id:"box-model",name:"Beware of broken box size",desc:"Don't use width or height when using padding or border.",browsers:"All",init:function(e,t){function u(){s={},o=!1}function a(){var e,u;if(!o){if(s.height)for(e in i)i.hasOwnProperty(e)&&s[e]&&(u=s[e].value,(e!="padding"||u.parts.length!==2||u.parts[0].value!==0)&&t.report("Using height with "+e+" can sometimes make elements larger than you expect.",s[e].line,s[e].col,n));if(s.width)for(e in r)r.hasOwnProperty(e)&&s[e]&&(u=s[e].value,(e!="padding"||u.parts.length!==2||u.parts[1].value!==0)&&t.report("Using width with "+e+" can sometimes make elements larger than you expect.",s[e].line,s[e].col,n))}}var n=this,r={border:1,"border-left":1,"border-right":1,padding:1,"padding-left":1,"padding-right":1},i={border:1,"border-bottom":1,"border-top":1,padding:1,"padding-bottom":1,"padding-top":1},s,o=!1;e.addListener("startrule",u),e.addListener("startfontface",u),e.addListener("startpage",u),e.addListener("startpagemargin",u),e.addListener("startkeyframerule",u),e.addListener("property",function(e){var t=e.property.text.toLowerCase();i[t]||r[t]?!/^0\S*$/.test(e.value)&&(t!="border"||e.value!="none")&&(s[t]={line:e.property.line,col:e.property.col,value:e.value}):/^(width|height)/i.test(t)&&/^(length|percentage)/.test(e.value.parts[0].type)?s[t]=1:t=="box-sizing"&&(o=!0)}),e.addListener("endrule",a),e.addListener("endfontface",a),e.addListener("endpage",a),e.addListener("endpagemargin",a),e.addListener("endkeyframerule",a)}}),CSSLint.addRule({id:"box-sizing",name:"Disallow use of box-sizing",desc:"The box-sizing properties isn't supported in IE6 and IE7.",browsers:"IE6, IE7",tags:["Compatibility"],init:function(e,t){var n=this;e.addListener("property",function(e){var r=e.property.text.toLowerCase();r=="box-sizing"&&t.report("The box-sizing property isn't supported in IE6 and IE7.",e.line,e.col,n)})}}),CSSLint.addRule({id:"compatible-vendor-prefixes",name:"Require compatible vendor prefixes",desc:"Include all compatible vendor prefixes to reach a wider range of users.",browsers:"All",init:function(e,t){var n=this,r,i,s,o,u,a,f,l=!1,c=Array.prototype.push,h=[];r={animation:"webkit moz","animation-delay":"webkit moz","animation-direction":"webkit moz","animation-duration":"webkit moz","animation-fill-mode":"webkit moz","animation-iteration-count":"webkit moz","animation-name":"webkit moz","animation-play-state":"webkit moz","animation-timing-function":"webkit moz",appearance:"webkit moz","border-end":"webkit moz","border-end-color":"webkit moz","border-end-style":"webkit moz","border-end-width":"webkit moz","border-image":"webkit moz o","border-radius":"webkit moz","border-start":"webkit moz","border-start-color":"webkit moz","border-start-style":"webkit moz","border-start-width":"webkit moz","box-align":"webkit moz ms","box-direction":"webkit moz ms","box-flex":"webkit moz ms","box-lines":"webkit ms","box-ordinal-group":"webkit moz ms","box-orient":"webkit moz ms","box-pack":"webkit moz ms","box-sizing":"webkit moz","box-shadow":"webkit moz","column-count":"webkit moz ms","column-gap":"webkit moz ms","column-rule":"webkit moz ms","column-rule-color":"webkit moz ms","column-rule-style":"webkit moz ms","column-rule-width":"webkit moz ms","column-width":"webkit moz ms",hyphens:"epub moz","line-break":"webkit ms","margin-end":"webkit moz","margin-start":"webkit moz","marquee-speed":"webkit wap","marquee-style":"webkit wap","padding-end":"webkit moz","padding-start":"webkit moz","tab-size":"moz o","text-size-adjust":"webkit ms",transform:"webkit moz ms o","transform-origin":"webkit moz ms o",transition:"webkit moz o","transition-delay":"webkit moz o","transition-duration":"webkit moz o","transition-property":"webkit moz o","transition-timing-function":"webkit moz o","user-modify":"webkit moz","user-select":"webkit moz ms","word-break":"epub ms","writing-mode":"epub ms"};for(s in r)if(r.hasOwnProperty(s)){o=[],u=r[s].split(" ");for(a=0,f=u.length;a<f;a++)o.push("-"+u[a]+"-"+s);r[s]=o,c.apply(h,o)}e.addListener("startrule",function(){i=[]}),e.addListener("startkeyframes",function(e){l=e.prefix||!0}),e.addListener("endkeyframes",function(e){l=!1}),e.addListener("property",function(e){var t=e.property;CSSLint.Util.indexOf(h,t.text)>-1&&(!l||typeof l!="string"||t.text.indexOf("-"+l+"-")!==0)&&i.push(t)}),e.addListener("endrule",function(e){if(!i.length)return;var s={},o,u,a,f,l,c,h,p,d,v;for(o=0,u=i.length;o<u;o++){a=i[o];for(f in r)r.hasOwnProperty(f)&&(l=r[f],CSSLint.Util.indexOf(l,a.text)>-1&&(s[f]||(s[f]={full:l.slice(0),actual:[],actualNodes:[]}),CSSLint.Util.indexOf(s[f].actual,a.text)===-1&&(s[f].actual.push(a.text),s[f].actualNodes.push(a))))}for(f in s)if(s.hasOwnProperty(f)){c=s[f],h=c.full,p=c.actual;if(h.length>p.length)for(o=0,u=h.length;o<u;o++)d=h[o],CSSLint.Util.indexOf(p,d)===-1&&(v=p.length===1?p[0]:p.length==2?p.join(" and "):p.join(", "),t.report("The property "+d+" is compatible with "+v+" and should be included as well.",c.actualNodes[0].line,c.actualNodes[0].col,n))}})}}),CSSLint.addRule({id:"display-property-grouping",name:"Require properties appropriate for display",desc:"Certain properties shouldn't be used with certain display property values.",browsers:"All",init:function(e,t){function s(e,s,o){i[e]&&(typeof r[e]!="string"||i[e].value.toLowerCase()!=r[e])&&t.report(o||e+" can't be used with display: "+s+".",i[e].line,i[e].col,n)}function o(){i={}}function u(){var e=i.display?i.display.value:null;if(e)switch(e){case"inline":s("height",e),s("width",e),s("margin",e),s("margin-top",e),s("margin-bottom",e),s("float",e,"display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");break;case"block":s("vertical-align",e);break;case"inline-block":s("float",e);break;default:e.indexOf("table-")===0&&(s("margin",e),s("margin-left",e),s("margin-right",e),s("margin-top",e),s("margin-bottom",e),s("float",e))}}var n=this,r={display:1,"float":"none",height:1,width:1,margin:1,"margin-left":1,"margin-right":1,"margin-bottom":1,"margin-top":1,padding:1,"padding-left":1,"padding-right":1,"padding-bottom":1,"padding-top":1,"vertical-align":1},i;e.addListener("startrule",o),e.addListener("startfontface",o),e.addListener("startkeyframerule",o),e.addListener("startpagemargin",o),e.addListener("startpage",o),e.addListener("property",function(e){var t=e.property.text.toLowerCase();r[t]&&(i[t]={value:e.value.text,line:e.property.line,col:e.property.col})}),e.addListener("endrule",u),e.addListener("endfontface",u),e.addListener("endkeyframerule",u),e.addListener("endpagemargin",u),e.addListener("endpage",u)}}),CSSLint.addRule({id:"duplicate-background-images",name:"Disallow duplicate background images",desc:"Every background-image should be unique. Use a common class for e.g. sprites.",browsers:"All",init:function(e,t){var n=this,r={};e.addListener("property",function(e){var i=e.property.text,s=e.value,o,u;if(i.match(/background/i))for(o=0,u=s.parts.length;o<u;o++)s.parts[o].type=="uri"&&(typeof r[s.parts[o].uri]=="undefined"?r[s.parts[o].uri]=e:t.report("Background image '"+s.parts[o].uri+"' was used multiple times, first declared at line "+r[s.parts[o].uri].line+", col "+r[s.parts[o].uri].col+".",e.line,e.col,n))})}}),CSSLint.addRule({id:"duplicate-properties",name:"Disallow duplicate properties",desc:"Duplicate properties must appear one after the other.",browsers:"All",init:function(e,t){function s(e){r={}}var n=this,r,i;e.addListener("startrule",s),e.addListener("startfontface",s),e.addListener("startpage",s),e.addListener("startpagemargin",s),e.addListener("startkeyframerule",s),e.addListener("property",function(e){var s=e.property,o=s.text.toLowerCase();r[o]&&(i!=o||r[o]==e.value.text)&&t.report("Duplicate property '"+e.property+"' found.",e.line,e.col,n),r[o]=e.value.text,i=o})}}),CSSLint.addRule({id:"empty-rules",name:"Disallow empty rules",desc:"Rules without any properties specified should be removed.",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("startrule",function(){r=0}),e.addListener("property",function(){r++}),e.addListener("endrule",function(e){var i=e.selectors;r===0&&t.report("Rule is empty.",i[0].line,i[0].col,n)})}}),CSSLint.addRule({id:"errors",name:"Parsing Errors",desc:"This rule looks for recoverable syntax errors.",browsers:"All",init:function(e,t){var n=this;e.addListener("error",function(e){t.error(e.message,e.line,e.col,n)})}}),CSSLint.addRule({id:"fallback-colors",name:"Require fallback colors",desc:"For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",browsers:"IE6,IE7,IE8",init:function(e,t){function o(e){s={},r=null}var n=this,r,i={color:1,background:1,"background-color":1},s;e.addListener("startrule",o),e.addListener("startfontface",o),e.addListener("startpage",o),e.addListener("startpagemargin",o),e.addListener("startkeyframerule",o),e.addListener("property",function(e){var s=e.property,o=s.text.toLowerCase(),u=e.value.parts,a=0,f="",l=u.length;if(i[o])while(a<l)u[a].type=="color"&&("alpha"in u[a]||"hue"in u[a]?(/([^\)]+)\(/.test(u[a])&&(f=RegExp.$1.toUpperCase()),(!r||r.property.text.toLowerCase()!=o||r.colorType!="compat")&&t.report("Fallback "+o+" (hex or RGB) should precede "+f+" "+o+".",e.line,e.col,n)):e.colorType="compat"),a++;r=e})}}),CSSLint.addRule({id:"floats",name:"Disallow too many floats",desc:"This rule tests if the float property is used too many times",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("property",function(e){e.property.text.toLowerCase()=="float"&&e.value.text.toLowerCase()!="none"&&r++}),e.addListener("endstylesheet",function(){t.stat("floats",r),r>=10&&t.rollupWarn("Too many floats ("+r+"), you're probably using them for layout. Consider using a grid system instead.",n)})}}),CSSLint.addRule({id:"font-faces",name:"Don't use too many web fonts",desc:"Too many different web fonts in the same stylesheet.",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("startfontface",function(){r++}),e.addListener("endstylesheet",function(){r>5&&t.rollupWarn("Too many @font-face declarations ("+r+").",n)})}}),CSSLint.addRule({id:"font-sizes",name:"Disallow too many font sizes",desc:"Checks the number of font-size declarations.",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("property",function(e){e.property=="font-size"&&r++}),e.addListener("endstylesheet",function(){t.stat("font-sizes",r),r>=10&&t.rollupWarn("Too many font-size declarations ("+r+"), abstraction needed.",n)})}}),CSSLint.addRule({id:"gradients",name:"Require all gradient definitions",desc:"When using a vendor-prefixed gradient, make sure to use them all.",browsers:"All",init:function(e,t){var n=this,r;e.addListener("startrule",function(){r={moz:0,webkit:0,oldWebkit:0,ms:0,o:0}}),e.addListener("property",function(e){/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(e.value)?r[RegExp.$1]=1:/\-webkit\-gradient/i.test(e.value)&&(r.oldWebkit=1)}),e.addListener("endrule",function(e){var i=[];r.moz||i.push("Firefox 3.6+"),r.webkit||i.push("Webkit (Safari 5+, Chrome)"),r.oldWebkit||i.push("Old Webkit (Safari 4+, Chrome)"),r.ms||i.push("Internet Explorer 10+"),r.o||i.push("Opera 11.1+"),i.length&&i.length<5&&t.report("Missing vendor-prefixed CSS gradients for "+i.join(", ")+".",e.selectors[0].line,e.selectors[0].col,n)})}}),CSSLint.addRule({id:"ids",name:"Disallow IDs in selectors",desc:"Selectors should not contain IDs.",browsers:"All",init:function(e,t){var n=this;e.addListener("startrule",function(r){var i=r.selectors,s,o,u,a,f,l,c;for(f=0;f<i.length;f++){s=i[f],a=0;for(l=0;l<s.parts.length;l++){o=s.parts[l];if(o.type==e.SELECTOR_PART_TYPE)for(c=0;c<o.modifiers.length;c++)u=o.modifiers[c],u.type=="id"&&a++}a==1?t.report("Don't use IDs in selectors.",s.line,s.col,n):a>1&&t.report(a+" IDs in the selector, really?",s.line,s.col,n)}})}}),CSSLint.addRule({id:"import",name:"Disallow @import",desc:"Don't use @import, use <link> instead.",browsers:"All",init:function(e,t){var n=this;e.addListener("import",function(e){t.report("@import prevents parallel downloads, use <link> instead.",e.line,e.col,n)})}}),CSSLint.addRule({id:"important",name:"Disallow !important",desc:"Be careful when using !important declaration",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("property",function(e){e.important===!0&&(r++,t.report("Use of !important",e.line,e.col,n))}),e.addListener("endstylesheet",function(){t.stat("important",r),r>=10&&t.rollupWarn("Too many !important declarations ("+r+"), try to use less than 10 to avoid specificity issues.",n)})}}),CSSLint.addRule({id:"known-properties",name:"Require use of known properties",desc:"Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",browsers:"All",init:function(e,t){var n=this;e.addListener("property",function(e){var r=e.property.text.toLowerCase();e.invalid&&t.report(e.invalid.message,e.line,e.col,n)})}}),CSSLint.addRule({id:"outline-none",name:"Disallow outline: none",desc:"Use of outline: none or outline: 0 should be limited to :focus rules.",browsers:"All",tags:["Accessibility"],init:function(e,t){function i(e){e.selectors?r={line:e.line,col:e.col,selectors:e.selectors,propCount:0,outline:!1}:r=null}function s(e){r&&r.outline&&(r.selectors.toString().toLowerCase().indexOf(":focus")==-1?t.report("Outlines should only be modified using :focus.",r.line,r.col,n):r.propCount==1&&t.report("Outlines shouldn't be hidden unless other visual changes are made.",r.line,r.col,n))}var n=this,r;e.addListener("startrule",i),e.addListener("startfontface",i),e.addListener("startpage",i),e.addListener("startpagemargin",i),e.addListener("startkeyframerule",i),e.addListener("property",function(e){var t=e.property.text.toLowerCase(),n=e.value;r&&(r.propCount++,t=="outline"&&(n=="none"||n=="0")&&(r.outline=!0))}),e.addListener("endrule",s),e.addListener("endfontface",s),e.addListener("endpage",s),e.addListener("endpagemargin",s),e.addListener("endkeyframerule",s)}}),CSSLint.addRule({id:"overqualified-elements",name:"Disallow overqualified elements",desc:"Don't use classes or IDs with elements (a.foo or a#foo).",browsers:"All",init:function(e,t){var n=this,r={};e.addListener("startrule",function(i){var s=i.selectors,o,u,a,f,l,c;for(f=0;f<s.length;f++){o=s[f];for(l=0;l<o.parts.length;l++){u=o.parts[l];if(u.type==e.SELECTOR_PART_TYPE)for(c=0;c<u.modifiers.length;c++)a=u.modifiers[c],u.elementName&&a.type=="id"?t.report("Element ("+u+") is overqualified, just use "+a+" without element name.",u.line,u.col,n):a.type=="class"&&(r[a]||(r[a]=[]),r[a].push({modifier:a,part:u}))}}}),e.addListener("endstylesheet",function(){var e;for(e in r)r.hasOwnProperty(e)&&r[e].length==1&&r[e][0].part.elementName&&t.report("Element ("+r[e][0].part+") is overqualified, just use "+r[e][0].modifier+" without element name.",r[e][0].part.line,r[e][0].part.col,n)})}}),CSSLint.addRule({id:"qualified-headings",name:"Disallow qualified headings",desc:"Headings should not be qualified (namespaced).",browsers:"All",init:function(e,t){var n=this;e.addListener("startrule",function(r){var i=r.selectors,s,o,u,a;for(u=0;u<i.length;u++){s=i[u];for(a=0;a<s.parts.length;a++)o=s.parts[a],o.type==e.SELECTOR_PART_TYPE&&o.elementName&&/h[1-6]/.test(o.elementName.toString())&&a>0&&t.report("Heading ("+o.elementName+") should not be qualified.",o.line,o.col,n)}})}}),CSSLint.addRule({id:"regex-selectors",name:"Disallow selectors that look like regexs",desc:"Selectors that look like regular expressions are slow and should be avoided.",browsers:"All",init:function(e,t){var n=this;e.addListener("startrule",function(r){var i=r.selectors,s,o,u,a,f,l;for(a=0;a<i.length;a++){s=i[a];for(f=0;f<s.parts.length;f++){o=s.parts[f];if(o.type==e.SELECTOR_PART_TYPE)for(l=0;l<o.modifiers.length;l++)u=o.modifiers[l],u.type=="attribute"&&/([\~\|\^\$\*]=)/.test(u)&&t.report("Attribute selectors with "+RegExp.$1+" are slow!",u.line,u.col,n)}}})}}),CSSLint.addRule({id:"rules-count",name:"Rules Count",desc:"Track how many rules there are.",browsers:"All",init:function(e,t){var n=this,r=0;e.addListener("startrule",function(){r++}),e.addListener("endstylesheet",function(){t.stat("rule-count",r)})}}),CSSLint.addRule({id:"shorthand",name:"Require shorthand properties",desc:"Use shorthand properties where possible.",browsers:"All",init:function(e,t){function f(e){u={}}function l(e){var r,i,s,o;for(r in a)if(a.hasOwnProperty(r)){o=0;for(i=0,s=a[r].length;i<s;i++)o+=u[a[r][i]]?1:0;o==a[r].length&&t.report("The properties "+a[r].join(", ")+" can be replaced by "+r+".",e.line,e.col,n)}}var n=this,r,i,s,o={},u,a={margin:["margin-top","margin-bottom","margin-left","margin-right"],padding:["padding-top","padding-bottom","padding-left","padding-right"]};for(r in a)if(a.hasOwnProperty(r))for(i=0,s=a[r].length;i<s;i++)o[a[r][i]]=r;e.addListener("startrule",f),e.addListener("startfontface",f),e.addListener("property",function(e){var t=e.property.toString().toLowerCase(),n=e.value.parts[0].value;o[t]&&(u[t]=1)}),e.addListener("endrule",l),e.addListener("endfontface",l)}}),CSSLint.addRule({id:"star-property-hack",name:"Disallow properties with a star prefix",desc:"Checks for the star property hack (targets IE6/7)",browsers:"All",init:function(e,t){var n=this;e.addListener("property",function(e){var r=e.property;r.hack=="*"&&t.report("Property with star prefix found.",e.property.line,e.property.col,n)})}}),CSSLint.addRule({id:"text-indent",name:"Disallow negative text-indent",desc:"Checks for text indent less than -99px",browsers:"All",init:function(e,t){function s(e){r=!1,i="inherit"}function o(e){r&&i!="ltr"&&t.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.",r.line,r.col,n)}var n=this,r,i;e.addListener("startrule",s),e.addListener("startfontface",s),e.addListener("property",function(e){var t=e.property.toString().toLowerCase(),n=e.value;t=="text-indent"&&n.parts[0].value<-99?r=e.property:t=="direction"&&n=="ltr"&&(i="ltr")}),e.addListener("endrule",o),e.addListener("endfontface",o)}}),CSSLint.addRule({id:"underscore-property-hack",name:"Disallow properties with an underscore prefix",desc:"Checks for the underscore property hack (targets IE6)",browsers:"All",init:function(e,t){var n=this;e.addListener("property",function(e){var r=e.property;r.hack=="_"&&t.report("Property with underscore prefix found.",e.property.line,e.property.col,n)})}}),CSSLint.addRule({id:"unique-headings",name:"Headings should only be defined once",desc:"Headings should be defined only once.",browsers:"All",init:function(e,t){var n=this,r={h1:0,h2:0,h3:0,h4:0,h5:0,h6:0};e.addListener("startrule",function(e){var i=e.selectors,s,o,u,a,f;for(a=0;a<i.length;a++){s=i[a],o=s.parts[s.parts.length-1];if(o.elementName&&/(h[1-6])/i.test(o.elementName.toString())){for(f=0;f<o.modifiers.length;f++)if(o.modifiers[f].type=="pseudo"){u=!0;break}u||(r[RegExp.$1]++,r[RegExp.$1]>1&&t.report("Heading ("+o.elementName+") has already been defined.",o.line,o.col,n))}}}),e.addListener("endstylesheet",function(e){var i,s=[];for(i in r)r.hasOwnProperty(i)&&r[i]>1&&s.push(r[i]+" "+i+"s");s.length&&t.rollupWarn("You have "+s.join(", ")+" defined in this stylesheet.",n)})}}),CSSLint.addRule({id:"universal-selector",name:"Disallow universal selector",desc:"The universal selector (*) is known to be slow.",browsers:"All",init:function(e,t){var n=this;e.addListener("startrule",function(e){var r=e.selectors,i,s,o,u,a,f;for(u=0;u<r.length;u++)i=r[u],s=i.parts[i.parts.length-1],s.elementName=="*"&&t.report(n.desc,s.line,s.col,n)})}}),CSSLint.addRule({id:"unqualified-attributes",name:"Disallow unqualified attribute selectors",desc:"Unqualified attribute selectors are known to be slow.",browsers:"All",init:function(e,t){var n=this;e.addListener("startrule",function(r){var i=r.selectors,s,o,u,a,f,l;for(a=0;a<i.length;a++){s=i[a],o=s.parts[s.parts.length-1];if(o.type==e.SELECTOR_PART_TYPE)for(l=0;l<o.modifiers.length;l++)u=o.modifiers[l],u.type=="attribute"&&(!o.elementName||o.elementName=="*")&&t.report(n.desc,o.line,o.col,n)}})}}),CSSLint.addRule({id:"vendor-prefix",name:"Require standard property with vendor prefix",desc:"When using a vendor-prefixed property, make sure to include the standard one.",browsers:"All",init:function(e,t){function o(){r={},i=1}function u(e){var i,o,u,a,f,l,c=[];for(i in r)s[i]&&c.push({actual:i,needed:s[i]});for(o=0,u=c.length;o<u;o++)f=c[o].needed,l=c[o].actual,r[f]?r[f][0].pos<r[l][0].pos&&t.report("Standard property '"+f+"' should come after vendor-prefixed property '"+l+"'.",r[l][0].name.line,r[l][0].name.col,n):t.report("Missing standard property '"+f+"' to go along with '"+l+"'.",r[l][0].name.line,r[l][0].name.col,n)}var n=this,r,i,s={"-webkit-border-radius":"border-radius","-webkit-border-top-left-radius":"border-top-left-radius","-webkit-border-top-right-radius":"border-top-right-radius","-webkit-border-bottom-left-radius":"border-bottom-left-radius","-webkit-border-bottom-right-radius":"border-bottom-right-radius","-o-border-radius":"border-radius","-o-border-top-left-radius":"border-top-left-radius","-o-border-top-right-radius":"border-top-right-radius","-o-border-bottom-left-radius":"border-bottom-left-radius","-o-border-bottom-right-radius":"border-bottom-right-radius","-moz-border-radius":"border-radius","-moz-border-radius-topleft":"border-top-left-radius","-moz-border-radius-topright":"border-top-right-radius","-moz-border-radius-bottomleft":"border-bottom-left-radius","-moz-border-radius-bottomright":"border-bottom-right-radius","-moz-column-count":"column-count","-webkit-column-count":"column-count","-moz-column-gap":"column-gap","-webkit-column-gap":"column-gap","-moz-column-rule":"column-rule","-webkit-column-rule":"column-rule","-moz-column-rule-style":"column-rule-style","-webkit-column-rule-style":"column-rule-style","-moz-column-rule-color":"column-rule-color","-webkit-column-rule-color":"column-rule-color","-moz-column-rule-width":"column-rule-width","-webkit-column-rule-width":"column-rule-width","-moz-column-width":"column-width","-webkit-column-width":"column-width","-webkit-column-span":"column-span","-webkit-columns":"columns","-moz-box-shadow":"box-shadow","-webkit-box-shadow":"box-shadow","-moz-transform":"transform","-webkit-transform":"transform","-o-transform":"transform","-ms-transform":"transform","-moz-transform-origin":"transform-origin","-webkit-transform-origin":"transform-origin","-o-transform-origin":"transform-origin","-ms-transform-origin":"transform-origin","-moz-box-sizing":"box-sizing","-webkit-box-sizing":"box-sizing","-moz-user-select":"user-select","-khtml-user-select":"user-select","-webkit-user-select":"user-select"};e.addListener("startrule",o),e.addListener("startfontface",o),e.addListener("startpage",o),e.addListener("startpagemargin",o),e.addListener("startkeyframerule",o),e.addListener("property",function(e){var t=e.property.text.toLowerCase();r[t]||(r[t]=[]),r[t].push({name:e.property,value:e.value,pos:i++})}),e.addListener("endrule",u),e.addListener("endfontface",u),e.addListener("endpage",u),e.addListener("endpagemargin",u),e.addListener("endkeyframerule",u)}}),CSSLint.addRule({id:"zero-units",name:"Disallow units for 0 values",desc:"You don't need to specify units when a value is 0.",browsers:"All",init:function(e,t){var n=this;e.addListener("property",function(e){var r=e.value.parts,i=0,s=r.length;while(i<s)(r[i].units||r[i].type=="percentage")&&r[i].value===0&&r[i].type!="time"&&t.report("Values of 0 shouldn't have units specified.",r[i].line,r[i].col,n),i++})}}),exports.CSSLint=CSSLint})
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/safecss-ace.js b/plugins/jetpack/modules/custom-css/custom-css/js/safecss-ace.js new file mode 100644 index 00000000..65b01d6e --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/js/safecss-ace.js @@ -0,0 +1,69 @@ +(function(global, $){ + // shared scope insied IIFE in case it's needed. + var editor; + var syncCSS = function() { + $("#safecss").val( editor.getSession().getValue() ); + }; + var loadAce = function() { + // Set up ACE editor + ace.config.set( 'modePath', safecssAceSrcPath ); + ace.config.set( 'workerPath', safecssAceSrcPath ); + ace.config.set( 'themePath', safecssAceSrcPath ); + + editor = ace.edit( 'safecss-ace' ); + // Globalize it so we can access it other places + global.safecss_editor = editor; + // Word-wrap, othewise the initial comments are borked. + editor.getSession().setUseWrapMode(true); + // This adds an annoying vertical line to the editor; get rid of it. + editor.setShowPrintMargin( false ); + // Grab straight from the textarea + editor.getSession().setValue( $("#safecss").val() ); + // We're editing CSS content + var CSSMode = ace.require( 'ace/mode/css' ).Mode; + editor.getSession().setMode( new CSSMode() ); + // ace.js comes with the textmate coloring scheme already. + // kill the spinner + jQuery.fn.spin && $("#safecss-container").spin( false ); + /* + // TODO: Add shortcuts for save and preview + editor.commands.addCommand({ + name: 'cssPreview', + bindKey: { + win: 'Ctrl-P', + mac: 'Command-P', + sender: 'editor' + }, + exec: function( env, args, request ) { + safecss_update_content(); + jQuery( '#preview' ).click(); // this doesn't work :( + } + } ); + */ + + // When submitting, make sure to include the updated CSS + // The Ace editor unfortunately doesn't handle this for us + $( '#safecssform' ).submit(syncCSS); + } + + // exit if we're on IE <= 7 + if ( ( $.browser.msie && parseInt( $.browser.version, 10 ) <= 7 ) || navigator.userAgent.match(/iPad/i) != null ) { + $("#safecss-container").hide(); + $("#safecss").removeClass('hide-if-js'); + return false; + } + // syntaxy goodness. + else { + $( '#safecss-ace, #safecss-container' ).css( 'height', + Math.max( 250, $( window ).height() - $( '#safecss-container' ).offset().top - $( '#wpadminbar' ).height() ) + ); + + $(global).load(loadAce); + } + + // for now, expose the syncCSS function. + global.aceSyncCSS = syncCSS; + +})(this, jQuery); + + diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php new file mode 100644 index 00000000..eef6ab43 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php @@ -0,0 +1,57 @@ +<?php + +/** + * CSS preprocessor registration. + * + * To add a new preprocessor (or replace an existing one), hook into the + * jetpack_custom_css_preprocessors filter and add an entry to the array + * that is passed in. + * + * Format is: + * $preprocessors[ UNIQUE_KEY ] => array( 'name' => 'Processor name', 'callback' => [processing function] ); + * + * The callback function accepts a single string argument (non-CSS markup) and returns a string (CSS). + * + * @param array $preprocessors The list of preprocessors added thus far. + * @return array + */ + +function jetpack_register_css_preprocessors( $preprocessors ) { + $preprocessors['less'] = array( + 'name' => 'LESS', + 'callback' => 'jetpack_less_css_preprocess' + ); + + $preprocessors['sass'] = array( + 'name' => 'Sass (SCSS Syntax)', + 'callback' => 'jetpack_sass_css_preprocess' + ); + + return $preprocessors; +} + +add_filter( 'jetpack_custom_css_preprocessors', 'jetpack_register_css_preprocessors' ); + +function jetpack_less_css_preprocess( $less ) { + require( dirname( __FILE__ ) . '/preprocessors/lessc.inc.php' ); + + $compiler = new lessc(); + + try { + return $compiler->compile( $less ); + } catch ( Exception $e ) { + return $less; + } +} + +function jetpack_sass_css_preprocess( $sass ) { + require_once( dirname( __FILE__ ) . '/preprocessors/scss.inc.php' ); + + $compiler = new scssc(); + + try { + return $compiler->compile( $sass ); + } catch ( Exception $e ) { + return $sass; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php new file mode 100644 index 00000000..c42147c5 --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php @@ -0,0 +1,3359 @@ +<?php + +/** + * lessphp v0.3.8 + * http://leafo.net/lessphp + * + * LESS css compiler, adapted from http://lesscss.org + * + * Copyright 2012, Leaf Corcoran <leafot@gmail.com> + * Licensed under MIT or GPLv3, see LICENSE + */ + + +/** + * The less compiler and parser. + * + * Converting LESS to CSS is a three stage process. The incoming file is parsed + * by `lessc_parser` into a syntax tree, then it is compiled into another tree + * representing the CSS structure by `lessc`. The CSS tree is fed into a + * formatter, like `lessc_formatter` which then outputs CSS as a string. + * + * During the first compile, all values are *reduced*, which means that their + * types are brought to the lowest form before being dump as strings. This + * handles math equations, variable dereferences, and the like. + * + * The `parse` function of `lessc` is the entry point. + * + * In summary: + * + * The `lessc` class creates an intstance of the parser, feeds it LESS code, + * then transforms the resulting tree to a CSS tree. This class also holds the + * evaluation context, such as all available mixins and variables at any given + * time. + * + * The `lessc_parser` class is only concerned with parsing its input. + * + * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string, + * handling things like indentation. + */ +class lessc { + static public $VERSION = "v0.3.8"; + static protected $TRUE = array("keyword", "true"); + static protected $FALSE = array("keyword", "false"); + + protected $libFunctions = array(); + protected $registeredVars = array(); + protected $preserveComments = false; + + public $vPrefix = '@'; // prefix of abstract properties + public $mPrefix = '$'; // prefix of abstract blocks + public $parentSelector = '&'; + + public $importDisabled = false; + public $importDir = ''; + + protected $numberPrecision = null; + + // set to the parser that generated the current line when compiling + // so we know how to create error messages + protected $sourceParser = null; + protected $sourceLoc = null; + + static public $defaultValue = array("keyword", ""); + + static protected $nextImportId = 0; // uniquely identify imports + + // attempts to find the path of an import url, returns null for css files + protected function findImport($url) { + foreach ((array)$this->importDir as $dir) { + $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; + if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { + return $file; + } + } + + return null; + } + + protected function fileExists($name) { + return is_file($name); + } + + static public function compressList($items, $delim) { + if (!isset($items[1]) && isset($items[0])) return $items[0]; + else return array('list', $delim, $items); + } + + static public function preg_quote($what) { + return preg_quote($what, '/'); + } + + protected function tryImport($importPath, $parentBlock, $out) { + if ($importPath[0] == "function" && $importPath[1] == "url") { + $importPath = $this->flattenList($importPath[2]); + } + + $str = $this->coerceString($importPath); + if ($str === null) return false; + + $url = $this->compileValue($this->lib_e($str)); + + // don't import if it ends in css + if (substr_compare($url, '.css', -4, 4) === 0) return false; + + $realPath = $this->findImport($url); + if ($realPath === null) return false; + + if ($this->importDisabled) { + return array(false, "/* import disabled */"); + } + + $this->addParsedFile($realPath); + $parser = $this->makeParser($realPath); + $root = $parser->parse(file_get_contents($realPath)); + + // set the parents of all the block props + foreach ($root->props as $prop) { + if ($prop[0] == "block") { + $prop[1]->parent = $parentBlock; + } + } + + // copy mixins into scope, set their parents + // bring blocks from import into current block + // TODO: need to mark the source parser these came from this file + foreach ($root->children as $childName => $child) { + if (isset($parentBlock->children[$childName])) { + $parentBlock->children[$childName] = array_merge( + $parentBlock->children[$childName], + $child); + } else { + $parentBlock->children[$childName] = $child; + } + } + + $pi = pathinfo($realPath); + $dir = $pi["dirname"]; + + list($top, $bottom) = $this->sortProps($root->props, true); + $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); + + return array(true, $bottom, $parser, $dir); + } + + protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { + $oldSourceParser = $this->sourceParser; + + $oldImport = $this->importDir; + + // TODO: this is because the importDir api is stupid + $this->importDir = (array)$this->importDir; + array_unshift($this->importDir, $importDir); + + foreach ($props as $prop) { + $this->compileProp($prop, $block, $out); + } + + $this->importDir = $oldImport; + $this->sourceParser = $oldSourceParser; + } + + /** + * Recursively compiles a block. + * + * A block is analogous to a CSS block in most cases. A single LESS document + * is encapsulated in a block when parsed, but it does not have parent tags + * so all of it's children appear on the root level when compiled. + * + * Blocks are made up of props and children. + * + * Props are property instructions, array tuples which describe an action + * to be taken, eg. write a property, set a variable, mixin a block. + * + * The children of a block are just all the blocks that are defined within. + * This is used to look up mixins when performing a mixin. + * + * Compiling the block involves pushing a fresh environment on the stack, + * and iterating through the props, compiling each one. + * + * See lessc::compileProp() + * + */ + protected function compileBlock($block) { + switch ($block->type) { + case "root": + $this->compileRoot($block); + break; + case null: + $this->compileCSSBlock($block); + break; + case "media": + $this->compileMedia($block); + break; + case "directive": + $name = "@" . $block->name; + if (!empty($block->value)) { + $name .= " " . $this->compileValue($this->reduce($block->value)); + } + + $this->compileNestedBlock($block, array($name)); + break; + default: + $this->throwError("unknown block type: $block->type\n"); + } + } + + protected function compileCSSBlock($block) { + $env = $this->pushEnv(); + + $selectors = $this->compileSelectors($block->tags); + $env->selectors = $this->multiplySelectors($selectors); + $out = $this->makeOutputBlock(null, $env->selectors); + + $this->scope->children[] = $out; + $this->compileProps($block, $out); + + $block->scope = $env; // mixins carry scope with them! + $this->popEnv(); + } + + protected function compileMedia($media) { + $env = $this->pushEnv($media); + $parentScope = $this->mediaParent($this->scope); + + $query = $this->compileMediaQuery($this->multiplyMedia($env)); + + $this->scope = $this->makeOutputBlock($media->type, array($query)); + $parentScope->children[] = $this->scope; + + $this->compileProps($media, $this->scope); + + if (count($this->scope->lines) > 0) { + $orphanSelelectors = $this->findClosestSelectors(); + if (!is_null($orphanSelelectors)) { + $orphan = $this->makeOutputBlock(null, $orphanSelelectors); + $orphan->lines = $this->scope->lines; + array_unshift($this->scope->children, $orphan); + $this->scope->lines = array(); + } + } + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function mediaParent($scope) { + while (!empty($scope->parent)) { + if (!empty($scope->type) && $scope->type != "media") { + break; + } + $scope = $scope->parent; + } + + return $scope; + } + + protected function compileNestedBlock($block, $selectors) { + $this->pushEnv($block); + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + + $this->compileProps($block, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function compileRoot($root) { + $this->pushEnv(); + $this->scope = $this->makeOutputBlock($root->type); + $this->compileProps($root, $this->scope); + $this->popEnv(); + } + + protected function compileProps($block, $out) { + foreach ($this->sortProps($block->props) as $prop) { + $this->compileProp($prop, $block, $out); + } + } + + protected function sortProps($props, $split = false) { + $vars = array(); + $imports = array(); + $other = array(); + + foreach ($props as $prop) { + switch ($prop[0]) { + case "assign": + if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { + $vars[] = $prop; + } else { + $other[] = $prop; + } + break; + case "import": + $id = self::$nextImportId++; + $prop[] = $id; + $imports[] = $prop; + $other[] = array("import_mixin", $id); + break; + default: + $other[] = $prop; + } + } + + if ($split) { + return array(array_merge($vars, $imports), $other); + } else { + return array_merge($vars, $imports, $other); + } + } + + protected function compileMediaQuery($queries) { + $compiledQueries = array(); + foreach ($queries as $query) { + $parts = array(); + foreach ($query as $q) { + switch ($q[0]) { + case "mediaType": + $parts[] = implode(" ", array_slice($q, 1)); + break; + case "mediaExp": + if (isset($q[2])) { + $parts[] = "($q[1]: " . + $this->compileValue($this->reduce($q[2])) . ")"; + } else { + $parts[] = "($q[1])"; + } + break; + } + } + + if (count($parts) > 0) { + $compiledQueries[] = implode(" and ", $parts); + } + } + + $out = "@media"; + if (!empty($parts)) { + $out .= " " . + implode($this->formatter->selectorSeparator, $compiledQueries); + } + return $out; + } + + protected function multiplyMedia($env, $childQueries = null) { + if (is_null($env) || + !empty($env->block->type) && $env->block->type != "media") + { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $out = array(); + $queries = $env->block->queries; + if (is_null($childQueries)) { + $out = $queries; + } else { + foreach ($queries as $parent) { + foreach ($childQueries as $child) { + $out[] = array_merge($parent, $child); + } + } + } + + return $this->multiplyMedia($env->parent, $out); + } + + protected function expandParentSelectors(&$tag, $replace) { + $parts = explode("$&$", $tag); + $count = 0; + foreach ($parts as &$part) { + $part = str_replace($this->parentSelector, $replace, $part, $c); + $count += $c; + } + $tag = implode($this->parentSelector, $parts); + return $count; + } + + protected function findClosestSelectors() { + $env = $this->env; + $selectors = null; + while ($env !== null) { + if (isset($env->selectors)) { + $selectors = $env->selectors; + break; + } + $env = $env->parent; + } + + return $selectors; + } + + + // multiply $selectors against the nearest selectors in env + protected function multiplySelectors($selectors) { + // find parent selectors + + $parentSelectors = $this->findClosestSelectors(); + if (is_null($parentSelectors)) { + // kill parent reference in top level selector + foreach ($selectors as &$s) { + $this->expandParentSelectors($s, ""); + } + + return $selectors; + } + + $out = array(); + foreach ($parentSelectors as $parent) { + foreach ($selectors as $child) { + $count = $this->expandParentSelectors($child, $parent); + + // don't prepend the parent tag if & was used + if ($count > 0) { + $out[] = trim($child); + } else { + $out[] = trim($parent . ' ' . $child); + } + } + } + + return $out; + } + + // reduces selector expressions + protected function compileSelectors($selectors) { + $out = array(); + + foreach ($selectors as $s) { + if (is_array($s)) { + list(, $value) = $s; + $out[] = $this->compileValue($this->reduce($value)); + } else { + $out[] = $s; + } + } + + return $out; + } + + protected function eq($left, $right) { + return $left == $right; + } + + protected function patternMatch($block, $callingArgs) { + // match the guards if it has them + // any one of the groups must have all its guards pass for a match + if (!empty($block->guards)) { + $groupPassed = false; + foreach ($block->guards as $guardGroup) { + foreach ($guardGroup as $guard) { + $this->pushEnv(); + $this->zipSetArgs($block->args, $callingArgs); + + $negate = false; + if ($guard[0] == "negate") { + $guard = $guard[1]; + $negate = true; + } + + $passed = $this->reduce($guard) == self::$TRUE; + if ($negate) $passed = !$passed; + + $this->popEnv(); + + if ($passed) { + $groupPassed = true; + } else { + $groupPassed = false; + break; + } + } + + if ($groupPassed) break; + } + + if (!$groupPassed) { + return false; + } + } + + $numCalling = count($callingArgs); + + if (empty($block->args)) { + return $block->isVararg || $numCalling == 0; + } + + $i = -1; // no args + // try to match by arity or by argument literal + foreach ($block->args as $i => $arg) { + switch ($arg[0]) { + case "lit": + if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) { + return false; + } + break; + case "arg": + // no arg and no default value + if (!isset($callingArgs[$i]) && !isset($arg[2])) { + return false; + } + break; + case "rest": + $i--; // rest can be empty + break 2; + } + } + + if ($block->isVararg) { + return true; // not having enough is handled above + } else { + $numMatched = $i + 1; + // greater than becuase default values always match + return $numMatched >= $numCalling; + } + } + + protected function patternMatchAll($blocks, $callingArgs) { + $matches = null; + foreach ($blocks as $block) { + if ($this->patternMatch($block, $callingArgs)) { + $matches[] = $block; + } + } + + return $matches; + } + + // attempt to find blocks matched by path and args + protected function findBlocks($searchIn, $path, $args, $seen=array()) { + if ($searchIn == null) return null; + if (isset($seen[$searchIn->id])) return null; + $seen[$searchIn->id] = true; + + $name = $path[0]; + + if (isset($searchIn->children[$name])) { + $blocks = $searchIn->children[$name]; + if (count($path) == 1) { + $matches = $this->patternMatchAll($blocks, $args); + if (!empty($matches)) { + // This will return all blocks that match in the closest + // scope that has any matching block, like lessjs + return $matches; + } + } else { + $matches = array(); + foreach ($blocks as $subBlock) { + $subMatches = $this->findBlocks($subBlock, + array_slice($path, 1), $args, $seen); + + if (!is_null($subMatches)) { + foreach ($subMatches as $sm) { + $matches[] = $sm; + } + } + } + + return count($matches) > 0 ? $matches : null; + } + } + + if ($searchIn->parent === $searchIn) return null; + return $this->findBlocks($searchIn->parent, $path, $args, $seen); + } + + // sets all argument names in $args to either the default value + // or the one passed in through $values + protected function zipSetArgs($args, $values) { + $i = 0; + $assignedValues = array(); + foreach ($args as $a) { + if ($a[0] == "arg") { + if ($i < count($values) && !is_null($values[$i])) { + $value = $values[$i]; + } elseif (isset($a[2])) { + $value = $a[2]; + } else $value = null; + + $value = $this->reduce($value); + $this->set($a[1], $value); + $assignedValues[] = $value; + } + $i++; + } + + // check for a rest + $last = end($args); + if ($last[0] == "rest") { + $rest = array_slice($values, count($args) - 1); + $this->set($last[1], $this->reduce(array("list", " ", $rest))); + } + + $this->env->arguments = $assignedValues; + } + + // compile a prop and update $lines or $blocks appropriately + protected function compileProp($prop, $block, $out) { + // set error position context + $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; + + switch ($prop[0]) { + case 'assign': + list(, $name, $value) = $prop; + if ($name[0] == $this->vPrefix) { + $this->set($name, $value); + } else { + $out->lines[] = $this->formatter->property($name, + $this->compileValue($this->reduce($value))); + } + break; + case 'block': + list(, $child) = $prop; + $this->compileBlock($child); + break; + case 'mixin': + list(, $path, $args, $suffix) = $prop; + + $args = array_map(array($this, "reduce"), (array)$args); + $mixins = $this->findBlocks($block, $path, $args); + + if ($mixins === null) { + // fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n"); + break; // throw error here?? + } + + foreach ($mixins as $mixin) { + $haveScope = false; + if (isset($mixin->parent->scope)) { + $haveScope = true; + $mixinParentEnv = $this->pushEnv(); + $mixinParentEnv->storeParent = $mixin->parent->scope; + } + + $haveArgs = false; + if (isset($mixin->args)) { + $haveArgs = true; + $this->pushEnv(); + $this->zipSetArgs($mixin->args, $args); + } + + $oldParent = $mixin->parent; + if ($mixin != $block) $mixin->parent = $block; + + foreach ($this->sortProps($mixin->props) as $subProp) { + if ($suffix !== null && + $subProp[0] == "assign" && + is_string($subProp[1]) && + $subProp[1]{0} != $this->vPrefix) + { + $subProp[2] = array( + 'list', ' ', + array($subProp[2], array('keyword', $suffix)) + ); + } + + $this->compileProp($subProp, $mixin, $out); + } + + $mixin->parent = $oldParent; + + if ($haveArgs) $this->popEnv(); + if ($haveScope) $this->popEnv(); + } + + break; + case 'raw': + $out->lines[] = $prop[1]; + break; + case "directive": + list(, $name, $value) = $prop; + $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';'; + break; + case "comment": + $out->lines[] = $prop[1]; + break; + case "import"; + list(, $importPath, $importId) = $prop; + $importPath = $this->reduce($importPath); + + if (!isset($this->env->imports)) { + $this->env->imports = array(); + } + + $result = $this->tryImport($importPath, $block, $out); + + $this->env->imports[$importId] = $result === false ? + array(false, "@import " . $this->compileValue($importPath).";") : + $result; + + break; + case "import_mixin": + list(,$importId) = $prop; + $import = $this->env->imports[$importId]; + if ($import[0] === false) { + $out->lines[] = $import[1]; + } else { + list(, $bottom, $parser, $importDir) = $import; + $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); + } + + break; + default: + $this->throwError("unknown op: {$prop[0]}\n"); + } + } + + + /** + * Compiles a primitive value into a CSS property value. + * + * Values in lessphp are typed by being wrapped in arrays, their format is + * typically: + * + * array(type, contents [, additional_contents]*) + * + * The input is expected to be reduced. This function will not work on + * things like expressions and variables. + */ + protected function compileValue($value) { + switch ($value[0]) { + case 'list': + // [1] - delimiter + // [2] - array of values + return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); + case 'raw_color': + if (!empty($this->formatter->compressColors)) { + return $this->compileValue($this->coerceColor($value)); + } + return $value[1]; + case 'keyword': + // [1] - the keyword + return $value[1]; + case 'number': + list(, $num, $unit) = $value; + // [1] - the number + // [2] - the unit + if ($this->numberPrecision !== null) { + $num = round($num, $this->numberPrecision); + } + return $num . $unit; + case 'string': + // [1] - contents of string (includes quotes) + list(, $delim, $content) = $value; + foreach ($content as &$part) { + if (is_array($part)) { + $part = $this->compileValue($part); + } + } + return $delim . implode($content) . $delim; + case 'color': + // [1] - red component (either number or a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + $r = round($r); + $g = round($g); + $b = round($b); + + if (count($value) == 5 && $value[4] != 1) { // rgba + return 'rgba('.$r.','.$g.','.$b.','.$value[4].')'; + } + + $h = sprintf("#%02x%02x%02x", $r, $g, $b); + + if (!empty($this->formatter->compressColors)) { + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + } + + return $h; + + case 'function': + list(, $name, $args) = $value; + return $name.'('.$this->compileValue($args).')'; + default: // assumed to be unit + $this->throwError("unknown value type: $value[0]"); + } + } + + protected function lib_isnumber($value) { + return $this->toBool($value[0] == "number"); + } + + protected function lib_isstring($value) { + return $this->toBool($value[0] == "string"); + } + + protected function lib_iscolor($value) { + return $this->toBool($this->coerceColor($value)); + } + + protected function lib_iskeyword($value) { + return $this->toBool($value[0] == "keyword"); + } + + protected function lib_ispixel($value) { + return $this->toBool($value[0] == "number" && $value[2] == "px"); + } + + protected function lib_ispercentage($value) { + return $this->toBool($value[0] == "number" && $value[2] == "%"); + } + + protected function lib_isem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "em"); + } + + protected function lib_rgbahex($color) { + $color = $this->coerceColor($color); + if (is_null($color)) + $this->throwError("color expected for rgbahex"); + + return sprintf("#%02x%02x%02x%02x", + isset($color[4]) ? $color[4]*255 : 255, + $color[1],$color[2], $color[3]); + } + + protected function lib_argb($color){ + return $this->lib_rgbahex($color); + } + + // utility func to unquote a string + protected function lib_e($arg) { + switch ($arg[0]) { + case "list": + $items = $arg[2]; + if (isset($items[0])) { + return $this->lib_e($items[0]); + } + return self::$defaultValue; + case "string": + $arg[1] = ""; + return $arg; + case "keyword": + return $arg; + default: + return array("keyword", $this->compileValue($arg)); + } + } + + protected function lib__sprintf($args) { + if ($args[0] != "list") return $args; + $values = $args[2]; + $string = array_shift($values); + $template = $this->compileValue($this->lib_e($string)); + + $i = 0; + if (preg_match_all('/%[dsa]/', $template, $m)) { + foreach ($m[0] as $match) { + $val = isset($values[$i]) ? + $this->reduce($values[$i]) : array('keyword', ''); + + // lessjs compat, renders fully expanded color, not raw color + if ($color = $this->coerceColor($val)) { + $val = $color; + } + + $i++; + $rep = $this->compileValue($this->lib_e($val)); + $template = preg_replace('/'.self::preg_quote($match).'/', + $rep, $template, 1); + } + } + + $d = $string[0] == "string" ? $string[1] : '"'; + return array("string", $d, array($template)); + } + + protected function lib_floor($arg) { + $value = $this->assertNumber($arg); + return array("number", floor($value), $arg[2]); + } + + protected function lib_ceil($arg) { + $value = $this->assertNumber($arg); + return array("number", ceil($value), $arg[2]); + } + + protected function lib_round($arg) { + $value = $this->assertNumber($arg); + return array("number", round($value), $arg[2]); + } + + /** + * Helper function to get arguments for color manipulation functions. + * takes a list that contains a color like thing and a percentage + */ + protected function colorArgs($args) { + if ($args[0] != 'list' || count($args[2]) < 2) { + return array(array('color', 0, 0, 0), 0); + } + list($color, $delta) = $args[2]; + $color = $this->assertColor($color); + $delta = floatval($delta[1]); + + return array($color, $delta); + } + + protected function lib_darken($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_lighten($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_saturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_desaturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_spin($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + + $hsl[1] = $hsl[1] + $delta % 360; + if ($hsl[1] < 0) $hsl[1] += 360; + + return $this->toRGB($hsl); + } + + protected function lib_fadeout($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); + return $color; + } + + protected function lib_fadein($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); + return $color; + } + + protected function lib_hue($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[1]); + } + + protected function lib_saturation($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[2]); + } + + protected function lib_lightness($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[3]); + } + + // get the alpha of a color + // defaults to 1 for non-colors or colors without an alpha + protected function lib_alpha($value) { + if (!is_null($color = $this->coerceColor($value))) { + return isset($color[4]) ? $color[4] : 1; + } + } + + // set the alpha of the color + protected function lib_fade($args) { + list($color, $alpha) = $this->colorArgs($args); + $color[4] = $this->clamp($alpha / 100.0); + return $color; + } + + protected function lib_percentage($arg) { + $num = $this->assertNumber($arg); + return array("number", $num*100, "%"); + } + + // mixes two colors by weight + // mix(@color1, @color2, @weight); + // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method + protected function lib_mix($args) { + if ($args[0] != "list" || count($args[2]) < 3) + $this->throwError("mix expects (color1, color2, weight)"); + + list($first, $second, $weight) = $args[2]; + $first = $this->assertColor($first); + $second = $this->assertColor($second); + + $first_a = $this->lib_alpha($first); + $second_a = $this->lib_alpha($second); + $weight = $weight[1] / 100.0; + + $w = $weight * 2 - 1; + $a = $first_a - $second_a; + + $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; + $w2 = 1.0 - $w1; + + $new = array('color', + $w1 * $first[1] + $w2 * $second[1], + $w1 * $first[2] + $w2 * $second[2], + $w1 * $first[3] + $w2 * $second[3], + ); + + if ($first_a != 1.0 || $second_a != 1.0) { + $new[] = $first_a * $weight + $second_a * ($weight - 1); + } + + return $this->fixColor($new); + } + + protected function assertColor($value, $error = "expected color value") { + $color = $this->coerceColor($value); + if (is_null($color)) $this->throwError($error); + return $color; + } + + protected function assertNumber($value, $error = "expecting number") { + if ($value[0] == "number") return $value[1]; + $this->throwError($error); + } + + protected function toHSL($color) { + if ($color[0] == 'hsl') return $color; + + $r = $color[1] / 255; + $g = $color[2] / 255; + $b = $color[3] / 255; + + $min = min($r, $g, $b); + $max = max($r, $g, $b); + + $L = ($min + $max) / 2; + if ($min == $max) { + $S = $H = 0; + } else { + if ($L < 0.5) + $S = ($max - $min)/($max + $min); + else + $S = ($max - $min)/(2.0 - $max - $min); + + if ($r == $max) $H = ($g - $b)/($max - $min); + elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); + elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); + + } + + $out = array('hsl', + ($H < 0 ? $H + 6 : $H)*60, + $S*100, + $L*100, + ); + + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function toRGB_helper($comp, $temp1, $temp2) { + if ($comp < 0) $comp += 1.0; + elseif ($comp > 1) $comp -= 1.0; + + if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; + if (2 * $comp < 1) return $temp2; + if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; + + return $temp1; + } + + /** + * Converts a hsl array into a color value in rgb. + * Expects H to be in range of 0 to 360, S and L in 0 to 100 + */ + protected function toRGB($color) { + if ($color == 'color') return $color; + + $H = $color[1] / 360; + $S = $color[2] / 100; + $L = $color[3] / 100; + + if ($S == 0) { + $r = $g = $b = $L; + } else { + $temp2 = $L < 0.5 ? + $L*(1.0 + $S) : + $L + $S - $L * $S; + + $temp1 = 2.0 * $L - $temp2; + + $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); + $g = $this->toRGB_helper($H, $temp1, $temp2); + $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); + } + + // $out = array('color', round($r*255), round($g*255), round($b*255)); + $out = array('color', $r*255, $g*255, $b*255); + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function clamp($v, $max = 1, $min = 0) { + return min($max, max($min, $v)); + } + + /** + * Convert the rgb, rgba, hsl color literals of function type + * as returned by the parser into values of color type. + */ + protected function funcToColor($func) { + $fname = $func[1]; + if ($func[2][0] != 'list') return false; // need a list of arguments + $rawComponents = $func[2][2]; + + if ($fname == 'hsl' || $fname == 'hsla') { + $hsl = array('hsl'); + $i = 0; + foreach ($rawComponents as $c) { + $val = $this->reduce($c); + $val = isset($val[1]) ? floatval($val[1]) : 0; + + if ($i == 0) $clamp = 360; + elseif ($i < 3) $clamp = 100; + else $clamp = 1; + + $hsl[] = $this->clamp($val, $clamp); + $i++; + } + + while (count($hsl) < 4) $hsl[] = 0; + return $this->toRGB($hsl); + + } elseif ($fname == 'rgb' || $fname == 'rgba') { + $components = array(); + $i = 1; + foreach ($rawComponents as $c) { + $c = $this->reduce($c); + if ($i < 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 255 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } elseif ($i == 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 1.0 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } else break; + + $i++; + } + while (count($components) < 3) $components[] = 0; + array_unshift($components, 'color'); + return $this->fixColor($components); + } + + return false; + } + + protected function reduce($value, $forExpression = false) { + switch ($value[0]) { + case "variable": + $key = $value[1]; + if (is_array($key)) { + $key = $this->reduce($key); + $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); + } + + $seen =& $this->env->seenNames; + + if (!empty($seen[$key])) { + $this->throwError("infinite loop detected: $key"); + } + + $seen[$key] = true; + $out = $this->reduce($this->get($key, self::$defaultValue)); + $seen[$key] = false; + return $out; + case "list": + foreach ($value[2] as &$item) { + $item = $this->reduce($item, $forExpression); + } + return $value; + case "expression": + return $this->evaluate($value); + case "string": + foreach ($value[2] as &$part) { + if (is_array($part)) { + $strip = $part[0] == "variable"; + $part = $this->reduce($part); + if ($strip) $part = $this->lib_e($part); + } + } + return $value; + case "escape": + list(,$inner) = $value; + return $this->lib_e($this->reduce($inner)); + case "function": + $color = $this->funcToColor($value); + if ($color) return $color; + + list(, $name, $args) = $value; + if ($name == "%") $name = "_sprintf"; + $f = isset($this->libFunctions[$name]) ? + $this->libFunctions[$name] : array($this, 'lib_'.$name); + + if (is_callable($f)) { + if ($args[0] == 'list') + $args = self::compressList($args[2], $args[1]); + + $ret = call_user_func($f, $this->reduce($args, true), $this); + + if (is_null($ret)) { + return array("string", "", array( + $name, "(", $args, ")" + )); + } + + // convert to a typed value if the result is a php primitive + if (is_numeric($ret)) $ret = array('number', $ret, ""); + elseif (!is_array($ret)) $ret = array('keyword', $ret); + + return $ret; + } + + // plain function, reduce args + $value[2] = $this->reduce($value[2]); + return $value; + case "unary": + list(, $op, $exp) = $value; + $exp = $this->reduce($exp); + + if ($exp[0] == "number") { + switch ($op) { + case "+": + return $exp; + case "-": + $exp[1] *= -1; + return $exp; + } + } + return array("string", "", array($op, $exp)); + } + + if ($forExpression) { + switch ($value[0]) { + case "keyword": + if ($color = $this->coerceColor($value)) { + return $color; + } + break; + case "raw_color": + return $this->coerceColor($value); + } + } + + return $value; + } + + + // coerce a value for use in color operation + protected function coerceColor($value) { + switch($value[0]) { + case 'color': return $value; + case 'raw_color': + $c = array("color", 0, 0, 0); + $colorStr = substr($value[1], 1); + $num = hexdec($colorStr); + $width = strlen($colorStr) == 3 ? 16 : 256; + + for ($i = 3; $i > 0; $i--) { // 3 2 1 + $t = $num % $width; + $num /= $width; + + $c[$i] = $t * (256/$width) + $t * floor(16/$width); + } + + return $c; + case 'keyword': + $name = $value[1]; + if (isset(self::$cssColors[$name])) { + list($r, $g, $b) = explode(',', self::$cssColors[$name]); + return array('color', $r, $g, $b); + } + return null; + } + } + + // make something string like into a string + protected function coerceString($value) { + switch ($value[0]) { + case "string": + return $value; + case "keyword": + return array("string", "", array($value[1])); + } + return null; + } + + // turn list of length 1 into value type + protected function flattenList($value) { + if ($value[0] == "list" && count($value[2]) == 1) { + return $this->flattenList($value[2][0]); + } + return $value; + } + + protected function toBool($a) { + if ($a) return self::$TRUE; + else return self::$FALSE; + } + + // evaluate an expression + protected function evaluate($exp) { + list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp; + + $left = $this->reduce($left, true); + $right = $this->reduce($right, true); + + if ($leftColor = $this->coerceColor($left)) { + $left = $leftColor; + } + + if ($rightColor = $this->coerceColor($right)) { + $right = $rightColor; + } + + $ltype = $left[0]; + $rtype = $right[0]; + + // operators that work on all types + if ($op == "and") { + return $this->toBool($left == self::$TRUE && $right == self::$TRUE); + } + + if ($op == "=") { + return $this->toBool($this->eq($left, $right) ); + } + + if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { + return $str; + } + + // type based operators + $fname = "op_${ltype}_${rtype}"; + if (is_callable(array($this, $fname))) { + $out = $this->$fname($op, $left, $right); + if (!is_null($out)) return $out; + } + + // make the expression look it did before being parsed + $paddedOp = $op; + if ($whiteBefore) $paddedOp = " " . $paddedOp; + if ($whiteAfter) $paddedOp .= " "; + + return array("string", "", array($left, $paddedOp, $right)); + } + + protected function stringConcatenate($left, $right) { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] == "string") { + $right[1] = ""; + } + $strLeft[2][] = $right; + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + array_unshift($strRight[2], $left); + return $strRight; + } + } + + + // make sure a color's components don't go out of bounds + protected function fixColor($c) { + foreach (range(1, 3) as $i) { + if ($c[$i] < 0) $c[$i] = 0; + if ($c[$i] > 255) $c[$i] = 255; + } + + return $c; + } + + protected function op_number_color($op, $lft, $rgt) { + if ($op == '+' || $op == '*') { + return $this->op_color_number($op, $rgt, $lft); + } + } + + protected function op_color_number($op, $lft, $rgt) { + if ($rgt[0] == '%') $rgt[1] /= 100; + + return $this->op_color_color($op, $lft, + array_fill(1, count($lft) - 1, $rgt[1])); + } + + protected function op_color_color($op, $left, $right) { + $out = array('color'); + $max = count($left) > count($right) ? count($left) : count($right); + foreach (range(1, $max - 1) as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + case '-': + $out[] = $lval - $rval; + break; + case '*': + $out[] = $lval * $rval; + break; + case '%': + $out[] = $lval % $rval; + break; + case '/': + if ($rval == 0) $this->throwError("evaluate error: can't divide by zero"); + $out[] = $lval / $rval; + break; + default: + $this->throwError('evaluate error: color op number failed on op '.$op); + } + } + return $this->fixColor($out); + } + + // operator on two numbers + protected function op_number_number($op, $left, $right) { + $unit = empty($left[2]) ? $right[2] : $left[2]; + + $value = 0; + switch ($op) { + case '+': + $value = $left[1] + $right[1]; + break; + case '*': + $value = $left[1] * $right[1]; + break; + case '-': + $value = $left[1] - $right[1]; + break; + case '%': + $value = $left[1] % $right[1]; + break; + case '/': + if ($right[1] == 0) $this->throwError('parse error: divide by zero'); + $value = $left[1] / $right[1]; + break; + case '<': + return $this->toBool($left[1] < $right[1]); + case '>': + return $this->toBool($left[1] > $right[1]); + case '>=': + return $this->toBool($left[1] >= $right[1]); + case '=<': + return $this->toBool($left[1] <= $right[1]); + default: + $this->throwError('parse error: unknown number operator: '.$op); + } + + return array("number", $value, $unit); + } + + + /* environment functions */ + + protected function makeOutputBlock($type, $selectors = null) { + $b = new stdclass; + $b->lines = array(); + $b->children = array(); + $b->selectors = $selectors; + $b->type = $type; + $b->parent = $this->scope; + return $b; + } + + // the state of execution + protected function pushEnv($block = null) { + $e = new stdclass; + $e->parent = $this->env; + $e->store = array(); + $e->block = $block; + + $this->env = $e; + return $e; + } + + // pop something off the stack + protected function popEnv() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // set something in the current env + protected function set($name, $value) { + $this->env->store[$name] = $value; + } + + + // get the highest occurrence entry for a name + protected function get($name, $default=null) { + $current = $this->env; + + $isArguments = $name == $this->vPrefix . 'arguments'; + while ($current) { + if ($isArguments && isset($current->arguments)) { + return array('list', ' ', $current->arguments); + } + + if (isset($current->store[$name])) + return $current->store[$name]; + else { + $current = isset($current->storeParent) ? + $current->storeParent : $current->parent; + } + } + + return $default; + } + + // inject array of unparsed strings into environment as variables + protected function injectVariables($args) { + $this->pushEnv(); + $parser = new lessc_parser($this, __METHOD__); + foreach ($args as $name => $strValue) { + if ($name{0} != '@') $name = '@'.$name; + $parser->count = 0; + $parser->buffer = (string)$strValue; + if (!$parser->propertyValue($value)) { + throw new Exception("failed to parse passed in variable $name: $strValue"); + } + + $this->set($name, $value); + } + } + + /** + * Initialize any static state, can initialize parser for a file + * $opts isn't used yet + */ + public function __construct($fname = null) { + if ($fname !== null) { + // used for deprecated parse method + $this->_parseFile = $fname; + } + } + + public function compile($string, $name = null) { + $locale = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, "C"); + + $this->parser = $this->makeParser($name); + $root = $this->parser->parse($string); + + $this->env = null; + $this->scope = null; + + $this->formatter = $this->newFormatter(); + + if (!empty($this->registeredVars)) { + $this->injectVariables($this->registeredVars); + } + + $this->sourceParser = $this->parser; // used for error messages + $this->compileBlock($root); + + ob_start(); + $this->formatter->block($this->scope); + $out = ob_get_clean(); + setlocale(LC_NUMERIC, $locale); + return $out; + } + + public function compileFile($fname, $outFname = null) { + if (!is_readable($fname)) { + throw new Exception('load error: failed to find '.$fname); + } + + $pi = pathinfo($fname); + + $oldImport = $this->importDir; + + $this->importDir = (array)$this->importDir; + $this->importDir[] = $pi['dirname'].'/'; + + $this->allParsedFiles = array(); + $this->addParsedFile($fname); + + $out = $this->compile(file_get_contents($fname), $fname); + + $this->importDir = $oldImport; + + if ($outFname !== null) { + return file_put_contents($outFname, $out); + } + + return $out; + } + + // compile only if changed input has changed or output doesn't exist + public function checkedCompile($in, $out) { + if (!is_file($out) || filemtime($in) > filemtime($out)) { + $this->compileFile($in, $out); + return true; + } + return false; + } + + /** + * Execute lessphp on a .less file or a lessphp cache structure + * + * The lessphp cache structure contains information about a specific + * less file having been parsed. It can be used as a hint for future + * calls to determine whether or not a rebuild is required. + * + * The cache structure contains two important keys that may be used + * externally: + * + * compiled: The final compiled CSS + * updated: The time (in seconds) the CSS was last compiled + * + * The cache structure is a plain-ol' PHP associative array and can + * be serialized and unserialized without a hitch. + * + * @param mixed $in Input + * @param bool $force Force rebuild? + * @return array lessphp cache structure + */ + public function cachedCompile($in, $force = false) { + // assume no root + $root = null; + + if (is_string($in)) { + $root = $in; + } elseif (is_array($in) and isset($in['root'])) { + if ($force or ! isset($in['files'])) { + // If we are forcing a recompile or if for some reason the + // structure does not contain any file information we should + // specify the root to trigger a rebuild. + $root = $in['root']; + } elseif (isset($in['files']) and is_array($in['files'])) { + foreach ($in['files'] as $fname => $ftime ) { + if (!file_exists($fname) or filemtime($fname) > $ftime) { + // One of the files we knew about previously has changed + // so we should look at our incoming root again. + $root = $in['root']; + break; + } + } + } + } else { + // TODO: Throw an exception? We got neither a string nor something + // that looks like a compatible lessphp cache structure. + return null; + } + + if ($root !== null) { + // If we have a root value which means we should rebuild. + $out = array(); + $out['root'] = $root; + $out['compiled'] = $this->compileFile($root); + $out['files'] = $this->allParsedFiles(); + $out['updated'] = time(); + return $out; + } else { + // No changes, pass back the structure + // we were given initially. + return $in; + } + + } + + // parse and compile buffer + // This is deprecated + public function parse($str = null, $initialVariables = null) { + if (is_array($str)) { + $initialVariables = $str; + $str = null; + } + + $oldVars = $this->registeredVars; + if ($initialVariables !== null) { + $this->setVariables($initialVariables); + } + + if ($str == null) { + if (empty($this->_parseFile)) { + throw new exception("nothing to parse"); + } + + $out = $this->compileFile($this->_parseFile); + } else { + $out = $this->compile($str); + } + + $this->registeredVars = $oldVars; + return $out; + } + + protected function makeParser($name) { + $parser = new lessc_parser($this, $name); + $parser->writeComments = $this->preserveComments; + + return $parser; + } + + public function setFormatter($name) { + $this->formatterName = $name; + } + + protected function newFormatter() { + $className = "lessc_formatter_lessjs"; + if (!empty($this->formatterName)) { + if (!is_string($this->formatterName)) + return $this->formatterName; + $className = "lessc_formatter_$this->formatterName"; + } + + return new $className; + } + + public function setPreserveComments($preserve) { + $this->preserveComments = $preserve; + } + + public function registerFunction($name, $func) { + $this->libFunctions[$name] = $func; + } + + public function unregisterFunction($name) { + unset($this->libFunctions[$name]); + } + + public function setVariables($variables) { + $this->registeredVars = array_merge($this->registeredVars, $variables); + } + + public function unsetVariable($name) { + unset($this->registeredVars[$name]); + } + + public function setImportDir($dirs) { + $this->importDir = (array)$dirs; + } + + public function addImportDir($dir) { + $this->importDir = (array)$this->importDir; + $this->importDir[] = $dir; + } + + public function allParsedFiles() { + return $this->allParsedFiles; + } + + protected function addParsedFile($file) { + $this->allParsedFiles[realpath($file)] = filemtime($file); + } + + /** + * Uses the current value of $this->count to show line and line number + */ + protected function throwError($msg = null) { + if ($this->sourceLoc >= 0) { + $this->sourceParser->throwError($msg, $this->sourceLoc); + } + throw new exception($msg); + } + + // compile file $in to file $out if $in is newer than $out + // returns true when it compiles, false otherwise + public static function ccompile($in, $out, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->checkedCompile($in, $out); + } + + public static function cexecute($in, $force = false, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->cachedCompile($in, $force); + } + + static protected $cssColors = array( + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'cyan' => '0,255,255', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkgrey' => '169,169,169', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'grey' => '128,128,128', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightgrey' => '211,211,211', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'magenta' => '255,0,255', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50' + ); +} + +// responsible for taking a string of LESS code and converting it into a +// syntax tree +class lessc_parser { + static protected $nextBlockId = 0; // used to uniquely identify blocks + + static protected $precedence = array( + '=<' => 0, + '>=' => 0, + '=' => 0, + '<' => 0, + '>' => 0, + + '+' => 1, + '-' => 1, + '*' => 2, + '/' => 2, + '%' => 2, + ); + + static protected $whitePattern; + static protected $commentMulti; + + static protected $commentSingle = "//"; + static protected $commentMultiLeft = "/*"; + static protected $commentMultiRight = "*/"; + + // regex string to match any of the operators + static protected $operatorString; + + // these properties will supress division unless it's inside parenthases + static protected $supressDivisionProps = + array('/border-radius$/i', '/^font$/i'); + + protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document"); + protected $lineDirectives = array("charset"); + + /** + * if we are in parens we can be more liberal with whitespace around + * operators because it must evaluate to a single value and thus is less + * ambiguous. + * + * Consider: + * property1: 10 -5; // is two numbers, 10 and -5 + * property2: (10 -5); // should evaluate to 5 + */ + protected $inParens = false; + + // caches preg escaped literals + static protected $literalCache = array(); + + public function __construct($lessc, $sourceName = null) { + $this->eatWhiteDefault = true; + // reference to less needed for vPrefix, mPrefix, and parentSelector + $this->lessc = $lessc; + + $this->sourceName = $sourceName; // name used for error messages + + $this->writeComments = false; + + if (!self::$operatorString) { + self::$operatorString = + '('.implode('|', array_map(array('lessc', 'preg_quote'), + array_keys(self::$precedence))).')'; + + $commentSingle = lessc::preg_quote(self::$commentSingle); + $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft); + $commentMultiRight = lessc::preg_quote(self::$commentMultiRight); + + self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; + self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; + } + } + + public function parse($buffer) { + $this->count = 0; + $this->line = 1; + + $this->env = null; // block stack + $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); + $this->pushSpecialBlock("root"); + $this->eatWhiteDefault = true; + $this->seenComments = array(); + + // trim whitespace on head + // if (preg_match('/^\s+/', $this->buffer, $m)) { + // $this->line += substr_count($m[0], "\n"); + // $this->buffer = ltrim($this->buffer); + // } + $this->whitespace(); + + // parse the entire file + $lastCount = $this->count; + while (false !== $this->parseChunk()); + + if ($this->count != strlen($this->buffer)) + $this->throwError(); + + // TODO report where the block was opened + if (!is_null($this->env->parent)) + throw new exception('parse error: unclosed block'); + + return $this->env; + } + + /** + * Parse a single chunk off the head of the buffer and append it to the + * current parse environment. + * Returns false when the buffer is empty, or when there is an error. + * + * This function is called repeatedly until the entire document is + * parsed. + * + * This parser is most similar to a recursive descent parser. Single + * functions represent discrete grammatical rules for the language, and + * they are able to capture the text that represents those rules. + * + * Consider the function lessc::keyword(). (all parse functions are + * structured the same) + * + * The function takes a single reference argument. When calling the + * function it will attempt to match a keyword on the head of the buffer. + * If it is successful, it will place the keyword in the referenced + * argument, advance the position in the buffer, and return true. If it + * fails then it won't advance the buffer and it will return false. + * + * All of these parse functions are powered by lessc::match(), which behaves + * the same way, but takes a literal regular expression. Sometimes it is + * more convenient to use match instead of creating a new function. + * + * Because of the format of the functions, to parse an entire string of + * grammatical rules, you can chain them together using &&. + * + * But, if some of the rules in the chain succeed before one fails, then + * the buffer position will be left at an invalid state. In order to + * avoid this, lessc::seek() is used to remember and set buffer positions. + * + * Before parsing a chain, use $s = $this->seek() to remember the current + * position into $s. Then if a chain fails, use $this->seek($s) to + * go back where we started. + */ + protected function parseChunk() { + if (empty($this->buffer)) return false; + $s = $this->seek(); + + // setting a property + if ($this->keyword($key) && $this->assign() && + $this->propertyValue($value, $key) && $this->end()) + { + $this->append(array('assign', $key, $value), $s); + return true; + } else { + $this->seek($s); + } + + + // look for special css blocks + if ($this->literal('@', false)) { + $this->count--; + + // media + if ($this->literal('@media')) { + if (($this->mediaQueryList($mediaQueries) || true) + && $this->literal('{')) + { + $media = $this->pushSpecialBlock("media"); + $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; + return true; + } else { + $this->seek($s); + return false; + } + } + + if ($this->literal("@", false) && $this->keyword($dirName)) { + if ($this->isDirective($dirName, $this->blockDirectives)) { + if (($this->openString("{", $dirValue, null, array(";")) || true) && + $this->literal("{")) + { + $dir = $this->pushSpecialBlock("directive"); + $dir->name = $dirName; + if (isset($dirValue)) $dir->value = $dirValue; + return true; + } + } elseif ($this->isDirective($dirName, $this->lineDirectives)) { + if ($this->propertyValue($dirValue) && $this->end()) { + $this->append(array("directive", $dirName, $dirValue)); + return true; + } + } + } + + $this->seek($s); + } + + // setting a variable + if ($this->variable($var) && $this->assign() && + $this->propertyValue($value) && $this->end()) + { + $this->append(array('assign', $var, $value), $s); + return true; + } else { + $this->seek($s); + } + + if ($this->import($importValue)) { + $this->append($importValue, $s); + return true; + } + + // opening parametric mixin + if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && + ($this->guards($guards) || true) && + $this->literal('{')) + { + $block = $this->pushBlock($this->fixTags(array($tag))); + $block->args = $args; + $block->isVararg = $isVararg; + if (!empty($guards)) $block->guards = $guards; + return true; + } else { + $this->seek($s); + } + + // opening a simple block + if ($this->tags($tags) && $this->literal('{')) { + $tags = $this->fixTags($tags); + $this->pushBlock($tags); + return true; + } else { + $this->seek($s); + } + + // closing a block + if ($this->literal('}', false)) { + try { + $block = $this->pop(); + } catch (exception $e) { + $this->seek($s); + $this->throwError($e->getMessage()); + } + + $hidden = false; + if (is_null($block->type)) { + $hidden = true; + if (!isset($block->args)) { + foreach ($block->tags as $tag) { + if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) { + $hidden = false; + break; + } + } + } + + foreach ($block->tags as $tag) { + if (is_string($tag)) { + $this->env->children[$tag][] = $block; + } + } + } + + if (!$hidden) { + $this->append(array('block', $block), $s); + } + + // this is done here so comments aren't bundled into he block that + // was just closed + $this->whitespace(); + return true; + } + + // mixin + if ($this->mixinTags($tags) && + ($this->argumentValues($argv) || true) && + ($this->keyword($suffix) || true) && $this->end()) + { + $tags = $this->fixTags($tags); + $this->append(array('mixin', $tags, $argv, $suffix), $s); + return true; + } else { + $this->seek($s); + } + + // spare ; + if ($this->literal(';')) return true; + + return false; // got nothing, throw error + } + + protected function isDirective($dirname, $directives) { + // TODO: cache pattern in parser + $pattern = implode("|", + array_map(array("lessc", "preg_quote"), $directives)); + $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; + + return preg_match($pattern, $dirname); + } + + protected function fixTags($tags) { + // move @ tags out of variable namespace + foreach ($tags as &$tag) { + if ($tag{0} == $this->lessc->vPrefix) + $tag[0] = $this->lessc->mPrefix; + } + return $tags; + } + + // a list of expressions + protected function expressionList(&$exps) { + $values = array(); + + while ($this->expression($exp)) { + $values[] = $exp; + } + + if (count($values) == 0) return false; + + $exps = lessc::compressList($values, ' '); + return true; + } + + /** + * Attempt to consume an expression. + * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code + */ + protected function expression(&$out) { + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); + + // look for / shorthand + if (!empty($this->env->supressedDivision)) { + unset($this->env->supressedDivision); + $s = $this->seek(); + if ($this->literal("/") && $this->value($rhs)) { + $out = array("list", "", + array($out, array("keyword", "/"), $rhs)); + } else { + $this->seek($s); + } + } + + return true; + } + return false; + } + + /** + * recursively parse infix equation with $lhs at precedence $minP + */ + protected function expHelper($lhs, $minP) { + $this->inExp = true; + $ss = $this->seek(); + + while (true) { + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + // If there is whitespace before the operator, then we require + // whitespace after the operator for it to be an expression + $needWhite = $whiteBefore && !$this->inParens; + + if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { + if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { + foreach (self::$supressDivisionProps as $pattern) { + if (preg_match($pattern, $this->env->currentProperty)) { + $this->env->supressedDivision = true; + break 2; + } + } + } + + + $whiteAfter = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + if (!$this->value($rhs)) break; + + // peek for next operator to see what to do with rhs + if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { + $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + } + + $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); + $ss = $this->seek(); + + continue; + } + + break; + } + + $this->seek($ss); + + return $lhs; + } + + // consume a list of values for a property + public function propertyValue(&$value, $keyName = null) { + $values = array(); + + if ($keyName !== null) $this->env->currentProperty = $keyName; + + $s = null; + while ($this->expressionList($v)) { + $values[] = $v; + $s = $this->seek(); + if (!$this->literal(',')) break; + } + + if ($s) $this->seek($s); + + if ($keyName !== null) unset($this->env->currentProperty); + + if (count($values) == 0) return false; + + $value = lessc::compressList($values, ', '); + return true; + } + + protected function parenValue(&$out) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { + return false; + } + + $inParens = $this->inParens; + if ($this->literal("(") && + ($this->inParens = true) && $this->expression($exp) && + $this->literal(")")) + { + $out = $exp; + $this->inParens = $inParens; + return true; + } else { + $this->inParens = $inParens; + $this->seek($s); + } + + return false; + } + + // a single value + protected function value(&$value) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { + // negation + if ($this->literal("-", false) && + (($this->variable($inner) && $inner = array("variable", $inner)) || + $this->unit($inner) || + $this->parenValue($inner))) + { + $value = array("unary", "-", $inner); + return true; + } else { + $this->seek($s); + } + } + + if ($this->parenValue($value)) return true; + if ($this->unit($value)) return true; + if ($this->color($value)) return true; + if ($this->func($value)) return true; + if ($this->string($value)) return true; + + if ($this->keyword($word)) { + $value = array('keyword', $word); + return true; + } + + // try a variable + if ($this->variable($var)) { + $value = array('variable', $var); + return true; + } + + // unquote string (should this work on any type? + if ($this->literal("~") && $this->string($str)) { + $value = array("escape", $str); + return true; + } else { + $this->seek($s); + } + + // css hack: \0 + if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { + $value = array('keyword', '\\'.$m[1]); + return true; + } else { + $this->seek($s); + } + + return false; + } + + // an import statement + protected function import(&$out) { + $s = $this->seek(); + if (!$this->literal('@import')) return false; + + // @import "something.css" media; + // @import url("something.css") media; + // @import url(something.css) media; + + if ($this->propertyValue($value)) { + $out = array("import", $value); + return true; + } + } + + protected function mediaQueryList(&$out) { + if ($this->genericList($list, "mediaQuery", ",", false)) { + $out = $list[2]; + return true; + } + return false; + } + + protected function mediaQuery(&$out) { + $s = $this->seek(); + + $expressions = null; + $parts = array(); + + if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { + $prop = array("mediaType"); + if (isset($only)) $prop[] = "only"; + if (isset($not)) $prop[] = "not"; + $prop[] = $mediaType; + $parts[] = $prop; + } else { + $this->seek($s); + } + + + if (!empty($mediaType) && !$this->literal("and")) { + // ~ + } else { + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + } + + if (count($parts) == 0) { + $this->seek($s); + return false; + } + + $out = $parts; + return true; + } + + protected function mediaExpression(&$out) { + $s = $this->seek(); + $value = null; + if ($this->literal("(") && + $this->keyword($feature) && + ($this->literal(":") && $this->expression($value) || true) && + $this->literal(")")) + { + $out = array("mediaExp", $feature); + if ($value) $out[] = $value; + return true; + } + + $this->seek($s); + return false; + } + + // an unbounded string stopped by $end + protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $stop = array("'", '"', "@{", $end); + $stop = array_map(array("lessc", "preg_quote"), $stop); + // $stop[] = self::$commentMulti; + + if (!is_null($rejectStrs)) { + $stop = array_merge($stop, $rejectStrs); + } + + $patt = '(.*?)('.implode("|", $stop).')'; + + $nestingLevel = 0; + + $content = array(); + while ($this->match($patt, $m, false)) { + if (!empty($m[1])) { + $content[] = $m[1]; + if ($nestingOpen) { + $nestingLevel += substr_count($m[1], $nestingOpen); + } + } + + $tok = $m[2]; + + $this->count-= strlen($tok); + if ($tok == $end) { + if ($nestingLevel == 0) { + break; + } else { + $nestingLevel--; + } + } + + if (($tok == "'" || $tok == '"') && $this->string($str)) { + $content[] = $str; + continue; + } + + if ($tok == "@{" && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + if (in_array($tok, $rejectStrs)) { + $count = null; + break; + } + + + $content[] = $tok; + $this->count+= strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($content) == 0) return false; + + // trim the end + if (is_string(end($content))) { + $content[count($content) - 1] = rtrim(end($content)); + } + + $out = array("string", "", $content); + return true; + } + + protected function string(&$out) { + $s = $this->seek(); + if ($this->literal('"', false)) { + $delim = '"'; + } elseif ($this->literal("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = array(); + + // look for either ending delim , escape, or string interpolation + $patt = '([^\n]*?)(@\{|\\\\|' . + lessc::preg_quote($delim).')'; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while ($this->match($patt, $m, false)) { + $content[] = $m[1]; + if ($m[2] == "@{") { + $this->count -= strlen($m[2]); + if ($this->interpolation($inter, false)) { + $content[] = $inter; + } else { + $this->count += strlen($m[2]); + $content[] = "@{"; // ignore it + } + } elseif ($m[2] == '\\') { + $content[] = $m[2]; + if ($this->literal($delim, false)) { + $content[] = $delim; + } + } else { + $this->count -= strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim)) { + $out = array("string", $delim, $content); + return true; + } + + $this->seek($s); + return false; + } + + protected function interpolation(&$out) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = true; + + $s = $this->seek(); + if ($this->literal("@{") && + $this->keyword($var) && + $this->literal("}", false)) + { + $out = array("variable", $this->lessc->vPrefix . $var); + $this->eatWhiteDefault = $oldWhite; + if ($this->eatWhiteDefault) $this->whitespace(); + return true; + } + + $this->eatWhiteDefault = $oldWhite; + $this->seek($s); + return false; + } + + protected function unit(&$unit) { + // speed shortcut + if (isset($this->buffer[$this->count])) { + $char = $this->buffer[$this->count]; + if (!ctype_digit($char) && $char != ".") return false; + } + + if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { + $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); + return true; + } + return false; + } + + // a # color + protected function color(&$out) { + if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { + if (strlen($m[1]) > 7) { + $out = array("string", "", array($m[1])); + } else { + $out = array("raw_color", $m[1]); + } + return true; + } + + return false; + } + + // consume a list of property values delimited by ; and wrapped in () + protected function argumentValues(&$args, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + while (true) { + if ($this->expressionList($value)) $values[] = $value; + if (!$this->literal($delim)) break; + else { + if ($value == null) $values[] = null; + $value = null; + } + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + return true; + } + + // consume an argument definition list surrounded by () + // each argument is a variable name with optional value + // or at the end a ... or a variable named followed by ... + protected function argumentDef(&$args, &$isVararg, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + + $isVararg = false; + while (true) { + if ($this->literal("...")) { + $isVararg = true; + break; + } + + if ($this->variable($vname)) { + $arg = array("arg", $vname); + $ss = $this->seek(); + if ($this->assign() && $this->expressionList($value)) { + $arg[] = $value; + } else { + $this->seek($ss); + if ($this->literal("...")) { + $arg[0] = "rest"; + $isVararg = true; + } + } + $values[] = $arg; + if ($isVararg) break; + continue; + } + + if ($this->value($literal)) { + $values[] = array("lit", $literal); + } + + if (!$this->literal($delim)) break; + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + + return true; + } + + // consume a list of tags + // this accepts a hanging delimiter + protected function tags(&$tags, $simple = false, $delim = ',') { + $tags = array(); + while ($this->tag($tt, $simple)) { + $tags[] = $tt; + if (!$this->literal($delim)) break; + } + if (count($tags) == 0) return false; + + return true; + } + + // list of tags of specifying mixin path + // optionally separated by > (lazy, accepts extra >) + protected function mixinTags(&$tags) { + $s = $this->seek(); + $tags = array(); + while ($this->tag($tt, true)) { + $tags[] = $tt; + $this->literal(">"); + } + + if (count($tags) == 0) return false; + + return true; + } + + // a bracketed value (contained within in a tag definition) + protected function tagBracket(&$value) { + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { + return false; + } + + $s = $this->seek(); + if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { + $value = '['.$c.']'; + // whitespace? + if ($this->whitespace()) $value .= " "; + + // escape parent selector, (yuck) + $value = str_replace($this->lessc->parentSelector, "$&$", $value); + return true; + } + + $this->seek($s); + return false; + } + + protected function tagExpression(&$value) { + $s = $this->seek(); + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $value = array('exp', $exp); + return true; + } + + $this->seek($s); + return false; + } + + // a single tag + protected function tag(&$tag, $simple = false) { + if ($simple) + $chars = '^,:;{}\][>\(\) "\''; + else + $chars = '^,;{}["\''; + + if (!$simple && $this->tagExpression($tag)) { + return true; + } + + $tag = ''; + while ($this->tagBracket($first)) $tag .= $first; + + while (true) { + if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { + $tag .= $m[1]; + if ($simple) break; + + while ($this->tagBracket($brack)) $tag .= $brack; + continue; + } elseif ($this->unit($unit)) { // for keyframes + $tag .= $unit[1] . $unit[2]; + continue; + } + break; + } + + + $tag = trim($tag); + if ($tag == '') return false; + + return true; + } + + // a css function + protected function func(&$func) { + $s = $this->seek(); + + if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { + $fname = $m[1]; + + $sPreArgs = $this->seek(); + + $args = array(); + while (true) { + $ss = $this->seek(); + // this ugly nonsense is for ie filter properties + if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { + $args[] = array("string", "", array($name, "=", $value)); + } else { + $this->seek($ss); + if ($this->expressionList($value)) { + $args[] = $value; + } + } + + if (!$this->literal(',')) break; + } + $args = array('list', ',', $args); + + if ($this->literal(')')) { + $func = array('function', $fname, $args); + return true; + } elseif ($fname == 'url') { + // couldn't parse and in url? treat as string + $this->seek($sPreArgs); + if ($this->openString(")", $string) && $this->literal(")")) { + $func = array('function', $fname, $string); + return true; + } + } + } + + $this->seek($s); + return false; + } + + // consume a less variable + protected function variable(&$name) { + $s = $this->seek(); + if ($this->literal($this->lessc->vPrefix, false) && + ($this->variable($sub) || $this->keyword($name))) + { + if (!empty($sub)) { + $name = array('variable', $sub); + } else { + $name = $this->lessc->vPrefix.$name; + } + return true; + } + + $name = null; + $this->seek($s); + return false; + } + + /** + * Consume an assignment operator + * Can optionally take a name that will be set to the current property name + */ + protected function assign($name = null) { + if ($name) $this->currentProperty = $name; + return $this->literal(':') || $this->literal('='); + } + + // consume a keyword + protected function keyword(&$word) { + if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { + $word = $m[1]; + return true; + } + return false; + } + + // consume an end of statement delimiter + protected function end() { + if ($this->literal(';')) { + return true; + } elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + return false; + } + + protected function guards(&$guards) { + $s = $this->seek(); + + if (!$this->literal("when")) { + $this->seek($s); + return false; + } + + $guards = array(); + + while ($this->guardGroup($g)) { + $guards[] = $g; + if (!$this->literal(",")) break; + } + + if (count($guards) == 0) { + $guards = null; + $this->seek($s); + return false; + } + + return true; + } + + // a bunch of guards that are and'd together + // TODO rename to guardGroup + protected function guardGroup(&$guardGroup) { + $s = $this->seek(); + $guardGroup = array(); + while ($this->guard($guard)) { + $guardGroup[] = $guard; + if (!$this->literal("and")) break; + } + + if (count($guardGroup) == 0) { + $guardGroup = null; + $this->seek($s); + return false; + } + + return true; + } + + protected function guard(&$guard) { + $s = $this->seek(); + $negate = $this->literal("not"); + + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $guard = $exp; + if ($negate) $guard = array("negate", $guard); + return true; + } + + $this->seek($s); + return false; + } + + /* raw parsing functions */ + + protected function literal($what, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + // shortcut on single letter + if (!isset($what[1]) && isset($this->buffer[$this->count])) { + if ($this->buffer[$this->count] == $what) { + if (!$eatWhitespace) { + $this->count++; + return true; + } + // goes below... + } else { + return false; + } + } + + if (!isset(self::$literalCache[$what])) { + self::$literalCache[$what] = lessc::preg_quote($what); + } + + return $this->match(self::$literalCache[$what], $m, $eatWhitespace); + } + + protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { + $s = $this->seek(); + $items = array(); + while ($this->$parseItem($value)) { + $items[] = $value; + if ($delim) { + if (!$this->literal($delim)) break; + } + } + + if (count($items) == 0) { + $this->seek($s); + return false; + } + + if ($flatten && count($items) == 1) { + $out = $items[0]; + } else { + $out = array("list", $delim, $items); + } + + return true; + } + + + // advance counter to next occurrence of $what + // $until - don't include $what in advance + // $allowNewline, if string, will be used as valid char set + protected function to($what, &$out, $until = false, $allowNewline = false) { + if (is_string($allowNewline)) { + $validChars = $allowNewline; + } else { + $validChars = $allowNewline ? "." : "[^\n]"; + } + if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false; + if ($until) $this->count -= strlen($what); // give back $what + $out = $m[1]; + return true; + } + + // try to match something on head of buffer + protected function match($regex, &$out, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais'; + if (preg_match($r, $this->buffer, $out, null, $this->count)) { + $this->count += strlen($out[0]); + if ($eatWhitespace && $this->writeComments) $this->whitespace(); + return true; + } + return false; + } + + // match some whitespace + protected function whitespace() { + if ($this->writeComments) { + $gotWhite = false; + while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { + $this->append(array("comment", $m[1])); + $this->commentsSeen[$this->count] = true; + } + $this->count += strlen($m[0]); + $gotWhite = true; + } + return $gotWhite; + } else { + $this->match("", $m); + return strlen($m[0]) > 0; + } + } + + // match something without consuming it + protected function peek($regex, &$out = null, $from=null) { + if (is_null($from)) $from = $this->count; + $r = '/'.$regex.'/Ais'; + $result = preg_match($r, $this->buffer, $out, null, $from); + + return $result; + } + + // seek to a spot in the buffer or return where we are on no argument + protected function seek($where = null) { + if ($where === null) return $this->count; + else $this->count = $where; + return true; + } + + /* misc functions */ + + public function throwError($msg = "parse error", $count = null) { + $count = is_null($count) ? $this->count : $count; + + $line = $this->line + + substr_count(substr($this->buffer, 0, $count), "\n"); + + if (!empty($this->sourceName)) { + $loc = "$this->sourceName on line $line"; + } else { + $loc = "line: $line"; + } + + // TODO this depends on $this->count + if ($this->peek("(.*?)(\n|$)", $m, $count)) { + throw new exception("$msg: failed at `$m[1]` $loc"); + } else { + throw new exception("$msg: $loc"); + } + } + + protected function pushBlock($selectors=null, $type=null) { + $b = new stdclass; + $b->parent = $this->env; + + $b->type = $type; + $b->id = self::$nextBlockId++; + + $b->isVararg = false; // TODO: kill me from here + $b->tags = $selectors; + + $b->props = array(); + $b->children = array(); + + $this->env = $b; + return $b; + } + + // push a block that doesn't multiply tags + protected function pushSpecialBlock($type) { + return $this->pushBlock(null, $type); + } + + // append a property to the current block + protected function append($prop, $pos = null) { + if ($pos !== null) $prop[-1] = $pos; + $this->env->props[] = $prop; + } + + // pop something off the stack + protected function pop() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // remove comments from $text + // todo: make it work for all functions, not just url + protected function removeComments($text) { + $look = array( + 'url(', '//', '/*', '"', "'" + ); + + $out = ''; + $min = null; + while (true) { + // find the next item + foreach ($look as $token) { + $pos = strpos($text, $token); + if ($pos !== false) { + if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); + } + } + + if (is_null($min)) break; + + $count = $min[1]; + $skip = 0; + $newlines = 0; + switch ($min[0]) { + case 'url(': + if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) + $count += strlen($m[0]) - strlen($min[0]); + break; + case '"': + case "'": + if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count)) + $count += strlen($m[0]) - 1; + break; + case '//': + $skip = strpos($text, "\n", $count); + if ($skip === false) $skip = strlen($text) - $count; + else $skip -= $count; + break; + case '/*': + if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { + $skip = strlen($m[0]); + $newlines = substr_count($m[0], "\n"); + } + break; + } + + if ($skip == 0) $count += strlen($min[0]); + + $out .= substr($text, 0, $count).str_repeat("\n", $newlines); + $text = substr($text, $count + $skip); + + $min = null; + } + + return $out.$text; + } + +} + +class lessc_formatter_classic { + public $indentChar = " "; + + public $break = "\n"; + public $open = " {"; + public $close = "}"; + public $selectorSeparator = ", "; + public $assignSeparator = ":"; + + public $openSingle = " { "; + public $closeSingle = " }"; + + public $disableSingle = false; + public $breakSelectors = false; + + public $compressColors = false; + + public function __construct() { + $this->indentLevel = 0; + } + + public function indentStr($n = 0) { + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + public function property($name, $value) { + return $name . $this->assignSeparator . $value . ";"; + } + + protected function isEmpty($block) { + if (empty($block->lines)) { + foreach ($block->children as $child) { + if (!$this->isEmpty($child)) return false; + } + + return true; + } + return false; + } + + public function block($block) { + if ($this->isEmpty($block)) return; + + $inner = $pre = $this->indentStr(); + + $isSingle = !$this->disableSingle && + is_null($block->type) && count($block->lines) == 1; + + if (!empty($block->selectors)) { + $this->indentLevel++; + + if ($this->breakSelectors) { + $selectorSeparator = $this->selectorSeparator . $this->break . $pre; + } else { + $selectorSeparator = $this->selectorSeparator; + } + + echo $pre . + implode($selectorSeparator, $block->selectors); + if ($isSingle) { + echo $this->openSingle; + $inner = ""; + } else { + echo $this->open . $this->break; + $inner = $this->indentStr(); + } + + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!$isSingle && !empty($block->children)) { + echo $this->break; + } + } + + foreach ($block->children as $child) { + $this->block($child); + } + + if (!empty($block->selectors)) { + if (!$isSingle && empty($block->children)) echo $this->break; + + if ($isSingle) { + echo $this->closeSingle . $this->break; + } else { + echo $pre . $this->close . $this->break; + } + + $this->indentLevel--; + } + } +} + +class lessc_formatter_compressed extends lessc_formatter_classic { + public $disableSingle = true; + public $open = "{"; + public $selectorSeparator = ","; + public $assignSeparator = ":"; + public $break = ""; + public $compressColors = true; + + public function indentStr($n = 0) { + return ""; + } +} + +class lessc_formatter_lessjs extends lessc_formatter_classic { + public $disableSingle = true; + public $breakSelectors = true; + public $assignSeparator = ": "; + public $selectorSeparator = ","; +} + + diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php new file mode 100644 index 00000000..5ad188cc --- /dev/null +++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php @@ -0,0 +1,3759 @@ +<?php + +class scssc { + static public $VERSION = "v0.0.4"; + + static protected $operatorNames = array( + '+' => "add", + '-' => "sub", + '*' => "mul", + '/' => "div", + '%' => "mod", + + '==' => "eq", + '!=' => "neq", + '<' => "lt", + '>' => "gt", + + '<=' => "lte", + '>=' => "gte", + ); + + static protected $namespaces = array( + "special" => "%", + "mixin" => "@", + "function" => "^", + ); + + static protected $numberPrecision = 3; + static protected $unitTable = array( + "in" => array( + "in" => 1, + "pt" => 72, + "pc" => 6, + "cm" => 2.54, + "mm" => 25.4, + "px" => 96, + ) + ); + + static public $true = array("keyword", "true"); + static public $false = array("keyword", "false"); + + static public $defaultValue = array("keyword", ""); + static public $selfSelector = array("self"); + + protected $importPaths = array(""); + protected $importCache = array(); + + protected $userFunctions = array(); + + protected $formatter = "scss_formatter_nested"; + + function compile($code, $name=null) { + $this->indentLevel = -1; + $this->commentsSeen = array(); + $this->extends = array(); + $this->extendsMap = array(); + + $locale = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, "C"); + + $this->parsedFiles = array(); + $this->parser = new scss_parser($name); + $tree = $this->parser->parse($code); + + $this->formatter = new $this->formatter(); + + $this->env = null; + $this->scope = null; + + $this->compileRoot($tree); + $this->flattenSelectors($this->scope); + + ob_start(); + $this->formatter->block($this->scope); + $out = ob_get_clean(); + + setlocale(LC_NUMERIC, $locale); + return $out; + } + + protected function pushExtends($target, $origin) { + $i = count($this->extends); + $this->extends[] = array($target, $origin); + + foreach ($target as $part) { + if (isset($this->extendsMap[$part])) { + $this->extendsMap[$part][] = $i; + } else { + $this->extendsMap[$part] = array($i); + } + } + } + + protected function makeOutputBlock($type, $selectors = null) { + $out = new stdclass; + $out->type = $type; + $out->lines = array(); + $out->children = array(); + $out->parent = $this->scope; + $out->selectors = $selectors; + $out->depth = $this->env->depth; + + return $out; + } + + protected function matchExtendsSingle($single, &$out_origin, &$out_rem) { + $counts = array(); + foreach ($single as $part) { + if (!is_string($part)) return false; // hmm + + if (isset($this->extendsMap[$part])) { + foreach ($this->extendsMap[$part] as $idx) { + $counts[$idx] = + isset($counts[$idx]) ? $counts[$idx] + 1 : 1; + } + } + } + + foreach ($counts as $idx => $count) { + list($target, $origin) = $this->extends[$idx]; + // check count + if ($count != count($target)) continue; + // check if target is subset of single + if (array_diff(array_intersect($single, $target), $target)) continue; + + $out_origin = $origin; + $out_rem = array_diff($single, $target); + + return true; + } + + return false; + } + + protected function combineSelectorSingle($base, $other) { + $tag = null; + $out = array(); + + foreach (array($base, $other) as $single) { + foreach ($single as $part) { + if (preg_match('/^[^.#:]/', $part)) { + $tag = $part; + } else { + $out[] = $part; + } + } + } + + if ($tag) { + array_unshift($out, $tag); + } + + return $out; + } + + protected function matchExtends($selector, &$out, $from = 0, $initial=true) { + foreach ($selector as $i => $part) { + if ($i < $from) continue; + + if ($this->matchExtendsSingle($part, $origin, $rem)) { + $before = array_slice($selector, 0, $i); + $after = array_slice($selector, $i + 1); + + foreach ($origin as $new) { + $new[count($new) - 1] = + $this->combineSelectorSingle(end($new), $rem); + + $k = 0; + // remove shared parts + if ($initial) { + foreach ($before as $k => $val) { + if (!isset($new[$k]) || $val != $new[$k]) { + break; + } + } + } + + $result = array_merge( + $before, + $k > 0 ? array_slice($new, $k) : $new, + $after); + + + if ($result == $selector) continue; + $out[] = $result; + + // recursively check for more matches + $this->matchExtends($result, $out, $i, false); + + // selector sequence merging + if (!empty($before) && count($new) > 1) { + $result2 = array_merge( + array_slice($new, 0, -1), + $k > 0 ? array_slice($before, $k) : $before, + array_slice($new, -1), + $after); + + $out[] = $result2; + } + } + } + } + } + + protected function flattenSelectors($block) { + if ($block->selectors) { + $selectors = array(); + foreach ($block->selectors as $s) { + $selectors[] = $s; + if (!is_array($s)) continue; + // check extends + if (!empty($this->extendsMap)) { + $this->matchExtends($s, $selectors); + } + } + + $selectors = array_map(array($this, "compileSelector"), $selectors); + $block->selectors = $selectors; + } + + foreach ($block->children as $child) { + $this->flattenSelectors($child); + } + } + + protected function compileRoot($rootBlock) { + $this->pushEnv($rootBlock); + $this->scope = $this->makeOutputBlock("root"); + $this->compileChildren($rootBlock->children, $this->scope); + $this->popEnv(); + } + + protected function compileMedia($media) { + $this->pushEnv($media); + $parentScope = $this->mediaParent($this->scope); + + $this->scope = $this->makeOutputBlock("media", array( + $this->compileMediaQuery($this->multiplyMedia($this->env))) + ); + + $parentScope->children[] = $this->scope; + + $this->compileChildren($media->children, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function mediaParent($scope) { + while (!empty($scope->parent)) { + if (!empty($scope->type) && $scope->type != "media") { + break; + } + $scope = $scope->parent; + } + + return $scope; + } + + // TODO refactor compileNestedBlock and compileMedia into same thing + protected function compileNestedBlock($block, $selectors) { + $this->pushEnv($block); + + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + $this->compileChildren($block->children, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function compileBlock($block) { + $env = $this->pushEnv($block); + + $env->selectors = + array_map(array($this, "evalSelector"), $block->selectors); + + $out = $this->makeOutputBlock(null, $this->multiplySelectors($env)); + $this->scope->children[] = $out; + $this->compileChildren($block->children, $out); + + $this->popEnv(); + } + + // joins together .classes and #ids + protected function flattenSelectorSingle($single) { + $joined = array(); + foreach ($single as $part) { + if (empty($joined) || + !is_string($part) || + preg_match('/[.:#]/', $part)) + { + $joined[] = $part; + continue; + } + + if (is_array(end($joined))) { + $joined[] = $part; + } else { + $joined[count($joined) - 1] .= $part; + } + } + + return $joined; + } + + // replaces all the interpolates + protected function evalSelector($selector) { + return array_map(array($this, "evalSelectorPart"), $selector); + } + + protected function evalSelectorPart($piece) { + foreach ($piece as &$p) { + if (!is_array($p)) continue; + + switch ($p[0]) { + case "interpolate": + $p = $this->compileValue($p); + break; + } + } + + return $this->flattenSelectorSingle($piece); + } + + // compiles to string + // self(&) should have been replaced by now + protected function compileSelector($selector) { + if (!is_array($selector)) return $selector; // media and the like + + return implode(" ", array_map( + array($this, "compileSelectorPart"), $selector)); + } + + protected function compileSelectorPart($piece) { + foreach ($piece as &$p) { + if (!is_array($p)) continue; + + switch ($p[0]) { + case "self": + $p = "&"; + break; + default: + $p = $this->compileValue($p); + break; + } + } + + return implode($piece); + } + + protected function compileChildren($stms, $out) { + foreach ($stms as $stm) { + $ret = $this->compileChild($stm, $out); + if (!is_null($ret)) return $ret; + } + } + + protected function compileMediaQuery($queryList) { + $out = "@media"; + $first = true; + foreach ($queryList as $query){ + $parts = array(); + foreach ($query as $q) { + switch ($q[0]) { + case "mediaType": + $parts[] = implode(" ", array_slice($q, 1)); + break; + case "mediaExp": + if (isset($q[2])) { + $parts[] = "($q[1]" . $this->formatter->assignSeparator . $this->compileValue($q[2]) . ")"; + } else { + $parts[] = "($q[1])"; + } + break; + } + } + if (!empty($parts)) { + if ($first) { + $first = false; + $out .= " "; + } else { + $out .= $this->formatter->tagSeparator; + } + $out .= implode(" and ", $parts); + } + } + return $out; + } + + // returns true if the value was something that could be imported + protected function compileImport($rawPath, $out) { + if ($rawPath[0] == "string") { + $path = $this->compileStringContent($rawPath); + if ($path = $this->findImport($path)) { + $this->importFile($path, $out); + return true; + } + return false; + } if ($rawPath[0] == "list") { + // handle a list of strings + if (count($rawPath[2]) == 0) return false; + foreach ($rawPath[2] as $path) { + if ($path[0] != "string") return false; + } + + foreach ($rawPath[2] as $path) { + $this->compileImport($path, $out); + } + + return true; + } + + return false; + } + + // return a value to halt execution + protected function compileChild($child, $out) { + switch ($child[0]) { + case "import": + list(,$rawPath) = $child; + $rawPath = $this->reduce($rawPath); + if (!$this->compileImport($rawPath, $out)) { + $out->lines[] = "@import " . $this->compileValue($rawPath) . ";"; + } + break; + case "directive": + list(, $directive) = $child; + $s = "@" . $directive->name; + if (!empty($directive->value)) { + $s .= " " . $this->compileValue($directive->value); + } + $this->compileNestedBlock($directive, array($s)); + break; + case "media": + $this->compileMedia($child[1]); + break; + case "block": + $this->compileBlock($child[1]); + break; + case "charset": + $out->lines[] = "@charset ".$this->compileValue($child[1]).";"; + break; + case "assign": + list(,$name, $value) = $child; + if ($name[0] == "var") { + $isDefault = !empty($child[3]); + if (!$isDefault || $this->get($name[1], true) === true) { + $this->set($name[1], $this->reduce($value)); + } + break; + } + + $out->lines[] = $this->formatter->property( + $this->compileValue($child[1]), + $this->compileValue($child[2])); + break; + case "comment": + $out->lines[] = $child[1]; + break; + case "mixin": + case "function": + list(,$block) = $child; + $this->set(self::$namespaces[$block->type] . $block->name, $block); + break; + case "extend": + list(, $selectors) = $child; + foreach ($selectors as $sel) { + // only use the first one + $sel = current($this->evalSelector($sel)); + $this->pushExtends($sel, $out->selectors); + } + break; + case "if": + list(, $if) = $child; + if ($this->reduce($if->cond, true) != self::$false) { + return $this->compileChildren($if->children, $out); + } else { + foreach ($if->cases as $case) { + if ($case->type == "else" || + $case->type == "elseif" && ($this->reduce($case->cond) != self::$false)) + { + return $this->compileChildren($case->children, $out); + } + } + } + break; + case "return": + return $this->reduce($child[1], true); + case "each": + list(,$each) = $child; + $list = $this->reduce($this->coerceList($each->list)); + foreach ($list[2] as $item) { + $this->pushEnv(); + $this->set($each->var, $item); + // TODO: allow return from here + $this->compileChildren($each->children, $out); + $this->popEnv(); + } + break; + case "while": + list(,$while) = $child; + while ($this->reduce($while->cond, true) != self::$false) { + $ret = $this->compileChildren($while->children, $out); + if ($ret) return $ret; + } + break; + case "for": + list(,$for) = $child; + $start = $this->reduce($for->start, true); + $start = $start[1]; + $end = $this->reduce($for->end, true); + $end = $end[1]; + $d = $start < $end ? 1 : -1; + + while (true) { + if ((!$for->until && $start - $d == $end) || + ($for->until && $start == $end)) + { + break; + } + + $this->set($for->var, array("number", $start, "")); + $start += $d; + + $ret = $this->compileChildren($for->children, $out); + if ($ret) return $ret; + } + + break; + case "nestedprop": + list(,$prop) = $child; + $prefixed = array(); + $prefix = $this->compileValue($prop->prefix) . "-"; + foreach ($prop->children as $child) { + if ($child[0] == "assign") { + array_unshift($child[1][2], $prefix); + } + if ($child[0] == "nestedprop") { + array_unshift($child[1]->prefix[2], $prefix); + } + $prefixed[] = $child; + } + $this->compileChildren($prefixed, $out); + break; + case "include": // including a mixin + list(,$name, $argValues, $content) = $child; + $mixin = $this->get(self::$namespaces["mixin"] . $name, false); + if (!$mixin) break; // throw error? + + $callingScope = $this->env; + + // push scope, apply args + $this->pushEnv(); + + if (!is_null($content)) { + $content->scope = $callingScope; + $this->setRaw(self::$namespaces["special"] . "content", $content); + } + + if (!is_null($mixin->args)) { + $this->applyArguments($mixin->args, $argValues); + } + + foreach ($mixin->children as $child) { + $this->compileChild($child, $out); + } + + $this->popEnv(); + + break; + case "mixin_content": + $content = $this->get(self::$namespaces["special"] . "content"); + if (is_null($content)) { + throw new \Exception("Unexpected @content inside of mixin"); + } + + $this->storeEnv = $content->scope; + + foreach ($content->children as $child) { + $this->compileChild($child, $out); + } + + unset($this->storeEnv); + break; + case "debug": + list(,$value, $pos) = $child; + $line = $this->parser->getLineNo($pos); + $value = $this->compileValue($this->reduce($value, true)); + fwrite(STDERR, "Line $line DEBUG: $value\n"); + break; + default: + throw new exception("unknown child type: $child[0]"); + } + } + + protected function expToString($exp) { + list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp; + $content = array($left); + if ($whiteLeft) $content[] = " "; + $content[] = $op; + if ($whiteRight) $content[] = " "; + $content[] = $right; + return array("string", "", $content); + } + + // should $value cause its operand to eval + protected function shouldEval($value) { + switch ($value[0]) { + case "exp": + if ($value[1] == "/") { + return $this->shouldEval($value[2], $value[3]); + } + case "var": + case "fncall": + return true; + } + return false; + } + + protected function reduce($value, $inExp = false) { + list($type) = $value; + switch ($type) { + case "exp": + list(, $op, $left, $right, $inParens) = $value; + $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op; + + $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right); + + $left = $this->reduce($left, true); + $right = $this->reduce($right, true); + + // only do division in special cases + if ($opName == "div" && !$inParens && !$inExp) { + if ($left[0] != "color" && $right[0] != "color") { + return $this->expToString($value); + } + } + + $left = $this->coerceForExpression($left); + $right = $this->coerceForExpression($right); + + $ltype = $left[0]; + $rtype = $right[0]; + + // this tries: + // 1. op_[op name]_[left type]_[right type] + // 2. op_[left type]_[right type] (passing the op as first arg + // 3. op_[op name] + $fn = "op_${opName}_${ltype}_${rtype}"; + if (is_callable(array($this, $fn)) || + (($fn = "op_${ltype}_${rtype}") && + is_callable(array($this, $fn)) && + $passOp = true) || + (($fn = "op_${opName}") && + is_callable(array($this, $fn)) && + $genOp = true)) + { + $unitChange = false; + if (!isset($genOp) && + $left[0] == "number" && $right[0] == "number") + { + if ($opName == "mod" && $right[2] != "") { + throw new \Exception(sprintf('Cannot modulo by a number with units: %s%s.', $right[1], $right[2])); + } + + $unitChange = true; + $emptyUnit = $left[2] == "" || $right[2] == ""; + $targetUnit = "" != $left[2] ? $left[2] : $right[2]; + + if ($opName != "mul") { + $left[2] = "" != $left[2] ? $left[2] : $targetUnit; + $right[2] = "" != $right[2] ? $right[2] : $targetUnit; + } + + if ($opName != "mod") { + $left = $this->normalizeNumber($left); + $right = $this->normalizeNumber($right); + } + + if ($opName == "div" && !$emptyUnit && $left[2] == $right[2]) { + $targetUnit = ""; + } + + if ($opName == "mul") { + $left[2] = "" != $left[2] ? $left[2] : $right[2]; + $right[2] = "" != $right[2] ? $right[2] : $left[2]; + } elseif ($opName == "div" && $left[2] == $right[2]) { + $left[2] = ""; + $right[2] = ""; + } + } + + $shouldEval = $inParens || $inExp; + if (isset($passOp)) { + $out = $this->$fn($op, $left, $right, $shouldEval); + } else { + $out = $this->$fn($left, $right, $shouldEval); + } + + if (!is_null($out)) { + if ($unitChange && $out[0] == "number") { + $out = $this->coerceUnit($out, $targetUnit); + } + return $out; + } + } + + return $this->expToString($value); + case "unary": + list(, $op, $exp, $inParens) = $value; + $inExp = $inExp || $this->shouldEval($exp); + + $exp = $this->reduce($exp); + if ($exp[0] == "number") { + switch ($op) { + case "+": + return $exp; + case "-": + $exp[1] *= -1; + return $exp; + } + } + + if ($op == "not") { + if ($inExp || $inParens) { + if ($exp == self::$false) { + return self::$true; + } else { + return self::$false; + } + } else { + $op = $op . " "; + } + } + + return array("string", "", array($op, $exp)); + case "var": + list(, $name) = $value; + return $this->reduce($this->get($name)); + case "list": + foreach ($value[2] as &$item) { + $item = $this->reduce($item); + } + return $value; + case "string": + foreach ($value[2] as &$item) { + if (is_array($item)) { + $item = $this->reduce($item); + } + } + return $value; + case "interpolate": + $value[1] = $this->reduce($value[1]); + return $value; + case "fncall": + list(,$name, $argValues) = $value; + + // user defined function? + $func = $this->get(self::$namespaces["function"] . $name, false); + if ($func) { + $this->pushEnv(); + + // set the args + if (isset($func->args)) { + $this->applyArguments($func->args, $argValues); + } + + // throw away lines and children + $tmp = (object)array( + "lines" => array(), + "children" => array() + ); + $ret = $this->compileChildren($func->children, $tmp); + $this->popEnv(); + + return is_null($ret) ? self::$defaultValue : $ret; + } + + // built in function + if ($this->callBuiltin($name, $argValues, $returnValue)) { + return $returnValue; + } + + // need to flatten the arguments into a list + $listArgs = array(); + foreach ((array)$argValues as $arg) { + if (empty($arg[0])) { + $listArgs[] = $this->reduce($arg[1]); + } + } + return array("function", $name, array("list", ",", $listArgs)); + default: + return $value; + } + } + + // just does physical lengths for now + protected function normalizeNumber($number) { + list(, $value, $unit) = $number; + if (isset(self::$unitTable["in"][$unit])) { + $conv = self::$unitTable["in"][$unit]; + return array("number", $value / $conv, "in"); + } + return $number; + } + + // $number should be normalized + protected function coerceUnit($number, $unit) { + list(, $value, $baseUnit) = $number; + if (isset(self::$unitTable[$baseUnit][$unit])) { + $value = $value * self::$unitTable[$baseUnit][$unit]; + } + + return array("number", $value, $unit); + } + + protected function op_add_number_number($left, $right) { + return array("number", $left[1] + $right[1], $left[2]); + } + + protected function op_mul_number_number($left, $right) { + return array("number", $left[1] * $right[1], $left[2]); + } + + protected function op_sub_number_number($left, $right) { + return array("number", $left[1] - $right[1], $left[2]); + } + + protected function op_div_number_number($left, $right) { + return array("number", $left[1] / $right[1], $left[2]); + } + + protected function op_mod_number_number($left, $right) { + return array("number", $left[1] % $right[1], $left[2]); + } + + // adding strings + protected function op_add($left, $right) { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] == "string") { + $right[1] = ""; + } + $strLeft[2][] = $right; + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + if ($left[0] == "string") { + $left[1] = ""; + } + array_unshift($strRight[2], $left); + return $strRight; + } + } + + protected function op_and($left, $right, $shouldEval) { + if (!$shouldEval) return; + if ($left != self::$false) return $right; + return $left; + } + + protected function op_or($left, $right, $shouldEval) { + if (!$shouldEval) return; + if ($left != self::$false) return $left; + return $right; + } + + protected function op_color_color($op, $left, $right) { + $out = array('color'); + foreach (range(1, 3) as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + case '-': + $out[] = $lval - $rval; + break; + case '*': + $out[] = $lval * $rval; + break; + case '%': + $out[] = $lval % $rval; + break; + case '/': + if ($rval == 0) { + throw new exception("color: Can't divide by zero"); + } + $out[] = $lval / $rval; + break; + default: + throw new exception("color: unknow op $op"); + } + } + + if (isset($left[4])) $out[4] = $left[4]; + elseif (isset($right[4])) $out[4] = $right[4]; + + return $this->fixColor($out); + } + + protected function op_color_number($op, $left, $right) { + $value = $right[1]; + return $this->op_color_color($op, $left, + array("color", $value, $value, $value)); + } + + protected function op_number_color($op, $left, $right) { + $value = $left[1]; + return $this->op_color_color($op, + array("color", $value, $value, $value), $right); + } + + protected function op_eq($left, $right) { + if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) { + $lStr[1] = ""; + $rStr[1] = ""; + return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr)); + } + + return $this->toBool($left == $right); + } + + protected function op_neq($left, $right) { + return $this->toBool($left != $right); + } + + protected function op_gte_number_number($left, $right) { + return $this->toBool($left[1] >= $right[1]); + } + + protected function op_gt_number_number($left, $right) { + return $this->toBool($left[1] > $right[1]); + } + + protected function op_lte_number_number($left, $right) { + return $this->toBool($left[1] <= $right[1]); + } + + protected function op_lt_number_number($left, $right) { + return $this->toBool($left[1] < $right[1]); + } + + protected function toBool($thing) { + return $thing ? self::$true : self::$false; + } + + protected function compileValue($value) { + $value = $this->reduce($value); + + list($type) = $value; + switch ($type) { + case "keyword": + return $value[1]; + case "color": + // [1] - red component (either number for a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + + $r = round($r); + $g = round($g); + $b = round($b); + + if (count($value) == 5 && $value[4] != 1) { // rgba + return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')'; + } + + $h = sprintf("#%02x%02x%02x", $r, $g, $b); + + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + + return $h; + case "number": + return round($value[1], self::$numberPrecision) . $value[2]; + case "string": + return $value[1] . $this->compileStringContent($value) . $value[1]; + case "function": + $args = !empty($value[2]) ? $this->compileValue($value[2]) : ""; + return "$value[1]($args)"; + case "list": + $value = $this->extractInterpolation($value); + if ($value[0] != "list") return $this->compileValue($value); + + list(, $delim, $items) = $value; + foreach ($items as &$item) { + $item = $this->compileValue($item); + } + return implode("$delim ", $items); + case "interpolated": # node created by extractInterpolation + list(, $interpolate, $left, $right) = $value; + list(,, $whiteLeft, $whiteRight) = $interpolate; + + $left = count($left[2]) > 0 ? + $this->compileValue($left).$whiteLeft : ""; + + $right = count($right[2]) > 0 ? + $whiteRight.$this->compileValue($right) : ""; + + return $left.$this->compileValue($interpolate).$right; + + case "interpolate": # raw parse node + list(, $exp) = $value; + + // strip quotes if it's a string + $reduced = $this->reduce($exp); + if ($reduced[0] == "string") { + $reduced = array("keyword", + $this->compileStringContent($reduced)); + } + + return $this->compileValue($reduced); + default: + throw new exception("unknown value type: $type"); + } + } + + protected function compileStringContent($string) { + $parts = array(); + foreach ($string[2] as $part) { + if (is_array($part)) { + $parts[] = $this->compileValue($part); + } else { + $parts[] = $part; + } + } + + return implode($parts); + } + + // doesn't need to be recursive, compileValue will handle that + protected function extractInterpolation($list) { + $items = $list[2]; + foreach ($items as $i => $item) { + if ($item[0] == "interpolate") { + $before = array("list", $list[1], array_slice($items, 0, $i)); + $after = array("list", $list[1], array_slice($items, $i + 1)); + return array("interpolated", $item, $before, $after); + } + } + return $list; + } + + // find the final set of selectors + protected function multiplySelectors($env, $childSelectors = null) { + if (is_null($env)) { + return $childSelectors; + } + + // skip env, has no selectors + if (empty($env->selectors)) { + return $this->multiplySelectors($env->parent, $childSelectors); + } + + if (is_null($childSelectors)) { + $selectors = $env->selectors; + } else { + $selectors = array(); + foreach ($env->selectors as $parent) { + foreach ($childSelectors as $child) { + $selectors[] = $this->joinSelectors($parent, $child); + } + } + } + + return $this->multiplySelectors($env->parent, $selectors); + } + + // looks for & to replace, or append parent before child + protected function joinSelectors($parent, $child) { + $setSelf = false; + $out = array(); + foreach ($child as $part) { + $newPart = array(); + foreach ($part as $p) { + if ($p == self::$selfSelector) { + $setSelf = true; + foreach ($parent as $i => $parentPart) { + if ($i > 0) { + $out[] = $newPart; + $newPart = array(); + } + + foreach ($parentPart as $pp) { + $newPart[] = $pp; + } + } + } else { + $newPart[] = $p; + } + } + + $out[] = $newPart; + } + + return $setSelf ? $out : array_merge($parent, $child); + } + + protected function multiplyMedia($env, $childQueries = null) { + if (is_null($env) || + !empty($env->block->type) && $env->block->type != "media") + { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $parentQueries = $env->block->queryList; + if ($childQueries == null) { + $childQueries = $parentQueries; + } else { + $originalQueries = $childQueries; + $childQueries = array(); + + foreach ($parentQueries as $parentQuery){ + foreach ($originalQueries as $childQuery) { + $childQueries []= array_merge($parentQuery, $childQuery); + } + } + } + + return $this->multiplyMedia($env->parent, $childQueries); + } + + // convert something to list + protected function coerceList($item, $delim = ",") { + if (!is_null($item) && $item[0] == "list") { + return $item; + } + + return array("list", $delim, is_null($item) ? array(): array($item)); + } + + protected function applyArguments($argDef, $argValues) { + $argValues = (array)$argValues; + + $keywordArgs = array(); + $remaining = array(); + + // assign the keyword args + foreach ($argValues as $arg) { + if (!empty($arg[0])) { + $keywordArgs[$arg[0][1]] = $arg[1]; + } else { + $remaining[] = $arg[1]; + } + } + + foreach ($argDef as $i => $arg) { + list($name, $default) = $arg; + + if (isset($remaining[$i])) { + $val = $remaining[$i]; + } elseif (isset($keywordArgs[$name])) { + $val = $keywordArgs[$name]; + } elseif (!empty($default)) { + $val = $default; + } else { + $val = self::$defaultValue; + } + + $this->set($name, $this->reduce($val, true), true); + } + } + + protected function pushEnv($block=null) { + $env = new stdclass; + $env->parent = $this->env; + $env->store = array(); + $env->block = $block; + $env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0; + + $this->env = $env; + return $env; + } + + protected function normalizeName($name) { + return str_replace("-", "_", $name); + } + + protected function getStoreEnv() { + return isset($this->storeEnv) ? $this->storeEnv : $this->env; + } + + protected function set($name, $value, $shadow=false) { + $name = $this->normalizeName($name); + if ($shadow) { + $this->setRaw($name, $value); + } else { + $this->setExisting($name, $value); + } + } + + // todo: this is bugged? + protected function setExisting($name, $value, $env = null) { + if (is_null($env)) $env = $this->getStoreEnv(); + + if (isset($env->store[$name])) { + $env->store[$name] = $value; + } elseif (!is_null($env->parent)) { + $this->setExisting($name, $value, $env->parent); + } else { + $this->env->store[$name] = $value; + } + } + + protected function setRaw($name, $value) { + $this->env->store[$name] = $value; + } + + protected function get($name, $defaultValue = null, $env = null) { + $name = $this->normalizeName($name); + + if (is_null($env)) $env = $this->getStoreEnv(); + if (is_null($defaultValue)) $defaultValue = self::$defaultValue; + + if (isset($env->store[$name])) { + return $env->store[$name]; + } elseif (!is_null($env->parent)) { + return $this->get($name, $defaultValue, $env->parent); + } + + return $defaultValue; // found nothing + } + + protected function popEnv() { + $env = $this->env; + $this->env = $this->env->parent; + return $env; + } + + public function getParsedFiles() { + return $this->parsedFiles; + } + + public function addImportPath($path) { + $this->importPaths[] = $path; + } + + public function setImportPaths($path) { + $this->importPaths = (array)$path; + } + + public function setFormatter($formatterName) { + $this->formatter = $formatterName; + } + + public function registerFunction($name, $func) { + $this->userFunctions[$this->normalizeName($name)] = $func; + } + + public function unregisterFunction($name) { + unset($this->userFunctions[$this->normalizeName($name)]); + } + + protected function importFile($path, $out) { + // see if tree is cached + $realPath = realpath($path); + if (isset($this->importCache[$realPath])) { + $tree = $this->importCache[$realPath]; + } else { + $code = file_get_contents($path); + $parser = new scss_parser($path); + $tree = $parser->parse($code); + $this->parsedFiles[] = $path; + + $this->importCache[$realPath] = $tree; + } + + $pi = pathinfo($path); + array_unshift($this->importPaths, $pi['dirname']); + $this->compileChildren($tree->children, $out); + array_shift($this->importPaths); + } + + // results the file path for an import url if it exists + protected function findImport($url) { + $urls = array(); + + // for "normal" scss imports (ignore vanilla css and external requests) + if (!preg_match('/\.css|^http:\/\/$/', $url)) { + // try both normal and the _partial filename + $urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url)); + } + + foreach ($this->importPaths as $dir) { + if (is_string($dir)) { + // check urls for normal import paths + foreach ($urls as $full) { + $full = $dir . + (!empty($dir) && substr($dir, -1) != '/' ? '/' : '') . + $full; + + if ($this->fileExists($file = $full.'.scss') || + $this->fileExists($file = $full)) + { + return $file; + } + } + } else { + // check custom callback for import path + $file = call_user_func($dir,$url,$this); + if ($file !== null) { + return $file; + } + } + } + + return null; + } + + protected function fileExists($name) { + return is_file($name); + } + + protected function callBuiltin($name, $args, &$returnValue) { + // try a lib function + $name = $this->normalizeName($name); + $libName = "lib_".$name; + $f = array($this, $libName); + $prototype = isset(self::$$libName) ? self::$$libName : null; + + if (is_callable($f)) { + $sorted = $this->sortArgs($prototype, $args); + foreach ($sorted as &$val) { + $val = $this->reduce($val, true); + } + $returnValue = call_user_func($f, $sorted, $this); + } else if (isset($this->userFunctions[$name])) { + // see if we can find a user function + $fn = $this->userFunctions[$name]; + + foreach ($args as &$val) { + $val = $this->reduce($val[1], true); + } + + $returnValue = call_user_func($fn, $args, $this); + } + + if (isset($returnValue)) { + // coerce a php value into a scss one + if (is_numeric($returnValue)) { + $returnValue = array('number', $returnValue, ""); + } elseif (is_bool($returnValue)) { + $returnValue = $returnValue ? self::$true : self::$false; + } elseif (!is_array($returnValue)) { + $returnValue = array('keyword', $returnValue); + } + + return true; + } + + return false; + } + + // sorts any keyword arguments + // TODO: merge with apply arguments + protected function sortArgs($prototype, $args) { + $keyArgs = array(); + $posArgs = array(); + + foreach ($args as $arg) { + list($key, $value) = $arg; + $key = $key[1]; + if (empty($key)) { + $posArgs[] = $value; + } else { + $keyArgs[$key] = $value; + } + } + + if (is_null($prototype)) return $posArgs; + + $finalArgs = array(); + foreach ($prototype as $i => $names) { + if (isset($posArgs[$i])) { + $finalArgs[] = $posArgs[$i]; + continue; + } + + $set = false; + foreach ((array)$names as $name) { + if (isset($keyArgs[$name])) { + $finalArgs[] = $keyArgs[$name]; + $set = true; + break; + } + } + + if (!$set) { + $finalArgs[] = null; + } + } + + return $finalArgs; + } + + protected function coerceForExpression($value) { + if ($color = $this->coerceColor($value)) { + return $color; + } + + return $value; + } + + protected function coerceColor($value) { + switch ($value[0]) { + case "color": return $value; + case "keyword": + $name = $value[1]; + if (isset(self::$cssColors[$name])) { + list($r, $g, $b) = explode(',', self::$cssColors[$name]); + return array('color', $r, $g, $b); + } + return null; + } + + return null; + } + + protected function coerceString($value) { + switch ($value[0]) { + case "string": + return $value; + case "keyword": + return array("string", "", array($value[1])); + } + return null; + } + + protected function assertColor($value) { + if ($color = $this->coerceColor($value)) return $color; + throw new exception("expecting color"); + } + + protected function assertNumber($value) { + if ($value[0] != "number") + throw new exception("expecting number"); + return $value[1]; + } + + protected function coercePercent($value) { + if ($value[0] == "number") { + if ($value[2] == "%") { + return $value[1] / 100; + } + return $value[1]; + } + return 0; + } + + // make sure a color's components don't go out of bounds + protected function fixColor($c) { + foreach (range(1, 3) as $i) { + if ($c[$i] < 0) $c[$i] = 0; + if ($c[$i] > 255) $c[$i] = 255; + } + + return $c; + } + + function toHSL($r, $g, $b) { + $r = $r / 255; + $g = $g / 255; + $b = $b / 255; + + $min = min($r, $g, $b); + $max = max($r, $g, $b); + + $L = ($min + $max) / 2; + if ($min == $max) { + $S = $H = 0; + } else { + if ($L < 0.5) + $S = ($max - $min)/($max + $min); + else + $S = ($max - $min)/(2.0 - $max - $min); + + if ($r == $max) $H = ($g - $b)/($max - $min); + elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); + elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); + + } + + return array('hsl', + ($H < 0 ? $H + 6 : $H)*60, + $S*100, + $L*100, + ); + } + + function toRGB_helper($comp, $temp1, $temp2) { + if ($comp < 0) $comp += 1.0; + elseif ($comp > 1) $comp -= 1.0; + + if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; + if (2 * $comp < 1) return $temp2; + if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; + + return $temp1; + } + + // H from 0 to 360, S and L from 0 to 100 + function toRGB($H, $S, $L) { + $H = $H % 360; + if ($H < 0) $H += 360; + + $S = min(100, max(0, $S)); + $L = min(100, max(0, $L)); + + $H = $H / 360; + $S = $S / 100; + $L = $L / 100; + + if ($S == 0) { + $r = $g = $b = $L; + } else { + $temp2 = $L < 0.5 ? + $L*(1.0 + $S) : + $L + $S - $L * $S; + + $temp1 = 2.0 * $L - $temp2; + + $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); + $g = $this->toRGB_helper($H, $temp1, $temp2); + $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); + } + + $out = array('color', $r*255, $g*255, $b*255); + return $out; + } + + // Built in functions + + protected static $lib_if = array("condition", "if-true", "if-false"); + protected function lib_if($args) { + list($cond,$t, $f) = $args; + if ($cond == self::$false) return $f; + return $t; + } + + protected static $lib_rgb = array("red", "green", "blue"); + protected function lib_rgb($args) { + list($r,$g,$b) = $args; + return array("color", $r[1], $g[1], $b[1]); + } + + protected static $lib_rgba = array( + array("red", "color"), + "green", "blue", "alpha"); + protected function lib_rgba($args) { + if ($color = $this->coerceColor($args[0])) { + $num = is_null($args[1]) ? $args[3] : $args[1]; + $alpha = $this->assertNumber($num); + $color[4] = $alpha; + return $color; + } + + list($r,$g,$b, $a) = $args; + return array("color", $r[1], $g[1], $b[1], $a[1]); + } + + // helper function for adjust_color, change_color, and scale_color + protected function alter_color($args, $fn) { + $color = $this->assertColor($args[0]); + + foreach (array(1,2,3,7) as $i) { + if (!is_null($args[$i])) { + $val = $this->assertNumber($args[$i]); + $ii = $i == 7 ? 4 : $i; // alpha + $color[$ii] = + $this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i); + } + } + + if (!is_null($args[4]) || !is_null($args[5]) || !is_null($args[6])) { + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + foreach (array(4,5,6) as $i) { + if (!is_null($args[$i])) { + $val = $this->assertNumber($args[$i]); + $hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i); + } + } + + $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); + if (isset($color[4])) $rgb[4] = $color[4]; + $color = $rgb; + } + + return $color; + } + + protected static $lib_adjust_color = array( + "color", "red", "green", "blue", + "hue", "saturation", "lightness", "alpha" + ); + protected function adjust_color_helper($base, $alter, $i) { + return $base += $alter; + } + protected function lib_adjust_color($args) { + return $this->alter_color($args, "adjust_color_helper"); + } + + protected static $lib_change_color = array( + "color", "red", "green", "blue", + "hue", "saturation", "lightness", "alpha" + ); + protected function change_color_helper($base, $alter, $i) { + return $alter; + } + protected function lib_change_color($args) { + return $this->alter_color($args, "change_color_helper"); + } + + protected static $lib_scale_color = array( + "color", "red", "green", "blue", + "hue", "saturation", "lightness", "alpha" + ); + protected function scale_color_helper($base, $scale, $i) { + // 1,2,3 - rgb + // 4, 5, 6 - hsl + // 7 - a + switch ($i) { + case 1: + case 2: + case 3: + $max = 255; break; + case 4: + $max = 360; break; + case 7: + $max = 1; break; + default: + $max = 100; + } + + $scale = $scale / 100; + if ($scale < 0) { + return $base * $scale + $base; + } else { + return ($max - $base) * $scale + $base; + } + } + protected function lib_scale_color($args) { + return $this->alter_color($args, "scale_color_helper"); + } + + protected static $lib_ie_hex_str = array("color"); + protected function lib_ie_hex_str($args) { + $color = $this->coerceColor($args[0]); + $color[4] = isset($color[4]) ? round(255*$color[4]) : 255; + + return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]); + } + + protected static $lib_red = array("color"); + protected function lib_red($args) { + list($color) = $args; + return $color[1]; + } + + protected static $lib_green = array("color"); + protected function lib_green($args) { + list($color) = $args; + return $color[2]; + } + + protected static $lib_blue = array("color"); + protected function lib_blue($args) { + list($color) = $args; + return $color[3]; + } + + protected static $lib_alpha = array("color"); + protected function lib_alpha($args) { + if ($color = $this->coerceColor($args[0])) { + return isset($color[4]) ? $color[4] : 1; + } + + // this might be the IE function, so return value unchanged + return array("function", "alpha", array("list", ",", $args)); + } + + protected static $lib_opacity = array("color"); + protected function lib_opacity($args) { + return $this->lib_alpha($args); + } + + // mix two colors + protected static $lib_mix = array("color-1", "color-2", "weight"); + protected function lib_mix($args) { + list($first, $second, $weight) = $args; + $first = $this->assertColor($first); + $second = $this->assertColor($second); + + if (is_null($weight)) { + $weight = 0.5; + } else { + $weight = $this->coercePercent($weight); + } + + $first_a = isset($first[4]) ? $first[4] : 1; + $second_a = isset($second[4]) ? $second[4] : 1; + + $w = $weight * 2 - 1; + $a = $first_a - $second_a; + + $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; + $w2 = 1.0 - $w1; + + $new = array('color', + $w1 * $first[1] + $w2 * $second[1], + $w1 * $first[2] + $w2 * $second[2], + $w1 * $first[3] + $w2 * $second[3], + ); + + if ($first_a != 1.0 || $second_a != 1.0) { + $new[] = $first_a * $weight + $second_a * ($weight - 1); + } + + return $this->fixColor($new); + } + + protected static $lib_hsl = array("hue", "saturation", "lightness"); + protected function lib_hsl($args) { + list($h, $s, $l) = $args; + return $this->toRGB($h[1], $s[1], $l[1]); + } + + protected static $lib_hsla = array("hue", "saturation", + "lightness", "alpha"); + protected function lib_hsla($args) { + list($h, $s, $l, $a) = $args; + $color = $this->toRGB($h[1], $s[1], $l[1]); + $color[4] = $a[1]; + return $color; + } + + protected static $lib_hue = array("color"); + protected function lib_hue($args) { + $color = $this->assertColor($args[0]); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + return array("number", $hsl[1], "deg"); + } + + protected static $lib_saturation = array("color"); + protected function lib_saturation($args) { + $color = $this->assertColor($args[0]); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + return array("number", $hsl[2], "%"); + } + + protected static $lib_lightness = array("color"); + protected function lib_lightness($args) { + $color = $this->assertColor($args[0]); + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + return array("number", $hsl[3], "%"); + } + + + protected function adjustHsl($color, $idx, $amount) { + $hsl = $this->toHSL($color[1], $color[2], $color[3]); + $hsl[$idx] += $amount; + $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); + if (isset($color[4])) $out[4] = $color[4]; + return $out; + } + + protected static $lib_adjust_hue = array("color", "degrees"); + protected function lib_adjust_hue($args) { + $color = $this->assertColor($args[0]); + $degrees = $this->assertNumber($args[1]); + return $this->adjustHsl($color, 1, $degrees); + } + + protected static $lib_lighten = array("color", "amount"); + protected function lib_lighten($args) { + $color = $this->assertColor($args[0]); + $amount = 100*$this->coercePercent($args[1]); + return $this->adjustHsl($color, 3, $amount); + } + + protected static $lib_darken = array("color", "amount"); + protected function lib_darken($args) { + $color = $this->assertColor($args[0]); + $amount = 100*$this->coercePercent($args[1]); + return $this->adjustHsl($color, 3, -$amount); + } + + protected static $lib_saturate = array("color", "amount"); + protected function lib_saturate($args) { + $color = $this->assertColor($args[0]); + $amount = 100*$this->coercePercent($args[1]); + return $this->adjustHsl($color, 2, $amount); + } + + protected static $lib_desaturate = array("color", "amount"); + protected function lib_desaturate($args) { + $color = $this->assertColor($args[0]); + $amount = 100*$this->coercePercent($args[1]); + return $this->adjustHsl($color, 2, -$amount); + } + + protected static $lib_grayscale = array("color"); + protected function lib_grayscale($args) { + return $this->adjustHsl($this->assertColor($args[0]), 2, -100); + } + + protected static $lib_complement = array("color"); + protected function lib_complement($args) { + return $this->adjustHsl($this->assertColor($args[0]), 1, 180); + } + + protected static $lib_invert = array("color"); + protected function lib_invert($args) { + $color = $this->assertColor($args[0]); + $color[1] = 255 - $color[1]; + $color[2] = 255 - $color[2]; + $color[3] = 255 - $color[3]; + return $color; + } + + + // increases opacity by amount + protected static $lib_opacify = array("color", "amount"); + protected function lib_opacify($args) { + $color = $this->assertColor($args[0]); + $amount = $this->coercePercent($args[1]); + + $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount; + $color[4] = min(1, max(0, $color[4])); + return $color; + } + + protected static $lib_fade_in = array("color", "amount"); + protected function lib_fade_in($args) { + return $this->lib_opacify($args); + } + + // decreases opacity by amount + protected static $lib_transparentize = array("color", "amount"); + protected function lib_transparentize($args) { + $color = $this->assertColor($args[0]); + $amount = $this->coercePercent($args[1]); + + $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount; + $color[4] = min(1, max(0, $color[4])); + return $color; + } + + protected static $lib_fade_out = array("color", "amount"); + protected function lib_fade_out($args) { + return $this->lib_transparentize($args); + } + + protected static $lib_unquote = array("string"); + protected function lib_unquote($args) { + $str = $args[0]; + if ($str[0] == "string") $str[1] = ""; + return $str; + } + + protected static $lib_quote = array("string"); + protected function lib_quote($args) { + $value = $args[0]; + if ($value[0] == "string" && !empty($value[1])) + return $value; + return array("string", '"', array($value)); + } + + protected static $lib_percentage = array("value"); + protected function lib_percentage($args) { + return array("number", + $this->coercePercent($args[0]) * 100, + "%"); + } + + protected static $lib_round = array("value"); + protected function lib_round($args) { + $num = $args[0]; + $num[1] = round($num[1]); + return $num; + } + + protected static $lib_floor = array("value"); + protected function lib_floor($args) { + $num = $args[0]; + $num[1] = floor($num[1]); + return $num; + } + + protected static $lib_ceil = array("value"); + protected function lib_ceil($args) { + $num = $args[0]; + $num[1] = ceil($num[1]); + return $num; + } + + protected static $lib_abs = array("value"); + protected function lib_abs($args) { + $num = $args[0]; + $num[1] = abs($num[1]); + return $num; + } + + protected function lib_min($args) { + $numbers = $this->getNormalizedNumbers($args); + $min = null; + foreach ($numbers as $key => $number) { + if (null === $min || $number <= $min[1]) { + $min = array($key, $number); + } + } + + return $args[$min[0]]; + } + + protected function lib_max($args) { + $numbers = $this->getNormalizedNumbers($args); + $max = null; + foreach ($numbers as $key => $number) { + if (null === $max || $number >= $max[1]) { + $max = array($key, $number); + } + } + + return $args[$max[0]]; + } + + protected function getNormalizedNumbers($args) { + $unit = null; + $originalUnit = null; + $numbers = array(); + foreach ($args as $key => $item) { + if ('number' != $item[0]) { + throw new Exception(sprintf('%s is not a number', $item[0])); + } + $number = $this->normalizeNumber($item); + + if (null === $unit) { + $unit = $number[2]; + } elseif ($unit !== $number[2]) { + throw new \Exception(sprintf('Incompatible units: "%s" and "%s".', $originalUnit, $item[2])); + } + + $originalUnit = $item[2]; + $numbers[$key] = $number[1]; + } + + return $numbers; + } + + protected static $lib_length = array("list"); + protected function lib_length($args) { + $list = $this->coerceList($args[0]); + return count($list[2]); + } + + protected static $lib_nth = array("list", "n"); + protected function lib_nth($args) { + $list = $this->coerceList($args[0]); + $n = $this->assertNumber($args[1]) - 1; + return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue; + } + + + protected function listSeparatorForJoin($list1, $sep) { + if (is_null($sep)) return $list1[1]; + switch ($this->compileValue($sep)) { + case "comma": + return ","; + case "space": + return ""; + default: + return $list1[1]; + } + } + + protected static $lib_join = array("list1", "list2", "separator"); + protected function lib_join($args) { + list($list1, $list2, $sep) = $args; + $list1 = $this->coerceList($list1, " "); + $list2 = $this->coerceList($list2, " "); + $sep = $this->listSeparatorForJoin($list1, $sep); + return array("list", $sep, array_merge($list1[2], $list2[2])); + } + + protected static $lib_append = array("list", "val", "separator"); + protected function lib_append($args) { + list($list1, $value, $sep) = $args; + $list1 = $this->coerceList($list1, " "); + $sep = $this->listSeparatorForJoin($list1, $sep); + return array("list", $sep, array_merge($list1[2], array($value))); + } + + + protected static $lib_type_of = array("value"); + protected function lib_type_of($args) { + $value = $args[0]; + switch ($value[0]) { + case "keyword": + if ($value == self::$true || $value == self::$false) { + return "bool"; + } + + if ($this->coerceColor($value)) { + return "color"; + } + + return "string"; + default: + return $value[0]; + } + } + + protected static $lib_unit = array("number"); + protected function lib_unit($args) { + $num = $args[0]; + if ($num[0] == "number") { + return array("string", '"', array($num[2])); + } + return ""; + } + + protected static $lib_unitless = array("number"); + protected function lib_unitless($args) { + $value = $args[0]; + return $value[0] == "number" && empty($value[2]); + } + + + protected static $lib_comparable = array("number-1", "number-2"); + protected function lib_comparable($args) { + return true; // TODO: THIS + } + + static protected $cssColors = array( + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'cyan' => '0,255,255', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkgrey' => '169,169,169', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'grey' => '128,128,128', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightgrey' => '211,211,211', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'magenta' => '255,0,255', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50' + ); +} + +class scss_parser { + static protected $precedence = array( + "or" => 0, + "and" => 1, + + '==' => 2, + '!=' => 2, + '<=' => 2, + '>=' => 2, + '=' => 2, + '<' => 3, + '>' => 2, + + '+' => 3, + '-' => 3, + '*' => 4, + '/' => 4, + '%' => 4, + ); + + static protected $operators = array("+", "-", "*", "/", "%", + "==", "!=", "<=", ">=", "<", ">", "and", "or"); + + static protected $operatorStr; + static protected $whitePattern; + static protected $commentMulti; + + static protected $commentSingle = "//"; + static protected $commentMultiLeft = "/*"; + static protected $commentMultiRight = "*/"; + + function __construct($sourceName = null) { + $this->sourceName = $sourceName; + + if (empty(self::$operatorStr)) { + self::$operatorStr = $this->makeOperatorStr(self::$operators); + + $commentSingle = $this->preg_quote(self::$commentSingle); + $commentMultiLeft = $this->preg_quote(self::$commentMultiLeft); + $commentMultiRight = $this->preg_quote(self::$commentMultiRight); + self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; + self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; + } + } + + static protected function makeOperatorStr($operators) { + return '('.implode('|', array_map(array('scss_parser','preg_quote'), + $operators)).')'; + } + + function parse($buffer) { + $this->count = 0; + $this->env = null; + $this->inParens = false; + $this->pushBlock(null); // root block + $this->eatWhiteDefault = true; + $this->insertComments = true; + + $this->buffer = $buffer; + + $this->whitespace(); + while (false !== $this->parseChunk()); + + if ($this->count != strlen($this->buffer)) + $this->throwParseError(); + + if (!empty($this->env->parent)) { + $this->throwParseError("unclosed block"); + } + + $this->env->isRoot = true; + return $this->env; + } + + protected function parseChunk() { + $s = $this->seek(); + + // the directives + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { + if ($this->literal("@media") && $this->mediaQueryList($mediaQueryList) && $this->literal("{")) { + $media = $this->pushSpecialBlock("media"); + $media->queryList = $mediaQueryList[2]; + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@mixin") && + $this->keyword($mixinName) && + ($this->argumentDef($args) || true) && + $this->literal("{")) + { + $mixin = $this->pushSpecialBlock("mixin"); + $mixin->name = $mixinName; + $mixin->args = $args; + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@include") && + $this->keyword($mixinName) && + ($this->literal("(") && + ($this->argValues($argValues) || true) && + $this->literal(")") || true) && + ($this->end() || + $this->literal("{") && $hasBlock = true)) + { + $child = array("include", + $mixinName, isset($argValues) ? $argValues : null, null); + + if (!empty($hasBlock)) { + $include = $this->pushSpecialBlock("include"); + $include->child = $child; + } else { + $this->append($child); + } + + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@import") && + $this->valueList($importPath) && + $this->end()) + { + $this->append(array("import", $importPath)); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@extend") && + $this->selectors($selector) && + $this->end()) + { + $this->append(array("extend", $selector)); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@function") && + $this->keyword($fn_name) && + $this->argumentDef($args) && + $this->literal("{")) + { + $func = $this->pushSpecialBlock("function"); + $func->name = $fn_name; + $func->args = $args; + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) { + $this->append(array("return", $retVal)); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@each") && + $this->variable($varName) && + $this->literal("in") && + $this->valueList($list) && + $this->literal("{")) + { + $each = $this->pushSpecialBlock("each"); + $each->var = $varName[1]; + $each->list = $list; + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@while") && + $this->expression($cond) && + $this->literal("{")) + { + $while = $this->pushSpecialBlock("while"); + $while->cond = $cond; + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@for") && + $this->variable($varName) && + $this->literal("from") && + $this->expression($start) && + ($this->literal("through") || + ($forUntil = true && $this->literal("to"))) && + $this->expression($end) && + $this->literal("{")) + { + $for = $this->pushSpecialBlock("for"); + $for->var = $varName[1]; + $for->start = $start; + $for->end = $end; + $for->until = isset($forUntil); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@if") && $this->valueList($cond) && $this->literal("{")) { + $if = $this->pushSpecialBlock("if"); + $if->cond = $cond; + $if->cases = array(); + return true; + } else { + $this->seek($s); + } + + if (($this->literal("@debug") || $this->literal("@warn")) && + $this->valueList($value) && + $this->end()) { + $this->append(array("debug", $value, $s)); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("@content") && $this->end()) { + $this->append(array("mixin_content")); + return true; + } else { + $this->seek($s); + } + + $last = $this->last(); + if (!is_null($last) && $last[0] == "if") { + list(, $if) = $last; + if ($this->literal("@else")) { + if ($this->literal("{")) { + $else = $this->pushSpecialBlock("else"); + } elseif ($this->literal("if") && $this->valueList($cond) && $this->literal("{")) { + $else = $this->pushSpecialBlock("elseif"); + $else->cond = $cond; + } + + if (isset($else)) { + $else->dontAppend = true; + $if->cases[] = $else; + return true; + } + } + + $this->seek($s); + } + + if ($this->literal("@charset") && + $this->valueList($charset) && $this->end()) + { + $this->append(array("charset", $charset)); + return true; + } else { + $this->seek($s); + } + + // doesn't match built in directive, do generic one + if ($this->literal("@", false) && $this->keyword($dirName) && + ($this->openString("{", $dirValue) || true) && + $this->literal("{")) + { + $directive = $this->pushSpecialBlock("directive"); + $directive->name = $dirName; + if (isset($dirValue)) $directive->value = $dirValue; + return true; + } + + $this->seek($s); + return false; + } + + // property shortcut + // captures most properties before having to parse a selector + if ($this->keyword($name, false) && + $this->literal(": ") && + $this->valueList($value) && + $this->end()) + { + $name = array("string", "", array($name)); + $this->append(array("assign", $name, $value)); + return true; + } else { + $this->seek($s); + } + + // variable assigns + if ($this->variable($name) && + $this->literal(":") && + $this->valueList($value) && $this->end()) + { + $defaultVar = false; + // check for !default + if ($value[0] == "list") { + $def = end($value[2]); + if ($def[0] == "keyword" && $def[1] == "!default") { + array_pop($value[2]); + $value = $this->flattenList($value); + $defaultVar = true; + } + } + $this->append(array("assign", $name, $value, $defaultVar)); + return true; + } else { + $this->seek($s); + } + + // misc + if ($this->literal("-->")) { + return true; + } + + // opening css block + $oldComments = $this->insertComments; + $this->insertComments = false; + if ($this->selectors($selectors) && $this->literal("{")) { + $this->pushBlock($selectors); + $this->insertComments = $oldComments; + return true; + } else { + $this->seek($s); + } + $this->insertComments = $oldComments; + + // property assign, or nested assign + if ($this->propertyName($name) && $this->literal(":")) { + $foundSomething = false; + if ($this->valueList($value)) { + $this->append(array("assign", $name, $value)); + $foundSomething = true; + } + + if ($this->literal("{")) { + $propBlock = $this->pushSpecialBlock("nestedprop"); + $propBlock->prefix = $name; + $foundSomething = true; + } elseif ($foundSomething) { + $foundSomething = $this->end(); + } + + if ($foundSomething) { + return true; + } + + $this->seek($s); + } else { + $this->seek($s); + } + + // closing a block + if ($this->literal("}")) { + $block = $this->popBlock(); + if (isset($block->type) && $block->type == "include") { + $include = $block->child; + unset($block->child); + $include[3] = $block; + $this->append($include); + } else if (empty($block->dontAppend)) { + $type = isset($block->type) ? $block->type : "block"; + $this->append(array($type, $block)); + } + return true; + } + + // extra stuff + if ($this->literal(";") || + $this->literal("<!--")) + { + return true; + } + + return false; + } + + protected function literal($what, $eatWhitespace = null) { + if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault; + + // this is here mainly prevent notice from { } string accessor + if ($this->count >= strlen($this->buffer)) return false; + + // shortcut on single letter + if (!$eatWhitespace && strlen($what) == 1) { + if ($this->buffer{$this->count} == $what) { + $this->count++; + return true; + } + else return false; + } + + return $this->match($this->preg_quote($what), $m, $eatWhitespace); + } + + // tree builders + + protected function pushBlock($selectors) { + $b = new stdclass; + $b->parent = $this->env; // not sure if we need this yet + + $b->selectors = $selectors; + $b->children = array(); + + $this->env = $b; + return $b; + } + + protected function pushSpecialBlock($type) { + $block = $this->pushBlock(null); + $block->type = $type; + return $block; + } + + protected function popBlock() { + if (empty($this->env->parent)) { + $this->throwParseError("unexpected }"); + } + + $old = $this->env; + $this->env = $this->env->parent; + unset($old->parent); + return $old; + } + + protected function append($statement) { + $this->env->children[] = $statement; + } + + // last child that was appended + protected function last() { + $i = count($this->env->children) - 1; + if (isset($this->env->children[$i])) + return $this->env->children[$i]; + } + + // high level parsers (they return parts of ast) + + protected function mediaQueryList(&$out) { + return $this->genericList($out, "mediaQuery", ",", false); + } + + protected function mediaQuery(&$out) { + $s = $this->seek(); + + $expressions = null; + $parts = array(); + + if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { + $prop = array("mediaType"); + if (isset($only)) $prop[] = "only"; + if (isset($not)) $prop[] = "not"; + $prop[] = $mediaType; + $parts[] = $prop; + } else { + $this->seek($s); + } + + + if (!empty($mediaType) && !$this->literal("and")) { + // ~ + } else { + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + } + + $out = $parts; + return true; + } + + protected function mediaExpression(&$out) { + $s = $this->seek(); + $value = null; + if ($this->literal("(") && + $this->keyword($feature) && + ($this->literal(":") && $this->expression($value) || true) && + $this->literal(")")) + { + $out = array("mediaExp", $feature); + if ($value) $out[] = $value; + return true; + } + + $this->seek($s); + return false; + } + + protected function argValues(&$out) { + if ($this->genericList($list, "argValue", ",", false)) { + $out = $list[2]; + return true; + } + return false; + } + + protected function argValue(&$out) { + $s = $this->seek(); + + $keyword = null; + if (!$this->variable($keyword) || !$this->literal(":")) { + $this->seek($s); + $keyword = null; + } + + if ($this->genericList($value, "expression")) { + $out = array($keyword, $value); + return true; + } + + return false; + } + + + protected function valueList(&$out) { + return $this->genericList($out, "commaList"); + } + + protected function commaList(&$out) { + return $this->genericList($out, "expression", ","); + } + + protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { + $s = $this->seek(); + $items = array(); + while ($this->$parseItem($value)) { + $items[] = $value; + if ($delim) { + if (!$this->literal($delim)) break; + } + } + + if (count($items) == 0) { + $this->seek($s); + return false; + } + + if ($flatten && count($items) == 1) { + $out = $items[0]; + } else { + $out = array("list", $delim, $items); + } + + return true; + } + + protected function expression(&$out) { + $s = $this->seek(); + + if ($this->literal("(")) { + if ($this->literal(")")) { + $out = array("list", "", array()); + return true; + } + + if ($this->valueList($out) && $this->literal(')') && $out[0] == "list") { + return true; + } + + $this->seek($s); + } + + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); + return true; + } + + return false; + } + + protected function expHelper($lhs, $minP) { + $opstr = self::$operatorStr; + + $ss = $this->seek(); + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + while ($this->match($opstr, $m) && self::$precedence[$m[1]] >= $minP) { + $whiteAfter = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + $op = $m[1]; + + // don't turn negative numbers into expressions + if ($op == "-" && $whiteBefore) { + if (!$whiteAfter) break; + } + + if (!$this->value($rhs)) break; + + // peek and see if rhs belongs to next operator + if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) { + $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + } + + $lhs = array("exp", $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter); + $ss = $this->seek(); + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + } + + $this->seek($ss); + return $lhs; + } + + protected function value(&$out) { + $s = $this->seek(); + + if ($this->literal("not", false) && $this->whitespace() && $this->value($inner)) { + $out = array("unary", "not", $inner, $this->inParens); + return true; + } else { + $this->seek($s); + } + + if ($this->literal("+") && $this->value($inner)) { + $out = array("unary", "+", $inner, $this->inParens); + return true; + } else { + $this->seek($s); + } + + // negation + if ($this->literal("-", false) && + ($this->variable($inner) || + $this->unit($inner) || + $this->parenValue($inner))) + { + $out = array("unary", "-", $inner, $this->inParens); + return true; + } else { + $this->seek($s); + } + + if ($this->parenValue($out)) return true; + if ($this->interpolation($out)) return true; + if ($this->variable($out)) return true; + if ($this->color($out)) return true; + if ($this->unit($out)) return true; + if ($this->string($out)) return true; + if ($this->func($out)) return true; + if ($this->progid($out)) return true; + + if ($this->keyword($keyword)) { + $out = array("keyword", $keyword); + return true; + } + + return false; + } + + // value wrappen in parentheses + protected function parenValue(&$out) { + $s = $this->seek(); + + $inParens = $this->inParens; + if ($this->literal("(") && + ($this->inParens = true) && $this->expression($exp) && + $this->literal(")")) + { + $out = $exp; + $this->inParens = $inParens; + return true; + } else { + $this->inParens = $inParens; + $this->seek($s); + } + + return false; + } + + protected function progid(&$out) { + $s = $this->seek(); + if ($this->literal("progid:", false) && + $this->openString("(", $fn) && + $this->literal("(")) + { + $this->openString(")", $args, "("); + if ($this->literal(")")) { + $out = array("string", "", array( + "progid:", $fn, "(", $args, ")" + )); + return true; + } + } + + $this->seek($s); + return false; + } + + protected function func(&$func) { + $s = $this->seek(); + + if ($this->keyword($name, false) && + $this->literal("(")) + { + if ($name != "expression" && false == preg_match("/^(-[a-z]+-)?calc$/", $name)) { + $ss = $this->seek(); + if ($this->argValues($args) && $this->literal(")")) { + $func = array("fncall", $name, $args); + return true; + } + $this->seek($ss); + } + + if (($this->openString(")", $str, "(") || true ) && + $this->literal(")")) + { + $args = array(); + if (!empty($str)) { + $args[] = array(null, array("string", "", array($str))); + } + + $func = array("fncall", $name, $args); + return true; + } + } + + $this->seek($s); + return false; + } + + protected function argumentDef(&$out) { + $s = $this->seek(); + $this->literal("("); + + $args = array(); + while ($this->variable($var)) { + $arg = array($var[1], null); + + $ss = $this->seek(); + if ($this->literal(":") && $this->expression($defaultVal)) { + $arg[1] = $defaultVal; + } else { + $this->seek($ss); + } + + $args[] = $arg; + if (!$this->literal(",")) break; + } + + if (!$this->literal(")")) { + $this->seek($s); + return false; + } + + $out = $args; + return true; + } + + protected function color(&$out) { + $color = array('color'); + + if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) { + if (isset($m[3])) { + $num = $m[3]; + $width = 16; + } else { + $num = $m[2]; + $width = 256; + } + + $num = hexdec($num); + foreach (array(3,2,1) as $i) { + $t = $num % $width; + $num /= $width; + + $color[$i] = $t * (256/$width) + $t * floor(16/$width); + } + + $out = $color; + return true; + } + + return false; + } + + protected function unit(&$unit) { + if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) { + $unit = array("number", $m[1], empty($m[3]) ? "" : $m[3]); + return true; + } + return false; + } + + protected function string(&$out) { + $s = $this->seek(); + if ($this->literal('"', false)) { + $delim = '"'; + } elseif ($this->literal("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = array(); + + // look for either ending delim , escape, or string interpolation + $patt = '([^\n]*?)(#\{|\\\\|' . + $this->preg_quote($delim).')'; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while ($this->match($patt, $m, false)) { + $content[] = $m[1]; + if ($m[2] == "#{") { + $this->count -= strlen($m[2]); + if ($this->interpolation($inter, false)) { + $content[] = $inter; + } else { + $this->count += strlen($m[2]); + $content[] = "#{"; // ignore it + } + } elseif ($m[2] == '\\') { + $content[] = $m[2]; + if ($this->literal($delim, false)) { + $content[] = $delim; + } + } else { + $this->count -= strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim)) { + $out = array("string", $delim, $content); + return true; + } + + $this->seek($s); + return false; + } + + protected function mixedKeyword(&$out) { + $s = $this->seek(); + + $parts = array(); + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while (true) { + if ($this->keyword($key)) { + $parts[] = $key; + continue; + } + + if ($this->interpolation($inter)) { + $parts[] = $inter; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($parts) == 0) return false; + + $out = $parts; + return true; + } + + // an unbounded string stopped by $end + protected function openString($end, &$out, $nestingOpen=null) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $stop = array("'", '"', "#{", $end); + $stop = array_map(array($this, "preg_quote"), $stop); + $stop[] = self::$commentMulti; + + $patt = '(.*?)('.implode("|", $stop).')'; + + $nestingLevel = 0; + + $content = array(); + while ($this->match($patt, $m, false)) { + if (!empty($m[1])) { + $content[] = $m[1]; + if ($nestingOpen) { + $nestingLevel += substr_count($m[1], $nestingOpen); + } + } + + $tok = $m[2]; + + $this->count-= strlen($tok); + if ($tok == $end) { + if ($nestingLevel == 0) { + break; + } else { + $nestingLevel--; + } + } + + if (($tok == "'" || $tok == '"') && $this->string($str)) { + $content[] = $str; + continue; + } + + if ($tok == "#{" && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + $content[] = $tok; + $this->count+= strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($content) == 0) return false; + + // trim the end + if (is_string(end($content))) { + $content[count($content) - 1] = rtrim(end($content)); + } + + $out = array("string", "", $content); + return true; + } + + // $lookWhite: save information about whitespace before and after + protected function interpolation(&$out, $lookWhite=true) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = true; + + $s = $this->seek(); + if ($this->literal("#{") && $this->valueList($value) && $this->literal("}", false)) { + + // TODO: don't error if out of bounds + + if ($lookWhite) { + $left = preg_match('/\s/', $this->buffer[$s - 1]) ? " " : ""; + $right = preg_match('/\s/', $this->buffer[$this->count]) ? " ": ""; + } else { + $left = $right = false; + } + + $out = array("interpolate", $value, $left, $right); + $this->eatWhiteDefault = $oldWhite; + if ($this->eatWhiteDefault) $this->whitespace(); + return true; + } + + $this->seek($s); + $this->eatWhiteDefault = $oldWhite; + return false; + } + + // low level parsers + + // returns an array of parts or a string + protected function propertyName(&$out) { + $s = $this->seek(); + $parts = array(); + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while (true) { + if ($this->interpolation($inter)) { + $parts[] = $inter; + } elseif ($this->keyword($text)) { + $parts[] = $text; + } elseif (count($parts) == 0 && $this->match('[:.#]', $m, false)) { + // css hacks + $parts[] = $m[0]; + } else { + break; + } + } + + $this->eatWhiteDefault = $oldWhite; + if (count($parts) == 0) return false; + + // match comment hack + if (preg_match(self::$whitePattern, + $this->buffer, $m, null, $this->count)) + { + if (!empty($m[0])) { + $parts[] = $m[0]; + $this->count += strlen($m[0]); + } + } + + $this->whitespace(); // get any extra whitespace + + $out = array("string", "", $parts); + return true; + } + + // comma separated list of selectors + protected function selectors(&$out) { + $s = $this->seek(); + $selectors = array(); + while ($this->selector($sel)) { + $selectors[] = $sel; + if (!$this->literal(",")) break; + while ($this->literal(",")); // ignore extra + } + + if (count($selectors) == 0) { + $this->seek($s); + return false; + } + + $out = $selectors; + return true; + } + + // whitepsace separated list of selectorSingle + protected function selector(&$out) { + $selector = array(); + + while (true) { + if ($this->match('[>+~]+', $m)) { + $selector[] = array($m[0]); + } elseif ($this->selectorSingle($part)) { + $selector[] = $part; + $this->whitespace(); + } else { + break; + } + + } + + if (count($selector) == 0) { + return false; + } + + $out = $selector; + return true; + } + + // the parts that make up + // div[yes=no]#something.hello.world:nth-child(-2n+1) + protected function selectorSingle(&$out) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $parts = array(); + + if ($this->literal("*", false)) { + $parts[] = "*"; + } + + while (true) { + // see if we can stop early + if ($this->match("\s*[{,]", $m)) { + $this->count--; + break; + } + + $s = $this->seek(); + // self + if ($this->literal("&", false)) { + $parts[] = scssc::$selfSelector; + continue; + } + + if ($this->literal(".", false)) { + $parts[] = "."; + continue; + } + + if ($this->literal("|", false)) { + $parts[] = "|"; + continue; + } + + // for keyframes + if ($this->unit($unit)) { + $parts[] = $unit; + continue; + } + + if ($this->keyword($name)) { + $parts[] = $name; + continue; + } + + if ($this->interpolation($inter)) { + $parts[] = $inter; + continue; + } + + if ($this->literal("#", false)) { + $parts[] = "#"; + continue; + } + + // a pseudo selector + if ($this->match("::?", $m) && $this->mixedKeyword($nameParts)) { + $parts[] = $m[0]; + foreach ($nameParts as $sub) { + $parts[] = $sub; + } + + $ss = $this->seek(); + if ($this->literal("(") && + ($this->openString(")", $str, "(") || true ) && + $this->literal(")")) + { + $parts[] = "("; + if (!empty($str)) $parts[] = $str; + $parts[] = ")"; + } else { + $this->seek($ss); + } + + continue; + } else { + $this->seek($s); + } + + // attribute selector + // TODO: replace with open string? + if ($this->literal("[", false)) { + $attrParts = array("["); + // keyword, string, operator + while (true) { + if ($this->literal("]", false)) { + $this->count--; + break; // get out early + } + + if ($this->match('\s+', $m)) { + $attrParts[] = " "; + continue; + } + if ($this->string($str)) { + $attrParts[] = $str; + continue; + } + + if ($this->keyword($word)) { + $attrParts[] = $word; + continue; + } + + if ($this->interpolation($inter, false)) { + $attrParts[] = $inter; + continue; + } + + // operator, handles attr namespace too + if ($this->match('[|-~\$\*\^=]+', $m)) { + $attrParts[] = $m[0]; + continue; + } + + break; + } + + if ($this->literal("]", false)) { + $attrParts[] = "]"; + foreach ($attrParts as $part) { + $parts[] = $part; + } + continue; + } + $this->seek($s); + // should just break here? + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($parts) == 0) return false; + + $out = $parts; + return true; + } + + protected function variable(&$out) { + $s = $this->seek(); + if ($this->literal("$", false) && $this->keyword($name)) { + $out = array("var", $name); + return true; + } + $this->seek($s); + return false; + } + + protected function keyword(&$word, $eatWhitespace = null) { + if ($this->match('([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)', + $m, $eatWhitespace)) + { + $word = $m[1]; + return true; + } + return false; + } + + // consume an end of statement delimiter + protected function end() { + if ($this->literal(';')) { + return true; + } elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + return false; + } + + // advance counter to next occurrence of $what + // $until - don't include $what in advance + // $allowNewline, if string, will be used as valid char set + protected function to($what, &$out, $until = false, $allowNewline = false) { + if (is_string($allowNewline)) { + $validChars = $allowNewline; + } else { + $validChars = $allowNewline ? "." : "[^\n]"; + } + if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false; + if ($until) $this->count -= strlen($what); // give back $what + $out = $m[1]; + return true; + } + + protected function throwParseError($msg = "parse error", $count = null) { + $count = is_null($count) ? $this->count : $count; + + $line = $this->getLineNo($count); + + if (!empty($this->sourceName)) { + $loc = "$this->sourceName on line $line"; + } else { + $loc = "line: $line"; + } + + if ($this->peek("(.*?)(\n|$)", $m, $count)) { + throw new exception("$msg: failed at `$m[1]` $loc"); + } else { + throw new exception("$msg: $loc"); + } + } + + public function getLineNo($pos) { + return 1 + substr_count(substr($this->buffer, 0, $pos), "\n"); + } + + // try to match something on head of buffer + protected function match($regex, &$out, $eatWhitespace = null) { + if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault; + + $r = '/'.$regex.'/Ais'; + if (preg_match($r, $this->buffer, $out, null, $this->count)) { + $this->count += strlen($out[0]); + if ($eatWhitespace) $this->whitespace(); + return true; + } + return false; + } + + // match some whitespace + protected function whitespace() { + $gotWhite = false; + while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + if ($this->insertComments) { + if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { + $this->append(array("comment", $m[1])); + $this->commentsSeen[$this->count] = true; + } + } + $this->count += strlen($m[0]); + $gotWhite = true; + } + return $gotWhite; + } + + protected function peek($regex, &$out, $from=null) { + if (is_null($from)) $from = $this->count; + + $r = '/'.$regex.'/Ais'; + $result = preg_match($r, $this->buffer, $out, null, $from); + + return $result; + } + + protected function seek($where = null) { + if ($where === null) return $this->count; + else $this->count = $where; + return true; + } + + static function preg_quote($what) { + return preg_quote($what, '/'); + } + + protected function show() { + if ($this->peek("(.*?)(\n|$)", $m, $this->count)) { + return $m[1]; + } + return ""; + } + + // turn list of length 1 into value type + protected function flattenList($value) { + if ($value[0] == "list" && count($value[2]) == 1) { + return $this->flattenList($value[2][0]); + } + return $value; + } +} + +class scss_formatter { + public $indentChar = " "; + + public $break = "\n"; + public $open = " {"; + public $close = "}"; + public $tagSeparator = ", "; + public $assignSeparator = ": "; + + public function __construct() { + $this->indentLevel = 0; + } + + public function indentStr($n = 0) { + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + public function property($name, $value) { + return $name . $this->assignSeparator . $value . ";"; + } + + public function block($block) { + if (empty($block->lines) && empty($block->children)) return; + + $inner = $pre = $this->indentStr(); + + if (!empty($block->selectors)) { + echo $pre . + implode($this->tagSeparator, $block->selectors) . + $this->open . $this->break; + $this->indentLevel++; + $inner = $this->indentStr(); + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!empty($block->children)) { + echo $this->break; + } + } + + foreach ($block->children as $child) { + $this->block($child); + } + + if (!empty($block->selectors)) { + $this->indentLevel--; + if (empty($block->children)) echo $this->break; + echo $pre . $this->close . $this->break; + } + } +} + + +class scss_formatter_nested extends scss_formatter { + public $close = " }"; + + // adjust the depths of all children, depth first + public function adjustAllChildren($block) { + // flatten empty nested blocks + $children = array(); + foreach ($block->children as $i => $child) { + if (empty($child->lines) && empty($child->children)) { + if (isset($block->children[$i + 1])) { + $block->children[$i + 1]->depth = $child->depth; + } + continue; + } + $children[] = $child; + } + $block->children = $children; + + // make relative to parent + foreach ($block->children as $child) { + $this->adjustAllChildren($child); + $child->depth = $child->depth - $block->depth; + } + } + + public function block($block) { + if ($block->type == "root") { + $this->adjustAllChildren($block); + } + + $inner = $pre = $this->indentStr($block->depth - 1); + if (!empty($block->selectors)) { + echo $pre . + implode($this->tagSeparator, $block->selectors) . + $this->open . $this->break; + $this->indentLevel++; + $inner = $this->indentStr($block->depth - 1); + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!empty($block->children)) echo $this->break; + } + + foreach ($block->children as $i => $child) { + // echo "*** block: ".$block->depth." child: ".$child->depth."\n"; + $this->block($child); + if ($i < count($block->children) - 1) { + echo $this->break; + + if (isset($block->children[$i + 1])) { + $next = $block->children[$i + 1]; + if ($next->depth == max($block->depth, 1) && $child->depth >= $next->depth) { + echo $this->break; + } + } + } + } + + if (!empty($block->selectors)) { + $this->indentLevel--; + echo $this->close; + } + + if ($block->type == "root") { + echo $this->break; + } + } +} + +class scss_formatter_compressed extends scss_formatter { + public $open = "{"; + public $tagSeparator = ","; + public $assignSeparator = ":"; + public $break = ""; + + public function indentStr($n = 0) { + return ""; + } +} + +class scss_server { + + protected function join($left, $right) { + return rtrim($left, "/") . "/" . ltrim($right, "/"); + } + + protected function inputName() { + if (isset($_GET["p"])) return $_GET["p"]; + + if (isset($_SERVER["PATH_INFO"])) return $_SERVER["PATH_INFO"]; + if (isset($_SERVER["DOCUMENT_URI"])) { + return substr($_SERVER["DOCUMENT_URI"], strlen($_SERVER["SCRIPT_NAME"])); + } + } + + protected function findInput() { + if ($input = $this->inputName()) { + $name = $this->join($this->dir, $input); + if (is_readable($name)) return $name; + } + return false; + } + + protected function cacheName($fname) { + return $this->join($this->cacheDir, md5($fname) . ".css"); + } + + protected function importsCacheName($out) { + return $out . ".imports"; + } + + protected function needsCompile($in, $out) { + if (!is_file($out)) return true; + + $mtime = filemtime($out); + if (filemtime($in) > $mtime) return true; + + // look for modified imports + $icache = $this->importsCacheName($out); + if (is_readable($icache)) { + $imports = unserialize(file_get_contents($icache)); + foreach ($imports as $import) { + if (filemtime($import) > $mtime) return true; + } + } + return false; + } + + protected function compile($in, $out) { + $start = microtime(true); + $css = $this->scss->compile(file_get_contents($in), $in); + $elapsed = round((microtime(true) - $start), 4); + + $v = scssc::$VERSION; + $t = date("r"); + $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css; + + file_put_contents($out, $css); + file_put_contents($this->importsCacheName($out), + serialize($this->scss->getParsedFiles())); + return $css; + } + + public function serve() { + if ($input = $this->findInput()) { + $output = $this->cacheName($input); + header("Content-type: text/css"); + + if ($this->needsCompile($input, $output)) { + try { + echo $this->compile($input, $output); + } catch (exception $e) { + header('HTTP/1.1 500 Internal Server Error'); + echo "Parse error: " . $e->getMessage() . "\n"; + } + } else { + header('X-SCSS-Cache: true'); + echo file_get_contents($output); + } + + return; + } + + header('HTTP/1.0 404 Not Found'); + header("Content-type: text"); + $v = scssc::$VERSION; + echo "/* INPUT NOT FOUND scss $v */\n"; + } + + public function __construct($dir, $cacheDir=null, $scss=null) { + $this->dir = $dir; + + if (is_null($cacheDir)) { + $cacheDir = $this->join($dir, "scss_cache"); + } + + $this->cacheDir = $cacheDir; + if (!is_dir($this->cacheDir)) mkdir($this->cacheDir); + + if (is_null($scss)) { + $scss = new scssc(); + $scss->setImportPaths($this->dir); + } + $this->scss = $scss; + } + + static public function serveFrom($path) { + $server = new self($path); + $server->serve(); + } +} + + diff --git a/plugins/jetpack/modules/enhanced-distribution.php b/plugins/jetpack/modules/enhanced-distribution.php index c3bc7aaf..b2a80155 100644 --- a/plugins/jetpack/modules/enhanced-distribution.php +++ b/plugins/jetpack/modules/enhanced-distribution.php @@ -6,4 +6,25 @@ * First Introduced: 1.2 */ -// Stub +Jetpack_Sync::sync_posts( __FILE__ ); +Jetpack_Sync::sync_comments( __FILE__ ); + +function jetpack_enhanced_distribution_activate() { + Jetpack::check_privacy( __FILE__ ); +} + + +// In case it's active prior to upgrading to 1.9 +function jetpack_enhanced_distribution_before_activate_default_modules() { + $old_version = Jetpack::get_option( 'old_version' ); + list( $old_version ) = explode( ':', $old_version ); + + if ( version_compare( $old_version, '1.9-something', '>=' ) ) { + return; + } + + Jetpack::check_privacy( __FILE__ ); +} + +add_action( 'jetpack_activate_module_enhanced-distribution', 'jetpack_enhanced_distribution_activate' ); +add_action( 'jetpack_before_activate_default_modules', 'jetpack_enhanced_distribution_before_activate_default_modules' ); diff --git a/plugins/jetpack/modules/featured-content/featured-content.php b/plugins/jetpack/modules/featured-content/featured-content.php new file mode 100644 index 00000000..9cd511ed --- /dev/null +++ b/plugins/jetpack/modules/featured-content/featured-content.php @@ -0,0 +1,454 @@ +<?php + +/** + * Featured Content. + * + * This module will allow users to define a subset of posts + * to be displayed in a theme-designated featured content area. + * + * This feature will only be activated for themes that declare + * that they support it. This can be done by adding code similar + * to the following during the "after_setup_theme" action: + * + * add_theme_support( 'featured-content', array( + * 'featured_content_filter' => 'mytheme_get_featured_content', + * 'description' => 'Describe the featured content area.', + * 'max_posts' => 20, + * ) ); + * + * For maximum compatibility with different methods of posting + * users will designate a featured post tag to associate posts + * to. Since this tag now has special meaning beyond that of a + * normal tags, users will have the ability to hide it from the + * front-end of their site. + */ +class Featured_Content { + + /** + * The maximum number of posts that a featured content + * area can contain. We define a default value here but + * themes can override this by defining a "max_posts" + * entry in the second parameter passed in the call to + * add_theme_support( 'featured-content' ). + * + * @see Featured_Content::init() + */ + public static $max_posts = 15; + + /** + * Instantiate. + * + * All custom functionality will be hooked into the "init" action. + */ + public static function setup() { + add_action( 'init', array( __class__, 'init' ) ); + } + + /** + * Conditionally Hook into WordPress. + * + * Themes must declare that they support this module by adding + * add_theme_support( 'featured-content' ); during after_setup_theme. + * + * If no theme support is found there is no need to hook into + * WordPress. We'll just return early instead. + * + * @uses Featured_Content::$max_posts + */ + public static function init() { + $theme_support = get_theme_support( 'featured-content' ); + + // Return early if theme does not support featured content. + if ( ! $theme_support ) + return; + + // An array of named arguments must be passed as + // the second parameter of add_theme_support(). + if ( ! isset( $theme_support[0] ) ) + return; + + // Return early if "featured_content_filter" has not been defined. + if ( ! isset( $theme_support[0]['featured_content_filter'] ) ) + return; + + $filter = $theme_support[0]['featured_content_filter']; + + // Themes can override the number of max posts. + if ( isset( $theme_support[0]['max_posts'] ) ) + self::$max_posts = absint( $theme_support[0]['max_posts'] ); + + add_filter( $filter, array( __CLASS__, 'get_featured_posts' ) ); + add_action( 'admin_init', array( __CLASS__, 'register_setting' ) ); + add_action( 'save_post', array( __CLASS__, 'delete_transient' ) ); + add_action( 'delete_post_tag', array( __CLASS__, 'delete_post_tag' ) ); + add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) ); + + // Hide "featured" tag from the front-end. + if ( self::get_setting( 'hide-tag' ) ) { + add_filter( 'get_terms', array( __CLASS__, 'hide_featured_term' ), 10, 2 ); + add_filter( 'get_the_terms', array( __CLASS__, 'hide_the_featured_term' ), 10, 3 ); + } + } + + /** + * Get Featured Posts. + * + * This method is not intended to be called directly. + * Theme developers should place a filter directly in + * their theme and then pass its name as a value of the + * "featured_content_filter" key in the array passed as + * the $args parameter during the call to: + * add_theme_support( 'featured-content', $args ). + * + * @uses Featured_Content::get_featured_post_ids() + * + * @return array|bool + */ + public static function get_featured_posts() { + $post_ids = self::get_featured_post_ids(); + + // User has disabled Featured Content. + if ( false === $post_ids ) + return false; + + // No need to query if there is are no featured posts. + if ( empty( $post_ids ) ) + return array(); + + $featured_posts = get_posts( array( + 'include' => $post_ids, + 'posts_per_page' => count( $post_ids ) + ) ); + + return $featured_posts; + } + + /** + * Get Featured Post IDs. + * + * This function will return the an array containing the + * post IDs of all featured posts. + * + * Sets the "featured_content_ids" transient. + * + * @return array|false Array of post IDs. false if user has disabled this feature. + */ + public static function get_featured_post_ids() { + $settings = self::get_setting(); + + // Return false if the user has disabled this feature. + $tag = $settings['tag-id']; + if ( empty( $tag ) ) + return false; + + // Return array of cached results if they exist. + $featured_ids = get_transient( 'featured_content_ids' ); + if ( ! empty( $featured_ids ) ) + return array_map( 'absint', (array) $featured_ids ); + + // Query for featured posts. + $featured = get_posts( array( + 'numberposts' => $settings['quantity'], + 'tax_query' => array( + array( + 'field' => 'term_id', + 'taxonomy' => 'post_tag', + 'terms' => $tag, + ), + ), + ) ); + + // Return empty array if no featured content exists. + if ( ! $featured ) + return array(); + + // Ensure correct format before save/return. + $featured_ids = wp_list_pluck( (array) $featured, 'ID' ); + $featured_ids = array_map( 'absint', $featured_ids ); + + set_transient( 'featured_content_ids', $featured_ids ); + + return $featured_ids; + } + + /** + * Delete Transient. + * + * Hooks in the "save_post" action. + * @see Featured_Content::validate_settings(). + */ + public static function delete_transient() { + delete_transient( 'featured_content_ids' ); + } + + /** + * Exclude featured posts from the blog query when the blog is the front-page. + * + * Filter the home page posts, and remove any featured post ID's from it. Hooked + * onto the 'pre_get_posts' action, this changes the parameters of the query + * before it gets any posts. + * + * @uses Featured_Content::get_featured_post_ids(); + * @param WP_Query $query + * @return WP_Query Possibly modified WP_query + */ + public static function pre_get_posts( $query = false ) { + + // Bail if not home, not a query, not main query. + if ( ! is_home() || ! is_a( $query, 'WP_Query' ) || ! $query->is_main_query() ) + return; + + $page_on_front = get_option( 'page_on_front' ); + + // Bail if the blog page is not the front page. + if ( ! empty( $page_on_front ) ) + return; + + $featured = self::get_featured_post_ids(); + + // Bail if no featured posts. + if ( ! $featured ) + return; + + // We need to respect post ids already in the blacklist. + $post__not_in = $query->get( 'post__not_in' ); + + if ( ! empty( $post__not_in ) ) { + $featured = array_merge( (array) $post__not_in, $featured ); + $featured = array_unique( $featured ); + } + + $query->set( 'post__not_in', $featured ); + } + + /** + * Reset tag option when the saved tag is deleted. + * + * It's important to mention that the transient needs + * to be deleted too. While it may not be obvious by + * looking at the function alone, the transient is + * deleted by Featured_Content::validate_settings(). + * + * @param int $tag_id the term_id of the tag that has been deleted. + * + * Hooks in the "delete_post_tag" action. + * @see Featured_Content::validate_settings(). + */ + public static function delete_post_tag( $tag_id ) { + $settings = self::get_setting(); + + if ( empty( $settings['tag-id'] ) ) + return; + + if ( $tag_id != $settings['tag-id'] ) + return; + + $settings['tag-id'] = 0; + $settings = self::validate_settings( $settings ); + update_option( 'featured-content', $settings ); + } + + /** + * Hide featured tag from displaying when global terms are queried from the front-end. + * + * Hooks into the "get_terms" filter. + * + * @param array $terms A list of term objects. This is the return value of get_terms(). + * @param array $taxonomies An array of taxonomy slugs. + * + * @uses Featured_Content::get_setting() + */ + public static function hide_featured_term( $terms, $taxonomies ) { + + // This filter is only appropriate on the front-end. + if ( is_admin() ) + return $terms; + + // We only want to hide the featured tag. + if ( ! in_array( 'post_tag', $taxonomies ) ) + return $terms; + + // Bail if no terms were returned. + if ( empty( $terms ) ) + return $terms; + + foreach( $terms as $order => $term ) { + if ( self::get_setting( 'tag-id' ) == $term->term_id && 'post_tag' == $term->taxonomy ) + unset( $terms[$order] ); + } + + return $terms; + } + + /** + * Hide featured tag from displaying when terms associated with + * a post object are queried from the front-end. + * + * Hooks into the "get_the_terms" filter. + * + * @param array $terms A list of term objects. This is the return value of get_the_terms(). + * @param int $id The ID field for the post object that terms are associated with. + * @param array $taxonomy An array of taxonomy slugs. + * + * @uses Featured_Content::get_setting() + */ + public static function hide_the_featured_term( $terms, $id, $taxonomy ) { + + // This filter is only appropriate on the front-end. + if ( is_admin() ) + return $terms; + + // Make sure we are in the correct taxonomy. + if ( ! 'post_tag' == $taxonomy ) + return $terms; + + // No terms? Return early! + if ( empty( $terms ) ) + return $terms; + + foreach( $terms as $order => $term ) { + if ( self::get_setting( 'tag-id' ) == $term->term_id ) + unset( $terms[$term->term_id] ); + } + + return $terms; + } + + /** + * Register custom setting on the Settings -> Reading screen. + * + * @uses Featured_Content::render_form() + * @uses Featured_Content::validate_settings() + */ + public static function register_setting() { + add_settings_field( 'featured-content', __( 'Featured content', 'jetpack' ), array( __class__, 'render_form' ), 'reading' ); + register_setting( 'reading', 'featured-content', array( __class__, 'validate_settings' ) ); + } + + /** + * Renders all form fields on the Settings -> Reading screen. + */ + public static function render_form() { + $settings = self::get_setting(); + + $tag_name = ''; + if ( ! empty( $settings['tag-id'] ) ) { + $tag = get_term( $settings['tag-id'], 'post_tag' ); + if ( ! is_wp_error( $tag ) && isset( $tag->name ) ) + $tag_name = $tag->name; + } + ?> + <div id="featured-content-ui"> + <p> + <label for="featured-content-tag-name"><?php echo _e( 'Tag name:', 'jetpack' ); ?></label> + <input type="text" id="featured-content-tag-name" name="featured-content[tag-name]" value="<?php echo esc_attr( $tag_name ); ?>"> + <input type="hidden" id="featured-content-tag-id" name="featured-content[tag-id]" value="<?php echo esc_attr( $settings['tag-id'] ); ?>"> + </p> + <p> + <label for="featured-content-quantity"><?php _e( 'Number of posts:', 'jetpack' ); ?></label> + <input class="small-text" type="number" step="1" min="1" max="<?php echo esc_attr( self::$max_posts ); ?>" id="featured-content-quantity" name="featured-content[quantity]" value="<?php echo esc_attr( $settings['quantity'] ); ?>"> + </p> + <p> + <input type="checkbox" id="featured-content-hide-tag" name="featured-content[hide-tag]" <?php checked( $settings['hide-tag'], 1 ); ?>"> + <label for="featured-content-hide-tag"><?php _e( 'Hide tag from displaying in post meta and tag clouds.', 'jetpack' ); ?></label> + </p> + </div> + <?php + } + + /** + * Get Settings. + * + * Get all settings recognized by this module. This + * function will return all settings whether or not + * they have been stored in the database yet. This + * ensures that all keys are available at all times. + * + * In the event that you only require one setting, you + * may pass its name as the first parameter to the function + * and only that value will be returned. + * + * @uses Featured_Content::self::sanitize_quantity() + * + * @param string $key The key of a recognized setting. + * @return mixed Array of all settings by default. A single value if passed as first parameter. + */ + public static function get_setting( $key = 'all' ) { + $saved = (array) get_option( 'featured-content' ); + + $defaults = array( + 'hide-tag' => 1, + 'quantity' => 5, + 'tag-id' => 0, + ); + + $options = wp_parse_args( $saved, $defaults ); + $options = array_intersect_key( $options, $defaults ); + $options['quantity'] = self::sanitize_quantity( $options['quantity'] ); + + if ( 'all' != $key ) { + if ( isset( $options[$key] ) ) + return $options[$key]; + else + return false; + } + + return $options; + } + + /** + * Validate Settings. + * + * Make sure that all user supplied content is in an + * expected format before saving to the database. This + * function will also delete the transient set in + * Featured_Content::get_featured_content(). + * + * @uses Featured_Content::self::sanitize_quantity() + * @uses Featured_Content::self::delete_transient() + */ + function validate_settings( $input ) { + $output = array(); + + if ( isset( $input['tag-id'] ) ) + $output['tag-id'] = absint( $input['tag-id'] ); + + if ( isset( $input['tag-name'] ) ) { + $new_tag = wp_create_tag( $input['tag-name'] ); + if ( ! is_wp_error( $new_tag ) && isset( $new_tag['term_id'] ) ) + $tag = get_term( $new_tag['term_id'], 'post_tag' ); + if ( isset( $tag->term_id ) ) + $output['tag-id'] = $tag->term_id; + } + + if ( isset( $input['quantity'] ) ) + $output['quantity'] = self::sanitize_quantity( $input['quantity'] ); + + $output['hide-tag'] = ( isset( $input['hide-tag'] ) ) ? 1 : 0; + + self::delete_transient(); + + return $output; + } + + /** + * Sanitize Quantity + * + * @param int $input The value to sanitize. + * @output int A number between 1 and FeaturedContent::$max_posts. + * + * @uses Featured_Content::$max_posts + */ + public static function sanitize_quantity( $input ) { + $quantity = absint( $input ); + + if ( $quantity > self::$max_posts ) + $quantity = self::$max_posts; + else if ( 1 > $quantity ) + $quantity = 1; + + return $quantity; + } +} + +Featured_Content::setup(); diff --git a/plugins/jetpack/modules/gravatar-hovercards.php b/plugins/jetpack/modules/gravatar-hovercards.php index 3678837f..9edd15a5 100644 --- a/plugins/jetpack/modules/gravatar-hovercards.php +++ b/plugins/jetpack/modules/gravatar-hovercards.php @@ -6,13 +6,13 @@ * First Introduced: 1.1 */ -define( 'GROFILES__CACHE_BUSTER', 'aa' ); // Break CDN cache, increment when gravatar.com/js/gprofiles.js changes +define( 'GROFILES__CACHE_BUSTER', gmdate( 'YM' ) . 'aa' ); // Break CDN cache, increment when gravatar.com/js/gprofiles.js changes function grofiles_hovercards_init() { - add_filter( 'get_avatar', 'grofiles_get_avatar', 10, 2 ); - add_action( 'wp_footer', 'grofiles_attach_cards' ); - add_action( 'wp_footer', 'grofiles_extra_data' ); - add_action( 'admin_init', 'grofiles_add_settings' ); + add_filter( 'get_avatar', 'grofiles_get_avatar', 10, 2 ); + add_action( 'wp_enqueue_scripts', 'grofiles_attach_cards' ); + add_action( 'wp_footer', 'grofiles_extra_data' ); + add_action( 'admin_init', 'grofiles_add_settings' ); add_action( 'load-index.php', 'grofiles_admin_cards' ); add_action( 'load-users.php', 'grofiles_admin_cards' ); @@ -141,7 +141,7 @@ function grofiles_get_avatar( $avatar, $author ) { if ( false !== strpos( $author, '@' ) ) { grofiles_gravatars_to_append( $author ); } else { - if ( $user = get_userdatabylogin( $author ) ) + if ( $user = get_user_by( 'slug', $author ) ) grofiles_gravatars_to_append( $user->ID ); } } else if ( isset( $author->comment_type ) ) { @@ -171,8 +171,8 @@ function grofiles_attach_cards() { if ( 'disabled' == get_option( 'gravatar_disable_hovercards' ) ) return; - wp_enqueue_script( 'grofiles-cards', ( is_ssl() ? 'https://secure' : 'http://s' ) . '.gravatar.com/js/gprofiles.js?' . GROFILES__CACHE_BUSTER, array( 'jquery' ) ); - wp_enqueue_script( 'wpgroho', plugins_url( 'wpgroho.js', __FILE__ ), array( 'grofiles-cards' ) ); + wp_enqueue_script( 'grofiles-cards', ( is_ssl() ? 'https://secure' : 'http://s' ) . '.gravatar.com/js/gprofiles.js', array( 'jquery' ), GROFILES__CACHE_BUSTER, true ); + wp_enqueue_script( 'wpgroho', plugins_url( 'wpgroho.js', __FILE__ ), array( 'grofiles-cards' ), false, true ); if ( is_user_logged_in() ) { $cu = wp_get_current_user(); $my_hash = md5( $cu->user_email ); @@ -182,7 +182,6 @@ function grofiles_attach_cards() { $my_hash = ''; } wp_localize_script( 'wpgroho', 'WPGroHo', compact( 'my_hash' ) ); - wp_print_scripts( 'wpgroho' ); } function grofiles_attach_cards_forced() { diff --git a/plugins/jetpack/modules/holiday-snow.php b/plugins/jetpack/modules/holiday-snow.php new file mode 100644 index 00000000..93e3f9cf --- /dev/null +++ b/plugins/jetpack/modules/holiday-snow.php @@ -0,0 +1,72 @@ +<?php + +/** + * Holiday Snow + * Adds falling snow to a blog starting December 1 and ending January 3. + * Not a module that is activated/deactivated + * First Introduced: 2.0.3 ?? + */ + +class Jetpack_Holiday_Snow_Settings { + function __construct() { + add_filter( 'admin_init' , array( &$this , 'register_fields' ) ); + } + + public function register_fields() { + register_setting( 'general', jetpack_holiday_snow_option_name(), 'esc_attr' ); + add_settings_field( jetpack_holiday_snow_option_name(), '<label for="' . esc_attr( jetpack_holiday_snow_option_name() ) . '">' . __( 'Snow' , 'jetpack') . '</label>' , array( &$this, 'blog_field_html' ) , 'general' ); + add_action( 'update_option_' . jetpack_holiday_snow_option_name(), array( &$this, 'holiday_snow_option_updated' ) ); + } + + public function blog_field_html() { + $id = esc_attr( jetpack_holiday_snow_option_name() ); + ?> + <label for="<?php echo $id; ?>"> + <input type="checkbox" name="<?php echo $id; ?>" id="<?php echo $id; ?>" value="letitsnow"<?php checked( get_option( jetpack_holiday_snow_option_name() ), 'letitsnow' ); ?> /> + <span><?php _e( 'Show falling snow on my blog until January 4<sup>th</sup>.' , 'jetpack'); ?></span> + </label> + <?php + } + + public function holiday_snow_option_updated() { + do_action( 'jetpack_holiday_snow_option_updated' ); + } +} + +function jetpack_holiday_snow_script() { + if ( ! apply_filters( 'jetpack_holiday_chance_of_snow', true ) ) + return; + + do_action( 'jetpack_holiday_snowing' ); + + $snowstorm_url = apply_filters( 'jetpack_holiday_snow_js_url', plugins_url( 'holiday-snow/snowstorm.js', __FILE__ ) ); + wp_enqueue_script( 'snowstorm', $snowstorm_url, array(), '1.43.20111201' ); +} + +function jetpack_maybe_holiday_snow() { + if ( ! jetpack_is_holiday_snow_season() ) + return; + + if ( is_admin() ) { + global $jetpack_holiday_snow; + $jetpack_holiday_snow = new Jetpack_Holiday_Snow_Settings(); + } elseif ( get_option( jetpack_holiday_snow_option_name() ) ) { + add_action( 'init', 'jetpack_holiday_snow_script' ); + } +} + +function jetpack_holiday_snow_option_name() { + return apply_filters( 'jetpack_holiday_snow_option_name', 'jetpack_holiday_snow_enabled' ); +} + +function jetpack_is_holiday_snow_season() { + $today = time(); + $first_snow_day = mktime( 0, 0, 0, 12, 1 ); + $last_snow_day = mktime( 0, 0, 0, 1, 4 ); + + $snow = ( $today >= $first_snow_day || $today < $last_snow_day ); + + return apply_filters( 'jetpack_is_holiday_snow_season', $snow ); +} + +jetpack_maybe_holiday_snow(); diff --git a/plugins/jetpack/modules/holiday-snow/snowstorm.js b/plugins/jetpack/modules/holiday-snow/snowstorm.js new file mode 100644 index 00000000..96fd609d --- /dev/null +++ b/plugins/jetpack/modules/holiday-snow/snowstorm.js @@ -0,0 +1,539 @@ +/** @license + * DHTML Snowstorm! JavaScript-based Snow for web pages + * -------------------------------------------------------- + * Version 1.43.20111201 (Previous rev: 1.42.20111120) + * Copyright (c) 2007, Scott Schiller. All rights reserved. + * Code provided under the BSD License: + * http://schillmania.com/projects/snowstorm/license.txt + */ + +/*global window, document, navigator, clearInterval, setInterval */ +/*jslint white: false, onevar: true, plusplus: false, undef: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */ + +var snowStorm = (function(window, document) { + + // --- common properties --- + + this.autoStart = true; // Whether the snow should start automatically or not. + this.flakesMax = 60; // Limit total amount of snow made (falling + sticking) + this.flakesMaxActive = 60; // Limit amount of snow falling at once (less = lower CPU use) + this.animationInterval = 40; // Theoretical "miliseconds per frame" measurement. 20 = fast + smooth, but high CPU use. 50 = more conservative, but slower + this.excludeMobile = true; // Snow is likely to be bad news for mobile phones' CPUs (and batteries.) By default, be nice. + this.flakeBottom = null; // Integer for Y axis snow limit, 0 or null for "full-screen" snow effect + this.followMouse = true; // Snow movement can respond to the user's mouse + this.snowColor = '#fff'; // Don't eat (or use?) yellow snow. + this.snowCharacter = '•'; // • = bullet, · is square on some systems etc. + this.snowStick = false; // Whether or not snow should "stick" at the bottom. When off, will never collect. + this.targetElement = null; // element which snow will be appended to (null = document.body) - can be an element ID eg. 'myDiv', or a DOM node reference + this.useMeltEffect = true; // When recycling fallen snow (or rarely, when falling), have it "melt" and fade out if browser supports it + this.useTwinkleEffect = false; // Allow snow to randomly "flicker" in and out of view while falling + this.usePositionFixed = false; // true = snow does not shift vertically when scrolling. May increase CPU load, disabled by default - if enabled, used only where supported + + // --- less-used bits --- + + this.freezeOnBlur = true; // Only snow when the window is in focus (foreground.) Saves CPU. + this.flakeLeftOffset = 0; // Left margin/gutter space on edge of container (eg. browser window.) Bump up these values if seeing horizontal scrollbars. + this.flakeRightOffset = 0; // Right margin/gutter space on edge of container + this.flakeWidth = 5; // Max pixel width reserved for snow element + this.flakeHeight = 5; // Max pixel height reserved for snow element + this.vMaxX = 2.5; // Maximum X velocity range for snow + this.vMaxY = 2.5; // Maximum Y velocity range for snow + this.zIndex = 100000; // CSS stacking order applied to each snowflake + + // --- End of user section --- + + var s = this, storm = this, i, + // UA sniffing and backCompat rendering mode checks for fixed position, etc. + isIE = navigator.userAgent.match(/msie/i), + isIE6 = navigator.userAgent.match(/msie 6/i), + isWin98 = navigator.appVersion.match(/windows 98/i), + isMobile = navigator.userAgent.match(/mobile|opera m(ob|in)/i), + isBackCompatIE = (isIE && document.compatMode === 'BackCompat'), + noFixed = (isMobile || isBackCompatIE || isIE6), + screenX = null, screenX2 = null, screenY = null, scrollY = null, vRndX = null, vRndY = null, + windOffset = 1, + windMultiplier = 2, + flakeTypes = 6, + fixedForEverything = false, + opacitySupported = (function(){ + try { + document.createElement('div').style.opacity = '0.5'; + } catch(e) { + return false; + } + return true; + }()), + didInit = false, + docFrag = document.createDocumentFragment(); + + this.timers = []; + this.flakes = []; + this.disabled = false; + this.active = false; + this.meltFrameCount = 20; + this.meltFrames = []; + + this.events = (function() { + + var old = (!window.addEventListener && window.attachEvent), slice = Array.prototype.slice, + evt = { + add: (old?'attachEvent':'addEventListener'), + remove: (old?'detachEvent':'removeEventListener') + }; + + function getArgs(oArgs) { + var args = slice.call(oArgs), len = args.length; + if (old) { + args[1] = 'on' + args[1]; // prefix + if (len > 3) { + args.pop(); // no capture + } + } else if (len === 3) { + args.push(false); + } + return args; + } + + function apply(args, sType) { + var element = args.shift(), + method = [evt[sType]]; + if (old) { + element[method](args[0], args[1]); + } else { + element[method].apply(element, args); + } + } + + function addEvent() { + apply(getArgs(arguments), 'add'); + } + + function removeEvent() { + apply(getArgs(arguments), 'remove'); + } + + return { + add: addEvent, + remove: removeEvent + }; + + }()); + + function rnd(n,min) { + if (isNaN(min)) { + min = 0; + } + return (Math.random()*n)+min; + } + + function plusMinus(n) { + return (parseInt(rnd(2),10)===1?n*-1:n); + } + + this.randomizeWind = function() { + var i; + vRndX = plusMinus(rnd(s.vMaxX,0.2)); + vRndY = rnd(s.vMaxY,0.2); + if (this.flakes) { + for (i=0; i<this.flakes.length; i++) { + if (this.flakes[i].active) { + this.flakes[i].setVelocities(); + } + } + } + }; + + this.scrollHandler = function() { + var i; + // "attach" snowflakes to bottom of window if no absolute bottom value was given + scrollY = (s.flakeBottom?0:parseInt(window.scrollY||document.documentElement.scrollTop||document.body.scrollTop,10)); + if (isNaN(scrollY)) { + scrollY = 0; // Netscape 6 scroll fix + } + if (!fixedForEverything && !s.flakeBottom && s.flakes) { + for (i=s.flakes.length; i--;) { + if (s.flakes[i].active === 0) { + s.flakes[i].stick(); + } + } + } + }; + + this.resizeHandler = function() { + if (window.innerWidth || window.innerHeight) { + screenX = window.innerWidth-16-s.flakeRightOffset; + screenY = (s.flakeBottom?s.flakeBottom:window.innerHeight); + } else { + screenX = (document.documentElement.clientWidth||document.body.clientWidth||document.body.scrollWidth)-(!isIE?8:0)-s.flakeRightOffset; + screenY = s.flakeBottom?s.flakeBottom:(document.documentElement.clientHeight||document.body.clientHeight||document.body.scrollHeight); + } + screenX2 = parseInt(screenX/2,10); + }; + + this.resizeHandlerAlt = function() { + screenX = s.targetElement.offsetLeft+s.targetElement.offsetWidth-s.flakeRightOffset; + screenY = s.flakeBottom?s.flakeBottom:s.targetElement.offsetTop+s.targetElement.offsetHeight; + screenX2 = parseInt(screenX/2,10); + }; + + this.freeze = function() { + // pause animation + var i; + if (!s.disabled) { + s.disabled = 1; + } else { + return false; + } + for (i=s.timers.length; i--;) { + clearInterval(s.timers[i]); + } + }; + + this.resume = function() { + if (s.disabled) { + s.disabled = 0; + } else { + return false; + } + s.timerInit(); + }; + + this.toggleSnow = function() { + if (!s.flakes.length) { + // first run + s.start(); + } else { + s.active = !s.active; + if (s.active) { + s.show(); + s.resume(); + } else { + s.stop(); + s.freeze(); + } + } + }; + + this.stop = function() { + var i; + this.freeze(); + for (i=this.flakes.length; i--;) { + this.flakes[i].o.style.display = 'none'; + } + s.events.remove(window,'scroll',s.scrollHandler); + s.events.remove(window,'resize',s.resizeHandler); + if (s.freezeOnBlur) { + if (isIE) { + s.events.remove(document,'focusout',s.freeze); + s.events.remove(document,'focusin',s.resume); + } else { + s.events.remove(window,'blur',s.freeze); + s.events.remove(window,'focus',s.resume); + } + } + }; + + this.show = function() { + var i; + for (i=this.flakes.length; i--;) { + this.flakes[i].o.style.display = 'block'; + } + }; + + this.SnowFlake = function(parent,type,x,y) { + var s = this, storm = parent; + this.type = type; + this.x = x||parseInt(rnd(screenX-20),10); + this.y = (!isNaN(y)?y:-rnd(screenY)-12); + this.vX = null; + this.vY = null; + this.vAmpTypes = [1,1.2,1.4,1.6,1.8]; // "amplification" for vX/vY (based on flake size/type) + this.vAmp = this.vAmpTypes[this.type]; + this.melting = false; + this.meltFrameCount = storm.meltFrameCount; + this.meltFrames = storm.meltFrames; + this.meltFrame = 0; + this.twinkleFrame = 0; + this.active = 1; + this.fontSize = (10+(this.type/5)*10); + this.o = document.createElement('div'); + this.o.innerHTML = storm.snowCharacter; + this.o.style.color = storm.snowColor; + this.o.style.position = (fixedForEverything?'fixed':'absolute'); + this.o.style.width = storm.flakeWidth+'px'; + this.o.style.height = storm.flakeHeight+'px'; + this.o.style.fontFamily = 'arial,verdana'; + this.o.style.cursor = 'default'; + this.o.style.overflow = 'hidden'; + this.o.style.fontWeight = 'normal'; + this.o.style.zIndex = storm.zIndex; + docFrag.appendChild(this.o); + + this.refresh = function() { + if (isNaN(s.x) || isNaN(s.y)) { + // safety check + return false; + } + s.o.style.left = s.x+'px'; + s.o.style.top = s.y+'px'; + }; + + this.stick = function() { + if (noFixed || (storm.targetElement !== document.documentElement && storm.targetElement !== document.body)) { + s.o.style.top = (screenY+scrollY-storm.flakeHeight)+'px'; + } else if (storm.flakeBottom) { + s.o.style.top = storm.flakeBottom+'px'; + } else { + s.o.style.display = 'none'; + s.o.style.top = 'auto'; + s.o.style.bottom = '0px'; + s.o.style.position = 'fixed'; + s.o.style.display = 'block'; + } + }; + + this.vCheck = function() { + if (s.vX>=0 && s.vX<0.2) { + s.vX = 0.2; + } else if (s.vX<0 && s.vX>-0.2) { + s.vX = -0.2; + } + if (s.vY>=0 && s.vY<0.2) { + s.vY = 0.2; + } + }; + + this.move = function() { + var vX = s.vX*windOffset, yDiff; + s.x += vX; + s.y += (s.vY*s.vAmp); + if (s.x >= screenX || screenX-s.x < storm.flakeWidth) { // X-axis scroll check + s.x = 0; + } else if (vX < 0 && s.x-storm.flakeLeftOffset < -storm.flakeWidth) { + s.x = screenX-storm.flakeWidth-1; // flakeWidth; + } + s.refresh(); + yDiff = screenY+scrollY-s.y; + if (yDiff<storm.flakeHeight) { + s.active = 0; + if (storm.snowStick) { + s.stick(); + } else { + s.recycle(); + } + } else { + if (storm.useMeltEffect && s.active && s.type < 3 && !s.melting && Math.random()>0.998) { + // ~1/1000 chance of melting mid-air, with each frame + s.melting = true; + s.melt(); + // only incrementally melt one frame + // s.melting = false; + } + if (storm.useTwinkleEffect) { + if (!s.twinkleFrame) { + if (Math.random()>0.9) { + s.twinkleFrame = parseInt(Math.random()*20,10); + } + } else { + s.twinkleFrame--; + s.o.style.visibility = (s.twinkleFrame && s.twinkleFrame%2===0?'hidden':'visible'); + } + } + } + }; + + this.animate = function() { + // main animation loop + // move, check status, die etc. + s.move(); + }; + + this.setVelocities = function() { + s.vX = vRndX+rnd(storm.vMaxX*0.12,0.1); + s.vY = vRndY+rnd(storm.vMaxY*0.12,0.1); + }; + + this.setOpacity = function(o,opacity) { + if (!opacitySupported) { + return false; + } + o.style.opacity = opacity; + }; + + this.melt = function() { + if (!storm.useMeltEffect || !s.melting) { + s.recycle(); + } else { + if (s.meltFrame < s.meltFrameCount) { + s.setOpacity(s.o,s.meltFrames[s.meltFrame]); + s.o.style.fontSize = s.fontSize-(s.fontSize*(s.meltFrame/s.meltFrameCount))+'px'; + s.o.style.lineHeight = storm.flakeHeight+2+(storm.flakeHeight*0.75*(s.meltFrame/s.meltFrameCount))+'px'; + s.meltFrame++; + } else { + s.recycle(); + } + } + }; + + this.recycle = function() { + s.o.style.display = 'none'; + s.o.style.position = (fixedForEverything?'fixed':'absolute'); + s.o.style.bottom = 'auto'; + s.setVelocities(); + s.vCheck(); + s.meltFrame = 0; + s.melting = false; + s.setOpacity(s.o,1); + s.o.style.padding = '0px'; + s.o.style.margin = '0px'; + s.o.style.fontSize = s.fontSize+'px'; + s.o.style.lineHeight = (storm.flakeHeight+2)+'px'; + s.o.style.textAlign = 'center'; + s.o.style.verticalAlign = 'baseline'; + s.x = parseInt(rnd(screenX-storm.flakeWidth-20),10); + s.y = parseInt(rnd(screenY)*-1,10)-storm.flakeHeight; + s.refresh(); + s.o.style.display = 'block'; + s.active = 1; + }; + + this.recycle(); // set up x/y coords etc. + this.refresh(); + + }; + + this.snow = function() { + var active = 0, used = 0, waiting = 0, flake = null, i; + for (i=s.flakes.length; i--;) { + if (s.flakes[i].active === 1) { + s.flakes[i].move(); + active++; + } else if (s.flakes[i].active === 0) { + used++; + } else { + waiting++; + } + if (s.flakes[i].melting) { + s.flakes[i].melt(); + } + } + if (active<s.flakesMaxActive) { + flake = s.flakes[parseInt(rnd(s.flakes.length),10)]; + if (flake.active === 0) { + flake.melting = true; + } + } + }; + + this.mouseMove = function(e) { + if (!s.followMouse) { + return true; + } + var x = parseInt(e.clientX,10); + if (x<screenX2) { + windOffset = -windMultiplier+(x/screenX2*windMultiplier); + } else { + x -= screenX2; + windOffset = (x/screenX2)*windMultiplier; + } + }; + + this.createSnow = function(limit,allowInactive) { + var i; + for (i=0; i<limit; i++) { + s.flakes[s.flakes.length] = new s.SnowFlake(s,parseInt(rnd(flakeTypes),10)); + if (allowInactive || i>s.flakesMaxActive) { + s.flakes[s.flakes.length-1].active = -1; + } + } + storm.targetElement.appendChild(docFrag); + }; + + this.timerInit = function() { + s.timers = (!isWin98?[setInterval(s.snow,s.animationInterval)]:[setInterval(s.snow,s.animationInterval*3),setInterval(s.snow,s.animationInterval)]); + }; + + this.init = function() { + var i; + for (i=0; i<s.meltFrameCount; i++) { + s.meltFrames.push(1-(i/s.meltFrameCount)); + } + s.randomizeWind(); + s.createSnow(s.flakesMax); // create initial batch + s.events.add(window,'resize',s.resizeHandler); + s.events.add(window,'scroll',s.scrollHandler); + if (s.freezeOnBlur) { + if (isIE) { + s.events.add(document,'focusout',s.freeze); + s.events.add(document,'focusin',s.resume); + } else { + s.events.add(window,'blur',s.freeze); + s.events.add(window,'focus',s.resume); + } + } + s.resizeHandler(); + s.scrollHandler(); + if (s.followMouse) { + s.events.add(isIE?document:window,'mousemove',s.mouseMove); + } + s.animationInterval = Math.max(20,s.animationInterval); + s.timerInit(); + }; + + this.start = function(bFromOnLoad) { + if (!didInit) { + didInit = true; + } else if (bFromOnLoad) { + // already loaded and running + return true; + } + if (typeof s.targetElement === 'string') { + var targetID = s.targetElement; + s.targetElement = document.getElementById(targetID); + if (!s.targetElement) { + throw new Error('Snowstorm: Unable to get targetElement "'+targetID+'"'); + } + } + if (!s.targetElement) { + s.targetElement = (!isIE?(document.documentElement?document.documentElement:document.body):document.body); + } + if (s.targetElement !== document.documentElement && s.targetElement !== document.body) { + s.resizeHandler = s.resizeHandlerAlt; // re-map handler to get element instead of screen dimensions + } + s.resizeHandler(); // get bounding box elements + s.usePositionFixed = (s.usePositionFixed && !noFixed); // whether or not position:fixed is supported + fixedForEverything = s.usePositionFixed; + if (screenX && screenY && !s.disabled) { + s.init(); + s.active = true; + } + }; + + function doDelayedStart() { + window.setTimeout(function() { + s.start(true); + }, 20); + // event cleanup + s.events.remove(isIE?document:window,'mousemove',doDelayedStart); + } + + function doStart() { + if (!s.excludeMobile || !isMobile) { + if (s.freezeOnBlur) { + s.events.add(isIE?document:window,'mousemove',doDelayedStart); + } else { + doDelayedStart(); + } + } + // event cleanup + s.events.remove(window, 'load', doStart); + } + + // hooks for starting the snow + if (s.autoStart) { + s.events.add(window, 'load', doStart, false); + } + + return this; + +}(window, document));
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll.php b/plugins/jetpack/modules/infinite-scroll.php new file mode 100644 index 00000000..857ac6e0 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll.php @@ -0,0 +1,196 @@ +<?php +/** + * Module Name: Infinite Scroll + * Module Description: Automatically pull the next set of posts into view when the reader approaches the bottom of the page. + * Sort Order: 14 + * First Introduced: 2.0 + */ + +/** + * Jetpack-specific elements of Infinite Scroll + */ +class Jetpack_Infinite_Scroll_Extras { + /** + * Class variables + */ + // Oh look, a singleton + private static $__instance = null; + + // Option names + private $option_name_google_analytics = 'infinite_scroll_google_analytics'; + + /** + * Singleton implementation + * + * @return object + */ + public static function instance() { + if ( ! is_a( self::$__instance, 'Jetpack_Infinite_Scroll_Extras' ) ) + self::$__instance = new Jetpack_Infinite_Scroll_Extras; + + return self::$__instance; + } + + /** + * Register actions and filters + * + * @uses add_action, add_filter + * @return null + */ + private function __construct() { + add_action( 'jetpack_modules_loaded', array( $this, 'action_jetpack_modules_loaded' ) ); + + add_action( 'admin_init', array( $this, 'action_admin_init' ), 11 ); + + add_action( 'after_setup_theme', array( $this, 'action_after_setup_theme' ), 5 ); + + add_filter( 'infinite_scroll_js_settings', array( $this, 'filter_infinite_scroll_js_settings' ) ); + + add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ) ); + } + + /** + * Enable "Configure" button on module card + * + * @uses Jetpack::enable_module_configurable, Jetpack::module_configuration_load + * @action jetpack_modules_loaded + * @return null + */ + public function action_jetpack_modules_loaded() { + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, array( $this, 'module_configuration_load' ) ); + } + + /** + * Redirect configure button to Settings > Reading + * + * @uses wp_safe_redirect, admin_url + * @return null + */ + public function module_configuration_load() { + wp_safe_redirect( admin_url( 'options-reading.php#infinite-scroll-options' ) ); + exit; + } + + /** + * Register Google Analytics setting + * + * @uses add_settings_field, __, register_setting + * @action admin_init + * @return null + */ + public function action_admin_init() { + add_settings_field( $this->option_name_google_analytics, '<span id="infinite-scroll-google-analytics">' . __( 'Use Google Analytics with Infinite Scroll', 'jetpack' ) . '</span>', array( $this, 'setting_google_analytics' ), 'reading' ); + register_setting( 'reading', $this->option_name_google_analytics, array( $this, 'sanitize_boolean_value' ) ); + } + + /** + * Render Google Analytics option + * + * @uses checked, get_option, __ + * @return html + */ + public function setting_google_analytics() { + echo '<label><input name="infinite_scroll_google_analytics" type="checkbox" value="1" ' . checked( true, (bool) get_option( $this->option_name_google_analytics, false ), false ) . ' /> ' . __( 'Track each Infinite Scroll post load as a page view in Google Analytics', 'jetpack' ) . '</br><small>' . __( 'By checking the box above, each new set of posts loaded via Infinite Scroll will be recorded as a page view in Google Analytics.', 'jetpack' ) . '</small>' . '</label>'; + } + + /** + * Sanitize value as a boolean + * + * @param mixed $value + * @return bool + */ + public function sanitize_boolean_value( $value ) { + return (bool) $value; + } + + /** + * Load theme's infinite scroll annotation file, if present in the IS plugin. + * The `setup_theme` action is used because the annotation files should be using `after_setup_theme` to register support for IS. + * + * As released in Jetpack 2.0, a child theme's parent wasn't checked for in the plugin's bundled support, hence the convoluted way the parent is checked for now. + * + * @uses is_admin, wp_get_theme, get_theme, get_current_theme, apply_filters + * @action setup_theme + * @return null + */ + function action_after_setup_theme() { + $theme = function_exists( 'wp_get_theme' ) ? wp_get_theme() : get_theme( get_current_theme() ); + + if ( ! is_a( $theme, 'WP_Theme' ) && ! is_array( $theme ) ) + return; + + $customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/infinite-scroll/themes/{$theme['Stylesheet']}.php", $theme['Stylesheet'] ); + + if ( is_readable( $customization_file ) ) { + require_once( $customization_file ); + } + elseif ( ! empty( $theme['Template'] ) ) { + $customization_file = dirname( __FILE__ ) . "/infinite-scroll/themes/{$theme['Template']}.php"; + + if ( is_readable( $customization_file ) ) + require_once( $customization_file ); + } + } + + /** + * Modify Infinite Scroll configuration information + * + * @uses Jetpack::get_active_modules, is_user_logged_in, stats_get_options, Jetpack::get_option, get_option, JETPACK__API_VERSION, JETPACK__VERSION + * @filter infinite_scroll_js_settings + * @return array + */ + public function filter_infinite_scroll_js_settings( $settings ) { + // Provide WP Stats info for tracking Infinite Scroll loads + // Abort if Stats module isn't active + if ( in_array( 'stats', Jetpack::get_active_modules() ) ) { + // Abort if user is logged in but logged-in users shouldn't be tracked. + if ( is_user_logged_in() ) { + $stats_options = stats_get_options(); + $track_loggedin_users = isset( $stats_options['reg_users'] ) ? (bool) $stats_options['reg_users'] : false; + + if ( ! $track_loggedin_users ) + return $settings; + } + + // We made it this far, so gather the data needed to track IS views + $settings['stats'] = 'blog=' . Jetpack::get_option( 'id' ) . '&host=' . parse_url( get_option( 'home' ), PHP_URL_HOST ) . '&v=ext&j=' . JETPACK__API_VERSION . ':' . JETPACK__VERSION; + + // Pagetype parameter + $settings['stats'] .= '&x_pagetype=infinite'; + if ( 'click' == $settings['type'] ) + $settings['stats'] .= '-click'; + + $settings['stats'] .= '-jetpack'; + } + + // Check if Google Analytics tracking is requested + $settings['google_analytics'] = (bool) get_option( $this->option_name_google_analytics ); + + return $settings; + } + + /** + * Load VideoPress scripts if plugin is active. + * + * @global $videopress + * @action wp_enqueue_scripts + * @return null + */ + public function action_wp_enqueue_scripts() { + global $videopress; + if ( ! empty( $videopress ) && The_Neverending_Home_Page::archive_supports_infinity() && is_a( $videopress, 'VideoPress' ) && method_exists( $videopress, 'enqueue_scripts' ) ) + $videopress->enqueue_scripts(); + } +} +Jetpack_Infinite_Scroll_Extras::instance(); + +/** + * Load main IS file + */ +require_once( dirname( __FILE__ ) . "/infinite-scroll/infinity.php" ); + +/** + * Remove the IS annotation loading function bundled with the IS plugin in favor of the Jetpack-specific version in Jetpack_Infinite_Scroll_Extras::action_after_setup_theme(); + */ +remove_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.css b/plugins/jetpack/modules/infinite-scroll/infinity.css new file mode 100644 index 00000000..dd39179e --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/infinity.css @@ -0,0 +1,137 @@ +/* =Infinity Styles +-------------------------------------------------------------- */ + +.infinite-wrap { +/* border-top: 2px solid #444; + border-top: 2px solid rgba(68,68,68,0.8); + padding: 20px 0 0; */ +} +.infinite-loader { + color: #000; + display: block; + height: 28px; + margin: 10px; + text-indent: -9999px; +} +#infinite-handle span { + background: #333; + border-radius: 1px; + color: #eee; + cursor: pointer; + font-size: 13px; + padding: 6px 16px; +} + +/** + * For smaller viewports, remove the down-arrow icon and turn + * the button into a block element, spanning the content's full width. + */ +@media (max-width: 800px) { + #infinite-handle span:before { + display: none; + } + #infinite-handle span { + display: block; + } +} + +/** + * Footer + */ +#infinite-footer { + position: fixed; + bottom: -50px; + left: 0; + width: 100%; +} +#infinite-footer a { + text-decoration: none; +} +#infinite-footer .blog-info a:hover, +#infinite-footer .blog-credits a:hover { + color: #444; + text-decoration: underline; +} +#infinite-footer .container { + background: rgba( 255, 255, 255, 0.8 ); + border-color: #ccc; + border-color: rgba( 0, 0, 0, 0.1 ); + border-style: solid; + border-width: 1px 0 0; + box-sizing: border-box; + margin: 0 auto; + overflow: hidden; + padding: 1px 20px; + width: 640px; +} +#infinite-footer .blog-info, +#infinite-footer .blog-credits { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + line-height: 25px; +} +#infinite-footer .blog-info { + float: left; + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + width: 40%; +} +#infinite-footer .blog-credits { + font-weight: normal; + float: right; + width: 60%; +} +#infinite-footer .blog-info a { + color: #111; + font-size: 14px; + font-weight: bold; +} +#infinite-footer .blog-credits { + color: #888; + font-size: 12px; + text-align: right; +} +#infinite-footer .blog-credits a { + color: #666; +} + +/** + * Hooks to infinity-end body class to restore footer + */ +.infinity-end.neverending #infinite-footer { + display: none; +} + +/** + * Responsive structure for the footer + */ +@media (max-width: 640px) { + #infinite-footer .container { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + } + #infinite-footer .blog-info { + width: 30%; + } + #infinite-footer .blog-credits { + width: 70%; + } + #infinite-footer .blog-info a, + #infinite-footer .blog-credits { + font-size: 10px; + } +} + +/** + * No fixed footer on small viewports + */ +@media ( max-width: 640px ) { + #infinite-footer { + position: static; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.js b/plugins/jetpack/modules/infinite-scroll/infinity.js new file mode 100644 index 00000000..7a76d8f2 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/infinity.js @@ -0,0 +1,490 @@ +(function($){ // Open closure + +// Local vars +var Scroller, ajaxurl, stats, type, text, totop, timer; + +// IE requires special handling +var isIE = ( -1 != navigator.userAgent.search( 'MSIE' ) ); +if ( isIE ) { + var IEVersion = navigator.userAgent.match(/MSIE\s?(\d+)\.?\d*;/); + var IEVersion = parseInt( IEVersion[1] ); +} + +/** + * Loads new posts when users scroll near the bottom of the page. + */ +Scroller = function( settings ) { + var self = this; + + // Initialize our variables + this.id = settings.id; + this.body = $( document.body ); + this.window = $( window ); + this.element = $( '#' + settings.id ); + this.wrapperClass = settings.wrapper_class; + this.ready = true; + this.disabled = false; + this.page = 1; + this.offset = settings.offset; + this.order = settings.order; + this.throttle = false; + this.handle = '<div id="infinite-handle"><span>' + text.replace( '\\', '' ) + '</span></div>'; + this.google_analytics = settings.google_analytics; + this.history = settings.history; + this.origURL = window.location.href; + + // Footer settings + this.footer = $( '#infinite-footer' ); + this.footer.wrap = settings.footer; + + // We have two type of infinite scroll + // cases 'scroll' and 'click' + + if ( type == 'scroll' ) { + // Bind refresh to the scroll event + // Throttle to check for such case every 300ms + + // On event the case becomes a fact + this.window.bind( 'scroll.infinity', function() { + this.throttle = true; + }); + + // Go back top method + self.gotop(); + + setInterval( function() { + if ( this.throttle ) { + // Once the case is the case, the action occurs and the fact is no more + this.throttle = false; + // Reveal or hide footer + self.thefooter(); + // Fire the refresh + self.refresh(); + } + }, 300 ); + + // Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays. + self.ensureFilledViewport(); + this.body.bind( 'post-load', { self: self }, self.checkViewportOnLoad ); + } else if ( type == 'click' ) { + this.element.append( self.handle ); + this.element.delegate( '#infinite-handle', 'click.infinity', function() { + // Handle the handle + $( '#infinite-handle' ).remove(); + // Fire the refresh + self.refresh(); + }); + } +}; + +/** + * Check whether we should fetch any additional posts. + * + * By default, checks whether the bottom of the viewport is within one + * viewport-height of the bottom of the content. + */ +Scroller.prototype.check = function() { + var bottom = this.window.scrollTop() + this.window.height(), + threshold = this.element.offset().top + this.element.outerHeight(false) - this.window.height(); + + return bottom > threshold; +}; + +/** + * Renders the results from a successful response. + */ +Scroller.prototype.render = function( response ) { + this.body.addClass( 'infinity-success' ); + + // Check if we can wrap the html + this.element.append( response.html ); + + this.body.trigger( 'post-load' ); + this.ready = true; +}; + +/** + * Returns the object used to query for new posts. + */ +Scroller.prototype.query = function() { + return { + page: this.page, + order: this.order, + scripts: window.infiniteScroll.settings.scripts, + styles: window.infiniteScroll.settings.styles + }; +}; + +/** + * Scroll back to top. + */ +Scroller.prototype.gotop = function() { + var blog = $( '#infinity-blog-title' ); + + blog.attr( 'title', totop ); + + // Scroll to top on blog title + blog.bind( 'click', function( e ) { + $( 'html, body' ).animate( { scrollTop: 0 }, 'fast' ); + e.preventDefault(); + }); +}; + + +/** + * The infinite footer. + */ +Scroller.prototype.thefooter = function() { + var self = this, + width; + + // Check if we have an id for the page wrapper + if ( $.type( this.footer.wrap ) === "string" ) { + width = $( 'body #' + this.footer.wrap ).outerWidth( false ); + + // Make the footer match the width of the page + if ( width > 479 ) + this.footer.find( '.container' ).css( 'width', width ); + } + + // Reveal footer + if ( this.window.scrollTop() >= 350 ) + self.footer.animate( { 'bottom': 0 }, 'fast' ); + else if ( this.window.scrollTop() < 350 ) + self.footer.animate( { 'bottom': '-50px' }, 'fast' ); +}; + + +/** + * Controls the flow of the refresh. Don't mess. + */ +Scroller.prototype.refresh = function() { + var self = this, + query, jqxhr, load, loader, color; + + // If we're disabled, ready, or don't pass the check, bail. + if ( this.disabled || ! this.ready || ! this.check() ) + return; + + // Let's get going -- set ready to false to prevent + // multiple refreshes from occurring at once. + this.ready = false; + + // Create a loader element to show it's working. + loader = '<span class="infinite-loader"></span>'; + this.element.append( loader ); + + loader = this.element.find( '.infinite-loader' ); + color = loader.css( 'color' ); + + try { + loader.spin( 'medium-left', color ); + } catch ( error ) { } + + // Generate our query vars. + query = $.extend({ + action: 'infinite_scroll' + }, this.query() ); + + // Fire the ajax request. + jqxhr = $.get( infiniteScroll.settings.ajaxurl, query ); + + // Allow refreshes to occur again if an error is triggered. + jqxhr.fail( function() { + loader.hide(); + self.ready = true; + }); + + // Success handler + jqxhr.done( function( response ) { + + // On success, let's hide the loader circle. + loader.hide(); + + // Check for and parse our response. + if ( ! response ) + return; + + response = $.parseJSON( response ); + + if ( ! response || ! response.type ) + return; + + // If there are no remaining posts... + if ( response.type == 'empty' ) { + // Disable the scroller. + self.disabled = true; + // Update body classes, allowing the footer to return to static positioning + self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' ); + + // If we've succeeded... + } else if ( response.type == 'success' ) { + // If additional scripts are required by the incoming set of posts, parse them + if ( response.scripts ) { + $( response.scripts ).each( function() { + // Add script handle to list of those already parsed + window.infiniteScroll.settings.scripts.push( this.handle ); + + // Output extra data, if present + if ( this.extra_data ) { + var data = document.createElement('script'), + dataContent = document.createTextNode( "//<![CDATA[ \n" + this.extra_data + "\n//]]>" ); + + data.type = 'text/javascript'; + data.appendChild( dataContent ); + + document.getElementsByTagName( this.footer ? 'body' : 'head' )[0].appendChild(data); + } + + // Build script tag and append to DOM in requested location + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = this.src; + script.id = this.handle; + document.getElementsByTagName( this.footer ? 'body' : 'head' )[0].appendChild(script); + } ); + } + + // If additional stylesheets are required by the incoming set of posts, parse them + if ( response.styles ) { + $( response.styles ).each( function() { + // Add stylesheet handle to list of those already parsed + window.infiniteScroll.settings.styles.push( this.handle ); + + // Build link tag + var style = document.createElement('link'); + style.rel = 'stylesheet'; + style.href = this.src; + style.id = this.handle + '-css'; + + // Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true + if ( this.conditional && ( ! isIE || ! eval( this.conditional.replace( /%ver/g, IEVersion ) ) ) ) + var style = false; + + // Append link tag if necessary + if ( style ) + document.getElementsByTagName('head')[0].appendChild(style); + } ); + } + + // Increment the page number + self.page++; + + // Record pageview in WP Stats, if available. + if ( stats ) + new Image().src = document.location.protocol + '//stats.wordpress.com/g.gif?' + stats + '&post=0&baba=' + Math.random(); + + // Add new posts to the postflair object + if ( 'object' == typeof response.postflair && 'object' == typeof WPCOM_sharing_counts ) + WPCOM_sharing_counts = $.extend( WPCOM_sharing_counts, response.postflair ); + + // Render the results + self.render.apply( self, arguments ); + + // If 'click' type, add back the handle + if ( type == 'click' ) + self.element.append( self.handle ); + + // Fire Google Analytics pageview + if ( self.google_analytics && 'object' == typeof _gaq ) + _gaq.push(['_trackPageview', self.history.path.replace( /%d/, self.page ) ]); + } + }); + + return jqxhr; +}; + +/** + * Trigger IS to load additional posts if the initial posts don't fill the window. + * On large displays, or when posts are very short, the viewport may not be filled with posts, so we overcome this by loading additional posts when IS initializes. + */ +Scroller.prototype.ensureFilledViewport = function() { + var self = this, + windowHeight = self.window.height(), + postsHeight = self.element.height() + aveSetHeight = 0, + wrapperQty = 0; + + // Account for situations where postsHeight is 0 because child list elements are floated + if ( postsHeight === 0 ) { + $( self.element.selector + ' > li' ).each( function() { + postsHeight += $( this ).height(); + } ); + + if ( postsHeight === 0 ) { + self.body.unbind( 'post-load', self.checkViewportOnLoad ); + return; + } + } + + // Calculate average height of a set of posts to prevent more posts than needed from being loaded. + $( '.' + self.wrapperClass ).each( function() { + aveSetHeight += $( this ).height(); + wrapperQty++; + } ); + + if ( wrapperQty > 0 ) + aveSetHeight = aveSetHeight / wrapperQty; + else + aveSetHeight = 0; + + // Load more posts if space permits, otherwise stop checking for a full viewport + if ( postsHeight < windowHeight && ( postsHeight + aveSetHeight < windowHeight ) ) { + self.ready = true; + self.refresh(); + } + else { + self.body.unbind( 'post-load', self.checkViewportOnLoad ); + } +} + +/** + * Event handler for ensureFilledViewport(), tied to the post-load trigger. + * Necessary to ensure that the variable `this` contains the scroller when used in ensureFilledViewport(). Since this function is tied to an event, `this` becomes the DOM element the event is tied to. + */ +Scroller.prototype.checkViewportOnLoad = function( ev ) { + ev.data.self.ensureFilledViewport(); +} + +/** + * Identify archive page that corresponds to majority of posts shown in the current browser window. + */ +Scroller.prototype.determineURL = function () { + var self = window.infiniteScroll.scroller, + windowTop = $( window ).scrollTop(), + windowBottom = windowTop + $( window ).height(), + windowSize = windowBottom - windowTop, + setsInView = [], + pageNum = false; + + // Find out which sets are in view + $( '.' + self.wrapperClass ).each( function() { + var id = $( this ).attr( 'id' ), + setTop = $( this ).offset().top, + setHeight = $( this ).outerHeight( false ), + setBottom = 0, + setPageNum = $( this ).data( 'page-num' ); + + // Account for containers that have no height because their children are floated elements. + if ( 0 == setHeight ) { + $( '> *', this ).each( function() { + setHeight += $( this ).outerHeight( false ); + } ); + } + + // Determine position of bottom of set by adding its height to the scroll position of its top. + setBottom = setTop + setHeight; + + // Populate setsInView object. While this logic could all be combined into a single conditional statement, this is easier to understand. + if ( setTop < windowTop && setBottom > windowBottom ) { // top of set is above window, bottom is below + setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); + } + else if( setTop > windowTop && setTop < windowBottom ) { // top of set is between top (gt) and bottom (lt) + setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); + } + else if( setBottom > windowTop && setBottom < windowBottom ) { // bottom of set is between top (gt) and bottom (lt) + setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); + } + } ); + + // Parse number of sets found in view in an attempt to update the URL to match the set that comprises the majority of the window. + if ( 0 == setsInView.length ) { + pageNum = -1; + } + else if ( 1 == setsInView.length ) { + var setData = setsInView.pop(); + + // If the first set of IS posts is in the same view as the posts loaded in the template by WordPress, determine how much of the view is comprised of IS-loaded posts + if ( ( ( windowBottom - setData.top ) / windowSize ) < 0.5 ) + pageNum = -1; + else + pageNum = setData.pageNum; + } + else { + var majorityPercentageInView = 0; + + // Identify the IS set that comprises the majority of the current window and set the URL to it. + $.each( setsInView, function( i, setData ) { + var topInView = 0, + bottomInView = 0, + percentOfView = 0; + + // Figure percentage of view the current set represents + if ( setData.top > windowTop && setData.top < windowBottom ) + topInView = ( windowBottom - setData.top ) / windowSize; + + if ( setData.bottom > windowTop && setData.bottom < windowBottom ) + bottomInView = ( setData.bottom - windowTop ) / windowSize; + + // Figure out largest percentage of view for current set + if ( topInView >= bottomInView ) + percentOfView = topInView; + else if ( bottomInView >= topInView ) + percentOfView = bottomInView; + + // Does current set's percentage of view supplant the largest previously-found set? + if ( percentOfView > majorityPercentageInView ) { + pageNum = setData.pageNum; + majorityPercentageInView = percentOfView; + } + } ); + } + + // If a page number could be determined, update the URL + // -1 indicates that the original requested URL should be used. + if ( 'number' == typeof pageNum ) { + if ( pageNum != -1 ) + pageNum += ( 0 == self.offset ) ? 1 : self.offset; + + self.updateURL( pageNum ); + } +} + +/** + * Update address bar to reflect archive page URL for a given page number. + * Checks if URL is different to prevent polution of browser history. + */ +Scroller.prototype.updateURL = function( page ) { + var self = this, + pageSlug = -1 == page ? self.origURL : window.location.protocol + '//' + self.history.host + self.history.path.replace( /%d/, page ); + + if ( window.location.href != pageSlug ) + history.pushState( null, null, pageSlug ); +} + +/** + * Ready, set, go! + */ +$( document ).ready( function() { + // Check for our variables + if ( ! infiniteScroll ) + return; + + // Set ajaxurl (for brevity) + ajaxurl = infiniteScroll.settings.ajaxurl; + + // Set stats, used for tracking stats + stats = infiniteScroll.settings.stats; + + // Define what type of infinity we have, grab text for click-handle + type = infiniteScroll.settings.type; + text = infiniteScroll.settings.text; + totop = infiniteScroll.settings.totop; + + // Initialize the scroller (with the ID of the element from the theme) + infiniteScroll.scroller = new Scroller( infiniteScroll.settings ); + + /** + * Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts + * IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met. + */ + if ( ! isIE || ( isIE && IEVersion >= 10 ) ) { + $( window ).bind( 'scroll', function() { + clearTimeout( timer ); + timer = setTimeout( infiniteScroll.scroller.determineURL , 100 ); + }); + } +}); + + +})(jQuery); // Close closure
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.php b/plugins/jetpack/modules/infinite-scroll/infinity.php new file mode 100644 index 00000000..ad57f1b6 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/infinity.php @@ -0,0 +1,945 @@ +<?php + +/* +Plugin Name: The Neverending Home Page. +Plugin URI: http://automattic.com/ +Description: Adds infinite scrolling support to the front-end blog post view for themes, pulling the next set of posts automatically into view when the reader approaches the bottom of the page. +Version: 1.1 +Author: Automattic +Author URI: http://automattic.com/ +License: GNU General Public License v2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html +*/ + +/** + * Class: The_Neverending_Home_Page relies on add_theme_support, expects specific + * styling from each theme; including fixed footer. + */ +class The_Neverending_Home_Page { + /** + * + */ + function __construct() { + add_filter( 'pre_get_posts', array( $this, 'posts_per_page_query' ) ); + + add_action( 'admin_init', array( $this, 'settings_api_init' ) ); + add_action( 'template_redirect', array( $this, 'action_template_redirect' ) ); + add_action( 'template_redirect', array( $this, 'ajax_response' ) ); + add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) ); + add_action( 'the_post', array( $this, 'preserve_more_tag' ) ); + add_action( 'get_footer', array( $this, 'footer' ) ); + + // Plugin compatibility + add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) ); + + // Parse IS settings from theme + self::get_settings(); + } + + /** + * Initialize our static variables + */ + static $the_time = null; + static $settings = null; // Don't access directly, instead use self::get_settings(). + + static $option_name_enabled = 'infinite_scroll'; + + /** + * Parse IS settings provided by theme + * + * @uses get_theme_support, infinite_scroll_has_footer_widgets, sanitize_title, add_action, get_option, wp_parse_args, is_active_sidebar + * @return object + */ + static function get_settings() { + if ( is_null( self::$settings ) ) { + $css_pattern = '#[^A-Z\d\-_]#i'; + + $settings = $defaults = array( + 'type' => 'scroll', // scroll | click + 'requested_type' => 'scroll', // store the original type for use when logic overrides it + 'footer_widgets' => false, // true | false | sidebar_id | array of sidebar_ids -- last two are checked with is_active_sidebar + 'container' => 'content', // container html id + 'wrapper' => true, // true | false | html class + 'render' => false, // optional function, otherwise the `content` template part will be used + 'footer' => true, // boolean to enable or disable the infinite footer | string to provide an html id to derive footer width from + 'posts_per_page' => false // int | false to set based on IS type + ); + + // Validate settings passed through add_theme_support() + $_settings = get_theme_support( 'infinite-scroll' ); + + if ( is_array( $_settings ) ) { + // Preferred implementation, where theme provides an array of options + if ( isset( $_settings[0] ) && is_array( $_settings[0] ) ) { + foreach ( $_settings[0] as $key => $value ) { + switch ( $key ) { + case 'type' : + if ( in_array( $value, array( 'scroll', 'click' ) ) ) + $settings[ $key ] = $settings['requested_type'] = $value; + + break; + + case 'footer_widgets' : + if ( is_string( $value ) ) + $settings[ $key ] = sanitize_title( $value ); + elseif ( is_array( $value ) ) + $settings[ $key ] = array_map( 'sanitize_title', $value ); + elseif ( is_bool( $value ) ) + $settings[ $key ] = $value; + + break; + + case 'container' : + case 'wrapper' : + if ( 'wrapper' == $key && is_bool( $value ) ) { + $settings[ $key ] = $value; + } + else { + $value = preg_replace( $css_pattern, '', $value ); + + if ( ! empty( $value ) ) + $settings[ $key ] = $value; + } + + break; + + case 'render' : + if ( false !== $value && is_callable( $value ) ) { + $settings[ $key ] = $value; + + add_action( 'infinite_scroll_render', $value ); + } + + break; + + case 'footer' : + if ( is_bool( $value ) ) { + $settings[ $key ] = $value; + } + elseif ( is_string( $value ) ) { + $value = preg_replace( $css_pattern, '', $value ); + + if ( ! empty( $value ) ) + $settings[ $key ] = $value; + } + + break; + + case 'posts_per_page' : + if ( is_numeric( $value ) ) + $settings[ $key ] = (int) $value; + + break; + + default: + continue; + + break; + } + } + } + // Checks below are for backwards compatibility + elseif ( is_string( $_settings[0] ) ) { + // Container to append new posts to + $settings['container'] = preg_replace( $css_pattern, '', $_settings[0] ); + + // Wrap IS elements? + if ( isset( $_settings[1] ) ) + $settings['wrapper'] = (bool) $_settings[1]; + } + } + + // Always ensure all values are present in the final array + $settings = wp_parse_args( $settings, $defaults ); + + // Check if a legacy `infinite_scroll_has_footer_widgets()` function is defined and override the footer_widgets parameter's value. + // Otherwise, if a widget area ID or array of IDs was provided in the footer_widgets parameter, check if any contains any widgets. + // It is safe to use `is_active_sidebar()` before the sidebar is registered as this function doesn't check for a sidebar's existence when determining if it contains any widgets. + if ( function_exists( 'infinite_scroll_has_footer_widgets' ) ) { + $settings['footer_widgets'] = (bool) infinite_scroll_has_footer_widgets(); + } + elseif ( is_array( $settings['footer_widgets'] ) ) { + $sidebar_ids = $settings['footer_widgets']; + $settings['footer_widgets'] = false; + + foreach ( $sidebar_ids as $sidebar_id ) { + if ( is_active_sidebar( $sidebar_id ) ) { + $settings['footer_widgets'] = true; + break; + } + } + + unset( $sidebar_ids ); + unset( $sidebar_id ); + } + elseif ( is_string( $settings['footer_widgets'] ) ) { + $settings['footer_widgets'] = (bool) is_active_sidebar( $settings['footer_widgets'] ); + } + + // For complex logic, let themes filter the `footer_widgets` parameter. + $settings['footer_widgets'] = apply_filters( 'infinite_scroll_has_footer_widgets', $settings['footer_widgets'] ); + + // Finally, after all of the sidebar checks and filtering, ensure that a boolean value is present, otherwise set to default of `false`. + if ( ! is_bool( $settings['footer_widgets'] ) ) + $settings['footer_widgets'] = false; + + // Ensure that IS is enabled and no footer widgets exist if the IS type isn't already "click". + if ( 'click' != $settings['type'] ) { + // Check the setting status + $disabled = '' === get_option( self::$option_name_enabled ) ? true : false; + + // Footer content or Reading option check + if ( $settings['footer_widgets'] || $disabled ) + $settings['type'] = 'click'; + } + + // Backwards compatibility for posts_per_page setting + if ( false === $settings['posts_per_page'] ) + $settings['posts_per_page'] = 'click' == $settings['type'] ? (int) get_option( 'posts_per_page' ) : 7; + + // Store final settings in a class static to avoid reparsing + self::$settings = apply_filters( 'infinite_scroll_settings', $settings ); + } + + return (object) self::$settings; + } + + /** + * Has infinite scroll been triggered? + */ + static function got_infinity() { + return isset( $_GET[ 'infinity' ] ); + } + + /** + * The more tag will be ignored by default if the blog page isn't our homepage. + * Let's force the $more global to false. + */ + function preserve_more_tag( $array ) { + global $more; + + if ( self::got_infinity() ) + $more = 0; //0 = show content up to the more tag. Add more link. + + return $array; + } + + /** + * Add a checkbox field to Settings > Reading + * for enabling infinite scroll. + * + * Only show if the current theme supports infinity. + * + * @uses current_theme_supports, add_settings_field, __, register_setting + * @action admin_init + * @return null + */ + function settings_api_init() { + if ( ! current_theme_supports( 'infinite-scroll' ) ) + return; + + // Add the setting field [infinite_scroll] and place it in Settings > Reading + add_settings_field( self::$option_name_enabled, '<span id="infinite-scroll-options">' . __( 'To infinity and beyond', 'jetpack' ) . '</span>', array( $this, 'infinite_setting_html' ), 'reading' ); + register_setting( 'reading', self::$option_name_enabled, 'esc_attr' ); + } + + /** + * HTML code to display a checkbox true/false option + * for the infinite_scroll setting. + */ + function infinite_setting_html() { + $notice = '<em>' . __( "We've disabled this option for you since you have footer widgets in Appearance → Widgets, or because your theme does not support infinite scroll.", 'jetpack' ) . '</em>'; + + // If the blog has footer widgets, show a notice instead of the checkbox + if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) { + echo '<label>' . $notice . '</label>'; + } else { + echo '<label><input name="infinite_scroll" type="checkbox" value="1" ' . checked( 1, '' !== get_option( self::$option_name_enabled ), false ) . ' /> ' . __( 'Scroll Infinitely', 'jetpack' ) . '</br><small>' . sprintf( __( '(Shows %s posts on each load)', 'jetpack' ), number_format_i18n( self::get_settings()->posts_per_page ) ) . '</small>' . '</label>'; + } + } + + /** + * Does the legwork to determine whether the feature is enabled. + * + * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, self::set_last_post_time, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action + * @action template_redirect + * @return null + */ + function action_template_redirect() { + // Check that we support infinite scroll, and are on the home page. + if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() ) + return; + + $id = self::get_settings()->container; + + // Check that we have an id. + if ( empty( $id ) ) + return; + + // Bail if there are not enough posts for infinity. + if ( ! self::set_last_post_time() ) + return; + + // Add a class to the body. + add_filter( 'body_class', array( $this, 'body_class' ) ); + + // Add our scripts. + wp_enqueue_script( 'the-neverending-homepage', plugins_url( 'infinity.js', __FILE__ ), array( 'jquery' ), '20130101' ); + + // Add our default styles. + wp_enqueue_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20120612' ); + + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_spinner_scripts' ) ); + + add_action( 'wp_head', array( $this, 'action_wp_head' ), 2 ); + + add_action( 'wp_footer', array( $this, 'action_wp_footer' ), 99999999 ); + + add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ), 10, 3 ); + } + + /** + * Enqueue spinner scripts. + */ + function enqueue_spinner_scripts() { + wp_enqueue_script( 'jquery.spin' ); + } + + /** + * Adds an 'infinite-scroll' class to the body. + */ + function body_class( $classes ) { + $classes[] = 'infinite-scroll'; + + if ( 'scroll' == self::get_settings()->type ) + $classes[] = 'neverending'; + + return $classes; + } + + /** + * Grab the timestamp for the last post. + * @return string 'Y-m-d H:i:s' or null + */ + function set_last_post_time( $date = false ) { + global $posts; + $count = count( $posts ); + + if ( ! empty( $date ) && preg_match( '|\d{4}\-\d{2}\-\d{2}|', $_GET['date'] ) ) { + self::$the_time = "$date 00:00:00"; + return self::$the_time; + } + + // If we don't have enough posts for infinity, return early + if ( ! $count || $count < self::get_settings()->posts_per_page ) + return self::$the_time; + + $last_post = end( $posts ); + + // If the function is called again but we already have a value, return it + if ( null != self::$the_time ) { + return self::$the_time; + } + else if ( isset( $last_post->post_date_gmt ) ) { + // Grab the latest post time in Y-m-d H:i:s gmt format + self::$the_time = $last_post->post_date_gmt; + } + + return self::$the_time; + } + + /** + * Create a where clause that will make sure post queries + * will always return results prior to (descending sort) + * or before (ascending sort) the last post date. + * + * @param string $where + * @param object $query + * @filter posts_where + * @return string + */ + function query_time_filter( $where, $query ) { + global $wpdb; + + $operator = 'ASC' == $query->get( 'order' ) ? '>' : '<'; + + // Construct the date query using our timestamp + $where .= $wpdb->prepare( " AND post_date_gmt {$operator} %s", self::set_last_post_time() ); + + return $where; + } + + /** + * Let's overwrite the default post_per_page setting to always display a fixed amount. + * + * @global $wp_the_query Used to provide compatibility back to WP 3.2 + * @param object $query + * @uses self::archive_supports_infinity, self::get_settings + * @return null + */ + function posts_per_page_query( $query ) { + global $wp_the_query; + + if ( self::archive_supports_infinity() && $query === $wp_the_query ) // After 3.3, this line would be: if ( self::archive_supports_infinity() && $query->is_main_query() ) + $query->set( 'posts_per_page', self::get_settings()->posts_per_page ); + } + + /** + * Check if the IS output should be wrapped in a div. + * Setting value can be a boolean or a string specifying the class applied to the div. + * + * @uses self::get_settings + * @return bool + */ + function has_wrapper() { + return (bool) self::get_settings()->wrapper; + } + + /** + * Returns the Ajax url + * + * @global $wp + * @uses home_url, is_ssl, add_query_arg, trailingslashit, apply_filters + * @return string + */ + function ajax_url() { + global $wp; + + // When using default permalinks, $wp->request will be null, so we reconstruct the request from the query arguments WP parsed. + if ( is_null( $wp->request ) ) { + $base_url = home_url( '/', is_ssl() ? 'https' : 'http' ); + $base_url = add_query_arg( $wp->query_vars, $base_url ); + } else { + $base_url = home_url( trailingslashit( $wp->request ), is_ssl() ? 'https' : 'http' ); + } + + $ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url ); + + return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl ); + } + + /** + * Our own Ajax response, avoiding calling admin-ajax + */ + function ajax_response() { + // Only proceed if the url query has a key of "Infinity" + if ( ! self::got_infinity() ) + return false; + + define( 'DOING_AJAX', true ); + + @header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) ); + send_nosniff_header(); + + do_action( 'custom_ajax_infinite_scroll' ); + die( '0' ); + } + + /** + * Prints the relevant infinite scroll settings in JS. + * + * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action + * @action wp_head + * @return string + */ + function action_wp_head() { + global $wp_query, $wp_the_query, $wp_rewrite; + + // Base JS settings + $js_settings = array( + 'id' => self::get_settings()->container, + 'ajaxurl' => esc_url_raw( self::ajax_url() ), + 'type' => esc_js( self::get_settings()->type ), + 'wrapper' => self::has_wrapper(), + 'wrapper_class' => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap', + 'footer' => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer, + 'text' => esc_js( __( 'Older posts', 'jetpack' ) ), + 'totop' => esc_js( __( 'Scroll back to top', 'jetpack' ) ), + 'order' => 'DESC', + 'scripts' => array(), + 'styles' => array(), + 'google_analytics' => false, + 'offset' => $wp_query->get( 'paged' ), + 'history' => array( + 'host' => preg_replace( '#^http(s)?://#i', '', untrailingslashit( get_option( 'home' ) ) ), + 'path' => self::get_request_path(), + 'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes + ) + ); + + // Optional order param + if ( isset( $_GET['order'] ) ) { + $order = strtoupper( $_GET['order'] ); + + if ( in_array( $order, array( 'ASC', 'DESC' ) ) ) + $js_settings['order'] = $order; + } + + $js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings ); + + do_action( 'infinite_scroll_wp_head' ); + + ?> + <script type="text/javascript"> + //<![CDATA[ + var infiniteScroll = <?php echo json_encode( array( 'settings' => $js_settings ) ); ?>; + //]]> + </script> + <?php + } + + /** + * Build path data for current request. + * Used for Google Analytics and pushState history tracking. + * + * @global $wp_rewrite + * @global $wp + * @uses user_trailingslashit, sanitize_text_field, add_query_arg + * @return string|bool + */ + private function get_request_path() { + global $wp_rewrite; + + if ( $wp_rewrite->using_permalinks() ) { + global $wp; + + // If called too early, bail + if ( ! isset( $wp->request ) ) + return false; + + // Determine path for paginated version of current request + if ( false != preg_match( '#' . $wp_rewrite->pagination_base . '/\d+/?$#i', $wp->request ) ) + $path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request ); + else + $path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d'; + + // Slashes everywhere we need them + if ( 0 !== strpos( $path, '/' ) ) + $path = '/' . $path; + + $path = user_trailingslashit( $path ); + } + else { + // Clean up raw $_GET input + $path = array_map( 'sanitize_text_field', $_GET ); + $path = array_filter( $path ); + + $path['paged'] = '%d'; + + $path = add_query_arg( $path, '/' ); + } + + return empty( $path ) ? false : $path; + } + + /** + * Provide IS with a list of the scripts and stylesheets already present on the page. + * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets. + * + * @global $wp_scripts, $wp_styles + * @action wp_footer + * @return string + */ + function action_wp_footer() { + global $wp_scripts, $wp_styles; + + $scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array(); + $scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts ); + + $styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array(); + $styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles ); + + ?><script type="text/javascript"> + jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> ); + jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> ); + </script><?php + } + + /** + * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler. + * + * @global $wp_scripts + * @uses sanitize_text_field, add_query_arg + * @filter infinite_scroll_results + * @return array + */ + function filter_infinite_scroll_results( $results, $query_args, $wp_query ) { + // Don't bother unless there are posts to display + if ( 'success' != $results['type'] ) + return $results; + + // Parse and sanitize the script handles already output + $initial_scripts = isset( $_GET['scripts'] ) && is_array( $_GET['scripts'] ) ? array_map( 'sanitize_text_field', $_GET['scripts'] ) : false; + + if ( is_array( $initial_scripts ) ) { + global $wp_scripts; + + // Identify new scripts needed by the latest set of IS posts + $new_scripts = array_diff( $wp_scripts->done, $initial_scripts ); + + // If new scripts are needed, extract relevant data from $wp_scripts + if ( ! empty( $new_scripts ) ) { + $results['scripts'] = array(); + + foreach ( $new_scripts as $handle ) { + // Abort if somehow the handle doesn't correspond to a registered script + if ( ! isset( $wp_scripts->registered[ $handle ] ) ) + continue; + + // Provide basic script data + $script_data = array( + 'handle' => $handle, + 'footer' => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ), + 'extra_data' => $wp_scripts->print_extra_script( $handle, false ) + ); + + // Base source + $src = $wp_scripts->registered[ $handle ]->src; + + // Take base_url into account + if ( strpos( $src, 'http' ) !== 0 ) + $src = $wp_scripts->base_url . $src; + + // Version and additional arguments + if ( null === $wp_scripts->registered[ $handle ]->ver ) + $ver = ''; + else + $ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version; + + if ( isset($wp_scripts->args[ $handle ] ) ) + $ver = $ver ? $ver . '&' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle]; + + // Full script source with version info + $script_data['src'] = add_query_arg( 'ver', $ver, $src ); + + // Add script to data that will be returned to IS JS + array_push( $results['scripts'], $script_data ); + } + } + } + + // Expose additional script data to filters, but only include in final `$results` array if needed. + if ( ! isset( $results['scripts'] ) ) + $results['scripts'] = array(); + + $results['scripts'] = apply_filters( 'infinite_scroll_additional_scripts', $results['scripts'], $initial_scripts, $results, $query_args, $wp_query ); + + if ( empty( $results['scripts'] ) ) + unset( $results['scripts' ] ); + + // Parse and sanitize the style handles already output + $initial_styles = isset( $_GET['styles'] ) && is_array( $_GET['styles'] ) ? array_map( 'sanitize_text_field', $_GET['styles'] ) : false; + + if ( is_array( $initial_styles ) ) { + global $wp_styles; + + // Identify new styles needed by the latest set of IS posts + $new_styles = array_diff( $wp_styles->done, $initial_styles ); + + // If new styles are needed, extract relevant data from $wp_styles + if ( ! empty( $new_styles ) ) { + $results['styles'] = array(); + + foreach ( $new_styles as $handle ) { + // Abort if somehow the handle doesn't correspond to a registered stylesheet + if ( ! isset( $wp_styles->registered[ $handle ] ) ) + continue; + + // Provide basic style data + $style_data = array( + 'handle' => $handle, + 'media' => 'all' + ); + + // Base source + $src = $wp_styles->registered[ $handle ]->src; + + // Take base_url into account + if ( strpos( $src, 'http' ) !== 0 ) + $src = $wp_styles->base_url . $src; + + // Version and additional arguments + if ( null === $wp_styles->registered[ $handle ]->ver ) + $ver = ''; + else + $ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version; + + if ( isset($wp_styles->args[ $handle ] ) ) + $ver = $ver ? $ver . '&' . $wp_styles->args[$handle] : $wp_styles->args[$handle]; + + // Full stylesheet source with version info + $style_data['src'] = add_query_arg( 'ver', $ver, $src ); + + // Parse stylesheet's conditional comments if present, converting to logic executable in JS + if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) { + // First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version + $style_data['conditional'] = str_replace( array( + 'lte', + 'lt', + 'gte', + 'gt' + ), array( + '%ver <=', + '%ver <', + '%ver >=', + '%ver >', + ), $wp_styles->registered[ $handle ]->extra['conditional'] ); + + // Next, replace any !IE checks. These shouldn't be present since WP's conditional stylesheet implementation doesn't support them, but someone could be _doing_it_wrong(). + $style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] ); + + // Lastly, remove the IE strings + $style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] ); + } + + // Parse requested media context for stylesheet + if ( isset( $wp_styles->registered[ $handle ]->args ) ) + $style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args ); + + // Add stylesheet to data that will be returned to IS JS + array_push( $results['styles'], $style_data ); + } + } + } + + // Expose additional stylesheet data to filters, but only include in final `$results` array if needed. + if ( ! isset( $results['styles'] ) ) + $results['styles'] = array(); + + $results['styles'] = apply_filters( 'infinite_scroll_additional_stylesheets', $results['styles'], $initial_styles, $results, $query_args, $wp_query ); + + if ( empty( $results['styles'] ) ) + unset( $results['styles' ] ); + + // Lastly, return the IS results array + return $results; + } + + /** + * Runs the query and returns the results via JSON. + * Triggered by an AJAX request. + * + * @global $wp_query + * @global $wp_the_query + * @uses current_user_can, get_option, self::set_last_post_time, current_user_can, apply_filters, self::get_settings, add_filter, WP_Query, remove_filter, have_posts, wp_head, do_action, add_action, this::render, this::has_wrapper, esc_attr, wp_footer, sharing_register_post_for_share_counts, get_the_id + * @return string or null + */ + function query() { + global $wp_query, $wp_the_query; + + if ( ! isset( $_GET['page'] ) || ! current_theme_supports( 'infinite-scroll' ) ) + die; + + $page = (int) $_GET['page']; + $sticky = get_option( 'sticky_posts' ); + + if ( ! empty( $_GET['date'] ) ) + self::set_last_post_time( $_GET['date'] ); + + $post_status = array( 'publish' ); + if ( current_user_can( 'read_private_posts' ) ) + array_push( $post_status, 'private' ); + + $order = in_array( $_GET['order'], array( 'ASC', 'DESC' ) ) ? $_GET['order'] : 'DESC'; + + $query_args = array_merge( $wp_the_query->query_vars, array( + 'paged' => $page, + 'post_status' => $post_status, + 'posts_per_page' => self::get_settings()->posts_per_page, + 'post__not_in' => ( array ) $sticky, + 'order' => $order + ) ); + + // By default, don't query for a specific page of a paged post object. + // This argument comes from merging $wp_the_query. + // Since IS is only used on archives, we should always display the first page of any paged content. + unset( $query_args['page'] ); + + $query_args = apply_filters( 'infinite_scroll_query_args', $query_args ); + + // Add query filter that checks for posts below the date + add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 ); + + $wp_the_query = $wp_query = new WP_Query( $query_args ); + + remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 ); + + $results = array(); + + if ( have_posts() ) { + // Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer. + ob_start(); + wp_head(); + ob_end_clean(); + + $results['type'] = 'success'; + + // First, try theme's specified rendering handler, either specified via `add_theme_support` or by hooking to this action directly. + ob_start(); + do_action( 'infinite_scroll_render' ); + $results['html'] = ob_get_clean(); + + // Fall back if a theme doesn't specify a rendering function. Because themes may hook additional functions to the `infinite_scroll_render` action, `has_action()` is ineffective here. + if ( empty( $results['html'] ) ) { + add_action( 'infinite_scroll_render', array( $this, 'render' ) ); + rewind_posts(); + + ob_start(); + do_action( 'infinite_scroll_render' ); + $results['html'] = ob_get_clean(); + } + + // If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested. + if ( empty( $results['html'] ) ) { + unset( $results['html'] ); + do_action( 'infinite_scroll_empty' ); + $results['type'] = 'empty'; + } + elseif ( $this->has_wrapper() ) { + $wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap'; + $wrapper_classes .= ' infinite-view-' . $page; + $wrapper_classes = trim( $wrapper_classes ); + + $results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>'; + } + + // Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer. + ob_start(); + wp_footer(); + ob_end_clean(); + + // Loop through posts to capture sharing data for new posts loaded via Infinite Scroll + if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) { + global $jetpack_sharing_counts; + + while( have_posts() ) { + the_post(); + + sharing_register_post_for_share_counts( get_the_ID() ); + } + + $results['postflair'] = array_flip( $jetpack_sharing_counts ); + } + } else { + do_action( 'infinite_scroll_empty' ); + $results['type'] = 'empty'; + } + + echo json_encode( apply_filters( 'infinite_scroll_results', $results, $query_args, $wp_query ) ); + die; + } + + /** + * Rendering fallback used when themes don't specify their own handler. + * + * @uses have_posts, the_post, get_template_part, get_post_format + * @action infinite_scroll_render + * @return string + */ + function render() { + while ( have_posts() ) { + the_post(); + + get_template_part( 'content', get_post_format() ); + } + } + + /** + * Allow plugins to filter what archives Infinite Scroll supports + * + * @uses apply_filters, current_theme_supports, is_home, is_archive, self::get_settings + * @return bool + */ + public static function archive_supports_infinity() { + return (bool) apply_filters( 'infinite_scroll_archive_supported', current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() ), self::get_settings() ); + } + + /** + * The Infinite Blog Footer + * + * @uses self::get_settings, self::set_last_post_time, self::archive_supports_infinity, __, wp_get_theme, get_current_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo + * @return string or null + */ + function footer() { + // Bail if theme requested footer not show + if ( false == self::get_settings()->footer ) + return; + + // Bail if there are not enough posts for infinity. + if ( ! self::set_last_post_time() ) + return; + + // We only need the new footer for the 'scroll' type + if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() ) + return; + + $credits = '<a href="http://wordpress.org/" rel="generator">Proudly powered by WordPress</a> '; + $credits .= sprintf( __( 'Theme: %1$s.', 'jetpack' ), function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme() ); + $credits = apply_filters( 'infinite_scroll_credit', $credits ); + + ?> + <div id="infinite-footer"> + <div class="container"> + <div class="blog-info"> + <a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" title="<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?>" rel="home"> + <?php bloginfo( 'name' ); ?> + </a> + </div> + <div class="blog-credits"> + <?php echo $credits; ?> + </div> + </div> + </div><!-- #infinite-footer --> + <?php + } + + /** + * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL. + * When arguments are present, Grunion redirects to the IS AJAX endpoint. + * + * @param string $url + * @uses remove_query_arg + * @filter grunion_contact_form_redirect_url + * @return string + */ + public function filter_grunion_redirect_url( $url ) { + // Remove IS query args, if present + if ( false !== strpos( $url, 'infinity=scrolling' ) ) { + $url = remove_query_arg( array( + 'infinity', + 'action', + 'page', + 'order', + 'scripts', + 'styles' + ), $url ); + } + + return $url; + } +}; + +/** + * Initialize The_Neverending_Home_Page + */ +function the_neverending_home_page_init() { + if ( ! current_theme_supports( 'infinite-scroll' ) ) + return; + + new The_Neverending_Home_Page; +} +add_action( 'init', 'the_neverending_home_page_init', 20 ); + +/** + * Check whether the current theme is infinite-scroll aware. + * If so, include the files which add theme support. + */ +function the_neverending_home_page_theme_support() { + $theme_name = get_stylesheet(); + + $customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name ); + + if ( is_readable( $customization_file ) ) + require_once( $customization_file ); +} +add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 ); diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css new file mode 100644 index 00000000..cc232785 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css @@ -0,0 +1,45 @@ +/* =Infinity Styles +-------------------------------------------------------------- */ +.infinite-scroll #main:after { + clear: both; + content: ''; + display: block; +} +.infinite-scroll #content { + margin-bottom: 40px; +} +.infinite-scroll.neverending #content { + margin-bottom: 70px; +} +.infinite-scroll .infinite-wrap { + border-top: none; + padding-top: 0; +} +.infinite-scroll .infinite-wrap .hentry:last-child { + border-bottom: 1px solid #ddd; +} +.infinite-scroll .infinite-wrap:last-of-type .hentry:last-child { + border-bottom: none; +} + +/** + * Elements to hide: + * (footer widgets, post navigation, regular footer) + */ +.infinite-scroll.neverending #colophon #supplementary, +.infinite-scroll #nav-below, +.infinite-scroll.neverending #colophon { + display: none; +} + +/* Hooks to infinity-end body class to restore footer */ +.infinity-end.neverending #colophon { + display: block; +} + +/* For responsive CSS */ +@media (max-width: 800px) { + .infinite-scroll #infinite-handle { + padding-bottom: 40px; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.php b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.php new file mode 100644 index 00000000..a80ee810 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.php @@ -0,0 +1,27 @@ +<?php +/** + * Infinite Scroll Theme Assets + * + * Register support for @Twenty Eleven and enqueue relevant styles. + */ + +/** + * Add theme support for infinity scroll + */ +function twenty_eleven_infinite_scroll_init() { + add_theme_support( 'infinite-scroll', array( + 'container' => 'content', + 'footer_widgets' => array( 'sidebar-3', 'sidebar-4', 'sidebar-5' ), + 'footer' => 'page', + ) ); +} +add_action( 'init', 'twenty_eleven_infinite_scroll_init' ); + +/** + * Enqueue CSS stylesheet with theme styles for infinity. + */ +function twenty_eleven_infinite_scroll_enqueue_styles() { + // Add theme specific styles. + wp_enqueue_style( 'infinity-twentyeleven', plugins_url( 'twentyeleven.css', __FILE__ ), array( 'the-neverending-homepage' ), '20121002' ); +} +add_action( 'wp_enqueue_scripts', 'twenty_eleven_infinite_scroll_enqueue_styles', 25 );
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyten.css b/plugins/jetpack/modules/infinite-scroll/themes/twentyten.css new file mode 100644 index 00000000..889abb3f --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyten.css @@ -0,0 +1,25 @@ +/* =Infinity Styles +-------------------------------------------------------------- */ +.infinite-scroll #wrapper { + margin-bottom: 40px; +} +.infinite-scroll #content { + margin-bottom: 50px; +} +.infinite-scroll #content .infinite-wrap { + padding-top: 0; + border-top: 0; +} +/* Elements to hide */ +.infinite-scroll #nav-above, +.infinite-scroll #nav-below, +.infinite-scroll.neverending #footer { + display: none; +} +/* Restore the footer when IS is finished */ +.infinity-end.neverending #footer { + display: block; +} +#infinite-footer .blog-info a { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyten.php b/plugins/jetpack/modules/infinite-scroll/themes/twentyten.php new file mode 100644 index 00000000..094cef9c --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyten.php @@ -0,0 +1,48 @@ +<?php +/** + * Infinite Scroll Theme Assets + * + * Register support for @Twenty Ten and enqueue relevant styles. + */ + +/** + * Add theme support for infinity scroll + */ +function twenty_ten_infinite_scroll_init() { + add_theme_support( 'infinite-scroll', array( + 'container' => 'content', + 'render' => 'twenty_ten_infinite_scroll_render', + 'footer' => 'wrapper', + ) ); +} +add_action( 'init', 'twenty_ten_infinite_scroll_init' ); + +/** + * Set the code to be rendered on for calling posts, + * hooked to template parts when possible. + * + * Note: must define a loop. + */ +function twenty_ten_infinite_scroll_render() { + get_template_part( 'loop' ); +} + +/** + * Enqueue CSS stylesheet with theme styles for infinity. + */ +function twenty_ten_infinite_scroll_enqueue_styles() { + // Add theme specific styles. + wp_enqueue_style( 'infinity-twentyten', plugins_url( 'twentyten.css', __FILE__ ), array( 'the-neverending-homepage' ), '20121002' ); +} +add_action( 'wp_enqueue_scripts', 'twenty_ten_infinite_scroll_enqueue_styles', 25 ); + +/** + * Do we have footer widgets? + */ +function twenty_ten_has_footer_widgets( $has_widgets ) { + if ( is_active_sidebar( 'first-footer-widget-area' ) || is_active_sidebar( 'second-footer-widget-area' ) || is_active_sidebar( 'third-footer-widget-area' ) || is_active_sidebar( 'fourth-footer-widget-area' ) ) + $has_widgets = true; + + return $has_widgets; +} +add_filter( 'infinite_scroll_has_footer_widgets', 'twenty_ten_has_footer_widgets' );
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.css b/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.css new file mode 100644 index 00000000..032c2c9a --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.css @@ -0,0 +1,33 @@ +/* =Infinity Styles +-------------------------------------------------------------- */ +.infinite-scroll .site-content:after { + clear: both; + content: ''; + display: block; +} +.infinite-wrap { + border-top: 0; +} +.infinite-scroll.neverending .site-content { + margin-bottom: 48px; + margin-bottom: 3.428571429rem; +} + +/* Elements to hide: post navigation, regular footer */ +.infinite-scroll #nav-below, +.infinite-scroll.neverending #colophon { + display: none; +} + +/* Hooks to infinity-end body class to restore footer */ +.infinity-end.neverending #colophon { + display: block; +} + +/* For responsive CSS */ +@media (max-width: 599px) { + .infinite-scroll #infinite-handle { + padding-bottom: 48px; + padding-bottom: 3.428571429rem; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.php b/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.php new file mode 100644 index 00000000..f8b77011 --- /dev/null +++ b/plugins/jetpack/modules/infinite-scroll/themes/twentytwelve.php @@ -0,0 +1,46 @@ +<?php +/** + * Infinite Scroll Theme Assets + * + * Register support for Twenty Twelve and enqueue relevant styles. + */ + +/** + * Add theme support for infinite scroll + */ +function twenty_twelve_infinite_scroll_init() { + add_theme_support( 'infinite-scroll', array( + 'container' => 'content', + 'footer' => 'page' + ) ); +} +add_action( 'after_setup_theme', 'twenty_twelve_infinite_scroll_init' ); + +/** + * Enqueue CSS stylesheet with theme styles for infinity. + */ +function twenty_twelve_infinite_scroll_enqueue_styles() { + // Add theme specific styles. + wp_enqueue_style( 'infinity-twentytwelve', plugins_url( 'twentytwelve.css', __FILE__ ), array( 'the-neverending-homepage' ), '20120817' ); +} +add_action( 'wp_enqueue_scripts', 'twenty_twelve_infinite_scroll_enqueue_styles', 25 ); + +/** + * Handle `footer_widgets` argument for mobile devices + * + * @param bool $has_widgets + * @uses jetpack_is_mobile, is_front_page, is_active_sidebar + * @filter infinite_scroll_has_footer_widgets + * @return bool + */ +function twenty_twelve_has_footer_widgets( $has_widgets ) { + if ( function_exists( 'jetpack_is_mobile' ) && jetpack_is_mobile() ) { + if ( is_front_page() && ( is_active_sidebar( 'sidebar-2' ) || is_active_sidebar( 'sidebar-3' ) ) ) + $has_widgets = true; + elseif ( is_active_sidebar( 'sidebar-1' ) ) + $has_widgets = true; + } + + return $has_widgets; +} +add_filter( 'infinite_scroll_has_footer_widgets', 'twenty_twelve_has_footer_widgets' );
\ No newline at end of file diff --git a/plugins/jetpack/modules/json-api.php b/plugins/jetpack/modules/json-api.php new file mode 100644 index 00000000..fb1473ff --- /dev/null +++ b/plugins/jetpack/modules/json-api.php @@ -0,0 +1,19 @@ +<?php +/** + * Module Name: JSON API + * Module Description: Allow applications to securely access your content through the cloud. + * Sort Order: 100 + * First Introduced: 1.9 + */ + +function jetpack_json_api_toggle() { + $jetpack = Jetpack::init(); + $jetpack->sync->register( 'noop' ); + + if ( false !== strpos( current_filter(), 'jetpack_activate_module_' ) ) { + Jetpack::check_privacy( __FILE__ ); + } +} + +add_action( 'jetpack_activate_module_json-api', 'jetpack_json_api_toggle' ); +add_action( 'jetpack_deactivate_module_json-api', 'jetpack_json_api_toggle' ); diff --git a/plugins/jetpack/modules/latex.php b/plugins/jetpack/modules/latex.php index 4e4d9db2..76066cd6 100644 --- a/plugins/jetpack/modules/latex.php +++ b/plugins/jetpack/modules/latex.php @@ -1,7 +1,7 @@ <?php /** * Module Name: Beautiful Math - * Module Description: Mark up your posts with the <img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-2" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /> markup language, perfect for complex mathematical equations and other über-geekery. + * Module Description: Mark up your posts with the <img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-2" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /> markup language, perfect for complex mathematical equations and other über-geekery. * Sort Order: 12 * First Introduced: 1.1 */ @@ -64,11 +64,11 @@ function latex_entity_decode( $latex ) { } function latex_render( $latex, $fg, $bg, $s = 0 ) { - $url = ( is_ssl() ? 'https://s-ssl.wordpress.com' : 'http://s0.wp.com' ) . "/latex.php?latex=" . urlencode( $latex ) . "&bg=$bg&fg=$fg&s=$s"; + $url = "//s0.wp.com/latex.php?latex=" . urlencode( $latex ) . "&bg=" . $bg . "&fg=" . $fg . "&s=" . $s; $url = esc_url( $url ); $alt = str_replace( '\\', '\', esc_attr( $latex ) ); - return "<img src='$url' alt='$alt' title='$alt' class='latex' />"; + return '<img src="' . $url . '" alt="' . $alt . '" title="' . $alt . '" class="latex" />'; } /** diff --git a/plugins/jetpack/modules/likes.php b/plugins/jetpack/modules/likes.php new file mode 100644 index 00000000..753d4ab4 --- /dev/null +++ b/plugins/jetpack/modules/likes.php @@ -0,0 +1,970 @@ +<?php +/** + * Module Name: Likes + * Module Description: Likes are a way for people to show their appreciation for content you have written. It’s also a way for you to show the world how popular your content has become. + * First Introduced: 2.2 + * Sort Order: 4 + */ +class Jetpack_Likes { + var $version = '20130226'; + + function &init() { + static $instance = NULL; + + if ( ! $instance ) { + $instance = new Jetpack_Likes; + } + + return $instance; + } + + function __construct() { + $this->in_jetpack = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? false : true; + + add_action( 'init', array( &$this, 'action_init' ) ); + + if ( $this->in_jetpack ) { + add_action( 'jetpack_activate_module_likes', array( $this, 'module_toggle' ) ); + add_action( 'jetpack_deactivate_module_likes', array( $this, 'module_toggle' ) ); + + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, array( 'Jetpack_Likes', 'configuration_redirect' ) ); + + add_action('admin_print_scripts-settings_page_sharing', array( &$this, 'load_jp_css' ) ); + add_filter( 'sharing_show_buttons_on_row_start', array( $this, 'configuration_target_area' ) ); + + $active = Jetpack::get_active_modules(); + + if ( ! in_array( 'sharedaddy', $active ) && ! in_array( 'publicize', $active ) ) { + add_action( 'admin_menu', array( $this, 'sharing_menu' ) ); // we don't have a sharing page yet + } + + if ( in_array( 'publicize', $active ) && ! in_array( 'sharedaddy', $active ) ) { + add_action( 'pre_admin_screen_sharing', array( $this, 'sharing_block' ), 20 ); // we have a sharing page but not the global options area + add_action( 'pre_admin_screen_sharing', array( $this, 'updated_message' ), -10 ); + } + + if( ! in_array( 'sharedaddy', $active ) ) { + add_action( 'admin_init', array( $this, 'process_update_requests_if_sharedaddy_not_loaded' ) ); + add_action( 'sharing_global_options', array( $this, 'admin_settings_showbuttonon_init' ), 19 ); + add_action( 'sharing_admin_update', array( $this, 'admin_settings_showbuttonon_callback' ), 19 ); + add_action( 'admin_init', array( $this, 'add_meta_box' ) ); + } else { + add_filter( 'sharing_meta_box_title', array( $this, 'add_likes_to_sharing_meta_box_title' ) ); + add_action( 'start_sharing_meta_box_content', array( $this, 'meta_box_content' ) ); + } + } else { // wpcom + add_action( 'admin_init', array( $this, 'add_meta_box' ) ); + add_action( 'end_likes_meta_box_content', array( $this, 'sharing_meta_box_content' ) ); + add_filter( 'likes_meta_box_title', array( $this, 'add_likes_to_sharing_meta_box_title' ) ); + } + + add_action( 'admin_bar_menu', array( $this, 'admin_bar_likes' ), 60 ); + + add_action( 'save_post', array( $this, 'meta_box_save' ) ); + add_action( 'sharing_global_options', array( $this, 'admin_settings_init' ), 20 ); + add_action( 'sharing_admin_update', array( $this, 'admin_settings_callback' ), 20 ); + } + + function module_toggle() { + $jetpack = Jetpack::init(); + $jetpack->sync->register( 'noop' ); + } + + /** + * Redirects to the likes section of the sharing page. + */ + function configuration_redirect() { + wp_safe_redirect( admin_url( 'options-general.php?page=sharing#likes' ) ); + die(); + } + + /** + * Loads Jetpack's CSS on the sharing page so we can use .jetpack-targetable + */ + function load_jp_css() { + Jetpack::init()->admin_styles(); + } + + /** + * Adds in the jetpack-targetable class so when we visit sharing#likes our like settings get highlighted by a yellow box + * @param string $html row heading for the sharedaddy "which page" setting + * @return string html with the jetpack-targetable class and likes id. tbody gets closed after the like settings + */ + function configuration_target_area( $html = '' ) { + $html = "<tbody id='likes' class='jetpack-targetable'>" . $html; + return $html; + } + + /** + * Replaces the "Sharing" title for the post screen metabox with "Likes and Shares" + * @param string $title The current title of the metabox, not needed/used. + */ + function add_likes_to_sharing_meta_box_title( $title ) { + return __( 'Likes and Shares', 'jetpack' ); + } + + /** + * Adds a metabox to the post screen if the sharing one doesn't currently exist. + */ + function add_meta_box() { + if ( apply_filters( 'post_flair_disable', false ) ) + return; + + $post_types = get_post_types( array( 'public' => true ) ); + $title = apply_filters( 'likes_meta_box_title', __( 'Likes', 'jetpack' ) ); + foreach( $post_types as $post_type ) { + add_meta_box( 'likes_meta', $title, array( $this, 'meta_box_content' ), $post_type, 'advanced', 'high' ); + } + } + + function meta_box_save( $post_id ) { + if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) + return $post_id; + + // Record sharing disable. Only needs to be done for WPCOM + if ( ! $this->in_jetpack ) { + if ( isset( $_POST['post_type'] ) && ( 'post' == $_POST['post_type'] || 'page' == $_POST['post_type'] ) ) { + if ( isset( $_POST['wpl_sharing_status_hidden'] ) && !isset( $_POST['wpl_enable_post_sharing'] ) ) { + update_post_meta( $post_id, 'sharing_disabled', 1 ); + } else { + delete_post_meta( $post_id, 'sharing_disabled' ); + } + } + } + + if ( empty( $_POST['wpl_like_status_hidden'] ) ) + return $post_id; + + if ( 'post' == $_POST['post_type'] ) { + if ( !current_user_can( 'edit_post', $post_id ) ) { + return $post_id; + } + } + + // Record a change in like status for this post - only if it contradicts the + // site like setting. + if ( ( $this->is_enabled_sitewide() && empty( $_POST['wpl_enable_post_likes'] ) ) || ( ! $this->is_enabled_sitewide() && !empty( $_POST['wpl_enable_post_likes'] ) ) ) { + update_post_meta( $post_id, 'switch_like_status', 1 ); + //$g_gif = file_get_contents( 'http://stats.wordpress.com/g.gif?v=wpcom-no-pv&x_likes=switched_post_like_status' ); @todo stat + } else { + delete_post_meta( $post_id, 'switch_like_status' ); + } + + return $post_id; + } + + /** + * Shows the likes option in the post screen metabox. + */ + function meta_box_content( $post ) { + $post_id = ! empty( $post->ID ) ? (int) $post->ID : get_the_ID(); + $checked = true; + $disabled = ! $this->is_enabled_sitewide(); + $switched_status = get_post_meta( $post_id, 'switch_like_status', true ); + + if ( $disabled && empty( $switched_status ) || false == $disabled && !empty( $switched_status ) ) + $checked = false; + + do_action( 'start_likes_meta_box_content', $post ); + ?> + + <p> + <label for="wpl_enable_post_likes"> + <input type="checkbox" name="wpl_enable_post_likes" id="wpl_enable_post_likes" value="1" <?php checked( $checked ); ?>> + <?php esc_html_e( 'Show likes.', 'jetpack' ); ?> + </label> + <input type="hidden" name="wpl_like_status_hidden" value="1" /> + </p> <?php + do_action( 'end_likes_meta_box_content', $post ); + } + + /** + * WordPress.com: Metabox option for sharing (sharedaddy will handle this on the JP blog) + */ + function sharing_meta_box_content( $post ) { + $post_id = ! empty( $post->ID ) ? (int) $post->ID : get_the_ID(); + $disabled = get_post_meta( $post_id, 'sharing_disabled', true ); ?> + <p> + <label for="wpl_enable_post_sharing"> + <input type="checkbox" name="wpl_enable_post_sharing" id="wpl_enable_post_sharing" value="1" <?php checked( !$disabled ); ?>> + <?php _e( 'Show sharing buttons.', 'jetpack' ); ?> + </label> + <input type="hidden" name="wpl_sharing_status_hidden" value="1" /> + </p> <?php + } + + /** + * The actual options block to be inserted into the sharing page. + */ + function admin_settings_init() { ?> + <tr> + <th scope="row"> + <label><?php esc_html_e( 'WordPress.com Likes are', 'jetpack' ); ?></label> + </th> + <td> + <div> + <label> + <input type="radio" class="code" name="wpl_default" value="on" <?php checked( $this->is_enabled_sitewide(), true ); ?> /> + <?php esc_html_e( 'On for all posts', 'jetpack' ); ?> + </label> + </div> + <div> + <label> + <input type="radio" class="code" name="wpl_default" value="off" <?php checked( $this->is_enabled_sitewide(), false ); ?> /> + <?php esc_html_e( 'Turned on per post', 'jetpack' ); ?> + </label> + <div> + </td> + </tr> <?php /* + <tr> + <th scope="row"> + <label><?php esc_html_e( 'Comment Likes', 'jetpack' ); ?></label> + </th> + <td> + <div> + <label> + <input type="checkbox" class="code" name="jetpack_comment_likes_enabled" value="1" <?php checked( $this->is_comments_enabled(), true ); ?> /> + <?php esc_html_e( 'Allow people to like comments', 'jetpack' ); ?> + </label> + </div> + </td> + </tr> */ ?> + </tbody> <?php // closes the tbody attached to sharing_show_buttons_on_row_start... ?> + <?php } + + /** + * If sharedaddy is not loaded, we don't have the "Show buttons on" yet, so we need to add that since it affects likes too. + */ + function admin_settings_showbuttonon_init() { ?> + <?php echo apply_filters( 'sharing_show_buttons_on_row_start', '<tr valign="top">' ); ?> + <th scope="row"><label><?php _e( 'Show buttons on', 'jetpack' ); ?></label></th> + <td> + <?php + $br = false; + $shows = array_values( get_post_types( array( 'public' => true ) ) ); + array_unshift( $shows, 'index' ); + $global = $this->get_options(); + foreach ( $shows as $show ) : + if ( 'index' == $show ) { + $label = __( 'Front Page, Archive Pages, and Search Results', 'jetpack' ); + } else { + $post_type_object = get_post_type_object( $show ); + $label = $post_type_object->labels->name; + } + ?> + <?php if ( $br ) echo '<br />'; ?><label><input type="checkbox"<?php checked( in_array( $show, $global['show'] ) ); ?> name="show[]" value="<?php echo esc_attr( $show ); ?>" /> <?php echo esc_html( $label ); ?></label> + <?php $br = true; endforeach; ?> + </td> + <?php echo apply_filters( 'sharing_show_buttons_on_row_end', '</tr>' ); ?> + <?php } + + + /** + * If sharedaddy is not loaded, we still need to save the the settings of the "Show buttons on" option. + */ + function admin_settings_showbuttonon_callback() { + $options = get_option( 'sharing-options' ); + if ( !is_array( $options ) ) + $options = array(); + + $shows = array_values( get_post_types( array( 'public' => true ) ) ); + $shows[] = 'index'; + $data = $_POST; + + if ( isset( $data['show'] ) ) { + if ( is_scalar( $data['show'] ) ) { + switch ( $data['show'] ) { + case 'posts' : + $data['show'] = array( 'post', 'page' ); + break; + case 'index' : + $data['show'] = array( 'index' ); + break; + case 'posts-index' : + $data['show'] = array( 'post', 'page', 'index' ); + break; + } + } + + if ( $data['show'] = array_intersect( $data['show'], $shows ) ) { + $options['global']['show'] = $data['show']; + } + } else { + $options['global']['show'] = array(); + } + + update_option( 'sharing-options', $options ); + } + + /** + * Adds the admin update hook so we can save settings even if Sharedaddy is not enabled. + */ + function process_update_requests_if_sharedaddy_not_loaded() { + if ( isset( $_GET['page'] ) && ( $_GET['page'] == 'sharing.php' || $_GET['page'] == 'sharing' ) ) { + if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options' ) ) { + do_action( 'sharing_admin_update' ); + wp_safe_redirect( admin_url( 'options-general.php?page=sharing&update=saved' ) ); + die(); + } + } + } + + /** + * Saves the setting in the database, bumps a stat on WordPress.com + */ + function admin_settings_callback() { + // We're looking for these, and doing a dance to set some stats and save + // them together in array option. + $new_state = !empty( $_POST['wpl_default'] ) ? $_POST['wpl_default'] : 'on'; + $db_state = $this->is_enabled_sitewide(); + + /** Default State *********************************************************/ + + // Checked (enabled) + switch( $new_state ) { + case 'off' : + if ( true == $db_state && ! $this->in_jetpack ) { + $g_gif = file_get_contents( 'http://stats.wordpress.com/g.gif?v=wpcom-no-pv&x_likes=disabled_likes' ); + } + update_option( 'disabled_likes', 1 ); + break; + case 'on' : + default: + if ( false == $db_state && ! $this->in_jetpack ) { + $g_gif = file_get_contents( 'http://stats.wordpress.com/g.gif?v=wpcom-no-pv&x_likes=reenabled_likes' ); + } + delete_option( 'disabled_likes' ); + break; + } + + + // comment setting + $new_comments_state = !empty( $_POST['jetpack_comment_likes_enabled'] ) ? $_POST['jetpack_comment_likes_enabled'] : false; + switch( (bool) $new_comments_state ) { + case true: + update_option( 'jetpack_comment_likes_enabled', 1 ); + break; + case false: + default: + update_option( 'jetpack_comment_likes_enabled', 0 ); + break; + } + } + + /** + * Adds the 'sharing' menu to the settings menu. + * Only ran if sharedaddy and publicize are not already active. + */ + function sharing_menu() { + add_submenu_page( 'options-general.php', esc_html__( 'Sharing Settings', 'jetpack' ), esc_html__( 'Sharing', 'jetpack' ), 'manage_options', 'sharing', array( $this, 'sharing_page' ) ); + } + + /** + * Provides a sharing page with the sharing_global_options hook + * so we can display the setting. + * Only ran if sharedaddy and publicize are not already active. + */ + function sharing_page() { + $this->updated_message(); ?> + <div class="wrap"> + <div class="icon32" id="icon-options-general"><br /></div> + <h2><?php esc_html_e( 'Sharing Settings', 'jetpack' ); ?></h2> + <?php do_action( 'pre_admin_screen_sharing' ) ?> + <?php $this->sharing_block(); ?> + </div> <?php + } + + /** + * Returns the settings have been saved message. + */ + function updated_message() { + if ( isset( $_GET['update'] ) && $_GET['update'] == 'saved' ) + echo '<div class="updated"><p>' . esc_html__( 'Settings have been saved', 'jetpack' ) . '</p></div>'; + } + + /** + * Returns just the "sharing buttons" w/ like option block, so it can be inserted into different sharing page contexts + */ + function sharing_block() { ?> + <h3><?php esc_html_e( 'Sharing Buttons', 'jetpack' ); ?></h3> + <form method="post" action=""> + <table class="form-table"> + <tbody> + <?php do_action( 'sharing_global_options' ); ?> + </tbody> + </table> + + <p class="submit"> + <input type="submit" name="submit" class="button-primary" value="<?php esc_attr_e( 'Save Changes', 'jetpack' ); ?>" /> + </p> + + <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'sharing-options' );?>" /> + </form> <?php + } + + function action_init() { + if ( is_admin() ) + return; + + if ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || + ( defined( 'APP_REQUEST' ) && APP_REQUEST ) || + ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) || + ( defined( 'COOKIE_AUTH_REQUEST' ) && COOKIE_AUTH_REQUEST ) || + ( defined( 'JABBER_SERVER' ) && JABBER_SERVER ) ) + return; + + // Comment Likes widget has been disabled, pending performance improvements. + // add_filter( 'comment_text', array( &$this, 'comment_likes' ), 10, 2 ); + + if ( $this->in_jetpack ) { + add_filter( 'the_content', array( &$this, 'post_likes' ), 30, 1 ); + wp_enqueue_script( 'postmessage', plugins_url( '_inc/postmessage.js', dirname(__FILE__) ), array( 'jquery' ), JETPACK__VERSION, false ); + wp_enqueue_script( 'jquery_inview', plugins_url( '_inc/jquery.inview.js', dirname(__FILE__) ), array( 'jquery' ), JETPACK__VERSION, false ); + wp_enqueue_style( 'jetpack_likes', plugins_url( 'likes/style.css', __FILE__ ), array(), JETPACK__VERSION ); + } else { + add_filter( 'post_flair', array( &$this, 'post_likes' ), 30, 1 ); + add_filter( 'post_flair_block_css', array( $this, 'post_flair_service_enabled_like' ) ); + + wp_enqueue_script( 'postmessage', '/wp-content/js/postmessage.js', array( 'jquery' ), JETPACK__VERSION, false ); + wp_enqueue_script( 'jquery_inview', '/wp-content/js/jquery/jquery.inview.js', array( 'jquery' ), JETPACK__VERSION, false ); + wp_enqueue_style( 'jetpack_likes', plugins_url( 'jetpack-likes.css', __FILE__ ), array(), JETPACK__VERSION ); + } + } + + function post_likes( $content ) { + global $post; + + if ( ! $this->is_likes_visible() ) + return $content; + + $protocol = 'http'; + if ( is_ssl() ) + $protocol = 'https'; + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $blog_id = get_current_blog_id(); + $bloginfo = get_blog_details( (int) $blog_id ); + $domain = $bloginfo->domain; + } else { + $jetpack = Jetpack::init(); + $blog_id = $jetpack->get_option( 'id' ); + $url = home_url(); + $url_parts = parse_url( $url ); + $domain = $url_parts['host']; + } + + add_filter( 'wp_footer', array( $this, 'likes_master' ) ); + + $src = sprintf( '%1$s://widgets.wp.com/likes/#blog_id=%2$d&post_id=%3$d&origin=%1$s://%4$s', $protocol, $blog_id, $post->ID, $domain ); + $name = sprintf( 'like-post-frame-%1$d-%2$d', $blog_id, $post->ID ); + $wrapper = sprintf( 'like-post-wrapper-%1$d-%2$d', $blog_id, $post->ID ); + + $html = "<div class='sharedaddy sd-block sd-like jetpack-likes-widget-wrapper jetpack-likes-widget-unloaded' id='$wrapper' data-src='$src' data-name='$name'><h3 class='sd-title'>" . esc_html__( 'Like this:', 'jetpack' ) . '</h3>'; + $html .= "<div class='post-likes-widget-placeholder' style='height:55px'><span class='button'><span>" . esc_html__( 'Like', 'jetpack' ) . '</span></span> <span class="loading">' . esc_html__( 'Loading...', 'jetpack' ) . '</span></div>'; + $html .= "<span class='sd-text-color'></span><a class='sd-link-color'></a>"; + $html .= '</div>'; + + return $content . $html; + } + + function comment_likes( $content, $comment = null ) { + if ( empty( $comment ) ) + return $content; + + if ( ! $this->is_comments_enabled() ) + return $content; + + $protocol = 'http'; + if ( is_ssl() ) + $protocol = 'https'; + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $blog_id = get_current_blog_id(); + $bloginfo = get_blog_details( (int) $blog_id ); + $domain = $bloginfo->domain; + } else { + $jetpack = Jetpack::init(); + $blog_id = $jetpack->get_option( 'id' ); + $url = home_url(); + $url_parts = parse_url( $url ); + $domain = $url_parts['host']; + } + + add_filter( 'wp_footer', array( $this, 'likes_master' ) ); + + $src = sprintf( '%1$s://widgets.wp.com/likes/#blog_id=%2$d&comment_id=%3$d&origin=%1$s://%4$s', $protocol, $blog_id, $comment->comment_ID, $domain ); + $name = sprintf( 'like-comment-frame-%1$d-%2$d', $blog_id, $comment->comment_ID ); + $wrapper = sprintf( 'like-comment-wrapper-%1$d-%2$d', $blog_id, $comment->comment_ID ); + + $html = "<div><div class='jetpack-likes-widget-wrapper jetpack-likes-widget-unloaded' id='$wrapper'>"; + $html .= "<iframe class='comment-likes-widget jetpack-likes-widget' name='$name' height='16px' width='100%' data='$src'></iframe>"; + $html .= '</div></div>'; + return $content . $html; + } + + function post_flair_service_enabled_like( $classes ) { + $classes[] = 'sd-like-enabled'; + return $classes; + } + + function admin_bar_likes() { + global $wp_admin_bar, $post; + + if ( ! $this->is_admin_bar_button_visible() ) { + return; + } + + $protocol = 'http'; + if ( is_ssl() ) + $protocol = 'https'; + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $blog_id = get_current_blog_id(); + $bloginfo = get_blog_details( (int) $blog_id ); + $domain = $bloginfo->domain; + } else { + $jetpack = Jetpack::init(); + $blog_id = $jetpack->get_option( 'id' ); + $url = home_url(); + $url_parts = parse_url( $url ); + $domain = $url_parts['host']; + } + + add_filter( 'wp_footer', array( $this, 'likes_master' ) ); + + $src = sprintf( '%1$s://widgets.wp.com/likes/#blog_id=%2$d&post_id=%3$d&origin=%1$s://%4$s', $protocol, $blog_id, $post->ID, $domain ); + + $html = "<iframe class='admin-bar-likes-widget jetpack-likes-widget' frameBorder='0' name='admin-bar-likes-widget' src='$src'></iframe>"; + + $node = array( + 'id' => 'admin-bar-likes-widget', + 'meta' => array( + 'html' => $html + ) + ); + + $wp_admin_bar->add_node( $node ); + } + + function likes_master() { + $protocol = 'http'; + if ( is_ssl() ) + $protocol = 'https'; + + $locale = ( '' == get_locale() || 'en' == get_locale() ) ? '' : '&lang=' . strtolower( substr( get_locale(), 0, 2 ) ); + $src = sprintf( '%1$s://widgets.wp.com/likes/master.html?ver=%2$s#ver=%2$s%3$s', $protocol, $this->version, $locale ); + + $likersText = wp_kses( __( '<span>%d</span> bloggers like this:', 'jetpack' ), array( 'span' => array() ) ); +?> + <iframe src='<?php echo $src; ?>' id='likes-master' name='likes-master' style='display:none;'></iframe> + <div id='likes-other-gravatars'><div class="likes-text"><?php echo $likersText; ?></div><ul class="wpl-avatars sd-like-gravatars"></ul></div> + <script type="text/javascript"> + //<![CDATA[ + var jetpackLikesWidgetQueue = []; + var jetpackLikesMasterReady = false; + + function JetpackLikespostMessage( message, target ) { + if ( "string" === typeof message ){ + try{ + message = JSON.parse( message ); + } + catch(e) { + return; + } + } + + pm( { + target: target, + type: 'likesMessage', + data: message, + origin: '*' + } ); + } + + function JetpackLikesMessageListener( event ) { + if ( "undefined" == typeof event.event ) + return; + + if ( 'masterReady' == event.event ) { + jQuery( document ).ready( function() { + jetpackLikesMasterReady = true; + + var stylesData = { + event: 'injectStyles' + }; + + if ( jQuery( 'iframe.admin-bar-likes-widget' ).length > 0 ) { + JetpackLikespostMessage( { event: 'adminBarEnabled' }, window.frames[ 'likes-master' ] ); + + stylesData.adminBarStyles = { + background: jQuery( '#wpadminbar .quicklinks li#wp-admin-bar-wpl-like > a' ).css( 'background' ) + }; + } + + if ( !window.addEventListener ) + jQuery( '#wp-admin-bar-admin-bar-likes-widget' ).hide(); + + stylesData.textStyles = { + color: jQuery( '.sd-text-color').css( 'color' ), + fontFamily: jQuery( '.sd-text-color' ).css( 'font-family' ), + fontSize: jQuery( '.sd-text-color' ).css( 'font-size' ), + direction: jQuery( '.sd-text-color' ).css( 'direction' ), + fontWeight: jQuery( '.sd-text-color' ).css( 'font-weight' ), + fontStyle: jQuery( '.sd-text-color' ).css( 'font-style' ), + textDecoration: jQuery( '.sd-text-color' ).css('text-decoration') + }; + + stylesData.linkStyles = { + color: jQuery( '.sd-link-color' ).css('color'), + fontFamily: jQuery( '.sd-link-color' ).css('font-family'), + fontSize: jQuery( '.sd-link-color' ).css('font-size'), + textDecoration: jQuery( '.sd-link-color' ).css('text-decoration'), + fontWeight: jQuery( '.sd-link-color' ).css( 'font-weight' ), + fontStyle: jQuery( '.sd-link-color' ).css( 'font-style' ) + }; + + JetpackLikespostMessage( stylesData, window.frames[ 'likes-master' ] ); + + var requests = []; + jQuery( '.jetpack-likes-widget-wrapper' ).each( function( i ) { + var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)/; + var match = regex.exec( this.id ); + if ( ! match || match.length != 4 ) + return; + + var info = { + blog_id: match[2], + width: this.width + }; + + if ( 'post' == match[1] ) { + info.post_id = match[3]; + } else if ( 'comment' == match[1] ) { + info.comment_id = match[3]; + } + + requests.push( info ); + }); + + JetpackLikespostMessage( { event: 'initialBatch', requests: requests }, window.frames['likes-master'] ); + + jQuery( document ).on( 'inview', 'div.jetpack-likes-widget-unloaded', function() { + jetpackLikesWidgetQueue.push( this.id ); + }); + }); + } + + if ( 'showLikeWidget' == event.event ) { + setTimeout( JetpackLikesWidgetQueueHandler, 10 ); + jQuery( '#' + event.id + ' .post-likes-widget-placeholder' ).fadeOut( 'fast', function() { + jQuery( '#' + event.id + ' .post-likes-widget' ).fadeIn( 'fast' ); + JetpackLikespostMessage( { event: 'likeWidgetDisplayed', blog_id: event.blog_id, post_id: event.post_id }, window.frames['likes-master'] ); + }); + } + + if ( 'showOtherGravatars' == event.event ) { + var $container = jQuery( '#likes-other-gravatars' ); + var $list = $container.find( 'ul' ); + + $container.hide(); + $list.html( '' ); + + $container.find( '.likes-text span' ).text( event.total ); + + jQuery.each( event.likers, function( i, liker ) { + $list.append( '<li class="' + liker.css_class + '"><a href="' + liker.profile_URL + '" class="wpl-liker" rel="nofollow" target="_parent"><img src="' + liker.avatar_URL + '" alt="' + liker.name + '" width="30" height="30" style="padding-right: 3px;" /></a></li>'); + } ); + + var offset = jQuery( "[name='" + event.parent + "']" ).offset(); + + $container.css( 'left', offset.left + event.position.left - 10 + 'px' ); + $container.css( 'top', offset.top + event.position.top - 33 + 'px' ); + + var rowLength = Math.floor( event.width / 37 ); + var height = ( Math.ceil( event.likers.length / rowLength ) * 37 ) + 13; + if ( height > 204 ) { + height = 204; + } + + $container.css( 'height', height + 'px' ); + $container.css( 'width', rowLength * 37 - 7 + 'px' ); + + $list.css( 'width', rowLength * 37 + 'px' ); + + $container.fadeIn( 'slow' ); + + var scrollbarWidth = $list[0].offsetWidth - $list[0].clientWidth; + if ( scrollbarWidth > 0 ) { + $container.width( $container.width() + scrollbarWidth ); + $list.width( $list.width() + scrollbarWidth ); + } + } + } + + pm.bind( 'likesMessage', function(e) { JetpackLikesMessageListener(e); } ); + + jQuery( document ).click( function( e ) { + var $container = jQuery( '#likes-other-gravatars' ); + + if ( $container.has( e.target ).length === 0 ) { + $container.fadeOut( 'slow' ); + } + }); + + function JetpackLikesWidgetQueueHandler() { + var wrapperID; + if ( ! jetpackLikesMasterReady ) { + setTimeout( JetpackLikesWidgetQueueHandler, 500 ); + return; + } + + if ( jetpackLikesWidgetQueue.length > 0 ) { + // We may have a widget that needs creating now + var found = false; + while( jetpackLikesWidgetQueue.length > 0 ) { + // Grab the first member of the queue that isn't already loading. + wrapperID = jetpackLikesWidgetQueue.splice( 0, 1 )[0]; + if ( jQuery( '#' + wrapperID ).hasClass( 'jetpack-likes-widget-unloaded' ) ) { + found = true; + break; + } + } + if ( ! found ) { + setTimeout( JetpackLikesWidgetQueueHandler, 500 ); + return; + } + } else if ( jQuery( 'div.jetpack-likes-widget-unloaded' ).length > 0 ) { + // Get the next unloaded widget + wrapperID = jQuery( 'div.jetpack-likes-widget-unloaded' ).first()[0].id; + if ( ! wrapperID ) { + // Everything is currently loaded + setTimeout( JetpackLikesWidgetQueueHandler, 500 ); + return; + } + } + + var $wrapper = jQuery( '#' + wrapperID ); + $wrapper.find( 'iframe' ).remove(); + + $wrapper.find( '.post-likes-widget-placeholder' ).after( "<iframe class='post-likes-widget jetpack-likes-widget' name='" + $wrapper.data( 'name' ) + "' height='55px' width='100%' frameBorder='0' src='" + $wrapper.data( 'src' ) + "'></iframe>" ); + + + $wrapper.removeClass( 'jetpack-likes-widget-unloaded' ).addClass( 'jetpack-likes-widget-loading' ); + + $wrapper.find( 'iframe' ).load( function( e ) { + var $iframe = jQuery( e.target ); + $wrapper.removeClass( 'jetpack-likes-widget-loading' ).addClass( 'jetpack-likes-widget-loaded' ); + + JetpackLikespostMessage( { event: 'loadLikeWidget', name: $iframe.attr( 'name' ), width: $iframe.width() }, window.frames[ 'likes-master' ] ); + }); + } + setInterval( JetpackLikesWidgetQueueHandler, 250 ); + //]]> + </script> +<?php + } + + /** + * Get the 'disabled_likes' option from the DB of the current blog. + * + * @return array + */ + function get_options() { + $setting = array(); + $setting['disabled'] = get_option( 'disabled_likes' ); + $sharing = get_option( 'sharing-options' ); + + // Default visibility settings + if ( ! isset( $sharing['global']['show'] ) ) { + $sharing['global']['show'] = array( 'post', 'page' ); + + // Scalar check + } elseif ( is_scalar( $sharing['global']['show'] ) ) { + switch ( $sharing['global']['show'] ) { + case 'posts' : + $sharing['global']['show'] = array( 'post', 'page' ); + break; + case 'index' : + $sharing['global']['show'] = array( 'index' ); + break; + case 'posts-index' : + $sharing['global']['show'] = array( 'post', 'page', 'index' ); + break; + } + } + + // Ensure it's always an array (even if not previously empty or scalar) + $setting['show'] = !empty( $sharing['global']['show'] ) ? (array) $sharing['global']['show'] : array(); + + return apply_filters( 'wpl_get_options', $setting ); + } + + /** _is_ functions ************************************************************/ + + /** + * Are likes visible in this context? + * + * Some of this code was taken and modified from sharing_display() to ensure + * similar logic and filters apply here, too. + */ + function is_likes_visible() { + + global $wp_current_filter; // Used to check 'get_the_excerpt' filter + global $post; // Used to apply 'sharing_show' filter + + // Never show on feeds or previews + if ( is_feed() || is_preview() || is_comments_popup() ) { + $enabled = false; + + // Not a feed or preview, so what is it? + } else { + + if ( in_the_loop() ) { + // If in the loop, check if the current post is likeable + $enabled = $this->is_post_likeable(); + } else { + // Otherwise, check and see if likes are enabled sitewide + $enabled = $this->is_enabled_sitewide(); + } + + /** Other Checks ******************************************************/ + + // Do not show on excerpts + if ( in_array( 'get_the_excerpt', (array) $wp_current_filter ) ) { + $enabled = false; + + // Sharing Setting Overrides **************************************** + } else { + // Single post + if ( is_singular( 'post' ) ) { + if ( ! $this->is_single_post_enabled() ) { + $enabled = false; + } + + // Single page + } elseif ( is_page() ) { + if ( ! $this->is_single_page_enabled() ) { + $enabled = false; + } + + // Attachment + } elseif ( is_attachment() ) { + if ( ! $this->is_attachment_enabled() ) { + $enabled = false; + } + + // All other loops + } elseif ( ! $this->is_index_enabled() ) { + $enabled = false; + } + } + } + + // Run through the sharing filters + $enabled = apply_filters( 'sharing_show', $enabled, $post ); + + return (bool) apply_filters( 'wpl_is_likes_visible', $enabled ); + } + + /** + * Returns the current state of the "WordPress.com Likes are" option. + * @return boolean true if enabled sitewide, false if not + */ + function is_enabled_sitewide() { + return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) ); + } + + /** + * Returns if comment likes are enabled. Defaults to 'on' + * @todo decide what the default should be + * @return boolean true if we should show comment likes, false if not + */ + function is_comments_enabled() { + return (bool) apply_filters( 'jetpack_comment_likes_enabled', get_option( 'jetpack_comment_likes_enabled', true ) ); + } + + function is_admin_bar_button_visible() { + global $wp_admin_bar; + + if ( ! is_object( $wp_admin_bar ) ) + return false; + + if ( ( ! is_singular( 'post' ) && ! is_attachment() && ! is_page() ) ) + return false; + + if ( ! $this->is_likes_visible() ) + return false; + + if ( ! $this->is_post_likeable() ) + return false; + + return (bool) apply_filters( 'jetpack_admin_bar_likes_enabled', true ); + } + + /** + * Are likes enabled for this post? + * + * @param int $post_id + * @retun bool + */ + function is_post_likeable( $post_id = 0 ) { + $post = get_post( $post_id ); + if ( !$post || is_wp_error( $post ) ) { + return false; + } + + $sitewide_likes_enabled = (bool) Jetpack_Likes::is_enabled_sitewide(); + $post_likes_switched = (bool) get_post_meta( $post->ID, 'switch_like_status', true ); + + $post_likes_enabled = $sitewide_likes_enabled; + if ( $post_likes_switched ) { + $post_likes_enabled = ! $post_likes_enabled; + } + + return $post_likes_enabled; + } + + /** + * Are Post Likes enabled on archive/front/search pages? + * + * @return bool + */ + function is_index_enabled() { + $options = $this->get_options(); + return (bool) apply_filters( 'wpl_is_index_disabled', (bool) in_array( 'index', $options['show'] ) ); + } + + /** + * Are Post Likes enabled on single posts? + * + * @return bool + */ + function is_single_post_enabled() { + $options = $this->get_options(); + return (bool) apply_filters( 'wpl_is_single_post_disabled', (bool) in_array( 'post', $options['show'] ) ); + } + + /** + * Are Post Likes enabled on single pages? + * + * @return bool + */ + function is_single_page_enabled() { + $options = $this->get_options(); + return (bool) apply_filters( 'wpl_is_single_page_disabled', (bool) in_array( 'page', $options['show'] ) ); + } + + /** + * Are Media Likes enabled on single pages? + * + * @return bool + */ + function is_attachment_enabled() { + $options = $this->get_options(); + return (bool) apply_filters( 'wpl_is_attachment_disabled', (bool) in_array( 'attachment', $options['show'] ) ); + } + +} + +Jetpack_Likes::init(); diff --git a/plugins/jetpack/modules/likes/style.css b/plugins/jetpack/modules/likes/style.css new file mode 100644 index 00000000..3885cace --- /dev/null +++ b/plugins/jetpack/modules/likes/style.css @@ -0,0 +1,189 @@ +#wpadminbar li#wp-admin-bar-admin-bar-likes-widget { + width: 61px; +} + +#wpadminbar iframe.admin-bar-likes-widget { + width: 61px; + height: 28px; + min-height: 28px; + border-width: 0px; + position: absolute; + top: 0; +} + +div.jetpack-likes-widget-wrapper { + width: 100%; +} + +#likes-other-gravatars { + display: none; + position: absolute; + padding: 10px; + background-color: #000; + border-width: 0; + opacity: 0.88; + filter: alpha(opacity=88); + box-shadow: 0 0 10px black; + min-width: 130px; + z-index: 1000; +} + +#likes-other-gravatars .likes-text { + color: white; + font-size: 14px; + padding-bottom: 5px; +} + +#likes-other-gravatars ul, +#likes-other-gravatars li { + margin: 0; + padding: 0; + text-indent: 0; + list-style-type: none; +} + +#likes-other-gravatars li::before { + content: ""; +} + +#likes-other-gravatars ul.wpl-avatars { + overflow: auto; + display: block; + position: absolute; + max-height: 190px; +} + +#likes-other-gravatars ul.wpl-avatars li { + width: 32px; + height: 32px; + float: left; + margin: 0 5px 5px 0; +} + +#likes-other-gravatars ul.wpl-avatars li a { + margin: 0 2px 0 0; + border-bottom: none !important; + display: block; +} + +#likes-other-gravatars ul.wpl-avatars li a img { + background: none; + border: none; + margin: 0 !important; + padding: 0 !important; + position: static; +} + + +div.sd-box { + border-top: 1px solid #ddd; + border-top: 1px solid rgba(0,0,0,.13); +} + +h3.sd-title { + font-size: 12px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + margin: 3px 0; + padding: 0; + text-transform: none; + letter-spacing: 0; + line-height: 1; + font-weight: bold; + width: 15.625%; /* 100px / 640px */ float: left; + position: static; + background: none; + border: none; +} + +.rtl .sd-title { + float: right; + text-align: right; +} + +.entry-content .post-likes-widget, .post-likes-widget, +.comment-likes-widget { + margin: 0; + border-width: 0; +} + +.post-likes-widget-placeholder { + margin: 0; + border-width: 0; +} + +.post-likes-widget-placeholder .button { + margin: 0; + padding: 0; + display: inline-block; + background: #efefef; + background: -moz-linear-gradient(top, #f7f7f7 0%, #efefef 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f7f7f7), color-stop(100%,#efefef)); + background: -webkit-linear-gradient(top, #f7f7f7 0%,#efefef 100%); + background: -o-linear-gradient(top, #f7f7f7 0%,#efefef 100%); + background: -ms-linear-gradient(top, #f7f7f7 0%,#efefef 100%); + background: linear-gradient(top, #f7f7f7 0%,#efefef 100%); + border-radius: 3px; + border: 1px solid #ddd !important; + box-shadow: inset 0 1px 0 #fff; + color: #999; + text-decoration: none; + line-height: 1; + font-size: 12px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: normal; +} + +.post-likes-widget-placeholder .button span { + padding: 1px 5px 1px 2px; + display: block; + opacity: .8; + line-height: 1.5em; + text-shadow: none; +} + +.post-likes-widget-placeholder .button span:before { + color: #97A8CC; + font-family: "Noticons"; + content: '\2605'; + font-size: 16px; + line-height: 0; + text-shadow: 0 1px 0 #fff; + position: relative; + top: 3px; +} + +.post-likes-widget-placeholder .loading { + color: #999; + font-size: 12px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.post-likes-widget { + width: 82.125%; + display: none; + float: right; +} + +/* Like Special cases (display on it's own) */ + +div.sharedaddy.sd-like-enabled .sd-like h3 { + display: none; +} + +div.sharedaddy.sd-like-enabled .sd-like .post-likes-widget { + width: 100%; + float: none; +} + +div.sharedaddy.sd-rating-enabled .sd-like .post-likes-widget, div.sharedaddy.sd-sharing-enabled .sd-like .post-likes-widget { + width: 82.125%; + float: right; +} + +div.sharedaddy.sd-rating-enabled .sd-like h3, div.sharedaddy.sd-sharing-enabled .sd-like h3 { + display: block; +} + +.comment-likes-widget { + width: 100%; +} diff --git a/plugins/jetpack/modules/minileven.php b/plugins/jetpack/modules/minileven.php new file mode 100644 index 00000000..e2047e50 --- /dev/null +++ b/plugins/jetpack/modules/minileven.php @@ -0,0 +1,108 @@ +<?php + +/** + * Module Name: Mobile Theme + * Module Description: Automatically optimize your site for mobile devices. + * Sort Order: 11 + * First Introduced: 1.8 + */ + +function jetpack_load_minileven() { + include dirname( __FILE__ ) . "/minileven/minileven.php"; + + if ( get_option( 'wp_mobile_app_promos' ) != '1' ) + remove_action( 'wp_mobile_theme_footer', 'jetpack_mobile_app_promo' ); +} + +add_action( 'jetpack_modules_loaded', 'minileven_loaded' ); + +function minileven_loaded() { + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, 'minileven_configuration_load' ); + Jetpack::module_configuration_screen( __FILE__, 'minileven_configuration_screen' ); +} + +function minileven_configuration_load() { + if ( isset( $_POST['action'] ) && $_POST['action'] == 'save_options' && $_POST['_wpnonce'] == wp_create_nonce( 'minileven' ) ) { + if ( isset( $_POST['wp_mobile_excerpt'] ) ) + update_option( 'wp_mobile_excerpt', '1' == $_POST['wp_mobile_excerpt'] ? '1' : '0' ); + + update_option( 'wp_mobile_app_promos', ( isset( $_POST['wp_mobile_app_promos'] ) ) ? '1' : '0' ); + + Jetpack::state( 'message', 'module_configured' ); + wp_safe_redirect( Jetpack::module_configuration_url( 'minileven' ) ); + exit; + } +} + +function minileven_configuration_screen() { + $excerpts = ( 0 == get_option( 'wp_mobile_excerpt' ) ) ? 0 : 1; + $promos = ( '1' == get_option( 'wp_mobile_app_promos' ) ) ? 1 : 0; + + ?> + <form method="post"> + <input type="hidden" name="action" value="save_options" /> + <?php wp_nonce_field( 'minileven' ); ?> + <table id="menu" class="form-table"> + <tr valign="top"> + <th scope="row"><?php _e( 'Excerpts', 'jetpack' ); ?></th> + <td> + <label> + <input name="wp_mobile_excerpt" type="radio" value="1" class="code" <?php checked( 1, $excerpts, true ); ?> /> + <?php _e( 'Enable excerpts on front page and on archive pages', 'jetpack' ); ?> + </label> + <br /> + <label> + <input name="wp_mobile_excerpt" type="radio" value="0" class="code" <?php checked( 0, $excerpts, true ); ?> /> + <?php _e( 'Show full posts on front page and on archive pages', 'jetpack' ); ?> + </label> + </td> + </tr> + <tr valign="top"> + <th scope="row"><?php _e( 'Mobile App Promos', 'jetpack' ); ?></th> + <td> + <label> + <input name="wp_mobile_app_promos" type="checkbox" value="1" <?php checked( 1, $promos, true ); ?> /> + <?php _e ( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ); ?> + </label> + </td> + </tr> + </table> + <p class="submit"> + <input type="submit" class="button-primary" value="<?php esc_attr_e( __( 'Save configuration', 'jetpack' ) ); ?>" /> + </p> + </form> + <h3><?php _e( 'Mobile Apps', 'jetpack' ); ?></h3> + <p><?php _e( 'Take WordPress with you.', 'jetpack' ); ?></p> + <a href="http://wordpress.org/extend/mobile/"><img src="<?php echo plugin_dir_url( __FILE__ ); ?>/minileven/images/wp-app-devices.png" width="332" height="73" /></a> + <p><?php printf( __( 'We have apps for <a href="%s">iOS (iPhone, iPad, iPod Touch)</a>, <a href="%s">Android</a>, <a href="%s">BlackBerry</a>, <a href="%s">Windows Phone</a>, and <a href="%s">more</a>!', 'jetpack' ), 'http://ios.wordpress.org/', 'http://android.wordpress.org/', 'http://blackberry.wordpress.org/', 'http://windowsphone.wordpress.org/', 'http://wordpress.org/extend/mobile/' ); ?></p> + <?php +} + +function minileven_theme_root( $theme_root ) { + if ( jetpack_check_mobile() ) { + return dirname( __FILE__ ) . '/minileven/theme'; + } + + return $theme_root; +} + +add_filter( 'theme_root', 'minileven_theme_root' ); + +function minileven_theme_root_uri( $theme_root_uri ) { + if ( jetpack_check_mobile() ) { + return plugins_url( 'modules/minileven/theme', dirname( __FILE__ ) ); + } + + return $theme_root_uri; +} + +add_filter( 'theme_root_uri', 'minileven_theme_root_uri' ); + +function minileven_enabled( $wp_mobile_disable_option ) { + return true; +} + +add_filter( 'option_wp_mobile_disable', 'minileven_enabled' ); + +jetpack_load_minileven();
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/images/wp-app-devices.png b/plugins/jetpack/modules/minileven/images/wp-app-devices.png Binary files differnew file mode 100644 index 00000000..4b5f4be9 --- /dev/null +++ b/plugins/jetpack/modules/minileven/images/wp-app-devices.png diff --git a/plugins/jetpack/modules/minileven/minileven.php b/plugins/jetpack/modules/minileven/minileven.php new file mode 100644 index 00000000..ae2ab016 --- /dev/null +++ b/plugins/jetpack/modules/minileven/minileven.php @@ -0,0 +1,313 @@ +<?php + +// ********** modify blog option 'wp_mobile_template' manually to specify a theme (ex. 'vip/cnnmobile') + +// WordPress Mobile Edition +// +// Copyright (c) 2002-2008 Alex King +// http://alexking.org/projects/wordpress +// +// Released under the GPL license +// http://www.opensource.org/licenses/gpl-license.php +// +// ********************************************************************** +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// ***************************************************************** + +/* +Plugin Name: WordPress Mobile Edition +Plugin URI: http://alexking.org/projects/wordpress +Description: Show a mobile view of the post/page if the visitor is on a known mobile device. Questions on configuration, etc.? Make sure to read the README. +Author: Alex King +Author URI: http://alexking.org +Version: 2.1a-WPCOM +*/ + +$_SERVER['REQUEST_URI'] = ( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['SCRIPT_NAME'] . (( isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''))); + +function jetpack_check_mobile() { + if ( ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST ) || ( defined('APP_REQUEST') && APP_REQUEST ) ) + return false; + if ( !isset($_SERVER["HTTP_USER_AGENT"]) || (isset($_COOKIE['akm_mobile']) && $_COOKIE['akm_mobile'] == 'false') ) + return false; + if ( jetpack_mobile_exclude() ) + return false; + if ( 1 == get_option('wp_mobile_disable') ) + return false; + if ( isset($_COOKIE['akm_mobile']) && $_COOKIE['akm_mobile'] == 'true' ) + return true; + + $is_mobile = jetpack_is_mobile(); + + return apply_filters( 'jetpack_check_mobile', $is_mobile ); +} + +function jetpack_mobile_exclude() { + $exclude = false; + $pages_to_exclude = array( + 'wp-admin', + 'wp-comments-post.php', + 'wp-mail.php', + 'wp-login.php', + 'wp-activate.php', + ); + foreach ( $pages_to_exclude as $exclude_page ) { + if ( strstr( strtolower( $_SERVER['REQUEST_URI'] ), $exclude_page ) ) + $exclude = true; + } + + if ( defined( 'DOING_AJAX' ) && true === DOING_AJAX ) + $exclude = false; + + if ( isset( $GLOBALS['wp_customize'] ) ) + return true; + + return $exclude; +} + +function wp_mobile_get_main_template() { + remove_action( 'option_template', 'jetpack_mobile_template' ); + $template = get_option( 'template' ); + add_action( 'option_template', 'jetpack_mobile_template' ); + return $template; +} + +function wp_mobile_get_main_stylesheet() { + remove_action( 'option_stylesheet', 'jetpack_mobile_stylesheet' ); + $stylesheet = get_option( 'stylesheet' ); + add_action( 'option_stylesheet', 'jetpack_mobile_stylesheet' ); + return $stylesheet; +} + +function jetpack_mobile_stylesheet( $theme ) { + return apply_filters( 'jetpack_mobile_stylesheet', 'pub/minileven', $theme ); +} + +function jetpack_mobile_template( $theme ) { + return apply_filters( 'jetpack_mobile_template', 'pub/minileven', $theme ); +} + +function jetpack_mobile_available() { + echo '<div style="text-align:center;margin:10px 0;"><a href="'. home_url( '?ak_action=accept_mobile' ) . '">' . __( 'View Mobile Site', 'jetpack' ) . '</a></div>'; +} + +function jetpack_mobile_request_handler() { + global $wpdb; + if (isset($_GET['ak_action'])) { + $url = parse_url( get_bloginfo( 'url' ) ); + $domain = $url['host']; + if (!empty($url['path'])) { + $path = $url['path']; + } + else { + $path = '/'; + } + $redirect = false; + switch ($_GET['ak_action']) { + case 'reject_mobile': + setcookie( + 'akm_mobile' + , 'false' + , time() + 300000 + , $path + , $domain + ); + $redirect = true; + + do_action( 'mobile_reject_mobile' ); + break; + case 'force_mobile': + case 'accept_mobile': + setcookie( + 'akm_mobile' + , 'true' + , time() + 300000 + , $path + , $domain + ); + $redirect = true; + + do_action( 'mobile_force_mobile' ); + break; + } + if ($redirect) { + if ( isset( $_GET['redirect_to'] ) && $_GET['redirect_to'] ) { + $go = urldecode( $_GET['redirect_to'] ); + } else if (!empty($_SERVER['HTTP_REFERER'])) { + $go = $_SERVER['HTTP_REFERER']; + } + else { + $go = remove_query_arg( array( 'ak_action' ) ); + } + wp_safe_redirect( $go ); + exit; + } + } +} +add_action('init', 'jetpack_mobile_request_handler'); + +function jetpack_mobile_theme_setup() { + if ( jetpack_check_mobile() ) { + // Redirect to download page if user clicked mobile app promo link in mobile footer + if ( isset( $_GET['app-download'] ) ) { + do_action( 'mobile_app_promo_download', $_GET['app-download'] ); + + switch ( $_GET['app-download'] ) { + case 'android': + header( 'Location: market://search?q=pname:org.wordpress.android' ); + exit; + break; + case 'ios': + header( 'Location: http://itunes.apple.com/us/app/wordpress/id335703880?mt=8' ); + exit; + break; + case 'blackberry': + header( 'Location: http://blackberry.wordpress.org/download/' ); + exit; + break; + case 'nokia': + header( 'Location: http://nokia.wordpress.org/download/' ); + exit; + break; + case 'windowsphone': + header( 'Location: http://social.zune.net/redirect?type=phoneApp&id=5f64ad85-f801-e011-9264-00237de2db9e' ); + exit; + break; + } + } + + add_action('stylesheet', 'jetpack_mobile_stylesheet'); + add_action('template', 'jetpack_mobile_template'); + add_action('option_template', 'jetpack_mobile_template'); + add_action('option_stylesheet', 'jetpack_mobile_stylesheet'); + + if ( class_exists( 'Jetpack_Custom_CSS' ) && method_exists( 'Jetpack_Custom_CSS', 'disable' ) && ! get_option( 'wp_mobile_custom_css' ) ) + add_action( 'init', array( 'Jetpack_Custom_CSS', 'disable' ), 11 ); + + do_action( 'mobile_setup' ); + } +} + +// Need a hook after plugins_loaded (since this code won't be loaded in Jetpack +// until then) but after init (because it has its own init hooks to add). +add_action( 'setup_theme', 'jetpack_mobile_theme_setup' ); + +if (isset($_COOKIE['akm_mobile']) && $_COOKIE['akm_mobile'] == 'false') { + add_action('wp_footer', 'jetpack_mobile_available'); +} + +add_action( 'wp_footer', 'mobile_admin_bar', 20 ); +function mobile_admin_bar() { + global $wp_version; + + if ( jetpack_is_mobile() && 1 != version_compare( $wp_version, '3.5-beta3-22631' ) ) : + // This fix was made unnecessary in http://core.trac.wordpress.org/changeset/22636 + ?> + <script type="text/javascript" id='mobile-admin-bar'> + jQuery( function( $ ) { + var menupop = $( '#wpadminbar .ab-top-menu > li.menupop' ) + .unbind( 'mouseover' ) + .unbind( 'mouseout' ) + .click( function ( e ) { + $( this ).toggleClass( 'hover' ); + $( '#wpadminbar .menupop' ).not( this ).removeClass( 'hover' ); + } ) + .children( 'a' ) + .click( function( e ) { + e.preventDefault(); + } ); + $( '#wpadminbar' ).css( 'position', 'absolute' ); + $( '#ab-reblog-box' ).css( 'position', 'absolute' ); + } ); + </script> + <?php + endif; +} + +function jetpack_mobile_app_promo() { + ?> + <script type="text/javascript"> + if ( ! navigator.userAgent.match( /wp-(iphone|android|blackberry|nokia|windowsphone)/i ) ) { + if ( ( navigator.userAgent.match( /iphone/i ) ) || ( navigator.userAgent.match( /ipod/i ) ) ) + document.write( '<span id="wpcom-mobile-app-promo" style="margin-top: 10px; font-size: 13px;"><strong>Now Available!</strong> <a href="/index.php?app-download=ios">Download WordPress for iOS</a></span><br /><br />' ); + else if ( ( navigator.userAgent.match( /android/i ) ) && ( null == navigator.userAgent.match( /playbook/i ) && null == navigator.userAgent.match( /bb10/i ) ) ) + document.write( '<span id="wpcom-mobile-app-promo" style="margin-top: 10px; font-size: 13px;"><strong>Now Available!</strong> <a href="/index.php?app-download=android">Download WordPress for Android</a></span><br /><br />' ); + else if ( ( navigator.userAgent.match( /blackberry/i ) ) || ( navigator.userAgent.match( /playbook/i ) ) || ( navigator.userAgent.match( /bb10/i ) ) ) + document.write( '<span id="wpcom-mobile-app-promo" style="margin-top: 10px; font-size: 13px;"><strong>Now Available!</strong> <a href="/index.php?app-download=blackberry">Download WordPress for BlackBerry</a></span><br /><br />' ); + else if ( ( navigator.userAgent.match( /windows phone os/i ) ) ) + document.write( '<span id="wpcom-mobile-app-promo" style="margin-top: 10px; font-size: 13px; line-height: 13px;"><strong>Now Available!</strong> <a href="/index.php?app-download=windowsphone">Download WordPress for <br />Windows Phone</a></span><br /><br />' ); + else if ( ( navigator.userAgent.match( /nokia/i ) ) ) + document.write( '<span id="wpcom-mobile-app-promo" style="margin-top: 10px; font-size: 13px;"><strong>Now Available!</strong> <a href="/index.php?app-download=nokia">Download WordPress for Nokia</a></span><br /><br />' ); + } + </script> + <?php +} + +add_action( 'wp_mobile_theme_footer', 'jetpack_mobile_app_promo' ); + +/** + * Adds an option to allow your Custom CSS to also be applied to the Mobile Theme. + * It's disabled by default, but this should allow people who know what they're + * doing to customize the mobile theme. + */ +function jetpack_mobile_css_settings() { + $mobile_css = get_option( 'wp_mobile_custom_css' ); + + ?> + <div class="misc-pub-section"> + <label><?php esc_html_e( 'Mobile-compatible:', 'jetpack' ); ?></label> + <span id="mobile-css-display"><?php echo $mobile_css ? __( 'Yes', 'jetpack' ) : __( 'No', 'jetpack' ); ?></span> + <a class="edit-mobile-css hide-if-no-js" href="#mobile-css"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a> + <div id="mobile-css-select" class="hide-if-js"> + <input type="hidden" name="mobile_css" id="mobile-css" value="<?php echo intval( $mobile_css ); ?>" /> + <label> + <input type="checkbox" id="mobile-css-visible" <?php checked( get_option( 'wp_mobile_custom_css' ) ); ?> /> + <?php esc_html_e( 'Include this CSS in the Mobile Theme', 'jetpack' ); ?> + </label> + <p> + <a class="save-mobile-css hide-if-no-js button" href="#mobile-css"><?php esc_html_e( 'OK', 'jetpack' ); ?></a> + <a class="cancel-mobile-css hide-if-no-js" href="#mobile-css"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a> + </p> + </div> + </div> + <script type="text/javascript"> + jQuery( function ( $ ) { + $( '.edit-mobile-css' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#mobile-css-select' ).slideDown(); + $( this ).hide(); + } ); + + $( '.cancel-mobile-css' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#mobile-css-select' ).slideUp( function () { + $( '.edit-mobile-css' ).show(); + + $( '#mobile-css-visible' ).prop( 'checked', $( '#mobile-css' ).val() == '1' ); + } ); + } ); + + $( '.save-mobile-css' ).bind( 'click', function ( e ) { + e.preventDefault(); + + $( '#mobile-css-select' ).slideUp(); + $( '#mobile-css-display' ).text( $( '#mobile-css-visible' ).prop( 'checked' ) ? 'Yes' : 'No' ); + $( '#mobile-css' ).val( $( '#mobile-css-visible' ).prop( 'checked' ) ? '1' : '0' ); + $( '.edit-mobile-css' ).show(); + } ); + } ); + </script> + <?php +} + +add_action( 'custom_css_submitbox_misc_actions', 'jetpack_mobile_css_settings' ); + +function jetpack_mobile_save_css_settings() { + update_option( 'wp_mobile_custom_css', isset( $_POST['mobile_css'] ) && ! empty( $_POST['mobile_css'] ) ); +} + +add_action( 'safecss_save_pre', 'jetpack_mobile_save_css_settings' ); diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/comments.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/comments.php new file mode 100644 index 00000000..55c35161 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/comments.php @@ -0,0 +1,52 @@ +<?php +/** + * The template for displaying Comments. + * + * The area of the page that contains both current comments + * and the comment form. The actual display of comments is + * handled by a callback to minileven_comment() which is + * located in the functions.php file. + * + * @package Minileven + */ +?> + <div id="comments"> + <?php if ( post_password_required() ) : ?> + <p class="nopassword"><?php _e( 'This post is password protected. Enter the password to view any comments.', 'jetpack' ); ?></p> + </div><!-- #comments --> + <?php + /* Stop the rest of comments.php from being processed, + * but don't kill the script entirely -- we still have + * to fully load the template. + */ + return; + endif; + ?> + + <?php // You can start editing here -- including this comment! ?> + + <?php comment_form(); ?> + + <?php if ( have_comments() ) : ?> + <ol class="commentlist"> + <?php + /* Loop through and list the comments. Tell wp_list_comments() + * to use minileven_comment() to format the comments. + * If you want to overload this in a child theme then you can + * define minileven_comment() and that will be used instead. + * See minileven_comment() in minileven/functions.php for more. + */ + wp_list_comments( array( 'callback' => 'minileven_comment' ) ); + ?> + </ol> + + <?php if ( get_comment_pages_count() > 1 && get_option( 'page_comments' ) ) : // are there comments to navigate through ?> + <nav id="comment-nav-below"> + <h1 class="assistive-text"><?php _e( 'Comment navigation', 'jetpack' ); ?></h1> + <div class="nav-previous"><?php previous_comments_link( __( '← Older Comments', 'jetpack' ) ); ?></div> + <div class="nav-next"><?php next_comments_link( __( 'Newer Comments →', 'jetpack' ) ); ?></div> + </nav> + <?php endif; // check for comment navigation + endif; // check for the existence of comments + ?> + </div><!-- #comments -->
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/content-gallery.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/content-gallery.php new file mode 100644 index 00000000..cf69f252 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/content-gallery.php @@ -0,0 +1,78 @@ +<?php +/** + * The template for displaying posts in the Gallery Post Format on index and archive pages + * + * Learn more: http://codex.wordpress.org/Post_Formats + * + * @package Minileven + */ +?> + +<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> + <header class="entry-header"> + <hgroup> + <h2 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'jetpack' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h2> + <h3 class="entry-format"><?php _e( 'Gallery', 'jetpack' ); ?></h3> + </hgroup> + </header><!-- .entry-header --> + + <div class="entry-content"> + <?php if ( is_single() ) : ?> + <?php the_content( __( 'Continue reading <span class="meta-nav">→</span>', 'jetpack' ) ); ?> + + <?php else : ?> + <?php + $images = get_children( array( 'post_parent' => $post->ID, 'post_type' => 'attachment', 'post_mime_type' => 'image', 'orderby' => 'rand', 'order' => 'ASC', 'numberposts' => 999 ) ); + if ( $images ) : + $total_images = count( $images ); + $large_image = array_shift( $images ); + $thumb1_image = array_shift( $images ); + $thumb2_image = array_shift( $images ); + $thumb3_image = array_shift( $images ); + + $image_img_tag = wp_get_attachment_image( $large_image->ID, 'large' ); + $thumb1_img_tag = wp_get_attachment_image( $thumb1_image->ID, 'thumbnail' ); + $thumb2_img_tag = wp_get_attachment_image( $thumb2_image->ID, 'thumbnail' ); + $thumb3_img_tag = wp_get_attachment_image( $thumb3_image->ID, 'thumbnail' ); + ?> + <div class="img-gallery"> + <div class="gallery-large"> + <a href="<?php the_permalink(); ?>"><?php echo $image_img_tag; ?></a> + </div><!-- .gallery-large --> + <?php if ( 3 == $total_images ) : ?> + <div class="gallery-thumbs-2"> + <a href="<?php the_permalink(); ?>" class="gallery-thumb-1"><?php echo $thumb1_img_tag; ?></a> + <a href="<?php the_permalink(); ?>" class="gallery-thumb-2"><?php echo $thumb2_img_tag; ?></a> + </div><!-- .gallery-thumbs --> + + <?php elseif ( 4 <= $total_images ) : ?> + <div class="gallery-thumbs-3"> + <a href="<?php the_permalink(); ?>" class="gallery-thumb-1"><?php echo $thumb1_img_tag; ?></a> + <a href="<?php the_permalink(); ?>" class="gallery-thumb-2"><?php echo $thumb2_img_tag; ?></a> + <a href="<?php the_permalink(); ?>" class="gallery-thumb-3"><?php echo $thumb3_img_tag; ?></a> + </div><!-- .gallery-thumbs --> + </div><!-- .img-gallery --> + <?php endif; ?> + + <p class="gallery-info"><em><?php printf( _n( 'This gallery contains <a %1$s>%2$s photo</a>.', 'This gallery contains <a %1$s>%2$s photos</a>.', $total_images, 'jetpack' ), + 'href="' . esc_url( get_permalink() ) . '" title="' . esc_attr( sprintf( __( 'Permalink to %s', 'jetpack' ), the_title_attribute( 'echo=0' ) ) ) . '" rel="bookmark"', + number_format_i18n( $total_images ) ); + ?></em></p> + + <?php endif; ?> + <?php endif; ?> + + <?php wp_link_pages( array( 'before' => '<div class="page-link"><span>' . __( 'Pages:', 'jetpack' ) . '</span>', 'after' => '</div>' ) ); ?> +</div><!-- .entry-content --> + + <footer class="entry-meta"> + <?php minileven_posted_on(); ?> + <?php if ( comments_open() ) : ?> + <span class="comments-link"><?php comments_popup_link( '<span class="leave-reply">' . __( 'Leave a Reply', 'jetpack' ) . '</span>', __( '<b>1</b> Reply', 'minileven' , 'jetpack'), __( '<b>%</b> Replies', 'minileven' , 'jetpack') ); ?></span> + <?php endif; // End if comments_open() ?> + + <?php edit_post_link( __( 'Edit', 'jetpack' ), '<span class="edit-link">', '</span>' ); ?> + </footer><!-- #entry-meta --> +</article><!-- #post-<?php the_ID(); ?> --> + +<?php comments_template( '', true ); ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/content.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/content.php new file mode 100644 index 00000000..9ab03516 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/content.php @@ -0,0 +1,60 @@ +<?php +/** + * The default template for displaying content + * + * @package Minileven + */ +?> + + <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> + <header class="entry-header"> + <?php if ( is_sticky() ) : ?> + <hgroup> + <h2 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'jetpack' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h2> + <h3 class="entry-format"><?php _e( 'Featured', 'jetpack' ); ?></h3> + </hgroup> + <?php else : ?> + <h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'jetpack' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1> + <?php endif; ?> + + <div class="entry-meta"> + <?php if ( is_singular() && is_multi_author() ) : ?> + <span class="author-link"> + <?php _e( 'Posted by ', 'jetpack' ); ?> + <a href="<?php echo esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ); ?>" rel="author"> + <?php printf( __( '%s', 'jetpack' ), get_the_author() ); ?> + </a> + </span><!-- .author-link --> + <?php endif; ?> + </div><!-- .entry-meta --> + </header><!-- .entry-header --> + + <div class="entry-content"> + <?php if ( '1' == get_option( 'wp_mobile_excerpt' ) && is_home() || is_search() || is_archive() ) : ?> + <?php the_excerpt(); ?> + <?php else : ?> + <?php the_content( __( 'Continue reading <span class="meta-nav">→</span>', 'jetpack' ) ); ?> + <?php endif; ?> + <?php wp_link_pages( array( 'before' => '<div class="page-link"><span>' . __( 'Pages:', 'jetpack' ) . '</span>', 'after' => '</div>' ) ); ?> + </div><!-- .entry-content --> + + <footer class="entry-meta"> + <?php if ( 'post' == get_post_type() ) : ?> + <?php minileven_posted_on(); ?> + <?php endif; ?> + <?php if ( comments_open() ) : ?> + <span class="comments-link"><?php comments_popup_link( '<span class="leave-reply">' . __( 'Leave a reply', 'jetpack' ) . '</span>', __( '<b>1</b> Reply', 'minileven' , 'jetpack'), __( '<b>%</b> Replies', 'minileven' , 'jetpack') ); ?></span> + <?php endif; // End if comments_open() ?> + <?php edit_post_link( __( 'Edit', 'jetpack' ), '<span class="edit-link">', '</span>' ); ?> + </footer><!-- #entry-meta --> + </article><!-- #post-<?php the_ID(); ?> --> + + <?php if ( is_single() ) : ?> + <nav id="nav-single"> + <h3 class="assistive-text"><?php _e( 'Post navigation', 'jetpack' ); ?></h3> + <span class="nav-previous"><?php previous_post_link( '%link', __( '« Previous', 'jetpack' ) ); ?></span> + <span class="nav-next"><?php next_post_link( '%link', __( 'Next »', 'jetpack' ) ); ?></span> + </nav><!-- #nav-single --> + <?php endif; ?> + + <?php comments_template( '', true ); ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php new file mode 100644 index 00000000..b739fe46 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/footer.php @@ -0,0 +1,36 @@ +<?php +/** + * The template for displaying the footer. + * + * Contains the closing of the id=main div and all content after + * + * @package Minileven + */ +?> + + </div><!-- #main --> +</div><!-- #page --> +<?php get_sidebar(); ?> + +</div><!-- #wrapper --> + +<footer id="colophon" role="contentinfo"> + <div id="site-generator"> + +<?php + global $wp; + $current_url = trailingslashit( home_url( add_query_arg( array(), $wp->request ) ) ); +?> + <a href="<?php echo $current_url . '?ak_action=reject_mobile'; ?>">View Full Site</a><br /> + <?php do_action( 'wp_mobile_theme_footer' ); ?> + <?php do_action( 'minileven_credits' ); ?> + <a href="<?php echo esc_url( __( 'http://wordpress.org/', 'jetpack' ) ); ?>" title="<?php esc_attr_e( 'Semantic Personal Publishing Platform', 'minileven' , 'jetpack'); ?>" rel="generator"><?php printf( __( 'Proudly powered by %s', 'minileven' , 'jetpack'), 'WordPress' ); ?></a> + </div> +</footer><!-- #colophon --> + + + +<?php wp_footer(); ?> + +</body> +</html>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php new file mode 100644 index 00000000..d13cb234 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/functions.php @@ -0,0 +1,152 @@ +<?php +/** + * Minileven functions and definitions + * + * Sets up the theme and provides some helper functions. Some helper functions + * are used in the theme as custom template tags. Others are attached to action and + * filter hooks in WordPress to change core functionality. + * + * The first function, minileven_setup(), sets up the theme by registering support + * for various features in WordPress, such as post thumbnails, navigation menus, and the like. + * + * @package Minileven + */ + +/** + * Set the content width based on the theme's design and stylesheet. + */ +if ( ! isset( $content_width ) ) + $content_width = 584; + +/** + * Tell WordPress to run minileven_setup() when the 'after_setup_theme' hook is run. + */ +add_action( 'after_setup_theme', 'minileven_setup' ); + +if ( ! function_exists( 'minileven_setup' ) ): +/** + * Sets up theme defaults and registers support for various WordPress features. + */ +function minileven_setup() { + global $wp_version; + + /** + * Custom template tags for this theme. + */ + require( get_template_directory() . '/inc/template-tags.php' ); + + /** + * Custom functions that act independently of the theme templates + */ + require( get_template_directory() . '/inc/tweaks.php' ); + + /* Make Minileven available for translation. + * Translations can be added to the /languages/ directory. + * If you're building a theme based on Minileven, use a find and replace + * to change 'minileven' to the name of your theme in all the template files. + */ + load_theme_textdomain( 'minileven', TEMPLATEPATH . '/languages' ); + + // Add default posts and comments RSS feed links to <head>. + add_theme_support( 'automatic-feed-links' ); + + // This theme uses wp_nav_menu() in one location. + register_nav_menu( 'primary', __( 'Primary Menu', 'jetpack' ) ); + + // Add support for a variety of post formats + add_theme_support( 'post-formats', array( 'gallery' ) ); + + // Add support for custom backgrounds + if ( version_compare( $wp_version, '3.4', '>=' ) ) + add_theme_support( 'custom-background' ); + else + add_custom_background(); + + // Add support for post thumbnails + add_theme_support( 'post-thumbnails' ); +} +endif; // minileven_setup + +/** + * Enqueue scripts and styles + */ +function minileven_scripts() { + global $post; + + wp_enqueue_style( 'style', get_stylesheet_uri() ); + + wp_enqueue_script( 'small-menu', get_template_directory_uri() . '/js/small-menu.js', array( 'jquery' ), '20120206', true ); + + if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { + wp_enqueue_script( 'comment-reply' ); + } +} +add_action( 'wp_enqueue_scripts', 'minileven_scripts' ); + +/** + * Register our sidebars and widgetized areas. + * @since Minileven 1.0 + */ +function minileven_widgets_init() { + register_sidebar( array( + 'name' => __( 'Main Sidebar', 'jetpack' ), + 'id' => 'sidebar-1', + 'before_widget' => '<aside id="%1$s" class="widget %2$s">', + 'after_widget' => "</aside>", + 'before_title' => '<h3 class="widget-title">', + 'after_title' => '</h3>', + ) ); +} +add_action( 'widgets_init', 'minileven_widgets_init' ); + +function minileven_posts_per_page() { + return 5; +} +add_filter('pre_option_posts_per_page', 'minileven_posts_per_page'); + +/* This function determines the actual theme the user is using. */ +function minileven_actual_current_theme() { + if ( function_exists( 'jetpack_mobile_template' ) ) + remove_action( 'option_template', 'jetpack_mobile_template' ); + + $template = get_option( 'template' ); + + if ( function_exists( 'jetpack_mobile_template' ) ) + add_action( 'option_template', 'jetpack_mobile_template' ); + + return $template; +} + +/* This function grabs the location of the custom menus from the current theme. If no menu is set in a location +* it will return a boolean "false". This function helps Minileven know which custom menu to display. */ +function minileven_get_menu_location() { + $theme_slug = minileven_actual_current_theme(); + $mods = get_option( "theme_mods_{$theme_slug}" ); + + if ( isset( $mods['nav_menu_locations'] ) && ! empty( $mods['nav_menu_locations'] ) ) + return $mods['nav_menu_locations']; + + return false; +} + +/* This function grabs the custom background image from the user's current theme so that Minileven can display it. */ +function minileven_get_background() { + $theme_slug = minileven_actual_current_theme(); + $mods = get_option( "theme_mods_$theme_slug" ); + + if ( ! empty( $mods ) ) { + return array( + 'color' => isset( $mods['background_color'] ) ? $mods['background_color'] : null, + 'image' => isset( $mods['background_image'] ) ? $mods['background_image'] : null, + 'repeat' => isset( $mods['background_repeat'] ) ? $mods['background_repeat'] : null, + 'position' => isset( $mods['background_position_x'] ) ? $mods['background_position_x'] : null, + 'attachment' => isset( $mods['attachment'] ) ? $mods['attachment'] : null, + ); + } + return false; +} + +/** + * Implement the Custom Header functions + */ +require( get_template_directory() . '/inc/custom-header.php' );
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/header.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/header.php new file mode 100644 index 00000000..7c5f9a66 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/header.php @@ -0,0 +1,49 @@ +<?php +/** + * The Header for our theme. + * + * Displays all of the <head> section and everything up till <div id="main"> + * + * @package Minileven + */ +?><!DOCTYPE html> +<html <?php language_attributes(); ?>> +<head> +<meta charset="<?php bloginfo( 'charset' ); ?>" /> +<meta name="viewport" content="width=device-width" /> +<title><?php wp_title( '|', true, 'right' ); ?></title> +<link rel="profile" href="http://gmpg.org/xfn/11" /> +<link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>" /> +<?php wp_head(); ?> + +<body <?php body_class(); ?>> +<div id="wrapper"> +<div id="page" class="hfeed"> + <header id="branding" role="banner"> + <?php + minileven_header(); + $location = minileven_get_menu_location(); // get the menu locations from the current theme in use + ?> + <div class="menu-search"> + <nav id="access" role="navigation"> + <div class="menu-handle"> + <h3 class="assistive-text"><?php _e( 'Menu', 'jetpack' ); ?></h3> + </div><!-- .menu-handle --> + <?php /* Allow screen readers / text browsers to skip the navigation menu and get right to the good stuff. */ ?> + <div class="skip-link"><a class="assistive-text" href="#content" title="<?php esc_attr_e( 'Skip to primary content', 'jetpack' ); ?>"><?php _e( 'Skip to primary content', 'minileven' , 'jetpack'); ?></a></div> + <?php /* Our navigation menu. If one isn't filled out, wp_nav_menu falls back to wp_page_menu. The menu assiged to the primary position is the one used. If none is assigned, the menu with the lowest ID is used. */ + if ( false !== $location ) : + $menu_id = array_shift( array_values( $location ) ); // acccess the ID of the menu assigned to that location. Using only the first menu ID returned in the array. + wp_nav_menu( array( 'theme_location' => 'primary', 'menu' => $menu_id ) ); + else: // if the $location variable is false, wp_page_menu() is shown instead. + wp_nav_menu( array( 'theme_location' => 'primary' ) ); + endif; + ?> + </nav><!-- #access --> + <div class="search-form"> + <?php get_search_form(); ?> + </div><!-- .search-form--> + </div><!-- .menu-search--> + </header><!-- #branding --> + + <div id="main">
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php new file mode 100644 index 00000000..be8402bd --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/image.php @@ -0,0 +1,98 @@ +<?php +/** + * The template for displaying image attachments. + * + * @package Minileven + */ + +get_header(); ?> + + <div id="primary" class="image-attachment"> + <div id="content" role="main"> + + <?php while ( have_posts() ) : the_post(); ?> + + <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> + <header class="entry-header"> + <h1 class="entry-title"><?php the_title(); ?></h1> + </header><!-- .entry-header --> + + <div class="entry-content"> + + <div class="entry-attachment"> + <div class="attachment"> +<?php + /** + * Grab the IDs of all the image attachments in a gallery so we can get the URL of the next adjacent image in a gallery, + * or the first image (if we're looking at the last image in a gallery), or, in a gallery of one, just the link to that image file + */ + $attachments = array_values( get_children( array( 'post_parent' => $post->post_parent, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID' ) ) ); + foreach ( $attachments as $k => $attachment ) { + if ( $attachment->ID == $post->ID ) + break; + } + $k++; + // If there is more than 1 attachment in a gallery + if ( count( $attachments ) > 1 ) { + if ( isset( $attachments[ $k ] ) ) + // get the URL of the next image attachment + $next_attachment_url = get_attachment_link( $attachments[ $k ]->ID ); + else + // or get the URL of the first image attachment + $next_attachment_url = get_attachment_link( $attachments[ 0 ]->ID ); + } else { + // or, if there's only 1 image, get the URL of the image + $next_attachment_url = wp_get_attachment_url(); + } +?> + <a href="<?php echo esc_url( $next_attachment_url ); ?>" title="<?php echo esc_attr( get_the_title() ); ?>" rel="attachment"><?php + $attachment_size = apply_filters( 'minileven_attachment_size', 848 ); + echo wp_get_attachment_image( $post->ID, array( $attachment_size, 1024 ) ); // filterable image width with 1024px limit for image height. + ?></a> + + <?php if ( ! empty( $post->post_excerpt ) ) : ?> + <div class="entry-caption"> + <?php the_excerpt(); ?> + </div> + <?php endif; ?> + </div><!-- .attachment --> + + </div><!-- .entry-attachment --> + + <div class="entry-description"> + <?php the_content(); ?> + <?php wp_link_pages( array( 'before' => '<div class="page-link"><span>' . __( 'Pages:', 'jetpack' ) . '</span>', 'after' => '</div>' ) ); ?> + </div><!-- .entry-description --> + + </div><!-- .entry-content --> + + <footer class="entry-meta"> + <div class="attachment-meta"> + <?php + $metadata = wp_get_attachment_metadata(); + printf( __( '<span class="entry-gallery">« <a href="%1$s" title="Back to %2$s" rel="gallery">Back to Gallery</a></span>', 'jetpack' ), + esc_url( get_permalink( $post->post_parent ) ), + get_the_title( $post->post_parent ) + ); + ?> + </div><!-- .attachment-meta--> + <?php if ( comments_open() ) : ?> + <span class="comments-link"><?php comments_popup_link( '<span class="leave-reply">' . __( 'Leave a reply', 'jetpack' ) . '</span>', __( '<b>1</b> Reply', 'minileven' , 'jetpack'), __( '<b>%</b> Replies', 'minileven' , 'jetpack') ); ?></span> + <?php endif; // End if comments_open() ?> + <?php edit_post_link( __( 'Edit', 'jetpack' ), '<span class="edit-link">', '</span>' ); ?> + </footer><!-- #entry-meta --> + </article><!-- #post-<?php the_ID(); ?> --> + + <nav id="nav-single"> + <h3 class="assistive-text"><?php _e( 'Image navigation', 'next-saturday' , 'jetpack' ); ?></h3> + <span class="nav-previous"><?php previous_image_link( false, __( '» Previous' , 'jetpack' ) ); ?></span> + <span class="nav-next"><?php next_image_link( false, __( 'Next » ' , 'jetpack' ) ); ?></span> + </nav><!-- #nav-single --> + + <?php comments_template(); ?> + + <?php endwhile; // end of the loop. ?> + + </div><!-- #content --> + </div><!-- #primary --> +<?php get_footer(); ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/custom-header.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/custom-header.php new file mode 100644 index 00000000..8a7a21e8 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/custom-header.php @@ -0,0 +1,101 @@ +<?php +/** + * @package Minileven + * @since Minileven 2.0 + */ + +/* This function grabs the custom header from the current theme so that Minileven can display it. */ +function minileven_get_header_image() { + $theme_slug = minileven_actual_current_theme(); + $mods = get_option( "theme_mods_{$theme_slug}" ); + + if ( isset( $mods['header_image'] ) && 'remove-header' != $mods['header_image'] && 'random-default-image' != $mods['header_image'] && 'random-uploaded-image' != $mods['header_image'] ) + return $mods['header_image']; + + return false; +} + +/* This function determines whether or not the user is displaying the header on the current theme */ +function minileven_header_text_display() { + $theme_slug = minileven_actual_current_theme(); + $mods = get_option( "theme_mods_{$theme_slug}" ); + + if ( isset( $mods['header_textcolor'] ) ) + return $mods['header_textcolor']; + + return false; +} + +/* This function determines how the header should be displayed. */ +function minileven_header() { + $header_image = minileven_get_header_image(); + $header_text = minileven_header_text_display(); + + if ( 'blank' != $header_text ) : ?> + <hgroup> + <h1 id="site-title"><span><a href="<?php echo esc_url( home_url( '/' ) ); ?>" title="<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></span></h1> + <h2 id="site-description"><?php bloginfo( 'description' ); ?></h2> + </hgroup> +<?php endif; + + if ( false !== $header_image ) : ?> + <div id="header-img"> + <a href="<?php echo esc_url( home_url( '/' ) ); ?>"> + <img src="<?php echo $header_image; ?>" alt="" /> + </a> + </div><!-- #header-img --> +<?php endif; // end check for header image existence. +} + +/* This function displays the custom background image or color, and custom text color */ +function minileven_show_background_and_header_color() { + $background = minileven_get_background(); + $header_text = minileven_header_text_display(); + + $style = ''; + + if ( $background['color'] || $background['image'] ) : + $style = $background['color'] ? "background-color: #$background[color];" : ''; + + if ( $background['image'] ) : + $image = " background-image: url('$background[image]');"; + + if ( ! in_array( $background['repeat'], array( 'no-repeat', 'repeat-x', 'repeat-y', 'repeat' ) ) ) + $background['repeat'] = 'repeat'; + $repeat = " background-repeat: $background[repeat];"; + + if ( ! in_array( $background['position'], array( 'center', 'right', 'left' ) ) ) + $background['position'] = 'left'; + $position = " background-position: top $background[position];"; + + if ( ! in_array( $background['attachment'], array( 'fixed', 'scroll' ) ) ) + $background['attachment'] = 'scroll'; + $attachment = " background-attachment: $background[attachment];"; + + $style .= $image . $repeat . $position . $attachment; + endif; + endif; +?> + <style type="text/css"> + <?php if ( $style ) { ?> + body { + <?php echo trim( $style ); ?> + } + <?php } ?> + #page { + margin: 0.6em 0.6em 0.8em; + } + #site-generator { + border: 0; + } + <?php if ( 'blank' != $header_text && '1' != get_option( 'wp_mobile_header_color' ) ) : ?> + /* If The user has set a header text color, use that */ + #site-title, + #site-title a { + color: #<?php echo $header_text; ?>; + <?php endif; ?> + } + </style> +<?php +} +add_action( 'wp_head', 'minileven_show_background_and_header_color' );
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/template-tags.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/template-tags.php new file mode 100644 index 00000000..2fc5f6d6 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/template-tags.php @@ -0,0 +1,99 @@ +<?php +/** + * Custom template tags for this theme. + * + * Eventually, some of the functionality here could be replaced by core features + * + * @package Minileven + * @since Minileven 2.0 + */ + +/** + * Display navigation to next/previous pages when applicable + */ +function minileven_content_nav( $nav_id ) { + global $wp_query; + + if ( $wp_query->max_num_pages > 1 ) : ?> + <nav id="<?php echo $nav_id; ?>"> + <h3 class="assistive-text"><?php _e( 'Post navigation', 'jetpack' ); ?></h3> + <div class="nav-previous"><?php next_posts_link( __( '<span class="meta-nav">«</span> Older', 'jetpack' ) ); ?></div> + <div class="nav-next"><?php previous_posts_link( __( 'Newer <span class="meta-nav">»</span>', 'jetpack' ) ); ?></div> + </nav><!-- #nav-above --> + <?php endif; +} + +/** + * Template for comments and pingbacks. + * Used as a callback by wp_list_comments() for displaying the comments. + * @since Minileven 1.0 + */ +function minileven_comment( $comment, $args, $depth ) { + $GLOBALS['comment'] = $comment; + switch ( $comment->comment_type ) : + case 'pingback' : + case 'trackback' : + ?> + <li class="post pingback"> + <p><?php _e( 'Pingback:', 'jetpack' ); ?> <?php comment_author_link(); ?></p> + <?php + break; + default : + ?> + <li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>"> + <article id="comment-<?php comment_ID(); ?>" class="comment"> + <footer class="comment-meta"> + <div class="comment-author vcard"> + <?php + $avatar_size = 32; + if ( '0' != $comment->comment_parent ) + $avatar_size = 24; + + echo get_avatar( $comment, $avatar_size ); + + /* translators: 1: comment author, 2: date and time */ + printf( __( '%1$s on %2$s', 'jetpack' ), + sprintf( '<span class="fn">%s</span>', get_comment_author_link() ), + sprintf( '<a href="%1$s"><time pubdate datetime="%2$s">%3$s</time></a>', + esc_url( get_comment_link( $comment->comment_ID ) ), + get_comment_time( 'c' ), + /* translators: 1: date, 2: time */ + sprintf( __( '%1$s at %2$s', 'jetpack' ), get_comment_date(), get_comment_time() ) + ) + ); + ?> + + <?php edit_comment_link( __( 'Edit', 'jetpack' ), '<span class="edit-link">', '</span>' ); ?> + </div><!-- .comment-author .vcard --> + + <?php if ( $comment->comment_approved == '0' ) : ?> + <em class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'jetpack' ); ?></em> + <br /> + <?php endif; ?> + + </footer> + + <div class="comment-content"><?php comment_text(); ?></div> + + <div class="reply"> + <?php comment_reply_link( array_merge( $args, array( 'reply_text' => __( 'Reply <span>↓</span>', 'jetpack' ), 'depth' => $depth, 'max_depth' => $args['max_depth'] ) ) ); ?> + </div><!-- .reply --> + </article><!-- #comment-## --> + + <?php + break; + endswitch; +} + +/** + * Prints HTML with meta information for the current post-date/time and author. + * @since Minileven 1.0 + */ +function minileven_posted_on() { + printf( __( '<span class="entry-date"><a href="%1$s" title="%2$s" rel="bookmark"><time datetime="%3$s" pubdate>%4$s</time></a></span>', 'jetpack' ), + esc_url( get_permalink() ), + esc_attr( get_the_time() ), + esc_attr( get_the_date( 'c' ) ), + esc_html( get_the_date() ) + ); +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/tweaks.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/tweaks.php new file mode 100644 index 00000000..35a96720 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/inc/tweaks.php @@ -0,0 +1,94 @@ +<?php +/** + * Custom functions that act independently of the theme templates + * + * Eventually, some of the functionality here could be replaced by core features + * + * @package Minileven + * @since Minileven 2.0 + */ + +/** + * Sets the post excerpt length to 40 words. + * + * To override this length in a child theme, remove the filter and add your own + * function tied to the excerpt_length filter hook. + */ +function minileven_excerpt_length( $length ) { + return 40; +} +add_filter( 'excerpt_length', 'minileven_excerpt_length' ); + +/** + * Returns a "Continue Reading" link for excerpts + */ +function minileven_continue_reading_link() { + return ' <a href="'. esc_url( get_permalink() ) . '">' . __( 'Continue reading <span class="meta-nav">→</span>', 'jetpack' ) . '</a>'; +} + +/** + * Replaces "[...]" (appended to automatically generated excerpts) with an ellipsis and minileven_continue_reading_link(). + */ +function minileven_auto_excerpt_more( $more ) { + return ' …' . minileven_continue_reading_link(); +} +add_filter( 'excerpt_more', 'minileven_auto_excerpt_more' ); + +/** + * Adds a pretty "Continue Reading" link to custom post excerpts. + * + * To override this link in a child theme, remove the filter and add your own + * function tied to the get_the_excerpt filter hook. + */ +function minileven_custom_excerpt_more( $output ) { + if ( has_excerpt() && ! is_attachment() ) { + $output .= minileven_continue_reading_link(); + } + return $output; +} +add_filter( 'get_the_excerpt', 'minileven_custom_excerpt_more' ); + +/** + * Get our wp_nav_menu() fallback, wp_page_menu(), to show a home link. + */ +function minileven_page_menu_args( $args ) { + $args['show_home'] = true; + return $args; +} +add_filter( 'wp_page_menu_args', 'minileven_page_menu_args' ); + +/** + * Adds a custom class to the array of body classes, to allow Minileven to be targeted with Custom CSS. + */ +function minileven_body_classes( $classes ) { + $classes[] = 'mobile-theme'; + return $classes; +} +add_filter( 'body_class', 'minileven_body_classes' ); + +/** + * Filters wp_title to print a neat <title> tag based on what is being viewed. + * + * @since Minileven 2.0 + */ +function minileven_wp_title( $title, $sep ) { + global $page, $paged; + + if ( is_feed() ) + return $title; + + // Add the blog name + $title .= get_bloginfo( 'name' ); + + // Add the blog description for the home/front page. + $site_description = get_bloginfo( 'description', 'display' ); + if ( $site_description && ( is_home() || is_front_page() ) ) + $title .= " $sep $site_description"; + + // Add a page number if necessary: + if ( $paged >= 2 || $page >= 2 ) + $title .= " $sep " . sprintf( __( 'Page %s', 'jetpack' ), max( $paged, $page ) ); + + return $title; +} +add_filter( 'wp_title', 'minileven_wp_title', 10, 2 ); diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/index.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/index.php new file mode 100644 index 00000000..71aa73b6 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/index.php @@ -0,0 +1,73 @@ +<?php +/** + * The main template file. + * + * This is the most generic template file in a WordPress theme + * and one of the two required files for a theme (the other being style.css). + * It is used to display a page when nothing more specific matches a query. + * E.g., it puts together the home page when no home.php file exists. + * Learn more: http://codex.wordpress.org/Template_Hierarchy + * + * @package Minileven + */ + +get_header(); ?> + + <div id="primary"> + <div id="content" role="main"> + + <?php if ( is_archive() ) : ?> + <header class="page-header"> + <h1 class="page-title"> + <?php if ( is_day() ) : ?> + <?php printf( __( 'Daily Archives: %s', 'jetpack' ), '<span>' . get_the_date() . '</span>' ); ?> + <?php elseif ( is_month() ) : ?> + <?php printf( __( 'Monthly Archives: %s', 'jetpack' ), '<span>' . get_the_date( 'F Y' ) . '</span>' ); ?> + <?php elseif ( is_year() ) : ?> + <?php printf( __( 'Yearly Archives: %s', 'jetpack' ), '<span>' . get_the_date( 'Y' ) . '</span>' ); ?> + <?php elseif ( is_category() ) : ?> + <?php printf( __( 'Posted in %s', 'jetpack' ), '<span>' . single_cat_title( '', false ) . '</span>' ); ?> + <?php elseif ( is_tag() ) : ?> + <?php printf( __( 'Tagged with %s', 'jetpack' ), '<span>' . single_tag_title( '', false ) . '</span>' ); ?> + <?php elseif( is_author() ) : ?> + <?php printf( __( 'Posted by', 'jetpack' ), '<span>' . get_the_author() . '</span>' ); ?> + <?php else : ?> + <?php _e( 'Blog Archives', 'jetpack' ); ?> + <?php endif; ?> + </h1> + </header> + <?php endif; ?> + + <?php if ( is_search() ) : ?> + <header class="page-header"> + <h1 class="page-title"><?php printf( __( 'Search Results for: %s', 'jetpack' ), '<span>' . get_search_query() . '</span>' ); ?></h1> + </header> + <?php endif; ?> + + <?php if ( have_posts() ) : // Start the loop ?> + <?php while ( have_posts() ) : the_post(); ?> + + <?php get_template_part( 'content', get_post_format() ); ?> + + <?php endwhile; ?> + + <?php else : ?> + <article id="post-0" class="post error404 not-found"> + <header class="entry-header"> + <h1 class="entry-title"><?php _e( 'Nothing Found', 'jetpack' ); ?></h1> + </header><!-- .entry-header --> + + <div class="entry-content"> + <p><?php _e( 'Apologies, but no results were found for the requested archive. Perhaps searching will help find a related post.', 'jetpack' ); ?></p> + <?php get_search_form(); ?> + </div><!-- .entry-content --> + </article><!-- #post-0 --> + + <?php endif; ?> + + </div><!-- #content --> + + <?php minileven_content_nav( 'nav-below' ); ?> + + </div><!-- #primary --> +<?php get_footer(); ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/js/small-menu.js b/plugins/jetpack/modules/minileven/theme/pub/minileven/js/small-menu.js new file mode 100644 index 00000000..f6e1d060 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/js/small-menu.js @@ -0,0 +1,41 @@ +/** + * Handles toggling the main navigation menu for small screens. + */ +jQuery( document ).ready( function( $ ) { + var $subsidiary = $( '#branding' ), + timeout = false; + + $.fn.smallMenu = function() { + $subsidiary.find( '#access' ).addClass( 'main-small-navigation' ); + $subsidiary.find( '#access h3' ).removeClass( 'assistive-text' ).addClass( 'menu-label' ); + $subsidiary.find( '#access .menu-handle' ).addClass( 'menu-toggle' ); + + $( '.menu-toggle' ).click( function() { + $subsidiary.find( '.menu' ).toggle(); + $( this ).toggleClass( 'toggled-on' ); + } ); + }; + + // Check viewport width on first load. + if ( $( window ).width() < 4000 ) + $.fn.smallMenu(); + + // Check viewport width when user resizes the browser window. + $( window ).resize( function() { + var browserWidth = $( window ).width(); + + if ( false !== timeout ) + clearTimeout( timeout ); + + timeout = setTimeout( function() { + if ( browserWidth < 4000 ) { + $.fn.smallMenu(); + } else { + $subsidiary.find( '#access' ).removeClass( 'main-small-navigation' ); + $subsidiary.find( '#access h3' ).removeClass( 'menu-label' ).addClass( 'assistive-text' ); + $subsidiary.find( '#access .menu-handle' ).removeClass( 'menu-toggle' ); + $subsidiary.find( '.menu' ).removeAttr( 'style' ); + } + }, 200 ); + } ); +} );
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/page.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/page.php new file mode 100644 index 00000000..785137ef --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/page.php @@ -0,0 +1,42 @@ +<?php +/** + * The template for displaying all pages. + * + * This is the template that displays all pages by default. + * Please note that this is the WordPress construct of pages + * and that other 'pages' on your WordPress site will use a + * different template. + * + * @package Minileven + */ + +get_header(); ?> + + <div id="primary"> + <div id="content" role="main"> + + <?php while ( have_posts() ) : the_post(); ?> + + <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> + <header class="entry-header"> + <h1 class="entry-title"><?php the_title(); ?></h1> + </header><!-- .entry-header --> + + <div class="entry-content"> + <?php the_content(); ?> + <?php wp_link_pages( array( 'before' => '<div class="page-link"><span>' . __( 'Pages:', 'jetpack' ) . '</span>', 'after' => '</div>' ) ); ?> + </div><!-- .entry-content --> + <?php if ( is_user_logged_in() ) : ?> + <footer class="entry-meta"> + <?php edit_post_link( __( 'Edit', 'jetpack' ), '<span class="edit-link">', '</span>' ); ?> + </footer><!-- .entry-meta --> + <?php endif; ?> + </article><!-- #post-<?php the_ID(); ?> --> + + <?php comments_template( '', true ); ?> + + <?php endwhile; // end of the loop. ?> + + </div><!-- #content --> + </div><!-- #primary --> +<?php get_footer(); ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/rtl.css b/plugins/jetpack/modules/minileven/theme/pub/minileven/rtl.css new file mode 100644 index 00000000..7b67e1f2 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/rtl.css @@ -0,0 +1,582 @@ +/* +Theme Name: Twenty Eleven + +Adding support for language written in a Right To Left (RTL) direction is easy - +it's just a matter of overwriting all the horizontal positioning attributes +of your CSS stylesheet in a separate stylesheet file named rtl.css. + +http://codex.wordpress.org/Right_to_Left_Language_Support + +*/ + +/* =Reset reset +----------------------------------------------- */ + +caption, th, td { + text-align: right; +} + +/* =Structure +----------------------------------------------- */ + +body { + direction:rtl; + unicode-bidi:embed; +} + +/* Showcase */ +.page-template-showcase-php section.recent-posts { + float: left; + margin: 0 31% 0 0; +} +.page-template-showcase-php #main .widget-area { + float: right; + margin: 0 0 0 -22.15%; +} + +/* One column */ + +.one-column article.feature-image.small .entry-summary a { + left: auto; + right: -9%; +} + +/* Simplify the pullquotes and pull styles */ +.one-column.singular .entry-meta .edit-link a { + right: 0px; + left: auto; +} +/* Make sure we have room for our comment avatars */ +.one-column .commentlist > li.comment { + margin-left: 0; + margin-right: 102px; +} +/* Make sure the logo and search form don't collide */ +.one-column #branding #searchform { + right: auto; + left: 40px; +} +/* Talking avatars take up too much room at this size */ +.one-column .commentlist > li.comment { + margin-right: 0; +} +.one-column .commentlist > li.comment .comment-meta, +.one-column .commentlist > li.comment .comment-content { + margin-right: 0; + margin-left: 85px; +} +.one-column .commentlist .avatar { + right: auto; + left: 1.625em; +} +.one-column .commentlist .children .avatar { + left: auto; + right: 2.2em; +} + +/* =Global +----------------------------------------------- */ + +/* Text elements */ +p { + margin-bottom: 1.625em; +} +ul, ol { + margin: 0 2.5em 1.625em 0; +} +.ltr ul, ol { + margin: 0 0 1.625em 2.5em; +} +blockquote { + font-family: Arial, sans-serif; +} +blockquote em, blockquote i, blockquote cite { + font-style: normal; +} + +/* Forms */ +textarea { + padding-left: 0; + padding-right: 3px; +} +input#s { + background-position: 97% 6px; + padding: 4px 28px 4px 10px; +} + +/* Assistive text */ +#access a.assistive-text:active, +#access a.assistive-text:focus { + left: auto; + right: 7.6%; +} + +/* =Header +----------------------------------------------- */ + +#site-title { + margin-right: 0; + margin-left: 270px; +} + +#site-description { + margin: 0 0 3.65625em 270px; +} + +/* =Menu +-------------------------------------------------------------- */ + +#access { + float: right; +} +#access ul { + margin: 0 -0.8125em 0 0; + padding-right: 0; +} +#access li { + float: right; +} +#access ul ul { + float: right; + left: auto; + right: 0; +} +#access ul ul ul { + left: auto; + right: 100%; +} + +/* Search Form */ +#branding #searchform { + right: auto; + left: 7.6%; + text-align: left; +} +#branding #s { + float: left; +} +#branding .only-search + #access div { + padding-right: 0; + padding-left: 205px; +} + + +/* =Content +----------------------------------------------- */ +.entry-title, +.entry-header .entry-meta { + padding-right: 0; + padding-left: 76px; +} +.entry-content td, +.comment-content td { + padding: 6px 0 6px 10px; +} +.page-link span { + margin-right: 0; + margin-left: 6px; +} +.entry-meta .edit-link a { + float: left; +} +/* Images */ + +.wp-caption .wp-caption-text, +.gallery-caption { + font-family: Arial, sans-serif; +} +.wp-caption .wp-caption-text { + padding: 10px 40px 5px 0px; +} +.wp-caption .wp-caption-text:before { + margin-right: 0; + margin-left: 5px; + left: auto; + right: 10px; +} +#content .gallery-columns-4 .gallery-item { + padding-right:0; + padding-left:2%; +} + +/* Author Info */ +.singular #author-info { + margin: 2.2em -35.4% 0 -35.6%; +} +#author-avatar { + float: right; + margin-right: 0; + margin-left: -78px; +} +#author-description { + float: right; + margin-left: 0; + margin-right: 108px; +} +/* Comments link */ +.entry-header .comments-link a { + background-image: url(images/comment-bubble-rtl.png); + right: auto; + left: 0; +} + +/* + Post Formats Headings +*/ +.singular .entry-title, +.singular .entry-header .entry-meta { + padding-left: 0; +} +.singular .entry-header .entry-meta { + left: auto; + right: 0; +} +.singular .entry-meta .edit-link a { + left: auto; + right: 50px; +} + + +/* =Gallery +----------------------------------------------- */ + +.format-gallery .gallery-thumb { + float: right; + margin: .375em 0 0 1.625em; +} + + +/* =Status +----------------------------------------------- */ + +.format-status img.avatar { + float: right; + margin: 4px 0 2px 10px; +} + + +/* =Image +----------------------------------------------- */ + +.indexed.format-image div.entry-meta { + float: right; +} +/* =error404 +---------------------- +------------------------- */ +.error404 #main .widget { + float: right; + margin-right: auto; + margin-left: 3.7%; +} +.error404 #main .widget_archive { + margin-left: 0; +} +.error404 #main .widget_tag_cloud { + margin-left: 0; +} + +/* =Showcase +----------------------------------------------- */ + +article.intro .edit-link a { + right: auto; + left: 20px; +} + +/* Featured post */ +section.featured-post { + float: right; +} + +/* Small featured post */ +section.featured-post .attachment-small-feature { + float: left; + margin: 0 0 1.625em -8.9%; + right: auto; + left: -15px; +} +article.feature-image.small { + float: right; +} +article.feature-image.small .entry-summary p a { + left:auto; + right: -23.8%; + padding: 9px 85px 9px 26px; +} + +/* Large featured post */ +section.feature-image.large .hentry { + left:auto; + right: 9%; + margin: 1.625em 0 0 9%; +} +/* Featured Slider */ +.featured-posts .showcase-heading { + padding-left: 0; + padding-right: 8.9%; +} +.featured-posts section.featured-post { + left: auto; + right: 0; +} +#content .feature-slider { + right: auto; + left: 8.9%; +} +.feature-slider li { + float: right; +} +/* Recent Posts */ +section.recent-posts .other-recent-posts a[rel="bookmark"] { + float: right; +} +section.recent-posts .other-recent-posts .comments-link a, +section.recent-posts .other-recent-posts .comments-link > span { + padding: 0.3125em 1em 0.3125em 0; + left: 0; + text-align: left; +} + +/* =Attachments +----------------------------------------------- */ + +/* =Navigation +-------------------------------------------------------------- */ + +.nav-previous { + float: right; +} +.nav-next { + float: left; + text-align: left; +} + +/* Singular navigation */ +#nav-single { + float: left; + text-align: left; +} +#nav-single .nav-next { + padding-left: 0; + padding-right: .5em; +} + + +/* =Widgets +----------------------------------------------- */ + +.widget ul ul { + margin-left: 0; + margin-right: 1.5em; +} + +/* Twitter */ +.widget_twitter .timesince { + margin-right: 0; + margin-left: -10px; + text-align: left; +} + +/* =Comments +----------------------------------------------- */ + +.commentlist .children li.comment { + border-left: none; + border-right: 1px solid #ddd; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.commentlist .children li.comment .comment-meta { + margin-left: 0; + margin-right: 50px; +} +.commentlist .avatar { + left: auto; + right: -102px; +} +.commentlist > li:before { + content: url(images/comment-arrow-rtl.png); + left:auto; + right: -21px; +} +.commentlist > li.pingback:before { + content: ''; +} +.commentlist .children .avatar { + left: auto; + right: 2.2em; +} + +/* Post author highlighting */ +.commentlist > li.bypostauthor:before { + content: url(images/comment-arrow-bypostauthor-rtl.png); +} + +/* sidebar-page.php comments */ +/* Make sure we have room for our comment avatars */ +.page-template-sidebar-page-php .commentlist > li.comment, +.page-template-sidebar-page-php.commentlist .pingback { + margin-left: 0; + margin-right: 102px; +} + +/* Comment Form */ +#respond .comment-form-author label, +#respond .comment-form-email label, +#respond .comment-form-url label, +#respond .comment-form-comment label { + left: auto; + right: 4px; +} +#respond .comment-form-author label, +#respond .comment-form-email label, +#respond .comment-form-url label, +#respond .comment-form-comment label { + -webkit-box-shadow: -1px 2px 2px rgba(204,204,204,0.8); + -moz-box-shadow: -1px 2px 2px rgba(204,204,204,0.8); + box-shadow: -1px 2px 2px rgba(204,204,204,0.8); +} +#respond .comment-form-author .required, +#respond .comment-form-email .required { + left: auto; + right: 75%; +} +#respond .form-submit { + float: left; +} +#respond input#submit { + left: auto; + right: 30px; + padding: 5px 22px 5px 42px; +} +#respond #cancel-comment-reply-link { + margin-left: 0; + margin-right: 10px; +} +#cancel-comment-reply-link { + right: auto; + left: 1.625em; +} + +/* =Footer +----------------------------------------------- */ + +/* Two Footer Widget Areas */ +#supplementary.two .widget-area { + float: right; + margin-right: 0; + margin-left: 3.7%; +} +#supplementary.two .widget-area + .widget-area { + margin-left: 0; +} + +/* Three Footer Widget Areas */ +#supplementary.three .widget-area { + float: right; + margin-right: 0; + margin-left: 3.7%; +} +#supplementary.three .widget-area + .widget-area + .widget-area { + margin-left: 0; +} + +/* Site Generator Line */ +#site-generator .sep { + background-position: right center; +} + + +/* =Responsive Structure +----------------------------------------------- */ + +@media (max-width: 800px) { + /* Simplify the showcase template when small feature */ + section.featured-post .attachment-small-feature, + .one-column section.featured-post .attachment-small-feature { + float: right; + } + article.feature-image.small { + float: left; + } + article.feature-image.small .entry-summary p a { + right: 0; + } + .singular .entry-meta .edit-link a { + left: auto; + right: 0px; + } + /* Make sure we have room for our comment avatars */ + .commentlist > li.comment, + .commentlist .pingback { + margin-left: 0; + margin-right: 102px; + } + /* No need to float footer widgets at this size */ + #colophon #supplementary .widget-area { + margin-left: 0; + } + /* No need to float 404 widgets at this size */ + .error404 #main .widget { + margin-left: 0; + } +} +@media (max-width: 650px) { + /* @media (max-width: 650px) Reduce font-sizes for better readability on smaller devices */ + #site-title, + #site-description { + margin-left: 0; + } + /* Talking avatars take up too much room at this size */ + .commentlist > li.comment, + .commentlist > li.pingback { + margin-right: 0 !important; + } + .commentlist .children .avatar { + left: auto; + right: 2.2em; + } + /* Use the available space in the smaller comment form */ + #respond .comment-form-author .required, + #respond .comment-form-email .required { + left: auto; + right: 95%; + } + #content .gallery-columns-3 .gallery-item { + padding-right: 0; + padding-left:2%; + } +} +@media (max-width: 450px) { + #content .gallery-columns-2 .gallery-item { + padding-right:0; + padding-left:4%; + } +} + +/* =Print +----------------------------------------------- */ + +@media print { + #primary { + float: right; + } + /* Comments */ + .commentlist .avatar { + left: auto; + right: 2.2em; + } + .commentlist li.comment .comment-meta { + margin-left: 0; + margin-right: 50px; + } +} + +/* =IE7 +----------------------------------------------- */ + +#ie7 section.recent-posts { + margin-right: 0; + margin-left: 7.6%; +} diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/screenshot.png b/plugins/jetpack/modules/minileven/theme/pub/minileven/screenshot.png Binary files differnew file mode 100644 index 00000000..348e94c0 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/screenshot.png diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/searchform.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/searchform.php new file mode 100644 index 00000000..b4fd79aa --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/searchform.php @@ -0,0 +1,12 @@ +<?php +/** + * The template for displaying search forms in Minileven + * + * @package Minileven + */ +?> + <form method="get" id="searchform" action="<?php echo esc_url( home_url( '/' ) ); ?>"> + <label for="s" class="assistive-text"><?php _e( 'Search', 'jetpack' ); ?></label> + <input type="text" class="field" name="s" id="s" placeholder="<?php esc_attr_e( 'Search', 'jetpack' ); ?>" /> + <input type="submit" class="submit" name="submit" id="searchsubmit" value="<?php esc_attr_e( 'Search', 'jetpack' ); ?>" /> + </form> diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/sidebar.php b/plugins/jetpack/modules/minileven/theme/pub/minileven/sidebar.php new file mode 100644 index 00000000..5d1a4a06 --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/sidebar.php @@ -0,0 +1,12 @@ +<?php +/** + * The Sidebar containing the main widget area. + * + * @package Minileven + */ +?> + <?php if ( is_active_sidebar( 'sidebar-1' ) ) : ?> + <div id="secondary" class="widget-area" role="complementary"> + <?php dynamic_sidebar( 'sidebar-1' ); ?> + </div><!-- #secondary .widget-area --> + <?php endif; ?>
\ No newline at end of file diff --git a/plugins/jetpack/modules/minileven/theme/pub/minileven/style.css b/plugins/jetpack/modules/minileven/theme/pub/minileven/style.css new file mode 100644 index 00000000..692a8f4f --- /dev/null +++ b/plugins/jetpack/modules/minileven/theme/pub/minileven/style.css @@ -0,0 +1,1437 @@ +/* +Theme Name: Minileven +Theme URI: http://automattic.com +Author: Automattic +Author URI: http://automattic.com +Description: The Minileven theme is a clean, lightweight mobile experience for your blog based on Twenty Eleven. +Version: 2.0-wpcom +License: GNU General Public License +License URI: license.txt +Tags: dark, light, white, black, gray, one-column, flexible-width, responsive-width, custom-background, custom-header, custom-menu, full-width-template, infinite-scroll, microformats, post-formats, rtl-language-support, sticky-post, theme-options, translation-ready, blog, bright, clean, contemporary, elegant, minimal, modern, photography, simple, tumblelog +*/ + + +/* =Reset default browser CSS. Based on work by Eric Meyer: http://meyerweb.com/eric/tools/css/reset/index.html +-------------------------------------------------------------- */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + border: 0; + font-family: inherit; + font-size: 100%; + font-style: inherit; + font-weight: inherit; + margin: 0; + outline: 0; + padding: 0; + vertical-align: baseline; +} +:focus {/* remember to define focus styles! */ + outline: 0; +} +body { + background: #fff; + line-height: 1; + -webkit-overflow-scrolling: touch; +} +ol, ul { + list-style: none; +} +a img { + border: 0; +} +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + + +/* =Global +----------------------------------------------- */ + +body, input, textarea { + color: #373737; + font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif; + line-height: 1.625; + word-wrap: break-word; +} +body { + background: #eee; +} +#page { + background: #fff; +} + +/* Headings */ +h1,h2,h3,h4,h5,h6 { + clear: both; +} +hr { + background-color: #ccc; + border: 0; + height: 1px; + margin-bottom: 1.625em; +} + +/* Text elements */ +p { + margin-bottom: 1.0em; +} +ul, ol { + margin: 0 0 1.625em 2.5em; +} +ul { + list-style: square; +} +ol { + list-style-type: decimal; +} +ul ul, ol ol, ul ol, ol ul { + margin-bottom: 0; +} +dl { + margin: 0 1.625em; +} +dt { + font-weight: bold; +} +dd { + margin-bottom: 1.625em; +} +strong { + font-weight: bold; +} +cite, em, i { + font-style: italic; +} +blockquote { + font-family: Georgia, "Bitstream Charter", serif; + font-style: italic; + font-weight: normal; + margin: 0; +} +blockquote em, blockquote i, blockquote cite { + font-style: normal; +} +blockquote cite { + color: #666; + font-size: 0.800em; + font-weight: 300; + letter-spacing: 0.05em; + text-transform: uppercase; +} +pre { + background: #f4f4f4; + font: 1em "Courier 10 Pitch", Courier, monospace; + line-height: 1.5; + margin-bottom: 1.625em; + overflow: auto; + padding: 0.75em 1.625em; +} +code, kbd { + font: 1em Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; +} +abbr, acronym, dfn { + border-bottom: 1px dotted #666; + cursor: help; +} +address { + display: block; + margin: 0 0 1.625em; +} +ins { + background: #fff9c0; + text-decoration: none; +} +sup, +sub { + font-size: 0.667em; + height: 0; + line-height: 1; + position: relative; + vertical-align: baseline; +} +sup { + bottom: 1ex; +} +sub { + top: .5ex; +} + +/* Forms */ +input[type=text], +input[type=email], +input[type=password], +textarea { + background: #fafafa; + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); + border: 1px solid #ddd; + color: #888; +} +input[type=text]:focus, +input[type=email]:focus, +textarea:focus { + color: #373737; +} +textarea { + padding-left: 3px; + width: 98%; +} +input[type=text], +input[type=email] { + padding: 3px; +} +input#s { + border-radius: 2px; + height: 1.692em; + line-height: 1.2em; + padding: 0.4em 0.6em; +} +input#searchsubmit { + display: none; +} + +/* Links */ +a { + color: #00a4bc; + text-decoration: none; +} +/* Assistive text */ +.assistive-text { + clip: rect(1px, 1px, 1px, 1px); + position: absolute !important; + visibility: hidden; +} + + +/* =Structure +----------------------------------------------- */ + +#page { + margin: 0 auto; + padding: 2.5%; +} + +#primary, +#secondary { + margin: 0 auto; + width: auto; +} + +/* Alignment */ +.aligncenter, +.alignleft, +.alignright { + clear: both; + display: block; + margin-left: auto; + margin-right: auto; +} + +/* Make sure embeds and iframes scale on smaller screens */ +embed, +iframe, +object { + height: auto; + width: auto; +} + + +/* =Header +----------------------------------------------- */ + +#branding hgroup { + margin-bottom: 1.3em; +} +#site-title, +#site-description { + clear: none; +} +#site-title a { + color: #111; + font-size: 1.846em; + font-weight: bold; + line-height: 1.3em; +} +#site-description { + color: #7a7a7a; + font-size: 0.923em; +} + +/* Header Image */ +#header-img { + text-align: center; +} +header img { + max-width: 100%; + height: auto; +} + + +/* =Navigation and Search Form +----------------------------------------------- */ + +.menu-search { + background: #dcdcdc; + background: -moz-linear-gradient(#dcdcdc, #c8c8c8); + background: -o-linear-gradient(#dcdcdc, #c8c8c8); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dcdcdc), to(#c8c8c8)); /* older webkit syntax */ + background: -webkit-linear-gradient(#dcdcdc, #c8c8c8); + clear: both; + margin-bottom: 1.3em; + width: 100%; +} +.menu-search:after { + clear: both; + content: ""; + display: block; +} + +/* Small menu */ +#access { + float: left; + width: 55%; +} +.search-form { + float: right; + text-align: right; + width: 43%; +} +.menu-toggle { + cursor: pointer; +} +#access .toggled-on { + background-color: #F1F1F1; + background-image: -ms-linear-gradient(top,#f9f9f9,#ececec); + background-image: -moz-linear-gradient(top,#f9f9f9,#ececec); + background-image: -o-linear-gradient(top,#f9f9f9,#ececec); + background-image: -webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#ececec)); + background-image: -webkit-linear-gradient(top,#f9f9f9,#ececec); + background-image: linear-gradient(top,#f9f9f9,#ececec); + border: 1px solid #e9e9e9; + border-bottom-width: 0; + box-shadow: 0 1px 0 #fff; +} +.main-small-navigation { + position: relative; + z-index: 99999; +} +.main-small-navigation .menu { + background: #f9f9f9; + border: 1px solid #e9e9e9; + position: absolute; + width: 100%; +} +.main-small-navigation ul li { + border-bottom: 1px solid #e9e9e9; + padding: 0.5em 0.8em; +} +.main-small-navigation ul li:last-of-type { + border: none; +} +.main-small-navigation ul ul li, +.main-small-navigation ul ul ul li { + border: none; + padding-bottom: 0; +} +.main-small-navigation a { + color: #373737; + display: block; + font-size: 0.923em; + font-weight: bold; +} +.main-small-navigation ul { + list-style: none; + margin: 0; + padding: 0.5em 0; +} +.main-small-navigation .sub-menu { + margin: 0 0 0 15px; +} +#access .menu-label { + clear: none; + font-size: 1.077em; + font-weight: bold; + line-height: 2.5em; + padding-left: 0.8em; + text-shadow: 0 1px 0 #fff; +} +#access .menu-label:after { + color: #999; + content: "+"; + cursor: pointer; + font-size: 1em; + font-weight: bold; + margin-left: 0.5em; + width: 18px; + height: 18px; + text-shadow: 0 1px 0 #fff; +} +#access .toggled-on .menu-label:after { + content: "-"; +} +.main-small-navigation .menu { + display: none; +} +.search-form #s { + background: #eee; + border: 1px solid #ddd; + border-radius: 0; + color: #888; + font-size: 0.923em; + margin-right: 1%; + text-shadow: 0 1px 0 #fff; + padding: 0.6em; + width: 80%; +} + + +/* =Content +----------------------------------------------- */ + +.page-title { + color: #666; + font-size: 0.769em; + font-weight: 500; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} +.page-title a { + font-size: 0.923em; + font-weight: bold; + letter-spacing: 0; + text-transform: none; +} +.hentry, +.no-results { + clear: both; + margin: 0 0 1.5em; + padding: 0 0 2em; + position: relative; +} +.hentry { + border-bottom: 1px solid #ddd; +} +.hentry:last-child, +.no-results, +body.singular .hentry { + border-bottom: none; +} +.blog .sticky .entry-header .entry-meta { + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); + position: absolute !important; +} +.entry-title { + clear: both; + color: #222; + font-size: 1.615em; + font-weight: bold; + line-height: 1.5em; + padding-bottom: .3em; + word-wrap: break-word; +} +.featured-post .entry-title { + font-size: 1.077em; +} +.entry-title, +.entry-title a { + color: #222; + text-decoration: none; +} +.entry-meta { + color: #666; + clear: both; + font-size: 0.923em; + line-height: 1.385em; + overflow: hidden; + padding: 0 0 0.6em 0; +} +.entry-meta a { + font-weight: bold; +} +.entry-content { + padding: 0; +} +.entry-content h1, +.entry-content h2, +.comment-content h1, +.comment-content h2 { + color: #000; + font-weight: bold; + margin: 0 0 .8125em; +} +.entry-content h3, +.comment-content h3 { + font-size: 0.769em; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} +.entry-content table, +.comment-content table { + border-bottom: 1px solid #ddd; + margin: 0 0 1.625em; + width: 100%; +} +.entry-content th, +.comment-content th { + color: #666; + font-size: 0.769em; + font-weight: 500; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} +.entry-content td, +.comment-content td { + border-top: 1px solid #ddd; + padding: 0.600em 1em 0.600em 0; +} +.entry-content #s { + width: 75%; +} +.comment-content ul, +.comment-content ol { + margin-bottom: 1.625em; +} +.comment-content ul ul, +.comment-content ol ol, +.comment-content ul ol, +.comment-content ol ul { + margin-bottom: 0; +} +dl.gallery-item { + margin: 0; +} +.page-link { + clear: both; + display: block; + margin: 0 0 1em; +} +.page-link a { + background: #eee; + color: #373737; + margin: 0; + padding: 0.154em 0.231em; + text-decoration: none; +} +.page-link span { + margin-right: 0.462em; +} +.entry-meta .entry-date, +.entry-meta .entry-gallery { + display: block; + float: left; +} +.entry-meta .comments-link { + display: block; + float: right; +} +.entry-meta .edit-link a { + margin-right: .5em; +} +.entry-meta .edit-link a, +.commentlist .edit-link a { + background: #eee; + -moz-border-radius: 3px; + border-radius: 3px; + color: #666; + float: right; + font-size: 1em; + font-weight: normal; + line-height: 1.5em; + text-decoration: none; + padding: 0 0.615em; +} +.entry-content .edit-link { + clear: both; + display: block; +} + +/* Images */ +.entry-content img, +.comment-content img, +.widget img { + display: block; + height: auto; + margin: 0 auto; + max-width: 100%; /* Fluid images for posts, comments, and widgets */ +} +#content .gallery-columns-3 .gallery-item img, +#content .gallery-columns-4 .gallery-item img, +#content .gallery-columns-2 .gallery-item img { + width: 100%; + height: auto; +} +img[class*="align"], +img[class*="wp-image-"], +img[class*="attachment-"] { + height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ +} +img.size-full, +img.size-large { + max-width: 100%; + width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */ + height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ +} +.entry-content img.wp-smiley { + border: none; + margin-bottom: 0; + margin-top: 0; + padding: 0; +} +img.alignleft, +img.alignright, +img.aligncenter { + margin-bottom: 1.625em; +} +p img, +.wp-caption { + margin-top: 0.4em; +} +.wp-caption { + max-width: 96%; +} +.wp-caption img { + display: block; + margin: 0 auto; + max-width: 98%; +} +.wp-caption .wp-caption-text, +.gallery-caption { + color: #666; + font-family: Georgia, serif; + font-size: 0.923em; +} +.wp-caption .wp-caption-text { + margin-bottom: 0.6em; + padding: 0.833em 0 0.417em 0; + position: relative; +} +#content .gallery { + margin: 0 auto 1.625em; +} +#content .gallery a img { + border: none; +} +#content .gallery-columns-3 .gallery-item { + width: 31%; + padding-right: 2%; +} +#content .gallery-columns-4 .gallery-item { + width: 23%; + padding-right: 2%; +} +#content .gallery-columns-2 .gallery-item { + width: 45%; + padding-right: 4%; +} + + +/* Make sure embeds and iframes fit their containers */ +embed, +iframe, +object { + max-width: 100%; +} + +/* Password Protected Posts */ +.post-password-required .entry-header .comments-link { + margin: 1.625em 0 0; +} +.post-password-required input[type=password] { + margin: 0.8125em 0; +} +.post-password-required input[type=password]:focus { + background: #f7f7f7; +} + + +/* +Post Formats Headings +To hide the headings, display: none the ".entry-header .entry-format" selector, +and remove the padding rules below. +*/ +.entry-header .entry-format { + color: #666; + font-size: 10px; + font-weight: 500; + letter-spacing: 0.1em; + line-height: 2.6em; + position: absolute; + text-transform: uppercase; + top: -5px; +} +.entry-header hgroup .entry-title { + padding-top: 0.8em; +} + +/* Singular content styles for Posts and Pages */ +.singular .hentry { + padding: 1.625em 0 0; + position: relative; +} +.page .hentry { + padding-bottom: .7em; +} +.singular .entry-meta .edit-link a { + bottom: auto; + left: 0; + position: absolute; + right: auto; + top: 40px; +} +.single-format-gallery .hentry { + margin-bottom: 0; +} +.singular #author-info { + margin: 2.2em -8.8% 0; + padding: 1.538em 8.8%; +} + + +/* =Gallery Posts +----------------------------------------------- */ + +#content .gallery { + margin-bottom: 0; +} +.format-gallery img { + margin: 0; +} +.format-gallery .gallery-large { + line-height: 1.2em; + margin: 0; + width: 100%; +} +.format-gallery .gallery-thumbs-2, + .format-gallery .gallery-thumbs-3 { + overflow: hidden; + width: 100%; +} +.format-gallery .gallery-thumbs-2 img { + margin-right: 0.3%; + max-width: 48%; +} +.format-gallery .gallery-thumbs-3 img { + float: left; + margin-left: 0.2%; + max-width: 33%; +} +.format-gallery .gallery-large, +.format-gallery .gallery-thumbs-2 img, + .format-gallery .gallery-thumbs-3 img { + display: inline-block; +} +.format-gallery .gallery-thumbs-3 .gallery-thumb-1 img { + margin: 0; +} +.gallery-info { + margin-top: 1.3em; +} + +/* =Quote Posts +----------------------------------------------- */ + +.format-quote blockquote { + color: #555; + font-size: 1.308em; + margin: 0; +} + +/* =error404 +----------------------------------------------- */ + +.error404 #main #searchform { + background: #f9f9f9; + border: 1px solid #ddd; + border-width: 1px 0; + margin: 0 -8.9% 1.625em; + overflow: hidden; + padding: 1.625em 8.9%; +} +.error404 #main #s { + width: 95%; +} +.error404 .widgettitle { + font-size: 0.769em; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} + + +/* =Attachments +----------------------------------------------- */ + +.image-attachment div.attachment { + background: #f9f9f9; + border: 1px solid #ddd; + border-width: 1px 0; + margin: 0 -8.9% 1.625em; + overflow: hidden; + padding: 1.625em 1.625em 0; + text-align: center; +} +.image-attachment div.attachment img { + display: block; + height: auto; + margin: 0 auto 1.625em; + max-width: 100%; +} +.image-attachment div.attachment a img { + border-color: #f9f9f9; +} +.image-attachment div.attachment a:focus img, +.image-attachment div.attachment a:hover img, +.image-attachment div.attachment a:active img { + border-color: #ddd; + background: #fff; +} +.image-attachment .entry-caption p { + font-size: 0.769em; + letter-spacing: 0.1em; + line-height: 2.6em; + margin: 0 0 2.6em; + text-transform: uppercase; +} + + +/* =Navigation +-------------------------------------------------------------- */ + +#content nav { + clear: both; +} +#nav-below, +#nav-single { + margin: 0 auto 0.6em; + overflow: hidden; + width: 100%; +} +.nav-previous { + float: left; + width: 48%; +} +.nav-next { + float: right; + width: 46%; +} +#nav-single { + display: block; + position: static; +} +#nav-single .nav-previous { + margin-left: 0; + width: 50%; +} +#nav-single .nav-next { + margin-right: 0; + width: 49%; +} +.nav-previous a, +.nav-next a { + background: #00a4bc; + color: #fff; + display: block; + font-size: 1.231em; + font-weight: bold; + padding: 1.2em 0; + text-align: center; + width: 100%; +} +#content nav .meta-nav { + font-weight: normal; +} +#jp-post-flair { + margin: 1em auto !important; +} + + + +/* =Widget Area & Widgets +----------------------------------------------- */ + +.widget-area { + background: #f9f9f9; + border-top: 1px solid #ddd; + color: #666; + font-size: 0.923em; + padding: .6em 0.8em; +} +.widget { + border-bottom: 1px solid #ddd; + clear: both; + margin: 0; + padding: 1.625em 0; +} +.widget:last-of-type { + border: 0; +} +.widget-title { + color: #666; + font-size: 0.833em; + letter-spacing: 0.1em; + line-height: 2em; + margin-bottom: 0.5em; + text-transform: uppercase; +} +.widget-title a { + color: #666; +} +.widget ul { + margin-bottom: 0; +} +.widget ul ul { + margin-left: 1.5em; +} +.widget ul li { + color: #777; +} +.widget a { + font-weight: bold; + text-decoration: none; +} +.widget a:hover, +.widget a:focus, +.widget a:active { + text-decoration: underline; +} + +/* Search Widget */ +.widget_search #s { + width: 77%; +} +.widget_search #searchsubmit { + background: #ddd; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + -moz-box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + color: #888; + line-height: 2.083em; + position: relative; + top: -2px; +} +.widget_search #searchsubmit:active { + background: #00a4bc; + border-color: #0861a5; + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + color: #bfddf3; +} + +/* Twitter */ +.tweets { + margin-left: 0; +} +.widget_twitter li { + list-style-type: none; + margin-bottom: 1.167em; +} +.widget_twitter .timesince { + font-size: 0.917em; + font-weight: normal; + text-align: right; +} + +/* RSS-Related Widgets */ +.widget_rss img { + display: inline-block; + margin: 0; + vertical-align: middle; +} +.widget_rss .rss-date { + font-size: 90%; +} +.widget_rss_links img, +.widget_rss_links a:hover img, +.widget_rss_links a:focus img, +.widget_rss_links a:active img { + background: transparent; + border: none; + padding: 0; +} + +/* Calendar Widget */ +.widget_calendar #wp-calendar { + color: #555; + width: 95%; + text-align: center; +} +.widget_calendar #wp-calendar caption, +.widget_calendar #wp-calendar td, +.widget_calendar #wp-calendar th { + text-align: center; +} +.widget_calendar #wp-calendar caption { + font-size: 11px; + font-weight: 500; + padding: 5px 0 3px 0; + text-transform: uppercase; +} +.widget_calendar #wp-calendar th { + background: #f4f4f4; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + font-weight: bold; +} +.widget_calendar #wp-calendar tfoot td { + background: #f4f4f4; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +/* Recent Comments */ +.widget_recent_comments td.recentcommentstexttop, +.widget_recent_comments td.recentcommentstextend { + vertical-align: top; +} + +/* Authors Widget */ +.widget_authors ul { + margin-left: 0; +} +.widget_authors li { + background: none !important; + overflow: hidden; +} +.widget_authors ul ul li { + overflow: hidden; +} +.widget_authors img { + float: left; + padding-right: 0.833em; + vertical-align: text-top; +} + +/*Flickr Widget */ +.widget_flickr #flickr_badge_wrapper { + background-color: transparent; + border: none; +} +#flickr_badge_uber_wrapper a:hover, +#flickr_badge_uber_wrapper a:link, +#flickr_badge_uber_wrapper a:active, +#flickr_badge_uber_wrapper a:visited { + color: #00a4bc !important; +} + + +/* =Comments +----------------------------------------------- */ + +#comments { + margin-top: 1.5em; +} +#comments-title { + color: #000; + font-size: 1.154em; + font-weight: bold; + line-height: 1em; + padding: 1em 0; +} +#comment-nav-below { + overflow: hidden; +} +.nopassword, +.nocomments { + color: #aaa; + font-size: 1.846em; + font-weight: 100; + margin: 2em 0; + text-align: center; +} +.commentlist { + list-style: none; + margin: 0 auto; + width: 100%; +} +.commentlist > li.comment { + background: #f6f6f6; + border: 1px solid #ddd; + border-width: 1px 0; + margin: 0 -2.5% 0.5em; + padding: .8em; + position: relative; +} +.commentlist > li.comment, +.commentlist .pingback { + width: auto; +} + +/* Reblogs */ +.commentlist > li.reblog { + border: 1px solid #eee; + -moz-border-radius: 3px; + border-radius: 3px; + margin: 0 0 1.625em; + padding: 1.625em; + position: relative; +} +.commentlist .reblog .comment-meta { + display: none; +} +.commentlist .reblog p:first-child { + color: #999; + font-size: 1em; +} +.commentlist .reblog p:first-child a { + font-weight: bold; +} +.commentlist .pingback, +.commentlist .pingback p { + margin: 0 0 .8em; +} +.commentlist .children { + list-style: none; + margin: 0; +} +.commentlist .children li.comment { + background: #fff; + border-left: 1px solid #ddd; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; + margin: 1.625em 0 0; + padding: 1.625em; + position: relative; +} +.commentlist .children li.comment .fn { + display: block; +} +.comment-meta .fn { + font-style: normal; +} +.comment-meta, +.comment-content { + margin-left: 4em; +} +.comment-meta { + color: #666; + font-size: 0.923em; +} +.comment-content { + padding-top: 0.2em; +} +.commentlist .children li.comment .comment-meta { + line-height: 1.625em; + margin-left: 3.462em; +} +.commentlist .children li.comment .comment-content { + margin: 1.625em 0 0; +} +.comment-meta a { + font-weight: bold; +} +.comment-meta a:focus, +.comment-meta a:active, +.comment-meta a:hover { +} +.commentlist .avatar { + background: transparent; + -moz-border-radius: 3px; + border-radius: 3px; + display: block; + padding: 0; + position: absolute; + left: 13px; + top: 13px; +} +.commentlist .children .avatar { + background: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + left: 2.2em; + padding: 0; + position: absolute; + top: 2.2em; +} +a.comment-reply-link { + background: #eee; + -moz-border-radius: 3px; + border-radius: 3px; + color: #666; + display: inline-block; + font-size: 0.923em; + padding: 0 0.615em; + text-decoration: none; +} +a.comment-reply-link:hover, +a.comment-reply-link:focus, +a.comment-reply-link:active { + background: #888; + color: #fff; +} +a.comment-reply-link > span { + display: inline-block; + position: relative; + top: -1px; +} + +/* Post author highlighting */ +.commentlist > li.bypostauthor { + background: #ddd; + border-color: #d3d3d3; +} +.commentlist > li.bypostauthor .comment-meta { + color: #575757; +} +.commentlist > li.bypostauthor .comment-meta a:focus, +.commentlist > li.bypostauthor .comment-meta a:active, +.commentlist > li.bypostauthor .comment-meta a:hover { +} +/* Post Author threaded comments */ +.commentlist .children > li.bypostauthor { + background: #ddd; + border-color: #d3d3d3; +} + +/* Comment Form */ +#respond { + margin: 0 auto 1.625em; + width: auto; +} +#respond input[type="text"], +#respond textarea { + background: #fff; + position: relative; + padding: 0.615em; +} +#respond .comment-form-author, +#respond .comment-form-email, +#respond .comment-form-url, +#respond .comment-form-comment { + position: relative; +} +#respond .comment-form-author label, +#respond .comment-form-email label, +#respond .comment-form-url label, +#respond .comment-form-comment label { + color: #555; + display: inline-block; + font-size: 1.077em; + font-weight: bold; + padding: 0.154em 0 0; + position: relative; +} +#respond textarea { + resize: vertical; + width: 95%; +} +#respond .comment-form-author .required, +#respond .comment-form-email .required { + color: #bd3500; + font-size: 1.692em; + font-weight: bold; + left: 95%; + position: absolute; + top: 45px; + z-index: 1; +} +#respond .comment-notes, +#respond .logged-in-as { + font-size: 0.8em; + color: #666; +} +#respond p { + margin: 0.769em 0 0; +} +#respond .form-submit { + margin: 0; +} +#respond input#submit { + background: #00a4bc; + border: none; + border-radius: 3px; + box-shadow: 0px 1px 2px rgba(0,0,0,0.3); + color: #fff; + cursor: pointer; + font-size: 1em; + font-weight: bold; + margin: 0.462em 0; + padding: 1em; + left: 30px; + text-shadow: 0 -1px 0 rgba(0,0,0,0.3); +} +#respond input#submit:active { + background: #00879c; + color: #fff; +} +#respond #cancel-comment-reply-link { + color: #666; + text-decoration: none; +} +#respond .logged-in-as a:hover, +#respond #cancel-comment-reply-link:hover { + text-decoration: underline; +} +.commentlist #respond { + margin: 1.625em 0 0; + width: auto; +} +#respond .comment-subscription-form { + margin: 6px 0; +} +#reply-title { + color: #373737; + font-size: 1.5em; + font-weight: bold; + line-height: 0.733em; +} +.comment #reply-title { + margin-top: 1em; +} +#cancel-comment-reply-link { + color: #888; + display: block; + font-size: 0.923em; + font-weight: normal; + line-height: 2.2em; + text-decoration: none; + text-transform: uppercase; +} +#cancel-comment-reply-link:focus, +#cancel-comment-reply-link:active, +#cancel-comment-reply-link:hover { + color: #ff4b33; +} +#respond label { + line-height: 2.2em; +} +#respond input[type=text] { + display: block; + height: 1.846em; + width: 95%; +} +#respond p { + font-size: 0.923em; +} +p.comment-form-comment { + margin: 0; +} +.form-allowed-tags { + display: none; +} + + +/* =Footer +----------------------------------------------- */ + +#colophon { + background: #434343; + clear: both; + margin-bottom: -2em; + padding-bottom: 1em; +} + +/* Site Generator Line */ +#site-generator { + background: #434343; + border-top: 1px solid #ddd; + color: #fff; + font-size: 0.923em; + line-height: 2.2em; + padding: 2.2em 0.5em; + text-align: center; +} +#site-generator a { + color: #00a4bc; + font-weight: bold; +} +#site-generator .sep { + color: transparent; + display: inline-block; + height: 16px; + line-height: 1.231em; + margin: 0 0.538em; + text-indent: 40px; /* Push the separator just out of the way */ + width: 3.077em; +} + + +/* =WP.com +----------------------------------------------- */ + +.video-player { + max-width: 100% !important; +} +.videopress-placeholder, +.video-player img { + max-width: 100% !important; + height: auto !important; +} +.syntaxhighlighter { + overflow: auto; +} +.single #content #wp-likebox, +.page #content #wp-likebox { + display: block; +} +#wpl-mustlogin { +width: 240px !important; +margin-left: -60px !important; +} + +/* WP.com comment form */ +#comments #respond { + max-width: 75%; + margin: 0 auto 15px; +} +.content #comments #respond, +#comments .commentlist #respond { + max-width: 100%; +} +#respond textarea { + text-indent: 0; +} +.singular #content .wpl-likebox { + width: 100%; +} +#comments #respond { + max-width: 100%; +} +#wpstats { + display: block; + margin: -1.8em auto 0; +} +#wpstats2 { + display: none; +} + +/* Adjust the width of PollDaddy polls */ +.PDS_Poll .pds-box { + width: 99% !important; +} + + +/* Remove margins and padding on outer containers for super-tiny screens */ +@media only screen and (min-device-width: 100px) and (max-device-width: 300px) { + #wrapper { + margin: 0; + padding: 0; + } + #access { + padding: 0.385em 0; + } + #page, + .widget-area, + #main { + width: 100%; + margin: 0; + } + .widget-area { + padding: 0.417em; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/mobile-push.php b/plugins/jetpack/modules/mobile-push.php new file mode 100644 index 00000000..8981348b --- /dev/null +++ b/plugins/jetpack/modules/mobile-push.php @@ -0,0 +1,11 @@ +<?php +/** + * Module Name: Mobile Push Notifications + * Module Description: Receive notifications on your Apple device. + * Sort Order: 100 + * First Introduced: 1.9 + */ + +Jetpack_Sync::sync_comments( __FILE__, array( + 'comment_stati' => array( 'approved', 'unapproved' ), +) ); diff --git a/plugins/jetpack/modules/module-extras.php b/plugins/jetpack/modules/module-extras.php new file mode 100644 index 00000000..5013f7cc --- /dev/null +++ b/plugins/jetpack/modules/module-extras.php @@ -0,0 +1,58 @@ +<?php +/* + * Load module code that is needed even when a module isn't active. + * For example, if a module shouldn't be activatable unless certain conditions are met, the code belongs in this file. + */ + +/** + * INFINITE SCROLL + */ + +/** + * Load theme's infinite scroll annotation file, if present in the IS plugin. + * The `setup_theme` action is used because the annotation files should be using `after_setup_theme` to register support for IS. + * + * As released in Jetpack 2.0, a child theme's parent wasn't checked for in the plugin's bundled support, hence the convoluted way the parent is checked for now. + * + * @uses is_admin, wp_get_theme, get_theme, get_current_theme, apply_filters + * @action setup_theme + * @return null + */ +function jetpack_load_infinite_scroll_annotation() { + if ( is_admin() && isset( $_GET['page'] ) && 'jetpack' == $_GET['page'] ) { + $theme = function_exists( 'wp_get_theme' ) ? wp_get_theme() : get_theme( get_current_theme() ); + + if ( ! is_a( $theme, 'WP_Theme' ) && ! is_array( $theme ) ) + return; + + $customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/infinite-scroll/themes/{$theme['Stylesheet']}.php", $theme['Stylesheet'] ); + + if ( is_readable( $customization_file ) ) { + require_once( $customization_file ); + } + elseif ( ! empty( $theme['Template'] ) ) { + $customization_file = dirname( __FILE__ ) . "/infinite-scroll/themes/{$theme['Template']}.php"; + + if ( is_readable( $customization_file ) ) + require_once( $customization_file ); + } + } +} +add_action( 'setup_theme', 'jetpack_load_infinite_scroll_annotation' ); + +/** + * Prevent IS from being activated if theme doesn't support it + * + * @param bool $can_activate + * @filter jetpack_can_activate_infinite-scroll + * @return bool + */ +function jetpack_can_activate_infinite_scroll( $can_activate ) { + return (bool) current_theme_supports( 'infinite-scroll' ); +} +add_filter( 'jetpack_can_activate_infinite-scroll', 'jetpack_can_activate_infinite_scroll' ); + +// Happy Holidays! +require_once( dirname( __FILE__ ) . '/holiday-snow.php' ); + +require_once( dirname( __FILE__ ) . '/featured-content/featured-content.php' ); diff --git a/plugins/jetpack/modules/module-info.php b/plugins/jetpack/modules/module-info.php index d1038208..218a9e80 100644 --- a/plugins/jetpack/modules/module-info.php +++ b/plugins/jetpack/modules/module-info.php @@ -42,7 +42,7 @@ function vaultpress_jetpack_load_more_link() { $vaultpress_url = 'http://vaultpress.com/jetpack/'; } - echo '<a class="button more-info-link" href="' . $vaultpress_url . '">' . __( "Learn More", 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="' . $vaultpress_url . '">' . __( "Learn More", 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_vaultpress', 'vaultpress_jetpack_load_more_link' ); @@ -78,7 +78,7 @@ function grofiles_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_gravatar-hovercards', 'grofiles_more_info_connected' ); function grofiles_load_more_link() { - echo '<a class="button more-info-link" href="http://blog.gravatar.com/2010/10/06/gravatar-hovercards-on-wordpress-com/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://blog.gravatar.com/2010/10/06/gravatar-hovercards-on-wordpress-com/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_gravatar-hovercards', 'grofiles_load_more_link' ); @@ -112,11 +112,8 @@ function jetpack_shortcodes_more_info_connected() { ?> 'audio' => 'http://support.wordpress.com/audio/', 'blip.tv' => 'http://support.wordpress.com/videos/bliptv/', 'dailymotion' => 'http://support.wordpress.com/videos/dailymotion/', - 'digg' => 'http://support.wordpress.com/digg/', 'flickr' => 'http://support.wordpress.com/videos/flickr-video/', - 'googlevideo' => 'http://support.wordpress.com/videos/google-video/', 'scribd' => 'http://support.wordpress.com/scribd/', - 'slide' => 'http://support.wordpress.com/slideshows/slide/', 'slideshare' => 'http://support.wordpress.com/slideshows/slideshare/', 'soundcloud' => 'http://support.wordpress.com/audio/soundcloud-audio-player/', 'vimeo' => 'http://support.wordpress.com/videos/vimeo/', @@ -138,7 +135,7 @@ function jetpack_shortcodes_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_shortcodes', 'jetpack_shortcodes_more_info_connected' ); function jetpack_shortcodes_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://en.support.wordpress.com/shortcodes/">' . esc_html__( 'Learn More' , 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.support.wordpress.com/shortcodes/">' . esc_html__( 'Learn More' , 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_shortcodes', 'jetpack_shortcodes_load_more_link' ); @@ -173,7 +170,7 @@ function wpme_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_shortlinks', 'wpme_more_info_connected' ); function wpme_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://wp.me/sf2B5-shorten">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://wp.me/sf2B5-shorten">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_shortlinks', 'wpme_load_more_link' ); @@ -207,10 +204,73 @@ function stats_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_stats', 'stats_more_info_connected' ); function stats_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://en.support.wordpress.com/stats/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.support.wordpress.com/stats/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_stats', 'stats_load_more_link' ); +// Publicize +function publicize_more_info() { ?> + <div class="jp-info-img"> + <a href="http://en.support.wordpress.com/publicize/"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/publicize.png' ) ?>" alt="<?php esc_attr_e( 'Publicize', 'jetpack' ) ?>" width="328" height="123" /> + </a> + </div> + + <h4><?php esc_html_e( 'Publicize' , 'jetpack' ); ?></h4> + <p><?php esc_html_e( 'Publicize allows you to connect your blog to popular social networking sites and automatically share new posts with your friends. You can make a connection for just yourself or for all users on your blog.', 'jetpack' ) ?></p> + <p><?php esc_html_e( 'Publicize allows you to share your posts on Facebook, Twitter, Tumblr, Yahoo!, and Linkedin.', 'jetpack' ); ?></p> + +<?php if ( 'jetpack_module_more_info_connected_publicize' == current_filter() ) : ?> + + <p><?php printf( __( 'Manage your <a href="%s">Publicize settings</a>.', 'jetpack' ), menu_page_url( 'sharing', false ) ); ?> + +<?php endif; ?> + + <p>→ <a href="http://jetpack.me/support/publicize/"><?php esc_html_e( 'More information on using Publicize.', 'jetpack' ); ?></a></p> +<?php +} + +add_action( 'jetpack_module_more_info_publicize', 'publicize_more_info' ); +add_action( 'jetpack_module_more_info_connected_publicize', 'publicize_more_info' ); + +function publicize_load_more_link( $description ) { + echo '<a class="button-secondary more-info-link" href="http://jetpack.me/support/publicize/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; +} +add_filter( 'jetpack_learn_more_button_publicize', 'publicize_load_more_link' ); + +// Notifications +function notes_more_info() { ?> + <div class="jp-info-img"> + <a href="http://support.wordpress.com/notifications/"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/notes.png' ) ?>" alt="<?php esc_attr_e( 'Notifications', 'jetpack' ) ?>" width="300" height="150" /> + </a> + </div> + + <h4><?php esc_html_e( 'Notifications' , 'jetpack' ); ?></h4> + <p><?php esc_html_e( 'Keep up with the latest happenings on all your WordPress sites and interact with other WordPress.com users.', 'jetpack' ) ?></p> +<?php +} +add_action( 'jetpack_module_more_info_notes', 'notes_more_info' ); + +function notes_more_info_connected() { ?> + <div class="jp-info-img"> + <a href="http://support.wordpress.com/notifications/"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/notes.png' ) ?>" alt="<?php esc_attr_e( 'Notifications', 'jetpack' ) ?>" width="300" height="150" /> + </a> + </div> + + <h4><?php esc_html_e( 'Notifications' , 'jetpack' ); ?></h4> + <p><?php esc_html_e( 'Keep up with the latest happenings on all your WordPress sites and interact with other WordPress.com users.', 'jetpack' ) ?></p> + <p><?php printf( __( 'You can view your notifications in the Toolbar and <a href="%s">on WordPress.com</a>.', 'jetpack' ), 'http://wordpress.com/#!/notifications/' ); ?></p> +<?php +} +add_filter( 'jetpack_module_more_info_connected_notes', 'notes_more_info_connected' ); + +function notes_load_more_link( $description ) { + echo '<a class="button-secondary more-info-link" href="http://support.wordpress.com/notifications/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; +} +add_filter( 'jetpack_learn_more_button_notes', 'notes_load_more_link' ); + // LaTeX function latex_more_info() { ?> @@ -220,9 +280,9 @@ function latex_more_info() { ?> </a> </div> - <h4><img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -27%" /> Makes Beautiful Math</h4> - <p><?php printf( esc_html__( '%s is a powerful markup language for writing complex mathematical equations, formulas, etc.', 'jetpack' ), '<a href="http://www.latex-project.org/" target="_blank"><img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /></a>' ); ?></p> - <p><?php printf( esc_html__( 'Jetpack combines the power of %s and the simplicity of WordPress to give you the ultimate in math blogging platforms.', 'jetpack' ), '<img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" />' ); ?></p> + <h4><img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -27%" /> Makes Beautiful Math</h4> + <p><?php printf( esc_html__( '%s is a powerful markup language for writing complex mathematical equations, formulas, etc.', 'jetpack' ), '<a href="http://www.latex-project.org/" target="_blank"><img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /></a>' ); ?></p> + <p><?php printf( esc_html__( 'Jetpack combines the power of %s and the simplicity of WordPress to give you the ultimate in math blogging platforms.', 'jetpack' ), '<img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" />' ); ?></p> <p><?php esc_html_e( 'Wow, that sounds nerdy.', 'jetpack' ) ?></p> <?php } @@ -235,15 +295,15 @@ function latex_more_info_connected() { ?> </a> </div> - <h4><img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -27%;" /> Makes Beautiful Math</h4> - <p><?php printf( esc_html__( '%s is a powerful markup language for writing complex mathematical equations, formulas, etc.', 'jetpack' ), '<a href="http://www.latex-project.org/" target="_blank"><img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /></a>' ); ?></p> - <p><?php printf( __( 'Use <code>$latex your latex code here$</code> or <code>[latex]your latex code here[/latex]</code> to include %s in your posts and comments. There are <a href="%s" target="_blank">all sorts of options</a> available.', 'jetpack' ), '<img src="http://l.wordpress.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" />', 'http://support.wordpress.com/latex/' ); ?></p> + <h4><img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -27%;" /> Makes Beautiful Math</h4> + <p><?php printf( esc_html__( '%s is a powerful markup language for writing complex mathematical equations, formulas, etc.', 'jetpack' ), '<a href="http://www.latex-project.org/" target="_blank"><img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" /></a>' ); ?></p> + <p><?php printf( __( 'Use <code>$latex your latex code here$</code> or <code>[latex]your latex code here[/latex]</code> to include %s in your posts and comments. There are <a href="%s" target="_blank">all sorts of options</a> available.', 'jetpack' ), '<img src="//s0.wp.com/latex.php?latex=%5CLaTeX&bg=transparent&fg=000&s=-1" alt="LaTeX logo" title="LaTeX" style="vertical-align: -25%" />', 'http://support.wordpress.com/latex/' ); ?></p> <?php } add_action( 'jetpack_module_more_info_connected_latex', 'latex_more_info_connected' ); function latex_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://support.wordpress.com/latex/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://support.wordpress.com/latex/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_latex', 'latex_load_more_link' ); @@ -294,7 +354,7 @@ function sharedaddy_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_sharedaddy', 'sharedaddy_more_info_connected' ); function sharedaddy_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://support.wordpress.com/sharing/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://support.wordpress.com/sharing/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_sharedaddy', 'sharedaddy_load_more_link' ); @@ -316,7 +376,7 @@ function jpatd_more_info() { ?> add_action( 'jetpack_module_more_info_after-the-deadline', 'jpatd_more_info' ); function jpatd_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://en.support.wordpress.com/proofreading/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.support.wordpress.com/proofreading/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_after-the-deadline', 'jpatd_load_more_link' ); @@ -328,7 +388,7 @@ function jetpack_widgets_more_info() { ?> </div> <h4><?php esc_html_e( 'Extra Sidebar Widgets' , 'jetpack' ); ?></h4> - + <p><strong><?php esc_html_e( 'The RSS Links Widget ', 'jetpack' ); ?></strong> <?php esc_html_e( "allows you to add links to your blog’s post and comment RSS feeds in your sidebar. This makes it easy for your readers to stay updated when you post new content or receive new comments.", 'jetpack' ) ?></p> <p><strong><?php esc_html_e( 'The Twitter Widget ', 'jetpack' ); ?></strong> <?php esc_html_e( "shows your latest tweets within a sidebar on your theme. It’s an easy way to add more activity to your site. There are also a number of customization options.", 'jetpack' ) ?> <strong><?php esc_html_e( 'The Facebook Like Box Widget ', 'jetpack' ); ?></strong> <?php esc_html_e( "shows your Facebook Like Box within a sidebar on your theme. It’s a great way to let your readers show their support.", 'jetpack' ) ?> <strong><?php esc_html_e( 'The Image Widget ', 'jetpack' ); ?></strong><?php esc_html_e( "allows you to easily add images to widget areas in your theme. It’s an easy way to add more visual interest to your site.", 'jetpack' ) ?></p> @@ -354,7 +414,7 @@ function jetpack_widgets_more_info_connected() { ?> add_action( 'jetpack_module_more_info_connected_widgets', 'jetpack_widgets_more_info_connected' ); function jetpack_widgets_load_more_link( $description ) { - echo '<a class="button more-info-link" href="http://en.support.wordpress.com/widgets/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.support.wordpress.com/widgets/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_filter( 'jetpack_learn_more_button_widgets', 'jetpack_widgets_load_more_link' ); @@ -373,12 +433,13 @@ function jetpack_subscriptions_more_info() { ?> if ( 'jetpack_module_more_info_connected_subscriptions' == current_filter() ) printf( '<p>' . __( 'To use the Subscriptions widget, go to Appearance → <a href="%s">Widgets</a>. Drag the widget labeled “Blog Subscriptions (Jetpack)” into one of your sidebars and configure away.', 'jetpack' ) . '</p>', admin_url( 'widgets.php' ) ); + printf( '<p>' . __( 'You can also make changes to your Subscription settings at the bottom of the <a href="%s">Discussion Settings</a> page.', 'jetpack' ) . '</p>', admin_url( 'options-discussion.php#jetpack-subscriptions-settings' ) ); } add_action( 'jetpack_module_more_info_subscriptions', 'jetpack_subscriptions_more_info' ); add_action( 'jetpack_module_more_info_connected_subscriptions', 'jetpack_subscriptions_more_info' ); function jetpack_subscriptions_load_more_link() { - echo '<a class="button more-info-link" href="http://en.support.wordpress.com/following/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.support.wordpress.com/following/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_action( 'jetpack_learn_more_button_subscriptions', 'jetpack_subscriptions_load_more_link' ); @@ -396,13 +457,32 @@ add_action( 'jetpack_module_more_info_enhanced-distribution', 'jetpack_enhanced_ add_action( 'jetpack_module_more_info_connected_enhanced-distribution', 'jetpack_enhanced_distribution_more_info' ); function jetpack_enhanced_distribution_more_link() { - echo '<a class="button more-info-link" href="http://en.wordpress.com/firehose/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://en.wordpress.com/firehose/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; } add_action( 'jetpack_learn_more_button_enhanced-distribution', 'jetpack_enhanced_distribution_more_link' ); +// JSON API +function jetpack_json_api_more_info() { ?> + <h4><?php esc_html_e( 'JSON API' , 'jetpack' ); ?></h4> + + <p><?php esc_html_e( 'Jetpack will allow you to authorize applications and services to securely connect to your blog and allow them to use your content in new ways and offer you new functionality.', 'jetpack' ); ?> + + <p><?php _e( "Developers can use WordPress.com's <a href='http://developer.wordpress.com/docs/oauth2/'>OAuth2</a> authentication system and <a href='http://developer.wordpress.com/docs/api/'>WordPress.com REST API</a> to manage and access your site's content.", 'jetpack' ); ?></p> + +<?php +} + +add_action( 'jetpack_module_more_info_json-api', 'jetpack_json_api_more_info' ); +add_action( 'jetpack_module_more_info_connected_json-api', 'jetpack_json_api_more_info' ); + +function jetpack_json_api_more_link() { + echo '<a class="button-secondary more-info-link" href="http://jetpack.me/support/json-api/">' . esc_html__( 'Learn More', 'jetpack' ) . '</a>'; +} +add_action( 'jetpack_learn_more_button_json-api', 'jetpack_json_api_more_link' ); + // Contact Form: START function jetpack_contact_form_learn_more_button() { - echo '<a class="button more-info-link" href="http://support.wordpress.com/contact-form/">' . __( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="http://support.wordpress.com/contact-form/">' . __( 'Learn More', 'jetpack' ) . '</a>'; } function jetpack_contact_form_more_info() { @@ -429,7 +509,7 @@ add_action( 'jetpack_module_more_info_connected_contact-form', 'jetpack_contact_ // Jetpack Comments: START function jetpack_comments_learn_more_button() { - echo '<a class="button more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; } function jetpack_comments_more_info() { @@ -460,13 +540,13 @@ add_action( 'jetpack_module_more_info_connected_comments', 'jetpack_comments_mor // Gallery Carousel: START function jetpack_carousel_learn_more_button() { - echo '<a class="button more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; } function jetpack_carousel_more_info() { ?> <div class="jp-info-img"> - <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/screenshot-6.png' ) ?>" alt="<?php esc_attr_e( 'Gallery Carousel Screenshot', 'jetpack' ) ?>" width="300" height="188" /> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/carousel.png' ) ?>" alt="<?php esc_attr_e( 'Gallery Carousel Screenshot', 'jetpack' ) ?>" width="300" height="188" /> </div> <h4><?php esc_html_e( 'Carousel', 'jetpack' ); ?></h4> @@ -479,3 +559,220 @@ add_action( 'jetpack_learn_more_button_carousel', 'jetpack_carousel_learn_more_b add_action( 'jetpack_module_more_info_carousel', 'jetpack_carousel_more_info' ); add_action( 'jetpack_module_more_info_connected_carousel', 'jetpack_carousel_more_info' ); // Gallery Carousel: STOP + +// Custom CSS: START +function jetpack_custom_css_more_info() { + ?> + <div class="jp-info-img"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/custom-css.png' ) ?>" alt="<?php esc_attr_e( 'Custom CSS', 'jetpack' ) ?>" width="300" height="150" /> + </div> + + <h4><?php esc_html_e( 'Custom CSS', 'jetpack' ); ?></h4> + <p><?php esc_html_e( "The Custom CSS editor gives you the ability to add to or replace your theme's CSS, all while supplying syntax coloring, auto-indentation, and immediate feedback on the validity of the CSS you're writing.", 'jetpack' ); ?></p> + <p><?php printf( __( 'To use the CSS editor, go to Appearance → <a href="%s">Edit CSS</a>.', 'jetpack' ), admin_url( 'themes.php?page=editcss' ) ); ?></p> + + <?php +} + +function jetpack_custom_css_more_button() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} + +add_action( 'jetpack_learn_more_button_custom-css', 'jetpack_custom_css_more_button' ); +add_action( 'jetpack_module_more_info_custom-css', 'jetpack_custom_css_more_info' ); +// Custom CSS: STOP + +// Minileven: START +function jetpack_minileven_more_info() { + ?> + <div class="jp-info-img"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/mobile-theme.png' ) ?>" alt="<?php esc_attr_e( 'Mobile Theme', 'jetpack' ) ?>" width="300" height="150" /> + </div> + + <h4><?php esc_html_e( 'Mobile Theme', 'jetpack' ); ?></h4> + <p><?php esc_html_e( "There's a good chance that visitors to your site will be using a smartphone, and it's important to provide them with a great reading experience while on the small screen.", 'jetpack' ); ?></p> + <p><?php esc_html_e( "Jetpack's mobile theme is optimized for small screens. It uses the header image, background, and widgets from your current theme for a great custom look. Post format support is included, so your photos and galleries will look fantastic on a smartphone.", 'jetpack' ); ?></p> + <p><?php esc_html_e( 'Visitors on iPhone, Android, Windows Phone, and other mobile devices will automatically see the mobile theme, with the option to view the full site. You can enable or disable the mobile theme by clicking the "Activate" or "Deactive" button above.', 'jetpack' ); ?></p> + <?php +} + +function jetpack_minileven_more_button() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} + +add_action( 'jetpack_learn_more_button_minileven', 'jetpack_minileven_more_button' ); +add_action( 'jetpack_module_more_info_minileven', 'jetpack_minileven_more_info' ); +// Minileven: STOP + + +// Mobile Push Notifications: START +function jetpack_mobile_push_notifications_more_info() { ?> + <div class="jp-info-img"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/mobile-push-notifications.jpg' ) ?>" alt="<?php esc_attr_e( 'Mobile Push Notifications', 'jetpack' ) ?>" width="300" height="150" /> + </div> + + <h4><?php esc_html_e( 'Mobile Push Notifications' , 'jetpack' ); ?></h4> + + <p><?php _e( 'If you have your blog added to the <a href="http://ios.wordpress.org/">WordPress for iOS app</a>, you’ll now be able to opt in to receive push notifications of new comments, which makes it easier than ever to keep up with your readers and moderate comments on the go.', 'jetpack' ); ?></p> + +<?php +} + +function jetpack_mobile_push_notifications_more_link() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} + +add_action( 'jetpack_learn_more_button_mobile-push', 'jetpack_mobile_push_notifications_more_link' ); +add_action( 'jetpack_module_more_info_mobile-push', 'jetpack_mobile_push_notifications_more_info' ); +// Mobile Push Notifications: STOP + +// Infinite Scroll: START +/** + * + */ +function jetpack_infinite_scroll_more_info() { + $support_text = sprintf( __( 'If you are a theme author, you can learn about adding support for Infinite Scroll at <a href="%1$s">%1$s</a>.', 'jetpack' ), 'http://jetpack.me/support/infinite-scroll/' ); + + ?> + <h4><?php esc_html_e( 'Infinite Scroll', 'jetpack' ); ?></h4> + + <?php if ( ! Jetpack::is_active() || ( Jetpack::is_active() && current_theme_supports( 'infinite-scroll' ) ) ) : ?> + <p><?php esc_html_e( 'When you write great content, all you really want is people to find it, right?', 'jetpack' ); ?></p> + + <p><?php esc_html_e( "With the Infinite Scroll module and a supported theme, that's exactly what happens. Instead of the old way of navigating down a page by scrolling and then clicking a link to get to the next page, waiting for a page refresh—the document model of the web—infinite scrolling pulls the next set of posts automatically into view when the reader approaches the bottom of the page, more like an application.", 'jetpack' ); ?></p> + + <?php else : ?> + <p><?php echo esc_html( sprintf( __( "At this time, your theme, %s, doesn't support Infinite Scroll. Unlike other Jetpack modules, Infinite Scroll needs information from your theme to function properly.", 'jetpack' ), ( function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme() ) ) ); ?></p> + + <p><?php esc_html_e( "Until your theme supports Infinite Scroll, you won't be able to activate this module.", 'jetpack' ); ?></p> + + <?php + + if ( current_user_can( 'update_themes' ) ) : + ob_start(); + theme_update_available( function_exists( 'wp_get_theme' ) ? wp_get_theme() : (object) get_theme( get_current_theme() ) ); + $theme_update_available = ob_get_clean(); + + if ( ! empty( $theme_update_available ) ) : ?> + <p><?php printf( __( 'There is an update available for your theme. You may wish to check if this update adds Infinite Scroll support by visiting the <a href="%s">WordPress Updates</a> page.', 'jetpack' ), esc_url( admin_url( 'update-core.php' ) ) ); ?></p> + <?php else : ?> + <p><?php echo $support_text; ?></p> + <?php endif; ?> + <?php else : ?> + <p><?php echo $support_text; ?></p> + <?php endif; ?> + <?php endif; +} +add_action( 'jetpack_module_more_info_infinite-scroll', 'jetpack_infinite_scroll_more_info' ); + +/** + * + */ +function jetpack_infinite_scroll_more_button() { + echo '<a class="button more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} +add_action( 'jetpack_learn_more_button_infinite-scroll', 'jetpack_infinite_scroll_more_button' ); +// Infinite Scroll: STOP + + +// Post by Email: START +function jetpack_post_by_email_more_info() { ?> + <div class="jp-info-img"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/post-by-email.png' ) ?>" alt="<?php esc_attr_e( 'Post by Email', 'jetpack' ) ?>" width="300" height="115" /> + </div> + + <h4><?php esc_html_e( 'Post by Email' , 'jetpack' ); ?></h4> + + <p><?php esc_html_e( 'Post by Email is a way of publishing posts on your blog by email. Any email client can be used to send the email, allowing you to publish quickly and easily from devices such as cell phones.', 'jetpack' ); ?></p> + +<?php if ( 'jetpack_module_more_info_connected_post-by-email' == current_filter() ) : ?> + + <p><?php printf( __( 'Manage your Post By Email address from your <a href="%s">profile settings</a>.', 'jetpack' ), esc_url( get_edit_profile_url( get_current_user_id() ) . '#post-by-email' ) ); ?> + +<?php endif; ?> + + <p>→ <a href="http://jetpack.me/support/post-by-email/"><?php esc_html_e( 'More information on sending emails, attachments, and customizing your posts.', 'jetpack' ); ?></a></p> + +<?php +} + +function jetpack_post_by_email_more_link() { + echo '<a class="button-secondary more-info-link" href="http://jetpack.me/support/post-by-email/">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} + +add_action( 'jetpack_module_more_info_post-by-email', 'jetpack_post_by_email_more_info' ); +add_action( 'jetpack_module_more_info_connected_post-by-email', 'jetpack_post_by_email_more_info' ); +add_action( 'jetpack_learn_more_button_post-by-email', 'jetpack_post_by_email_more_link' ); +// Post by Email: STOP + + +// Photon: START +/** + * + */ +function jetpack_photon_more_info() { ?> + <h4><?php esc_html_e( 'Photon' , 'jetpack' ); ?></h4> + + <p><?php esc_html_e( "Give your site a boost by loading images in posts from the WordPress.com content delivery network. We cache your images and serve them from our super-fast network, reducing the burden on your Web host with the click of a button.", 'jetpack' ); ?></p> +<?php +} +add_action( 'jetpack_module_more_info_photon', 'jetpack_photon_more_info' ); + +/** + * Display "Learn More" button for Photon module + * @uses __ + * @action jetpack_learn_more_button_photon + * @return string + */ +function jetpack_photon_more_link() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} +add_action( 'jetpack_learn_more_button_photon', 'jetpack_photon_more_link' ); +// Photon: STOP + +// Tiled Galleries: START +function jetpack_tiled_gallery_more_info() { ?> + <h4><?php esc_html_e( 'Tiled Galleries' , 'jetpack' ); ?></h4> + + <div class="jp-info-img"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/tiled-gallery.png' ) ?>" alt="<?php esc_attr_e( 'Tiled Galleries', 'jetpack' ) ?>" width="300" height="150" /> + </div> + + <p><?php esc_html_e( 'Create elegant magazine-style mosaic layouts for your photos without having to use an external graphic editor.', 'jetpack' ); ?></p> + <p><?php printf( __( 'When adding a gallery to your post, you now have the option to select a layout style for your images. We\'ve added support for Rectangular, Square, and Circular galleries. By default, galleries will continue to display using the standard thumbnail grid layout. To make the rectangular layout the default for all of your site\'s galleries, head over to <a href="%s">Settings → Media</a> and check the box next to "Display all your gallery pictures in a cool mosaic."', 'jetpack' ), admin_url( 'options-media.php' ) ); ?></p> + <p><em><?php esc_html_e( 'Note: Images in tiled galleries require extra-special processing, so they will be served from WordPress.com\'s CDN even if the Photon module is disabled.', 'jetpack' ); ?></em></p> +<?php +} +add_action( 'jetpack_module_more_info_tiled-gallery', 'jetpack_tiled_gallery_more_info' ); + +function jetpack_tiled_gallery_more_link() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} +add_action( 'jetpack_learn_more_button_tiled-gallery', 'jetpack_tiled_gallery_more_link' ); +// Tiled Galleries: STOP + +// Likes: START +function jetpack_likes_more_info() { ?> + + <div class="jp-info-img"> + <a href="http://jetpack.me/support/likes/"> + <img class="jp-info-img" src="<?php echo plugins_url( basename( dirname( dirname( __FILE__ ) ) ) . '/_inc/images/screenshots/likes.png' ) ?>" alt="<?php esc_attr_e( 'Likes', 'jetpack' ) ?>" width="323" height="69" /> + </a> + </div> + + <h4><?php esc_html_e( 'Likes' , 'jetpack' ); ?></h4> + + <p><?php esc_html_e( 'Likes allow your readers to show their appreciation for your posts and other published content using their WordPress.com accounts. Your readers will then be able to review their liked posts from WordPress.com.', 'jetpack' ) ?></p> + <p><?php esc_html_e( 'Displayed below your posts will be how many people have liked your posts and the Gravatars of those who have liked them.', 'jetpack' ); ?></p> + + <p>→ <a href="http://jetpack.me/support/likes/"><?php esc_html_e( 'More information on using Likes.', 'jetpack' ); ?></a></p> + +<?php +} +add_action( 'jetpack_module_more_info_likes', 'jetpack_likes_more_info' ); + +function jetpack_likes_more_link() { + echo '<a class="button-secondary more-info-link" href="#">' . __( 'Learn More', 'jetpack' ) . '</a>'; +} +add_action( 'jetpack_learn_more_button_likes', 'jetpack_likes_more_link' ); +// Likes: STOP diff --git a/plugins/jetpack/modules/notes.php b/plugins/jetpack/modules/notes.php new file mode 100644 index 00000000..e33f7ff8 --- /dev/null +++ b/plugins/jetpack/modules/notes.php @@ -0,0 +1,195 @@ +<?php +/** + * Module Name: Notifications + * Module Description: Monitor and manage your site's activity with Notifications in your Toolbar and on WordPress.com. + * Sort Order: 1 + * First Introduced: 1.9 + */ + +if ( !defined( 'JETPACK_NOTES__CACHE_BUSTER' ) ) define( 'JETPACK_NOTES__CACHE_BUSTER', JETPACK__VERSION . '-' . gmdate( 'oW' ) ); + +Jetpack_Sync::sync_options( __FILE__, + 'home', + 'blogname', + 'siteurl', + 'permalink_structure', + 'category_base', + 'tag_base', + 'comment_moderation', + 'default_comment_status', + 'thread_comments', + 'thread_comments_depth' +); + +class Jetpack_Notifications { + var $jetpack = false; + + /** + * Singleton + * @static + */ + function &init() { + static $instance = array(); + + if ( !$instance ) { + $instance[0] = new Jetpack_Notifications; + } + + return $instance[0]; + } + + function Jetpack_Notifications() { + $this->jetpack = Jetpack::init(); + + add_action( 'init', array( &$this, 'action_init' ) ); + + //post types that support comments + $filt_post_types = array(); + foreach ( get_post_types() as $post_type ) { + if ( post_type_supports( $post_type, 'comments' ) ) { + $filt_post_types[] = $post_type; + } + } + + Jetpack_Sync::sync_posts( __FILE__, array( + 'post_types' => $filt_post_types, + 'post_stati' => array( 'publish' ), + ) ); + Jetpack_Sync::sync_comments( __FILE__, array( + 'post_types' => $filt_post_types, + 'post_stati' => array( 'publish' ), + 'comment_stati' => array( 'approve', 'approved', '1', 'hold', 'unapproved', 'unapprove', '0', 'spam', 'trash' ), + ) ); + } + + function wpcom_static_url($file) { + $i = hexdec( substr( md5( $file ), -1 ) ) % 2; + $http = is_ssl() ? 'https' : 'http'; + $url = $http . '://s' . $i . '.wp.com' . $file; + return $url; + } + + // return the major version of Internet Explorer the viewer is using or false if it's not IE + public static function get_internet_explorer_version() { + static $version; + if ( isset( $version ) ) { + return $version; + } + + preg_match( '/MSIE (\d+)/', $_SERVER['HTTP_USER_AGENT'], $matches ); + $version = empty( $matches[1] ) ? null : $matches[1]; + if ( empty( $version ) || !$version ) { + return false; + } + return $version; + } + + public static function current_browser_is_supported() { + static $supported; + + if ( isset( $supported ) ) { + return $supported; + } + + $ie_version = self::get_internet_explorer_version(); + if ( false === $ie_version ) { + return $supported = true; + } + + if ( $ie_version < 8 ) { + return $supported = false; + } + + return $supported = true; + } + + function action_init() { + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) + return; + + if ( !has_filter( 'show_admin_bar', '__return_true' ) && !is_user_logged_in() ) + return; + + if ( !self::current_browser_is_supported() ) + return; + + add_action( 'admin_bar_menu', array( &$this, 'admin_bar_menu'), 120 ); + add_action( 'wp_head', array( &$this, 'styles_and_scripts'), 120 ); + add_action( 'admin_head', array( &$this, 'styles_and_scripts') ); + } + + function styles_and_scripts() { + wp_enqueue_style( 'notes-admin-bar-rest', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/admin-bar-rest.css' ), array(), JETPACK_NOTES__CACHE_BUSTER ); + wp_enqueue_style( 'noticons', $this->wpcom_static_url( '/i/noticons/noticons.css' ), array(), JETPACK_NOTES__CACHE_BUSTER ); + + $this->print_js(); + + // attempt to use core or plugin libraries if registered + if ( wp_script_is( 'mustache', 'registered' ) ) { + if ( !wp_script_is( 'mustache', 'queue' ) ) { + wp_enqueue_script( 'mustache' ); + } + } + else { + wp_enqueue_script( 'mustache', $this->wpcom_static_url( '/wp-content/js/mustache.js' ), null, JETPACK_NOTES__CACHE_BUSTER ); + } + + if ( wp_script_is( 'underscore', 'registered' ) ) { + if ( !wp_script_is( 'underscore', 'queue' ) ) { + wp_enqueue_script( 'underscore' ); + } + } + else { + wp_enqueue_script( 'underscore', $this->wpcom_static_url( '/wp-content/js/underscore.js' ), null, JETPACK_NOTES__CACHE_BUSTER ); + } + if ( wp_script_is( 'backbone', 'registered' ) ) { + if ( !wp_script_is( 'backbone', 'queue' ) ) { + wp_enqueue_script( 'backbone' ); + } + } + else { + wp_enqueue_script( 'backbone', $this->wpcom_static_url( '/wp-content/js/backbone.js' ), array( 'jquery', 'underscore' ), JETPACK_NOTES__CACHE_BUSTER ); + } + + wp_enqueue_script( 'notes-postmessage', $this->wpcom_static_url( '/wp-content/js/postmessage.js' ), array(), JETPACK_NOTES__CACHE_BUSTER ); + wp_enqueue_script( 'notes-rest-common', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/notes-rest-common.js' ), array( 'backbone', 'mustache', 'jquery.spin' ), JETPACK_NOTES__CACHE_BUSTER ); + wp_enqueue_script( 'notes-admin-bar-rest', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/admin-bar-rest.js' ), array( 'jquery', 'underscore', 'backbone', 'jquery.spin' ), JETPACK_NOTES__CACHE_BUSTER ); + } + + function admin_bar_menu() { + global $wp_admin_bar, $current_blog; + + if ( !is_object( $wp_admin_bar ) ) + return; + + $classes = 'wpnt-loading wpn-read'; + $wp_admin_bar->add_menu( array( + 'id' => 'notes', + 'title' => '<span id="wpnt-notes-unread-count" class="' . esc_attr( $classes ) . '"> + <span class="noticon noticon-notification" /></span> + </span>', + 'meta' => array( + 'html' => '<div id="wpnt-notes-panel" style="display:none"><div class="wpnt-notes-panel-header"><span class="wpnt-notes-header">' . __('Notifications', 'jetpack') . '</span><span class="wpnt-notes-panel-link"></span></div></div>', + 'class' => 'menupop', + ), + 'parent' => 'top-secondary', + ) ); + } + + function print_js() { + $link_accounts_url = is_user_logged_in() && !Jetpack::is_user_connected() ? Jetpack::admin_url() : false; +?> +<script type="text/javascript"> +/* <![CDATA[ */ + var wpNotesIsJetpackClient = true; +<?php if ( $link_accounts_url ) : ?> + var wpNotesLinkAccountsURL = '<?php print $link_accounts_url; ?>'; +<?php endif; ?> +/* ]]> */ +</script> +<?php + } + +} + +Jetpack_Notifications::init(); diff --git a/plugins/jetpack/modules/photon.php b/plugins/jetpack/modules/photon.php new file mode 100644 index 00000000..06db8680 --- /dev/null +++ b/plugins/jetpack/modules/photon.php @@ -0,0 +1,9 @@ +<?php +/** + * Module Name: Photon + * Module Description: Give your site a boost by loading images from the WordPress.com content delivery network. + * Sort Order: 15 + * First Introduced: 2.0 + */ + +Jetpack_Photon::instance();
\ No newline at end of file diff --git a/plugins/jetpack/modules/photon/photon.js b/plugins/jetpack/modules/photon/photon.js new file mode 100644 index 00000000..050e73b6 --- /dev/null +++ b/plugins/jetpack/modules/photon/photon.js @@ -0,0 +1,42 @@ +(function($){ + /** + * For images lacking explicit dimensions and needing them, try to add them. + */ + var restore_dims = function() { + $( 'img[data-recalc-dims]' ).each( function() { + if ( this.complete ) { + var width = this.width, + height = this.height; + + if ( width && width > 0 && height && height > 0 ) { + $( this ).attr( { + width: width, + height: height + } ); + + reset_for_retina( this ); + } + } + else { + $( this ).load( arguments.callee ); + } + } ); + }, + + /** + * Modify given image's markup so that devicepx-jetpack.js will act on the image and it won't be reprocessed by this script. + */ + reset_for_retina = function( img ) { + $( img ).removeAttr( 'data-recalc-dims' ).removeAttr( 'scale' ); + }; + + /** + * Check both when page loads, and when IS is triggered. + */ + $( document ).ready( restore_dims ); + + if ( "on" in $.fn ) + $( document.body ).on( 'post-load', restore_dims ); + else + $( document ).delegate( 'body', 'post-load', restore_dims ); +})(jQuery);
\ No newline at end of file diff --git a/plugins/jetpack/modules/post-by-email.php b/plugins/jetpack/modules/post-by-email.php new file mode 100644 index 00000000..e8cd8873 --- /dev/null +++ b/plugins/jetpack/modules/post-by-email.php @@ -0,0 +1,240 @@ +<?php + +/** + * Module Name: Post by Email + * Module Description: Publish posts to your blog directly from your personal email account. + * First Introduced: 2.0 + * Sort Order: 4 + */ + +add_action( 'jetpack_modules_loaded', array( 'Jetpack_Post_By_Email', 'init' ) ); + +Jetpack_Sync::sync_options( __FILE__, + 'large_size_w', + 'large_size_h', + 'thumbnail_size_w', + 'thumbnail_size_h', + 'medium_size_w', + 'medium_size_h' +); + +add_action( 'jetpack_activate_module_post-by-email', array( 'Jetpack_Post_By_Email', 'module_toggle' ) ); +add_action( 'jetpack_deactivate_module_post-by-email', array( 'Jetpack_Post_By_Email', 'module_toggle' ) ); + +Jetpack::enable_module_configurable( __FILE__ ); +Jetpack::module_configuration_load( __FILE__, array( 'Jetpack_Post_By_Email', 'configuration_redirect' ) ); + +class Jetpack_Post_By_Email { + function &init() { + static $instance = NULL; + + if ( !$instance ) { + $instance = new Jetpack_Post_By_Email; + } + + return $instance; + } + + function __construct() { + add_action( 'init', array( &$this, 'action_init' ) ); + } + + function module_toggle() { + $jetpack = Jetpack::init(); + $jetpack->sync->register( 'noop' ); + } + + function configuration_redirect() { + wp_safe_redirect( get_edit_profile_url( get_current_user_id() ) . '#post-by-email' ); + exit; + } + + function action_init() { + if ( ! current_user_can( 'edit_posts' ) ) + return; + + add_action( 'profile_personal_options', array( &$this, 'user_profile' ) ); + add_action( 'admin_print_scripts-profile.php', array( &$this, 'profile_scripts' ) ); + + add_action( 'wp_ajax_jetpack_post_by_email_enable', array( &$this, 'create_post_by_email_address' ) ); + add_action( 'wp_ajax_jetpack_post_by_email_regenerate', array( &$this, 'regenerate_post_by_email_address' ) ); + add_action( 'wp_ajax_jetpack_post_by_email_disable', array( &$this, 'delete_post_by_email_address' ) ); + } + + function profile_scripts() { + wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email/post-by-email.js', __FILE__ ), array( 'jquery' ) ); + wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email/post-by-email.css', __FILE__ ) ); + Jetpack::init()->admin_styles(); + } + + function check_user_connection() { + $user_token = Jetpack_Data::get_access_token( get_current_user_id() ); + $is_user_connected = $user_token && !is_wp_error( $user_token ); + + // If the user is already connected via Jetpack, then we're good + if ( $is_user_connected ) + return true; + + return false; + } + + function user_profile() { + $blog_name = get_bloginfo( 'blogname' ); + if ( empty( $blog_name ) ) { + $blog_name = home_url( '/' ); + } + + ?> + <div id="post-by-email" class="jetpack-targetable"> + <h3><?php esc_html_e( 'Post by Email', 'jetpack' ); ?></h3> + <table class="form-table"> + <tr> + <th scope="row"><?php esc_html_e( 'Email Address', 'jetpack' ); ?><span id="jp-pbe-spinner" class="spinner"></span></th> + <td> + <div id="jp-pbe-error" class="jetpack-inline-error"></div> <?php + + if ( $this->check_user_connection() ) { + $email = $this->get_post_by_email_address(); + + if ( empty( $email ) ) { + $enable_hidden = ''; + $info_hidden = ' style="display: none;"'; + } else { + $enable_hidden = ' style="display: none;"'; + $info_hidden = ''; + } ?> + + <input type="button" name="jp-pbe-enable" id="jp-pbe-enable" class="button" value="<?php esc_attr_e( 'Enable Post By Email', 'jetpack' ); ?> "<?php echo $enable_hidden; ?> /> + <div id="jp-pbe-info"<?php echo $info_hidden; ?>> + <p id="jp-pbe-email-wrapper"> + <input type="text" id="jp-pbe-email" value="<?php echo esc_attr( $email ); ?>" readonly="readonly" class="regular-text" /> + <span class="description"><a target="_blank" href="http://jetpack.me/support/post-by-email/"><?php esc_html_e( 'More information', 'jetpack' ); ?></a></span> + </p> + <p> + <input type="button" name="jp-pbe-regenerate" id="jp-pbe-regenerate" class="button" value="<?php esc_attr_e( 'Regenerate Address', 'jetpack' ); ?> " /> + <input type="button" name="jp-pbe-disable" id="jp-pbe-disable" class="button" value="<?php esc_attr_e( 'Disable Post By Email', 'jetpack' ); ?> " /> + </p> + </div> <?php + } else { + $jetpack = Jetpack::init(); ?> + + <p class="jetpack-inline-message"> + <?php printf( + esc_html( wptexturize( __( 'To use Post By Email, you need to link your %s account to your WordPress.com account.', 'jetpack' ) ) ), + '<strong>' . esc_html( $blog_name ) . '</strong>' + ); ?><br /> + <?php echo esc_html( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?> + </p> + <p> + <a href="<?php echo $jetpack->build_connect_url( false, get_edit_profile_url( get_current_user_id() ) . '#post-by-email' ); ?>" class="button button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a> + </p> + <?php + } ?> + </td> + </tr> + </table> + </div> + <?php + } + + function get_post_by_email_address() { + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client( array( + 'user_id' => get_current_user_id(), + ) ); + $xml->query( 'jetpack.getPostByEmailAddress' ); + + if ( $xml->isError() ) + return NULL; + + $response = $xml->getResponse(); + if ( empty( $response ) ) + return NULL; + + return $response; + } + + function create_post_by_email_address() { + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client( array( + 'user_id' => get_current_user_id(), + ) ); + $xml->query( 'jetpack.createPostByEmailAddress' ); + + if ( $xml->isError() ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to create your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to create your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + echo $response; + die(); + } + + function regenerate_post_by_email_address() { + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client( array( + 'user_id' => get_current_user_id(), + ) ); + $xml->query( 'jetpack.regeneratePostByEmailAddress' ); + + if ( $xml->isError() ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to regenerate your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to regenerate your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + echo $response; + die(); + } + + function delete_post_by_email_address() { + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client( array( + 'user_id' => get_current_user_id(), + ) ); + $xml->query( 'jetpack.deletePostByEmailAddress' ); + + if ( $xml->isError() ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to disable your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + echo json_encode( array( + 'response' => 'error', + 'message' => __( 'Unable to disable your Post By Email address. Please try again later.', 'jetpack' ) + ) ); + die(); + } + + echo $response; + die(); + } +} diff --git a/plugins/jetpack/modules/post-by-email/post-by-email.css b/plugins/jetpack/modules/post-by-email/post-by-email.css new file mode 100644 index 00000000..c3b88def --- /dev/null +++ b/plugins/jetpack/modules/post-by-email/post-by-email.css @@ -0,0 +1,6 @@ +#jp-pbe-error { + display: none; +} +#post-by-email:target .jetpack-inline-message { + background-color: #fff; +} diff --git a/plugins/jetpack/modules/post-by-email/post-by-email.js b/plugins/jetpack/modules/post-by-email/post-by-email.js new file mode 100644 index 00000000..7ba14bd7 --- /dev/null +++ b/plugins/jetpack/modules/post-by-email/post-by-email.js @@ -0,0 +1,129 @@ +jetpack_post_by_email = { + init: function() { + jQuery( '#jp-pbe-enable' ).click( jetpack_post_by_email.enable ); + jQuery( '#jp-pbe-regenerate' ).click( jetpack_post_by_email.regenerate ); + jQuery( '#jp-pbe-disable' ).click( jetpack_post_by_email.disable ); + }, + + enable: function() { + jQuery( '#jp-pbe-enable' ).attr( 'disabled', 'disabled' ); + jQuery( '#jp-pbe-error' ).fadeOut(); + jQuery( '#jp-pbe-spinner' ).fadeIn(); + + var data = { + action: 'jetpack_post_by_email_enable' + }; + + jQuery.post( ajaxurl, data, jetpack_post_by_email.handle_enabled ); + }, + + handle_enabled: function( response ) { + var enabled = false; + var error; + try { + error = JSON.parse( response ); + } catch ( e ) { + enabled = true; + } + + jQuery( '#jp-pbe-regenerate' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-disable' ).removeAttr( 'disabled' ); + + if ( enabled ) { + jQuery( '#jp-pbe-enable' ).fadeOut( 400, function() { + jQuery( '#jp-pbe-enable' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-email' ).val( response ); + jQuery( '#jp-pbe-info' ).fadeIn(); + }); + } else { + jQuery( '#jp-pbe-error' ).text( error.message ); + jQuery( '#jp-pbe-error' ).fadeIn(); + jQuery( '#jp-pbe-enable' ).removeAttr( 'disabled' ); + } + + jQuery( '#jp-pbe-spinner' ).fadeOut(); + }, + + regenerate: function() { + jQuery( '#jp-pbe-regenerate' ).attr( 'disabled', 'disabled' ); + jQuery( '#jp-pbe-disable' ).attr( 'disabled', 'disabled' ); + jQuery( '#jp-pbe-error' ).fadeOut(); + jQuery( '#jp-pbe-spinner' ).fadeIn(); + + var data = { + action: 'jetpack_post_by_email_regenerate' + }; + + jQuery.post( ajaxurl, data, jetpack_post_by_email.handle_regenerated ); + }, + + handle_regenerated: function( response ) { + var regenerated = false; + var error; + try { + error = JSON.parse( response ); + } catch ( e ) { + regenerated = true; + } + + if ( regenerated ) { + jQuery( '#jp-pbe-email-wrapper' ).fadeOut( 400, function() { + jQuery( '#jp-pbe-email' ).val( response ); + jQuery( '#jp-pbe-email-wrapper' ).fadeIn(); + }); + } else { + jQuery( '#jp-pbe-error' ).text( error.message ); + jQuery( '#jp-pbe-error' ).fadeIn(); + } + + jQuery( '#jp-pbe-regenerate' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-disable' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-spinner' ).fadeOut(); + }, + + disable: function() { + jQuery( '#jp-pbe-regenerate' ).attr( 'disabled', 'disabled' ); + jQuery( '#jp-pbe-disable' ).attr( 'disabled', 'disabled' ); + jQuery( '#jp-pbe-error' ).fadeOut(); + jQuery( '#jp-pbe-spinner' ).fadeIn(); + + var data = { + action: 'jetpack_post_by_email_disable' + }; + + jQuery.post( ajaxurl, data, jetpack_post_by_email.handle_disabled ); + }, + + handle_disabled: function( response ) { + var disabled = false; + var error; + try { + error = JSON.parse( response ); + } catch ( e ) { + disabled = true; + } + + if ( 'error' != error.response ) { + disabled = true; + } + + if ( disabled ) { + jQuery( '#jp-pbe-enable' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-info' ).fadeOut( 400, function() { + jQuery( '#jp-pbe-regenerate' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-disable' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-enable' ).fadeIn(); + }); + } else { + jQuery( '#jp-pbe-regenerate' ).removeAttr( 'disabled' ); + jQuery( '#jp-pbe-disable' ).removeAttr( 'disabled' ); + + jQuery( '#jp-pbe-error' ).text( error.message ); + jQuery( '#jp-pbe-error' ).fadeIn(); + } + + jQuery( '#jp-pbe-spinner' ).fadeOut(); + } +}; + +jQuery( function() { jetpack_post_by_email.init(); } ); diff --git a/plugins/jetpack/modules/publicize.php b/plugins/jetpack/modules/publicize.php new file mode 100644 index 00000000..c9a37e5d --- /dev/null +++ b/plugins/jetpack/modules/publicize.php @@ -0,0 +1,283 @@ +<?php +/** + * Module Name: Publicize + * Module Description: Connect your site to popular social networks and automatically share new posts with your friends. + * Sort Order: 1 + * First Introduced: 2.0 + */ + +class Jetpack_Publicize { + + var $in_jetpack = true; + + function __construct() { + global $publicize_ui; + + $this->in_jetpack = ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'enable_module_configurable' ) ) ? true : false; + + if ( $this->in_jetpack && method_exists( 'Jetpack', 'module_configuration_load' ) ) { + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, array( $this, 'jetpack_configuration_load' ) ); + Jetpack_Sync::sync_posts( __FILE__ ); + } + + require_once dirname( __FILE__ ) . '/publicize/publicize.php'; + + if ( $this->in_jetpack ) + require_once dirname( __FILE__ ) . '/publicize/publicize-jetpack.php'; + else { + require_once dirname( dirname( __FILE__ ) ) . '/mu-plugins/keyring/keyring.php'; + require_once dirname( __FILE__ ) . '/publicize/publicize-wpcom.php'; + } + + require_once dirname( __FILE__ ) . '/publicize/ui.php'; + $publicize_ui = new Publicize_UI(); + $publicize_ui->in_jetpack = $this->in_jetpack; + + // Jetpack specific checks / hooks + if ( $this->in_jetpack) { + add_action( 'jetpack_activate_module_publicize', array( $this, 'module_state_toggle' ) ); + add_action( 'jetpack_deactivate_module_publicize', array( $this, 'module_state_toggle' ) ); + + // if sharedaddy isn't active, the sharing menu hasn't been added yet + $active = Jetpack::get_active_modules(); + if ( in_array( 'publicize', $active ) && !in_array( 'sharedaddy', $active ) ) + add_action( 'admin_menu', array( &$publicize_ui, 'sharing_menu' ) ); + } + } + + function module_state_toggle() { + // extra check that we are on the JP blog, just incase + if ( class_exists( 'Jetpack' ) && $this->in_jetpack ) { + $jetpack = Jetpack::init(); + $jetpack->sync->register( 'noop' ); + } + } + + function jetpack_configuration_load() { + wp_safe_redirect( menu_page_url( 'sharing', false ) ); + exit; + } +} + +global $publicize_ui; +new Jetpack_Publicize; + +/** +* Helper functions for shared use in the services files +*/ +class Publicize_Util { + /** + * Truncates a string to be shorter than or equal to the length specified + * Attempts to truncate on word boundaries + * + * @param string $string + * @param int $length + * @return string + */ + function crop_str( $string, $length = 256 ) { + $string = wp_strip_all_tags( (string) $string, true ); // true: collapse Linear Whitespace into " " + $length = absint( $length ); + + if ( mb_strlen( $string, 'UTF-8' ) <= $length ) { + return $string; + } + + // @see wp_trim_words() + if ( 'characters' == _x( 'words', 'word count: words or characters?', 'jetpack' ) ) { + return trim( mb_substr( $string, 0, $length - 1, 'UTF-8' ) ) . "\xE2\x80\xA6"; // ellipsis + } + + $words = explode( ' ', $string ); + + $return = ''; + while ( strlen( $word = array_shift( $words ) ) ) { + $new_return = $return ? "$return $word" : $word; + $new_return_length = mb_strlen( $new_return, 'UTF-8' ); + if ( $new_return_length < $length - 1 ) { + $return = $new_return; + continue; + } elseif ( $new_return_length == $length - 1 ) { + $return = $new_return; + break; + } + + if ( !$return ) { + $return = mb_substr( $new_return, 0, $length - 1, 'UTF-8' ); + } + + break; + } + + return "$return\xE2\x80\xA6"; // ellipsis + } + + + /** + * Returns an array of DOMNodes that are comments (including recursing child nodes) + * + * @param DOMNode $node + * @return array + */ + + function get_comment_nodes( $node ) { + $comment_nodes = array(); + foreach ( $node->childNodes as $child ) { + + if ( XML_COMMENT_NODE === $child->nodeType ) { + $comment_nodes[] = $child; + } + + if ( $child->hasChildNodes() ) { + $child_comment_nodes = self::get_comment_nodes( $child ); + $comment_nodes = array_merge( $comment_nodes, $child_comment_nodes ); + } + } + + return $comment_nodes; + } + + /** + * Truncates HTML so that its textContent (text without markup) is shorter than or equal to the length specified. + * The length of the returned string may be larger than the specified length due to the markup. + * Attempts to truncate on word boundaries. + * + * @param string $string + * @param int $length + * @param array $allowed_tags KSES input + * @return string + */ + function crop_html( $string, $length = 256, $allowed_tags = array() ) { + $tags = $GLOBALS['allowedtags']; // Markup allowed in comments... + + $tags['img'] = array( // ... plus images ... + 'alt' => true, + 'height' => true, + 'src' => true, + 'width' => true, + ); + + // ... and some other basics + $tags['p'] = array(); + $tags['ul'] = array(); + $tags['ol'] = array(); + $tags['li'] = array(); + $tags['br'] = array(); + + $tags = array_merge( $tags, $allowed_tags ); + + // Clean up, then KSES to really lock it down + $string = trim( (string) $string ); + $string = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string ); + $string = wp_kses( $string, $tags ); + + $string = mb_convert_encoding( $string, 'HTML-ENTITIES', 'UTF-8' ); + $dom = new DOMDocument( '1.0', 'UTF-8' ); + @$dom->loadHTML( "<html><body>$string</body></html>" ); // suppress parser warning + + // Strip comment nodes, if any + $comment_nodes = self::get_comment_nodes( $dom->documentElement ); + foreach ( $comment_nodes as &$comment_node ) { + $comment_node->parentNode->removeChild( $comment_node ); + } + if ( $comment_nodes ) { + // Update the $string (some return paths work from just $string) + $string = $dom->saveHTML(); + $string = preg_replace( '/^<!DOCTYPE.+?>/', '', $string ); + $string = str_replace( array('<html>', '</html>', '<body>', '</body>' ), array( '', '', '', '' ), $string ); + $string = trim( $string ); + } + + // Find the body + $body = false; + foreach ( $dom->childNodes as $child ) { + if ( XML_ELEMENT_NODE === $child->nodeType && 'html' === strtolower( $child->tagName ) ) { + $body = $child->firstChild; + break; + } + } + + if ( !$body ) { + return self::crop_str( $string, $length ); + } + + // If the text (without the markup) is shorter than $length, just return + if ( mb_strlen( $body->textContent, 'UTF-8' ) <= $length ) { + return $string; + } + + $node = false; + do { + $node = self::remove_innermost_last_child( $body, $node_removed_from ); + $new_string_length = mb_strlen( $body->textContent, 'UTF-8' ); + } while ( $new_string_length > $length ); + + $new_string = $dom->saveHTML( $body ); + $new_string = mb_substr( $new_string, 6, -7, 'UTF-8' ); // 6: <body>, 7: </body> + + if ( !$node ) { + return $new_string ? $new_string : self::crop_str( $string, $length ); + } + + $append_string_length = $length - $new_string_length; + + if ( !$append_string_length ) { + return $new_string; + } + + if ( $append_string_length > 1 && XML_TEXT_NODE === $node->nodeType ) { // 1: ellipsis + $append_string = self::crop_str( $node->textContent, $append_string_length ); // includes ellipsis + $append_node = $dom->createTextNode( $append_string ); + $node_removed_from->appendChild( $append_node ); + $new_string = $dom->saveHTML( $body ); + $new_string = mb_substr( $new_string, 6, -7, 'UTF-8' ); + } elseif ( $append_string_length > 9 && XML_ELEMENT_NODE === $node->nodeType && 'p' == strtolower( $node->nodeName ) ) { // 9: '<p>X{\xE2\x80\xA6}</p>' + $new_string .= '<p>' . self::crop_str( $node->textContent, $append_string_length - 8 ) . '</p>'; + } + + // Clean up any empty Paragraphs that might have occurred after removing their children + return trim( preg_replace( '#<p>\s*</p>#i', '', $new_string ) ); + } + + function remove_innermost_last_child( $node, &$node_removed_from ) { + $node_removed_from = $node; + + if ( !$node->lastChild ) { + return false; + } + + if ( $node->lastChild->hasChildNodes() ) { + return self::remove_innermost_last_child( $node->lastChild, $node_removed_from ); + } + + $innermost_last_child = $node->lastChild; + $node->removeChild( $innermost_last_child ); + + return $innermost_last_child; + } + + function bump_stats_extras_publicize_url( $bin, $post_id ) { + static $done = array(); + if ( isset( $done[$post_id] ) ) { + return; + } + $done[$post_id] = true; + + if ( function_exists( 'bump_stats_extras' ) ) + bump_stats_extras( 'publicize_url', $bin ); + } + + public static function build_sprintf( $args ) { + $search = array(); + $replace = array(); + foreach ( $args as $k => $arg ) { + if ( 0 == $k ) { + $string = $arg; + continue; + } + $search[] = "%$arg%"; + $replace[] = "%$k\$s"; + } + return str_replace( $search, $replace, $string ); + } +} diff --git a/plugins/jetpack/modules/publicize/assets/connected.gif b/plugins/jetpack/modules/publicize/assets/connected.gif Binary files differnew file mode 100644 index 00000000..24e0c11f --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/connected.gif diff --git a/plugins/jetpack/modules/publicize/assets/facebook-logo.png b/plugins/jetpack/modules/publicize/assets/facebook-logo.png Binary files differnew file mode 100644 index 00000000..b9181cc4 --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/facebook-logo.png diff --git a/plugins/jetpack/modules/publicize/assets/linkedin-logo.png b/plugins/jetpack/modules/publicize/assets/linkedin-logo.png Binary files differnew file mode 100644 index 00000000..27018dcd --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/linkedin-logo.png diff --git a/plugins/jetpack/modules/publicize/assets/publicize.css b/plugins/jetpack/modules/publicize/assets/publicize.css new file mode 100644 index 00000000..4065cb01 --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/publicize.css @@ -0,0 +1,175 @@ +div#publicize-services-block { + display: inline-block; + clear: both; + margin-bottom: 25px; + background-color: #fff; + width: 100%; +} + +/* Add the logos for the Publicize services */ +span.pub-logos { + float: left; + display: block; + width: 130px; + height: 75px; + margin-top: -18px; + margin-left: 5px; + vertical-align: top; +} + +span#facebook { background: url( facebook-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#twitter { background: url( twitter-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#yahoo { background: url( yahoo-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#linkedin { background: url( linkedin-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#tumblr { background: url( tumblr-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } + +a.publicize-profile-link, a.publicize-profile-link:visited { + text-decoration: none; +} + +a.publicize-profile-link:hover { + color: #f1831e; +} + +a.publicize-add-connection, a.publicize-add-connection:visited { + display: block; + vertical-align: middle; + text-decoration: none; +} + +a.publicize-add-connection:hover { + color: #f1831e; +} + +div.publicize-service-entry { + width: 100%; + clear: both; + margin-bottom: 10px; +} + +div.publicize-service-left { + display: inline-block; + width: 150px; + vertical-align: top; +} + +div.publicize-service-right { + display: inline-block; + margin-left: 5px; + margin-top: 5px; + width: 300px; + padding: 10px; + background-color: #f1f1f1; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; + border: 1px solid #e5e5e5; +} + +div.publicize-service-right ul { + margin-top: 0; +} + +div.publicize-service-right li { + list-style-type: none; +} + +.pub-disconnect-button { + -webkit-border-image: none; + border-bottom-color: #CCC; + border-bottom-style: none; + border-bottom-width: 0px; + border-left-color: #CCC; + border-left-style: none; + border-left-width: 0px; + border-right-color: #CCC; + border-right-style: none; + border-right-width: 0px; + border-top-color: #CCC; + border-top-style: none; + border-top-width: 0px; + color: #CCC; + cursor: auto; + display: inline; + font-family: sans-serif; + font-size: 15px; + font-style: normal; + font-weight: normal; + height: auto; + line-height: 22px; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + margin-bottom: 0px; + margin-left: 0px; + margin-right: 0px; + margin-top: 0px; + outline-color: #CCC; + outline-style: none; + outline-width: 0px; + overflow-y: visible; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + position: static; + right: auto; + text-align: left; + text-decoration: none; + top: auto; + vertical-align: baseline; + width: auto; + z-index: auto; +} + +.pub-disconnect-button:hover { + color: #f1831e; +} + +table#option-profile { + padding-bottom: 8px; +} + +table#option-profile td { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + vertical-align: middle; +} + +table#option-profile td.radio { + padding-right: 20px; +} + +table#option-profile td.thumbnail { + padding-right: 20px; +} + +table#option-profile td.details { + font-weight: bold; color: #333333 +} + +table#option-fb-fanpage td { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + vertical-align: middle; +} + +table#option-fb-fanpage td.thumbnail { + padding: 5px 20px 5px 20px; +} + +table#option-fb-fanpage td.details { + width: 130px; + padding-right: 10px; +} + +table#option-fb-fanpage td.details span.name { + font-weight: bold; color: #333333; +} + +table#option-fb-fanpage td.details span.category { + font-size: 10px; color: #888888; +} + +input.fb-options { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + font-size: 12px; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/publicize/assets/publicize.js b/plugins/jetpack/modules/publicize/assets/publicize.js new file mode 100644 index 00000000..bb1f8d4c --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/publicize.js @@ -0,0 +1,108 @@ +var showOptionsPage; + +jQuery( function( $ ) { + + showOptionsPage = function( service, nonce, connection, blogId ) { + tb_show( null, null, null ); + $("body").append( "<div id='TB_load'><img src='" + tb_pathToImage + "' /></div>" ); + $('#TB_load').show(); + + var query = ''; + if ( null != connection ) { + query += '&connection=' + encodeURIComponent( connection ); + } + if ( 'undefined' != typeof( blogId ) && null != blogId ) { + query += '&blog_id=' + Number( blogId ); + } + + $.post( ajaxurl, 'action=publicize_' + service + '_options_page&_wpnonce=' + nonce + query, function( response ) { + $("#TB_load").remove(); + + try { + var obj = jQuery.parseJSON( response ); + if ( null != obj && 'object' == typeof( obj ) ) { + if ( obj.hasOwnProperty( 'fb_redirect' ) ) { + location.href = obj.fb_redirect + '&redirect_uri=' + encodeURIComponent( location.href ); + return; + } + } + } catch (err) { + // Do nothing and move on + } + + if ( response != '' ) { + var blogID = $( 'input[name=wpas_ajax_blog_id]' ).val(); + + var message = $( '<div id="wpas-ajax-' + blogID + '" class="wrap"></div>' ).append( response ); + message.append( '<a href="#TB_inline?thickbox&height=420&width=555&inlineId=wpas-ajax-' + blogID + '" id="wpas-click-' + blogID + '" class="new-thickbox" style="display: none;"></a>' ); + $('#wpas-message').html( message ); + + + tb_init( 'a.new-thickbox' ); + $('#wpas-click-' + blogID).click(); + + var tb_height = parseInt( $('#TB_ajaxContent').css('height') ); + var content_height = $('#thickbox-content').height(); + if ( content_height < tb_height ) { + var new_height = content_height + 15; + $('#TB_ajaxContent').css( 'height', new_height ); + + var new_margin = parseInt( $('#TB_window').css( 'margin-top') ) + (tb_height - new_height) / 2 + 'px' + $('#TB_window').css( 'margin-top', new_margin); + } + + $('.save-options').unbind('click').click( function() { + var sel = $( "input[name='option']:checked" ); + var global = $( "input[name='global']:checked" ); + + var connection = $(this).data('connection'); + var token = encodeURIComponent( sel.val() ); + var id = encodeURIComponent( sel.attr( 'id' ) ); + var type = encodeURIComponent( sel.attr( 'data-type' ) ); + var nonce = $(this).attr('rel'); + var global_conn = 'off'; + var global_nonce = ''; + + if ( global.length ) { + global_conn = 'on'; + global_nonce = global.val(); + } + + $.post( ajaxurl, 'action=publicize_'+ service + '_options_save&connection=' + connection + '&selected_id=' + id + '&token=' + token + '&type=' + type + '&_wpnonce=' + nonce + '&global=' + global_conn + '&global_nonce=' + global_nonce, function( response ) { + tb_remove(); + window.location = 'options-general.php?page=sharing'; + } ); + + } ); + } + + }, 'html' ); + } + + $( 'body' ).append( '<div id="wpas-message" style="display: none"></div>' ); + var messageDiv = $( '#wpas-message' ); + + $( '.wpas-posts' ).change( function() { + var inputs = $(this).parents( 'td:first' ).find( ':input' ); + var _this = this; + var blogID = inputs.filter( '[name=wpas_ajax_blog_id]' ).val(); + + $( '#waiting_' + blogID ).show(); + $.post( ajaxurl, inputs.serialize() + '&action=wpas_post', function( response ) { myblogsResponse.call( _this, blogID, response ) }, 'html' ); + } ); + + $( '.options' ).unbind('click').bind( 'click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var service = $(this).attr('class').replace( 'options ', '' ); + + var blogId = null; + if( 'undefined' != typeof( $(this).attr('id') ) ) + blogId = parseInt( $(this).attr('id').replace( 'options-', '' ) ); + + var nonce = $(this).attr('href').replace( '#nonce=', '' ); + var connection = $(this).data( 'connection' ); + showOptionsPage.call( this, service, nonce, connection, blogId ); + }); +} ); diff --git a/plugins/jetpack/modules/publicize/assets/rtl/publicize-rtl.css b/plugins/jetpack/modules/publicize/assets/rtl/publicize-rtl.css new file mode 100644 index 00000000..b56ab2fb --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/rtl/publicize-rtl.css @@ -0,0 +1,177 @@ +/* This file was automatically generated on Dec 18 2012 19:58:18 */ + +div#publicize-services-block { + display: inline-block; + clear: both; + margin-bottom: 25px; + background-color: #fff; + width: 100%; +} + +/* Add the logos for the Publicize services */ +span.pub-logos { + float: right; + display: block; + width: 130px; + height: 75px; + margin-top: -18px; + margin-right: 5px; + vertical-align: top; +} + +span#facebook { background: url( ../facebook-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#twitter { background: url( ../twitter-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#yahoo { background: url( ../yahoo-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#linkedin { background: url( ../linkedin-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } +span#tumblr { background: url( ../tumblr-logo.png ) 50% 19px no-repeat; background-size: 125px 47px; } + +a.publicize-profile-link, a.publicize-profile-link:visited { + text-decoration: none; +} + +a.publicize-profile-link:hover { + color: #f1831e; +} + +a.publicize-add-connection, a.publicize-add-connection:visited { + display: block; + vertical-align: middle; + text-decoration: none; +} + +a.publicize-add-connection:hover { + color: #f1831e; +} + +div.publicize-service-entry { + width: 100%; + clear: both; + margin-bottom: 10px; +} + +div.publicize-service-left { + display: inline-block; + width: 150px; + vertical-align: top; +} + +div.publicize-service-right { + display: inline-block; + margin-right: 5px; + margin-top: 5px; + width: 300px; + padding: 10px; + background-color: #f1f1f1; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; + border: 1px solid #e5e5e5; +} + +div.publicize-service-right ul { + margin-top: 0; +} + +div.publicize-service-right li { + list-style-type: none; +} + +.pub-disconnect-button { + -webkit-border-image: none; + border-bottom-color: #CCC; + border-bottom-style: none; + border-bottom-width: 0px; + border-right-color: #CCC; + border-right-style: none; + border-right-width: 0px; + border-left-color: #CCC; + border-left-style: none; + border-left-width: 0px; + border-top-color: #CCC; + border-top-style: none; + border-top-width: 0px; + color: #CCC; + cursor: auto; + display: inline; + font-family: sans-serif; + font-size: 15px; + font-style: normal; + font-weight: normal; + height: auto; + line-height: 22px; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + margin-bottom: 0px; + margin-right: 0px; + margin-left: 0px; + margin-top: 0px; + outline-color: #CCC; + outline-style: none; + outline-width: 0px; + overflow-y: visible; + padding-bottom: 0px; + padding-right: 0px; + padding-left: 0px; + padding-top: 0px; + position: static; + left: auto; + text-align: right; + text-decoration: none; + top: auto; + vertical-align: baseline; + width: auto; + z-index: auto; +} + +.pub-disconnect-button:hover { + color: #f1831e; +} + +table#option-profile { + padding-bottom: 8px; +} + +table#option-profile td { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + vertical-align: middle; +} + +table#option-profile td.radio { + padding-left: 20px; +} + +table#option-profile td.thumbnail { + padding-left: 20px; +} + +table#option-profile td.details { + font-weight: bold; color: #333333 +} + +table#option-fb-fanpage td { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + vertical-align: middle; +} + +table#option-fb-fanpage td.thumbnail { + padding: 5px 20px 5px 20px; +} + +table#option-fb-fanpage td.details { + width: 130px; + padding-left: 10px; +} + +table#option-fb-fanpage td.details span.name { + font-weight: bold; color: #333333; +} + +table#option-fb-fanpage td.details span.category { + font-size: 10px; color: #888888; +} + +input.fb-options { + font-family: "Lucida Grande",Verdana,Arial,sans-serif; + font-size: 12px; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/publicize/assets/spinner.gif b/plugins/jetpack/modules/publicize/assets/spinner.gif Binary files differnew file mode 100644 index 00000000..6e5bace6 --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/spinner.gif diff --git a/plugins/jetpack/modules/publicize/assets/tumblr-logo.png b/plugins/jetpack/modules/publicize/assets/tumblr-logo.png Binary files differnew file mode 100644 index 00000000..1ec62d39 --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/tumblr-logo.png diff --git a/plugins/jetpack/modules/publicize/assets/twitter-logo.png b/plugins/jetpack/modules/publicize/assets/twitter-logo.png Binary files differnew file mode 100644 index 00000000..6c7b89b3 --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/twitter-logo.png diff --git a/plugins/jetpack/modules/publicize/assets/yahoo-logo.png b/plugins/jetpack/modules/publicize/assets/yahoo-logo.png Binary files differnew file mode 100644 index 00000000..b7ffe2bb --- /dev/null +++ b/plugins/jetpack/modules/publicize/assets/yahoo-logo.png diff --git a/plugins/jetpack/modules/publicize/publicize-jetpack.php b/plugins/jetpack/modules/publicize/publicize-jetpack.php new file mode 100644 index 00000000..58e25648 --- /dev/null +++ b/plugins/jetpack/modules/publicize/publicize-jetpack.php @@ -0,0 +1,578 @@ +<?php + +class Publicize extends Publicize_Base { + + function __construct() { + parent::__construct(); + + add_action( 'load-settings_page_sharing', array( $this, 'admin_page_load' ), 9 ); + + add_action( 'wp_ajax_publicize_tumblr_options_page', array( $this, 'options_page_tumblr' ) ); + add_action( 'wp_ajax_publicize_facebook_options_page', array( $this, 'options_page_facebook' ) ); + add_action( 'wp_ajax_publicize_twitter_options_page', array( $this, 'options_page_twitter' ) ); + add_action( 'wp_ajax_publicize_linkedin_options_page', array( $this, 'options_page_linkedin' ) ); + add_action( 'wp_ajax_publicize_yahoo_options_page', array( $this, 'options_page_yahoo' ) ); + + add_action( 'wp_ajax_publicize_tumblr_options_save', array( $this, 'options_save_tumblr' ) ); + add_action( 'wp_ajax_publicize_facebook_options_save', array( $this, 'options_save_facebook' ) ); + add_action( 'wp_ajax_publicize_twitter_options_save', array( $this, 'options_save_twitter' ) ); + add_action( 'wp_ajax_publicize_linkedin_options_save', array( $this, 'options_save_linkedin' ) ); + add_action( 'wp_ajax_publicize_yahoo_options_save', array( $this, 'options_save_yahoo' ) ); + + add_action( 'load-settings_page_sharing', array( $this, 'force_user_connection' ) ); + + add_action( 'transition_post_status', array( $this, 'save_publicized' ), 10, 3 ); + } + + function force_user_connection() { + global $current_user; + $user_token = Jetpack_Data::get_access_token( $current_user->ID ); + $is_user_connected = $user_token && !is_wp_error( $user_token ); + + // If the user is already connected via Jetpack, then we're good + if ( $is_user_connected ) + return; + + // If they're not connected, then remove the Publicize UI and tell them they need to connect first + global $publicize_ui; + remove_action( 'pre_admin_screen_sharing', array( $publicize_ui, 'admin_page' ) ); + + Jetpack::init()->admin_styles(); + add_action( 'pre_admin_screen_sharing', array( $this, 'admin_page_warning' ), 1 ); + } + + function admin_page_warning() { + $jetpack = Jetpack::init(); + $blog_name = get_bloginfo( 'blogname' ); + if ( empty( $blog_name ) ) { + $blog_name = home_url( '/' ); + } + + ?> + <div id="message" class="updated jetpack-message jp-connect"> + <div class="jetpack-wrap-container"> + <div class="jetpack-text-container"> + <h4> + <p><?php printf( + esc_html( wptexturize( __( "To use Publicize, you'll need to link your %s account to your WordPress.com account using the button to the right.", 'jetpack' ) ) ), + '<strong>' . esc_html( $blog_name ) . '</strong>' + ); ?></p> + <p><?php echo esc_html( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?></p> + </h4> + </div> + <div class="jetpack-install-container"> + <p class="submit"><a href="<?php echo $jetpack->build_connect_url( false, menu_page_url( 'sharing', false ) ); ?>" class="button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a></p> + </div> + </div> + </div> + <?php + } + + function get_connections( $service_name, $_blog_id = false, $_user_id = false ) { + $connections = Jetpack::get_option( 'publicize_connections' ); + $connections_to_return = array(); + if ( !empty( $connections ) && is_array( $connections ) ) { + if ( !empty( $connections[$service_name] ) ) { + foreach( $connections[$service_name] as $id => $connection ) { + if ( 0 == $connection['connection_data']['user_id'] || $this->user_id() == $connection['connection_data']['user_id'] ) { + $connections_to_return[$id] = $connection; + } + } + } + return $connections_to_return; + } + return false; + } + + function get_connection_id( $connection ) { + return $connection['connection_data']['id']; + } + + function get_connection_meta( $connection ) { + $connection['user_id'] = $connection['connection_data']['user_id']; // Allows for shared connections + return $connection; + } + + function admin_page_load() { + if ( isset( $_GET['action'] ) ) { + if ( isset( $_GET['service'] ) ) + $service_name = $_GET['service']; + + switch ( $_GET['action'] ) { + case 'error': + add_action( 'pre_admin_screen_sharing', array( $this, 'display_connection_error' ), 9 ); + break; + + case 'request': + check_admin_referer( 'keyring-request', 'kr_nonce' ); + check_admin_referer( "keyring-request-$service_name", 'nonce' ); + + $verification = Jetpack::create_nonce( 'publicize' ); + + $stats_options = get_option( 'stats_options' ); + $wpcom_blog_id = Jetpack::get_option('id'); + $wpcom_blog_id = !empty( $wpcom_blog_id ) ? $wpcom_blog_id : $stats_options['blog_id']; + + $user = wp_get_current_user(); + $redirect = $this->api_url( $service_name, urlencode_deep( array( + 'action' => 'request', + 'redirect_uri' => add_query_arg( array( 'action' => 'done' ), menu_page_url( 'sharing', false ) ), + 'for' => 'publicize', // required flag that says this connection is intended for publicize + 'siteurl' => site_url(), + 'state' => $user->ID, + 'blog_id' => $wpcom_blog_id, + 'secret_1' => $verification['secret_1'], + 'secret_2' => $verification['secret_2'], + 'eol' => $verification['eol'], + ) ) ); + wp_redirect( $redirect ); + exit; + break; + + case 'completed': + // Jetpack blog requests Publicize Connections via new XML-RPC method + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.fetchPublicizeConnections' ); + + if ( !$xml->isError() ) { + $response = $xml->getResponse(); + Jetpack::update_option( 'publicize_connections', $response ); + } + break; + + case 'delete': + $id = $_GET['id']; + + check_admin_referer( 'keyring-request', 'kr_nonce' ); + check_admin_referer( "keyring-request-$service_name", 'nonce' ); + + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.deletePublicizeConnection', $id ); + + if ( !$xml->isError() ) { + $response = $xml->getResponse(); + Jetpack::update_option( 'publicize_connections', $response ); + } + add_action( 'admin_notices', array( $this, 'display_disconnected' ) ); + break; + } + } + + // Errors encountered on WordPress.com's end are passed back as a code + if ( isset( $_GET['action'] ) && 'error' == $_GET['action'] ) { + // Load Jetpack's styles to handle the box + Jetpack::init()->admin_styles(); + } + } + + function display_connection_error() { + $code = false; + if ( isset( $_GET['service'] ) ) { + $service_name = $_GET['service']; + $error = sprintf( __( 'There was a problem connecting to %s to create an authorized connection. Please try again in a moment.', 'jetpack' ), Publicize::get_service_label( $service_name ) ); + } else { + if ( isset( $_GET['publicize_error'] ) ) { + $code = strtolower( $_GET['publicize_error'] ); + switch ( $code ) { + case '400': + $error = __( 'An invalid request was made. This normally means that something intercepted or corrupted the request from your server to the Jetpack Server. Try again and see if it works this time.', 'jetpack' ); + break; + case 'secret_mismatch': + $error = __( 'We could not verify that your server is making an authorized request. Please try again, and make sure there is nothing interfering with requests from your server to the Jetpack Server.', 'jetpack' ); + break; + case 'empty_blog_id': + $error = __( 'No blog_id was included in your request. Please try disconnecting Jetpack from WordPress.com and then reconnecting it. Once you have done that, try connecting Publicize again.', 'jetpack' ); + break; + case 'empty_state': + $error = sprintf( __( 'No user information was included in your request. Please make sure that your user account has connected to Jetpack. Connect your user account by going to the <a href="%s">Jetpack page</a> within wp-admin.', 'jetpack' ), Jetpack::admin_url() ); + break; + default: + $error = __( 'Something which should never happen, happened. Sorry about that. If you try again, maybe it will work.', 'jetpack' ); + break; + } + } else { + $error = __( 'There was a problem connecting with Publicize. Please try again in a moment.', 'jetpack' ); + } + } + // Using the same formatting/style as Jetpack::admin_notices() error + ?> + <div id="message" class="jetpack-message jetpack-err"> + <div class="squeezer"> + <h4><?php echo wp_kses( $error, array( 'a' => array( 'href' => true ), 'code' => true, 'strong' => true, 'br' => true, 'b' => true ) ); ?></h4> + <?php if ( $code ) : ?> + <p><?php printf( __( 'Error code: %s', 'jetpack' ), esc_html( stripslashes( $code ) ) ); ?></p> + <?php endif; ?> + </div> + </div> + <?php + } + + function display_disconnected() { + echo "<div class='updated'>\n"; + echo '<p>' . esc_html( __( 'That connection has been removed.', 'jetpack' ) ) . "</p>\n"; + echo "</div>\n\n"; + } + + function globalization() { + if ( 'on' == $_REQUEST['global'] ) { + $id = $_REQUEST['connection']; + + if ( !current_user_can( Publicize::GLOBAL_CAP ) ) + return; + + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.globalizePublicizeConnection', $id, 'globalize' ); + + if ( !$xml->isError() ) { + $response = $xml->getResponse(); + Jetpack::update_option( 'publicize_connections', $response ); + } + } + } + + /** + * Gets a URL to the public-api actions. Works like WP's admin_url + * + * @param string $service Shortname of a specific service. + * @return URL to specific public-api process + */ + // on WordPress.com this is/calls Keyring::admin_url + function api_url( $service = false, $params = array() ) { + $url = apply_filters( 'publicize_api_url', 'https://public-api.wordpress.com/connect/?jetpack=publicize' ); + + if ( $service ) + $url = add_query_arg( array( 'service' => $service ), $url ); + + if ( count ( $params ) ) + $url = add_query_arg( $params, $url ); + + return $url; + } + + function connect_url( $service_name ) { + return add_query_arg( array( + 'action' => 'request', + 'service' => $service_name, + 'kr_nonce' => wp_create_nonce( 'keyring-request' ), + 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), + ), menu_page_url( 'sharing', false ) ); + } + + function disconnect_url( $service_name, $id ) { + return add_query_arg( array ( + 'action' => 'delete', + 'service' => $service_name, + 'id' => $id, + 'kr_nonce' => wp_create_nonce( 'keyring-request' ), + 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), + ), menu_page_url( 'sharing', false ) ); + } + + function get_services( $filter ) { + if ( !in_array( $filter, array( 'all', 'connected' ) ) ) + $filter = 'all'; + + $services = array( + 'facebook' => array(), + 'twitter' => array(), + 'linkedin' => array(), + 'tumblr' => array(), + 'yahoo' => array(), + ); + + if ( 'all' == $filter ) { + return $services; + } else { + $connected_services = array(); + foreach ( $services as $service => $empty ) { + $connections = $this->get_connections( $service ); + if ( $connections ) + $connected_services[$service] = $connections; + } + return $connected_services; + } + } + + function get_connection( $service, $id, $_blog_id = false, $_user_id = false ) { + // Stub + } + + function flag_post_for_publicize( $new_status, $old_status, $post ) { + // Stub only. Doesn't need to do anything on Jetpack Client + } + + /** + * Save a flag locally to indicate that this post has already been Publicized via the selected + * connections. + */ + function save_publicized( $new_status, $old_status, $post ) { + // Only do this when a post transitions to being published + if ( 'publish' == $new_status && 'publish' != $old_status ) { + update_post_meta( $post->ID, $this->POST_DONE . 'all', true ); + } + } + + /** + * Options Code + */ + + function options_page_facebook() { + $connected_services = Jetpack::get_option( 'publicize_connections' ); + $connection = $connected_services['facebook'][$_REQUEST['connection']]; + $options_to_show = $connection['connection_data']['meta']['options_responses']; + + // Nonce check + check_admin_referer( 'options_page_facebook_' . $_REQUEST['connection'] ); + + $me = $options_to_show[0]; + $pages = $options_to_show[1]['data']; + + $profile_checked = true; + $page_selected = false; + + if ( !empty( $connection['connection_data']['meta']['facebook_page'] ) ) { + $found = false; + if ( is_array( $pages->data ) ) { + foreach ( $pages->data as $page ) { + if ( $page->id == $connection['connection_data']['meta']['facebook_page'] ) { + $found = true; + break; + } + } + } + + if ( $found ) { + $profile_checked = false; + $page_selected = $connection['connection_data']['meta']['facebook_page']; + } + } + + ?> + + <div id="thickbox-content"> + + <?php + ob_start(); + Publicize_UI::connected_notice( 'Facebook' ); + $update_notice = ob_get_clean(); + + if ( ! empty( $update_notice ) ) + echo $update_notice; + ?> + + <?php if ( !empty( $me['name'] ) ) : ?> + <p><?php printf( + esc_html__( 'Publicize to my %s:', 'jetpack' ), + '<strong>' . esc_html__( 'Facebook Wall', 'jetpack' ) . '</strong>' + ); ?></p> + <table id="option-profile"> + <tbody> + <tr> + <td class="radio"><input type="radio" name="option" data-type="profile" id="<?php echo esc_attr( $me['id'] ) ?>" value="" <?php checked( $profile_checked, true ); ?> /></td> + <td class="thumbnail"><label for="<?php echo esc_attr( $me['id'] ) ?>"><img src="<?php echo esc_url( $me['picture']['data']['url'] ) ?>" width="50" height="50" /></label></td> + <td class="details"><label for="<?php echo esc_attr( $me['id'] ) ?>"><?php echo esc_html( $me['name'] ) ?></label></td> + </tr> + </tbody> + </table> + <?php endif; ?> + + <?php if ( $pages ) : ?> + + <p><?php printf( + esc_html__( 'Publicize to my %s:', 'jetpack' ), + '<strong>' . esc_html__( 'Facebook Page', 'jetpack' ) . '</strong>' + ); ?></p> + <table id="option-fb-fanpage"> + <tbody> + + <?php foreach ( $pages as $i => $page ) : ?> + <?php if ( ! isset( $page['perms'] ) ) { continue; } ?> + <?php if ( ! ( $i % 2 ) ) : ?> + <tr> + <?php endif; ?> + <td class="radio"><input type="radio" name="option" data-type="page" id="<?php echo esc_attr( $page['id'] ) ?>" value="<?php echo esc_attr( $page['id'] ) ?>" <?php checked( $page_selected && $page_selected == $page['id'], true ); ?> /></td> + <td class="thumbnail"><label for="<?php echo esc_attr( $page['id'] ) ?>"><img src="<?php echo esc_url( str_replace( '_s', '_q', $page['picture']['data']['url'] ) ) ?>" width="50" height="50" /></label></td> + <td class="details"> + <label for="<?php echo esc_attr( $page['id'] ) ?>"> + <span class="name"><?php echo esc_html( $page['name'] ) ?></span><br/> + <span class="category"><?php echo esc_html( $page['category'] ) ?></span> + </label> + </td> + <?php if ( ( $i % 2 ) || ( $i == count( $pages ) - 1 ) ): ?> + </tr> + <?php endif; ?> + <?php endforeach; ?> + + </tbody> + </table> + + <?php endif; ?> + + <?php Publicize_UI::global_checkbox( 'facebook', $_REQUEST['connection'] ); ?> + + <p style="text-align: center;"> + <input type="submit" value="<?php esc_attr_e( 'OK', 'jetpack' ) ?>" class="button fb-options save-options" name="save" data-connection="<?php echo esc_attr( $_REQUEST['connection'] ); ?>" rel="<?php echo wp_create_nonce('save_fb_token_' . $_REQUEST['connection'] ) ?>" /> + </p><br/> + </div> + + <?php + } + + function options_save_facebook() { + // Nonce check + check_admin_referer( 'save_fb_token_' . $_REQUEST['connection'] ); + + $id = $_POST['connection']; + + // Check for a numeric page ID + $page_id = $_POST['selected_id']; + if ( !ctype_digit( $page_id ) ) + die( 'Security check' ); + + if ( isset( $_POST['selected_id'] ) && 'profile' == $_POST['type'] ) { + // Publish to User Wall/Profile + $options = array( + 'facebook_page' => null, + 'facebook_profile' => true + ); + + } else { + if ( 'page' != $_POST['type'] || !isset( $_POST['selected_id'] ) ) { + return; + } + + // Publish to Page + $options = array( + 'facebook_page' => $page_id, + 'facebook_profile' => null + ); + } + + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.setPublicizeOptions', $id, $options ); + + if ( !$xml->isError() ) { + $response = $xml->getResponse(); + Jetpack::update_option( 'publicize_connections', $response ); + } + + $this->globalization(); + } + + function options_page_tumblr() { + // Nonce check + check_admin_referer( 'options_page_tumblr_' . $_REQUEST['connection'] ); + + $connected_services = Jetpack::get_option( 'publicize_connections' ); + $connection = $connected_services['tumblr'][$_POST['connection']]; + $options_to_show = $connection['connection_data']['meta']['options_responses']; + $request = $options_to_show[0]; + + $blogs = $request['response']['user']['blogs']; + + $blog_selected = false; + + if ( !empty( $connection['connection_data']['meta']['tumblr_base_hostname'] ) ) { + foreach ( $blogs as $blog ) { + if ( $connection['connection_data']['meta']['tumblr_base_hostname'] == $this->get_basehostname( $blog['url'] ) ) { + $blog_selected = $connection['connection_data']['meta']['tumblr_base_hostname']; + break; + } + } + + } + + // Use their Primary blog if they haven't selected one yet + if ( !$blog_selected ) { + foreach ( $blogs as $blog ) { + if ( $blog['primary'] ) + $blog_selected = $this->get_basehostname( $blog['url'] ); + } + } ?> + + <div id="thickbox-content"> + + <?php + ob_start(); + Publicize_UI::connected_notice( 'Tumblr' ); + $update_notice = ob_get_clean(); + + if ( ! empty( $update_notice ) ) + echo $update_notice; + ?> + + <p><?php printf( + esc_html__( 'Publicize to my %s:', 'jetpack' ), + '<strong>' . esc_html__( 'Tumblr blog', 'jetpack' ) . '</strong>' + ); ?></p> + + <ul id="option-tumblr-blog"> + + <?php + foreach ( $blogs as $blog ) { + $url = $this->get_basehostname( $blog['url'] ); ?> + <li> + <input type="radio" name="option" data-type="blog" id="<?php echo esc_attr( $url ) ?>" value="<?php echo esc_attr( $url ) ?>" <?php checked( $blog_selected == $url, true ); ?> /> + <label for="<?php echo esc_attr( $url ) ?>"><span class="name"><?php echo esc_html( $blog['title'] ) ?></span></label> + </li> + <?php } ?> + + </ul> + + <?php Publicize_UI::global_checkbox( 'tumblr', $_REQUEST['connection'] ); ?> + + <p style="text-align: center;"> + <input type="submit" value="<?php esc_attr_e( 'OK', 'jetpack' ) ?>" class="button tumblr-options save-options" name="save" data-connection="<?php echo esc_attr( $_REQUEST['connection'] ); ?>" rel="<?php echo wp_create_nonce( 'save_tumblr_blog_' . $_REQUEST['connection'] ) ?>" /> + </p> <br /> + </div> + + <?php + } + + function get_basehostname( $url ) { + return parse_url( $url, PHP_URL_HOST ); + } + + function options_save_tumblr() { + // Nonce check + check_admin_referer( 'save_tumblr_blog_' . $_REQUEST['connection'] ); + + $id = $_POST['connection']; + + $options = array( 'tumblr_base_hostname' => $_POST['selected_id'] ); + + Jetpack::load_xml_rpc_client(); + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.setPublicizeOptions', $id, $options ); + + if ( !$xml->isError() ) { + $response = $xml->getResponse(); + Jetpack::update_option( 'publicize_connections', $response ); + } + + $this->globalization(); + } + + function options_page_twitter() { Publicize_UI::options_page_other( 'twitter' ); } + function options_page_linkedin() { Publicize_UI::options_page_other( 'linkedin' ); } + function options_page_yahoo() { Publicize_UI::options_page_other( 'yahoo' ); } + + function options_save_twitter() { $this->options_save_other( 'twitter' ); } + function options_save_linkedin() { $this->options_save_other( 'linkedin' ); } + function options_save_yahoo() { $this->options_save_other( 'yahoo' ); } + + function options_save_other( $service_name ) { + // Nonce check + check_admin_referer( 'save_' . $service_name . '_token_' . $_REQUEST['connection'] ); + $this->globalization(); + } + + // stub + function refresh_tokens_message() { + + } +} diff --git a/plugins/jetpack/modules/publicize/publicize.php b/plugins/jetpack/modules/publicize/publicize.php new file mode 100644 index 00000000..16d4541c --- /dev/null +++ b/plugins/jetpack/modules/publicize/publicize.php @@ -0,0 +1,328 @@ +<?php + +abstract class Publicize_Base { + + /** + * Services that are currently connected to the given user + * through publicize. + */ + var $connected_services = array(); + + /** + * Sservices that are supported by publicize. They don't + * neccessarly need to be connected to the current user. + */ + var $services; + + /** + * key names for post meta + */ + var $ADMIN_PAGE = 'wpas'; + var $POST_MESS = '_wpas_mess'; + var $POST_SKIP = '_wpas_skip_'; // connection id appended to indicate that a connection should NOT be publicized to + var $POST_DONE = '_wpas_done_'; // connection id appended to indicate a connection has already been publicized to + var $USER_AUTH = 'wpas_authorize'; + var $USER_OPT = 'wpas_'; + var $PENDING = '_publicize_pending'; // ready for Publicize to do its thing + var $POST_SERVICE_DONE = '_publicize_done_external'; // array of external ids where we've Publicized + + /** + * default pieces of the message used in constructing the + * content pushed out to other social networks + */ + var $default_prefix = ''; + var $default_message = '%title%'; + var $default_suffix = ' %url%'; + + /** + * What WP capability is require to create/delete global connections? + * All users with this cap can unglobalize all other global connections, and globalize any of their own + * Globalized connections cannot be unselected by users without this capability when publishing + */ + const GLOBAL_CAP = 'edit_others_posts'; + + /** + * Sets up the basics of Publicize + */ + function __construct() { + $this->default_message = Publicize_Util::build_sprintf( array( + apply_filters( 'wpas_default_message', $this->default_message ), + 'title', + 'url', + ) ); + + $this->default_prefix = Publicize_Util::build_sprintf( array( + apply_filters( 'wpas_default_prefix', $this->default_prefix ), + 'url', + ) ); + + $this->default_suffix = Publicize_Util::build_sprintf( array( + apply_filters( 'wpas_default_suffix', $this->default_suffix ), + 'url', + ) ); + + + // stage 1 and 2 of 3-stage Publicize. Flag for Publicize on creation, save meta, + // then check meta and publicze based on that. stage 3 implemented on wpcom + add_action( 'transition_post_status', array( $this, 'flag_post_for_publicize' ), 10, 3 ); + add_action( 'save_post', array( &$this, 'save_meta' ), 20, 2 ); + } + + /** + * Functions to be implemented by the extended class (publicize-wpcom or publicize-jetpack) + */ + abstract function get_connection_id( $connection ); + abstract function connect_url( $service_name ); + abstract function disconnect_url( $service_name, $id ); + abstract function get_connection_meta( $connection ); + abstract function get_services( $filter ); + abstract function get_connections( $service, $_blog_id = false, $_user_id = false ); + abstract function get_connection( $service, $id, $_blog_id = false, $_user_id = false ); + abstract function flag_post_for_publicize( $new_status, $old_status, $post ); + + /** + * Shared Functions + */ + + /** + * Returns an external URL to the connection's profile + */ + function get_profile_link( $service_name, $c ) { + $cmeta = $this->get_connection_meta( $c ); + + if ( isset( $cmeta['connection_data']['meta']['link'] ) ) { + return $cmeta['connection_data']['meta']['link']; + } elseif ( 'facebook' == $service_name && isset( $cmeta['connection_data']['meta']['facebook_page'] ) ) { + return 'http://facebook.com/' . $cmeta['connection_data']['meta']['facebook_page']; + } elseif ( 'facebook' == $service_name ) { + return 'http://www.facebook.com/' . $cmeta['external_id']; + } elseif ( 'tumblr' == $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) { + return 'http://' . $cmeta['connection_data']['meta']['tumblr_base_hostname']; + } elseif ( 'twitter' == $service_name ) { + return 'http://twitter.com/' . substr( $cmeta['external_display'], 1 ); // Has a leading '@' + } else if ( 'yahoo' == $service_name ) { + return 'http://profile.yahoo.com/' . $cmeta['external_id']; + } else if ( 'linkedin' == $service_name ) { + if ( !isset( $cmeta['connection_data']['meta']['profile_url'] ) ) { + return false; + } + + $profile_url_query = parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY ); + wp_parse_str( $profile_url_query, $profile_url_query_args ); + if ( !isset( $profile_url_query_args['key'] ) ) { + return false; + } + + return esc_url_raw( add_query_arg( 'id', urlencode( $profile_url_query_args['key'] ), 'http://www.linkedin.com/profile/view' ) ); + } else { + return false; // no fallback. we just won't link it + } + } + + /** + * Returns a display name for the connection + */ + function get_display_name( $service_name, $c ) { + $cmeta = $this->get_connection_meta( $c ); + + if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) { + return $cmeta['connection_data']['meta']['display_name']; + } elseif ( $service_name == 'tumblr' && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) { + return $cmeta['connection_data']['meta']['tumblr_base_hostname']; + } elseif ( $service_name == 'twitter' ) { + return $cmeta['external_display']; + } else { + $connection_display = $cmeta['external_display']; + if ( empty( $connection_display ) ) + $connection_display = $cmeta['external_name']; + return $connection_display; + } + } + + function get_service_label( $service_name ) { + switch ( $service_name ) { + case 'yahoo': + return 'Yahoo!'; + break; + case 'linkedin': + return 'LinkedIn'; + break; + case 'twitter': + case 'facebook': + case 'tumblr': + default: + return ucfirst( $service_name ); + break; + } + } + + function show_options_popup( $service_name, $c ) { + $cmeta = $this->get_connection_meta( $c ); + + // always show if no selection has been made for facebook + if ( 'facebook' == $service_name && empty( $cmeta['connection_data']['meta']['facebook_profile'] ) && empty( $cmeta['connection_data']['meta']['facebook_page'] ) ) + return true; + + // always show if no selection has been made for tumblr + if ( 'tumblr' == $service_name && empty ( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) + return true; + + // if we have the specific conncetion info.. + if ( isset( $_GET['id'] ) ) { + if ( $cmeta['connection_data']['id'] == $_GET['id'] ) + return true; + } else { + // otherwise, just show if this is the completed step / first load + if ( !empty( $_GET['action'] ) && 'completed' == $_GET['action'] && !empty( $_GET['service'] ) && $service_name == $_GET['service'] && ! in_array( $_GET['service'], array( 'facebook', 'tumblr' ) ) ) + return true; + } + + return false; + } + + function user_id() { + global $current_user; + return $current_user->ID; + } + + function blog_id() { + return get_current_blog_id(); + } + + /** + * Returns true if a user has a connection to a particular service, false otherwise + */ + function is_enabled( $service, $_blog_id = false, $_user_id = false ) { + if ( !$_blog_id ) + $_blog_id = $this->blog_id(); + + if ( !$_user_id ) + $_user_id = $this->user_id(); + + $connections = $this->get_connections( $service, $_blog_id, $_user_id ); + return ( is_array( $connections ) && count( $connections ) > 0 ? true : false ); + } + + /** + * Fires when a post is saved, checks conditions and saves state in postmeta so that it + * can be picked up later by @see ::publicize_post() + */ + function save_meta( $post_id, $post ) { + $cron_user = null; + $submit_post = true; + + // don't do anything if its not actually a post + if ( 'post' !== $post->post_type ) + return; + + // Don't Publicize during certain contexts: + + // - import + if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) + $submit_post = false; + + // - on quick edit, autosave, etc but do fire on p2, quickpress, and instapost ajax + if ( + defined( 'DOING_AJAX' ) + && + DOING_AJAX + && + !did_action( 'p2_ajax' ) + && + !did_action( 'wp_ajax_json_quickpress_post' ) + && + !did_action( 'wp_ajax_instapost_publish' ) + && + !did_action( 'wp_ajax_post_reblog' ) + ) { + $submit_post = false; + } + + // - bulk edit + if ( isset( $_GET['bulk_edit'] ) ) + $submit_post = false; + + // - API/XML-RPC Test Posts + if ( + ( + defined( 'XMLRPC_REQUEST' ) + && + XMLRPC_REQUEST + || + defined( 'APP_REQUEST' ) + && + APP_REQUEST + ) + && + 0 === strpos( $post->post_title, 'Temporary Post Used For Theme Detection' ) + ) { + $submit_post = false; + } + + // only work with certain statuses (avoids inherits, auto drafts etc) + if ( !in_array( $post->post_status, array( 'publish', 'draft', 'future' ) ) ) + $submit_post = false; + + // don't publish password protected posts + if ( '' !== $post->post_password ) + $submit_post = false; + + // Did this request happen via wp-admin? + $from_web = 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST[$this->ADMIN_PAGE] ); + + if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && !empty( $_POST['wpas_title'] ) ) + update_post_meta( $post_id, $this->POST_MESS, trim( stripslashes( $_POST['wpas_title'] ) ) ); + + // change current user to provide context for get_services() if we're running during cron + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + $cron_user = (int) $GLOBALS['user_ID']; + wp_set_current_user( $post->post_author ); + } + + /** + * In this phase, we mark connections that we want to SKIP. When Publicize is actually triggered, + * it will Publicize to everything *except* those marked for skipping. + */ + foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) { + foreach ( $connections as $connection ) { + if ( false == apply_filters( 'wpas_submit_post?', $submit_post, $post_id, $service_name ) ) { + delete_post_meta( $post_id, $this->PENDING ); + continue; + } + + if ( !empty( $connection->unique_id ) ) + $unique_id = $connection->unique_id; + else if ( !empty( $connection['connection_data']['token_id'] ) ) + $unique_id = $connection['connection_data']['token_id']; + + // This was a wp-admin request, so we need to check the state of checkboxes + if ( $from_web ) { + // We *unchecked* this stream from the admin page, or it's set to readonly, or it's a new addition + if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$unique_id] ) ) { + // Also make sure that the service-specific input isn't there. + // If the user connected to a new service 'in-page' then a hidden field with the service + // name is added, so we just assume they wanted to Publicize to that service. + if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$service_name] ) ) { + // Nothing seems to be checked, so we're going to mark this one to be skipped + update_post_meta( $post_id, $this->POST_SKIP . $unique_id, 1 ); + continue; + } + } else { + // The checkbox for this connection is explicitly checked -- make sure we DON'T skip it + delete_post_meta( $post_id, $this->POST_SKIP . $unique_id ); + } + } + + // Users may hook in here and do anything else they need to after meta is written, + // and before the post is processed for Publicize. + do_action( 'publicize_save_meta', $submit_post, $post_id, $service_name, $connection ); + } + } + + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + wp_set_current_user( $cron_user ); + } + + // Next up will be ::publicize_post() + } +} diff --git a/plugins/jetpack/modules/publicize/ui.php b/plugins/jetpack/modules/publicize/ui.php new file mode 100644 index 00000000..d9d2b37c --- /dev/null +++ b/plugins/jetpack/modules/publicize/ui.php @@ -0,0 +1,545 @@ +<?php + +/** +* Only user facing pieces of Publicize are found here. +*/ +class Publicize_UI { + + /** + * Contains an instance of class 'publicize' which loads Keyring, sets up services, etc. + */ + var $publicize; + + /** + * Hooks into WordPress to display the various pieces of UI and load our assets + */ + function __construct() { + global $publicize; + + $this->publicize = $publicize = new Publicize; + + // assets (css, js) + add_action( 'load-settings_page_sharing', array( &$this, 'load_assets' ) ); + add_action( 'admin_head-post.php', array( &$this, 'post_page_metabox_assets' ) ); + add_action( 'admin_head-post-new.php', array( &$this, 'post_page_metabox_assets' ) ); + + // management of publicize (sharing screen, ajax/lightbox popup, and metabox on post screen) + add_action( 'pre_admin_screen_sharing', array( &$this, 'admin_page' ) ); + add_action( 'post_submitbox_misc_actions', array( &$this, 'post_page_metabox' ) ); + } + + /** + * If the ShareDaddy plugin is not active we need to add the sharing settings page to the menu still + */ + function sharing_menu() { + add_submenu_page( 'options-general.php', __( 'Sharing Settings', 'jetpack' ), __( 'Sharing', 'jetpack' ), 'publish_posts', 'sharing', array( &$this, 'management_page' ) ); + } + + + /** + * Management page to load if Sharedaddy is not active so the 'pre_admin_screen_sharing' action exists. + */ + function management_page() { ?> + <div class="wrap"> + <div class="icon32" id="icon-options-general"><br /></div> + <h2><?php _e( 'Sharing Settings', 'jetpack' ); ?></h2> + + <?php do_action( 'pre_admin_screen_sharing' ) ?> + + </div> <?php + } + + /** + * styling for the sharing screen and popups + * JS for the options and switching + */ + function load_assets() { + wp_enqueue_script( + 'publicize', + plugins_url( 'assets/publicize.js', __FILE__ ), + array( 'jquery', 'thickbox' ), + '20121019' + ); + + wp_enqueue_style( + 'publicize', + plugins_url( 'assets/publicize.css', __FILE__ ), + array(), + '20120925' + ); + + add_thickbox(); + } + + function connected_notice( $service_name ) { ?> + <div class='updated'> + <p><?php printf( __( 'You have successfully connected your blog with your %s account.', 'jetpack' ), Publicize::get_service_label( $service_name ) ); ?></p> + </div><?php + } + + /** + * Lists the current user's publicized accounts for the blog + * looks exactly like Publicize v1 for now, UI and functionality updates will come after the move to keyring + */ + function admin_page() { + $_blog_id = get_current_blog_id(); + ?> + + <form action="" id="publicize-form"> + <h3 id="publicize"><?php _e( 'Publicize', 'jetpack' ) ?></h3> + <p> + <?php esc_html_e( 'Connect your blog to popular social networking sites and automatically share new posts with your friends.', 'jetpack' ) ?> + <?php esc_html_e( 'You can make a connection for just yourself or for all users on your blog. Shared connections are marked with the (Shared) text.', 'jetpack' ); ?> + </p> + + <?php + if ( $this->in_jetpack ) + $doc_link = "http://jetpack.me/support/publicize/"; + else + $doc_link = "http://en.support.wordpress.com/publicize/"; + ?> + + <p>→ <a href="<?php echo esc_url( $doc_link ); ?>"><?php esc_html_e( 'More information on using Publicize.', 'jetpack' ); ?></a></p> + + <div id="publicize-services-block"> + <?php + foreach ( $this->publicize->get_services( 'all' ) as $name => $service ) : + $connect_url = $this->publicize->connect_url( $name ); + ?> + <div class="publicize-service-entry"> + <div id="<?php echo esc_attr( $name ); ?>" class="publicize-service-left"> + <a href="<?php echo esc_url( $connect_url ); ?>"><span class="pub-logos" id="<?php echo esc_attr( $name ); ?>"> </span></a> + </div> + + <div class="publicize-service-right"> + <?php if ( $this->publicize->is_enabled( $name ) && $connections = $this->publicize->get_connections( $name ) ) : ?> + <ul> + <?php + foreach( $connections as $c ) : + $id = $this->publicize->get_connection_id( $c ); + $disconnect_url = $this->publicize->disconnect_url( $name, $id ); + + $cmeta = $this->publicize->get_connection_meta( $c ); + $profile_link = $this->publicize->get_profile_link( $name, $c ); + $connection_display = $this->publicize->get_display_name( $name, $c ); + + $options_nonce = wp_create_nonce( 'options_page_' . $name . '_' . $id ); ?> + + <?php if ( $this->publicize->show_options_popup( $name, $c ) ): ?> + <script type="text/javascript"> + jQuery(document).ready( function($) { + showOptionsPage.call( + this, + '<?php echo esc_js( $name ); ?>', + '<?php echo esc_js( $options_nonce ); ?>', + '<?php echo esc_js( $id ); ?>' + ); + } ); + </script> + <?php endif; ?> + + <li> + <?php + if ( !empty( $profile_link ) ) : ?> + <a class="publicize-profile-link" href="<?php echo esc_url( $profile_link ); ?>"> + <?php echo esc_html( $connection_display ); ?> + </a><?php + else : + echo esc_html( $connection_display ); + endif; + ?> + + <?php if ( 0 == $cmeta['connection_data']['user_id'] ) : ?> + <small>(<?php esc_html_e( 'Shared', 'jetpack' ); ?>)</small> + + <?php if ( current_user_can( Publicize::GLOBAL_CAP ) ) : ?> + <a class="pub-disconnect-button" title="<?php esc_html_e( 'Disconnect', 'jetpack' ); ?>" href="<?php echo esc_url( $disconnect_url ); ?>">×</a> + <?php endif; ?> + + <?php else : ?> + <a class="pub-disconnect-button" title="<?php esc_html_e( 'Disconnect', 'jetpack' ); ?>" href="<?php echo esc_url( $disconnect_url ); ?>">×</a> + <?php endif; ?> + </li> + + <?php + endforeach; + ?> + </ul> + <?php endif; ?> + <a id="<?php echo esc_attr( $name ); ?>" class="publicize-add-connection" href="<?php echo esc_url( $connect_url); ?>"><?php echo esc_html( sprintf( __( 'Add new %s connection.', 'jetpack' ), $this->publicize->get_service_label( $name ) ) ); ?></a> + </div> + </div> + <?php endforeach; ?> + </div> + + <?php wp_nonce_field( "wpas_posts_{$_blog_id}", "_wpas_posts_{$_blog_id}_nonce" ); ?> + <input type="hidden" id="wpas_ajax_blog_id" name="wpas_ajax_blog_id" value="<?php echo $_blog_id; ?>" /> + </form><?php + + } + + function global_checkbox( $service_name, $id ) { + if ( current_user_can( Publicize::GLOBAL_CAP ) ) : ?> + <p> + <input id="globalize_<?php echo $service_name; ?>" type="checkbox" name="global" value="<?php echo wp_create_nonce( 'publicize-globalize-' . $id ) ?>" /> + <label for="globalize_<?php echo $service_name; ?>"><?php _e( 'Make this connection available to all users of this blog?', 'jetpack' ); ?></label> + </p> + <?php endif; + } + + function broken_connection( $service_name, $id ) { ?> + <div id="thickbox-content"> + <div class='error'> + <p><?php printf( __( 'There was a problem connecting to %s. Please disconnect and try again.', 'jetpack' ), Publicize::get_service_label( $service_name ) ); ?></p> + </div> + </div><?php + } + + function options_page_other( $service_name ) { + // Nonce check + check_admin_referer( "options_page_{$service_name}_" . $_REQUEST['connection'] ); + ?> + <div id="thickbox-content"> + <?php + ob_start(); + Publicize_UI::connected_notice( $service_name ); + $update_notice = ob_get_clean(); + if ( ! empty( $update_notice ) ) + echo $update_notice; + ?> + + <?php Publicize_UI::global_checkbox( $service_name, $_REQUEST['connection'] ); ?> + + <p style="text-align: center;"> + <input type="submit" value="<?php esc_attr_e( 'OK', 'jetpack' ) ?>" class="button <?php echo $service_name; ?>-options save-options" name="save" data-connection="<?php echo esc_attr( $_REQUEST['connection'] ); ?>" rel="<?php echo wp_create_nonce( 'save_'.$service_name.'_token_' . $_REQUEST['connection'] ) ?>" /> + </p> <br /> + </div> + <?php + } + + /** + * CSS for styling the publicize message box and counter that displays on the post page. + * There is also some Javascript for length counting and some basic display effects. + */ + function post_page_metabox_assets() { + global $post; + $user_id = empty( $post->post_author ) ? $GLOBALS['user_ID'] : $post->post_author; + + $default_prefix = $this->publicize->default_prefix; + $default_prefix = preg_replace( '/%([0-9])\$s/', "' + %\\1\$s + '", esc_js( $default_prefix ) ); + + $default_message = $this->publicize->default_message; + $default_message = preg_replace( '/%([0-9])\$s/', "' + %\\1\$s + '", esc_js( $default_message ) ); + + $default_suffix = $this->publicize->default_suffix; + $default_suffix = preg_replace( '/%([0-9])\$s/', "' + %\\1\$s + '", esc_js( $default_suffix ) ); ?> + +<script type="text/javascript"> +jQuery( function($) { + var wpasTitleCounter = $( '#wpas-title-counter' ), + wpasTwitterCheckbox = $( '.wpas-submit-twitter' ).size(), + wpasTitle = $('#wpas-title').keyup( function() { + var length = wpasTitle.val().length; + wpasTitleCounter.text( length ); + if ( wpasTwitterCheckbox && length > 140 ) { + wpasTitleCounter.addClass( 'wpas-twitter-length-limit' ); + } else { + wpasTitleCounter.removeClass( 'wpas-twitter-length-limit' ); + } + } ), + authClick = false; + + $('#publicize-disconnected-form-show').click( function() { + $('#publicize-form').slideDown( 'fast' ); + $(this).hide(); + } ); + + $('#publicize-disconnected-form-hide').click( function() { + $('#publicize-form').slideUp( 'fast' ); + $('#publicize-disconnected-form-show').show(); + } ); + + $('#publicize-form-edit').click( function() { + $('#publicize-form').slideDown( 'fast', function() { + wpasTitle.focus(); + if ( !wpasTitle.text() ) { + var url = $('#shortlink').size() ? $('#shortlink').val() : ''; + + var defaultMessage = $.trim( '<?php printf( $default_prefix, 'url' ); printf( $default_message, '$("#title").val()', 'url' ); printf( $default_suffix, 'url' ); ?>' ); + + wpasTitle.append( defaultMessage.replace( /<[^>]+>/g,'') ); + + var selBeg = defaultMessage.indexOf( $("#title").val() ); + if ( selBeg < 0 ) { + selBeg = 0; + selEnd = 0; + } else { + selEnd = selBeg + $("#title").val().length; + } + + var domObj = wpasTitle.get(0); + if ( domObj.setSelectionRange ) { + domObj.setSelectionRange( selBeg, selEnd ); + } else if ( domObj.createTextRange ) { + var r = domObj.createTextRange(); + r.moveStart( 'character', selBeg ); + r.moveEnd( 'character', selEnd ); + r.select(); + } + } + wpasTitle.keyup(); + } ); + $('#publicize-defaults').hide(); + $(this).hide(); + return false; + } ); + + $('#publicize-form-hide').click( function() { + var newList = $.map( $('#publicize-form').slideUp( 'fast' ).find( ':checked' ), function( el ) { + return $.trim( $(el).parent( 'label' ).text() ); + } ); + $('#publicize-defaults').html( '<strong>' + newList.join( '</strong>, <strong>' ) + '</strong>' ).show(); + $('#publicize-form-edit').show(); + return false; + } ); + + $('.authorize-link').click( function() { + if ( authClick ) { + return false; + } + authClick = true; + $(this).after( '<img src="images/loading.gif" class="alignleft" style="margin: 0 .5em" />' ); + $.ajaxSetup( { async: false } ); + autosave(); + return true; + } ); + + $( '.pub-service' ).click( function() { + var service = $(this).data( 'service' ), + fakebox = '<input id="wpas-submit-' + service + '" type="hidden" value="1" name="wpas[submit][' + service + ']" />'; + $( '#add-publicize-check' ).append( fakebox ); + } ); +} ); +</script> + +<style type="text/css"> +#publicize { + line-height: 1.5; +} +#publicize ul { + margin: 4px 0 4px 6px; +} +#publicize li { + margin: 0; +} +#publicize textarea { + margin: 4px 0 0; + width: 100% +} +#publicize ul.not-connected { + list-style: square; + padding-left: 1em; +} +.post-new-php .authorize-link, .post-php .authorize-link { + line-height: 1.5em; +} +.post-new-php .authorize-message, .post-php .authorize-message { + margin-bottom: 0; +} +#poststuff #publicize .updated p { + margin: .5em 0; +} +.wpas-twitter-length-limit { + color: red; +} +</style><?php + } + + /** + * Controls the metabox that is displayed on the post page + * Allows the user to customize the message that will be sent out to the social network, as well as pick which + * networks to publish to. Also displays the character counter and some other information. + */ + function post_page_metabox() { + global $post; + + if ( 'post' != $post->post_type ) + return; + + $user_id = empty( $post->post_author ) ? $GLOBALS['user_ID'] : $post->post_author; + $services = $this->publicize->get_services( 'connected' ); + $available_services = $this->publicize->get_services( 'all' ); + + if ( ! is_array( $available_services ) ) + $available_services = array(); + + if ( ! is_array( $services ) ) + $services = array(); + + $active = array(); ?> + + <div id="publicize" class="misc-pub-section misc-pub-section-last"> + <?php + _e( 'Publicize:', 'jetpack' ); + + if ( 0 < count( $services ) ) : + ob_start(); + ?> + + <div id="publicize-form" class="hide-if-js"> + <ul> + + <?php + // We can set an _all flag to indicate that this post is completely done as + // far as Publicize is concerned. Jetpack uses this approach. All published posts in Jetpack + // have Publicize disabled. + $all_done = get_post_meta( $post->ID, $this->publicize->POST_DONE . 'all', true ) || ( $this->in_jetpack && 'publish' == $post->post_status ); + + // We don't allow Publicizing to the same external id twice, to prevent spam + $service_id_done = (array) get_post_meta( $post->ID, $this->publicize->POST_SERVICE_DONE, true ); + + foreach ( $services as $name => $connections ) { + foreach ( $connections as $connection ) { + if ( !$continue = apply_filters( 'wpas_submit_post?', true, $post->ID, $name ) ) + continue; + + if ( !empty( $connection->unique_id ) ) + $unique_id = $connection->unique_id; + else if ( !empty( $connection['connection_data']['token_id'] ) ) + $unique_id = $connection['connection_data']['token_id']; + + // Should we be skipping this one? + $skip = ( + get_post_meta( $post->ID, $this->publicize->POST_SKIP . $unique_id, true ) + || + ( + is_array( $connection ) + && + ( + ( isset( $connection['meta']['external_id'] ) && ! empty( $service_id_done[ $name ][ $connection['meta']['external_id'] ] ) ) + || + // Jetpack's connection data looks a little different. + ( isset( $connection['external_id'] ) && ! empty( $service_id_done[ $name ][ $connection['external_id'] ] ) ) + ) + ) + ); + + // Was this connections (OR, old-format service) already Publicized to? + $done = ( 1 == get_post_meta( $post->ID, $this->publicize->POST_DONE . $unique_id, true ) || 1 == get_post_meta( $post->ID, $this->publicize->POST_DONE . $name, true ) ); // New and old style flags + + // If this one has already been publicized to, don't let it happen again + $disabled = ''; + if ( $done ) + $disabled = ' disabled="disabled"'; + + // If this is a global connection and this user doesn't have enough permissions to modify + // those connections, don't let them change it + $cmeta = $this->publicize->get_connection_meta( $connection ); + $hidden_checkbox = false; + if ( !$done && ( 0 == $cmeta['connection_data']['user_id'] && !current_user_can( Publicize::GLOBAL_CAP ) ) ) { + $disabled = ' disabled="disabled"'; + $hidden_checkbox = true; + } + + // Determine the state of the checkbox (on/off) and allow filtering + $checked = $skip != 1 || $done; + $checked = apply_filters( 'publicize_checkbox_default', $checked, $post->ID, $name, $connection ); + + // This post has been handled, so disable everything + if ( $all_done ) + $disabled = ' disabled="disabled"'; + + $label = sprintf( + _x( '%1$s: %2$s', 'Service: Account connected as', 'jetpack' ), + esc_html( $this->publicize->get_service_label( $name ) ), + esc_html( $this->publicize->get_display_name( $name, $connection ) ) + ); + if ( !$skip || $done ) { + $active[] = $label; + } + ?> + <li> + <label for="wpas-submit-<?php echo esc_attr( $unique_id ); ?>"> + <input type="checkbox" name="wpas[submit][<?php echo $unique_id; ?>]" id="wpas-submit-<?php echo $unique_id; ?>" class="wpas-submit-<?php echo $name; ?>" value="1" <?php + checked( true, $checked ); + echo $disabled; + ?> /> + <?php + if ( $hidden_checkbox ) { + // Need to submit a value to force a global connection to post + echo '<input type="hidden" name="wpas[submit][' . $unique_id . ']" value="1" />'; + } + echo esc_html( $label ); + ?> + </label> + </li> + <?php + } + } + + if ( $title = get_post_meta( $post->ID, $this->publicize->POST_MESS, true ) ) + $title = esc_html( $title ); + else + $title = ''; + ?> + + </ul> + + <label for="wpas-title"><?php _e( 'Custom Message:', 'jetpack' ); ?></label> + <span id="wpas-title-counter" class="alignright hide-if-no-js">0</span> + + <textarea name="wpas_title" id="wpas-title"<?php disabled( $all_done ); ?>><?php echo $title; ?></textarea> + + <a href="#" class="hide-if-no-js" id="publicize-form-hide"><?php _e( 'Hide', 'jetpack' ); ?></a> + <input type="hidden" name="wpas[0]" value="1" /> + + </div> <?php // #publicize-form + + $this->publicize->refresh_tokens_message(); + + $publicize_form = ob_get_clean(); + else : + echo " " . __( 'Not Connected', 'jetpack' ); + ob_start(); + ?> + + <div id="publicize-form" class="hide-if-js"> + <div id="add-publicize-check" style="display: none;"></div> + + <strong><?php _e( 'Connect to', 'jetpack' ); ?>:</strong> + + <ul class="not-connected"> + <?php foreach ( $available_services as $service_name => $service ) : ?> + <li> + <a class="pub-service" data-service="<?php echo esc_attr( $service_name ); ?>" title="<?php echo esc_attr( sprintf( __( 'Connect and share your posts on %s', 'jetpack' ), $this->publicize->get_service_label( $service_name ) ) ); ?>" target="_blank" href="<?php echo $this->publicize->connect_url( $service_name ); ?>"> + <?php echo esc_html( $this->publicize->get_service_label( $service_name ) ); ?> + </a> + </li> + <?php endforeach; ?> + </ul> + + <?php if ( 0 < count( $services ) ) : ?> + <a href="#" class="hide-if-no-js" id="publicize-form-hide"><?php _e( 'Hide', 'jetpack' ); ?></a> + <?php else : ?> + <a href="#" class="hide-if-no-js" id="publicize-disconnected-form-hide"><?php _e( 'Hide', 'jetpack' ); ?></a> + <?php endif; ?> + </div> <?php // #publicize-form + + $publicize_form = ob_get_clean(); + endif; + ?> + + <span id="publicize-defaults"><strong><?php echo join( '</strong>, <strong>', array_map( 'esc_html', $active ) ); ?></strong></span> + + <?php if ( 0 < count( $services ) ) : ?> + <a href="#" id="publicize-form-edit"><?php _e( 'Edit', 'jetpack' ); ?></a> <a href="<?php echo admin_url( 'options-general.php?page=sharing' ); ?>" target="_blank"><?php _e( 'Settings', 'jetpack' ); ?></a><br /> + <?php else : ?> + <a href="#" id="publicize-disconnected-form-show"><?php _e( 'Show', 'jetpack' ); ?></a><br /> + <?php endif; ?> + + <?php echo apply_filters( 'publicize_form', $publicize_form ); ?> + + </div> <?php // #publicize + } + +} diff --git a/plugins/jetpack/modules/sharedaddy.php b/plugins/jetpack/modules/sharedaddy.php index fa7b36a1..f6f4ad6e 100644 --- a/plugins/jetpack/modules/sharedaddy.php +++ b/plugins/jetpack/modules/sharedaddy.php @@ -18,6 +18,6 @@ function sharedaddy_loaded() { } function sharedaddy_configuration_load() { - wp_safe_redirect( menu_page_url( 'sharing', false ) ); + wp_safe_redirect( menu_page_url( 'sharing', false ) . "#sharing-buttons" ); exit; } diff --git a/plugins/jetpack/modules/sharedaddy/admin-sharing.css b/plugins/jetpack/modules/sharedaddy/admin-sharing.css index fd0e3d43..aea30792 100644 --- a/plugins/jetpack/modules/sharedaddy/admin-sharing.css +++ b/plugins/jetpack/modules/sharedaddy/admin-sharing.css @@ -95,54 +95,6 @@ -@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) { - - .services ul li#facebook, #available-services .preview-facebook div.option-smart-off{background: #FFF url(images/facebook@2x.png) no-repeat 4px 5px;} - .services ul li#twitter, #available-services .preview-twitter div.option-smart-off{background: #FFF url(images/twitter@2x.png?1) no-repeat 4px 5px;} - .services ul li#wordpress, #available-services .preview-wordpress{background: #FFF url(images/wordpress@2x.png?1) no-repeat 4px 5px;} - .services ul li#digg, #available-services .preview-digg div.option-smart-off{background: #FFF url(images/digg@2x.png) no-repeat 4px 5px;} - .services ul li#reddit, #available-services .preview-reddit div.option-smart-off{background: #FFF url(images/reddit@2x.png) no-repeat 4px 5px;} - .services ul li#stumbleupon, #available-services .preview-stumbleupon div.option-smart-off{background: #FFF url(images/stumbleupon@2x.png) no-repeat 4px 5px;} - .services ul li#email, #available-services .preview-email{background: #FFF url(images/email@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li#print, #available-services .preview-print{background: #FFF url(images/print@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li#press-this, #available-services .preview-press-this{background: #FFF url(images/wordpress@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li#linkedin, #available-services .preview-linkedin div.option-smart-off{background: #FFF url(images/linkedin@2x.png) no-repeat 4px 5px;} - .services ul li#tumblr,#available-services .preview-tumblr{background: #FFF url(images/tumblr@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li#google-plus-1,#available-services .preview-google-plus-1{background: #FFF url(images/googleplus1@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li#pinterest,#available-services .preview-pinterest{background: #FFF url(images/pinterest@2x.png) no-repeat 4px 5px; padding-right: 10px;} - .services ul li.share-custom, #available-services .preview-custom{background: #FFF url(images/custom@2x.png) no-repeat 4px 5px; no-repeat 4px 5px; padding-right: 10px;} - - .services ul li#facebook, #available-services .preview-facebook div.option-smart-off, - .services ul li#twitter, #available-services .preview-twitter div.option-smart-off, - .services ul li#wordpress, #available-services .preview-wordpress, - .services ul li#digg, #available-services .preview-digg div.option-smart-off, - .services ul li#reddit, #available-services .preview-reddit div.option-smart-off, - .services ul li#stumbleupon, #available-services .preview-stumbleupon div.option-smart-off, - .services ul li#email, #available-services .preview-email, - .services ul li#print, #available-services .preview-print, - .services ul li#press-this, #available-services .preview-press-this, - .services ul li#linkedin, #available-services .preview-linkedin div.option-smart-off, - .services ul li#tumblr,#available-services .preview-tumblr, - .services ul li#google-plus-1,#available-services .preview-google-plus-1, - .services ul li#pinterest,#available-services .preview-pinterest, - .services ul li.share-custom, #available-services .preview-custom{ - background-size: 16px 16px; - } - - - - - - - - - - - - -} - - /****************************************************************************/ .preview li.preview-item { @@ -165,6 +117,7 @@ .preview-digg .option-smart-on { background: #FFF url(images/smart-digg.png) no-repeat top left; + background-size: 76px 17px; width:76px; height:17px; margin-top: 2px; @@ -172,12 +125,14 @@ .preview-reddit .option-smart-on { background: #FFF url(images/smart-reddit.png) no-repeat top left; + background-size: 104px 21px; width:104px; height:21px; } .preview-stumbleupon .option-smart-on { background: #FFF url(images/smart-stumbleupon.png) no-repeat top left; + background-size: 74px 18px; width: 74px; height: 18px; margin-top: 1px; @@ -185,44 +140,47 @@ .preview-facebook .option-smart-on { background: #FFF url(images/smart-like.png) no-repeat top left; + background-size: 50px 20px; width:50px; height:20px; } .preview-twitter .option-smart-on { background: #FFF url(images/smart-twitter.png?1) no-repeat top left; + background-size: 92px 20px; width:92px; height:20px; } .preview-linkedin .option-smart-on { background: #FFF url(images/linkedin-smart.png) no-repeat top center; + background-size: 99px 18px; width:99px; - height:22px; + height:18px; margin-top: 1px; } .preview-google-plus-1 .option-smart-on { background: #FFF url(images/smart-googleplus1.png) no-repeat top left; - max-width: 70px; + background-size: 60px 20px; width: 60px; height: 20px; } .preview-tumblr .option-smart-on { background: #FFF url(images/smart-tumblr.png) no-repeat top left; + background-size: 62px 20px; width: 62px; height: 20px; } .preview-pinterest .option-smart-on { background: #FFF url(images/smart-pinterest.png) no-repeat top left; + background-size: 58px 20px; width: 58px; height: 20px; } -div.sharedaddy .preview-google-plus-1 .no-text { width: 60px; } - .services .preview li.share-custom { border-radius: 6px; -moz-border-radius: 6px; @@ -390,3 +348,81 @@ div.sharedaddy .preview-google-plus-1 .no-text { width: 60px; } #services-config a.remove:hover { background: #f00; } + + + + +@media print, + (-o-min-device-pixel-ratio: 5/4), + (-webkit-min-device-pixel-ratio: 1.25), + (min-resolution: 120dpi) { + + .services ul li#facebook, #available-services .preview-facebook div.option-smart-off{background: #FFF url(images/facebook@2x.png) no-repeat 4px 5px;} + .services ul li#twitter, #available-services .preview-twitter div.option-smart-off{background: #FFF url(images/twitter@2x.png?1) no-repeat 4px 5px;} + .services ul li#wordpress, #available-services .preview-wordpress{background: #FFF url(images/wordpress@2x.png?1) no-repeat 4px 5px;} + .services ul li#digg, #available-services .preview-digg div.option-smart-off{background: #FFF url(images/digg@2x.png) no-repeat 4px 5px;} + .services ul li#reddit, #available-services .preview-reddit div.option-smart-off{background: #FFF url(images/reddit@2x.png) no-repeat 4px 5px;} + .services ul li#stumbleupon, #available-services .preview-stumbleupon div.option-smart-off{background: #FFF url(images/stumbleupon@2x.png) no-repeat 4px 5px;} + .services ul li#email, #available-services .preview-email{background: #FFF url(images/email@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li#print, #available-services .preview-print{background: #FFF url(images/print@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li#press-this, #available-services .preview-press-this{background: #FFF url(images/wordpress@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li#linkedin, #available-services .preview-linkedin div.option-smart-off{background: #FFF url(images/linkedin@2x.png) no-repeat 4px 5px;} + .services ul li#tumblr,#available-services .preview-tumblr{background: #FFF url(images/tumblr@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li#google-plus-1,#available-services .preview-google-plus-1{background: #FFF url(images/googleplus1@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li#pinterest,#available-services .preview-pinterest{background: #FFF url(images/pinterest@2x.png) no-repeat 4px 5px; padding-right: 10px;} + .services ul li.share-custom, #available-services .preview-custom{background: #FFF url(images/custom@2x.png) no-repeat 4px 5px; no-repeat 4px 5px; padding-right: 10px;} + + .services ul li#facebook, #available-services .preview-facebook div.option-smart-off, + .services ul li#twitter, #available-services .preview-twitter div.option-smart-off, + .services ul li#wordpress, #available-services .preview-wordpress, + .services ul li#digg, #available-services .preview-digg div.option-smart-off, + .services ul li#reddit, #available-services .preview-reddit div.option-smart-off, + .services ul li#stumbleupon, #available-services .preview-stumbleupon div.option-smart-off, + .services ul li#email, #available-services .preview-email, + .services ul li#print, #available-services .preview-print, + .services ul li#press-this, #available-services .preview-press-this, + .services ul li#linkedin, #available-services .preview-linkedin div.option-smart-off, + .services ul li#tumblr,#available-services .preview-tumblr, + .services ul li#google-plus-1,#available-services .preview-google-plus-1, + .services ul li#pinterest,#available-services .preview-pinterest, + .services ul li.share-custom, #available-services .preview-custom{ + background-size: 16px 16px; + } + + .preview-digg .option-smart-on { + background-image: url(images/smart-digg@2x.png); + } + + .preview-reddit .option-smart-on { + background-image: url(images/smart-reddit@2x.png); + } + + .preview-stumbleupon .option-smart-on { + background-image: url(images/smart-stumbleupon@2x.png); + } + + .preview-facebook .option-smart-on { + background-image: url(images/smart-like@2x.png); + } + + .preview-twitter .option-smart-on { + background-image: url(images/smart-twitter@2x.png); + } + + .preview-linkedin .option-smart-on { + background-image: url(images/linkedin-smart@2x.png); + } + + .preview-google-plus-1 .option-smart-on { + background-image: url(images/smart-googleplus1@2x.png); + } + + .preview-tumblr .option-smart-on { + background-image: url(images/smart-tumblr@2x.png); + } + + .preview-pinterest .option-smart-on { + background-image: url(images/smart-pinterest@2x.png); + } + +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-facebook-2x.png b/plugins/jetpack/modules/sharedaddy/images/icon-facebook-2x.png Binary files differnew file mode 100644 index 00000000..a5c71943 --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-facebook-2x.png diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-facebook.png b/plugins/jetpack/modules/sharedaddy/images/icon-facebook.png Binary files differnew file mode 100644 index 00000000..de6a8a14 --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-facebook.png diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-twitter-2x.png b/plugins/jetpack/modules/sharedaddy/images/icon-twitter-2x.png Binary files differnew file mode 100644 index 00000000..85f2f34c --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-twitter-2x.png diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-twitter.png b/plugins/jetpack/modules/sharedaddy/images/icon-twitter.png Binary files differnew file mode 100644 index 00000000..ec41046e --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-twitter.png diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-wordpress-2x.png b/plugins/jetpack/modules/sharedaddy/images/icon-wordpress-2x.png Binary files differnew file mode 100644 index 00000000..4d81cabc --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-wordpress-2x.png diff --git a/plugins/jetpack/modules/sharedaddy/images/icon-wordpress.png b/plugins/jetpack/modules/sharedaddy/images/icon-wordpress.png Binary files differnew file mode 100644 index 00000000..86f1bd35 --- /dev/null +++ b/plugins/jetpack/modules/sharedaddy/images/icon-wordpress.png diff --git a/plugins/jetpack/modules/sharedaddy/sharedaddy.php b/plugins/jetpack/modules/sharedaddy/sharedaddy.php index 727005e7..ab8085ca 100644 --- a/plugins/jetpack/modules/sharedaddy/sharedaddy.php +++ b/plugins/jetpack/modules/sharedaddy/sharedaddy.php @@ -20,21 +20,27 @@ function sharing_email_send_post( $data ) { function sharing_add_meta_box() { $post_types = get_post_types( array( 'public' => true ) ); - + $title = apply_filters( 'sharing_meta_box_title', __( 'Sharing', 'jetpack' ) ); foreach( $post_types as $post_type ) { - add_meta_box( 'sharing_meta', __( 'Sharing', 'jetpack' ), 'sharing_meta_box_content', $post_type, 'advanced', 'high' ); + add_meta_box( 'sharing_meta', $title, 'sharing_meta_box_content', $post_type, 'advanced', 'high' ); } } function sharing_meta_box_content( $post ) { - $sharing_checked = get_post_meta( $post->ID, 'sharing_disabled', false ); + do_action( 'start_sharing_meta_box_content', $post ); + + $disabled = get_post_meta( $post->ID, 'sharing_disabled', true ); ?> - if ( empty( $sharing_checked ) || $sharing_checked === false ) - $sharing_checked = ' checked="checked"'; - else - $sharing_checked = ''; + <p> + <label for="enable_post_sharing"> + <input type="checkbox" name="enable_post_sharing" id="enable_post_sharing" value="1" <?php checked( !$disabled ); ?>> + <?php _e( 'Show sharing buttons.' , 'jetpack'); ?> + </label> + <input type="hidden" name="sharing_status_hidden" value="1" /> + </p> - echo '<p><label for="enable_post_sharing"><input name="enable_post_sharing" id="enable_post_sharing" value="1"' . $sharing_checked . ' type="checkbox"> ' . __( 'Show sharing buttons on this post.', 'jetpack' ) . '</label><input type="hidden" name="sharing_status_hidden" value="1" /></p>'; + <?php + do_action( 'end_sharing_meta_box_content', $post ); } function sharing_meta_box_save( $post_id ) { @@ -101,10 +107,6 @@ function sharing_disable_js() { return false; } -if ( !function_exists( 'sharing_register_post_for_share_counts' ) ) { - function sharing_register_post_for_share_counts() {} -} - function sharing_global_resources() { $disable = get_option( 'sharedaddy_disable_resources' ); ?> @@ -133,20 +135,17 @@ function sharing_email_check( $true, $post, $data ) { return $recaptcha_result->is_valid; } -// Only run if PHP5 -if ( version_compare( phpversion(), '5.0', '>=' ) ) { - add_action( 'init', 'sharing_init' ); - add_action( 'admin_init', 'sharing_add_meta_box' ); - add_action( 'save_post', 'sharing_meta_box_save' ); - add_action( 'sharing_email_send_post', 'sharing_email_send_post' ); - add_action( 'sharing_global_options', 'sharing_global_resources' ); - add_action( 'sharing_admin_update', 'sharing_global_resources_save' ); - add_filter( 'sharing_services', 'sharing_restrict_to_single' ); - add_action( 'plugin_action_links_'.basename( dirname( __FILE__ ) ).'/'.basename( __FILE__ ), 'sharing_plugin_settings', 10, 4 ); - add_filter( 'plugin_row_meta', 'sharing_add_plugin_settings', 10, 2 ); - - if ( defined( 'RECAPTCHA_PRIVATE_KEY' ) ) { - add_action( 'sharing_email_dialog', 'sharing_email_dialog' ); - add_filter( 'sharing_email_check', 'sharing_email_check', 10, 3 ); - } -} +add_action( 'init', 'sharing_init' ); +add_action( 'admin_init', 'sharing_add_meta_box' ); +add_action( 'save_post', 'sharing_meta_box_save' ); +add_action( 'sharing_email_send_post', 'sharing_email_send_post' ); +add_action( 'sharing_global_options', 'sharing_global_resources', 30 ); +add_action( 'sharing_admin_update', 'sharing_global_resources_save' ); +add_filter( 'sharing_services', 'sharing_restrict_to_single' ); +add_action( 'plugin_action_links_'.basename( dirname( __FILE__ ) ).'/'.basename( __FILE__ ), 'sharing_plugin_settings', 10, 4 ); +add_filter( 'plugin_row_meta', 'sharing_add_plugin_settings', 10, 2 ); + +if ( defined( 'RECAPTCHA_PRIVATE_KEY' ) ) { + add_action( 'sharing_email_dialog', 'sharing_email_dialog' ); + add_filter( 'sharing_email_check', 'sharing_email_check', 10, 3 ); +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/sharedaddy/sharing-service.php b/plugins/jetpack/modules/sharedaddy/sharing-service.php index 82d7293d..c8efee33 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing-service.php +++ b/plugins/jetpack/modules/sharedaddy/sharing-service.php @@ -2,10 +2,15 @@ include_once dirname( __FILE__ ).'/sharing-sources.php'; -define( 'WP_SHARING_PLUGIN_VERSION', '0.3.1' ); +define( 'WP_SHARING_PLUGIN_VERSION', JETPACK__VERSION ); class Sharing_Service { private $global = false; + var $default_sharing_label = ''; + + public function __construct() { + $this->default_sharing_label = __( 'Share this:', 'jetpack' ); + } /** * Gets a generic list of all services, without any config @@ -15,11 +20,11 @@ class Sharing_Service { $all = $this->get_all_services(); $services = array(); - + foreach ( $all AS $id => $name ) { if ( isset( $all[$id] ) ) { $config = array(); - + // Pre-load custom modules otherwise they won't know who they are if ( substr( $id, 0, 7 ) == 'custom-' && is_array( $options[$id] ) ) $config = $options[$id]; @@ -30,7 +35,7 @@ class Sharing_Service { return $services; } - + /** * Gets a list of all available service names and classes */ @@ -50,29 +55,29 @@ class Sharing_Service { 'tumblr' => 'Share_Tumblr', 'pinterest' => 'Share_Pinterest', ); - + // Add any custom services in $options = $this->get_global_options(); foreach ( (array)$options['custom'] AS $custom_id ) { $services[$custom_id] = 'Share_Custom'; } - + return apply_filters( 'sharing_services', $services ); } - + public function new_service( $label, $url, $icon ) { // Validate $label = trim( wp_html_excerpt( wp_kses( $label, array() ), 30 ) ); $url = trim( esc_url_raw( $url ) ); $icon = trim( esc_url_raw( $icon ) ); - + if ( $label && $url && $icon ) { $options = get_option( 'sharing-options' ); if ( !is_array( $options ) ) $options = array(); - + $service_id = 'custom-'.time(); - + // Add a new custom service $options['global']['custom'][] = $service_id; @@ -85,23 +90,23 @@ class Sharing_Service { // Return the service return $service; } - + return false; } - + public function delete_service( $service_id ) { $options = get_option( 'sharing-options' ); if ( isset( $options[$service_id] ) ) unset( $options[$service_id] ); - + $key = array_search( $service_id, $options['global']['custom'] ); if ( $key !== false ) unset( $options['global']['custom'][$key] ); - + update_option( 'sharing-options', $options ); return true; } - + public function set_blog_services( array $visible, array $hidden ) { $services = $this->get_all_services(); // Validate the services @@ -113,12 +118,12 @@ class Sharing_Service { // Ensure we don't have the same ones in hidden and visible $hidden = array_diff( $hidden, $visible ); - - do_action( 'sharing_get_services_state', array( + + do_action( 'sharing_get_services_state', array( 'services' => $services, - 'available' => $available, - 'hidden' => $hidden, - 'visible' => $visible, + 'available' => $available, + 'hidden' => $hidden, + 'visible' => $visible, 'currently_enabled' => $this->get_blog_services() ) ); @@ -148,10 +153,10 @@ class Sharing_Service { // Cleanup after any filters that may have produced duplicate services $enabled['visible'] = array_unique( $enabled['visible'] ); $enabled['hidden'] = array_unique( $enabled['hidden'] ); - + // Form the enabled services $blog = array( 'visible' => array(), 'hidden' => array() ); - + foreach ( $blog AS $area => $stuff ) { foreach ( (array)$enabled[$area] AS $service ) { if ( isset( $services[$service] ) ) { @@ -170,7 +175,7 @@ class Sharing_Service { $blog['all'] = array_flip( array_merge( array_keys( $blog['visible'] ), array_keys( $blog['hidden'] ) ) ); return $blog; } - + public function get_service( $service_name ) { $services = $this->get_blog_services(); @@ -179,10 +184,10 @@ class Sharing_Service { if ( isset( $services['hidden'][$service_name] ) ) return $services['hidden'][$service_name]; - + return false; } - + public function set_global_options( $data ) { $options = get_option( 'sharing-options' ); @@ -193,20 +198,25 @@ class Sharing_Service { // Defaults $options['global'] = array( 'button_style' => 'icon-text', - 'sharing_label' => __( 'Share this:', 'jetpack' ), + 'sharing_label' => $this->default_sharing_label, 'open_links' => 'same', 'show' => array( 'post', 'page' ), 'custom' => isset( $options['global']['custom'] ) ? $options['global']['custom'] : array() ); - + $options['global'] = apply_filters( 'sharing_default_global', $options['global'] ); // Validate options and set from our data if ( isset( $data['button_style'] ) && in_array( $data['button_style'], array( 'icon-text', 'icon', 'text', 'official' ) ) ) $options['global']['button_style'] = $data['button_style']; - if ( isset( $data['sharing_label'] ) ) - $options['global']['sharing_label'] = trim( wp_kses( stripslashes( $data['sharing_label'] ), array() ) ); + if ( isset( $data['sharing_label'] ) ) { + if ( $this->default_sharing_label === $data['sharing_label'] ) { + $options['global']['sharing_label'] = false; + } else { + $options['global']['sharing_label'] = trim( wp_kses( stripslashes( $data['sharing_label'] ), array() ) ); + } + } if ( isset( $data['open_links'] ) && in_array( $data['open_links'], array( 'new', 'same' ) ) ) $options['global']['open_links'] = $data['open_links']; @@ -238,7 +248,7 @@ class Sharing_Service { update_option( 'sharing-options', $options ); return $options['global']; } - + public function get_global_options() { if ( $this->global === false ) { $options = get_option( 'sharing-options' ); @@ -247,7 +257,7 @@ class Sharing_Service { $this->global = $options['global']; else $this->global = $this->set_global_options( $options['global'] ); - } + } if ( ! isset( $this->global['show'] ) ) { $this->global['show'] = array( 'post', 'page' ); @@ -264,24 +274,29 @@ class Sharing_Service { break; } } + + if ( false === $this->global['sharing_label'] ) { + $this->global['sharing_label'] = $this->default_sharing_label; + } + return $this->global; } - + public function set_service( $id, Sharing_Source $service ) { // Update the options for this service $options = get_option( 'sharing-options' ); - + // No options yet if ( !is_array( $options ) ) $options = array(); - + do_action( 'sharing_get_button_state', array( 'id' => $id, 'options' => $options, 'service' => $service ) ); - + $options[$id] = $service->get_options(); update_option( 'sharing-options', array_filter( $options ) ); } - + // Soon to come to a .org plugin near you! public function get_total( $service_name = false, $post_id = false, $_blog_id = false ) { global $wpdb, $blog_id; @@ -297,40 +312,40 @@ class Sharing_Service { return (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d", $_blog_id ) ); } } - + if ( $post_id > 0 ) return (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND post_id = %d AND share_service = %s", $_blog_id, $post_id, $service_name ) ); else return (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND share_service = %s", $_blog_id, $service_name ) ); } - + public function get_services_total( $post_id = false ) { $totals = array(); $services = $this->get_blog_services(); - + if ( !empty( $services ) && isset( $services[ 'all' ] ) ) foreach( $services[ 'all' ] as $key => $value ) { $totals[$key] = new Sharing_Service_Total( $key, $this->get_total( $key, $post_id ) ); } usort( $totals, array( 'Sharing_Service_Total', 'cmp' ) ); - + return $totals; - } - + } + public function get_posts_total() { $totals = array(); global $wpdb, $blog_id; - + $my_data = $wpdb->get_results( $wpdb->prepare( "SELECT post_id as id, SUM( count ) as total FROM sharing_stats WHERE blog_id = %d GROUP BY post_id ORDER BY count DESC ", $blog_id ) ); - + if ( !empty( $my_data ) ) foreach( $my_data as $row ) $totals[] = new Sharing_Post_Total( $row->id, $row->total ); - + usort( $totals, array( 'Sharing_Post_Total', 'cmp' ) ); - + return $totals; - } + } } class Sharing_Service_Total { @@ -338,16 +353,16 @@ class Sharing_Service_Total { var $name = ''; var $service = ''; var $total = 0; - + public function Sharing_Service_Total( $id, $total ) { $services = new Sharing_Service(); $this->id = esc_html( $id ); $this->service = $services->get_service( $id ); $this->total = (int) $total; - + $this->name = $this->service->get_name(); } - + static function cmp( $a, $b ) { if ( $a->total == $b->total ) return $a->name < $b->name; @@ -360,14 +375,14 @@ class Sharing_Post_Total { var $total = 0; var $title = ''; var $url = ''; - + public function Sharing_Post_Total( $id, $total ) { $this->id = (int) $id; $this->total = (int) $total; - $this->title = get_the_title( $this->id ); - $this->url = get_permalink( $this->id ); + $this->title = get_the_title( $this->id ); + $this->url = get_permalink( $this->id ); } - + static function cmp( $a, $b ) { if ( $a->total == $b->total ) return $a->id < $b->id; @@ -375,16 +390,41 @@ class Sharing_Post_Total { } } +function sharing_register_post_for_share_counts( $post_id ) { + global $jetpack_sharing_counts; + + if ( ! isset( $jetpack_sharing_counts ) || ! is_array( $jetpack_sharing_counts ) ) + $jetpack_sharing_counts = array(); + + $jetpack_sharing_counts[ (int) $post_id ] = get_permalink( $post_id ); +} + function sharing_add_footer() { - if ( apply_filters( 'sharing_js', true ) ) + global $jetpack_sharing_counts; + + if ( apply_filters( 'sharing_js', true ) ) { + + if ( is_array( $jetpack_sharing_counts ) && count( $jetpack_sharing_counts ) ) : + $sharing_post_urls = array_filter( $jetpack_sharing_counts ); + if ( $sharing_post_urls ) : +?> + + <script type="text/javascript"> + WPCOM_sharing_counts = <?php echo json_encode( array_flip( $sharing_post_urls ) ); ?> + </script> +<?php + endif; + endif; + wp_print_scripts( 'sharing-js' ); - + } + $sharer = new Sharing_Service(); $enabled = $sharer->get_blog_services(); foreach ( array_merge( $enabled['visible'], $enabled['hidden'] ) AS $service ) { $service->display_footer(); } -} +} function sharing_add_header() { $sharer = new Sharing_Service(); @@ -393,9 +433,9 @@ function sharing_add_header() { foreach ( array_merge( $enabled['visible'], $enabled['hidden'] ) AS $service ) { $service->display_header(); } - + if ( count( $enabled['all'] ) > 0 ) - wp_enqueue_style( 'sharedaddy', plugin_dir_url( __FILE__ ) .'sharing.css', array(), WP_SHARING_PLUGIN_VERSION ); + wp_enqueue_style( 'sharedaddy', plugin_dir_url( __FILE__ ) .'sharing.css', array(), JETPACK__VERSION ); } add_action( 'wp_head', 'sharing_add_header', 1 ); @@ -409,7 +449,7 @@ function sharing_process_requests() { $service = $sharer->get_service( $_GET['share'] ); if ( $service ) { $service->process_request( $post, $_POST ); - } + } } } add_action( 'template_redirect', 'sharing_process_requests' ); @@ -421,10 +461,35 @@ function sharing_display( $text = '' ) { return $text; } + // Don't output flair on excerpts if ( in_array( 'get_the_excerpt', (array) $wp_current_filter ) ) { return $text; } + // Don't allow flair to be added to the_content more than once (prevent infinite loops) + $done = false; + foreach ( $wp_current_filter as $filter ) { + if ( 'the_content' == $filter ) { + if ( $done ) + return $text; + else + $done = true; + } + } + + // check whether we are viewing the front page and whether the front page option is checked + $options = get_option( 'sharing-options' ); + $display_options = $options['global']['show']; + + if ( is_front_page() && ( is_array( $display_options ) && ! in_array( 'index', $display_options ) ) ) + return $text; + + if ( is_attachment() && in_array( 'the_excerpt', (array) $wp_current_filter ) ) { + // Many themes run the_excerpt() conditionally on an attachment page, then run the_content(). + // We only want to output the sharing buttons once. Let's stick with the_content(). + return $text; + } + $sharer = new Sharing_Service(); $global = $sharer->get_global_options(); @@ -439,25 +504,25 @@ function sharing_display( $text = '' ) { // Pass through a filter for final say so $show = apply_filters( 'sharing_show', $show, $post ); - + // Disabled for this post? $switched_status = get_post_meta( $post->ID, 'sharing_disabled', false ); if ( !empty( $switched_status ) ) $show = false; - + // Allow to be used on P2 ajax requests for latest posts. if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_REQUEST['action'] ) && 'get_latest_posts' == $_REQUEST['action'] ) $show = true; - + $sharing_content = ''; - + if ( $show ) { $enabled = $sharer->get_blog_services(); if ( count( $enabled['all'] ) > 0 ) { global $post; - + $dir = get_option( 'text_direction' ); // Wrapper @@ -465,7 +530,7 @@ function sharing_display( $text = '' ) { if ( $global['sharing_label'] != '' ) $sharing_content .= '<h3 class="sd-title">' . $global['sharing_label'] . '</h3>'; $sharing_content .= '<div class="sd-content"><ul>'; - + // Visible items $visible = ''; foreach ( $enabled['visible'] as $id => $service ) { @@ -486,47 +551,47 @@ function sharing_display( $text = '' ) { if ( $dir == 'rtl' ) $parts = array_reverse( $parts ); - $sharing_content .= implode( '', $parts ); + $sharing_content .= implode( '', $parts ); $sharing_content .= '<li class="share-end"></li></ul>'; - + if ( count( $enabled['hidden'] ) > 0 ) { $sharing_content .= '<div class="sharing-hidden"><div class="inner" style="display: none;'; if ( count( $enabled['hidden'] ) == 1 ) $sharing_content .= 'width:150px;'; - + $sharing_content .= '">'; - + if ( count( $enabled['hidden'] ) == 1 ) $sharing_content .= '<ul style="background-image:none;">'; else $sharing_content .= '<ul>'; - + $count = 1; foreach ( $enabled['hidden'] as $id => $service ) { // Individual HTML for sharing service $sharing_content .= '<li class="share-'.$service->get_class().'">'; $sharing_content .= $service->get_display( $post ); $sharing_content .= '</li>'; - + if ( ( $count % 2 ) == 0 ) $sharing_content .= '<li class="share-end"></li>'; $count ++; } - + // End of wrapper $sharing_content .= '<li class="share-end"></li></ul></div></div>'; } $sharing_content .= '<div class="sharing-clear"></div></div></div></div>'; - + // Register our JS - wp_register_script( 'sharing-js', plugin_dir_url( __FILE__ ).'sharing.js', array( 'jquery' ), '20120131' ); + wp_register_script( 'sharing-js', plugin_dir_url( __FILE__ ).'sharing.js', array( 'jquery' ), '20121205' ); add_action( 'wp_footer', 'sharing_add_footer' ); } } - + return $text.$sharing_content; } diff --git a/plugins/jetpack/modules/sharedaddy/sharing-sources.php b/plugins/jetpack/modules/sharedaddy/sharing-sources.php index bd8b2eb9..256513cb 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing-sources.php +++ b/plugins/jetpack/modules/sharedaddy/sharing-sources.php @@ -5,7 +5,7 @@ abstract class Sharing_Source { public $smart; protected $open_links; protected $id; - + public function __construct( $id, array $settings ) { $this->id = $id; @@ -14,41 +14,49 @@ abstract class Sharing_Source { if ( isset( $settings['open_links'] ) ) $this->open_links = $settings['open_links']; - + if ( isset( $settings['smart'] ) ) $this->smart = $settings['smart']; } - + + public function http() { + return is_ssl() ? 'https' : 'http'; + } + public function get_id() { return $this->id; } - + public function get_class() { return $this->id; } - + + public function get_share_url( $post_id ) { + return apply_filters( 'sharing_permalink', get_permalink( $post_id ), $post_id, $this->id ); + } + public function has_custom_button_style() { return false; } - + public function get_link( $url, $text, $title, $query = '', $id = false ) { $klasses = array( 'share-'.$this->get_class(), 'sd-button' ); - + if ( $this->button_style == 'icon' || $this->button_style == 'icon-text' ) $klasses[] = 'share-icon'; - + if ( $this->button_style == 'icon' ) { $text = ''; $klasses[] = 'no-text'; } - + if ( !empty( $query ) ) { if ( stripos( $url, '?' ) === false ) $url .= '?'.$query; else $url .= '&'.$query; } - + if ( $this->button_style == 'text' ) $klasses[] = 'no-icon'; @@ -67,29 +75,29 @@ abstract class Sharing_Source { abstract public function get_display( $post ); public function display_header() { - } - + } + public function display_footer() { } - + public function has_advanced_options() { return false; } - + public function display_preview() { $text = ' '; if ( !$this->smart ) if ( $this->button_style != 'icon' ) $text = $this->get_name(); - + $klasses = array( 'share-'.$this->get_class(), 'sd-button' ); - + if ( $this->button_style == 'icon' || $this->button_style == 'icon-text' ) $klasses[] = 'share-icon'; - + if ( $this->button_style == 'icon' ) $klasses[] = 'no-text'; - + if ( $this->button_style == 'text' ) $klasses[] = 'no-icon'; @@ -107,39 +115,39 @@ abstract class Sharing_Source { public function get_total( $post = false ) { global $wpdb, $blog_id; - - $name = strtolower( $this->get_id() ); - + + $name = strtolower( $this->get_id() ); + if ( $post == false ) { // get total number of shares for service return (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND share_service = %s", $blog_id, $name ) ); } - + // get total shares for a post return (int) $wpdb->get_var( $wpdb->prepare( "SELECT count FROM sharing_stats WHERE blog_id = %d AND post_id = %d AND share_service = %s", $blog_id, $post->ID, $name ) ); - } - + } + public function get_posts_total() { global $wpdb, $blog_id; - + $totals = array(); - $name = strtolower( $this->get_id() ); - + $name = strtolower( $this->get_id() ); + $my_data = $wpdb->get_results( $wpdb->prepare( "SELECT post_id as id, SUM( count ) as total FROM sharing_stats WHERE blog_id = %d AND share_service = %s GROUP BY post_id ORDER BY count DESC ", $blog_id, $name ) ); - + if ( !empty( $my_data ) ) foreach( $my_data as $row ) $totals[] = new Sharing_Post_Total( $row->id, $row->total ); - + usort( $totals, array( 'Sharing_Post_Total', 'cmp' ) ); - + return $totals; } - + public function process_request( $post, array $post_data ) { do_action( 'sharing_bump_stats', array( 'service' => $this, 'post' => $post ) ); } - + public function js_dialog( $name, $params = array() ) { $defaults = array( 'menubar' => 1, @@ -154,9 +162,9 @@ abstract class Sharing_Source { } $opts = implode( ',', $opts ); ?> - <script type="text/javascript" charset="utf-8"> - jQuery(document).ready(function(){ - jQuery( '.share-<?php echo $name; ?>' ).click(function(){ + <script type="text/javascript"> + jQuery(document).on( 'ready post-load', function(){ + jQuery( 'a.share-<?php echo $name; ?>' ).on( 'click', function() { window.open( jQuery(this).attr( 'href' ), 'wpcom<?php echo $name; ?>', '<?php echo $opts; ?>' ); return false; }); @@ -170,7 +178,7 @@ abstract class Sharing_Advanced_Source extends Sharing_Source { public function has_advanced_options() { return true; } - + abstract public function display_options(); abstract public function update_options( array $data ); abstract public function get_options(); @@ -187,12 +195,12 @@ class Share_Email extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Email', 'jetpack' ); } - // Default does nothing + // Default does nothing public function process_request( $post, array $post_data ) { $ajax = false; if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest' ) @@ -205,10 +213,10 @@ class Share_Email extends Sharing_Source { if ( isset( $post_data['target_email'] ) && is_email( $post_data['target_email'] ) ) $target_email = $post_data['target_email']; - + if ( isset( $post_data['source_name'] ) ) $source_name = $post_data['source_name']; - + // Test email $error = 1; // Failure in data if ( $source_email && $target_email && $source_name ) { @@ -219,21 +227,21 @@ class Share_Email extends Sharing_Source { 'target' => $target_email, 'name' => $source_name ); - + if ( ( $data = apply_filters( 'sharing_email_can_send', $data ) ) !== false ) { // Record stats parent::process_request( $data['post'], $post_data ); do_action( 'sharing_email_send_post', $data ); } - + // Return a positive regardless of whether the user is subscribed or not if ( $ajax ) { ?> <div class="response"> - <div class="response-title"><?php _e( 'This post has been shared!', 'jetpack' ); ?></div> - <div class="response-sub"><?php printf( __( 'You have shared this post with %s', 'jetpack' ), esc_html( $target_email ) ); ?></div> - <div class="response-close"><a href="#" class="sharing_cancel"><?php _e( 'Close', 'jetpack' ); ?></a></div> + <div class="response-title"><?php _e( 'This post has been shared!', 'jetpack' ); ?></div> + <div class="response-sub"><?php printf( __( 'You have shared this post with %s', 'jetpack' ), esc_html( $target_email ) ); ?></div> + <div class="response-close"><a href="#" class="sharing_cancel"><?php _e( 'Close', 'jetpack' ); ?></a></div> </div> <?php } @@ -245,7 +253,7 @@ class Share_Email extends Sharing_Source { else $error = 2; // Email check failed } - + if ( $ajax ) echo $error; else @@ -253,24 +261,24 @@ class Share_Email extends Sharing_Source { die(); } - + public function get_display( $post ) { return $this->get_link( get_permalink( $post->ID ), _x( 'Email', 'share to', 'jetpack' ), __( 'Click to email this to a friend', 'jetpack' ), 'share=email' ); } - + /** * Outputs the hidden email dialog */ public function display_footer() { global $current_user; - + $visible = $status = false; ?> <div id="sharing_email" style="display: none;"> - <form action="" method="post"> + <form action="<?php echo esc_url( $_SERVER['REQUEST_URI'] ); ?>" method="post"> <label for="target_email"><?php _e( 'Send to Email Address', 'jetpack' ) ?></label> <input type="text" name="target_email" id="target_email" value="" /> - + <?php if ( is_user_logged_in() ) : ?> <input type="hidden" name="source_name" value="<?php echo esc_attr( $current_user->display_name ); ?>" /> <input type="hidden" name="source_email" value="<?php echo esc_attr( $current_user->user_email ); ?>" /> @@ -278,18 +286,18 @@ class Share_Email extends Sharing_Source { <label for="source_name"><?php _e( 'Your Name', 'jetpack' ) ?></label> <input type="text" name="source_name" id="source_name" value="" /> - + <label for="source_email"><?php _e( 'Your Email Address', 'jetpack' ) ?></label> <input type="text" name="source_email" id="source_email" value="" /> <?php endif; ?> - + <?php do_action( 'sharing_email_dialog', 'jetpack' ); ?> - <img style="float: right; display: none" class="loading" src="<?php echo plugin_dir_url( __FILE__ ) . 'images/loading.gif'; ?>" alt="loading" width="16" height="16" /> + <img style="float: right; display: none" class="loading" src="<?php echo apply_filters( 'jetpack_static_url', plugin_dir_url( __FILE__ ) . 'images/loading.gif' ); ?>" alt="loading" width="16" height="16" /> <input type="submit" value="<?php _e( 'Send Email', 'jetpack' ); ?>" class="sharing_send" /> <a href="#cancel" class="sharing_cancel"><?php _e( 'Cancel', 'jetpack' ); ?></a> - + <div class="errors errors-1" style="display: none;"> <?php _e( 'Post was not sent - check your email addresses!', 'jetpack' ); ?> </div> @@ -297,7 +305,7 @@ class Share_Email extends Sharing_Source { <div class="errors errors-2" style="display: none;"> <?php _e( 'Email check failed, please try again', 'jetpack' ); ?> </div> - + <div class="errors errors-3" style="display: none;"> <?php _e( 'Sorry, your blog cannot share posts by email.', 'jetpack' ); ?> </div> @@ -317,41 +325,61 @@ class Share_Twitter extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Twitter', 'jetpack' ); } function sharing_twitter_via( $post ) { - return ''; + // Allow themes to customize the via + return apply_filters( 'jetpack_sharing_twitter_via', '', $post->ID ); + } - // Default 'via' is always us. - $via = preg_replace( '/(https?:\/\/)|(\.)|(\/)/i', '', home_url() ); + public function get_related_accounts( $post ) { + // Format is 'username' => 'Optional description' + $related_accounts = apply_filters( 'jetpack_sharing_twitter_related', array(), $post->ID ); - // Allow themes to customize the via - return apply_filters( 'sharing_twitter_via', $via, $post->ID ); + // Example related string: account1,account2:Account 2 description,account3 + $related = array(); + + foreach ( $related_accounts as $related_account_username => $related_account_description ) { + // Join the description onto the end of the username + if ( $related_account_description ) + $related_account_username .= ':' . $related_account_description; + + $related[] = $related_account_username; + } + + return implode( ',', $related ); } public function get_display( $post ) { $via = $this->sharing_twitter_via( $post ); if ( $via ) { - $via = sprintf( '&via=%1$s', rawurlencode( $via ) ); + $via = '&via=' . rawurlencode( $via ); + + $related = $this->get_related_accounts( $post ); + if ( ! empty( $related ) && $related !== $via ) + $via .= '&related=' . rawurlencode( $related ); } else { $via = ''; } + + $share_url = $this->get_share_url( $post->ID ); + if ( $this->smart ) { - return '<div class="twitter_button"><iframe allowtransparency="true" frameborder="0" scrolling="no" src="' . esc_url( 'http://platform.twitter.com/widgets/tweet_button.html?url=' . rawurlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&counturl=' . rawurlencode( str_replace( 'https://', 'http://', get_permalink( $post->ID ) ) ) . '&count=horizontal&text=' . rawurlencode( $post->post_title . ':' ) . $via ) . '" style="width:101px; height:20px;"></iframe></div>'; + return '<div class="twitter_button"><iframe allowtransparency="true" frameborder="0" scrolling="no" src="' . esc_url( $this->http() . '://platform.twitter.com/widgets/tweet_button.html?url=' . rawurlencode( $share_url ) . '&counturl=' . rawurlencode( str_replace( 'https://', 'http://', get_permalink( $post->ID ) ) ) . '&count=horizontal&text=' . rawurlencode( $post->post_title . ':' ) . $via ) . '" style="width:101px; height:20px;"></iframe></div>'; } else { if ( 'icon-text' == $this->button_style || 'text' == $this->button_style ) sharing_register_post_for_share_counts( $post->ID ); return $this->get_link( get_permalink( $post->ID ), _x( 'Twitter', 'share to', 'jetpack' ), __( 'Click to share on Twitter', 'jetpack' ), 'share=twitter', 'sharing-twitter-' . $post->ID ); } } - + public function process_request( $post, array $post_data ) { $post_title = $post->post_title; - $post_link = apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ); + $post_link = $this->get_share_url( $post->ID ); if ( function_exists( 'mb_stripos' ) ) { $strlen = 'mb_strlen'; @@ -360,10 +388,13 @@ class Share_Twitter extends Sharing_Source { $strlen = 'strlen'; $substr = 'substr'; } - + $via = $this->sharing_twitter_via( $post ); if ( $via ) { - $related = false; + $related = $this->get_related_accounts( $post ); + if ( $related === $via ) + $related = false; + $sig = " via @$via"; } else { $via = false; @@ -384,22 +415,22 @@ class Share_Twitter extends Sharing_Source { // Record stats parent::process_request( $post, $post_data ); - + $url = $post_link; $twitter_url = add_query_arg( - urlencode_deep( compact( 'via', 'related', 'text', 'url' ) ), - sprintf( '%s://twitter.com/intent/tweet', ( is_ssl() ? 'https' : 'http' ) ) + urlencode_deep( array_filter( compact( 'via', 'related', 'text', 'url' ) ) ), + sprintf( '%s://twitter.com/intent/tweet', $this->http() ) ); // Redirect to Twitter wp_redirect( $twitter_url ); die(); } - + public function has_custom_button_style() { return $this->smart; } - + public function display_footer() { $this->js_dialog( $this->shortname, array( 'height' => 350 ) ); } @@ -415,7 +446,7 @@ class Share_Stumbleupon extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'StumbleUpon', 'jetpack' ); } @@ -426,17 +457,17 @@ class Share_Stumbleupon extends Sharing_Source { public function get_display( $post ) { if ( $this->smart ) - return '<div class="stumbleupon_button"><iframe src="http://www.stumbleupon.com/badge/embed/1/?url=' . urlencode( get_permalink( $post->ID ) ) . '&title=' . urlencode( $post->post_title ) . '" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:74px; height: 18px;" allowTransparency="true"></iframe></div>'; + return '<div class="stumbleupon_button"><iframe src="http://www.stumbleupon.com/badge/embed/1/?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ) . '" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:74px; height: 18px;" allowTransparency="true"></iframe></div>'; else return $this->get_link( get_permalink( $post->ID ), _x( 'StumbleUpon', 'share to', 'jetpack' ), __( 'Click to share on StumbleUpon', 'jetpack' ), 'share=stumbleupon' ); } - + public function process_request( $post, array $post_data ) { - $stumbleupon_url = 'http://www.stumbleupon.com/submit?url=' . urlencode( get_permalink( $post->ID ) ) . '&title=' . urlencode( $post->post_title ); - + $stumbleupon_url = $this->http() . '://www.stumbleupon.com/submit?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ); + // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Stumbleupon wp_redirect( $stumbleupon_url ); die(); @@ -460,17 +491,17 @@ class Share_Reddit extends Sharing_Source { public function get_display( $post ) { if ( $this->smart ) - return '<div class="reddit_button"><iframe src="http://www.reddit.com/static/button/button1.html?width=120&url=' . urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&title=' . rawurlencode( $post->post_title ) . '" height="22" width="120" scrolling="no" frameborder="0"></iframe></div>'; + return '<div class="reddit_button"><iframe src="http://www.reddit.com/static/button/button1.html?width=120&url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ) . '" height="22" width="120" scrolling="no" frameborder="0"></iframe></div>'; else return $this->get_link( get_permalink( $post->ID ), __( 'Reddit', 'share to', 'jetpack' ), __( 'Click to share on Reddit', 'jetpack' ), 'share=reddit' ); - } - + } + public function process_request( $post, array $post_data ) { - $reddit_url = 'http://reddit.com/submit?url=' . urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&title=' . urlencode( $post->post_title ); - + $reddit_url = 'http://reddit.com/submit?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ); + // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Reddit wp_redirect( $reddit_url ); die(); @@ -487,7 +518,7 @@ class Share_Digg extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Digg', 'jetpack' ); } @@ -498,24 +529,24 @@ class Share_Digg extends Sharing_Source { public function get_display( $post ) { if ( $this->smart ) { - $url = $this->get_link( 'http://digg.com/submit?url='. urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&title=' . urlencode( $post->post_title ), 'Digg', __( 'Click to Digg this post' ) ); + $url = $this->get_link( 'http://digg.com/submit?url='. rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ), 'Digg', __( 'Click to Digg this post', 'jetpack' ) ); return '<div class="digg_button">' . str_replace( 'class="', 'class="DiggThisButton DiggCompact ', $url ) . '</div>'; } else { - return $this->get_link( get_permalink( $post->ID ), _x( 'Digg', 'share to', 'jetpack' ), __( 'Click to Digg this post' ), 'share=digg' ); + return $this->get_link( get_permalink( $post->ID ), _x( 'Digg', 'share to', 'jetpack' ), __( 'Click to Digg this post', 'jetpack' ), 'share=digg' ); } - } - + } + public function process_request( $post, array $post_data ) { - $digg_url = 'http://digg.com/submit?url=' . urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&title=' . urlencode( $post->post_title ); - + $digg_url = 'http://digg.com/submit?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&title=' . rawurlencode( $post->post_title ); + // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Digg wp_redirect( $digg_url ); die(); } - + public function display_header() { if ( $this->smart ) { ?> @@ -553,25 +584,25 @@ class Share_LinkedIn extends Sharing_Source { } public function get_display( $post ) { - $permalink = get_permalink( $post->ID ); + $share_url = $this->get_share_url( $post->ID ); $display = ''; - + if ( $this->smart ) - $display .= sprintf( '<div class="linkedin_button"><script type="in/share" data-url="%s" data-counter="right"></script></div>', esc_url( $permalink ) ); + $display .= sprintf( '<div class="linkedin_button"><script type="in/share" data-url="%s" data-counter="right"></script></div>', esc_url( $share_url ) ); else - $display = $this->get_link( $permalink, _x( 'LinkedIn', 'share to', 'jetpack' ), __( 'Click to share on LinkedIn', 'jetpack' ), 'share=linkedin', 'sharing-linkedin-' . $post->ID ); + $display = $this->get_link( get_permalink( $post->ID ), _x( 'LinkedIn', 'share to', 'jetpack' ), __( 'Click to share on LinkedIn', 'jetpack' ), 'share=linkedin', 'sharing-linkedin-' . $post->ID ); if ( 'icon-text' == $this->button_style || 'text' == $this->button_style ) sharing_register_post_for_share_counts( $post->ID ); return $display; } - + public function process_request( $post, array $post_data ) { setup_postdata( $post ); - $post_link = apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ); + $post_link = $this->get_share_url( $post->ID ); // http://www.linkedin.com/shareArticle?mini=true&url={articleUrl}&title={articleTitle}&summary={articleSummary}&source={articleSource} @@ -582,18 +613,16 @@ class Share_LinkedIn extends Sharing_Source { $encoded_summary = rawurlencode( strip_tags( get_the_excerpt() ) ); if( strlen( $encoded_summary ) > 256 ) $encoded_summary = substr( $encoded_summary, 0, 253 ) . '...'; - + $source = get_bloginfo( 'name' ); - $query = add_query_arg( array( + $linkedin_url = add_query_arg( array( 'title' => $encoded_title, 'url' => rawurlencode( $post_link ), 'source' => rawurlencode( $source ), 'summary' => $encoded_summary, - ) ); + ), 'http://www.linkedin.com/shareArticle?mini=true' ); - $linkedin_url = 'http://www.linkedin.com/shareArticle?mini=true' . $query; - // Record stats parent::process_request( $post, $post_data ); @@ -601,25 +630,36 @@ class Share_LinkedIn extends Sharing_Source { wp_redirect( $linkedin_url ); die(); } - + public function display_footer() { - if ( !$this->smart ) + if ( !$this->smart ) { $this->js_dialog( $this->shortname, array( 'width' => 580, 'height' => 450 ) ); - else - echo '<script type="text/javascript" src="//platform.linkedin.com/in.js"></script>'; + } else { + ?><script type="text/javascript"> + jQuery( document ).ready( function() { + jQuery.getScript( 'http://platform.linkedin.com/in.js?async=true', function success() { + IN.init(); + }); + }); + jQuery( document.body ).on( 'post-load', function() { + if ( typeof IN != 'undefined' ) + IN.parse(); + }); + </script><?php + } } } class Share_Facebook extends Sharing_Source { var $shortname = 'facebook'; private $share_type = 'default'; - + public function __construct( $id, array $settings ) { parent::__construct( $id, $settings ); if ( isset( $settings['share_type'] ) ) $this->share_type = $settings['share_type']; - + if ( 'official' == $this->button_style ) $this->smart = true; else @@ -629,21 +669,30 @@ class Share_Facebook extends Sharing_Source { public function get_name() { return __( 'Facebook', 'jetpack' ); } - + public function display_header() { } - + function guess_locale_from_lang( $lang ) { if ( 'en' == $lang || 'en_US' == $lang || !$lang ) { return 'en_US'; } if ( !class_exists( 'GP_Locales' ) ) { - require JETPACK__PLUGIN_DIR . 'locales.php'; + if ( !defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || !file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) { + return false; + } + + require JETPACK__GLOTPRESS_LOCALES_PATH; } - // Jetpack: get_locale() returns 'it_IT'; - $locale = GP_Locales::by_field( 'wp_locale', $lang ); + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + // WP.com: get_locale() returns 'it' + $locale = GP_Locales::by_slug( $lang ); + } else { + // Jetpack: get_locale() returns 'it_IT'; + $locale = GP_Locales::by_field( 'wp_locale', $lang ); + } if ( !$locale || empty( $locale->facebook_locale ) ) { return false; @@ -653,22 +702,25 @@ class Share_Facebook extends Sharing_Source { } public function get_display( $post ) { + $share_url = $this->get_share_url( $post->ID ); if ( $this->smart ) { - $url = 'http://www.facebook.com/plugins/like.php?href=' . rawurlencode( get_permalink( $post->ID ) ) . '&layout=button_count&show_faces=false&action=like&colorscheme=light&height=21'; - + $url = $this->http() . '://www.facebook.com/plugins/like.php?href=' . rawurlencode( $share_url ) . '&layout=button_count&show_faces=false&action=like&colorscheme=light&height=21'; + // Default widths to suit English $inner_w = 90; - + // Locale-specific widths/overrides $widths = array( 'bg_BG' => 120, - 'de_DE' => 100, + 'cs_CZ' => 135, + 'de_DE' => 120, 'da_DK' => 120, - 'es_ES' => 100, - 'es_LA' => 100, + 'es_ES' => 122, + 'es_LA' => 110, 'fi_FI' => 100, 'it_IT' => 100, 'ja_JP' => 100, + 'nl_NL' => 130, 'ru_RU' => 128, ); @@ -676,9 +728,7 @@ class Share_Facebook extends Sharing_Source { $locale = $this->guess_locale_from_lang( get_locale() ); if ( $locale ) { - if ( 'en_US' != $locale ) { - $url .= '&locale=' . $locale; - } + $url .= '&locale=' . $locale; if ( isset( $widths[$locale] ) ) { $inner_w = $widths[$locale]; @@ -688,23 +738,23 @@ class Share_Facebook extends Sharing_Source { $url .= '&width='.$inner_w; return '<div class="like_button"><iframe src="'.$url.'" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:'.( $inner_w + 6 ).'px; height:21px;" allowTransparency="true"></iframe></div>'; } - + if ( 'icon-text' == $this->button_style || 'text' == $this->button_style ) sharing_register_post_for_share_counts( $post->ID ); return $this->get_link( get_permalink( $post->ID ), _x( 'Facebook', 'share to', 'jetpack' ), __( 'Share on Facebook', 'jetpack' ), 'share=facebook', 'sharing-facebook-' . $post->ID ); } - + public function process_request( $post, array $post_data ) { - $fb_url = 'http://www.facebook.com/sharer.php?u=' . urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&t=' . urlencode( $post->post_title ); - + $fb_url = $this->http() . '://www.facebook.com/sharer.php?u=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&t=' . rawurlencode( $post->post_title ); + // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Facebook wp_redirect( $fb_url ); die(); } - + public function display_footer() { $this->js_dialog( $this->shortname ); } @@ -720,13 +770,13 @@ class Share_Print extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Print', 'jetpack' ); } public function get_display( $post ) { - return $this->get_link( get_permalink( $post->ID ). ( ( is_single() || is_page() ) ? '#print': '' ), _x( 'Print', 'share to', 'jetpack' ), __( 'Click to print', 'jetpack' ) ); + return $this->get_link( get_permalink( $post->ID ) . ( ( is_single() || is_page() ) ? '#print': '' ), _x( 'Print', 'share to', 'jetpack' ), __( 'Click to print', 'jetpack' ) ); } } @@ -740,30 +790,30 @@ class Share_PressThis extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Press This', 'jetpack' ); } public function process_request( $post, array $post_data ) { global $current_user; - + $blogs = get_blogs_of_user( $current_user->ID ); if ( empty( $blogs ) ) { wp_safe_redirect( get_permalink( $post->ID ) ); die(); } - + $blog = current( $blogs ); - $url = $blog->siteurl.'/wp-admin/press-this.php?u='.urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ).'&t='.urlencode( $post->post_title ).'&v=4'; + $url = $blog->siteurl.'/wp-admin/press-this.php?u='.rawurlencode( $this->get_share_url( $post->ID ) ).'&t='.rawurlencode( $post->post_title ).'&v=4'; if ( isset( $_GET['sel'] ) ) - $url .= '&s='.urlencode( $_GET['sel'] ); + $url .= '&s='.rawurlencode( $_GET['sel'] ); // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Press This wp_safe_redirect( $url ); die(); @@ -786,65 +836,77 @@ class Share_GooglePlus1 extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Google +1', 'jetpack' ); } - public function get_display( $post ) { - // Smart or not, return the G+ button - return '<div class="googleplus1_button"><g:plusone size="medium" callback="sharing_plusone" href="' . esc_attr( get_permalink( $post->ID ) ) . '"></g:plusone></div>'; + public function has_custom_button_style() { + return $this->smart; } - - public function display_preview() { - ?> - <div class="option option-smart-on"> - <a href="javascript:void(0);return false;" class="share-<?php echo $this->shortname; ?>"> - <span></span> - </a> - </div><?php + + public function get_display( $post ) { + $share_url = $this->get_share_url( $post->ID ); + + if ( $this->smart ) { + return '<div class="googleplus1_button"><div class="g-plusone" data-size="medium" data-callback="sharing_plusone" data-href="' . esc_url( $share_url ) . '"></div></div>'; + } else { + //if ( 'icon-text' == $this->button_style || 'text' == $this->button_style ) + //sharing_register_post_for_share_counts( $post->ID ); + return $this->get_link( get_permalink( $post->ID ), _x( 'Google +1', 'share to', 'jetpack' ), __( 'Click to share on Google+', 'jetpack' ), 'share=google-plus-1', 'sharing-google-' . $post->ID ); + } } - + public function get_state() { return $this->state; } - + public function process_request( $post, array $post_data ) { - + if ( isset( $post_data['state'] ) ) { $this->state = $post_data['state']; } // Record stats parent::process_request( $post, $post_data ); + + // Redirect to Google +'s sharing endpoint + $url = 'https://plus.google.com/share?url=' . rawurlencode( $this->get_share_url( $post->ID ) ); + wp_redirect( $url ); die(); } - + public function display_footer() { global $post; -?> - <script type="text/javascript" charset="utf-8"> - function sharing_plusone( obj ) { - jQuery.ajax( { - url: '<?php echo get_permalink( $post->ID ) . '?share=google-plus-1'; ?>', - type: 'POST', - data: obj - } ); - } - </script> - <script type="text/javascript" src="http://apis.google.com/js/plusone.js"></script> -<?php - } + + if ( $this->smart ) { ?> + <script type="text/javascript"> + function sharing_plusone( obj ) { + jQuery.ajax( { + url: '<?php echo get_permalink( $post->ID ) . '?share=google-plus-1'; ?>', + type: 'POST', + data: obj + } ); + } + jQuery( document.body ).on( 'post-load', function() { + gapi.plusone.go(); + }); + </script> + <script type="text/javascript" src="//apis.google.com/js/plusone.js"></script> <?php + } else { + $this->js_dialog( 'google-plus-1', array( 'width' => 600, 'height' => 600 ) ); + } + } public function get_total( $post = false ) { global $wpdb, $blog_id; - - $name = strtolower( $this->get_id() ); - + + $name = strtolower( $this->get_id() ); + if ( $post == false ) { // get total number of shares for service return $wpdb->get_var( $wpdb->prepare( "SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND share_service = %s", $blog_id, $name ) ); } - + //get total shares for a post return $wpdb->get_var( $wpdb->prepare( "SELECT count FROM sharing_stats WHERE blog_id = %d AND post_id = %d AND share_service = %s", $blog_id, $post->ID, $name ) ); } @@ -856,14 +918,14 @@ class Share_Custom extends Sharing_Advanced_Source { private $url; public $smart = true; var $shortname; - + public function get_class() { return 'custom'; } public function __construct( $id, array $settings ) { parent::__construct( $id, $settings ); - + $opts = $this->get_options(); if ( isset( $settings['name'] ) ) { @@ -877,46 +939,46 @@ class Share_Custom extends Sharing_Advanced_Source { if ( isset( $settings['url'] ) ) $this->url = $settings['url']; } - + public function get_name() { return $this->name; } - + public function get_display( $post ) { $str = $this->get_link( get_permalink( $post->ID ), esc_html( $this->name ), __( 'Click to share', 'jetpack' ), 'share='.$this->id ); - return str_replace( '<span>', '<span style="background-image:url(' . esc_url( $this->icon ) . ');">', $str ); + return str_replace( '<span>', '<span style="' . esc_attr( 'background-image:url("' . addcslashes( esc_url_raw( $this->icon ), '"' ) . '");' ) . '">', $str ); } public function process_request( $post, array $post_data ) { $url = str_replace( '&', '&', $this->url ); - $url = str_replace( '%post_url%', urlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ), $url ); - $url = str_replace( '%post_full_url%', urlencode( get_permalink( $post->ID ) ), $url ); - $url = str_replace( '%post_title%', urlencode( $post->post_title ), $url ); + $url = str_replace( '%post_url%', rawurlencode( $this->get_share_url( $post->ID ) ), $url ); + $url = str_replace( '%post_full_url%', rawurlencode( get_permalink( $post->ID ) ), $url ); + $url = str_replace( '%post_title%', rawurlencode( $post->post_title ), $url ); if ( strpos( $url, '%post_tags%' ) !== false ) { $tags = get_the_tags( $post->ID ); $tagged = ''; - + if ( $tags ) { foreach ( $tags AS $tag ) { - $tagged[] = urlencode( $tag->name ); + $tagged[] = rawurlencode( $tag->name ); } - + $tagged = implode( ',', $tagged ); } $url = str_replace( '%post_tags%', $tagged, $url ); } - + if ( strpos( $url, '%post_excerpt%' ) !== false ) { $url_excerpt = $post->post_excerpt; if ( empty( $url_excerpt ) ) $url_excerpt = $post->post_content; - + $url_excerpt = strip_tags( strip_shortcodes( $url_excerpt ) ); $url_excerpt = wp_html_excerpt( $url_excerpt, 100 ); $url_excerpt = rtrim( preg_replace( '/[^ .]*$/', '', $url_excerpt ) ); - $url = str_replace( '%post_excerpt%', urlencode( $url_excerpt ), $url ); + $url = str_replace( '%post_excerpt%', rawurlencode( $url_excerpt ), $url ); } // Record stats @@ -926,7 +988,7 @@ class Share_Custom extends Sharing_Advanced_Source { wp_redirect( $url ); die(); } - + public function display_options() { ?> <div class="input"> @@ -964,13 +1026,13 @@ class Share_Custom extends Sharing_Advanced_Source { $name = trim( wp_html_excerpt( wp_kses( stripslashes( $data['name'] ), array() ), 30 ) ); $url = trim( esc_url_raw( $data['url'] ) ); $icon = trim( esc_url_raw( $data['icon'] ) ); - + if ( $name ) $this->name = $name; if ( $url ) $this->url = $url; - + if ( $icon ) $this->icon = $icon; } @@ -982,25 +1044,25 @@ class Share_Custom extends Sharing_Advanced_Source { 'url' => $this->url, ); } - + public function display_preview() { $opts = $this->get_options(); - + $text = ' '; if ( !$this->smart ) if ( $this->button_style != 'icon' ) $text = $this->get_name(); - + $klasses = array( 'share-'.$this->shortname ); - + if ( $this->button_style == 'icon' || $this->button_style == 'icon-text' ) $klasses[] = 'share-icon'; - + if ( $this->button_style == 'icon' ) { $text = ''; $klasses[] = 'no-text'; } - + if ( $this->button_style == 'text' ) $klasses[] = 'no-icon'; @@ -1028,24 +1090,29 @@ class Share_Tumblr extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Tumblr', 'jetpack' ); } public function get_display( $post ) { - if ( $this->smart ) - return '<a href="http://www.tumblr.com/share" title="Share on Tumblr" style="display:inline-block; text-indent:-9999px; overflow:hidden; width:62px; height:20px; background:url(\'http://platform.tumblr.com/v1/share_2.png\') top left no-repeat transparent;">Share on Tumblr</a>'; - else + if ( $this->smart ) { + $target = ''; + if ( 'new' == $this->open_links ) + $target = '_blank'; + + return '<a target="' . $target . '" href="http://www.tumblr.com/share/link/?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&name=' . rawurlencode( $post->post_title ) . '" title="Share on Tumblr" style="display:inline-block; text-indent:-9999px; overflow:hidden; width:62px; height:20px; background:url(\'http://platform.tumblr.com/v1/share_2.png\') top left no-repeat transparent;">Share on Tumblr</a>'; + } else { return $this->get_link( get_permalink( $post->ID ), _x( 'Tumblr', 'share to', 'jetpack' ), __( 'Click to share on Tumblr', 'jetpack' ), 'share=tumblr' ); + } } - + public function process_request( $post, array $post_data ) { // Record stats parent::process_request( $post, $post_data ); - + // Redirect to Tumblr's sharing endpoint (a la their bookmarklet) - $url = 'http://www.tumblr.com/share?v=3&u=' . rawurlencode( get_permalink( $post->ID ) ) . '&t=' . rawurlencode( $post->post_title ) . '&s='; + $url = 'http://www.tumblr.com/share?v=3&u=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&t=' . rawurlencode( $post->post_title ) . '&s='; wp_redirect( $url ); die(); } @@ -1070,7 +1137,7 @@ class Share_Pinterest extends Sharing_Source { else $this->smart = false; } - + public function get_name() { return __( 'Pinterest', 'jetpack' ); } @@ -1078,10 +1145,24 @@ class Share_Pinterest extends Sharing_Source { public function get_post_image( $content ) { $image = ''; + if ( class_exists( 'Jetpack_PostImages' ) ) { + // Use the full stack of methods to find an image, except for HTML, which can cause loops + $img = Jetpack_PostImages::get_image( $content->ID ); + if ( !empty( $img['src'] ) ) + return $img['src']; + } + + // If we have to fall back to the following, we only do a few basic image checks + $content = $content->post_content; if ( function_exists('has_post_thumbnail') && has_post_thumbnail() ) { $thumb_id = get_post_thumbnail_id(); - $thumb = wp_get_attachment_image_src( $thumb_id ); - $image = remove_query_arg( array('w', 'h'), $thumb[0] ); + $thumb = wp_get_attachment_image_src( $thumb_id, 'full' ); + + // This shouldn't be necessary, since has_post_thumbnail() is true, + // but... see http://wordpress.org/support/topic/jetpack-youtube-embeds + if ( ! $thumb ) return ''; + + $image = remove_query_arg( array('w', 'h'), $thumb[0] ); } else if ( preg_match_all('/<img (.+?)>/', $content, $matches) ) { foreach ( $matches[1] as $attrs ) { $media = $img = array(); @@ -1091,7 +1172,7 @@ class Share_Pinterest extends Sharing_Source { continue; } else { - $image = $img['src']; + $image = htmlspecialchars_decode( $img['src'] ); break; } } @@ -1102,43 +1183,60 @@ class Share_Pinterest extends Sharing_Source { public function get_display( $post ) { if ( $this->smart ) - return '<div class="pinterest_button"><a href="http://pinterest.com/pin/create/button/?url='. rawurlencode( apply_filters( 'sharing_permalink', get_permalink( $post->ID ), $post->ID, $this->id ) ) . '&description=' . rawurlencode( esc_attr( $post->post_title ) ) . '&media=' . rawurlencode( esc_url( $this->get_post_image( $post->post_content ) ) ) . '" class="pin-it-button" count-layout="horizontal"> '. __( 'Pin It', 'sharedaddy') .'</a></div>'; + return '<div class="pinterest_button"><a href="' . esc_url( 'http://pinterest.com/pin/create/button/?url='. rawurlencode( $this->get_share_url( $post->ID ) ) . '&description=' . rawurlencode( $post->post_title ) . '&media=' . rawurlencode( esc_url_raw( $this->get_post_image( $post ) ) ) ) . '" class="pin-it-button" count-layout="horizontal"> '. __( 'Pin It', 'jetpack') .'</a></div>'; else return $this->get_link( get_permalink( $post->ID ), _x( 'Pinterest', 'share to', 'jetpack' ), __( 'Click to share on Pinterest', 'jetpack' ), 'share=pinterest' ); } - + public function process_request( $post, array $post_data ) { - $pinterest_url = 'http://pinterest.com/pin/create/button/?url=' . rawurlencode( get_permalink( $post->ID ) ) . '&description=' . rawurlencode( esc_attr( $post->post_title ) ) . '&media=' . rawurlencode( esc_url( $this->get_post_image( $post->post_content ) ) ); - // Record stats parent::process_request( $post, $post_data ); - - // Redirect to Pinterest - wp_redirect( $pinterest_url ); - die(); - } - public function display_footer() { - if ( !$this->smart ) { - $this->js_dialog( $this->shortname, array( 'width' => 650, 'height' => 280 ) ); + // If we're triggering the multi-select panel, then we don't need to redirect to Pinterest + if ( !isset( $_GET['js_only'] ) ) { + $pinterest_url = esc_url_raw( 'http://pinterest.com/pin/create/button/?url=' . rawurlencode( $this->get_share_url( $post->ID ) ) . '&description=' . rawurlencode( $post->post_title ) . '&media=' . rawurlencode( esc_url_raw( $this->get_post_image( $post ) ) ) ); + wp_redirect( $pinterest_url ); } else { -?> - <script type="text/javascript"> - function pinterest_async_load() { - var s = document.createElement("script"); - s.type = "text/javascript"; - s.async = true; - s.src = window.location.protocol + "//assets.pinterest.com/js/pinit.js"; - var x = document.getElementsByTagName("script")[0]; - x.parentNode.insertBefore(s, x); - } - jQuery(document).on('ready post-load', function() { - pinterest_async_load(); - }); - </script> -<?php + echo '// share count bumped'; } - } + die(); + } + public function display_footer() { + ?> + <?php if ( $this->smart ) : ?> + <script type="text/javascript"> + // Pinterest shared resources + var s = document.createElement("script"); + s.type = "text/javascript"; + s.async = true; + s.src = window.location.protocol + "//assets.pinterest.com/js/pinit.js"; + var x = document.getElementsByTagName("script")[0]; + x.parentNode.insertBefore(s, x); + </script> + <?php else : ?> + <script type="text/javascript"> + jQuery(document).on('ready', function(){ + jQuery('body').on('click', 'a.share-pinterest', function(e){ + e.preventDefault(); + + // Load Pinterest Bookmarklet code + var s = document.createElement("script"); + s.type = "text/javascript"; + s.src = window.location.protocol + "//assets.pinterest.com/js/pinmarklet.js?r=" + ( Math.random() * 99999999 ); + var x = document.getElementsByTagName("script")[0]; + x.parentNode.insertBefore(s, x); + + // Trigger Stats + var s = document.createElement("script"); + s.type = "text/javascript"; + s.src = this + ( this.toString().indexOf( '?' ) ? '&' : '?' ) + 'js_only=1'; + var x = document.getElementsByTagName("script")[0]; + x.parentNode.insertBefore(s, x); + }); + }); + </script> + <?php endif; + } } diff --git a/plugins/jetpack/modules/sharedaddy/sharing.css b/plugins/jetpack/modules/sharedaddy/sharing.css index fb45c924..95a09d0e 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing.css +++ b/plugins/jetpack/modules/sharedaddy/sharing.css @@ -40,6 +40,7 @@ div.sharedaddy h3, border: none; } +/* @noflip */ .rtl div.sharedaddy h3, .rtl #content div.sharedaddy h3, .rtl #main div.sharedaddy h3, @@ -121,7 +122,7 @@ div.sharedaddy.sharedaddy-dark .sd-block { div.sharedaddy .sd-content { width: 82.125%; /* 530px / 640px */ float: right; - margin: 0; + margin: -2px 0 0 0; } div.sharedaddy .sd-content ul { @@ -134,10 +135,12 @@ div.sharedaddy .sd-content li { display: block; } +/* @noflip */ .rtl div.sharedaddy .sd-content { float: right; } +/* @noflip */ .rtl div.sharedaddy .sd-content li { float: right; margin: 0 0 5px 5px !important; @@ -190,7 +193,7 @@ a.sd-button > span { padding: 4px 8px; display: block; opacity: .8; - line-height: 1; + line-height: 1.5em; text-shadow: none; } @@ -202,8 +205,10 @@ a.sd-button:hover span { font-size: 90%; color: #666; margin-left: 5px; + line-height: 1; } +/* @noflip */ .rtl .sd-button span.share-count { margin-right: 5px; } @@ -212,7 +217,7 @@ a.sd-button:hover span { .sd-social-icon-text a.sd-button > span, a.sd-button > span { - padding: 3px 5px 3px 23px; + padding: 1px 5px 1px 23px; background-position: 2px center; background-repeat: no-repeat; } @@ -249,17 +254,20 @@ div.sharedaddy .no-icon a span, div.sharedaddy li.no-icon div a span { background-image: none; } +/* @noflip */ .rtl .sd-social-icon-text a.sd-button > span, .rtl a.sd-button > span { padding: 3px 23px 3px 5px; background-position: 98% center; } +/* @noflip */ .rtl .sd-social-text a.sd-button > span { padding-left: 0; padding-right: 5px } +/* @noflip */ .rtl div.sharedaddy .no-icon a span, .rtl div.sharedaddy li.no-icon div a span { padding-left: 0; padding-right: 5px @@ -279,6 +287,10 @@ li.share-twitter a.sd-button > span { background-image: url('images/twitter.png?1'); } +li.share-google-plus-1 a.sd-button > span { + background-image: url('images/googleplus1.png?1'); +} + li.share-linkedin a.sd-button > span { background-image: url('images/linkedin.png'); } @@ -332,6 +344,11 @@ a.sd-button.share-more span { background-size: 16px 16px; } + li.share-google-plus-1 a.sd-button > span { + background-image: url('images/googleplus1@2x.png'); + background-size: 16px 16px; + } + li.share-linkedin a.sd-button > span { background-image: url('images/linkedin@2x.png'); background-size: 16px 16px; @@ -379,19 +396,6 @@ a.sd-button.share-more span { } - -/* Special case for non-smart implementations of Google+ button */ - -div.sharedaddy .sd-content ul li.share-google-plus-1 { - line-height: 90%; - margin-bottom: 2px !important; - min-height: 20px; -} - -div.sharedaddy .sd-social-official .sd-content ul li.share-google-plus-1 { - padding-top: 0; -} - /* More pannel */ div.sharedaddy .sharing-hidden .inner { @@ -413,6 +417,7 @@ div.sharedaddy .sharing-hidden .inner { box-shadow: 0px 2px 8px rgba(0, 0, 0, .2); } +/* @noflip */ .rtl div.sharedaddy .sharing-hidden .inner { margin-left: 0; margin-right: -100px; @@ -422,82 +427,97 @@ div.sharedaddy.sharedaddy-dark .sharing-hidden .inner { border-color: #222; } -div.sd-content a.sd-button > span { - line-height: 1.5em; -} +/* =Sharing: Email Dialog +-------------------------------------------------------------- */ #sharing_email { - background-color: #FFFFFF; - border: 1px solid #CCCCCC; - border-radius: 3px 3px 3px 3px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - margin-left: -120px; - padding: 15px; - position: absolute; - text-align: left; - width: 312px; - z-index: 1001; + width: 312px; + padding: 15px; + position: absolute; + margin-left: -120px; + z-index: 1001; + background-color: #fff; + border: 1px solid #ccc; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0px 2px 8px #ccc; + -webkit-box-shadow: 0px 2px 8px #ccc; + box-shadow: 0px 2px 8px #ccc; + -webkit-box-shadow: 0px 2px 8px rgba(0, 0, 0, .2); + -moz-box-shadow: 0px 2px 8px rgba(0, 0, 0, .2); + box-shadow: 0px 2px 8px rgba(0, 0, 0, .2); + text-align: left; } div.sharedaddy.sharedaddy-dark #sharing_email { - border-color: #FFFFFF; + border-color: #fff; } #sharing_email .errors { - background-color: #771A09; - color: #FFFFFF; - font-size: 11px; - line-height: 11px; - margin: 10px 0 0; - padding: 5px 8px; + color: #fff; + background-color: #771a09; + font-size: 11px; + padding: 5px 8px; + line-height: 11px; + margin: 10px 0 0 0; } #sharing_email label { - color: #333333; - display: block; - font-size: 11px; - font-weight: bold; - padding: 0 0 4px; - text-align: left; - text-shadow: none; + font-size: 11px; + color: #333; + font-weight: bold; + display: block; + padding: 0 0 4px 0; + text-align: left; + text-shadow: none; } #sharing_email input[type="text"] { - background: none repeat scroll 0 0 #FFFFFF; - border: 1px solid #CCCCCC; - color: #333333; - margin-bottom: 12px; - width: 98.5%; + width: 98.5%; + margin-bottom: 12px; + border: 1px solid #ccc; + background: #fff; + color: #333; } #sharing_email .sharing_cancel { - font-size: 11px; - padding: 0 0 0 10px; - text-shadow: none; + padding: 0 0 0 10px; + font-size: 11px; + text-shadow: none; } #sharing_email .recaptcha { - height: 123px; - margin: 10px 0 14px; - width: 312px; + width: 312px; + height: 123px; + margin: 10px 0 14px 0; } /* =RTL -------------------------------------------------------------- */ +/* @noflip */ body.rtl .sharing ul { float: right; } + +/* @noflip */ body.rtl .sharing li { margin: 0 0 0 10px !important; } + +/* @noflip */ .rtl #sharing_email { - margin-left: 0; - margin-right: -120px; - text-align: right; + margin-left: -0px; + margin-right: -120px; + text-align: right } + +/* @noflip */ +.rtl #sharing_email label { + text-align: right +} + +/* @noflip */ .rtl #sharing_email .sharing_cancel { - padding: 0 10px 0 0; + padding: 0 10px 0 0; } -.rtl #sharing_email label { - text-align: right; -}
\ No newline at end of file diff --git a/plugins/jetpack/modules/sharedaddy/sharing.js b/plugins/jetpack/modules/sharedaddy/sharing.js index a6c29494..68482c60 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing.js +++ b/plugins/jetpack/modules/sharedaddy/sharing.js @@ -1,15 +1,21 @@ var WPCOMSharing = { + done_urls : [], get_counts : function( url ) { + if ( 'undefined' != typeof WPCOMSharing.done_urls[ WPCOM_sharing_counts[ url ] ] ) + return; + if ( jQuery( '#sharing-facebook-' + WPCOM_sharing_counts[ url ] ).length ) - jQuery.getScript( 'https://graph.facebook.com/' + encodeURIComponent( url ) + '?callback=WPCOMSharing.update_facebook_count' ); + jQuery.getScript( 'https://api.facebook.com/method/fql.query?query=' + encodeURIComponent( "SELECT total_count, url FROM link_stat WHERE url='" + url + "'" ) + '&format=json&callback=WPCOMSharing.update_facebook_count' ); if ( jQuery( '#sharing-twitter-' + WPCOM_sharing_counts[ url ] ).length ) - jQuery.getScript( 'http://urls.api.twitter.com/1/urls/count.json?callback=WPCOMSharing.update_twitter_count&url=' + encodeURIComponent( url ) ); + jQuery.getScript( window.location.protocol + '//cdn.api.twitter.com/1/urls/count.json?callback=WPCOMSharing.update_twitter_count&url=' + encodeURIComponent( url ) ); if ( jQuery( '#sharing-linkedin-' + WPCOM_sharing_counts[ url ] ).length ) - jQuery.getScript( 'http://www.linkedin.com/countserv/count/share?format=jsonp&callback=WPCOMSharing.update_linkedin_count&url=' + encodeURIComponent( url ) ); + jQuery.getScript( window.location.protocol + '//www.linkedin.com/countserv/count/share?format=jsonp&callback=WPCOMSharing.update_linkedin_count&url=' + encodeURIComponent( url ) ); + + WPCOMSharing.done_urls[ WPCOM_sharing_counts[ url ] ] = true; }, update_facebook_count : function( data ) { - if ( 'undefined' != typeof data.shares && ( data.shares * 1 ) > 0 ) { - WPCOMSharing.inject_share_count( 'sharing-facebook-' + WPCOM_sharing_counts[ data.id ], data.shares ); + if ( 'undefined' != typeof data[0].total_count && ( data[0].total_count * 1 ) > 0 ) { + WPCOMSharing.inject_share_count( 'sharing-facebook-' + WPCOM_sharing_counts[ data[0].url ], data[0].total_count ); } }, update_twitter_count : function( data ) { @@ -40,14 +46,16 @@ var WPCOMSharing = { return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test( this.val() ); } } ); - - if ( 'undefined' != typeof WPCOM_sharing_counts ) { - for ( var url in WPCOM_sharing_counts ) { - WPCOMSharing.get_counts( url ); - } - } - $( document ).on( 'ready post-load', function() { + $( document ).on( 'ready', WPCOMSharing_do ); + $( document.body ).on( 'post-load', WPCOMSharing_do ); + + function WPCOMSharing_do() { + if ( 'undefined' != typeof WPCOM_sharing_counts ) { + for ( var url in WPCOM_sharing_counts ) { + WPCOMSharing.get_counts( url ); + } + } var $more_sharing_buttons = $( '.sharedaddy a.sharing-anchor' ); $more_sharing_buttons.click( function() { @@ -58,7 +66,7 @@ var WPCOMSharing = { if ( $( this ).attr( 'href' ) && $( this ).attr( 'href' ).indexOf( 'share=' ) != -1 ) $( this ).attr( 'href', $( this ).attr( 'href' ) + '&nb=1' ); } ); - + // Show hidden buttons // Touchscreen device: use click. @@ -71,14 +79,14 @@ var WPCOMSharing = { // We're in the middle of some other event's animation return; } - + if ( true === $more_sharing_pane.data( 'justSlid' ) ) { // We just finished some other event's animation - don't process click event so that slow-to-react-clickers don't get confused return; } - + $( '#sharing_email' ).slideUp( 200 ); - + $more_sharing_pane.css( { left: $more_sharing_button.position().left + 'px', top: $more_sharing_button.position().top + $more_sharing_button.height() + 3 + 'px' @@ -95,7 +103,7 @@ var WPCOMSharing = { // Create a timer to make the area appear if the mouse hovers for a period var timer = setTimeout( function() { $( '#sharing_email' ).slideUp( 200 ); - + $more_sharing_pane.data( 'justSlid', true ); $more_sharing_pane.css( { left: $more_sharing_button.position().left + 'px', @@ -103,44 +111,49 @@ var WPCOMSharing = { } ).slideDown( 200, function() { // Mark the item as have being appeared by the hover $more_sharing_button.data( 'hasoriginal', true ).data( 'hasitem', false ); - - // Remove all special handlers - $more_sharing_pane.mouseleave( handler_item_leave ).mouseenter( handler_item_enter ); - $more_sharing_button.mouseleave( handler_original_leave ).mouseenter( handler_original_enter ); + setTimeout( function() { $more_sharing_pane.data( 'justSlid', false ); }, 300 ); + + if ( $more_sharing_pane.find( '.share-google-plus-1' ).size() ) { + // The pane needs to stay open for the Google+ Button + return; + } + + $more_sharing_pane.mouseleave( handler_item_leave ).mouseenter( handler_item_enter ); + $more_sharing_button.mouseleave( handler_original_leave ).mouseenter( handler_original_enter ); } ); - + // The following handlers take care of the mouseenter/mouseleave for the share button and the share area - if both are left then we close the share area var handler_item_leave = function() { $more_sharing_button.data( 'hasitem', false ); - + if ( $more_sharing_button.data( 'hasoriginal' ) === false ) { var timer = setTimeout( close_it, 800 ); $more_sharing_button.data( 'timer2', timer ); } }; - + var handler_item_enter = function() { $more_sharing_button.data( 'hasitem', true ); clearTimeout( $more_sharing_button.data( 'timer2' ) ); - } - + } + var handler_original_leave = function() { $more_sharing_button.data( 'hasoriginal', false ); - + if ( $more_sharing_button.data( 'hasitem' ) === false ) { var timer = setTimeout( close_it, 800 ); $more_sharing_button.data( 'timer2', timer ); } }; - + var handler_original_enter = function() { $more_sharing_button.data( 'hasoriginal', true ); clearTimeout( $more_sharing_button.data( 'timer2' ) ); }; - + var close_it = function() { $more_sharing_pane.data( 'justSlid', true ); $more_sharing_pane.slideUp( 200, function() { @@ -148,36 +161,36 @@ var WPCOMSharing = { $more_sharing_pane.data( 'justSlid', false ); }, 300 ); } ); - + // Clear all hooks $more_sharing_button.unbind( 'mouseleave', handler_original_leave ).unbind( 'mouseenter', handler_original_enter ); $more_sharing_pane.unbind( 'mouseleave', handler_item_leave ).unbind( 'mouseenter', handler_item_leave ); return false; }; }, 200 ); - + // Remember the timer so we can detect it on the mouseout $more_sharing_button.data( 'timer', timer ); } }, function() { // Mouse out - remove any timer - $more_sharing_buttons.each( function() { + $more_sharing_buttons.each( function() { clearTimeout( $( this ).data( 'timer' ) ); } ); $more_sharing_buttons.data( 'timer', false ); } ); } - + // Add click functionality $( '.sharedaddy ul' ).each( function( item ) { printUrl = function ( uniqueId, urlToPrint ) { $( 'body:first' ).append( '<iframe style="position:fixed;top:100;left:100;height:1px;width:1px;border:none;" id="printFrame-' + uniqueId + '" name="printFrame-' + uniqueId + '" src="' + urlToPrint + '" onload="frames[\'printFrame-' + uniqueId + '\'].focus();frames[\'printFrame-' + uniqueId + '\'].print();"></iframe>' ) }; - + // Print button $( this ).find( 'a.share-print' ).click( function() { ref = $( this ).attr( 'href' ); - + var do_print = function() { if ( ref.indexOf( '#print' ) == -1 ) { uid = new Date().getTime(); @@ -186,7 +199,7 @@ var WPCOMSharing = { else print(); } - + // Is the button in a dropdown? if ( $( this ).parents( '.sharing-hidden' ).length > 0 ) { $( this ).parents( '.inner' ).slideUp( 0, function() { @@ -198,11 +211,11 @@ var WPCOMSharing = { return false; } ); - + // Press This button $( this ).find( 'a.share-press-this' ).click( function() { var s = ''; - + if ( window.getSelection ) s = window.getSelection(); else if( document.getSelection ) @@ -213,7 +226,7 @@ var WPCOMSharing = { if ( s ) $( this ).attr( 'href', $( this ).attr( 'href' ) + '&sel=' + encodeURI( s ) ); - if ( !window.open( $( this ).attr( 'href' ), 't', 'toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570' ) ) + if ( !window.open( $( this ).attr( 'href' ), 't', 'toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570' ) ) document.location.href = $( this ).attr( 'href' ); return false; @@ -221,8 +234,8 @@ var WPCOMSharing = { // Email button $( this ).find( 'a.share-email' ).click( function() { - var url = $( this ).attr( 'href' ); - + var url = $( this ).attr( 'href' ), key; + if ( $( '#sharing_email' ).is( ':visible' ) ) $( '#sharing_email' ).slideUp( 200 ); else { @@ -232,8 +245,8 @@ var WPCOMSharing = { $( '#sharing_email form' ).show(); $( '#sharing_email form input[type=submit]' ).removeAttr( 'disabled' ); $( '#sharing_email form a.sharing_cancel' ).show(); - - var key = ''; + + key = ''; if ( $( '#recaptcha_public_key' ).length > 0 ) key = $( '#recaptcha_public_key' ).val(); @@ -245,7 +258,7 @@ var WPCOMSharing = { left: $( this ).offset().left + 'px', top: $( this ).offset().top + $( this ).height() + 'px' } ).slideDown( 200 ); - + // Hook up other buttons $( '#sharing_email a.sharing_cancel' ).unbind( 'click' ).click( function() { $( '#sharing_email .errors' ).hide(); @@ -253,25 +266,25 @@ var WPCOMSharing = { $( '#sharing_background' ).fadeOut(); return false; } ); - + // Submit validation $( '#sharing_email input[type=submit]' ).unbind( 'click' ).click( function() { var form = $( this ).parents( 'form' ); - + // Disable buttons + enable loading icon $( this ).prop( 'disabled', true ); form.find( 'a.sharing_cancel' ).hide(); form.find( 'img.loading' ).show(); - + $( '#sharing_email .errors' ).hide(); $( '#sharing_email .error' ).removeClass( 'error' ); - + if ( $( '#sharing_email input[name=source_email]' ).share_is_email() == false ) $( '#sharing_email input[name=source_email]' ).addClass( 'error' ); - + if ( $( '#sharing_email input[name=target_email]' ).share_is_email() == false ) $( '#sharing_email input[name=target_email]' ).addClass( 'error' ); - + if ( $( '#sharing_email .error' ).length == 0 ) { // AJAX send the form $.ajax( { @@ -298,10 +311,10 @@ var WPCOMSharing = { } } } ); - + return false; } - + form.find( 'img.loading' ).hide(); form.find( 'input[type=submit]' ).removeAttr( 'disabled' ); form.find( 'a.sharing_cancel' ).show(); @@ -310,13 +323,13 @@ var WPCOMSharing = { return false; } ); } - + return false; } ); } ); - + $( 'li.share-email, li.share-custom a.sharing-anchor' ).addClass( 'share-service-visible' ); - } ); + } })( jQuery ); // Recaptcha code diff --git a/plugins/jetpack/modules/sharedaddy/sharing.php b/plugins/jetpack/modules/sharedaddy/sharing.php index 3dd6f8d4..118f851c 100644 --- a/plugins/jetpack/modules/sharedaddy/sharing.php +++ b/plugins/jetpack/modules/sharedaddy/sharing.php @@ -6,7 +6,7 @@ class Sharing_Admin { define( 'WP_SHARING_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'WP_SHARING_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); } - + require_once WP_SHARING_PLUGIN_DIR.'sharing-service.php'; add_action( 'admin_init', array( &$this, 'admin_init' ) ); @@ -21,16 +21,16 @@ class Sharing_Admin { add_action( 'wp_ajax_sharing_new_service', array( &$this, 'ajax_new_service' ) ); add_action( 'wp_ajax_sharing_delete_service', array( &$this, 'ajax_delete_service' ) ); } - + public function sharing_head() { wp_enqueue_script( 'sharing-js', WP_SHARING_PLUGIN_URL.'admin-sharing.js', array( 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-sortable', 'jquery-form' ), 2 ); - wp_enqueue_style( 'sharing-admin', WP_SHARING_PLUGIN_URL.'admin-sharing.css', false, WP_SHARING_PLUGIN_VERSION ); - wp_enqueue_style( 'sharing', WP_SHARING_PLUGIN_URL.'sharing.css', false, WP_SHARING_PLUGIN_VERSION ); - wp_enqueue_script( 'sharing-js-fe', WP_SHARING_PLUGIN_URL . 'sharing.js', array( ), 2 ); + wp_enqueue_style( 'sharing-admin', WP_SHARING_PLUGIN_URL.'admin-sharing.css', false, JETPACK__VERSION ); + wp_enqueue_style( 'sharing', WP_SHARING_PLUGIN_URL.'sharing.css', false, JETPACK__VERSION ); + wp_enqueue_script( 'sharing-js-fe', WP_SHARING_PLUGIN_URL . 'sharing.js', array( ), 3 ); add_thickbox(); } - + public function admin_init() { if ( isset( $_GET['page'] ) && ( $_GET['page'] == 'sharing.php' || $_GET['page'] == 'sharing' ) ) $this->process_requests(); @@ -41,29 +41,35 @@ class Sharing_Admin { $sharer = new Sharing_Service(); $sharer->set_global_options( $_POST ); do_action( 'sharing_admin_update' ); - + wp_safe_redirect( admin_url( 'options-general.php?page=sharing&update=saved' ) ); die(); } } - + public function subscription_menu( $user ) { + if ( !defined( 'IS_WPCOM' ) || !IS_WPCOM ) { + $active = Jetpack::get_active_modules(); + if ( !in_array( 'publicize', $active ) && !current_user_can( 'manage_options' ) ) + return; + } + add_submenu_page( 'options-general.php', __( 'Sharing Settings', 'jetpack' ), __( 'Sharing', 'jetpack' ), 'publish_posts', 'sharing', array( &$this, 'management_page' ) ); } - + public function ajax_save_services() { if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options' ) && isset( $_POST['hidden'] ) && isset( $_POST['visible'] ) ) { $sharer = new Sharing_Service(); - + $sharer->set_blog_services( explode( ',', $_POST['visible'] ), explode( ',', $_POST['hidden'] ) ); die(); } } - + public function ajax_new_service() { if ( isset( $_POST['_wpnonce'] ) && isset( $_POST['sharing_name'] ) && isset( $_POST['sharing_url'] ) && isset( $_POST['sharing_icon'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-new_service' ) ) { $sharer = new Sharing_Service(); - if ( $service = $sharer->new_service( $_POST['sharing_name'], $_POST['sharing_url'], $_POST['sharing_icon'] ) ) { + if ( $service = $sharer->new_service( stripslashes( $_POST['sharing_name'] ), stripslashes( $_POST['sharing_url'] ), stripslashes( $_POST['sharing_icon'] ) ) ) { $this->output_service( $service->get_id(), $service ); echo '<!--->'; $service->button_style = 'icon-text'; @@ -76,14 +82,14 @@ class Sharing_Admin { // Fail die( '1' ); } - + public function ajax_delete_service() { if ( isset( $_POST['_wpnonce'] ) && isset( $_POST['service'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options_'.$_POST['service'] ) ) { $sharer = new Sharing_Service(); $sharer->delete_service( $_POST['service'] ); } } - + public function ajax_save_options() { if ( isset( $_POST['_wpnonce'] ) && isset( $_POST['service'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options_'.$_POST['service'] ) ) { $sharer = new Sharing_Service(); @@ -102,29 +108,23 @@ class Sharing_Admin { die(); } } - + public function output_preview( $service ) { $klasses = array( 'advanced', 'preview-item' ); - - if ( - 'googleplus1' == $service->shortname - || - $service->button_style != 'text' - || - $service->has_custom_button_style() - ) { + + if ( $service->button_style != 'text' || $service->has_custom_button_style() ) { $klasses[] = 'preview-'.$service->get_class(); $klasses[] = 'share-'.$service->get_class(); - + if ( $service->get_class() != $service->get_id() ) $klasses[] = 'preview-'.$service->get_id(); } - + echo '<li class="'.implode( ' ', $klasses ).'">'; echo $service->display_preview(); echo '</li>'; } - + public function output_service( $id, $service, $show_dropdown = false ) { ?> <li class="service advanced share-<?php echo $service->get_class(); ?>" id="<?php echo $service->get_id(); ?>"> @@ -161,21 +161,21 @@ class Sharing_Admin { <div class="wrap"> <div class="icon32" id="icon-options-general"><br /></div> <h2><?php _e( 'Sharing Settings', 'jetpack' ); ?></h2> - + <?php do_action( 'pre_admin_screen_sharing' ) ?> - + <?php if ( current_user_can( 'manage_options' ) ) : ?> - - <h3><?php _e( 'Sharing Buttons' ) ?></h3> - <p><?php _e( 'Add sharing buttons to your blog and allow your visitors to share posts with their friends.' ) ?></p> - + + <h3><?php _e( 'Sharing Buttons', 'jetpack' ) ?></h3> + <p><?php _e( 'Add sharing buttons to your blog and allow your visitors to share posts with their friends.', 'jetpack' ) ?></p> + <div id="services-config"> <table id="available-services"> <tr> <td class="description"> <h3><?php _e( 'Available Services', 'jetpack' ); ?></h3> <p><?php _e( "Drag and drop the services you'd like to enable into the box below.", 'jetpack' ); ?></p> - <p><a href="#TB_inline?height=395&width=600&inlineId=new-service" title="<?php echo esc_attr( __( 'Add a new service', 'jetpack' ) ); ?>" class="thickbox"><?php _e( 'Add a new service', 'jetpack' ); ?></a></p> + <p><a href="#TB_inline?height=395&width=600&inlineId=new-service" title="<?php echo esc_attr( __( 'Add a new service', 'jetpack' ) ); ?>" class="thickbox" id="add-a-new-service"><?php _e( 'Add a new service', 'jetpack' ); ?></a></p> </td> <td class="services"> <ul class="services-available" style="height: 100px;"> @@ -186,11 +186,15 @@ class Sharing_Admin { ?> <?php endforeach; ?> </ul> + <?php + if ( -1 == get_option( 'blog_public' ) ) + echo '<p><strong>'.__( 'Please note that your services have been restricted because your site is private.', 'jetpack' ).'</strong></p>'; + ?> <br class="clearing" /> </td> </tr> </table> - + <table id="enabled-services"> <tr> <td class="description"> @@ -201,19 +205,19 @@ class Sharing_Admin { <p><?php _e( 'Services dragged here will appear individually.', 'jetpack' ); ?></p> </td> <td class="services" id="share-drop-target"> - <h2 id="drag-instructions" <?php if ( count( $enabled['visible'] ) > 0 ) echo ' style="display: none"'; ?>><?php _e( 'Drag and drop available services here', 'jetpack' ); ?></h2> - + <h2 id="drag-instructions" <?php if ( count( $enabled['visible'] ) > 0 ) echo ' style="display: none"'; ?>><?php _e( 'Drag and drop available services here.', 'jetpack' ); ?></h2> + <ul class="services-enabled"> <?php foreach ( $enabled['visible'] as $id => $service ) : ?> <?php $this->output_service( $id, $service, true ); ?> <?php endforeach; ?> - + <li class="end-fix"></li> </ul> - </td> + </td> <td id="hidden-drop-target" class="services"> <p><?php _e( 'Services dragged here will be hidden behind a share button.', 'jetpack' ); ?></p> - + <ul class="services-hidden"> <?php foreach ( $enabled['hidden'] as $id => $service ) : ?> <?php $this->output_service( $id, $service, true ); ?> @@ -222,8 +226,8 @@ class Sharing_Admin { </ul> </td> </tr> - </table> - + </table> + <table id="live-preview"> <tr> <td class="description"> @@ -240,12 +244,12 @@ class Sharing_Admin { <?php foreach ( $enabled['visible'] as $id => $service ) : ?> <?php $this->output_preview( $service ); ?> <?php endforeach; ?> - + <?php if ( count( $enabled['hidden'] ) > 0 ) : ?> <li class="advanced"><a href="#" class="sharing-anchor sd-button share-more"><span><?php _e( 'More', 'jetpack' ); ?></span></a></li> <?php endif; ?> </ul> - + <?php if ( count( $enabled['hidden'] ) > 0 ) : ?> <div class="sharing-hidden"> <div class="inner" style="display: none; <?php echo count( $enabled['hidden'] ) == 1 ? 'width:150px;' : ''; ?>"> @@ -254,18 +258,18 @@ class Sharing_Admin { <?php else: ?> <ul> <?php endif; ?> - + <?php foreach ( $enabled['hidden'] as $id => $service ) { $this->output_preview( $service ); }?> - </ul> + </ul> </div> </div> <?php endif; ?> <ul class="archive" style="display:none;"> - <?php - foreach ( $sharer->get_all_services_blog() as $id => $service ) : + <?php + foreach ( $sharer->get_all_services_blog() as $id => $service ) : if ( isset( $enabled['visible'][$id] ) ) $service = $enabled['visible'][$id]; elseif ( isset( $enabled['hidden'][$id] ) ) @@ -284,7 +288,7 @@ class Sharing_Admin { </td> </tr> </table> - + <form method="post" action="<?php echo admin_url( 'admin-ajax.php' ); ?>" id="save-enabled-shares"> <input type="hidden" name="action" value="sharing_save_services" /> <input type="hidden" name="visible" value="<?php echo implode( ',', array_keys( $enabled['visible'] ) ); ?>" /> @@ -322,7 +326,7 @@ class Sharing_Admin { </select> </td> </tr> - <tr valign="top"> + <?php echo apply_filters( 'sharing_show_buttons_on_row_start', '<tr valign="top">' ); ?> <th scope="row"><label><?php _e( 'Show buttons on', 'jetpack' ); ?></label></th> <td> <?php @@ -338,19 +342,19 @@ class Sharing_Admin { <?php if ( $br ) echo '<br />'; ?><label><input type="checkbox"<?php checked( in_array( $show, $global['show'] ) ); ?> name="show[]" value="<?php echo esc_attr( $show ); ?>" /> <?php echo esc_html( $label ); ?></label> <?php $br = true; endforeach; ?> </td> - </tr> - + <?php echo apply_filters( 'sharing_show_buttons_on_row_end', '</tr>' ); ?> + <?php do_action( 'sharing_global_options' ); ?> </tbody> </table> - + <p class="submit"> <input type="submit" name="submit" class="button-primary" value="<?php _e( 'Save Changes', 'jetpack' ); ?>" /> </p> - + <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'sharing-options' );?>" /> </form> - + <div id="new-service" style="display: none"> <form method="post" action="<?php echo admin_url( 'admin-ajax.php' ); ?>" id="new-service-form"> <table class="form-table"> @@ -365,9 +369,9 @@ class Sharing_Admin { <th scope="row" width="100"><label><?php _e( 'Sharing URL', 'jetpack' ); ?></label></th> <td> <input type="text" name="sharing_url" id="new_sharing_url" size="40" /> - + <p><?php _e( 'You can add the following variables to your service sharing URL:', 'jetpack' ); ?><br/> - <code>%post_title%</code>, <code>%post_url%</code>, <code>%post_full_url%</code>, <code>%post_excerpt%</code>, <code>%post_full_url%</code>, <code>%post_tags%</code></p> + <code>%post_title%</code>, <code>%post_url%</code>, <code>%post_full_url%</code>, <code>%post_excerpt%</code>, <code>%post_tags%</code></p> </td> </tr> <tr valign="top"> @@ -384,7 +388,7 @@ class Sharing_Admin { <img src="<?php echo admin_url( 'images/loading.gif' ); ?>" width="16" height="16" alt="loading" style="vertical-align: middle; display: none" /> </td> </tr> - + <?php do_action( 'sharing_new_service_form' ); ?> </tbody> </table> @@ -394,18 +398,18 @@ class Sharing_Admin { <div class="inerror" style="display: none; margin-top: 15px"> <p><?php _e( 'An error occurred creating your new sharing service - please check you gave valid details.', 'jetpack' ); ?></p> </div> - + <input type="hidden" name="action" value="sharing_new_service" /> - <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'sharing-new_service' );?>" /> + <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce( 'sharing-new_service' );?>" /> </form> </div> - - + + <?php endif; ?> </div> - + <script type="text/javascript"> var sharing_loading_icon = '<?php echo esc_js( admin_url( "/images/loading.gif" ) ); ?>'; <?php if ( isset( $_GET['create_new_service'] ) && 'true' == $_GET['create_new_service'] ) : ?> diff --git a/plugins/jetpack/modules/shortcodes/audio.php b/plugins/jetpack/modules/shortcodes/audio.php index 7b507a5f..9c6b49eb 100644 --- a/plugins/jetpack/modules/shortcodes/audio.php +++ b/plugins/jetpack/modules/shortcodes/audio.php @@ -6,7 +6,7 @@ class AudioShortcode { static $add_script = false; - + /** * Add all the actions & resgister the shortcode */ @@ -161,8 +161,8 @@ class AudioShortcode { // generate default titles $file_titles = array(); - for ( $i = 0; $i < $num_files; $i++ ) { - $file_titles[] = 'Track #' . ($i+1); + for ( $i = 0; $i < $num_files; $i++ ) { + $file_titles[] = 'Track #' . ($i+1); } // replace with real titles if they exist @@ -190,7 +190,7 @@ class AudioShortcode { $to_remove = array(); foreach ( $sound_files as $i => $sfile ) { $file_extension = pathinfo( $sfile, PATHINFO_EXTENSION ); - if ( ! preg_match( '/^(mp3|wav|ogg|oga|m4a|aac|webm)$/', $file_extension ) ) { + if ( ! preg_match( '/^(mp3|wav|ogg|oga|m4a|aac|webm)$/i', $file_extension ) ) { $html5_audio .= '<!-- Audio shortcode unsupported audio format -->'; if ( 1 == $num_files ) { $html5_audio .= $not_supported; @@ -199,7 +199,7 @@ class AudioShortcode { $to_remove[] = $i; // make a note of the bad files $all_mp3 = false; continue; - } elseif ( ! preg_match( '/^mp3$/', $file_extension ) ) { + } elseif ( ! preg_match( '/^mp3$/i', $file_extension ) ) { $all_mp3 = false; } @@ -240,9 +240,9 @@ CONTROLS; // override html5 audio code w/ just not supported code if ( is_feed() ) { $html5_audio = $not_supported; - } + } - if ( $all_mp3 ) { + if ( $all_mp3 ) { // process regular flash player, inserting HTML5 tags into object as fallback $audio_tags = <<<FLASH <object id='wp-as-{$post->ID}_{$ap_playerID}-flash' type='application/x-shockwave-flash' data='$swfurl' width='$width' height='24'> @@ -268,7 +268,7 @@ FLASH; // mashup the artist/titles for the script $script_titles = array(); - for ( $i = 0; $i < $num_files; $i++ ) { + for ( $i = 0; $i < $num_files; $i++ ) { $script_titles[] = $file_artists[$i] . $file_titles[$i]; } @@ -279,16 +279,27 @@ FLASH; $script = <<<SCRIPT <script type='text/javascript'> //<![CDATA[ - jQuery(document).on( 'ready as-script-load', function($) { - if ( typeof window.audioshortcode != 'undefined' ) { + (function() { + var prep = function() { + if ( 'undefined' === typeof window.audioshortcode ) { return; } audioshortcode.prep( '{$post->ID}_{$ap_playerID}', $script_files, $script_titles, $volume, - $script_loop ); + $script_loop + ); + }; + if ( 'undefined' === typeof jQuery ) { + if ( document.addEventListener ) { + window.addEventListener( 'load', prep, false ); + } else if ( document.attachEvent ) { + window.attachEvent( 'onload', prep ); + } + } else { + jQuery(document).on( 'ready as-script-load', prep ); } - } ); + })(); //]]> </script> SCRIPT; @@ -297,7 +308,7 @@ SCRIPT; if ( 0 < $num_good && ! is_feed() ) { $audio_tags .= $script; } - + return "<span style='text-align:left;display:block;'><p>$audio_tags</p></span>"; } @@ -305,11 +316,11 @@ SCRIPT; * If the theme uses infinite scroll, include jquery at the start */ function check_infinite() { - if ( current_theme_supports( 'infinite-scroll' ) ) { + if ( current_theme_supports( 'infinite-scroll' ) && class_exists( 'The_Neverending_Home_Page' ) && The_Neverending_Home_Page::archive_supports_infinity() ) wp_enqueue_script( 'jquery' ); - } } + /** * Dynamically load the .js, if needed * @@ -331,8 +342,8 @@ SCRIPT; wp_as_js.type = 'text/javascript'; wp_as_js.src = $script_url; wp_as_js.async = true; - wp_as_js.onload = function() { - jQuery( document.body ).trigger( 'as-script-load' ); + wp_as_js.onload = function() { + jQuery( document.body ).trigger( 'as-script-load' ); }; document.getElementsByTagName( 'head' )[0].appendChild( wp_as_js ); } else { diff --git a/plugins/jetpack/modules/shortcodes/css/rtl/slideshow-shortcode-rtl.css b/plugins/jetpack/modules/shortcodes/css/rtl/slideshow-shortcode-rtl.css new file mode 100644 index 00000000..167ec706 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/css/rtl/slideshow-shortcode-rtl.css @@ -0,0 +1,131 @@ +/* This file was automatically generated on Jan 06 2013 05:39:50 */ + +.slideshow-window { + background-color: #222; + border: 20px solid #222; + border-radius: 11px; + -moz-border-radius: 11px; + -webkit-border-radius: 11px; + -khtml-border-radius: 11px; + margin-bottom: 20px; +} + +.slideshow-window, .slideshow-window * { + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.slideshow-loading { + height: 100%; + text-align: center; + margin: auto; +} + +body div.slideshow-window * img { + /* Override any styles that might be present in the page stylesheet */ + border-width: 0 !important; + padding: 0 !important; + background-color: transparent !important; + background-image: none !important; + max-width: 100%; +} + +.slideshow-loading img { + vertical-align: middle; +} + +.slideshow-slide { + height: 100%; + width: 100%; + text-align: center; + margin: auto; + display: none; +} + +.slideshow-slide img { + vertical-align: middle; +} + +.slideshow-line-height-hack { + overflow: hide; + width: 0px; + font-size: 0px; +} + +.slideshow-slide-caption { + font-size: 13px; + font-family: "Helvetica Neue", sans-serif; + color: #f7f7f7; + text-shadow: #222 2px 1px 1px; + line-height: 25px; + height: 25px; + position: absolute; + bottom: 5px; + right: 0; + z-index: 100; + width: 100%; + text-align: center; +} + +/* @noflip */ +.slideshow-controls { + z-index: 1000; + position: absolute; + bottom: 30px; + margin: auto; + text-align: center; + width: 100%; + display: none; + direction:ltr; +} + +body div div.slideshow-controls a, +body div div.slideshow-controls a:hover { + border:2px solid rgba(255,255,255,0.1) !important; + background-color: #000 !important; + background-color: rgba(0,0,0,0.6) !important; + background-image: url('../../img/slideshow-controls.png') !important; + background-repeat: no-repeat; + background-size: 142px 16px !important; + background-position: -34px 8px !important; + color: #222 !important; + margin: 0 5px !important; + padding: 0 !important; + display: inline-block !important; + *display: inline; + zoom: 1; + height: 32px !important; + width: 32px !important; + line-height: 32px !important; + text-align: center !important; + -khtml-border-radius: 10em !important; + -webkit-border-radius: 10em !important; + -moz-border-radius: 10em !important; + border-radius: 10em !important; + -webkit-transition: 300ms border-color ease-out; + -moz-transition: 300ms border-color ease-out; + -o-transition: 300ms border-color ease-out; + transition: 300ms border-color ease-out; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5) { + body div div.slideshow-controls a, + body div div.slideshow-controls a:hover { + background-image: url('../../img/slideshow-controls-2x.png') !important; + } +} + +body div div.slideshow-controls a:hover { + border-color: rgba(255,255,255,1) !important; +} + +body div div.slideshow-controls a:first-child { background-position: -76px 8px !important;} +body div div.slideshow-controls a:last-child { background-position: -117px 8px !important;} +body div div.slideshow-controls a:nth-child(2) { background-position: -34px 8px !important;} +body div div.slideshow-controls a.running { background-position: -34px 8px !important;} +body div div.slideshow-controls a.paused { background-position: 9px 8px !important;} + +.slideshow-controls a img { + border: 50px dotted fuchsia; +} diff --git a/plugins/jetpack/modules/shortcodes/css/slideshow-shortcode.css b/plugins/jetpack/modules/shortcodes/css/slideshow-shortcode.css new file mode 100644 index 00000000..e6d05b23 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/css/slideshow-shortcode.css @@ -0,0 +1,129 @@ +.slideshow-window { + background-color: #222; + border: 20px solid #222; + border-radius: 11px; + -moz-border-radius: 11px; + -webkit-border-radius: 11px; + -khtml-border-radius: 11px; + margin-bottom: 20px; +} + +.slideshow-window, .slideshow-window * { + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.slideshow-loading { + height: 100%; + text-align: center; + margin: auto; +} + +body div.slideshow-window * img { + /* Override any styles that might be present in the page stylesheet */ + border-width: 0 !important; + padding: 0 !important; + background-color: transparent !important; + background-image: none !important; + max-width: 100%; +} + +.slideshow-loading img { + vertical-align: middle; +} + +.slideshow-slide { + height: 100%; + width: 100%; + text-align: center; + margin: auto; + display: none; +} + +.slideshow-slide img { + vertical-align: middle; +} + +.slideshow-line-height-hack { + overflow: hide; + width: 0px; + font-size: 0px; +} + +.slideshow-slide-caption { + font-size: 13px; + font-family: "Helvetica Neue", sans-serif; + color: #f7f7f7; + text-shadow: #222 1px 1px 2px; + line-height: 25px; + height: 25px; + position: absolute; + bottom: 5px; + left: 0; + z-index: 100; + width: 100%; + text-align: center; +} + +/* @noflip */ +.slideshow-controls { + z-index: 1000; + position: absolute; + bottom: 30px; + margin: auto; + text-align: center; + width: 100%; + display: none; + direction:ltr; +} + +body div div.slideshow-controls a, +body div div.slideshow-controls a:hover { + border:2px solid rgba(255,255,255,0.1) !important; + background-color: #000 !important; + background-color: rgba(0,0,0,0.6) !important; + background-image: url('../img/slideshow-controls.png') !important; + background-repeat: no-repeat; + background-size: 142px 16px !important; + background-position: -34px 8px !important; + color: #222 !important; + margin: 0 5px !important; + padding: 0 !important; + display: inline-block !important; + *display: inline; + zoom: 1; + height: 32px !important; + width: 32px !important; + line-height: 32px !important; + text-align: center !important; + -khtml-border-radius: 10em !important; + -webkit-border-radius: 10em !important; + -moz-border-radius: 10em !important; + border-radius: 10em !important; + -webkit-transition: 300ms border-color ease-out; + -moz-transition: 300ms border-color ease-out; + -o-transition: 300ms border-color ease-out; + transition: 300ms border-color ease-out; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5) { + body div div.slideshow-controls a, + body div div.slideshow-controls a:hover { + background-image: url('../img/slideshow-controls-2x.png') !important; + } +} + +body div div.slideshow-controls a:hover { + border-color: rgba(255,255,255,1) !important; +} + +body div div.slideshow-controls a:first-child { background-position: -76px 8px !important;} +body div div.slideshow-controls a:last-child { background-position: -117px 8px !important;} +body div div.slideshow-controls a:nth-child(2) { background-position: -34px 8px !important;} +body div div.slideshow-controls a.running { background-position: -34px 8px !important;} +body div div.slideshow-controls a.paused { background-position: 9px 8px !important;} + +.slideshow-controls a img { + border: 50px dotted fuchsia; +} diff --git a/plugins/jetpack/modules/shortcodes/googlemaps.php b/plugins/jetpack/modules/shortcodes/googlemaps.php index 8ced50ea..fe79a9e6 100644 --- a/plugins/jetpack/modules/shortcodes/googlemaps.php +++ b/plugins/jetpack/modules/shortcodes/googlemaps.php @@ -77,7 +77,7 @@ function jetpack_googlemaps_shortcode( $atts ) { $url = substr( $url, 0, -5 ); $link_url = preg_replace( '!output=embed!', 'source=embed', $url ); - return '<iframe width="' . $width . '" height="' . $height . '" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="' . $url . '"></iframe><br /><small><a href="' . $link_url . '" style="text-align:left">View Larger Map</a></small>'; + return '<div class="googlemaps"><iframe width="' . $width . '" height="' . $height . '" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="' . $url . '"></iframe><br /><small><a href="' . $link_url . '" style="text-align:left">View Larger Map</a></small></div>'; } } add_shortcode( 'googlemaps', 'jetpack_googlemaps_shortcode' ); diff --git a/plugins/jetpack/modules/shortcodes/img/slideshow-controls-2x.png b/plugins/jetpack/modules/shortcodes/img/slideshow-controls-2x.png Binary files differnew file mode 100644 index 00000000..2c76ac05 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/img/slideshow-controls-2x.png diff --git a/plugins/jetpack/modules/shortcodes/img/slideshow-controls.png b/plugins/jetpack/modules/shortcodes/img/slideshow-controls.png Binary files differnew file mode 100644 index 00000000..09ca4871 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/img/slideshow-controls.png diff --git a/plugins/jetpack/modules/shortcodes/img/slideshow-loader.gif b/plugins/jetpack/modules/shortcodes/img/slideshow-loader.gif Binary files differnew file mode 100644 index 00000000..ce1c594e --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/img/slideshow-loader.gif diff --git a/plugins/jetpack/modules/shortcodes/js/audio-shortcode.js b/plugins/jetpack/modules/shortcodes/js/audio-shortcode.js new file mode 100644 index 00000000..7697258e --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/js/audio-shortcode.js @@ -0,0 +1,154 @@ +(function($) { + +window.audioshortcode = { + + /** + * Prep the audio player once the page is ready, add listeners, etc + */ + prep: function( player_id, files, titles, volume, loop ) { + // check if the player has already been prepped, no-op if it has + var container = $( '#wp-as-' + player_id + '-container' ); + if ( container.hasClass( 'wp-as-prepped' ) ) { + return; + } + container.addClass( 'wp-as-prepped' ); + + // browser doesn't support HTML5 audio, no-op + if ( ! document.createElement('audio').canPlayType ) { + return; + } + + // if the browser removed the script, no-op + player = $( '#wp-as-' + player_id ).get(0); + if ( typeof player === 'undefined' ) { + return; + } + + this[player_id] = []; + this[player_id].i = 0; + this[player_id].files = files; + this[player_id].titles = titles; + player.volume = volume; + + var type_map = { + 'mp3': 'mpeg', + 'wav': 'wav', + 'ogg': 'ogg', + 'oga': 'ogg', + 'm4a': 'mp4', + 'aac': 'mp4', + 'webm': 'webm' + }; + + // strip out all the files that can't be played + for ( var i = this[player_id].files.length-1; i >= 0; i-- ) { + var extension = this[player_id].files[i].split( '.' ).pop(); + var type = 'audio/' + type_map[extension]; + if ( ! player.canPlayType( type ) ) { + this.remove_track( player_id, i ); + } + } + + // bail if there are no more good files + if ( 0 == this[player_id].files.length ) { + return; + } + player.src = this[player_id].files[0]; + + // show the controls if there are still 2+ files remaining + if ( 1 < this[player_id].files.length ) { + $( '#wp-as-' + player_id + '-controls' ).show(); + } + + player.addEventListener( 'error', function() { + audioshortcode.remove_track( player_id, audioshortcode[player_id].i ); + if ( 0 < audioshortcode[player_id].files.length ) { + audioshortcode[player_id].i--; + audioshortcode.next_track( player_id, false, loop ); + } + }, false ); + + player.addEventListener( 'ended', function() { + audioshortcode.next_track( player_id, false, loop ); + }, false ); + + player.addEventListener( 'play', function() { + var i = audioshortcode[player_id].i; + var titles = audioshortcode[player_id].titles; + $( '#wp-as-' + player_id + '-playing' ).text( ' ' + titles[i] ); + }, false ); + + player.addEventListener( 'pause', function() { + $( '#wp-as-' + player_id + '-playing' ).text( '' ); + }, false ); + }, + + /** + * Remove the track and update the player/controls if needed + */ + remove_track: function( player_id, index ) { + this[player_id].files.splice( index, 1 ); + this[player_id].titles.splice( index, 1 ); + + // get rid of player/controls if they can't be played + if ( 0 == this[player_id].files.length ) { + $( '#wp-as-' + player_id + '-container' ).html( $( '#wp-as-' + player_id + '-nope' ).html() ); + $( '#wp-as-' + player_id + '-controls' ).html( '' ); + } else if ( 1 == this[player_id].files.length ) { + $( '#wp-as-' + player_id + '-controls' ).html( '' ); + } + }, + + /** + * Change the src of the player, load the file, then play it + */ + start_track: function( player_id, file ) { + var player = $( '#wp-as-' + player_id ).get(0); + player.src = file; + player.load(); + player.play(); + }, + + /** + * Play the previous track + */ + prev_track: function( player_id ) { + var player = $( '#wp-as-' + player_id ).get(0); + var files = this[player_id].files; + if ( player.paused || 0 == this[player_id].i ) { + return + }; + + player.pause(); + if ( 0 < this[player_id].i ) { + this[player_id].i--; + this.start_track( player_id, files[this[player_id].i] ); + } + }, + + /** + * Play the next track + */ + next_track: function( player_id, fromClick, loop ) { + var player = $( '#wp-as-' + player_id ).get(0); + var files = this[player_id].files; + if ( fromClick && ( player.paused || files.length-1 == this[player_id].i ) ) { + return; + } + + player.pause(); + if ( files.length-1 > this[player_id].i ) { + this[player_id].i++; + this.start_track( player_id, files[this[player_id].i] ); + } else if ( loop ) { + this[player_id].i = 0; + this.start_track( player_id, 0 ); + } else { + this[player_id].i = 0; + player.src = files[0]; + $( '#wp-as-' + player_id + '-playing' ).text( '' ); + } + } +}; + +})(jQuery); diff --git a/plugins/jetpack/modules/shortcodes/js/jquery.cycle.js b/plugins/jetpack/modules/shortcodes/js/jquery.cycle.js new file mode 100644 index 00000000..89d583eb --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/js/jquery.cycle.js @@ -0,0 +1,1551 @@ +/*! + * jQuery Cycle Plugin (with Transition Definitions) + * Examples and documentation at: http://jquery.malsup.com/cycle/ + * Copyright (c) 2007-2010 M. Alsup + * Version: 2.9999.8 (26-OCT-2012) + * Dual licensed under the MIT and GPL licenses. + * http://jquery.malsup.com/license.html + * Requires: jQuery v1.3.2 or later + */ +;(function($, undefined) { +"use strict"; + +var ver = '2.9999.8'; + +// if $.support is not defined (pre jQuery 1.3) add what I need +if ($.support === undefined) { + $.support = { + opacity: !($.browser.msie) + }; +} + +function debug(s) { + if ($.fn.cycle.debug) + log(s); +} +function log() { + if (window.console && console.log) + console.log('[cycle] ' + Array.prototype.join.call(arguments,' ')); +} +$.expr[':'].paused = function(el) { + return el.cyclePause; +}; + + +// the options arg can be... +// a number - indicates an immediate transition should occur to the given slide index +// a string - 'pause', 'resume', 'toggle', 'next', 'prev', 'stop', 'destroy' or the name of a transition effect (ie, 'fade', 'zoom', etc) +// an object - properties to control the slideshow +// +// the arg2 arg can be... +// the name of an fx (only used in conjunction with a numeric value for 'options') +// the value true (only used in first arg == 'resume') and indicates +// that the resume should occur immediately (not wait for next timeout) + +$.fn.cycle = function(options, arg2) { + var o = { s: this.selector, c: this.context }; + + // in 1.3+ we can fix mistakes with the ready state + if (this.length === 0 && options != 'stop') { + if (!$.isReady && o.s) { + log('DOM not ready, queuing slideshow'); + $(function() { + $(o.s,o.c).cycle(options,arg2); + }); + return this; + } + // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready() + log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)')); + return this; + } + + // iterate the matched nodeset + return this.each(function() { + var opts = handleArguments(this, options, arg2); + if (opts === false) + return; + + opts.updateActivePagerLink = opts.updateActivePagerLink || $.fn.cycle.updateActivePagerLink; + + // stop existing slideshow for this container (if there is one) + if (this.cycleTimeout) + clearTimeout(this.cycleTimeout); + this.cycleTimeout = this.cyclePause = 0; + this.cycleStop = 0; // issue #108 + + var $cont = $(this); + var $slides = opts.slideExpr ? $(opts.slideExpr, this) : $cont.children(); + var els = $slides.get(); + + if (els.length < 2) { + log('terminating; too few slides: ' + els.length); + return; + } + + var opts2 = buildOptions($cont, $slides, els, opts, o); + if (opts2 === false) + return; + + var startTime = opts2.continuous ? 10 : getTimeout(els[opts2.currSlide], els[opts2.nextSlide], opts2, !opts2.backwards); + + // if it's an auto slideshow, kick it off + if (startTime) { + startTime += (opts2.delay || 0); + if (startTime < 10) + startTime = 10; + debug('first timeout: ' + startTime); + this.cycleTimeout = setTimeout(function(){go(els,opts2,0,!opts.backwards);}, startTime); + } + }); +}; + +function triggerPause(cont, byHover, onPager) { + var opts = $(cont).data('cycle.opts'); + if (!opts) + return; + var paused = !!cont.cyclePause; + if (paused && opts.paused) + opts.paused(cont, opts, byHover, onPager); + else if (!paused && opts.resumed) + opts.resumed(cont, opts, byHover, onPager); +} + +// process the args that were passed to the plugin fn +function handleArguments(cont, options, arg2) { + if (cont.cycleStop === undefined) + cont.cycleStop = 0; + if (options === undefined || options === null) + options = {}; + if (options.constructor == String) { + switch(options) { + case 'destroy': + case 'stop': + var opts = $(cont).data('cycle.opts'); + if (!opts) + return false; + cont.cycleStop++; // callbacks look for change + if (cont.cycleTimeout) + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + if (opts.elements) + $(opts.elements).stop(); + $(cont).removeData('cycle.opts'); + if (options == 'destroy') + destroy(cont, opts); + return false; + case 'toggle': + cont.cyclePause = (cont.cyclePause === 1) ? 0 : 1; + checkInstantResume(cont.cyclePause, arg2, cont); + triggerPause(cont); + return false; + case 'pause': + cont.cyclePause = 1; + triggerPause(cont); + return false; + case 'resume': + cont.cyclePause = 0; + checkInstantResume(false, arg2, cont); + triggerPause(cont); + return false; + case 'prev': + case 'next': + opts = $(cont).data('cycle.opts'); + if (!opts) { + log('options not found, "prev/next" ignored'); + return false; + } + $.fn.cycle[options](opts); + return false; + default: + options = { fx: options }; + } + return options; + } + else if (options.constructor == Number) { + // go to the requested slide + var num = options; + options = $(cont).data('cycle.opts'); + if (!options) { + log('options not found, can not advance slide'); + return false; + } + if (num < 0 || num >= options.elements.length) { + log('invalid slide index: ' + num); + return false; + } + options.nextSlide = num; + if (cont.cycleTimeout) { + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + } + if (typeof arg2 == 'string') + options.oneTimeFx = arg2; + go(options.elements, options, 1, num >= options.currSlide); + return false; + } + return options; + + function checkInstantResume(isPaused, arg2, cont) { + if (!isPaused && arg2 === true) { // resume now! + var options = $(cont).data('cycle.opts'); + if (!options) { + log('options not found, can not resume'); + return false; + } + if (cont.cycleTimeout) { + clearTimeout(cont.cycleTimeout); + cont.cycleTimeout = 0; + } + go(options.elements, options, 1, !options.backwards); + } + } +} + +function removeFilter(el, opts) { + if (!$.support.opacity && opts.cleartype && el.style.filter) { + try { el.style.removeAttribute('filter'); } + catch(smother) {} // handle old opera versions + } +} + +// unbind event handlers +function destroy(cont, opts) { + if (opts.next) + $(opts.next).unbind(opts.prevNextEvent); + if (opts.prev) + $(opts.prev).unbind(opts.prevNextEvent); + + if (opts.pager || opts.pagerAnchorBuilder) + $.each(opts.pagerAnchors || [], function() { + this.unbind().remove(); + }); + opts.pagerAnchors = null; + $(cont).unbind('mouseenter.cycle mouseleave.cycle'); + if (opts.destroy) // callback + opts.destroy(opts); +} + +// one-time initialization +function buildOptions($cont, $slides, els, options, o) { + var startingSlideSpecified; + // support metadata plugin (v1.0 and v2.0) + var opts = $.extend({}, $.fn.cycle.defaults, options || {}, $.metadata ? $cont.metadata() : $.meta ? $cont.data() : {}); + var meta = $.isFunction($cont.data) ? $cont.data(opts.metaAttr) : null; + if (meta) + opts = $.extend(opts, meta); + if (opts.autostop) + opts.countdown = opts.autostopCount || els.length; + + var cont = $cont[0]; + $cont.data('cycle.opts', opts); + opts.$cont = $cont; + opts.stopCount = cont.cycleStop; + opts.elements = els; + opts.before = opts.before ? [opts.before] : []; + opts.after = opts.after ? [opts.after] : []; + + // push some after callbacks + if (!$.support.opacity && opts.cleartype) + opts.after.push(function() { removeFilter(this, opts); }); + if (opts.continuous) + opts.after.push(function() { go(els,opts,0,!opts.backwards); }); + + saveOriginalOpts(opts); + + // clearType corrections + if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) + clearTypeFix($slides); + + // container requires non-static position so that slides can be position within + if ($cont.css('position') == 'static') + $cont.css('position', 'relative'); + if (opts.width) + $cont.width(opts.width); + if (opts.height && opts.height != 'auto') + $cont.height(opts.height); + + if (opts.startingSlide !== undefined) { + opts.startingSlide = parseInt(opts.startingSlide,10); + if (opts.startingSlide >= els.length || opts.startSlide < 0) + opts.startingSlide = 0; // catch bogus input + else + startingSlideSpecified = true; + } + else if (opts.backwards) + opts.startingSlide = els.length - 1; + else + opts.startingSlide = 0; + + // if random, mix up the slide array + if (opts.random) { + opts.randomMap = []; + for (var i = 0; i < els.length; i++) + opts.randomMap.push(i); + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + if (startingSlideSpecified) { + // try to find the specified starting slide and if found set start slide index in the map accordingly + for ( var cnt = 0; cnt < els.length; cnt++ ) { + if ( opts.startingSlide == opts.randomMap[cnt] ) { + opts.randomIndex = cnt; + } + } + } + else { + opts.randomIndex = 1; + opts.startingSlide = opts.randomMap[1]; + } + } + else if (opts.startingSlide >= els.length) + opts.startingSlide = 0; // catch bogus input + opts.currSlide = opts.startingSlide || 0; + var first = opts.startingSlide; + + // set position and zIndex on all the slides + $slides.css({position: 'absolute', top:0, left:0}).hide().each(function(i) { + var z; + if (opts.backwards) + z = first ? i <= first ? els.length + (i-first) : first-i : els.length-i; + else + z = first ? i >= first ? els.length - (i-first) : first-i : els.length-i; + $(this).css('z-index', z); + }); + + // make sure first slide is visible + $(els[first]).css('opacity',1).show(); // opacity bit needed to handle restart use case + removeFilter(els[first], opts); + + // stretch slides + if (opts.fit) { + if (!opts.aspect) { + if (opts.width) + $slides.width(opts.width); + if (opts.height && opts.height != 'auto') + $slides.height(opts.height); + } else { + $slides.each(function(){ + var $slide = $(this); + var ratio = (opts.aspect === true) ? $slide.width()/$slide.height() : opts.aspect; + if( opts.width && $slide.width() != opts.width ) { + $slide.width( opts.width ); + $slide.height( opts.width / ratio ); + } + + if( opts.height && $slide.height() < opts.height ) { + $slide.height( opts.height ); + $slide.width( opts.height * ratio ); + } + }); + } + } + + if (opts.center && ((!opts.fit) || opts.aspect)) { + $slides.each(function(){ + var $slide = $(this); + $slide.css({ + "margin-left": opts.width ? + ((opts.width - $slide.width()) / 2) + "px" : + 0, + "margin-top": opts.height ? + ((opts.height - $slide.height()) / 2) + "px" : + 0 + }); + }); + } + + if (opts.center && !opts.fit && !opts.slideResize) { + $slides.each(function(){ + var $slide = $(this); + $slide.css({ + "margin-left": opts.width ? ((opts.width - $slide.width()) / 2) + "px" : 0, + "margin-top": opts.height ? ((opts.height - $slide.height()) / 2) + "px" : 0 + }); + }); + } + + // stretch container + var reshape = (opts.containerResize || opts.containerResizeHeight) && !$cont.innerHeight(); + if (reshape) { // do this only if container has no size http://tinyurl.com/da2oa9 + var maxw = 0, maxh = 0; + for(var j=0; j < els.length; j++) { + var $e = $(els[j]), e = $e[0], w = $e.outerWidth(), h = $e.outerHeight(); + if (!w) w = e.offsetWidth || e.width || $e.attr('width'); + if (!h) h = e.offsetHeight || e.height || $e.attr('height'); + maxw = w > maxw ? w : maxw; + maxh = h > maxh ? h : maxh; + } + if (opts.containerResize && maxw > 0 && maxh > 0) + $cont.css({width:maxw+'px',height:maxh+'px'}); + if (opts.containerResizeHeight && maxh > 0) + $cont.css({height:maxh+'px'}); + } + + var pauseFlag = false; // https://github.com/malsup/cycle/issues/44 + if (opts.pause) + $cont.bind('mouseenter.cycle', function(){ + pauseFlag = true; + this.cyclePause++; + triggerPause(cont, true); + }).bind('mouseleave.cycle', function(){ + if (pauseFlag) + this.cyclePause--; + triggerPause(cont, true); + }); + + if (supportMultiTransitions(opts) === false) + return false; + + // apparently a lot of people use image slideshows without height/width attributes on the images. + // Cycle 2.50+ requires the sizing info for every slide; this block tries to deal with that. + var requeue = false; + options.requeueAttempts = options.requeueAttempts || 0; + $slides.each(function() { + // try to get height/width of each slide + var $el = $(this); + this.cycleH = (opts.fit && opts.height) ? opts.height : ($el.height() || this.offsetHeight || this.height || $el.attr('height') || 0); + this.cycleW = (opts.fit && opts.width) ? opts.width : ($el.width() || this.offsetWidth || this.width || $el.attr('width') || 0); + + if ( $el.is('img') ) { + // sigh.. sniffing, hacking, shrugging... this crappy hack tries to account for what browsers do when + // an image is being downloaded and the markup did not include sizing info (height/width attributes); + // there seems to be some "default" sizes used in this situation + var loadingIE = ($.browser.msie && this.cycleW == 28 && this.cycleH == 30 && !this.complete); + var loadingFF = ($.browser.mozilla && this.cycleW == 34 && this.cycleH == 19 && !this.complete); + var loadingOp = ($.browser.opera && ((this.cycleW == 42 && this.cycleH == 19) || (this.cycleW == 37 && this.cycleH == 17)) && !this.complete); + var loadingOther = (this.cycleH === 0 && this.cycleW === 0 && !this.complete); + // don't requeue for images that are still loading but have a valid size + if (loadingIE || loadingFF || loadingOp || loadingOther) { + if (o.s && opts.requeueOnImageNotLoaded && ++options.requeueAttempts < 100) { // track retry count so we don't loop forever + log(options.requeueAttempts,' - img slide not loaded, requeuing slideshow: ', this.src, this.cycleW, this.cycleH); + setTimeout(function() {$(o.s,o.c).cycle(options);}, opts.requeueTimeout); + requeue = true; + return false; // break each loop + } + else { + log('could not determine size of image: '+this.src, this.cycleW, this.cycleH); + } + } + } + return true; + }); + + if (requeue) + return false; + + opts.cssBefore = opts.cssBefore || {}; + opts.cssAfter = opts.cssAfter || {}; + opts.cssFirst = opts.cssFirst || {}; + opts.animIn = opts.animIn || {}; + opts.animOut = opts.animOut || {}; + + $slides.not(':eq('+first+')').css(opts.cssBefore); + $($slides[first]).css(opts.cssFirst); + + if (opts.timeout) { + opts.timeout = parseInt(opts.timeout,10); + // ensure that timeout and speed settings are sane + if (opts.speed.constructor == String) + opts.speed = $.fx.speeds[opts.speed] || parseInt(opts.speed,10); + if (!opts.sync) + opts.speed = opts.speed / 2; + + var buffer = opts.fx == 'none' ? 0 : opts.fx == 'shuffle' ? 500 : 250; + while((opts.timeout - opts.speed) < buffer) // sanitize timeout + opts.timeout += opts.speed; + } + if (opts.easing) + opts.easeIn = opts.easeOut = opts.easing; + if (!opts.speedIn) + opts.speedIn = opts.speed; + if (!opts.speedOut) + opts.speedOut = opts.speed; + + opts.slideCount = els.length; + opts.currSlide = opts.lastSlide = first; + if (opts.random) { + if (++opts.randomIndex == els.length) + opts.randomIndex = 0; + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else if (opts.backwards) + opts.nextSlide = opts.startingSlide === 0 ? (els.length-1) : opts.startingSlide-1; + else + opts.nextSlide = opts.startingSlide >= (els.length-1) ? 0 : opts.startingSlide+1; + + // run transition init fn + if (!opts.multiFx) { + var init = $.fn.cycle.transitions[opts.fx]; + if ($.isFunction(init)) + init($cont, $slides, opts); + else if (opts.fx != 'custom' && !opts.multiFx) { + log('unknown transition: ' + opts.fx,'; slideshow terminating'); + return false; + } + } + + // fire artificial events + var e0 = $slides[first]; + if (!opts.skipInitializationCallbacks) { + if (opts.before.length) + opts.before[0].apply(e0, [e0, e0, opts, true]); + if (opts.after.length) + opts.after[0].apply(e0, [e0, e0, opts, true]); + } + if (opts.next) + $(opts.next).bind(opts.prevNextEvent,function(){return advance(opts,1);}); + if (opts.prev) + $(opts.prev).bind(opts.prevNextEvent,function(){return advance(opts,0);}); + if (opts.pager || opts.pagerAnchorBuilder) + buildPager(els,opts); + + exposeAddSlide(opts, els); + + return opts; +} + +// save off original opts so we can restore after clearing state +function saveOriginalOpts(opts) { + opts.original = { before: [], after: [] }; + opts.original.cssBefore = $.extend({}, opts.cssBefore); + opts.original.cssAfter = $.extend({}, opts.cssAfter); + opts.original.animIn = $.extend({}, opts.animIn); + opts.original.animOut = $.extend({}, opts.animOut); + $.each(opts.before, function() { opts.original.before.push(this); }); + $.each(opts.after, function() { opts.original.after.push(this); }); +} + +function supportMultiTransitions(opts) { + var i, tx, txs = $.fn.cycle.transitions; + // look for multiple effects + if (opts.fx.indexOf(',') > 0) { + opts.multiFx = true; + opts.fxs = opts.fx.replace(/\s*/g,'').split(','); + // discard any bogus effect names + for (i=0; i < opts.fxs.length; i++) { + var fx = opts.fxs[i]; + tx = txs[fx]; + if (!tx || !txs.hasOwnProperty(fx) || !$.isFunction(tx)) { + log('discarding unknown transition: ',fx); + opts.fxs.splice(i,1); + i--; + } + } + // if we have an empty list then we threw everything away! + if (!opts.fxs.length) { + log('No valid transitions named; slideshow terminating.'); + return false; + } + } + else if (opts.fx == 'all') { // auto-gen the list of transitions + opts.multiFx = true; + opts.fxs = []; + for (var p in txs) { + if (txs.hasOwnProperty(p)) { + tx = txs[p]; + if (txs.hasOwnProperty(p) && $.isFunction(tx)) + opts.fxs.push(p); + } + } + } + if (opts.multiFx && opts.randomizeEffects) { + // munge the fxs array to make effect selection random + var r1 = Math.floor(Math.random() * 20) + 30; + for (i = 0; i < r1; i++) { + var r2 = Math.floor(Math.random() * opts.fxs.length); + opts.fxs.push(opts.fxs.splice(r2,1)[0]); + } + debug('randomized fx sequence: ',opts.fxs); + } + return true; +} + +// provide a mechanism for adding slides after the slideshow has started +function exposeAddSlide(opts, els) { + opts.addSlide = function(newSlide, prepend) { + var $s = $(newSlide), s = $s[0]; + if (!opts.autostopCount) + opts.countdown++; + els[prepend?'unshift':'push'](s); + if (opts.els) + opts.els[prepend?'unshift':'push'](s); // shuffle needs this + opts.slideCount = els.length; + + // add the slide to the random map and resort + if (opts.random) { + opts.randomMap.push(opts.slideCount-1); + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + } + + $s.css('position','absolute'); + $s[prepend?'prependTo':'appendTo'](opts.$cont); + + if (prepend) { + opts.currSlide++; + opts.nextSlide++; + } + + if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) + clearTypeFix($s); + + if (opts.fit && opts.width) + $s.width(opts.width); + if (opts.fit && opts.height && opts.height != 'auto') + $s.height(opts.height); + s.cycleH = (opts.fit && opts.height) ? opts.height : $s.height(); + s.cycleW = (opts.fit && opts.width) ? opts.width : $s.width(); + + $s.css(opts.cssBefore); + + if (opts.pager || opts.pagerAnchorBuilder) + $.fn.cycle.createPagerAnchor(els.length-1, s, $(opts.pager), els, opts); + + if ($.isFunction(opts.onAddSlide)) + opts.onAddSlide($s); + else + $s.hide(); // default behavior + }; +} + +// reset internal state; we do this on every pass in order to support multiple effects +$.fn.cycle.resetState = function(opts, fx) { + fx = fx || opts.fx; + opts.before = []; opts.after = []; + opts.cssBefore = $.extend({}, opts.original.cssBefore); + opts.cssAfter = $.extend({}, opts.original.cssAfter); + opts.animIn = $.extend({}, opts.original.animIn); + opts.animOut = $.extend({}, opts.original.animOut); + opts.fxFn = null; + $.each(opts.original.before, function() { opts.before.push(this); }); + $.each(opts.original.after, function() { opts.after.push(this); }); + + // re-init + var init = $.fn.cycle.transitions[fx]; + if ($.isFunction(init)) + init(opts.$cont, $(opts.elements), opts); +}; + +// this is the main engine fn, it handles the timeouts, callbacks and slide index mgmt +function go(els, opts, manual, fwd) { + var p = opts.$cont[0], curr = els[opts.currSlide], next = els[opts.nextSlide]; + + // opts.busy is true if we're in the middle of an animation + if (manual && opts.busy && opts.manualTrump) { + // let manual transitions requests trump active ones + debug('manualTrump in go(), stopping active transition'); + $(els).stop(true,true); + opts.busy = 0; + clearTimeout(p.cycleTimeout); + } + + // don't begin another timeout-based transition if there is one active + if (opts.busy) { + debug('transition active, ignoring new tx request'); + return; + } + + + // stop cycling if we have an outstanding stop request + if (p.cycleStop != opts.stopCount || p.cycleTimeout === 0 && !manual) + return; + + // check to see if we should stop cycling based on autostop options + if (!manual && !p.cyclePause && !opts.bounce && + ((opts.autostop && (--opts.countdown <= 0)) || + (opts.nowrap && !opts.random && opts.nextSlide < opts.currSlide))) { + if (opts.end) + opts.end(opts); + return; + } + + // if slideshow is paused, only transition on a manual trigger + var changed = false; + if ((manual || !p.cyclePause) && (opts.nextSlide != opts.currSlide)) { + changed = true; + var fx = opts.fx; + // keep trying to get the slide size if we don't have it yet + curr.cycleH = curr.cycleH || $(curr).height(); + curr.cycleW = curr.cycleW || $(curr).width(); + next.cycleH = next.cycleH || $(next).height(); + next.cycleW = next.cycleW || $(next).width(); + + // support multiple transition types + if (opts.multiFx) { + if (fwd && (opts.lastFx === undefined || ++opts.lastFx >= opts.fxs.length)) + opts.lastFx = 0; + else if (!fwd && (opts.lastFx === undefined || --opts.lastFx < 0)) + opts.lastFx = opts.fxs.length - 1; + fx = opts.fxs[opts.lastFx]; + } + + // one-time fx overrides apply to: $('div').cycle(3,'zoom'); + if (opts.oneTimeFx) { + fx = opts.oneTimeFx; + opts.oneTimeFx = null; + } + + $.fn.cycle.resetState(opts, fx); + + // run the before callbacks + if (opts.before.length) + $.each(opts.before, function(i,o) { + if (p.cycleStop != opts.stopCount) return; + o.apply(next, [curr, next, opts, fwd]); + }); + + // stage the after callacks + var after = function() { + opts.busy = 0; + $.each(opts.after, function(i,o) { + if (p.cycleStop != opts.stopCount) return; + o.apply(next, [curr, next, opts, fwd]); + }); + if (!p.cycleStop) { + // queue next transition + queueNext(); + } + }; + + debug('tx firing('+fx+'); currSlide: ' + opts.currSlide + '; nextSlide: ' + opts.nextSlide); + + // get ready to perform the transition + opts.busy = 1; + if (opts.fxFn) // fx function provided? + opts.fxFn(curr, next, opts, after, fwd, manual && opts.fastOnEvent); + else if ($.isFunction($.fn.cycle[opts.fx])) // fx plugin ? + $.fn.cycle[opts.fx](curr, next, opts, after, fwd, manual && opts.fastOnEvent); + else + $.fn.cycle.custom(curr, next, opts, after, fwd, manual && opts.fastOnEvent); + } + else { + queueNext(); + } + + if (changed || opts.nextSlide == opts.currSlide) { + // calculate the next slide + var roll; + opts.lastSlide = opts.currSlide; + if (opts.random) { + opts.currSlide = opts.nextSlide; + if (++opts.randomIndex == els.length) { + opts.randomIndex = 0; + opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;}); + } + opts.nextSlide = opts.randomMap[opts.randomIndex]; + if (opts.nextSlide == opts.currSlide) + opts.nextSlide = (opts.currSlide == opts.slideCount - 1) ? 0 : opts.currSlide + 1; + } + else if (opts.backwards) { + roll = (opts.nextSlide - 1) < 0; + if (roll && opts.bounce) { + opts.backwards = !opts.backwards; + opts.nextSlide = 1; + opts.currSlide = 0; + } + else { + opts.nextSlide = roll ? (els.length-1) : opts.nextSlide-1; + opts.currSlide = roll ? 0 : opts.nextSlide+1; + } + } + else { // sequence + roll = (opts.nextSlide + 1) == els.length; + if (roll && opts.bounce) { + opts.backwards = !opts.backwards; + opts.nextSlide = els.length-2; + opts.currSlide = els.length-1; + } + else { + opts.nextSlide = roll ? 0 : opts.nextSlide+1; + opts.currSlide = roll ? els.length-1 : opts.nextSlide-1; + } + } + } + if (changed && opts.pager) + opts.updateActivePagerLink(opts.pager, opts.currSlide, opts.activePagerClass); + + function queueNext() { + // stage the next transition + var ms = 0, timeout = opts.timeout; + if (opts.timeout && !opts.continuous) { + ms = getTimeout(els[opts.currSlide], els[opts.nextSlide], opts, fwd); + if (opts.fx == 'shuffle') + ms -= opts.speedOut; + } + else if (opts.continuous && p.cyclePause) // continuous shows work off an after callback, not this timer logic + ms = 10; + if (ms > 0) + p.cycleTimeout = setTimeout(function(){ go(els, opts, 0, !opts.backwards); }, ms); + } +} + +// invoked after transition +$.fn.cycle.updateActivePagerLink = function(pager, currSlide, clsName) { + $(pager).each(function() { + $(this).children().removeClass(clsName).eq(currSlide).addClass(clsName); + }); +}; + +// calculate timeout value for current transition +function getTimeout(curr, next, opts, fwd) { + if (opts.timeoutFn) { + // call user provided calc fn + var t = opts.timeoutFn.call(curr,curr,next,opts,fwd); + while (opts.fx != 'none' && (t - opts.speed) < 250) // sanitize timeout + t += opts.speed; + debug('calculated timeout: ' + t + '; speed: ' + opts.speed); + if (t !== false) + return t; + } + return opts.timeout; +} + +// expose next/prev function, caller must pass in state +$.fn.cycle.next = function(opts) { advance(opts,1); }; +$.fn.cycle.prev = function(opts) { advance(opts,0);}; + +// advance slide forward or back +function advance(opts, moveForward) { + var val = moveForward ? 1 : -1; + var els = opts.elements; + var p = opts.$cont[0], timeout = p.cycleTimeout; + if (timeout) { + clearTimeout(timeout); + p.cycleTimeout = 0; + } + if (opts.random && val < 0) { + // move back to the previously display slide + opts.randomIndex--; + if (--opts.randomIndex == -2) + opts.randomIndex = els.length-2; + else if (opts.randomIndex == -1) + opts.randomIndex = els.length-1; + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else if (opts.random) { + opts.nextSlide = opts.randomMap[opts.randomIndex]; + } + else { + opts.nextSlide = opts.currSlide + val; + if (opts.nextSlide < 0) { + if (opts.nowrap) return false; + opts.nextSlide = els.length - 1; + } + else if (opts.nextSlide >= els.length) { + if (opts.nowrap) return false; + opts.nextSlide = 0; + } + } + + var cb = opts.onPrevNextEvent || opts.prevNextClick; // prevNextClick is deprecated + if ($.isFunction(cb)) + cb(val > 0, opts.nextSlide, els[opts.nextSlide]); + go(els, opts, 1, moveForward); + return false; +} + +function buildPager(els, opts) { + var $p = $(opts.pager); + $.each(els, function(i,o) { + $.fn.cycle.createPagerAnchor(i,o,$p,els,opts); + }); + opts.updateActivePagerLink(opts.pager, opts.startingSlide, opts.activePagerClass); +} + +$.fn.cycle.createPagerAnchor = function(i, el, $p, els, opts) { + var a; + if ($.isFunction(opts.pagerAnchorBuilder)) { + a = opts.pagerAnchorBuilder(i,el); + debug('pagerAnchorBuilder('+i+', el) returned: ' + a); + } + else + a = '<a href="#">'+(i+1)+'</a>'; + + if (!a) + return; + var $a = $(a); + // don't reparent if anchor is in the dom + if ($a.parents('body').length === 0) { + var arr = []; + if ($p.length > 1) { + $p.each(function() { + var $clone = $a.clone(true); + $(this).append($clone); + arr.push($clone[0]); + }); + $a = $(arr); + } + else { + $a.appendTo($p); + } + } + + opts.pagerAnchors = opts.pagerAnchors || []; + opts.pagerAnchors.push($a); + + var pagerFn = function(e) { + e.preventDefault(); + opts.nextSlide = i; + var p = opts.$cont[0], timeout = p.cycleTimeout; + if (timeout) { + clearTimeout(timeout); + p.cycleTimeout = 0; + } + var cb = opts.onPagerEvent || opts.pagerClick; // pagerClick is deprecated + if ($.isFunction(cb)) + cb(opts.nextSlide, els[opts.nextSlide]); + go(els,opts,1,opts.currSlide < i); // trigger the trans +// return false; // <== allow bubble + }; + + if ( /mouseenter|mouseover/i.test(opts.pagerEvent) ) { + $a.hover(pagerFn, function(){/* no-op */} ); + } + else { + $a.bind(opts.pagerEvent, pagerFn); + } + + if ( ! /^click/.test(opts.pagerEvent) && !opts.allowPagerClickBubble) + $a.bind('click.cycle', function(){return false;}); // suppress click + + var cont = opts.$cont[0]; + var pauseFlag = false; // https://github.com/malsup/cycle/issues/44 + if (opts.pauseOnPagerHover) { + $a.hover( + function() { + pauseFlag = true; + cont.cyclePause++; + triggerPause(cont,true,true); + }, function() { + if (pauseFlag) + cont.cyclePause--; + triggerPause(cont,true,true); + } + ); + } +}; + +// helper fn to calculate the number of slides between the current and the next +$.fn.cycle.hopsFromLast = function(opts, fwd) { + var hops, l = opts.lastSlide, c = opts.currSlide; + if (fwd) + hops = c > l ? c - l : opts.slideCount - l; + else + hops = c < l ? l - c : l + opts.slideCount - c; + return hops; +}; + +// fix clearType problems in ie6 by setting an explicit bg color +// (otherwise text slides look horrible during a fade transition) +function clearTypeFix($slides) { + debug('applying clearType background-color hack'); + function hex(s) { + s = parseInt(s,10).toString(16); + return s.length < 2 ? '0'+s : s; + } + function getBg(e) { + for ( ; e && e.nodeName.toLowerCase() != 'html'; e = e.parentNode) { + var v = $.css(e,'background-color'); + if (v && v.indexOf('rgb') >= 0 ) { + var rgb = v.match(/\d+/g); + return '#'+ hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]); + } + if (v && v != 'transparent') + return v; + } + return '#ffffff'; + } + $slides.each(function() { $(this).css('background-color', getBg(this)); }); +} + +// reset common props before the next transition +$.fn.cycle.commonReset = function(curr,next,opts,w,h,rev) { + $(opts.elements).not(curr).hide(); + if (typeof opts.cssBefore.opacity == 'undefined') + opts.cssBefore.opacity = 1; + opts.cssBefore.display = 'block'; + if (opts.slideResize && w !== false && next.cycleW > 0) + opts.cssBefore.width = next.cycleW; + if (opts.slideResize && h !== false && next.cycleH > 0) + opts.cssBefore.height = next.cycleH; + opts.cssAfter = opts.cssAfter || {}; + opts.cssAfter.display = 'none'; + $(curr).css('zIndex',opts.slideCount + (rev === true ? 1 : 0)); + $(next).css('zIndex',opts.slideCount + (rev === true ? 0 : 1)); +}; + +// the actual fn for effecting a transition +$.fn.cycle.custom = function(curr, next, opts, cb, fwd, speedOverride) { + var $l = $(curr), $n = $(next); + var speedIn = opts.speedIn, speedOut = opts.speedOut, easeIn = opts.easeIn, easeOut = opts.easeOut; + $n.css(opts.cssBefore); + if (speedOverride) { + if (typeof speedOverride == 'number') + speedIn = speedOut = speedOverride; + else + speedIn = speedOut = 1; + easeIn = easeOut = null; + } + var fn = function() { + $n.animate(opts.animIn, speedIn, easeIn, function() { + cb(); + }); + }; + $l.animate(opts.animOut, speedOut, easeOut, function() { + $l.css(opts.cssAfter); + if (!opts.sync) + fn(); + }); + if (opts.sync) fn(); +}; + +// transition definitions - only fade is defined here, transition pack defines the rest +$.fn.cycle.transitions = { + fade: function($cont, $slides, opts) { + $slides.not(':eq('+opts.currSlide+')').css('opacity',0); + opts.before.push(function(curr,next,opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.opacity = 0; + }); + opts.animIn = { opacity: 1 }; + opts.animOut = { opacity: 0 }; + opts.cssBefore = { top: 0, left: 0 }; + } +}; + +$.fn.cycle.ver = function() { return ver; }; + +// override these globally if you like (they are all optional) +$.fn.cycle.defaults = { + activePagerClass: 'activeSlide', // class name used for the active pager link + after: null, // transition callback (scope set to element that was shown): function(currSlideElement, nextSlideElement, options, forwardFlag) + allowPagerClickBubble: false, // allows or prevents click event on pager anchors from bubbling + animIn: null, // properties that define how the slide animates in + animOut: null, // properties that define how the slide animates out + aspect: false, // preserve aspect ratio during fit resizing, cropping if necessary (must be used with fit option) + autostop: 0, // true to end slideshow after X transitions (where X == slide count) + autostopCount: 0, // number of transitions (optionally used with autostop to define X) + backwards: false, // true to start slideshow at last slide and move backwards through the stack + before: null, // transition callback (scope set to element to be shown): function(currSlideElement, nextSlideElement, options, forwardFlag) + center: null, // set to true to have cycle add top/left margin to each slide (use with width and height options) + cleartype: !$.support.opacity, // true if clearType corrections should be applied (for IE) + cleartypeNoBg: false, // set to true to disable extra cleartype fixing (leave false to force background color setting on slides) + containerResize: 1, // resize container to fit largest slide + containerResizeHeight: 0, // resize containers height to fit the largest slide but leave the width dynamic + continuous: 0, // true to start next transition immediately after current one completes + cssAfter: null, // properties that defined the state of the slide after transitioning out + cssBefore: null, // properties that define the initial state of the slide before transitioning in + delay: 0, // additional delay (in ms) for first transition (hint: can be negative) + easeIn: null, // easing for "in" transition + easeOut: null, // easing for "out" transition + easing: null, // easing method for both in and out transitions + end: null, // callback invoked when the slideshow terminates (use with autostop or nowrap options): function(options) + fastOnEvent: 0, // force fast transitions when triggered manually (via pager or prev/next); value == time in ms + fit: 0, // force slides to fit container + fx: 'fade', // name of transition effect (or comma separated names, ex: 'fade,scrollUp,shuffle') + fxFn: null, // function used to control the transition: function(currSlideElement, nextSlideElement, options, afterCalback, forwardFlag) + height: 'auto', // container height (if the 'fit' option is true, the slides will be set to this height as well) + manualTrump: true, // causes manual transition to stop an active transition instead of being ignored + metaAttr: 'cycle', // data- attribute that holds the option data for the slideshow + next: null, // element, jQuery object, or jQuery selector string for the element to use as event trigger for next slide + nowrap: 0, // true to prevent slideshow from wrapping + onPagerEvent: null, // callback fn for pager events: function(zeroBasedSlideIndex, slideElement) + onPrevNextEvent: null, // callback fn for prev/next events: function(isNext, zeroBasedSlideIndex, slideElement) + pager: null, // element, jQuery object, or jQuery selector string for the element to use as pager container + pagerAnchorBuilder: null, // callback fn for building anchor links: function(index, DOMelement) + pagerEvent: 'click.cycle', // name of event which drives the pager navigation + pause: 0, // true to enable "pause on hover" + pauseOnPagerHover: 0, // true to pause when hovering over pager link + prev: null, // element, jQuery object, or jQuery selector string for the element to use as event trigger for previous slide + prevNextEvent: 'click.cycle',// event which drives the manual transition to the previous or next slide + random: 0, // true for random, false for sequence (not applicable to shuffle fx) + randomizeEffects: 1, // valid when multiple effects are used; true to make the effect sequence random + requeueOnImageNotLoaded: true, // requeue the slideshow if any image slides are not yet loaded + requeueTimeout: 250, // ms delay for requeue + rev: 0, // causes animations to transition in reverse (for effects that support it such as scrollHorz/scrollVert/shuffle) + shuffle: null, // coords for shuffle animation, ex: { top:15, left: 200 } + skipInitializationCallbacks: false, // set to true to disable the first before/after callback that occurs prior to any transition + slideExpr: null, // expression for selecting slides (if something other than all children is required) + slideResize: 1, // force slide width/height to fixed size before every transition + speed: 1000, // speed of the transition (any valid fx speed value) + speedIn: null, // speed of the 'in' transition + speedOut: null, // speed of the 'out' transition + startingSlide: undefined,// zero-based index of the first slide to be displayed + sync: 1, // true if in/out transitions should occur simultaneously + timeout: 4000, // milliseconds between slide transitions (0 to disable auto advance) + timeoutFn: null, // callback for determining per-slide timeout value: function(currSlideElement, nextSlideElement, options, forwardFlag) + updateActivePagerLink: null,// callback fn invoked to update the active pager link (adds/removes activePagerClass style) + width: null // container width (if the 'fit' option is true, the slides will be set to this width as well) +}; + +})(jQuery); + + +/*! + * jQuery Cycle Plugin Transition Definitions + * This script is a plugin for the jQuery Cycle Plugin + * Examples and documentation at: http://malsup.com/jquery/cycle/ + * Copyright (c) 2007-2010 M. Alsup + * Version: 2.73 + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +(function($) { +"use strict"; + +// +// These functions define slide initialization and properties for the named +// transitions. To save file size feel free to remove any of these that you +// don't need. +// +$.fn.cycle.transitions.none = function($cont, $slides, opts) { + opts.fxFn = function(curr,next,opts,after){ + $(next).show(); + $(curr).hide(); + after(); + }; +}; + +// not a cross-fade, fadeout only fades out the top slide +$.fn.cycle.transitions.fadeout = function($cont, $slides, opts) { + $slides.not(':eq('+opts.currSlide+')').css({ display: 'block', 'opacity': 1 }); + opts.before.push(function(curr,next,opts,w,h,rev) { + $(curr).css('zIndex',opts.slideCount + (rev !== true ? 1 : 0)); + $(next).css('zIndex',opts.slideCount + (rev !== true ? 0 : 1)); + }); + opts.animIn.opacity = 1; + opts.animOut.opacity = 0; + opts.cssBefore.opacity = 1; + opts.cssBefore.display = 'block'; + opts.cssAfter.zIndex = 0; +}; + +// scrollUp/Down/Left/Right +$.fn.cycle.transitions.scrollUp = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var h = $cont.height(); + opts.cssBefore.top = h; + opts.cssBefore.left = 0; + opts.cssFirst.top = 0; + opts.animIn.top = 0; + opts.animOut.top = -h; +}; +$.fn.cycle.transitions.scrollDown = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var h = $cont.height(); + opts.cssFirst.top = 0; + opts.cssBefore.top = -h; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.top = h; +}; +$.fn.cycle.transitions.scrollLeft = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var w = $cont.width(); + opts.cssFirst.left = 0; + opts.cssBefore.left = w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = 0-w; +}; +$.fn.cycle.transitions.scrollRight = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push($.fn.cycle.commonReset); + var w = $cont.width(); + opts.cssFirst.left = 0; + opts.cssBefore.left = -w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = w; +}; +$.fn.cycle.transitions.scrollHorz = function($cont, $slides, opts) { + $cont.css('overflow','hidden').width(); + opts.before.push(function(curr, next, opts, fwd) { + if (opts.rev) + fwd = !fwd; + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.left = fwd ? (next.cycleW-1) : (1-next.cycleW); + opts.animOut.left = fwd ? -curr.cycleW : curr.cycleW; + }); + opts.cssFirst.left = 0; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.top = 0; +}; +$.fn.cycle.transitions.scrollVert = function($cont, $slides, opts) { + $cont.css('overflow','hidden'); + opts.before.push(function(curr, next, opts, fwd) { + if (opts.rev) + fwd = !fwd; + $.fn.cycle.commonReset(curr,next,opts); + opts.cssBefore.top = fwd ? (1-next.cycleH) : (next.cycleH-1); + opts.animOut.top = fwd ? curr.cycleH : -curr.cycleH; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.left = 0; +}; + +// slideX/slideY +$.fn.cycle.transitions.slideX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $(opts.elements).not(curr).hide(); + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.animIn.width = next.cycleW; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; + opts.animIn.width = 'show'; + opts.animOut.width = 0; +}; +$.fn.cycle.transitions.slideY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $(opts.elements).not(curr).hide(); + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.animIn.height = next.cycleH; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.height = 0; + opts.animIn.height = 'show'; + opts.animOut.height = 0; +}; + +// shuffle +$.fn.cycle.transitions.shuffle = function($cont, $slides, opts) { + var i, w = $cont.css('overflow', 'visible').width(); + $slides.css({left: 0, top: 0}); + opts.before.push(function(curr,next,opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + }); + // only adjust speed once! + if (!opts.speedAdjusted) { + opts.speed = opts.speed / 2; // shuffle has 2 transitions + opts.speedAdjusted = true; + } + opts.random = 0; + opts.shuffle = opts.shuffle || {left:-w, top:15}; + opts.els = []; + for (i=0; i < $slides.length; i++) + opts.els.push($slides[i]); + + for (i=0; i < opts.currSlide; i++) + opts.els.push(opts.els.shift()); + + // custom transition fn (hat tip to Benjamin Sterling for this bit of sweetness!) + opts.fxFn = function(curr, next, opts, cb, fwd) { + if (opts.rev) + fwd = !fwd; + var $el = fwd ? $(curr) : $(next); + $(next).css(opts.cssBefore); + var count = opts.slideCount; + $el.animate(opts.shuffle, opts.speedIn, opts.easeIn, function() { + var hops = $.fn.cycle.hopsFromLast(opts, fwd); + for (var k=0; k < hops; k++) { + if (fwd) + opts.els.push(opts.els.shift()); + else + opts.els.unshift(opts.els.pop()); + } + if (fwd) { + for (var i=0, len=opts.els.length; i < len; i++) + $(opts.els[i]).css('z-index', len-i+count); + } + else { + var z = $(curr).css('z-index'); + $el.css('z-index', parseInt(z,10)+1+count); + } + $el.animate({left:0, top:0}, opts.speedOut, opts.easeOut, function() { + $(fwd ? this : curr).hide(); + if (cb) cb(); + }); + }); + }; + $.extend(opts.cssBefore, { display: 'block', opacity: 1, top: 0, left: 0 }); +}; + +// turnUp/Down/Left/Right +$.fn.cycle.transitions.turnUp = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.cssBefore.top = next.cycleH; + opts.animIn.height = next.cycleH; + opts.animOut.width = next.cycleW; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.cssBefore.height = 0; + opts.animIn.top = 0; + opts.animOut.height = 0; +}; +$.fn.cycle.transitions.turnDown = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssFirst.top = 0; + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.cssBefore.height = 0; + opts.animOut.height = 0; +}; +$.fn.cycle.transitions.turnLeft = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.cssBefore.left = next.cycleW; + opts.animIn.width = next.cycleW; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; + opts.animIn.left = 0; + opts.animOut.width = 0; +}; +$.fn.cycle.transitions.turnRight = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.animIn.width = next.cycleW; + opts.animOut.left = curr.cycleW; + }); + $.extend(opts.cssBefore, { top: 0, left: 0, width: 0 }); + opts.animIn.left = 0; + opts.animOut.width = 0; +}; + +// zoom +$.fn.cycle.transitions.zoom = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,false,true); + opts.cssBefore.top = next.cycleH/2; + opts.cssBefore.left = next.cycleW/2; + $.extend(opts.animIn, { top: 0, left: 0, width: next.cycleW, height: next.cycleH }); + $.extend(opts.animOut, { width: 0, height: 0, top: curr.cycleH/2, left: curr.cycleW/2 }); + }); + opts.cssFirst.top = 0; + opts.cssFirst.left = 0; + opts.cssBefore.width = 0; + opts.cssBefore.height = 0; +}; + +// fadeZoom +$.fn.cycle.transitions.fadeZoom = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,false); + opts.cssBefore.left = next.cycleW/2; + opts.cssBefore.top = next.cycleH/2; + $.extend(opts.animIn, { top: 0, left: 0, width: next.cycleW, height: next.cycleH }); + }); + opts.cssBefore.width = 0; + opts.cssBefore.height = 0; + opts.animOut.opacity = 0; +}; + +// blindX +$.fn.cycle.transitions.blindX = function($cont, $slides, opts) { + var w = $cont.css('overflow','hidden').width(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.width = next.cycleW; + opts.animOut.left = curr.cycleW; + }); + opts.cssBefore.left = w; + opts.cssBefore.top = 0; + opts.animIn.left = 0; + opts.animOut.left = w; +}; +// blindY +$.fn.cycle.transitions.blindY = function($cont, $slides, opts) { + var h = $cont.css('overflow','hidden').height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssBefore.top = h; + opts.cssBefore.left = 0; + opts.animIn.top = 0; + opts.animOut.top = h; +}; +// blindZ +$.fn.cycle.transitions.blindZ = function($cont, $slides, opts) { + var h = $cont.css('overflow','hidden').height(); + var w = $cont.width(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH; + }); + opts.cssBefore.top = h; + opts.cssBefore.left = w; + opts.animIn.top = 0; + opts.animIn.left = 0; + opts.animOut.top = h; + opts.animOut.left = w; +}; + +// growX - grow horizontally from centered 0 width +$.fn.cycle.transitions.growX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true); + opts.cssBefore.left = this.cycleW/2; + opts.animIn.left = 0; + opts.animIn.width = this.cycleW; + opts.animOut.left = 0; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; +}; +// growY - grow vertically from centered 0 height +$.fn.cycle.transitions.growY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false); + opts.cssBefore.top = this.cycleH/2; + opts.animIn.top = 0; + opts.animIn.height = this.cycleH; + opts.animOut.top = 0; + }); + opts.cssBefore.height = 0; + opts.cssBefore.left = 0; +}; + +// curtainX - squeeze in both edges horizontally +$.fn.cycle.transitions.curtainX = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,false,true,true); + opts.cssBefore.left = next.cycleW/2; + opts.animIn.left = 0; + opts.animIn.width = this.cycleW; + opts.animOut.left = curr.cycleW/2; + opts.animOut.width = 0; + }); + opts.cssBefore.top = 0; + opts.cssBefore.width = 0; +}; +// curtainY - squeeze in both edges vertically +$.fn.cycle.transitions.curtainY = function($cont, $slides, opts) { + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,false,true); + opts.cssBefore.top = next.cycleH/2; + opts.animIn.top = 0; + opts.animIn.height = next.cycleH; + opts.animOut.top = curr.cycleH/2; + opts.animOut.height = 0; + }); + opts.cssBefore.height = 0; + opts.cssBefore.left = 0; +}; + +// cover - curr slide covered by next slide +$.fn.cycle.transitions.cover = function($cont, $slides, opts) { + var d = opts.direction || 'left'; + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts); + opts.cssAfter.display = ''; + if (d == 'right') + opts.cssBefore.left = -w; + else if (d == 'up') + opts.cssBefore.top = h; + else if (d == 'down') + opts.cssBefore.top = -h; + else + opts.cssBefore.left = w; + }); + opts.animIn.left = 0; + opts.animIn.top = 0; + opts.cssBefore.top = 0; + opts.cssBefore.left = 0; +}; + +// uncover - curr slide moves off next slide +$.fn.cycle.transitions.uncover = function($cont, $slides, opts) { + var d = opts.direction || 'left'; + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + if (d == 'right') + opts.animOut.left = w; + else if (d == 'up') + opts.animOut.top = -h; + else if (d == 'down') + opts.animOut.top = h; + else + opts.animOut.left = -w; + }); + opts.animIn.left = 0; + opts.animIn.top = 0; + opts.cssBefore.top = 0; + opts.cssBefore.left = 0; +}; + +// toss - move top slide and fade away +$.fn.cycle.transitions.toss = function($cont, $slides, opts) { + var w = $cont.css('overflow','visible').width(); + var h = $cont.height(); + opts.before.push(function(curr, next, opts) { + $.fn.cycle.commonReset(curr,next,opts,true,true,true); + // provide default toss settings if animOut not provided + if (!opts.animOut.left && !opts.animOut.top) + $.extend(opts.animOut, { left: w*2, top: -h/2, opacity: 0 }); + else + opts.animOut.opacity = 0; + }); + opts.cssBefore.left = 0; + opts.cssBefore.top = 0; + opts.animIn.left = 0; +}; + +// wipe - clip animation +$.fn.cycle.transitions.wipe = function($cont, $slides, opts) { + var w = $cont.css('overflow','hidden').width(); + var h = $cont.height(); + opts.cssBefore = opts.cssBefore || {}; + var clip; + if (opts.clip) { + if (/l2r/.test(opts.clip)) + clip = 'rect(0px 0px '+h+'px 0px)'; + else if (/r2l/.test(opts.clip)) + clip = 'rect(0px '+w+'px '+h+'px '+w+'px)'; + else if (/t2b/.test(opts.clip)) + clip = 'rect(0px '+w+'px 0px 0px)'; + else if (/b2t/.test(opts.clip)) + clip = 'rect('+h+'px '+w+'px '+h+'px 0px)'; + else if (/zoom/.test(opts.clip)) { + var top = parseInt(h/2,10); + var left = parseInt(w/2,10); + clip = 'rect('+top+'px '+left+'px '+top+'px '+left+'px)'; + } + } + + opts.cssBefore.clip = opts.cssBefore.clip || clip || 'rect(0px 0px 0px 0px)'; + + var d = opts.cssBefore.clip.match(/(\d+)/g); + var t = parseInt(d[0],10), r = parseInt(d[1],10), b = parseInt(d[2],10), l = parseInt(d[3],10); + + opts.before.push(function(curr, next, opts) { + if (curr == next) return; + var $curr = $(curr), $next = $(next); + $.fn.cycle.commonReset(curr,next,opts,true,true,false); + opts.cssAfter.display = 'block'; + + var step = 1, count = parseInt((opts.speedIn / 13),10) - 1; + (function f() { + var tt = t ? t - parseInt(step * (t/count),10) : 0; + var ll = l ? l - parseInt(step * (l/count),10) : 0; + var bb = b < h ? b + parseInt(step * ((h-b)/count || 1),10) : h; + var rr = r < w ? r + parseInt(step * ((w-r)/count || 1),10) : w; + $next.css({ clip: 'rect('+tt+'px '+rr+'px '+bb+'px '+ll+'px)' }); + (step++ <= count) ? setTimeout(f, 13) : $curr.css('display', 'none'); + })(); + }); + $.extend(opts.cssBefore, { display: 'block', opacity: 1, top: 0, left: 0 }); + opts.animIn = { left: 0 }; + opts.animOut = { left: 0 }; +}; + +})(jQuery); diff --git a/plugins/jetpack/modules/shortcodes/js/slideshow-shortcode.js b/plugins/jetpack/modules/shortcodes/js/slideshow-shortcode.js new file mode 100644 index 00000000..5e132097 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/js/slideshow-shortcode.js @@ -0,0 +1,187 @@ +function JetpackSlideshow( element, width, height, transition ) { + this.element = element; + this.images = []; + this.controls = {}; + this.transition = transition || 'fade'; + + var currentWidth = this.element.width(); + if ( !width || width > currentWidth ) + width = currentWidth; + + this.width = width; + this.height = height; + this.element.css( { + 'height': this.height + 'px' + } ); +} + +JetpackSlideshow.prototype.showLoadingImage = function( toggle ) { + if ( toggle ) { + this.loadingImage_ = document.createElement( 'div' ); + this.loadingImage_.className = 'slideshow-loading'; + var img = document.createElement( 'img' ); + img.src = jetpackSlideshowSettings.spinner; + this.loadingImage_.appendChild( img ); + this.loadingImage_.appendChild( this.makeZeroWidthSpan() ); + this.loadingImage_.style.lineHeight = this.height + 'px'; + this.element.append( this.loadingImage_ ); + } else if ( this.loadingImage_ ) { + this.loadingImage_.parentNode.removeChild( this.loadingImage_ ); + this.loadingImage_ = null; + } +}; + +JetpackSlideshow.prototype.init = function() { + this.showLoadingImage(true); + + var self = this; + // Set up DOM. + for ( var i = 0; i < this.images.length; i++ ) { + var imageInfo = this.images[i]; + var img = document.createElement( 'img' ); + img.src = imageInfo.src + '?w=' + this.width; + img.align = 'middle'; + var caption = document.createElement( 'div' ); + caption.className = 'slideshow-slide-caption'; + caption.innerHTML = imageInfo.caption; + var container = document.createElement('div'); + container.className = 'slideshow-slide'; + container.style.lineHeight = this.height + 'px'; + + // Hide loading image once first image has loaded. + if ( i == 0 ) { + if ( img.complete ) { + // IE, image in cache + setTimeout( function() { + self.finishInit_(); + }, 1); + } else { + jQuery( img ).load(function() { + self.finishInit_(); + }); + } + } + container.appendChild( img ); + // I'm not sure where these were coming from, but IE adds + // bad values for width/height for portrait-mode images + img.removeAttribute('width'); + img.removeAttribute('height'); + container.appendChild( this.makeZeroWidthSpan() ); + container.appendChild( caption ); + this.element.append( container ); + } +}; + +JetpackSlideshow.prototype.makeZeroWidthSpan = function() { + var emptySpan = document.createElement( 'span' ); + emptySpan.className = 'slideshow-line-height-hack'; + // Having a NBSP makes IE act weird during transitions, but other + // browsers ignore a text node with a space in it as whitespace. + if (jQuery.browser.msie) { + emptySpan.appendChild( document.createTextNode(' ') ); + } else { + emptySpan.innerHTML = ' '; + } + return emptySpan; +}; + +JetpackSlideshow.prototype.finishInit_ = function() { + this.showLoadingImage( false ); + this.renderControls_(); + + var self = this; + // Initialize Cycle instance. + this.element.cycle( { + fx: this.transition, + prev: this.controls.prev, + next: this.controls.next, + slideExpr: '.slideshow-slide', + onPrevNextEvent: function() { + return self.onCyclePrevNextClick_.apply( self, arguments ); + } + } ); + + var slideshow = this.element; + jQuery( this.controls['stop'] ).click( function() { + var button = jQuery(this); + if ( ! button.hasClass( 'paused' ) ) { + slideshow.cycle( 'pause' ); + button.removeClass( 'running' ); + button.addClass( 'paused' ); + } else { + button.addClass( 'running' ); + button.removeClass( 'paused' ); + slideshow.cycle( 'resume', true ); + } + return false; + } ); + + var controls = jQuery( this.controlsDiv_ ); + slideshow.mouseenter( function() { + controls.fadeIn(); + } ); + slideshow.mouseleave( function() { + controls.fadeOut(); + } ); + + this.initialized_ = true; +}; + +JetpackSlideshow.prototype.renderControls_ = function() { + if ( this.controlsDiv_ ) + return; + + var controlsDiv = document.createElement( 'div' ); + controlsDiv.className = 'slideshow-controls'; + + controls = [ 'prev', 'stop', 'next' ]; + for ( var i = 0; i < controls.length; i++ ) { + var controlName = controls[i]; + var a = document.createElement( 'a' ); + a.href = '#'; + controlsDiv.appendChild( a ); + this.controls[controlName] = a; + } + this.element.append( controlsDiv ); + this.controlsDiv_ = controlsDiv; +}; + +JetpackSlideshow.prototype.onCyclePrevNextClick_ = function( isNext, i, slideElement ) { + // If blog_id not present don't track page views + if ( ! jetpackSlideshowSettings.blog_id ) + return; + + var postid = this.images[i].id; + var stats = new Image(); + stats.src = document.location.protocol + + '//stats.wordpress.com/g.gif?host=' + + escape( document.location.host ) + + '&rand=' + Math.random() + + '&blog=' + jetpackSlideshowSettings.blog_id + + '&subd=' + jetpackSlideshowSettings.blog_subdomain + + '&user_id=' + jetpackSlideshowSettings.user_id + + '&post=' + postid + + '&ref=' + escape( document.location ); +}; + +( function ( $ ) { + function jetpack_slideshow_init() { + $( '.jetpack-slideshow-noscript' ).remove(); + + $( '.jetpack-slideshow' ).each( function () { + var container = $( this ); + + if ( container.data( 'processed' ) ) + return; + + var slideshow = new JetpackSlideshow( container, container.data( 'width' ), container.data( 'height' ), container.data( 'trans' ) ); + slideshow.images = container.data( 'gallery' ); + slideshow.init(); + + container.data( 'processed', true ); + } ); + } + + $( document ).ready( jetpack_slideshow_init ); + $( 'body' ).on( 'post-load', jetpack_slideshow_init ); +} )( jQuery );
\ No newline at end of file diff --git a/plugins/jetpack/modules/shortcodes/polldaddy.php b/plugins/jetpack/modules/shortcodes/polldaddy.php index bcb30f81..7eb30edf 100644 --- a/plugins/jetpack/modules/shortcodes/polldaddy.php +++ b/plugins/jetpack/modules/shortcodes/polldaddy.php @@ -193,7 +193,7 @@ SCRIPT; $data = esc_attr( json_encode( $data ) ); return <<<CONTAINER -<a name="pd_a_{$poll}"></a> +<a id="pd_a_{$poll}"></a> <div class="PDS_Poll" id="PDI_container{$poll}" data-settings="{$data}" style="display:inline-block;{$float}{$margins}"></div> <div id="PD_superContainer"></div> <noscript>{$poll_link}</noscript> @@ -204,7 +204,7 @@ CONTAINER; $cb = ''; return <<<CONTAINER -<a name="pd_a_{$poll}"></a> +<a id="pd_a_{$poll}"></a> <div class="PDS_Poll" id="PDI_container{$poll}" style="display:inline-block;{$float}{$margins}"></div> <div id="PD_superContainer"></div> <script type="text/javascript" charset="UTF-8" src="{$poll_js}{$cb}"></script> @@ -395,4 +395,4 @@ if ( !function_exists( 'polldaddy_link' ) ) { add_filter( 'comment_text', 'polldaddy_link', 1 ); } -}
\ No newline at end of file +} diff --git a/plugins/jetpack/modules/shortcodes/slideshow.php b/plugins/jetpack/modules/shortcodes/slideshow.php new file mode 100644 index 00000000..ab1145d8 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/slideshow.php @@ -0,0 +1,208 @@ +<?php + +/** + * Slideshow shortcode usage: [gallery type="slideshow"] or the older [slideshow] + */ +class Jetpack_Slideshow_Shortcode { + public $instance_count = 0; + + function __construct() { + global $shortcode_tags; + + $needs_scripts = false; + + // Only if the slideshow shortcode has not already been defined. + if ( ! array_key_exists( 'slideshow', $shortcode_tags ) ) { + add_shortcode( 'slideshow', array( $this, 'shortcode_callback' ) ); + $needs_scripts = true; + } + + // Only if the gallery shortcode has not been redefined. + if ( isset( $shortcode_tags['gallery'] ) && $shortcode_tags['gallery'] == 'gallery_shortcode' ) { + add_filter( 'post_gallery', array( $this, 'post_gallery' ), 1002, 2 ); + add_filter( 'jetpack_gallery_types', array( $this, 'add_gallery_type' ), 10 ); + $needs_scripts = true; + } + + if ( $needs_scripts ) + add_action( 'wp_enqueue_scripts', array( $this, 'maybe_enqueue_scripts' ), 1 ); + } + + /** + * Responds to the [gallery] shortcode, but not an actual shortcode callback. + * + * @param $value An empty string if nothing has modified the gallery output, the output html otherwise + * @param $attr The shortcode attributes array + * + * @return string The (un)modified $value + */ + function post_gallery( $value, $attr ) { + // Bail if somebody else has done something + if ( ! empty( $value ) ) + return $value; + + // If [gallery type="slideshow"] have it behave just like [slideshow] + if ( ! empty( $attr['type'] ) && 'slideshow' == $attr['type'] ) + return $this->shortcode_callback( $attr ); + + return $value; + } + + /** + * Add the Slideshow type to gallery settings + * + * @param $types An array of types where the key is the value, and the value is the caption. + * @see Jetpack_Tiled_Gallery::media_ui_print_templates + */ + function add_gallery_type( $types = array() ) { + $types['slideshow'] = esc_html__( 'Slideshow', 'jetpack' ); + return $types; + } + + function shortcode_callback( $attr, $content = null ) { + global $post, $content_width; + + $attr = shortcode_atts( array( + 'trans' => 'fade', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'id' => $post->ID, + 'include' => '', + 'exclude' => '', + ), $attr ); + + if ( 'rand' == strtolower( $attr['order'] ) ) + $attr['orderby'] = 'none'; + + $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] ); + if ( ! $attr['orderby'] ) + $attr['orderby'] = 'menu_order ID'; + + // Don't restrict to the current post if include + $post_parent = ( empty( $attr['include'] ) ) ? intval( $attr['id'] ) : null; + + $attachments = get_posts( array( + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'posts_per_page' => -1, + 'post_parent' => $post_parent, + 'order' => $attr['order'], + 'orderby' => $attr['orderby'], + 'include' => $attr['include'], + 'exclude' => $attr['exclude'], + ) ); + + if ( count( $attachments ) < 2 ) + return; + + $gallery_instance = sprintf( "gallery-%d-%d", $attr['id'], ++$this->instance_count ); + + $gallery = array(); + foreach ( $attachments as $attachment ) { + $attachment_image_src = wp_get_attachment_image_src( $attachment->ID, 'full' ); + $attachment_image_src = $attachment_image_src[0]; // [url, width, height] + $caption = wptexturize( strip_tags( $attachment->post_excerpt ) ); + + $gallery[] = (object) array( + 'src' => (string) esc_url_raw( $attachment_image_src ), + 'id' => (string) $attachment->ID, + 'caption' => (string) $caption, + ); + } + + $max_width = intval( get_option( 'large_size_w' ) ); + $max_height = 450; + if ( intval( $content_width ) > 0 ) + $max_width = min( intval( $content_width ), $max_width ); + + $js_attr = array( + 'gallery' => $gallery, + 'selector' => $gallery_instance, + 'width' => $max_width, + 'height' => $max_height, + 'trans' => $attr['trans'] ? $attr['trans'] : 'fade', + ); + + // Show a link to the gallery in feeds. + if ( is_feed() ) + return sprintf( '<a href="%s">%s</a>', + esc_url( get_permalink( $post->ID ) . '#' . $gallery_instance . '-slideshow' ), + esc_html__( 'Click to view slideshow.', 'jetpack' ) + ); + + return $this->slideshow_js( $js_attr ); + } + + /** + * Render the slideshow js + * + * Returns the necessary markup and js to fire a slideshow. + * + * @uses $this->enqueue_scripts() + */ + function slideshow_js( $attr ) { + // Enqueue scripts + $this->enqueue_scripts(); + + if ( $attr['width'] <= 100 ) + $attr['width'] = 450; + + if ( $attr['height'] <= 100 ) + $attr['height'] = 450; + + // 40px padding + $attr['width'] -= 40; + $attr['height'] -= 40; + + $output = ''; + + $output .= '<p class="jetpack-slideshow-noscript robots-nocontent">' . esc_html__( 'This slideshow requires JavaScript.', 'jetpack' ) . '</p>'; + $output .= '<div id="' . esc_attr( $attr['selector'] . '-slideshow' ) . '" class="slideshow-window jetpack-slideshow" data-width="' . esc_attr( $attr['width'] ) . '" data-height="' . esc_attr( $attr['height'] ) . '" data-trans="' . esc_attr( $attr['trans'] ) . '" data-gallery="' . esc_attr( json_encode( $attr['gallery'] ) ) . '"></div>'; + + $output .= " + <style> + #{$attr['selector']}-slideshow .slideshow-slide img { + max-height: " . intval( $attr['height'] ) ."px; + /* Emulate max-height in IE 6 */ + _height: expression(this.scrollHeight >= " . intval( $attr['height'] ) . " ? '" . intval( $attr['height'] ) . "px' : 'auto'); + } + </style> + "; + + return $output; + } + + /** + * Infinite Scroll needs the scripts to be present at all times + */ + function maybe_enqueue_scripts() { + if ( is_home() && current_theme_supports( 'infinite-scroll' ) ) + $this->enqueue_scripts(); + } + + /** + * Actually enqueues the scripts and styles. + */ + function enqueue_scripts() { + static $enqueued = false; + + if ( $enqueued ) + return; + + wp_enqueue_script( 'jquery-cycle', plugins_url( '/js/jquery.cycle.js', __FILE__ ) , array( 'jquery' ), '2.9999.8', true ); + wp_enqueue_script( 'jetpack-slideshow', plugins_url( '/js/slideshow-shortcode.js', __FILE__ ), array( 'jquery-cycle' ), '20121214.1', true ); + wp_enqueue_style( 'jetpack-slideshow', plugins_url( '/css/slideshow-shortcode.css', __FILE__ ) ); + + wp_localize_script( 'jetpack-slideshow', 'jetpackSlideshowSettings', apply_filters( 'jetpack_js_slideshow_settings', array( + 'spinner' => plugins_url( '/img/slideshow-loader.gif', __FILE__ ), + ) ) ); + + $enqueued = true; + } + + public static function init() { + $gallery = new Jetpack_Slideshow_Shortcode; + } +} +add_action( 'init', array( 'Jetpack_Slideshow_Shortcode', 'init' ) ); diff --git a/plugins/jetpack/modules/shortcodes/soundcloud.php b/plugins/jetpack/modules/shortcodes/soundcloud.php index 8aa35380..a7486115 100644 --- a/plugins/jetpack/modules/shortcodes/soundcloud.php +++ b/plugins/jetpack/modules/shortcodes/soundcloud.php @@ -1,60 +1,214 @@ <?php - /* Plugin Name: SoundCloud Shortcode -Plugin URI: http://www.soundcloud.com -Description: SoundCloud Shortcode. Usage in your posts: [soundcloud]http://soundcloud.com/TRACK_PERMALINK[/soundcloud] . Works also with set or group instead of track. You can provide optional parameters height/width/params like that [soundcloud height="82" params="auto_play=true"]http.... -Version: 1.1.5 -Author: Johannes Wagener <johannes@soundcloud.com> added to wpcom by tott -Author URI: http://johannes.wagener.cc - -[soundcloud url="http://api.soundcloud.com/tracks/9408008"] -<object height="81" width="100%"> <param name="movie" value="http://player.soundcloud.com/player.swf?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F8781356"></param> <param name="allowscriptaccess" value="always"></param> <embed allowscriptaccess="always" height="81" src="http://player.soundcloud.com/player.swf?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F8781356" type="application/x-shockwave-flash" width="100%"></embed> </object> <span><a href="http://soundcloud.com/robokopbeats/robokop-we-move-at-midnight-preview-forthcoming-on-mwm-recordings">Robokop - We move at midnight preview ( FORTHCOMING ON MWM recordings)</a> by <a href="http://soundcloud.com/robokopbeats">Robokop</a></span> +Plugin URI: http://wordpress.org/extend/plugins/soundcloud-shortcode/ +Description: Converts SoundCloud WordPress shortcodes to a SoundCloud widget. Example: [soundcloud]http://soundcloud.com/forss/flickermood[/soundcloud] +Version: 2.3 +Author: SoundCloud Inc., simplified for Jetpack by Automattic, Inc. +Author URI: http://soundcloud.com +License: GPLv2 + +Original version: Johannes Wagener <johannes@soundcloud.com> +Options support: Tiffany Conroy <tiffany@soundcloud.com> +HTML5 & oEmbed support: Tim Bormans <tim@soundcloud.com> +*/ + +/* +A8C: Taken from http://plugins.svn.wordpress.org/soundcloud-shortcode/trunk/ +at revision 664386. + +Commenting out (instead of removing) and replacing code with custom modifs +so it's eqsy to see what differs from the standard DOTORG version. + +All custom modifs are annoted with "A8C" keyword in comment. */ -add_filter( "pre_kses", "soundcloud_reverse_shortcode" ); +/* Register oEmbed provider + -------------------------------------------------------------------------- */ + +wp_oembed_add_provider('#https?://(?:api\.)?soundcloud\.com/.*#i', 'http://soundcloud.com/oembed', true); + + +/* Register SoundCloud shortcode + -------------------------------------------------------------------------- */ + +add_shortcode("soundcloud", "soundcloud_shortcode"); + + +/** + * SoundCloud shortcode handler + * @param {string|array} $atts The attributes passed to the shortcode like [soundcloud attr1="value" /]. + * Is an empty string when no arguments are given. + * @param {string} $content The content between non-self closing [soundcloud]…[/soundcloud] tags. + * @return {string} Widget embed code HTML + */ +function soundcloud_shortcode($atts, $content = null) { + + // Custom shortcode options + $shortcode_options = array_merge(array('url' => trim($content)), is_array($atts) ? $atts : array()); + + // Turn shortcode option "param" (param=value¶m2=value) into array + $shortcode_params = array(); + if (isset($shortcode_options['params'])) { + parse_str(html_entity_decode($shortcode_options['params']), $shortcode_params); + } + $shortcode_options['params'] = $shortcode_params; + + // User preference options + $plugin_options = array_filter(array( + 'iframe' => soundcloud_get_option('player_iframe', true), + 'width' => soundcloud_get_option('player_width'), + 'height' => soundcloud_url_has_tracklist($shortcode_options['url']) ? soundcloud_get_option('player_height_multi') : soundcloud_get_option('player_height'), + 'params' => array_filter(array( + 'auto_play' => soundcloud_get_option('auto_play'), + 'show_comments' => soundcloud_get_option('show_comments'), + 'color' => soundcloud_get_option('color'), + 'theme_color' => soundcloud_get_option('theme_color'), + )), + )); + // Needs to be an array + if (!isset($plugin_options['params'])) { $plugin_options['params'] = array(); } + + // plugin options < shortcode options + $options = array_merge( + $plugin_options, + $shortcode_options + ); + + // plugin params < shortcode params + $options['params'] = array_merge( + $plugin_options['params'], + $shortcode_options['params'] + ); + + // The "url" option is required + if (!isset($options['url'])) { + return ''; + } else { + $options['url'] = trim($options['url']); + } + + // Both "width" and "height" need to be integers + if (isset($options['width']) && !preg_match('/^\d+$/', $options['width'])) { + // set to 0 so oEmbed will use the default 100% and WordPress themes will leave it alone + $options['width'] = 0; + } + if (isset($options['height']) && !preg_match('/^\d+$/', $options['height'])) { unset($options['height']); } -function soundcloud_reverse_shortcode_preg_replace_callback( $a ) { - $pattern = '/([a-zA-Z0-9\-_%=&]*)&?url=([^&]+)&?([a-zA-Z0-9\-_%&=]*)/'; - preg_match( $pattern, str_replace( "&", "&", $a[3] ), $params ); + // The "iframe" option must be true to load the iframe widget + $iframe = soundcloud_booleanize($options['iframe']) + // Default to flash widget for permalink urls (e.g. http://soundcloud.com/{username}) + // because HTML5 widget doesn’t support those yet + ? preg_match('/api.soundcloud.com/i', $options['url']) + : false; - return '[soundcloud width="' . esc_attr( $a[2] ) . '" height="' . esc_attr( $a[1] ) . '" params="' . esc_attr( $params[1] . $params[3] ) . '" url="' . urldecode( $params[2] ) . '"]'; + // Return html embed code + if ($iframe) { + return soundcloud_iframe_widget($options); + } else { + return soundcloud_flash_widget($options); + } + +} + +/** + * Plugin options getter + * @param {string|array} $option Option name + * @param {mixed} $default Default value + * @return {mixed} Option value + */ +function soundcloud_get_option($option, $default = false) { + $value = get_option('soundcloud_' . $option); + return $value === '' ? $default : $value; +} + +/** + * Booleanize a value + * @param {boolean|string} $value + * @return {boolean} + */ +function soundcloud_booleanize($value) { + return is_bool($value) ? $value : $value === 'true' ? true : false; } -function soundcloud_reverse_shortcode( $content ){ - if ( false === stripos( $content, 'http://player.soundcloud.com/player.swf' ) ) - return $content; +/** + * Decide if a url has a tracklist + * @param {string} $url + * @return {boolean} + */ +function soundcloud_url_has_tracklist($url) { + return preg_match('/^(.+?)\/(sets|groups|playlists)\/(.+?)$/', $url); +} + +/** + * Parameterize url + * @param {array} $match Matched regex + * @return {string} Parameterized url + */ +function soundcloud_oembed_params_callback($match) { + global $soundcloud_oembed_params; - $pattern = '!<object\s*height="(\d+%?)"\s*width="(\d+%?)".*?src="http://.*?soundcloud\.com/player.swf\?([^"]+)".*?</object>.*?</span>!'; - $pattern_ent = str_replace( '&#0*58;', '&#0*58;|�*58;', htmlspecialchars( $pattern, ENT_NOQUOTES ) ); + // Convert URL to array + $url = parse_url(urldecode($match[1])); + // Convert URL query to array + parse_str($url['query'], $query_array); + // Build new query string + $query = http_build_query(array_merge($query_array, $soundcloud_oembed_params)); - if ( preg_match( $pattern_ent, $content ) ) - return( preg_replace_callback( $pattern_ent, 'soundcloud_reverse_shortcode_preg_replace_callback', $content ) ); - else - return( preg_replace_callback( $pattern, 'soundcloud_reverse_shortcode_preg_replace_callback', $content ) ); + return 'src="' . $url['scheme'] . '://' . $url['host'] . $url['path'] . '?' . $query; } -add_shortcode( "soundcloud", "soundcloud_shortcode" ); - -function soundcloud_shortcode( $atts, $url = '' ) { - if ( empty( $url ) ) - extract( shortcode_atts( array( 'url' => '', 'params' => '', 'height' => '', 'width' => '100%' ), $atts ) ); - else - extract( shortcode_atts( array( 'params' => '', 'height' => '', 'width' => '100%' ), $atts ) ); - - $encoded_url = urlencode( $url ); - if ( $url = parse_url( $url ) ) { - $splitted_url = split( "/", $url['path'] ); - $media_type = $splitted_url[ count( $splitted_url ) - 2 ]; - - if ( '' == $height ){ - if ( in_array( $media_type, array( 'groups', 'sets' ) ) ) - $height = 225; - else - $height = 81; - } - $player_params = "url=$encoded_url&g=1&$params"; - - return '<object height="' . esc_attr( $height ) . '" width="' . esc_attr( $width ) . '"><param name="movie" value="' . esc_url( "http://player.soundcloud.com/player.swf?$player_params" ) . '"></param><embed height="' . esc_attr( $height ) . '" src="' . esc_url( "http://player.soundcloud.com/player.swf?$player_params" ) . '" type="application/x-shockwave-flash" width="' . esc_attr( $width ) . '"> </embed> </object>'; - } +/** + * Iframe widget embed code + * @param {array} $options Parameters + * @return {string} Iframe embed code + */ +function soundcloud_iframe_widget($options) { + + // Merge in "url" value + $options['params'] = array_merge(array( + 'url' => $options['url'] + ), $options['params']); + + // Build URL + $url = 'http://w.soundcloud.com/player?' . http_build_query($options['params']); + // Set default width if not defined + $width = isset($options['width']) && $options['width'] !== 0 ? $options['width'] : '100%'; + // Set default height if not defined + $height = isset($options['height']) && $options['height'] !== 0 ? $options['height'] : (soundcloud_url_has_tracklist($options['url']) ? '450' : '166'); + + return sprintf('<iframe width="%s" height="%s" scrolling="no" frameborder="no" src="%s"></iframe>', $width, $height, $url); } + +/** + * Legacy Flash widget embed code + * @param {array} $options Parameters + * @return {string} Flash embed code + */ +function soundcloud_flash_widget($options) { + + // Merge in "url" value + $options['params'] = array_merge(array( + 'url' => $options['url'] + ), $options['params']); + + // Build URL + $url = 'http://player.soundcloud.com/player.swf?' . http_build_query($options['params']); + // Set default width if not defined + $width = isset($options['width']) && $options['width'] !== 0 ? $options['width'] : '100%'; + // Set default height if not defined + $height = isset($options['height']) && $options['height'] !== 0 ? $options['height'] : (soundcloud_url_has_tracklist($options['url']) ? '255' : '81'); + + return preg_replace('/\s\s+/', "", sprintf('<object width="%s" height="%s"> + <param name="movie" value="%s"></param> + <param name="allowscriptaccess" value="always"></param> + <embed width="%s" height="%s" src="%s" allowscriptaccess="always" type="application/x-shockwave-flash"></embed> + </object>', $width, $height, $url, $width, $height, $url)); +} + + + +/* Settings + -------------------------------------------------------------------------- */ + +/* A8C: no user-defined options, KISS */ diff --git a/plugins/jetpack/modules/shortcodes/ted.php b/plugins/jetpack/modules/shortcodes/ted.php new file mode 100644 index 00000000..afd35961 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/ted.php @@ -0,0 +1,68 @@ +<?php +/* + * TED Player embed code + * http://www.ted.com + * + * http://www.ted.com/talks/view/id/210 + * http://www.ted.com/talks/marc_goodman_a_vision_of_crimes_in_the_future.html + * [ted id="210" lang="eng"] + * [ted id="http://www.ted.com/talks/view/id/210" lang="eng"] + * [ted id=1539 lang=fr width=560 height=315] + */ + +wp_oembed_add_provider( '!https?://(www\.)?ted.com/talks/view/id/.+!i', 'http://www.ted.com/talks/oembed.json', true ); +wp_oembed_add_provider( '!https?://(www\.)?ted.com/talks/[a-zA-Z\-\_]+\.html!i', 'http://www.ted.com/talks/oembed.json', true ); + +add_shortcode( 'ted', 'shortcode_ted' ); +function shortcode_ted( $atts, $content = '' ) { + global $wp_embed; + + $defaults = array( + 'id' => '', + 'width' => '', + 'height' => '', + 'lang' => 'eng', + ); + $atts = shortcode_atts( $defaults, $atts ); + + if ( empty( $atts['id'] ) ) + return '<!-- Missing TED ID -->'; + + if ( preg_match( "#^[\d]+$#", $atts['id'], $matches ) ) + $url = 'http://ted.com/talks/view/id/' . $matches[0]; + elseif ( preg_match( "#^https?://(www\.)?ted\.com/talks/view/id/[0-9]+$#", $atts['id'], $matches ) ) + $url = $matches[0]; + + unset( $atts['id'] ); + + $args = array(); + if ( is_numeric( $atts['width'] ) ) + $args['width'] = $atts['width']; + else if ( $embed_size_w = get_option( 'embed_size_w' ) ) + $args['width'] = $embed_size_w; + else if ( ! empty( $GLOBALS['content_width'] ) ) + $args['width'] = (int)$GLOBALS['content_width']; + else + $args['width'] = 500; + + // Default to a 16x9 aspect ratio if there's no height set + if ( is_numeric( $atts['height'] ) ) + $args['height'] = $atts['height']; + else + $args['height'] = $args['width'] * 0.5625; + + if ( ! empty( $atts['lang'] ) ) { + $args['lang'] = sanitize_key( $atts['lang'] ); + add_filter( 'oembed_fetch_url', 'ted_filter_oembed_fetch_url', 10, 3 ); + } + $retval = $wp_embed->shortcode( $args, $url ); + remove_filter( 'oembed_fetch_url', 'ted_filter_oembed_fetch_url', 10 ); + return $retval; +} + +/** + * Filter the request URL to also include the $lang parameter + */ +function ted_filter_oembed_fetch_url( $provider, $url, $args ) { + return add_query_arg( 'lang', $args['lang'], $provider ); +} diff --git a/plugins/jetpack/modules/shortcodes/videopress.php b/plugins/jetpack/modules/shortcodes/videopress.php index 912ab4cf..6aebae03 100644 --- a/plugins/jetpack/modules/shortcodes/videopress.php +++ b/plugins/jetpack/modules/shortcodes/videopress.php @@ -4,7 +4,7 @@ * @category video * @author Automattic Inc * @link http://automattic.com/wordpress-plugins/#videopress VideoPress - * @version 1.5 + * @version 1.5.4 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ @@ -15,8 +15,8 @@ Description: Upload new videos to <a href="http://videopress.com/">VideoPress</a Author: Automattic, Niall Kennedy, Joseph Scott, Gary Pendergast Contributor: Hailin Wu Author URI: http://automattic.com/wordpress-plugins/#videopress -Version: 1.5 -Stable tag: 1.5 +Version: 1.5.4 +Stable tag: 1.5.4 License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ @@ -33,7 +33,7 @@ class VideoPress { * @var string * @since 1.3 */ - const version = '1.5'; + const version = '1.5.4'; /** * Minimum allowed width. We don't expect videos viewed below this width to be useful; we drop small values to help save publishers from themselves. @@ -186,7 +186,7 @@ class VideoPress { wp_enqueue_script( 'swfobject', $swfobject, false. '2.2' ); wp_enqueue_script( 'jquery', $jquery, false, '1.4.4' ); - wp_enqueue_script( 'videopress', $vpjs, array( 'jquery','swfobject' ), '1.07' ); + wp_enqueue_script( 'videopress', $vpjs, array( 'jquery','swfobject' ), '1.09' ); $this->js_loaded = true; return true; @@ -228,10 +228,24 @@ class VideoPress { 'w' => 0, 'freedom' => false, 'flashonly' => false, - 'autoplay' => false + 'autoplay' => false, + 'hd' => false ), $attr ) ); $freedom = (bool) $freedom; + /** + * Test if embedded blog prefers videos only displayed in Freedom-loving formats + */ + if ( $freedom === false && (bool) get_option( 'video_player_freedom', false ) ) + $freedom = true; + + $forcestatic = get_option( 'video_player_static', false ); + + /** + * Set the video to HD if the blog option has it enabled + */ + if ( (bool) get_option( 'video_player_high_quality', false ) ) + $hd = true; $width = absint($w); unset($w); @@ -250,7 +264,9 @@ class VideoPress { $options = array( 'freedom' => $freedom, 'force_flash' => (bool) $flashonly, - 'autoplay' => (bool) $autoplay + 'autoplay' => (bool) $autoplay, + 'forcestatic' => $forcestatic, + 'hd' => (bool) $hd ); unset( $freedom ); unset( $flashonly ); @@ -586,7 +602,6 @@ class VideoPress_Video { $url = 'https://v.wordpress.com/data/wordpress.json'; $response = wp_remote_get( $url . '?' . http_build_query( $request_params, null, '&' ), array( - 'httpversion' => '1.1', 'redirection' => 1, 'user-agent' => 'VideoPress plugin ' . VideoPress::version . '; WordPress ' . $wp_version . ' (' . home_url('/') . ')' ) ); @@ -791,12 +806,15 @@ class VideoPress_Player { $content = ''; } elseif ( is_wp_error( $this->video ) ) { $content = $this->error_message( $this->video ); - } elseif ( ( isset( $this->video->restricted_embed ) && $this->video->restricted_embed === true ) || ( isset( $this->options['force_flash'] ) && $this->options['force_flash'] === true ) ) { + } elseif ( isset( $this->options['force_flash'] ) && $this->options['force_flash'] === true ) { $content = $this->flash_object(); + } elseif ( isset( $this->video->restricted_embed ) && $this->video->restricted_embed === true ) { + if( $this->options['forcestatic'] ) + $content = $this->flash_object(); + else + $content = $this->html5_dynamic(); } elseif ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) { $content = $this->html5_static(); - } elseif ( ! in_the_loop() ) { - $content = $this->flash_object(); } else { $content = $this->html5_dynamic(); } @@ -1029,7 +1047,8 @@ class VideoPress_Player { 'blog' => absint( $this->video->blog_id ), 'post' => absint( $this->video->post_id ), 'duration'=> absint( $this->video->duration ), - 'poster' => esc_url_raw( $this->video->poster_frame_uri, array( 'http', 'https' ) ) + 'poster' => esc_url_raw( $this->video->poster_frame_uri, array( 'http', 'https' ) ), + 'hd' => (bool) $this->options['hd'] ); if ( isset( $this->video->videos ) ) { if ( isset( $this->video->videos->mp4 ) && isset( $this->video->videos->mp4->url ) ) @@ -1046,10 +1065,7 @@ class VideoPress_Player { $guid = $this->video->guid; $guid_js = json_encode( $guid ); $html .= '<script type="text/javascript">' . PHP_EOL; - - // Only need to wait until document is ready if the JS is being loaded in the footer - if ( ! $videopress->js_loaded ) - $html .= 'jQuery(document).ready(function() {'; + $html .= 'jQuery(document).ready(function() {'; $html .= 'if ( !jQuery.VideoPress.data[' . json_encode($guid) . '] ) { jQuery.VideoPress.data[' . json_encode($guid) . '] = new Array(); }' . PHP_EOL; $html .= 'jQuery.VideoPress.data[' . json_encode( $guid ) . '][' . $videopress->shown[$guid] . ']=' . json_encode($data) . ';' . PHP_EOL; @@ -1096,8 +1112,7 @@ class VideoPress_Player { $html .= '}'; // close the jQuery(document).ready() function - if ( !$videopress->js_loaded ) - $html .= '});'; + $html .= '});'; } $html .= '</script>' . PHP_EOL; $html .= '</div>' . PHP_EOL; diff --git a/plugins/jetpack/modules/shortcodes/vimeo.php b/plugins/jetpack/modules/shortcodes/vimeo.php index 2225a5fe..02b417e1 100644 --- a/plugins/jetpack/modules/shortcodes/vimeo.php +++ b/plugins/jetpack/modules/shortcodes/vimeo.php @@ -39,14 +39,14 @@ function vimeo_shortcode( $atts ) { if ( isset( $args['w'] ) ) { $width = (int) $args['w']; - + if ( ! isset( $args['h'] ) ) { // The case where w=300 is specified without h=200, otherwise $height // will always equal the default of 300, no matter what w was set to. $height = round( ( $width / 640 ) * 360 ); } } - + if ( isset( $args['h'] ) ) { $height = (int) $args['h']; @@ -54,7 +54,7 @@ function vimeo_shortcode( $atts ) { $width = round( ( $height / 360 ) * 640 ); } } - + if ( ! $width ) $width = absint( $content_width ); @@ -71,13 +71,13 @@ function vimeo_shortcode( $atts ) { add_shortcode( 'vimeo', 'vimeo_shortcode' ); function vimeo_embed_to_shortcode( $content ) { - if ( false === stripos( $content, 'player.vimeo.com/video/' ) ) + if ( false === stripos( $content, 'player.vimeo.com/video/' ) ) return $content; $regexp = '!<iframe\s+src=[\'"]http://player\.vimeo\.com/video/(\d+)[\'"]((?:\s+\w+=[\'"][^\'"]*[\'"])*)></iframe>!i'; - $regexp_ent = str_replace( '&#0*58;', '&#0*58;|�*58;', htmlspecialchars( $regexp, ENT_NOQUOTES ) ); - - foreach ( array( 'regexp', 'regexp_ent' ) as $reg ) { + $regexp_ent = str_replace( '&#0*58;', '&#0*58;|�*58;', htmlspecialchars( $regexp, ENT_NOQUOTES ) ); + + foreach ( array( 'regexp', 'regexp_ent' ) as $reg ) { if ( !preg_match_all( $$reg, $content, $matches, PREG_SET_ORDER ) ) continue; @@ -86,7 +86,7 @@ function vimeo_embed_to_shortcode( $content ) { $params = $match[2]; - if ( 'regexp_ent' == $reg ) + if ( 'regexp_ent' == $reg ) $params = html_entity_decode( $params ); $params = wp_kses_hair( $params, array( 'http' ) ); @@ -95,8 +95,8 @@ function vimeo_embed_to_shortcode( $content ) { $height = isset( $params['height'] ) ? (int) $params['height']['value'] : 0; $wh = ''; - if ( $width && $height ) - $wh = ' w=' . $width . ' h=' . $height; + if ( $width && $height ) + $wh = ' w=' . $width . ' h=' . $height; $shortcode = '[vimeo ' . $id . $wh . ']'; $content = str_replace( $match[0], $shortcode, $content ); diff --git a/plugins/jetpack/modules/shortcodes/youtube.php b/plugins/jetpack/modules/shortcodes/youtube.php index ebf989f0..74c497ed 100644 --- a/plugins/jetpack/modules/shortcodes/youtube.php +++ b/plugins/jetpack/modules/shortcodes/youtube.php @@ -140,6 +140,7 @@ function youtube_sanitize_url( $url ) { function get_youtube_id( $url ) { $url = youtube_sanitize_url( $url ); $url = parse_url( $url ); + $id = false; if ( ! isset( $url['query'] ) ) return false; @@ -149,7 +150,8 @@ function get_youtube_id( $url ) { if ( ! isset( $qargs['v'] ) && ! isset( $qargs['list'] ) ) return false; - $id = preg_replace( '|[^_a-z0-9-]|i', '', $qargs['list'] ); + if ( isset( $qargs['list'] ) ) + $id = preg_replace( '|[^_a-z0-9-]|i', '', $qargs['list'] ); if ( empty( $id ) ) $id = preg_replace( '|[^_a-z0-9-]|i', '', $qargs['v'] ); @@ -175,12 +177,6 @@ function youtube_id( $url ) { parse_str( $url['query'], $qargs ); - $agent = $_SERVER['HTTP_USER_AGENT']; - // Bloglines & Google Reader handle YouTube well now, instead of - // big blank space of yester year, so they can skip this treatment - if ( is_feed() && ! preg_match( '#' . apply_filters( 'jetpack_shortcode_youtube_whitelist_user_agents', 'Bloglines|FeedFetcher-Google|feedburner' ) . '#i', $agent ) ) - return '<span style="text-align:center; display: block;"><a href="' . get_permalink() . '"><img src="http://img.youtube.com/vi/' . $id . '/2.jpg" alt="" /></a></span>'; - // calculate the width and height, taking content_width into consideration global $content_width; diff --git a/plugins/jetpack/modules/stats.php b/plugins/jetpack/modules/stats.php index 32c6cf2c..260b3f58 100644 --- a/plugins/jetpack/modules/stats.php +++ b/plugins/jetpack/modules/stats.php @@ -10,11 +10,26 @@ if ( defined( 'STATS_VERSION' ) ) { return; } -define( 'STATS_VERSION', '7' ); +define( 'STATS_VERSION', '8' ); defined( 'STATS_DASHBOARD_SERVER' ) or define( 'STATS_DASHBOARD_SERVER', 'dashboard.wordpress.com' ); add_action( 'jetpack_modules_loaded', 'stats_load' ); +// Tell HQ about changed settings +Jetpack_Sync::sync_options( __FILE__, + 'stats_options', + 'home', + 'siteurl', + 'blogname', + 'blogdescription', + 'gmt_offset', + 'timezone_string', + 'page_on_front', + 'permalink_structure', + 'category_base', + 'tag_base' +); + function stats_load() { global $wp_roles; @@ -23,6 +38,14 @@ function stats_load() { Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' ); Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' ); + // Tell HQ about changed posts + $post_stati = get_post_stati( array( 'public' => true ) ); // All public post stati + $post_stati[] = 'private'; // Content from private stati will be redacted + Jetpack_Sync::sync_posts( __FILE__, array( + 'post_types' => get_post_types( array( 'public' => true ) ), // All public post types + 'post_stati' => $post_stati, + ) ); + // Generate the tracking code after wp() has queried for posts. add_action( 'template_redirect', 'stats_template_redirect', 1 ); @@ -32,22 +55,6 @@ function stats_load() { add_action( 'wp_dashboard_setup', 'stats_register_dashboard_widget' ); - // Tell HQ about changed settings - add_action( 'update_option_home', 'stats_update_blog' ); - add_action( 'update_option_siteurl', 'stats_update_blog' ); - add_action( 'update_option_blogname', 'stats_update_blog' ); - add_action( 'update_option_blogdescription', 'stats_update_blog' ); - add_action( 'update_option_timezone_string', 'stats_update_blog' ); - add_action( 'add_option_timezone_string', 'stats_update_blog' ); - add_action( 'update_option_gmt_offset', 'stats_update_blog' ); - add_action( 'update_option_page_on_front', 'stats_update_blog' ); - add_action( 'update_option_permalink_structure', 'stats_update_blog' ); - add_action( 'update_option_category_base', 'stats_update_blog' ); - add_action( 'update_option_tag_base', 'stats_update_blog' ); - - // Tell HQ about changed posts - add_action( 'save_post', 'stats_update_post', 10, 1 ); - add_filter( 'jetpack_xmlrpc_methods', 'stats_xmlrpc_methods' ); // Map stats caps @@ -111,6 +118,7 @@ function stats_template_redirect() { add_action( 'wp_head', 'stats_add_shutdown_action' ); $blog = Jetpack::get_option( 'id' ); + $tz = get_option( 'gmt_offset' ); $v = 'ext'; $j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ); if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) { @@ -134,7 +142,7 @@ function stats_template_redirect() { $http = is_ssl() ? 'https' : 'http'; $week = gmdate( 'YW' ); - $data = stats_array( compact( 'v', 'j', 'blog', 'post' ) ); + $data = stats_array( compact( 'v', 'j', 'blog', 'post', 'tz' ) ); $stats_footer = <<<END @@ -369,7 +377,7 @@ function stats_reports_page() { 'data' => 'data', 'blog_subscribers' => 'int', 'comment_subscribers' => null, - 'type' => array( 'email', 'pending' ), + 'type' => array( 'wpcom', 'email', 'pending' ), 'pagenum' => 'int', ); foreach ( $args as $var => $vals ) { @@ -401,24 +409,21 @@ function stats_reports_page() { $url = add_query_arg( $q, $url ); $method = 'GET'; $timeout = 90; - $user_id = 1; // means send the wp.com user_id, not 1 + $user_id = JETPACK_MASTER_USER; // means send the wp.com user_id $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); $get_code = wp_remote_retrieve_response_code( $get ); - $get_code_type = intval( $get_code / 100 ); - if ( is_wp_error( $get ) || ( 2 != $get_code_type && 304 != $get_code ) ) { - // @todo nicer looking error - if ( 3 == $get_code_type ) { - echo '<p>' . __( 'We were unable to get your stats just now (too many redirects). Please try again.', 'jetpack' ) . '</p>'; - } else { - echo '<p>' . __( 'We were unable to get your stats just now. Please try again.', 'jetpack' ) . '</p>'; - } + if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) { + stats_print_wp_remote_error( $get, $url ); } else { if ( !empty( $get['headers']['content-type'] ) ) { $type = $get['headers']['content-type']; if ( substr( $type, 0, 5 ) == 'image' ) { + $img = $get['body']; header( 'Content-Type: ' . $type ); - die( $get['body'] ); + header( 'Content-Length: ' . strlen( $img ) ); + echo $img; + die(); } } $body = stats_convert_post_titles( $get['body'] ); @@ -442,7 +447,13 @@ function stats_convert_image_urls( $html ) { } function stats_convert_chart_urls( $html ) { - $html = preg_replace( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php|', 'admin.php?page=stats&noheader&chart=$1', $html ); + $html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|', + create_function( + '$matches', + // If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string + 'return "admin.php?page=stats&noheader&chart=" . $matches[1] . str_replace( "?", "&", $matches[2] );' + ), + $html ); return $html; } @@ -591,7 +602,9 @@ function stats_admin_bar_menu( &$wp_admin_bar ) { $img_src = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale' ), $url ) ); $img_src_2x = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale-2x' ), $url ) ); - $title = __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ); + $alt = esc_attr( __( 'Stats', 'jetpack' ) ); + + $title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) ); $menu = array( 'id' => 'stats', 'title' => "<div><script type='text/javascript'>var src;if(typeof(window.devicePixelRatio)=='undefined'||window.devicePixelRatio<2){src='$img_src';}else{src='$img_src_2x';}document.write('<img src=\''+src+'\' alt=\'$alt\' title=\'$title\' />');</script></div>", 'href' => $url ); @@ -602,14 +615,6 @@ function stats_update_blog() { Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() ); } -function stats_update_post( $post ) { - if ( !$stats_post = stats_get_post( $post ) ) - return; - - $jetpack = Jetpack::init(); - $jetpack->sync->post( $stats_post->ID, array_keys( get_object_vars( $stats_post ) ) ); -} - function stats_get_blog() { $home = parse_url( trailingslashit( get_option( 'home' ) ) ); $blog = array( @@ -632,36 +637,9 @@ function stats_get_blog() { return array_map( 'esc_html', $blog ); } -function stats_get_posts( $args ) { - list( $post_ids ) = $args; - $post_ids = array_map( 'intval', (array) $post_ids ); - $r = array( - 'include' => $post_ids, - 'post_type' => array_values( get_post_types( array( 'public' => true ) ) ), - 'post_status' => array_values( get_post_stati( array( 'public' => true ) ) ), - ); - $posts = get_posts( $r ); - foreach ( $posts as $i => $post ) - $posts[$i] = stats_get_post( $post ); - return $posts; -} - -function stats_get_post( $post ) { - if ( !$post = get_post( $post ) ) { - return null; - } - - $stats_post = wp_clone( $post ); - $stats_post->permalink = get_permalink( $post ); - foreach ( array( 'post_content', 'post_excerpt', 'post_content_filtered', 'post_password' ) as $do_not_want ) - unset( $stats_post->$do_not_want ); - return $stats_post; -} - function stats_xmlrpc_methods( $methods ) { $my_methods = array( 'jetpack.getBlog' => 'stats_get_blog', - 'jetpack.getPosts' => 'stats_get_posts', ); return array_merge( $methods, $my_methods ); @@ -737,7 +715,7 @@ function stats_dashboard_widget_control() { </p> <p> - <label for="top"><?php _e( 'Show top posts over', 'jetpack'); ?></label> + <label for="top"><?php _e( 'Show top posts over', 'jetpack' ); ?></label> <select id="top" name="top"> <?php foreach ( $intervals as $val => $label ) { @@ -750,7 +728,7 @@ function stats_dashboard_widget_control() { </p> <p> - <label for="search"><?php _e( 'Show top search terms over', 'jetpack'); ?></label> + <label for="search"><?php _e( 'Show top search terms over', 'jetpack' ); ?></label> <select id="search" name="search"> <?php foreach ( $intervals as $val => $label ) { @@ -892,18 +870,12 @@ function stats_dashboard_widget_content() { $url = add_query_arg( $q, $url ); $method = 'GET'; $timeout = 90; - $user_id = 1; // means send the wp.com user_id, not 1 + $user_id = JETPACK_MASTER_USER; $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); $get_code = wp_remote_retrieve_response_code( $get ); - $get_code_type = intval( $get_code / 100 ); - if ( is_wp_error( $get ) || ( 2 != $get_code_type && 304 != $get_code ) || empty( $get['body'] ) ) { - // @todo - if ( 3 == $get_code_type ) { - echo '<p>' . __( 'We were unable to get your stats just now (too many redirects). Please try again.', 'jetpack' ) . '</p>'; - } else { - echo '<p>' . __( 'We were unable to get your stats just now. Please try again.', 'jetpack' ) . '</p>'; - } + if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) { + stats_print_wp_remote_error( $get, $url ); } else { $body = stats_convert_post_titles($get['body']); $body = stats_convert_chart_urls($body); @@ -976,6 +948,50 @@ function stats_dashboard_widget_content() { exit; } +function stats_print_wp_remote_error( $get, $url ) { + $state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 ); + $previous_error = Jetpack::state( $state_name ); + $error = md5( serialize( compact( 'get', 'url' ) ) ); + Jetpack::state( $state_name, $error ); + if ( $error !== $previous_error ) { +?> + <div class="wrap"> + <p><?php _e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p> + </div> +<?php + return; + } +?> + <div class="wrap"> + <p><?php printf( __( 'We were unable to get your stats just now. Please reload this page to try again. If this error persists, please <a href="%1$s">contact support</a>. In your report please include the information below.', 'jetpack' ), 'http://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p> + <pre> + User Agent: "<?php print htmlspecialchars( $_SERVER['HTTP_USER_AGENT'] ); ?>" + Page URL: "http<?php print (is_ssl()?'s':'') . '://' . htmlspecialchars( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>" + API URL: "<?php print clean_url( $url ); ?>" +<?php + if ( is_wp_error( $get ) ) { + foreach ( $get->get_error_codes() as $code ) { + foreach ( $get->get_error_messages($code) as $message ) { + ?> + <?php print $code . ': "' . $message . '"' ?> + +<?php + } + } + } else { + $get_code = wp_remote_retrieve_response_code( $get ); + $content_length = strlen( wp_remote_retrieve_body( $get ) ); + ?> + Response code: "<?php print $get_code ?>" + Content length: "<?php print $content_length ?>" + +<?php + } + ?></pre> + </div> + <?php +} + function stats_get_csv( $table, $args = null ) { $defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' ); @@ -1034,7 +1050,7 @@ function stats_get_csv( $table, $args = null ) { function stats_get_remote_csv( $url ) { $method = 'GET'; $timeout = 90; - $user_id = 1; // means send the wp.com user_id, not 1 + $user_id = JETPACK_MASTER_USER; $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); $get_code = wp_remote_retrieve_response_code( $get ); diff --git a/plugins/jetpack/modules/subscriptions.php b/plugins/jetpack/modules/subscriptions.php index 2355867e..39c639ff 100644 --- a/plugins/jetpack/modules/subscriptions.php +++ b/plugins/jetpack/modules/subscriptions.php @@ -8,6 +8,23 @@ add_action( 'jetpack_modules_loaded', 'jetpack_subscriptions_load' ); +Jetpack_Sync::sync_options( __FILE__, + 'home', + 'blogname', + 'siteurl', + 'page_on_front', + 'permalink_structure', + 'category_base', + 'rss_use_excerpt', + 'subscription_options', + 'stb_enabled', + 'stc_enabled', + 'tag_base' +); + +Jetpack_Sync::sync_posts( __FILE__ ); +Jetpack_Sync::sync_comments( __FILE__ ); + function jetpack_subscriptions_load() { Jetpack::enable_module_configurable( __FILE__ ); Jetpack::module_configuration_load( __FILE__, 'jetpack_subscriptions_configuration_load' ); @@ -17,6 +34,7 @@ function jetpack_subscriptions_configuration_load() { wp_safe_redirect( admin_url( 'options-discussion.php#jetpack-subscriptions-settings' ) ); exit; } + class Jetpack_Subscriptions { var $jetpack = false; @@ -43,22 +61,6 @@ class Jetpack_Subscriptions { // Add Configuration Page add_action( 'admin_init', array( $this, 'configure' ) ); - - // Handle Posts - add_action( 'transition_post_status', array( $this, 'transition_post_status' ), 10, 3 ); - add_action( 'trashed_post', array( $this, 'delete_post' ) ); - add_action( 'delete_post', array( $this, 'delete_post' ) ); - - // Handle Taxonomy - add_action( 'created_term', array( $this, 'save_taxonomy'), 10, 3); - add_action( 'edited_term', array( $this, 'save_taxonomy'), 10, 3 ); - add_action( 'delete_term', array( $this, 'delete_taxonomy'), 10, 3 ); - - // Handle Comments - add_action( 'wp_insert_comment', array( $this, 'save_comment' ), 10, 2 ); - add_action( 'transition_comment_status', array( $this, 'transition_comment_status' ), 10, 3 ); - add_action( 'trashed_comment', array( $this, 'delete_comment' ) ); - add_action( 'delete_comment', array( $this, 'delete_comment' ) ); // Set up the subscription widget. add_action( 'widgets_init', array( $this, 'widget_init' ) ); @@ -82,73 +84,6 @@ class Jetpack_Subscriptions { return 'publish' === $post->post_status && strlen( (string) $post->post_password ) < 1; } - function transition_post_status( $new, $old, $the_post ) { - if ( 'publish' == $old && 'publish' != $new ) { - // A published post was trashed or something else - $this->delete_post( $the_post->ID ); - return; - } - - clean_post_cache( $the_post->ID ); - - // Publish a new post - if ( - 'publish' != $old - && - $this->post_is_public( $the_post->ID ) - && - ( 'post' == $the_post->post_type || 'page' == $the_post->post_type ) - ) { - $this->jetpack->sync->post( $the_post->ID ); - } - } - - function save_taxonomy( $term, $tt_id, $taxonomy = null ) { - if ( is_null( $taxonomy ) ) - return; - - $tax = get_term_by( 'id', $term, $taxonomy ); - $this->jetpack->sync->taxonomy( $tax->slug, true, $taxonomy ); - } - - function delete_taxonomy( $term, $tt_id, $taxonomy ) { - $tags = get_terms( $taxonomy, array( 'hide_empty' => 0 ) ); // since we can't figure out what the slug is... we will do an array comparison on the remote site and remove old taxonomy... - $this->jetpack->sync->delete_taxonomy( $tags, $taxonomy ); - } - - function delete_post( $id ) { - $the_post = get_post( $id ); - if ( 'post' == $the_post->post_type || 'page' == $the_post->post_type ) - $this->jetpack->sync->delete_post( $id ); - } - - function save_comment( $id, $comment ) { - if ( !$this->post_is_public( $comment->comment_post_ID ) ) { - return; - } - - if ( 1 == $comment->comment_approved ) { - $this->jetpack->sync->comment( $id ); - } - } - - function transition_comment_status( $new, $old, $the_comment ) { - if ( !$this->post_is_public( $the_comment->comment_post_ID ) ) { - return; - } - - if ( 'approved' == $new ) { - $this->jetpack->sync->comment( $the_comment->comment_ID ); - } else if ( 'approved' == $old && 'approved' != $new ) { - // Delete comments that are changing to anything but approved - $this->jetpack->sync->delete_comment( $the_comment->comment_ID ); - } - } - - function delete_comment( $id ) { - $this->jetpack->sync->delete_comment( $id ); - } - /** * Jetpack_Subscriptions::xmlrpc_methods() * @@ -204,6 +139,37 @@ class Jetpack_Subscriptions { 'discussion', 'stc_enabled' ); + + /** Subscription Messaging Options ******************************************************/ + + register_setting( + 'reading', + 'subscription_options', + array( $this, 'validate_settings' ) + ); + + add_settings_section( + 'email_settings', + __( 'Follower Settings', 'jetpack' ), + array( $this, 'reading_section' ), + 'reading' + ); + + add_settings_field( + 'invitation', + __( 'Blog follow email text' , 'jetpack' ), + array( $this, 'setting_invitation' ), + 'reading', + 'email_settings' + ); + + add_settings_field( + 'comment-follow', + __( 'Comment follow email text', 'jetpack' ), + array( $this, 'setting_comment_follow' ), + 'reading', + 'email_settings' + ); } /** @@ -249,6 +215,51 @@ class Jetpack_Subscriptions { <?php } + function validate_settings( $settings ) { + global $allowedposttags; + + $default = $this->get_default_settings(); + + // Blog Follow + $settings['invitation'] = trim( wp_kses( $settings['invitation'], $allowedposttags ) ); + if ( empty( $settings['invitation'] ) ) + $settings['invitation'] = $default['invitation']; + + // Comments Follow (single post) + $settings['comment_follow'] = trim( wp_kses( $settings['comment_follow'], $allowedposttags ) ); + if ( empty( $settings['comment_follow'] ) ) + $settings['comment_follow'] = $default['comment_follow']; + + return $settings; + } + + public function reading_section() { + _e( 'These settings change emails sent from your blog to followers.' , 'jetpack'); + } + + public function setting_invitation() { + $settings = $this->get_settings(); + echo '<textarea name="subscription_options[invitation]" class="large-text" cols="50" rows="5">'.$settings['invitation'].'</textarea>'; + echo '<p><span class="description">'.__( 'Introduction text sent when someone follows your blog. (Site and confirmation details will be automatically added for you.)' , 'jetpack').'</span></p>'; + } + + public function setting_comment_follow() { + $settings = $this->get_settings(); + echo '<textarea name="subscription_options[comment_follow]" class="large-text" cols="50" rows="5">'.$settings['comment_follow'].'</textarea>'; + echo '<p><span class="description">'.__( 'Introduction text sent when someone follows a post on your blog. (Site and confirmation details will be automatically added for you.)' , 'jetpack').'</span></p>'; + } + + function get_default_settings() { + return array( + 'invitation' => __( "Howdy.\n\nYou recently followed this blog's posts. This means you will receive each new post by email.\n\nTo activate, click confirm below. If you believe this is an error, ignore this message and we'll never bother you again." , 'jetpack'), + 'comment_follow' => __( "Howdy.\n\nYou recently followed one of my posts. This means you will receive an email when new comments are posted.\n\nTo activate, click confirm below. If you believe this is an error, ignore this message and we'll never bother you again." , 'jetpack') + ); + } + + function get_settings() { + return wp_parse_args( (array) get_option( 'subscription_options', array() ), $this->get_default_settings() ); + } + /** * Jetpack_Subscriptions::subscribe() * @@ -356,7 +367,9 @@ class Jetpack_Subscriptions { */ function widget_submit() { // Check the nonce. - check_admin_referer( 'blogsub_subscribe_' . get_current_blog_id() ); + if ( is_user_logged_in() ) { + check_admin_referer( 'blogsub_subscribe_' . get_current_blog_id() ); + } if ( empty( $_REQUEST['email'] ) ) return false; @@ -428,17 +441,19 @@ class Jetpack_Subscriptions { $str = ''; - if ( FALSE === has_filter( 'comment_form', 'show_subscription_checkbox' ) ) { + if ( FALSE === has_filter( 'comment_form', 'show_subscription_checkbox' ) && 1 == get_option( 'stc_enabled', 1 ) ) { // Subscribe to comments checkbox $str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_comments" id="subscribe_comments" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $comments_checked . ' /> '; - $str .= '<label class="subscribe-label" id="subscribe-label" for="subscribe_comments">' . __( 'Notify me of follow-up comments by email.', 'jetpack' ) . '</label>'; + $str .= '<label class="subscribe-label" id="subscribe-label" for="subscribe_comments" style="display: inline;">' . __( 'Notify me of follow-up comments by email.', 'jetpack' ) . '</label>'; $str .= '</p>'; } - // Subscribe to blog checkbox - $str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_blog" id="subscribe_blog" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $blog_checked . ' /> '; - $str .= '<label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog">' . __( 'Notify me of new posts by email.', 'jetpack' ) . '</label>'; - $str .= '</p>'; + if ( 1 == get_option( 'stb_enabled', 1 ) ) { + // Subscribe to blog checkbox + $str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_blog" id="subscribe_blog" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $blog_checked . ' /> '; + $str .= '<label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog" style="display: inline;">' . __( 'Notify me of new posts by email.', 'jetpack' ) . '</label>'; + $str .= '</p>'; + } echo apply_filters( 'jetpack_comment_subscription_form', $str ); } @@ -516,83 +531,79 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { extract( $args ); $instance = wp_parse_args( (array) $instance, $this->defaults() ); - $title = stripslashes( $instance['title'] ); - $subscribe_text = stripslashes( $instance['subscribe_text'] ); - $subscribe_button = stripslashes( $instance['subscribe_button'] ); - $subscribe_logged_in = stripslashes( $instance['subscribe_logged_in'] ); + $title = isset( $instance['title'] ) ? stripslashes( $instance['title'] ) : ''; + $subscribe_text = isset( $instance['subscribe_text'] ) ? stripslashes( $instance['subscribe_text'] ) : ''; + $subscribe_button = isset( $instance['subscribe_button'] ) ? stripslashes( $instance['subscribe_button'] ) : ''; + $subscribe_logged_in = isset( $instance['subscribe_logged_in'] ) ? stripslashes( $instance['subscribe_logged_in'] ) : ''; $show_subscribers_total = (bool) $instance['show_subscribers_total']; $subscribers_total = $this->fetch_subscriber_count(); if ( ! is_array( $subscribers_total ) ) $show_subscribers_total = FALSE; - echo $before_widget; - echo $before_title . '<label for="subscribe-field">' . esc_attr( $instance['title'] ) . '</label>' . $after_title . "\n"; + echo $args['before_widget']; + echo $args['before_title'] . '<label for="subscribe-field">' . esc_attr( $instance['title'] ) . '</label>' . $args['after_title'] . "\n"; $referer = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; // Check for subscription confirmation. - if ( isset( $_GET['subscribe'] ) && 'success' == $_GET['subscribe'] ) { - ?> + if ( isset( $_GET['subscribe'] ) && 'success' == $_GET['subscribe'] ) : ?> <div class="success"> - <p><?php _e( 'An email was just sent to confirm your subscription. Please find the email now and click activate to start subscribing.', 'jetpack' ); ?></p> + <p><?php esc_html_e( 'An email was just sent to confirm your subscription. Please find the email now and click activate to start subscribing.', 'jetpack' ); ?></p> </div> - <?php - } + <?php endif; // Display any errors if ( isset( $_GET['subscribe'] ) ) : switch ( $_GET['subscribe'] ) : - case 'invalid_email' : ?> - <p class="error"><?php _e( 'The email you entered was invalid, please check and try again.', 'jetpack' ); ?></p> - <?php break; - case 'already' : ?> - <p class="error"><?php _e( 'You have already subscribed to this site, please check your inbox.', 'jetpack' ); ?></p> - <?php break; - case 'success' : - - echo wpautop( $subscribe_text ); - break; - default : ?> - <p class="error"><?php _e( 'There was an error when subscribing, please try again.', 'jetpack' ) ?></p> - <?php break; + case 'invalid_email' : ?> + <p class="error"><?php esc_html_e( 'The email you entered was invalid, please check and try again.', 'jetpack' ); ?></p> + <?php break; + case 'already' : ?> + <p class="error"><?php esc_html_e( 'You have already subscribed to this site, please check your inbox.', 'jetpack' ); ?></p> + <?php break; + case 'success' : + echo wpautop( $subscribe_text ); + break; + default : ?> + <p class="error"><?php esc_html_e( 'There was an error when subscribing, please try again.', 'jetpack' ) ?></p> + <?php break; endswitch; endif; - $email_address = ''; - if ( ! empty( $current_user->user_email ) ) - $email_address = $current_user->user_email; - // Display a subscribe form ?> - <a name="subscribe-blog"></a> - <form action="" method="post" accept-charset="utf-8" id="subscribe-blog"> + <form action="" method="post" accept-charset="utf-8" id="subscribe-blog-<?php echo !empty( $args['widget_id'] ) ? esc_attr( $args['widget_id'] ) : mt_rand( 450, 550 ); ?>"> <?php if ( ! isset ( $_GET['subscribe'] ) ) { ?><p><?php echo $subscribe_text ?></p><?php } - if ( $show_subscribers_total && $subscribers_total['value'] > 0 ) { + if ( $show_subscribers_total && 0 < $subscribers_total['value'] ) { echo wpautop( sprintf( _n( 'Join %s other subscriber', 'Join %s other subscribers', $subscribers_total['value'], 'jetpack' ), number_format_i18n( $subscribers_total['value'] ) ) ); } ?> - <p><input type="text" name="email" style="width: 95%; padding: 1px 2px" value="<?php if ( !empty( $email_address ) ) { echo $email_address; } else { _e( 'Email Address', 'jetpack' ); } ?>" id="subscribe-field" onclick="if ( this.value == '<?php _e( 'Email Address', 'jetpack' ) ?>' ) { this.value = ''; }" onblur="if ( this.value == '' ) { this.value = '<?php _e( 'Email Address', 'jetpack' ) ?>'; }" /></p> + <p><input type="text" name="email" style="width: 95%; padding: 1px 2px" value="<?php echo !empty( $current_user->user_email ) ? esc_attr( $current_user->user_email ) : esc_html__( 'Email Address', 'jetpack' ); ?>" id="subscribe-field" onclick="if ( this.value == '<?php esc_html_e( 'Email Address', 'jetpack' ) ?>' ) { this.value = ''; }" onblur="if ( this.value == '' ) { this.value = '<?php esc_html_e( 'Email Address', 'jetpack' ) ?>'; }" /></p> <p> <input type="hidden" name="action" value="subscribe" /> <input type="hidden" name="source" value="<?php echo esc_url( $referer ); ?>" /> <input type="hidden" name="sub-type" value="<?php echo esc_attr( $source ); ?>" /> <input type="hidden" name="redirect_fragment" value="<?php echo esc_attr( $widget_id ); ?>" /> - <?php wp_nonce_field( 'blogsub_subscribe_'. get_current_blog_id(), '_wpnonce', false ); ?> + <?php + if ( is_user_logged_in() ) { + wp_nonce_field( 'blogsub_subscribe_'. get_current_blog_id(), '_wpnonce', false ); + } + ?> <input type="submit" value="<?php echo esc_attr( $subscribe_button ); ?>" name="jetpack_subscriptions_widget" /> </p> </form> <?php - echo "\n" . $after_widget; + echo "\n" . $args['after_widget']; } function increment_subscriber_count( $current_subs_array = array() ) { @@ -610,7 +621,7 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { 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.fetchSubscriberCount' ); @@ -638,10 +649,10 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { function update( $new_instance, $old_instance ) { $instance = $old_instance; - $instance['title'] = strip_tags( stripslashes( $new_instance['title'] ) ); + $instance['title'] = wp_kses( stripslashes( $new_instance['title'] ), array() ); $instance['subscribe_text'] = wp_filter_post_kses( stripslashes( $new_instance['subscribe_text'] ) ); $instance['subscribe_logged_in'] = wp_filter_post_kses( stripslashes( $new_instance['subscribe_logged_in'] ) ); - $instance['subscribe_button'] = strip_tags( stripslashes( $new_instance['subscribe_button'] ) ); + $instance['subscribe_button'] = wp_kses( stripslashes( $new_instance['subscribe_button'] ), array() ); $instance['show_subscribers_total'] = isset( $new_instance['show_subscribers_total'] ) && $new_instance['show_subscribers_total']; return $instance; @@ -649,10 +660,10 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { function defaults() { return array( - 'title' => __( 'Subscribe to Blog via Email', 'jetpack' ), - 'subscribe_text' => __( 'Enter your email address to subscribe to this blog and receive notifications of new posts by email.', 'jetpack' ), - 'subscribe_button' => __( 'Subscribe', 'jetpack' ), - 'subscribe_logged_in' => __( 'Click to subscribe to this blog and receive notifications of new posts by email.', 'jetpack' ), + 'title' => esc_html__( 'Subscribe to Blog via Email', 'jetpack' ), + 'subscribe_text' => esc_html__( 'Enter your email address to subscribe to this blog and receive notifications of new posts by email.', 'jetpack' ), + 'subscribe_button' => esc_html__( 'Subscribe', 'jetpack' ), + 'subscribe_logged_in' => esc_html__( 'Click to subscribe to this blog and receive notifications of new posts by email.', 'jetpack' ), 'show_subscribers_total' => true, ); } @@ -660,9 +671,9 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->defaults() ); - $title = esc_attr( stripslashes( $instance['title'] ) ); - $subscribe_text = esc_attr( stripslashes( $instance['subscribe_text'] ) ); - $subscribe_button = esc_attr( stripslashes( $instance['subscribe_button'] ) ); + $title = stripslashes( $instance['title'] ); + $subscribe_text = stripslashes( $instance['subscribe_text'] ); + $subscribe_button = stripslashes( $instance['subscribe_button'] ); $show_subscribers_total = checked( $instance['show_subscribers_total'], true, false ); $subs_fetch = $this->fetch_subscriber_count(); @@ -702,3 +713,13 @@ class Jetpack_Subscriptions_Widget extends WP_Widget { } } +add_shortcode( 'jetpack_subscription_form', 'jetpack_do_subscription_form' ); + +function jetpack_do_subscription_form( $args ) { + $args['show_subscribers_total'] = empty( $args['show_subscribers_total'] ) ? false : true; + $args = shortcode_atts( Jetpack_Subscriptions_Widget::defaults(), $args ); + ob_start(); + the_widget( 'Jetpack_Subscriptions_Widget', $args ); + $output = ob_get_clean(); + return $output; +} diff --git a/plugins/jetpack/modules/tiled-gallery.php b/plugins/jetpack/modules/tiled-gallery.php new file mode 100644 index 00000000..6db87144 --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery.php @@ -0,0 +1,25 @@ +<?php + +/** + * Module Name: Tiled Galleries + * Module Description: Create elegant magazine-style mosaic layouts for your photos without using an external graphic editor. + * First Introduced: 2.1 + */ + +function jetpack_load_tiled_gallery() { + include dirname( __FILE__ ) . "/tiled-gallery/tiled-gallery.php"; +} + +add_action( 'jetpack_modules_loaded', 'jetpack_tiled_gallery_loaded' ); + +function jetpack_tiled_gallery_loaded() { + Jetpack::enable_module_configurable( __FILE__ ); + Jetpack::module_configuration_load( __FILE__, 'jetpack_tiled_gallery_configuration_load' ); +} + +function jetpack_tiled_gallery_configuration_load() { + wp_safe_redirect( admin_url( 'options-media.php' ) ); + exit; +} + +jetpack_load_tiled_gallery();
\ No newline at end of file diff --git a/plugins/jetpack/modules/tiled-gallery/math/class-constrained-array-rounding.php b/plugins/jetpack/modules/tiled-gallery/math/class-constrained-array-rounding.php new file mode 100644 index 00000000..dab5fd27 --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery/math/class-constrained-array-rounding.php @@ -0,0 +1,75 @@ +<?php + +/** + * Lets you round the numeric elements of an array to integers while preserving their sum. + * + * Usage: + * + * Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $bound_array ) + * if a specific sum doesn't need to be specified for the bound array + * + * Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $bound_array, $sum ) + * If the sum of $bound_array must equal $sum after rounding. + * + * If $sum is less than the sum of the floor of the elements of the array, the class defaults to using the sum of the array elements. + */ +class Jetpack_Constrained_Array_Rounding { + public static function get_rounded_constrained_array( $bound_array, $sum = false ) { + // Convert associative arrays before working with them and convert them back before returning the values + $keys = array_keys( $bound_array ); + $bound_array = array_values( $bound_array ); + + $bound_array_int = self::get_int_floor_array( $bound_array ); + + $lower_sum = array_sum( wp_list_pluck( $bound_array_int, 'floor' ) ); + if ( ! $sum || ( $sum < $lower_sum ) ) { + // If value of sum is not supplied or is invalid, calculate the sum that the returned array is constrained to match + $sum = array_sum( $bound_array ); + } + $diff_sum = $sum - $lower_sum; + + self::adjust_constrained_array( $bound_array_int, $diff_sum ); + + $bound_array_fin = wp_list_pluck( $bound_array_int, 'floor' ); + return array_combine( $keys, $bound_array_fin ); + } + + private static function get_int_floor_array( $bound_array ) { + $bound_array_int_floor = array(); + foreach ( $bound_array as $i => $value ){ + $bound_array_int_floor[$i] = array( + 'floor' => (int) floor( $value ), + 'fraction' => $value - floor( $value ), + 'index' => $i, + ); + } + + return $bound_array_int_floor; + } + + private static function adjust_constrained_array( &$bound_array_int, $adjustment ) { + usort( $bound_array_int, array( 'self', 'cmp_desc_fraction' ) ); + + $start = 0; + $end = $adjustment - 1; + $length = count( $bound_array_int ); + + for ( $i = $start; $i <= $end; $i++ ) { + $bound_array_int[ $i % $length ]['floor']++; + } + + usort( $bound_array_int, array( 'self', 'cmp_asc_index' ) ); + } + + private static function cmp_desc_fraction( $a, $b ) { + if ( $a['fraction'] == $b['fraction'] ) + return 0; + return $a['fraction'] > $b['fraction'] ? -1 : 1; + } + + private static function cmp_asc_index( $a, $b ) { + if ( $a['index'] == $b['index'] ) + return 0; + return $a['index'] < $b['index'] ? -1 : 1; + } +} diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php new file mode 100644 index 00000000..342e8a65 --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php @@ -0,0 +1,588 @@ +<?php + +// Include the class file containing methods for rounding constrained array elements. +// Here the constrained array element is the dimension of a row, group or an image in the tiled gallery. +include_once dirname( __FILE__ ) . '/math/class-constrained-array-rounding.php'; + +class Jetpack_Tiled_Gallery { + + public function __construct() { + add_action( 'admin_init', array( $this, 'settings_api_init' ) ); + add_filter( 'jetpack_gallery_types', array( $this, 'jetpack_gallery_types' ), 9 ); + } + + public function tiles_enabled() { + // Check the setting status + return '' != get_option( 'tiled_galleries' ); + } + + public function set_atts( $atts ) { + global $post; + + $this->atts = shortcode_atts( array( + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'id' => $post->ID, + 'include' => '', + 'exclude' => '', + 'type' => '', + 'grayscale' => false, + 'link' => '', + ), $atts ); + + $this->atts['id'] = (int) $this->atts['id']; + $this->float = is_rtl() ? 'right' : 'left'; + + // Default to rectangular is tiled galleries are checked + if ( $this->tiles_enabled() && ( ! $this->atts['type'] || 'default' == $this->atts['type'] ) ) + $this->atts['type'] = 'rectangular'; + + if ( !$this->atts['orderby'] ) { + $this->atts['orderby'] = sanitize_sql_orderby( $this->atts['orderby'] ); + if ( !$this->atts['orderby'] ) + $this->atts['orderby'] = 'menu_order ID'; + } + + if ( 'RAND' == $this->atts['order'] ) + $this->atts['orderby'] = 'none'; + } + + public function get_attachments() { + extract( $this->atts ); + + 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 ) ); + } + return $attachments; + } + + public function get_attachment_link( $attachment_id, $orig_file ) { + if ( isset( $this->atts['link'] ) && $this->atts['link'] == 'file' ) + return $orig_file; + else + return get_attachment_link( $attachment_id ); + } + + public function default_scripts_and_styles() { + wp_enqueue_script( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.js', __FILE__ ), array( 'jquery' ) ); + wp_enqueue_style( 'tiled-gallery', plugins_url( 'tiled-gallery/tiled-gallery.css', __FILE__ ), array(), '2012-09-21' ); + } + + public function gallery_shortcode( $val, $atts ) { + if ( ! empty( $val ) ) // something else is overriding post_gallery, like a custom VIP shortcode + return $val; + + global $post; + + $this->set_atts( $atts ); + + $attachments = $this->get_attachments(); + if ( empty( $attachments ) ) + return ''; + + if ( is_feed() || defined( 'IS_HTML_EMAIL' ) ) + return ''; + + if ( method_exists( $this, $this->atts['type'] . '_talavera' ) ) { + // Enqueue styles and scripts + $this->default_scripts_and_styles(); + $gallery_html = call_user_func_array( array( $this, $this->atts['type'] . '_talavera' ), array( $attachments ) ); + + if ( $gallery_html && class_exists( 'Jetpack' ) && class_exists( 'Jetpack_Photon' ) ) { + // Tiled Galleries in Jetpack require that Photon be active. + // If it's not active, run it just on the gallery output. + if ( ! in_array( 'photon', Jetpack::get_active_modules() ) ) + $gallery_html = Jetpack_Photon::filter_the_content( $gallery_html ); + } + + return $gallery_html; + } + + return ''; + } + + public function rectangular_talavera( $attachments ) { + $grouper = new Jetpack_Tiled_Gallery_Grouper( $attachments ); + + Jetpack_Tiled_Gallery_Shape::reset_last_shape(); + + $output = $this->generate_carousel_container(); + foreach ( $grouper->grouped_images as $row ) { + $output .= '<div class="gallery-row" style="' . esc_attr( 'width: ' . $row->width . 'px; height: ' . ( $row->height - 4 ) . 'px;' ) . '">'; + foreach( $row->groups as $group ) { + $count = count( $group->images ); + $output .= '<div class="gallery-group images-' . esc_attr( $count ) . '" style="' . esc_attr( 'width: ' . $group->width . 'px; height: ' . $group->height . 'px;' ) . '">'; + foreach ( $group->images as $image ) { + + $size = 'large'; + if ( $image->width < 250 ) + $size = 'small'; + + $image_title = $image->post_title; + $orig_file = wp_get_attachment_url( $image->ID ); + $link = $this->get_attachment_link( $image->ID, $orig_file ); + + $img_src = add_query_arg( array( 'w' => $image->width, 'h' => $image->height ), $orig_file ); + + $output .= '<div class="tiled-gallery-item tiled-gallery-item-' . esc_attr( $size ) . '"><a href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' src="' . esc_url( $img_src ) . '" width="' . esc_attr( $image->width ) . '" height="' . esc_attr( $image->height ) . '" align="left" title="' . esc_attr( $image_title ) . '" /></a>'; + + if ( $this->atts['grayscale'] == true ) { + $img_src_grayscale = jetpack_photon_url( $img_src, array( 'filter' => 'grayscale' ) ); + $output .= '<a href="'. esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' class="grayscale" src="' . esc_url( $img_src_grayscale ) . '" width="' . esc_attr( $image->width ) . '" height="' . esc_attr( $image->height ) . '" align="left" title="' . esc_attr( $image_title ) . '" /></a>'; + } + + if ( trim( $image->post_excerpt ) ) + $output .= '<div class="tiled-gallery-caption">' . wptexturize( $image->post_excerpt ) . '</div>'; + + $output .= '</div>'; + } + $output .= '</div>'; + } + $output .= '</div>'; + } + $output .= '</div>'; + return $output; + } + + public function square_talavera( $attachments ) { + $content_width = self::get_content_width(); + $images_per_row = 3; + $margin = 2; + + $margin_space = ( $images_per_row * $margin ) * 2; + $size = floor( ( $content_width - $margin_space ) / $images_per_row ); + $remainder = count( $attachments ) % $images_per_row; + if ( $remainder > 0 ) { + $remainder_space = ( $remainder * $margin ) * 2; + $remainder_size = ceil( ( $content_width - $remainder_space - $margin ) / $remainder ); + } + $output = $this->generate_carousel_container(); + $c = 1; + foreach( $attachments as $image ) { + if ( $remainder > 0 && $c <= $remainder ) + $img_size = $remainder_size; + else + $img_size = $size; + + $orig_file = wp_get_attachment_url( $image->ID ); + $link = $this->get_attachment_link( $image->ID, $orig_file ); + $image_title = $image->post_title; + + $img_src = add_query_arg( array( 'w' => $img_size, 'h' => $img_size, 'crop' => 1 ), $orig_file ); + + $output .= '<div class="tiled-gallery-item">'; + $output .= '<a border="0" href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' style="' . esc_attr( 'margin: ' . $margin . 'px' ) . '" src="' . esc_url( $img_src ) . '" width=' . esc_attr( $img_size ) . ' height=' . esc_attr( $img_size ) . ' title="' . esc_attr( $image_title ) . '" /></a>'; + + // Grayscale effect + if ( $this->atts['grayscale'] == true ) { + $src = urlencode( $image->guid ); + $output .= '<a border="0" href="' . esc_url( $link ) . '"><img ' . $this->generate_carousel_image_args( $image ) . ' style="margin: 2px" class="grayscale" src="' . esc_url( 'http://en.wordpress.com/imgpress?url=' . urlencode( $image->guid ) . '&resize=' . $img_size . ',' . $img_size . '&filter=grayscale' ) . '" width=' . esc_attr( $img_size ) . ' height=' . esc_attr( $img_size ) . ' title="' . esc_attr( $image_title ) . '" /></a>'; + } + + // Captions + if ( trim( $image->post_excerpt ) ) + $output .= '<div class="tiled-gallery-caption">' . wptexturize( $image->post_excerpt ) . '</div>'; + $output .= '</div>'; + $c ++; + } + $output .= '</div>'; + return $output; + } + + public function circle_talavera( $attachments ) { + return $this->square_talavera( $attachments ); + } + + public function rectangle_talavera( $attachments ) { + return $this->rectangular_talavera( $attachments ); + } + + function generate_carousel_container() { + global $post; + + $html = '<div '. $this->gallery_classes() . ' data-original-width="' . esc_attr( self::get_content_width() ) . '">'; + $blog_id = (int) get_current_blog_id(); + $extra_data = array( 'data-carousel-extra' => array( 'blog_id' => $blog_id, 'permalink' => get_permalink( $post->ID ) ) ); + + foreach ( (array) $extra_data as $data_key => $data_values ) { + $html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' ", $html ); + } + + return $html; + } + + function generate_carousel_image_args( $image ) { + $attachment_id = $image->ID; + $orig_file = wp_get_attachment_url( $attachment_id ); + $meta = wp_get_attachment_metadata( $attachment_id ); + $size = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : ''; + $img_meta = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array(); + $comments_opened = intval( comments_open( $attachment_id ) ); + + $medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' ); + $medium_file = isset( $medium_file_info[0] ) ? $medium_file_info[0] : ''; + + $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' ); + $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : ''; + $attachment_title = wptexturize( $image->post_title ); + $attachment_desc = wpautop( wptexturize( $image->post_content ) ); + + // Not yet providing geo-data, need to "fuzzify" for privacy + if ( ! empty( $img_meta ) ) { + foreach ( $img_meta as $k => $v ) { + if ( 'latitude' == $k || 'longitude' == $k ) + unset( $img_meta[$k] ); + } + } + + $img_meta = json_encode( array_map( 'strval', $img_meta ) ); + + $output = sprintf( + 'data-attachment-id="%1$d" data-orig-file="%2$s" data-orig-size="%3$s" data-comments-opened="%4$s" data-image-meta="%5$s" data-image-title="%6$s" data-image-description="%7$s" data-medium-file="%8$s" data-large-file="%9$s"', + esc_attr( $attachment_id ), + esc_url( wp_get_attachment_url( $attachment_id ) ), + esc_attr( $size ), + esc_attr( $comments_opened ), + esc_attr( $img_meta ), + esc_attr( $attachment_title ), + esc_attr( $attachment_desc ), + esc_url( $medium_file ), + esc_url( $large_file ) + ); + return $output; + } + + public function gallery_classes() { + $classes = 'class="tiled-gallery type-' . esc_attr( $this->atts['type'] ) . '"'; + return $classes; + } + + public static function gallery_already_redefined() { + global $shortcode_tags; + if ( ! isset( $shortcode_tags[ 'gallery' ] ) || $shortcode_tags[ 'gallery' ] !== 'gallery_shortcode' ) + return true; + } + + public static function init() { + if ( self::gallery_already_redefined() ) + return; + + $gallery = new Jetpack_Tiled_Gallery; + add_filter( 'post_gallery', array( $gallery, 'gallery_shortcode' ), 1001, 2 ); + } + + public static function get_content_width() { + global $content_width; + + $tiled_gallery_content_width = $content_width; + + if ( ! $tiled_gallery_content_width ) + $tiled_gallery_content_width = 500; + + return apply_filters( 'tiled_gallery_content_width', $tiled_gallery_content_width ); + } + + /** + * Media UI integration + */ + function jetpack_gallery_types( $types ) { + $types['rectangular'] = __( 'Tiles', 'jetpack' ); + $types['square'] = __( 'Square Tiles', 'jetpack' ); + $types['circle'] = __( 'Circles', 'jetpack' ); + return $types; + } + + /** + * Add a checkbox field to the Carousel section in Settings > Media + * for setting tiled galleries as the default. + */ + function settings_api_init() { + global $wp_settings_sections; + + // Add the setting field [tiled_galleries] and place it in Settings > Media + if ( isset( $wp_settings_sections['media']['carousel_section'] ) ) + $section = 'carousel_section'; + else + $section = 'default'; + + add_settings_field( 'tiled_galleries', __( 'Tiled Galleries', 'jetpack' ), array( $this, 'setting_html' ), 'media', $section ); + register_setting( 'media', 'tiled_galleries', 'esc_attr' ); + } + + function setting_html() { + echo '<label><input name="tiled_galleries" type="checkbox" value="1" ' . + checked( 1, '' != get_option( 'tiled_galleries' ), false ) . ' /> ' . + __( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ) . '</br></label>'; + } +} + +class Jetpack_Tiled_Gallery_Shape { + static $shapes_used = array(); + + public function __construct( $images ) { + $this->images = $images; + $this->images_left = count( $images ); + } + + public function sum_ratios( $number_of_images = 3 ) { + return array_sum( array_slice( wp_list_pluck( $this->images, 'ratio' ), 0, $number_of_images ) ); + } + + public function next_images_are_symmetric() { + return $this->images_left > 2 && $this->images[0]->ratio == $this->images[2]->ratio; + } + + public function is_not_as_previous( $n = 1 ) { + return ! in_array( get_class( $this ), array_slice( self::$shapes_used, -$n ) ); + } + + public function is_wide_theme() { + global $content_width; + return $content_width > 1000; + } + + public static function set_last_shape( $last_shape ) { + self::$shapes_used[] = $last_shape; + } + + public static function reset_last_shape() { + self::$shapes_used = array(); + } +} + +class Jetpack_Tiled_Gallery_Three extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 1, 1 ); + + public function is_possible() { + $ratio = $this->sum_ratios( 3 ); + return $this->images_left > 2 && $this->is_not_as_previous() && + ( ( $ratio < 2.5 ) || ( $ratio < 5 && $this->next_images_are_symmetric() ) || $this->is_wide_theme() ); + } +} + +class Jetpack_Tiled_Gallery_Four extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 1, 1, 1 ); + + public function is_possible() { + return $this->is_not_as_previous() && $this->sum_ratios( 4 ) < 3.5 && + ( $this->images_left == 4 || ( $this->images_left != 8 && $this->images_left > 5 ) ); + } +} + +class Jetpack_Tiled_Gallery_Five extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 1, 1, 1, 1 ); + + public function is_possible() { + return $this->is_wide_theme() && $this->is_not_as_previous() && $this->sum_ratios( 5 ) < 5 && + ( $this->images_left == 5 || ( $this->images_left != 10 && $this->images_left > 6 ) ); + } +} + +class Jetpack_Tiled_Gallery_Two_One extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 2, 1 ); + + public function is_possible() { + return $this->is_not_as_previous( 3 ) && $this->images_left >= 2 && + $this->images[2]->ratio < 1.6 && $this->images[0]->ratio >=0.9 && $this->images[1]->ratio >= 0.9; + } +} + +class Jetpack_Tiled_Gallery_One_Two extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 2 ); + + public function is_possible() { + return $this->is_not_as_previous( 3 ) && $this->images_left >= 2 && + $this->images[0]->ratio < 1.6 && $this->images[1]->ratio >=0.9 && $this->images[2]->ratio >= 0.9; + } +} + +class Jetpack_Tiled_Gallery_One_Three extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 3 ); + + public function is_possible() { + return $this->is_not_as_previous() && $this->images_left >= 3 && + $this->images[0]->ratio < 0.8 && $this->images[1]->ratio >=0.9 && $this->images[2]->ratio >= 0.9 && $this->images[3]->ratio >= 0.9; + } +} + +class Jetpack_Tiled_Gallery_Symmetric_Row extends Jetpack_Tiled_Gallery_Shape { + public $shape = array( 1, 2, 1 ); + + public function is_possible() { + return $this->is_not_as_previous() && $this->images_left >= 3 && $this->images_left != 5 && + $this->images[0]->ratio < 0.8 && $this->images[0]->ratio == $this->images[3]->ratio; + } +} + +class Jetpack_Tiled_Gallery_Grouper { + public $margin = 4; + public function __construct( $attachments ) { + $content_width = Jetpack_Tiled_Gallery::get_content_width(); + $ua_info = new Jetpack_User_Agent_Info(); + + $this->last_shape = ''; + $this->images = $this->get_images_with_sizes( $attachments ); + $this->grouped_images = $this->get_grouped_images(); + $this->apply_content_width( $content_width - 5 ); //reduce the margin hack to 5px. It will be further reduced when we fix more themes and the rounding error. + } + + public function get_current_row_size() { + $images_left = count( $this->images ); + if ( $images_left < 3 ) + return array_fill( 0, $images_left, 1 ); + + foreach ( array( 'One_Three', 'One_Two', 'Five', 'Four', 'Three', 'Two_One', 'Symmetric_Row' ) as $shape_name ) { + $class_name = "Jetpack_Tiled_Gallery_$shape_name"; + $shape = new $class_name( $this->images ); + if ( $shape->is_possible() ) { + Jetpack_Tiled_Gallery_Shape::set_last_shape( $class_name ); + return $shape->shape; + } + } + + Jetpack_Tiled_Gallery_Shape::set_last_shape( 'Two' ); + return array( 1, 1 ); + } + + public function get_images_with_sizes( $attachments ) { + $images_with_sizes = array(); + + foreach ( $attachments as $image ) { + $meta = wp_get_attachment_metadata( $image->ID ); + $image->width_orig = ( $meta['width'] > 0 )? $meta['width'] : 1; + $image->height_orig = ( $meta['height'] > 0 )? $meta['height'] : 1; + $image->ratio = $image->width_orig / $image->height_orig; + $image->ratio = $image->ratio? $image->ratio : 1; + $images_with_sizes[] = $image; + } + + return $images_with_sizes; + } + + public function read_row() { + $vector = $this->get_current_row_size(); + + $row = array(); + foreach ( $vector as $group_size ) { + $row[] = new Jetpack_Tiled_Gallery_Group( array_splice( $this->images, 0, $group_size ) ); + } + + return $row; + } + + public function get_grouped_images() { + $grouped_images = array(); + + while( !empty( $this->images ) ) { + $grouped_images[] = new Jetpack_Tiled_Gallery_Row( $this->read_row() ); + } + + return $grouped_images; + } + + // todo: split in functions + // todo: do not stretch images + public function apply_content_width( $width ) { + foreach ( $this->grouped_images as $row ) { + $row->width = $width; + $row->raw_height = 1 / $row->ratio * ( $width - $this->margin * ( count( $row->groups ) - $row->weighted_ratio ) ); + $row->height = round( $row->raw_height ); + + $this->calculate_group_sizes( $row ); + } + } + + public function calculate_group_sizes( $row ) { + // Storing the calculated group heights in an array for rounding them later while preserving their sum + // This fixes the rounding error that can lead to a few ugly pixels sticking out in the gallery + $group_widths_array = array(); + foreach ( $row->groups as $group ) { + $group->height = $row->height; + // Storing the raw calculations in a separate property to prevent rounding errors from cascading down and for diagnostics + $group->raw_width = ( $row->raw_height - $this->margin * count( $group->images ) ) * $group->ratio + $this->margin; + $group_widths_array[] = $group->raw_width; + } + $rounded_group_widths_array = Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $group_widths_array, $row->width ); + + foreach ( $row->groups as $group ) { + $group->width = array_shift( $rounded_group_widths_array ); + $this->calculate_image_sizes( $group ); + } + } + + public function calculate_image_sizes( $group ) { + // Storing the calculated image heights in an array for rounding them later while preserving their sum + // This fixes the rounding error that can lead to a few ugly pixels sticking out in the gallery + $image_heights_array = array(); + foreach ( $group->images as $image ) { + $image->width = $group->width - $this->margin; + // Storing the raw calculations in a separate property for diagnostics + $image->raw_height = ( $group->raw_width - $this->margin ) / $image->ratio; + $image_heights_array[] = $image->raw_height; + } + + $image_height_sum = $group->height - count( $image_heights_array ) * $this->margin; + $rounded_image_heights_array = Jetpack_Constrained_Array_Rounding::get_rounded_constrained_array( $image_heights_array, $image_height_sum ); + + foreach ( $group->images as $image ) { + $image->height = array_shift( $rounded_image_heights_array ); + } + } +} + +class Jetpack_Tiled_Gallery_Row { + public function __construct( $groups ) { + $this->groups = $groups; + $this->ratio = $this->get_ratio(); + $this->weighted_ratio = $this->get_weighted_ratio(); + } + + public function get_ratio() { + $ratio = 0; + foreach ( $this->groups as $group ) { + $ratio += $group->ratio; + } + return $ratio > 0? $ratio : 1; + } + + public function get_weighted_ratio() { + $weighted_ratio = 0; + foreach ( $this->groups as $group ) { + $weighted_ratio += $group->ratio * count( $group->images ); + } + return $weighted_ratio > 0 ? $weighted_ratio : 1; + } +} + +class Jetpack_Tiled_Gallery_Group { + public function __construct( $images ) { + $this->images = $images; + $this->ratio = $this->get_ratio(); + } + + public function get_ratio() { + $ratio = 0; + foreach ( $this->images as $image ) { + if ( $image->ratio ) + $ratio += 1/$image->ratio; + } + if ( !$ratio ) + return 1; + + return 1/$ratio; + } +} + +add_action( 'init', array( 'Jetpack_Tiled_Gallery', 'init' ) ); + diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css new file mode 100644 index 00000000..007c0ccf --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css @@ -0,0 +1,88 @@ +/* This file was automatically generated on Jan 05 2013 15:45:53 */ + +/* =Tiled Gallery Default Styles +-------------------------------------------------------------- */ + +.tiled-gallery { + clear: both; + margin: 0; + overflow: hidden; +} +.tiled-gallery img { + margin: 2px !important; /* Ensure that this value isn't overridden by themes that give content images blanket margins */ +} +.tiled-gallery .gallery-group { + float: right; + overflow-y: hidden; + position: relative; +} +.tiled-gallery .tiled-gallery-item { + float: right; + margin: 0; + position: relative; +} +.tiled-gallery .gallery-row { + overflow: hidden; + margin-bottom: 2px; +} +.tiled-gallery .tiled-gallery-item a { /* Needs to reset some properties for theme compatibility */ + background: transparent; + border: none; + color: none; + margin: 0; + padding: 0; + text-decoration: none; + width: auto; +} +.tiled-gallery .tiled-gallery-item img, +.tiled-gallery .tiled-gallery-item img:hover { /* Needs to reset some properties for theme compatibility */ + background: none; + border: none; + box-shadow: none; + max-width: 100%; + padding: 0; + vertical-align: middle; +} +.tiled-gallery-caption { /* Captions */ + background: #eee; + background: rgba( 255,255,255,0.8 ); + color: #333; + font-size: 13px; + font-weight: 400; + overflow: hidden; + padding: 10px 0; + position: absolute; + bottom: 0; + text-indent: 10px; + text-overflow: ellipsis; + width: 100%; + white-space: nowrap; +} +.tiled-gallery .tiled-gallery-item-small .tiled-gallery-caption { /* Smaller captions */ + font-size: 11px; +} + + +/* =Greyscale +-------------------------------------------------------------- */ + +.tiled-gallery .tiled-gallery-item img.grayscale { + position: absolute; + right: 0; + top: 0; +} +.tiled-gallery .tiled-gallery-item img.grayscale:hover { + opacity: 0; +} + + +/* =Circles Layout +-------------------------------------------------------------- */ + +.tiled-gallery.type-circle .tiled-gallery-item img { + border-radius: 50% !important; /* Ensure that circles are displayed in themes that add border-radius to all images as a default */ +} +.tiled-gallery.type-circle .tiled-gallery-caption { + display: none; + opacity: 0; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css new file mode 100644 index 00000000..3a1924df --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css @@ -0,0 +1,85 @@ +/* =Tiled Gallery Default Styles +-------------------------------------------------------------- */ + +.tiled-gallery { + clear: both; + margin: 0; + overflow: hidden; +} +.tiled-gallery img { + margin: 2px !important; /* Ensure that this value isn't overridden by themes that give content images blanket margins */ +} +.tiled-gallery .gallery-group { + float: left; + position: relative; +} +.tiled-gallery .tiled-gallery-item { + float: left; + margin: 0; + position: relative; +} +.tiled-gallery .gallery-row { + overflow: hidden; + margin-bottom: 2px; +} +.tiled-gallery .tiled-gallery-item a { /* Needs to reset some properties for theme compatibility */ + background: transparent; + border: none; + color: none; + margin: 0; + padding: 0; + text-decoration: none; + width: auto; +} +.tiled-gallery .tiled-gallery-item img, +.tiled-gallery .tiled-gallery-item img:hover { /* Needs to reset some properties for theme compatibility */ + background: none; + border: none; + box-shadow: none; + max-width: 100%; + padding: 0; + vertical-align: middle; +} +.tiled-gallery-caption { /* Captions */ + background: #eee; + background: rgba( 255,255,255,0.8 ); + color: #333; + font-size: 13px; + font-weight: 400; + overflow: hidden; + padding: 10px 0; + position: absolute; + bottom: 0; + text-indent: 10px; + text-overflow: ellipsis; + width: 100%; + white-space: nowrap; +} +.tiled-gallery .tiled-gallery-item-small .tiled-gallery-caption { /* Smaller captions */ + font-size: 11px; +} + + +/* =Greyscale +-------------------------------------------------------------- */ + +.tiled-gallery .tiled-gallery-item img.grayscale { + position: absolute; + left: 0; + top: 0; +} +.tiled-gallery .tiled-gallery-item img.grayscale:hover { + opacity: 0; +} + + +/* =Circles Layout +-------------------------------------------------------------- */ + +.tiled-gallery.type-circle .tiled-gallery-item img { + border-radius: 50% !important; /* Ensure that circles are displayed in themes that add border-radius to all images as a default */ +} +.tiled-gallery.type-circle .tiled-gallery-caption { + display: none; + opacity: 0; +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.js b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.js new file mode 100644 index 00000000..90f8ed76 --- /dev/null +++ b/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.js @@ -0,0 +1,150 @@ +( function($) { + +var TiledGallery = function() { + this.resizeTimeout = null; + + this.populate(); + + var self = this; + + $( window ).on( 'resize', function () { + clearTimeout( self.resizeTimeout ); + + self.resizeTimeout = setTimeout( function () { self.resize(); }, 150 ); + } ); + + // Make any new galleries loaded by Infinite Scroll flexible + $( 'body' ).on( 'post-load', $.proxy( self.initialize, self ) ); + + // Populate and set up captions on newdash galleries. + $( document ).on( 'page-rendered.wpcom-newdash', $.proxy( self.populate, self ) ); + + this.resize(); +}; + +TiledGallery.prototype.populate = function() { + this.gallery = $( '.tiled-gallery' ); + this.item = this.gallery.find( '.tiled-gallery-item' ); + this.caption = this.gallery.find( '.tiled-gallery-caption' ); + + this.Captions(); +}; + +TiledGallery.prototype.initialize = function() { + var self = this; + + self.populate(); + + // After each image load, run resize in case all images in the gallery are loaded. + self.gallery.find( 'img' ).off( 'load.tiled-gallery' ).on( 'load.tiled-gallery', function () { + self.resize(); + } ); + + // Run resize now in case all images loaded from cache. + self.resize(); +}; + +/** + * Story + */ +TiledGallery.prototype.Captions = function() { + /* Hide captions */ + this.caption.hide(); + + this.item.on( 'hover', function() { + $( this ).find( '.tiled-gallery-caption' ).slideToggle( 'fast' ); + }); +}; + +TiledGallery.prototype.resize = function() { + var resizeableElements = '.gallery-row, .gallery-group, .tiled-gallery-item img'; + + this.gallery.each( function ( galleryIndex, galleryElement ) { + var thisGallery = $( galleryElement ); + + // All images must be loaded before proceeding. + var imagesLoaded = true; + + thisGallery.find( 'img' ).each( function () { + if ( ! this.complete ) { + imagesLoaded = false; + return false; + } + } ); + + if ( ! imagesLoaded ) { + var loadCallback = arguments.callee; + + // Once all of the images have loaded, + // re-call this containing function. + $( window ).load( function () { + loadCallback( null, thisGallery ); + } ); + + return; + } + + if ( ! thisGallery.data( 'sizes-set' ) ) { + // Maintain a record of the original widths and heights of these elements + // for proper scaling. + thisGallery.data( 'sizes-set', true ); + + thisGallery.find( resizeableElements ).each( function () { + var thisGalleryElement = $( this ); + + // Don't change margins, but remember what they were so they can be + // accounted for in size calculations. When the screen width gets + // small enough, ignoring the margins can cause images to overflow + // into new rows. + var extraWidth = ( parseInt( thisGalleryElement.css( 'marginLeft' ), 10 ) || 0 ) + ( parseInt( thisGalleryElement.css( 'marginRight' ), 10 ) || 0 ); + var extraHeight = ( parseInt( thisGalleryElement.css( 'marginTop' ), 10 ) || 0 ) + ( parseInt( thisGalleryElement.css( 'marginBottom' ), 10 ) || 0 ) + + // In some situations, tiled galleries in Firefox have shown scrollbars on the images because + // the .outerWidth() call on the image returns a value larger than the container. Restrict + // widths used in the resizing functions to the maximum width of the container. + var parentElement = $( thisGalleryElement.parents( resizeableElements ).get( 0 ) ); + + if ( parentElement && parentElement.data( 'original-width' ) ) { + thisGalleryElement + .data( 'original-width', Math.min( parentElement.data( 'original-width' ), thisGalleryElement.outerWidth( true ) ) ) + .data( 'original-height', Math.min( parentElement.data( 'original-height' ), thisGalleryElement.outerHeight( true ) ) ); + } + else { + thisGalleryElement + .data( 'original-width', thisGalleryElement.outerWidth( true ) ) + .data( 'original-height', thisGalleryElement.outerHeight( true ) ); + } + + thisGalleryElement + .data( 'extra-width', extraWidth ) + .data( 'extra-height', extraHeight ); + } ); + } + + // Resize everything in the gallery based on the ratio of the current content width + // to the original content width; + var originalWidth = thisGallery.data( 'original-width' ); + var currentWidth = thisGallery.parent().width(); + var resizeRatio = Math.min( 1, currentWidth / originalWidth ); + + thisGallery.find( resizeableElements ).each( function () { + var thisGalleryElement = $( this ); + + thisGalleryElement + .width( Math.floor( resizeRatio * thisGalleryElement.data( 'original-width' ) ) - thisGalleryElement.data( 'extra-width' ) ) + .height( Math.floor( resizeRatio * thisGalleryElement.data( 'original-height' ) ) - thisGalleryElement.data( 'extra-height' ) ); + } ); + } ); +}; + +/** + * Ready, set... + */ +$( document ).ready( function() { + + // Instance! + var TiledGalleryInstance = new TiledGallery; + +}); + +})(jQuery);
\ No newline at end of file diff --git a/plugins/jetpack/modules/widgets.php b/plugins/jetpack/modules/widgets.php index 3d9c2f8b..5771cdbe 100644 --- a/plugins/jetpack/modules/widgets.php +++ b/plugins/jetpack/modules/widgets.php @@ -24,11 +24,22 @@ function jetpack_widgets_configuration_load() { exit; } -function jetpack_register_widgets() { - register_widget( 'WPCOM_Widget_Facebook_LikeBox' ); - register_widget( 'Jetpack_Gravatar_Profile_Widget' ); +/** + * Loads file for front-end widget styles. + */ +function jetpack_widgets_styles() { + wp_enqueue_style( 'jetpack-widgets', plugins_url( 'widgets/widgets.css', __FILE__ ), array(), '20121003' ); } +add_action( 'wp_enqueue_scripts', 'jetpack_widgets_styles' ); + +/** + * Add the "(Jetpack)" suffix to the widget names + */ +function jetpack_widgets_add_suffix( $widget_name ) { + return sprintf( __( '%s (Jetpack)', 'jetpack' ), $widget_name ); +} +add_filter( 'jetpack_widget_name', 'jetpack_widgets_add_suffix' ); -jetpack_load_widgets(); -add_action( 'widgets_init', 'jetpack_register_widgets' ); + +jetpack_load_widgets(); diff --git a/plugins/jetpack/modules/widgets/facebook-likebox.php b/plugins/jetpack/modules/widgets/facebook-likebox.php index 64d03f8a..f8ab2512 100644 --- a/plugins/jetpack/modules/widgets/facebook-likebox.php +++ b/plugins/jetpack/modules/widgets/facebook-likebox.php @@ -1,6 +1,15 @@ <?php /** + * Register the widget for use in Appearance -> Widgets + */ +add_action( 'widgets_init', 'jetpack_facebook_likebox_init' ); + +function jetpack_facebook_likebox_init() { + register_widget( 'WPCOM_Widget_Facebook_LikeBox' ); +} + +/** * Facebook Like Box widget class * Display a Facebook Like Box as a widget * http://developers.facebook.com/docs/reference/plugins/like-box/ @@ -17,7 +26,14 @@ class WPCOM_Widget_Facebook_LikeBox extends WP_Widget { private $allowed_colorschemes = array( 'light', 'dark' ); function __construct() { - parent::__construct( 'facebook-likebox', __( 'Facebook Like Box', 'jetpack' ), array( 'classname' => 'widget_facebook_likebox', 'description' => __( 'Display a Facebook Like Box to connect visitors to your Facebook Page', 'jetpack' ) ) ); + parent::__construct( + 'facebook-likebox', + apply_filters( 'jetpack_widget_name', __( 'Facebook Like Box', 'jetpack' ) ), + array( + 'classname' => 'widget_facebook_likebox', + 'description' => __( 'Display a Facebook Like Box to connect visitors to your Facebook Page', 'jetpack' ) + ) + ); } function widget( $args, $instance ) { @@ -29,7 +45,7 @@ class WPCOM_Widget_Facebook_LikeBox extends WP_Widget { if ( empty( $like_args['href'] ) || ! $this->is_valid_facebook_url( $like_args['href'] ) ) { if ( current_user_can('edit_theme_options') ) { echo $before_widget; - echo '<p>' . sprintf( __( 'It looks like your Facebook URL is incorrectly configured. Please check it in your <a href="%s">widget settings</a>.' ), admin_url( 'widgets.php' ) ) . '</p>'; + echo '<p>' . sprintf( __( 'It looks like your Facebook URL is incorrectly configured. Please check it in your <a href="%s">widget settings</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>'; echo $after_widget; } echo '<!-- Invalid Facebook Page URL -->'; @@ -244,26 +260,33 @@ class WPCOM_Widget_Facebook_LikeBox extends WP_Widget { } function guess_locale_from_lang( $lang ) { - $lang = strtolower( str_replace( '-', '_', $lang ) ); + if ( 'en' == $lang || 'en_US' == $lang || !$lang ) { + return 'en_US'; + } + + if ( !class_exists( 'GP_Locales' ) ) { + if ( !defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || !file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) { + return false; + } + + require JETPACK__GLOTPRESS_LOCALES_PATH; + } - if ( 5 == strlen( $lang ) ) { - $lang = substr( $lang, 0, 3 ) . strtoupper( substr( $lang, 3, 2 ) ); - } else if ( 3 == strlen( $lang ) ) { - $lang = $lang; + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + // WP.com: get_locale() returns 'it' + $locale = GP_Locales::by_slug( $lang ); } else { - $lang = $lang . '_' . strtoupper( $lang ); + // Jetpack: get_locale() returns 'it_IT'; + $locale = GP_Locales::by_field( 'wp_locale', $lang ); } - - if ( 'en_EN' == $lang ) { - $lang = 'en_US'; - } else if ( 'he_HE' == $lang ) { - $lang = 'he_IL'; - } else if ( 'ja_JA' == $lang ) - $lang = 'ja_JP'; - return $lang; + if ( !$locale || empty( $locale->facebook_locale ) ) { + return false; + } + + return $locale->facebook_locale; } - + function get_locale() { return $this->guess_locale_from_lang( get_locale() ); } diff --git a/plugins/jetpack/modules/widgets/gravatar-profile.css b/plugins/jetpack/modules/widgets/gravatar-profile.css index 230a3b16..1663ae07 100644 --- a/plugins/jetpack/modules/widgets/gravatar-profile.css +++ b/plugins/jetpack/modules/widgets/gravatar-profile.css @@ -1,8 +1,7 @@ .widget-grofile { } .widget-grofile h4 { - margin: 1em 0 1.2em; - /*font-size: 1.2em;*/ + margin: 1em 0 .5em; } .widget-grofile ul.grofile-urls { margin-left: 0; @@ -17,7 +16,7 @@ content: "" !important; /* Kubrick :( */ } .widget-grofile .grofile-accounts-logo { - background-image: url('http://0.gravatar.com/images/grav-share-sprite.png'); + background-image: url('//0.gravatar.com/images/grav-share-sprite.png'); background-repeat: no-repeat; /*background-position: -16px -16px;*/ width: 16px; /* So we don't show the topmost logo */ @@ -34,3 +33,13 @@ .grofile-thumbnail { width: 100%; } + @media +only screen and (-webkit-min-device-pixel-ratio: 1.5), +only screen and (-o-min-device-pixel-ratio: 3/2), +only screen and (min--moz-device-pixel-ratio: 1.5), +only screen and (min-device-pixel-ratio: 1.5) { + .widget-grofile .grofile-accounts-logo { + background-image: url('//0.gravatar.com/images/grav-share-sprite-2x.png'); + background-size: 16px 784px; + } +}
\ No newline at end of file diff --git a/plugins/jetpack/modules/widgets/gravatar-profile.php b/plugins/jetpack/modules/widgets/gravatar-profile.php index c33e6996..190427fe 100644 --- a/plugins/jetpack/modules/widgets/gravatar-profile.php +++ b/plugins/jetpack/modules/widgets/gravatar-profile.php @@ -1,15 +1,29 @@ <?php + +/** + * Register the widget for use in Appearance -> Widgets + */ +add_action( 'widgets_init', 'jetpack_gravatar_profile_widget_init' ); + +function jetpack_gravatar_profile_widget_init() { + register_widget( 'Jetpack_Gravatar_Profile_Widget' ); +} + /** * Display a widgetized version of your Gravatar Profile * http://blog.gravatar.com/2010/03/26/gravatar-profiles/ */ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { - + function __construct() { - parent::__construct( 'grofile', __( 'Gravatar Profile', 'jetpack' ), array( - 'classname' => 'widget-grofile grofile', - 'description' => __( 'Display a mini version of your Gravatar Profile', 'jetpack' ) - ) ); + parent::__construct( + 'grofile', + apply_filters( 'jetpack_widget_name', __( 'Gravatar Profile', 'jetpack' ) ), + array( + 'classname' => 'widget-grofile grofile', + 'description' => __( 'Display a mini version of your Gravatar Profile', 'jetpack' ) + ) + ); if ( is_admin() ) { add_action( 'admin_footer-widgets.php', array( $this, 'admin_script' ) ); @@ -24,7 +38,7 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { echo $args['before_widget']; if ( ! empty( $title ) ) echo $args['before_title'] . $title . $args['after_title']; - echo '<p>' . sprintf( __( 'You need to select what to show in this <a href="%s">Gravatar Profile widget</a>.' ), admin_url( 'widgets.php' ) ) . '</p>'; + echo '<p>' . sprintf( __( 'You need to select what to show in this <a href="%s">Gravatar Profile widget</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>'; echo $args['after_widget']; } return; @@ -35,24 +49,39 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { echo $args['before_title'] . $title . $args['after_title']; $profile = $this->get_profile( $instance['email'] ); - + if( ! empty( $profile ) ) { - $gravatar_url = add_query_arg( 's', 500, $profile['thumbnailUrl'] ); // the default grav returned by grofiles is super small - + $profile = wp_parse_args( $profile, array( + 'thumbnailUrl' => '', + 'profileUrl' => '', + 'displayName' => '', + 'aboutMe' => '', + 'urls' => array(), + 'accounts' => array(), + ) ); + $gravatar_url = add_query_arg( 's', 200, $profile['thumbnailUrl'] ); // the default grav returned by grofiles is super small + wp_enqueue_style( 'gravatar-profile-widget', plugins_url( 'gravatar-profile.css', __FILE__ ), array(), '20120711' ); - + + wp_enqueue_style( + 'gravatar-card-services', + is_ssl() ? 'https://secure.gravatar.com/css/services.css' : 'http://s.gravatar.com/css/services.css', + array(), + defined( 'GROFILES__CACHE_BUSTER' ) ? GROFILES__CACHE_BUSTER : gmdate( 'YW' ) + ); + ?> - <img src="<?php echo esc_url( $gravatar_url ); ?>" class="grofile-thumbnail no-grav" /> + <img src="<?php echo esc_url( $gravatar_url ); ?>" class="grofile-thumbnail no-grav" style="width: auto; max-width: 200px;" /> <div class="grofile-meta"> <h4><a href="<?php echo esc_url( $profile['profileUrl'] ); ?>"><?php echo esc_html( $profile['displayName'] ); ?></a></h4> - <p><?php echo esc_html( wp_kses( $profile['aboutMe'], array() ) ); ?></p> + <p><?php echo wp_kses_data( $profile['aboutMe'] ); ?></p> </div> - + <?php if( $instance['show_personal_links'] ) @@ -60,15 +89,15 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { if( $instance['show_account_links'] ) $this->display_accounts( (array) $profile['accounts'] ); - + ?> - - <h4><a href="<?php echo esc_url( $profile['profileUrl'] ); ?>" class="grofile-full-link"><?php esc_html_e( 'View Full Profile →' ); ?></a></h4> - + + <h4><a href="<?php echo esc_url( $profile['profileUrl'] ); ?>" class="grofile-full-link"><?php esc_html_e( 'View Full Profile →', 'jetpack' ); ?></a></h4> + <?php - do_action( 'jetpack_stats_extra', 'widgets', 'grofile' ); - + do_action( 'jetpack_stats_extra', 'widget', 'grofile' ); + } else { if ( current_user_can( 'edit_theme_options' ) ) { echo '<p>' . esc_html__( 'Error loading profile', 'jetpack' ) . '</p>'; @@ -85,7 +114,7 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { <h4><?php esc_html_e( 'Personal Links', 'jetpack' ); ?></h4> <ul class="grofile-urls grofile-links"> - + <?php foreach( $personal_links as $personal_link ) : ?> <li> <a href="<?php echo esc_url( $personal_link['value'] ); ?>"> @@ -98,30 +127,30 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { <?php } - function display_accounts( $accounts = array() ) { + function display_accounts( $accounts = array() ) { if ( empty( $accounts ) ) return; ?> - + <h4><?php esc_html_e( 'Verified Services', 'jetpack' ); ?></h4> <ul class="grofile-urls grofile-accounts"> - + <?php foreach( $accounts as $account ) : if( $account['verified'] != 'true' ) continue; - + $sanitized_service_name = $this->get_sanitized_service_name( $account['shortname'] ); ?> - + <li> <a href="<?php echo esc_url( $account['url'] ); ?>" title="<?php echo sprintf( _x( '%1$s on %2$s', '1: User Name, 2: Service Name (Facebook, Twitter, ...)', 'jetpack' ), esc_html( $account['display'] ), esc_html( $sanitized_service_name ) ); ?>"> <span class="grofile-accounts-logo grofile-accounts-<?php echo esc_attr( $account['shortname'] ); ?> accounts_<?php echo esc_attr( $account['shortname'] ); ?>"></span> </a> </li> - + <?php endforeach; ?> </ul> - + <?php } @@ -133,6 +162,12 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { $show_personal_links = isset( $instance['show_personal_links'] ) ? (bool) $instance['show_personal_links'] : ''; $show_account_links = isset( $instance['show_account_links'] ) ? (bool) $instance['show_account_links'] : ''; + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $profile_url = admin_url( 'profile.php' ); + } else { + $profile_url = 'https://gravatar.com/profile/edit'; + } + ?> <p> <label for="<?php echo $this->get_field_id( 'title' ); ?>"> @@ -144,7 +179,7 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { <label for="<?php echo $this->get_field_id( 'email_user' ); ?>"> <?php esc_html_e( 'Select a user or pick "custom" and enter a custom email address.', 'jetpack' ); ?> <br /> - + <?php wp_dropdown_users( array( 'show_option_none' => __( 'Custom', 'jetpack' ), 'selected' => $email_user, @@ -154,13 +189,13 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { ) );?> </label> </p> - + <p class="gprofile-email-container <?php echo empty( $email_user ) || $email_user == -1 ? '' : 'hidden'; ?>"> <label for="<?php echo $this->get_field_id( 'email' ); ?>"><?php esc_html_e( 'Custom Email Address', 'jetpack' ); ?> <input class="widefat" id="<?php echo $this->get_field_id('email'); ?>" name="<?php echo $this->get_field_name( 'email' ); ?>" type="text" value="<?php echo esc_attr( $email ); ?>" /> </label> </p> - + <p> <label for="<?php echo $this->get_field_id( 'show_personal_links' ); ?>"> <input type="checkbox" name="<?php echo $this->get_field_name( 'show_personal_links' ); ?>" id="<?php echo $this->get_field_id( 'show_personal_links' ); ?>" <?php checked( $show_personal_links ); ?> /> @@ -178,8 +213,8 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { <small><?php esc_html_e( 'Links to services that you use across the web.', 'jetpack' ); ?></small> </label> </p> - - <p><a href="<?php echo admin_url( 'profile.php' ); ?>" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( 'Edit Your Profile', 'jetpack' )?></a> | <a href="http://gravatar.com" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( "What's a Gravatar?", 'jetpack' ); ?></a></p> + + <p><a href="<?php echo esc_url( $profile_url ); ?>" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( 'Edit Your Profile', 'jetpack' )?></a> | <a href="http://gravatar.com" target="_blank" title="<?php esc_attr_e( 'Opens in new window', 'jetpack' ); ?>"><?php esc_html_e( "What's a Gravatar?", 'jetpack' ); ?></a></p> <?php } @@ -202,7 +237,7 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { } function update( $new_instance, $old_instance ) { - + $instance = array(); $instance['title'] = isset( $new_instance['title'] ) ? wp_kses( $new_instance['title'], array() ) : ''; @@ -216,42 +251,45 @@ class Jetpack_Gravatar_Profile_Widget extends WP_Widget { $instance['email'] = $user->user_email; } + $hashed_email = md5( strtolower( trim( $instance['email'] ) ) ); + $cache_key = 'grofile-' . $hashed_email; + delete_transient( $cache_key ); + return $instance; } - + private function get_profile( $email ) { $hashed_email = md5( strtolower( trim( $email ) ) ); - $cache_key = 'widget-grofile-' . $hashed_email; - - if( ! $profile = get_transient( $cache_key, 'widget' ) ) { - + $cache_key = 'grofile-' . $hashed_email; + + if( ! $profile = get_transient( $cache_key ) ) { $profile_url = esc_url_raw( sprintf( '%s.gravatar.com/%s.php', ( is_ssl() ? 'https://secure' : 'http://www' ), $hashed_email ), array( 'http', 'https' ) ); - + $expire = 300; $response = wp_remote_get( $profile_url, array( 'User-Agent' => 'WordPress.com Gravatar Profile Widget' ) ); $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 == $response_code ) { $profile = wp_remote_retrieve_body( $response ); $profile = unserialize( $profile ); - + if ( is_array( $profile ) && ! empty( $profile['entry'] ) && is_array( $profile['entry'] ) ) { $expire = 900; // cache for 15 minutes $profile = $profile['entry'][0]; } else { + // Something strange happend. Cache for 5 minutes. $profile = array(); } - + } else { - $expire = + $expire = 900; // cache for 15 minutes $profile = array(); - set_transient( $cache_key . '-response-code', $response_code, $expire ); } - + set_transient( $cache_key, $profile, $expire ); } return $profile; } - + private function get_sanitized_service_name( $shortname ) { // Some services have stylized or mixed cap names *cough* WP *cough* switch( $shortname ) { diff --git a/plugins/jetpack/modules/widgets/image-widget.php b/plugins/jetpack/modules/widgets/image-widget.php index 0e7e227b..03d4d5c7 100644 --- a/plugins/jetpack/modules/widgets/image-widget.php +++ b/plugins/jetpack/modules/widgets/image-widget.php @@ -43,7 +43,7 @@ class Jetpack_Image_Widget extends WP_Widget { if ( '' != $instance['caption'] ) $output = '[caption align="align' . esc_attr( $instance['align'] ) . '" width="' . esc_attr( $instance['img_width'] ) .'" caption="' . esc_attr( $instance['caption'] ) . '"]' . $output . '[/caption]'; - echo '<div style="overflow:hidden;">' . do_shortcode( $output ) . '</div>'; + echo '<div class="jetpack-image-container">' . do_shortcode( $output ) . '</div>'; } echo "\n" . $after_widget; diff --git a/plugins/jetpack/modules/widgets/readmill.php b/plugins/jetpack/modules/widgets/readmill.php new file mode 100644 index 00000000..7729ed3e --- /dev/null +++ b/plugins/jetpack/modules/widgets/readmill.php @@ -0,0 +1,138 @@ +<?php +class Jetpack_Readmill_Widget extends WP_Widget { + var $default_title, $default_size; + + /** + * Registers the widget with WordPress. + */ + function __construct() { + parent::__construct( + 'jetpack_readmill_widget', // Base ID + apply_filters( 'jetpack_widget_name', esc_html__( 'Send To Readmill', 'jetpack' ) ), + array( + 'description' => esc_html__( 'Readmill is the best book reader for phones and tablets. With this widget you can enable users to send a book to their device with one click.', 'jetpack' ), + ) + ); + + if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) ) { + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_script' ) ); + } + + $this->default_title = __( 'Send To Readmill', 'jetpack' ); + $this->default_size = 'large'; + } + + function enqueue_script() { + wp_enqueue_script( 'readmill', 'https://platform.readmill.com/send.js', array(), '20130220', false ); + } + + /** + * Back-end widget form. + * + * @see WP_Widget::form() + * + * @param array $instance Previously saved values from database. + */ + function form( $instance ) { + $title = isset( $instance['title' ] ) ? $instance['title'] : false; + if ( false === $title ) { + $title = $this->default_title; + } + + $epub_link = isset( $instance['epub_link'] ) ? $instance['epub_link'] : ''; + $buy_link = isset( $instance['buy_link'] ) ? $instance['buy_link'] : ''; + $size = isset( $instance['size'] ) ? $instance['size'] : $this->default_size; + ?> + + <p><?php printf( __( "Just enter the URL to your book, make sure it's a PDF or EPUB file, and you are ready to go. For more help, head to <a href='%s'>the Readmill WordPress Widget support page</a>." ), 'http://en.support.wordpress.com/widgets/readmill/' ); ?></p> + + <p> + <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label> + <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'epub_link' ); ?>"><?php esc_html_e( 'Download URL:', 'jetpack' ); ?></label> + <input class="widefat" id="<?php echo $this->get_field_id( 'epub_link' ); ?>" name="<?php echo $this->get_field_name( 'epub_link' ); ?>" type="text" value="<?php echo esc_attr( $epub_link ); ?>" /> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'buy_link' ); ?>"><?php esc_html_e( 'Item URL:', 'jetpack' ); ?></label> + <input class="widefat" id="<?php echo $this->get_field_id( 'buy_link' ); ?>" name="<?php echo $this->get_field_name( 'buy_link' ); ?>" type="text" value="<?php echo esc_attr( $buy_link ); ?>" /> + </p> + + <p> + <label><?php esc_html_e( 'What size icon?', 'jetpack' ); ?></label> + <ul> + <li><label><input id="<?php echo $this->get_field_id( 'size' ); ?>-few" name="<?php echo $this->get_field_name( 'size' ); ?>" type="radio" value="large" <?php checked( 'large', $size ); ?> /> <?php esc_html_e( 'Large', 'jetpack' ); ?></label></li> + <li><label><input id="<?php echo $this->get_field_id( 'size' ); ?>-lots" name="<?php echo $this->get_field_name( 'size' ); ?>" type="radio" value="small" <?php checked( 'small', $size ); ?> /> <?php esc_html_e( 'Small', 'jetpack' ); ?></label></li> + </ul> + </p> + + <?php + } + + /** + * Sanitize widget form values as they are saved. + * + * @see WP_Widget::update() + * + * @param array $new_instance Values just sent to be saved. + * @param array $old_instance Previously saved values from database. + * + * @return array Updated safe values to be saved. + */ + function update( $new_instance, $old_instance ) { + $instance = array(); + $instance['title'] = wp_kses( $new_instance['title'], array() ); + $instance['epub_link'] = wp_kses( $new_instance['epub_link'], array() ); + $instance['buy_link'] = wp_kses( $new_instance['buy_link'], array() ); + $instance['size'] = wp_kses( $new_instance['size'], array() ); + + if ( $this->default_title === $instance['title'] ) { + $instance['title'] = false; // Store as false in case of language change + } + + return $instance; + } + + /** + * Front-end display of widget. + * + * @see WP_Widget::widget() + * + * @param array $args Widget arguments. + * @param array $instance Saved values from database. + */ + function widget( $args, $instance ) { + $title = isset( $instance['title' ] ) ? $instance['title'] : false; + + if ( false === $title ) + $title = $this->default_title; + + $title = apply_filters( 'widget_title', $title ); + + echo $args['before_widget']; + + if ( ! empty( $title ) ) + echo $args['before_title'] . $title . $args['after_title']; + + $epub_link = isset( $instance['epub_link'] ) ? $instance['epub_link'] : ''; + $buy_link = isset( $instance['buy_link'] ) ? $instance['buy_link'] : ''; + $size = isset( $instance['size'] ) ? $instance['size'] : $this->default_size; + + if ( empty( $epub_link ) && current_user_can( 'edit_theme_options' ) ) : + ?><p><?php esc_html_e( 'Your ePub link is empty. Provide an ePub link to display the Send to Readmill widget.', 'jetpack' ); ?></p><?php + else : + ?><a class="send-to-readmill" href="https://readmill.com" data-download-url="<?php echo esc_attr( $epub_link ); ?>" data-buy-url="<?php echo esc_attr( $epub_link ); ?>" data-display="<?php echo esc_attr( $size ); ?>">Send to Readmill</a><?php + endif; + + echo $args['after_widget']; + } +} + +function jetpack_readmill_widget_init() { + register_widget( 'Jetpack_Readmill_Widget' ); +} + +add_action( 'widgets_init', 'jetpack_readmill_widget_init' ); diff --git a/plugins/jetpack/modules/widgets/top-posts.php b/plugins/jetpack/modules/widgets/top-posts.php new file mode 100644 index 00000000..b6f2c8d7 --- /dev/null +++ b/plugins/jetpack/modules/widgets/top-posts.php @@ -0,0 +1,295 @@ +<?php + +/* + * Currently, this widget depends on the Stats Module. To not load this file + * when the Stats Module is not active would potentially bypass Jetpack's + * fatal error detection on module activation, so we always load this file. + * Instead, we don't register the widget if the Stats Module isn't active. + */ + +/** + * Register the widget for use in Appearance -> Widgets + */ +add_action( 'widgets_init', 'jetpack_top_posts_widget_init' ); + +function jetpack_top_posts_widget_init() { + // Currently, this widget depends on the Stats Module + if ( + ( !defined( 'IS_WPCOM' ) || !IS_WPCOM ) + && + !function_exists( 'stats_get_csv' ) + ) { + return; + } + + register_widget( 'Jetpack_Top_Posts_Widget' ); +} + +class Jetpack_Top_Posts_Widget extends WP_Widget { + var $alt_option_name = 'widget_stats_topposts'; + var $default_title = ''; + + function __construct() { + parent::__construct( + 'top-posts', + apply_filters( 'jetpack_widget_name', __( 'Top Posts & Pages', 'jetpack' ) ), + array( + 'description' => __( 'Shows your most viewed posts and pages.', 'jetpack' ), + ) + ); + + $this->default_title = __( 'Top Posts & Pages', 'jetpack' ); + + if ( is_active_widget( false, false, $this->id_base ) ) { + add_action( 'wp_print_styles', array( $this, 'enqueue_style' ) ); + } + } + + function enqueue_style() { + wp_register_style( 'widget-grid-and-list', plugins_url( 'widget-grid-and-list.css', __FILE__ ) ); + wp_enqueue_style( 'widget-grid-and-list' ); + } + + function form( $instance ) { + $title = isset( $instance['title' ] ) ? $instance['title'] : false; + if ( false === $title ) { + $title = $this->default_title; + } + + $count = isset( $instance['count'] ) ? (int) $instance['count'] : 10; + if ( $count < 1 || 20 < $count ) { + $count = 10; + } + + if ( isset( $instance['display'] ) && in_array( $instance['display'], array( 'grid', 'list', 'text' ) ) ) { + $display = $instance['display']; + } else { + $display = 'text'; + } + + ?> + + <p> + <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'jetpack' ); ?></label> + <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'count' ); ?>"><?php esc_html_e( 'Number of posts to show:', 'jetpack' ); ?></label> + <input id="<?php echo $this->get_field_id( 'count' ); ?>" name="<?php echo $this->get_field_name( 'count' ); ?>" type="number" value="<?php echo (int) $count; ?>" min="1" max="20" /> + </p> + + <p> + <label><?php esc_html_e( 'Display as:', 'jetpack' ); ?></label> + <ul> + <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-text" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="text" <?php checked( 'text', $display ); ?> /> <?php esc_html_e( 'Text List', 'jetpack' ); ?></label></li> + <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-list" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="list" <?php checked( 'list', $display ); ?> /> <?php esc_html_e( 'Image List', 'jetpack' ); ?></label></li> + <li><label><input id="<?php echo $this->get_field_id( 'display' ); ?>-grid" name="<?php echo $this->get_field_name( 'display' ); ?>" type="radio" value="grid" <?php checked( 'grid', $display ); ?> /> <?php esc_html_e( 'Image Grid', 'jetpack' ); ?></label></li> + </ul> + </p> + + <p><?php esc_html_e( 'Top Posts & Pages by views are calculated from 24-48 hours of stats. They take a while to change.', 'jetpack' ); ?></p> + + <?php + } + + function update( $new_instance, $old_instance ) { + $instance = array(); + $instance['title'] = wp_kses( $new_instance['title'], array() ); + if ( $instance['title'] === $this->default_title ) { + $instance['title'] = false; // Store as false in case of language change + } + + $instance['count'] = (int) $new_instance['count']; + if ( $instance['count'] < 1 || 20 < $instance['count'] ) { + $instance['count'] = 10; + } + + if ( isset( $new_instance['display'] ) && in_array( $new_instance['display'], array( 'grid', 'list', 'text' ) ) ) { + $instance['display'] = $new_instance['display']; + } else { + $instance['display'] = 'text'; + } + + return $instance; + } + + function widget( $args, $instance ) { + $title = isset( $instance['title' ] ) ? $instance['title'] : false; + if ( false === $title ) + $title = $this->default_title; + $title = apply_filters( 'widget_title', $title ); + + $count = isset( $instance['count'] ) ? (int) $instance['count'] : false; + if ( $count < 1 || 20 < $count ) { + $count = 10; + } + + if ( isset( $instance['display'] ) && in_array( $instance['display'], array( 'grid', 'list', 'text' ) ) ) { + $display = $instance['display']; + } else { + $display = 'text'; + } + + if ( 'text' != $display ) { + $get_image_options = array( + 'fallback_to_avatars' => true, + 'gravatar_default' => apply_filters( 'jetpack_static_url', is_ssl() ? 'https' : 'http' . '://en.wordpress.com/i/logo/white-gray-80.png' ), + ); + if ( 'grid' == $display ) { + if ( $count %2 != 0 ) { + $count++; + } + + $get_image_options['avatar_size'] = 200; + } else { + $get_image_options['avatar_size'] = 40; + } + $get_image_options = apply_filters( 'jetpack_top_posts_widget_image_options', $get_image_options ); + } + + $posts = $this->get_by_views( $count ); + + if ( !$posts ) { + $posts = $this->get_fallback_posts(); + } + + echo $args['before_widget']; + if ( ! empty( $title ) ) + echo $args['before_title'] . $title . $args['after_title']; + + if ( !$posts ) { + if ( current_user_can( 'edit_theme_options' ) ) { + echo '<p>' . sprintf( + __( 'There are no posts to display. <a href="%s">Want more traffic?</a>', 'jetpack' ), + 'http://en.support.wordpress.com/getting-more-site-traffic/' + ) . '</p>'; + } + + echo $args['after_widget']; + return; + } + + switch ( $display ) { + case 'list' : + case 'grid' : + wp_enqueue_style( 'widget-grid-and-list' ); + foreach ( $posts as &$post ) { + $image = Jetpack_PostImages::get_image( $post['post_id'] ); + $post['image'] = $image['src']; + if ( 'blavatar' != $image['from'] && 'gravatar' != $image['from'] ) { + $size = (int) $get_image_options['avatar_size']; + $post['image'] = jetpack_photon_url( $post['image'], array( 'resize' => "$size,$size" ) ); + } + } + + unset( $post ); + + if ( 'grid' == $display ) { + echo "<div class='widgets-grid-layout no-grav'>\n"; + foreach ( $posts as $post ) : + ?> + <div class="widget-grid-view-image"> + <a href="<?php echo esc_url( $post['permalink'] ); ?>" title="<?php echo esc_attr( wp_kses( $post['title'], array() ) ); ?>" class="bump-view" data-bump-view="tp"><img src="<?php echo esc_url( $post['image'] ); ?>" /></a> + </div> + + <?php + endforeach; + echo "</div>\n"; + } else { + echo "<ul class='widgets-list-layout no-grav'>\n"; + foreach ( $posts as $post ) : + ?> + <li> + <img src="<?php echo esc_url( $post['image'] ); ?>" class='widgets-list-layout-blavatar' /> + <div class="widgets-list-layout-links"><a href="<?php echo esc_url( $post['permalink'] ); ?>" class="bump-view" data-bump-view="tp"><?php echo esc_html( wp_kses( $post['title'], array() ) ); ?></a></div> + </li> + <?php + endforeach; + echo "</ul>\n"; + } + break; + default : + echo '<ul>'; + foreach ( $posts as $post ) { + echo '<li><a href="' . esc_url( $post['permalink'] ) . '" class="bump-view" data-bump-view="tp">' . esc_html( $post['title'] ) . "</a></li>\n"; + } + echo '</ul>'; + } + + echo $args['after_widget']; + } + + function get_by_views( $count ) { + global $wpdb; + $post_view_posts = stats_get_csv( 'postviews', array( 'days' => 2, 'limit' => 10 ) ); + if ( !$post_view_posts ) { + return array(); + } + + $post_view_ids = array_filter( wp_list_pluck( $post_view_posts, 'post_id' ) ); + if ( !$post_view_ids ) { + return array(); + } + + return $this->get_posts( $post_view_ids, $count ); + } + + function get_fallback_posts() { + if ( current_user_can( 'edit_theme_options' ) ) { + return array(); + } + + $post_query = new WP_Query; + + $posts = $post_query->query( array( + 'posts_per_page' => 1, + 'post_status' => 'publish', + 'post_type' => array( 'post', 'page' ), + 'no_found_rows' => true, + ) ); + + if ( !$posts ) { + return array(); + } + + $post = array_pop( $posts ); + + return $this->get_posts( $post->ID, 1 ); + } + + function get_posts( $post_ids, $count ) { + $counter = 0; + + $posts = array(); + foreach ( (array) $post_ids as $post_id ) { + $post = get_post( $post_id ); + + if ( !$post ) + continue; + + // hide private and password protected posts + if ( 'publish' != $post->post_status || !empty( $post->post_password ) || empty( $post->ID ) ) + continue; + + // Both get HTML stripped etc on display + if ( empty( $post->post_title ) ) { + $title_source = $post->post_content; + $title = wp_html_excerpt( $title_source, 50 ); + $title .= '…'; + } else { + $title = $post->post_title; + } + + $permalink = get_permalink( $post->ID ); + + $posts[] = compact( 'title', 'permalink', 'post_id' ); + $counter++; + + if ( $counter == $count ) + break; // only need to load and show x number of likes + } + + return $posts; + } +} diff --git a/plugins/jetpack/modules/widgets/twitter.php b/plugins/jetpack/modules/widgets/twitter.php new file mode 100644 index 00000000..ff49abf7 --- /dev/null +++ b/plugins/jetpack/modules/widgets/twitter.php @@ -0,0 +1,407 @@ +<?php + +/** + * Twitter widget class + * Display the latest N tweets from a Twitter screenname as a widget + * Customize screenname, maximum number of tweets displayed, show or hide @replies, and text displayed between tweet text and a timestamp + * + */ + +/** + * Register the widget for use in Appearance -> Widgets + */ +add_action( 'widgets_init', 'jetpack_twitter_widget_init' ); + +function jetpack_twitter_widget_init() { + register_widget( 'Jetpack_Widget_Twitter' ); +} + +class Jetpack_Widget_Twitter extends WP_Widget { + + function __construct() { + parent::__construct( + 'twitter', + apply_filters( 'jetpack_widget_name', __( 'Twitter', 'jetpack' ) ), + array( + 'classname' => 'widget_twitter', + 'description' => __( 'Display your Tweets from Twitter', 'jetpack' ) + ) + ); + + if ( is_active_widget( false, false, $this->id_base ) || is_active_widget( false, false, 'monster' ) ) { + add_action( 'wp_head', array( $this, 'style' ) ); + } + } + + function style() { +?> +<style type="text/css"> +.widget_twitter li { + word-wrap: break-word; +} +</style> +<?php + } + + function widget( $args, $instance ) { + $account = trim( urlencode( $instance['account'] ) ); + + if ( empty( $account ) ) { + if ( current_user_can('edit_theme_options') ) { + echo $args['before_widget']; + echo '<p>' . sprintf( __( 'Please configure your Twitter username for the <a href="%s">Twitter Widget</a>.', 'jetpack' ), admin_url( 'widgets.php' ) ) . '</p>'; + echo $args['after_widget']; + } + + return; + } + + $title = apply_filters( 'widget_title', $instance['title'] ); + + if ( empty( $title ) ) + $title = __( 'Twitter Updates', 'jetpack' ); + + $show = absint( $instance['show'] ); // # of Updates to show + + if ( $show > 200 ) // Twitter paginates at 200 max tweets. update() should not have accepted greater than 20 + $show = 200; + + $hidereplies = (bool) $instance['hidereplies']; + $hidepublicized = (bool) $instance['hidepublicized']; + $include_retweets = (bool) $instance['includeretweets']; + $follow_button = (bool) $instance['followbutton']; + + echo "{$args['before_widget']}{$args['before_title']}<a href='" . esc_url( "http://twitter.com/{$account}" ) . "'>" . esc_html( $title ) . "</a>{$args['after_title']}"; + + $tweets = $this->fetch_twitter_user_stream( $account, $hidereplies, $show, $include_retweets ); + + if ( isset( $tweets['error'] ) && ( isset( $tweets['data'] ) && ! empty( $tweets['data'] ) ) ) + $tweets['error'] = ''; + + if ( empty( $tweets['error'] ) ) { + $before_tweet = isset( $instance['beforetweet'] ) ? stripslashes( wp_filter_post_kses( $instance['beforetweet'] ) ) : ''; + $before_timesince = ( isset( $instance['beforetimesince'] ) && ! empty( $instance['beforetimesince'] ) ) ? esc_html( $instance['beforetimesince'] ) : ' '; + + $this->display_tweets( $show, $tweets['data'], $hidepublicized, $before_tweet, $before_timesince, $account ); + + if ( $follow_button ) + $this->display_follow_button( $account ); + + add_action( 'wp_footer', array( $this, 'twitter_widget_script' ) ); + } else { + echo $tweets['error']; + } + + echo $args['after_widget']; + do_action( 'jetpack_bump_stats_extras', 'widget', 'twitter' ); + } + + function display_tweets( $show, $tweets, $hidepublicized, $before_tweet, $before_timesince, $account ) { + $tweets_out = 0; + ?><ul class='tweets'><?php + + foreach( (array) $tweets as $tweet ) { + if ( $tweets_out >= $show ) + break; + + if ( empty( $tweet['text'] ) ) + continue; + + if( $hidepublicized && false !== strstr( $tweet['source'], 'http://publicize.wp.com/' ) ) + continue; + + $tweet['text'] = esc_html( $tweet['text'] ); // escape here so that Twitter handles in Tweets don't get mangled + $tweet = $this->expand_tco_links( $tweet ); + $tweet['text'] = make_clickable( $tweet['text'] ); + + /* + * Create links from plain text based on Twitter patterns + * @link http://github.com/mzsanford/twitter-text-rb/blob/master/lib/regex.rb Official Twitter regex + */ + $tweet['text'] = preg_replace_callback( '/(^|[^0-9A-Z&\/]+)(#|\xef\xbc\x83)([0-9A-Z_]*[A-Z_]+[a-z0-9_\xc0-\xd6\xd8-\xf6\xf8\xff]*)/iu', array( $this, '_jetpack_widget_twitter_hashtag' ), $tweet['text'] ); + $tweet['text'] = preg_replace_callback( '/([^a-zA-Z0-9_]|^)([@\xef\xbc\xa0]+)([a-zA-Z0-9_]{1,20})(\/[a-zA-Z][a-zA-Z0-9\x80-\xff-]{0,79})?/u', array( $this, '_jetpack_widget_twitter_username' ), $tweet['text'] ); + + if ( isset( $tweet['id_str'] ) ) + $tweet_id = urlencode( $tweet['id_str'] ); + else + $tweet_id = urlencode( $tweet['id'] ); + + ?> + + <li> + <?php echo esc_attr( $before_tweet ) . $tweet['text'] . esc_attr( $before_timesince ) ?> + <a href="<?php echo esc_url( "http://twitter.com/{$account}/statuses/{$tweet_id}" ); ?>" class="timesince"><?php echo esc_html( str_replace( ' ', ' ', $this->time_since( strtotime( $tweet['created_at'] ) ) ) ); ?> ago</a> + </li> + + <?php + + unset( $tweet_it ); + $tweets_out++; + } + + ?></ul><?php + } + + function display_follow_button( $account ) { + global $themecolors; + + $follow_colors = isset( $themecolors['link'] ) ? " data-link-color='#{$themecolors['link']}'" : ''; + $follow_colors .= isset( $themecolors['text'] ) ? " data-text-color='#{$themecolors['text']}'" : ''; + $follow_button_attrs = " class='twitter-follow-button' data-show-count='false'{$follow_colors}"; + + ?><a href="http://twitter.com/<?php echo esc_attr( $account ); ?>" <?php echo $follow_button_attrs; ?>>Follow @<?php echo esc_attr( $account ); ?></a><?php + } + + function expand_tco_links( $tweet ) { + if ( ! empty( $tweet['entities']['urls'] ) && is_array( $tweet['entities']['urls'] ) ) { + foreach ( $tweet['entities']['urls'] as $entity_url ) { + if ( ! empty( $entity_url['expanded_url'] ) ) { + $tweet['text'] = str_replace( + $entity_url['url'], + '<a href="' . esc_url( $entity_url['expanded_url'] ) . '"> ' . esc_html( $entity_url['display_url'] ) . '</a>', + $tweet['text'] + ); + } + } + } + + return $tweet; + } + + function fetch_twitter_user_stream( $account, $hidereplies, $show, $include_retweets ) { + $tweets = get_transient( 'widget-twitter-' . $this->number ); + $the_error = get_transient( 'widget-twitter-error-' . $this->number ); + + if ( ! $tweets ) { + $params = array( + 'screen_name' => $account, // Twitter account name + 'trim_user' => true, // only basic user data (slims the result) + 'include_entities' => true + ); + + // If combined with $count, $exclude_replies only filters that number of tweets (not all tweets up to the requested count). + if ( $hidereplies ) + $params['exclude_replies'] = true; + else + $params['count'] = $show; + + if ( $include_retweets ) + $params['include_rts'] = true; + + $twitter_json_url = esc_url_raw( 'http://api.twitter.com/1/statuses/user_timeline.json?' . http_build_query( $params ), array( 'http', 'https' ) ); + unset( $params ); + + $response = wp_remote_get( $twitter_json_url, array( 'User-Agent' => 'WordPress.com Twitter Widget' ) ); + $response_code = wp_remote_retrieve_response_code( $response ); + + switch( $response_code ) { + case 200 : // process tweets and display + $tweets = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( ! is_array( $tweets ) || isset( $tweets['error'] ) ) { + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', "request-fail-{$response_code}-bad-data" ); + $the_error = '<p>' . esc_html__( 'Error: Twitter did not respond. Please wait a few minutes and refresh this page.', 'jetpack' ) . '</p>'; + $tweet_cache_expire = 300; + break; + } else { + set_transient( 'widget-twitter-backup-' . $this->number, $tweets, 86400 ); // A one day backup in case there is trouble talking to Twitter + } + + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', 'request-success' ); + $tweet_cache_expire = 900; + break; + case 401 : // display private stream notice + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', "request-fail-{$response_code}" ); + + $tweets = array(); + $the_error = '<p>' . sprintf( esc_html__( 'Error: Please make sure the Twitter account is %1$spublic%2$s.', 'jetpack' ), '<a href="http://support.twitter.com/forums/10711/entries/14016">', '</a>' ) . '</p>'; + $tweet_cache_expire = 300; + break; + default : // display an error message + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', "request-fail-{$response_code}" ); + + $tweets = get_transient( 'widget-twitter-backup-' . $this->number ); + $the_error = '<p>' . esc_html__( 'Error: Twitter did not respond. Please wait a few minutes and refresh this page.', 'jetpack' ) . '</p>'; + $tweet_cache_expire = 300; + break; + } + + set_transient( 'widget-twitter-' . $this->number, $tweets, $tweet_cache_expire ); + set_transient( 'widget-twitter-error-' . $this->number, $the_error, $tweet_cache_expire ); + } + + return array( 'data' => $tweets, 'error' => $the_error ); + } + + function update( $new_instance, $old_instance ) { + $instance = array(); + + $instance['account'] = trim( wp_kses( $new_instance['account'], array() ) ); + $instance['account'] = str_replace( array( 'http://twitter.com/', '/', '@', '#!', ), array( '', '', '', '', ), $instance['account'] ); + + $instance['title'] = wp_kses( $new_instance['title'], array() ); + $instance['show'] = absint( $new_instance['show'] ); + $instance['hidereplies'] = isset( $new_instance['hidereplies'] ); + $instance['hidepublicized'] = isset( $new_instance['hidepublicized'] ); + $instance['includeretweets'] = isset( $new_instance['includeretweets'] ); + + if ( $instance['followbutton'] != $new_instance['followbutton'] ) { + if ( $new_instance['followbutton'] ) + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', 'follow_button_enabled' ); + else + do_action( 'jetpack_bump_stats_extras', 'twitter_widget', 'follow_button_disabled' ); + } + + $instance['followbutton'] = ! isset( $new_instance['followbutton'] ) ? 0 : 1; + $instance['beforetimesince'] = $new_instance['beforetimesince']; + + delete_transient( 'widget-twitter-' . $this->number ); + delete_transient( 'widget-twitter-error-' . $this->number ); + + return $instance; + } + + function form( $instance ) { + //Defaults + $account = isset( $instance['account'] ) ? wp_kses( $instance['account'], array() ) : ''; + $title = isset( $instance['title'] ) ? $instance['title'] : ''; + $show = isset( $instance['show'] ) ? absint( $instance['show'] ) : 5; + $show = ( $show < 1 || 20 < $show ) ? 5 : $show; + $hidereplies = isset( $instance['hidereplies'] ) && ! empty( $instance['hidereplies'] ) ? (bool) $instance['hidereplies'] : false; + $hidepublicized = isset( $instance['hidepublicized'] ) && ! empty( $instance['hidepublicized'] ) ? (bool) $instance['hidepublicized'] : false; + $include_retweets = isset( $instance['includeretweets'] ) && ! empty( $instance['includeretweets'] ) ? (bool) $instance['includeretweets'] : false; + $follow_button = isset( $instance['followbutton'] ) && ! empty( $instance['followbutton'] ) ? 1 : 0; + $before_timesince = isset( $instance['beforetimesince'] ) && ! empty( $instance['beforetimesince'] ) ? esc_attr( $instance['beforetimesince'] ) : ''; + ?> + + <p> + <label for="<?php echo $this->get_field_id( 'title' ); ?>"> + <?php esc_html_e( 'Title:', 'jetpack' )?> + <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'account' ); ?>"> + <?php esc_html_e( 'Twitter username:', 'jetpack' ); ?> <a href="http://support.wordpress.com/widgets/twitter-widget/#twitter-username" target="_blank">( ? )</a> + <input class="widefat" id="<?php echo $this->get_field_id( 'account' ); ?>" name="<?php echo $this->get_field_name( 'account' ); ?>" type="text" value="<?php echo esc_attr( $account ); ?>" /> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'show' ); ?>"> + <?php esc_html_e( 'Maximum number of Tweets to show:', 'jetpack' ); ?> + <select id="<?php echo $this->get_field_id( 'show' ); ?>" name="<?php echo $this->get_field_name( 'show' ); ?>"> + <?php + for ( $i = 1; $i <= 20; ++$i ) : + ?><option value="<?php echo esc_attr( $i ); ?>" <?php selected( $show, $i ); ?>><?php echo esc_attr( $i ); ?></option><?php + endfor; + ?> + </select> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'hidereplies' ); ?>"> + <input id="<?php echo $this->get_field_id( 'hidereplies' );?>" class="checkbox" type="checkbox" name="<?php echo $this->get_field_name( 'hidereplies' ); ?>" <?php checked( $hidereplies, true ); ?> /> + <?php esc_html_e( 'Hide replies', 'jetpack' ); ?> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'hidepublicized' ); ?>"> + <input id="<?php echo $this->get_field_id( 'hidepublicized' ); ?>" class="checkbox" type="checkbox" name="<?php echo $this->get_field_name( 'hidepublicized' ); ?>" <?php checked( $hidepublicized, true ); ?> /> + <?php esc_html_e( 'Hide Tweets pushed by Publicize', 'jetpack' ); ?> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'includeretweets' ); ?>"> + <input id="<?php echo $this->get_field_id( 'includeretweets' ); ?>" class="checkbox" type="checkbox" name="<?php echo $this->get_field_name( 'includeretweets' ); ?>" <?php checked( $include_retweets, true ); ?> /> + <?php esc_html_e( 'Include retweets', 'jetpack' ); ?> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'followbutton' ); ?>"> + <input id="<?php echo $this->get_field_id( 'followbutton' ); ?>" class="checkbox" type="checkbox" name="<?php echo $this->get_field_name( 'followbutton' ); ?>" <?php checked( $follow_button, 1 ); ?> /> + <?php esc_html_e( 'Display Follow Button', 'jetpack' ); ?> + </label> + </p> + + <p> + <label for="<?php echo $this->get_field_id( 'beforetimesince' ); ?>"> + <?php esc_html_e( 'Text to display between Tweet and timestamp:', 'jetpack' ); ?> + <input class="widefat" id="<?php echo $this->get_field_id( 'beforetimesince' ); ?>" name="<?php echo $this->get_field_name( 'beforetimesince' ); ?>" type="text" value="<?php echo esc_attr( $before_timesince ); ?>" /> + </label> + </p> + + <?php + } + + function time_since( $original, $do_more = 0 ) { + // array of time period chunks + $chunks = array( + array(60 * 60 * 24 * 365 , 'year'), + array(60 * 60 * 24 * 30 , 'month'), + array(60 * 60 * 24 * 7, 'week'), + array(60 * 60 * 24 , 'day'), + array(60 * 60 , 'hour'), + array(60 , 'minute'), + ); + + $today = time(); + $since = $today - $original; + + for ($i = 0, $j = count($chunks); $i < $j; $i++) { + $seconds = $chunks[$i][0]; + $name = $chunks[$i][1]; + + if (($count = floor($since / $seconds)) != 0) + break; + } + + $print = ($count == 1) ? '1 '.$name : "$count {$name}s"; + + if ($i + 1 < $j) { + $seconds2 = $chunks[$i + 1][0]; + $name2 = $chunks[$i + 1][1]; + + // add second item if it's greater than 0 + if ( (($count2 = floor(($since - ($seconds * $count)) / $seconds2)) != 0) && $do_more ) + $print .= ($count2 == 1) ? ', 1 '.$name2 : ", $count2 {$name2}s"; + } + return $print; + } + + /** + * Link a Twitter user mentioned in the tweet text to the user's page on Twitter. + * + * @param array $matches regex match + * @return string Tweet text with inserted @user link + */ + function _jetpack_widget_twitter_username( array $matches ) { // $matches has already been through wp_specialchars + return "$matches[1]@<a href='" . esc_url( 'http://twitter.com/' . urlencode( $matches[3] ) ) . "'>$matches[3]</a>"; + } + + /** + * Link a Twitter hashtag with a search results page on Twitter.com + * + * @param array $matches regex match + * @return string Tweet text with inserted #hashtag link + */ + function _jetpack_widget_twitter_hashtag( array $matches ) { // $matches has already been through wp_specialchars + return "$matches[1]<a href='" . esc_url( 'http://twitter.com/search?q=%23' . urlencode( $matches[3] ) ) . "'>#$matches[3]</a>"; + } + + function twitter_widget_script() { + if ( ! wp_script_is( 'twitter-widgets', 'registered' ) ) { + if ( is_ssl() ) + $twitter_widget_js = 'https://platform.twitter.com/widgets.js'; + else + $twitter_widget_js = 'http://platform.twitter.com/widgets.js'; + wp_register_script( 'twitter-widgets', $twitter_widget_js, array(), '20111117', true ); + wp_print_scripts( 'twitter-widgets' ); + } + } +} diff --git a/plugins/jetpack/modules/widgets/widget-grid-and-list.css b/plugins/jetpack/modules/widgets/widget-grid-and-list.css new file mode 100644 index 00000000..984016c9 --- /dev/null +++ b/plugins/jetpack/modules/widgets/widget-grid-and-list.css @@ -0,0 +1,110 @@ +/* 2-Column Grid Layout */ + +.widgets-grid-layout { + width: 100%; +} + +.widgets-grid-layout:before, +.widgets-grid-layout:after { + content: " "; + display: table; +} + +.widgets-grid-layout:after { + clear: both; +} + +.widget-grid-view-image { + float: left; + max-width: 50%; +} + +.widget-grid-view-image a { + display: block; + margin: 0 2px 4px 0; +} + +.widget-grid-view-image:image:nth-child(even) { + float: right; +} + +.widget-grid-view-image:nth-child(even) a { + margin: 0 0 4px 2px; +} + +.widgets-grid-layout .widget-grid-view-image img { + max-width: 100%; + height: auto; +} + +/* Multi-Column Grid Layout */ + +.widgets-multi-column-grid ul { + overflow: hidden; + padding: 0; + margin: 0; + list-style-type: none; +} + +.widgets-multi-column-grid ul li { + background: none; + clear: none; + float: left; + margin: 0 -5px -3px 0; + padding: 0 8px 6px 0; + border: none; + list-style-type: none !important; +} + +.widgets-multi-column-grid ul li a { + background: none; + margin: 0; + padding: 0; + border: 0; +} + +.widgets-multi-column-grid .avatar { + vertical-align: middle; +} + +/* List Layout */ + +.widgets-list-layout { + padding: 0; + margin: 0; + list-style-type: none; +} + +.widgets-list-layout li:before, +.widgets-list-layout li:after { + content:""; + display:table; +} +.widgets-list-layout li:after { + clear:both; +} +.widgets-list-layout li { + zoom:1; + margin-bottom: 1em; + list-style-type: none !important; +} + +.widgets-list-layout .widgets-list-layout-blavatar { + float: left; + width: 21.276596%; + max-width: 40px; + height: auto; +} + +.widgets-list-layout-links { + float: right; + width: 73.404255%; +} + +.widgets-list-layout span { + opacity: 0.5; +} + +.widgets-list-layout span:hover { + opacity: 0.8; +} diff --git a/plugins/jetpack/modules/widgets/widgets.css b/plugins/jetpack/modules/widgets/widgets.css new file mode 100644 index 00000000..a2d62957 --- /dev/null +++ b/plugins/jetpack/modules/widgets/widgets.css @@ -0,0 +1,13 @@ +/* + * Widget styles for Jetpack + */ + +/* Clear floats */ +.jetpack-image-container:after { + clear: both; +} +.jetpack-image-container:before, +.jetpack-image-container:after { + display: table; + content: ""; +} diff --git a/plugins/jetpack/readme.txt b/plugins/jetpack/readme.txt index 60de369f..f4808dd2 100644 --- a/plugins/jetpack/readme.txt +++ b/plugins/jetpack/readme.txt @@ -1,9 +1,9 @@ === Jetpack by WordPress.com === -Contributors: automattic, apeatling, beaulebens, hugobaeta, joen, mdawaffe, andy, designsimply, hew, westi, eoigal, tmoorewp, matt -Tags: WordPress.com, statistics, stats, views, tweets, twitter, widget, gravatar, hovercards, profile, equations, latex, math, maths, youtube, shortcode, archives, audio, blip, bliptv, dailymotion, digg, flickr, googlevideo, google, googlemaps, kyte, kytetv, livevideo, redlasso, rockyou, rss, scribd, slide, slideshare, soundcloud, vimeo, shortlinks, wp.me, subscriptions -Requires at least: 3.2 -Tested up to: 3.4.1 -Stable tag: 1.6.1 +Contributors: automattic, apeatling, beaulebens, hugobaeta, Joen, mdawaffe, andy, designsimply, hew, westi, eoigal, tmoorewp, matt, pento, cfinke, daniloercoli, chellycat, gibrown, jblz, jshreve, barry, alternatekev, azaozz, ethitter, johnjamesjacoby, lancewillett, martinremy, nickmomrik, stephdau, yoavf, matveb, jeherve +Tags: WordPress.com, statistics, stats, views, tweets, twitter, widget, gravatar, hovercards, profile, equations, latex, math, maths, youtube, shortcode, archives, audio, blip, bliptv, dailymotion, digg, flickr, googlevideo, google, googlemaps, kyte, kytetv, livevideo, redlasso, rockyou, rss, scribd, slide, slideshare, soundcloud, vimeo, shortlinks, wp.me, subscriptions, notifications, notes, json, api, rest, mosaic, gallery, slideshow +Requires at least: 3.3 +Tested up to: 3.5 +Stable tag: 2.2 Supercharge your WordPress site with powerful features previously only available to WordPress.com users. @@ -18,15 +18,24 @@ Features include: * Simple, concise stats with no additional load on your server. Previously provided by [WordPress.com Stats](http://wordpress.org/extend/plugins/stats/). * Email subscriptions for your blog's posts and your post's comments. * Social networking enabled comment system. +* Likes, allowing your readers to show their appreciation of your posts. +* Monitor and manage your site's activity with Notifications in your Toolbar and on WordPress.com. * Simple, Akismet-backed contact forms. * The [WP.me URL shortener](http://wp.me/sf2B5-shorten). * Hovercard popups for your commenters via [Gravatar](http://gravatar.com/). * Easily embedded media from popular sites like YouTube, Digg, and Vimeo. +* The ability to post to your blog from any email client. +* Integration with and automatic posting to your favorite social networks including Twitter, Facebook, Tumblr, and LinkedIn. * For the Math geeks, a simple way to include beautiful mathematical expressions on your site. * A widget for displaying recent tweets. Previously provided by [Wickett Twitter Widget](http://wordpress.org/extend/plugins/wickett-twitter-widget/) * Your readers can easily share your posts via email or their favorite social networks. Previously provided by the [Sharedaddy](http://wordpress.org/extend/plugins/sharedaddy/) WordPress plugin. * Your writing will improve thanks to After the Deadline, an artificial intelligence based spell, style, and grammar checker. Previously provided by the [After the Deadline](http://wordpress.org/extend/plugins/after-the-deadline/) WordPress plugin. * With Carousel active, any standard WordPress galleries you have embedded in posts or pages will launch a gorgeous full-screen photo browsing experience with comments and EXIF metadata. +* A CSS editor that lets you customize your site design without modifying your theme. +* A mobile theme that automatically streamlines your site for visitors on mobile devices. +* Mobile push notifications for new comments via WordPress mobile apps. +* The ability to allow applications to securely authenticate and access your site with your permission. +* Creative formats for your image galleries: mosaic, circles, squares, and a slideshow view. * and *many* more to come! Note: The stats portion of Jetpack uses Quantcast to enhance its data. @@ -59,14 +68,15 @@ Use [shortcodes](http://support.wordpress.com/shortcodes/) to embed your media. * [[dailymotion]](http://support.wordpress.com/videos/dailymotion/) * [[digg]](http://support.wordpress.com/digg/) * [[flickr]](http://support.wordpress.com/videos/flickr-video/) +* [[googlemaps]](http://en.support.wordpress.com/google-maps/) * [[googlevideo]](http://support.wordpress.com/videos/google-video/) * [[polldaddy]](http://support.polldaddy.com/wordpress-shortcodes/) * [[scribd]](http://support.wordpress.com/scribd/) * [[slideshare]](http://support.wordpress.com/slideshows/slideshare/) * [[soundcloud]](http://support.wordpress.com/audio/soundcloud-audio-player/) +* [[videopress]](http://support.wordpress.com/videopress/) * [[vimeo]](http://support.wordpress.com/videos/vimeo/) * [[youtube]](http://support.wordpress.com/videos/youtube/) -* [[googlemaps]](http://en.support.wordpress.com/google-maps/) == Screenshots == @@ -76,9 +86,236 @@ Use [shortcodes](http://support.wordpress.com/shortcodes/) to embed your media. 4. Gravatar Hovercards settings. 5. Spelling and Grammar demo. 6. Gallery Carousel. +7. CSS Editor +8. Mobile Theme == Changelog == += 2.2 = +* Enhancement: Likes: Allow your readers to show their appreciation of your posts. +* Enhancement: Shortcodes: SoundCloud: Update to version 2.3 of the SoundCloud plugin (HTML5 default player, various fixes). +* Enhancement: Shortcodes: Subscriptions: Add a shortcode to enable placement of a subscription signup form in a post or page. +* Enhancement: Sharedaddy: Allow selecting multiple images from a post using the Pinterest share button. +* Enhancement: Contact Form: Allow feedbacks to be marked spam in bulk. +* Enhancement: Widgets: Readmill Widget: Give your visitors a link to send your book to their Readmill library. +* Note: Notifications: Discontinue support for Internet Explorer 7 and below. +* Bug Fix: JSON API: Fix authorization problems that some users were experiencing. +* Bug Fix: JSON API: Sticky posts were not being sorted correctly in /posts requests. +* Bug Fix: Stats: sync stats_options so server has roles array needed for view_stats cap check. +* Bug Fix: Infinite Scroll: Display improvements. +* Bug Fix: Infinite Scroll: WordPress compatibility fixes. +* Bug Fix: Photon: Only rewrite iamge urls if the URL is compatible with Photon. +* Bug Fix: Photon: Account for registered image sizes with one or more dimesions set to zero. +* Bug Fix: Subscriptions: Make HTML markup more valid. +* Bug Fix: Subscriptions: Fixed notices displayed in debug mode. +* Bug Fix: Custom CSS: CSS warnings and errors should now work in environments where JavaScript is concatenated or otherwise modified before being served. +* Bug Fix: Hovercards: WordPress compatibility fixes. +* Bug Fix: Improved image handling for the Sharing and Publicize modules. +* Bug Fix: Carousel: Display and Scrollbar fixes. +* Bug Fix: Tiled Galleries: Restrict images in tiled galleries from being set larger than their containers. +* Bug Fix: Widgets: Gravatar Profile: CSS fixes. +* Bug Fix: Publicize: Strip HTML comments from the data we send to the third party services. +* Bug Fix: Notifications: Dropped support for IE7 and below in the notifications menu. +* Bug Fix: Custom CSS Editor: Allow custom themes to save CSS more easily. +* Bug Fix: Infinite Scroll: Waits until the DOM is ready before loading the scrolling code. +* Bug Fix: Mobile Theme: If the user has disabled the custom header text color, show the default black header text color. +* Bug Fix: Mobile Theme: Fix for the "View Full Site" link. +* Bug Fix: Mobile Theme: Use a filter to modify the output of wp_title(). +* Bug Fix: Publicize: Twitter: Re-enable character count turning red when more than 140 characters are typed. + += 2.1.2 = +* Enhancement: Infinite Scroll: Introduce filters for Infinite Scroll. +* Enhancement: Shortcodes: TED shortcode. +* Bug Fix: Carousel: Make sure to use large image sizes. +* Bug Fix: Carousel: Clicking the back button in your browser after exiting a carousel gallery brings you back to the gallery. +* Bug Fix: Carousel: Fix a scrollbar issue. +* Bug Fix: Comments: Move the get_avatar() function out of the base class. +* Bug Fix: Contact Form: Prevent the form from displaying i18n characters. +* Bug Fix: Contact Form: Remove the !important CSS rule. +* Bug Fix: Infinite Scroll: Main query arguments are not respected when using default permalink. +* Bug Fix: JSON API: Trap 'wp_die' for new comments and image uploads. +* Bug Fix: JSON API: Use a better array key for the user_ID. +* Bug Fix: JSON API: Make the class instantiable only once, but multi-use. +* Bug Fix: JSON API: Fix lookup of pages by page slug. +* Bug Fix: JSON API: Updates for post likes. +* Bug Fix: Mobile Theme: Remove Android download link for BB10 and Playbook. +* Bug Fix: Open Graph: Stop using Loop functions to get post data for meta tags. +* Bug Fix: Photon: Suppress and check for warnings when pasing_url and using it. +* Bug Fix: Photon: Ensure full image size can be used. +* Bug Fix: Photon: Resolve Photon / YouTube embed conflict. +* Bug Fix: Photon: Fix dimension parsing from URLs. +* Bug Fix: Photon: Make sure that width/height atts are greater than zero. +* Bug Fix: Sharedaddy: Layout fixes for share buttons. +* Bug Fix: Sharedaddy: Always send Facebook a language locale. +* Bug Fix: Sharedaddy: Don't look up share counts for empty URLs. +* Bug Fix: Shortcodes: Ensure that images don't overflow their containers in the slideshow shortcode. +* Bug Fix: Shortcodes: only enqueue jquery if archive supports Infinite Scroll in the Audio Shortcode. +* Bug Fix: Tiled Galleries: Use a more specific class for gallery item size to avoid conflicts. +* Bug Fix: Tiled Galleries: Fixing scrolling issue when tapping on a Tiled Gallery on Android. +* Bug Fix: Widgets: Gravatar profile widget typo. +* Bug Fix: Widgets: Add (Jetpack) to widget titles. +* Bug Fix: Widgets: Twitter wasn't wrapping links in the t.co shortener. +* Bug Fix: Widgets: Facebook Likebox updates to handling the language locale. +* Bug Fix: Widgets: Top Posts: Fixed a WP_DEBUG notice. +* Bug Fix: Widgets: Gravatar Profile Widget: transient names must be less than 45 characters long. +* Bug Fix: typo in delete_post_action function. +* Bug Fix: Load rendered LaTeX image on same protocol as its page. + + += 2.1.1 = +* Bug Fix: Fix for an error appearing for blogs updating from Jetpack 1.9.2 or earlier to 2.1. + += 2.1 = +* Enhancement: Tiled Galleries: Show off your photos with cool mosaic galleries. +* Enhancement: Slideshow gallery type: Display any gallery as a slideshow. +* Enhancement: Custom CSS: Allow zoom property. +* Enhancement: Stats: Show WordPress.com subscribers in stats. +* Bug Fix: Fix errors shown after connecting Jetpack to WordPress.com. +* Bug Fix: Photon: Fix bug causing errors to be shown in some posts. +* Bug Fix: Photon: Convert all images in posts when Photon is active. +* Bug Fix: Infinite Scroll: Improved compatibility with the other modules. +* Bug Fix: Custom CSS: Updated editor to fix missing file errors. +* Bug Fix: Publicize: Don't show the Facebook profile option if this is a Page-only account. +* Bug Fix: Photon: A fix for photos appearing shrunken if they didn't load quickly enough. +* Bug Fix: Sharing: A compatibility fix for posts that only have partial featured image data. +* Bug Fix: Publicize/Sharing: For sites without a static homepage, don't set the OpenGraph url value to the first post permalink. +* Bug Fix: Mobile Theme: Better compatibility with the customizer on mobile devices. +* Bug Fix: Sharing: Don't show sharing options on front page if that option is turned off. +* Bug Fix: Contact Form: Fix PHP warning shown when adding a Contact Form in WordPress 3.5. +* Bug Fix: Photon: Handle images with relative paths. +* Bug Fix: Contact Form: Fix compatibility with the Shortcode Embeds module. + + += 2.0.4 = +* Bug Fix: Open Graph: Correct a bug that prevents Jetpack from being activated if the SharePress plugin isn't installed. + += 2.0.3 = +* Enhancement: Infinite Scroll: support [VideoPress](http://wordpress.org/extend/plugins/video/) plugin. +* Enhancement: Photon: Apply to all images retrieved from the Media Library. +* Enhancement: Photon: Retina image support. +* Enhancement: Custom CSS: Refined editor interface. +* Enhancement: Custom CSS: Support [Sass](http://sass-lang.com/) and [LESS](http://lesscss.org/) with built-in preprocessors. +* Enhancement: Open Graph: Better checks for other plugins that may be loading Open Graph tags to prevent Jetpack from doubling meta tag output. +* Bug Fix: Infinite Scroll: Respect relative image dimensions. +* Bug Fix: Photon: Detect custom-cropped images and use those with Photon, rather than trying to use the original. +* Bug Fix: Custom CSS: Fix for bug preventing @import from working with url()-style URLs. + += 2.0.2 = +* Bug Fix: Remove an erroneous PHP short open tag with the full tag to correct fatal errors under certain PHP configurations. + += 2.0.1 = +* Enhancement: Photon: Support for the [Lazy Load](http://wordpress.org/extend/plugins/lazy-load/) plugin. +* Bug Fix: Photon: Fix warped images with un- or under-specified dimensions. +* Bug Fix: Photon: Fix warped images with pre-photonized URLs; don't try to photonize them twice. +* Bug Fix: Infinite Scroll: Check a child theme's parent theme for infinite scroll support. +* Bug Fix: Infinite Scroll: Correct a bug with archives that resulted in posts appearing on archives that they didn't belong on. +* Bug Fix: Publicize: Send the correct shortlink to Twitter (et al.) if your site uses a shortener other than wp.me. +* Bug Fix: Sharing: Improved theme compatibility for the Google+ button. +* Bug Fix: Notifications: Use locally-installed Javascript libraries if available. + += 2.0 = +* Enhancement: Publicize: Connect your site to popular social networks and automatically share new posts with your friends. +* Enhancement: Post By Email: Publish posts to your blog directly from your personal email account. +* Enhancement: Photon: Images served through the global WordPress.com cloud. +* Enhancement: Infinite Scroll: Better/faster browsing by pulling the next set of posts into view automatically when the reader approaches the bottom of the page. +* Enhancement: Open Graph: Provides more detailed information about your posts to social networks. +* Enhancement: JSON API: New parameters for creating and viewing posts. +* Enhancement: Improved compatibility for the upcoming WordPress 3.5. +* Bug Fix: Sharing: When you set your sharing buttons to use icon, text, or icon + text mode, the Google+ button will display accordingly. +* Bug Fix: Gravatar Profile Widget: Allow basic HTML to be displayed. +* Bug Fix: Twitter Widget: Error handling fixes. +* Bug Fix: Sharing: Improved theme compatibility +* Bug Fix: JSON API: Fixed error when creating some posts in some versions of PHP. + += 1.9.2 = +* Bug Fix: Only sync options on upgrade once. + += 1.9.1 = +* Enhancement: Notifications feature is enabled for logged-out users when the module is active & the toolbar is shown by another plugin. +* Bug Fix: Use proper CDN addresses to avoid SSL cert issues. +* Bug Fix: Prioritize syncing comments over deleting comments on WordPress.com. Fixes comment notifications marked as spam appearing to be trashed. + += 1.9 = +* Enhancement: Notifications: Display Notifications in the toolbar and support reply/moderation of comment notifications. +* Enhancement: Mobile Push Notifications: Added support for mobile push notifications of new comments for users that linked their accounts to WordPress.com accounts. +* Enhancement: JSON API: Allows applications to send API requests via WordPress.com (see [the docs](http://developer.wordpress.com/docs/api/) ) +* Enhancement: Sync: Modules (that require the data) sync full Post/Comment to ensure consistent data on WP.com (eg Stats) +* Enhancement: Sync: Improve syncing of site options to WP.com +* Enhancement: Sync: Sync attachment parents to WP.com +* Enhancement: Sync: Add signing of WP.com user ids for Jetpack Comments +* Enhancement: Sync: Mark and obfuscate private posts. +* Enhancement: Privacy: Default disable enhanced-distribution and json-api modules if site appears to be private. +* Enhancement: Custom CSS: allow applying Custom CSS to mobile theme. +* Enhancement: Sharing: On HTTPS pageloads, load as much of the sharing embeds as possible from HTTPS URLs. +* Enhancement: Contact Form: Overhaul of the contact form code to fix incompatibilites with other plugins. +* Bug Fix: Only allow users with manage_options permission to enable/disable modules +* Bug Fix: Custom CSS: allow '/' in media query units; e.g. (-o-min-device-pixel-ratio: 3/2) +* Bug Fix: Custom CSS: leave comments alone in CSS when editing but minify on the frontend +* Bug Fix: Sharing: Keep "more" pane open so Google+ Button isn't obscured +* Bug Fix: Carousel: Make sure the original size is used, even when it is exceedingly large. +* Bug Fix: Exclude iPad from Twitter on iPhone mobile browsing +* Bug Fix: Sync: On .org user role changes synchronize the change to .com +* Bug Fix: Contact Form: Fix a bug where some web hosts would reject mail from the contact form due to email address spoofing. + += 1.8.3 = +* Bug Fix: Subscriptions: Fix a bug where subscriptions were not being sent from the blog. +* Bug Fix: Twitter: Fix a bug where the Twitter username was being saved as blank. +* Bug Fix: Fix a bug where Contact Form notification emails were not being sent. + += 1.8.2 = +* Bug Fix: Subscriptions: Fix a bug where subscriptions were not sent for posts and comments written by some authors. +* Bug Fix: Widgets: Fix CSS that was uglifying some themes (like P2). +* Bug Fix: Widgets: Improve Top Posts and Pages styling. +* Bug Fix: Custom CSS: Make the default "Welcome" message translatable. +* Bug Fix: Fix Lithuanian translation. + += 1.8.1 = +* Bug Fix: Stats: Fixed a bug preventing some users from viewing stats. +* Bug Fix: Mobile Theme: Fixed some disabled toolbar buttons. +* Bug Fix: Top Posts widget: Fixed a bug preventing the usage of the Top Posts widget. +* Bug Fix: Mobile Theme: Fixed a bug that broke some sites when the Subscriptions module was not enabled and the Mobile Theme module was enabled. +* Bug Fix: Mobile Theme: Made mobile app promos in the Mobile Theme footer opt-in. +* Bug Fix: Twitter Widget: A fix to prevent malware warnings. +* Bug Fix: Mobile Theme: Fixed a bug that caused errors for some users with custom header images. + += 1.8 = +* Enhancement: Mobile Theme: Automatically serve a slimmed down version of your site to users on mobile devices. +* Enhancement: Multiuser: Allow multiple users to link their accounts to WordPress.com accounts. +* Enhancement: Custom CSS: Added support for object-fit, object-position, transition, and filter properties. +* Enhancement: Twitter Widget: Added Follow button +* Enhancement: Widgets: Added Top Posts and Pages widget +* Enhancement: Mobile Push Notifications: Added support for mobile push notifications on new comments. +* Enhancement: VideoPress: Shortcodes now support the HD option, for default HD playback. +* Bug Fix: Twitter Widget: Fixed tweet permalinks in the Twitter widget +* Bug Fix: Custom CSS: @import rules and external images are no longer stripped out of custom CSS +* Bug Fix: Custom CSS: Fixed warnings and notices displayed in debug mode +* Bug Fix: Sharing: Fixed double-encoding of image URLs +* Bug Fix: Sharing: Fix Google +1 button HTML validation issues (again :)) +* Bug Fix: Gravatar Profile Widget: Reduce size of header margins + += 1.7 = +* Enhancement: CSS Editor: Customize your site's design without modifying your theme. +* Enhancement: Comments: Submit the comment within the iframe. No more full page load to jetpack.wordpress.com. +* Enhancement: Sharing: Share counts for Twitter, Facebook, LinkedIn +* Enhancement: Sharing: Improve styling +* Enhancement: Sharing: Add support for ReCaptcha +* Enhancement: Sharing: Better extensability through filters +* Enhancement: Widgets: Twitter: Attempt to reduce errors by storing a long lasting copy of the data. Thanks, kareldonk :) +* Regression Fix: Sharing: Properly store and display the sharing label option's default value. +* Bug Fix: Contact Form: remove worse-than-useless nonce. +* Bug Fix: Subscriptions: remove worse-than-useless nonce. +* Bug Fix: Sharing: Don't show sharing buttons twice on attachment pages. +* Bug Fix: Sharing: Increase width of Spanish Like button for Facebook. +* Bug Fix: Sharing: Use the correct URL to the throbber. +* Bug Fix: Stats: Fix notice about undefined variable $alt +* Bug Fix: Subscriptions: Make Subscriptions module obey the settings of the Settngs -> Discussion checkboxes for Follow Blog/Comments +* Bug Fix: Shortcodes: VideoPress: Compatibility with the latest version of VideoPress +* Bug Fix: Shortcodes: Audio: Include JS File for HTML5 audio player +* Bug Fix: Hovercards: Improve cache handling. +* Bug Fix: Widgets: Gravatar Profle: Correctly display service icons in edge cases. +* Bug Fix: Widgets: Gravatar Profle: Prevent ugly "flash" of too-large image when page first loads on some sites +* Bug Fix: Carousel: CSS Compatibility with more themes. + = 1.6.1 = * Bug Fix: Prevent Fatal error under certain conditions in sharing module * Bug Fix: Add cachebuster to sharing.css @@ -135,7 +372,7 @@ Use [shortcodes](http://support.wordpress.com/shortcodes/) to embed your media. = 1.3.2 = * Bug Fix: Fix Jetpack menu so that Akismet and VaultPress submenus show up. -= 1.3.1 = += 1.3.1 = * Enhancement: Add a new widget, the Facebook Likebox * Bug Fix: Sharing: Sharing buttons can now be used on custom post types. * Bug Fix: Contact Forms: Make Contact Forms widget shortcode less aggressive about the shortcodes it converts. |