Archived
1
0
Fork 0

initial commit

This commit is contained in:
Henrik Hautakoski 2017-09-01 17:10:27 +02:00
commit e869a1cab4
107 changed files with 9029 additions and 0 deletions

27
.gitattributes vendored Normal file
View file

@ -0,0 +1,27 @@
# normalize these files. Always LF on checkout and always LF on checkin
.gitignore eol=lf
.gitattributes eol=lf
*.php text eol=lf diff=php
*.phtml text eol=lf diff=html
*.volt text eol=lf diff=html
*.html text eol=lf diff=html
*.sh text eol=lf
*.ini text eol=lf
*.xml text eol=lf
*.js text eol=lf
*.json text eol=lf
*.css text eol=lf
*.less text eol=lf
*.sql text eol=lf
*.txt text eol=lf
# Binary files, should never be normalized.
*.exe binary
*.dll binary
*.so binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
.idea/
app/cache/
node_modules/
vendor/
*.lock
app/data/*
app/config/conf.local.yml
app/log.txt
# Ignore transpiled files
/public/css/application.min.css
/public/js/

80
Gruntfile.js Normal file
View file

@ -0,0 +1,80 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
sitecss: {
files: 'app/assets/less/**/*.less',
tasks: 'less:dev'
}
},
less: {
dev: {
files: {
'public/css/application.min.css': 'app/assets/less/application.less'
}
},
production: {
options: {
plugins : [
new (require('less-plugin-clean-css'))({compatibility:'ie8'})
]
},
files: {
'public/css/application.min.css': 'app/assets/less/application.less'
}
}
},
uglify: {
options: {},
jquery: {
files: {
'public/js/jquery-3.0.0.min.js' : [
'node_modules/jquery/dist/jquery.js'
]
}
},
bootstrap: {
files: {
'public/js/bootstrap.min.js': [
'app/assets/js/bootstrap/tooltip.js',
'app/assets/js/bootstrap/affix.js',
'app/assets/js/bootstrap/alert.js',
'app/assets/js/bootstrap/button.js',
'app/assets/js/bootstrap/carousel.js',
'app/assets/js/bootstrap/collapse.js',
'app/assets/js/bootstrap/dropdown.js',
'app/assets/js/bootstrap/modal.js',
'app/assets/js/bootstrap/popover.js',
'app/assets/js/bootstrap/scrollspy.js',
'app/assets/js/bootstrap/tab.js',
'app/assets/js/bootstrap/transition.js'
]
}
}
}
});
// Load grunt plugins.
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('js', ['uglify']);
var target = grunt.option('target') || 'dev';
console.log("Building for: " + target);
grunt.registerTask('less-compile', ['less:' + target]);
// Default task(s).
grunt.registerTask('default', ['less-compile', 'js']);
if (target == 'dev') {
grunt.registerTask('watchless', ['watch:sitecss']);
}
};

View file

