[
  {
    "path": ".gitignore",
    "content": ".svn\nbower_components\nnode_modules\nnpm-debug.log"
  },
  {
    "path": ".jshintrc",
    "content": "{\n  \"bitwise\": true,\n  \"browser\": true,\n  \"curly\": true,\n  \"eqeqeq\": true,\n  \"eqnull\": true,\n  \"esnext\": true,\n  \"immed\": true,\n  \"jquery\": true,\n  \"latedef\": true,\n  \"newcap\": true,\n  \"noarg\": true,\n  \"node\": true,\n  \"strict\": false,\n  \"trailing\": true\n}\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "'use strict';\nmodule.exports = function (grunt) {\n    require('load-grunt-tasks')(grunt);\n    require('time-grunt')(grunt);\n\n    var jsFileList = [\n        'bower_components/jquery.facedetection/dist/jquery.facedetection.js',\n        'assets/js/main.js'\n    ];\n\n    grunt.initConfig({\n        makepot: {\n            options: {\n                type: 'wp-plugin',\n                domainPath: 'languages',\n                potHeaders: {\n                    'report-msgid-bugs-to': 'https://github.com/interconnectit/my-eyes-are-up-here/issues',\n                    'language-team': 'LANGUAGE <EMAIL@ADDRESS>'\n                }\n            },\n            dist: {\n                options: {\n                    potFilename: 'my-eyes-are-up-here.pot',\n                    exclude: [\n                        'assets/.*'\n                    ]\n                }\n            }\n        },\n        checktextdomain: {\n            options: {\n                text_domain: 'my-eyes-are-up-here',\n                keywords: [\n                    '__:1,2d',\n                    '_e:1,2d',\n                    '_x:1,2c,3d',\n                    'esc_html__:1,2d',\n                    'esc_html_e:1,2d',\n                    'esc_html_x:1,2c,3d',\n                    'esc_attr__:1,2d',\n                    'esc_attr_e:1,2d',\n                    'esc_attr_x:1,2c,3d',\n                    '_ex:1,2c,3d',\n                    '_n:1,2,4d',\n                    '_nx:1,2,4c,5d',\n                    '_n_noop:1,2,3d',\n                    '_nx_noop:1,2,3c,4d'\n                ]\n            },\n            files: {\n                src: [\n                    '**/*.php',\n                    '!assets/**',\n                    '!bower_components/**',\n                    '!node_modules/**',\n                ],\n                expand: true\n            }\n        },\n        autoprefixer: {\n            options: {\n                browsers: [\n                    'last 2 versions',\n                    'ie 8',\n                    'ie 9',\n                    'android 2.3',\n                    'android 4',\n                    'opera 12'\n                ]\n            },\n            dist: {\n                src: 'assets/css/main.min.css'\n            }\n        },\n        cssmin: {\n            options: {\n                compatibility: 'ie8',\n                keepSpecialComments: '*',\n                noAdvanced: true\n            },\n            dist: {\n                files: [{\n                    expand: true,\n                    cwd: 'assets/css',\n                    src: ['*.css', '!*.min.css'],\n                    dest: 'assets/css',\n                    ext: '.min.css'\n                }]\n            }\n        },\n        jshint: {\n            options: {\n                jshintrc: '.jshintrc'\n            },\n            all: [\n                'Gruntfile.js',\n                'assets/js/*.js',\n                '!assets/js/scripts.min.js'\n            ]\n        },\n        uglify: {\n            options: {\n                preserveComments: 'some'\n            },\n            dist: {\n                src: jsFileList,\n                dest: 'assets/js/scripts.min.js'\n            }\n        },\n    });\n\n    grunt.registerTask('build', [\n        'makepot',\n        'autoprefixer',\n        'cssmin',\n        'jshint',\n        'uglify'\n    ]);\n};"
  },
  {
    "path": "README.md",
    "content": "My eyes are up here\n===================\n\nFace detection for generating cropped thumbnails in WordPress. Avoiding automatically generated crotch shots since 2013.\n\n## Why would I want this?\n\nConsider a common problem with automatically generated thumbnails in WordPress themes. You need an image of a precise width and height to fit into the design but you never know what images people are uploading.\n\nYou could control the width and height of the standard WP thumbnail sizes and let folks alter the crop as necessary themselves but if you have more than a few custom image sizes you can't alter the crop of those.\n\nLet's say you have a portrait image of someone and your theme needs a landscape crop of the image. WP centers the crop so you'll get an image of the persons crotch... Not ideal. I assume.\n\nThis plugin detects faces in an image and centers the crop using an average of all the faces it finds.\n\n```\nPortrait image:\n\n+-----------+\n|           |\n|     O     |\n|   --|--   |\n|     |     |\n|    | |    |\n|    | |    |\n|           |\n+-----------+\n\nCropped landscape version with default WP cropping:\n\n+-----------+\n|   --|--   |\n|     |     |\n|    | |    |\n+-----------+\n\nCropped landscape version using this plugin:\n\n+-----------+\n|           |\n|     O     |\n|   --|--   |\n+-----------+\n```"
  },
  {
    "path": "assets/css/main.css",
    "content": ".face-detect-panel {\n    margin-bottom: 15px;\n}\n\n.face-detection-ui {\n    overflow: hidden;\n}\n\n.face-detection-image {\n    position: relative;\n    overflow: hidden;\n    float: left;\n    max-width: 100%;\n}\n\n.face-detection-image.active {\n    cursor: crosshair;\n}\n\n.face-detection-image img {\n    max-width: 100%;\n    vertical-align: middle;\n    height: auto;\n    width: auto;\n}\n\n.face-detection-image .hotspot {\n    position: absolute;\n    max-width: 150px;\n    min-width: 10px;\n    height: 0;\n    border: 0px solid #ccc;\n    border: solid 1px rgba(230, 230, 230, .85);\n    background: rgba(255, 155, 155, .5);\n    border-radius: 50%;\n}\n\n.hotspot.face {\n    background: rgba(155, 155, 255, .5);\n}\n\n.hotspot.normal {\n    cursor: pointer;\n}\n\n.face-detect-panel .button {\n    margin-right: 5px;\n    margin-bottom: 5px;\n    display: inline-block;\n}\n\n.status.loading {\n    padding-left: 20px;\n    background: url(../img/spin.gif) no-repeat left center;\n    background-size: contain;\n}\n\n.found-faces img,\n.found-faces canvas {\n    position: static;\n    width: 40px;\n    height: auto;\n    margin: 10px 10px 0 0;\n    display: inline-block;\n}\n\n.post-thumbnail-preview {\n    background: #f5f5f5;\n    background-image: -webkit-gradient(linear, left bottom, left top, from(#f5f5f5), to(#fafafa));\n    background-image: -webkit-linear-gradient(bottom, #f5f5f5, #fafafa);\n    background-image: -moz-linear-gradient(bottom, #f5f5f5, #fafafa);\n    background-image: -o-linear-gradient(bottom, #f5f5f5, #fafafa);\n    background-image: linear-gradient(to top, #f5f5f5, #fafafa);\n    overflow: auto;\n    margin-bottom: 10px;\n    padding: 10px;\n    border: 1px solid #dfdfdf;\n    border-radius: 3px;\n    font-size: 12px;\n    line-height: 0.9;\n    width: 100%;\n    box-sizing: border-box;\n}\n\n.post-thumbnail-preview .preview-wrap {\n    display: inline-block;\n    margin-top: 5px;\n    margin-right: 5px;\n    background: url(../img/spin.gif) no-repeat center;\n}\n\n.post-thumbnail-preview .preview-wrap img {\n    max-width: 100%;\n    width: auto;\n    max-height: 50px;\n    height: auto;\n}\n\n.media-modal .compat-field-face_detection .label {\n    margin-right: 0;\n}\n\n.face-detect-large-hidden,\n.face-detect-large-hidden-copy {\n    position: absolute;\n    left: 9999px;\n    top: -9999px;\n}\n"
  },
  {
    "path": "assets/js/main.js",
    "content": "window.hotspots = {};\n\n// prevent errors while logging to browsers that support it\nif (!window.console) {\n    window.console = {\n        log: function () {\n        }\n    };\n}\n\n;(function ($) {\n\n    function Hotspots() {\n\n        var that = this;\n\n        $.extend(that, {\n\n            _construct: function () {\n\n                // bind behaviour to buttons\n                $(document).on('click', '.face-detection-activate', function () {\n                    that.set_context(this);\n                    that.get_image(that.detect_faces);\n                });\n                $(document).on('click', '.add-hotspots', function () {\n                    that.set_context(this);\n                    that.get_image(that.add_hotspots);\n                });\n\n            },\n\n            attachment_id: null,\n            el: null,\n            image: null,\n            hidden: null,\n            images: null,\n            $context: null,\n            $status_box: null,\n            hotspots: [],\n            faces: [],\n\n            set_context: function (el) {\n                that.el = el;\n                that.attachment_id = $(el).data('attachment-id');\n                that.$ui = $(el).parents('.face-detection-ui');\n                that.$context = $(el).parents('.face-detect-panel');\n                that.$status_box = that.$context.find('.status');\n            },\n\n            // request full image\n            get_image: function (callback) {\n                callback = callback || function () {\n                        return false;\n                    };\n\n                if (that.image && that.$ui.find('.face-detection-image img').length) {\n                    return callback();\n                }\n\n                that.update_status('Loading full size image', true);\n                $.post(meauh.ajax_url, {\n                    action: 'meauh_get_image',\n                    nonce: meauh.get_image_nonce,\n                    attachment_id: that.attachment_id\n                }, function (response) {\n                    if (response.success) {\n                        // set our image\n                        that.image = new Image();\n\n                        // save for later\n                        that.images = response.data;\n\n                        // set source to original uncropped image\n                        that.image.src = response.data.original[0];\n\n                        $(that.image)\n                            .appendTo('.face-detection-image')\n                            .on('load', function () {\n                                that.update_status('Image loaded');\n\n                                // add our large off-screen sampler for pixastic etc...\n                                if (!$('.face-detect-large-hidden').length) {\n                                    $('body').append('<img class=\"face-detect-large-hidden\" src=\"\" alt=\"\" />');\n                                }\n                                $('.face-detect-large-hidden').attr('src', response.data.original[0]);\n\n                                // show current data\n                                that.show_existing($('.face-detection-image').data('hotspots'));\n                                that.show_existing($('.face-detection-image').data('faces'), 'face');\n\n                                return callback();\n                            });\n\n                    }\n                }, 'json');\n\n                return false;\n            },\n\n            update_status: function (status, loading) {\n                loading = loading || false;\n                that.$status_box.html(status);\n                if (loading) {\n                    that.$status_box.addClass('loading');\n                } else {\n                    that.$status_box.removeClass('loading');\n                }\n            },\n\n            detect_faces: function () {\n\n                // Remove the previous copy, end up with one for every button press otherwise.\n                $('.face-detect-large-hidden-copy').remove();\n\n                var $found_box = that.$context.find('.found-faces'),\n                    image = $('.face-detect-large-hidden').get(0),\n                    image_copy = $(image)\n                        .clone()\n                        .removeClass('face-detect-large-hidden')\n                        .addClass('face-detect-large-hidden-copy')\n                        .appendTo('body')\n                        .get(0);\n\n                if ($(that.el).hasClass('has-faces')) {\n\n                    $(image_copy).remove();\n                    //$found_box.html( '' );\n                    $(that.el)\n                        .removeClass('has-faces')\n                        .html('Detect faces');\n\n                    $('.face-detection-image')\n                        .data('faces', '')\n                        .find('.face')\n                        .remove();\n\n                    return that.save({faces: 0});\n                }\n\n                // face detection\n                return $(image_copy).faceDetection({\n                    complete: function (faces) {\n                        // update status - found faces\n                        that.faces = faces;\n\n                        if (!that.faces.length) {\n                            that.update_status('No faces were found');\n                            return;\n                        }\n\n                        // allow removal of found faces\n                        $(that.el)\n                            .addClass('has-faces')\n                            .html('Forget found faces');\n\n                        that.update_status('Found ' + that.faces.length + ' faces, re-cropping thumbnails', true);\n\n                        that.show_existing(that.faces, 'face');\n\n                        // cleanup\n                        $(image_copy).remove();\n\n                        // save data & regen\n                        that.save({faces: that.faces});\n                    },\n                    error: function (img, code, message) {\n                        // update status - error, message\n                        console.log('error', message, img);\n                        that.update_status('Error (' + code + '): ' + message);\n                    }\n                });\n\n            },\n\n            show_existing: function (data, type) {\n                type = type || 'normal';\n\n                var width = $(that.image).width(),\n                    correction = that.images.original[1] / width,\n                    hotspot_width;\n\n                if ('undefined' !== typeof data && data.length) {\n                    $.each(data, function (i, hotspot) {\n                        that.add_hotspot({\n                            x: (hotspot.x / correction),\n                            y: (hotspot.y / correction),\n                            width: hotspot.width / correction,\n                            type: type\n                        });\n                    });\n                }\n            },\n\n            add_hotspots: function () {\n\n                var width = $(that.image).width(),\n                    hotspot_width = width * 0.15,\n                    correction = that.images.original[1] / width;\n\n                // activate hotspots\n                if (!$('.face-detection-image').hasClass('active')) {\n\n                    // edit button\n                    $(that.el)\n                        .addClass('active')\n                        .html('Finish adding hotspots');\n\n                    that.$ui.find('button').not(that.el).attr('disabled', 'disabled');\n\n                    that.update_status('Click on the image below to add hotspots. Clicking a hotspot will remove it.');\n\n                    // bind hotspot toggling\n                    $(that.image).on('click.hotspots', that.hotspot_click);\n\n                    $('.face-detection-image').addClass('active');\n\n                    // deactivate & save\n                } else {\n\n                    // edit button\n                    $(that.el)\n                        .removeClass('active')\n                        .html('Edit hotspots');\n\n                    // remove hotspot toggling\n                    $(that.image).off('click.hotspots');\n\n                    that.hotspots = [];\n                    $('.face-detection-image .hotspot').not('.face').each(function () {\n                        that.hotspots.push({\n                            width: Math.round($(this).width() * correction),\n                            x: Math.round(( $(this).position().left ) * correction),\n                            y: Math.round(( $(this).position().top ) * correction)\n                        });\n                    });\n\n                    $('.face-detection-image').removeClass('active');\n\n                    if (!that.hotspots.length) {\n                        that.hotspots = 0;\n                    }\n\n                    // save data\n                    that.save({hotspots: that.hotspots});\n\n                }\n\n            },\n\n            hotspot_click: function (e) {\n\n                var width = $(that.image).width(),\n                    hotspot_maxwidth = 150,\n                    hotspot_width = width * 0.15 > hotspot_maxwidth ? hotspot_maxwidth : width * 0.15,\n                    hotspot_offset = hotspot_width / 2;\n\n                // Firefox doesn't do offsetX/Y so need to do something a little more complex\n                that.add_hotspot({\n                    x: ( e.offsetX || e.clientX - ( $(e.target).offset().left - window.scrollX ) ) - hotspot_offset,\n                    y: ( e.offsetY || e.clientY - ( $(e.target).offset().top - window.scrollY ) ) - hotspot_offset\n                });\n\n            },\n\n            add_hotspot: function (hotspot) {\n\n                var width = $(that.image).width(),\n                    height = $(that.image).height(),\n                    $parent = $('.face-detection-image'),\n                    hotspot_maxwidth = 150,\n                    hotspot_width = width * 0.15 > hotspot_maxwidth ? hotspot_maxwidth : width * 0.15;\n\n                hotspot = $.extend({\n                    x: 0,\n                    y: 0,\n                    width: hotspot_width, // default 15% wide, max-width 120px\n                    type: 'normal'\n                }, hotspot);\n\n                // Prevent hotspots from being placed outside edges of image.\n                hotspot.x = Math.max((0 - (hotspot.width / 2)), Math.min(hotspot.x, (width - (hotspot.width / 2))));\n                hotspot.y = Math.max((0 - (hotspot.width / 2)), Math.min(hotspot.y, (height - (hotspot.width / 2))));\n\n                $('<div class=\"hotspot ' + hotspot.type + '\"></div>')\n                    .css({\n                        left: ( ( hotspot.x / width ) * 100 ) + '%',\n                        top: ( ( hotspot.y / height ) * 100 ) + '%',\n                        width: ( ( hotspot.width / width ) * 100 ) + '%',\n                        paddingBottom: ( ( hotspot.width / width ) * 100 ) + '%'\n                    })\n                    .attr('title', hotspot.type === 'normal' ? 'Click to toggle on/off' : '')\n                    .appendTo($parent)\n                    .click(function () {\n                        if (!$(this).hasClass('face') && $parent.hasClass('active')) {\n                            $(this).remove();\n                        }\n                    });\n\n            },\n\n            // show a cropped thumbnail preview\n            preview: function () {\n\n                var $previews = $('.post-thumbnail-preview img'),\n                    previews_length = $previews.length;\n\n                that.update_status('Updating preview', true);\n\n                $previews.each(function (i) {\n                    if (!that.images[$(this).data('size')]) {\n                        return;\n                    }\n\n                    $(this)\n                        .fadeTo(300, 0.25)\n                        .attr('src', that.images[$(this).data('size')][0] + '?t=' + new Date().getTime())\n                        .on('load', function () {\n                            $(this).fadeTo(300, 1);\n                            if (i === previews_length - 1) {\n                                that.update_status('');\n                            }\n                        });\n                });\n\n            },\n\n            save: function (data) {\n\n                that.update_status('Re-cropping thumbnails', true);\n\n                that.$ui.find('button').attr('disabled', 'disabled');\n\n                $.post(meauh.ajax_url, $.extend({\n                    action: 'meauh_save_image',\n                    nonce: meauh.save_image_nonce,\n                    attachment_id: that.attachment_id\n                }, data), function (response) {\n                    if (response.success) {\n\n                        that.update_status('Thumbnails re-cropped');\n\n                        $.extend(that.images, response.data.resized);\n\n                        that.preview();\n\n                    } else {\n\n                        that.update_status('No thumbnails were re-cropped');\n\n                    }\n\n                    that.$ui.find('button').removeAttr('disabled');\n                }, 'json');\n\n            }\n\n\n        });\n\n        // initialise\n        that._construct();\n\n        return that;\n\n    }\n\n    // initialise\n    window.hotspots = new Hotspots();\n\n}(jQuery));\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"my-eyes-are-up-here\",\n  \"homepage\": \"https://interconnectit.com\",\n  \"authors\": [\n    \"Evgenii Nasyrov <evgenii@interconnectit.com>\"\n  ],\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"dependencies\": {\n    \"jquery.facedetection\": \"~2.0.2\"\n  }\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"interconnectit/my-eyes-are-up-here\",\n  \"type\": \"wordpress-plugin\",\n  \"license\": \"GPLv2\",\n  \"description\": \"My Eyes Are Up Here helps you control how WordPress generates thumbnails.\",\n  \"homepage\": \"https://github.com/interconnectit/my-eyes-are-up-here\",\n  \"authors\": [\n    {\n      \"name\": \"interconnect/it\",\n      \"email\": \"support@interconnectit.com\",\n      \"homepage\": \"https://github.com/interconnectit\"\n    }\n  ],\n  \"keywords\": [\n    \"wordpress\"\n  ],\n  \"support\": {\n    \"issues\": \"https://github.com/interconnectit/my-eyes-are-up-here/issues\"\n  },\n  \"require\": {\n    \"php\": \">=5.4.0\",\n    \"composer/installers\": \"~1.0\"\n  }\n}"
  },
  {
    "path": "includes/class-meauh-admin.php",
    "content": "<?php\n/**\n * Admin\n *\n * @package my-eyes-are-up-here\n * @author interconnect/it\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n/**\n * Class MEAUH_Admin\n */\nclass MEAUH_Admin {\n\t/**\n\t * Init\n\t */\n\tpublic static function init() {\n\t\t$self = new self;\n\n\t\tadd_action( 'admin_enqueue_scripts', array( $self, 'assets' ) );\n\t}\n\n\t/**\n\t * Constructor\n\t */\n\tpublic function __construct() {\n\t\t$this->includes();\n\t}\n\n\t/**\n\t * Assets\n\t */\n\tpublic function assets() {\n\t\t// Main script.\n\t\twp_enqueue_script(\n\t\t\t'meauh-main',\n\t\t\tmeauh()->plugin_url() . '/assets/js/scripts.min.js',\n\t\t\tarray( 'jquery' ),\n\t\t\tfilemtime( meauh()->plugin_path() . '/assets/js/scripts.min.js' ),\n\t\t\ttrue\n\t\t);\n\n\t\t// Main script variables.\n\t\twp_localize_script( 'meauh-main', 'meauh', array(\n\t\t\t'ajax_url'         => meauh()->ajax_url(),\n\t\t\t'get_image_nonce'  => wp_create_nonce( MEAUH_Ajax::NONCE_GET_IMAGE ),\n\t\t\t'save_image_nonce' => wp_create_nonce( MEAUH_Ajax::NONCE_SAVE_IMAGE ),\n\t\t) );\n\n\t\t// Main style.\n\t\twp_enqueue_style(\n\t\t\t'meauh-main',\n\t\t\tmeauh()->plugin_url() . '/assets/css/main.min.css',\n\t\t\tarray(),\n\t\t\tfilemtime( meauh()->plugin_path() . '/assets/css/main.min.css' ),\n\t\t\t'all'\n\t\t);\n\t}\n\n\t/**\n\t * Includes\n\t */\n\tprotected function includes() {\n\t}\n}\n\nMEAUH_Admin::init();\n"
  },
  {
    "path": "includes/class-meauh-ajax.php",
    "content": "<?php\n/**\n * Ajax\n *\n * @package my-eyes-are-up-here\n * @author interconnect/it\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n/**\n * Class MEAUH_Ajax\n */\nclass MEAUH_Ajax {\n\tconst NONCE_GET_IMAGE = 'meauh-get-image';\n\tconst NONCE_SAVE_IMAGE = 'meauh-save-image';\n\n\t/**\n\t * Ajax events\n\t *\n\t * @var array\n\t */\n\tprotected static $events = array(\n\t\t'get_image'  => false,\n\t\t'save_image' => false,\n\t);\n\n\t/**\n\t * Init\n\t */\n\tpublic static function init() {\n\t\t$self = new self;\n\n\t\tforeach ( self::$events as $event => $nopriv ) {\n\t\t\tadd_action( 'wp_ajax_meauh_' . $event, array( $self, $event ) );\n\n\t\t\tif ( $nopriv ) {\n\t\t\t\tadd_action( 'wp_ajax_nopriv_meauh_' . $event, array( $self, $event ) );\n\t\t\t}\n\t\t}\n\n        add_action( 'wp_ajax_save-attachment-compat', 'save_image', 0, 1 );\n\t}\n\n\t/**\n\t * Get an image\n\t */\n\tpublic function get_image() {\n\t\tcheck_ajax_referer( self::NONCE_GET_IMAGE, 'nonce' );\n\n\t\t$attachment_id = isset( $_POST['attachment_id'] ) ?\n\t\t\tabsint( $_POST['attachment_id'] ) :\n\t\t\tfalse;\n\n\t\tif ( $attachment_id && $this->is_attachment( $attachment_id ) ) {\n\t\t\twp_send_json_success( array(\n\t\t\t\t'original' => wp_get_attachment_image_src( $attachment_id, 'full' ),\n\t\t\t) );\n\t\t} else {\n\t\t\twp_send_json_error();\n\t\t}\n\t}\n\n\t/**\n\t * Save an image\n\t */\n\tpublic function save_image() {\n\t\tcheck_ajax_referer( self::NONCE_SAVE_IMAGE, 'nonce' );\n\n\t\t$attachment_id = isset( $_POST['attachment_id'] ) ?\n\t\t\tabsint( $_POST['attachment_id'] ) :\n\t\t\tfalse;\n\n\t\tif ( ! $this->is_attachment( $attachment_id ) ) {\n\t\t\twp_send_json_error();\n\t\t}\n\n\t\t// WP Offload S3 Compatibility.\n\t\t$this->as3cf_compatibility( $attachment_id );\n\n\t\t// Save faces.\n\t\t$this->save_image_faces( $attachment_id );\n\n\t\t// Save hotspots.\n\t\t$this->save_image_hotspots( $attachment_id );\n\n\t\t// Regenerate thumbs.\n\t\t$resized = MEAUH_Attachment::regenerate( $attachment_id );\n\t\tif ( $resized ) {\n\t\t\twp_send_json_success( array(\n\t\t\t\t'resized' => $resized,\n\t\t\t) );\n\t\t}\n\t}\n\n\t/**\n\t * Is attachment\n\t *\n\t * @param int $attachment_id Attachment id.\n\t *\n\t * @return bool\n\t */\n\tprotected function is_attachment( $attachment_id ) {\n\t\treturn $attachment_id &&\n\t\t       get_post( $attachment_id ) &&\n\t\t       'attachment' === get_post_type( $attachment_id );\n\t}\n\n\t/**\n\t * WP Offload S3 Compatibility\n\t *\n\t * @param $attachment_id Attachment ID.\n\t */\n\tprotected function as3cf_compatibility( $attachment_id ) {\n\t\tif ( ! is_plugin_active( 'amazon-s3-and-cloudfront/wordpress-s3.php' ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$file = get_attached_file( $attachment_id );\n\n\t\tfile_put_contents( $file, file_get_contents( $file ) );\n\t}\n\n\t/**\n\t * Save image faces\n\t *\n\t * @param int $attachment_id Attachment ID.\n\t */\n\tprotected function save_image_faces( $attachment_id ) {\n\t\tif ( ! empty( $_POST['faces'] ) ) {\n\t\t\tupdate_post_meta(\n\t\t\t\t$attachment_id,\n\t\t\t\t'faces',\n\t\t\t\tarray_filter( $_POST['faces'], array( $this, 'filter' ) )\n\t\t\t);\n\t\t} else {\n\t\t\tdelete_post_meta( $attachment_id, 'faces' );\n\t\t}\n\t}\n\n\t/**\n\t * Save image hotspots\n\t *\n\t * @param int $attachment_id Attachment ID.\n\t */\n\tprotected function save_image_hotspots( $attachment_id ) {\n\t\tif ( ! empty( $_POST['hotspots'] ) ) {\n\t\t\tupdate_post_meta(\n\t\t\t\t$attachment_id,\n\t\t\t\t'hotspots',\n\t\t\t\tarray_filter( $_POST['hotspots'], array( $this, 'filter' ) )\n\t\t\t);\n\t\t} else {\n\t\t\tdelete_post_meta( $attachment_id, 'hotspots' );\n\t\t}\n\t}\n\n\t/**\n\t * Make sure we got solid data\n\t *\n\t * @param array $value Values to filter.\n\t *\n\t * @return array\n\t */\n\tprotected function filter( $value ) {\n\t\t$allowed_keys = array(\n\t\t\t'x',\n\t\t\t'y',\n\t\t\t'width',\n\t\t);\n\n\t\t$value = array_intersect_key( $value, array_flip( $allowed_keys ) );\n\n\t\tif ( isset( $value['x'], $value['y'], $value['width'] ) ) {\n\t\t\treturn array_filter( $value, 'is_numeric' );\n\t\t} else {\n\t\t\treturn array();\n\t\t}\n\t}\n}\n\nMEAUH_Ajax::init();\n"
  },
  {
    "path": "includes/class-meauh-attachment.php",
    "content": "<?php\n/**\n * Attachment\n *\n * @package my-eyes-are-up-here\n * @author interconnect/it\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n/**\n * Class MEAUH_Attachment\n */\nclass MEAUH_Attachment {\n\t/**\n\t * Faces\n\t *\n\t * @var array\n\t */\n\tprotected $faces = array();\n\n\t/**\n\t * Hotspots\n\t *\n\t * @var array\n\t */\n\tprotected $hotspots = array();\n\n\t/**\n\t * Init\n\t */\n\tpublic static function init() {\n\t\t$self = new self;\n\n\t\t// Current attachment data.\n\t\tadd_filter( 'get_attached_file', array( $self, 'set_attachment_id' ), 10, 2 );\n\t\tadd_filter( 'update_attached_file', array( $self, 'set_attachment_id' ), 10, 2 );\n\n\t\t// Image resize dimensions.\n\t\tadd_filter( 'image_resize_dimensions', array( $self, 'crop' ), 11, 6 );\n\n\t\t// Add button.\n\t\tadd_filter( 'attachment_fields_to_edit', array( $self, 'edit_fields' ), 10, 2 );\n\t}\n\n\t/**\n\t * Regenerate thumbnails\n\t *\n\t * @param int $attachment_id Attachment id.\n\t *\n\t * @return array\n\t */\n\tpublic static function regenerate( $attachment_id ) {\n\t\t// 5 minutes per image should be PLENTY.\n\t\t@set_time_limit( 5 * MINUTE_IN_SECONDS );\n\n\t\t// Resize thumbnail.\n\t\t$file     = get_attached_file( $attachment_id );\n\t\t$metadata = wp_generate_attachment_metadata( $attachment_id, $file );\n\n\t\tif ( is_wp_error( $metadata ) ) {\n\t\t\treturn array( 'id' => $attachment_id, 'error' => $metadata->get_error_message() );\n\t\t}\n\n\t\tif ( empty( $metadata ) ) {\n\t\t\treturn array( 'id' => $attachment_id, 'error' => __( 'Unknown failure reason', 'my-eyes-are-up-here' ) );\n\t\t}\n\n\t\t// If this fails, then it just means that nothing was changed.\n\t\twp_update_attachment_metadata( $attachment_id, $metadata );\n\n\t\t$sizes   = self::get_cropped_sizes();\n\t\t$resized = array();\n\n\t\tforeach ( $sizes as $size => $atts ) {\n\t\t\t$resized[ $size ] = wp_get_attachment_image_src( $attachment_id, $size );\n\t\t}\n\n\t\treturn $resized;\n\t}\n\n\t/**\n\t * Hacky use of attached_file filters to get current attachment ID being resized\n\t * Used to store face location and dimensions\n\t *\n\t * @param string $file File name.\n\t * @param int $attachment_id Attachment id.\n\t *\n\t * @return string\n\t */\n\tpublic function set_attachment_id( $file, $attachment_id ) {\n\t\t$this->faces    = (array) get_post_meta( $attachment_id, 'faces', true );\n\t\t$this->hotspots = (array) get_post_meta( $attachment_id, 'hotspots', true );\n\n\t\treturn $file;\n\t}\n\n\t/**\n\t * Alters the crop location of the GD image editor class by detecting faces\n\t * and centering the crop around them\n\t *\n\t * @param array $payload Payload.\n\t * @param int $orig_w Original width.\n\t * @param int $orig_h Original height.\n\t * @param int $dest_w width.\n\t * @param int $dest_h height.\n\t * @param bool $crop Crop.\n\t *\n\t * @return array\n\t */\n\tpublic function crop( $payload, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) {\n\t\t$hotspots = array_filter( array_merge( $this->faces, $this->hotspots ) );\n\t\tif ( ! $crop || empty( $hotspots ) ) {\n\t\t\treturn $payload;\n\t\t}\n\n\t\tif ( is_array( $payload ) ) {\n\t\t\tlist( $dest_x, $dest_y, $src_x, $src_y, $new_w, $new_h, $src_w, $src_h ) = $payload;\n\t\t}\n\n\t\t// Get faces area.\n\t\t$hotspot_src_x     = $hotspot_src_y = PHP_INT_MAX;\n\t\t$hotspot_src_max_x = $hotspot_src_max_w = 0;\n\t\t$hotspot_src_max_y = $hotspot_src_max_h = 0;\n\n\t\t// Create bounding box.\n\t\tforeach ( $hotspots as $hotspot ) {\n\t\t\t$hotspot = array_map( 'absint', $hotspot );\n\n\t\t\t// Left and top most x,y.\n\t\t\tif ( $hotspot_src_x > $hotspot['x'] ) {\n\t\t\t\t$hotspot_src_x = $hotspot['x'];\n\t\t\t}\n\n\t\t\tif ( $hotspot_src_y > $hotspot['y'] ) {\n\t\t\t\t$hotspot_src_y = $hotspot['y'];\n\t\t\t}\n\n\t\t\t// Right and bottom most x,y.\n\t\t\tif ( $hotspot_src_max_x < $hotspot['x'] ) {\n\t\t\t\t$hotspot_src_max_x = $hotspot['x'];\n\t\t\t}\n\n\t\t\tif ( $hotspot_src_max_y < $hotspot['y'] ) {\n\t\t\t\t$hotspot_src_max_y = $hotspot['y'];\n\t\t\t}\n\t\t}\n\n\t\t$hotspot_src_w = $hotspot_src_max_x - $hotspot_src_x;\n\t\t$hotspot_src_h = $hotspot_src_max_y - $hotspot_src_y;\n\n\t\t// Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h.\n\t\t$aspect_ratio = $orig_w / $orig_h;\n\n\t\t// Preserve settings already filtered in.\n\t\tif ( null === $payload ) {\n\t\t\t$new_w = min( $dest_w, $orig_w );\n\t\t\t$new_h = min( $dest_h, $orig_h );\n\n\t\t\tif ( ! $new_w ) {\n\t\t\t\t$new_w = intval( $new_h * $aspect_ratio );\n\t\t\t}\n\n\t\t\tif ( ! $new_h ) {\n\t\t\t\t$new_h = intval( $new_w / $aspect_ratio );\n\t\t\t}\n\t\t}\n\n\t\t$size_ratio = max( $new_w / $orig_w, $new_h / $orig_h );\n\n\t\t$crop_w = round( $new_w / $size_ratio );\n\t\t$crop_h = round( $new_h / $size_ratio );\n\n\t\t$src_x = floor( ( $orig_w - $crop_w ) / 2 );\n\t\t$src_y = floor( ( $orig_h - $crop_h ) / 2 );\n\n\t\t// Bounding box.\n\t\tif ( 0 == $src_x ) {\n\t\t\t$src_y = ( $hotspot_src_y + $hotspot_src_h / 2 ) - $crop_h / 2;\n\t\t\t$src_y = min( max( 0, $src_y ), $orig_h - $crop_h );\n\t\t}\n\n\t\tif ( 0 == $src_y ) {\n\t\t\t$src_x = ( $hotspot_src_x + $hotspot_src_w / 2 ) - $crop_w / 2;\n\t\t\t$src_x = min( max( 0, $src_x ), $orig_w - $crop_w );\n\t\t}\n\n\t\treturn array( 0, 0, (int) $src_x, (int) $src_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h );\n\t}\n\n\t/**\n\t * Edit fields\n\t *\n\t * @param array $form_fields Form fields.\n\t * @param stdClass $attachment Attachment.\n\t *\n\t * @return mixed\n\t */\n\tpublic function edit_fields( array $form_fields, $attachment ) {\n\t\tif ( ! wp_attachment_is_image( $attachment->ID ) ) {\n\t\t\treturn $form_fields;\n\t\t}\n\n\t\t$faces    = get_post_meta( $attachment->ID, 'faces', true );\n\t\t$hotspots = get_post_meta( $attachment->ID, 'hotspots', true );\n\n\t\t$data_atts = '';\n\t\tif ( $faces ) {\n\t\t\t$data_atts .= ' data-faces=\"' . esc_attr( json_encode( $faces ) ) . '\"';\n\t\t}\n\t\tif ( $hotspots ) {\n\t\t\t$data_atts .= ' data-hotspots=\"' . esc_attr( json_encode( $hotspots ) ) . '\"';\n\t\t}\n\n\t\t$button = '\n\t\t<div class=\"face-detection-ui hide-if-no-js\">\n\t\t\t<div class=\"post-thumbnail-preview\">\n\t\t\t\t<div><strong>' . __( 'Thumb Previews', 'my-eyes-are-up-here' ) . '</strong></div>';\n\n\t\tforeach ( self::get_cropped_sizes() as $size => $atts ) {\n\t\t\t$src = wp_get_attachment_image_src( $attachment->ID, $size );\n\t\t\t$button .= '<div class=\"preview-wrap\"><img src=\"' . $src[0] . '?v=' . time() . '\" alt=\"' . $size . '\" data-size=\"' . $size . '\"></div>';\n\t\t}\n\n\t\t$button .= '\n\t\t\t</div>\n\t\t\t<div class=\"face-detection face-detect-panel\">';\n\n\t\tif ( $faces ) {\n\t\t\t$button .= sprintf( '<button class=\"button face-detection-activate has-faces\" type=\"button\" data-attachment-id=\"%d\">%s</button>',\n\t\t\t\t$attachment->ID,\n\t\t\t\t__( 'Forget found faces', 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t} else {\n\t\t\t$button .= sprintf( '<button class=\"button face-detection-activate\" type=\"button\" data-attachment-id=\"%d\">%s</button>',\n\t\t\t\t$attachment->ID,\n\t\t\t\t__( 'Detect faces', 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t}\n\n\t\t$button .= '<span class=\"status\"></span>';\n\t\t$button .= sprintf( '<p class=\"description\">%s</p>',\n\t\t\t__( \"Please note this is basic face detection and won't find everything. Use hotspots to highlight any that were missed.\",\n\t\t\t\t'my-eyes-are-up-here' )\n\t\t);\n\t\t$button .= '<div class=\"found-faces\"></div>';\n\n\t\tif ( false && $faces ) {\n\t\t\t$button .= '<p class=\"detected-faces\">';\n\t\t\t$button .= sprintf( __( '%d %s found, thumbnails regenerated to fit them into crop area.',\n\t\t\t\t'my-eyes-are-up-here' ),\n\t\t\t\tcount( $faces ),\n\t\t\t\t_n( 'face', 'faces', count( $faces ), 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t\t$button .= '</p>';\n\t\t}\n\n\t\t$button .= '\n\t\t\t</div>\n\t\t\t<div class=\"image-hotspots face-detect-panel\">';\n\n\t\tif ( $hotspots ) {\n\t\t\t$button .= sprintf( '<button class=\"button add-hotspots has-hotspots\" type=\"button\" data-attachment-id=\"%d\">%s</button>',\n\t\t\t\t$attachment->ID,\n\t\t\t\t__( 'Edit hotspots', 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t} else {\n\t\t\t$button .= sprintf( '<button class=\"button add-hotspots\" type=\"button\" data-attachment-id=\"%d\">%s</button>',\n\t\t\t\t$attachment->ID,\n\t\t\t\t__( 'Add hotspots', 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t}\n\n\t\t$button .= '<span class=\"status\"></span>';\n\t\t$button .= sprintf( '<p class=\"description\">%s</p>',\n\t\t\t__( 'Manually add hotspots that you want to avoid cropping.', 'my-eyes-are-up-here' )\n\t\t);\n\n\t\tif ( false && $hotspots ) {\n\t\t\t$button .= '<p class=\"added-hotspots\">';\n\t\t\t$button .= sprintf( __( '%d %s found, thumbnails regenerated to fit them into crop area.',\n\t\t\t\t'my-eyes-are-up-here' ),\n\t\t\t\tcount( $hotspots ),\n\t\t\t\t_n( 'hotspot', 'hotspots', count( $hotspots ), 'my-eyes-are-up-here' )\n\t\t\t);\n\t\t\t$button .= '</p>';\n\t\t}\n\n\t\t$button .= '\n\t\t\t</div>\n\t\t\t<div class=\"face-detection-crop-preview\"></div>\n\t\t\t<div class=\"face-detection-image\"' . $data_atts . '></div>\n\t\t</div>\n\t\t<div class=\"hide-if-js\">\n\t\t\t<p>' . __( 'This plugin requires javascript to work', 'my-eyes-are-up-here' ) . '</p>\n\t\t</div>';\n\n\t\t$form_fields['face_detection'] = array(\n\t\t\t'label' => __( 'Face detection', 'my-eyes-are-up-here' ),\n\t\t\t'input' => 'html',\n\t\t\t'html'  => $button,\n\t\t);\n\n\t\treturn $form_fields;\n\t}\n\n\t/**\n\t * Get cropped sizes\n\t *\n\t * @return array\n\t */\n\tprotected static function get_cropped_sizes() {\n\t\tglobal $_wp_additional_image_sizes;\n\n\t\t$sizes      = array();\n\t\t$size_names = get_intermediate_image_sizes();\n\n\t\tforeach ( $size_names as $size ) {\n\t\t\tif ( in_array( $size, array( 'thumbnail', 'medium', 'large' ) ) ) {\n\t\t\t\t$width  = intval( get_option( $size . '_size_w' ) );\n\t\t\t\t$height = intval( get_option( $size . '_size_h' ) );\n\t\t\t\t$crop   = get_option( $size . '_crop' );\n\t\t\t} else if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {\n\t\t\t\t$width  = $_wp_additional_image_sizes[ $size ]['width'];\n\t\t\t\t$height = $_wp_additional_image_sizes[ $size ]['height'];\n\t\t\t\t$crop   = $_wp_additional_image_sizes[ $size ]['crop'];\n\t\t\t}\n\t\t\tif ( $crop ) {\n\t\t\t\t$sizes[ $size ] = array(\n\t\t\t\t\t'width'  => $width,\n\t\t\t\t\t'height' => $height,\n\t\t\t\t\t'crop'   => $crop,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn $sizes;\n\t}\n}\n\nMEAUH_Attachment::init();\n"
  },
  {
    "path": "languages/meauh.pot",
    "content": "# Copyright (C) 2016 interconnect/it\n# This file is distributed under the same license as the My Eyes Are Up Here package.\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: My Eyes Are Up Here 1.1.3\\n\"\n\"Report-Msgid-Bugs-To: \"\n\"https://github.com/interconnectit/my-eyes-are-up-here/issues\\n\"\n\"POT-Creation-Date: 2016-04-01 11:05:34+00:00\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n\"X-Generator: grunt-wp-i18n 0.5.4\\n\"\n\n#: includes/class-meauh-attachment.php:68\nmsgid \"Unknown failure reason\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:220\nmsgid \"Thumb Previews\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:234\nmsgid \"Forget found faces\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:239\nmsgid \"Detect faces\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:245\nmsgid \"\"\n\"Please note this is basic face detection and won't find everything. Use \"\n\"hotspots to highlight any that were missed.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:251\n#: includes/class-meauh-attachment.php:281\nmsgid \"%d %s found, thumbnails regenerated to fit them into crop area.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:253\nmsgid \"face\"\nmsgid_plural \"faces\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: includes/class-meauh-attachment.php:265\nmsgid \"Edit hotspots\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:270\nmsgid \"Add hotspots\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:276\nmsgid \"Manually add hotspots that you want to avoid cropping.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:283\nmsgid \"hotspot\"\nmsgid_plural \"hotspots\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: includes/class-meauh-attachment.php:294\nmsgid \"This plugin requires javascript to work\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:298\nmsgid \"Face detection\"\nmsgstr \"\"\n\n#. Plugin Name of the plugin/theme\nmsgid \"My Eyes Are Up Here\"\nmsgstr \"\"\n\n#. Plugin URI of the plugin/theme\nmsgid \"https://github.com/interconnectit/my-eyes-are-up-here\"\nmsgstr \"\"\n\n#. Description of the plugin/theme\nmsgid \"\"\n\"Detects faces during thumbnail cropping and moves the crop position \"\n\"accordingly.\"\nmsgstr \"\"\n\n#. Author of the plugin/theme\nmsgid \"interconnect/it\"\nmsgstr \"\"\n\n#. Author URI of the plugin/theme\nmsgid \"http://interconnectit.com\"\nmsgstr \"\""
  },
  {
    "path": "languages/my-eyes-are-up-here.pot",
    "content": "# Copyright (C) 2017 interconnect/it\n# This file is distributed under the same license as the My Eyes Are Up Here package.\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: My Eyes Are Up Here 1.1.8\\n\"\n\"Report-Msgid-Bugs-To: \"\n\"https://github.com/interconnectit/my-eyes-are-up-here/issues\\n\"\n\"POT-Creation-Date: 2017-05-15 10:38:31+00:00\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n\"X-Generator: grunt-wp-i18n 0.5.4\\n\"\n\n#: includes/class-meauh-attachment.php:68\nmsgid \"Unknown failure reason\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:220\nmsgid \"Thumb Previews\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:234\nmsgid \"Forget found faces\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:239\nmsgid \"Detect faces\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:245\nmsgid \"\"\n\"Please note this is basic face detection and won't find everything. Use \"\n\"hotspots to highlight any that were missed.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:252\n#: includes/class-meauh-attachment.php:283\nmsgid \"%d %s found, thumbnails regenerated to fit them into crop area.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:255\nmsgid \"face\"\nmsgid_plural \"faces\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: includes/class-meauh-attachment.php:267\nmsgid \"Edit hotspots\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:272\nmsgid \"Add hotspots\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:278\nmsgid \"Manually add hotspots that you want to avoid cropping.\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:286\nmsgid \"hotspot\"\nmsgid_plural \"hotspots\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n#: includes/class-meauh-attachment.php:297\nmsgid \"This plugin requires javascript to work\"\nmsgstr \"\"\n\n#: includes/class-meauh-attachment.php:301\nmsgid \"Face detection\"\nmsgstr \"\"\n\n#. Plugin Name of the plugin/theme\nmsgid \"My Eyes Are Up Here\"\nmsgstr \"\"\n\n#. Plugin URI of the plugin/theme\nmsgid \"https://github.com/interconnectit/my-eyes-are-up-here\"\nmsgstr \"\"\n\n#. Description of the plugin/theme\nmsgid \"\"\n\"Detects faces during thumbnail cropping and moves the crop position \"\n\"accordingly.\"\nmsgstr \"\"\n\n#. Author of the plugin/theme\nmsgid \"interconnect/it\"\nmsgstr \"\"\n\n#. Author URI of the plugin/theme\nmsgid \"http://interconnectit.com\"\nmsgstr \"\""
  },
  {
    "path": "my-eyes-are-up-here.php",
    "content": "<?php\n/**\n * Plugin Name: My Eyes Are Up Here\n * Plugin URI: https://github.com/interconnectit/my-eyes-are-up-here\n * Description: Detects faces during thumbnail cropping and moves the crop position accordingly.\n * Version: 1.1.9\n * Author: interconnect/it\n * Author URI: http://interconnectit.com\n *\n * Text Domain: my-eyes-are-up-here\n * Domain Path: /languages/\n *\n * @package my-eyes-are-up-here\n * @author interconnect/it\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n/**\n * Class MyEyesAreUpHere\n */\nfinal class MyEyesAreUpHere {\n\tconst REQUEST_ADMIN = 'admin';\n\tconst REQUEST_AJAX = 'ajax';\n\n\t/**\n\t * Instance\n\t *\n\t * @var MyEyesAreUpHere\n\t */\n\tprivate static $_instance;\n\n\t/**\n\t * Get instance\n\t *\n\t * @return MyEyesAreUpHere\n\t */\n\tpublic static function instance() {\n\t\tif ( is_null( self::$_instance ) ) {\n\t\t\tself::$_instance = new self;\n\t\t}\n\n\t\treturn self::$_instance;\n\t}\n\n\t/**\n\t * Constructor\n\t */\n\tpublic function __construct() {\n\t\t$this->includes();\n\t\t$this->init_hooks();\n\t}\n\n\t/**\n\t * Determine request type\n\t *\n\t * @param string $type Request type.\n\t *\n\t * @return bool\n\t */\n\tpublic function is_request( $type ) {\n\t\tswitch ( $type ) {\n\t\t\tcase self::REQUEST_ADMIN:\n\t\t\t\treturn is_admin();\n\n\t\t\tcase self::REQUEST_AJAX:\n\t\t\t\treturn defined( 'DOING_AJAX' );\n\t\t}\n\t}\n\n\t/**\n\t * Get plugin path\n\t *\n\t * @return string\n\t */\n\tpublic function plugin_path() {\n\t\treturn untrailingslashit( plugin_dir_path( __FILE__ ) );\n\t}\n\n\t/**\n\t * Get plugin URL\n\t *\n\t * @return string\n\t */\n\tpublic function plugin_url() {\n\t\treturn untrailingslashit( plugins_url( '/', __FILE__ ) );\n\t}\n\n\t/**\n\t * Get ajax URL\n\t *\n\t * @return string\n\t */\n\tpublic function ajax_url() {\n\t\treturn admin_url( 'admin-ajax.php', 'relative' );\n\t}\n\n\t/**\n\t * Load localisation\n\t */\n\tpublic function localisation() {\n\t\t$locale = apply_filters( 'plugin_locale', get_locale(), 'my-eyes-are-up-here' );\n\n\t\tload_textdomain( 'my-eyes-are-up-here', WP_LANG_DIR . '/my-eyes-are-up-here/my-eyes-are-up-here-' . $locale . '.mo' );\n\t\tload_plugin_textdomain( 'my-eyes-are-up-here', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );\n\t}\n\n\t/**\n\t * Includes\n\t */\n\tprotected function includes() {\n\t\trequire_once 'includes/class-meauh-ajax.php';\n\t\trequire_once 'includes/class-meauh-attachment.php';\n\n\t\tif ( $this->is_request( self::REQUEST_ADMIN ) ) {\n\t\t\trequire_once 'includes/class-meauh-admin.php';\n\t\t}\n\t}\n\n\t/**\n\t * Init hooks\n\t */\n\tprotected function init_hooks() {\n\t\tadd_action( 'init', array( $this, 'localisation' ), 0 );\n\t}\n}\n\n/**\n * Get instance\n *\n * @return MyEyesAreUpHere\n */\nfunction meauh() {\n\treturn MyEyesAreUpHere::instance();\n}\n\n// Global for backwards compatibility.\n$GLOBALS['meauh'] = meauh();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"my-eyes-are-up-here\",\n  \"version\": \"1.1.9\",\n  \"author\": \"interconnect/it <support@interconnectit.com>\",\n  \"homepage\": \"https://interconnectit.com\",\n  \"private\": true,\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/interconnectit/my-eyes-are-up-here.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/interconnectit/my-eyes-are-up-here/issues\"\n  },\n  \"licenses\": [\n    {\n      \"type\": \"GPLv2\",\n      \"url\": \"http://www.gnu.org/licenses/gpl-2.0.html\"\n    }\n  ],\n  \"scripts\": {\n    \"postinstall\": \"bower install && grunt build\"\n  },\n  \"devDependencies\": {\n    \"bower\": \"^1.7.1\",\n    \"grunt\": \"^0.4.5\",\n    \"grunt-autoprefixer\": \"^3.0.3\",\n    \"grunt-contrib-cssmin\": \"^0.14.0\",\n    \"grunt-contrib-jshint\": \"^0.11.3\",\n    \"grunt-contrib-uglify\": \"^0.11.0\",\n    \"grunt-wp-i18n\": \"^0.5.4\",\n    \"load-grunt-tasks\": \"^3.3.0\",\n    \"time-grunt\": \"^1.2.2\"\n  }\n}\n"
  },
  {
    "path": "readme.txt",
    "content": "=== My Eyes Are Up Here ===\nContributors: interconnectit, sanchothefat, spectacula, AndyWalmsley\nDonate link: https://myeyesareuphere.interconnectit.com/donate/\nTags: thumbnails, image editing, image, featured image\nRequires at least: 3.8.1\nTested up to: 4.7.4\nStable tag: 1.1.9\nLicense: GPLv2\nLicense URI: http://www.gnu.org/licenses/gpl-2.0.html\n\nMy Eyes Are Up Here helps you control how WordPress generates thumbnails.\n\n== Description == \n\n= What is it? =\nA fantastic new plugin that helps you control how WordPress generates thumbnails.\n\n= Why use it? =\nWhen WordPress automatically generates thumbnails, it sometimes doesn't crop them in a way that is suitable for the image you've uploaded. If your image isn't the correct format, and let's face it, you never know what images people are uploading - you'll run the risk of a badly cropped image. Not good.\nIf you have a full portrait image of a person that you've uploaded, but you need the image to appear landscape, you're in trouble! WordPress will centre the image so that you end up with person's crotch. Not good. Or let's say you have a landscape image, with a person's face on the right hand side, but you need it to display in a square thumbnail. You'll end up with half a face as WordPress centres the image.\n\n= What does the plugin do? =\nYou can control how you want your WordPress thumbnails to appear on your website. Regardless of the image format you upload, you can either use the automatic face detector or if you want even more control, you can manually add hotspots.\n\n= How do I use it? =\nNavigate to your media library then click on the image you want to edit. Use the detect faces or edit hotspots option to edit your image. You'll see thumbnail previews when you've applied these edits, when you're happy hit update. Simple. \n\n== Installation ==\n\n= The install =\n1. You can install the plugin using the auto-install tool from the WordPress back-end.\n2. To manually install, upload the folder `/myeyesareuphere/` to `/wp-content/plugins/` directory.\n3. Activate the plugin through the 'Plugins' menu in WordPress\n\n== Usage ==\n\n1. Once the plugin is activated, navigate to your 'Media Library'.\n2. Click on the image you want to edit to bring up your 'Edit Media' options.\n3. You should now be able to see extra image editing options, below the 'Description' box.\n4. By clicking 'Detect faces' or 'Add hotspots' you can now start to edit your image thumbnails.\n5. If you click the 'Detect faces' button, it will centre the crop using an average of all the faces it finds.\n5. Please note this is basic face detection and won't find everything. \n6. You can click and create 1 or several hotspots to centre the crop of your thumbnails, if 'Detect faces' doesn't work.   \n7. If you're happy with your 'Thumb Previews' hit save, and you're done. Simple. \n\n== Frequently Asked Questions ==\n\n= What happens when there are multiple hotspots/faces detected? =\n\nThis will crop the image to get as many hotspots in the thumbnail as possible or crop around the center of the hotspots if not.\n\n= How do I report a problem? =\n\nYou can email us at cases@interconnectit.fogbugz.com with \"My Eyes Are Up Here\" in the subject and the following information:\n\n1. What browser and version is this problem occurring with?\n2. What WordPress version are you using?\n3. What version of My Eyes Are Up Here are you using?\n4. Are there any errors in the javascript console?\n\t* Chrome and Firefox: ctrl + shift + j (Win) or alt + cmd + j (Mac)\n\t* Internet Explorer: F12 and click on 'Script' then 'Console'\n5. What are the steps you used to produce this problem?\n\n== Screenshots ==\n\n1. Default WordPress cropping where the thumbnail is cropped to the centre of the image.\n\n2. Thumbnail after My Eyes Are Up Here has detected any faces in the image.\n\n3. How WordPress crops the image without My Eyes Are Up Here.\n\n4. How the image appears once My Eyes Are Up Here has been installed and applied.\n\n== Changelog ==\n\n= 1.1.9 =\n* Fix the js error for WP Customizer\n\n= 1.1.8 =\n* Remove deprecated jQuery methods\n\n= 1.1.7 =\n* Remove PHP warnings\n* Add composer support\n\n= 1.1.6 =\n* Prevent hotspots from being placed outside edges of image\n\n= 1.1.5 =\n* AS3CF compatibility\n\n= 1.1.4 =\n* Fix translation file\n\n= 1.1.3 =\n* Fix text domain\n\n= 1.1.2 =\n* Add translation file\n\n= 1.1.1 =\n* Better ajax validation\n* Fix regenerate thumbnail issue\n\n= 1.1.0 =\n* Complete plugin refactoring\n\n= 1.0.2 =\n* Fixed for versions 4.4.*\n\n= 1.0.1 =\n* Now only runs on image attachments\n\n= 1.0 =\n* Release version\n\n= 0.4 =\n* Bugfixes, play nicely with other plugins/themes that modify image sizes\n\n= 0.3 =\n* Hotspots!\n\n= 0.2: =\n* jQuery option for speed\n\n== Upgrade Notice ==\n\n= 1.0.1 =\n* No longer runs on non image media\n\n= 1.0 =\n* Release version\n\n= 0.4 =\n* Lot's of bugfixes"
  }
]