diff --git a/Lesson33/alecaddd-plugin.php b/Lesson33/alecaddd-plugin.php new file mode 100644 index 0000000..9321153 --- /dev/null +++ b/Lesson33/alecaddd-plugin.php @@ -0,0 +1,63 @@ +=0;)a[c.charAt(d)]=u;var f=u[1],p=""+f;i.hasOwnProperty(p)||(s.push(f),i[p]=null)}s.push(/[\0-\uffff]/),r=function(e){function t(e){var t=e.charCodeAt(0);if(92!==t)return t;var n=e.charAt(1);return(t=c[n])||("0"<=n&&n<="7"?parseInt(e.substring(1),8):"u"===n||"x"===n?parseInt(e.substring(2),16):e.charCodeAt(1))}function n(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);var t=String.fromCharCode(e);return"\\"===t||"-"===t||"]"===t||"^"===t?"\\"+t:t}function r(e){var r=e.substring(1,e.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g")),a=[],s="^"===r[0],i=["["];s&&i.push("^");for(var l=s?1:0,o=r.length;l122||(c<65||d>90||a.push([32|Math.max(65,d),32|Math.min(c,90)]),c<97||d>122||a.push([-33&Math.max(97,d),-33&Math.min(c,122)]))}}a.sort(function(e,t){return e[0]-t[0]||t[1]-e[1]});var f=[],p=[];for(l=0;lg[0]&&(g[1]+1>g[0]&&i.push("-"),i.push(n(g[1])))}return i.push("]"),i.join("")}function a(e){for(var t=e.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=t.length,l=[],o=0,u=0;o=2&&"["===f?t[o]=r(d):"\\"!==f&&(t[o]=d.replace(/[a-zA-Z]/g,function(e){var t=e.charCodeAt(0);return"["+String.fromCharCode(-33&t,32|t)+"]"}))}return t.join("")}for(var s=0,i=!1,l=!1,o=0,u=e.length;o=5&&"lang-"===b.substring(0,5))||x&&"string"==typeof x[1]||(v=!1,b=A),v||(g[y]=b)}var C=f;if(f+=y.length,v){var N=x[1],_=y.indexOf(N),E=_+N.length;x[2]&&(_=(E=y.length-x[2].length)-N.length);var L=b.substring(5);e(c,u+C,y.substring(0,_),l,d),e(c,u+C+_,N,i(L,N),d),e(c,u+C+E,y.substring(E),l,d)}else d.push(u+C,b)}t.decorations=d};return l}function r(e){var t=[],r=[];e.tripleQuotedStrings?t.push([S,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""]):e.multiLineStrings?t.push([S,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):t.push([S,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]),e.verbatimStrings&&r.push([S,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);var a=e.hashComments;a&&(e.cStyleComments?(a>1?t.push([N,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"]):t.push([N,/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]),r.push([S,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,null])):t.push([N,/^#[^\r\n]*/,null,"#"])),e.cStyleComments&&(r.push([N,/^\/\/[^\r\n]*/,null]),r.push([N,/^\/\*[\s\S]*?(?:\*\/|$)/,null]));var s=e.regexLiterals;if(s){var i=s>1?"":"\n\r",l=i?".":"[\\S\\s]",o="/(?=[^/*"+i+"])(?:[^/\\x5B\\x5C"+i+"]|\\x5C"+l+"|\\x5B(?:[^\\x5C\\x5D"+i+"]|\\x5C"+l+")*(?:\\x5D|$))+/";r.push(["lang-regex",RegExp("^"+P+"("+o+")")])}var u=e.types;u&&r.push([_,u]);var c=(""+e.keywords).replace(/^ | $/g,"");c.length&&r.push([C,new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]),t.push([k,/^\s+/,null," \r\n\t "]);var d="^.[^\\s\\w.$@'\"`/\\\\]*";return e.regexLiterals&&(d+="(?!s*/)"),r.push([E,/^@[a-z_$][a-z_$@0-9]*/i,null],[_,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[k,/^[a-z_$][a-z_$@0-9]*/i,null],[E,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[k,/^\\[\s\S]?/,null],[L,new RegExp(d),null]),n(t,r)}function a(e,t,n){function r(e){var t=e.nodeType;if(1!=t||s.test(e.className)){if((3==t||4==t)&&n){var o=e.nodeValue,u=o.match(i);if(u){var c=o.substring(0,u.index);e.nodeValue=c;var d=o.substring(u.index+u[0].length);if(d){e.parentNode.insertBefore(l.createTextNode(d),e.nextSibling)}a(e),c||e.parentNode.removeChild(e)}}}else if("br"===e.nodeName)a(e),e.parentNode&&e.parentNode.removeChild(e);else for(var f=e.firstChild;f;f=f.nextSibling)r(f)}function a(e){function t(e,n){var r=n?e.cloneNode(!1):e,a=e.parentNode;if(a){var s=t(a,1),i=e.nextSibling;s.appendChild(r);for(var l=i;l;l=i)i=l.nextSibling,s.appendChild(l)}return r}for(;!e.nextSibling;)if(!(e=e.parentNode))return;for(var n,r=t(e.nextSibling,0);(n=r.parentNode)&&1===n.nodeType;)r=n;u.push(r)}for(var s=/(?:^|\s)nocode(?:\s|$)/,i=/\r\n?|\n/,l=e.ownerDocument,o=l.createElement("li");e.firstChild;)o.appendChild(e.firstChild);for(var u=[o],c=0;c=0;){var r=t[n];T.hasOwnProperty(r)?c.console&&console.warn("cannot override language handler %s",r):T[r]=e}}function i(e,t){return e&&T.hasOwnProperty(e)||(e=/^\s*=x&&(o+=2),s>=w&&(d+=2)}}finally{v&&(v.style.display=y)}}(e)}catch(e){c.console&&console.log(e&&e.stack||e)}}function o(e,t,n){var r=n||!1,s=t||null,i=document.createElement("div");i.innerHTML="
"+e+"
",i=i.firstChild,r&&a(i,r,!0);return l({langExtension:s,numberLines:r,sourceNode:i,pre:1,sourceCode:null,basePos:null,spans:null,decorations:null}),i.innerHTML}function u(e,n){function r(e){return i.getElementsByTagName(e)}function s(){for(var n=c.PR_SHOULD_USE_CONTINUATION?h.now()+250:1/0;m|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*",R=/\S/,T={};s(r({keywords:[p,h,g,m,v,y,b,x],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),["default-code"]),s(n([],[[k,/^[^]*(?:>|$)/],[N,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]),s(n([[k,/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]),s(n([],[["atv",/^[\s\S]+/]]),["uq.val"]),s(r({keywords:p,hashComments:!0,cStyleComments:!0,types:w}),["c","cc","cpp","cxx","cyc","m"]),s(r({keywords:"null,true,false"}),["json"]),s(r({keywords:h,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:w}),["cs"]),s(r({keywords:g,cStyleComments:!0}),["java"]),s(r({keywords:x,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]),s(r({keywords:y,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]),s(r({keywords:v,hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]),s(r({keywords:b,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]),s(r({keywords:m,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]),s(r({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]),s(n([],[[S,/^[\s\S]+/]]),["regex"]);var $=c.PR={createSimpleLexer:n,registerLangHandler:s,sourceDecorator:r,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:N,PR_DECLARATION:"dec",PR_KEYWORD:C,PR_LITERAL:E,PR_NOCODE:"nocode",PR_PLAIN:k,PR_PUNCTUATION:L,PR_SOURCE:A,PR_STRING:S,PR_TAG:"tag",PR_TYPE:_,prettyPrintOne:o,prettyPrint:u},O=c.define;"function"==typeof O&&O.amd&&O("google-code-prettify",[],function(){return $})}()},{}],2:[function(e,t,n){"use strict";e("code-prettify"),window.addEventListener("load",function(){PR.prettyPrint();for(var e=document.querySelectorAll("ul.nav-tabs > li"),t=0;t\n * For a fairly comprehensive set of languages see the\n * README\n * file that came with this source. At a minimum, the lexer should work on a\n * number of languages including C and friends, Java, Python, Bash, SQL, HTML,\n * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk\n * and a subset of Perl, but, because of commenting conventions, doesn't work on\n * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.\n *

\n * Usage:

    \n *
  1. include this source file in an html page via\n * {@code }\n *
  2. define style rules. See the example page for examples.\n *
  3. mark the {@code
    } and {@code } tags in your source with\n *    {@code class=prettyprint.}\n *    You can also use the (html deprecated) {@code } tag, but the pretty\n *    printer needs to do more substantial DOM manipulations to support that, so\n *    some css styles may not be preserved.\n * </ol>\n * That's it.  I wanted to keep the API as simple as possible, so there's no\n * need to specify which language the code is in, but if you wish, you can add\n * another class to the {@code <pre>} or {@code <code>} element to specify the\n * language, as in {@code <pre class=\"prettyprint lang-java\">}.  Any class that\n * starts with \"lang-\" followed by a file extension, specifies the file type.\n * See the \"lang-*.js\" files in this directory for code that implements\n * per-language file handlers.\n * <p>\n * Change log:<br>\n * cbeust, 2006/08/22\n * <blockquote>\n *   Java annotations (start with \"@\") are now captured as literals (\"lit\")\n * </blockquote>\n * @requires console\n */\n\n// JSLint declarations\n/*global console, document, navigator, setTimeout, window, define */\n\n\n/**\n* @typedef {!Array.<number|string>}\n* Alternating indices and the decorations that should be inserted there.\n* The indices are monotonically increasing.\n*/\nvar DecorationsT;\n\n/**\n* @typedef {!{\n*   sourceNode: !Element,\n*   pre: !(number|boolean),\n*   langExtension: ?string,\n*   numberLines: ?(number|boolean),\n*   sourceCode: ?string,\n*   spans: ?(Array.<number|Node>),\n*   basePos: ?number,\n*   decorations: ?DecorationsT\n* }}\n* <dl>\n*  <dt>sourceNode<dd>the element containing the source\n*  <dt>sourceCode<dd>source as plain text\n*  <dt>pre<dd>truthy if white-space in text nodes\n*     should be considered significant.\n*  <dt>spans<dd> alternating span start indices into source\n*     and the text node or element (e.g. {@code <BR>}) corresponding to that\n*     span.\n*  <dt>decorations<dd>an array of style classes preceded\n*     by the position at which they start in job.sourceCode in order\n*  <dt>basePos<dd>integer position of this.sourceCode in the larger chunk of\n*     source.\n* </dl>\n*/\nvar JobT;\n\n/**\n* @typedef {!{\n*   sourceCode: string,\n*   spans: !(Array.<number|Node>)\n* }}\n* <dl>\n*  <dt>sourceCode<dd>source as plain text\n*  <dt>spans<dd> alternating span start indices into source\n*     and the text node or element (e.g. {@code <BR>}) corresponding to that\n*     span.\n* </dl>\n*/\nvar SourceSpansT;\n\n/** @define {boolean} */\nvar IN_GLOBAL_SCOPE = false;\n\nvar HACK_TO_FIX_JS_INCLUDE_PL;\n\n/**\n * {@type !{\n *   'createSimpleLexer': function (Array, Array): (function (JobT)),\n *   'registerLangHandler': function (function (JobT), Array.<string>),\n *   'PR_ATTRIB_NAME': string,\n *   'PR_ATTRIB_NAME': string,\n *   'PR_ATTRIB_VALUE': string,\n *   'PR_COMMENT': string,\n *   'PR_DECLARATION': string,\n *   'PR_KEYWORD': string,\n *   'PR_LITERAL': string,\n *   'PR_NOCODE': string,\n *   'PR_PLAIN': string,\n *   'PR_PUNCTUATION': string,\n *   'PR_SOURCE': string,\n *   'PR_STRING': string,\n *   'PR_TAG': string,\n *   'PR_TYPE': string,\n *   'prettyPrintOne': function (string, string, number|boolean),\n *   'prettyPrint': function (?function, ?(HTMLElement|HTMLDocument))\n * }}\n * @const\n */\nvar PR;\n\n/**\n * Split {@code prettyPrint} into multiple timeouts so as not to interfere with\n * UI events.\n * If set to {@code false}, {@code prettyPrint()} is synchronous.\n */\nwindow['PR_SHOULD_USE_CONTINUATION'] = true;\n\n/**\n * Pretty print a chunk of code.\n * @param {string} sourceCodeHtml The HTML to pretty print.\n * @param {string} opt_langExtension The language name to use.\n *     Typically, a filename extension like 'cpp' or 'java'.\n * @param {number|boolean} opt_numberLines True to number lines,\n *     or the 1-indexed number of the first line in sourceCodeHtml.\n * @return {string} code as html, but prettier\n */\nvar prettyPrintOne;\n/**\n * Find all the {@code <pre>} and {@code <code>} tags in the DOM with\n * {@code class=prettyprint} and prettify them.\n *\n * @param {Function} opt_whenDone called when prettifying is done.\n * @param {HTMLElement|HTMLDocument} opt_root an element or document\n *   containing all the elements to pretty print.\n *   Defaults to {@code document.body}.\n */\nvar prettyPrint;\n\n\n(function () {\n  var win = window;\n  // Keyword lists for various languages.\n  // We use things that coerce to strings to make them compact when minified\n  // and to defeat aggressive optimizers that fold large string constants.\n  var FLOW_CONTROL_KEYWORDS = [\"break,continue,do,else,for,if,return,while\"];\n  var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,\"auto,case,char,const,default,\" +\n      \"double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,\" +\n      \"sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"];\n  var COMMON_KEYWORDS = [C_KEYWORDS,\"catch,class,delete,false,import,\" +\n      \"new,operator,private,protected,public,this,throw,true,try,typeof\"];\n  var CPP_KEYWORDS = [COMMON_KEYWORDS,\"alignas,alignof,align_union,asm,axiom,bool,\" +\n      \"concept,concept_map,const_cast,constexpr,decltype,delegate,\" +\n      \"dynamic_cast,explicit,export,friend,generic,late_check,\" +\n      \"mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,\" +\n      \"static_cast,template,typeid,typename,using,virtual,where\"];\n  var JAVA_KEYWORDS = [COMMON_KEYWORDS,\n      \"abstract,assert,boolean,byte,extends,finally,final,implements,import,\" +\n      \"instanceof,interface,null,native,package,strictfp,super,synchronized,\" +\n      \"throws,transient\"];\n  var CSHARP_KEYWORDS = [COMMON_KEYWORDS,\n      \"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,\" +\n      \"dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,\" +\n      \"internal,into,is,join,let,lock,null,object,out,override,orderby,params,\" +\n      \"partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,\" +\n      \"unchecked,unsafe,ushort,value,var,virtual,where,yield\"];\n  var COFFEE_KEYWORDS = \"all,and,by,catch,class,else,extends,false,finally,\" +\n      \"for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,\" +\n      \"throw,true,try,unless,until,when,while,yes\";\n  var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,\n      \"abstract,async,await,constructor,debugger,enum,eval,export,function,\" +\n      \"get,implements,instanceof,interface,let,null,set,undefined,var,with,\" +\n      \"yield,Infinity,NaN\"];\n  var PERL_KEYWORDS = \"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,\" +\n      \"goto,if,import,last,local,my,next,no,our,print,package,redo,require,\" +\n      \"sub,undef,unless,until,use,wantarray,while,BEGIN,END\";\n  var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"and,as,assert,class,def,del,\" +\n      \"elif,except,exec,finally,from,global,import,in,is,lambda,\" +\n      \"nonlocal,not,or,pass,print,raise,try,with,yield,\" +\n      \"False,True,None\"];\n  var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"alias,and,begin,case,class,\" +\n      \"def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,\" +\n      \"rescue,retry,self,super,then,true,undef,unless,until,when,yield,\" +\n      \"BEGIN,END\"];\n  var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"case,done,elif,esac,eval,fi,\" +\n      \"function,in,local,set,then,until\"];\n  var ALL_KEYWORDS = [\n      CPP_KEYWORDS, CSHARP_KEYWORDS, JAVA_KEYWORDS, JSCRIPT_KEYWORDS,\n      PERL_KEYWORDS, PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];\n  var C_TYPES = /^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\\d*)\\b/;\n\n  // token style names.  correspond to css classes\n  /**\n   * token style for a string literal\n   * @const\n   */\n  var PR_STRING = 'str';\n  /**\n   * token style for a keyword\n   * @const\n   */\n  var PR_KEYWORD = 'kwd';\n  /**\n   * token style for a comment\n   * @const\n   */\n  var PR_COMMENT = 'com';\n  /**\n   * token style for a type\n   * @const\n   */\n  var PR_TYPE = 'typ';\n  /**\n   * token style for a literal value.  e.g. 1, null, true.\n   * @const\n   */\n  var PR_LITERAL = 'lit';\n  /**\n   * token style for a punctuation string.\n   * @const\n   */\n  var PR_PUNCTUATION = 'pun';\n  /**\n   * token style for plain text.\n   * @const\n   */\n  var PR_PLAIN = 'pln';\n\n  /**\n   * token style for an sgml tag.\n   * @const\n   */\n  var PR_TAG = 'tag';\n  /**\n   * token style for a markup declaration such as a DOCTYPE.\n   * @const\n   */\n  var PR_DECLARATION = 'dec';\n  /**\n   * token style for embedded source.\n   * @const\n   */\n  var PR_SOURCE = 'src';\n  /**\n   * token style for an sgml attribute name.\n   * @const\n   */\n  var PR_ATTRIB_NAME = 'atn';\n  /**\n   * token style for an sgml attribute value.\n   * @const\n   */\n  var PR_ATTRIB_VALUE = 'atv';\n\n  /**\n   * A class that indicates a section of markup that is not code, e.g. to allow\n   * embedding of line numbers within code listings.\n   * @const\n   */\n  var PR_NOCODE = 'nocode';\n\n  \n  \n  /**\n   * A set of tokens that can precede a regular expression literal in\n   * javascript\n   * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html\n   * has the full list, but I've removed ones that might be problematic when\n   * seen in languages that don't support regular expression literals.\n   *\n   * <p>Specifically, I've removed any keywords that can't precede a regexp\n   * literal in a syntactically legal javascript program, and I've removed the\n   * \"in\" keyword since it's not a keyword in many languages, and might be used\n   * as a count of inches.\n   *\n   * <p>The link above does not accurately describe EcmaScript rules since\n   * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works\n   * very well in practice.\n   *\n   * @private\n   * @const\n   */\n  var REGEXP_PRECEDER_PATTERN = '(?:^^\\\\.?|[+-]|[!=]=?=?|\\\\#|%=?|&&?=?|\\\\(|\\\\*=?|[+\\\\-]=|->|\\\\/=?|::?|<<?=?|>>?>?=?|,|;|\\\\?|@|\\\\[|~|{|\\\\^\\\\^?=?|\\\\|\\\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\\\s*';\n  \n  // CAVEAT: this does not properly handle the case where a regular\n  // expression immediately follows another since a regular expression may\n  // have flags for case-sensitivity and the like.  Having regexp tokens\n  // adjacent is not valid in any language I'm aware of, so I'm punting.\n  // TODO: maybe style special characters inside a regexp as punctuation.\n\n  /**\n   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally\n   * matches the union of the sets of strings matched by the input RegExp.\n   * Since it matches globally, if the input strings have a start-of-input\n   * anchor (/^.../), it is ignored for the purposes of unioning.\n   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.\n   * @return {RegExp} a global regex.\n   */\n  function combinePrefixPatterns(regexs) {\n    var capturedGroupIndex = 0;\n  \n    var needToFoldCase = false;\n    var ignoreCase = false;\n    for (var i = 0, n = regexs.length; i < n; ++i) {\n      var regex = regexs[i];\n      if (regex.ignoreCase) {\n        ignoreCase = true;\n      } else if (/[a-z]/i.test(regex.source.replace(\n                     /\\\\u[0-9a-f]{4}|\\\\x[0-9a-f]{2}|\\\\[^ux]/gi, ''))) {\n        needToFoldCase = true;\n        ignoreCase = false;\n        break;\n      }\n    }\n  \n    var escapeCharToCodeUnit = {\n      'b': 8,\n      't': 9,\n      'n': 0xa,\n      'v': 0xb,\n      'f': 0xc,\n      'r': 0xd\n    };\n  \n    function decodeEscape(charsetPart) {\n      var cc0 = charsetPart.charCodeAt(0);\n      if (cc0 !== 92 /* \\\\ */) {\n        return cc0;\n      }\n      var c1 = charsetPart.charAt(1);\n      cc0 = escapeCharToCodeUnit[c1];\n      if (cc0) {\n        return cc0;\n      } else if ('0' <= c1 && c1 <= '7') {\n        return parseInt(charsetPart.substring(1), 8);\n      } else if (c1 === 'u' || c1 === 'x') {\n        return parseInt(charsetPart.substring(2), 16);\n      } else {\n        return charsetPart.charCodeAt(1);\n      }\n    }\n  \n    function encodeEscape(charCode) {\n      if (charCode < 0x20) {\n        return (charCode < 0x10 ? '\\\\x0' : '\\\\x') + charCode.toString(16);\n      }\n      var ch = String.fromCharCode(charCode);\n      return (ch === '\\\\' || ch === '-' || ch === ']' || ch === '^')\n          ? \"\\\\\" + ch : ch;\n    }\n  \n    function caseFoldCharset(charSet) {\n      var charsetParts = charSet.substring(1, charSet.length - 1).match(\n          new RegExp(\n              '\\\\\\\\u[0-9A-Fa-f]{4}'\n              + '|\\\\\\\\x[0-9A-Fa-f]{2}'\n              + '|\\\\\\\\[0-3][0-7]{0,2}'\n              + '|\\\\\\\\[0-7]{1,2}'\n              + '|\\\\\\\\[\\\\s\\\\S]'\n              + '|-'\n              + '|[^-\\\\\\\\]',\n              'g'));\n      var ranges = [];\n      var inverse = charsetParts[0] === '^';\n  \n      var out = ['['];\n      if (inverse) { out.push('^'); }\n  \n      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {\n        var p = charsetParts[i];\n        if (/\\\\[bdsw]/i.test(p)) {  // Don't muck with named groups.\n          out.push(p);\n        } else {\n          var start = decodeEscape(p);\n          var end;\n          if (i + 2 < n && '-' === charsetParts[i + 1]) {\n            end = decodeEscape(charsetParts[i + 2]);\n            i += 2;\n          } else {\n            end = start;\n          }\n          ranges.push([start, end]);\n          // If the range might intersect letters, then expand it.\n          // This case handling is too simplistic.\n          // It does not deal with non-latin case folding.\n          // It works for latin source code identifiers though.\n          if (!(end < 65 || start > 122)) {\n            if (!(end < 65 || start > 90)) {\n              ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);\n            }\n            if (!(end < 97 || start > 122)) {\n              ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);\n            }\n          }\n        }\n      }\n  \n      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]\n      // -> [[1, 12], [14, 14], [16, 17]]\n      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });\n      var consolidatedRanges = [];\n      var lastRange = [];\n      for (var i = 0; i < ranges.length; ++i) {\n        var range = ranges[i];\n        if (range[0] <= lastRange[1] + 1) {\n          lastRange[1] = Math.max(lastRange[1], range[1]);\n        } else {\n          consolidatedRanges.push(lastRange = range);\n        }\n      }\n  \n      for (var i = 0; i < consolidatedRanges.length; ++i) {\n        var range = consolidatedRanges[i];\n        out.push(encodeEscape(range[0]));\n        if (range[1] > range[0]) {\n          if (range[1] + 1 > range[0]) { out.push('-'); }\n          out.push(encodeEscape(range[1]));\n        }\n      }\n      out.push(']');\n      return out.join('');\n    }\n  \n    function allowAnywhereFoldCaseAndRenumberGroups(regex) {\n      // Split into character sets, escape sequences, punctuation strings\n      // like ('(', '(?:', ')', '^'), and runs of characters that do not\n      // include any of the above.\n      var parts = regex.source.match(\n          new RegExp(\n              '(?:'\n              + '\\\\[(?:[^\\\\x5C\\\\x5D]|\\\\\\\\[\\\\s\\\\S])*\\\\]'  // a character set\n              + '|\\\\\\\\u[A-Fa-f0-9]{4}'  // a unicode escape\n              + '|\\\\\\\\x[A-Fa-f0-9]{2}'  // a hex escape\n              + '|\\\\\\\\[0-9]+'  // a back-reference or octal escape\n              + '|\\\\\\\\[^ux0-9]'  // other escape sequence\n              + '|\\\\(\\\\?[:!=]'  // start of a non-capturing group\n              + '|[\\\\(\\\\)\\\\^]'  // start/end of a group, or line start\n              + '|[^\\\\x5B\\\\x5C\\\\(\\\\)\\\\^]+'  // run of other characters\n              + ')',\n              'g'));\n      var n = parts.length;\n  \n      // Maps captured group numbers to the number they will occupy in\n      // the output or to -1 if that has not been determined, or to\n      // undefined if they need not be capturing in the output.\n      var capturedGroups = [];\n  \n      // Walk over and identify back references to build the capturedGroups\n      // mapping.\n      for (var i = 0, groupIndex = 0; i < n; ++i) {\n        var p = parts[i];\n        if (p === '(') {\n          // groups are 1-indexed, so max group index is count of '('\n          ++groupIndex;\n        } else if ('\\\\' === p.charAt(0)) {\n          var decimalValue = +p.substring(1);\n          if (decimalValue) {\n            if (decimalValue <= groupIndex) {\n              capturedGroups[decimalValue] = -1;\n            } else {\n              // Replace with an unambiguous escape sequence so that\n              // an octal escape sequence does not turn into a backreference\n              // to a capturing group from an earlier regex.\n              parts[i] = encodeEscape(decimalValue);\n            }\n          }\n        }\n      }\n  \n      // Renumber groups and reduce capturing groups to non-capturing groups\n      // where possible.\n      for (var i = 1; i < capturedGroups.length; ++i) {\n        if (-1 === capturedGroups[i]) {\n          capturedGroups[i] = ++capturedGroupIndex;\n        }\n      }\n      for (var i = 0, groupIndex = 0; i < n; ++i) {\n        var p = parts[i];\n        if (p === '(') {\n          ++groupIndex;\n          if (!capturedGroups[groupIndex]) {\n            parts[i] = '(?:';\n          }\n        } else if ('\\\\' === p.charAt(0)) {\n          var decimalValue = +p.substring(1);\n          if (decimalValue && decimalValue <= groupIndex) {\n            parts[i] = '\\\\' + capturedGroups[decimalValue];\n          }\n        }\n      }\n  \n      // Remove any prefix anchors so that the output will match anywhere.\n      // ^^ really does mean an anchored match though.\n      for (var i = 0; i < n; ++i) {\n        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }\n      }\n  \n      // Expand letters to groups to handle mixing of case-sensitive and\n      // case-insensitive patterns if necessary.\n      if (regex.ignoreCase && needToFoldCase) {\n        for (var i = 0; i < n; ++i) {\n          var p = parts[i];\n          var ch0 = p.charAt(0);\n          if (p.length >= 2 && ch0 === '[') {\n            parts[i] = caseFoldCharset(p);\n          } else if (ch0 !== '\\\\') {\n            // TODO: handle letters in numeric escapes.\n            parts[i] = p.replace(\n                /[a-zA-Z]/g,\n                function (ch) {\n                  var cc = ch.charCodeAt(0);\n                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';\n                });\n          }\n        }\n      }\n  \n      return parts.join('');\n    }\n  \n    var rewritten = [];\n    for (var i = 0, n = regexs.length; i < n; ++i) {\n      var regex = regexs[i];\n      if (regex.global || regex.multiline) { throw new Error('' + regex); }\n      rewritten.push(\n          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');\n    }\n  \n    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');\n  }\n\n  /**\n   * Split markup into a string of source code and an array mapping ranges in\n   * that string to the text nodes in which they appear.\n   *\n   * <p>\n   * The HTML DOM structure:</p>\n   * <pre>\n   * (Element   \"p\"\n   *   (Element \"b\"\n   *     (Text  \"print \"))       ; #1\n   *   (Text    \"'Hello '\")      ; #2\n   *   (Element \"br\")            ; #3\n   *   (Text    \"  + 'World';\")) ; #4\n   * </pre>\n   * <p>\n   * corresponds to the HTML\n   * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>\n   *\n   * <p>\n   * It will produce the output:</p>\n   * <pre>\n   * {\n   *   sourceCode: \"print 'Hello '\\n  + 'World';\",\n   *   //                     1          2\n   *   //           012345678901234 5678901234567\n   *   spans: [0, #1, 6, #2, 14, #3, 15, #4]\n   * }\n   * </pre>\n   * <p>\n   * where #1 is a reference to the {@code \"print \"} text node above, and so\n   * on for the other text nodes.\n   * </p>\n   *\n   * <p>\n   * The {@code} spans array is an array of pairs.  Even elements are the start\n   * indices of substrings, and odd elements are the text nodes (or BR elements)\n   * that contain the text for those substrings.\n   * Substrings continue until the next index or the end of the source.\n   * </p>\n   *\n   * @param {Node} node an HTML DOM subtree containing source-code.\n   * @param {boolean|number} isPreformatted truthy if white-space in\n   *    text nodes should be considered significant.\n   * @return {SourceSpansT} source code and the nodes in which they occur.\n   */\n  function extractSourceSpans(node, isPreformatted) {\n    var nocode = /(?:^|\\s)nocode(?:\\s|$)/;\n  \n    var chunks = [];\n    var length = 0;\n    var spans = [];\n    var k = 0;\n  \n    function walk(node) {\n      var type = node.nodeType;\n      if (type == 1) {  // Element\n        if (nocode.test(node.className)) { return; }\n        for (var child = node.firstChild; child; child = child.nextSibling) {\n          walk(child);\n        }\n        var nodeName = node.nodeName.toLowerCase();\n        if ('br' === nodeName || 'li' === nodeName) {\n          chunks[k] = '\\n';\n          spans[k << 1] = length++;\n          spans[(k++ << 1) | 1] = node;\n        }\n      } else if (type == 3 || type == 4) {  // Text\n        var text = node.nodeValue;\n        if (text.length) {\n          if (!isPreformatted) {\n            text = text.replace(/[ \\t\\r\\n]+/g, ' ');\n          } else {\n            text = text.replace(/\\r\\n?/g, '\\n');  // Normalize newlines.\n          }\n          // TODO: handle tabs here?\n          chunks[k] = text;\n          spans[k << 1] = length;\n          length += text.length;\n          spans[(k++ << 1) | 1] = node;\n        }\n      }\n    }\n  \n    walk(node);\n  \n    return {\n      sourceCode: chunks.join('').replace(/\\n$/, ''),\n      spans: spans\n    };\n  }\n\n  /**\n   * Apply the given language handler to sourceCode and add the resulting\n   * decorations to out.\n   * @param {!Element} sourceNode\n   * @param {number} basePos the index of sourceCode within the chunk of source\n   *    whose decorations are already present on out.\n   * @param {string} sourceCode\n   * @param {function(JobT)} langHandler\n   * @param {DecorationsT} out\n   */\n  function appendDecorations(\n      sourceNode, basePos, sourceCode, langHandler, out) {\n    if (!sourceCode) { return; }\n    /** @type {JobT} */\n    var job = {\n      sourceNode: sourceNode,\n      pre: 1,\n      langExtension: null,\n      numberLines: null,\n      sourceCode: sourceCode,\n      spans: null,\n      basePos: basePos,\n      decorations: null\n    };\n    langHandler(job);\n    out.push.apply(out, job.decorations);\n  }\n\n  var notWs = /\\S/;\n\n  /**\n   * Given an element, if it contains only one child element and any text nodes\n   * it contains contain only space characters, return the sole child element.\n   * Otherwise returns undefined.\n   * <p>\n   * This is meant to return the CODE element in {@code <pre><code ...>} when\n   * there is a single child element that contains all the non-space textual\n   * content, but not to return anything where there are multiple child elements\n   * as in {@code <pre><code>...</code><code>...</code></pre>} or when there\n   * is textual content.\n   */\n  function childContentWrapper(element) {\n    var wrapper = undefined;\n    for (var c = element.firstChild; c; c = c.nextSibling) {\n      var type = c.nodeType;\n      wrapper = (type === 1)  // Element Node\n          ? (wrapper ? element : c)\n          : (type === 3)  // Text Node\n          ? (notWs.test(c.nodeValue) ? element : wrapper)\n          : wrapper;\n    }\n    return wrapper === element ? undefined : wrapper;\n  }\n\n  /** Given triples of [style, pattern, context] returns a lexing function,\n    * The lexing function interprets the patterns to find token boundaries and\n    * returns a decoration list of the form\n    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]\n    * where index_n is an index into the sourceCode, and style_n is a style\n    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to\n    * all characters in sourceCode[index_n-1:index_n].\n    *\n    * The stylePatterns is a list whose elements have the form\n    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].\n    *\n    * Style is a style constant like PR_PLAIN, or can be a string of the\n    * form 'lang-FOO', where FOO is a language extension describing the\n    * language of the portion of the token in $1 after pattern executes.\n    * E.g., if style is 'lang-lisp', and group 1 contains the text\n    * '(hello (world))', then that portion of the token will be passed to the\n    * registered lisp handler for formatting.\n    * The text before and after group 1 will be restyled using this decorator\n    * so decorators should take care that this doesn't result in infinite\n    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks\n    * something like ['lang-js', /<[s]cript>(.+?)<\\/script>/].  This may match\n    * '<script>foo()<\\/script>', which would cause the current decorator to\n    * be called with '<script>' which would not match the same rule since\n    * group 1 must not be empty, so it would be instead styled as PR_TAG by\n    * the generic tag rule.  The handler registered for the 'js' extension would\n    * then be called with 'foo()', and finally, the current decorator would\n    * be called with '<\\/script>' which would not match the original rule and\n    * so the generic tag rule would identify it as a tag.\n    *\n    * Pattern must only match prefixes, and if it matches a prefix, then that\n    * match is considered a token with the same style.\n    *\n    * Context is applied to the last non-whitespace, non-comment token\n    * recognized.\n    *\n    * Shortcut is an optional string of characters, any of which, if the first\n    * character, gurantee that this pattern and only this pattern matches.\n    *\n    * @param {Array} shortcutStylePatterns patterns that always start with\n    *   a known character.  Must have a shortcut string.\n    * @param {Array} fallthroughStylePatterns patterns that will be tried in\n    *   order if the shortcut ones fail.  May have shortcuts.\n    *\n    * @return {function (JobT)} a function that takes an undecorated job and\n    *   attaches a list of decorations.\n    */\n  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {\n    var shortcuts = {};\n    var tokenizer;\n    (function () {\n      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);\n      var allRegexs = [];\n      var regexKeys = {};\n      for (var i = 0, n = allPatterns.length; i < n; ++i) {\n        var patternParts = allPatterns[i];\n        var shortcutChars = patternParts[3];\n        if (shortcutChars) {\n          for (var c = shortcutChars.length; --c >= 0;) {\n            shortcuts[shortcutChars.charAt(c)] = patternParts;\n          }\n        }\n        var regex = patternParts[1];\n        var k = '' + regex;\n        if (!regexKeys.hasOwnProperty(k)) {\n          allRegexs.push(regex);\n          regexKeys[k] = null;\n        }\n      }\n      allRegexs.push(/[\\0-\\uffff]/);\n      tokenizer = combinePrefixPatterns(allRegexs);\n    })();\n\n    var nPatterns = fallthroughStylePatterns.length;\n\n    /**\n     * Lexes job.sourceCode and attaches an output array job.decorations of\n     * style classes preceded by the position at which they start in\n     * job.sourceCode in order.\n     *\n     * @type{function (JobT)}\n     */\n    var decorate = function (job) {\n      var sourceCode = job.sourceCode, basePos = job.basePos;\n      var sourceNode = job.sourceNode;\n      /** Even entries are positions in source in ascending order.  Odd enties\n        * are style markers (e.g., PR_COMMENT) that run from that position until\n        * the end.\n        * @type {DecorationsT}\n        */\n      var decorations = [basePos, PR_PLAIN];\n      var pos = 0;  // index into sourceCode\n      var tokens = sourceCode.match(tokenizer) || [];\n      var styleCache = {};\n\n      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {\n        var token = tokens[ti];\n        var style = styleCache[token];\n        var match = void 0;\n\n        var isEmbedded;\n        if (typeof style === 'string') {\n          isEmbedded = false;\n        } else {\n          var patternParts = shortcuts[token.charAt(0)];\n          if (patternParts) {\n            match = token.match(patternParts[1]);\n            style = patternParts[0];\n          } else {\n            for (var i = 0; i < nPatterns; ++i) {\n              patternParts = fallthroughStylePatterns[i];\n              match = token.match(patternParts[1]);\n              if (match) {\n                style = patternParts[0];\n                break;\n              }\n            }\n\n            if (!match) {  // make sure that we make progress\n              style = PR_PLAIN;\n            }\n          }\n\n          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);\n          if (isEmbedded && !(match && typeof match[1] === 'string')) {\n            isEmbedded = false;\n            style = PR_SOURCE;\n          }\n\n          if (!isEmbedded) { styleCache[token] = style; }\n        }\n\n        var tokenStart = pos;\n        pos += token.length;\n\n        if (!isEmbedded) {\n          decorations.push(basePos + tokenStart, style);\n        } else {  // Treat group 1 as an embedded block of source code.\n          var embeddedSource = match[1];\n          var embeddedSourceStart = token.indexOf(embeddedSource);\n          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;\n          if (match[2]) {\n            // If embeddedSource can be blank, then it would match at the\n            // beginning which would cause us to infinitely recurse on the\n            // entire token, so we catch the right context in match[2].\n            embeddedSourceEnd = token.length - match[2].length;\n            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;\n          }\n          var lang = style.substring(5);\n          // Decorate the left of the embedded source\n          appendDecorations(\n              sourceNode,\n              basePos + tokenStart,\n              token.substring(0, embeddedSourceStart),\n              decorate, decorations);\n          // Decorate the embedded source\n          appendDecorations(\n              sourceNode,\n              basePos + tokenStart + embeddedSourceStart,\n              embeddedSource,\n              langHandlerForExtension(lang, embeddedSource),\n              decorations);\n          // Decorate the right of the embedded section\n          appendDecorations(\n              sourceNode,\n              basePos + tokenStart + embeddedSourceEnd,\n              token.substring(embeddedSourceEnd),\n              decorate, decorations);\n        }\n      }\n      job.decorations = decorations;\n    };\n    return decorate;\n  }\n\n  /** returns a function that produces a list of decorations from source text.\n    *\n    * This code treats \", ', and ` as string delimiters, and \\ as a string\n    * escape.  It does not recognize perl's qq() style strings.\n    * It has no special handling for double delimiter escapes as in basic, or\n    * the tripled delimiters used in python, but should work on those regardless\n    * although in those cases a single string literal may be broken up into\n    * multiple adjacent string literals.\n    *\n    * It recognizes C, C++, and shell style comments.\n    *\n    * @param {Object} options a set of optional parameters.\n    * @return {function (JobT)} a function that examines the source code\n    *     in the input job and builds a decoration list which it attaches to\n    *     the job.\n    */\n  function sourceDecorator(options) {\n    var shortcutStylePatterns = [], fallthroughStylePatterns = [];\n    if (options['tripleQuotedStrings']) {\n      // '''multi-line-string''', 'single-line-string', and double-quoted\n      shortcutStylePatterns.push(\n          [PR_STRING,  /^(?:\\'\\'\\'(?:[^\\'\\\\]|\\\\[\\s\\S]|\\'{1,2}(?=[^\\']))*(?:\\'\\'\\'|$)|\\\"\\\"\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S]|\\\"{1,2}(?=[^\\\"]))*(?:\\\"\\\"\\\"|$)|\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$))/,\n           null, '\\'\"']);\n    } else if (options['multiLineStrings']) {\n      // 'multi-line-string', \"multi-line-string\"\n      shortcutStylePatterns.push(\n          [PR_STRING,  /^(?:\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$)|\\`(?:[^\\\\\\`]|\\\\[\\s\\S])*(?:\\`|$))/,\n           null, '\\'\"`']);\n    } else {\n      // 'single-line-string', \"single-line-string\"\n      shortcutStylePatterns.push(\n          [PR_STRING,\n           /^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*(?:\\'|$)|\\\"(?:[^\\\\\\\"\\r\\n]|\\\\.)*(?:\\\"|$))/,\n           null, '\"\\'']);\n    }\n    if (options['verbatimStrings']) {\n      // verbatim-string-literal production from the C# grammar.  See issue 93.\n      fallthroughStylePatterns.push(\n          [PR_STRING, /^@\\\"(?:[^\\\"]|\\\"\\\")*(?:\\\"|$)/, null]);\n    }\n    var hc = options['hashComments'];\n    if (hc) {\n      if (options['cStyleComments']) {\n        if (hc > 1) {  // multiline hash comments\n          shortcutStylePatterns.push(\n              [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);\n        } else {\n          // Stop C preprocessor declarations at an unclosed open comment\n          shortcutStylePatterns.push(\n              [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\\b|[^\\r\\n]*)/,\n               null, '#']);\n        }\n        // #include <stdio.h>\n        fallthroughStylePatterns.push(\n            [PR_STRING,\n             /^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h(?:h|pp|\\+\\+)?|[a-z]\\w*)>/,\n             null]);\n      } else {\n        shortcutStylePatterns.push([PR_COMMENT, /^#[^\\r\\n]*/, null, '#']);\n      }\n    }\n    if (options['cStyleComments']) {\n      fallthroughStylePatterns.push([PR_COMMENT, /^\\/\\/[^\\r\\n]*/, null]);\n      fallthroughStylePatterns.push(\n          [PR_COMMENT, /^\\/\\*[\\s\\S]*?(?:\\*\\/|$)/, null]);\n    }\n    var regexLiterals = options['regexLiterals'];\n    if (regexLiterals) {\n      /**\n       * @const\n       */\n      var regexExcls = regexLiterals > 1\n        ? ''  // Multiline regex literals\n        : '\\n\\r';\n      /**\n       * @const\n       */\n      var regexAny = regexExcls ? '.' : '[\\\\S\\\\s]';\n      /**\n       * @const\n       */\n      var REGEX_LITERAL = (\n          // A regular expression literal starts with a slash that is\n          // not followed by * or / so that it is not confused with\n          // comments.\n          '/(?=[^/*' + regexExcls + '])'\n          // and then contains any number of raw characters,\n          + '(?:[^/\\\\x5B\\\\x5C' + regexExcls + ']'\n          // escape sequences (\\x5C),\n          +    '|\\\\x5C' + regexAny\n          // or non-nesting character sets (\\x5B\\x5D);\n          +    '|\\\\x5B(?:[^\\\\x5C\\\\x5D' + regexExcls + ']'\n          +             '|\\\\x5C' + regexAny + ')*(?:\\\\x5D|$))+'\n          // finally closed by a /.\n          + '/');\n      fallthroughStylePatterns.push(\n          ['lang-regex',\n           RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')\n           ]);\n    }\n\n    var types = options['types'];\n    if (types) {\n      fallthroughStylePatterns.push([PR_TYPE, types]);\n    }\n\n    var keywords = (\"\" + options['keywords']).replace(/^ | $/g, '');\n    if (keywords.length) {\n      fallthroughStylePatterns.push(\n          [PR_KEYWORD,\n           new RegExp('^(?:' + keywords.replace(/[\\s,]+/g, '|') + ')\\\\b'),\n           null]);\n    }\n\n    shortcutStylePatterns.push([PR_PLAIN,       /^\\s+/, null, ' \\r\\n\\t\\xA0']);\n\n    var punctuation =\n      // The Bash man page says\n\n      // A word is a sequence of characters considered as a single\n      // unit by GRUB. Words are separated by metacharacters,\n      // which are the following plus space, tab, and newline: { }\n      // | & $ ; < >\n      // ...\n\n      // A word beginning with # causes that word and all remaining\n      // characters on that line to be ignored.\n\n      // which means that only a '#' after /(?:^|[{}|&$;<>\\s])/ starts a\n      // comment but empirically\n      // $ echo {#}\n      // {#}\n      // $ echo \\$#\n      // $#\n      // $ echo }#\n      // }#\n\n      // so /(?:^|[|&;<>\\s])/ is more appropriate.\n\n      // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3\n      // suggests that this definition is compatible with a\n      // default mode that tries to use a single token definition\n      // to recognize both bash/python style comments and C\n      // preprocessor directives.\n\n      // This definition of punctuation does not include # in the list of\n      // follow-on exclusions, so # will not be broken before if preceeded\n      // by a punctuation character.  We could try to exclude # after\n      // [|&;<>] but that doesn't seem to cause many major problems.\n      // If that does turn out to be a problem, we should change the below\n      // when hc is truthy to include # in the run of punctuation characters\n      // only when not followint [|&;<>].\n      '^.[^\\\\s\\\\w.$@\\'\"`/\\\\\\\\]*';\n    if (options['regexLiterals']) {\n      punctuation += '(?!\\s*\\/)';\n    }\n\n    fallthroughStylePatterns.push(\n        // TODO(mikesamuel): recognize non-latin letters and numerals in idents\n        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],\n        [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)/, null],\n        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],\n        [PR_LITERAL,\n         new RegExp(\n             '^(?:'\n             // A hex number\n             + '0x[a-f0-9]+'\n             // or an octal or decimal number,\n             + '|(?:\\\\d(?:_\\\\d+)*\\\\d*(?:\\\\.\\\\d*)?|\\\\.\\\\d\\\\+)'\n             // possibly in scientific notation\n             + '(?:e[+\\\\-]?\\\\d+)?'\n             + ')'\n             // with an optional modifier like UL for unsigned long\n             + '[a-z]*', 'i'),\n         null, '0123456789'],\n        // Don't treat escaped quotes in bash as starting strings.\n        // See issue 144.\n        [PR_PLAIN,       /^\\\\[\\s\\S]?/, null],\n        [PR_PUNCTUATION, new RegExp(punctuation), null]);\n\n    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);\n  }\n\n  var decorateSource = sourceDecorator({\n        'keywords': ALL_KEYWORDS,\n        'hashComments': true,\n        'cStyleComments': true,\n        'multiLineStrings': true,\n        'regexLiterals': true\n      });\n\n  /**\n   * Given a DOM subtree, wraps it in a list, and puts each line into its own\n   * list item.\n   *\n   * @param {Node} node modified in place.  Its content is pulled into an\n   *     HTMLOListElement, and each line is moved into a separate list item.\n   *     This requires cloning elements, so the input might not have unique\n   *     IDs after numbering.\n   * @param {number|null|boolean} startLineNum\n   *     If truthy, coerced to an integer which is the 1-indexed line number\n   *     of the first line of code.  The number of the first line will be\n   *     attached to the list.\n   * @param {boolean} isPreformatted true iff white-space in text nodes should\n   *     be treated as significant.\n   */\n  function numberLines(node, startLineNum, isPreformatted) {\n    var nocode = /(?:^|\\s)nocode(?:\\s|$)/;\n    var lineBreak = /\\r\\n?|\\n/;\n  \n    var document = node.ownerDocument;\n  \n    var li = document.createElement('li');\n    while (node.firstChild) {\n      li.appendChild(node.firstChild);\n    }\n    // An array of lines.  We split below, so this is initialized to one\n    // un-split line.\n    var listItems = [li];\n  \n    function walk(node) {\n      var type = node.nodeType;\n      if (type == 1 && !nocode.test(node.className)) {  // Element\n        if ('br' === node.nodeName) {\n          breakAfter(node);\n          // Discard the <BR> since it is now flush against a </LI>.\n          if (node.parentNode) {\n            node.parentNode.removeChild(node);\n          }\n        } else {\n          for (var child = node.firstChild; child; child = child.nextSibling) {\n            walk(child);\n          }\n        }\n      } else if ((type == 3 || type == 4) && isPreformatted) {  // Text\n        var text = node.nodeValue;\n        var match = text.match(lineBreak);\n        if (match) {\n          var firstLine = text.substring(0, match.index);\n          node.nodeValue = firstLine;\n          var tail = text.substring(match.index + match[0].length);\n          if (tail) {\n            var parent = node.parentNode;\n            parent.insertBefore(\n              document.createTextNode(tail), node.nextSibling);\n          }\n          breakAfter(node);\n          if (!firstLine) {\n            // Don't leave blank text nodes in the DOM.\n            node.parentNode.removeChild(node);\n          }\n        }\n      }\n    }\n  \n    // Split a line after the given node.\n    function breakAfter(lineEndNode) {\n      // If there's nothing to the right, then we can skip ending the line\n      // here, and move root-wards since splitting just before an end-tag\n      // would require us to create a bunch of empty copies.\n      while (!lineEndNode.nextSibling) {\n        lineEndNode = lineEndNode.parentNode;\n        if (!lineEndNode) { return; }\n      }\n  \n      function breakLeftOf(limit, copy) {\n        // Clone shallowly if this node needs to be on both sides of the break.\n        var rightSide = copy ? limit.cloneNode(false) : limit;\n        var parent = limit.parentNode;\n        if (parent) {\n          // We clone the parent chain.\n          // This helps us resurrect important styling elements that cross lines.\n          // E.g. in <i>Foo<br>Bar</i>\n          // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.\n          var parentClone = breakLeftOf(parent, 1);\n          // Move the clone and everything to the right of the original\n          // onto the cloned parent.\n          var next = limit.nextSibling;\n          parentClone.appendChild(rightSide);\n          for (var sibling = next; sibling; sibling = next) {\n            next = sibling.nextSibling;\n            parentClone.appendChild(sibling);\n          }\n        }\n        return rightSide;\n      }\n  \n      var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);\n  \n      // Walk the parent chain until we reach an unattached LI.\n      for (var parent;\n           // Check nodeType since IE invents document fragments.\n           (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {\n        copiedListItem = parent;\n      }\n      // Put it on the list of lines for later processing.\n      listItems.push(copiedListItem);\n    }\n  \n    // Split lines while there are lines left to split.\n    for (var i = 0;  // Number of lines that have been split so far.\n         i < listItems.length;  // length updated by breakAfter calls.\n         ++i) {\n      walk(listItems[i]);\n    }\n  \n    // Make sure numeric indices show correctly.\n    if (startLineNum === (startLineNum|0)) {\n      listItems[0].setAttribute('value', startLineNum);\n    }\n  \n    var ol = document.createElement('ol');\n    ol.className = 'linenums';\n    var offset = Math.max(0, ((startLineNum - 1 /* zero index */)) | 0) || 0;\n    for (var i = 0, n = listItems.length; i < n; ++i) {\n      li = listItems[i];\n      // Stick a class on the LIs so that stylesheets can\n      // color odd/even rows, or any other row pattern that\n      // is co-prime with 10.\n      li.className = 'L' + ((i + offset) % 10);\n      if (!li.firstChild) {\n        li.appendChild(document.createTextNode('\\xA0'));\n      }\n      ol.appendChild(li);\n    }\n  \n    node.appendChild(ol);\n  }\n\n  /**\n   * Breaks {@code job.sourceCode} around style boundaries in\n   * {@code job.decorations} and modifies {@code job.sourceNode} in place.\n   * @param {JobT} job\n   * @private\n   */\n  function recombineTagsAndDecorations(job) {\n    var isIE8OrEarlier = /\\bMSIE\\s(\\d+)/.exec(navigator.userAgent);\n    isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;\n    var newlineRe = /\\n/g;\n  \n    var source = job.sourceCode;\n    var sourceLength = source.length;\n    // Index into source after the last code-unit recombined.\n    var sourceIndex = 0;\n  \n    var spans = job.spans;\n    var nSpans = spans.length;\n    // Index into spans after the last span which ends at or before sourceIndex.\n    var spanIndex = 0;\n  \n    var decorations = job.decorations;\n    var nDecorations = decorations.length;\n    // Index into decorations after the last decoration which ends at or before\n    // sourceIndex.\n    var decorationIndex = 0;\n  \n    // Remove all zero-length decorations.\n    decorations[nDecorations] = sourceLength;\n    var decPos, i;\n    for (i = decPos = 0; i < nDecorations;) {\n      if (decorations[i] !== decorations[i + 2]) {\n        decorations[decPos++] = decorations[i++];\n        decorations[decPos++] = decorations[i++];\n      } else {\n        i += 2;\n      }\n    }\n    nDecorations = decPos;\n  \n    // Simplify decorations.\n    for (i = decPos = 0; i < nDecorations;) {\n      var startPos = decorations[i];\n      // Conflate all adjacent decorations that use the same style.\n      var startDec = decorations[i + 1];\n      var end = i + 2;\n      while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {\n        end += 2;\n      }\n      decorations[decPos++] = startPos;\n      decorations[decPos++] = startDec;\n      i = end;\n    }\n  \n    nDecorations = decorations.length = decPos;\n  \n    var sourceNode = job.sourceNode;\n    var oldDisplay = \"\";\n    if (sourceNode) {\n      oldDisplay = sourceNode.style.display;\n      sourceNode.style.display = 'none';\n    }\n    try {\n      var decoration = null;\n      while (spanIndex < nSpans) {\n        var spanStart = spans[spanIndex];\n        var spanEnd = /** @type{number} */ (spans[spanIndex + 2])\n            || sourceLength;\n  \n        var decEnd = decorations[decorationIndex + 2] || sourceLength;\n  \n        var end = Math.min(spanEnd, decEnd);\n  \n        var textNode = /** @type{Node} */ (spans[spanIndex + 1]);\n        var styledText;\n        if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s\n            // Don't introduce spans around empty text nodes.\n            && (styledText = source.substring(sourceIndex, end))) {\n          // This may seem bizarre, and it is.  Emitting LF on IE causes the\n          // code to display with spaces instead of line breaks.\n          // Emitting Windows standard issue linebreaks (CRLF) causes a blank\n          // space to appear at the beginning of every line but the first.\n          // Emitting an old Mac OS 9 line separator makes everything spiffy.\n          if (isIE8OrEarlier) {\n            styledText = styledText.replace(newlineRe, '\\r');\n          }\n          textNode.nodeValue = styledText;\n          var document = textNode.ownerDocument;\n          var span = document.createElement('span');\n          span.className = decorations[decorationIndex + 1];\n          var parentNode = textNode.parentNode;\n          parentNode.replaceChild(span, textNode);\n          span.appendChild(textNode);\n          if (sourceIndex < spanEnd) {  // Split off a text node.\n            spans[spanIndex + 1] = textNode\n                // TODO: Possibly optimize by using '' if there's no flicker.\n                = document.createTextNode(source.substring(end, spanEnd));\n            parentNode.insertBefore(textNode, span.nextSibling);\n          }\n        }\n  \n        sourceIndex = end;\n  \n        if (sourceIndex >= spanEnd) {\n          spanIndex += 2;\n        }\n        if (sourceIndex >= decEnd) {\n          decorationIndex += 2;\n        }\n      }\n    } finally {\n      if (sourceNode) {\n        sourceNode.style.display = oldDisplay;\n      }\n    }\n  }\n\n  /** Maps language-specific file extensions to handlers. */\n  var langHandlerRegistry = {};\n  /** Register a language handler for the given file extensions.\n    * @param {function (JobT)} handler a function from source code to a list\n    *      of decorations.  Takes a single argument job which describes the\n    *      state of the computation and attaches the decorations to it.\n    * @param {Array.<string>} fileExtensions\n    */\n  function registerLangHandler(handler, fileExtensions) {\n    for (var i = fileExtensions.length; --i >= 0;) {\n      var ext = fileExtensions[i];\n      if (!langHandlerRegistry.hasOwnProperty(ext)) {\n        langHandlerRegistry[ext] = handler;\n      } else if (win['console']) {\n        console['warn']('cannot override language handler %s', ext);\n      }\n    }\n  }\n  function langHandlerForExtension(extension, source) {\n    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {\n      // Treat it as markup if the first non whitespace character is a < and\n      // the last non-whitespace character is a >.\n      extension = /^\\s*</.test(source)\n          ? 'default-markup'\n          : 'default-code';\n    }\n    return langHandlerRegistry[extension];\n  }\n  registerLangHandler(decorateSource, ['default-code']);\n  registerLangHandler(\n      createSimpleLexer(\n          [],\n          [\n           [PR_PLAIN,       /^[^<?]+/],\n           [PR_DECLARATION, /^<!\\w[^>]*(?:>|$)/],\n           [PR_COMMENT,     /^<\\!--[\\s\\S]*?(?:-\\->|$)/],\n           // Unescaped content in an unknown language\n           ['lang-',        /^<\\?([\\s\\S]+?)(?:\\?>|$)/],\n           ['lang-',        /^<%([\\s\\S]+?)(?:%>|$)/],\n           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],\n           ['lang-',        /^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],\n           // Unescaped content in javascript.  (Or possibly vbscript).\n           ['lang-js',      /^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],\n           // Contains unescaped stylesheet content\n           ['lang-css',     /^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],\n           ['lang-in.tag',  /^(<\\/?[a-z][^<>]*>)/i]\n          ]),\n      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);\n  registerLangHandler(\n      createSimpleLexer(\n          [\n           [PR_PLAIN,        /^[\\s]+/, null, ' \\t\\r\\n'],\n           [PR_ATTRIB_VALUE, /^(?:\\\"[^\\\"]*\\\"?|\\'[^\\']*\\'?)/, null, '\\\"\\'']\n           ],\n          [\n           [PR_TAG,          /^^<\\/?[a-z](?:[\\w.:-]*\\w)?|\\/?>$/i],\n           [PR_ATTRIB_NAME,  /^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],\n           ['lang-uq.val',   /^=\\s*([^>\\'\\\"\\s]*(?:[^>\\'\\\"\\s\\/]|\\/(?=\\s)))/],\n           [PR_PUNCTUATION,  /^[=<>\\/]+/],\n           ['lang-js',       /^on\\w+\\s*=\\s*\\\"([^\\\"]+)\\\"/i],\n           ['lang-js',       /^on\\w+\\s*=\\s*\\'([^\\']+)\\'/i],\n           ['lang-js',       /^on\\w+\\s*=\\s*([^\\\"\\'>\\s]+)/i],\n           ['lang-css',      /^style\\s*=\\s*\\\"([^\\\"]+)\\\"/i],\n           ['lang-css',      /^style\\s*=\\s*\\'([^\\']+)\\'/i],\n           ['lang-css',      /^style\\s*=\\s*([^\\\"\\'>\\s]+)/i]\n           ]),\n      ['in.tag']);\n  registerLangHandler(\n      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\\s\\S]+/]]), ['uq.val']);\n  registerLangHandler(sourceDecorator({\n          'keywords': CPP_KEYWORDS,\n          'hashComments': true,\n          'cStyleComments': true,\n          'types': C_TYPES\n        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);\n  registerLangHandler(sourceDecorator({\n          'keywords': 'null,true,false'\n        }), ['json']);\n  registerLangHandler(sourceDecorator({\n          'keywords': CSHARP_KEYWORDS,\n          'hashComments': true,\n          'cStyleComments': true,\n          'verbatimStrings': true,\n          'types': C_TYPES\n        }), ['cs']);\n  registerLangHandler(sourceDecorator({\n          'keywords': JAVA_KEYWORDS,\n          'cStyleComments': true\n        }), ['java']);\n  registerLangHandler(sourceDecorator({\n          'keywords': SH_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true\n        }), ['bash', 'bsh', 'csh', 'sh']);\n  registerLangHandler(sourceDecorator({\n          'keywords': PYTHON_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'tripleQuotedStrings': true\n        }), ['cv', 'py', 'python']);\n  registerLangHandler(sourceDecorator({\n          'keywords': PERL_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'regexLiterals': 2  // multiline regex literals\n        }), ['perl', 'pl', 'pm']);\n  registerLangHandler(sourceDecorator({\n          'keywords': RUBY_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'regexLiterals': true\n        }), ['rb', 'ruby']);\n  registerLangHandler(sourceDecorator({\n          'keywords': JSCRIPT_KEYWORDS,\n          'cStyleComments': true,\n          'regexLiterals': true\n        }), ['javascript', 'js', 'ts', 'typescript']);\n  registerLangHandler(sourceDecorator({\n          'keywords': COFFEE_KEYWORDS,\n          'hashComments': 3,  // ### style block comments\n          'cStyleComments': true,\n          'multilineStrings': true,\n          'tripleQuotedStrings': true,\n          'regexLiterals': true\n        }), ['coffee']);\n  registerLangHandler(\n      createSimpleLexer([], [[PR_STRING, /^[\\s\\S]+/]]), ['regex']);\n\n  /** @param {JobT} job */\n  function applyDecorator(job) {\n    var opt_langExtension = job.langExtension;\n\n    try {\n      // Extract tags, and convert the source code to plain text.\n      var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);\n      /** Plain text. @type {string} */\n      var source = sourceAndSpans.sourceCode;\n      job.sourceCode = source;\n      job.spans = sourceAndSpans.spans;\n      job.basePos = 0;\n\n      // Apply the appropriate language handler\n      langHandlerForExtension(opt_langExtension, source)(job);\n\n      // Integrate the decorations and tags back into the source code,\n      // modifying the sourceNode in place.\n      recombineTagsAndDecorations(job);\n    } catch (e) {\n      if (win['console']) {\n        console['log'](e && e['stack'] || e);\n      }\n    }\n  }\n\n  /**\n   * Pretty print a chunk of code.\n   * @param sourceCodeHtml {string} The HTML to pretty print.\n   * @param opt_langExtension {string} The language name to use.\n   *     Typically, a filename extension like 'cpp' or 'java'.\n   * @param opt_numberLines {number|boolean} True to number lines,\n   *     or the 1-indexed number of the first line in sourceCodeHtml.\n   */\n  function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {\n    /** @type{number|boolean} */\n    var nl = opt_numberLines || false;\n    /** @type{string|null} */\n    var langExtension = opt_langExtension || null;\n    /** @type{!Element} */\n    var container = document.createElement('div');\n    // This could cause images to load and onload listeners to fire.\n    // E.g. <img onerror=\"alert(1337)\" src=\"nosuchimage.png\">.\n    // We assume that the inner HTML is from a trusted source.\n    // The pre-tag is required for IE8 which strips newlines from innerHTML\n    // when it is injected into a <pre> tag.\n    // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie\n    // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript\n    container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';\n    container = /** @type{!Element} */(container.firstChild);\n    if (nl) {\n      numberLines(container, nl, true);\n    }\n\n    /** @type{JobT} */\n    var job = {\n      langExtension: langExtension,\n      numberLines: nl,\n      sourceNode: container,\n      pre: 1,\n      sourceCode: null,\n      basePos: null,\n      spans: null,\n      decorations: null\n    };\n    applyDecorator(job);\n    return container.innerHTML;\n  }\n\n   /**\n    * Find all the {@code <pre>} and {@code <code>} tags in the DOM with\n    * {@code class=prettyprint} and prettify them.\n    *\n    * @param {Function} opt_whenDone called when prettifying is done.\n    * @param {HTMLElement|HTMLDocument} opt_root an element or document\n    *   containing all the elements to pretty print.\n    *   Defaults to {@code document.body}.\n    */\n  function $prettyPrint(opt_whenDone, opt_root) {\n    var root = opt_root || document.body;\n    var doc = root.ownerDocument || document;\n    function byTagName(tn) { return root.getElementsByTagName(tn); }\n    // fetch a list of nodes to rewrite\n    var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];\n    var elements = [];\n    for (var i = 0; i < codeSegments.length; ++i) {\n      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {\n        elements.push(codeSegments[i][j]);\n      }\n    }\n    codeSegments = null;\n\n    var clock = Date;\n    if (!clock['now']) {\n      clock = { 'now': function () { return +(new Date); } };\n    }\n\n    // The loop is broken into a series of continuations to make sure that we\n    // don't make the browser unresponsive when rewriting a large page.\n    var k = 0;\n\n    var langExtensionRe = /\\blang(?:uage)?-([\\w.]+)(?!\\S)/;\n    var prettyPrintRe = /\\bprettyprint\\b/;\n    var prettyPrintedRe = /\\bprettyprinted\\b/;\n    var preformattedTagNameRe = /pre|xmp/i;\n    var codeRe = /^code$/i;\n    var preCodeXmpRe = /^(?:pre|code|xmp)$/i;\n    var EMPTY = {};\n\n    function doWork() {\n      var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?\n                     clock['now']() + 250 /* ms */ :\n                     Infinity);\n      for (; k < elements.length && clock['now']() < endTime; k++) {\n        var cs = elements[k];\n\n        // Look for a preceding comment like\n        // <?prettify lang=\"...\" linenums=\"...\"?>\n        var attrs = EMPTY;\n        {\n          for (var preceder = cs; (preceder = preceder.previousSibling);) {\n            var nt = preceder.nodeType;\n            // <?foo?> is parsed by HTML 5 to a comment node (8)\n            // like <!--?foo?-->, but in XML is a processing instruction\n            var value = (nt === 7 || nt === 8) && preceder.nodeValue;\n            if (value\n                ? !/^\\??prettify\\b/.test(value)\n                : (nt !== 3 || /\\S/.test(preceder.nodeValue))) {\n              // Skip over white-space text nodes but not others.\n              break;\n            }\n            if (value) {\n              attrs = {};\n              value.replace(\n                  /\\b(\\w+)=([\\w:.%+-]+)/g,\n                function (_, name, value) { attrs[name] = value; });\n              break;\n            }\n          }\n        }\n\n        var className = cs.className;\n        if ((attrs !== EMPTY || prettyPrintRe.test(className))\n            // Don't redo this if we've already done it.\n            // This allows recalling pretty print to just prettyprint elements\n            // that have been added to the page since last call.\n            && !prettyPrintedRe.test(className)) {\n\n          // make sure this is not nested in an already prettified element\n          var nested = false;\n          for (var p = cs.parentNode; p; p = p.parentNode) {\n            var tn = p.tagName;\n            if (preCodeXmpRe.test(tn)\n                && p.className && prettyPrintRe.test(p.className)) {\n              nested = true;\n              break;\n            }\n          }\n          if (!nested) {\n            // Mark done.  If we fail to prettyprint for whatever reason,\n            // we shouldn't try again.\n            cs.className += ' prettyprinted';\n\n            // If the classes includes a language extensions, use it.\n            // Language extensions can be specified like\n            //     <pre class=\"prettyprint lang-cpp\">\n            // the language extension \"cpp\" is used to find a language handler\n            // as passed to PR.registerLangHandler.\n            // HTML5 recommends that a language be specified using \"language-\"\n            // as the prefix instead.  Google Code Prettify supports both.\n            // http://dev.w3.org/html5/spec-author-view/the-code-element.html\n            var langExtension = attrs['lang'];\n            if (!langExtension) {\n              langExtension = className.match(langExtensionRe);\n              // Support <pre class=\"prettyprint\"><code class=\"language-c\">\n              var wrapper;\n              if (!langExtension && (wrapper = childContentWrapper(cs))\n                  && codeRe.test(wrapper.tagName)) {\n                langExtension = wrapper.className.match(langExtensionRe);\n              }\n\n              if (langExtension) { langExtension = langExtension[1]; }\n            }\n\n            var preformatted;\n            if (preformattedTagNameRe.test(cs.tagName)) {\n              preformatted = 1;\n            } else {\n              var currentStyle = cs['currentStyle'];\n              var defaultView = doc.defaultView;\n              var whitespace = (\n                  currentStyle\n                  ? currentStyle['whiteSpace']\n                  : (defaultView\n                     && defaultView.getComputedStyle)\n                  ? defaultView.getComputedStyle(cs, null)\n                  .getPropertyValue('white-space')\n                  : 0);\n              preformatted = whitespace\n                  && 'pre' === whitespace.substring(0, 3);\n            }\n\n            // Look for a class like linenums or linenums:<n> where <n> is the\n            // 1-indexed number of the first line.\n            var lineNums = attrs['linenums'];\n            if (!(lineNums = lineNums === 'true' || +lineNums)) {\n              lineNums = className.match(/\\blinenums\\b(?::(\\d+))?/);\n              lineNums =\n                lineNums\n                ? lineNums[1] && lineNums[1].length\n                  ? +lineNums[1] : true\n                : false;\n            }\n            if (lineNums) { numberLines(cs, lineNums, preformatted); }\n\n            // do the pretty printing\n            var prettyPrintingJob = {\n              langExtension: langExtension,\n              sourceNode: cs,\n              numberLines: lineNums,\n              pre: preformatted,\n              sourceCode: null,\n              basePos: null,\n              spans: null,\n              decorations: null\n            };\n            applyDecorator(prettyPrintingJob);\n          }\n        }\n      }\n      if (k < elements.length) {\n        // finish up in a continuation\n        win.setTimeout(doWork, 250);\n      } else if ('function' === typeof opt_whenDone) {\n        opt_whenDone();\n      }\n    }\n\n    doWork();\n  }\n\n  /**\n   * Contains functions for creating and registering new language handlers.\n   * @type {Object}\n   */\n  var PR = win['PR'] = {\n        'createSimpleLexer': createSimpleLexer,\n        'registerLangHandler': registerLangHandler,\n        'sourceDecorator': sourceDecorator,\n        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,\n        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,\n        'PR_COMMENT': PR_COMMENT,\n        'PR_DECLARATION': PR_DECLARATION,\n        'PR_KEYWORD': PR_KEYWORD,\n        'PR_LITERAL': PR_LITERAL,\n        'PR_NOCODE': PR_NOCODE,\n        'PR_PLAIN': PR_PLAIN,\n        'PR_PUNCTUATION': PR_PUNCTUATION,\n        'PR_SOURCE': PR_SOURCE,\n        'PR_STRING': PR_STRING,\n        'PR_TAG': PR_TAG,\n        'PR_TYPE': PR_TYPE,\n        'prettyPrintOne':\n           IN_GLOBAL_SCOPE\n             ? (win['prettyPrintOne'] = $prettyPrintOne)\n             : (prettyPrintOne = $prettyPrintOne),\n        'prettyPrint': prettyPrint =\n           IN_GLOBAL_SCOPE\n             ? (win['prettyPrint'] = $prettyPrint)\n             : (prettyPrint = $prettyPrint)\n      };\n\n  // Make PR available via the Asynchronous Module Definition (AMD) API.\n  // Per https://github.com/amdjs/amdjs-api/wiki/AMD:\n  // The Asynchronous Module Definition (AMD) API specifies a\n  // mechanism for defining modules such that the module and its\n  // dependencies can be asynchronously loaded.\n  // ...\n  // To allow a clear indicator that a global define function (as\n  // needed for script src browser loading) conforms to the AMD API,\n  // any global define function SHOULD have a property called \"amd\"\n  // whose value is an object. This helps avoid conflict with any\n  // other existing JavaScript code that could have defined a define()\n  // function that does not conform to the AMD API.\n  var define = win['define'];\n  if (typeof define === \"function\" && define['amd']) {\n    define(\"google-code-prettify\", [], function () {\n      return PR;\n    });\n  }\n})();\n\n},{}],2:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"code-prettify\");\n\nwindow.addEventListener(\"load\", function () {\n\n\tPR.prettyPrint();\n\n\t// store tabs variables\n\tvar tabs = document.querySelectorAll(\"ul.nav-tabs > li\");\n\n\tfor (var i = 0; i < tabs.length; i++) {\n\t\ttabs[i].addEventListener(\"click\", switchTab);\n\t}\n\n\tfunction switchTab(event) {\n\t\tevent.preventDefault();\n\n\t\tdocument.querySelector(\"ul.nav-tabs li.active\").classList.remove(\"active\");\n\t\tdocument.querySelector(\".tab-pane.active\").classList.remove(\"active\");\n\n\t\tvar clickedTab = event.currentTarget;\n\t\tvar anchor = event.target;\n\t\tvar activePaneID = anchor.getAttribute(\"href\");\n\n\t\tclickedTab.classList.add(\"active\");\n\t\tdocument.querySelector(activePaneID).classList.add(\"active\");\n\t}\n});\n\n},{\"code-prettify\":1}]},{},[2]);\n"]}
    \ No newline at end of file
    diff --git a/Lesson33/assets/mystyle.css b/Lesson33/assets/mystyle.css
    new file mode 100644
    index 0000000..62ed24e
    --- /dev/null
    +++ b/Lesson33/assets/mystyle.css
    @@ -0,0 +1,3 @@
    +@import url(./../node_modules/code-prettify/styles/desert.css);.nav-tabs{float:left;width:100%;margin:0;list-style-type:none;border-bottom:1px solid transparent}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.5;padding:10px;border:1px solid transparent;border-radius:4px 4px 0 0;float:left;text-decoration:none}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border-color:transparent}.tab-content>.tab-pane{float:left;width:98%;display:none}.tab-content>.tab-pane.active{display:block;padding:10px;background-color:#fff;-webkit-box-shadow:0 5px 4px -2px rgba(0,0,0,0.15);box-shadow:0 5px 4px -2px rgba(0,0,0,0.15)}div.ui-toggle{margin:0;padding:0}div.ui-toggle input[type='checkbox']{display:none}div.ui-toggle input[type='checkbox']:checked+label{border-color:#009eea;background:#009eea;-webkit-box-shadow:inset 0 0 0 10px #009eea;box-shadow:inset 0 0 0 10px #009eea}div.ui-toggle input[type='checkbox']:checked+label>div{margin-left:20px}div.ui-toggle label{-webkit-transition:all 200ms ease;transition:all 200ms ease;display:inline-block;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#8c8c8c;-webkit-box-shadow:inset 0 0 0 0 #009eea;box-shadow:inset 0 0 0 0 #009eea;border:2px solid #8c8c8c;border-radius:22px;width:40px;height:20px}div.ui-toggle label div{-webkit-transition:all 200ms ease;transition:all 200ms ease;background:#FFFFFF;width:20px;height:20px;border-radius:10px}div.ui-toggle label:hover,div.ui-toggle label>div:hover{cursor:pointer}.cpt-table{width:100%;border-spacing:5px;text-align:left}.cpt-table,.cpt-table th,.cpt-table td{border:1px solid #ccc;border-collapse:collapse;padding:10px}.cpt-table th{background-color:#f5f5f5}.text-center{text-align:center}.inline-block{display:inline-block}
    +
    +/*# sourceMappingURL=mystyle.css.map */
    diff --git a/Lesson33/assets/mystyle.css.map b/Lesson33/assets/mystyle.css.map
    new file mode 100644
    index 0000000..aed459e
    --- /dev/null
    +++ b/Lesson33/assets/mystyle.css.map
    @@ -0,0 +1 @@
    +{"version":3,"sources":["mystyle.scss","modules/tabs.scss","modules/checkbox.scss","modules/table.scss","modules/form.scss"],"names":[],"mappings":"AAAA,+DCAA,UACC,WACA,WACA,SACA,qBACA,mCAAoC,CALrC,aAQE,WACA,kBAAmB,CATrB,eAYG,iBACA,gBACA,aACA,6BACA,0BACA,WACA,oBAAqB,CAlBxB,qBAqBI,2BAA4B,CArBhC,8EA6BI,WACA,eACA,sBACA,wBAAyB,CACzB,uBAMH,WACA,UACA,YAAa,CAHd,8BAME,cACA,aACA,sBACA,mDAAA,AAA8C,0CAAA,CAC9C,cC3BD,SACA,SAAU,CAEV,qCACC,YAAa,CAED,mDACX,qBACA,mBACA,4CAAA,AA9BS,mCAAA,CAgCP,uDACD,gBAmCoB,CAlCpB,oBAKF,kCAAA,AACA,0BAAA,qBACA,kBA7BD,2BACA,yBACA,AACA,sBACA,qBACA,iBA4BC,mBACA,yCAAA,AACA,iCAAA,yBACA,mBACA,WACA,WAkBsB,CAhBtB,wBACC,kCAAA,AACA,0BAAA,mBACA,WACA,YACA,kBAA0B,CAnB5B,wDAwBE,cAAe,CACf,WC9DF,WACA,mBACA,eAAgB,CAHjB,uCAQE,sBACA,yBACA,YAAa,CAVf,cAcE,wBAAyB,CACzB,aAID,iBAAkB,CAClB,cCnBA,oBAAqB,CACrB","file":"mystyle.css","sourcesContent":["@import './../node_modules/code-prettify/styles/desert.css';\n\n@import 'modules/tabs';\n@import 'modules/checkbox';\n@import 'modules/table';\n@import 'modules/form';",".nav-tabs {\n\tfloat: left;\n\twidth: 100%;\n\tmargin: 0;\n\tlist-style-type: none;\n\tborder-bottom: 1px solid transparent;\n\n\t> li {\n\t\tfloat: left;\n\t\tmargin-bottom: -1px;\n\n\t\t> a {\n\t\t\tmargin-right: 2px;\n\t\t\tline-height: 1.5;\n\t\t\tpadding: 10px;\n\t\t\tborder: 1px solid transparent;\n\t\t\tborder-radius: 4px 4px 0 0;\n\t\t\tfloat: left;\n\t\t\ttext-decoration: none;\n\n\t\t\t&:hover {\n\t\t\t\tborder-color: #eee #eee #ddd;\n\t\t\t}\n\t\t}\n\n\t\t&.active > a {\n\t\t\t&,\n\t\t\t&:hover,\n\t\t\t&:focus {\n\t\t\t\tcolor: #555;\n\t\t\t\tcursor: default;\n\t\t\t\tbackground-color: #fff;\n\t\t\t\tborder-color: transparent;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.tab-content > .tab-pane {\n\tfloat: left;\n\twidth: 98%;\n\tdisplay: none;\n\n\t&.active {\n\t\tdisplay: block;\n\t\tpadding: 10px;\n\t\tbackground-color: #fff;\n\t\tbox-shadow: 0 5px 4px -2px rgba(0, 0, 0, 0.15);\n\t}\n}","$on: #009eea;\n$bg: #D9CB9E;\n$off: #8c8c8c;\n\n@mixin center {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\ttransform: translate(-50%, -50%);\n}\n\n@mixin userSelect($value) {\n\t-webkit-touch-callout: $value;\n\t-webkit-user-select: $value;\n\t-khtml-user-select: $value;\n\t-moz-user-select: $value;\n\t-ms-user-select: $value;\n\tuser-select: $value;\n}\n\n@mixin ui-toggle($height, $on, $off) {\n\tmargin: 0;\n\tpadding: 0;\n\n\tinput[type='checkbox'] {\n\t\tdisplay: none;\n\n\t\t&:checked + label {\n\t\t\tborder-color: $on;\n\t\t\tbackground: $on;\n\t\t\tbox-shadow: inset 0 0 0 #{$height / 2} $on;\n\n\t\t\t> div {\n\t\t\t\tmargin-left: $height;\n\t\t\t}\n\t\t}\n\t}\n\n\tlabel {\n\t\ttransition: all 200ms ease;\n\t\tdisplay: inline-block;\n\t\tposition: relative;\n\n\t\t@include userSelect(none);\n\n\t\tbackground: $off;\n\t\tbox-shadow: inset 0 0 0 0 $on;\n\t\tborder: 2px solid $off;\n\t\tborder-radius: $height + 2;\n\t\twidth: $height * 2;\n\t\theight: $height;\n\n\t\tdiv {\n\t\t\ttransition: all 200ms ease;\n\t\t\tbackground: #FFFFFF;\n\t\t\twidth: $height;\n\t\t\theight: $height;\n\t\t\tborder-radius: $height / 2;\n\t\t}\n\n\t\t&:hover,\n\t\t& > div:hover {\n\t\t\tcursor: pointer;\n\t\t}\n\t}\n}\n\ndiv.ui-toggle {\n\t@include ui-toggle(20px, $on, $off);\n}",".cpt-table {\n\twidth: 100%;\n\tborder-spacing: 5px;\n\ttext-align: left;\n\n\t&,\n\t& th,\n\t& td {\n\t\tborder: 1px solid #ccc;\n\t\tborder-collapse: collapse;\n\t\tpadding: 10px;\n\t}\n\n\t& th {\n\t\tbackground-color: #f5f5f5;\n\t}\n}\n\n.text-center {\n\ttext-align: center;\n}",".inline-block {\n\tdisplay: inline-block;\n}"]}
    \ No newline at end of file
    diff --git a/Lesson33/composer.json b/Lesson33/composer.json
    new file mode 100644
    index 0000000..cc3cc19
    --- /dev/null
    +++ b/Lesson33/composer.json
    @@ -0,0 +1,17 @@
    +{
    +    "name": "alecaddd/alecaddd-plugin",
    +    "description": "Awesome starter plugin example",
    +    "type": "project",
    +    "license": "GPL-3.0",
    +    "authors": [
    +        {
    +            "name": "Alecaddd",
    +            "email": "castellani.ale@gmail.com"
    +        }
    +    ],
    +    "minimum-stability": "dev",
    +    "require": {},
    +    "autoload": {
    +        "psr-4": {"Inc\\": "./inc"}
    +    }
    +}
    diff --git a/Lesson33/gulpfile.js b/Lesson33/gulpfile.js
    new file mode 100644
    index 0000000..d166c0c
    --- /dev/null
    +++ b/Lesson33/gulpfile.js
    @@ -0,0 +1,104 @@
    +// Load Gulp...of course
    +var gulp         = require( 'gulp' );
    +
    +// CSS related plugins
    +var sass         = require( 'gulp-sass' );
    +var autoprefixer = require( 'gulp-autoprefixer' );
    +var minifycss    = require( 'gulp-uglifycss' );
    +
    +// JS related plugins
    +var concat       = require( 'gulp-concat' );
    +var uglify       = require( 'gulp-uglify' );
    +var babelify     = require( 'babelify' );
    +var browserify   = require( 'browserify' );
    +var source       = require( 'vinyl-source-stream' );
    +var buffer       = require( 'vinyl-buffer' );
    +var stripDebug   = require( 'gulp-strip-debug' );
    +
    +// Utility plugins
    +var rename       = require( 'gulp-rename' );
    +var sourcemaps   = require( 'gulp-sourcemaps' );
    +var notify       = require( 'gulp-notify' );
    +var plumber      = require( 'gulp-plumber' );
    +var options      = require( 'gulp-options' );
    +var gulpif       = require( 'gulp-if' );
    +
    +// Browers related plugins
    +var browserSync  = require( 'browser-sync' ).create();
    +var reload       = browserSync.reload;
    +
    +// Project related variables
    +var projectURL   = 'https://test.dev';
    +
    +var styleSRC     = 'src/scss/mystyle.scss';
    +var styleURL     = './assets/';
    +var mapURL       = './';
    +
    +var jsSRC        = 'src/js/myscript.js';
    +var jsURL        = './assets/';
    +
    +var styleWatch   = 'src/scss/**/*.scss';
    +var jsWatch      = 'src/js/**/*.js';
    +var phpWatch     = '**/*.php';
    +
    +// Tasks
    +gulp.task( 'browser-sync', function() {
    +	browserSync.init({
    +		proxy: projectURL,
    +		https: {
    +			key: '/Users/alecaddd/.valet/Certificates/test.dev.key',
    +			cert: '/Users/alecaddd/.valet/Certificates/test.dev.crt'
    +		},
    +		injectChanges: true,
    +		open: false
    +	});
    +});
    +
    +gulp.task( 'styles', function() {
    +	gulp.src( styleSRC )
    +		.pipe( sourcemaps.init() )
    +		.pipe( sass({
    +			errLogToConsole: true,
    +			outputStyle: 'compressed'
    +		}) )
    +		.on( 'error', console.error.bind( console ) )
    +		.pipe( autoprefixer({ browsers: [ 'last 2 versions', '> 5%', 'Firefox ESR' ] }) )
    +		.pipe( sourcemaps.write( mapURL ) )
    +		.pipe( gulp.dest( styleURL ) )
    +		.pipe( browserSync.stream() );
    +});
    +
    +gulp.task( 'js', function() {
    +	return browserify({
    +		entries: [jsSRC]
    +	})
    +	.transform( babelify, { presets: [ 'env' ] } )
    +	.bundle()
    +	.pipe( source( 'myscript.js' ) )
    +	.pipe( buffer() )
    +	.pipe( gulpif( options.has( 'production' ), stripDebug() ) )
    +	.pipe( sourcemaps.init({ loadMaps: true }) )
    +	.pipe( uglify() )
    +	.pipe( sourcemaps.write( '.' ) )
    +	.pipe( gulp.dest( jsURL ) )
    +	.pipe( browserSync.stream() );
    + });
    +
    +function triggerPlumber( src, url ) {
    +	return gulp.src( src )
    +	.pipe( plumber() )
    +	.pipe( gulp.dest( url ) );
    +}
    +
    + gulp.task( 'default', ['styles', 'js'], function() {
    +	gulp.src( jsURL + 'myscript.min.js' )
    +		.pipe( notify({ message: 'Assets Compiled!' }) );
    + });
    +
    + gulp.task( 'watch', ['default', 'browser-sync'], function() {
    +	gulp.watch( phpWatch, reload );
    +	gulp.watch( styleWatch, [ 'styles' ] );
    +	gulp.watch( jsWatch, [ 'js', reload ] );
    +	gulp.src( jsURL + 'myscript.min.js' )
    +		.pipe( notify({ message: 'Gulp is Watching, Happy Coding!' }) );
    + });
    diff --git a/Lesson33/inc/Api/Callbacks/AdminCallbacks.php b/Lesson33/inc/Api/Callbacks/AdminCallbacks.php
    new file mode 100644
    index 0000000..07c0d0f
    --- /dev/null
    +++ b/Lesson33/inc/Api/Callbacks/AdminCallbacks.php
    @@ -0,0 +1,60 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Callbacks;
    +
    +use Inc\Base\BaseController;
    +
    +class AdminCallbacks extends BaseController
    +{
    +	public function adminDashboard()
    +	{
    +		return require_once( "$this->plugin_path/templates/admin.php" );
    +	}
    +
    +	public function adminCpt()
    +	{
    +		return require_once( "$this->plugin_path/templates/cpt.php" );
    +	}
    +
    +	public function adminTaxonomy()
    +	{
    +		return require_once( "$this->plugin_path/templates/taxonomy.php" );
    +	}
    +
    +	public function adminWidget()
    +	{
    +		return require_once( "$this->plugin_path/templates/widget.php" );
    +	}
    +
    +	public function adminGallery()
    +	{
    +		echo "<h1>Gallery Manager</h1>";
    +	}
    +
    +	public function adminTestimonial()
    +	{
    +		echo "<h1>Testimonial Manager</h1>";
    +	}
    +
    +	public function adminTemplates()
    +	{
    +		echo "<h1>Templates Manager</h1>";
    +	}
    +
    +	public function adminAuth()
    +	{
    +		echo "<h1>Templates Manager</h1>";
    +	}
    +
    +	public function adminMembership()
    +	{
    +		echo "<h1>Membership Manager</h1>";
    +	}
    +
    +	public function adminChat()
    +	{
    +		echo "<h1>Chat Manager</h1>";
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Api/Callbacks/CptCallbacks.php b/Lesson33/inc/Api/Callbacks/CptCallbacks.php
    new file mode 100644
    index 0000000..3638ebf
    --- /dev/null
    +++ b/Lesson33/inc/Api/Callbacks/CptCallbacks.php
    @@ -0,0 +1,70 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Callbacks;
    +
    +class CptCallbacks
    +{
    +
    +	public function cptSectionManager()
    +	{
    +		echo 'Create as many Custom Post Types as you want.';
    +	}
    +
    +	public function cptSanitize( $input )
    +	{
    +		$output = get_option('alecaddd_plugin_cpt');
    +
    +		if ( isset($_POST["remove"]) ) {
    +			unset($output[$_POST["remove"]]);
    +
    +			return $output;
    +		}
    +
    +		if ( count($output) == 0 ) {
    +			$output[$input['post_type']] = $input;
    +
    +			return $output;
    +		}
    +
    +		foreach ($output as $key => $value) {
    +			if ($input['post_type'] === $key) {
    +				$output[$key] = $input;
    +			} else {
    +				$output[$input['post_type']] = $input;
    +			}
    +		}
    +		
    +		return $output;
    +	}
    +
    +	public function textField( $args )
    +	{
    +		$name = $args['label_for'];
    +		$option_name = $args['option_name'];
    +		$value = '';
    +
    +		if ( isset($_POST["edit_post"]) ) {
    +			$input = get_option( $option_name );
    +			$value = $input[$_POST["edit_post"]][$name];
    +		}
    +
    +		echo '<input type="text" class="regular-text" id="' . $name . '" name="' . $option_name . '[' . $name . ']" value="' . $value . '" placeholder="' . $args['placeholder'] . '" required>';
    +	}
    +
    +	public function checkboxField( $args )
    +	{
    +		$name = $args['label_for'];
    +		$classes = $args['class'];
    +		$option_name = $args['option_name'];
    +		$checked = false;
    +
    +		if ( isset($_POST["edit_post"]) ) {
    +			$checkbox = get_option( $option_name );
    +			$checked = isset($checkbox[$_POST["edit_post"]][$name]) ?: false;
    +		}
    +
    +		echo '<div class="' . $classes . '"><input type="checkbox" id="' . $name . '" name="' . $option_name . '[' . $name . ']" value="1" class="" ' . ( $checked ? 'checked' : '') . '><label for="' . $name . '"><div></div></label></div>';
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Api/Callbacks/ManagerCallbacks.php b/Lesson33/inc/Api/Callbacks/ManagerCallbacks.php
    new file mode 100644
    index 0000000..264188f
    --- /dev/null
    +++ b/Lesson33/inc/Api/Callbacks/ManagerCallbacks.php
    @@ -0,0 +1,37 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Callbacks;
    +
    +use Inc\Base\BaseController;
    +
    +class ManagerCallbacks extends BaseController
    +{
    +	public function checkboxSanitize( $input )
    +	{
    +		$output = array();
    +
    +		foreach ( $this->managers as $key => $value ) {
    +			$output[$key] = isset( $input[$key] ) ? true : false;
    +		}
    +
    +		return $output;
    +	}
    +
    +	public function adminSectionManager()
    +	{
    +		echo 'Manage the Sections and Features of this Plugin by activating the checkboxes from the following list.';
    +	}
    +
    +	public function checkboxField( $args )
    +	{
    +		$name = $args['label_for'];
    +		$classes = $args['class'];
    +		$option_name = $args['option_name'];
    +		$checkbox = get_option( $option_name );
    +		$checked = isset($checkbox[$name]) ? ($checkbox[$name] ? true : false) : false;
    +
    +		echo '<div class="' . $classes . '"><input type="checkbox" id="' . $name . '" name="' . $option_name . '[' . $name . ']" value="1" class="" ' . ( $checked ? 'checked' : '') . '><label for="' . $name . '"><div></div></label></div>';
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Api/Callbacks/TaxonomyCallbacks.php b/Lesson33/inc/Api/Callbacks/TaxonomyCallbacks.php
    new file mode 100644
    index 0000000..bddb5a1
    --- /dev/null
    +++ b/Lesson33/inc/Api/Callbacks/TaxonomyCallbacks.php
    @@ -0,0 +1,68 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Callbacks;
    +
    +class TaxonomyCallbacks
    +{
    +	public function taxSectionManager() {
    +		echo 'Create as many Custom Taxonomies as you want.';
    +	}
    +
    +	public function taxSanitize( $input )
    +	{
    +		$output = get_option('alecaddd_plugin_tax');
    +
    +		if ( isset($_POST["remove"]) ) {
    +			unset($output[$_POST["remove"]]);
    +
    +			return $output;
    +		}
    +
    +		if ( count($output) == 0 ) {
    +			$output[$input['taxonomy']] = $input;
    +
    +			return $output;
    +		}
    +
    +		foreach ($output as $key => $value) {
    +			if ($input['taxonomy'] === $key) {
    +				$output[$key] = $input;
    +			} else {
    +				$output[$input['taxonomy']] = $input;
    +			}
    +		}
    +		
    +		return $output;
    +	}
    +
    +	public function textField( $args )
    +	{
    +		$name = $args['label_for'];
    +		$option_name = $args['option_name'];
    +		$value = '';
    +
    +		if ( isset($_POST["edit_taxonomy"]) ) {
    +			$input = get_option( $option_name );
    +			$value = $input[$_POST["edit_taxonomy"]][$name];
    +		}
    +
    +		echo '<input type="text" class="regular-text" id="' . $name . '" name="' . $option_name . '[' . $name . ']" value="' . $value . '" placeholder="' . $args['placeholder'] . '" required>';
    +	}
    +
    +	public function checkboxField( $args )
    +	{
    +		$name = $args['label_for'];
    +		$classes = $args['class'];
    +		$option_name = $args['option_name'];
    +		$checked = false;
    +
    +		if ( isset($_POST["edit_taxonomy"]) ) {
    +			$checkbox = get_option( $option_name );
    +			$checked = isset($checkbox[$_POST["edit_taxonomy"]][$name]) ?: false;
    +		}
    +
    +		echo '<div class="' . $classes . '"><input type="checkbox" id="' . $name . '" name="' . $option_name . '[' . $name . ']" value="1" class="" ' . ( $checked ? 'checked' : '') . '><label for="' . $name . '"><div></div></label></div>';
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Api/SettingsApi.php b/Lesson33/inc/Api/SettingsApi.php
    new file mode 100644
    index 0000000..bd01bfe
    --- /dev/null
    +++ b/Lesson33/inc/Api/SettingsApi.php
    @@ -0,0 +1,117 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api;
    +
    +class SettingsApi
    +{
    +	public $admin_pages = array();
    +
    +	public $admin_subpages = array();
    +
    +	public $settings = array();
    +
    +	public $sections = array();
    +
    +	public $fields = array();
    +
    +	public function register()
    +	{
    +		if ( ! empty($this->admin_pages) || ! empty($this->admin_subpages) ) {
    +			add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
    +		}
    +
    +		if ( !empty($this->settings) ) {
    +			add_action( 'admin_init', array( $this, 'registerCustomFields' ) );
    +		}
    +	}
    +
    +	public function addPages( array $pages )
    +	{
    +		$this->admin_pages = $pages;
    +
    +		return $this;
    +	}
    +
    +	public function withSubPage( string $title = null ) 
    +	{
    +		if ( empty($this->admin_pages) ) {
    +			return $this;
    +		}
    +
    +		$admin_page = $this->admin_pages[0];
    +
    +		$subpage = array(
    +			array(
    +				'parent_slug' => $admin_page['menu_slug'], 
    +				'page_title' => $admin_page['page_title'], 
    +				'menu_title' => ($title) ? $title : $admin_page['menu_title'], 
    +				'capability' => $admin_page['capability'], 
    +				'menu_slug' => $admin_page['menu_slug'], 
    +				'callback' => $admin_page['callback']
    +			)
    +		);
    +
    +		$this->admin_subpages = $subpage;
    +
    +		return $this;
    +	}
    +
    +	public function addSubPages( array $pages )
    +	{
    +		$this->admin_subpages = array_merge( $this->admin_subpages, $pages );
    +
    +		return $this;
    +	}
    +
    +	public function addAdminMenu()
    +	{
    +		foreach ( $this->admin_pages as $page ) {
    +			add_menu_page( $page['page_title'], $page['menu_title'], $page['capability'], $page['menu_slug'], $page['callback'], $page['icon_url'], $page['position'] );
    +		}
    +
    +		foreach ( $this->admin_subpages as $page ) {
    +			add_submenu_page( $page['parent_slug'], $page['page_title'], $page['menu_title'], $page['capability'], $page['menu_slug'], $page['callback'] );
    +		}
    +	}
    +
    +	public function setSettings( array $settings )
    +	{
    +		$this->settings = $settings;
    +
    +		return $this;
    +	}
    +
    +	public function setSections( array $sections )
    +	{
    +		$this->sections = $sections;
    +
    +		return $this;
    +	}
    +
    +	public function setFields( array $fields )
    +	{
    +		$this->fields = $fields;
    +
    +		return $this;
    +	}
    +
    +	public function registerCustomFields()
    +	{
    +		// register setting
    +		foreach ( $this->settings as $setting ) {
    +			register_setting( $setting["option_group"], $setting["option_name"], ( isset( $setting["callback"] ) ? $setting["callback"] : '' ) );
    +		}
    +
    +		// add settings section
    +		foreach ( $this->sections as $section ) {
    +			add_settings_section( $section["id"], $section["title"], ( isset( $section["callback"] ) ? $section["callback"] : '' ), $section["page"] );
    +		}
    +
    +		// add settings field
    +		foreach ( $this->fields as $field ) {
    +			add_settings_field( $field["id"], $field["title"], ( isset( $field["callback"] ) ? $field["callback"] : '' ), $field["page"], $field["section"], ( isset( $field["args"] ) ? $field["args"] : '' ) );
    +		}
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/Activate.php b/Lesson33/inc/Base/Activate.php
    new file mode 100644
    index 0000000..57c833f
    --- /dev/null
    +++ b/Lesson33/inc/Base/Activate.php
    @@ -0,0 +1,26 @@
    +<?php
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +class Activate
    +{
    +	public static function activate() {
    +		flush_rewrite_rules();
    +
    +		$default = array();
    +
    +		if ( ! get_option( 'alecaddd_plugin' ) ) {
    +			update_option( 'alecaddd_plugin', $default );
    +		}
    +
    +		if ( ! get_option( 'alecaddd_plugin_cpt' ) ) {
    +			update_option( 'alecaddd_plugin_cpt', $default );
    +		}
    +
    +		if ( ! get_option( 'alecaddd_plugin_tax' ) ) {
    +			update_option( 'alecaddd_plugin_tax', $default );
    +		}
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/AuthController.php b/Lesson33/inc/Base/AuthController.php
    new file mode 100644
    index 0000000..a50a020
    --- /dev/null
    +++ b/Lesson33/inc/Base/AuthController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class AuthController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'login_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Login Manager', 
    +				'menu_title' => 'Login Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_auth', 
    +				'callback' => array( $this->callbacks, 'adminAuth' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/BaseController.php b/Lesson33/inc/Base/BaseController.php
    new file mode 100644
    index 0000000..a49e221
    --- /dev/null
    +++ b/Lesson33/inc/Base/BaseController.php
    @@ -0,0 +1,41 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +class BaseController
    +{
    +	public $plugin_path;
    +
    +	public $plugin_url;
    +
    +	public $plugin;
    +
    +	public $managers = array();
    +
    +	public function __construct() {
    +		$this->plugin_path = plugin_dir_path( dirname( __FILE__, 2 ) );
    +		$this->plugin_url = plugin_dir_url( dirname( __FILE__, 2 ) );
    +		$this->plugin = plugin_basename( dirname( __FILE__, 3 ) ) . '/alecaddd-plugin.php';
    +
    +		$this->managers = array(
    +			'cpt_manager' => 'Activate CPT Manager',
    +			'taxonomy_manager' => 'Activate Taxonomy Manager',
    +			'media_widget' => 'Activate Media Widget',
    +			'gallery_manager' => 'Activate Gallery Manager',
    +			'testimonial_manager' => 'Activate Testimonial Manager',
    +			'templates_manager' => 'Activate Templates Manager',
    +			'login_manager' => 'Activate Ajax Login/Signup',
    +			'membership_manager' => 'Activate Membership Manager',
    +			'chat_manager' => 'Activate Chat Manager'
    +		);
    +	}
    +
    +	public function activated( string $key )
    +	{
    +		$option = get_option( 'alecaddd_plugin' );
    +
    +		return isset( $option[ $key ] ) ? $option[ $key ] : false;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/ChatController.php b/Lesson33/inc/Base/ChatController.php
    new file mode 100644
    index 0000000..7d2df5a
    --- /dev/null
    +++ b/Lesson33/inc/Base/ChatController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class ChatController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'media_widget' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Widgets Manager', 
    +				'menu_title' => 'Widgets Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_widget', 
    +				'callback' => array( $this->callbacks, 'adminWidget' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/CustomPostTypeController.php b/Lesson33/inc/Base/CustomPostTypeController.php
    new file mode 100644
    index 0000000..dfef3b2
    --- /dev/null
    +++ b/Lesson33/inc/Base/CustomPostTypeController.php
    @@ -0,0 +1,277 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\CptCallbacks;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class CustomPostTypeController extends BaseController
    +{
    +	public $settings;
    +
    +	public $callbacks;
    +
    +	public $cpt_callbacks;
    +
    +	public $subpages = array();
    +
    +	public $custom_post_types = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'cpt_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->cpt_callbacks = new CptCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->setSettings();
    +
    +		$this->setSections();
    +
    +		$this->setFields();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +
    +		$this->storeCustomPostTypes();
    +
    +		if ( ! empty( $this->custom_post_types ) ) {
    +			add_action( 'init', array( $this, 'registerCustomPostTypes' ) );
    +		}
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Custom Post Types', 
    +				'menu_title' => 'CPT Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_cpt', 
    +				'callback' => array( $this->callbacks, 'adminCpt' )
    +			)
    +		);
    +	}
    +
    +	public function setSettings()
    +	{
    +		$args = array(
    +			array(
    +				'option_group' => 'alecaddd_plugin_cpt_settings',
    +				'option_name' => 'alecaddd_plugin_cpt',
    +				'callback' => array( $this->cpt_callbacks, 'cptSanitize' )
    +			)
    +		);
    +
    +		$this->settings->setSettings( $args );
    +	}
    +
    +	public function setSections()
    +	{
    +		$args = array(
    +			array(
    +				'id' => 'alecaddd_cpt_index',
    +				'title' => 'Custom Post Type Manager',
    +				'callback' => array( $this->cpt_callbacks, 'cptSectionManager' ),
    +				'page' => 'alecaddd_cpt'
    +			)
    +		);
    +
    +		$this->settings->setSections( $args );
    +	}
    +
    +	public function setFields()
    +	{
    +		$args = array(
    +			array(
    +				'id' => 'post_type',
    +				'title' => 'Custom Post Type ID',
    +				'callback' => array( $this->cpt_callbacks, 'textField' ),
    +				'page' => 'alecaddd_cpt',
    +				'section' => 'alecaddd_cpt_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_cpt',
    +					'label_for' => 'post_type',
    +					'placeholder' => 'eg. product',
    +					'array' => 'post_type'
    +				)
    +			),
    +			array(
    +				'id' => 'singular_name',
    +				'title' => 'Singular Name',
    +				'callback' => array( $this->cpt_callbacks, 'textField' ),
    +				'page' => 'alecaddd_cpt',
    +				'section' => 'alecaddd_cpt_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_cpt',
    +					'label_for' => 'singular_name',
    +					'placeholder' => 'eg. Product',
    +					'array' => 'post_type'
    +				)
    +			),
    +			array(
    +				'id' => 'plural_name',
    +				'title' => 'Plural Name',
    +				'callback' => array( $this->cpt_callbacks, 'textField' ),
    +				'page' => 'alecaddd_cpt',
    +				'section' => 'alecaddd_cpt_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_cpt',
    +					'label_for' => 'plural_name',
    +					'placeholder' => 'eg. Products',
    +					'array' => 'post_type'
    +				)
    +			),
    +			array(
    +				'id' => 'public',
    +				'title' => 'Public',
    +				'callback' => array( $this->cpt_callbacks, 'checkboxField' ),
    +				'page' => 'alecaddd_cpt',
    +				'section' => 'alecaddd_cpt_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_cpt',
    +					'label_for' => 'public',
    +					'class' => 'ui-toggle',
    +					'array' => 'post_type'
    +				)
    +			),
    +			array(
    +				'id' => 'has_archive',
    +				'title' => 'Archive',
    +				'callback' => array( $this->cpt_callbacks, 'checkboxField' ),
    +				'page' => 'alecaddd_cpt',
    +				'section' => 'alecaddd_cpt_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_cpt',
    +					'label_for' => 'has_archive',
    +					'class' => 'ui-toggle',
    +					'array' => 'post_type'
    +				)
    +			)
    +		);
    +
    +		$this->settings->setFields( $args );
    +	}
    +
    +	public function storeCustomPostTypes()
    +	{
    +		$options = get_option( 'alecaddd_plugin_cpt' ) ?: array();
    +
    +		foreach ($options as $option) {
    +
    +			$this->custom_post_types[] = array(
    +				'post_type'             => $option['post_type'],
    +				'name'                  => $option['plural_name'],
    +				'singular_name'         => $option['singular_name'],
    +				'menu_name'             => $option['plural_name'],
    +				'name_admin_bar'        => $option['singular_name'],
    +				'archives'              => $option['singular_name'] . ' Archives',
    +				'attributes'            => $option['singular_name'] . ' Attributes',
    +				'parent_item_colon'     => 'Parent ' . $option['singular_name'],
    +				'all_items'             => 'All ' . $option['plural_name'],
    +				'add_new_item'          => 'Add New ' . $option['singular_name'],
    +				'add_new'               => 'Add New',
    +				'new_item'              => 'New ' . $option['singular_name'],
    +				'edit_item'             => 'Edit ' . $option['singular_name'],
    +				'update_item'           => 'Update ' . $option['singular_name'],
    +				'view_item'             => 'View ' . $option['singular_name'],
    +				'view_items'            => 'View ' . $option['plural_name'],
    +				'search_items'          => 'Search ' . $option['plural_name'],
    +				'not_found'             => 'No ' . $option['singular_name'] . ' Found',
    +				'not_found_in_trash'    => 'No ' . $option['singular_name'] . ' Found in Trash',
    +				'featured_image'        => 'Featured Image',
    +				'set_featured_image'    => 'Set Featured Image',
    +				'remove_featured_image' => 'Remove Featured Image',
    +				'use_featured_image'    => 'Use Featured Image',
    +				'insert_into_item'      => 'Insert into ' . $option['singular_name'],
    +				'uploaded_to_this_item' => 'Upload to this ' . $option['singular_name'],
    +				'items_list'            => $option['plural_name'] . ' List',
    +				'items_list_navigation' => $option['plural_name'] . ' List Navigation',
    +				'filter_items_list'     => 'Filter' . $option['plural_name'] . ' List',
    +				'label'                 => $option['singular_name'],
    +				'description'           => $option['plural_name'] . 'Custom Post Type',
    +				'supports'              => array( 'title', 'editor', 'thumbnail' ),
    +				'taxonomies'            => array( 'category', 'post_tag' ),
    +				'hierarchical'          => false,
    +				'public'                => isset($option['public']) ?: false,
    +				'show_ui'               => true,
    +				'show_in_menu'          => true,
    +				'menu_position'         => 5,
    +				'show_in_admin_bar'     => true,
    +				'show_in_nav_menus'     => true,
    +				'can_export'            => true,
    +				'has_archive'           => isset($option['has_archive']) ?: false,
    +				'exclude_from_search'   => false,
    +				'publicly_queryable'    => true,
    +				'capability_type'       => 'post'
    +			);
    +		}
    +	}
    +
    +	public function registerCustomPostTypes()
    +	{
    +		foreach ($this->custom_post_types as $post_type) {
    +			register_post_type( $post_type['post_type'],
    +				array(
    +					'labels' => array(
    +						'name'                  => $post_type['name'],
    +						'singular_name'         => $post_type['singular_name'],
    +						'menu_name'             => $post_type['menu_name'],
    +						'name_admin_bar'        => $post_type['name_admin_bar'],
    +						'archives'              => $post_type['archives'],
    +						'attributes'            => $post_type['attributes'],
    +						'parent_item_colon'     => $post_type['parent_item_colon'],
    +						'all_items'             => $post_type['all_items'],
    +						'add_new_item'          => $post_type['add_new_item'],
    +						'add_new'               => $post_type['add_new'],
    +						'new_item'              => $post_type['new_item'],
    +						'edit_item'             => $post_type['edit_item'],
    +						'update_item'           => $post_type['update_item'],
    +						'view_item'             => $post_type['view_item'],
    +						'view_items'            => $post_type['view_items'],
    +						'search_items'          => $post_type['search_items'],
    +						'not_found'             => $post_type['not_found'],
    +						'not_found_in_trash'    => $post_type['not_found_in_trash'],
    +						'featured_image'        => $post_type['featured_image'],
    +						'set_featured_image'    => $post_type['set_featured_image'],
    +						'remove_featured_image' => $post_type['remove_featured_image'],
    +						'use_featured_image'    => $post_type['use_featured_image'],
    +						'insert_into_item'      => $post_type['insert_into_item'],
    +						'uploaded_to_this_item' => $post_type['uploaded_to_this_item'],
    +						'items_list'            => $post_type['items_list'],
    +						'items_list_navigation' => $post_type['items_list_navigation'],
    +						'filter_items_list'     => $post_type['filter_items_list']
    +					),
    +					'label'                     => $post_type['label'],
    +					'description'               => $post_type['description'],
    +					'supports'                  => $post_type['supports'],
    +					'taxonomies'                => $post_type['taxonomies'],
    +					'hierarchical'              => $post_type['hierarchical'],
    +					'public'                    => $post_type['public'],
    +					'show_ui'                   => $post_type['show_ui'],
    +					'show_in_menu'              => $post_type['show_in_menu'],
    +					'menu_position'             => $post_type['menu_position'],
    +					'show_in_admin_bar'         => $post_type['show_in_admin_bar'],
    +					'show_in_nav_menus'         => $post_type['show_in_nav_menus'],
    +					'can_export'                => $post_type['can_export'],
    +					'has_archive'               => $post_type['has_archive'],
    +					'exclude_from_search'       => $post_type['exclude_from_search'],
    +					'publicly_queryable'        => $post_type['publicly_queryable'],
    +					'capability_type'           => $post_type['capability_type']
    +				)
    +			);
    +		}
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/CustomTaxonomyController.php b/Lesson33/inc/Base/CustomTaxonomyController.php
    new file mode 100644
    index 0000000..15935c2
    --- /dev/null
    +++ b/Lesson33/inc/Base/CustomTaxonomyController.php
    @@ -0,0 +1,135 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +use Inc\Api\Callbacks\TaxonomyCallbacks;
    +
    +/**
    +* 
    +*/
    +class CustomTaxonomyController extends BaseController
    +{
    +	public $settings;
    +
    +	public $callbacks;
    +
    +	public $tax_callbacks;
    +
    +	public $subpages = array();
    +
    +	public $taxonomies = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'taxonomy_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->tax_callbacks = new TaxonomyCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->setSettings();
    +
    +		$this->setSections();
    +
    +		$this->setFields();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Custom Taxonomies', 
    +				'menu_title' => 'Taxonomy Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_taxonomy', 
    +				'callback' => array( $this->callbacks, 'adminTaxonomy' )
    +			)
    +		);
    +	}
    +
    +	public function setSettings()
    +	{
    +		$args = array(
    +			array(
    +				'option_group' => 'alecaddd_plugin_tax_settings',
    +				'option_name' => 'alecaddd_plugin_tax',
    +				'callback' => array($this->tax_callbacks, 'taxSanitize')
    +			)
    +		);
    +
    +		$this->settings->setSettings( $args );
    +	}
    +
    +	public function setSections()
    +	{
    +		$args = array(
    +			array(
    +				'id' => 'alecaddd_tax_index',
    +				'title' => 'Custom Taxonomy Manager',
    +				'callback' => array($this->tax_callbacks, 'taxSectionManager'),
    +				'page' => 'alecaddd_taxonomy'
    +			)
    +		);
    +
    +		$this->settings->setSections( $args );
    +	}
    +
    +	public function setFields()
    +	{
    +		$args = array(
    +			array(
    +				'id' => 'taxonomy',
    +				'title' => 'Custom Taxonomy ID',
    +				'callback' => array($this->tax_callbacks, 'textField'),
    +				'page' => 'alecaddd_taxonomy',
    +				'section' => 'alecaddd_tax_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_tax',
    +					'label_for' => 'taxonomy',
    +					'placeholder' => 'eg. genre',
    +					'array' => 'taxonomy'
    +				)
    +			),
    +			array(
    +				'id' => 'singular_name',
    +				'title' => 'Singular Name',
    +				'callback' => array( $this->tax_callbacks, 'textField' ),
    +				'page' => 'alecaddd_taxonomy',
    +				'section' => 'alecaddd_tax_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_tax',
    +					'label_for' => 'singular_name',
    +					'placeholder' => 'eg. Genre',
    +					'array' => 'taxonomy'
    +				)
    +			),
    +			array(
    +				'id' => 'hierarchical',
    +				'title' => 'Hierarchical',
    +				'callback' => array( $this->tax_callbacks, 'checkboxField' ),
    +				'page' => 'alecaddd_taxonomy',
    +				'section' => 'alecaddd_tax_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_tax',
    +					'label_for' => 'hierarchical',
    +					'class' => 'ui-toggle',
    +					'array' => 'taxonomy'
    +				)
    +			)
    +		);
    +
    +		$this->settings->setFields( $args );
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/Deactivate.php b/Lesson33/inc/Base/Deactivate.php
    new file mode 100644
    index 0000000..c13c98d
    --- /dev/null
    +++ b/Lesson33/inc/Base/Deactivate.php
    @@ -0,0 +1,12 @@
    +<?php
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +class Deactivate
    +{
    +	public static function deactivate() {
    +		flush_rewrite_rules();
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/Enqueue.php b/Lesson33/inc/Base/Enqueue.php
    new file mode 100644
    index 0000000..fa7dd94
    --- /dev/null
    +++ b/Lesson33/inc/Base/Enqueue.php
    @@ -0,0 +1,23 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Base\BaseController;
    +
    +/**
    +* 
    +*/
    +class Enqueue extends BaseController
    +{
    +	public function register() {
    +		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
    +	}
    +	
    +	function enqueue() {
    +		// enqueue all our scripts
    +		wp_enqueue_style( 'mypluginstyle', $this->plugin_url . 'assets/mystyle.css' );
    +		wp_enqueue_script( 'mypluginscript', $this->plugin_url . 'assets/myscript.js' );
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/GalleryController.php b/Lesson33/inc/Base/GalleryController.php
    new file mode 100644
    index 0000000..fe161b4
    --- /dev/null
    +++ b/Lesson33/inc/Base/GalleryController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class GalleryController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'gallery_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Gallery Manager', 
    +				'menu_title' => 'Gallery Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_gallery', 
    +				'callback' => array( $this->callbacks, 'adminGallery' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/MembershipController.php b/Lesson33/inc/Base/MembershipController.php
    new file mode 100644
    index 0000000..2d8300c
    --- /dev/null
    +++ b/Lesson33/inc/Base/MembershipController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class MembershipController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'membership_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Membership Manager', 
    +				'menu_title' => 'Membership Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_membership', 
    +				'callback' => array( $this->callbacks, 'adminMembership' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/SettingsLinks.php b/Lesson33/inc/Base/SettingsLinks.php
    new file mode 100644
    index 0000000..78ce823
    --- /dev/null
    +++ b/Lesson33/inc/Base/SettingsLinks.php
    @@ -0,0 +1,22 @@
    +<?php
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Base\BaseController;
    +
    +class SettingsLinks extends BaseController
    +{
    +	public function register() 
    +	{
    +		add_filter( "plugin_action_links_$this->plugin", array( $this, 'settings_link' ) );
    +	}
    +
    +	public function settings_link( $links ) 
    +	{
    +		$settings_link = '<a href="admin.php?page=alecaddd_plugin">Settings</a>';
    +		array_push( $links, $settings_link );
    +		return $links;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/TemplateController.php b/Lesson33/inc/Base/TemplateController.php
    new file mode 100644
    index 0000000..dd63eeb
    --- /dev/null
    +++ b/Lesson33/inc/Base/TemplateController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class TemplateController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'templates_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Templates Manager', 
    +				'menu_title' => 'Templates Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_templates', 
    +				'callback' => array( $this->callbacks, 'adminTemplates' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/TestimonialController.php b/Lesson33/inc/Base/TestimonialController.php
    new file mode 100644
    index 0000000..4ec0207
    --- /dev/null
    +++ b/Lesson33/inc/Base/TestimonialController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class TestimonialController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'testimonial_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Testimonial Manager', 
    +				'menu_title' => 'Testimonial Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_testimonial', 
    +				'callback' => array( $this->callbacks, 'adminTestimonial' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Base/WidgetController.php b/Lesson33/inc/Base/WidgetController.php
    new file mode 100644
    index 0000000..77b4000
    --- /dev/null
    +++ b/Lesson33/inc/Base/WidgetController.php
    @@ -0,0 +1,46 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +
    +/**
    +* 
    +*/
    +class WidgetController extends BaseController
    +{
    +	public $callbacks;
    +
    +	public $subpages = array();
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'chat_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->setSubpages();
    +
    +		$this->settings->addSubPages( $this->subpages )->register();
    +	}
    +
    +	public function setSubpages()
    +	{
    +		$this->subpages = array(
    +			array(
    +				'parent_slug' => 'alecaddd_plugin', 
    +				'page_title' => 'Chat Manager', 
    +				'menu_title' => 'Chat Manager', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_chat', 
    +				'callback' => array( $this->callbacks, 'adminChat' )
    +			)
    +		);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Init.php b/Lesson33/inc/Init.php
    new file mode 100644
    index 0000000..f3f5070
    --- /dev/null
    +++ b/Lesson33/inc/Init.php
    @@ -0,0 +1,57 @@
    +<?php
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc;
    +
    +final class Init
    +{
    +	/**
    +	 * Store all the classes inside an array
    +	 * @return array Full list of classes
    +	 */
    +	public static function get_services() 
    +	{
    +		return [
    +			Pages\Dashboard::class,
    +			Base\Enqueue::class,
    +			Base\SettingsLinks::class,
    +			Base\CustomPostTypeController::class,
    +			Base\CustomTaxonomyController::class,
    +			Base\WidgetController::class,
    +			Base\GalleryController::class,
    +			Base\TestimonialController::class,
    +			Base\TemplateController::class,
    +			Base\AuthController::class,
    +			Base\MembershipController::class,
    +			Base\ChatController::class,
    +		];
    +	}
    +
    +	/**
    +	 * Loop through the classes, initialize them, 
    +	 * and call the register() method if it exists
    +	 * @return
    +	 */
    +	public static function register_services() 
    +	{
    +		foreach ( self::get_services() as $class ) {
    +			$service = self::instantiate( $class );
    +			if ( method_exists( $service, 'register' ) ) {
    +				$service->register();
    +			}
    +		}
    +	}
    +
    +	/**
    +	 * Initialize the class
    +	 * @param  class $class    class from the services array
    +	 * @return class instance  new instance of the class
    +	 */
    +	private static function instantiate( $class )
    +	{
    +		$service = new $class();
    +
    +		return $service;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/inc/Pages/Dashboard.php b/Lesson33/inc/Pages/Dashboard.php
    new file mode 100644
    index 0000000..0eba57c
    --- /dev/null
    +++ b/Lesson33/inc/Pages/Dashboard.php
    @@ -0,0 +1,102 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Pages;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\AdminCallbacks;
    +use Inc\Api\Callbacks\ManagerCallbacks;
    +
    +class Dashboard extends BaseController
    +{
    +	public $settings;
    +
    +	public $callbacks;
    +
    +	public $callbacks_mngr;
    +
    +	public $pages = array();
    +
    +	public function register() 
    +	{
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new AdminCallbacks();
    +
    +		$this->callbacks_mngr = new ManagerCallbacks();
    +
    +		$this->setPages();
    +
    +		$this->setSettings();
    +		$this->setSections();
    +		$this->setFields();
    +
    +		$this->settings->addPages( $this->pages )->withSubPage( 'Dashboard' )->register();
    +	}
    +
    +	public function setPages() 
    +	{
    +		$this->pages = array(
    +			array(
    +				'page_title' => 'Alecaddd Plugin', 
    +				'menu_title' => 'Alecaddd', 
    +				'capability' => 'manage_options', 
    +				'menu_slug' => 'alecaddd_plugin', 
    +				'callback' => array( $this->callbacks, 'adminDashboard' ), 
    +				'icon_url' => 'dashicons-store', 
    +				'position' => 110
    +			)
    +		);
    +	}
    +
    +	public function setSettings()
    +	{
    +		$args = array(
    +			array(
    +				'option_group' => 'alecaddd_plugin_settings',
    +				'option_name' => 'alecaddd_plugin',
    +				'callback' => array( $this->callbacks_mngr, 'checkboxSanitize' )
    +			)
    +		);
    +
    +		$this->settings->setSettings( $args );
    +	}
    +
    +	public function setSections()
    +	{
    +		$args = array(
    +			array(
    +				'id' => 'alecaddd_admin_index',
    +				'title' => 'Settings Manager',
    +				'callback' => array( $this->callbacks_mngr, 'adminSectionManager' ),
    +				'page' => 'alecaddd_plugin'
    +			)
    +		);
    +
    +		$this->settings->setSections( $args );
    +	}
    +
    +	public function setFields()
    +	{
    +		$args = array();
    +
    +		foreach ( $this->managers as $key => $value ) {
    +			$args[] = array(
    +				'id' => $key,
    +				'title' => $value,
    +				'callback' => array( $this->callbacks_mngr, 'checkboxField' ),
    +				'page' => 'alecaddd_plugin',
    +				'section' => 'alecaddd_admin_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin',
    +					'label_for' => $key,
    +					'class' => 'ui-toggle'
    +				)
    +			);
    +		}
    +
    +		$this->settings->setFields( $args );
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/index.php b/Lesson33/index.php
    new file mode 100644
    index 0000000..7e91415
    --- /dev/null
    +++ b/Lesson33/index.php
    @@ -0,0 +1,2 @@
    +<?php
    +// Silence is golden.
    \ No newline at end of file
    diff --git a/Lesson33/package.json b/Lesson33/package.json
    new file mode 100644
    index 0000000..6daf8b3
    --- /dev/null
    +++ b/Lesson33/package.json
    @@ -0,0 +1,62 @@
    +{
    +	"name": "alecaddd-plugin",
    +	"version": "1.0.0",
    +	"description": "Awesome starter plugin example",
    +	"author": "Alessandro Castellani <me@alecaddd.com> (http://alecaddd.com)",
    +	"repository": {
    +		"type": "git",
    +		"url": "https://github.com/Alecaddd/WordPressPlugin101"
    +	},
    +	"keywords": [
    +		"wordpress",
    +		"plugin",
    +		"PHP",
    +		"composer",
    +		"gulp",
    +		"es6",
    +		"scss"
    +	],
    +	"devDependencies": {
    +		"babel-core": "^6.26.0",
    +		"babel-preset-env": "^1.6.1",
    +		"babelify": "^8.0.0",
    +		"browser-sync": "^2.18.13",
    +		"browserify": "^14.5.0",
    +		"browserify-shim": "^3.8.14",
    +		"gulp": "^3.9.1",
    +		"gulp-autoprefixer": "^4.0.0",
    +		"gulp-concat": "^2.5.2",
    +		"gulp-if": "^2.0.2",
    +		"gulp-notify": "^3.0.0",
    +		"gulp-options": "^1.1.1",
    +		"gulp-plumber": "^1.1.0",
    +		"gulp-rename": "^1.2.0",
    +		"gulp-sass": "^3.1.0",
    +		"gulp-sourcemaps": "^2.6.1",
    +		"gulp-strip-debug": "^1.1.0",
    +		"gulp-uglify": "^3.0.0",
    +		"gulp-uglifycss": "^1.0.9",
    +		"vinyl-buffer": "^1.0.0",
    +		"vinyl-source-stream": "^1.1.0"
    +	},
    +	"babel": {
    +		"presets": [
    +			"env"
    +		]
    +	},
    +	"browserify": {
    +		"transform": [
    +			"browserify-shim"
    +		]
    +	},
    +	"browser": {
    +		"jquery": "./node_modules/jquery/dist/jquery.js"
    +	},
    +	"browserify-shim": {
    +		"jquery": "$"
    +	},
    +	"license": "GPL-3.0",
    +	"dependencies": {
    +		"code-prettify": "^0.1.0"
    +	}
    +}
    diff --git a/Lesson33/readme.md b/Lesson33/readme.md
    new file mode 100644
    index 0000000..2d10c74
    --- /dev/null
    +++ b/Lesson33/readme.md
    @@ -0,0 +1,14 @@
    +# Plugin 101 Series
    +
    +Full list of sections and features we will build during the series of Tutorials
    +
    +* Modular Administration Area
    +* CPT Manager
    +* Custom Taxonomy Manager
    +* Widget to Upload and Display media in sidebars
    +* Post and Pages Gallery integration
    +* Testimonial section: Comment in the front-end, Admins can approve comments, select which comments to display
    +* Custom template section
    +* Ajax based Login/Register system
    +* Membership protected area
    +* Chat system
    \ No newline at end of file
    diff --git a/Lesson33/src/js/myscript.js b/Lesson33/src/js/myscript.js
    new file mode 100644
    index 0000000..05bb01b
    --- /dev/null
    +++ b/Lesson33/src/js/myscript.js
    @@ -0,0 +1,29 @@
    +import 'code-prettify';
    +
    +window.addEventListener("load", function() {
    +
    +	PR.prettyPrint();
    +
    +	// store tabs variables
    +	var tabs = document.querySelectorAll("ul.nav-tabs > li");
    +
    +	for (var i = 0; i < tabs.length; i++) {
    +		tabs[i].addEventListener("click", switchTab);
    +	}
    +
    +	function switchTab(event) {
    +		event.preventDefault();
    +
    +		document.querySelector("ul.nav-tabs li.active").classList.remove("active");
    +		document.querySelector(".tab-pane.active").classList.remove("active");
    +
    +		var clickedTab = event.currentTarget;
    +		var anchor = event.target;
    +		var activePaneID = anchor.getAttribute("href");
    +
    +		clickedTab.classList.add("active");
    +		document.querySelector(activePaneID).classList.add("active");
    +
    +	}
    +
    +});
    \ No newline at end of file
    diff --git a/Lesson33/src/scss/modules/checkbox.scss b/Lesson33/src/scss/modules/checkbox.scss
    new file mode 100644
    index 0000000..2bb94e1
    --- /dev/null
    +++ b/Lesson33/src/scss/modules/checkbox.scss
    @@ -0,0 +1,70 @@
    +$on: #009eea;
    +$bg: #D9CB9E;
    +$off: #8c8c8c;
    +
    +@mixin center {
    +	position: absolute;
    +	top: 50%;
    +	left: 50%;
    +	transform: translate(-50%, -50%);
    +}
    +
    +@mixin userSelect($value) {
    +	-webkit-touch-callout: $value;
    +	-webkit-user-select: $value;
    +	-khtml-user-select: $value;
    +	-moz-user-select: $value;
    +	-ms-user-select: $value;
    +	user-select: $value;
    +}
    +
    +@mixin ui-toggle($height, $on, $off) {
    +	margin: 0;
    +	padding: 0;
    +
    +	input[type='checkbox'] {
    +		display: none;
    +
    +		&:checked + label {
    +			border-color: $on;
    +			background: $on;
    +			box-shadow: inset 0 0 0 #{$height / 2} $on;
    +
    +			> div {
    +				margin-left: $height;
    +			}
    +		}
    +	}
    +
    +	label {
    +		transition: all 200ms ease;
    +		display: inline-block;
    +		position: relative;
    +
    +		@include userSelect(none);
    +
    +		background: $off;
    +		box-shadow: inset 0 0 0 0 $on;
    +		border: 2px solid $off;
    +		border-radius: $height + 2;
    +		width: $height * 2;
    +		height: $height;
    +
    +		div {
    +			transition: all 200ms ease;
    +			background: #FFFFFF;
    +			width: $height;
    +			height: $height;
    +			border-radius: $height / 2;
    +		}
    +
    +		&:hover,
    +		& > div:hover {
    +			cursor: pointer;
    +		}
    +	}
    +}
    +
    +div.ui-toggle {
    +	@include ui-toggle(20px, $on, $off);
    +}
    \ No newline at end of file
    diff --git a/Lesson33/src/scss/modules/form.scss b/Lesson33/src/scss/modules/form.scss
    new file mode 100644
    index 0000000..847c2ba
    --- /dev/null
    +++ b/Lesson33/src/scss/modules/form.scss
    @@ -0,0 +1,3 @@
    +.inline-block {
    +	display: inline-block;
    +}
    \ No newline at end of file
    diff --git a/Lesson33/src/scss/modules/table.scss b/Lesson33/src/scss/modules/table.scss
    new file mode 100644
    index 0000000..762d474
    --- /dev/null
    +++ b/Lesson33/src/scss/modules/table.scss
    @@ -0,0 +1,21 @@
    +.cpt-table {
    +	width: 100%;
    +	border-spacing: 5px;
    +	text-align: left;
    +
    +	&,
    +	& th,
    +	& td {
    +		border: 1px solid #ccc;
    +		border-collapse: collapse;
    +		padding: 10px;
    +	}
    +
    +	& th {
    +		background-color: #f5f5f5;
    +	}
    +}
    +
    +.text-center {
    +	text-align: center;
    +}
    \ No newline at end of file
    diff --git a/Lesson33/src/scss/modules/tabs.scss b/Lesson33/src/scss/modules/tabs.scss
    new file mode 100644
    index 0000000..f2e3131
    --- /dev/null
    +++ b/Lesson33/src/scss/modules/tabs.scss
    @@ -0,0 +1,50 @@
    +.nav-tabs {
    +	float: left;
    +	width: 100%;
    +	margin: 0;
    +	list-style-type: none;
    +	border-bottom: 1px solid transparent;
    +
    +	> li {
    +		float: left;
    +		margin-bottom: -1px;
    +
    +		> a {
    +			margin-right: 2px;
    +			line-height: 1.5;
    +			padding: 10px;
    +			border: 1px solid transparent;
    +			border-radius: 4px 4px 0 0;
    +			float: left;
    +			text-decoration: none;
    +
    +			&:hover {
    +				border-color: #eee #eee #ddd;
    +			}
    +		}
    +
    +		&.active > a {
    +			&,
    +			&:hover,
    +			&:focus {
    +				color: #555;
    +				cursor: default;
    +				background-color: #fff;
    +				border-color: transparent;
    +			}
    +		}
    +	}
    +}
    +
    +.tab-content > .tab-pane {
    +	float: left;
    +	width: 98%;
    +	display: none;
    +
    +	&.active {
    +		display: block;
    +		padding: 10px;
    +		background-color: #fff;
    +		box-shadow: 0 5px 4px -2px rgba(0, 0, 0, 0.15);
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson33/src/scss/mystyle.scss b/Lesson33/src/scss/mystyle.scss
    new file mode 100644
    index 0000000..9de4e0b
    --- /dev/null
    +++ b/Lesson33/src/scss/mystyle.scss
    @@ -0,0 +1,6 @@
    +@import './../node_modules/code-prettify/styles/desert.css';
    +
    +@import 'modules/tabs';
    +@import 'modules/checkbox';
    +@import 'modules/table';
    +@import 'modules/form';
    \ No newline at end of file
    diff --git a/Lesson33/templates/admin.php b/Lesson33/templates/admin.php
    new file mode 100644
    index 0000000..da6529a
    --- /dev/null
    +++ b/Lesson33/templates/admin.php
    @@ -0,0 +1,32 @@
    +<div class="wrap">
    +	<h1>Alecaddd Plugin</h1>
    +	<?php settings_errors(); ?>
    +
    +	<ul class="nav nav-tabs">
    +		<li class="active"><a href="#tab-1">Manage Settings</a></li>
    +		<li><a href="#tab-2">Updates</a></li>
    +		<li><a href="#tab-3">About</a></li>
    +	</ul>
    +
    +	<div class="tab-content">
    +		<div id="tab-1" class="tab-pane active">
    +
    +			<form method="post" action="options.php">
    +				<?php 
    +					settings_fields( 'alecaddd_plugin_settings' );
    +					do_settings_sections( 'alecaddd_plugin' );
    +					submit_button();
    +				?>
    +			</form>
    +			
    +		</div>
    +
    +		<div id="tab-2" class="tab-pane">
    +			<h3>Updates</h3>
    +		</div>
    +
    +		<div id="tab-3" class="tab-pane">
    +			<h3>About</h3>
    +		</div>
    +	</div>
    +</div>
    \ No newline at end of file
    diff --git a/Lesson33/templates/cpt.php b/Lesson33/templates/cpt.php
    new file mode 100644
    index 0000000..7b7c7a0
    --- /dev/null
    +++ b/Lesson33/templates/cpt.php
    @@ -0,0 +1,130 @@
    +<div class="wrap">
    +	<h1>CPT Manager</h1>
    +	<?php settings_errors(); ?>
    +
    +	<ul class="nav nav-tabs">
    +		<li class="<?php echo !isset($_POST["edit_post"]) ? 'active' : '' ?>"><a href="#tab-1">Your Custom Post Types</a></li>
    +		<li class="<?php echo isset($_POST["edit_post"]) ? 'active' : '' ?>">
    +			<a href="#tab-2">
    +				<?php echo isset($_POST["edit_post"]) ? 'Edit' : 'Add' ?> Custom Post Type
    +			</a>
    +		</li>
    +		<li><a href="#tab-3">Export</a></li>
    +	</ul>
    +
    +	<div class="tab-content">
    +		<div id="tab-1" class="tab-pane <?php echo !isset($_POST["edit_post"]) ? 'active' : '' ?>">
    +
    +			<h3>Manage Your Custom Post Types</h3>
    +
    +			<?php 
    +				$options = get_option( 'alecaddd_plugin_cpt' ) ?: array();
    +
    +				echo '<table class="cpt-table"><tr><th>ID</th><th>Singular Name</th><th>Plural Name</th><th class="text-center">Public</th><th class="text-center">Archive</th><th class="text-center">Actions</th></tr>';
    +
    +				foreach ($options as $option) {
    +					$public = isset($option['public']) ? "TRUE" : "FALSE";
    +					$archive = isset($option['has_archive']) ? "TRUE" : "FALSE";
    +
    +					echo "<tr><td>{$option['post_type']}</td><td>{$option['singular_name']}</td><td>{$option['plural_name']}</td><td class=\"text-center\">{$public}</td><td class=\"text-center\">{$archive}</td><td class=\"text-center\">";
    +
    +					echo '<form method="post" action="" class="inline-block">';
    +					echo '<input type="hidden" name="edit_post" value="' . $option['post_type'] . '">';
    +					submit_button( 'Edit', 'primary small', 'submit', false);
    +					echo '</form> ';
    +
    +					echo '<form method="post" action="options.php" class="inline-block">';
    +					settings_fields( 'alecaddd_plugin_cpt_settings' );
    +					echo '<input type="hidden" name="remove" value="' . $option['post_type'] . '">';
    +					submit_button( 'Delete', 'delete small', 'submit', false, array(
    +						'onclick' => 'return confirm("Are you sure you want to delete this Custom Post Type? The data associated with it will not be deleted.");'
    +					));
    +					echo '</form></td></tr>';
    +				}
    +
    +				echo '</table>';
    +			?>
    +			
    +		</div>
    +
    +		<div id="tab-2" class="tab-pane <?php echo isset($_POST["edit_post"]) ? 'active' : '' ?>">
    +			<form method="post" action="options.php">
    +				<?php 
    +					settings_fields( 'alecaddd_plugin_cpt_settings' );
    +					do_settings_sections( 'alecaddd_cpt' );
    +					submit_button();
    +				?>
    +			</form>
    +		</div>
    +
    +		<div id="tab-3" class="tab-pane">
    +			<h3>Export Your Custom Post Types</h3>
    +
    +			<?php foreach ($options as $option) { ?>
    +
    +				<h3><?php echo $option['singular_name']; ?></h3>
    +
    +			<pre class="prettyprint">
    +// Register Custom Post Type
    +function custom_post_type() {
    +
    +	$labels = array(
    +		'name'                  => _x( 'Post Types', 'Post Type General Name', 'text_domain' ),
    +		'singular_name'         => _x( '<?php echo $option['singular_name']; ?>', 'Post Type Singular Name', 'text_domain' ),
    +		'menu_name'             => __( '<?php echo $option['plural_name']; ?>', 'text_domain' ),
    +		'plural_name'             => __( '<?php echo $option['plural_name']; ?>', 'text_domain' ),
    +		'name_admin_bar'        => __( 'Post Type', 'text_domain' ),
    +		'archives'              => __( 'Item Archives', 'text_domain' ),
    +		'attributes'            => __( 'Item Attributes', 'text_domain' ),
    +		'parent_item_colon'     => __( 'Parent Item:', 'text_domain' ),
    +		'all_items'             => __( 'All Items', 'text_domain' ),
    +		'add_new_item'          => __( 'Add New Item', 'text_domain' ),
    +		'add_new'               => __( 'Add New', 'text_domain' ),
    +		'new_item'              => __( 'New Item', 'text_domain' ),
    +		'edit_item'             => __( 'Edit Item', 'text_domain' ),
    +		'update_item'           => __( 'Update Item', 'text_domain' ),
    +		'view_item'             => __( 'View Item', 'text_domain' ),
    +		'view_items'            => __( 'View Items', 'text_domain' ),
    +		'search_items'          => __( 'Search Item', 'text_domain' ),
    +		'not_found'             => __( 'Not found', 'text_domain' ),
    +		'not_found_in_trash'    => __( 'Not found in Trash', 'text_domain' ),
    +		'featured_image'        => __( 'Featured Image', 'text_domain' ),
    +		'set_featured_image'    => __( 'Set featured image', 'text_domain' ),
    +		'remove_featured_image' => __( 'Remove featured image', 'text_domain' ),
    +		'use_featured_image'    => __( 'Use as featured image', 'text_domain' ),
    +		'insert_into_item'      => __( 'Insert into item', 'text_domain' ),
    +		'uploaded_to_this_item' => __( 'Uploaded to this item', 'text_domain' ),
    +		'items_list'            => __( 'Items list', 'text_domain' ),
    +		'items_list_navigation' => __( 'Items list navigation', 'text_domain' ),
    +		'filter_items_list'     => __( 'Filter items list', 'text_domain' ),
    +	);
    +	$args = array(
    +		'label'                 => __( 'Post Type', 'text_domain' ),
    +		'description'           => __( 'Post Type Description', 'text_domain' ),
    +		'labels'                => $labels,
    +		'supports'              => false,
    +		'taxonomies'            => array( 'category', 'post_tag' ),
    +		'hierarchical'          => false,
    +		'public'                => <?php echo isset($option['public']) ? "true" : "false"; ?>,
    +		'show_ui'               => true,
    +		'show_in_menu'          => true,
    +		'menu_position'         => 5,
    +		'show_in_admin_bar'     => true,
    +		'show_in_nav_menus'     => true,
    +		'can_export'            => true,
    +		'has_archive'           => <?php echo isset($option['has_archive']) ? "true" : "false"; ?>,
    +		'exclude_from_search'   => false,
    +		'publicly_queryable'    => true,
    +		'capability_type'       => 'page',
    +	);
    +	register_post_type( '<?php echo $option['post_type']; ?>', $args );
    +
    +}
    +add_action( 'init', 'custom_post_type', 0 );
    +			</pre>
    +
    +			<?php } ?>
    +
    +		</div>
    +	</div>
    +</div>
    \ No newline at end of file
    diff --git a/Lesson33/templates/taxonomy.php b/Lesson33/templates/taxonomy.php
    new file mode 100644
    index 0000000..dc4ad21
    --- /dev/null
    +++ b/Lesson33/templates/taxonomy.php
    @@ -0,0 +1,65 @@
    +<div class="wrap">
    +	<h1>Taxonomy Manager</h1>
    +	<?php settings_errors(); ?>
    +
    +	<ul class="nav nav-tabs">
    +		<li class="<?php echo !isset($_POST["edit_taxonomy"]) ? 'active' : '' ?>"><a href="#tab-1">Your Taxonomies</a></li>
    +		<li class="<?php echo isset($_POST["edit_taxonomy"]) ? 'active' : '' ?>">
    +			<a href="#tab-2">
    +				<?php echo isset($_POST["edit_taxonomy"]) ? 'Edit' : 'Add' ?> Taxonomy
    +			</a>
    +		</li>
    +		<li><a href="#tab-3">Export</a></li>
    +	</ul>
    +
    +	<div class="tab-content">
    +		<div id="tab-1" class="tab-pane <?php echo !isset($_POST["edit_taxonomy"]) ? 'active' : '' ?>">
    +
    +			<h3>Manage Your Custom Taxonomies</h3>
    +
    +			<?php 
    +				// $options = get_option( 'alecaddd_plugin_cpt' ) ?: array();
    +
    +				// echo '<table class="cpt-table"><tr><th>ID</th><th>Singular Name</th><th>Plural Name</th><th class="text-center">Public</th><th class="text-center">Archive</th><th class="text-center">Actions</th></tr>';
    +
    +				// foreach ($options as $option) {
    +				// 	$public = isset($option['public']) ? "TRUE" : "FALSE";
    +				// 	$archive = isset($option['has_archive']) ? "TRUE" : "FALSE";
    +
    +				// 	echo "<tr><td>{$option['post_type']}</td><td>{$option['singular_name']}</td><td>{$option['plural_name']}</td><td class=\"text-center\">{$public}</td><td class=\"text-center\">{$archive}</td><td class=\"text-center\">";
    +
    +				// 	echo '<form method="post" action="" class="inline-block">';
    +				// 	echo '<input type="hidden" name="edit_post" value="' . $option['post_type'] . '">';
    +				// 	submit_button( 'Edit', 'primary small', 'submit', false);
    +				// 	echo '</form> ';
    +
    +				// 	echo '<form method="post" action="options.php" class="inline-block">';
    +				// 	settings_fields( 'alecaddd_plugin_cpt_settings' );
    +				// 	echo '<input type="hidden" name="remove" value="' . $option['post_type'] . '">';
    +				// 	submit_button( 'Delete', 'delete small', 'submit', false, array(
    +				// 		'onclick' => 'return confirm("Are you sure you want to delete this Custom Post Type? The data associated with it will not be deleted.");'
    +				// 	));
    +				// 	echo '</form></td></tr>';
    +				// }
    +
    +				// echo '</table>';
    +			?>
    +			
    +		</div>
    +
    +		<div id="tab-2" class="tab-pane <?php echo isset($_POST["edit_taxonomy"]) ? 'active' : '' ?>">
    +			<form method="post" action="options.php">
    +				<?php 
    +					settings_fields( 'alecaddd_plugin_tax_settings' );
    +					do_settings_sections( 'alecaddd_taxonomy' );
    +					submit_button();
    +				?>
    +			</form>
    +		</div>
    +
    +		<div id="tab-3" class="tab-pane">
    +			<h3>Export Your Taxonomies</h3>
    +
    +		</div>
    +	</div>
    +</div>
    \ No newline at end of file
    diff --git a/Lesson33/templates/widget.php b/Lesson33/templates/widget.php
    new file mode 100644
    index 0000000..006722d
    --- /dev/null
    +++ b/Lesson33/templates/widget.php
    @@ -0,0 +1 @@
    +<h1>Widgets Manager</h1>
    \ No newline at end of file
    diff --git a/Lesson33/uninstall.php b/Lesson33/uninstall.php
    new file mode 100644
    index 0000000..69fb64b
    --- /dev/null
    +++ b/Lesson33/uninstall.php
    @@ -0,0 +1,24 @@
    +<?php
    +
    +/**
    + * Trigger this file on Plugin uninstall
    + *
    + * @package  AlecadddPlugin
    + */
    +
    +if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    +	die;
    +}
    +
    +// Clear Database stored data
    +$books = get_posts( array( 'post_type' => 'book', 'numberposts' => -1 ) );
    +
    +foreach( $books as $book ) {
    +	wp_delete_post( $book->ID, true );
    +}
    +
    +// Access the database via SQL
    +global $wpdb;
    +$wpdb->query( "DELETE FROM wp_posts WHERE post_type = 'book'" );
    +$wpdb->query( "DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT id FROM wp_posts)" );
    +$wpdb->query( "DELETE FROM wp_term_relationships WHERE object_id NOT IN (SELECT id FROM wp_posts)" );
    \ No newline at end of file