@ -0,0 +1,42 @@
{
"disallowEmptyBlocks": true,
"disallowKeywords": ["with"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowMultipleVarDecl": true,
"disallowQuotedKeysInObjects": "allButReserved",
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforeBinaryOperators": [","],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true },
"disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true },
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireCapitalizedConstructors": true,
"requireCommaBeforeLineBreak": true,
"requireDollarBeforejQueryAssignment": true,
"requireDotNotation": true,
"requireLineFeedAtFileEnd": true,
"requirePaddingNewLinesAfterUseStrict": true,
"requirePaddingNewLinesBeforeExport": true,
"requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpaceAfterLineComment": true,
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
"requireSpaceBetweenArguments": true,
"requireSpacesInAnonymousFunctionExpression": { "beforeOpeningCurlyBrace": true, "beforeOpeningRoundBrace": true },
"requireSpacesInConditionalExpression": true,
"requireSpacesInForStatement": true,
"requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true },
"requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
"requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true },
"requireSpacesInsideObjectBrackets": "allButNested",
"validateAlignedFunctionParameters": true,
"validateIndentation": 2,
"validateLineBreaks": "LF",
"validateNewlineAfterArrayElements": true,
"validateQuoteMarks": "'"
}

View file

@ -0,0 +1,15 @@
{
"asi" : true,
"browser" : true,
"eqeqeq" : false,
"eqnull" : true,
"es3" : true,
"expr" : true,
"jquery" : true,
"latedef" : true,
"laxbreak" : true,
"nonbsp" : true,
"strict" : true,
"undef" : true,
"unused" : true
}

View file

@ -0,0 +1,162 @@
/* ========================================================================
* Bootstrap: affix.js v3.3.6
* http://getbootstrap.com/javascript/#affix
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// AFFIX CLASS DEFINITION
// ======================
var Affix = function (element, options) {
this.options = $.extend({}, Affix.DEFAULTS, options)
this.$target = $(this.options.target)
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element)
this.affixed = null
this.unpin = null
this.pinnedOffset = null
this.checkPosition()
}
Affix.VERSION = '3.3.6'
Affix.RESET = 'affix affix-top affix-bottom'
Affix.DEFAULTS = {
offset: 0,
target: window
}
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
var targetHeight = this.$target.height()
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
if (this.affixed == 'bottom') {
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
}
var initializing = this.affixed == null
var colliderTop = initializing ? scrollTop : position.top
var colliderHeight = initializing ? targetHeight : height
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
return false
}
Affix.prototype.getPinnedOffset = function () {
if (this.pinnedOffset) return this.pinnedOffset
this.$element.removeClass(Affix.RESET).addClass('affix')
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
return (this.pinnedOffset = position.top - scrollTop)
}
Affix.prototype.checkPositionWithEventLoop = function () {
setTimeout($.proxy(this.checkPosition, this), 1)
}
Affix.prototype.checkPosition = function () {
if (!this.$element.is(':visible')) return
var height = this.$element.height()
var offset = this.options.offset
var offsetTop = offset.top
var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height())
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
if (this.affixed != affix) {
if (this.unpin != null) this.$element.css('top', '')
var affixType = 'affix' + (affix ? '-' + affix : '')
var e = $.Event(affixType + '.bs.affix')
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
this.affixed = affix
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
this.$element
.removeClass(Affix.RESET)
.addClass(affixType)
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
}
if (affix == 'bottom') {
this.$element.offset({
top: scrollHeight - height - offsetBottom
})
}
}
// AFFIX PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.affix')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.affix
$.fn.affix = Plugin
$.fn.affix.Constructor = Affix
// AFFIX NO CONFLICT
// =================
$.fn.affix.noConflict = function () {
$.fn.affix = old
return this
}
// AFFIX DATA-API
// ==============
$(window).on('load', function () {
$('[data-spy="affix"]').each(function () {
var $spy = $(this)
var data = $spy.data()
data.offset = data.offset || {}
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
if (data.offsetTop != null) data.offset.top = data.offsetTop
Plugin.call($spy, data)
})
})
}(jQuery);

View file

@ -0,0 +1,94 @@
/* ========================================================================
* Bootstrap: alert.js v3.3.6
* http://getbootstrap.com/javascript/#alerts
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// ALERT CLASS DEFINITION
// ======================
var dismiss = '[data-dismiss="alert"]'
var Alert = function (el) {
$(el).on('click', dismiss, this.close)
}
Alert.VERSION = '3.3.6'
Alert.TRANSITION_DURATION = 150
Alert.prototype.close = function (e) {
var $this = $(this)
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = $(selector)
if (e) e.preventDefault()
if (!$parent.length) {
$parent = $this.closest('.alert')
}
$parent.trigger(e = $.Event('close.bs.alert'))
if (e.isDefaultPrevented()) return
$parent.removeClass('in')
function removeElement() {
// detach from parent, fire event then clean up data
$parent.detach().trigger('closed.bs.alert').remove()
}
$.support.transition && $parent.hasClass('fade') ?
$parent
.one('bsTransitionEnd', removeElement)
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
removeElement()
}
// ALERT PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.alert')
if (!data) $this.data('bs.alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.alert
$.fn.alert = Plugin
$.fn.alert.Constructor = Alert
// ALERT NO CONFLICT
// =================
$.fn.alert.noConflict = function () {
$.fn.alert = old
return this
}
// ALERT DATA-API
// ==============
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
}(jQuery);

View file

@ -0,0 +1,120 @@
/* ========================================================================
* Bootstrap: button.js v3.3.6
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// BUTTON PUBLIC CLASS DEFINITION
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false
}
Button.VERSION = '3.3.6'
Button.DEFAULTS = {
loadingText: 'loading...'
}
Button.prototype.setState = function (state) {
var d = 'disabled'
var $el = this.$element
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
state += 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
// push to event loop to allow forms to submit
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d)
}
}, this), 0)
}
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false
$parent.find('.active').removeClass('active')
this.$element.addClass('active')
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
this.$element.toggleClass('active')
}
$input.prop('checked', this.$element.hasClass('active'))
if (changed) $input.trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
this.$element.toggleClass('active')
}
}
// BUTTON PLUGIN DEFINITION
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.button')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// BUTTON NO CONFLICT
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
}
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault()
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
})
}(jQuery);

View file

@ -0,0 +1,237 @@
/* ========================================================================
* Bootstrap: carousel.js v3.3.6
* http://getbootstrap.com/javascript/#carousel
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CAROUSEL CLASS DEFINITION
// =========================
var Carousel = function (element, options) {
this.$element = $(element)
this.$indicators = this.$element.find('.carousel-indicators')
this.options = options
this.paused = null
this.sliding = null
this.interval = null
this.$active = null
this.$items = null
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
.on('mouseenter.bs.carousel', $.proxy(this.pause, this))
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
}
Carousel.VERSION = '3.3.6'
Carousel.TRANSITION_DURATION = 600
Carousel.DEFAULTS = {
interval: 5000,
pause: 'hover',
wrap: true,
keyboard: true
}
Carousel.prototype.keydown = function (e) {
if (/input|textarea/i.test(e.target.tagName)) return
switch (e.which) {
case 37: this.prev(); break
case 39: this.next(); break
default: return
}
e.preventDefault()
}
Carousel.prototype.cycle = function (e) {
e || (this.paused = false)
this.interval && clearInterval(this.interval)
this.options.interval
&& !this.paused
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
return this
}
Carousel.prototype.getItemIndex = function (item) {
this.$items = item.parent().children('.item')
return this.$items.index(item || this.$active)
}
Carousel.prototype.getItemForDirection = function (direction, active) {
var activeIndex = this.getItemIndex(active)
var willWrap = (direction == 'prev' && activeIndex === 0)
|| (direction == 'next' && activeIndex == (this.$items.length - 1))
if (willWrap && !this.options.wrap) return active
var delta = direction == 'prev' ? -1 : 1
var itemIndex = (activeIndex + delta) % this.$items.length
return this.$items.eq(itemIndex)
}
Carousel.prototype.to = function (pos) {
var that = this
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
if (pos > (this.$items.length - 1) || pos < 0) return
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
if (activeIndex == pos) return this.pause().cycle()
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
}
Carousel.prototype.pause = function (e) {
e || (this.paused = true)
if (this.$element.find('.next, .prev').length && $.support.transition) {
this.$element.trigger($.support.transition.end)
this.cycle(true)
}
this.interval = clearInterval(this.interval)
return this
}
Carousel.prototype.next = function () {
if (this.sliding) return
return this.slide('next')
}
Carousel.prototype.prev = function () {
if (this.sliding) return
return this.slide('prev')
}
Carousel.prototype.slide = function (type, next) {
var $active = this.$element.find('.item.active')
var $next = next || this.getItemForDirection(type, $active)
var isCycling = this.interval
var direction = type == 'next' ? 'left' : 'right'
var that = this
if ($next.hasClass('active')) return (this.sliding = false)
var relatedTarget = $next[0]
var slideEvent = $.Event('slide.bs.carousel', {
relatedTarget: relatedTarget,
direction: direction
})
this.$element.trigger(slideEvent)
if (slideEvent.isDefaultPrevented()) return
this.sliding = true
isCycling && this.pause()
if (this.$indicators.length) {
this.$indicators.find('.active').removeClass('active')
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
$nextIndicator && $nextIndicator.addClass('active')
}
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
if ($.support.transition && this.$element.hasClass('slide')) {
$next.addClass(type)
$next[0].offsetWidth // force reflow
$active.addClass(direction)
$next.addClass(direction)
$active
.one('bsTransitionEnd', function () {
$next.removeClass([type, direction].join(' ')).addClass('active')
$active.removeClass(['active', direction].join(' '))
that.sliding = false
setTimeout(function () {
that.$element.trigger(slidEvent)
}, 0)
})
.emulateTransitionEnd(Carousel.TRANSITION_DURATION)
} else {
$active.removeClass('active')
$next.addClass('active')
this.sliding = false
this.$element.trigger(slidEvent)
}
isCycling && this.cycle()
return this
}
// CAROUSEL PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.carousel')
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
var action = typeof option == 'string' ? option : options.slide
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
if (typeof option == 'number') data.to(option)
else if (action) data[action]()
else if (options.interval) data.pause().cycle()
})
}
var old = $.fn.carousel
$.fn.carousel = Plugin
$.fn.carousel.Constructor = Carousel
// CAROUSEL NO CONFLICT
// ====================
$.fn.carousel.noConflict = function () {
$.fn.carousel = old
return this
}
// CAROUSEL DATA-API
// =================
var clickHandler = function (e) {
var href
var $this = $(this)
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
if (!$target.hasClass('carousel')) return
var options = $.extend({}, $target.data(), $this.data())
var slideIndex = $this.attr('data-slide-to')
if (slideIndex) options.interval = false
Plugin.call($target, options)
if (slideIndex) {
$target.data('bs.carousel').to(slideIndex)
}
e.preventDefault()
}
$(document)
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
$(window).on('load', function () {
$('[data-ride="carousel"]').each(function () {
var $carousel = $(this)
Plugin.call($carousel, $carousel.data())
})
})
}(jQuery);

View file

@ -0,0 +1,211 @@
/* ========================================================================
* Bootstrap: collapse.js v3.3.6
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
this.$parent = this.getParent()
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
}
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.6'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
if (actives && actives.length) {
activesData = actives.data('bs.collapse')
if (activesData && activesData.transitioning) return
}
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true)
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
this.transitioning = 1
var complete = function () {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('')
this.transitioning = 0
this.$element
.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false)
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
this.transitioning = 1
var complete = function () {
this.transitioning = 0
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
Collapse.prototype.getParent = function () {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
}, this))
.end()
}
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
$element.attr('aria-expanded', isOpen)
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
}
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target')
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
return $(target)
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.collapse
$.fn.collapse = Plugin
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault()
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
Plugin.call($target, option)
})
}(jQuery);

View file

@ -0,0 +1,165 @@
/* ========================================================================
* Bootstrap: dropdown.js v3.3.6
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// DROPDOWN CLASS DEFINITION
// =========================
var backdrop = '.dropdown-backdrop'
var toggle = '[data-toggle="dropdown"]'
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.6'
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
clearMenus()
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this
.trigger('focus')
.attr('aria-expanded', 'true')
$parent
.toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget))
}
return false
}
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
var $this = $(this)
e.preventDefault()
e.stopPropagation()
if ($this.is('.disabled, :disabled')) return
var $parent = getParent($this)
var isActive = $parent.hasClass('open')
if (!isActive && e.which != 27 || isActive && e.which == 27) {
if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click')
}
var desc = ' li:not(.disabled):visible a'
var $items = $parent.find('.dropdown-menu' + desc)
if (!$items.length) return
var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items.eq(index).trigger('focus')
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown')
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
var old = $.fn.dropdown
$.fn.dropdown = Plugin
$.fn.dropdown.Constructor = Dropdown
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old
return this
}
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
}(jQuery);

View file

@ -0,0 +1,337 @@
/* ========================================================================
* Bootstrap: modal.js v3.3.6
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// MODAL CLASS DEFINITION
// ======================
var Modal = function (element, options) {
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.$dialog = this.$element.find('.modal-dialog')
this.$backdrop = null
this.isShown = null
this.originalBodyPad = null
this.scrollbarWidth = 0
this.ignoreBackdropClick = false
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
}
}
Modal.VERSION = '3.3.6'
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
Modal.DEFAULTS = {
backdrop: true,
keyboard: true,
show: true
}
Modal.prototype.toggle = function (_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget)
}
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
this.$element.trigger(e)
if (this.isShown || e.isDefaultPrevented()) return
this.isShown = true
this.checkScrollbar()
this.setScrollbar()
this.$body.addClass('modal-open')
this.escape()
this.resize()
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
})
})
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position
}
that.$element
.show()
.scrollTop(0)
that.adjustDialog()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
that.enforceFocus()
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
transition ?
that.$dialog // wait for modal to slide in
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
e = $.Event('hide.bs.modal')
this.$element.trigger(e)
if (!this.isShown || e.isDefaultPrevented()) return
this.isShown = false
this.escape()
this.resize()
$(document).off('focusin.bs.modal')
this.$element
.removeClass('in')
.off('click.dismiss.bs.modal')
.off('mouseup.dismiss.bs.modal')
this.$dialog.off('mousedown.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal()
}
Modal.prototype.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.trigger('focus')
}
}, this))
}
Modal.prototype.escape = function () {
if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide()
}, this))
} else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal')
}
}
Modal.prototype.resize = function () {
if (this.isShown) {
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
} else {
$(window).off('resize.bs.modal')
}
}
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.$body.removeClass('modal-open')
that.resetAdjustments()
that.resetScrollbar()
that.$element.trigger('hidden.bs.modal')
})
}
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $(document.createElement('div'))
.addClass('modal-backdrop ' + animate)
.appendTo(this.$body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (this.ignoreBackdropClick) {
this.ignoreBackdropClick = false
return
}
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus()
: this.hide()
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
if (!callback) return
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
} else if (callback) {
callback()
}
}
// these following methods are used to handle overflowing modals
Modal.prototype.handleUpdate = function () {
this.adjustDialog()
}
Modal.prototype.adjustDialog = function () {
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
this.$element.css({
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
})
}
Modal.prototype.resetAdjustments = function () {
this.$element.css({
paddingLeft: '',
paddingRight: ''
})
}
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
var documentElementRect = document.documentElement.getBoundingClientRect()
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
this.scrollbarWidth = this.measureScrollbar()
}
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
this.originalBodyPad = document.body.style.paddingRight || ''
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', this.originalBodyPad)
}
Modal.prototype.measureScrollbar = function () { // thx walsh
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
this.$body.append(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
this.$body[0].removeChild(scrollDiv)
return scrollbarWidth
}
// MODAL PLUGIN DEFINITION
// =======================
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
}
var old = $.fn.modal
$.fn.modal = Plugin
$.fn.modal.Constructor = Modal
// MODAL NO CONFLICT
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old
return this
}
// MODAL DATA-API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
if ($this.is('a')) e.preventDefault()
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
Plugin.call($target, option, this)
})
}(jQuery);

View file

@ -0,0 +1,108 @@
/* ========================================================================
* Bootstrap: popover.js v3.3.6
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// POPOVER PUBLIC CLASS DEFINITION
// ===============================
var Popover = function (element, options) {
this.init('popover', element, options)
}
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.VERSION = '3.3.6'
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
trigger: 'click',
content: '',
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
})
// NOTE: POPOVER EXTENDS tooltip.js
// ================================
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
Popover.prototype.constructor = Popover
Popover.prototype.getDefaults = function () {
return Popover.DEFAULTS
}
Popover.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
var content = this.getContent()
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
](content)
$tip.removeClass('fade top bottom left right in')
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
}
Popover.prototype.hasContent = function () {
return this.getTitle() || this.getContent()
}
Popover.prototype.getContent = function () {
var $e = this.$element
var o = this.options
return $e.attr('data-content')
|| (typeof o.content == 'function' ?
o.content.call($e[0]) :
o.content)
}
Popover.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
}
// POPOVER PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.popover')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.popover
$.fn.popover = Plugin
$.fn.popover.Constructor = Popover
// POPOVER NO CONFLICT
// ===================
$.fn.popover.noConflict = function () {
$.fn.popover = old
return this
}
}(jQuery);

View file

@ -0,0 +1,172 @@
/* ========================================================================
* Bootstrap: scrollspy.js v3.3.6
* http://getbootstrap.com/javascript/#scrollspy
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// SCROLLSPY CLASS DEFINITION
// ==========================
function ScrollSpy(element, options) {
this.$body = $(document.body)
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
this.selector = (this.options.target || '') + ' .nav li > a'
this.offsets = []
this.targets = []
this.activeTarget = null
this.scrollHeight = 0
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
this.refresh()
this.process()
}
ScrollSpy.VERSION = '3.3.6'
ScrollSpy.DEFAULTS = {
offset: 10
}
ScrollSpy.prototype.getScrollHeight = function () {
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
}
ScrollSpy.prototype.refresh = function () {
var that = this
var offsetMethod = 'offset'
var offsetBase = 0
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
if (!$.isWindow(this.$scrollElement[0])) {
offsetMethod = 'position'
offsetBase = this.$scrollElement.scrollTop()
}
this.$body
.find(this.selector)
.map(function () {
var $el = $(this)
var href = $el.data('target') || $el.attr('href')
var $href = /^#./.test(href) && $(href)
return ($href
&& $href.length
&& $href.is(':visible')
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
})
.sort(function (a, b) { return a[0] - b[0] })
.each(function () {
that.offsets.push(this[0])
that.targets.push(this[1])
})
}
ScrollSpy.prototype.process = function () {
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
var scrollHeight = this.getScrollHeight()
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
var offsets = this.offsets
var targets = this.targets
var activeTarget = this.activeTarget
var i
if (this.scrollHeight != scrollHeight) {
this.refresh()
}
if (scrollTop >= maxScroll) {
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
}
if (activeTarget && scrollTop < offsets[0]) {
this.activeTarget = null
return this.clear()
}
for (i = offsets.length; i--;) {
activeTarget != targets[i]
&& scrollTop >= offsets[i]
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
&& this.activate(targets[i])
}
}
ScrollSpy.prototype.activate = function (target) {
this.activeTarget = target
this.clear()
var selector = this.selector +
'[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]'
var active = $(selector)
.parents('li')
.addClass('active')
if (active.parent('.dropdown-menu').length) {
active = active
.closest('li.dropdown')
.addClass('active')
}
active.trigger('activate.bs.scrollspy')
}
ScrollSpy.prototype.clear = function () {
$(this.selector)
.parentsUntil(this.options.target, '.active')
.removeClass('active')
}
// SCROLLSPY PLUGIN DEFINITION
// ===========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.scrollspy')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.scrollspy
$.fn.scrollspy = Plugin
$.fn.scrollspy.Constructor = ScrollSpy
// SCROLLSPY NO CONFLICT
// =====================
$.fn.scrollspy.noConflict = function () {
$.fn.scrollspy = old
return this
}
// SCROLLSPY DATA-API
// ==================
$(window).on('load.bs.scrollspy.data-api', function () {
$('[data-spy="scroll"]').each(function () {
var $spy = $(this)
Plugin.call($spy, $spy.data())
})
})
}(jQuery);

View file

@ -0,0 +1,155 @@
/* ========================================================================
* Bootstrap: tab.js v3.3.6
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TAB CLASS DEFINITION
// ====================
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.6'
Tab.TRANSITION_DURATION = 150
Tab.prototype.show = function () {
var $this = this.element
var $ul = $this.closest('ul:not(.dropdown-menu)')
var selector = $this.data('target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
if ($this.parent('li').hasClass('active')) return
var $previous = $ul.find('.active:last a')
var hideEvent = $.Event('hide.bs.tab', {
relatedTarget: $this[0]
})
var showEvent = $.Event('show.bs.tab', {
relatedTarget: $previous[0]
})
$previous.trigger(hideEvent)
$this.trigger(showEvent)
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
var $target = $(selector)
this.activate($this.closest('li'), $ul)
this.activate($target, $target.parent(), function () {
$previous.trigger({
type: 'hidden.bs.tab',
relatedTarget: $this[0]
})
$this.trigger({
type: 'shown.bs.tab',
relatedTarget: $previous[0]
})
})
}
Tab.prototype.activate = function (element, container, callback) {
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if (element.parent('.dropdown-menu').length) {
element
.closest('li.dropdown')
.addClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
}
callback && callback()
}
$active.length && transition ?
$active
.one('bsTransitionEnd', next)
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
next()
$active.removeClass('in')
}
// TAB PLUGIN DEFINITION
// =====================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tab')
if (!data) $this.data('bs.tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tab
$.fn.tab = Plugin
$.fn.tab.Constructor = Tab
// TAB NO CONFLICT
// ===============
$.fn.tab.noConflict = function () {
$.fn.tab = old
return this
}
// TAB DATA-API
// ============
var clickHandler = function (e) {
e.preventDefault()
Plugin.call($(this), 'show')
}
$(document)
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
}(jQuery);

View file

@ -0,0 +1,514 @@
/* ========================================================================
* Bootstrap: tooltip.js v3.3.6
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TOOLTIP PUBLIC CLASS DEFINITION
// ===============================
var Tooltip = function (element, options) {
this.type = null
this.options = null
this.enabled = null
this.timeout = null
this.hoverState = null
this.$element = null
this.inState = null
this.init('tooltip', element, options)
}
Tooltip.VERSION = '3.3.6'
Tooltip.TRANSITION_DURATION = 150
Tooltip.DEFAULTS = {
animation: true,
placement: 'top',
selector: false,
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
container: false,
viewport: {
selector: 'body',
padding: 0
}
}
Tooltip.prototype.init = function (type, element, options) {
this.enabled = true
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
this.inState = { click: false, hover: false, focus: false }
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
}
var triggers = this.options.trigger.split(' ')
for (var i = triggers.length; i--;) {
var trigger = triggers[i]
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (trigger != 'manual') {
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS
}
Tooltip.prototype.getOptions = function (options) {
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay,
hide: options.delay
}
}
return options
}
Tooltip.prototype.getDelegateOptions = function () {
var options = {}
var defaults = this.getDefaults()
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value
})
return options
}
Tooltip.prototype.enter = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
}
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in'
return
}
clearTimeout(self.timeout)
self.hoverState = 'in'
if (!self.options.delay || !self.options.delay.show) return self.show()
self.timeout = setTimeout(function () {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self)
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
}
if (self.isInStateTrue()) return
clearTimeout(self.timeout)
self.hoverState = 'out'
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.timeout = setTimeout(function () {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
Tooltip.prototype.show = function () {
var e = $.Event('show.bs.' + this.type)
if (this.hasContent() && this.enabled) {
this.$element.trigger(e)
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
if (e.isDefaultPrevented() || !inDom) return
var that = this
var $tip = this.tip()
var tipId = this.getUID(this.type)
this.setContent()
$tip.attr('id', tipId)
this.$element.attr('aria-describedby', tipId)
if (this.options.animation) $tip.addClass('fade')
var placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
var autoToken = /\s?auto?\s?/i
var autoPlace = autoToken.test(placement)
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.addClass(placement)
.data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (autoPlace) {
var orgPlacement = placement
var viewportDim = this.getPosition(this.$viewport)
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement
$tip
.removeClass(orgPlacement)
.addClass(placement)
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
this.applyPlacement(calculatedOffset, placement)
var complete = function () {
var prevHoverState = that.hoverState
that.$element.trigger('shown.bs.' + that.type)
that.hoverState = null
if (prevHoverState == 'out') that.leave(that)
}
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
}
}
Tooltip.prototype.applyPlacement = function (offset, placement) {
var $tip = this.tip()
var width = $tip[0].offsetWidth
var height = $tip[0].offsetHeight
// manually read margins because getBoundingClientRect includes difference
var marginTop = parseInt($tip.css('margin-top'), 10)
var marginLeft = parseInt($tip.css('margin-left'), 10)
// we must check for NaN for ie 8/9
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
offset.top += marginTop
offset.left += marginLeft
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
$.offset.setOffset($tip[0], $.extend({
using: function (props) {
$tip.css({
top: Math.round(props.top),
left: Math.round(props.left)
})
}
}, offset), 0)
$tip.addClass('in')
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
}
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
if (delta.left) offset.left += delta.left
else offset.top += delta.top
var isVertical = /top|bottom/.test(placement)
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
$tip.offset(offset)
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
}
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
this.arrow()
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isVertical ? 'top' : 'left', '')
}
Tooltip.prototype.setContent = function () {
var $tip = this.tip()
var title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
Tooltip.prototype.hide = function (callback) {
var that = this
var $tip = $(this.$tip)
var e = $.Event('hide.bs.' + this.type)
function complete() {
if (that.hoverState != 'in') $tip.detach()
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
callback && callback()
}
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$tip.removeClass('in')
$.support.transition && $tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete()
this.hoverState = null
return this
}
Tooltip.prototype.fixTitle = function () {
var $e = this.$element
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
Tooltip.prototype.hasContent = function () {
return this.getTitle()
}
Tooltip.prototype.getPosition = function ($element) {
$element = $element || this.$element
var el = $element[0]
var isBody = el.tagName == 'BODY'
var elRect = el.getBoundingClientRect()
if (elRect.width == null) {
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
}
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
return $.extend({}, elRect, scroll, outerDims, elOffset)
}
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
}
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 }
if (!this.$viewport) return delta
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
var viewportDimensions = this.getPosition(this.$viewport)
if (/right|left/.test(placement)) {
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
if (topEdgeOffset < viewportDimensions.top) { // top overflow
delta.top = viewportDimensions.top - topEdgeOffset
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
}
} else {
var leftEdgeOffset = pos.left - viewportPadding
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
return delta
}
Tooltip.prototype.getTitle = function () {
var title
var $e = this.$element
var o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000)
while (document.getElementById(prefix))
return prefix
}
Tooltip.prototype.tip = function () {
if (!this.$tip) {
this.$tip = $(this.options.template)
if (this.$tip.length != 1) {
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
}
}
return this.$tip
}
Tooltip.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
}
Tooltip.prototype.enable = function () {
this.enabled = true
}
Tooltip.prototype.disable = function () {
this.enabled = false
}
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled
}
Tooltip.prototype.toggle = function (e) {
var self = this
if (e) {
self = $(e.currentTarget).data('bs.' + this.type)
if (!self) {
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
$(e.currentTarget).data('bs.' + this.type, self)
}
}
if (e) {
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
}
Tooltip.prototype.destroy = function () {
var that = this
clearTimeout(this.timeout)
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach()
}
that.$tip = null
that.$arrow = null
that.$viewport = null
})
}
// TOOLTIP PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tooltip')
var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tooltip
$.fn.tooltip = Plugin
$.fn.tooltip.Constructor = Tooltip
// TOOLTIP NO CONFLICT
// ===================
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old
return this
}
}(jQuery);

View file

@ -0,0 +1,59 @@
/* ========================================================================
* Bootstrap: transition.js v3.3.6
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false
var $el = this
$(this).one('bsTransitionEnd', function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
$(function () {
$.support.transition = transitionEnd()
if (!$.support.transition) return
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function (e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
}
}
})
}(jQuery);

View file

@ -0,0 +1,46 @@
// ---------------------------
// Framework
// ---------------------------
@import "vendor/bootstrap-framework";
// Webfonts
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i,700,700i');
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700,700i');
// ---------------------------
// Site
// ---------------------------
// variables & mixins.
@import "variables";
@import "mixins";
// Base
@import "base/global";
@import "base/typography";
// Layout
@import "layout/sections";
@import "layout/navigation";
@import "layout/masthead";
@import "layout/footer";
// Components
@import "components/icon";
@import "components/button";
@import "components/badge";
@import "components/section";
@import "components/pagination";
@import "components/blankslate";
@import "components/request-item";
@import "components/callback-list";
// Views
@import "views/landingpage";
@import "views/about";
@import "views/login";
// Plugins
@import "vendor/ionicons/ionicons";

View file

@ -0,0 +1,5 @@
html {
position: relative;
min-height: 100%;
}

View file

@ -0,0 +1,11 @@
// Section header (part of .section)
.hdr-center {
//&:extend(.text-center);
display: inline-block;
padding: 20px 80px;
margin: 30px auto;
border-bottom: 2px solid @border-color;
}

View file

@ -0,0 +1,18 @@
.badge {
display: inline-block;
padding: .2em .4em;
line-height: 1;
text-align: center;
border-radius: 2px;
vertical-align: baseline;
&:empty {
display:none;
}
}
.badge-primary {
color: white;
background-color: @brand-color;
}

View file

@ -0,0 +1,36 @@
// ----------------------------------
// Base style.
// ----------------------------------
.blankslate {
border-radius: 4px;
.box-shadow(inset 0 0 4px rgba(0,0,0, .1));
border: solid 1px @blankslate-border;
color: @blankslate-color;
background-color: @blankslate-bg;
padding: @blankslate-spacing;
h3, p {
text-align: center;
}
:last-child {
margin-bottom: 0;
}
}
// ----------------------------------
// Variants
// ----------------------------------
// Sizes
// ----------------------------------
.blankslate-sm {
padding: @blankslate-spacing-sm;
}

View file

@ -0,0 +1,68 @@
// ----------------------------------
// Base style.
// ----------------------------------
.button {
display: inline-block;
padding: 0.5rem 2rem;
cursor: pointer;
vertical-align: middle;
// Resets for <button> elements.
border: 1px solid transparent;
transition: all .2s ease-in-out;
&:active,
&:hover {
color: inherit;
text-decoration: none;
}
&.disabled {
cursor: not-allowed;
}
}
// ----------------------------------
// Variants
// ----------------------------------
// Round button
// ----------------------------------
.button-round {
padding: 0;
.button-circle(40px);
}
// Colors
// ----------------------------------
.button-default { .button-variant(@text-color, @button-bg-color); }
.button-primary { .button-variant(@button-primary-color, @button-primary-bg); }
.button-brand { .button-variant(@button-brand-color, @button-brand-bg); }
.button-light { .button-variant(@text-light-color, @button-bg-color, @button-hover-color); }
.button-github { .button-variant(@button-github-color, @button-github-bg); }
.button-google { .button-variant(@button-google-color, @button-google-bg); }
// Outline
// ----------------------------------
.button-outline-primary { .button-outline-variant(@text-color, @button-bg-color); }
// Sizes
// ----------------------------------
.button-large { font-size: @text-size-large; }
.button-small { font-size: 0.6em; }
// Block button
// ----------------------------------
.button-block {
display: block;
width: 100%;
}

View file

@ -0,0 +1,79 @@
.callback-list-item {
position: relative;
&:extend(.clearfix all);
padding: .6em 1em;
&:hover {
background: @lightest-gray;
}
// Borders.
// --------
border: solid @callback-list-border-color @callback-list-border-size;
// Remove top border for all except first child.
&:not(:first-child) {
border-top: 0;
}
// top border radius for first child.
&:first-child {
border-top-left-radius: @callback-list-border-radius;
border-top-right-radius: @callback-list-border-radius;
}
// bottom border radius for first child.
&:last-child {
border-bottom-left-radius: @callback-list-border-radius;
border-bottom-right-radius: @callback-list-border-radius;
}
// Sections.
// ---------
&-header {
margin-bottom: .4em;
}
&-info {
font-size: @font-size-small;
color: @callback-list-info-color;
> span {
display: inline-block;
margin-right: 1.6em;
}
.icon {
.icon-vcenter();
font-size: 20px;
padding: .2em;
}
}
&-name {
margin-right: 1em;
font-size: @callback-list-name-text-size;
color: @callback-list-name-color;
&:hover {
color: @callback-list-name-hover-color;
text-decoration: none;
}
}
&-arrow {
color: #bcbcbc;
position: absolute;
right: .8em;
top: 50%;
> .icon {
font-size: 35px;
transform: translateY(-50%);
}
}
}

View file

@ -0,0 +1,4 @@
.icon {
display: inline-block;
}

View file

@ -0,0 +1,31 @@
.pagination {
&:extend(.nav);
margin-top: 1em;
margin-bottom: 1em;
> li {
display: inline-block;
.middle, a {
padding: .4em .8em;
margin: 0 .4em;
}
a {
&:extend(.button all);
display: block;
border: solid 1px @pagination-border;
color: @pagination-color;
&:hover {
color: @pagination-color;
}
}
&.active a {
background: @pagination-active-bg;
border-color: @pagination-active-border;
}
}
}

View file

@ -0,0 +1,102 @@
.request-list {
&-item {
// Header Section
// --------------
&-header {
display: block;
padding: .5em 1em;
color: @request-item-active-color;
background-color: @request-item-active-background;
border: 1px solid @request-item-active-border-color;
&:hover,
&:active,
&:focus {
color: @request-item-active-color;
text-decoration: none;
}
&.collapsed {
background-color: @request-item-background;
&:hover {
background-color: @request-item-hover-background;
}
&:hover,
&:active,
&:focus {
color: @request-item-color;
}
}
&-index {
display: inline-block;
width: 10%;
}
&-type,
&-size,
&-timestamp {
display: inline-block;
width: 20%;
.icon {
padding: 0 .4em;
}
}
}
// Detail section
// --------------
&-detail {
background-color: @request-detail-background;
border: 1px solid @request-item-border-color;
padding: 1em;
&-headers {
&:extend(.table all);
&:extend(.table-bordered all);
background: white;
&-key {
width: 30%;
}
}
&-body {
margin-bottom: 1em;
}
&-button {
&:extend(.button all);
&:extend(.button-brand all);
display: block;
margin-bottom: 1em;
}
}
// Border overrides
// ----------------
// Remove bottom border from header for all but last request-item.
&:not(:last-child) &-header {
border-bottom: 0;
}
// Remove top border from detail element for last request-item.
&:last-child &-detail {
border-top: 0;
}
// Remove bottom border from detail element for all but last request-item.
&:not(:last-child) &-detail {
border-bottom: 0;
}
}
}

View file

@ -0,0 +1,24 @@
// ---------------------------
// Section
// ---------------------------
.section {
position: relative;
background-color: @section-bg;
padding: @section-padding;
margin-bottom: 60px;
border-radius: 2px;
.box-shadow(@block-shadow-third);
&-header {
text-align: center;
margin-bottom: 15px;
> h1 {
display: inline-block;
border-bottom: 2px solid @border-color;
padding: 20px 80px;
}
}
}

View file

@ -0,0 +1,41 @@
.footer {
&:extend(.container all);
display: flex;
align-items: center;
min-height: @footer-height;
a {
color: @footer-link-color;
font-weight: 700;
&:hover,
&:active {
color: @footer-link-hover-color;
}
}
&-left,
&-right {
.make-xs-column(5);
}
&-middle {
.make-xs-column(2);
}
&-right {
text-align: right;
}
// Top button
&-button-top {
&:extend(.center-block);
&:extend(.button);
&:extend(.button-round);
font-size: 1.5em;
.button-variant(@footer-link-color, white, white);
box-shadow: 0 2px 2px rgba(0, 0, 0, .25);
}
}

View file

@ -0,0 +1,26 @@
.masthead {
&:extend(.container all);
display: block;
text-align: center;
padding-top: 2em;
padding-bottom: 4em;
h1, p {
color: @text-light-color;
text-shadow: @header-text-shadow;
}
h1 {
font-size: 48px;
}
p {
font-size: 22px;
}
.masthead-get-started-button {
margin-top: 2em;
.button-outline-variant(black, white);
}
}

View file

@ -0,0 +1,127 @@
// ----------------------------------
// Navigation
// ----------------------------------
.navigation {
// General styling (for all viewport widths)
&:extend(.clearfix all);
&:extend(.container all);
min-height: @navbar-height;
color: @navbar-link-color;
text-shadow: @navbar-link-text-shadow;
font-size: @font-size-navigation;
// Links inside navigation.
a {
color: @navbar-link-color;
&:hover,
&:active {
color: @navbar-link-hover-color;
text-decoration: none;
}
}
// Menu button
// -------------------
.menu-button {
.button();
.button-variant(white, #242424);
margin-top: 8px;
font-size: 24px;
// Hide for larger view-ports.
@media (min-width: @navbar-breakpoint-min) {
display: none;
}
}
// Menu
// -------------------
&-menu ul {
&:extend(.nav);
> li {
// Inline list for larger viewports.
@media (min-width: @navbar-breakpoint-min) {
display: inline-block;
a {
display: block;
.navbar-vertical-align(@line-height-computed + (@navbar-link-vertical-spacing * 2));
padding: @navbar-link-vertical-spacing 10px @navbar-link-vertical-spacing;
line-height: @line-height-computed;
margin-left: (@navbar-link-spacing / 2);
margin-right: (@navbar-link-spacing / 2);
&:hover {
color: @navbar-link-hover-color;
.box-shadow(0 -@navbar-link-underline-height
0 @navbar-link-hover-underline inset);
}
}
&.active a {
color: @navbar-link-active-color;
.box-shadow(0 -@navbar-link-underline-height
0 @navbar-link-active-underline inset);
}
}
}
// Only for small viewports.
@media (max-width: @navbar-breakpoint-min) {
margin: 1em 0;
> li {
a {
display: block;
padding: .5em 1em;
background: @navbar-background;
&:hover {
background: darken(@navbar-background, 10%);
}
}
&:not(:last-child) {
margin-bottom: 1px;
}
}
}
}
// Always show menu for larger viewport.
@media (min-width: @navbar-breakpoint-min) {
&-menu.collapse {
display: block;
}
}
// User Menu
// -------------------
&-user-menu {
&:extend(.nav);
.pull-right();
margin-top: 18px;
> li {
display: inline-block;
.icon {
margin-right: .2em;
}
&:not(:first-child) {
padding-left: 1em;
}
}
}
}

View file

@ -0,0 +1,43 @@
// ----------------------------------
// Head section
// ----------------------------------
.head-section {
background-color: @brand-color;
background-image: url('@{image-path}/header-pattern.jpg');
margin-bottom: 60px;
.top-section {
background-color: @navbar-background;
}
}
// ----------------------------------
// Content section
// ----------------------------------
.content-section {
&:extend(.container);
margin-bottom: @footer-height;
}
// ----------------------------------
// Footer section
// ----------------------------------
.footer-section {
position: absolute;
bottom: 0;
width: 100%;
color: @footer-text-color;
border-top: 1px solid #bcbcbc;
background-color: @footer-border-color;
background-image: url("@{image-path}/footer-pattern.jpg");
}

View file

@ -0,0 +1,34 @@
@import "mixins/button";
@import "mixins/icon-vcenter";
// Rectangular button.
.rect-button(@color, @hover-color: darken(@color, 20%)) {
border-radius: 0;
border: 2px solid @color;
color: @color;
&:hover {
color: @hover-color;
border-color: @hover-color;
}
}
.button-color(@color, @background-color, @hover-color: darken(@background-color, 10%)) {
&:not(.button-ghost) {
color: @color;
background-color: @background-color;
&:hover {
color: @color;
background-color: @hover-color;
}
}
.button-ghost& {
border: 2px solid @background-color;
color: @background-color;
}
}

View file

@ -0,0 +1,33 @@
.button-variant(@color, @background, @hover: darken(@background, 10%)) {
color: @color;
background-color: @background;
&:hover,
&:active {
color: @color;
background-color: @hover;
}
}
.button-outline-variant(@color, @background) {
box-shadow: inset 0 0 0 2px @background;
color: @background;
&:hover,
&:active {
color: @color;
background-color: @background;
}
}
.button-circle(@width) {
display: block;
text-align: center;
width: @width;
height: @width;
line-height: @width;
border-radius: 50%;
}

View file

@ -0,0 +1,5 @@
.icon-vcenter() {
margin-top: -0.2em;
vertical-align: middle;
}

View file

@ -0,0 +1,173 @@
// ----------------------------------
// Paths
// ----------------------------------
@base-path: '..';
@image-path: '@{base-path}/img';
// ----------------------------------
// General colors.
// ----------------------------------
@blue: #0000ff;
@lightest-gray: #f9f9f9;
@light-grey: #dcdcdc;
@dark-grey: #444;
@darker-grey: #202020;
@black: #000;
@white: #fff;
// ----------------------------------
// Global colors.
// ----------------------------------
@brand-color: #03a9f4;
@brand-info: hsl(210, 70%, 50%);
@foreground-color: white;
//@body-bg: #f3f6fc;
@body-bg: #f5f5f6;
@border-color: @light-grey;
@text-color: #353535;
@text-secondary-color: #757575;
@text-light-color: #f1f1f1;
// Branding colors
@google-color: #db4437;
@github-color: #4183c4;
// ----------------------------------
// Font
// ----------------------------------
@font-size-base: 15px;
@font-size-navigation: 20px;
@text-size-large: 20px;
@font-size-small: 14px;
@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
//@font-family-sans-serif: 'Open Sans', sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
@font-family-base: @font-family-sans-serif;
// ----------------------------------
// Layout
// ----------------------------------
@header-text-shadow: 1px 3px 4px rgba(0, 0, 0, 0.45);
// navigation
@navbar-breakpoint-min: @screen-md-min;
@navbar-height: 60px;
@navbar-link-vertical-spacing: 8px;
@navbar-link-underline-height: 2px;
@navbar-link-spacing: 20px;
@navbar-background: fade(#1d7ed1, 50%);
@navbar-link-text-shadow: 0px 2px 1px rgba(0, 0, 0, 0.3);
@navbar-link-color: white;
@navbar-link-hover-color: lighten(@navbar-link-color, 10%);
@navbar-link-hover-underline: @navbar-link-color;
@navbar-link-active-color: @navbar-link-color;
@navbar-link-active-underline: @navbar-link-color;
@navbar-brand-color: @brand-color;
@navbar-brand-hover-color: darken(@brand-color, 15%);
// Footer
@footer-height: 80px;
@footer-border-color: @border-color;
@footer-text-color: @text-secondary-color;
@footer-link-color: @text-secondary-color;
@footer-link-hover-color: darken(@text-secondary-color, 10%);
// ----------------------------------
// Buttons
// ----------------------------------
@button-bg-color: #f1f1f1;
@button-hover-color: darken(@button-bg-color, 10%);
@button-border: darken(@button-bg-color, 10%);
@button-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12),
0 1px 5px 0 rgba(0, 0, 0, 0.1);
// Variations
@button-primary-color: white;
@button-primary-bg: #5fbc49;
@button-brand-color: white;
@button-brand-bg: @brand-color;
// Company.
@button-google-color: white;
@button-google-bg: @google-color;
@button-google-border: darken(@button-google-bg, 5%);
@button-github-color: white;
@button-github-bg: @github-color;
@button-github-border: darken(@button-github-bg, 5%);
// ----------------------------------
// Shadows
// ----------------------------------
@block-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
@block-shadow-raised: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12);
@block-shadow-third: 0 3px 3px 0 rgba(0, 0, 0, 0.16);
// ----------------------------------
// Blankslate
// ----------------------------------
@blankslate-bg: @well-bg;
@blankslate-color: @text-secondary-color;
@blankslate-border: @well-border;
@blankslate-spacing: 25px 10px;
@blankslate-spacing-sm: 15px 5px;
// ----------------------------------
// Pagination
// ----------------------------------
@pagination-color: @text-color;
@pagination-border: @border-color;
@pagination-hover-color: lighten(@text-color, 25%);
@pagination-active-bg: @brand-color;
@pagination-active-border: darken(@brand-color, 5%);
// ----------------------------------
// Section
// ----------------------------------
@section-bg: white;
@section-border: rgb(200, 200, 200);
@section-padding: 20px;
// ----------------------------------
// Callback list
// ----------------------------------
@callback-list-border-size: 1px;
@callback-list-border-color: @light-grey;
@callback-list-border-radius: 3px;
@callback-list-name-text-size: @font-size-h4;
@callback-list-name-color: @text-color;
@callback-list-name-hover-color: @link-color;
@callback-list-info-color: @text-secondary-color;
// ----------------------------------
// Request list
// ----------------------------------
@request-item-color: @dark-grey;
@request-item-background: transparent;
@request-item-border-color: @light-grey;
@request-item-active-color: @dark-grey;
@request-item-active-background: @lightest-gray;
@request-item-active-border-color: darken(@request-item-active-background, 10%);
@request-item-hover-background: @request-item-active-background;
@request-detail-background: lighten(@brand-color, 45%);

View file

@ -0,0 +1,13 @@
.about {
&:extend(.row all);
&-main {
.make-sm-column(8);
}
&-reference {
.make-sm-column(4);
}
}

View file

@ -0,0 +1,43 @@
// ----------------------------------
// Feature section
// ----------------------------------
.feature-section {
&:extend(.section);
> div + div {
margin-top: 2em;
}
.steps {
&:extend(.row all);
.steps-img {
&:extend(.center-block);
width: 270px;
height: 220px;
}
.steps-step1 .steps-img {
background-image: url('@{image-path}/step1.png');
}
.steps-step2 .steps-img {
background-image: url('@{image-path}/step2.png');
}
.steps-step3 .steps-img {
background-image: url('@{image-path}/step3.png');
}
.steps-step1,
.steps-step2,
.steps-step3 {
.make-md-column(4);
.text-center();
}
}
.call-to-action {
&:extend(.text-center);
}
}

View file

@ -0,0 +1,24 @@
.login-container {
.center-block();
width: 400px;
h3 {
width: 100%;
text-align: center;
}
.oauth > a {
font-size: @font-size-large;
width: 100%;
margin-bottom: 4px;
text-align: center;
.icon {
.icon-vcenter();
font-size: 24px;
margin-right: .2em;
}
}
}

12
app/config/conf.app.yml Normal file
View file

@ -0,0 +1,12 @@
application:
controllersDir : ../app/controllers/
modelsDir : ../app/models/
migrationsDir : ../app/migrations/
viewsDir : ../app/views/
pluginsDir : ../app/plugins/
libraryDir : ../app/library/
formsDir : ../app/forms/
cacheDir : ../app/cache/
sessionDir : ../app/data/sessions/
baseUri : /

View file

@ -0,0 +1,25 @@
application:
debug: false
# Database
database:
adapter: Mysql
host: localhost
username: user
password: password
dbname: httpcb
charset: utf8
# OAuth
#oauth:
#security_salt: value
#debug: 1
#Strategy:
# Google:
# client_id: value
# client_secret: value
# GitHub:
# client_id: value
# client_secret: value

15
app/config/config.php Normal file
View file

@ -0,0 +1,15 @@
<?php
use Phalcon\Config\Adapter\Yaml as Config;
$config = new Config(APP_PATH . '/app/config/conf.app.yml');
try {
$local = new Config(APP_PATH . '/app/config/conf.local.yml');
$config->merge($local);
} catch(Phalcon\Config\Exception $e) {
// Sometime went wrong.
}
return $config;

21
app/config/loader.php Normal file
View file

@ -0,0 +1,21 @@
<?php
$loader = new \Phalcon\Loader();
/**
* We're a registering a set of directories taken from the configuration file
*/
$loader->registerDirs(
array(
$config->application->controllersDir,
$config->application->libraryDir,
$config->application->pluginsDir
)
)->register();
$loader->registerNamespaces(array(
'Model' => $config->application->modelsDir,
'Form' => $config->application->formsDir,
));
require_once __DIR__ . '/../../vendor/autoload.php';

330
app/config/services.php Normal file
View file

@ -0,0 +1,330 @@
<?php
/**
* Services are globally registered in this file
*
* @var \Phalcon\Config $config
*/
use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\View;
use Phalcon\Assets\Manager as AssetsManager;
use Phalcon\Mvc\Url as UrlResolver;
use Phalcon\Mvc\View\Engine\Volt as VoltEngine;
use Navigation\Navigation;
use Phalcon\Flash\Direct as FlashDirect;
use Phalcon\Mvc\Model\Metadata\Memory as MemoryMetaData;
use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData;
use Phalcon\Cache\Frontend\Data as FrontendDataCache;
use Phalcon\Cache\Backend\Apc as BackendApcCache;
use Phalcon\Translate\Adapter\NativeArray as TranslateAdapter;
use Phalcon\Logger;
use Phalcon\Logger\Adapter\Firephp as FirephpAdapter;
use Phalcon\Session\Adapter\Files as SessionAdapter;
use Phalcon\Mvc\Router;
/**
* The FactoryDefault Dependency Injector automatically register the right services providing a full stack framework
*/
$di = new FactoryDefault();
/**
* Setup logging
*/
$di->setShared('logger', function() {
//return new Phalcon\Logger\Adapter\Firephp("");
return new Phalcon\Logger\Adapter\File(APP_PATH . "/app/log.txt");
});
$di->setShared('dispatcher', function() use ($di, $config) {
$eventsManager = new Phalcon\Events\Manager();
$eventsManager->attach("dispatch", function($event, $dispatcher) {
$actionName = Phalcon\Text::camelize($dispatcher->getActionName());
$dispatcher->setActionName($actionName);
});
if ($config->application->debug == false) {
$eventsManager->attach('dispatch:beforeException', new \ExceptionHandlerPlugin());
}
$eventsManager->attach('dispatch:beforeExecuteRoute', new \AclPlugin());
$dispatcher = new \Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
/**
* The URL component is used to generate all kind of urls in the application
*/
$di->setShared('url', function() use ($config) {
$url = new UrlResolver();
$url->setBaseUri($config->application->baseUri);
return $url;
});
$di->setShared('router', function() {
// Create the router
$router = new Router();
$router->removeExtraSlashes(true);
$router->add('/', array(
'controller' => 'index',
'action' => 'index'
))->setName('home-route');
// Route about page to index controller
$router->add('/about', array(
'controller' => 'index',
'action' => 'about'
))->setName('about-route');
$router->add('/callback/created/{id}', array(
'controller' => 'callback',
'action' => 'created',
))->setName('cb-created');
// Route callback endpoints to callback controller.
$router->add('/cb/{id}', array(
'controller' => 'callback',
'action' => 'endpoint',
))->setName('cb-endpoint');
// Login routes.
$router->add('/login', array(
'controller' => 'auth',
'action' => 'index',
))->setName('login');
$router->add('/logout', array(
'controller' => 'auth',
'action' => 'logout',
))->setName('logout');
$router->add('/oauth/{strategy:([a-z]+)}/:params', array(
'controller' => 'auth',
'action' => 'oauth'
))->setName('oauth');
$router->add('/user', array(
'controller' => 'user',
'action' => 'profile',
))->setName('profile');
return $router;
});
$di->setShared('viewHelper', '\ViewHelper\Service');
/**
* Setting up the view component
*/
$di->setShared('view', function () use ($di, $config) {
$view = new View();
$view->setViewsDir($config->application->viewsDir);
$view->setLayoutsDir('_layouts/');
$view->setPartialsDir('_partials/');
$view->registerEngines(array(
'.volt' => function ($view, $di) use ($config) {
$volt = new VoltEngine($view, $di);
$volt->setOptions(array(
'compiledPath' => $config->application->cacheDir,
'compiledSeparator' => '_',
'compileAlways' => true,
));
// Register view helpers
$compiler = $volt->getCompiler();
$compiler->addExtension(new \ViewHelper\Volt\Extension($di));
return $volt;
},
'.phtml' => 'Phalcon\Mvc\View\Engine\Php'
));
// Set default main layout.
$view->setMainView('layout');
return $view;
});
$di->setShared('assets', function () {
$manager = new AssetsManager();
// CSS
$manager->collection('css')
->setPrefix('css/')
->addCss('application.min.css');
// Javascript
$manager->collection('js')
->setPrefix('js/')
->addJs('jquery-3.0.0.min.js')
->addJs('bootstrap.min.js');
return $manager;
});
/**
* Database connection is created based in the parameters defined in the configuration file
*/
$di->setShared('db', function () use ($di, $config) {
$dbConfig = $config->database->toArray();
$adapter = $dbConfig['adapter'];
unset($dbConfig['adapter']);
$class = 'Phalcon\Db\Adapter\Pdo\\' . $adapter;
$db = new $class($dbConfig);
$logger = $di->get('logger');
$eventsManager = new Phalcon\Events\Manager();
$eventsManager->attach('db', function ($event, $connection) use ($di, $logger) {
if ($event->getType() == 'beforeQuery') {
$logger->log($connection->getRealSQLStatement(), Logger::INFO);
}
});
$db->setEventsManager($eventsManager);
return $db;
});
/**
* If the configuration specify the use of metadata adapter use it or use memory otherwise
*/
$di->setShared('modelsMetadata', function () use ($config) {
// Use adapter and options from config if defined.
if (isset($config->cache->metadata)) {
$mdConfig = $config->cache->metadata->toArray();
$options = $mdConfig['options'];
$adapter = $mdConfig['adapter'];
$class = 'Phalcon\Mvc\Model\MetaData\\' . $adapter;
$metadata = new $class($options);
}
// Otherwise, default to Memory.
else {
$metadata = new Phalcon\Mvc\Model\MetaData\Memory();
}
return $metadata;
});
// Set the models cache service
/*
$di->set('modelsCache', function () {
// Cache data for one day by default
$frontCache = new FrontendDataCache(
array(
"lifetime" => 86400
)
);
// Memcached connection settings
$cache = new BackendApcCache(
$frontCache,
array(
'prefix' => 'httpcb_'
)
);
return $cache;
});*/
/**
* Start the session the first time some component request the session service
*/
$di->set('session', function () use ($config) {
// Set session directory if defined.
if (isset($config->application->sessionDir)) {
session_save_path($config->application->sessionDir);
}
// Create and start session.
$session = new SessionAdapter();
$session->start();
return $session;
});
$di->setShared('flash', function () {
return new Phalcon\Flash\Session();
});
$di->set('oauth', function() use ($config) {
return new OAuth($config);
});
$di->set('auth', function() use ($config) {
return new Auth\Auth($config);
});
$di->set('acl', function() {
return new Acl\Acl();
});
$di->set('menu', function() use ($di) {
$config = array(
'home' => array(
'caption' => 'Home',
'route' => 'home-route',
'controller' => 'index',
'action' => 'index',
),
'create-new ' => array(
'caption' => 'Create new',
'resource' => 'callback',
'controller' => 'callback',
'action' => 'new',
),
'my-callbacks' => array(
'caption' => 'List callbacks',
'resource' => 'callback',
'controller' => 'callback',
'action' => 'list',
'children' => array(
'show' => array(
'resource' => 'callback',
'controller' => 'callback',
'action' => 'show',
),
),
),
'about' => array(
'route' => 'about-route',
'caption' => 'About',
'controller' => 'index',
'action' => 'about'
),
);
$navigation = new Navigation($config);
$menu = new Menu($navigation);
$menu->setMenuClass(null);
if ($di->get('auth')->hasIdentity()) {
$menu->setAclRole(Acl\Acl::ROLE_USER);
} else {
$menu->setAclRole(Acl\Acl::ROLE_GUEST);
}
return $menu;
});

View file

@ -0,0 +1,52 @@
<?php
class AuthController extends ControllerBase
{
public function indexAction()
{
$form = new Form\Login();
if ($this->request->isPost()) {
$data = $this->request->getPost();
if ($form->isValid($data)) {
$email = $form->getValue('Email');
$passwd = $form->getValue('Password');
// Perform login
if ($this->auth->login($email, $passwd)) {
$this->response->redirect('/');
} else {
$this->flash->message('error', "Invalid credentials");
}
} else {
$msg = '<ul>';
foreach($form->getMessages() as $message) {
$msg .= '<li><strong>' . $message->getField() . '</strong> '. $message->getMessage() . '</li>';
}
$msg .= '</ul>';
$this->flash->message('error', $msg);
}
}
$this->view->form = $form;
}
public function oauthAction()
{
$response = $this->oauth->perform();
if (is_array($response)) {
$this->auth->loginOauth($response['auth']);
}
$this->response->redirect('/');
}
public function logoutAction()
{
$this->auth->clearIdentity();
$this->response->redirect('/');
}
}

View file

@ -0,0 +1,144 @@
<?php
class CallbackController extends ControllerBase
{
/**
* @var \Model\Data\User
*/
protected $_user;
public function initialize()
{
$this->_user = $this->_getAuth()->getUser();
}
/**
* @param $page
*/
public function listAction($page = 1)
{
$paginator = Model\Data\Callback::getPaginationList($this->_user->getId(), $page, 10);
if ($paginator->getPaginate()->current > $paginator->getPaginate()->total_pages) {
$paginator->setCurrentPage(1);
}
$this->view->page = $paginator->getPaginate();
$this->view->pagination_url = '/callback/list/';
}
/**
* Create a new test session.
*/
public function newAction()
{
$form = new Form\CallbackCreate();
if ($this->request->isPost()) {
$data = $this->request->getPost();
if ($form->isValid($data)) {
$callback = new Model\Data\Callback();
$callback->User = $this->_user;
$callback->setName($this->request->getPost());
$result = $callback->save();
if ($result) {
$callback->refresh();
return $this->response->redirect(array(
'for' => 'cb-created',
'id' => $callback->getPublicId()));
} else {
foreach($callback->getMessages() as $msg) {
$this->flash->error($msg);
}
}
} else {
$msg = '<ul>';
foreach($form->getMessages() as $message) {
$msg .= '<li><strong>' . $message->getField() . '</strong>: '. $message->getMessage() . '</li>';
}
$msg .= '</ul>';
$this->flash->message('error', $msg);
}
}
$this->view->form = $form;
}
/**
*
*/
public function createdAction($id)
{
$row = Model\Data\Callback::get($id);
if (!$row) {
}
$this->view->id = $id;
}
/**
* Monitor a test session.
*
* @param $id
* @param $page
*/
public function showAction($id = null, $page = 1)
{
$callback = Model\Data\Callback::findFirst(array(
'conditions' => 'public_id = ?0 AND userid = ?1',
'bind' => array($id, $this->_user->getId())
));
if (!$callback) {
$this->_forward404();
return;
}
$paginator = $callback->getRequestPaginator($page, 30);
$this->view->item = $callback;
$this->view->page = $paginator->getPaginate();
$this->view->pagination_url = '/callback/show/' . $id . '/';
}
/**
* This is the action that the API to be
* tested should make it's callback to. So we can catch it.
*
* @Acl(resource="api")
* @param int $id The test session id so
* we know what test it belongs to.
* @return string
*/
public function endpointAction($id)
{
$this->view->disable();
$callback = Model\Data\Callback::get($id);
$request = new Model\Data\Request();
$request->setHeaders($this->request->getHeaders());
$request->setBody($this->request->getRawBody());
$dt = new DateTime();
$callback->setLastRequest($dt->format('Y-m-d H:i:s'));
$meta = new Model\Data\RequestMeta();
$meta->Callback = $callback;
$meta->RequestObject = $request;
$result = $meta->save();
if ($result == false) {
var_dump($meta->getMessages());
}
}
}

View file

@ -0,0 +1,25 @@
<?php
use Phalcon\Mvc\Controller;
class ControllerBase extends Controller
{
/**
* Helper method to forward to the 404 - file not found page.
*/
protected function _forward404()
{
$this->dispatcher->forward(array(
'controller' => 'error',
'action' => 'show404'
));
}
/**
* @return Auth\Auth
*/
protected function _getAuth()
{
return $this->di->get('auth');
}
}

View file

@ -0,0 +1,14 @@
<?php
class ErrorController extends ControllerBase
{
public function errorAction()
{
}
public function show404Action()
{
// Set status code to 404.
$this->response->setStatusCode(404);
}
}

View file

@ -0,0 +1,14 @@
<?php
class IndexController extends ControllerBase
{
public function indexAction()
{
$this->view->setMainView('layout-front');
}
public function aboutAction()
{
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Form;
use Phalcon\Forms\Form;
/**
* Element types
*/
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Submit;
/**
* Validators
*/
use Phalcon\Validation\Validator\StringLength;
class CallbackCreate extends Form
{
public function initialize()
{
$this->setEntity($this);
// Name
$name = new Text('Name', array(
'class' => 'form-control',
));
$validator = new StringLength([
'max' => 50,
'min' => 2,
'messageMaximum' => 'Must be less than :max characters.',
'messageMinimum' => 'Must be atleast :min characters long.'
]);
$name->addValidator($validator);
$this->add($name);
// Submit
$submit = new Submit('Create', array('class' => 'button button-brand'));
$this->add($submit);
}
}

59
app/forms/Login.php Normal file
View file

@ -0,0 +1,59 @@
<?php
namespace Form;
use Phalcon\Forms\Form;
/**
* Element types
*/
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Password;
use Phalcon\Forms\Element\Submit;
/**
* Validators
*/
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\Email as EmailValidator;
use Phalcon\Validation\Validator\StringLength;
class Login extends Form
{
public function initialize()
{
$this->setEntity($this);
// Email
$email = new Text('Email', array(
'class' => 'form-control',
'placeholder' => 'Username/Email',
));
$validator = new EmailValidator(array(
'message' => 'The e-mail is not valid',
));
$email->addValidator($validator);
$this->add($email);
// Password
$passwd = new Password('Password', array(
'class' => 'form-control',
'placeholder' => 'Password',
));
$validator = new StringLength(array(
'min' => 8,
'messageMinimum' => 'Password must be atleast 6 characters long',
));
$passwd->addValidator($validator);
$this->add($passwd);
// Submit
$submit = new Submit('Login', array('class' => 'button button-default button-block'));
$this->add($submit);
}
}

53
app/library/Acl/Acl.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Adapter\Memory as AclList;
class Acl extends AclList
{
const ROLE_USER = 'user';
const ROLE_GUEST = 'guest';
public function __construct()
{
$this->_build();
}
protected function _build()
{
// Deny access to everything by default.
$this->setDefaultAction(\Phalcon\Acl::DENY);
// Roles
$user = new Role('user');
$guest = new Role('guest');
$this->addRole($guest);
$this->addRole($user, $guest);
$public = array(
'index',
'error',
'auth',
'api',
);
// Public Resources
foreach($public as $resource) {
$this->addResource($resource, 'Read');
$this->allow($guest->getName(), $resource, 'Read');
}
$protected = array(
'callback',
'user',
);
foreach($protected as $resource) {
$this->addResource($resource, 'Read');
$this->allow($user->getName(), $resource, 'Read');
}
}
}

121
app/library/Auth/Auth.php Normal file
View file

@ -0,0 +1,121 @@
<?php
namespace Auth;
use Phalcon\Mvc\User\Component;
use Model\Data\User;
class Auth extends Component
{
/**
* @var string
*/
protected $_session_key = 'auth';
public function login($email, $password)
{
// Look for a user with this email.
$user = User::findFirstByEmail($email);
if ($user) {
// Verify password
$hash = $user->getPassword();
if (strlen($hash) > 1 && password_verify($password, $hash)) {
$this->setIdentity($user->getId());
return true;
}
}
return false;
}
/**
* Login using OAuth
*
* @param $auth
*/
public function loginOauth($auth)
{
$email = '';
if (isset($auth['info']['email'])) {
$email = $auth['info']['email'];
}
// Look for a user with this email.
$user = User::findFirstByEmail($email);
if (!$user) {
// Did not find any user. create him.
if (isset($auth['info']['nickname'])) {
$name = $auth['info']['nickname'];
} else if(isset($auth['info']['name'])) {
$name = $auth['info']['name'];
} else {
$name = '';
}
$user = new User();
$user->setEmail($email)
->setUsername($name);
$user->save();
}
$this->setIdentity($user->getId());
}
/**
* @param $identity
* @return Auth
*/
public function setIdentity($identity)
{
$this->session->set($this->_session_key, $identity);
return $this;
}
/**
* return \Model\Data\User
*/
public function getIdentity()
{
$id = $this->session->get($this->_session_key);
if ($id !== null) {
return \Model\Data\User::findFirst($id);
}
return null;
}
/**
* return \Model\Data\User
*/
public function getUser()
{
if ($this->hasIdentity()) {
$id = $this->session->get($this->_session_key);
return \Model\Data\User::findFirst($id);
}
return null;
}
public function hasIdentity()
{
return $this->getIdentity() !== NULL;
}
/**
* Clears the identity information.
*
* @return Auth
*/
public function clearIdentity()
{
$this->session->remove($this->_session_key);
return $this;
}
}

24
app/library/Debug.php Normal file
View file

@ -0,0 +1,24 @@
<?php
class Debug {
public static function dump($var, $label = null, $echo = true)
{
// format the label
$label = ($label===null) ? '' : rtrim($label) . ' ';
// var_dump the variable into a buffer and keep the output
ob_start();
var_dump($var);
$output = ob_get_clean();
// neaten the newlines and indents
$output = preg_replace("/\]\=\>\n(\s+)/m", "] => ", $output);
$output = '<pre>'
. $label
. $output
. '</pre>';
if ($echo) {
echo $output;
}
return $output;
}
}

140
app/library/Menu.php Normal file
View file

@ -0,0 +1,140 @@
<?php
use Phalcon\Tag;
use Navigation\Node;
use Navigation\Navigation;
class Menu extends Tag
{
/**
* ACL Role
*
* @var string
*/
protected $_role = null;
/**
* @var Navigation
*/
protected $_navigation;
/**
* css class to use for the whole menu.
*
* @var string
*/
protected $_menuClass = 'menu';
/**
* Class to use for active nodes.
*
* @var string
*/
protected $_activeClass = 'active';
/**
* @param Navigation $navigation
*/
public function __construct(Navigation $navigation)
{
$this->_navigation = $navigation;
}
/**
* @return Navigation
*/
public function getNavigation()
{
return $this->_navigation;
}
/**
* @param Navigation $navigation
* @return Menu
*/
public function setNavigation(Navigation $navigation)
{
$this->_navigation = $navigation;
return $this;
}
public function setMenuClass($class)
{
$this->_menuClass = (string) $class;
return $this;
}
/**
* @param $role
*/
public function setAclRole($role)
{
$this->_role = $role;
}
/**
* Render the menu.
*
* @return string
*/
public function render($max_depth = null)
{
return $this->_renderMenu($this->_navigation->getChildren(), 0, $max_depth);
}
protected function _renderMenu($nodes, $depth, $max_depth = null)
{
$xhtml = '';
foreach($nodes as $node) {
$xhtml .= $this->_renderNode($node, $depth, $max_depth);
}
if (strlen($xhtml) > 0) {
$attribs = array();
if (strlen($this->_menuClass) > 0) {
$attribs['class'] = $this->_menuClass;
}
return self::tagHtml('ul', $attribs, false, false, true)
. $xhtml
. self::tagHtmlClose('ul', true);
}
return $xhtml;
}
protected function _renderNode(Node $node, $depth, $max_depth = null)
{
$xhtml = '';
// ACL.
$resource = $node->getResource();
if (strlen($this->_role) > 0 && strlen($resource) > 0 && $this->getDI()->has('acl')) {
$acl = $this->getDI()->get('acl');
if (!$acl->isAllowed($this->_role, $resource, 'Read')) {
return $xhtml;
}
}
// Only render this node if it is visible and has a caption.
if (!$node->isVisible() || strlen($node->getCaption()) < 1) {
return $xhtml;
}
$xhtml = self::tagHtml('li', $node->isActive()
? array('class' => $this->_activeClass) : null,
false, false, true);
// Generate the link.
$xhtml .= self::linkTo($node->getHref(), $node->getCaption());
if ($node->isActive() && $node->hasChildren()
&& ($max_depth === null || $depth < $max_depth)) {
$xhtml .= $this->_renderMenu($node->getChildren(), $depth + 1, $max_depth);
}
return $xhtml . self::tagHtmlClose('li', true);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Mvc\Model\Behavior;
use \Phalcon\Mvc\Model\Behavior;
use \Phalcon\Mvc\Model\BehaviorInterface;
use \Phalcon\Exception;
/**
* Generates a unique base64 url-safe id for the field specified.
*
* Class RandomId
* @package Mvc\Model\Behavior
*/
class RandomId extends Behavior implements BehaviorInterface
{
public function __construct($options = null)
{
$field = null;
if (isset($options['field'])) {
$field = $options['field'];
}
if (isset($options['length'])) {
if (!is_numeric($options['length'])) {
throw new Exception("'length' must be a number.");
}
} else {
$options['length'] = 32;
}
if (strlen($field) < 1) {
throw new Exception("'field' must be set in the option array.");
}
parent::__construct($options);
}
public function notify($type, \Phalcon\Mvc\ModelInterface $model)
{
switch($type) {
case 'beforeValidationOnCreate' :
$this->generateId($model);
break;
}
}
public function generateId(\Phalcon\Mvc\ModelInterface $model)
{
$field = $this->_options['field'];
if ($model->$field === null) {
$random = new \Phalcon\Security\Random();
for($i = 0; $i < 3; $i++) {
$id = $random->base64Safe();
$id = substr($id, 0, $this->_options['length']);
$count = $model->count(array(
"$field = ?0",
'bind' => array($id)
));
if ($count < 1) {
$model->$field = $id;
break;
}
}
}
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Navigation;
class Container
{
/**
* Children
*
* @var array
*/
protected $_children = array();
/**
* @return array
*/
public function getChildren()
{
return $this->_children;
}
/**
* @return bool
*/
public function hasChildren()
{
return empty($this->getChildren()) === false;
}
/**
* @param $child
* @return Node
*/
public function addChild($child)
{
if (is_array($child)) {
$node = new Node();
foreach($child as $k => $v) {
if ($k == 'children') {
continue;
}
$node->{'set' . ucfirst($k)}($v);
}
if (isset($child['children'])) {
foreach($child['children'] as $c_data) {
$node->addChild($c_data);
}
}
$child = $node;
}
if (!($child instanceof Node)) {
throw new Exception('Must be of type node.');
}
$this->_children[] = $child;
$child->setParent($this);
return $this;
}
/**
* @param $children
* @return Node
*/
public function addChildren($children)
{
foreach($children as $child) {
$this->addChild($child);
}
return $this;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Navigation;
class Exception extends \Exception
{
}

View file

@ -0,0 +1,13 @@
<?php
namespace Navigation;
class Navigation extends Container
{
public function __construct($config)
{
foreach($config as $node) {
$this->addChild($node);
}
}
}

View file

@ -0,0 +1,256 @@
<?php
namespace Navigation;
use \Phalcon\Di;
class Node extends Container
{
/**
* Caption
*
* @var string
*/
protected $_caption = null;
/**
* Controller name
*
* @var string
*/
protected $_controller = null;
/**
* Controller action.
*
* @var string
*/
protected $_action = null;
/**
* ACL Resource.
*
* @var string
*/
protected $_resource = null;
/**
* Route name.
*
* @var string
*/
protected $_route = null;
/**
* Active flag.
*
* @var bool
*/
protected $_active = null;
/**
* Visible flag
*
* @var bool
*/
protected $_visible = true;
/**
* @var Node
*/
protected $_parent = null;
/**
* @param $parent
* @return Node
*/
public function setParent($parent)
{
$this->_parent = $parent;
return $this;
}
/**
* @return Node
*/
public function getParent()
{
return $this->_parent;
}
/**
* @return string
*/
public function getCaption()
{
return $this->_caption;
}
/**
* @param string $caption
* @return Node
*/
public function setCaption($caption)
{
$this->_caption = (string) $caption;
return $this;
}
/**
* @return string
*/
public function getController()
{
return $this->_controller;
}
/**
* @param string $controller
* @return Node
*/
public function setController($controller)
{
$this->_controller = (string) $controller;
return $this;
}
/**
* @return string
*/
public function getAction()
{
return $this->_action;
}
/**
* @param string $action
* @return Node
*/
public function setAction($action)
{
$this->_action = (string) $action;
return $this;
}
/**
* @return string
*/
public function getResource()
{
return $this->_resource;
}
/**
* @param string $resource
* @return Node
*/
public function setResource($resource)
{
$this->_resource = (string) $resource;
return $this;
}
/**
* @return string
*/
public function getRoute()
{
return $this->_route;
}
/**
* @param string $route
* @return Node
*/
public function setRoute($route)
{
$this->_route = (string) $route;
return $this;
}
/**
* Get the href for this node.
*
* @return string
*/
public function getHref()
{
/** @var \Phalcon\Mvc\Url */
$url = Di::getDefault()->get('url');
// Assemble route if set.
if (strlen($this->getRoute()) > 0) {
$href = array(
'for' => $this->getRoute(),
'controller' => $this->getController(),
'action' => $this->getAction()
);
}
// Otherwise, use default route.
else {
$href = $this->getController();
if (is_string($this->getAction())) {
$href .= '/' . $this->getAction();
}
}
return $url->get($href);
}
/**
* @param $value
* @return Node
*/
public function setActive($value)
{
$this->_active = (bool) $value;
return $this;
}
/**
* @return bool
*/
public function isActive()
{
// If active flag is not set explicitly.
// Test this node against the current request.
if ($this->_active === null) {
// But only if the node is visible.
if ($this->isVisible() === false) {
return false;
}
// first. Check children.
foreach($this->getChildren() as $child) {
if ($child->isActive() == true) {
$this->setActive(true);
return $this->_active;
}
}
$dispatcher = Di::getDefault()->get('dispatcher');
$controller = strtolower($dispatcher->getControllerName());
$action = strtolower($dispatcher->getActionName());
$active = $controller == $this->_controller && $action == $this->_action;
$this->setActive($active);
}
return $this->_active;
}
public function setVisible($value)
{
$this->_visible = (bool) $value;
return $this;
}
public function isVisible()
{
return $this->_visible;
}
}

100
app/library/OAuth.php Normal file
View file

@ -0,0 +1,100 @@
<?php
//use Opauth;
use Phalcon\Mvc\User\Component;
class OAuth extends Component
{
/**
* Configuration
*
* @var array
*/
protected $_config = array(
'path' => '/oauth/',
'callback_url' => '/oauth/callback'
);
protected $_oauth;
protected $_callbackName = 'callback';
public function __construct($config)
{
$config = $this->objectToArray($config->oauth);
$this->_config = array_merge($this->_config, $config);
$this->_oauth = new Opauth($this->_config, false);
}
/**
* @return mixed|null|void
*/
public function perform()
{
$strategy = $this->dispatcher->getParam('strategy', null, false);
if ($strategy == $this->_callbackName) {
return $this->getResponse();
}
$this->_oauth->run();
exit;
}
/**
* @return array|string
*/
public function getResponse()
{
$response = null;
switch($this->_oauth->env['callback_transport']) {
case 'session':
$response = $this->session->get('opauth');
$this->session->remove('opauth');
break;
case 'post':
$response = unserialize(base64_decode( $_POST['opauth'] ));
break;
case 'get':
$response = unserialize(base64_decode( $_GET['opauth'] ));
break;
}
$ret = $this->_validate($response, $reason);
if ($ret === false) {
return $reason;
}
return $response;
}
public function objectToArray($object)
{
if(!is_object($object) && !is_array($object))
{
return $object;
}
if(is_object($object))
{
$object = get_object_vars( $object );
}
return array_map(array($this,"objectToArray"), $object );
}
protected function _validate($response, &$reason)
{
if (isset($response['auth']) &&
isset($response['timestamp']) &&
isset($response['signature'])) {
$hash = sha1(print_r($response['auth'], true));
return $this->_oauth->validate($hash, $response['timestamp'],
$response['signature'], $reason);
}
$reason = "Invalid auth response";
return false;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace ViewHelper;
use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;
abstract class AbstractHelper implements InjectionAwareInterface
{
protected $_di;
/**
* Sets the dependency injector
*
* @param mixed $dependencyInjector
*/
public function setDI(DiInterface $dependencyInjector)
{
$this->_di = $dependencyInjector;
}
/**
* Returns the internal dependency injector
*
* @return \Phalcon\DiInterface
*/
public function getDI()
{
return $this->_di;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace ViewHelper;
/**
* Class Icon
*
* @package ViewHelper
*/
class Icon extends AbstractHelper
{
public function icon($name, $args = array())
{
$classes = array(
'icon',
'ion-' . $name
);
if (is_array($args)) {
foreach($args as $arg) {
$classes[] .= 'ion-' . $arg;
}
}
$classes = implode(' ', $classes);
return '<i class="' . $classes . '"></i>';
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace ViewHelper;
/**
* Class ServerUrl
*
* @package ViewHelper
*/
class ServerUrl extends AbstractHelper
{
protected $_request;
public function getScheme()
{
return $this->_getRequest()->getScheme();
}
public function getHost()
{
return $this->_getRequest()->getHttpHost();
}
public function getPort()
{
return $this->_getRequest()->getPort();
}
public function serverUrl()
{
$port = $this->getPort();
$scheme = $this->getScheme();
// remove port if it's the default port.
if (($scheme == 'http' && $port == 80)
|| ($scheme == 'https' && $port == 443)) {
$port = null;
}
$url = $scheme . '://' . $this->getHost();
if ($port !== null) {
$url .= ':' . $port;
}
return $url;
}
/**
* @return \Phalcon\Http\RequestInterface
*/
protected function _getRequest()
{
if ($this->_request === null) {
$this->_request = $this->getDI()->getRequest();
}
return $this->_request;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace ViewHelper;
use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;
class Service implements InjectionAwareInterface
{
protected $_helpers = array();
protected $_di;
/**
* Sets the dependency injector
*
* @param mixed $dependencyInjector
*/
public function setDI(DiInterface $dependencyInjector)
{
$this->_di = $dependencyInjector;
}
/**
* Returns the internal dependency injector
*
* @return \Phalcon\DiInterface
*/
public function getDI()
{
return $this->_di;
}
public function set($name, AbstractHelper $helper)
{
$helper->setDI($this->getDI());
$this->_helpers[$name] = $helper;
}
public function has($name)
{
return $this->_locateHelper($name) !== false;
}
public function get($name)
{
if ($this->has($name)) {
return $this->_helpers[$name];
}
return false;
}
public function __call($name, $args)
{
$helper = $this->get($name);
if ($helper) {
return call_user_func_array(array($helper, $name), $args);
}
return false;
}
protected function _locateHelper($name)
{
if (array_key_exists($name, $this->_helpers)) {
return $this->_helpers[$name];
}
$class = '\ViewHelper\\' . ucfirst($name);
if (class_exists($class)) {
$helper = new $class();
$this->set($name, $helper);
return $helper;
}
return false;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace ViewHelper\Volt;
use \Phalcon\Di\InjectionAwareInterface;
class Extension implements InjectionAwareInterface
{
protected $_serviceKey = 'viewHelper';
/**
* @var \Phalcon\DiInterface
*/
protected $_di;
public function __construct(\Phalcon\DiInterface $dependencyInjector)
{
$this->_di = $dependencyInjector;
if (!$this->_di->has($this->_serviceKey)) {
$this->_di->set($this->_serviceKey, '\ViewHelper\Service', true);
}
}
public function compileFunction($name, $args)
{
// Get the view helper service.
$service = $this->_di->getShared($this->_serviceKey);
// Search for the helper in service.
if ($service->has($name)) {
return "\$this->{$this->_serviceKey}->{$name}({$args})";
}
return false;
}
/**
* Sets the dependency injector
*
* @param mixed $dependencyInjector
*/
public function setDI(\Phalcon\DiInterface $dependencyInjector)
{
$this->_di = $dependencyInjector;
}
/**
* Returns the internal dependency injector
*
* @return \Phalcon\DiInterface
*/
public function getDI()
{
return $this->_di;
}
}

7
app/models/Data/Base.php Normal file
View file

@ -0,0 +1,7 @@
<?php
namespace Model\Data;
class Base
{
}

View file

@ -0,0 +1,275 @@
<?php
namespace Model\Data;
use Mvc\Model\Behavior\RandomId as RandomIdBehavior;
use Phalcon\Mvc\Model;
class Callback extends Model
{
/**
*
* @var integer
*/
protected $id;
/**
*
* @var integer
*/
protected $userid;
/**
*
* @var string
*/
protected $name;
/**
*
* @var string
*/
protected $public_id;
/**
*
* @var string
*/
protected $color;
/**
*
* @var string
*/
protected $created_at;
/**
*
* @var string
*/
protected $last_request;
/**
* Method to set the value of field id
*
* @param integer $id
* @return $this
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Method to set the value of field userid
*
* @param integer $userid
* @return $this
*/
public function setUserid($userid)
{
$this->userid = $userid;
return $this;
}
/**
* Method to set the value of field name
*
* @param string $name
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Method to set the value of field public_id
*
* @param string $public_id
* @return $this
*/
public function setPublicId($public_id)
{
$this->public_id = $public_id;
return $this;
}
/**
* Method to set the value of field color
*
* @param string $color
* @return $this
*/
public function setColor($color)
{
$this->color = $color;
return $this;
}
/**
* Method to set the value of field created_at
*
* @param string $created_at
* @return $this
*/
public function setCreatedAt($created_at)
{
$this->created_at = $created_at;
return $this;
}
/**
* Method to set the value of field last_request
*
* @param string $last_request
* @return $this
*/
public function setLastRequest($last_request)
{
$this->last_request = $last_request;
return $this;
}
/**
* Returns the value of field id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Returns the value of field userid
*
* @return integer
*/
public function getUserid()
{
return $this->userid;
}
/**
* Returns the value of field name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the value of field public_id
*
* @return string
*/
public function getPublicId()
{
return $this->public_id;
}
/**
* Returns the value of field color
*
* @return string
*/
public function getColor()
{
return $this->color;
}
/**
* Returns the value of field created_at
*
* @return string
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Returns the value of field last_request
*
* @return string
*/
public function getLastRequest()
{
return $this->last_request;
}
/**
* Initialize method for model.
*/
public function initialize()
{
$this->useDynamicUpdate(true);
$this->hasMany('id', 'Model\Data\RequestMeta', 'callbackid', array('foreignKey' => true, 'alias' => 'Requests'));
$this->belongsTo('userid', 'Model\Data\User', 'id', array('foreignKey' => true, 'alias' => 'User'));
$this->addBehavior(new RandomIdBehavior(array(
'field' => 'public_id',
'length' => 8,
)));
}
/**
* Returns table name mapped in the model.
*
* @return string
*/
public function getSource()
{
return 'callback';
}
public function getRequestPaginator($page = 1, $limit = 30)
{
return RequestMeta::getPaginator($this, $page, $limit);
}
public static function get($pid)
{
return parent::findFirst(array(
'public_id = :pid:',
'bind' => array('pid' => $pid),
));
}
/**
* @param $userid
* @param int $page
* @param int $limit
* @return \Phalcon\Paginator\AdapterInterface
*/
public static function getPaginationList($userid, $page = 1, $limit = 30)
{
$builder = (new self())->getModelsManager()->createBuilder();
$builder->from('Model\Data\Callback')
->where('userid = :uid:', array('uid' => $userid))
->orderBy('created_at DESC');
$paginator = new \Phalcon\Paginator\Adapter\QueryBuilder(array(
'builder' => $builder,
'page' => $page,
'limit' => $limit
));
return $paginator;
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Model\Data;
use Phalcon\Mvc\Model;
class Request extends Model
{
protected $id;
protected $headers = array();
/**
* @var string|null
*/
protected $body;
public function initialize()
{
$this->useDynamicUpdate(true);
$this->setSource('request_object');
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
* @return Request
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @param array $headers
* @return Request
*/
public function setHeaders($headers)
{
foreach($headers as $k => $v) {
if (strlen($v) < 1) {
unset($headers[$k]);
}
}
$this->headers = $headers;
return $this;
}
/**
* @return mixed
*/
public function getBody()
{
return $this->body;
}
/**
* @param mixed $body
* @return Request
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
public function afterFetch()
{
$this->headers = json_decode($this->headers, true);
}
public function beforeSave()
{
$this->headers = json_encode($this->headers);
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace Model\Data;
use Phalcon\Mvc\Model;
class RequestMeta extends Model
{
/**
*
* @var integer
*/
protected $id;
/**
*
* @var integer
*/
protected $callbackid;
/**
*
* @var string
*/
protected $timestamp;
/**
* Initialize method for model.
*/
public function initialize()
{
$this->skipAttributes(array('request_object_id'));
$this->setSource('request_meta');
$this->useDynamicUpdate(true);
// Relationships
$this->belongsTo('callbackid', 'Model\Data\Callback', 'id', array('alias' => 'Callback'));
$this->hasOne('id', 'Model\Data\Request', 'id', array('alias' => 'RequestObject'));
}
/**
* Method to set the value of field id
*
* @param integer $id
* @return $this
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Method to set the value of field callbackid
*
* @param integer $callbackid
* @return $this
*/
public function setCallbackid($callbackid)
{
$this->callbackid = $callbackid;
return $this;
}
/**
* Method to set the value of field Timestamp
*
* @param string $Timestamp
* @return $this
*/
public function setTimestamp($timestamp)
{
$this->timestamp = $timestamp;
return $this;
}
/**
* Returns the value of field id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Returns the value of field callbackid
*
* @return integer
*/
public function getCallbackid()
{
return $this->callbackid;
}
public function getSize()
{
$headers = $this->getHeaders();
foreach($headers as $k => $v) {
if ($k == 'Content-Length') {
return $v;
}
}
return 0;
}
public function getType()
{
$headers = $this->getHeaders();
foreach($headers as $k => $v) {
if ($k == 'Content-Type') {
return substr($v, strrpos($v, '/') + 1);
}
}
return 'Unknown';
}
public function getHeaders()
{
return $this->getRequestObject()->getHeaders();
}
public function getBody()
{
return $this->getRequestObject()->getBody();
}
/**
* Returns the value of field Timestamp
*
* @return string
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* @param Callback $callback
* @param int $page
* @param int $limit
* @return \Phalcon\Paginator\AdapterInterface
*/
static public function getPaginator(Callback $callback, $page = 1, $limit = 30)
{
$builder = (new self())->getModelsManager()->createBuilder();
$builder->from('Model\Data\RequestMeta')
->where('callbackid = :cid:', array('cid' => $callback->getId()))
->orderBy('timestamp desc');
$paginator = new \Phalcon\Paginator\Adapter\QueryBuilder(array(
'builder' => $builder,
'page' => $page,
'limit' => $limit
));
return $paginator;
}
}

113
app/models/Data/User.php Normal file
View file

@ -0,0 +1,113 @@
<?php
namespace Model\Data;
use Phalcon\Mvc\Model;
class User extends Model
{
protected $id;
protected $username;
protected $email;
protected $deleted;
protected $password;
public function initialize()
{
$this->useDynamicUpdate(true);
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
* @return User
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* @return mixed
*/
public function getUsername()
{
return $this->username;
}
/**
* @param mixed $username
* @return User
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* @param mixed $email
* @return User
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* @return mixed
*/
public function getDeleted()
{
return $this->deleted;
}
/**
* @param mixed $deleted
* @return User
*/
public function setDeleted($deleted)
{
$this->deleted = $deleted;
return $this;
}
/**
* @return mixed
*/
public function getPassword()
{
return $this->password;
}
/**
* @param mixed $password
* @return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
}

47
app/plugins/AclPlugin.php Normal file
View file

@ -0,0 +1,47 @@
<?php
use Phalcon\Acl;
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
class AclPlugin extends Phalcon\Mvc\User\Plugin
{
public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher)
{
// We only have two roles for now, authenticated users and guests.
if ($this->auth->hasIdentity()) {
$role = \Acl\Acl::ROLE_USER;
} else {
$role = \Acl\Acl::ROLE_GUEST;
}
// Support annotations for actions to define custom resources.
$controllerClass = $dispatcher->getControllerClass();
$activeMethod = $dispatcher->getActiveMethod();
$annotation = $this->annotations->getMethod($controllerClass, $activeMethod);
// ACL annotation found. use that.
if ($annotation->has('Acl')) {
$resource = $annotation->get('Acl')->getArgument('resource');
}
// Otherwise, default to controller name.
else {
$resource = $dispatcher->getControllerName();
}
// Now, check and redirect user to login page if
// this role does not have access to this resource.
if ($this->acl->isAllowed($role, $resource, 'Read') == Acl::DENY) {
// Forward to login page.
$dispatcher->forward(array(
'controller' => 'auth',
'action' => 'index',
));
// Return false to stop the dispatch loop.
return false;
}
}
}

View file

@ -0,0 +1,49 @@
<?php
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Dispatcher\Exception as DispatcherException;
/**
* Class ExceptionHandlerPlugin
*
* Plugin for forwarding user to 404 (not found) page
* if a request could not be dispatched.
*/
class ExceptionHandlerPlugin extends Plugin
{
protected $_route_notfound = array(
'controller' => 'error',
'action' => 'show404'
);
protected $_route_error = array(
'controller' => 'error',
'action' => 'error',
);
/**
* @param Event $event
* @param Dispatcher $dispatcher
* @param Exception $exception
* @return bool
*/
public function beforeException(Event $event, Dispatcher $dispatcher, Exception $exception)
{
// Figure out if this was a exception from dispatcher and that exception
// was that an controller or action was not found.
if ($exception instanceof DispatcherException) {
switch ($exception->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND :
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND :
// in this case, forward to 404 page.
$dispatcher->forward($this->_route_notfound);
return false;
}
}
$dispatcher->forward($this->_route_error);
return false;
}
}

View file

@ -0,0 +1,61 @@
{%- macro max(a, b) %}
{% return a > b ? a : b %}
{%- endmacro %}
{%- macro min(a, b) %}
{% return a < b ? a : b %}
{%- endmacro %}
{% set pagination_slider = 2 %}
{% if (page.total_pages > 1) %}
<ul class="pagination">
{% if page.current !== page.before %}
<li>
<a href="{{ pagination_url ~ page.before }}">
{{ icon('android-arrow-back') }} Previous
</a>
</li>
{% endif %}
{% if page.total_pages > pagination_slider and page.current > pagination_slider %}
<li>
<a href="{{ pagination_url ~ 1 }}">1</a>
</li>
<li class="middle">
...
</li>
{% endif %}
{% for n in max(page.current - pagination_slider, 1)..min(page.current + pagination_slider, page.total_pages) %}
{% if (n == page.current) %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="{{ pagination_url ~ n }}">{{ n }}</a>
</li>
{% endfor %}
{% if page.total_pages > pagination_slider and page.current < page.total_pages - pagination_slider %}
<li class="middle">
...
</li>
<li>
<a href="{{ pagination_url ~ page.total_pages }}">{{ page.total_pages }}</a>
</li>
{% endif %}
{% if page.current !== page.next %}
<li>
<a href="{{ pagination_url ~ page.next }}">
Next {{ icon('android-arrow-forward') }}
</a>
</li>
{% endif %}
</ul>
{% endif %}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,22 @@
{% set types = {
'error': 'danger',
'success': 'success',
'notice': 'info',
'warning': 'warning'
} %}
{% if (flash.has()) %}
{% for type, messages in flash.getMessages() %}
{% for message in messages %}
<div class="alert alert-{{ types[type] }} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>{{ type|capitalize }}</strong>
<p>
{{ message }}
</p>
</div>
{% endfor%}
{% endfor %}
{% endif %}

View file

@ -0,0 +1,19 @@
<footer class="footer">
<div class="footer-left">
Copyright &copy; 2017
<a target="_blank" href="http://www.shufflingpixels.com">
Shufflingpixels
</a>
</div>
<div class="footer-middle">
<a class="footer-button-top" href="#top">
{{ icon('android-arrow-dropup') }}
</a>
</div>
<div class="footer-right">
Version 0.1
</div>
</footer>

View file

@ -0,0 +1,20 @@
<div class="navigation" role="navigation">
<button class="menu-button" type="button" data-toggle="collapse" data-target="#main-menu" aria-expanded="false" aria-label="Toggle navigation">
<i class="icon ion-navicon-round"></i>
</button>
<ul class="navigation-user-menu">
{% if auth.hasIdentity() %}
<li>{{ icon('android-person') }} Signed in as <strong>{{ auth.getUser().username }}</strong></li>
<li>{{ link_to(['for': 'logout'], '<i class="icon ion-log-out"></i> Log out') }}</li>
{% else %}
<li>{{ link_to(['for': 'login'], '<i class="icon ion-log-in"></i> Login', 'class': 'login-button') }}</li>
{% endif %}
</ul>
<nav class="navigation-menu collapse" id="main-menu">
{{ menu.render(0) }}
</nav>
</div>

42
app/views/auth/index.volt Normal file
View file

@ -0,0 +1,42 @@
<div class="login-container section">
<h3>Login</h3>
<div class="alert alert-info">
<p class="text-center"><strong>Heads up!</strong> Signup is currently not available.</p>
<p>
Login using username/password can be setup after registration.
First time users can only register with third party services below
</p>
</div>
<span class="spacer"></span>
<form class="form" method="post" action="">
<div class="form-group">
{{ form.render('Email') }}
</div>
<div class="form-group">
{{ form.render('Password') }}
</div>
<div class="form-group">
{{ form.render('Login') }}
</div>
</form>
<span class="spacer"></span>
<div class="oauth">
<a class="button button-github" href="{{ url(['for': 'oauth', 'strategy': 'github']) }}">
{{ icon('social-github') }} GitHub
</a>
<a class="button button-google" href="{{ url(['for': 'oauth', 'strategy': 'google']) }}">
{{ icon('social-google') }} Google
</a>
</div>
</div>

View file

@ -0,0 +1,47 @@
{% if error %}
<p><strong>Error: </strong>{{ error }}</p>
{% else %}
<section>
<h2>Authentication sucessful</h2>
<p>Authentication is successful and auth response is <span>validated</span>.</p>
<p>Returned auth response:</p>
<table class="table table-striped">
<tbody>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
{% for key,auth in auths['auth'] %}
{% if auth is scalar %}
<tr>
<th> {{key }}</th>
<th> {{auth}} </th>
</tr>
{% else %}
<tr>
<th> {{key}} </th>
<td><ul>
{% for key1,item in auth %}
{% if key1 is sameas('image') %}
<li><strong>{{key1}}</strong>:<img src="{{item}}" width="100" height="100"> </li>
{% elseif key1 == 'urls' %}
<li><strong>{{key1}}</strong>: {{item['google']}} </li>
{% else %}
<li><strong>{{key1}}</strong>: {{item}} </li>
{% endif %}
{% endfor %}
</ul></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<h2>Raw response</h2>
{{"<pre>"}}
<?php print_r($auths);?>
</section>
{% endif %}

View file

@ -0,0 +1,14 @@
<div class="section">
<h1>Callback created</h1>
<p>Set this link as callback url for the service you want to debug:</p>
<strong>{{ serverUrl() }}{{ url(['for': 'cb-endpoint', 'id': id]) }}</strong>
<a class="button button-default" href="{{ url('/callback/show/' ~ id) }}">
{{ icon('eye') }} View
</a>
</div>

View file

@ -0,0 +1,69 @@
<div class="section">
<div class="clearfix">
<h2 class="pull-left">Callbacks</h2>
<div class="pull-right">
<a class="button button-large button-primary" href="{{ url('/callback/new') }}">
{{ icon('android-add') }} New
</a>
</div>
</div>
{% if page.items|length > 0 %}
<div class="callback-list">
{% for item in page.items %}
<div class="callback-list-item">
<div class="callback-list-item-header">
<a class="callback-list-item-name" href="/callback/show/{{ item.public_id }}">{{ item.name|e }}</a>
{% if item.countRequests() > 0 %}
<span class="badge badge-primary">
{{ item.countRequests() }} Requests
</span>
{% endif %}
</div>
<div class="callback-list-item-info">
<span>{{ icon('android-time') }} Created at: {{ item.created_at }}</span>
<span>
{{ icon('paper-airplane') }}
{% if item.countRequests() > 0 %}
Last request: {{ item.last_request }}
{% else %}
No requests yet.
{% endif %}
</span>
<span>
{{ icon('link') }}
{{ serverUrl() }}{{ url(['for': 'cb-endpoint', 'id': item.public_id]) }}
</span>
</div>
<a class="callback-list-item-arrow" href="/callback/show/{{ item.public_id }}">
{{ icon('android-arrow-dropright-circle') }}
</a>
</div>
{% endfor %}
</div>
{% else %}
<div class="blankslate">
<h3>No callbacks made yet.</h3>
<p><a href="{{ url('/callback/new') }}">Create</a> a callback to begin!</p>
</div>
{% endif %}
<nav class="text-center" aria-label="Page navigation">
{{ partial('pagination') }}
</nav>
</div>

View file

@ -0,0 +1,39 @@
<div class="section center-block" style="width: 400px">
<h2>Create callback</h2>
<!--
<form class="form-horizontal" method="post">
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="name">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" class="button button-brand" value="Create">
</div>
</div>
</form>
-->
<form class="form-horizontal" method="post">
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Name</label>
<div class="col-sm-10">
{{ form.render('Name') }}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
{{ form.render('Create') }}
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,95 @@
<div class="section">
<div class="clearfix">
<h3 class="pull-left">{{ item.name|e }}</h3>
<h5 class="pull-right"><strong>Created at:</strong> {{ item.created_at }}</h5>
</div>
<div class="request-list" id="request-log" role="tablist" aria-multiselectable="true">
{% for index, req in page.items %}
<div class="request-list-item">
<a class="request-list-item-header collapsed" href="#request{{ index }}"
data-toggle="collapse" data-parent="#request-log"
aria-expanded="true" aria-controls="request{{ index }}">
<span class="request-list-item-header-index">
#{{ index + 1 }}
</span>
<span class="request-list-item-header-timestamp">
{{ icon('android-time') }} {{ req.getTimestamp() }}
</span>
<span class="request-list-item-header-type">
{{ icon('android-list') }} {{ req.getType() }}
</span>
<span class="request-list-item-header-size">
{{ icon('cube') }} {{ req.getSize() }} b
</span>
</a>
<div class="collapse" id="request{{ index }}"
role="tabpanel" aria-labelledby="head{{ index }}">
<div class="request-list-item-detail">
<button class="request-list-item-detail-button" type="button"
data-toggle="collapse" data-target="#headers{{ index }}"
aria-expanded="false" aria-controls="headers{{ index }}">
Headers
</button>
<div class="collapse" id="headers{{ index }}">
<table class="request-list-item-detail-headers">
<thead>
<th class="request-list-item-detail-headers-key">Key</th>
<th class="request-list-item-detail-headers-value">Value</th>
</thead>
<tbody>
{% for key, val in req.getHeaders() %}
<tr>
<td><strong>{{ key|e }}</strong></td>
<td>{{ val|e }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button class="request-list-item-detail-button" type="button"
data-toggle="collapse" data-target="#body{{ index }}"
aria-expanded="false" aria-controls="body{{ index }}">
Body
</button>
<div class="request-list-item-detail-body collapse in" id="body{{ index }}">
{% if (req.getBody()|length < 1) %}
<div class="blankslate blankslate-sm">
<h3>Empty body</h3>
</div>
{% else %}
<pre>{{ req.getBody() }}</pre>
{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="blankslate">
<h3>No requests made yet.</h3>
<p>No http requests has been made to this callback.</p>
</div>
{% endfor %}
</div>
<nav class="text-center" aria-label="Page navigation">
{{ partial('pagination') }}
</nav>
</div>

View file

@ -0,0 +1,2 @@
<h1>Error</h1>

View file

@ -0,0 +1,4 @@
<div class="text-center">
<h1>404 not found</h1>
</div>

View file

@ -0,0 +1,64 @@
<div class="about">
<div class="about-main">
<div class="section">
<div class="section-header">
<h1>About</h1>
</div>
<p>
httpcb was created because I needed to debug some API's with
the callback concept over the years (The problem is you can't call
your local webserver most of the time because it's behind NAT and
your development server is not configured to handle public traffic etc.)
</p>
<p>
There is ofcourse alot of similar applications out there
(alot of people run into the <i>"local dev server"</i>-problem).
However. They where mostly annoying with trail periods, super
advanced UI and limited amount of requests. etc. So i decided to
make my own.
</p>
<p>
Later this project evolved to be my
<i>"try that new thing"</i>-project.
So now, it serves as both a quick tool to check the HTTP request
some API will send you and a place where i can try out new web technologies.
And because the whole point of this application is that it's on a public webserver.
Why not let others use it if they want!
</p>
<p>
So if you want you can send me a <a href="mailto:henrik.hautakoski@gmail.com">email</a>
if you find a bug, request som future or just to let me know that i helped someone with debugging.
</p>
</div>
</div>
<div class="about-reference">
<div class="section">
<h4 class="text-center">Built with</h4>
<div class="phalcon">
<a target="_blank" href="http://phalconphp.com">
<img class="img-responsive" src="/img/phalcon-php.png" />
</a>
</div>
<br/>
<div class="text-center">
<a target="_blank" href="http://getbootstrap.com">
<img height="40" src="/img/bootstrap-solid.svg" />
Bootstrap
</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,32 @@
<div class="feature-section">
<div class="section-header">
<h1>How Does It Work?</h1>
</div>
<div class="steps">
<div class="steps-step1">
<div class="steps-img"></div>
<h3>1. Create a new Callback</h3>
<p>Register a callback and you will be given a unique url</p>
</div>
<div class="steps-step2">
<div class="steps-img"></div>
<h3>2. Set endpoint URL</h3>
<p>Configure your API that you want to test to send to that url</p>
</div>
<div class="steps-step3">
<div class="steps-img"></div>
<h3>3. Monitor traffic</h3>
<p>Make API Calls and watch what your API will send you</p>
</div>
</div>
<div class="text-center">
<a href="/callback/new" class="button button-large button-brand">Get started</a>
</div>
</div>

View file

@ -0,0 +1,23 @@
{% extends 'layout.volt' %}
{% block masthead %}
<div class="masthead">
<h1>Welcome to HTTP Callback</h1>
<p>
This tool is created to help developers integrate
API's that uses HTTP Callbacks. Give it a go!
</p>
<p>
Find out what HTTP Callback can do for you today!
</p>
<a class="button button-large masthead-get-started-button" href="/callback/new">Get started</a>
</div>
{% endblock %}

35
app/views/layout.volt Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
{{ assets.outputCss() }}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>HTTP Callback</title>
</head>
<body>
{% block body %}{% endblock %}
<header class="head-section">
<div class="top-section">
{% include "_templates/navigation.volt" %}
</div>
{% block masthead %}{% endblock %}
</header>
<main class="content-section">
{% include "_templates/flash.volt" %}
{{ content() }}
</main>
<div class="footer-section">
{% include "_templates/footer.volt" %}
</div>
{{ assets.outputJs() }}
</body>
</html>

3
build.sh Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
grunt --target=production

8
composer.json Normal file
View file

@ -0,0 +1,8 @@
{
"require": {
"robmorgan/phinx": "^0.6.2",
"opauth/google": "^0.2.2",
"opauth/twitter": "^0.3.1",
"opauth/github": "^0.1.0",
}
}

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "httpcb",
"version": "0.1.0",
"description": "HTTP Callback Tool",
"main": "Gruntfile.js",
"dependencies": {
"grunt-cli": "^1.2.0",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-less": "^1.3.0",
"grunt-contrib-uglify": "^2.0.0",
"less-plugin-clean-css": "^1.5.1",
"jquery": "~3.0.0"
},
"devDependencies": {
"grunt": "^1.0.1",
"grunt-contrib-watch": "^1.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Henrik Hautakoski <henrik.hautakoski@gmail.com>",
"license": "ISC"
}

8
public/.htaccess Normal file
View file

@ -0,0 +1,8 @@
AddDefaultCharset UTF-8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
</IfModule>

BIN
public/fonts/ionicons.eot Normal file

Binary file not shown.

2230
public/fonts/ionicons.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 326 KiB

BIN
public/fonts/ionicons.ttf Normal file

Binary file not shown.

BIN
public/fonts/ionicons.woff Normal file

Binary file not shown.

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 612 612" enable-background="new 0 0 612 612" xml:space="preserve">
<g id="solid" sodipodi:docname="twitter_bootstrap_logo.svg" inkscape:version="0.48.1 r9760" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<path id="bg" fill="#563D7C" d="M612,510c0,56.1-45.9,102-102,102H102C45.9,612,0,566.1,0,510V102C0,45.9,45.9,0,102,0h408
c56.1,0,102,45.9,102,102V510z"/>
<g id="B" enable-background="new ">
<path fill="#FFFFFF" d="M166.3,133h173.5c32,0,57.7,7.3,77,22s29,36.8,29,66.5c0,18-4.4,33.4-13.2,46.2
c-8.8,12.8-21.4,22.8-37.8,29.8v1c22,4.7,38.7,15.1,50,31.2c11.3,16.2,17,36.4,17,60.8c0,14-2.5,27.1-7.5,39.2
c-5,12.2-12.8,22.7-23.5,31.5s-24.3,15.8-41,21s-36.5,7.8-59.5,7.8h-164L166.3,133L166.3,133z M228.8,282.5h102
c15,0,27.5-4.2,37.5-12.8s15-20.8,15-36.8c0-18-4.5-30.7-13.5-38s-22-11-39-11h-102L228.8,282.5L228.8,282.5z M228.8,439h110.5
c19,0,33.8-4.9,44.2-14.8c10.5-9.8,15.8-23.8,15.8-41.8c0-17.7-5.2-31.2-15.8-40.8s-25.2-14.2-44.2-14.2H228.8V439z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

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