diff --git a/includes/ckeditor.lib.inc b/includes/ckeditor.lib.inc
index a28c439..1a5dbb4 100644
--- a/includes/ckeditor.lib.inc
+++ b/includes/ckeditor.lib.inc
@@ -817,14 +817,9 @@ function ckeditor_profile_settings_compile($global_profile, $profile) {
     $settings['loadPlugins'] = ckeditor_plugins_render($conf['loadPlugins']);
 
     if (array_key_exists('media', $settings['loadPlugins']) && module_exists('media')) {
-      module_load_include('inc', 'media', 'includes/media.browser');
-      $javascript = media_browser_js();
-      foreach ($javascript as $key => $definitions) {
-        foreach ($definitions as $definition) {
-          $function = 'drupal_add_' . $key;
-          call_user_func_array($function, $definition);
-        }
-      }
+      module_load_include('inc', 'media', 'wysiwyg_plugins/media');
+      media_include_browser_js();
+      drupal_add_js(drupal_get_path('module', 'media') . '/js/media.filter.js', array('scope' => 'footer', 'weight' => -21));
       drupal_add_js(drupal_get_path('module', 'ckeditor') . '/plugins/media/library.js', array('scope' => 'footer', 'weight' => -20));
     }
   }
diff --git a/plugins/media/library.js b/plugins/media/library.js
index 1295ad5..f7f056c 100644
--- a/plugins/media/library.js
+++ b/plugins/media/library.js
@@ -8,18 +8,9 @@
   Drupal.media = Drupal.media || {};
 
   Drupal.settings.ckeditor.plugins['media'] = {
-
-    /**
-       * Initializes the tag map.
-       */
-    initializeTagMap: function () {
-      if (typeof Drupal.settings.tagmap == 'undefined') {
-        Drupal.settings.tagmap = { };
-      }
-    },
     /**
-       * Execute the button.
-       */
+     * Execute the button.
+     */
     invoke: function (data, settings, instanceId) {
       if (data.format == 'html') {
         Drupal.media.popups.mediaBrowser(function (mediaFiles) {
@@ -29,241 +20,56 @@
     },
 
     /**
-       * Respond to the mediaBrowser's onSelect event.
-       */
+     * Respond to the mediaBrowser's onSelect event.
+     */
     mediaBrowserOnSelect: function (mediaFiles, instanceId) {
       var mediaFile = mediaFiles[0];
       var options = {};
       Drupal.media.popups.mediaStyleSelector(mediaFile, function (formattedMedia) {
-        Drupal.settings.ckeditor.plugins['media'].insertMediaFile(mediaFile, formattedMedia.type, formattedMedia.html, formattedMedia.options, CKEDITOR.instances[instanceId]);
+        Drupal.settings.ckeditor.plugins['media'].insertMediaFile(mediaFile, formattedMedia, CKEDITOR.instances[instanceId]);
       }, options);
 
       return;
     },
 
-    insertMediaFile: function (mediaFile, viewMode, formattedMedia, options, ckeditorInstance) {
-
-      this.initializeTagMap();
-      // @TODO: the folks @ ckeditor have told us that there is no way
-      // to reliably add wrapper divs via normal HTML.
-      // There is some method of adding a "fake element"
-      // But until then, we're just going to embed to img.
-      // This is pretty hacked for now.
-      //
-      var imgElement = $(this.stripDivs(formattedMedia));
-      this.addImageAttributes(imgElement, mediaFile.fid, viewMode, options);
-
-      var toInsert = this.outerHTML(imgElement);
-      // Create an inline tag
-      var inlineTag = Drupal.settings.ckeditor.plugins['media'].createTag(imgElement);
-      // Add it to the tag map in case the user switches input formats
-      Drupal.settings.tagmap[inlineTag] = toInsert;
-      ckeditorInstance.insertHtml(toInsert);
-    },
-
-    /**
-       * Gets the HTML content of an element
-       *
-       * @param jQuery element
-       */
-    outerHTML: function (element) {
-      return $('<div>').append( element.eq(0).clone() ).html();
-    },
-
-    addImageAttributes: function (imgElement, fid, view_mode, additional) {
-      imgElement.addClass('media-image');
-
-      this.forceAttributesIntoClass(imgElement, fid, view_mode, additional);
-    },
-
-    /**
-       * Due to problems handling wrapping divs in ckeditor, this is needed.
-       *
-       * Going forward, if we don't care about supporting other editors
-       * we can use the fakeobjects plugin to ckeditor to provide cleaner
-       * transparency between what Drupal will output <div class="field..."><img></div>
-       * instead of just <img>, for now though, we're going to remove all the stuff surrounding the images.
-       *
-       * @param String formattedMedia
-       *  Element containing the image
-       *
-       * @return HTML of <img> tag inside formattedMedia
-       */
-    stripDivs: function (formattedMedia) {
-      // Check to see if the image tag has divs to strip
-      var stripped = null;
-      if ($(formattedMedia).is('img')) {
-        stripped = this.outerHTML($(formattedMedia));
-      } else {
-        stripped = this.outerHTML($('img', $(formattedMedia)));
-      }
-      // This will fail if we pass the img tag without anything wrapping it, like we do when re-enabling ckeditor
-      return stripped;
-    },
-
-    /**
-       * Attach function, called when a rich text editor loads.
-       * This finds all [[tags]] and replaces them with the html
-       * that needs to show in the editor.
-       *
-       */
-    attach: function (content, settings, instanceId) {
-      var matches = content.match(/\[\[.*?\]\]/g);
-      this.initializeTagMap();
-      var tagmap = Drupal.settings.tagmap;
-      if (matches) {
-        var inlineTag = "";
-        for (i = 0; i < matches.length; i++) {
-          inlineTag = matches[i];
-          if (tagmap[inlineTag]) {
-            // This probably needs some work...
-            // We need to somehow get the fid propogated here.
-            // We really want to
-            var tagContent = tagmap[inlineTag];
-            var mediaMarkup = this.stripDivs(tagContent); // THis is <div>..<img>
-
-            var _tag = inlineTag;
-            _tag = _tag.replace('[[','');
-            _tag = _tag.replace(']]','');
-            mediaObj = JSON.parse(_tag);
-
-            var imgElement = $(mediaMarkup);
-            this.addImageAttributes(imgElement, mediaObj.fid, mediaObj.view_mode);
-            var toInsert = this.outerHTML(imgElement);
-            content = content.replace(inlineTag, toInsert);
-          }
-          else {
-            debug.debug("Could not find content for " + inlineTag);
-          }
-        }
-      }
-      return content;
-    },
-
-    /**
-       * Detach function, called when a rich text editor detaches
-       */
-    detach: function (content, settings, instanceId) {
-      var content = $('<div>' + content + '</div>');
-      $('img.media-image',content).each(function (elem) {
-        var tag = Drupal.settings.ckeditor.plugins['media'].createTag($(this));
-        $(this).replaceWith(tag);
-        var newContent = content.html();
-        var tagContent = $('<div></div>').append($(this)).html();
-        Drupal.settings.tagmap[tag] = tagContent;
+    insertMediaFile: function (mediaFile, formattedMedia, ckeditorInstance) {
+      // Customization of Drupal.media.filter.registerNewElement().
+      var element = Drupal.media.filter.create_element(formattedMedia.html, {
+        fid: mediaFile.fid,
+        view_mode: formattedMedia.type,
+        attributes: formattedMedia.options
       });
-      return content.html();
-    },
 
-    /**
-       * @param jQuery imgNode
-       *  Image node to create tag from
-       */
-    createTag: function (imgNode) {
-      // Currently this is the <img> itself
-      // Collect all attribs to be stashed into tagContent
-      var mediaAttributes = {};
-      var imgElement = imgNode[0];
-      var sorter = [];
+      this.forceAttributesIntoClass(
+        element,
+        mediaFile.fid,
+        formattedMedia.type,
+        formattedMedia.options
+      );
 
-      // @todo: this does not work in IE, width and height are always 0.
-      for (i=0; i< imgElement.attributes.length; i++) {
-        var attr = imgElement.attributes[i];
-        if (attr.specified == true) {
-          if (attr.name !== 'class') {
-            sorter.push(attr);
-          }
-          else {
-            // Exctract expando properties from the class field.
-            var attributes = this.getAttributesFromClass(attr.value);
-            for (var name in attributes) {
-              if (attributes.hasOwnProperty(name)) {
-                var value = attributes[name];
-                if (value.type && value.type === 'attr') {
-                  sorter.push(value);
-                }
-              }
-            }
-          }
-        }
-      }
-
-      sorter.sort(this.sortAttributes);
-
-      for (var prop in sorter) {
-        mediaAttributes[sorter[prop].name] = sorter[prop].value;
-      }
-
-      // The following 5 ifs are dedicated to IE7
-      // If the style is null, it is because IE7 can't read values from itself
-      if (jQuery.browser.msie && jQuery.browser.version == '7.0') {
-        if (mediaAttributes.style === "null") {
-          var imgHeight = imgNode.css('height');
-          var imgWidth = imgNode.css('width');
-          mediaAttributes.style = {
-            height: imgHeight,
-            width: imgWidth
-          }
-          if (!mediaAttributes['width']) {
-            mediaAttributes['width'] = imgWidth;
-          }
-          if (!mediaAttributes['height']) {
-            mediaAttributes['height'] = imgHeight;
-          }
-        }
-        // If the attribute width is zero, get the CSS width
-        if (Number(mediaAttributes['width']) === 0) {
-          mediaAttributes['width'] = imgNode.css('width');
-        }
-        // IE7 does support 'auto' as a value of the width attribute. It will not
-        // display the image if this value is allowed to pass through
-        if (mediaAttributes['width'] === 'auto') {
-          delete mediaAttributes['width'];
-        }
-        // If the attribute height is zero, get the CSS height
-        if (Number(mediaAttributes['height']) === 0) {
-          mediaAttributes['height'] = imgNode.css('height');
-        }
-        // IE7 does support 'auto' as a value of the height attribute. It will not
-        // display the image if this value is allowed to pass through
-        if (mediaAttributes['height'] === 'auto') {
-          delete mediaAttributes['height'];
-        }
-      }
-
-      // Remove elements from attribs using the blacklist
-      for (var blackList in Drupal.settings.media.blacklist) {
-        delete mediaAttributes[Drupal.settings.media.blacklist[blackList]];
-      }
-      tagContent = {
-        "type": 'media',
-        // @todo: This will be selected from the format form
-        "view_mode": attributes['view_mode'].value,
-        "fid" : attributes['fid'].value,
-        "attributes": mediaAttributes
-      };
-      return '[[' + JSON.stringify(tagContent) + ']]';
+      // Insert markup with delimiting start and end tags.
+      ckeditorInstance.insertHtml(Drupal.media.filter.getWysiwygHTML(element));
     },
 
     /**
-       * Forces custom attributes into the class field of the specified image.
-       *
-       * Due to a bug in some versions of Firefox
-       * (http://forums.mozillazine.org/viewtopic.php?f=9&t=1991855), the
-       * custom attributes used to share information about the image are
-       * being stripped as the image markup is set into the rich text
-       * editor.  Here we encode these attributes into the class field so
-       * the data survives.
-       *
-       * @param imgElement
-       *   The image
-       * @fid
-       *   The file id.
-       * @param view_mode
-       *   The view mode.
-       * @param additional
-       *   Additional attributes to add to the image.
-       */
+     * Forces custom attributes into the class field of the specified image.
+     *
+     * Due to a bug in some versions of Firefox
+     * (http://forums.mozillazine.org/viewtopic.php?f=9&t=1991855), the
+     * custom attributes used to share information about the image are
+     * being stripped as the image markup is set into the rich text
+     * editor.  Here we encode these attributes into the class field so
+     * the data survives.
+     *
+     * @param imgElement
+     *   The image
+     * @fid
+     *   The file id.
+     * @param view_mode
+     *   The view mode.
+     * @param additional
+     *   Additional attributes to add to the image.
+     */
     forceAttributesIntoClass: function (imgElement, fid, view_mode, additional) {
       var wysiwyg = imgElement.attr('wysiwyg');
       if (wysiwyg) {
@@ -295,16 +101,16 @@
     },
 
     /**
-       * Retrieves encoded attributes from the specified class string.
-       *
-       * @param classString
-       *   A string containing the value of the class attribute.
-       * @return
-       *   An array containing the attribute names as keys, and an object
-       *   with the name, value, and attribute type (either 'attr' or
-       *   'img', depending on whether it is an image attribute or should
-       *   be it the attributes section)
-       */
+     * Retrieves encoded attributes from the specified class string.
+     *
+     * @param classString
+     *   A string containing the value of the class attribute.
+     * @return
+     *   An array containing the attribute names as keys, and an object
+     *   with the name, value, and attribute type (either 'attr' or
+     *   'img', depending on whether it is an image attribute or should
+     *   be it the attributes section)
+     */
     getAttributesFromClass: function (classString) {
       var actualClasses = [];
       var otherAttributes = [];
diff --git a/plugins/media/plugin.js b/plugins/media/plugin.js
index 363f843..09390c4 100644
--- a/plugins/media/plugin.js
+++ b/plugins/media/plugin.js
@@ -30,6 +30,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
               if (CKEDITOR.env.ie) {
                 data.content = selection.getNative().createRange().text;
               }
+
               else {
                 data.content = selection.getNative().toString();
               }
@@ -50,6 +51,39 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
         command: 'media',
         icon: this.path + 'images/icon.gif'
       });
+
+      // Ensure tokens instead the html element is saved.
+      editor.on('getData', function( event ) {
+        event.data.dataValue = Drupal.media.filter.replacePlaceholderWithToken(event.data.dataValue);
+      });
+
+      // Ensure the tokens are replaced by placeholders while editing.
+      if (parseInt(CKEDITOR.version) > 3) {
+        // CKEditor 4.x behaviour.
+        editor.on('setData', function( event ) {
+          event.data.dataValue = Drupal.media.filter.replaceTokenWithPlaceholder(event.data.dataValue);
+        });
+      }
+      else {
+        // CKEditor 3.6.x behaviour.
+        editor.on( 'beforeSetMode', function( event, data ) {
+          event.removeListener();
+          var wysiwyg = editor._.modes[ 'wysiwyg' ];
+          var source = editor._.modes[ 'source' ];
+          wysiwyg.loadData = CKEDITOR.tools.override( wysiwyg.loadData, function( org )
+          {
+            return function( data ) {
+              return ( org.call( this, Drupal.media.filter.replaceTokenWithPlaceholder( data ) ) );
+            };
+          } );
+          source.loadData = CKEDITOR.tools.override( source.loadData, function( org )
+          {
+            return function( data ) {
+              return ( org.call( this, Drupal.media.filter.replacePlaceholderWithToken( data ) ) );
+            };
+          } );
+        });
+      };
     }
   });
 
