diff --git a/Lesson58/alecaddd-plugin.php b/Lesson58/alecaddd-plugin.php new file mode 100644 index 0000000..9f08e9d --- /dev/null +++ b/Lesson58/alecaddd-plugin.php @@ -0,0 +1,63 @@ +()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(String(r.email).toLowerCase()))if(r.message){var t=a.dataset.url,s=new URLSearchParams(new FormData(a));a.querySelector(".js-form-submission").classList.add("show"),fetch(t,{method:"POST",body:s}).then(function(e){return e.json()}).catch(function(e){o(),a.querySelector(".js-form-error").classList.add("show")}).then(function(e){o(),0!==e&&"error"!==e.status?(a.querySelector(".js-form-success").classList.add("show"),a.reset()):a.querySelector(".js-form-error").classList.add("show")})}else a.querySelector('[data-error="invalidMessage"]').classList.add("show");else a.querySelector('[data-error="invalidEmail"]').classList.add("show");else a.querySelector('[data-error="invalidName"]').classList.add("show")})})},{}]},{},[1]); +//# sourceMappingURL=form.js.map diff --git a/Lesson58/assets/form.js.map b/Lesson58/assets/form.js.map new file mode 100644 index 0000000..e8b2614 --- /dev/null +++ b/Lesson58/assets/form.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["form.js"],"names":["r","e","n","t","o","i","f","c","require","u","a","Error","code","p","exports","call","length","1","module","resetMessages","document","querySelectorAll","forEach","classList","remove","addEventListener","testimonialForm","getElementById","preventDefault","data","name","querySelector","value","email","message","nonce","test","String","toLowerCase","url","dataset","params","URLSearchParams","FormData","add","fetch","method","body","then","res","json","catch","error","response","status","reset"],"mappings":"CAAY,SAASA,EAAEC,EAAEC,EAAEC,GAAG,SAASC,EAAEC,EAAEC,GAAG,IAAIJ,EAAEG,GAAG,CAAC,IAAIJ,EAAEI,GAAG,CAAC,IAAIE,EAAE,mBAAmBC,SAASA,QAAQ,IAAIF,GAAGC,EAAE,OAAOA,EAAEF,GAAE,GAAI,GAAGI,EAAE,OAAOA,EAAEJ,GAAE,GAAI,IAAIK,EAAE,IAAIC,MAAM,uBAAuBN,EAAE,KAAK,MAAMK,EAAEE,KAAK,mBAAmBF,EAAE,IAAIG,EAAEX,EAAEG,GAAG,CAACS,QAAQ,IAAIb,EAAEI,GAAG,GAAGU,KAAKF,EAAEC,QAAQ,SAASd,GAAoB,OAAOI,EAAlBH,EAAEI,GAAG,GAAGL,IAAeA,IAAIa,EAAEA,EAAEC,QAAQd,EAAEC,EAAEC,EAAEC,GAAG,OAAOD,EAAEG,GAAGS,QAAQ,IAAI,IAAIL,EAAE,mBAAmBD,SAASA,QAAQH,EAAE,EAAEA,EAAEF,EAAEa,OAAOX,IAAID,EAAED,EAAEE,IAAI,OAAOD,EAA7b,CAA4c,CAACa,EAAE,CAAC,SAAST,EAAQU,EAAOJ,GACxe,aA0DA,SAASK,IACPC,SAASC,iBAAiB,cAAcC,QAAQ,SAAUhB,GACxD,OAAOA,EAAEiB,UAAUC,OAAO,UA1D9BJ,SAASK,iBAAiB,mBAAoB,SAAUxB,GACtD,IAAIyB,EAAkBN,SAASO,eAAe,6BAC9CD,EAAgBD,iBAAiB,SAAU,SAAUxB,GACnDA,EAAE2B,iBAEFT,IAEA,IAAIU,EAAO,CACTC,KAAMJ,EAAgBK,cAAc,iBAAiBC,MACrDC,MAAOP,EAAgBK,cAAc,kBAAkBC,MACvDE,QAASR,EAAgBK,cAAc,oBAAoBC,MAC3DG,MAAOT,EAAgBK,cAAc,kBAAkBC,OAIzD,GAAKH,EAAKC,KAKV,GA2CO,0JACCM,KAAKC,OA5CMR,EAAKI,OA4CGK,eAvC3B,GAAKT,EAAKK,QAAV,CAMA,IAAIK,EAAMb,EAAgBc,QAAQD,IAC9BE,EAAS,IAAIC,gBAAgB,IAAIC,SAASjB,IAC9CA,EAAgBK,cAAc,uBAAuBR,UAAUqB,IAAI,QACnEC,MAAMN,EAAK,CACTO,OAAQ,OACRC,KAAMN,IACLO,KAAK,SAAUC,GAChB,OAAOA,EAAIC,SACVC,MAAM,SAAUC,GACjBjC,IACAO,EAAgBK,cAAc,kBAAkBR,UAAUqB,IAAI,UAC7DI,KAAK,SAAUK,GAChBlC,IAEiB,IAAbkC,GAAsC,UAApBA,EAASC,QAK/B5B,EAAgBK,cAAc,oBAAoBR,UAAUqB,IAAI,QAChElB,EAAgB6B,SALd7B,EAAgBK,cAAc,kBAAkBR,UAAUqB,IAAI,eApBhElB,EAAgBK,cAAc,iCAAiCR,UAAUqB,IAAI,aAL7ElB,EAAgBK,cAAc,+BAA+BR,UAAUqB,IAAI,aAL3ElB,EAAgBK,cAAc,8BAA8BR,UAAUqB,IAAI,aAmD9E,KAAK,GAAG,CAAC","file":"form.js","sourcesContent":["(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error(\"Cannot find module '\"+i+\"'\");throw a.code=\"MODULE_NOT_FOUND\",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=\"function\"==typeof require&&require,i=0;i()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n return re.test(String(email).toLowerCase());\n}\n\n},{}]},{},[1]);\n"]} \ No newline at end of file diff --git a/Lesson58/assets/myscript.js b/Lesson58/assets/myscript.js new file mode 100644 index 0000000..04e9478 --- /dev/null +++ b/Lesson58/assets/myscript.js @@ -0,0 +1,2 @@ +!function s(i,l,o){function u(t,e){if(!l[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);var r=new Error("Cannot find module '"+t+"'");throw r.code="MODULE_NOT_FOUND",r}var a=l[t]={exports:{}};i[t][0].call(a.exports,function(e){return u(i[t][1][e]||e)},a,a.exports,s,i,l,o)}return l[t].exports}for(var c="function"==typeof require&&require,e=0;ef[0]&&(f[1]+1>f[0]&&a.push("-"),a.push(g(f[1])))}return a.push("]"),a.join("")}function s(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")),n=t.length,r=[],a=0,s=0;a/,null])):t.push([f,/^#[^\r\n]*/,null,"#"])),e.cStyleComments&&(n.push([f,/^\/\/[^\r\n]*/,null]),n.push([f,/^\/\*[\s\S]*?(?:\*\/|$)/,null]));var a=e.regexLiterals;if(a){var s=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+l+")")])}var o=e.types;o&&n.push([p,o]);var u=(""+e.keywords).replace(/^ | $/g,"");u.length&&n.push(["kwd",new RegExp("^(?:"+u.replace(/[\s,]+/g,"|")+")\\b"),null]),t.push([E,/^\s+/,null," \r\n\t "]);var c="^.[^\\s\\w.$@'\"`/\\\\]*";return e.regexLiterals&&(c+="(?!s*/)"),n.push([g,/^@[a-z_$][a-z_$@0-9]*/i,null],[p,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[E,/^[a-z_$][a-z_$@0-9]*/i,null],[g,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[E,/^\\[\s\S]?/,null],[h,new RegExp(c),null]),y(t,n)}var x=b({keywords:[n,a,r,s,i,l,o,u],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0});function T(e,t,l){for(var o=/(?:^|\s)nocode(?:\s|$)/,u=/\r\n?|\n/,c=e.ownerDocument,n=c.createElement("li");e.firstChild;)n.appendChild(e.firstChild);var r=[n];function d(e){var t=e.nodeType;if(1!=t||o.test(e.className)){if((3==t||4==t)&&l){var n=e.nodeValue,r=n.match(u);if(r){var a=n.substring(0,r.index);e.nodeValue=a;var s=n.substring(r.index+r[0].length);if(s)e.parentNode.insertBefore(c.createTextNode(s),e.nextSibling);f(e),a||e.parentNode.removeChild(e)}}}else if("br"===e.nodeName)f(e),e.parentNode&&e.parentNode.removeChild(e);else for(var i=e.firstChild;i;i=i.nextSibling)d(i)}function f(e){for(;!e.nextSibling;)if(!(e=e.parentNode))return;for(var t,n=function e(t,n){var r=n?t.cloneNode(!1):t,a=t.parentNode;if(a){var s=e(a,1),i=t.nextSibling;s.appendChild(r);for(var l=i;l;l=i)i=l.nextSibling,s.appendChild(l)}return r}(e.nextSibling,0);(t=n.parentNode)&&1===t.nodeType;)n=t;r.push(n)}for(var a=0;a",s=s.firstChild,r&&T(s,r,!0),$({langExtension:a,numberLines:r,sourceNode:s,pre:1,sourceCode:null,basePos:null,spans:null,decorations:null}),s.innerHTML}function N(y,e){var t=e||document.body,b=t.ownerDocument||document;function n(e){return t.getElementsByTagName(e)}for(var r=[n("pre"),n("code"),n("xmp")],x=[],a=0;a]*(?:>|$)/],[f,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[h,/^(?:<[%?]|[%?]>)/],["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(y([[E,/^[\s]+/,null," \t\r\n"],[m,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[h,/^[=<>\/]+/],["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(y([],[[m,/^[\s\S]+/]]),["uq.val"]),S(b({keywords:n,hashComments:!0,cStyleComments:!0,types:c}),["c","cc","cpp","cxx","cyc","m"]),S(b({keywords:"null,true,false"}),["json"]),S(b({keywords:a,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:c}),["cs"]),S(b({keywords:r,cStyleComments:!0}),["java"]),S(b({keywords:u,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]),S(b({keywords:l,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]),S(b({keywords:i,hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]),S(b({keywords:o,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]),S(b({keywords:s,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]),S(b({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(y([],[[d,/^[\s\S]+/]]),["regex"]);var _=P.PR={createSimpleLexer:y,registerLangHandler:S,sourceDecorator:b,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:m,PR_COMMENT:f,PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:g,PR_NOCODE:"nocode",PR_PLAIN:E,PR_PUNCTUATION:h,PR_SOURCE:L,PR_STRING:d,PR_TAG:"tag",PR_TYPE:p,prettyPrintOne:C,prettyPrint:N},O=P.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  PR.prettyPrint(); // store tabs variables\n\n  var tabs = document.querySelectorAll(\"ul.nav-tabs > li\");\n\n  for (var i = 0; i < tabs.length; i++) {\n    tabs[i].addEventListener(\"click\", switchTab);\n  }\n\n  function switchTab(event) {\n    event.preventDefault();\n    document.querySelector(\"ul.nav-tabs li.active\").classList.remove(\"active\");\n    document.querySelector(\".tab-pane.active\").classList.remove(\"active\");\n    var clickedTab = event.currentTarget;\n    var anchor = event.target;\n    var activePaneID = anchor.getAttribute(\"href\");\n    clickedTab.classList.add(\"active\");\n    document.querySelector(activePaneID).classList.add(\"active\");\n  }\n});\njQuery(document).ready(function ($) {\n  $(document).on('click', '.js-image-upload', function (e) {\n    e.preventDefault();\n    var $button = $(this);\n    var file_frame = wp.media.frames.file_frame = wp.media({\n      title: 'Select or Upload an Image',\n      library: {\n        type: 'image' // mime type\n\n      },\n      button: {\n        text: 'Select Image'\n      },\n      multiple: false\n    });\n    file_frame.on('select', function () {\n      var attachment = file_frame.state().get('selection').first().toJSON();\n      $button.siblings('.image-upload').val(attachment.url);\n    });\n    file_frame.open();\n  });\n});\n\n},{\"code-prettify\":1}]},{},[2]);\n"]}
    \ No newline at end of file
    diff --git a/Lesson58/assets/mystyle.css b/Lesson58/assets/mystyle.css
    new file mode 100644
    index 0000000..bce9271
    --- /dev/null
    +++ b/Lesson58/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}div.ui-toggle.mb-10{margin-bottom:10px}.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}.text-left{text-align:left}.text-right{text-align:right}.w-50{width:49%}.inline{display:inline-block}.meta-label{display:inline-block;font-weight:bold;margin-bottom:5px}.meta-container{display:block;margin-top:20px}
    +
    +/*# sourceMappingURL=mystyle.css.map */
    diff --git a/Lesson58/assets/mystyle.css.map b/Lesson58/assets/mystyle.css.map
    new file mode 100644
    index 0000000..38ea5cf
    --- /dev/null
    +++ b/Lesson58/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,CAEb,mDACC,qBACA,mBACA,4CAAA,AA9BS,mCAAA,CAgCT,uDACC,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,CAC1B,wDAIA,cAAe,CACf,oBAQD,kBAAmB,CACnB,WCvED,WACA,mBACA,eAAgB,CAHjB,uCAQE,sBACA,yBACA,YAAa,CAVf,cAcE,wBAAyB,CACzB,aAID,iBAAkB,CAClB,cCnBA,oBAAqB,CACrB,WAGA,eAAgB,CAChB,YAGA,gBAAiB,CACjB,MAGA,SAAU,CACV,QAGA,oBAAqB,CACrB,YAGA,qBACA,iBACA,iBAAkB,CAClB,gBAGA,cACA,eAAgB,CAChB","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\n\t&.mb-10 {\n\t\tmargin-bottom: 10px;\n\t}\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}\n\n.text-left {\n\ttext-align: left;\n}\n\n.text-right {\n\ttext-align: right;\n}\n\n.w-50 {\n\twidth: 49%;\n}\n\n.inline {\n\tdisplay: inline-block;\n}\n\n.meta-label {\n\tdisplay: inline-block;\n\tfont-weight: bold;\n\tmargin-bottom: 5px;\n}\n\n.meta-container {\n\tdisplay: block;\n\tmargin-top: 20px;\n}"]}
    \ No newline at end of file
    diff --git a/Lesson58/assets/slider.css b/Lesson58/assets/slider.css
    new file mode 100644
    index 0000000..4b2e097
    --- /dev/null
    +++ b/Lesson58/assets/slider.css
    @@ -0,0 +1,3 @@
    +.ac-slider--wrapper{margin:0 auto;max-width:calc(90% - 150px)}.ac-slider--container{position:relative;width:100%}.ac-slider--container .ac-slider--view{display:block;width:100%;margin:0 auto;overflow:hidden}.ac-slider--container .ac-slider--view ul{white-space:nowrap;text-align:center;list-style:none;margin:0;padding:0;-webkit-transition:-webkit-transform 0.25s ease-out;transition:-webkit-transform 0.25s ease-out;transition:transform 0.25s ease-out;transition:transform 0.25s ease-out, -webkit-transform 0.25s ease-out}.ac-slider--container .ac-slider--view ul .ac-slider--view__slides{white-space:normal;display:inline-block;width:100%;position:relative}.ac-slider--container .ac-slider--arrows{width:100%;margin:0 auto}.ac-slider--container .ac-slider--arrows span{position:absolute;top:50%;-webkit-transform:translateX(0) translateY(-50%);transform:translateX(0) translateY(-50%);width:20px;height:20px;display:block;cursor:pointer;border-radius:50%;background:#f5f5f5;line-height:1.3em;text-align:center;opacity:0.5}.ac-slider--container .ac-slider--arrows span.ac-slider--arrows__left{left:0;-webkit-transform:translateX(-100%) translateY(-50%);transform:translateX(-100%) translateY(-50%)}.ac-slider--container .ac-slider--arrows span.ac-slider--arrows__right{right:0;-webkit-transform:translateX(100%) translateY(-50%);transform:translateX(100%) translateY(-50%)}.ac-slider--container .ac-slider--arrows span:hover{opacity:1}.testimonial-quote{font-style:italic;font-weight:200;font-size:0.9em;letter-spacing:0.05em;position:relative;margin-bottom:0.5em;padding:0 10px}.testimonial-author{font-size:0.6em;font-weight:800;margin-bottom:0.5em}
    +
    +/*# sourceMappingURL=slider.css.map */
    diff --git a/Lesson58/assets/slider.css.map b/Lesson58/assets/slider.css.map
    new file mode 100644
    index 0000000..be8edf4
    --- /dev/null
    +++ b/Lesson58/assets/slider.css.map
    @@ -0,0 +1 @@
    +{"version":3,"sources":["slider.scss"],"names":[],"mappings":"AAAA,oBACC,cACA,2BAA4B,CAC5B,sBAGA,kBACA,UAAW,CAFZ,uCAIE,cACA,WACA,cACA,eAAgB,CAPlB,0CASG,mBACA,kBACA,gBACA,SACA,UACA,oDAAA,AAAoC,4CAApC,AAAoC,oCAApC,AAAoC,qEAAA,CAdvC,mEAgBI,mBACA,qBACA,WACA,iBAAkB,CAnBtB,yCAwBE,WACA,aAAc,CAzBhB,8CA2BG,kBACA,QACA,iDAAA,AACA,yCAAA,WACA,YACA,cACA,eACA,kBACA,mBACA,kBACA,kBACA,WAAY,CAtCf,sEAwCI,OACA,qDAAA,AAA6C,4CAAA,CAzCjD,uEA4CI,QACA,oDAAA,AAA4C,2CAAA,CA7ChD,oDAgDI,SAAU,CACV,mBAMH,kBACA,gBACA,gBACA,sBACA,kBACA,oBACA,cAAe,CACf,oBAGA,gBACA,gBACA,mBAAoB,CACpB","file":"slider.css","sourcesContent":[".ac-slider--wrapper {\n\tmargin: 0 auto;\n\tmax-width: calc(90% - 150px);\n}\n\n.ac-slider--container {\n\tposition: relative;\n\twidth: 100%;\n\t.ac-slider--view {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\tmargin: 0 auto;\n\t\toverflow: hidden;\n\t\tul {\n\t\t\twhite-space: nowrap;\n\t\t\ttext-align: center;\n\t\t\tlist-style: none;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\ttransition: transform 0.25s ease-out;\n\t\t\t.ac-slider--view__slides {\n\t\t\t\twhite-space: normal;\n\t\t\t\tdisplay: inline-block;\n\t\t\t\twidth: 100%;\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t}\n\t}\n\t.ac-slider--arrows {\n\t\twidth: 100%;\n\t\tmargin: 0 auto;\n\t\tspan {\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\ttransform: translateX(0) translateY(-50%);\n\t\t\twidth: 20px;\n\t\t\theight: 20px;\n\t\t\tdisplay: block;\n\t\t\tcursor: pointer;\n\t\t\tborder-radius: 50%;\n\t\t\tbackground: #f5f5f5;\n\t\t\tline-height: 1.3em;\n\t\t\ttext-align: center;\n\t\t\topacity: 0.5;\n\t\t\t&.ac-slider--arrows__left {\n\t\t\t\tleft: 0;\n\t\t\t\ttransform: translateX(-100%) translateY(-50%);\n\t\t\t}\n\t\t\t&.ac-slider--arrows__right {\n\t\t\t\tright: 0;\n\t\t\t\ttransform: translateX(100%) translateY(-50%);\n\t\t\t}\n\t\t\t&:hover {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.testimonial-quote {\n\tfont-style: italic;\n\tfont-weight: 200;\n\tfont-size: 0.9em;\n\tletter-spacing: 0.05em;\n\tposition: relative;\n\tmargin-bottom: 0.5em;\n\tpadding: 0 10px;\n}\n\n.testimonial-author {\n\tfont-size: 0.6em;\n\tfont-weight: 800;\n\tmargin-bottom: 0.5em;\n}"]}
    \ No newline at end of file
    diff --git a/Lesson58/assets/slider.js b/Lesson58/assets/slider.js
    new file mode 100644
    index 0000000..b4d67f4
    --- /dev/null
    +++ b/Lesson58/assets/slider.js
    @@ -0,0 +1,2 @@
    +!function n(o,s,u){function l(r,e){if(!s[r]){if(!o[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(d)return d(r,!0);var i=new Error("Cannot find module '"+r+"'");throw i.code="MODULE_NOT_FOUND",i}var c=s[r]={exports:{}};o[r][0].call(c.exports,function(e){return l(o[r][1][e]||e)},c,c.exports,n,o,s,u)}return s[r].exports}for(var d="function"==typeof require&&require,e=0;e<u.length;e++)l(u[e]);return l}({1:[function(e,r,t){"use strict";var n=document.querySelector(".ac-slider--view > ul"),o=document.querySelectorAll(".ac-slider--view__slides"),i=document.querySelector(".ac-slider--arrows__left"),c=document.querySelector(".ac-slider--arrows__right"),s=o.length,u=function(e){var r,t=document.querySelector(".ac-slider--view__slides.is-active"),i=Array.from(o).indexOf(t)+e+e,c=document.querySelector(".ac-slider--view__slides:nth-child(".concat(i,")"));s<i&&(c=document.querySelector(".ac-slider--view__slides:nth-child(1)")),0==i&&(c=document.querySelector(".ac-slider--view__slides:nth-child(".concat(s,")"))),r=c,t.classList.remove("is-active"),r.classList.add("is-active"),n.setAttribute("style","transform:translateX(-"+r.offsetLeft+"px)")};c.addEventListener("click",function(){return u(1)}),i.addEventListener("click",function(){return u(0)})},{}]},{},[1]);
    +//# sourceMappingURL=slider.js.map
    diff --git a/Lesson58/assets/slider.js.map b/Lesson58/assets/slider.js.map
    new file mode 100644
    index 0000000..5d9b363
    --- /dev/null
    +++ b/Lesson58/assets/slider.js.map
    @@ -0,0 +1 @@
    +{"version":3,"sources":["slider.js"],"names":["r","e","n","t","o","i","f","c","require","u","a","Error","code","p","exports","call","length","1","module","sliderView","document","querySelector","sliderViewSlides","querySelectorAll","arrowLeft","arrowRight","sliderLength","beforeSliding","sliderViewItems","isActiveItem","nextItem","Array","from","indexOf","concat","classList","remove","add","setAttribute","offsetLeft","addEventListener"],"mappings":"CAAY,SAASA,EAAEC,EAAEC,EAAEC,GAAG,SAASC,EAAEC,EAAEC,GAAG,IAAIJ,EAAEG,GAAG,CAAC,IAAIJ,EAAEI,GAAG,CAAC,IAAIE,EAAE,mBAAmBC,SAASA,QAAQ,IAAIF,GAAGC,EAAE,OAAOA,EAAEF,GAAE,GAAI,GAAGI,EAAE,OAAOA,EAAEJ,GAAE,GAAI,IAAIK,EAAE,IAAIC,MAAM,uBAAuBN,EAAE,KAAK,MAAMK,EAAEE,KAAK,mBAAmBF,EAAE,IAAIG,EAAEX,EAAEG,GAAG,CAACS,QAAQ,IAAIb,EAAEI,GAAG,GAAGU,KAAKF,EAAEC,QAAQ,SAASd,GAAoB,OAAOI,EAAlBH,EAAEI,GAAG,GAAGL,IAAeA,IAAIa,EAAEA,EAAEC,QAAQd,EAAEC,EAAEC,EAAEC,GAAG,OAAOD,EAAEG,GAAGS,QAAQ,IAAI,IAAIL,EAAE,mBAAmBD,SAASA,QAAQH,EAAE,EAAEA,EAAEF,EAAEa,OAAOX,IAAID,EAAED,EAAEE,IAAI,OAAOD,EAA7b,CAA4c,CAACa,EAAE,CAAC,SAAST,EAAQU,EAAOJ,GACxe,aAGA,IAAIK,EAAaC,SAASC,cAAc,yBACpCC,EAAmBF,SAASG,iBAAiB,4BAC7CC,EAAYJ,SAASC,cAAc,4BACnCI,EAAaL,SAASC,cAAc,6BACpCK,EAAeJ,EAAiBN,OAWhCW,EAAgB,SAAuBtB,GACzC,IAV6BuB,EAUzBC,EAAeT,SAASC,cAAc,sCAEtCS,EADcC,MAAMC,KAAKV,GAAkBW,QAAQJ,GAAgBxB,EAC1CA,EACzBuB,EAAkBR,SAASC,cAAc,sCAAsCa,OAAOJ,EAAU,MAErFJ,EAAXI,IACFF,EAAkBR,SAASC,cAAc,0CAI3B,GAAZS,IACFF,EAAkBR,SAASC,cAAc,sCAAsCa,OAAOR,EAAc,OArBzEE,EAyBrBA,EAAiBC,EAvBZM,UAAUC,OAAO,aAC9BR,EAAgBO,UAAUE,IAAI,aAE9BlB,EAAWmB,aAAa,QAAS,yBAA2BV,EAAgBW,WAAa,QAwB3Fd,EAAWe,iBAAiB,QAAS,WACnC,OAAOb,EAAc,KAEvBH,EAAUgB,iBAAiB,QAAS,WAClC,OAAOb,EAAc,MAGrB,KAAK,GAAG,CAAC","file":"slider.js","sourcesContent":["(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error(\"Cannot find module '\"+i+\"'\");throw a.code=\"MODULE_NOT_FOUND\",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=\"function\"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){\n\"use strict\";\n\n// global variables\nvar sliderView = document.querySelector('.ac-slider--view > ul'),\n    sliderViewSlides = document.querySelectorAll('.ac-slider--view__slides'),\n    arrowLeft = document.querySelector('.ac-slider--arrows__left'),\n    arrowRight = document.querySelector('.ac-slider--arrows__right'),\n    sliderLength = sliderViewSlides.length; // sliding function\n\nvar slideMe = function slideMe(sliderViewItems, isActiveItem) {\n  // update the classes\n  isActiveItem.classList.remove('is-active');\n  sliderViewItems.classList.add('is-active'); // css transform the active slide position\n\n  sliderView.setAttribute('style', 'transform:translateX(-' + sliderViewItems.offsetLeft + 'px)');\n}; // before sliding function\n\n\nvar beforeSliding = function beforeSliding(i) {\n  var isActiveItem = document.querySelector('.ac-slider--view__slides.is-active'),\n      currentItem = Array.from(sliderViewSlides).indexOf(isActiveItem) + i,\n      nextItem = currentItem + i,\n      sliderViewItems = document.querySelector(\".ac-slider--view__slides:nth-child(\".concat(nextItem, \")\")); // if nextItem is bigger than the # of slides\n\n  if (nextItem > sliderLength) {\n    sliderViewItems = document.querySelector('.ac-slider--view__slides:nth-child(1)');\n  } // if nextItem is 0\n\n\n  if (nextItem == 0) {\n    sliderViewItems = document.querySelector(\".ac-slider--view__slides:nth-child(\".concat(sliderLength, \")\"));\n  } // trigger the sliding method\n\n\n  slideMe(sliderViewItems, isActiveItem);\n}; // triggers arrows\n\n\narrowRight.addEventListener('click', function () {\n  return beforeSliding(1);\n});\narrowLeft.addEventListener('click', function () {\n  return beforeSliding(0);\n});\n\n},{}]},{},[1]);\n"]}
    \ No newline at end of file
    diff --git a/Lesson58/composer.json b/Lesson58/composer.json
    new file mode 100644
    index 0000000..cc3cc19
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/gulpfile.js b/Lesson58/gulpfile.js
    new file mode 100644
    index 0000000..f2c9662
    --- /dev/null
    +++ b/Lesson58/gulpfile.js
    @@ -0,0 +1,110 @@
    +// // Load Gulp...of course
    +const { src, dest, task, series, watch, parallel } = require('gulp');
    +
    +// // CSS related plugins
    +var sass         = require( 'gulp-sass' );
    +var autoprefixer = require( 'gulp-autoprefixer' );
    +
    +// // JS related plugins
    +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 options      = require( 'gulp-options' );
    +var gulpif       = require( 'gulp-if' );
    +
    +// // Browers related plugins
    +var browserSync  = require( 'browser-sync' ).create();
    +
    +// // Project related variables
    +var projectURL   = 'https://wp.dev';
    +
    +var styleSRC     = 'src/scss/mystyle.scss';
    +var styleForm    = 'src/scss/form.scss';
    +var styleSlider  = 'src/scss/slider.scss';
    +var styleAuth    = 'src/scss/auth.scss';
    +var styleURL     = './assets/';
    +var mapURL       = './';
    +
    +var jsSRC        = 'src/js/';
    +var jsAdmin      = 'myscript.js';
    +var jsForm       = 'form.js';
    +var jsSlider     = 'slider.js';
    +var jsAuth       = 'auth.js';
    +var jsFiles      = [jsAdmin, jsForm, jsSlider, jsAuth];
    +var jsURL        = './assets/';
    +
    +var styleWatch   = 'src/scss/**/*.scss';
    +var jsWatch      = 'src/js/**/*.js';
    +var phpWatch     = './**/*.php';
    +
    +function css(done) {
    +	src([styleSRC, styleForm, styleSlider, styleAuth])
    +		.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( dest( styleURL ) )
    +		.pipe( browserSync.stream() );
    +	done();
    +}
    +
    +function js(done) {
    +	jsFiles.map(function (entry) {
    +		return browserify({
    +			entries: [jsSRC + entry]
    +		})
    +		.transform( babelify, { presets: [ '@babel/preset-env' ] } )
    +		.bundle()
    +		.pipe( source( entry ) )
    +		.pipe( buffer() )
    +		.pipe( gulpif( options.has( 'production' ), stripDebug() ) )
    +		.pipe( sourcemaps.init({ loadMaps: true }) )
    +		.pipe( uglify() )
    +		.pipe( sourcemaps.write( '.' ) )
    +		.pipe( dest( jsURL ) )
    +		.pipe( browserSync.stream() );
    +	});
    +	done();
    +}
    +
    +function reload(done) {
    +	browserSync.reload();
    +	done();
    +}
    +
    +function browser_sync() {
    +	browserSync.init({
    +		proxy: projectURL,
    +		https: {
    +			key: '/home/alecaddd/.valet/Certificates/wp.dev.key',
    +			cert: '/home/alecaddd/.valet/Certificates/wp.dev.crt'
    +		},
    +		injectChanges: true,
    +		open: false
    +	});
    +}
    +
    +function watch_files() {
    +	watch( phpWatch, reload );
    +	watch( styleWatch, css );
    +	watch( jsWatch, series( js, reload ) );
    +	src( jsURL + 'myscript.js' )
    +		.pipe( notify({ message: 'Gulp is Watching, Happy Coding!' }) );
    +};
    +
    +task("css", css);
    +task("js", js);
    +task("default", series(css, js));
    +task("watch", parallel(browser_sync, watch_files));
    \ No newline at end of file
    diff --git a/Lesson58/inc/Api/Callbacks/AdminCallbacks.php b/Lesson58/inc/Api/Callbacks/AdminCallbacks.php
    new file mode 100644
    index 0000000..07c0d0f
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Api/Callbacks/CptCallbacks.php b/Lesson58/inc/Api/Callbacks/CptCallbacks.php
    new file mode 100644
    index 0000000..3638ebf
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Api/Callbacks/ManagerCallbacks.php b/Lesson58/inc/Api/Callbacks/ManagerCallbacks.php
    new file mode 100644
    index 0000000..264188f
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Api/Callbacks/TaxonomyCallbacks.php b/Lesson58/inc/Api/Callbacks/TaxonomyCallbacks.php
    new file mode 100644
    index 0000000..012299d
    --- /dev/null
    +++ b/Lesson58/inc/Api/Callbacks/TaxonomyCallbacks.php
    @@ -0,0 +1,94 @@
    +<?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>';
    +	}
    +
    +	public function checkboxPostTypesField( $args )
    +	{
    +		$output = '';
    +		$name = $args['label_for'];
    +		$classes = $args['class'];
    +		$option_name = $args['option_name'];
    +		$checked = false;
    +
    +		if ( isset($_POST["edit_taxonomy"]) ) {
    +			$checkbox = get_option( $option_name );
    +		}
    +
    +		$post_types = get_post_types( array( 'show_ui' => true ) );
    +
    +		foreach ($post_types as $post) {
    +
    +			if ( isset($_POST["edit_taxonomy"]) ) {
    +				$checked = isset($checkbox[$_POST["edit_taxonomy"]][$name][$post]) ?: false;
    +			}
    +
    +			$output .= '<div class="' . $classes . ' mb-10"><input type="checkbox" id="' . $post . '" name="' . $option_name . '[' . $name . '][' . $post . ']" value="1" class="" ' . ( $checked ? 'checked' : '') . '><label for="' . $post . '"><div></div></label> <strong>' . $post . '</strong></div>';
    +		}
    +
    +		echo $output;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Api/Callbacks/TestimonialCallbacks.php b/Lesson58/inc/Api/Callbacks/TestimonialCallbacks.php
    new file mode 100644
    index 0000000..180b42e
    --- /dev/null
    +++ b/Lesson58/inc/Api/Callbacks/TestimonialCallbacks.php
    @@ -0,0 +1,15 @@
    +<?php
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Callbacks;
    +
    +use Inc\Base\BaseController;
    +
    +class TestimonialCallbacks extends BaseController
    +{
    +    public function shortcodePage()
    +    {
    +        return require_once( "$this->plugin_path/templates/testimonial.php" );
    +    }
    +}
    diff --git a/Lesson58/inc/Api/SettingsApi.php b/Lesson58/inc/Api/SettingsApi.php
    new file mode 100644
    index 0000000..bd01bfe
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Api/Widgets/MediaWidget.php b/Lesson58/inc/Api/Widgets/MediaWidget.php
    new file mode 100644
    index 0000000..ce79b30
    --- /dev/null
    +++ b/Lesson58/inc/Api/Widgets/MediaWidget.php
    @@ -0,0 +1,85 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Api\Widgets;
    +
    +use WP_Widget;
    +
    +/**
    +* 
    +*/
    +class MediaWidget extends WP_Widget
    +{
    +	public $widget_ID;
    +
    +	public $widget_name;
    +
    +	public $widget_options = array();
    +
    +	public $control_options = array();
    +
    +	function __construct() {
    +
    +		$this->widget_ID = 'alecaddd_media_widget';
    +		$this->widget_name = 'Alecaddd Media Widget';
    +
    +		$this->widget_options = array(
    +			'classname' => $this->widget_ID,
    +			'description' => $this->widget_name,
    +			'customize_selective_refresh' => true,
    +		);
    +
    +		$this->control_options = array(
    +			'width' => 400,
    +			'height' => 350,
    +		);
    +	}
    +
    +	public function register()
    +	{
    +		parent::__construct( $this->widget_ID, $this->widget_name, $this->widget_options, $this->control_options );
    +
    +		add_action( 'widgets_init', array( $this, 'widgetsInit' ) );
    +	}
    +
    +	public function widgetsInit()
    +	{
    +		register_widget( $this );
    +	}
    +
    +	public function widget( $args, $instance ) {
    +		echo $args['before_widget'];
    +		if ( ! empty( $instance['title'] ) ) {
    +			echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
    +		}
    +		if ( ! empty( $instance['image'] ) ) {
    +			echo '<img src="'. esc_url( $instance['image'] ) .'" alt="">';
    +		}
    +		echo $args['after_widget'];
    +	}
    +
    +	public function form( $instance ) {
    +		$title = ! empty( $instance['title'] ) ? $instance['title'] : esc_html__( 'Custom Text', 'awps' );
    +		$image = ! empty( $instance['image'] ) ? $instance['image'] : '';
    +		?>
    +		<p>
    +			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_attr_e( 'Title:', 'awps' ); ?></label> 
    +			<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
    +		</p>
    +		<p>
    +			<label for="<?php echo esc_attr( $this->get_field_id( 'image' ) ); ?>"><?php esc_attr_e( 'Image:', 'awps' ); ?></label> 
    +			<input class="widefat image-upload" id="<?php echo esc_attr( $this->get_field_id( 'image' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'image' ) ); ?>" type="text" value="<?php echo esc_url( $image ); ?>">
    +			<button type="button" class="button button-primary js-image-upload">Select Image</button>
    +		</p>
    +		<?php 
    +	}
    +
    +	public function update( $new_instance, $old_instance ) {
    +		$instance = $old_instance;
    +		$instance['title'] = sanitize_text_field( $new_instance['title'] );
    +		$instance['image'] = ! empty( $new_instance['image'] ) ? $new_instance['image'] : '';
    +
    +		return $instance;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Base/Activate.php b/Lesson58/inc/Base/Activate.php
    new file mode 100644
    index 0000000..57c833f
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Base/AuthController.php b/Lesson58/inc/Base/AuthController.php
    new file mode 100644
    index 0000000..3199ec4
    --- /dev/null
    +++ b/Lesson58/inc/Base/AuthController.php
    @@ -0,0 +1,38 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Base\BaseController;
    +
    +/**
    +* 
    +*/
    +class AuthController extends BaseController
    +{
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'login_manager' ) ) return;
    +
    +		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) );
    +		add_action( 'wp_head', array( $this, 'add_auth_template' ) );
    +	}
    +
    +	public function enqueue()
    +	{
    +		wp_enqueue_style( 'authstyle', $this->plugin_url . 'assets/auth.css' );
    +		wp_enqueue_script( 'authscript', $this->plugin_url . 'assets/auth.js' );
    +	}
    +
    +	public function add_auth_template()
    +	{
    +		if ( is_user_logged_in() ) return;
    +
    +		$file = $this->plugin_path . 'templates/auth.php';
    +
    +		if ( file_exists( $file ) ) {
    +			load_template( $file, true );
    +		}
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Base/BaseController.php b/Lesson58/inc/Base/BaseController.php
    new file mode 100644
    index 0000000..c71b1a7
    --- /dev/null
    +++ b/Lesson58/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 Custom Templates',
    +			'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/Lesson58/inc/Base/ChatController.php b/Lesson58/inc/Base/ChatController.php
    new file mode 100644
    index 0000000..a137e57
    --- /dev/null
    +++ b/Lesson58/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( '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/Lesson58/inc/Base/CustomPostTypeController.php b/Lesson58/inc/Base/CustomPostTypeController.php
    new file mode 100644
    index 0000000..87f22ee
    --- /dev/null
    +++ b/Lesson58/inc/Base/CustomPostTypeController.php
    @@ -0,0 +1,279 @@
    +<?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' ),
    +				'show_in_rest'			=> true,
    +				'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'],
    +					'show_in_rest'              => $post_type['show_in_rest'],
    +					'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/Lesson58/inc/Base/CustomTaxonomyController.php b/Lesson58/inc/Base/CustomTaxonomyController.php
    new file mode 100644
    index 0000000..9b1a901
    --- /dev/null
    +++ b/Lesson58/inc/Base/CustomTaxonomyController.php
    @@ -0,0 +1,195 @@
    +<?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();
    +
    +		$this->storeCustomTaxonomies();
    +
    +		if ( ! empty( $this->taxonomies ) ) {
    +			add_action( 'init', array( $this, 'registerCustomTaxonomy' ));
    +		}
    +	}
    +
    +	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'
    +				)
    +			),
    +			array(
    +				'id' => 'objects',
    +				'title' => 'Post Types',
    +				'callback' => array( $this->tax_callbacks, 'checkboxPostTypesField' ),
    +				'page' => 'alecaddd_taxonomy',
    +				'section' => 'alecaddd_tax_index',
    +				'args' => array(
    +					'option_name' => 'alecaddd_plugin_tax',
    +					'label_for' => 'objects',
    +					'class' => 'ui-toggle',
    +					'array' => 'taxonomy'
    +				)
    +			)
    +		);
    +
    +		$this->settings->setFields( $args );
    +	}
    +
    +	public function storeCustomTaxonomies()
    +	{
    +		$options = get_option( 'alecaddd_plugin_tax' ) ?: array();
    +
    +		foreach ($options as $option) {
    +			$labels = array(
    +				'name'              => $option['singular_name'],
    +				'singular_name'     => $option['singular_name'],
    +				'search_items'      => 'Search ' . $option['singular_name'],
    +				'all_items'         => 'All ' . $option['singular_name'],
    +				'parent_item'       => 'Parent ' . $option['singular_name'],
    +				'parent_item_colon' => 'Parent ' . $option['singular_name'] . ':',
    +				'edit_item'         => 'Edit ' . $option['singular_name'],
    +				'update_item'       => 'Update ' . $option['singular_name'],
    +				'add_new_item'      => 'Add New ' . $option['singular_name'],
    +				'new_item_name'     => 'New ' . $option['singular_name'] . ' Name',
    +				'menu_name'         => $option['singular_name'],
    +			);
    +
    +			$this->taxonomies[] = array(
    +				'hierarchical'      => isset($option['hierarchical']) ? true : false,
    +				'labels'            => $labels,
    +				'show_ui'           => true,
    +				'show_admin_column' => true,
    +				'query_var'         => true,
    +				'show_in_rest'		=> true,
    +				'rewrite'           => array( 'slug' => $option['taxonomy'] ),
    +				'objects'           => isset($option['objects']) ? $option['objects'] : null
    +			);
    +
    +		}
    +	}
    +
    +	public function registerCustomTaxonomy()
    +	{
    +		foreach ($this->taxonomies as $taxonomy) {
    +			$objects = isset($taxonomy['objects']) ? array_keys($taxonomy['objects']) : null;
    +			register_taxonomy( $taxonomy['rewrite']['slug'], $objects, $taxonomy );
    +		}
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Base/Deactivate.php b/Lesson58/inc/Base/Deactivate.php
    new file mode 100644
    index 0000000..c13c98d
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Base/Enqueue.php b/Lesson58/inc/Base/Enqueue.php
    new file mode 100644
    index 0000000..7b36e1b
    --- /dev/null
    +++ b/Lesson58/inc/Base/Enqueue.php
    @@ -0,0 +1,25 @@
    +<?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_script( 'media-upload' );
    +		wp_enqueue_media();
    +		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/Lesson58/inc/Base/GalleryController.php b/Lesson58/inc/Base/GalleryController.php
    new file mode 100644
    index 0000000..fe161b4
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Base/MembershipController.php b/Lesson58/inc/Base/MembershipController.php
    new file mode 100644
    index 0000000..2d8300c
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Base/SettingsLinks.php b/Lesson58/inc/Base/SettingsLinks.php
    new file mode 100644
    index 0000000..78ce823
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/inc/Base/TemplateController.php b/Lesson58/inc/Base/TemplateController.php
    new file mode 100644
    index 0000000..6b4a6d3
    --- /dev/null
    +++ b/Lesson58/inc/Base/TemplateController.php
    @@ -0,0 +1,66 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Base\BaseController;
    +
    +/**
    +* 
    +*/
    +class TemplateController extends BaseController
    +{
    +	public $templates;
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'templates_manager' ) ) return;
    +
    +		$this->templates = array(
    +			'page-templates/two-columns-tpl.php' => 'Two Columns Layout'
    +		);
    +
    +		add_filter( 'theme_page_templates', array( $this, 'custom_template' ) );
    +		add_filter( 'template_include', array( $this, 'load_template' ) );
    +	}
    +
    +	public function custom_template( $templates )
    +	{
    +		$templates = array_merge( $templates, $this->templates );
    +
    +		return $templates;
    +	}
    +
    +	public function load_template( $template )
    +	{
    +		global $post;
    +
    +		if ( ! $post ) {
    +			return $template;
    +		}
    +
    +		// If is the front page, load a custom template
    +		if ( is_front_page() ) {
    +			$file = $this->plugin_path . 'page-templates/front-page.php';
    +
    +			if ( file_exists( $file ) ) {
    +				return $file;
    +			}
    +		}
    +
    +		$template_name = get_post_meta( $post->ID, '_wp_page_template', true );
    +
    +		if ( ! isset( $this->templates[$template_name] ) ) {
    +			return $template;
    +		}
    +
    +		$file = $this->plugin_path . $template_name;
    +
    +		if ( file_exists( $file ) ) {
    +			return $file;
    +		}
    +
    +		return $template;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Base/TestimonialController.php b/Lesson58/inc/Base/TestimonialController.php
    new file mode 100644
    index 0000000..278e3c1
    --- /dev/null
    +++ b/Lesson58/inc/Base/TestimonialController.php
    @@ -0,0 +1,268 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Api\SettingsApi;
    +use Inc\Base\BaseController;
    +use Inc\Api\Callbacks\TestimonialCallbacks;
    +
    +/**
    +* 
    +*/
    +class TestimonialController extends BaseController
    +{
    +	public $settings;
    +
    +	public $callbacks;
    +
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'testimonial_manager' ) ) return;
    +
    +		$this->settings = new SettingsApi();
    +
    +		$this->callbacks = new TestimonialCallbacks();
    +
    +		add_action( 'init', array( $this, 'testimonial_cpt' ) );
    +		add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
    +		add_action( 'save_post', array( $this, 'save_meta_box' ) );
    +		add_action( 'manage_testimonial_posts_columns', array( $this, 'set_custom_columns' ) );
    +		add_action( 'manage_testimonial_posts_custom_column', array( $this, 'set_custom_columns_data' ), 10, 2 );
    +		add_filter( 'manage_edit-testimonial_sortable_columns', array( $this, 'set_custom_columns_sortable' ) );
    +
    +		$this->setShortcodePage();
    +
    +		add_shortcode( 'testimonial-form', array( $this, 'testimonial_form' ) );
    +		add_shortcode( 'testimonial-slideshow', array( $this, 'testimonial_slideshow' ) );
    +		add_action( 'wp_ajax_submit_testimonial', array( $this, 'submit_testimonial' ) );
    +		add_action( 'wp_ajax_nopriv_submit_testimonial', array( $this, 'submit_testimonial' ) );
    +	}
    +
    +	public function submit_testimonial()
    +	{
    +		if (! DOING_AJAX || ! check_ajax_referer('testimonial-nonce', 'nonce') ) {
    +			return $this->return_json('error');
    +		}
    +
    +		$name = sanitize_text_field($_POST['name']);
    +		$email = sanitize_email($_POST['email']);
    +		$message = sanitize_textarea_field($_POST['message']);
    +
    +		$data = array(
    +			'name' => $name,
    +			'email' => $email,
    +			'approved' => 0,
    +			'featured' => 0,
    +		);
    +
    +		$args = array(
    +			'post_title' => 'Testimonial from ' . $name,
    +			'post_content' => $message,
    +			'post_author' => 1,
    +			'post_status' => 'publish',
    +			'post_type' => 'testimonial',
    +			'meta_input' => array(
    +				'_alecaddd_testimonial_key' => $data
    +			)
    +		);
    +
    +		$postID = wp_insert_post($args);
    +
    +		if ($postID) {
    +			return $this->return_json('success');
    +		}
    +
    +		return $this->return_json('error');
    +	}
    +
    +	public function return_json($status)
    +	{
    +		$return = array(
    +			'status' => $status
    +		);
    +		wp_send_json($return);
    +
    +		wp_die();
    +	}
    +
    +	public function testimonial_form()
    +	{
    +		ob_start();
    +		echo "<link rel=\"stylesheet\" href=\"$this->plugin_url/assets/form.css\" type=\"text/css\" media=\"all\" />";
    +		require_once( "$this->plugin_path/templates/contact-form.php" );
    +		echo "<script src=\"$this->plugin_url/assets/form.js\"></script>";
    +		return ob_get_clean();
    +	}
    +
    +	public function testimonial_slideshow()
    +	{
    +		ob_start();
    +		echo "<link rel=\"stylesheet\" href=\"$this->plugin_url/assets/slider.css\" type=\"text/css\" media=\"all\" />";
    +		require_once( "$this->plugin_path/templates/slider.php" );
    +		echo "<script src=\"$this->plugin_url/assets/slider.js\"></script>";
    +		return ob_get_clean();
    +	}
    +
    +	public function setShortcodePage()
    +	{
    +		$subpage = array(
    +			array(
    +				'parent_slug' => 'edit.php?post_type=testimonial',
    +				'page_title' => 'Shortcodes',
    +				'menu_title' => 'Shortcodes',
    +				'capability' => 'manage_options',
    +				'menu_slug' => 'alecaddd_testimonial_shortcode',
    +				'callback' => array( $this->callbacks, 'shortcodePage' )
    +			)
    +		);
    +
    +		$this->settings->addSubPages( $subpage )->register();
    +	}
    +
    +	public function testimonial_cpt ()
    +	{
    +		$labels = array(
    +			'name' => 'Testimonials',
    +			'singular_name' => 'Testimonial'
    +		);
    +
    +		$args = array(
    +			'labels' => $labels,
    +			'public' => true,
    +			'has_archive' => false,
    +			'menu_icon' => 'dashicons-testimonial',
    +			'exclude_from_search' => true,
    +			'publicly_queryable' => false,
    +			'supports' => array( 'title', 'editor' ),
    +			'show_in_rest' => true
    +		);
    +
    +		register_post_type ( 'testimonial', $args );
    +	}
    +
    +	public function add_meta_boxes()
    +	{
    +		add_meta_box(
    +			'testimonial_author',
    +			'Testimonial Options',
    +			array( $this, 'render_features_box' ),
    +			'testimonial',
    +			'side',
    +			'default'
    +		);
    +	}
    +
    +	public function render_features_box($post)
    +	{
    +		wp_nonce_field( 'alecaddd_testimonial', 'alecaddd_testimonial_nonce' );
    +
    +		$data = get_post_meta( $post->ID, '_alecaddd_testimonial_key', true );
    +		$name = isset($data['name']) ? $data['name'] : '';
    +		$email = isset($data['email']) ? $data['email'] : '';
    +		$approved = isset($data['approved']) ? $data['approved'] : false;
    +		$featured = isset($data['featured']) ? $data['featured'] : false;
    +		?>
    +		<p>
    +			<label class="meta-label" for="alecaddd_testimonial_author">Author Name</label>
    +			<input type="text" id="alecaddd_testimonial_author" name="alecaddd_testimonial_author" class="widefat" value="<?php echo esc_attr( $name ); ?>">
    +		</p>
    +		<p>
    +			<label class="meta-label" for="alecaddd_testimonial_email">Author Email</label>
    +			<input type="email" id="alecaddd_testimonial_email" name="alecaddd_testimonial_email" class="widefat" value="<?php echo esc_attr( $email ); ?>">
    +		</p>
    +		<div class="meta-container">
    +			<label class="meta-label w-50 text-left" for="alecaddd_testimonial_approved">Approved</label>
    +			<div class="text-right w-50 inline">
    +				<div class="ui-toggle inline"><input type="checkbox" id="alecaddd_testimonial_approved" name="alecaddd_testimonial_approved" value="1" <?php echo $approved ? 'checked' : ''; ?>>
    +					<label for="alecaddd_testimonial_approved"><div></div></label>
    +				</div>
    +			</div>
    +		</div>
    +		<div class="meta-container">
    +			<label class="meta-label w-50 text-left" for="alecaddd_testimonial_featured">Featured</label>
    +			<div class="text-right w-50 inline">
    +				<div class="ui-toggle inline"><input type="checkbox" id="alecaddd_testimonial_featured" name="alecaddd_testimonial_featured" value="1" <?php echo $featured ? 'checked' : ''; ?>>
    +					<label for="alecaddd_testimonial_featured"><div></div></label>
    +				</div>
    +			</div>
    +		</div>
    +		<?php
    +	}
    +
    +	public function save_meta_box($post_id)
    +	{
    +		if (! isset($_POST['alecaddd_testimonial_nonce'])) {
    +			return $post_id;
    +		}
    +
    +		$nonce = $_POST['alecaddd_testimonial_nonce'];
    +		if (! wp_verify_nonce( $nonce, 'alecaddd_testimonial' )) {
    +			return $post_id;
    +		}
    +
    +		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
    +			return $post_id;
    +		}
    +
    +		if (! current_user_can( 'edit_post', $post_id ) ) {
    +			return $post_id;
    +		}
    +
    +		$data = array(
    +			'name' => sanitize_text_field( $_POST['alecaddd_testimonial_author'] ),
    +			'email' => sanitize_email( $_POST['alecaddd_testimonial_email'] ),
    +			'approved' => isset($_POST['alecaddd_testimonial_approved']) ? 1 : 0,
    +			'featured' => isset($_POST['alecaddd_testimonial_featured']) ? 1 : 0,
    +		);
    +		update_post_meta( $post_id, '_alecaddd_testimonial_key', $data );
    +	}
    +
    +	public function set_custom_columns($columns)
    +	{
    +		$title = $columns['title'];
    +		$date = $columns['date'];
    +		unset( $columns['title'], $columns['date'] );
    +
    +		$columns['name'] = 'Author Name';
    +		$columns['title'] = $title;
    +		$columns['approved'] = 'Approved';
    +		$columns['featured'] = 'Featured';
    +		$columns['date'] = $date;
    +
    +		return $columns;
    +	}
    +
    +	public function set_custom_columns_data($column, $post_id)
    +	{
    +		$data = get_post_meta( $post_id, '_alecaddd_testimonial_key', true );
    +		$name = isset($data['name']) ? $data['name'] : '';
    +		$email = isset($data['email']) ? $data['email'] : '';
    +		$approved = isset($data['approved']) && $data['approved'] === 1 ? '<strong>YES</strong>' : 'NO';
    +		$featured = isset($data['featured']) && $data['featured'] === 1 ? '<strong>YES</strong>' : 'NO';
    +
    +		switch($column) {
    +			case 'name':
    +				echo '<strong>' . $name . '</strong><br/><a href="mailto:' . $email . '">' . $email . '</a>';
    +				break;
    +
    +			case 'approved':
    +				echo $approved;
    +				break;
    +
    +			case 'featured':
    +				echo $featured;
    +				break;
    +		}
    +	}
    +
    +	public function set_custom_columns_sortable($columns)
    +	{
    +		$columns['name'] = 'name';
    +		$columns['approved'] = 'approved';
    +		$columns['featured'] = 'featured';
    +
    +		return $columns;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Base/WidgetController.php b/Lesson58/inc/Base/WidgetController.php
    new file mode 100644
    index 0000000..76c93bc
    --- /dev/null
    +++ b/Lesson58/inc/Base/WidgetController.php
    @@ -0,0 +1,22 @@
    +<?php 
    +/**
    + * @package  AlecadddPlugin
    + */
    +namespace Inc\Base;
    +
    +use Inc\Base\BaseController;
    +use Inc\Api\Widgets\MediaWidget;
    +
    +/**
    +* 
    +*/
    +class WidgetController extends BaseController
    +{
    +	public function register()
    +	{
    +		if ( ! $this->activated( 'media_widget' ) ) return;
    +
    +		$media_widget = new MediaWidget();
    +		$media_widget->register();
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/inc/Init.php b/Lesson58/inc/Init.php
    new file mode 100644
    index 0000000..cf1307d
    --- /dev/null
    +++ b/Lesson58/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 getServices()
    +	{
    +		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 registerServices()
    +	{
    +		foreach (self::getServices() 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;
    +	}
    +}
    diff --git a/Lesson58/inc/Pages/Dashboard.php b/Lesson58/inc/Pages/Dashboard.php
    new file mode 100644
    index 0000000..1aa9e8f
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/index.php b/Lesson58/index.php
    new file mode 100644
    index 0000000..7e91415
    --- /dev/null
    +++ b/Lesson58/index.php
    @@ -0,0 +1,2 @@
    +<?php
    +// Silence is golden.
    \ No newline at end of file
    diff --git a/Lesson58/package.json b/Lesson58/package.json
    new file mode 100644
    index 0000000..44ce441
    --- /dev/null
    +++ b/Lesson58/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": "^7.2.2",
    +		"@babel/preset-env": "^7.2.3",
    +		"babelify": "^10.0.0",
    +		"browser-sync": "^2.24.4",
    +		"browserify": "^16.2.3",
    +		"browserify-shim": "^3.8.14",
    +		"gulp": "^4.0.0",
    +		"gulp-autoprefixer": "^6.0.0",
    +		"gulp-concat": "^2.5.2",
    +		"gulp-if": "^2.0.2",
    +		"gulp-notify": "^3.2.0",
    +		"gulp-options": "^1.1.1",
    +		"gulp-plumber": "^1.2.0",
    +		"gulp-rename": "^1.2.3",
    +		"gulp-sass": "^4.0.2",
    +		"gulp-sourcemaps": "^2.6.4",
    +		"gulp-strip-debug": "^3.0.0",
    +		"gulp-uglify": "^3.0.0",
    +		"gulp-uglifycss": "^1.0.9",
    +		"vinyl-buffer": "^1.0.0",
    +		"vinyl-source-stream": "^2.0.0"
    +	},
    +	"babel": {
    +		"presets": [
    +			"@babel/preset-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/Lesson58/page-templates/front-page.php b/Lesson58/page-templates/front-page.php
    new file mode 100644
    index 0000000..a59ed34
    --- /dev/null
    +++ b/Lesson58/page-templates/front-page.php
    @@ -0,0 +1,9 @@
    +<?php
    +get_header(); ?>
    +
    +<h1>Front Page Template</h1>
    +
    +<p>This is loading properly!</p>
    +
    +<?php
    +get_footer();
    diff --git a/Lesson58/page-templates/two-columns-tpl.php b/Lesson58/page-templates/two-columns-tpl.php
    new file mode 100644
    index 0000000..f46174d
    --- /dev/null
    +++ b/Lesson58/page-templates/two-columns-tpl.php
    @@ -0,0 +1,14 @@
    +<?php
    +
    +/* 
    +Template Name: Two Columns Layout
    +*/
    +
    +get_header(); ?>
    +
    +<h1>Custom 2 Columns Template</h1>
    +
    +<p>This is loading properly!</p>
    +
    +<?php
    +get_footer();
    diff --git a/Lesson58/readme.md b/Lesson58/readme.md
    new file mode 100644
    index 0000000..2d10c74
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/ruleset.xml b/Lesson58/ruleset.xml
    new file mode 100644
    index 0000000..db56ce8
    --- /dev/null
    +++ b/Lesson58/ruleset.xml
    @@ -0,0 +1,15 @@
    +<?xml version="1.0"?>
    +<ruleset name="MyStandard">
    +    <description>PSR2 with tabs instead of spaces.</description>
    +    <arg name="tab-width" value="4"/>
    +    <rule ref="PSR2">
    +        <exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
    +    </rule>
    +    <rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/>
    +    <rule ref="Generic.WhiteSpace.ScopeIndent">
    +        <properties>
    +            <property name="indent" value="4"/>
    +            <property name="tabIndent" value="true"/>
    +        </properties>
    +    </rule>
    +</ruleset>
    \ No newline at end of file
    diff --git a/Lesson58/src/js/auth.js b/Lesson58/src/js/auth.js
    new file mode 100644
    index 0000000..c56ad50
    --- /dev/null
    +++ b/Lesson58/src/js/auth.js
    @@ -0,0 +1,15 @@
    +document.addEventListener('DOMContentLoaded', function (e) {
    +    const showAuthBtn = document.getElementById('alecaddd-show-auth-form'),
    +        authContainer = document.getElementById('alecaddd-auth-container'),
    +        close = document.getElementById('alecaddd-auth-close');
    +    
    +    showAuthBtn.addEventListener('click', () => {
    +        authContainer.classList.add('show');        
    +        showAuthBtn.parentElement.classList.add('hide');
    +    });
    +
    +    close.addEventListener('click', () => {
    +        authContainer.classList.remove('show');
    +        showAuthBtn.parentElement.classList.remove('hide');
    +    });
    +});
    \ No newline at end of file
    diff --git a/Lesson58/src/js/form.js b/Lesson58/src/js/form.js
    new file mode 100644
    index 0000000..0a5307a
    --- /dev/null
    +++ b/Lesson58/src/js/form.js
    @@ -0,0 +1,69 @@
    +document.addEventListener('DOMContentLoaded', function(e) {
    +	let testimonialForm = document.getElementById('alecaddd-testimonial-form');
    +
    +	testimonialForm.addEventListener('submit', (e) => {
    +		e.preventDefault();
    +
    +		// reset the form messages
    +		resetMessages();
    +
    +		// collect all the data
    +		let data = {
    +			name: testimonialForm.querySelector('[name="name"]').value,
    +			email: testimonialForm.querySelector('[name="email"]').value,
    +			message: testimonialForm.querySelector('[name="message"]').value,
    +			nonce: testimonialForm.querySelector('[name="nonce"]').value
    +		}
    +		
    +		// validate everything
    +		if (! data.name) {
    +			testimonialForm.querySelector('[data-error="invalidName"]').classList.add('show');
    +			return;
    +		}
    +
    +		if (! validateEmail(data.email)) {
    +			testimonialForm.querySelector('[data-error="invalidEmail"]').classList.add('show');
    +			return;
    +		}
    +
    +		if (!data.message) {
    +			testimonialForm.querySelector('[data-error="invalidMessage"]').classList.add('show');
    +			return;
    +		}
    +
    +		// ajax http post request
    +		let url = testimonialForm.dataset.url;
    +		let params = new URLSearchParams(new FormData(testimonialForm));
    +
    +		testimonialForm.querySelector('.js-form-submission').classList.add('show');
    +
    +		fetch(url, {
    +			method: "POST",
    +			body: params
    +		}).then(res => res.json())
    +			.catch(error => {
    +				resetMessages();
    +				testimonialForm.querySelector('.js-form-error').classList.add('show');
    +			})
    +			.then(response => {
    +				resetMessages();
    +				
    +				if (response === 0 || response.status === 'error') {
    +					testimonialForm.querySelector('.js-form-error').classList.add('show');
    +					return;
    +				}
    +
    +				testimonialForm.querySelector('.js-form-success').classList.add('show');
    +				testimonialForm.reset();
    +			})
    +	});
    +});
    +  
    +function resetMessages() {
    +	document.querySelectorAll('.field-msg').forEach(f => f.classList.remove('show'));
    +}
    +
    +function validateEmail(email) {
    +	let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    +	return re.test(String(email).toLowerCase());
    +}
    \ No newline at end of file
    diff --git a/Lesson58/src/js/myscript.js b/Lesson58/src/js/myscript.js
    new file mode 100644
    index 0000000..5f4a5f7
    --- /dev/null
    +++ b/Lesson58/src/js/myscript.js
    @@ -0,0 +1,54 @@
    +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");
    +
    +	}
    +
    +});
    +
    +jQuery(document).ready(function ($) {
    +	$(document).on('click', '.js-image-upload', function (e) {
    +		e.preventDefault();
    +		var $button = $(this);
    +
    +		var file_frame = wp.media.frames.file_frame = wp.media({
    +			title: 'Select or Upload an Image',
    +			library: {
    +				type: 'image' // mime type
    +			},
    +			button: {
    +				text: 'Select Image'
    +			},
    +			multiple: false
    +		});
    +
    +		file_frame.on('select', function() {
    +			var attachment = file_frame.state().get('selection').first().toJSON();
    +			$button.siblings('.image-upload').val(attachment.url);
    +		});
    +
    +		file_frame.open();
    +	});
    +});
    \ No newline at end of file
    diff --git a/Lesson58/src/js/slider.js b/Lesson58/src/js/slider.js
    new file mode 100644
    index 0000000..a09f7fe
    --- /dev/null
    +++ b/Lesson58/src/js/slider.js
    @@ -0,0 +1,41 @@
    +// global variables
    +const sliderView = document.querySelector('.ac-slider--view > ul'),
    +    sliderViewSlides = document.querySelectorAll('.ac-slider--view__slides'),
    +    arrowLeft = document.querySelector('.ac-slider--arrows__left'),
    +    arrowRight = document.querySelector('.ac-slider--arrows__right'),
    +    sliderLength = sliderViewSlides.length;
    +
    +// sliding function
    +const slideMe = (sliderViewItems, isActiveItem) => {
    +    // update the classes
    +    isActiveItem.classList.remove('is-active');
    +    sliderViewItems.classList.add('is-active');
    +
    +    // css transform the active slide position
    +    sliderView.setAttribute('style', 'transform:translateX(-' + sliderViewItems.offsetLeft + 'px)');
    +}
    +
    +// before sliding function
    +const beforeSliding = i => {
    +    let isActiveItem = document.querySelector('.ac-slider--view__slides.is-active'),
    +        currentItem = Array.from(sliderViewSlides).indexOf(isActiveItem) + i,
    +        nextItem = currentItem + i,
    +        sliderViewItems = document.querySelector(`.ac-slider--view__slides:nth-child(${nextItem})`);
    +
    +    // if nextItem is bigger than the # of slides
    +    if (nextItem > sliderLength) {
    +        sliderViewItems = document.querySelector('.ac-slider--view__slides:nth-child(1)');
    +    }
    +
    +    // if nextItem is 0
    +    if (nextItem == 0) {
    +        sliderViewItems = document.querySelector(`.ac-slider--view__slides:nth-child(${sliderLength})`);
    +    }
    +
    +    // trigger the sliding method
    +    slideMe(sliderViewItems, isActiveItem);
    +}
    +
    +// triggers arrows
    +arrowRight.addEventListener('click', () => beforeSliding(1));
    +arrowLeft.addEventListener('click', () => beforeSliding(0));
    \ No newline at end of file
    diff --git a/Lesson58/src/scss/auth.scss b/Lesson58/src/scss/auth.scss
    new file mode 100644
    index 0000000..d99adf0
    --- /dev/null
    +++ b/Lesson58/src/scss/auth.scss
    @@ -0,0 +1,72 @@
    +#alecaddd-auth-form {
    +    position: absolute;
    +    top: 0;
    +    right: 0;
    +    z-index: 10;
    +    overflow: hidden;
    +
    +    .auth-btn {
    +        margin: 20px;
    +        top: 0;
    +        right: 0;
    +        position: absolute;
    +
    +        &.hide {
    +            display: none;
    +        }
    +    }
    +
    +    .auth-container {
    +        transition: transform 200ms ease;
    +        transform: translateX(300px);
    +
    +        width: 220px;
    +        margin: 20px;
    +        position: relative;
    +        background: #fff;
    +        padding: 10px;
    +        border-radius: 4px;
    +
    +        &.show {
    +            transform: translateX(0);
    +        }
    +    }
    +
    +    h2 {
    +        margin-top: 0;
    +        margin-bottom: 10px;
    +        padding-top: 0;
    +        font-size: 16px;
    +    }
    +
    +    .close {
    +        position: absolute;
    +        top: 3px;
    +        right: 10px;
    +        font-size: 16px;
    +    }
    +
    +    label {
    +        font-size: 10px;
    +        text-transform: uppercase;
    +    }
    +
    +    input[type="text"],
    +    input[type="password"] {
    +        margin-bottom: 10px;
    +        padding: 4px;
    +        font-size: 12px;
    +    }
    +
    +    .actions {
    +        text-align: center;
    +        margin-bottom: 0;
    +
    +        a {
    +            font-size: 10px;
    +            text-transform: uppercase;
    +            display: inline-block;
    +            margin: 0 4px;
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/Lesson58/src/scss/form.scss b/Lesson58/src/scss/form.scss
    new file mode 100644
    index 0000000..ec1fd40
    --- /dev/null
    +++ b/Lesson58/src/scss/form.scss
    @@ -0,0 +1,29 @@
    +#alecaddd-testimonial-form {
    +    .field-container {
    +        position: relative;
    +        margin-bottom: 20px;
    +    }
    +
    +    .field-msg {
    +        display: none;
    +        position: absolute;
    +        left: 2px;
    +        bottom: -14px;
    +        font-size: 9px;
    +        text-transform: uppercase;
    +        font-weight: 600;
    +        letter-spacing: 0.05em;
    +
    +        &.show {
    +            display: block;
    +        }
    +    }
    +
    +    .error {
    +        color: rgb(144, 19, 19);
    +    }
    +
    +    .success {
    +        color: rgb(47, 129, 47);
    +    }
    +}
    \ No newline at end of file
    diff --git a/Lesson58/src/scss/modules/checkbox.scss b/Lesson58/src/scss/modules/checkbox.scss
    new file mode 100644
    index 0000000..9557350
    --- /dev/null
    +++ b/Lesson58/src/scss/modules/checkbox.scss
    @@ -0,0 +1,74 @@
    +$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);
    +
    +	&.mb-10 {
    +		margin-bottom: 10px;
    +	}
    +}
    \ No newline at end of file
    diff --git a/Lesson58/src/scss/modules/form.scss b/Lesson58/src/scss/modules/form.scss
    new file mode 100644
    index 0000000..f275545
    --- /dev/null
    +++ b/Lesson58/src/scss/modules/form.scss
    @@ -0,0 +1,30 @@
    +.inline-block {
    +	display: inline-block;
    +}
    +
    +.text-left {
    +	text-align: left;
    +}
    +
    +.text-right {
    +	text-align: right;
    +}
    +
    +.w-50 {
    +	width: 49%;
    +}
    +
    +.inline {
    +	display: inline-block;
    +}
    +
    +.meta-label {
    +	display: inline-block;
    +	font-weight: bold;
    +	margin-bottom: 5px;
    +}
    +
    +.meta-container {
    +	display: block;
    +	margin-top: 20px;
    +}
    \ No newline at end of file
    diff --git a/Lesson58/src/scss/modules/table.scss b/Lesson58/src/scss/modules/table.scss
    new file mode 100644
    index 0000000..762d474
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/src/scss/modules/tabs.scss b/Lesson58/src/scss/modules/tabs.scss
    new file mode 100644
    index 0000000..f2e3131
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/src/scss/mystyle.scss b/Lesson58/src/scss/mystyle.scss
    new file mode 100644
    index 0000000..9de4e0b
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/src/scss/slider.scss b/Lesson58/src/scss/slider.scss
    new file mode 100644
    index 0000000..4311354
    --- /dev/null
    +++ b/Lesson58/src/scss/slider.scss
    @@ -0,0 +1,74 @@
    +.ac-slider--wrapper {
    +	margin: 0 auto;
    +	max-width: calc(90% - 150px);
    +}
    +
    +.ac-slider--container {
    +	position: relative;
    +	width: 100%;
    +	.ac-slider--view {
    +		display: block;
    +		width: 100%;
    +		margin: 0 auto;
    +		overflow: hidden;
    +		ul {
    +			white-space: nowrap;
    +			text-align: center;
    +			list-style: none;
    +			margin: 0;
    +			padding: 0;
    +			transition: transform 0.25s ease-out;
    +			.ac-slider--view__slides {
    +				white-space: normal;
    +				display: inline-block;
    +				width: 100%;
    +				position: relative;
    +			}
    +		}
    +	}
    +	.ac-slider--arrows {
    +		width: 100%;
    +		margin: 0 auto;
    +		span {
    +			position: absolute;
    +			top: 50%;
    +			transform: translateX(0) translateY(-50%);
    +			width: 20px;
    +			height: 20px;
    +			display: block;
    +			cursor: pointer;
    +			border-radius: 50%;
    +			background: #f5f5f5;
    +			line-height: 1.3em;
    +			text-align: center;
    +			opacity: 0.5;
    +			&.ac-slider--arrows__left {
    +				left: 0;
    +				transform: translateX(-100%) translateY(-50%);
    +			}
    +			&.ac-slider--arrows__right {
    +				right: 0;
    +				transform: translateX(100%) translateY(-50%);
    +			}
    +			&:hover {
    +				opacity: 1;
    +			}
    +		}
    +	}
    +}
    +
    +.testimonial-quote {
    +	font-style: italic;
    +	font-weight: 200;
    +	font-size: 0.9em;
    +	letter-spacing: 0.05em;
    +	position: relative;
    +	margin-bottom: 0.5em;
    +	padding: 0 10px;
    +}
    +
    +.testimonial-author {
    +	font-size: 0.6em;
    +	font-weight: 800;
    +	margin-bottom: 0.5em;
    +}
    \ No newline at end of file
    diff --git a/Lesson58/templates/admin.php b/Lesson58/templates/admin.php
    new file mode 100644
    index 0000000..da6529a
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/templates/auth.php b/Lesson58/templates/auth.php
    new file mode 100644
    index 0000000..f64cbc5
    --- /dev/null
    +++ b/Lesson58/templates/auth.php
    @@ -0,0 +1,21 @@
    +<form id="alecaddd-auth-form" action="#" method="post" data-url="<?php echo admin_url('admin-ajax.php'); ?>">
    +    <div class="auth-btn">
    +        <input class="submit_button" type="button" value="Login" id="alecaddd-show-auth-form">
    +    </div>
    +    <div id="alecaddd-auth-container" class="auth-container">
    +        <a id="alecaddd-auth-close" class="close" href="#">&times;</a>
    +        <h2>Site Login</h2>
    +        <label for="username">Username</label>
    +        <input id="username" type="text" name="username">
    +        <label for="password">Password</label>
    +        <input id="password" type="password" name="password">
    +        <input class="submit_button" type="submit" value="Login" name="submit">
    +        <p class="status"></p>
    +
    +        <p class="actions">
    +            <a href="<?php echo wp_lostpassword_url(); ?>">Forgot Password?</a> - <a href="<?php echo wp_registration_url(); ?>">Register</a>
    +        </p>
    +        
    +        <?php wp_nonce_field( 'ajax-login-nonce', 'alecaddd_auth' ); ?>
    +    </div>
    +</form>
    \ No newline at end of file
    diff --git a/Lesson58/templates/contact-form.php b/Lesson58/templates/contact-form.php
    new file mode 100644
    index 0000000..2b3c9aa
    --- /dev/null
    +++ b/Lesson58/templates/contact-form.php
    @@ -0,0 +1,30 @@
    +<form id="alecaddd-testimonial-form" action="#" method="post" data-url="<?php echo admin_url('admin-ajax.php'); ?>">
    +
    +	<div class="field-container">
    +		<input type="text" class="field-input" placeholder="Your Name" id="name" name="name" required>
    +		<small class="field-msg error" data-error="invalidName">Your Name is Required</small>
    +	</div>
    +
    +	<div class="field-container">
    +		<input type="email" class="field-input" placeholder="Your Email" id="email" name="email" required>
    +		<small class="field-msg error" data-error="invalidEmail">The Email address is not valid</small>
    +	</div>
    +
    +	<div class="field-container">
    +		<textarea name="message" id="message" class="field-input" placeholder="Your Message" required></textarea>
    +		<small class="field-msg error" data-error="invalidMessage">A Message is Required</small>
    +	</div>
    +	
    +	<div class="field-container">
    +		<div>
    +            <button type="stubmit" class="btn btn-default btn-lg btn-sunset-form">Submit</button>
    +        </div>
    +		<small class="field-msg js-form-submission">Submission in process, please wait&hellip;</small>
    +		<small class="field-msg success js-form-success">Message Successfully submitted, thank you!</small>
    +		<small class="field-msg error js-form-error">There was a problem with the Contact Form, please try again!</small>
    +	</div>
    +
    +	<input type="hidden" name="action" value="submit_testimonial">
    +	<input type="hidden" name="nonce" value="<?php echo wp_create_nonce("testimonial-nonce") ?>">
    +
    +</form>
    \ No newline at end of file
    diff --git a/Lesson58/templates/cpt.php b/Lesson58/templates/cpt.php
    new file mode 100644
    index 0000000..7b7c7a0
    --- /dev/null
    +++ b/Lesson58/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/Lesson58/templates/slider.php b/Lesson58/templates/slider.php
    new file mode 100644
    index 0000000..af44500
    --- /dev/null
    +++ b/Lesson58/templates/slider.php
    @@ -0,0 +1,35 @@
    +<?php
    +
    +$args = array(
    +    'post_type' => 'testimonial',
    +    'post_status' => 'publish',
    +    'posts_per_page' => 5,
    +    'meta_query' => array(
    +        array(
    +            'key' => '_alecaddd_testimonial_key',
    +            'value' => 's:8:"approved";i:1;s:8:"featured";i:1;',
    +            'compare' => 'LIKE'
    +        )
    +    )
    +);
    +
    +$query = new WP_Query( $args );
    +
    +if ($query->have_posts()) :
    +	$i = 1;
    +
    +	echo '<div class="ac-slider--wrapper"><div class="ac-slider--container"><div class="ac-slider--view"><ul>';
    +
    +	while ($query->have_posts()) : $query->the_post();
    +		$name = get_post_meta( get_the_ID(), '_alecaddd_testimonial_key', true )['name'] ?? '';
    +
    +		echo '<li class="ac-slider--view__slides' . ($i === 1 ? ' is-active' : '' ) . '"><p class="testimonial-quote">"'.get_the_content().'"</p><p class="testimonial-author">~ '.$name.' ~</p></li>';
    +		
    +		$i++;
    +	endwhile;
    +
    +	echo '</ul></div><div class="ac-slider--arrows"><span class="arrow ac-slider--arrows__left">&#x3c;</span><span class="arrow ac-slider--arrows__right">&#x3e;</span></div></div></div>';
    +
    +endif;
    +
    +wp_reset_postdata();
    \ No newline at end of file
    diff --git a/Lesson58/templates/taxonomy.php b/Lesson58/templates/taxonomy.php
    new file mode 100644
    index 0000000..a665eb4
    --- /dev/null
    +++ b/Lesson58/templates/taxonomy.php
    @@ -0,0 +1,64 @@
    +<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_tax' ) ?: array();
    +
    +				echo '<table class="cpt-table"><tr><th>ID</th><th>Singular Name</th><th class="text-center">Hierarchical</th><th class="text-center">Actions</th></tr>';
    +
    +				foreach ($options as $option) {
    +					$hierarchical = isset($option['hierarchical']) ? "TRUE" : "FALSE";
    +
    +					echo "<tr><td>{$option['taxonomy']}</td><td>{$option['singular_name']}</td><td class=\"text-center\">{$hierarchical}</td><td class=\"text-center\">";
    +
    +					echo '<form method="post" action="" class="inline-block">';
    +					echo '<input type="hidden" name="edit_taxonomy" value="' . $option['taxonomy'] . '">';
    +					submit_button( 'Edit', 'primary small', 'submit', false);
    +					echo '</form> ';
    +
    +					echo '<form method="post" action="options.php" class="inline-block">';
    +					settings_fields( 'alecaddd_plugin_tax_settings' );
    +					echo '<input type="hidden" name="remove" value="' . $option['taxonomy'] . '">';
    +					submit_button( 'Delete', 'delete small', 'submit', false, array(
    +						'onclick' => 'return confirm("Are you sure you want to delete this Custom Taxonomy? 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/Lesson58/templates/testimonial.php b/Lesson58/templates/testimonial.php
    new file mode 100644
    index 0000000..ebdb877
    --- /dev/null
    +++ b/Lesson58/templates/testimonial.php
    @@ -0,0 +1,9 @@
    +<div class="wrap">
    +    <h1>Testimonial Shortcode</h1>
    +
    +    <p>Testimonial Form Shortcode</p>
    +    <p><code>[testimonial-form]</code></p>
    +
    +    <p>Testimonial SlideShow Shortcode</p>
    +    <p><code>[testimonial-slideshow]</code></p>
    +</div>
    \ No newline at end of file
    diff --git a/Lesson58/templates/widget.php b/Lesson58/templates/widget.php
    new file mode 100644
    index 0000000..006722d
    --- /dev/null
    +++ b/Lesson58/templates/widget.php
    @@ -0,0 +1 @@
    +<h1>Widgets Manager</h1>
    \ No newline at end of file
    diff --git a/Lesson58/uninstall.php b/Lesson58/uninstall.php
    new file mode 100644
    index 0000000..69fb64b
    --- /dev/null
    +++ b/Lesson58/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