function Plugin(editor) {
		var clipboardRows, self = this, dialogs = new Dialogs(editor), resizeBars;

		if (editor.settings.object_resizing && editor.settings.table_resize_bars !== false &&
			(editor.settings.object_resizing === true || editor.settings.object_resizing === 'table')) {
			resizeBars = ResizeBars(editor);
		}

		function cmd(command) {
			return function() {
				editor.execCommand(command);
			};
		}

		function insertTable(cols, rows) {
			var y, x, html, tableElm;

			html = '<table id="__mce"><tbody>';

			for (y = 0; y < rows; y++) {
				html += '<tr>';

				for (x = 0; x < cols; x++) {
					html += '<td>' + (Env.ie && Env.ie < 10 ? '&nbsp;' : '<br>') + '</td>';
				}

				html += '</tr>';
			}

			html += '</tbody></table>';

			editor.undoManager.transact(function() {
				editor.insertContent(html);

				tableElm = editor.dom.get('__mce');
				editor.dom.setAttrib(tableElm, 'id', null);

				editor.$('tr', tableElm).each(function(index, row) {
					editor.fire('newrow', {
						node: row
					});

					editor.$('th,td', row).each(function(index, cell) {
						editor.fire('newcell', {
							node: cell
						});
					});
				});

				editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {});
				editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {});
			});

			return tableElm;
		}

		function handleDisabledState(ctrl, selector, sameParts) {
			function bindStateListener() {
				var selectedElm, selectedCells, parts = {}, sum = 0, state;

				selectedCells = editor.dom.select('td[data-mce-selected],th[data-mce-selected]');
				selectedElm = selectedCells[0];
				if (!selectedElm) {
					selectedElm = editor.selection.getStart();
				}

				// Make sure that we don't have a selection inside thead and tbody at the same time
				if (sameParts && selectedCells.length > 0) {
					each(selectedCells, function(cell) {
						return parts[cell.parentNode.parentNode.nodeName] = 1;
					});

					each(parts, function(value) {
						sum += value;
					});

					state = sum !== 1;
				} else {
					state = !editor.dom.getParent(selectedElm, selector);
				}

				ctrl.disabled(state);

				editor.selection.selectorChanged(selector, function(state) {
					ctrl.disabled(!state);
				});
			}

			if (editor.initialized) {
				bindStateListener();
			} else {
				editor.on('init', bindStateListener);
			}
		}

		function postRender() {
			/*jshint validthis:true*/
			handleDisabledState(this, 'table');
		}

		function postRenderCell() {
			/*jshint validthis:true*/
			handleDisabledState(this, 'td,th');
		}

		function postRenderMergeCell() {
			/*jshint validthis:true*/
			handleDisabledState(this, 'td,th', true);
		}

		function generateTableGrid() {
			var html = '';

			html = '<table role="grid" class="mce-grid mce-grid-border" aria-readonly="true">';

			for (var y = 0; y < 10; y++) {
				html += '<tr>';

				for (var x = 0; x < 10; x++) {
					html += '<td role="gridcell" tabindex="-1"><a id="mcegrid' + (y * 10 + x) + '" href="#" ' +
						'data-mce-x="' + x + '" data-mce-y="' + y + '"></a></td>';
				}

				html += '</tr>';
			}

			html += '</table>';

			html += '<div class="mce-text-center" role="presentation">1 x 1</div>';

			return html;
		}

		function selectGrid(tx, ty, control) {
			var table = control.getEl().getElementsByTagName('table')[0];
			var x, y, focusCell, cell, active;
			var rtl = control.isRtl() || control.parent().rel == 'tl-tr';

			table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1);

			if (rtl) {
				tx = 9 - tx;
			}

			for (y = 0; y < 10; y++) {
				for (x = 0; x < 10; x++) {
					cell = table.rows[y].childNodes[x].firstChild;
					active = (rtl ? x >= tx : x <= tx) && y <= ty;

					editor.dom.toggleClass(cell, 'mce-active', active);

					if (active) {
						focusCell = cell;
					}
				}
			}

			return focusCell.parentNode;
		}

		if (editor.settings.table_grid === false) {
			editor.addMenuItem('inserttable', {
				text: 'Insert table',
				icon: 'table',
				context: 'table',
				onclick: dialogs.table
			});
		} else {
			editor.addMenuItem('inserttable', {
				text: 'Insert table',
				icon: 'table',
				context: 'table',
				ariaHideMenu: true,
				onclick: function(e) {
					if (e.aria) {
						this.parent().hideAll();
						e.stopImmediatePropagation();
						dialogs.table();
					}
				},
				onshow: function() {
					selectGrid(0, 0, this.menu.items()[0]);
				},
				onhide: function() {
					var elements = this.menu.items()[0].getEl().getElementsByTagName('a');
					editor.dom.removeClass(elements, 'mce-active');
					editor.dom.addClass(elements[0], 'mce-active');
				},
				menu: [
					{
						type: 'container',
						html: generateTableGrid(),

						onPostRender: function() {
							this.lastX = this.lastY = 0;
						},

						onmousemove: function(e) {
							var target = e.target, x, y;

							if (target.tagName.toUpperCase() == 'A') {
								x = parseInt(target.getAttribute('data-mce-x'), 10);
								y = parseInt(target.getAttribute('data-mce-y'), 10);

								if (this.isRtl() || this.parent().rel == 'tl-tr') {
									x = 9 - x;
								}

								if (x !== this.lastX || y !== this.lastY) {
									selectGrid(x, y, e.control);

									this.lastX = x;
									this.lastY = y;
								}
							}
						},

						onclick: function(e) {
							var self = this;

							if (e.target.tagName.toUpperCase() == 'A') {
								e.preventDefault();
								e.stopPropagation();
								self.parent().cancel();

								editor.undoManager.transact(function() {
									insertTable(self.lastX + 1, self.lastY + 1);
								});

								editor.addVisual();
							}
						}
					}
				]
			});
		}

		editor.addMenuItem('tableprops', {
			text: 'Table properties',
			context: 'table',
			onPostRender: postRender,
			onclick: dialogs.tableProps
		});

		editor.addMenuItem('deletetable', {
			text: 'Delete table',
			context: 'table',
			onPostRender: postRender,
			cmd: 'mceTableDelete'
		});

		editor.addMenuItem('cell', {
			separator: 'before',
			text: 'Cell',
			context: 'table',
			menu: [
				{text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell},
				{text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderMergeCell},
				{text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell}
			]
		});

		editor.addMenuItem('row', {
			text: 'Row',
			context: 'table',
			menu: [
				{text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell},
				{text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell},
				{text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell},
				{text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell},
				{text: '-'},
				{text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell},
				{text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell},
				{text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell},
				{text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell}
			]
		});

		editor.addMenuItem('column', {
			text: 'Column',
			context: 'table',
			menu: [
				{text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell},
				{text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell},
				{text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell}
			]
		});

		var menuItems = [];
		each("inserttable tableprops deletetable | cell row column".split(' '), function(name) {
			if (name == '|') {
				menuItems.push({text: '-'});
			} else {
				menuItems.push(editor.menuItems[name]);
			}
		});

		editor.addButton("table", {
			type: "menubutton",
			title: "Table",
			menu: menuItems
		});

		// Select whole table is a table border is clicked
		if (!Env.isIE) {
			editor.on('click', function(e) {
				e = e.target;

				if (e.nodeName === 'TABLE') {
					editor.selection.select(e);
					editor.nodeChanged();
				}
			});
		}

		self.quirks = new Quirks(editor);

		editor.on('Init', function() {
			self.cellSelection = new CellSelection(editor, function (selecting) {
				if (selecting) {
					resizeBars.clearBars();
				}
			});
			self.resizeBars = resizeBars;
		});

		editor.on('PreInit', function() {
			// Remove internal data attributes
			editor.serializer.addAttributeFilter(
				'data-mce-cell-padding,data-mce-border,data-mce-border-color',
				function(nodes, name) {

					var i = nodes.length;

					while (i--) {
						nodes[i].attr(name, null);
					}
				});
		});

		// Register action commands
		each({
			mceTableSplitCells: function(grid) {
				grid.split();
			},

			mceTableMergeCells: function(grid) {
				var cell;

				cell = editor.dom.getParent(editor.selection.getStart(), 'th,td');

				if (!editor.dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
					dialogs.merge(grid, cell);
				} else {
					grid.merge();
				}
			},

			mceTableInsertRowBefore: function(grid) {
				grid.insertRow(true);
			},

			mceTableInsertRowAfter: function(grid) {
				grid.insertRow();
			},

			mceTableInsertColBefore: function(grid) {
				grid.insertCol(true);
			},

			mceTableInsertColAfter: function(grid) {
				grid.insertCol();
			},

			mceTableDeleteCol: function(grid) {
				grid.deleteCols();
			},

			mceTableDeleteRow: function(grid) {
				grid.deleteRows();
			},

			mceTableCutRow: function(grid) {
				clipboardRows = grid.cutRows();
			},

			mceTableCopyRow: function(grid) {
				clipboardRows = grid.copyRows();
			},

			mceTablePasteRowBefore: function(grid) {
				grid.pasteRows(clipboardRows, true);
			},

			mceTablePasteRowAfter: function(grid) {
				grid.pasteRows(clipboardRows);
			},

			mceTableDelete: function(grid) {
				if (resizeBars) {
					resizeBars.clearBars();
				}
				grid.deleteTable();
			}
		}, function(func, name) {
			editor.addCommand(name, function() {
				var grid = new TableGrid(editor);

				if (grid) {
					func(grid);
					editor.execCommand('mceRepaint');
					self.cellSelection.clear();
				}
			});
		});

		// Register dialog commands
		each({
			mceInsertTable: dialogs.table,
			mceTableProps: function() {
				dialogs.table(true);
			},
			mceTableRowProps: dialogs.row,
			mceTableCellProps: dialogs.cell
		}, function(func, name) {
			editor.addCommand(name, function(ui, val) {
				func(val);
			});
		});

		function addButtons() {
			editor.addButton('tableprops', {
				title: 'Table properties',
				onclick: dialogs.tableProps,
				icon: 'table'
			});

			editor.addButton('tabledelete', {
				title: 'Delete table',
				onclick: cmd('mceTableDelete')
			});

			editor.addButton('tablecellprops', {
				title: 'Cell properties',
				onclick: cmd('mceTableCellProps')
			});

			editor.addButton('tablemergecells', {
				title: 'Merge cells',
				onclick: cmd('mceTableMergeCells')
			});

			editor.addButton('tablesplitcells', {
				title: 'Split cell',
				onclick: cmd('mceTableSplitCells')
			});

			editor.addButton('tableinsertrowbefore', {
				title: 'Insert row before',
				onclick: cmd('mceTableInsertRowBefore')
			});

			editor.addButton('tableinsertrowafter', {
				title: 'Insert row after',
				onclick: cmd('mceTableInsertRowAfter')
			});

			editor.addButton('tabledeleterow', {
				title: 'Delete row',
				onclick: cmd('mceTableDeleteRow')
			});

			editor.addButton('tablerowprops', {
				title: 'Row properties',
				onclick: cmd('mceTableRowProps')
			});

			editor.addButton('tablecutrow', {
				title: 'Cut row',
				onclick: cmd('mceTableCutRow')
			});

			editor.addButton('tablecopyrow', {
				title: 'Copy row',
				onclick: cmd('mceTableCopyRow')
			});

			editor.addButton('tablepasterowbefore', {
				title: 'Paste row before',
				onclick: cmd('mceTablePasteRowBefore')
			});

			editor.addButton('tablepasterowafter', {
				title: 'Paste row after',
				onclick: cmd('mceTablePasteRowAfter')
			});

			editor.addButton('tableinsertcolbefore', {
				title: 'Insert column before',
				onclick: cmd('mceTableInsertColBefore')
			});

			editor.addButton('tableinsertcolafter', {
				title: 'Insert column after',
				onclick: cmd('mceTableInsertColAfter')
			});

			editor.addButton('tabledeletecol', {
				title: 'Delete column',
				onclick: cmd('mceTableDeleteCol')
			});

		}

		function isTable(table) {

			var selectorMatched = editor.dom.is(table, 'table') && editor.getBody().contains(table);

			return selectorMatched;
		}

		function addToolbars() {
			var toolbarItems = editor.settings.table_toolbar;

			if (toolbarItems === '' || toolbarItems === false) {
				return;
			}

			if (!toolbarItems) {
				toolbarItems = 'tableprops tabledelete | ' +
					'tableinsertrowbefore tableinsertrowafter tabledeleterow | ' +
					'tableinsertcolbefore tableinsertcolafter tabledeletecol';
			}

			editor.addContextToolbar(
				isTable,
				toolbarItems
			);
		}

		function getClipboardRows() {
			return clipboardRows;
		}

		function setClipboardRows(rows) {
			clipboardRows = rows;
		}

		addButtons();
		addToolbars();

		// Enable tab key cell navigation
		if (editor.settings.table_tab_navigation !== false) {
			editor.on('keydown', function(e) {
				var cellElm, grid, delta;

				if (e.keyCode == 9) {
					cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td');

					if (cellElm) {
						e.preventDefault();

						grid = new TableGrid(editor);
						delta = e.shiftKey ? -1 : 1;

						editor.undoManager.transact(function() {
							if (!grid.moveRelIdx(cellElm, delta) && delta > 0) {
								grid.insertRow();
								grid.refresh();
								grid.moveRelIdx(cellElm, delta);
							}
						});
					}
				}
			});
		}

		self.insertTable = insertTable;
		self.setClipboardRows = setClipboardRows;
		self.getClipboardRows = getClipboardRows;
	}
This class contains all core logic for the table plugin.

@class tinymce.tableplugin.Plugin
@private