WP Recipe Maker - Version 1.0.0

Version Description

  • Very first version of this recipe plugin
  • Feature: JSON-LD Metadata
  • Feature: Intuitive workflow using regular posts or pages
  • Feature: Import from EasyRecipe
  • Feature: Clean printing of recipes
  • Feature: Fallback recipe when the plugin is disabled

=

Download this release

Release Info

Developer BrechtVds
Plugin Icon 128x128 WP Recipe Maker
Version 1.0.0
Comparing to
See all releases

Version 1.0.0

Files changed (74) hide show
  1. assets/css/admin/faq.min.css +1 -0
  2. assets/css/admin/faq.scss +5 -0
  3. assets/css/admin/import.min.css +1 -0
  4. assets/css/admin/import.scss +14 -0
  5. assets/css/admin/modal.min.css +1 -0
  6. assets/css/admin/modal.scss +3 -0
  7. assets/css/admin/modal/_container.scss +363 -0
  8. assets/css/admin/modal/_recipe-details.scss +87 -0
  9. assets/css/admin/modal/_recipe-ingredients.scss +119 -0
  10. assets/icons/clock.svg +5 -0
  11. assets/icons/cutlery.svg +6 -0
  12. assets/icons/knife.svg +7 -0
  13. assets/icons/pan.svg +7 -0
  14. assets/icons/printer.svg +5 -0
  15. assets/icons/tag.svg +6 -0
  16. assets/js/admin/import.js +17 -0
  17. assets/js/admin/modal.js +85 -0
  18. assets/js/admin/recipe-form.js +477 -0
  19. assets/js/admin/shortcode-tinymce.js +76 -0
  20. assets/js/admin/shortcode.js +15 -0
  21. assets/js/print.js +42 -0
  22. includes/admin/class-wprm-import-manager.php +231 -0
  23. includes/admin/class-wprm-recipe-parser.php +188 -0
  24. includes/admin/class-wprm-recipe-sanitizer.php +152 -0
  25. includes/admin/class-wprm-recipe-saver.php +155 -0
  26. includes/admin/import/class-wprm-import-easyrecipe.php +487 -0
  27. includes/admin/import/class-wprm-import.php +58 -0
  28. includes/admin/menu/class-wprm-admin-menu-faq.php +70 -0
  29. includes/admin/menu/class-wprm-admin-menu.php +43 -0
  30. includes/admin/modal/class-wprm-button.php +48 -0
  31. includes/admin/modal/class-wprm-modal.php +156 -0
  32. includes/admin/modal/class-wprm-shortcode-preview.php +72 -0
  33. includes/class-wp-recipe-maker.php +90 -0
  34. includes/class-wprm-activator.php +35 -0
  35. includes/class-wprm-deactivator.php +32 -0
  36. includes/class-wprm-i18n.php +48 -0
  37. includes/index.php +1 -0
  38. includes/public/class-wprm-fallback-recipe.php +108 -0
  39. includes/public/class-wprm-metadata.php +121 -0
  40. includes/public/class-wprm-post-type.php +75 -0
  41. includes/public/class-wprm-print.php +77 -0
  42. includes/public/class-wprm-recipe-manager.php +109 -0
  43. includes/public/class-wprm-recipe.php +329 -0
  44. includes/public/class-wprm-shortcode.php +57 -0
  45. includes/public/class-wprm-taxonomies.php +109 -0
  46. includes/public/class-wprm-template-manager.php +79 -0
  47. index.php +1 -0
  48. readme.txt +80 -0
  49. templates/admin/menu/faq.php +32 -0
  50. templates/admin/menu/import-overview.php +90 -0
  51. templates/admin/menu/import-recipes.php +51 -0
  52. templates/admin/menu/support-widget.html +4 -0
  53. templates/admin/modal/modal.php +101 -0
  54. templates/admin/modal/tabs/recipe-details.php +73 -0
  55. templates/admin/modal/tabs/recipe-ingredients-instructions.php +85 -0
  56. templates/admin/modal/tabs/recipe-notes.php +24 -0
  57. templates/public/fallback-recipe.php +99 -0
  58. templates/recipe/clean-print/clean-print.min.css +1 -0
  59. templates/recipe/clean-print/clean-print.php +135 -0
  60. templates/recipe/clean-print/clean-print.scss +72 -0
  61. templates/recipe/simple/simple.min.css +1 -0
  62. templates/recipe/simple/simple.php +142 -0
  63. templates/recipe/simple/simple.scss +114 -0
  64. vendor/medium-editor/LICENSE +36 -0
  65. vendor/medium-editor/README.md +710 -0
  66. vendor/medium-editor/css/medium-editor.min.css +1 -0
  67. vendor/medium-editor/css/themes/beagle.min.css +1 -0
  68. vendor/medium-editor/js/medium-editor.min.js +4 -0
  69. vendor/select2/LICENSE.md +21 -0
  70. vendor/select2/README.md +121 -0
  71. vendor/select2/css/select2.min.css +1 -0
  72. vendor/select2/js/select2.min.js +3 -0
  73. vendor/simple_html_dom/simple_html_dom.php +1721 -0
  74. wp-recipe-maker.php +71 -0
assets/css/admin/faq.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .wprm-faq p{max-width:600px}
assets/css/admin/faq.scss ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ .wprm-faq {
2
+ p {
3
+ max-width: 600px;
4
+ }
5
+ }
assets/css/admin/import.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .wprm-import p{max-width:600px}.wprm-import .wprm-import-recipes{width:100%}.wprm-import .wprm-import-recipes td:nth-child(1){width:2%}
assets/css/admin/import.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wprm-import {
2
+ p {
3
+ max-width: 600px;
4
+ }
5
+ .wprm-import-recipes {
6
+ width: 100%;
7
+
8
+ td {
9
+ &:nth-child(1) {
10
+ width: 2%;
11
+ }
12
+ }
13
+ }
14
+ }
assets/css/admin/modal.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .select2_wprm-container{z-index:100075}.select2_wprm-container.select2_wprm-container--focus .select2_wprm-selection{border-color:#5b9dd9}.select2_wprm-container li{margin:0}.select2_wprm-container .select2_wprm-selection{border-color:#ddd}.medium-editor-toolbar{z-index:100080}.medium-editor-element{border:1px solid #ddd;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.07);box-shadow:inset 0 1px 2px rgba(0,0,0,0.07);min-height:68px;padding:3px 5px}.medium-editor-element:focus{border-color:#5b9dd9;-webkit-box-shadow:0 0 2px rgba(30,140,190,0.8);box-shadow:0 0 2px rgba(30,140,190,0.8)}.medium-editor-element:after{font-style:normal;color:#999;opacity:0.5;font-size:14px;line-height:28px}.medium-editor-element p:first-child{margin-top:0}.medium-editor-element p:last-child{margin-bottom:0}.wprm-modal-container{display:none}.wprm-modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;min-height:360px;background:#000;opacity:0.7;z-index:100000}.wprm-modal{position:fixed;top:30px;left:30px;right:30px;bottom:30px;z-index:100050}.wprm-modal *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.wprm-modal-close{position:absolute;top:0;right:0;width:50px;height:50px;padding:0;z-index:1000;-webkit-transition:color 0.1s ease-in-out,background 0.1s ease-in-out;transition:color 0.1s ease-in-out,background 0.1s ease-in-out}.wprm-modal-close .wprm-modal-icon:before{content:"\f158";font:400 20px/1 dashicons;speak:none;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#666}.wprm-modal-content{position:absolute;top:0;left:0;right:0;bottom:0;overflow:auto;min-height:300px;-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.7);box-shadow:0 5px 15px rgba(0,0,0,0.7);background:#fcfcfc;-webkit-font-smoothing:subpixel-antialiased}.wprm-frame{overflow:hidden;right:0}.wprm-frame,.wprm-frame-menu{position:absolute;left:0;bottom:0;top:0}.wprm-frame-menu{width:200px;z-index:150}.wprm-frame-title .dashicons,.wprm-frame.hide-router .wprm-frame-router{display:none}.wprm-frame-title{top:0;height:50px}.wprm-frame-router,.wprm-frame-title{z-index:200;left:200px;position:absolute;right:0}.wprm-menu-item{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.wprm-menu{position:absolute;left:0;margin:0;padding:10px 0;border-right-width:1px;border-right-style:solid;border-right-color:#ccc;user-select:none}.wprm-menu,.wprm-sidebar{top:0;bottom:0;background:#f3f3f3;right:0}.wprm-menu .active,.wprm-menu .active:hover{color:#23282d;font-weight:700}.wprm-menu>a{display:block;position:relative;padding:8px 20px;margin:0;color:#0073aa}.wprm-menu>a,.wprm-router>a{line-height:18px;font-size:14px;text-decoration:none}.wprm-frame a{border-bottom:none;color:#0073aa}.wprm-frame .hidden{display:none}.wprm-menu .separator{height:0;margin:12px 20px;padding:0;border-top:1px solid #ddd}.wprm-frame-title h1{padding:0 16px;font-size:22px;line-height:50px;margin:0}.wprm-frame-router{top:50px;height:36px}.wprm-router{position:relative;padding:0 6px;margin:0;clear:both;user-select:none}.wprm-router:not(.active){display:none}.wprm-router>a{position:relative;float:left;padding:8px 10px 9px;margin:0;height:18px}.wprm-router a{-webkit-transition:none;transition:none}.wprm-router>a:last-child{border-right:0}.wprm-router .active,.wprm-router>a.active:last-child{margin:-1px -1px 0;background:#fff;border:1px solid #ddd;border-bottom:none}.wprm-router .active,.wprm-router .active:hover{color:#32373c}.wprm-frame-content{position:absolute;top:84px;left:200px;right:0;bottom:61px;height:auto;width:auto;margin:0;overflow:auto;background:#fff;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.wprm-frame-content-tab{margin:20px}.wprm-frame-content-tab:not(.active){display:none}.wprm-frame-toolbar{position:absolute;left:200px;right:0;bottom:0;height:60px;z-index:100}.wprm-toolbar{position:absolute;top:0;left:0;right:0;z-index:100;height:60px;padding:0 16px;border:0 solid #ddd;overflow:hidden}.wprm-modal-content .wprm-toolbar-primary.search-form{width:33%}.wprm-toolbar-primary{float:right;height:100%}.wprm-modal-content .wprm-toolbar-primary .wprm-button{float:right}.wprm-toolbar-primary>.wprm-button,.wprm-toolbar-primary>.wprm-button-group{margin-left:10px;float:left;margin-top:15px}@media only screen and (max-width: 900px){.wprm-frame:not(.hide-menu) .wprm-frame-title .dashicons{display:inline-block;line-height:50px}.wprm-frame:not(.hide-menu) .wprm-frame-menu{position:static;width:0}.wprm-frame:not(.hide-menu) .wprm-frame-title{left:0}.wprm-frame:not(.hide-menu) .wprm-frame-content,.wprm-frame:not(.hide-menu) .wprm-frame-router,.wprm-frame:not(.hide-menu) .wprm-frame-title,.wprm-frame:not(.hide-menu) .wprm-frame-toolbar{left:0}.wprm-frame:not(.hide-menu) .wprm-menu.visible{left:0}.wprm-frame:not(.hide-menu) .wprm-menu{width:auto;max-width:80%;overflow:auto;z-index:2000;top:50px;left:-300px;right:auto;bottom:auto;padding:5px 0;border:1px solid #ccc}.wprm-frame:not(.hide-menu) .wprm-menu>a.active{display:none}.wprm-frame:not(.hide-menu) .wprm-menu>a{padding:12px 16px;font-size:16px}.wprm-frame:not(.hide-menu) .wprm-menu .separator{margin:5px 10px}.wprm-frame:not(.hide-menu) .wprm-frame-title h1{color:#0073aa;line-height:3;font-size:18px;float:left;cursor:pointer}}@media only screen and (max-width: 640px), screen and (max-height: 400px){.wprm-modal{position:fixed;top:0;left:0;right:0;bottom:0}.wprm-modal .wprm-frame-title{height:40px}}@media only screen and (max-width: 480px){.wprm-frame:not(.hide-menu) .wprm-frame-title .dashicons{line-height:40px}.wprm-frame-router,.wprm-frame:not(.hide-menu) .wprm-menu{top:40px}.wprm-frame:not(.hide-menu) .wprm-frame-title h1,.wprm-modal .wprm-frame-title h1{font-size:18px;line-height:40px}.wprm-frame-content{top:74px}}.wprm-recipe-details-form,.wprm-recipe-notes-form{max-width:600px}.wprm-recipe-details-form .wprm-recipe-details-form-container,.wprm-recipe-details-form .wprm-recipe-notes-form-container,.wprm-recipe-notes-form .wprm-recipe-details-form-container,.wprm-recipe-notes-form .wprm-recipe-notes-form-container{margin-bottom:15px;vertical-align:top}.wprm-recipe-details-form .wprm-recipe-details-form-container-halfs,.wprm-recipe-notes-form .wprm-recipe-details-form-container-halfs{display:inline-block;width:50%}.wprm-recipe-details-form .wprm-recipe-details-form-container-thirds,.wprm-recipe-notes-form .wprm-recipe-details-form-container-thirds{display:inline-block;width:33.3%}.wprm-recipe-details-form label,.wprm-recipe-notes-form label{display:block;font-weight:bold;margin-bottom:5px}.wprm-recipe-details-form input,.wprm-recipe-notes-form input{margin:0;width:100%;max-width:120px;height:34px;line-height:34px}.wprm-recipe-details-form input#wprm-recipe-name,.wprm-recipe-notes-form input#wprm-recipe-name{max-width:450px}.wprm-recipe-details-form input[type="number"],.wprm-recipe-notes-form input[type="number"]{max-width:50px}.wprm-recipe-details-form input.select2_wprm-search__field,.wprm-recipe-notes-form input.select2_wprm-search__field{height:18px;line-height:18px}.wprm-recipe-details-form select,.wprm-recipe-notes-form select{width:100%}.wprm-recipe-details-form textarea,.wprm-recipe-notes-form textarea{width:100%;resize:vertical}.wprm-recipe-details-form ::-webkit-input-placeholder,.wprm-recipe-notes-form ::-webkit-input-placeholder{color:#999;opacity:0.5}.wprm-recipe-details-form :-moz-placeholder,.wprm-recipe-notes-form :-moz-placeholder{color:#999;opacity:0.5}.wprm-recipe-details-form ::-moz-placeholder,.wprm-recipe-notes-form ::-moz-placeholder{color:#999;opacity:0.5}.wprm-recipe-details-form :-ms-input-placeholder,.wprm-recipe-notes-form :-ms-input-placeholder{color:#999;opacity:0.5}.wprm-recipe-details-form .wprm-recipe-image-preview,.wprm-recipe-notes-form .wprm-recipe-image-preview{float:right;max-width:100px}.wprm-recipe-details-form .wprm-recipe-image-preview img,.wprm-recipe-notes-form .wprm-recipe-image-preview img{max-width:100%;height:auto}.wprm-recipe-details-form .wprm-recipe-summary-container,.wprm-recipe-notes-form .wprm-recipe-summary-container{clear:both}@media only screen and (max-width: 480px){.wprm-recipe-details-form .wprm-recipe-details-form-container-halfs,.wprm-recipe-details-form .wprm-recipe-details-form-container-thirds{display:block;width:100%}.wprm-recipe-details-form .wprm-recipe-image-preview{float:none;margin:10px auto}}.wprm-recipe-ingredients-form,.wprm-recipe-instructions-form{max-width:750px;margin-bottom:15px}.wprm-recipe-ingredients-form input,.wprm-recipe-instructions-form input{margin:0;width:100%;height:34px;line-height:34px}.wprm-recipe-ingredients-form textarea,.wprm-recipe-instructions-form textarea{width:100%;resize:vertical}.wprm-recipe-ingredients-form ::-webkit-input-placeholder,.wprm-recipe-instructions-form ::-webkit-input-placeholder{color:#999;opacity:0.5}.wprm-recipe-ingredients-form :-moz-placeholder,.wprm-recipe-instructions-form :-moz-placeholder{color:#999;opacity:0.5}.wprm-recipe-ingredients-form ::-moz-placeholder,.wprm-recipe-instructions-form ::-moz-placeholder{color:#999;opacity:0.5}.wprm-recipe-ingredients-form :-ms-input-placeholder,.wprm-recipe-instructions-form :-ms-input-placeholder{color:#999;opacity:0.5}.wprm-recipe-ingredients-form table,.wprm-recipe-instructions-form table{width:100%}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th,.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th{text-align:left}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(1),.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(6),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(1),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(6){width:5%;text-align:center}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(2),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(2){width:10%}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(3),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(3){width:15%}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(4),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(4){width:40%}.wprm-recipe-ingredients-form table.wprm-recipe-ingredients-container th:nth-child(5),.wprm-recipe-instructions-form table.wprm-recipe-ingredients-container th:nth-child(5){width:25%}.wprm-recipe-ingredients-form table.wprm-recipe-instructions-container th,.wprm-recipe-instructions-form table.wprm-recipe-instructions-container th{text-align:left}.wprm-recipe-ingredients-form table.wprm-recipe-instructions-container th:nth-child(1),.wprm-recipe-ingredients-form table.wprm-recipe-instructions-container th:nth-child(4),.wprm-recipe-instructions-form table.wprm-recipe-instructions-container th:nth-child(1),.wprm-recipe-instructions-form table.wprm-recipe-instructions-container th:nth-child(4){width:5%;text-align:center}.wprm-recipe-ingredients-form table.wprm-recipe-instructions-container th:nth-child(2),.wprm-recipe-instructions-form table.wprm-recipe-instructions-container th:nth-child(2){width:65%}.wprm-recipe-ingredients-form table.wprm-recipe-instructions-container th:nth-child(3),.wprm-recipe-instructions-form table.wprm-recipe-instructions-container th:nth-child(3){width:25%}.wprm-recipe-ingredients-form table .wprm-recipe-ingredients-placeholder,.wprm-recipe-ingredients-form table .wprm-recipe-instructions-placeholder,.wprm-recipe-instructions-form table .wprm-recipe-ingredients-placeholder,.wprm-recipe-instructions-form table .wprm-recipe-instructions-placeholder{display:none}.wprm-recipe-ingredients-form table td,.wprm-recipe-instructions-form table td{text-align:left;vertical-align:top}.wprm-recipe-ingredients-form table td:first-child,.wprm-recipe-ingredients-form table td:last-child,.wprm-recipe-instructions-form table td:first-child,.wprm-recipe-instructions-form table td:last-child{text-align:center;vertical-align:middle}.wprm-recipe-ingredients-form .ui-sortable-helper,.wprm-recipe-instructions-form .ui-sortable-helper{display:table}.wprm-recipe-ingredients-form .ui-sortable-helper .wprm-recipe-instruction-text,.wprm-recipe-instructions-form .ui-sortable-helper .wprm-recipe-instruction-text{min-width:200px}.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-sort,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-sort{cursor:move}.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-delete,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-delete{cursor:pointer}.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-delete,.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-sort,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-delete,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-sort{color:#999;opacity:0.5}.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-delete:hover,.wprm-recipe-ingredients-form .wprm-recipe-ingredients-instructions-sort:hover,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-delete:hover,.wprm-recipe-instructions-form .wprm-recipe-ingredients-instructions-sort:hover{color:#444;opacity:1}.wprm-recipe-ingredients-form .wprm-recipe-ingredients-actions,.wprm-recipe-ingredients-form .wprm-recipe-instructions-actions,.wprm-recipe-instructions-form .wprm-recipe-ingredients-actions,.wprm-recipe-instructions-form .wprm-recipe-instructions-actions{margin:10px}.wprm-recipe-ingredients-form .wprm-recipe-image-preview,.wprm-recipe-instructions-form .wprm-recipe-image-preview{max-width:75px}.wprm-recipe-ingredients-form .wprm-recipe-image-preview img,.wprm-recipe-instructions-form .wprm-recipe-image-preview img{max-width:100%;height:auto}
assets/css/admin/modal.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ @import "modal/container";
2
+ @import "modal/recipe-details";
3
+ @import "modal/recipe-ingredients";
assets/css/admin/modal/_container.scss ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .select2_wprm-container {
2
+ z-index: 100075;
3
+ &.select2_wprm-container--focus .select2_wprm-selection {
4
+ border-color: #5b9dd9;
5
+ }
6
+ li {
7
+ margin: 0;
8
+ }
9
+ .select2_wprm-selection {
10
+ border-color: #ddd;
11
+ }
12
+ }
13
+ .medium-editor-toolbar {
14
+ z-index: 100080;
15
+ }
16
+ .medium-editor-element {
17
+ border: 1px solid #ddd;
18
+ -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
19
+ box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
20
+ min-height: 68px;
21
+ padding: 3px 5px;
22
+ &:focus {
23
+ border-color: #5b9dd9;
24
+ -webkit-box-shadow: 0 0 2px rgba(30,140,190,.8);
25
+ box-shadow: 0 0 2px rgba(30,140,190,.8);
26
+ }
27
+ &:after {
28
+ font-style: normal;
29
+ color: #999;
30
+ opacity: 0.5;
31
+ font-size: 14px;
32
+ line-height: 28px;
33
+ }
34
+ p:first-child {
35
+ margin-top: 0;
36
+ }
37
+ p:last-child {
38
+ margin-bottom: 0;
39
+ }
40
+ }
41
+ .wprm-modal-container {
42
+ display: none;
43
+ }
44
+ .wprm-modal-backdrop {
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ right: 0;
49
+ bottom: 0;
50
+ min-height: 360px;
51
+ background: #000;
52
+ opacity: 0.7;
53
+ z-index: 100000;
54
+ }
55
+ .wprm-modal {
56
+ position: fixed;
57
+ top: 30px;
58
+ left: 30px;
59
+ right: 30px;
60
+ bottom: 30px;
61
+ z-index: 100050;
62
+ * {
63
+ -webkit-box-sizing: border-box;
64
+ -moz-box-sizing: border-box;
65
+ box-sizing: border-box;
66
+ }
67
+ }
68
+ .wprm-modal-close {
69
+ position: absolute;
70
+ top: 0;
71
+ right: 0;
72
+ width: 50px;
73
+ height: 50px;
74
+ padding: 0;
75
+ z-index: 1000;
76
+ -webkit-transition: color 0.1s ease-in-out,background 0.1s ease-in-out;
77
+ transition: color 0.1s ease-in-out,background 0.1s ease-in-out;
78
+ .wprm-modal-icon:before {
79
+ content: "\f158";
80
+ font: 400 20px/1 dashicons;
81
+ speak: none;
82
+ vertical-align: middle;
83
+ -webkit-font-smoothing: antialiased;
84
+ -moz-osx-font-smoothing: grayscale;
85
+ color: #666;
86
+ }
87
+ }
88
+ .wprm-modal-content {
89
+ position: absolute;
90
+ top: 0;
91
+ left: 0;
92
+ right: 0;
93
+ bottom: 0;
94
+ overflow: auto;
95
+ min-height: 300px;
96
+ -webkit-box-shadow: 0 5px 15px rgba(0,0,0,.7);
97
+ box-shadow: 0 5px 15px rgba(0,0,0,.7);
98
+ background: #fcfcfc;
99
+ -webkit-font-smoothing: subpixel-antialiased;
100
+ }
101
+ .wprm-frame {
102
+ overflow: hidden;
103
+ right: 0;
104
+ }
105
+ .wprm-frame,
106
+ .wprm-frame-menu {
107
+ position: absolute;
108
+ left: 0;
109
+ bottom: 0;
110
+ top: 0;
111
+ }
112
+ .wprm-frame-menu {
113
+ width: 200px;
114
+ z-index: 150;
115
+ }
116
+ .wprm-frame-title .dashicons,
117
+ .wprm-frame.hide-router .wprm-frame-router {
118
+ display: none;
119
+ }
120
+ .wprm-frame-title {
121
+ top: 0;
122
+ height: 50px;
123
+ }
124
+ .wprm-frame-router,
125
+ .wprm-frame-title {
126
+ z-index: 200;
127
+ left: 200px;
128
+ position: absolute;
129
+ right: 0;
130
+ }
131
+ .wprm-menu-item {
132
+ -webkit-box-sizing: content-box;
133
+ -moz-box-sizing: content-box;
134
+ box-sizing: content-box;
135
+ }
136
+ .wprm-menu {
137
+ position: absolute;
138
+ left: 0;
139
+ margin: 0;
140
+ padding: 10px 0;
141
+ border-right-width: 1px;
142
+ border-right-style: solid;
143
+ border-right-color: #ccc;
144
+ user-select: none;
145
+ }
146
+ .wprm-menu,
147
+ .wprm-sidebar {
148
+ top: 0;
149
+ bottom: 0;
150
+ background: #f3f3f3;
151
+ right: 0;
152
+ }
153
+ .wprm-menu .active,
154
+ .wprm-menu .active:hover {
155
+ color: #23282d;
156
+ font-weight: 700;
157
+ }
158
+ .wprm-menu > a {
159
+ display: block;
160
+ position: relative;
161
+ padding: 8px 20px;
162
+ margin: 0;
163
+ color: #0073aa;
164
+ }
165
+ .wprm-menu > a,
166
+ .wprm-router > a {
167
+ line-height: 18px;
168
+ font-size: 14px;
169
+ text-decoration: none;
170
+ }
171
+ .wprm-frame a {
172
+ border-bottom: none;
173
+ color: #0073aa;
174
+ }
175
+ .wprm-frame .hidden {
176
+ display: none;
177
+ }
178
+ .wprm-menu .separator {
179
+ height: 0;
180
+ margin: 12px 20px;
181
+ padding: 0;
182
+ border-top: 1px solid #ddd;
183
+ }
184
+ .wprm-frame-title h1 {
185
+ padding: 0 16px;
186
+ font-size: 22px;
187
+ line-height: 50px;
188
+ margin: 0;
189
+ }
190
+ .wprm-frame-router {
191
+ top: 50px;
192
+ height: 36px;
193
+ }
194
+ .wprm-router {
195
+ position: relative;
196
+ padding: 0 6px;
197
+ margin: 0;
198
+ clear: both;
199
+ user-select: none;
200
+ &:not(.active) {
201
+ display: none;
202
+ }
203
+ }
204
+ .wprm-router > a {
205
+ position: relative;
206
+ float: left;
207
+ padding: 8px 10px 9px;
208
+ margin: 0;
209
+ height: 18px;
210
+ }
211
+ .wprm-router a {
212
+ -webkit-transition: none;
213
+ transition: none;
214
+ }
215
+ .wprm-router > a:last-child {
216
+ border-right: 0;
217
+ }
218
+ .wprm-router .active,
219
+ .wprm-router > a.active:last-child {
220
+ margin: -1px -1px 0;
221
+ background: #fff;
222
+ border: 1px solid #ddd;
223
+ border-bottom: none;
224
+ }
225
+ .wprm-router .active,
226
+ .wprm-router .active:hover {
227
+ color: #32373c;
228
+ }
229
+ .wprm-frame-content {
230
+ position: absolute;
231
+ top: 84px;
232
+ left: 200px;
233
+ right: 0;
234
+ bottom: 61px;
235
+ height: auto;
236
+ width: auto;
237
+ margin: 0;
238
+ overflow: auto;
239
+ background: #fff;
240
+ border-top: 1px solid #ddd;
241
+ border-bottom: 1px solid #ddd;
242
+ }
243
+ .wprm-frame-content-tab {
244
+ margin: 20px;
245
+ &:not(.active) {
246
+ display: none;
247
+ }
248
+ }
249
+ .wprm-frame-toolbar {
250
+ position: absolute;
251
+ left: 200px;
252
+ right: 0;
253
+ bottom: 0;
254
+ height: 60px;
255
+ z-index: 100;
256
+ }
257
+ .wprm-toolbar {
258
+ position: absolute;
259
+ top: 0;
260
+ left: 0;
261
+ right: 0;
262
+ z-index: 100;
263
+ height: 60px;
264
+ padding: 0 16px;
265
+ border: 0 solid #ddd;
266
+ overflow: hidden;
267
+ }
268
+ .wprm-modal-content .wprm-toolbar-primary.search-form {
269
+ width: 33%;
270
+ }
271
+ .wprm-toolbar-primary {
272
+ float: right;
273
+ height: 100%;
274
+ }
275
+ .wprm-modal-content .wprm-toolbar-primary .wprm-button {
276
+ float: right;
277
+ }
278
+ .wprm-toolbar-primary > .wprm-button,
279
+ .wprm-toolbar-primary > .wprm-button-group {
280
+ margin-left: 10px;
281
+ float: left;
282
+ margin-top: 15px;
283
+ }
284
+ @media only screen and (max-width: 900px) {
285
+ .wprm-frame:not(.hide-menu) .wprm-frame-title .dashicons {
286
+ display: inline-block;
287
+ line-height: 50px;
288
+ }
289
+ .wprm-frame:not(.hide-menu) .wprm-frame-menu {
290
+ position: static;
291
+ width: 0;
292
+ }
293
+ .wprm-frame:not(.hide-menu) .wprm-frame-title {
294
+ left: 0;
295
+ }
296
+ .wprm-frame:not(.hide-menu) .wprm-frame-content,
297
+ .wprm-frame:not(.hide-menu) .wprm-frame-router,
298
+ .wprm-frame:not(.hide-menu) .wprm-frame-title,
299
+ .wprm-frame:not(.hide-menu) .wprm-frame-toolbar {
300
+ left: 0;
301
+ }
302
+ .wprm-frame:not(.hide-menu) .wprm-menu.visible {
303
+ left: 0;
304
+ }
305
+ .wprm-frame:not(.hide-menu) .wprm-menu {
306
+ width: auto;
307
+ max-width: 80%;
308
+ overflow: auto;
309
+ z-index: 2000;
310
+ top: 50px;
311
+ left: -300px;
312
+ right: auto;
313
+ bottom: auto;
314
+ padding: 5px 0;
315
+ border: 1px solid #ccc;
316
+ }
317
+ .wprm-frame:not(.hide-menu) .wprm-menu > a.active {
318
+ display: none;
319
+ }
320
+ .wprm-frame:not(.hide-menu) .wprm-menu > a {
321
+ padding: 12px 16px;
322
+ font-size: 16px;
323
+ }
324
+ .wprm-frame:not(.hide-menu) .wprm-menu .separator {
325
+ margin: 5px 10px;
326
+ }
327
+ .wprm-frame:not(.hide-menu) .wprm-frame-title h1 {
328
+ color: #0073aa;
329
+ line-height: 3;
330
+ font-size: 18px;
331
+ float: left;
332
+ cursor: pointer;
333
+ }
334
+ }
335
+ @media only screen and (max-width: 640px), screen and (max-height: 400px) {
336
+ .wprm-modal {
337
+ position: fixed;
338
+ top: 0;
339
+ left: 0;
340
+ right: 0;
341
+ bottom: 0;
342
+ .wprm-frame-title {
343
+ height: 40px;
344
+ }
345
+ }
346
+ }
347
+ @media only screen and (max-width: 480px) {
348
+ .wprm-frame:not(.hide-menu) .wprm-frame-title .dashicons {
349
+ line-height: 40px;
350
+ }
351
+ .wprm-frame-router,
352
+ .wprm-frame:not(.hide-menu) .wprm-menu {
353
+ top: 40px;
354
+ }
355
+ .wprm-frame:not(.hide-menu) .wprm-frame-title h1,
356
+ .wprm-modal .wprm-frame-title h1 {
357
+ font-size: 18px;
358
+ line-height: 40px;
359
+ }
360
+ .wprm-frame-content {
361
+ top: 74px;
362
+ }
363
+ }
assets/css/admin/modal/_recipe-details.scss ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wprm-recipe-details-form,
2
+ .wprm-recipe-notes-form {
3
+ max-width: 600px;
4
+ .wprm-recipe-details-form-container,
5
+ .wprm-recipe-notes-form-container {
6
+ margin-bottom: 15px;
7
+ vertical-align: top;
8
+ }
9
+ .wprm-recipe-details-form-container-halfs {
10
+ display: inline-block;
11
+ width: 50%;
12
+ }
13
+ .wprm-recipe-details-form-container-thirds {
14
+ display: inline-block;
15
+ width: 33.3%;
16
+ }
17
+ label {
18
+ display: block;
19
+ font-weight: bold;
20
+ margin-bottom: 5px;
21
+ }
22
+ input {
23
+ margin: 0;
24
+ width: 100%;
25
+ max-width: 120px;
26
+ height: 34px;
27
+ line-height: 34px;
28
+ &#wprm-recipe-name {
29
+ max-width: 450px;
30
+ }
31
+ &[type="number"] {
32
+ max-width: 50px;
33
+ }
34
+ &.select2_wprm-search__field {
35
+ height: 18px;
36
+ line-height: 18px;
37
+ }
38
+ }
39
+ select {
40
+ width: 100%;
41
+ }
42
+ textarea {
43
+ width: 100%;
44
+ resize: vertical;
45
+ }
46
+ ::-webkit-input-placeholder {
47
+ color: #999;
48
+ opacity: 0.5;
49
+ }
50
+ :-moz-placeholder {
51
+ color: #999;
52
+ opacity: 0.5;
53
+ }
54
+ ::-moz-placeholder {
55
+ color: #999;
56
+ opacity: 0.5;
57
+ }
58
+ :-ms-input-placeholder {
59
+ color: #999;
60
+ opacity: 0.5;
61
+ }
62
+ .wprm-recipe-image-preview {
63
+ float: right;
64
+ max-width: 100px;
65
+
66
+ img {
67
+ max-width: 100%;
68
+ height: auto;
69
+ }
70
+ }
71
+ .wprm-recipe-summary-container {
72
+ clear: both;
73
+ }
74
+ }
75
+ @media only screen and (max-width: 480px) {
76
+ .wprm-recipe-details-form {
77
+ .wprm-recipe-details-form-container-halfs,
78
+ .wprm-recipe-details-form-container-thirds {
79
+ display: block;
80
+ width: 100%;
81
+ }
82
+ .wprm-recipe-image-preview {
83
+ float: none;
84
+ margin: 10px auto;
85
+ }
86
+ }
87
+ }
assets/css/admin/modal/_recipe-ingredients.scss ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wprm-recipe-ingredients-form,
2
+ .wprm-recipe-instructions-form {
3
+ max-width: 750px;
4
+ margin-bottom: 15px;
5
+ input {
6
+ margin: 0;
7
+ width: 100%;
8
+ height: 34px;
9
+ line-height: 34px;
10
+ }
11
+ textarea {
12
+ width: 100%;
13
+ resize: vertical;
14
+ }
15
+ ::-webkit-input-placeholder {
16
+ color: #999;
17
+ opacity: 0.5;
18
+ }
19
+ :-moz-placeholder {
20
+ color: #999;
21
+ opacity: 0.5;
22
+ }
23
+ ::-moz-placeholder {
24
+ color: #999;
25
+ opacity: 0.5;
26
+ }
27
+ :-ms-input-placeholder {
28
+ color: #999;
29
+ opacity: 0.5;
30
+ }
31
+ table {
32
+ width: 100%;
33
+ &.wprm-recipe-ingredients-container {
34
+ th {
35
+ text-align: left;
36
+ &:nth-child(1),
37
+ &:nth-child(6) {
38
+ width: 5%;
39
+ text-align: center;
40
+ }
41
+ &:nth-child(2) {
42
+ width: 10%;
43
+ }
44
+ &:nth-child(3) {
45
+ width: 15%;
46
+ }
47
+ &:nth-child(4) {
48
+ width: 40%;
49
+ }
50
+ &:nth-child(5) {
51
+ width: 25%;
52
+ }
53
+ }
54
+ }
55
+ &.wprm-recipe-instructions-container {
56
+ th {
57
+ text-align: left;
58
+ &:nth-child(1),
59
+ &:nth-child(4) {
60
+ width: 5%;
61
+ text-align: center;
62
+ }
63
+ &:nth-child(2) {
64
+ width: 65%;
65
+ }
66
+ &:nth-child(3) {
67
+ width: 25%;
68
+ }
69
+ }
70
+ }
71
+ .wprm-recipe-ingredients-placeholder,
72
+ .wprm-recipe-instructions-placeholder {
73
+ display: none;
74
+ }
75
+ td {
76
+ text-align: left;
77
+ vertical-align: top;
78
+ &:first-child,
79
+ &:last-child {
80
+ text-align: center;
81
+ vertical-align: middle;
82
+ }
83
+ }
84
+ }
85
+ .ui-sortable-helper {
86
+ display: table;
87
+ .wprm-recipe-instruction-text {
88
+ min-width: 200px;
89
+ }
90
+ }
91
+ .wprm-recipe-ingredients-instructions-sort {
92
+ cursor: move;
93
+ }
94
+ .wprm-recipe-ingredients-instructions-delete {
95
+ cursor: pointer;
96
+ }
97
+ .wprm-recipe-ingredients-instructions-delete,
98
+ .wprm-recipe-ingredients-instructions-sort {
99
+ color: #999;
100
+ opacity: 0.5;
101
+ &:hover {
102
+ color: #444;
103
+ opacity: 1;
104
+ }
105
+ }
106
+ .wprm-recipe-ingredients-actions,
107
+ .wprm-recipe-instructions-actions {
108
+ margin: 10px;
109
+ }
110
+ .wprm-recipe-image-preview {
111
+ max-width: 75px;
112
+
113
+ img {
114
+ max-width: 100%;
115
+ height: auto;
116
+ }
117
+ }
118
+ }
119
+ @media only screen and (max-width: 480px) {}
assets/icons/clock.svg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path fill="#000000" d="M21,11h2.949C23.466,5.181,18.819,0.534,13,0.051V3h-2V0.051C5.181,0.534,0.534,5.181,0.051,11H3v2H0.051
3
+ C0.534,18.819,5.181,23.466,11,23.949V21h2v2.949c5.819-0.484,10.466-5.13,10.949-10.949H21V11z M17,13h-5.535L6.613,5.723
4
+ l1.664-1.109L12.535,11H17V13z"/>
5
+ </g></svg>
assets/icons/cutlery.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path fill="#000000" d="M10,0C9.4,0,9,0.4,9,1v4H7V1c0-0.6-0.4-1-1-1S5,0.4,5,1v4H3V1c0-0.6-0.4-1-1-1S1,0.4,1,1v8c0,1.7,1.3,3,3,3
3
+ v10c0,1.1,0.9,2,2,2s2-0.9,2-2V12c1.7,0,3-1.3,3-3V1C11,0.4,10.6,0,10,0z"/>
4
+ <path data-color="color-2" fill="#000000" d="M19,0c-3.3,0-6,2.7-6,6v9c0,0.6,0.4,1,1,1h2v6c0,1.1,0.9,2,2,2s2-0.9,2-2V1
5
+ C20,0.4,19.6,0,19,0z"/>
6
+ </g></svg>
assets/icons/knife.svg ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path data-color="color-2" fill="#000000" d="M4.3,16.6l-2.2,2.2c-0.6,0.6-0.9,1.3-0.9,2.1c0,0.8,0.3,1.6,0.9,2.1s1.3,0.9,2.1,0.9
3
+ c0.8,0,1.6-0.3,2.1-0.9l2.2-2.2L4.3,16.6z"/>
4
+ <path fill="#000000" d="M22.6,5.4l-3.5-3.5c-1.1-1.1-2.6-1.8-4.2-1.8s-3.1,0.6-4.2,1.8l-8.4,8.4c-0.4,0.4-0.4,1,0,1.4l7.1,7.1
5
+ C9.5,18.9,9.7,19,10,19c0,0,0,0,0,0c0.3,0,0.5-0.1,0.7-0.3L22.6,6.8C23,6.4,23,5.8,22.6,5.4z M9.2,14.6l-1.4-1.4l6.4-6.4l1.4,1.4
6
+ L9.2,14.6z"/>
7
+ </g></svg>
assets/icons/pan.svg ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path data-color="color-2" fill="#000000" d="M9,9c0.6,0,1-0.4,1-1V4c0-0.6-0.4-1-1-1S8,3.4,8,4v4C8,8.6,8.4,9,9,9z"/>
3
+ <path data-color="color-2" fill="#000000" d="M4,12c0.6,0,1-0.4,1-1V7c0-0.6-0.4-1-1-1S3,6.4,3,7v4C3,11.6,3.4,12,4,12z"/>
4
+ <path data-color="color-2" fill="#000000" d="M14,12c0.6,0,1-0.4,1-1V7c0-0.6-0.4-1-1-1s-1,0.4-1,1v4C13,11.6,13.4,12,14,12z"/>
5
+ <path fill="#000000" d="M23,14h-5H1c-0.6,0-1,0.4-1,1v3c0,1.7,1.3,3,3,3h13c1.7,0,3-1.3,3-3v-1h4c0.6,0,1-0.4,1-1v-1
6
+ C24,14.4,23.6,14,23,14z"/>
7
+ </g></svg>
assets/icons/printer.svg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path fill="#343434" d="M19,5.09V1c0-0.552-0.448-1-1-1H6C5.448,0,5,0.448,5,1v4.09C2.167,5.569,0,8.033,0,11v7c0,0.552,0.448,1,1,1
3
+ h4v4c0,0.552,0.448,1,1,1h12c0.552,0,1-0.448,1-1v-4h4c0.552,0,1-0.448,1-1v-7C24,8.033,21.833,5.569,19,5.09z M7,2h10v3H7V2z
4
+ M17,22H7v-9h10V22z M18,10c-0.552,0-1-0.448-1-1c0-0.552,0.448-1,1-1s1,0.448,1,1C19,9.552,18.552,10,18,10z"/>
5
+ </g></svg>
assets/icons/tag.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 24 24"><g >
2
+ <path fill="#000000" d="M22.707,12.293l-11-11C11.52,1.106,11.265,1,11,1H2C1.448,1,1,1.448,1,2v9c0,0.265,0.105,0.52,0.293,0.707
3
+ l11,11C12.488,22.903,12.744,23,13,23s0.512-0.098,0.707-0.293l9-9C23.098,13.317,23.098,12.684,22.707,12.293z M7,9
4
+ C5.895,9,5,8.105,5,7c0-1.105,0.895-2,2-2s2,0.895,2,2C9,8.105,8.105,9,7,9z M13,17.414L8.586,13L10,11.586L14.414,16L13,17.414z
5
+ M16,14.414L11.586,10L13,8.586L17.414,13L16,14.414z"/>
6
+ </g></svg>
assets/js/admin/import.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var wprm_admin = wprm_admin || {};
2
+
3
+ jQuery(document).ready(function($) {
4
+ // Quick select functionality.
5
+ jQuery('.wprm-import-recipes-select-all').on('click', function(e) {
6
+ e.preventDefault();
7
+ jQuery('.wprm-import-recipes').find(':checkbox').each(function() {
8
+ jQuery(this).prop('checked', true);
9
+ });
10
+ });
11
+ jQuery('.wprm-import-recipes-select-none').on('click', function(e) {
12
+ e.preventDefault();
13
+ jQuery('.wprm-import-recipes').find(':checkbox').each(function() {
14
+ jQuery(this).prop('checked', false);
15
+ });
16
+ });
17
+ });
assets/js/admin/modal.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var wprm_admin = wprm_admin || {};
2
+
3
+ wprm_admin.active_editor_id = false;
4
+
5
+ wprm_admin.open_modal = function(editor_id, recipe_id) {
6
+ // Reset buttons
7
+ jQuery('.wprm-button-insert').show();
8
+ jQuery('.wprm-button-update').hide();
9
+
10
+ wprm_admin.active_editor_id = editor_id;
11
+ wprm_admin.set_recipe(recipe_id);
12
+ jQuery('.wprm-modal-container').show();
13
+
14
+ // Default to first menu item
15
+ jQuery('.wprm-menu').find('.wprm-menu-item').first().click();
16
+ };
17
+
18
+ wprm_admin.close_modal = function() {
19
+ wprm_admin.active_editor_id = false;
20
+ jQuery('.wprm-menu').removeClass('visible');
21
+ jQuery('.wprm-modal-container').hide();
22
+ };
23
+
24
+ jQuery(document).ready(function($) {
25
+ // Opening Modal
26
+ jQuery(document).on('click', '.wprm-modal-button', function() {
27
+ var editor_id = jQuery(this).data('editor');
28
+ wprm_admin.open_modal(editor_id, 0);
29
+ });
30
+
31
+ // Closing Modal
32
+ jQuery('.wprm-modal-container').on('click', '.wprm-modal-close, .wprm-modal-backdrop', function() {
33
+ wprm_admin.close_modal();
34
+ });
35
+
36
+ // Modal Menu
37
+ jQuery('.wprm-menu').on('click', '.wprm-menu-item', function() {
38
+ var menu_item = jQuery(this),
39
+ menu_target = menu_item.data('menu'),
40
+ menu_tab = menu_item.data('tab');
41
+
42
+ // Hide Menu if on Mobile
43
+ jQuery('.wprm-menu').removeClass('visible');
44
+
45
+ // Set clicked on tab as the active one
46
+ jQuery('.wprm-menu').find('.wprm-menu-item').removeClass('active');
47
+ menu_item.addClass('active');
48
+
49
+ // Show correct menu
50
+ jQuery('.wprm-frame-router').find('.wprm-router').removeClass('active');
51
+ jQuery('.wprm-frame-router').find('#wprm-menu-' + menu_target).addClass('active');
52
+
53
+ // Show the first tab as active or whichever tab was passed along
54
+ var active_tab = false;
55
+ jQuery('.wprm-router').find('.wprm-menu-item').removeClass('active');
56
+ jQuery('.wprm-frame-router').find('#wprm-menu-' + menu_target).find('.wprm-menu-item').each(function(index) {
57
+ if (index == 0 || jQuery(this).data('tab') == menu_tab) {
58
+ active_tab = jQuery(this);
59
+ }
60
+ });
61
+
62
+ if (active_tab) {
63
+ active_tab.click();
64
+ }
65
+ });
66
+
67
+ // Modal Menu on Mobile
68
+ jQuery('.wprm-modal-container').on('click', '.wprm-frame-title', function() {
69
+ jQuery('.wprm-menu').toggleClass('visible');
70
+ });
71
+
72
+ // Modal Tabs
73
+ jQuery('.wprm-router').on('click', '.wprm-menu-item', function() {
74
+ var menu_item = jQuery(this),
75
+ tab_target = menu_item.data('tab');
76
+
77
+ // Set clicked on tab as the active one
78
+ jQuery('.wprm-router').find('.wprm-menu-item').removeClass('active');
79
+ menu_item.addClass('active');
80
+
81
+ // Show correct tab
82
+ jQuery('.wprm-frame-content').find('.wprm-frame-content-tab').removeClass('active');
83
+ jQuery('.wprm-frame-content').find('#wprm-tab-' + tab_target).addClass('active');
84
+ });
85
+ });
assets/js/admin/recipe-form.js ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var wprm_admin = wprm_admin || {};
2
+ wprm_admin.prep_time_set = false;
3
+ wprm_admin.cook_time_set = false;
4
+ wprm_admin.total_time_set = false;
5
+
6
+ wprm_admin.editing_recipe = 0;
7
+ wprm_admin.set_recipe = function(recipe_id) {
8
+ wprm_admin.editing_recipe = recipe_id;
9
+ wprm_admin.clear_recipe_fields();
10
+
11
+ if (recipe_id !== 0) {
12
+ jQuery('.wprm-button-insert').hide();
13
+ jQuery('.wprm-button-update').show();
14
+
15
+ var button = jQuery('.wprm-button-update');
16
+ var data = {
17
+ action: 'wprm_get_recipe',
18
+ security: wprm_modal.nonce,
19
+ recipe_id: recipe_id
20
+ };
21
+
22
+ wprm_admin.start_loader(button);
23
+
24
+ jQuery.post(wprm_modal.ajax_url, data, function(out) {
25
+ wprm_admin.stop_loader(button);
26
+
27
+ if (out.success) {
28
+ wprm_admin.set_recipe_fields(out.data.recipe);
29
+ }
30
+ }, 'json');
31
+ }
32
+ };
33
+
34
+ wprm_admin.clear_recipe_fields = function() {
35
+ // Recipe Details
36
+ wprm_admin.remove_media_image(jQuery('.wprm-recipe-image-container'));
37
+ jQuery('#wprm-recipe-name').val('');
38
+ wprm_admin.rich_editor.resetContent(''); // Recipe summary
39
+ jQuery('#wprm-recipe-servings').val('');
40
+ jQuery('#wprm-recipe-servings-unit').val('');
41
+ jQuery('#wprm-recipe-prep-time').val('');
42
+ jQuery('#wprm-recipe-cook-time').val('');
43
+ jQuery('#wprm-recipe-total-time').val('');
44
+
45
+ jQuery('.wprm-recipe-tags').val(null).trigger('change');
46
+
47
+ // Ingredients & Instructions
48
+ jQuery('.wprm-recipe-ingredients .wprm-recipe-ingredients-instructions-delete, .wprm-recipe-instructions .wprm-recipe-ingredients-instructions-delete').each(function() {
49
+ jQuery(this).click();
50
+ });
51
+ jQuery('.wprm-recipe-ingredients-add').click();
52
+ jQuery('.wprm-recipe-instructions-add').click();
53
+
54
+ // Recipe Notes
55
+ if (typeof tinyMCE !== 'undefined' && tinyMCE.get('wprm_recipe_notes') && !tinyMCE.get('wprm_recipe_notes').isHidden()) {
56
+ tinyMCE.get('wprm_recipe_notes').focus(true);
57
+ tinyMCE.activeEditor.setContent('');
58
+ } else {
59
+ jQuery('#wprm_recipe_notes').val('');
60
+ }
61
+ };
62
+
63
+ wprm_admin.set_recipe_fields = function(recipe) {
64
+ // Recipe Details
65
+ if (parseInt(recipe.image_id) > 0) {
66
+ wprm_admin.set_media_image(jQuery('.wprm-recipe-details-form .wprm-recipe-image-container'), recipe.image_id, recipe.image_url);
67
+ }
68
+
69
+ jQuery('#wprm-recipe-name').val(recipe.name);
70
+ wprm_admin.rich_editor.setContent(recipe.summary);
71
+ jQuery('#wprm-recipe-servings-unit').val(recipe.servings_unit);
72
+
73
+ var servings = parseInt(recipe.servings) > 0 ? parseInt(recipe.servings) : '',
74
+ prep_time = parseInt(recipe.prep_time) > 0 ? parseInt(recipe.prep_time) : '',
75
+ cook_time = parseInt(recipe.cook_time) > 0 ? parseInt(recipe.cook_time) : '',
76
+ total_time = parseInt(recipe.total_time) > 0 ? parseInt(recipe.total_time) : '';
77
+
78
+ jQuery('#wprm-recipe-servings').val(servings);
79
+ jQuery('#wprm-recipe-prep-time').val(prep_time);
80
+ jQuery('#wprm-recipe-cook-time').val(cook_time);
81
+ jQuery('#wprm-recipe-total-time').val(total_time);
82
+
83
+ wprm_admin.set_recipe_tags(recipe, 'course');
84
+ wprm_admin.set_recipe_tags(recipe, 'cuisine');
85
+
86
+ // Ingredients & Instructions
87
+ jQuery('.wprm-recipe-ingredients .wprm-recipe-ingredients-instructions-delete, .wprm-recipe-instructions .wprm-recipe-ingredients-instructions-delete').each(function() {
88
+ jQuery(this).click();
89
+ });
90
+
91
+ for (var i = 0, l = recipe.ingredients.length; i < l; i++) {
92
+ var group = recipe.ingredients[i];
93
+
94
+ if (i > 0 || group.name !== '') {
95
+ wprm_admin.add_ingredient_group(group.name);
96
+ }
97
+
98
+ for (var j = 0, m = group.ingredients.length; j < m; j++) {
99
+ var ingredient = group.ingredients[j];
100
+ wprm_admin.add_ingredient(ingredient.amount, ingredient.unit, ingredient.name, ingredient.notes);
101
+ }
102
+ }
103
+
104
+ for (var i = 0, l = recipe.instructions.length; i < l; i++) {
105
+ var group = recipe.instructions[i];
106
+
107
+ if (i > 0 || group.name !== '') {
108
+ wprm_admin.add_instruction_group(group.name);
109
+ }
110
+
111
+ for (var j = 0, m = group.instructions.length; j < m; j++) {
112
+ var instruction = group.instructions[j];
113
+ wprm_admin.add_instruction(instruction.text, instruction.image);
114
+ }
115
+ }
116
+
117
+ // Recipe Notes
118
+ if (typeof tinyMCE !== 'undefined' && tinyMCE.get('wprm_recipe_notes') && !tinyMCE.get('wprm_recipe_notes').isHidden()) {
119
+ tinyMCE.get('wprm_recipe_notes').focus(true);
120
+ tinyMCE.activeEditor.setContent(recipe.notes);
121
+ } else {
122
+ jQuery('#wprm_recipe_notes').val(recipe.notes);
123
+ }
124
+ };
125
+
126
+ wprm_admin.set_recipe_tags = function(recipe, tag) {
127
+ var term_ids = [],
128
+ select = jQuery('#wprm-recipe-tag-' + tag);
129
+
130
+ for (var i = 0, l = recipe.tags[tag].length; i < l; i++) {
131
+ var term = recipe.tags[tag][i];
132
+ term_ids.push(term.term_id);
133
+
134
+ // Add term to options if not in there
135
+ if (select.find('option[value=' + term.term_id + ']').length == 0) {
136
+ select.append('<option value="' + term.term_id + '">' + term.name + '</option>');
137
+ }
138
+ }
139
+ select.val(term_ids).trigger('change');
140
+ };
141
+
142
+ wprm_admin.rich_editor = false;
143
+ wprm_admin.init_rich_editor = function() {
144
+ if (wprm_admin.rich_editor) {
145
+ wprm_admin.rich_editor.addElements('.wprm-rich-editor');
146
+ } else {
147
+ wprm_admin.rich_editor = new MediumEditor('.wprm-rich-editor', {
148
+ autoLink: true,
149
+ imageDragging: false,
150
+ toolbar: {
151
+ buttons: ['bold', 'italic', 'underline', {
152
+ name: 'anchor',
153
+ contentDefault: '<span class="dashicons dashicons-admin-links"></span>'
154
+ }]
155
+ }
156
+ });
157
+ }
158
+ };
159
+
160
+ wprm_admin.select_media_image = function(container) {
161
+ // Create a new media frame (don't reuse because we have multiple different inputs)
162
+ var frame = wp.media({
163
+ title: wprm_modal.text.media_title,
164
+ button: {
165
+ text: wprm_modal.text.media_button
166
+ },
167
+ multiple: false
168
+ });
169
+
170
+
171
+ // When an image is selected in the media frame...
172
+ frame.on('select', function() {
173
+ var attachment = frame.state().get('selection').first().toJSON();
174
+ wprm_admin.set_media_image(container, attachment.id, attachment.url);
175
+ });
176
+
177
+ // Finally, open the modal on click
178
+ frame.open();
179
+ };
180
+ wprm_admin.set_media_image = function(container, image_id, image_url) {
181
+ container.find('.wprm-recipe-image-preview').append('<img src="' + image_url + '" />');
182
+ container.find('input').val(image_id);
183
+
184
+ container.find('.wprm-recipe-image-add').addClass('hidden');
185
+ container.find('.wprm-recipe-image-remove').removeClass('hidden');
186
+ };
187
+ wprm_admin.remove_media_image = function(container) {
188
+ container.find('.wprm-recipe-image-preview').html('');
189
+ container.find('input').val('');
190
+
191
+ container.find('.wprm-recipe-image-add').removeClass('hidden');
192
+ container.find('.wprm-recipe-image-remove').addClass('hidden');
193
+ };
194
+
195
+ wprm_admin.start_loader = function(button) {
196
+ button
197
+ .prop('disabled', true)
198
+ .css('width', button.outerWidth())
199
+ .data('text', button.html())
200
+ .html('...');
201
+ };
202
+
203
+ wprm_admin.stop_loader = function(button) {
204
+ button
205
+ .prop('disabled', false)
206
+ .css('width', '')
207
+ .html(button.data('text'));
208
+ };
209
+
210
+ wprm_admin.add_ingredient = function(amount, unit, name, notes) {
211
+ amount = amount || '';
212
+ unit = unit || '';
213
+ name = name || '';
214
+ notes = notes || '';
215
+
216
+ var clone = jQuery('.wprm-recipe-ingredients-placeholder').find('.wprm-recipe-ingredient').clone();
217
+ jQuery('.wprm-recipe-ingredients').append(clone);
218
+
219
+ clone.find('.wprm-recipe-ingredient-amount').val(amount).focus();
220
+ clone.find('.wprm-recipe-ingredient-unit').val(unit);
221
+ clone.find('.wprm-recipe-ingredient-name').val(name);
222
+ clone.find('.wprm-recipe-ingredient-notes').val(notes);
223
+ };
224
+
225
+ wprm_admin.add_ingredient_group = function(name) {
226
+ name = name || '';
227
+
228
+ var clone = jQuery('.wprm-recipe-ingredients-placeholder').find('.wprm-recipe-ingredient-group').clone();
229
+ jQuery('.wprm-recipe-ingredients').append(clone);
230
+ clone.find('input:first').val(name).focus();
231
+ };
232
+
233
+ wprm_admin.add_instruction = function(text, image_id) {
234
+ text = text || '';
235
+ image_id = image_id || 0;
236
+
237
+ var clone = jQuery('.wprm-recipe-instructions-placeholder').find('.wprm-recipe-instruction').clone();
238
+ clone.find('.wprm-recipe-instruction-text').addClass('wprm-rich-editor');
239
+ jQuery('.wprm-recipe-instructions').append(clone);
240
+ clone.find('.wprm-recipe-instruction-text').val(text);
241
+ wprm_admin.init_rich_editor();
242
+ clone.find('.wprm-recipe-instruction-text').focus();
243
+
244
+ // Get image thumbnail if there is an instruction image.
245
+ if (parseInt(image_id) > 0) {
246
+ var image_container = clone.find('.wprm-recipe-image-container'),
247
+ button = image_container.find('.wprm-recipe-image-add');
248
+
249
+ var data = {
250
+ action: 'wprm_get_thumbnail',
251
+ security: wprm_modal.nonce,
252
+ image_id: image_id
253
+ };
254
+
255
+ wprm_admin.start_loader(button);
256
+
257
+ jQuery.post(wprm_modal.ajax_url, data, function(out) {
258
+ wprm_admin.stop_loader(button);
259
+
260
+ if (out.success) {
261
+ wprm_admin.set_media_image(image_container, image_id, out.data.image_url);
262
+ }
263
+ }, 'json');
264
+ }
265
+ };
266
+
267
+ wprm_admin.add_instruction_group = function(name) {
268
+ name = name || '';
269
+
270
+ var clone = jQuery('.wprm-recipe-instructions-placeholder').find('.wprm-recipe-instruction-group').clone();
271
+ jQuery('.wprm-recipe-instructions').append(clone);
272
+ clone.find('input:first').val(name).focus();
273
+ };
274
+
275
+ jQuery(document).ready(function($) {
276
+ // Recipe and Instruction Image handler
277
+ jQuery('.wprm-recipe-details-form, .wprm-recipe-instructions-form').on('click', '.wprm-recipe-image-add', function(e) {
278
+ wprm_admin.select_media_image(jQuery(this).parents('.wprm-recipe-image-container'));
279
+ });
280
+ jQuery('.wprm-recipe-details-form, .wprm-recipe-instructions-form').on('click', '.wprm-recipe-image-remove', function(e) {
281
+ wprm_admin.remove_media_image(jQuery(this).parents('.wprm-recipe-image-container'));
282
+ });
283
+
284
+ // Initialize rich editor
285
+ wprm_admin.init_rich_editor();
286
+
287
+ // Recipe Times
288
+ jQuery('.wprm-recipe-time').on('keyup change', function() {
289
+ var container = jQuery(this),
290
+ prep_time_container = jQuery('#wprm-recipe-prep-time'),
291
+ prep_time = prep_time_container.val(),
292
+ cook_time_container = jQuery('#wprm-recipe-cook-time'),
293
+ cook_time = cook_time_container.val(),
294
+ total_time_container = jQuery('#wprm-recipe-total-time'),
295
+ total_time = total_time_container.val();
296
+
297
+ if (container.is('#wprm-recipe-prep-time')) wprm_admin.prep_time_set = true;
298
+ if (container.is('#wprm-recipe-cook-time')) wprm_admin.cook_time_set = true;
299
+ if (container.is('#wprm-recipe-total-time')) wprm_admin.total_time_set = true;
300
+
301
+ if (prep_time && cook_time && !wprm_admin.total_time_set) total_time_container.val(parseInt(prep_time) + parseInt(cook_time));
302
+ if (total_time && prep_time && !wprm_admin.cook_time_set) cook_time_container.val(parseInt(total_time) - parseInt(prep_time));
303
+ if (total_time && cook_time && !wprm_admin.prep_time_set) prep_time_container.val(parseInt(total_time) - parseInt(cook_time));
304
+ });
305
+
306
+ // Recipe Tags
307
+ jQuery('.wprm-recipe-tags').select2_wprm({
308
+ width: '95%',
309
+ tags: true
310
+ });
311
+
312
+ // Add Recipe Ingredients and Instructions
313
+ jQuery('.wprm-recipe-ingredients-add').on('click', function() {
314
+ wprm_admin.add_ingredient();
315
+ });
316
+ jQuery('.wprm-recipe-ingredients-add-group').on('click', function() {
317
+ wprm_admin.add_ingredient_group();
318
+ });
319
+ jQuery('.wprm-recipe-instructions-add').on('click', function() {
320
+ wprm_admin.add_instruction('');
321
+ });
322
+ jQuery('.wprm-recipe-instructions-add-group').on('click', function() {
323
+ wprm_admin.add_instruction_group();
324
+ });
325
+
326
+ // Add new ingredient/instruction on TAB
327
+ jQuery('.wprm-recipe-ingredients').on('keydown', '.wprm-recipe-ingredient-notes, .wprm-recipe-ingredient-group-name', function(e) {
328
+ var keyCode = e.keyCode || e.which,
329
+ input = jQuery(this);
330
+
331
+ if (!e.shiftKey && keyCode == 9 && jQuery(this).parents('tr').is('tr:last-child')) {
332
+ e.preventDefault();
333
+ wprm_admin.add_ingredient();
334
+ }
335
+ })
336
+ jQuery('.wprm-recipe-instructions').on('keydown', '.wprm-recipe-instruction-text, .wprm-recipe-instruction-group-name', function(e) {
337
+ var keyCode = e.keyCode || e.which,
338
+ input = jQuery(this);
339
+
340
+ if (!e.shiftKey && keyCode == 9 && jQuery(this).parents('tr').is('tr:last-child')) {
341
+ e.preventDefault();
342
+ wprm_admin.add_instruction();
343
+ }
344
+ })
345
+
346
+ // Remove Recipe Ingredients and Instructions
347
+ jQuery('.wprm-recipe-ingredients-instructions-form').on('click', '.wprm-recipe-ingredients-instructions-delete', function() {
348
+ jQuery(this).parents('tr').remove();
349
+ });
350
+
351
+ // Sort Recipe Ingredients and Instructions
352
+ jQuery('.wprm-recipe-ingredients, .wprm-recipe-instructions').sortable({
353
+ opacity: 0.6,
354
+ revert: true,
355
+ cursor: 'move',
356
+ handle: '.wprm-recipe-ingredients-instructions-sort',
357
+ });
358
+
359
+ // Handle recipe insert/update
360
+ jQuery('.wprm-button-insert, .wprm-button-update').on('click', function() {
361
+ var button = jQuery(this);
362
+
363
+ // Recipe Details
364
+ var recipe = {
365
+ image_id: jQuery('#wprm-recipe-image-id').val(),
366
+ name: jQuery('#wprm-recipe-name').val(),
367
+ summary: jQuery('#wprm-recipe-summary').val(),
368
+ servings: jQuery('#wprm-recipe-servings').val(),
369
+ servings_unit: jQuery('#wprm-recipe-servings-unit').val(),
370
+ prep_time: jQuery('#wprm-recipe-prep-time').val(),
371
+ cook_time: jQuery('#wprm-recipe-cook-time').val(),
372
+ total_time: jQuery('#wprm-recipe-total-time').val(),
373
+ tags: {
374
+ course: jQuery('#wprm-recipe-tag-course').val(),
375
+ cuisine: jQuery('#wprm-recipe-tag-cuisine').val()
376
+ }
377
+ };
378
+
379
+ // Recipe Ingredients
380
+ var ingredients = [];
381
+ var ingredient_group = {
382
+ name: '',
383
+ ingredients: []
384
+ };
385
+ jQuery('.wprm-recipe-ingredients').find('tr').each(function() {
386
+ var row = jQuery(this);
387
+ if (row.hasClass('wprm-recipe-ingredient-group')) {
388
+ // Add current ingredient group to ingredients
389
+ if (ingredient_group.ingredients.length > 0) {
390
+ ingredients.push(ingredient_group);
391
+ }
392
+
393
+ ingredient_group = {
394
+ name: row.find('.wprm-recipe-ingredient-group-name').val(),
395
+ ingredients: []
396
+ };
397
+ } else {
398
+ ingredient_group.ingredients.push({
399
+ amount: row.find('.wprm-recipe-ingredient-amount').val(),
400
+ unit: row.find('.wprm-recipe-ingredient-unit').val(),
401
+ name: row.find('.wprm-recipe-ingredient-name').val(),
402
+ notes: row.find('.wprm-recipe-ingredient-notes').val()
403
+ });
404
+ }
405
+ });
406
+ // Add remaining ingredient group
407
+ if (ingredient_group.ingredients.length > 0) {
408
+ ingredients.push(ingredient_group);
409
+ }
410
+ recipe.ingredients = ingredients;
411
+
412
+ // Recipe Instructions
413
+ var instructions = [];
414
+ var instruction_group = {
415
+ name: '',
416
+ instructions: []
417
+ };
418
+ jQuery('.wprm-recipe-instructions').find('tr').each(function() {
419
+ var row = jQuery(this);
420
+ if (row.hasClass('wprm-recipe-instruction-group')) {
421
+ // Add current instruction group to instructions
422
+ if (instruction_group.instructions.length > 0) {
423
+ instructions.push(instruction_group);
424
+ }
425
+
426
+ instruction_group = {
427
+ name: row.find('.wprm-recipe-instruction-group-name').val(),
428
+ instructions: []
429
+ };
430
+ } else {
431
+ instruction_group.instructions.push({
432
+ text: row.find('textarea.wprm-recipe-instruction-text').val(),
433
+ image: row.find('.wprm-recipe-instruction-image').val()
434
+ });
435
+ }
436
+ });
437
+ // Add remaining ingredient group
438
+ if (instruction_group.instructions.length > 0) {
439
+ instructions.push(instruction_group);
440
+ }
441
+ recipe.instructions = instructions;
442
+
443
+ // Recipe Notes
444
+ if (jQuery('#wp-wprm_recipe_notes-wrap').hasClass('tmce-active')) {
445
+ recipe.notes = tinyMCE.activeEditor.getContent();
446
+ } else {
447
+ recipe.notes = jQuery('#wprm_recipe_notes').val();
448
+ }
449
+
450
+ // Ajax call to recipe saver
451
+ var data = {
452
+ action: 'wprm_save_recipe',
453
+ security: wprm_modal.nonce,
454
+ recipe_id: wprm_admin.editing_recipe,
455
+ recipe: recipe
456
+ };
457
+
458
+ wprm_admin.start_loader(button);
459
+
460
+ jQuery.post(wprm_modal.ajax_url, data, function(out) {
461
+ wprm_admin.stop_loader(button);
462
+
463
+ if (out.success) {
464
+ if (wprm_admin.editing_recipe == 0) {
465
+ wprm_admin.add_shortcode_to_editor(out.data.id);
466
+ } else {
467
+ // Refresh content in editor to reload recipe preview
468
+ if (typeof tinyMCE !== 'undefined' && tinyMCE.get(wprm_admin.active_editor_id) && !tinyMCE.get(wprm_admin.active_editor_id).isHidden()) {
469
+ tinyMCE.get(wprm_admin.active_editor_id).focus(true);
470
+ tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent());
471
+ }
472
+ }
473
+ wprm_admin.close_modal();
474
+ }
475
+ }, 'json');
476
+ });
477
+ });
assets/js/admin/shortcode-tinymce.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function() {
2
+ tinymce.PluginManager.add('wprecipemaker', function(editor, url) {
3
+ function replaceShortcodes(content) {
4
+ return content.replace(/\[wprm-recipe([^\]]*)\]/g, function(match) {
5
+ return html(match);
6
+ });
7
+ }
8
+
9
+ function html(data) {
10
+ var id = data.match(/id="?'?(\d+)/i);
11
+ data = window.encodeURIComponent(data);
12
+
13
+ var ajax_data = {
14
+ action: 'wprm_shortcode_preview',
15
+ security: wprm_modal.nonce,
16
+ recipe_id: id[1]
17
+ };
18
+
19
+ jQuery.post(wprm_modal.ajax_url, ajax_data, function(preview) {
20
+ var content = jQuery(editor.iframeElement).contents().find('#tinymce').html();
21
+ content = content.replace('>Loading WP Recipe Maker #' + id[1] + '<', '>' + preview + '<');
22
+ editor.setContent(content);
23
+ }, 'html');
24
+
25
+ return '<span class="wprm-placeholder" contentEditable="false">&nbsp;</span><div class="wprm-shortcode" style="display: block; cursor: pointer; margin: 5px; padding: 10px; border: 1px solid #999;" contentEditable="false" ' +
26
+ 'data-wprm-recipe="' + id[1] + '" data-wprm-shortcode="' + data + '" data-mce-resize="false" data-mce-placeholder="1">Loading WP Recipe Maker #' + id[1] + '</div><span class="wprm-placeholder" contentEditable="false">&nbsp;</span>';
27
+ }
28
+
29
+ function restoreShortcodes(content) {
30
+ function getAttr(str, name) {
31
+ name = new RegExp(name + '=\"([^\"]+)\"').exec(str);
32
+ return name ? window.decodeURIComponent(name[1]) : '';
33
+ }
34
+
35
+ content = content.replace(/<p><span class="wprm-(?=(.*?span>))\1\s*<\/p>/g, '');
36
+ content = content.replace(/<span class="wprm-.*?span>/g, '');
37
+
38
+ return content.replace(/(?:<p(?: [^>]+)?>)*(<div [^>]+>.*?<\/div>)(?:<\/p>)*/g, function(match, div) {
39
+ var data = getAttr(div, 'data-wprm-shortcode');
40
+
41
+ if (data) {
42
+ return '<p>' + data + '</p>';
43
+ }
44
+
45
+ return match;
46
+ });
47
+ }
48
+
49
+ editor.on('mouseup', function(event) {
50
+ var dom = editor.dom,
51
+ node = event.target;
52
+
53
+ if (event.button !== 2) {
54
+ if (dom.getAttrib(node, 'data-wprm-recipe')) {
55
+ var id = dom.getAttrib(node, 'data-wprm-recipe');
56
+ wprm_admin.open_modal(editor.id, id);
57
+ } else if (dom.getAttrib(node, 'data-wprm-recipe-remove')) {
58
+ if (confirm(wprm_modal.text.shortcode_remove)) {
59
+ editor.dom.remove(node.parentNode);
60
+ }
61
+ }
62
+ }
63
+ });
64
+
65
+ editor.on('BeforeSetContent', function(event) {
66
+ event.content = event.content.replace(/^(\s*<p>)(\s*\[wprm-recipe)/, '$1<span class="wprm-placeholder" contentEditable="false">&nbsp;</span>$2');
67
+ event.content = replaceShortcodes(event.content);
68
+ });
69
+
70
+ editor.on('PostProcess', function(event) {
71
+ if (event.get) {
72
+ event.content = restoreShortcodes(event.content);
73
+ }
74
+ });
75
+ });
76
+ })();
assets/js/admin/shortcode.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var wprm_admin = wprm_admin || {};
2
+
3
+ wprm_admin.add_shortcode_to_editor = function(recipe_id) {
4
+ if (wprm_admin.active_editor_id) {
5
+ var text = ' [wprm-recipe id="' + recipe_id + '"] ';
6
+
7
+ if (typeof tinyMCE == 'undefined' || !tinyMCE.get(wprm_admin.active_editor_id) || tinyMCE.get(wprm_admin.active_editor_id).isHidden()) {
8
+ var current = jQuery('textarea#' + wprm_admin.active_editor_id).val();
9
+ jQuery('textarea#' + wprm_admin.active_editor_id).val(current + text);
10
+ } else {
11
+ tinyMCE.get(wprm_admin.active_editor_id).focus(true);
12
+ tinyMCE.activeEditor.execCommand('mceInsertContent', false, text);
13
+ }
14
+ }
15
+ };
assets/js/print.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var wprm = wprm || {};
2
+
3
+ wprm.print_html = function(html) {
4
+ var iframe = document.createElement('iframe');
5
+
6
+ iframe.style.visibility = 'hidden';
7
+ iframe.style.position = 'fixed';
8
+ iframe.style.right = '0';
9
+ iframe.style.bottom = '0';
10
+
11
+ document.body.appendChild(iframe);
12
+ iframe.contentWindow.document.open();
13
+ iframe.contentWindow.document.write(html);
14
+ iframe.contentWindow.document.close();
15
+
16
+ iframe.contentWindow.focus(); // Required for IE
17
+ iframe.contentWindow.print();
18
+
19
+ setTimeout(function() {
20
+ jQuery(iframe).remove();
21
+ }, 500);
22
+ };
23
+
24
+ jQuery(document).ready(function($) {
25
+ jQuery('.wprm-recipe-print').on('click', function(e) {
26
+ e.preventDefault();
27
+
28
+ var recipe_id = jQuery(this).parents('.wprm-recipe-container').data('recipe-id');
29
+
30
+ var data = {
31
+ action: 'wprm_print_recipe',
32
+ security: wprm_public.nonce,
33
+ recipe_id: recipe_id
34
+ };
35
+
36
+ jQuery.post(wprm_public.ajax_url, data, function(out) {
37
+ if (out.success) {
38
+ wprm.print_html(out.data.html);
39
+ }
40
+ }, 'json');
41
+ });
42
+ });
includes/admin/class-wprm-import-manager.php ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for handling the import of recipes from other sources.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin
10
+ */
11
+
12
+ /**
13
+ * Responsible for handling the import of recipes from other sources.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Import_Manager {
21
+
22
+ /**
23
+ * Importers that can be used to import recipes from other sources.
24
+ *
25
+ * @since 1.0.0
26
+ * @access private
27
+ * @var array $importers Array containing all available importers.
28
+ */
29
+ private static $importers = array();
30
+
31
+ /**
32
+ * Register actions and filters.
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ public static function init() {
37
+ self::load_importers();
38
+ add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
39
+ add_action( 'admin_menu', array( __CLASS__, 'add_submenu_page' ) );
40
+ add_action( 'admin_post_wprm_import_recipes', array( __CLASS__, 'form_import_recipes' ) );
41
+ add_action( 'admin_post_wprm_check_imported_recipes', array( __CLASS__, 'form_check_imported_recipes' ) );
42
+ }
43
+
44
+ /**
45
+ * Add the import submenu to the WPRM menu.
46
+ *
47
+ * @since 1.0.0
48
+ */
49
+ public static function add_submenu_page() {
50
+ add_submenu_page( 'wprecipemaker', 'Import Recipes', 'Import Recipes', 'manage_options', 'wprm_import_overview', array( __CLASS__, 'overview_page_template' ) );
51
+ add_submenu_page( null, 'Import Recipes', 'Import Recipes', 'manage_options', 'wprm_import', array( __CLASS__, 'import_page_template' ) );
52
+ }
53
+
54
+ /**
55
+ * Enqueue stylesheets and scripts.
56
+ *
57
+ * @since 1.0.0
58
+ */
59
+ public static function enqueue() {
60
+ wp_enqueue_style( 'wprm-import', WPRM_URL . '/assets/css/admin/import.min.css', array(), WPRM_VERSION, 'all' );
61
+
62
+ wp_enqueue_script( 'wprm-import', WPRM_URL . '/assets/js/admin/import.js', array( 'jquery' ), WPRM_VERSION, true );
63
+ }
64
+
65
+ /**
66
+ * Get the template for the import overview page.
67
+ *
68
+ * @since 1.0.0
69
+ */
70
+ public static function overview_page_template() {
71
+ require_once( WPRM_DIR . 'templates/admin/menu/import-overview.php' );
72
+ }
73
+
74
+ /**
75
+ * Get the template for the import page.
76
+ *
77
+ * @since 1.0.0
78
+ */
79
+ public static function import_page_template() {
80
+ require_once( WPRM_DIR . 'templates/admin/menu/import-recipes.php' );
81
+ }
82
+
83
+ /**
84
+ * Import the recipes selected in the import form.
85
+ *
86
+ * @since 1.0.0
87
+ */
88
+ public static function form_import_recipes() {
89
+ if ( isset( $_POST['wprm_import_recipes'] ) && wp_verify_nonce( sanitize_key( $_POST['wprm_import_recipes'] ), 'wprm_import_recipes' ) ) { // Input var okay.
90
+ $importer_uid = isset( $_POST['importer'] ) ? sanitize_title( wp_unslash( $_POST['importer'] ) ) : ''; // Input var okay.
91
+ $recipes = isset( $_POST['recipes'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['recipes'] ) ) : array(); // Input var okay.
92
+
93
+ $importer = self::get_importer( $importer_uid );
94
+
95
+ if ( $importer && count( $recipes ) > 0 ) {
96
+ self::import_recipes( $importer, $recipes );
97
+ }
98
+ }
99
+ wp_safe_redirect( admin_url( 'admin.php?page=wprm_import_overview' ) );
100
+ exit();
101
+ }
102
+
103
+ /**
104
+ * Mark the recipes selected in the form as checked.
105
+ *
106
+ * @since 1.0.0
107
+ */
108
+ public static function form_check_imported_recipes() {
109
+ if ( isset( $_POST['wprm_check_imported_recipes'] ) && wp_verify_nonce( sanitize_key( $_POST['wprm_check_imported_recipes'] ), 'wprm_check_imported_recipes' ) ) { // Input var okay.
110
+ $importer_uid = isset( $_POST['importer'] ) ? sanitize_title( wp_unslash( $_POST['importer'] ) ) : ''; // Input var okay.
111
+ $recipes = isset( $_POST['recipes'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['recipes'] ) ) : array(); // Input var okay.
112
+
113
+ foreach ( $recipes as $recipe_id ) {
114
+ $uid = get_post_meta( $recipe_id, 'wprm_import_source', true );
115
+
116
+ if ( $uid === $importer_uid ) {
117
+ update_post_meta( $recipe_id, 'wprm_import_source', $uid . '-checked' );
118
+ }
119
+ }
120
+ }
121
+ wp_safe_redirect( admin_url( 'admin.php?page=wprm_import_overview' ) );
122
+ exit();
123
+ }
124
+
125
+ /**
126
+ * Import recipes using the specified importer.
127
+ *
128
+ * @since 1.0.0
129
+ * @param object $importer Importer to use for importing.
130
+ * @param array $recipes IDs of recipes to import.
131
+ */
132
+ public static function import_recipes( $importer, $recipes ) {
133
+ // Reverse sort by ID to make sure multiple recipes in the same post are handled correctly.
134
+ arsort( $recipes );
135
+
136
+ foreach ( $recipes as $import_recipe_id ) {
137
+ $imported_recipe = $importer->get_recipe( $import_recipe_id );
138
+
139
+ if ( $imported_recipe ) {
140
+ $imported_recipe['import_source'] = $importer->get_uid();
141
+
142
+ $recipe_id = isset( $imported_recipe['import_id'] ) ? intval( $imported_recipe['import_id'] ) : 0;
143
+ $recipe = WPRM_Recipe_Sanitizer::sanitize( $imported_recipe );
144
+
145
+ if ( $recipe_id ) {
146
+ if ( WPRM_POST_TYPE !== get_post_type( $recipe_id ) ) {
147
+ set_post_type( $recipe_id, WPRM_POST_TYPE );
148
+ }
149
+ WPRM_Recipe_Saver::update_recipe( $recipe_id, $recipe );
150
+ } else {
151
+ $recipe_id = WPRM_Recipe_Saver::create_recipe( $recipe );
152
+ }
153
+
154
+ $importer->replace_recipe( $import_recipe_id, $recipe_id );
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Get importer by UID.
161
+ *
162
+ * @since 1.0.0
163
+ * @param int $uid UID of the importer.
164
+ */
165
+ public static function get_importer( $uid ) {
166
+ $importer = false;
167
+ foreach ( self::$importers as $possible_importer ) {
168
+ if ( sanitize_title( $possible_importer->get_uid() ) === $uid ) {
169
+ $importer = $possible_importer;
170
+ }
171
+ }
172
+
173
+ return $importer;
174
+ }
175
+
176
+ /**
177
+ * Get recipes that were imported by a specific importer.
178
+ *
179
+ * @since 1.0.0
180
+ * @param int $uid UID of the importer.
181
+ * @param boolean $exclude_checked Wether to exclude recipes that have already been checked.
182
+ */
183
+ public static function get_imported_recipes( $uid, $exclude_checked = false ) {
184
+ $args = array(
185
+ 'post_type' => WPRM_POST_TYPE,
186
+ 'post_status' => 'any',
187
+ 'orderby' => 'date',
188
+ 'order' => 'DESC',
189
+ 'meta_key' => 'wprm_import_source',
190
+ );
191
+
192
+ if ( $exclude_checked ) {
193
+ $args['meta_value'] = $uid;
194
+ } else {
195
+ $args['meta_value'] = array( $uid, $uid . '-checked' );
196
+ $args['meta_compare'] = 'IN';
197
+ }
198
+
199
+ $query = new WP_Query( $args );
200
+
201
+ if ( $query->have_posts() ) {
202
+ return $query->posts;
203
+ } else {
204
+ return array();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Load all available importers from the /includes/admin/import directory.
210
+ *
211
+ * @since 1.0.0
212
+ */
213
+ private static function load_importers() {
214
+ $dir = WPRM_DIR . 'includes/admin/import';
215
+ $importers = array();
216
+
217
+ if ( $handle = opendir( $dir ) ) {
218
+ while ( false !== ( $file = readdir( $handle ) ) ) {
219
+ preg_match( '/^class-wprm-import-(.*?).php/', $file, $match );
220
+ if ( isset( $match[1] ) ) {
221
+ require_once( $dir . '/' . $match[0] );
222
+ $class_name = 'WPRM_Import_' . ucfirst( strtolower( $match[1] ) );
223
+ $importers[] = new $class_name();
224
+ }
225
+ }
226
+ }
227
+ self::$importers = $importers;
228
+ }
229
+ }
230
+
231
+ WPRM_Import_Manager::init();
includes/admin/class-wprm-recipe-parser.php ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for parsing recipe (parts) into our recipe format.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin
10
+ */
11
+
12
+ /**
13
+ * Responsible for parsing recipe (parts) into our recipe format.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Recipe_Parser {
21
+
22
+ /**
23
+ * Sanitize recipe array.
24
+ *
25
+ * @since 1.0.0
26
+ * @param mixed $raw Text to parse into an ingredient.
27
+ */
28
+ public static function parse_ingredient( $raw ) {
29
+ // Amount.
30
+ $amount = '';
31
+
32
+ preg_match( '/^\s*(\d[\s\/\-\d.,]*)(.*)/', $raw, $match );
33
+ if ( isset( $match[0] ) ) {
34
+ $amount = trim( $match[1] );
35
+ $raw = trim( $match[2] );
36
+ }
37
+
38
+ // Units.
39
+ $unit = '';
40
+
41
+ $possible_units = self::parse_ingredient_units();
42
+ $possible_units = array_map( 'preg_quote', $possible_units );
43
+ $pattern = '/\b' . implode( '\b|\b', $possible_units ) . '\b/i';
44
+ preg_match( $pattern, $raw, $match );
45
+ if ( isset( $match[0] ) ) {
46
+ $unit = $match[0];
47
+ $raw = preg_replace( $pattern, '', $raw, 1 );
48
+ }
49
+
50
+ // Notes.
51
+ $notes = array();
52
+
53
+ preg_match( '/(.*?),(.*)/i', $raw, $match );
54
+ if ( isset( $match[0] ) ) {
55
+ $notes[] = trim( $match[2] );
56
+ $raw = trim( $match[1] );
57
+ }
58
+
59
+ preg_match_all( '/\((.*?)\)/i', $raw, $matches );
60
+
61
+ if ( isset( $matches[1] ) ) {
62
+ foreach ( $matches[1] as $match ) {
63
+ $notes[] = trim( $match );
64
+ }
65
+
66
+ $raw = preg_replace( '/\((.*?)\)/i', '', $raw );
67
+ }
68
+
69
+ $notes = implode( ', ', $notes );
70
+
71
+ // Name.
72
+ $name = trim( $raw );
73
+
74
+ return array(
75
+ 'amount' => $amount,
76
+ 'unit' => $unit,
77
+ 'name' => $name,
78
+ 'notes' => $notes,
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Get an array of possible ingredient units.
84
+ *
85
+ * @since 1.0.0
86
+ */
87
+ private static function parse_ingredient_units() {
88
+ $units = array(
89
+ // Weight.
90
+ __( 'kilograms', 'wp-recipe-maker' ),
91
+ __( 'kilogram', 'wp-recipe-maker' ),
92
+ __( 'kg', 'wp-recipe-maker' ),
93
+ __( 'grams', 'wp-recipe-maker' ),
94
+ __( 'gram', 'wp-recipe-maker' ),
95
+ __( 'gr', 'wp-recipe-maker' ),
96
+ __( 'g', 'wp-recipe-maker' ),
97
+ __( 'milligrams', 'wp-recipe-maker' ),
98
+ __( 'milligram', 'wp-recipe-maker' ),
99
+ __( 'mg', 'wp-recipe-maker' ),
100
+ __( 'pounds', 'wp-recipe-maker' ),
101
+ __( 'pound', 'wp-recipe-maker' ),
102
+ __( 'lbs', 'wp-recipe-maker' ),
103
+ __( 'lb', 'wp-recipe-maker' ),
104
+ __( 'ounces', 'wp-recipe-maker' ),
105
+ __( 'ounce', 'wp-recipe-maker' ),
106
+ __( 'oz', 'wp-recipe-maker' ),
107
+ // Volume.
108
+ __( 'liters', 'wp-recipe-maker' ),
109
+ __( 'liter', 'wp-recipe-maker' ),
110
+ __( 'l', 'wp-recipe-maker' ),
111
+ __( 'deciliters', 'wp-recipe-maker' ),
112
+ __( 'deciliter', 'wp-recipe-maker' ),
113
+ __( 'dl', 'wp-recipe-maker' ),
114
+ __( 'centiliters', 'wp-recipe-maker' ),
115
+ __( 'centiliter', 'wp-recipe-maker' ),
116
+ __( 'cl', 'wp-recipe-maker' ),
117
+ __( 'milliliters', 'wp-recipe-maker' ),
118
+ __( 'milliliter', 'wp-recipe-maker' ),
119
+ __( 'ml', 'wp-recipe-maker' ),
120
+ __( 'gallons', 'wp-recipe-maker' ),
121
+ __( 'gallon', 'wp-recipe-maker' ),
122
+ __( 'gal', 'wp-recipe-maker' ),
123
+ __( 'quarts', 'wp-recipe-maker' ),
124
+ __( 'quart', 'wp-recipe-maker' ),
125
+ __( 'qt', 'wp-recipe-maker' ),
126
+ __( 'pints', 'wp-recipe-maker' ),
127
+ __( 'pint', 'wp-recipe-maker' ),
128
+ __( 'pt', 'wp-recipe-maker' ),
129
+ __( 'cups', 'wp-recipe-maker' ),
130
+ __( 'cup', 'wp-recipe-maker' ),
131
+ __( 'cu', 'wp-recipe-maker' ),
132
+ __( 'c', 'wp-recipe-maker' ),
133
+ __( 'fluid ounces', 'wp-recipe-maker' ),
134
+ __( 'fluid ounce', 'wp-recipe-maker' ),
135
+ __( 'fl ounces', 'wp-recipe-maker' ),
136
+ __( 'fl ounce', 'wp-recipe-maker' ),
137
+ __( 'floz', 'wp-recipe-maker' ),
138
+ __( 'tablespoons', 'wp-recipe-maker' ),
139
+ __( 'tablespoon', 'wp-recipe-maker' ),
140
+ __( 'tbsps', 'wp-recipe-maker' ),
141
+ __( 'tbsp', 'wp-recipe-maker' ),
142
+ __( 'tbls', 'wp-recipe-maker' ),
143
+ __( 'tbs', 'wp-recipe-maker' ),
144
+ __( 'tb', 'wp-recipe-maker' ),
145
+ __( 'T', 'wp-recipe-maker' ),
146
+ __( 'teaspoons', 'wp-recipe-maker' ),
147
+ __( 'teaspoon', 'wp-recipe-maker' ),
148
+ __( 'tsps', 'wp-recipe-maker' ),
149
+ __( 'tsp', 'wp-recipe-maker' ),
150
+ __( 'ts', 'wp-recipe-maker' ),
151
+ __( 't', 'wp-recipe-maker' ),
152
+ // Length.
153
+ __( 'meters', 'wp-recipe-maker' ),
154
+ __( 'meter', 'wp-recipe-maker' ),
155
+ __( 'm', 'wp-recipe-maker' ),
156
+ __( 'centimeters', 'wp-recipe-maker' ),
157
+ __( 'centimeter', 'wp-recipe-maker' ),
158
+ __( 'cm', 'wp-recipe-maker' ),
159
+ __( 'millimeters', 'wp-recipe-maker' ),
160
+ __( 'millimeter', 'wp-recipe-maker' ),
161
+ __( 'mm', 'wp-recipe-maker' ),
162
+ __( 'yards', 'wp-recipe-maker' ),
163
+ __( 'yard', 'wp-recipe-maker' ),
164
+ __( 'yd', 'wp-recipe-maker' ),
165
+ __( 'feet', 'wp-recipe-maker' ),
166
+ __( 'foot', 'wp-recipe-maker' ),
167
+ __( 'ft', 'wp-recipe-maker' ),
168
+ __( 'inches', 'wp-recipe-maker' ),
169
+ __( 'inch', 'wp-recipe-maker' ),
170
+ __( 'in', 'wp-recipe-maker' ),
171
+ // General.
172
+ __( 'cloves', 'wp-recipe-maker' ),
173
+ __( 'clove', 'wp-recipe-maker' ),
174
+ __( 'leaves', 'wp-recipe-maker' ),
175
+ __( 'leave', 'wp-recipe-maker' ),
176
+ __( 'slices', 'wp-recipe-maker' ),
177
+ __( 'slice', 'wp-recipe-maker' ),
178
+ __( 'pieces', 'wp-recipe-maker' ),
179
+ __( 'piece', 'wp-recipe-maker' ),
180
+ __( 'pinches', 'wp-recipe-maker' ),
181
+ __( 'pinche', 'wp-recipe-maker' ),
182
+ );
183
+
184
+ $units = apply_filters( 'wprm_parse_ingredient_units', $units );
185
+
186
+ return array_map( 'sanitize_text_field', $units );
187
+ }
188
+ }
includes/admin/class-wprm-recipe-sanitizer.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Santize recipe input fields.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin
10
+ */
11
+
12
+ /**
13
+ * Santize recipe input fields.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Recipe_Sanitizer {
21
+
22
+ /**
23
+ * Sanitize recipe array.
24
+ *
25
+ * @since 1.0.0
26
+ * @param array $recipe Array containing all recipe input data.
27
+ */
28
+ public static function sanitize( $recipe ) {
29
+ $sanitized_recipe = array();
30
+
31
+ // Text fields.
32
+ $sanitized_recipe['name'] = isset( $recipe['name'] ) ? sanitize_text_field( $recipe['name'] ) : '';
33
+ $sanitized_recipe['summary'] = isset( $recipe['summary'] ) ? wp_kses_post( $recipe['summary'] ) : '';
34
+ $sanitized_recipe['servings_unit'] = isset( $recipe['servings_unit'] ) ? sanitize_text_field( $recipe['servings_unit'] ) : '';
35
+ $sanitized_recipe['notes'] = isset( $recipe['notes'] ) ? wp_kses_post( $recipe['notes'] ) : '';
36
+
37
+ // Number fields.
38
+ $sanitized_recipe['image_id'] = isset( $recipe['image_id'] ) ? intval( $recipe['image_id'] ) : 0;
39
+ $sanitized_recipe['servings'] = isset( $recipe['servings'] ) ? intval( $recipe['servings'] ) : 0;
40
+ $sanitized_recipe['prep_time'] = isset( $recipe['prep_time'] ) ? intval( $recipe['prep_time'] ) : 0;
41
+ $sanitized_recipe['cook_time'] = isset( $recipe['cook_time'] ) ? intval( $recipe['cook_time'] ) : 0;
42
+ $sanitized_recipe['total_time'] = isset( $recipe['total_time'] ) ? intval( $recipe['total_time'] ) : 0;
43
+
44
+ // Recipe Tags.
45
+ $sanitized_recipe['tags'] = array();
46
+ $sanitized_recipe['tags']['course'] = isset( $recipe['tags'] ) && isset( $recipe['tags']['course'] ) && $recipe['tags']['course'] ? array_map( array( __CLASS__, 'sanitize_tags' ), $recipe['tags']['course'] ) : array();
47
+ $sanitized_recipe['tags']['cuisine'] = isset( $recipe['tags'] ) && isset( $recipe['tags']['cuisine'] ) && $recipe['tags']['cuisine'] ? array_map( array( __CLASS__, 'sanitize_tags' ), $recipe['tags']['cuisine'] ) : array();
48
+
49
+ // Recipe Ingredients.
50
+ $sanitized_recipe['ingredients'] = array();
51
+
52
+ if ( isset( $recipe['ingredients'] ) ) {
53
+ foreach ( $recipe['ingredients'] as $ingredient_group ) {
54
+ $sanitized_group = array(
55
+ 'ingredients' => array(),
56
+ 'name' => isset( $ingredient_group['name'] ) ? sanitize_text_field( $ingredient_group['name'] ) : '',
57
+ );
58
+
59
+ if ( isset( $ingredient_group['ingredients'] ) ) {
60
+ foreach ( $ingredient_group['ingredients'] as $ingredient ) {
61
+ if ( isset( $ingredient['raw'] ) && ! isset( $ingredient['name'] ) ) {
62
+ $ingredient = WPRM_Recipe_Parser::parse_ingredient( $ingredient['raw'] );
63
+ }
64
+
65
+ $sanitized_ingredient = array(
66
+ 'amount' => isset( $ingredient['amount'] ) ? sanitize_text_field( $ingredient['amount'] ) : '',
67
+ 'unit' => isset( $ingredient['unit'] ) ? sanitize_text_field( $ingredient['unit'] ) : '',
68
+ 'name' => isset( $ingredient['name'] ) ? sanitize_text_field( $ingredient['name'] ) : '',
69
+ 'notes' => isset( $ingredient['notes'] ) ? sanitize_text_field( $ingredient['notes'] ) : '',
70
+ );
71
+
72
+ // Get ingredient ID from name.
73
+ if ( $sanitized_ingredient['name'] ) {
74
+ $term = term_exists( $sanitized_ingredient['name'], 'wprm_ingredient' ); // @codingStandardsIgnoreLine
75
+
76
+ if ( 0 === $term || null === $term ) {
77
+ $term = wp_insert_term( $sanitized_ingredient['name'], 'wprm_ingredient' );
78
+ }
79
+
80
+ if ( is_wp_error( $term ) ) {
81
+ if ( isset( $term->error_data['term_exists'] ) ) {
82
+ $term_id = $term->error_data['term_exists'];
83
+ } else {
84
+ $term_id = 0;
85
+ }
86
+ } else {
87
+ $term_id = $term['term_id'];
88
+ }
89
+
90
+ $sanitized_ingredient['id'] = intval( $term_id );
91
+
92
+ $sanitized_group['ingredients'][] = $sanitized_ingredient;
93
+ }
94
+ }
95
+ }
96
+
97
+ if ( count( $sanitized_group['ingredients'] ) > 0 ) {
98
+ $sanitized_recipe['ingredients'][] = $sanitized_group;
99
+ }
100
+ }
101
+ }
102
+
103
+ // Recipe Instructions.
104
+ $sanitized_recipe['instructions'] = array();
105
+
106
+ if ( isset( $recipe['instructions'] ) ) {
107
+ foreach ( $recipe['instructions'] as $instruction_group ) {
108
+ $sanitized_group = array(
109
+ 'instructions' => array(),
110
+ 'name' => isset( $instruction_group['name'] ) ? sanitize_text_field( $instruction_group['name'] ) : '',
111
+ );
112
+
113
+ if ( isset( $instruction_group['instructions'] ) ) {
114
+ foreach ( $instruction_group['instructions'] as $instruction ) {
115
+ $sanitized_instruction = array(
116
+ 'text' => isset( $instruction['text'] ) ? wp_kses_post( $instruction['text'] ) : '',
117
+ 'image' => isset( $instruction['image'] ) ? intval( $instruction['image'] ) : 0,
118
+ );
119
+
120
+ if ( $sanitized_instruction['text'] || $sanitized_instruction['image'] ) {
121
+ $sanitized_group['instructions'][] = $sanitized_instruction;
122
+ }
123
+ }
124
+ }
125
+
126
+ if ( count( $sanitized_group['instructions'] ) > 0 ) {
127
+ $sanitized_recipe['instructions'][] = $sanitized_group;
128
+ }
129
+ }
130
+ }
131
+
132
+ // Other fields.
133
+ $sanitized_recipe['import_source'] = isset( $recipe['import_source'] ) ? sanitize_text_field( $recipe['import_source'] ) : '';
134
+ $sanitized_recipe['import_backup'] = isset( $recipe['import_backup'] ) ? $recipe['import_backup'] : array();
135
+
136
+ return $sanitized_recipe;
137
+ }
138
+
139
+ /**
140
+ * Sanitize recipe tags.
141
+ *
142
+ * @since 1.0.0
143
+ * @param mixed $tag Tag ID or new tag name.
144
+ */
145
+ public static function sanitize_tags( $tag ) {
146
+ if ( is_numeric( $tag ) ) {
147
+ return intval( $tag );
148
+ } else {
149
+ return sanitize_text_field( $tag );
150
+ }
151
+ }
152
+ }
includes/admin/class-wprm-recipe-saver.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for saving recipes.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin
10
+ */
11
+
12
+ /**
13
+ * Responsible for saving recipes.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Recipe_Saver {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'wp_ajax_wprm_save_recipe', array( __CLASS__, 'ajax_save_recipe' ) );
29
+ add_action( 'save_post', array( __CLASS__, 'update_post' ), 10, 2 );
30
+ }
31
+
32
+ /**
33
+ * Save recipe submitted through AJAX.
34
+ *
35
+ * @since 1.0.0
36
+ */
37
+ public static function ajax_save_recipe() {
38
+ if ( check_ajax_referer( 'wprm', 'security', false ) ) {
39
+ $recipe = isset( $_POST['recipe'] ) ? WPRM_Recipe_Sanitizer::sanitize( wp_unslash( $_POST['recipe'] ) ) : array(); // Input var okay.
40
+ $recipe_id = isset( $_POST['recipe_id'] ) ? intval( $_POST['recipe_id'] ) : 0; // Input var okay.
41
+
42
+ if ( 0 !== $recipe_id && WPRM_POST_TYPE === get_post_type( $recipe_id ) ) {
43
+ if ( current_user_can( 'edit_post', $recipe_id ) ) {
44
+ WPRM_Recipe_Saver::update_recipe( $recipe_id, $recipe );
45
+ }
46
+ } else {
47
+ if ( current_user_can( 'edit_posts' ) ) {
48
+ $recipe_id = WPRM_Recipe_Saver::create_recipe( $recipe );
49
+ }
50
+ }
51
+
52
+ wp_send_json_success( array(
53
+ 'id' => $recipe_id,
54
+ ) );
55
+ }
56
+
57
+ wp_die();
58
+ }
59
+
60
+ /**
61
+ * Create a new recipe.
62
+ *
63
+ * @since 1.0.0
64
+ * @param array $recipe Recipe fields to save.
65
+ */
66
+ public static function create_recipe( $recipe ) {
67
+ $post = array(
68
+ 'post_type' => WPRM_POST_TYPE,
69
+ 'post_status' => 'draft',
70
+ );
71
+
72
+ $recipe_id = wp_insert_post( $post );
73
+ WPRM_Recipe_Saver::update_recipe( $recipe_id, $recipe );
74
+
75
+ return $recipe_id;
76
+ }
77
+
78
+ /**
79
+ * Save recipe fields.
80
+ *
81
+ * @since 1.0.0
82
+ * @param int $id Post ID of the recipe.
83
+ * @param array $recipe Recipe fields to save.
84
+ */
85
+ public static function update_recipe( $id, $recipe ) {
86
+ // Post Fields.
87
+ $post = array(
88
+ 'ID' => $id,
89
+ 'post_title' => $recipe['name'],
90
+ 'post_content' => $recipe['summary'],
91
+ );
92
+ wp_update_post( $post );
93
+
94
+ // Featured Image.
95
+ if ( $recipe['image_id'] ) {
96
+ set_post_thumbnail( $id, $recipe['image_id'] );
97
+ } else {
98
+ delete_post_thumbnail( $id );
99
+ }
100
+
101
+ // Recipe Taxonomies.
102
+ wp_set_object_terms( $id, $recipe['tags']['course'], 'wprm_course', false );
103
+ wp_set_object_terms( $id, $recipe['tags']['cuisine'], 'wprm_cuisine', false );
104
+
105
+ // Recipe Ingredients.
106
+ $ingredient_ids = array();
107
+ foreach ( $recipe['ingredients'] as $ingredient_group ) {
108
+ foreach ( $ingredient_group['ingredients'] as $ingredient ) {
109
+ $ingredient_ids = intval( $ingredient['id'] );
110
+ }
111
+ }
112
+ wp_set_object_terms( $id, $ingredient_ids, 'wprm_ingredient', false );
113
+
114
+ // Meta Fields.
115
+ update_post_meta( $id, 'wprm_servings', $recipe['servings'] );
116
+ update_post_meta( $id, 'wprm_servings_unit', $recipe['servings_unit'] );
117
+ update_post_meta( $id, 'wprm_prep_time', $recipe['prep_time'] );
118
+ update_post_meta( $id, 'wprm_cook_time', $recipe['cook_time'] );
119
+ update_post_meta( $id, 'wprm_total_time', $recipe['total_time'] );
120
+ update_post_meta( $id, 'wprm_ingredients', $recipe['ingredients'] );
121
+ update_post_meta( $id, 'wprm_instructions', $recipe['instructions'] );
122
+ update_post_meta( $id, 'wprm_notes', $recipe['notes'] );
123
+
124
+ update_post_meta( $id, 'wprm_import_source', $recipe['import_source'] );
125
+ update_post_meta( $id, 'wprm_import_backup', $recipe['import_backup'] );
126
+
127
+ WPRM_Recipe_Manager::invalidate_recipe( $id );
128
+ }
129
+
130
+ /**
131
+ * Check if post being saved contains recipes we need to update.
132
+ *
133
+ * @since 1.0.0
134
+ * @param int $id Post ID being saved.
135
+ * @param objoct $post Post being saved.
136
+ */
137
+ public static function update_post( $id, $post ) {
138
+ $recipe_ids = WPRM_Recipe_Manager::get_recipe_ids_from_content( $post->post_content );
139
+
140
+ foreach ( $recipe_ids as $recipe_id ) {
141
+ $recipe = array(
142
+ 'ID' => $recipe_id,
143
+ 'post_status' => $post->post_status,
144
+ 'post_author' => $post->post_author,
145
+ 'post_date' => $post->post_date,
146
+ 'post_modified' => $post->post_modified,
147
+ );
148
+ wp_update_post( $recipe );
149
+
150
+ update_post_meta( $recipe_id, 'wprm_parent_post_id', $post->ID );
151
+ }
152
+ }
153
+ }
154
+
155
+ WPRM_Recipe_Saver::init();
includes/admin/import/class-wprm-import-easyrecipe.php ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for importing EasyRecipe recipes.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/import
10
+ */
11
+
12
+ /**
13
+ * Responsible for importing EasyRecipe recipes.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/import
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Import_Easyrecipe extends WPRM_Import {
21
+ /**
22
+ * Get the UID of this import source.
23
+ *
24
+ * @since 1.0.0
25
+ */
26
+ public function get_uid() {
27
+ return 'easyrecipe';
28
+ }
29
+
30
+ /**
31
+ * Get the name of this import source.
32
+ *
33
+ * @since 1.0.0
34
+ */
35
+ public function get_name() {
36
+ return 'EasyRecipe';
37
+ }
38
+
39
+ /**
40
+ * Get a list of recipes that are available to import.
41
+ *
42
+ * @since 1.0.0
43
+ */
44
+ public function get_recipes() {
45
+ if ( ! class_exists( 'simple_html_dom' ) && ! class_exists( 'simple_html_dom_node' ) ) {
46
+ require_once( WPRM_DIR . '/vendor/simple_html_dom/simple_html_dom.php' );
47
+ libxml_use_internal_errors( true );
48
+ }
49
+
50
+ $recipes = array();
51
+
52
+ // Loop through all posts.
53
+ $limit = 100;
54
+ $offset = 0;
55
+
56
+ while ( true ) {
57
+ $args = array(
58
+ 'post_type' => array( 'post', 'page' ),
59
+ 'post_status' => 'any',
60
+ 'orderby' => 'date',
61
+ 'order' => 'DESC',
62
+ 'posts_per_page' => $limit,
63
+ 'offset' => $offset,
64
+ );
65
+
66
+ $query = new WP_Query( $args );
67
+
68
+ if ( ! $query->have_posts() ) {
69
+ break;
70
+ }
71
+
72
+ $posts = $query->posts;
73
+
74
+ foreach ( $posts as $post ) {
75
+ $recipes_html = $this->get_easyrecipe_recipes( $post->post_content );
76
+
77
+ if ( count( $recipes_html ) > 0 ) {
78
+ foreach ( $recipes_html as $index => $recipe_html ) {
79
+ $name = $recipe_html->find( 'div[class=ERName]', 0 );
80
+ $name = is_object( $name ) ? $this->strip_easyrecipe_tags( $name->plaintext ) : __( 'Unknown', 'wp-recipe-maker' );
81
+
82
+ $recipe_id = $post->ID . '-' . $index;
83
+ $recipes[ $recipe_id ] = array(
84
+ 'name' => $name,
85
+ 'url' => get_edit_post_link( $post->ID ),
86
+ );
87
+ }
88
+ }
89
+
90
+ wp_cache_delete( $post->ID, 'posts' );
91
+ wp_cache_delete( $post->ID, 'post_meta' );
92
+ }
93
+
94
+ $offset += $limit;
95
+ wp_cache_flush();
96
+ }
97
+
98
+ return $recipes;
99
+ }
100
+
101
+ /**
102
+ * Get recipe with the specified ID in the import format.
103
+ *
104
+ * @since 1.0.0
105
+ * @param mixed $id ID of the recipe we want to import.
106
+ */
107
+ public function get_recipe( $id ) {
108
+ if ( ! class_exists( 'simple_html_dom' ) && ! class_exists( 'simple_html_dom_node' ) ) {
109
+ require_once( WPRM_DIR . '/vendor/simple_html_dom/simple_html_dom.php' );
110
+ libxml_use_internal_errors( true );
111
+ }
112
+
113
+ $id_parts = explode( '-', $id, 2 );
114
+ $post_id = intval( $id_parts[0] );
115
+ $recipe_index = intval( $id_parts[1] );
116
+
117
+ $post = get_post( $post_id );
118
+ $recipes = $this->get_easyrecipe_recipes( $post->post_content );
119
+ $recipe_html = isset( $recipes[ $recipe_index ] ) ? $recipes[ $recipe_index ] : false;
120
+
121
+ if ( $recipe_html ) {
122
+ $recipe = array(
123
+ 'import_id' => 0, // Set to 0 because we need to create a new recipe post.
124
+ 'import_backup' => array(
125
+ 'easyrecipe_content' => $post->post_content,
126
+ ),
127
+ );
128
+
129
+ // Featured Image.
130
+ $images = $this->get_easyrecipe_images( $recipe_html->innertext );
131
+ if ( isset( $images[0] ) ) {
132
+ $recipe['image_id'] = $images[0]['id'];
133
+ }
134
+
135
+ // Simple matching.
136
+ $easyrecipe_field = $recipe_html->find( 'div[class=ERName]', 0 );
137
+ $wprm_field = is_object( $easyrecipe_field ) ? $this->strip_easyrecipe_tags( $easyrecipe_field->plaintext ) : '';
138
+ $recipe['name'] = $wprm_field;
139
+
140
+ $easyrecipe_field = $recipe_html->find( 'div[class=ERSummary]', 0 );
141
+ $wprm_field = is_object( $easyrecipe_field ) ? $this->replace_easyrecipe_tags( $easyrecipe_field->plaintext ) : '';
142
+ $recipe['summary'] = $wprm_field;
143
+
144
+ $easyrecipe_field = $recipe_html->find( 'div[class=ERNotes]', 0 );
145
+ $wprm_field = is_object( $easyrecipe_field ) ? $this->replace_easyrecipe_tags( $easyrecipe_field->plaintext, true ) : '';
146
+ $recipe['notes'] = $wprm_field;
147
+
148
+ // Servings.
149
+ $servings = $recipe_html->find( 'span[class=yield]', 0 );
150
+ $easyrecipe_servings = is_object( $servings ) ? trim( $servings->plaintext ) : '';
151
+
152
+ $match = preg_match( '/^\s*\d+/', $easyrecipe_servings, $servings_array );
153
+ if ( 1 === $match ) {
154
+ $servings = str_replace( ' ','', $servings_array[0] );
155
+ } else {
156
+ $servings = '';
157
+ }
158
+
159
+ $servings_unit = preg_replace( '/^\s*\d+\s*/', '', $easyrecipe_servings );
160
+
161
+ $recipe['servings'] = $servings;
162
+ $recipe['servings_unit'] = $servings_unit;
163
+
164
+ // Cook times.
165
+ $easyrecipe_times = array();
166
+ $times = $recipe_html->find( 'time' );
167
+ foreach ( $times as $time ) {
168
+ $easyrecipe_times[ $time->itemprop ] = $this->easyrecipe_time_to_minutes( $time->datetime );
169
+ }
170
+
171
+ $recipe['prep_time'] = isset( $easyrecipe_times['prepTime'] ) ? $easyrecipe_times['prepTime'] : 0;
172
+ $recipe['cook_time'] = isset( $easyrecipe_times['cookTime'] ) ? $easyrecipe_times['cookTime'] : 0;
173
+ $recipe['total_time'] = isset( $easyrecipe_times['totalTime'] ) ? $easyrecipe_times['totalTime'] : 0;
174
+
175
+ // Recipe Tags.
176
+ $easyrecipe_field = $recipe_html->find( 'span[class=type]', 0 );
177
+ $wprm_field = is_object( $easyrecipe_field ) ? trim( $easyrecipe_field->plaintext ) : '';
178
+ $wprm_field = str_replace( ';', ',', $wprm_field );
179
+ $courses = preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $wprm_field );
180
+ $courses = '' === $courses[0] ? array() : $courses;
181
+
182
+ $easyrecipe_field = $recipe_html->find( 'span[class=cuisine]', 0 );
183
+ $wprm_field = is_object( $easyrecipe_field ) ? trim( $easyrecipe_field->plaintext ) : '';
184
+ $wprm_field = str_replace( ';', ',', $wprm_field );
185
+ $cuisines = preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $wprm_field );
186
+ $cuisines = '' === $cuisines[0] ? array() : $cuisines;
187
+
188
+ $recipe['tags'] = array(
189
+ 'course' => $courses,
190
+ 'cuisine' => $cuisines,
191
+ );
192
+
193
+ // Ingredients.
194
+ $ingredients = array();
195
+ $ingredient_list = $recipe_html->find( 'ul[class=ingredients]' );
196
+ $ingredient_elements = isset( $ingredient_list[0] ) && is_object( $ingredient_list[0] ) ? $ingredient_list[0]->children() : array();
197
+
198
+ $group = array(
199
+ 'ingredients' => array(),
200
+ 'name' => '',
201
+ );
202
+ foreach ( $ingredient_elements as $ingredient_element ) {
203
+ if ( strpos( $ingredient_element->class, 'ERSeparator' ) !== false ) {
204
+ // Ingredient group.
205
+ $ingredients[] = $group;
206
+
207
+ $group = array(
208
+ 'ingredients' => array(),
209
+ 'name' => $this->strip_easyrecipe_tags( $ingredient_element->plaintext ),
210
+ );
211
+ } else {
212
+ // Ingredient.
213
+ $text = trim( $this->strip_easyrecipe_tags( $ingredient_element->plaintext ) );
214
+
215
+ if ( strlen( $text ) > 0 ) {
216
+ $group['ingredients'][] = array(
217
+ 'raw' => $text,
218
+ );
219
+ }
220
+ }
221
+ }
222
+ $ingredients[] = $group;
223
+ $recipe['ingredients'] = $ingredients;
224
+
225
+ // Instructions.
226
+ $instructions = array();
227
+ $instruction_div = $recipe_html->find( 'div[class=instructions]' );
228
+ $instruction_children = isset( $instruction_div[0] ) && is_object( $instruction_div[0] ) ? $instruction_div[0]->children() : array();
229
+
230
+ $group = array(
231
+ 'instructions' => array(),
232
+ 'name' => '',
233
+ );
234
+ foreach ( $instruction_children as $instruction_child ) {
235
+ if ( 'div' === $instruction_child->tag && false !== strpos( $instruction_child->class, 'ERSeparator' ) ) {
236
+ // Instruction Group.
237
+ $instructions[] = $group;
238
+
239
+ $group = array(
240
+ 'instructions' => array(),
241
+ 'name' => $this->strip_easyrecipe_tags( $instruction_child->plaintext ),
242
+ );
243
+ } elseif ( 'ol' === $instruction_child->tag ) {
244
+ $instruction_steps = $instruction_child->children();
245
+
246
+ foreach ( $instruction_steps as $instruction_step ) {
247
+ if ( false !== strpos( $instruction_step->class, 'instruction' ) ) {
248
+ $text = $this->replace_easyrecipe_tags( $instruction_step->plaintext );
249
+ $images = $this->get_easyrecipe_images( $instruction_step->plaintext );
250
+
251
+ if ( count( $images ) === 0 ) {
252
+ // Create an instruction step without image.
253
+ $group['instructions'][] = array(
254
+ 'text' => $text,
255
+ 'image' => '',
256
+ );
257
+ } else {
258
+ // We have at least 1 image, create an instruction step for each image.
259
+ foreach ( $images as $image ) {
260
+ $group['instructions'][] = array(
261
+ 'text' => $text,
262
+ 'image' => $image['id'],
263
+ );
264
+ $text = ''; // Only use description for first step.
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ $instructions[] = $group;
272
+ $recipe['instructions'] = $instructions;
273
+ } else {
274
+ $recipe = false;
275
+ }
276
+
277
+ return $recipe;
278
+ }
279
+
280
+ /**
281
+ * Replace the original recipe with the newly imported WPRM one.
282
+ *
283
+ * @since 1.0.0
284
+ * @param mixed $id ID of the recipe we want replace.
285
+ * @param mixed $wprm_id ID of the WPRM recipe to replace with.
286
+ */
287
+ public function replace_recipe( $id, $wprm_id ) {
288
+ if ( ! class_exists( 'simple_html_dom' ) && ! class_exists( 'simple_html_dom_node' ) ) {
289
+ require_once( WPRM_DIR . '/vendor/simple_html_dom/simple_html_dom.php' );
290
+ libxml_use_internal_errors( true );
291
+ }
292
+
293
+ $id_parts = explode( '-', $id, 2 );
294
+ $post_id = intval( $id_parts[0] );
295
+ $recipe_index = intval( $id_parts[1] );
296
+
297
+ $post = get_post( $post_id );
298
+ $html = $this->get_html( $post->post_content );
299
+
300
+ // Find EasyRecipe to replace with our shortcode.
301
+ $easyrecipe = $html->find( 'div[class=easyrecipe]', $recipe_index );
302
+
303
+ // If surrounded by wrapper we need to replace that as well.
304
+ $parent = $easyrecipe->parent();
305
+ if( isset( $parent->class ) && false !== strpos( $parent->class, 'easyrecipeWrapper' ) ) {
306
+ $easyrecipe = $parent;
307
+ }
308
+ $easyrecipe->outertext = '[wprm-recipe id="' . $wprm_id . '"]';
309
+
310
+ $body = $html->find( 'body', 0 );
311
+ $content = $body->innertext;
312
+
313
+ $update_content = array(
314
+ 'ID' => $post_id,
315
+ 'post_content' => $content,
316
+ );
317
+ wp_update_post( $update_content );
318
+ }
319
+
320
+ /**
321
+ * Get EasyRecipe recipes that are used in this content.
322
+ *
323
+ * @since 1.0.0
324
+ * @param mixed $post_content Post content to find recipes in.
325
+ */
326
+ private function get_easyrecipe_recipes( $post_content ) {
327
+ $html = $this->get_html( $post_content );
328
+ $recipes = $html->find( 'div[class=easyrecipe]' );
329
+ return $recipes;
330
+ }
331
+
332
+ /**
333
+ * Get post content as HTML.
334
+ *
335
+ * @since 1.0.0
336
+ * @param mixed $post_content Post content to get the HTML for.
337
+ */
338
+ private function get_html( $post_content ) {
339
+ $content = wpautop( $post_content );
340
+ return str_get_html( '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $content . '</body>' );
341
+ }
342
+
343
+ /**
344
+ * Get images that are used in the ER recipe.
345
+ *
346
+ * @since 1.0.0
347
+ * @param mixed $text Text to find images in.
348
+ */
349
+ private function get_easyrecipe_images( $text ) {
350
+ $images = array();
351
+
352
+ preg_match_all( '/\[img[^\]]*]/i', $text, $easyrecipe_images );
353
+
354
+ if ( isset( $easyrecipe_images[0] ) ) {
355
+ foreach ( $easyrecipe_images[0] as $easyrecipe_image ) {
356
+ preg_match( '/src=\"([^\"]*)\"/i', $easyrecipe_image, $image );
357
+
358
+ if ( isset( $image[1] ) ) {
359
+ $id = $this->get_attachment_id_from_url( $image[1] );
360
+ $image = wp_get_attachment_image_src( $id, array( 9999, 150 ) );
361
+
362
+ $images[] = array(
363
+ 'id' => $id,
364
+ 'img' => $image[0],
365
+ );
366
+ }
367
+ }
368
+ }
369
+
370
+ return $images;
371
+ }
372
+
373
+ /**
374
+ * Get image attachment ID from a given URL.
375
+ * Source: https://philipnewcomer.net/2012/11/get-the-attachment-id-from-an-image-url-in-wordpress/
376
+ *
377
+ * @since 1.0.0
378
+ * @param mixed $attachment_url Image URL.
379
+ */
380
+ function get_attachment_id_from_url( $attachment_url = '' ) {
381
+ global $wpdb;
382
+ $attachment_id = false;
383
+
384
+ // If there is no url, return.
385
+ if ( '' === $attachment_url ) {
386
+ return;
387
+ }
388
+
389
+ // Get the upload directory paths.
390
+ $upload_dir_paths = wp_upload_dir();
391
+
392
+ // Make sure the upload path base directory exists in the attachment URL, to verify that we're working with a media library image.
393
+ if ( false !== strpos( $attachment_url, $upload_dir_paths['baseurl'] ) ) {
394
+
395
+ // If this is the URL of an auto-generated thumbnail, get the URL of the original image.
396
+ $attachment_url = preg_replace( '/-\d+x\d+(?=\.(jpg|jpeg|png|gif)$)/i', '', $attachment_url );
397
+
398
+ // Remove the upload path base directory from the attachment URL.
399
+ $attachment_url = str_replace( $upload_dir_paths['baseurl'] . '/', '', $attachment_url );
400
+
401
+ // Finally, run a custom database query to get the attachment ID from the modified attachment URL.
402
+ $attachment_id = $wpdb->get_var( $wpdb->prepare( "SELECT wposts.ID FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta WHERE wposts.ID = wpostmeta.post_id AND wpostmeta.meta_key = '_wp_attached_file' AND wpostmeta.meta_value = '%s' AND wposts.post_type = 'attachment'", $attachment_url ) ); // @codingStandardsIgnoreLine
403
+ }
404
+
405
+ return $attachment_id;
406
+ }
407
+
408
+ /**
409
+ * Strip any EasyRecipe tags from the provided string.
410
+ *
411
+ * @since 1.0.0
412
+ * @param mixed $string Text to strip the tags from.
413
+ */
414
+ private function strip_easyrecipe_tags( $string ) {
415
+ $string = str_ireplace( '[b]', '', $string );
416
+ $string = str_ireplace( '[/b]', '', $string );
417
+ $string = str_ireplace( '[i]', '', $string );
418
+ $string = str_ireplace( '[/i]', '', $string );
419
+ $string = str_ireplace( '[u]', '', $string );
420
+ $string = str_ireplace( '[/u]', '', $string );
421
+ $string = str_ireplace( '[br]', '', $string );
422
+
423
+ $string = preg_replace( '/\[img[^\]]*]/i', '', $string );
424
+
425
+ $string = preg_replace( '/\[url[^\]]*]/i', '', $string );
426
+ $string = str_ireplace( '[/url]', '', $string );
427
+
428
+ return trim( $string );
429
+ }
430
+
431
+ /**
432
+ * Replace any EasyRecipe tags in the provided string with their HTML.
433
+ *
434
+ * @since 1.0.0
435
+ * @param mixed $string Text to replace the tags in.
436
+ * @param boolean $images Allow images in the output.
437
+ */
438
+ private function replace_easyrecipe_tags( $string, $images = false ) {
439
+ $string = str_ireplace( '[b]', '<strong>', $string );
440
+ $string = str_ireplace( '[/b]', '</strong>', $string );
441
+ $string = str_ireplace( '[i]', '<em>', $string );
442
+ $string = str_ireplace( '[/i]', '</em>', $string );
443
+ $string = str_ireplace( '[u]', '<span style="text-decoration: underline;">', $string );
444
+ $string = str_ireplace( '[/u]', '</span>', $string );
445
+ $string = str_ireplace( '[br]', '<br/>', $string );
446
+
447
+ if( $images ) {
448
+ $string = preg_replace( '/\[img([^\]]*)]/i', "<img$1 />", $string );
449
+ } else {
450
+ $string = preg_replace( '/\[img[^\]]*]/i', '', $string );
451
+ }
452
+
453
+ $string = preg_replace( '/\[url([^\]]*)]/i', "<a$1>", $string );
454
+ $string = str_ireplace( '[/url]', '</a>', $string );
455
+
456
+ return trim( $string );
457
+ }
458
+
459
+ /**
460
+ * Get time in minutes from ER time string.
461
+ *
462
+ * @since 1.0.0
463
+ * @param mixed $duration ER time string.
464
+ */
465
+ private function easyrecipe_time_to_minutes( $duration = 'PT' ) {
466
+ $date_abbr = array(
467
+ 'd' => 60*24,
468
+ 'h' => 60,
469
+ 'i' => 1,
470
+ );
471
+ $result = 0;
472
+
473
+ $arr = explode( 'T', $duration );
474
+ if ( isset( $arr[1] ) ) {
475
+ $arr[1] = str_replace( 'M', 'I', $arr[1] );
476
+ }
477
+ $duration = implode( 'T', $arr );
478
+
479
+ foreach ( $date_abbr as $abbr => $time ) {
480
+ if ( preg_match( '/(\d+)' . $abbr . '/i', $duration, $val ) ) {
481
+ $result += intval( $val[1] ) * $time;
482
+ }
483
+ }
484
+
485
+ return $result;
486
+ }
487
+ }
includes/admin/import/class-wprm-import.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Abstract class for importing to WPRM.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/import
10
+ */
11
+
12
+ /**
13
+ * Abstract class for importing to WPRM.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/import
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ abstract class WPRM_Import {
21
+ /**
22
+ * Get the UID of this import source.
23
+ *
24
+ * @since 1.0.0
25
+ */
26
+ abstract public function get_uid();
27
+
28
+ /**
29
+ * Get the name of this import source.
30
+ *
31
+ * @since 1.0.0
32
+ */
33
+ abstract public function get_name();
34
+
35
+ /**
36
+ * Get a list of recipes that are available to import.
37
+ *
38
+ * @since 1.0.0
39
+ */
40
+ abstract public function get_recipes();
41
+
42
+ /**
43
+ * Get recipe with the specified ID in the import format.
44
+ *
45
+ * @since 1.0.0
46
+ * @param mixed $id ID of the recipe we want to import.
47
+ */
48
+ abstract public function get_recipe( $id );
49
+
50
+ /**
51
+ * Replace the original recipe with the newly imported WPRM one.
52
+ *
53
+ * @since 1.0.0
54
+ * @param mixed $id ID of the recipe we want replace.
55
+ * @param mixed $wprm_id ID of the WPRM recipe to replace with.
56
+ */
57
+ abstract public function replace_recipe( $id, $wprm_id );
58
+ }
includes/admin/menu/class-wprm-admin-menu-faq.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Show a FAQ in the backend menu.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin
10
+ */
11
+
12
+ /**
13
+ * Show a FAQ in the backend menu.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Admin_Menu_Faq {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
29
+ add_action( 'admin_head-toplevel_page_wprecipemaker', array( __CLASS__, 'add_support_widget' ) );
30
+ add_action( 'admin_menu', array( __CLASS__, 'add_submenu_page' ) );
31
+ }
32
+
33
+ /**
34
+ * Enqueue stylesheets and scripts.
35
+ *
36
+ * @since 1.0.0
37
+ */
38
+ public static function enqueue() {
39
+ wp_enqueue_style( 'wprm-import', WPRM_URL . '/assets/css/admin/faq.min.css', array(), WPRM_VERSION, 'all' );
40
+ }
41
+
42
+ /**
43
+ * Add our support widget to the page.
44
+ *
45
+ * @since 1.0.0
46
+ */
47
+ public static function add_support_widget( $hook ) {
48
+ require_once( WPRM_DIR . 'templates/admin/menu/support-widget.html' );
49
+ }
50
+
51
+ /**
52
+ * Add the FAQ & Support submenu to the WPRM menu.
53
+ *
54
+ * @since 1.0.0
55
+ */
56
+ public static function add_submenu_page() {
57
+ add_submenu_page( 'wprecipemaker', 'FAQ & Support', 'FAQ & Support', 'manage_options', 'wprecipemaker', array( __CLASS__, 'page_template' ) );
58
+ }
59
+
60
+ /**
61
+ * Get the template for this submenu.
62
+ *
63
+ * @since 1.0.0
64
+ */
65
+ public static function page_template() {
66
+ require_once( WPRM_DIR . 'templates/admin/menu/faq.php' );
67
+ }
68
+ }
69
+
70
+ WPRM_Admin_Menu_Faq::init();
includes/admin/menu/class-wprm-admin-menu.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for showing the WPRM menu in the WP backend.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/menu
10
+ */
11
+
12
+ /**
13
+ * Responsible for showing the WPRM menu in the WP backend.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/menu
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Admin_Menu {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'admin_menu', array( __CLASS__, 'add_menu_page' ) );
29
+ }
30
+
31
+ /**
32
+ * Add WPRM to the wordpress menu.
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ public static function add_menu_page() {
37
+ // Base64 encoded svg icon.
38
+ $icon = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZyA+DQo8cGF0aCBmaWxsPSIjMDAwMDAwIiBkPSJNMTAsMEM5LjQsMCw5LDAuNCw5LDF2NEg3VjFjMC0wLjYtMC40LTEtMS0xUzUsMC40LDUsMXY0SDNWMWMwLTAuNi0wLjQtMS0xLTFTMSwwLjQsMSwxdjhjMCwxLjcsMS4zLDMsMywzDQp2MTBjMCwxLjEsMC45LDIsMiwyczItMC45LDItMlYxMmMxLjcsMCwzLTEuMywzLTNWMUMxMSwwLjQsMTAuNiwwLDEwLDB6Ii8+DQo8cGF0aCBkYXRhLWNvbG9yPSJjb2xvci0yIiBmaWxsPSIjMDAwMDAwIiBkPSJNMTksMGMtMy4zLDAtNiwyLjctNiw2djljMCwwLjYsMC40LDEsMSwxaDJ2NmMwLDEuMSwwLjksMiwyLDJzMi0wLjksMi0yVjENCkMyMCwwLjQsMTkuNiwwLDE5LDB6Ii8+DQo8L2c+PC9zdmc+';
39
+ add_menu_page( 'WP Recipe Maker', 'WP Recipe Maker', 'manage_options', 'wprecipemaker', array( 'WPRM_Admin_Menu_Faq', 'page_template' ), 'data:image/svg+xml;base64,' . $icon, 58 );
40
+ }
41
+ }
42
+
43
+ WPRM_Admin_Menu::init();
includes/admin/modal/class-wprm-button.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Add the "WP Recipe Maker" button to posts and pages.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
10
+ */
11
+
12
+ /**
13
+ * Add the "WP Recipe Maker" button to posts and pages.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Button {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'media_buttons', array( __CLASS__, 'add_shortcode_button' ) );
29
+ }
30
+
31
+ /**
32
+ * Add the "WP Recipe Maker" button as a media button for posts and pages.
33
+ *
34
+ * @since 1.0.0
35
+ * @param object $editor_id Name of the tinymce editor where this media button will be added.
36
+ */
37
+ public static function add_shortcode_button( $editor_id ) {
38
+ $screen = get_current_screen();
39
+
40
+ if ( 'wprm_recipe_notes' !== $editor_id && in_array( $screen->base, array( 'post', 'page' ), true ) ) {
41
+ $title = 'WP Recipe Maker';
42
+
43
+ echo '<button type="button" class="button wprm-modal-button" data-editor="' . esc_attr( $editor_id ) . '" title="' . esc_attr( $title ) . '">' . esc_html( $title ) . '</button>';
44
+ }
45
+ }
46
+ }
47
+
48
+ WPRM_Button::init();
includes/admin/modal/class-wprm-modal.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Add the recipe modal to posts and pages.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
10
+ */
11
+
12
+ /**
13
+ * Add the recipe modal to posts and pages.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Modal {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'admin_footer', array( __CLASS__, 'add_modal_content' ) );
29
+ add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
30
+
31
+ add_action( 'wp_ajax_wprm_get_thumbnail', array( __CLASS__, 'ajax_get_thumbnail' ) );
32
+ }
33
+
34
+ /**
35
+ * Enqueue stylesheets and scripts.
36
+ *
37
+ * @since 1.0.0
38
+ */
39
+ public static function enqueue() {
40
+ wp_enqueue_style( 'wprm-medium-editor', WPRM_URL . '/vendor/medium-editor/css/medium-editor.min.css', array(), WPRM_VERSION, 'all' );
41
+ wp_enqueue_style( 'wprm-medium-editor-theme', WPRM_URL . '/vendor/medium-editor/css/themes/beagle.min.css', array(), WPRM_VERSION, 'all' );
42
+ wp_enqueue_style( 'wprm-select2', WPRM_URL . '/vendor/select2/css/select2.min.css', array(), WPRM_VERSION, 'all' );
43
+ wp_enqueue_style( 'wprm-modal', WPRM_URL . '/assets/css/admin/modal.min.css', array(), WPRM_VERSION, 'all' );
44
+
45
+ wp_enqueue_script( 'wprm-medium-editor', WPRM_URL . '/vendor/medium-editor/js/medium-editor.min.js', array( 'jquery' ), WPRM_VERSION, true );
46
+ wp_enqueue_script( 'wprm-select2', WPRM_URL . '/vendor/select2/js/select2.min.js', array( 'jquery' ), WPRM_VERSION, true );
47
+ wp_enqueue_script( 'wprm-modal', WPRM_URL . '/assets/js/admin/modal.js', array( 'jquery' ), WPRM_VERSION, true );
48
+ wp_enqueue_script( 'wprm-shortcode', WPRM_URL . '/assets/js/admin/shortcode.js', array( 'jquery' ), WPRM_VERSION, true );
49
+ wp_enqueue_script( 'wprm-recipe-form', WPRM_URL . '/assets/js/admin/recipe-form.js', array( 'jquery', 'jquery-ui-sortable', 'wprm-modal', 'wprm-shortcode', 'wprm-medium-editor', 'wprm-select2' ), WPRM_VERSION, true );
50
+
51
+ wp_localize_script( 'wprm-modal', 'wprm_modal', array(
52
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
53
+ 'nonce' => wp_create_nonce( 'wprm' ),
54
+ 'text' => array(
55
+ 'media_title' => __( 'Select or Upload Image', 'wp-recipe-maker' ),
56
+ 'media_button' => __( 'Use Image', 'wp-recipe-maker' ),
57
+ 'shortcode_remove' => __( 'Are you sure you want to remove this recipe?', 'wp-recipe-maker' ),
58
+ ),
59
+ ));
60
+ }
61
+
62
+ /**
63
+ * Get the image thumbnail from its ID.
64
+ *
65
+ * @since 1.0.0
66
+ */
67
+ public static function ajax_get_thumbnail() {
68
+ if ( check_ajax_referer( 'wprm', 'security', false ) ) {
69
+ $image_id = isset( $_POST['image_id'] ) ? intval( $_POST['image_id'] ) : 0; // Input var okay.
70
+
71
+ $thumb = wp_get_attachment_image_src( $image_id, 'medium' );
72
+ $image_url = $thumb && isset( $thumb[0] ) ? $thumb[0] : '';
73
+
74
+ wp_send_json_success( array(
75
+ 'image_url' => $image_url,
76
+ ) );
77
+ }
78
+
79
+ wp_die();
80
+ }
81
+
82
+ /**
83
+ * Add modal template to edit screen.
84
+ *
85
+ * @since 1.0.0
86
+ */
87
+ public static function add_modal_content() {
88
+ $screen = get_current_screen();
89
+
90
+ if ( in_array( $screen->base, array( 'post', 'page' ), true ) ) {
91
+ $menu = WPRM_Modal::get_modal_menu();
92
+ require_once( WPRM_DIR . 'templates/admin/modal/modal.php' );
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get menu to show in modal.
98
+ *
99
+ * @since 1.0.0
100
+ */
101
+ public static function get_modal_menu() {
102
+ $menu = array(
103
+ 'recipe' => array(
104
+ 'order' => 100,
105
+ 'default' => true,
106
+ 'label' => __( 'Recipe', 'wp-recipe-maker' ),
107
+ 'tabs' => array(
108
+ 'recipe-details' => array(
109
+ 'order' => 100,
110
+ 'label' => __( 'Recipe Details', 'wp-recipe-maker' ),
111
+ 'template' => WPRM_DIR . 'templates/admin/modal/tabs/recipe-details.php',
112
+ ),
113
+ 'recipe-ingredients-instructions' => array(
114
+ 'order' => 200,
115
+ 'label' => __( 'Ingredients & Instructions', 'wp-recipe-maker' ),
116
+ 'template' => WPRM_DIR . 'templates/admin/modal/tabs/recipe-ingredients-instructions.php',
117
+ ),
118
+ 'recipe-notes' => array(
119
+ 'order' => 300,
120
+ 'label' => __( 'Recipe Notes', 'wp-recipe-maker' ),
121
+ 'template' => WPRM_DIR . 'templates/admin/modal/tabs/recipe-notes.php',
122
+ ),
123
+ ),
124
+ 'default_tab' => 'recipe-details',
125
+ ),
126
+ );
127
+
128
+ // Allow menu to be altered.
129
+ $menu = apply_filters( 'wprm_admin_modal_menu', $menu );
130
+
131
+ // Sort menu before returning.
132
+ $sorted_menu = array();
133
+ foreach ( $menu as $menu_item => $options ) {
134
+ uasort( $options['tabs'], array( __CLASS__, 'sort_by_order' ) );
135
+
136
+ $sorted_menu[ $menu_item ] = $options;
137
+ }
138
+
139
+ uasort( $sorted_menu, array( __CLASS__, 'sort_by_order' ) );
140
+
141
+ return $sorted_menu;
142
+ }
143
+
144
+ /**
145
+ * Custom sorting function for menu array.
146
+ *
147
+ * @since 1.0.0
148
+ * @param mixed $a First array to compare.
149
+ * @param mixed $b Second array to compare.
150
+ */
151
+ public static function sort_by_order( $a, $b ) {
152
+ return $a['order'] - $b['order'];
153
+ }
154
+ }
155
+
156
+ WPRM_Modal::init();
includes/admin/modal/class-wprm-shortcode-preview.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handle the display of the shortcode in the TinyMCE editor.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
10
+ */
11
+
12
+ /**
13
+ * Handle the display of the shortcode in the TinyMCE editor.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/admin/modal
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Shortcode_Preview {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'wp_ajax_wprm_shortcode_preview', array( __CLASS__, 'ajax_shortcode_preview' ) );
29
+ add_filter( 'mce_external_plugins', array( __CLASS__, 'tinymce_shortcode_plugin' ) );
30
+ }
31
+
32
+ /**
33
+ * Return preview to be used for recipe shortcode.
34
+ *
35
+ * @since 1.0.0
36
+ */
37
+ public static function ajax_shortcode_preview() {
38
+ $preview = '';
39
+
40
+ if ( check_ajax_referer( 'wprm', 'security', false ) ) {
41
+ $recipe_id = isset( $_POST['recipe_id'] ) ? intval( $_POST['recipe_id'] ) : 0; // Input var okay.
42
+
43
+ $post = get_post( $recipe_id );
44
+
45
+ $preview .= '<span contentEditable="false" style="font-weight: bold;" data-wprm-recipe="' . $recipe_id . '">WP Recipe Maker #' . $recipe_id . '</span>';
46
+ $preview .= '<span contentEditable="false" style="float: right; color: darkred;" data-wprm-recipe-remove="' . $recipe_id . '">' . esc_html__( 'remove', 'wp-recipe-maker' ) . '</span>';
47
+ $preview .= '<br/><br/>';
48
+
49
+ if ( ! is_null( $post ) && WPRM_POST_TYPE === $post->post_type ) {
50
+ $recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
51
+ $preview .= '<span contentEditable="false">' . $recipe->name() . '</span>';
52
+ //$preview .= '<span contentEditable="false" style="display: inline-block; background-image: url(\'' . $thumb[0] . '\'); background-size: ' . $thumb[1] . 'px ' . $thumb[2] . 'px; width: ' . $thumb[1] . 'px; height: ' . $thumb[2] . 'px;" data-wprm-recipe="' . $recipe_id . '">&nbsp;</span>';
53
+ }
54
+ }
55
+
56
+ echo $preview; // @codingStandardsIgnoreLine
57
+ wp_die();
58
+ }
59
+
60
+ /**
61
+ * Load custom TinyMCE plugin for handling the recipe shortcode.
62
+ *
63
+ * @since 1.0.0
64
+ * @param array $plugin_array Plugins to be used by TinyMCE.
65
+ */
66
+ public static function tinymce_shortcode_plugin( $plugin_array ) {
67
+ $plugin_array['wprecipemaker'] = WPRM_URL . '/assets/js/admin/shortcode-tinymce.js';
68
+ return $plugin_array;
69
+ }
70
+ }
71
+
72
+ WPRM_Shortcode_Preview::init();
includes/class-wp-recipe-maker.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The core plugin class.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes
10
+ */
11
+
12
+ /**
13
+ * The core plugin class.
14
+ *
15
+ * This is used to define internationalization, admin-specific hooks, and
16
+ * public-facing site hooks.
17
+ *
18
+ * Also maintains the unique identifier of this plugin as well as the current
19
+ * version of the plugin.
20
+ *
21
+ * @since 1.0.0
22
+ * @package WP_Recipe_Maker
23
+ * @subpackage WP_Recipe_Maker/includes
24
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
25
+ */
26
+ class WP_Recipe_Maker {
27
+
28
+ /**
29
+ * Make sure all is set up for the plugin to load.
30
+ *
31
+ * @since 1.0.0
32
+ */
33
+ public function __construct() {
34
+ $this->define_constants();
35
+ $this->load_dependencies();
36
+ }
37
+
38
+ /**
39
+ * Define any constants to be used in the plugin.
40
+ *
41
+ * @since 1.0.0
42
+ */
43
+ private function define_constants() {
44
+ define( 'WPRM_VERSION', '1.0.0' );
45
+ define( 'WPRM_POST_TYPE', 'wprm_recipe' );
46
+ define( 'WPRM_DIR', plugin_dir_path( dirname( __FILE__ ) ) );
47
+ define( 'WPRM_URL', plugin_dir_url( dirname( __FILE__ ) ) );
48
+ }
49
+
50
+ /**
51
+ * Load all plugin dependencies.
52
+ *
53
+ * @since 1.0.0
54
+ */
55
+ private function load_dependencies() {
56
+ // General.
57
+ require_once( WPRM_DIR . 'includes/class-wprm-i18n.php' );
58
+
59
+ // Public.
60
+ require_once( WPRM_DIR . 'includes/public/class-wprm-fallback-recipe.php' );
61
+ require_once( WPRM_DIR . 'includes/public/class-wprm-metadata.php' );
62
+ require_once( WPRM_DIR . 'includes/public/class-wprm-post-type.php' );
63
+ require_once( WPRM_DIR . 'includes/public/class-wprm-print.php' );
64
+ require_once( WPRM_DIR . 'includes/public/class-wprm-recipe-manager.php' );
65
+ require_once( WPRM_DIR . 'includes/public/class-wprm-recipe.php' );
66
+ require_once( WPRM_DIR . 'includes/public/class-wprm-shortcode.php' );
67
+ require_once( WPRM_DIR . 'includes/public/class-wprm-taxonomies.php' );
68
+ require_once( WPRM_DIR . 'includes/public/class-wprm-template-manager.php' );
69
+
70
+ // Admin.
71
+ if ( is_admin() ) {
72
+ // Import.
73
+ require_once( WPRM_DIR . 'includes/admin/import/class-wprm-import.php' );
74
+
75
+ // Menu.
76
+ require_once( WPRM_DIR . 'includes/admin/menu/class-wprm-admin-menu-faq.php' );
77
+ require_once( WPRM_DIR . 'includes/admin/menu/class-wprm-admin-menu.php' );
78
+
79
+ // Button.
80
+ require_once( WPRM_DIR . 'includes/admin/modal/class-wprm-button.php' );
81
+ require_once( WPRM_DIR . 'includes/admin/modal/class-wprm-modal.php' );
82
+ require_once( WPRM_DIR . 'includes/admin/modal/class-wprm-shortcode-preview.php' );
83
+
84
+ require_once( WPRM_DIR . 'includes/admin/class-wprm-import-manager.php' );
85
+ require_once( WPRM_DIR . 'includes/admin/class-wprm-recipe-parser.php' );
86
+ require_once( WPRM_DIR . 'includes/admin/class-wprm-recipe-sanitizer.php' );
87
+ require_once( WPRM_DIR . 'includes/admin/class-wprm-recipe-saver.php' );
88
+ }
89
+ }
90
+ }
includes/class-wprm-activator.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Fired during plugin activation.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes
10
+ */
11
+
12
+ /**
13
+ * Fired during plugin activation.
14
+ *
15
+ * This class defines all code necessary to run during the plugin's activation.
16
+ *
17
+ * @since 1.0.0
18
+ * @package WP_Recipe_Maker
19
+ * @subpackage WP_Recipe_Maker/includes
20
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
21
+ */
22
+ class WPRM_Activator {
23
+
24
+ /**
25
+ * Execute this on activation of the plugin.
26
+ *
27
+ * @since 1.0.0
28
+ */
29
+ public static function activate() {
30
+ // Set up recipe taxonomies.
31
+ WPRM_Post_Type::register_post_type();
32
+ WPRM_Taxonomies::register_taxonomies();
33
+ WPRM_Taxonomies::insert_default_taxonomy_terms();
34
+ }
35
+ }
includes/class-wprm-deactivator.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Fired during plugin deactivation.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes
10
+ */
11
+
12
+ /**
13
+ * Fired during plugin deactivation.
14
+ *
15
+ * This class defines all code necessary to run during the plugin's deactivation.
16
+ *
17
+ * @since 1.0.0
18
+ * @package WP_Recipe_Maker
19
+ * @subpackage WP_Recipe_Maker/includes
20
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
21
+ */
22
+ class WPRM_Deactivator {
23
+
24
+ /**
25
+ * Execute this on deactivation of the plugin.
26
+ *
27
+ * @since 1.0.0
28
+ */
29
+ public static function deactivate() {
30
+
31
+ }
32
+ }
includes/class-wprm-i18n.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Define the internationalization functionality.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes
10
+ */
11
+
12
+ /**
13
+ * Define the internationalization functionality.
14
+ *
15
+ * Loads and defines the internationalization files for this plugin
16
+ * so that it is ready for translation.
17
+ *
18
+ * @since 1.0.0
19
+ * @package WP_Recipe_Maker
20
+ * @subpackage WP_Recipe_Maker/includes
21
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
22
+ */
23
+ class WPRM_i18n {
24
+
25
+ /**
26
+ * Register actions and filters.
27
+ *
28
+ * @since 1.0.0
29
+ */
30
+ public static function init() {
31
+ add_action( 'plugins_loaded', array( __CLASS__, 'load_plugin_textdomain' ) );
32
+ }
33
+
34
+ /**
35
+ * Load the plugin text domain for translation.
36
+ *
37
+ * @since 1.0.0
38
+ */
39
+ public static function load_plugin_textdomain() {
40
+ load_plugin_textdomain(
41
+ 'wp-recipe-maker',
42
+ false,
43
+ dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
44
+ );
45
+ }
46
+ }
47
+
48
+ WPRM_i18n::init();
includes/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden
includes/public/class-wprm-fallback-recipe.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Replace shortcode with fallback recipe for when plugin is deactivated.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Replace shortcode with fallback recipe for when plugin is deactivated.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Fallback_Recipe {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_filter( 'the_content', array( __CLASS__, 'replace_fallback_with_shortcode' ), 1 );
29
+ add_filter( 'content_edit_pre', array( __CLASS__, 'replace_fallback_with_shortcode' ) );
30
+
31
+ add_filter( 'content_save_pre', array( __CLASS__, 'replace_shortcode_with_fallback' ) );
32
+ }
33
+
34
+ /**
35
+ * Replace shortcode with fallback recipe.
36
+ *
37
+ * @since 1.0.0
38
+ * @param mixed $content Content that is being saved.
39
+ */
40
+ public static function replace_shortcode_with_fallback( $content ) {
41
+ $recipe_shortcodes = array();
42
+ $pattern = get_shortcode_regex( array( 'wprm-recipe' ) );
43
+
44
+ if ( preg_match_all( '/'. $pattern .'/s', $content, $matches ) && array_key_exists( 2, $matches ) ) {
45
+ foreach ( $matches[2] as $key => $value ) {
46
+ if ( 'wprm-recipe' === $value ) {
47
+ $recipe_shortcodes[ $matches[0][ $key ] ] = shortcode_parse_atts( stripslashes( $matches[3][ $key ] ) );
48
+ }
49
+ }
50
+ }
51
+
52
+ foreach ( $recipe_shortcodes as $shortcode => $shortcode_options ) {
53
+ $recipe_id = isset( $shortcode_options['id'] ) ? intval( $shortcode_options['id'] ) : 0;
54
+ $fallback_recipe = self::get_fallback_recipe( $recipe_id );
55
+
56
+ $content = str_replace( $shortcode, $fallback_recipe, $content );
57
+ }
58
+
59
+ return $content;
60
+ }
61
+
62
+ /**
63
+ * Replace fallback recipe with shortcode for the content editor.
64
+ *
65
+ * @since 1.0.0
66
+ * @param mixed $content Content we want to filter before it gets passed along.
67
+ */
68
+ public static function replace_fallback_with_shortcode( $content ) {
69
+ preg_match_all( self::get_fallback_regex(), $content, $matches );
70
+ foreach ( $matches[0] as $key => $match ) {
71
+ $content = str_replace( $match, '[wprm-recipe id="' . $matches[1][ $key ] . '"]', $content );
72
+ }
73
+
74
+ return $content;
75
+ }
76
+
77
+ /**
78
+ * Get fallback HTML for a specific recipe.
79
+ *
80
+ * @since 1.0.0
81
+ * @param int $recipe_id ID of the recipe we want to get the fallback HTML for.
82
+ */
83
+ public static function get_fallback_recipe( $recipe_id ) {
84
+ $recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
85
+
86
+ if ( $recipe ) {
87
+ ob_start();
88
+ require( WPRM_DIR . 'templates/public/fallback-recipe.php' );
89
+ $fallback = ob_get_contents();
90
+ ob_end_clean();
91
+ } else {
92
+ $fallback = '';
93
+ }
94
+
95
+ return trim( $fallback );
96
+ }
97
+
98
+ /**
99
+ * Get the regex pattern to find fallback recipes.
100
+ *
101
+ * @since 1.0.0
102
+ */
103
+ public static function get_fallback_regex() {
104
+ return '/<!--WPRM Recipe (\d+)-->.+?<!--End WPRM Recipe-->/ms';
105
+ }
106
+ }
107
+
108
+ WPRM_Fallback_Recipe::init();
includes/public/class-wprm-metadata.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handle the recipe metadata.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Handle the recipe metadata.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Metadata {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ }
29
+
30
+ /**
31
+ * Get the metadata to output for a recipe.
32
+ *
33
+ * @since 1.0.0
34
+ * @param object $recipe Recipe to get the metadata for.
35
+ */
36
+ public static function get_metadata_output( $recipe ) {
37
+ $metadata = self::get_metadata( $recipe );
38
+ return '<script type="application/ld+json">' . wp_json_encode( $metadata ) . '</script>';
39
+ }
40
+
41
+ /**
42
+ * Get the metadata for a recipe.
43
+ *
44
+ * @since 1.0.0
45
+ * @param object $recipe Recipe to get the metadata for.
46
+ */
47
+ public static function get_metadata( $recipe ) {
48
+ // Essentials.
49
+ $metadata = array(
50
+ '@context' => 'http://schema.org/',
51
+ '@type' => 'Recipe',
52
+ 'name' => $recipe->name(),
53
+ 'author' => array(
54
+ '@type' => 'Person',
55
+ 'name' => $recipe->author(),
56
+ ),
57
+ 'datePublished' => $recipe->date(),
58
+ 'image' => $recipe->image_url( 'full' ),
59
+ 'description' => esc_html( $recipe->summary() ),
60
+ );
61
+
62
+ // Yield.
63
+ if ( $recipe->servings() ) {
64
+ $metadata['recipeYield'] = $recipe->servings() . ' ' . $recipe->servings_unit();
65
+ }
66
+
67
+ // Times.
68
+ if ( $recipe->prep_time() && $recipe->cook_time() ) {
69
+ // Only use separate ones when we have both.
70
+ $metadata['prepTime'] = 'PT' . $recipe->prep_time() . 'M';
71
+ $metadata['cookTime'] = 'PT' . $recipe->cook_time() . 'M';
72
+ } elseif ( $recipe->total_time() ) {
73
+ // Otherwise use total time.
74
+ $metadata['totalTime'] = 'PT' . $recipe->total_time() . 'M';
75
+ }
76
+
77
+ // Ingredients.
78
+ $ingredients = $recipe->ingredients_without_groups();
79
+ if( count( $ingredients ) > 0 ) {
80
+ $metadata_ingredients = array();
81
+
82
+ foreach ( $ingredients as $ingredient ) {
83
+ $metadata_ingredient = $ingredient['amount'] . ' ' . $ingredient['unit'] . ' ' . $ingredient['name'];
84
+ if ( trim( $ingredient['notes'] ) !== '' ) {
85
+ $metadata_ingredient .= ' (' . $ingredient['notes'] . ')';
86
+ }
87
+
88
+ $metadata_ingredients[] = $metadata_ingredient;
89
+ }
90
+
91
+ $metadata['recipeIngredient'] = $metadata_ingredients;
92
+ }
93
+
94
+ // Instructions.
95
+ $instructions = $recipe->instructions_without_groups();
96
+ if( count( $instructions ) > 0 ) {
97
+ $metadata_instructions = array();
98
+
99
+ foreach ( $instructions as $instruction ) {
100
+ $metadata_instructions[] = esc_html( $instruction['text'] );
101
+ }
102
+
103
+ $metadata['recipeInstructions'] = $metadata_instructions;
104
+ }
105
+
106
+ // Category & Cuisine.
107
+ $courses = $recipe->tags( 'course' );
108
+ if ( count( $courses ) > 0 ) {
109
+ $metadata['recipeCategory'] = wp_list_pluck( $courses, 'name' );
110
+ }
111
+ $cuisines = $recipe->tags( 'cuisine' );
112
+ if ( count( $cuisines ) > 0 ) {
113
+ $metadata['recipeCuisine'] = wp_list_pluck( $cuisines, 'name' );
114
+ }
115
+
116
+ // Allow external filtering of metadata.
117
+ return apply_filters( 'wprm_recipe_metadata', $metadata, $recipe );
118
+ }
119
+ }
120
+
121
+ WPRM_Metadata::init();
includes/public/class-wprm-post-type.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Register the Recipe post type.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Register the Recipe post type.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Post_Type {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'init', array( __CLASS__, 'register_post_type' ), 1 );
29
+ add_filter( 'post_type_link', array( __CLASS__, 'recipe_permalink' ), 10, 2 );
30
+ }
31
+
32
+ /**
33
+ * Register the Recipe post type.
34
+ *
35
+ * @since 1.0.0
36
+ */
37
+ public static function register_post_type() {
38
+ $labels = array(
39
+ 'name' => _x( 'Recipes', 'post type general name', 'wp-recipe-maker' ),
40
+ 'singular_name' => _x( 'Recipe', 'post type singular name', 'wp-recipe-maker' ),
41
+ );
42
+
43
+ $args = apply_filters( 'wprm_recipe_post_type_arguments', array(
44
+ 'labels' => $labels,
45
+ 'public' => false,
46
+ 'rewrite' => false,
47
+ 'capability_type' => 'post',
48
+ 'query_var' => false,
49
+ 'has_archive' => false,
50
+ ));
51
+
52
+ register_post_type( WPRM_POST_TYPE, $args );
53
+ }
54
+
55
+ /**
56
+ * Register the Recipe post type.
57
+ *
58
+ * @since 1.0.0
59
+ * @param mixed $url The post URL.
60
+ * @param object $post The post object.
61
+ */
62
+ public static function recipe_permalink( $url, $post ) {
63
+ if ( WPRM_POST_TYPE === $post->post_type ) {
64
+ $recipe = WPRM_Recipe_Manager::get_recipe( $post );
65
+ $parent_post_id = $recipe->parent_post_id();
66
+
67
+ if ( $parent_post_id ) {
68
+ $url = get_permalink( $parent_post_id );
69
+ }
70
+ }
71
+ return $url;
72
+ }
73
+ }
74
+
75
+ WPRM_Post_Type::init();
includes/public/class-wprm-print.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handle the recipe printing.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Handle the recipe printing.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Print {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'wp_ajax_wprm_print_recipe', array( __CLASS__, 'ajax_print_recipe' ) );
29
+ add_action( 'wp_ajax_nopriv_wprm_print_recipe', array( __CLASS__, 'ajax_print_recipe' ) );
30
+
31
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
32
+ }
33
+
34
+ /**
35
+ * Enqueue stylesheets and scripts.
36
+ *
37
+ * @since 1.0.0
38
+ */
39
+ public static function enqueue() {
40
+ wp_enqueue_script( 'wprm-print', WPRM_URL . '/assets/js/print.js', array( 'jquery' ), WPRM_VERSION, true );
41
+
42
+ wp_localize_script( 'wprm-print', 'wprm_public', array(
43
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
44
+ 'nonce' => wp_create_nonce( 'wprm' ),
45
+ ));
46
+ }
47
+
48
+ /**
49
+ * Get print HTML for a specific recipe.
50
+ *
51
+ * @since 1.0.0
52
+ */
53
+ public static function ajax_print_recipe() {
54
+ if ( check_ajax_referer( 'wprm', 'security', false ) ) {
55
+ $recipe_id = isset( $_POST['recipe_id'] ) ? intval( $_POST['recipe_id'] ) : 0; // Input var okay.
56
+
57
+ $print_html = '';
58
+ if ( 0 !== $recipe_id && WPRM_POST_TYPE === get_post_type( $recipe_id ) ) {
59
+ $recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
60
+
61
+ $styles = WPRM_Template_Manager::get_template_styles( $recipe, 'print' );
62
+
63
+ $print_html = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . $styles . '</head><body>';
64
+ $print_html .= WPRM_Template_Manager::get_template( $recipe, 'print' );
65
+ }
66
+ $print_html .= '</body></html>';
67
+
68
+ wp_send_json_success( array(
69
+ 'html' => $print_html,
70
+ ) );
71
+ }
72
+
73
+ wp_die();
74
+ }
75
+ }
76
+
77
+ WPRM_Print::init();
includes/public/class-wprm-recipe-manager.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for returning recipes.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Responsible for returning recipes.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Recipe_Manager {
21
+
22
+ /**
23
+ * Recipes that have already been requested for easy subsequent access.
24
+ *
25
+ * @since 1.0.0
26
+ * @access private
27
+ * @var array $recipes Array containing recipes that have already been requested for easy access.
28
+ */
29
+ private static $recipes = array();
30
+
31
+ /**
32
+ * Register actions and filters.
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ public static function init() {
37
+ add_action( 'wp_ajax_wprm_get_recipe', array( __CLASS__, 'ajax_get_recipe' ) );
38
+ }
39
+
40
+ /**
41
+ * Get recipe data by ID through AJAX.
42
+ *
43
+ * @since 1.0.0
44
+ */
45
+ public static function ajax_get_recipe() {
46
+ if ( check_ajax_referer( 'wprm', 'security', false ) ) {
47
+ $recipe_id = isset( $_POST['recipe_id'] ) ? intval( $_POST['recipe_id'] ) : 0; // Input var okay.
48
+
49
+ $recipe = self::get_recipe( $recipe_id );
50
+ $recipe_data = $recipe ? $recipe->get_data() : array();
51
+
52
+ wp_send_json_success( array(
53
+ 'recipe' => $recipe_data,
54
+ ) );
55
+ }
56
+
57
+ wp_die();
58
+ }
59
+
60
+ /**
61
+ * Get recipe object by ID.
62
+ *
63
+ * @since 1.0.0
64
+ * @param mixed $post_or_recipe_id ID or Post Object for the recipe we want.
65
+ */
66
+ public static function get_recipe( $post_or_recipe_id ) {
67
+ $recipe_id = is_object( $post_or_recipe_id ) && $post_or_recipe_id instanceof WP_Post ? $post_or_recipe_id->ID : intval( $post_or_recipe_id );
68
+
69
+ // Only get new recipe object if it hasn't been retrieved before.
70
+ if ( ! array_key_exists( $recipe_id, self::$recipes ) ) {
71
+ $post = is_object( $post_or_recipe_id ) && $post_or_recipe_id instanceof WP_Post ? $post_or_recipe_id : get_post( intval( $post_or_recipe_id ) );
72
+
73
+ if ( $post instanceof WP_Post && WPRM_POST_TYPE === $post->post_type ) {
74
+ $recipe = new WPRM_Recipe( $post );
75
+ } else {
76
+ $recipe = false;
77
+ }
78
+
79
+ self::$recipes[ $recipe_id ] = $recipe;
80
+ }
81
+
82
+ return self::$recipes[ $recipe_id ];
83
+ }
84
+
85
+ /**
86
+ * Get an array of recipe IDs that are in the content.
87
+ *
88
+ * @since 1.0.0
89
+ * @param mixed $content Content we want to check for recipes.
90
+ */
91
+ public static function get_recipe_ids_from_content( $content ) {
92
+ preg_match_all( WPRM_Fallback_Recipe::get_fallback_regex(), $content, $matches );
93
+ return isset( $matches[1] ) ? array_map( 'intval', $matches[1] ) : array();
94
+ }
95
+
96
+ /**
97
+ * Invalidate cached recipe.
98
+ *
99
+ * @since 1.0.0
100
+ * @param int $recipe_id ID of the recipe to invalidate.
101
+ */
102
+ public static function invalidate_recipe( $recipe_id ) {
103
+ if ( array_key_exists( $recipe_id, self::$recipes ) ) {
104
+ unset( self::$recipes[ $recipe_id ] );
105
+ }
106
+ }
107
+ }
108
+
109
+ WPRM_Recipe_Manager::init();
includes/public/class-wprm-recipe.php ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Represents a recipe.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Represents a recipe.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Recipe {
21
+
22
+ /**
23
+ * WP_Post object associated with this recipe post type.
24
+ *
25
+ * @since 1.0.0
26
+ * @access private
27
+ * @var object $post WP_Post object of this recipe post type.
28
+ */
29
+ private $post;
30
+
31
+ /**
32
+ * Metadata associated with this recipe post type.
33
+ *
34
+ * @since 1.0.0
35
+ * @access private
36
+ * @var array $meta Recipe metadata.
37
+ */
38
+ private $meta = false;
39
+
40
+ /**
41
+ * Get new recipe object from associated post.
42
+ *
43
+ * @since 1.0.0
44
+ * @param object $post WP_Post object for this recipe post type.
45
+ */
46
+ public function __construct( $post ) {
47
+ $this->post = $post;
48
+ }
49
+
50
+ /**
51
+ * Get recipe data.
52
+ *
53
+ * @since 1.0.0
54
+ */
55
+ public function get_data() {
56
+ $recipe = array();
57
+
58
+ // Technical Fields.
59
+ $recipe['id'] = $this->id();
60
+
61
+ // Recipe Details.
62
+ $recipe['image_id'] = $this->image_id();
63
+ $recipe['image_url'] = $this->image_url();
64
+ $recipe['name'] = $this->name();
65
+ $recipe['summary'] = $this->summary();
66
+
67
+ $recipe['servings'] = $this->servings();
68
+ $recipe['servings_unit'] = $this->servings_unit();
69
+ $recipe['prep_time'] = $this->prep_time();
70
+ $recipe['cook_time'] = $this->cook_time();
71
+ $recipe['total_time'] = $this->total_time();
72
+
73
+ $recipe['tags'] = array();
74
+ $recipe['tags']['course'] = $this->tags( 'course' );
75
+ $recipe['tags']['cuisine'] = $this->tags( 'cuisine' );
76
+
77
+ // Ingredients & Instructions.
78
+ $recipe['ingredients'] = $this->ingredients();
79
+ $recipe['instructions'] = $this->instructions();
80
+
81
+ // Recipe Notes.
82
+ $recipe['notes'] = $this->notes();
83
+
84
+ return $recipe;
85
+ }
86
+
87
+ /**
88
+ * Get metadata value.
89
+ *
90
+ * @since 1.0.0
91
+ * @param mixed $field Metadata field to retrieve.
92
+ * @param mixed $default Default to return if metadata is not set.
93
+ */
94
+ public function meta( $field, $default ) {
95
+ if ( ! $this->meta ) {
96
+ $this->meta = get_post_custom( $this->id() );
97
+ }
98
+
99
+ if ( isset( $this->meta[ $field ] ) ) {
100
+ return $this->meta[ $field ][0];
101
+ }
102
+
103
+ return $default;
104
+ }
105
+
106
+ /**
107
+ * Get the recipe author.
108
+ *
109
+ * @since 1.0.0
110
+ */
111
+ public function author() {
112
+ $author_id = $this->post->post_author;
113
+
114
+ if ( $author_id ) {
115
+ $author = get_userdata( $author_id );
116
+ return $author->data->display_name;
117
+ } else {
118
+ return '';
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get the recipe publish date.
124
+ *
125
+ * @since 1.0.0
126
+ */
127
+ public function date() {
128
+ return $this->post->post_date;
129
+ }
130
+
131
+ /**
132
+ * Get the recipe ID.
133
+ *
134
+ * @since 1.0.0
135
+ */
136
+ public function id() {
137
+ return $this->post->ID;
138
+ }
139
+
140
+ /**
141
+ * Get the recipe image HTML.
142
+ *
143
+ * @since 1.0.0
144
+ * @param mixed $size Thumbnail name or size array of the image we want.
145
+ */
146
+ public function image( $size = 'thumbnail' ) {
147
+ return wp_get_attachment_image( $this->image_id(), $size );
148
+ }
149
+
150
+ /**
151
+ * Get the recipe image ID.
152
+ *
153
+ * @since 1.0.0
154
+ */
155
+ public function image_id() {
156
+ $image_id = get_post_thumbnail_id( $this->id() );
157
+ if ( ! $image_id ) {
158
+ $image_id = 0;
159
+ }
160
+ return $image_id;
161
+ }
162
+
163
+ /**
164
+ * Get the recipe image URL.
165
+ *
166
+ * @since 1.0.0
167
+ * @param mixed $size Thumbnail name or size array of the image we want.
168
+ */
169
+ public function image_url( $size = 'thumbnail' ) {
170
+ $thumb = wp_get_attachment_image_src( $this->image_id(), $size );
171
+ $image_url = $thumb && isset( $thumb[0] ) ? $thumb[0] : '';
172
+
173
+ return $image_url;
174
+ }
175
+
176
+ /**
177
+ * Get the recipe name.
178
+ *
179
+ * @since 1.0.0
180
+ */
181
+ public function name() {
182
+ return $this->post->post_title;
183
+ }
184
+
185
+ /**
186
+ * Get the recipe summary.
187
+ *
188
+ * @since 1.0.0
189
+ */
190
+ public function summary() {
191
+ return $this->post->post_content;
192
+ }
193
+
194
+ /**
195
+ * Get the recipe servings.
196
+ *
197
+ * @since 1.0.0
198
+ */
199
+ public function servings() {
200
+ return $this->meta( 'wprm_servings', 0 );
201
+ }
202
+
203
+ /**
204
+ * Get the recipe servings unit.
205
+ *
206
+ * @since 1.0.0
207
+ */
208
+ public function servings_unit() {
209
+ return $this->meta( 'wprm_servings_unit', '' );
210
+ }
211
+
212
+ /**
213
+ * Get the recipe prep time.
214
+ *
215
+ * @since 1.0.0
216
+ */
217
+ public function prep_time() {
218
+ return $this->meta( 'wprm_prep_time', 0 );
219
+ }
220
+
221
+ /**
222
+ * Get the recipe cook time.
223
+ *
224
+ * @since 1.0.0
225
+ */
226
+ public function cook_time() {
227
+ return $this->meta( 'wprm_cook_time', 0 );
228
+ }
229
+
230
+ /**
231
+ * Get the recipe total time.
232
+ *
233
+ * @since 1.0.0
234
+ */
235
+ public function total_time() {
236
+ return $this->meta( 'wprm_total_time', 0 );
237
+ }
238
+
239
+ /**
240
+ * Get the recipe tags for a certain tag type.
241
+ *
242
+ * @since 1.0.0
243
+ * @param mixed $taxonomy Taxonomy to get the tags for.
244
+ */
245
+ public function tags( $taxonomy ) {
246
+ $taxonomy = 'wprm_' . $taxonomy;
247
+ $terms = get_the_terms( $this->id(), $taxonomy );
248
+
249
+ return is_array( $terms ) ? $terms : array();
250
+ }
251
+
252
+ /**
253
+ * Get the template for this recipe.
254
+ *
255
+ * @since 1.0.0
256
+ * @param mixed $type Type of template to get, defaults to single.
257
+ */
258
+ public function template( $type = 'single' ) {
259
+ return WPRM_Template_Manager::get_template( $this, $type );
260
+ }
261
+
262
+ /**
263
+ * Get the recipe ingredients.
264
+ *
265
+ * @since 1.0.0
266
+ */
267
+ public function ingredients() {
268
+ return maybe_unserialize( $this->meta( 'wprm_ingredients', array() ) );
269
+ }
270
+
271
+ /**
272
+ * Get the recipe ingredients without nested groups.
273
+ *
274
+ * @since 1.0.0
275
+ */
276
+ public function ingredients_without_groups() {
277
+ $ingredients = $this->ingredients();
278
+ $ingredients_without_groups = array();
279
+
280
+ foreach ( $ingredients as $ingredient_group ) {
281
+ $ingredients_without_groups = array_merge( $ingredients_without_groups, $ingredient_group['ingredients'] );
282
+ }
283
+
284
+ return $ingredients_without_groups;
285
+ }
286
+
287
+ /**
288
+ * Get the recipe instructions.
289
+ *
290
+ * @since 1.0.0
291
+ */
292
+ public function instructions() {
293
+ return maybe_unserialize( $this->meta( 'wprm_instructions', array() ) );
294
+ }
295
+
296
+ /**
297
+ * Get the recipe instructions without nested groups.
298
+ *
299
+ * @since 1.0.0
300
+ */
301
+ public function instructions_without_groups() {
302
+ $instructions = $this->instructions();
303
+ $instructions_without_groups = array();
304
+
305
+ foreach ( $instructions as $instruction_group ) {
306
+ $instructions_without_groups = array_merge( $instructions_without_groups, $instruction_group['instructions'] );
307
+ }
308
+
309
+ return $instructions_without_groups;
310
+ }
311
+
312
+ /**
313
+ * Get the recipe notes.
314
+ *
315
+ * @since 1.0.0
316
+ */
317
+ public function notes() {
318
+ return $this->meta( 'wprm_notes', '' );
319
+ }
320
+
321
+ /**
322
+ * Get the parent post ID.
323
+ *
324
+ * @since 1.0.0
325
+ */
326
+ public function parent_post_id() {
327
+ return $this->meta( 'wprm_parent_post_id', 0 );
328
+ }
329
+ }
includes/public/class-wprm-shortcode.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handle the recipe shortcode.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Handle the recipe shortcode.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Shortcode {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_shortcode( 'wprm-recipe', array( __CLASS__, 'recipe_shortcode' ) );
29
+ }
30
+
31
+ /**
32
+ * Output for the recipe shortcode.
33
+ *
34
+ * @since 1.0.0
35
+ * @param array $atts Options passed along with the shortcode.
36
+ */
37
+ public static function recipe_shortcode( $atts ) {
38
+ $atts = shortcode_atts( array(
39
+ 'id' => '0',
40
+ ), $atts, 'wprm_recipe' );
41
+
42
+ $recipe_id = intval( $atts['id'] );
43
+ $recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
44
+
45
+ if ( $recipe ) {
46
+ $output = '<div id="wprm-recipe-container-' . esc_attr( $recipe_id ) . '" class="wprm-recipe-container" data-recipe-id="' . esc_attr( $recipe_id ) . '">';
47
+ $output .= WPRM_Metadata::get_metadata_output( $recipe );
48
+ $output .= WPRM_Template_Manager::get_template( $recipe, 'single' );
49
+ $output .= '</div>';
50
+ return $output;
51
+ } else {
52
+ return '';
53
+ }
54
+ }
55
+ }
56
+
57
+ WPRM_Shortcode::init();
includes/public/class-wprm-taxonomies.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Register the recipe taxonomies.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Register the recipe taxonomies.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Taxonomies {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'init', array( __CLASS__, 'register_taxonomies' ), 2 );
29
+ }
30
+
31
+ /**
32
+ * Register the recipe taxonomies.
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ public static function register_taxonomies() {
37
+ $taxonomies = WPRM_Taxonomies::get_taxonomies();
38
+
39
+ foreach ( $taxonomies as $taxonomy => $labels ) {
40
+ $args = array(
41
+ 'labels' => $labels,
42
+ 'hierarchical' => true,
43
+ 'public' => false,
44
+ 'show_ui' => false,
45
+ 'query_var' => false,
46
+ 'rewrite' => false,
47
+ );
48
+
49
+ register_taxonomy( $taxonomy, WPRM_POST_TYPE, $args );
50
+ register_taxonomy_for_object_type( $taxonomy, WPRM_POST_TYPE );
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Get the recipe taxonomies.
56
+ *
57
+ * @since 1.0.0
58
+ */
59
+ public static function get_taxonomies() {
60
+ $taxonomies = apply_filters( 'wprm_recipe_taxonomies', array(
61
+ 'wprm_course' => array(
62
+ 'name' => _x( 'Courses', 'taxonomy general name', 'wp-recipe-maker' ),
63
+ 'singular_name' => _x( 'Course', 'taxonomy singular name', 'wp-recipe-maker' ),
64
+ ),
65
+ 'wprm_cuisine' => array(
66
+ 'name' => _x( 'Cuisines', 'taxonomy general name', 'wp-recipe-maker' ),
67
+ 'singular_name' => _x( 'Cuisine', 'taxonomy singular name', 'wp-recipe-maker' ),
68
+ ),
69
+ 'wprm_ingredient' => array(
70
+ 'name' => _x( 'Ingredients', 'taxonomy general name', 'wp-recipe-maker' ),
71
+ 'singular_name' => _x( 'Ingredient', 'taxonomy singular name', 'wp-recipe-maker' ),
72
+ ),
73
+ ));
74
+
75
+ return $taxonomies;
76
+ }
77
+
78
+ /**
79
+ * Insert default terms for recipe taxonomies.
80
+ *
81
+ * @since 1.0.0
82
+ */
83
+ public static function insert_default_taxonomy_terms() {
84
+ if ( taxonomy_exists( 'wprm_course' ) ) {
85
+ wp_insert_term( __( 'Breakfast', 'wp-recipe-maker' ), 'wprm_course' );
86
+ wp_insert_term( __( 'Appetizer', 'wp-recipe-maker' ), 'wprm_course' );
87
+ wp_insert_term( __( 'Soup', 'wp-recipe-maker' ), 'wprm_course' );
88
+ wp_insert_term( __( 'Main Course', 'wp-recipe-maker' ), 'wprm_course' );
89
+ wp_insert_term( __( 'Side Dish', 'wp-recipe-maker' ), 'wprm_course' );
90
+ wp_insert_term( __( 'Salad', 'wp-recipe-maker' ), 'wprm_course' );
91
+ wp_insert_term( __( 'Dessert', 'wp-recipe-maker' ), 'wprm_course' );
92
+ wp_insert_term( __( 'Snack', 'wp-recipe-maker' ), 'wprm_course' );
93
+ wp_insert_term( __( 'Drinks', 'wp-recipe-maker' ), 'wprm_course' );
94
+ }
95
+
96
+ if ( taxonomy_exists( 'wprm_cuisine' ) ) {
97
+ wp_insert_term( __( 'French', 'wp-recipe-maker' ), 'wprm_cuisine' );
98
+ wp_insert_term( __( 'Italian', 'wp-recipe-maker' ), 'wprm_cuisine' );
99
+ wp_insert_term( __( 'Mediterranean', 'wp-recipe-maker' ), 'wprm_cuisine' );
100
+ wp_insert_term( __( 'Indian', 'wp-recipe-maker' ), 'wprm_cuisine' );
101
+ wp_insert_term( __( 'Chinese', 'wp-recipe-maker' ), 'wprm_cuisine' );
102
+ wp_insert_term( __( 'Japanese', 'wp-recipe-maker' ), 'wprm_cuisine' );
103
+ wp_insert_term( __( 'American', 'wp-recipe-maker' ), 'wprm_cuisine' );
104
+ wp_insert_term( __( 'Mexican', 'wp-recipe-maker' ), 'wprm_cuisine' );
105
+ }
106
+ }
107
+ }
108
+
109
+ WPRM_Taxonomies::init();
includes/public/class-wprm-template-manager.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Responsible for the recipe template.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/includes/public
10
+ */
11
+
12
+ /**
13
+ * Responsible for the recipe template.
14
+ *
15
+ * @since 1.0.0
16
+ * @package WP_Recipe_Maker
17
+ * @subpackage WP_Recipe_Maker/includes/public
18
+ * @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
19
+ */
20
+ class WPRM_Template_Manager {
21
+
22
+ /**
23
+ * Register actions and filters.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ public static function init() {
28
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
29
+ }
30
+
31
+ /**
32
+ * Enqueue stylesheets and scripts.
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ public static function enqueue() {
37
+ wp_enqueue_style( 'wprm-template', WPRM_URL . '/templates/recipe/simple/simple.min.css', array(), WPRM_VERSION, 'all' );
38
+ }
39
+
40
+ /**
41
+ * Get template for a specific recipe.
42
+ *
43
+ * @since 1.0.0
44
+ * @param object $recipe Recipe object to get the template for.
45
+ * @param mixed $type Type of template we want to get, defaults to single.
46
+ */
47
+ public static function get_template( $recipe, $type = 'single' ) {
48
+ $template_name = 'print' === $type ? 'clean-print' : 'simple';
49
+
50
+ ob_start();
51
+ require( WPRM_DIR . 'templates/recipe/' . $template_name . '/' . $template_name . '.php' );
52
+ $template = ob_get_contents();
53
+ ob_end_clean();
54
+
55
+ return $template;
56
+ }
57
+
58
+ /**
59
+ * Get template styles for a specific recipe.
60
+ *
61
+ * @since 1.0.0
62
+ * @param object $recipe Recipe object to get the template for.
63
+ * @param mixed $type Type of template we want to get, defaults to single.
64
+ */
65
+ public static function get_template_styles( $recipe, $type = 'single' ) {
66
+ $template_name = 'print' === $type ? 'clean-print' : 'simple';
67
+
68
+ ob_start();
69
+ require( WPRM_DIR . 'templates/recipe/' . $template_name . '/' . $template_name . '.min.css' );
70
+ $css = ob_get_contents();
71
+ ob_end_clean();
72
+
73
+ $style = '<style type="text/css">' . $css . '</style>';
74
+
75
+ return $style;
76
+ }
77
+ }
78
+
79
+ WPRM_Template_Manager::init();
index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden
readme.txt ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === WP Recipe Maker ===
2
+ Contributors: BrechtVds
3
+ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QG7KZMGFU325Y
4
+ Tags: recipe, recipes, ingredients, food, cooking, seo, schema.org, json-ld
5
+ Requires at least: 4.0
6
+ Tested up to: 4.6
7
+ Stable tag: trunk
8
+ License: GPLv2 or later
9
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ The easy and user-friendly recipe plugin for everyone. Automatic JSON-LD metadata for better SEO will get you more visitors!
12
+
13
+ == Description ==
14
+
15
+ WP Recipe Maker is the easy recipe plugin that everyone can use. An easy workflow allows you to add recipes to any post or page with automatic JSON-LD metadata for your recipes. This recipe metadata will improve your SEO and get you more visitors!
16
+
17
+ An overview of WP Recipe Maker features:
18
+
19
+ * **Easy workflow** to add recipes to any post or page.
20
+ * Uses schema.org/Recipe JSON-LD metadata optimised for **Google Recipe search**.
21
+ * Clean **print recipe** version for your visitors
22
+ * **Fallback recipe** shows up when the plugin is disabled
23
+ * Add **photos** to any step of the recipe.
24
+ * This plugin is **fully responsive**. That means that your recipes will look good on any device.
25
+ * Structure your ingredients and instructions in **groups** (e.g. icing and cake batter)
26
+ * **Full text search** for your recipes
27
+
28
+ Currently using another recipe plugin? No problem! You can easily migrate all your existing recipes to WP Recipe Maker if you're using any of the following plugins:
29
+
30
+ * EasyRecipe
31
+ * (More coming soon!)
32
+
33
+ This plugin is in active development and following features (and more) are coming soon:
34
+
35
+ * Adjustable servings
36
+ * More recipe templates
37
+ * Template customization options
38
+
39
+ Feel free to contact us with any feature requests or ideas.
40
+
41
+ == Installation ==
42
+
43
+ 1. Upload the `wp-recipe-maker` directory (directory included) to the `/wp-content/plugins/` directory
44
+ 1. Activate the plugin through the 'Plugins' menu in WordPress
45
+ 1. Add recipes using the "WP Recipe Maker" button when editing posts or pages
46
+
47
+ == Frequently asked questions ==
48
+
49
+ = What's the difference with WP Ultimate Recipe? =
50
+
51
+ [WP Ultimate Recipe](http://www.wpultimaterecipe.com/) is the popular recipe plugin that we released in 2013 and have been working on ever since. This gave us a great idea of what most food bloggers are looking for.
52
+
53
+ Why the new plugin? A few structural choices we made early on have caused WP Ultimate Recipe to be quite complex and not 100% compatible with all themes. With WP Recipe Maker we're building the perfect recipe plugin from scratch, without all the baggage of years of development.
54
+
55
+ WP Ultimate Recipe is still in active development and will be maintained alongside this new alternative.
56
+
57
+ = Do you offer any support? =
58
+
59
+ Yes! We pride ourselves on offering awesome support and almost always answer support questions within 24 hours. Send us an email at [support@bootstrapped.ventures](mailto:support@bootstrapped.ventures) whenever you have a question or suggestion!
60
+
61
+ == Screenshots ==
62
+
63
+ 1. Example Pasta Pesto recipe with the Simple template.
64
+ 2. Use the "WP Recipe Maker" button to add recipes or click on the recipe preview to edit one.
65
+ 3. The recipe form.
66
+
67
+ == Changelog ==
68
+
69
+ = 1.0.0 =
70
+ * Very first version of this recipe plugin
71
+ * Feature: JSON-LD Metadata
72
+ * Feature: Intuitive workflow using regular posts or pages
73
+ * Feature: Import from EasyRecipe
74
+ * Feature: Clean printing of recipes
75
+ * Feature: Fallback recipe when the plugin is disabled
76
+
77
+ == Upgrade notice ==
78
+
79
+ = 1.0.0 =
80
+ First version of this recipe plugin, no upgrades needed.
templates/admin/menu/faq.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for the WP Recipe Maker FAQ.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/menu
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wrap wprm-faq">
15
+ <h2><?php esc_html_e( 'FAQ & Support', 'wp-recipe-maker' ); ?></h2>
16
+ <h3>How can I add a recipe?</h3>
17
+ <p>
18
+ When creating or editing a regular post or page you should see a "WP Recipe Maker" button appear next to the "Add Media" button right above the post editor. Click that button to insert recipes.
19
+ </p>
20
+ <h3>How can I edit a recipe?</h3>
21
+ <p>
22
+ You can edit a recipe by clicking on the placeholder that will appear in the visual editor after adding a recipe.
23
+ </p>
24
+ <h3>Are there any other features?</h3>
25
+ <p>
26
+ WP Recipe Maker is in active development with many features planned. Any suggestions are welcome as well. See the next question on how to contact us.
27
+ </p>
28
+ <h3>I need more help!</h3>
29
+ <p>
30
+ We're happy to help you with any questions or suggestions you have, just contact us using the "Help" icon in the bottom right of this page (it might take a couple of seconds to load) or send us an email at <a href="mailto:support@bootstrapped.ventures">support@bootstrapped.ventures</a>!
31
+ </p>
32
+ </div>
templates/admin/menu/import-overview.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for recipe import overview page.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/menu
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wrap wprm-import">
15
+ <h2><?php esc_html_e( 'Import Recipes', 'wp-recipe-maker' ); ?></h2>
16
+ <h3><?php esc_html_e( 'Recipes to Import', 'wp-recipe-maker' ); ?></h3>
17
+ <?php
18
+ $recipes_to_import = array();
19
+ foreach ( self::$importers as $importer ) {
20
+ $recipes = $importer->get_recipes();
21
+
22
+ if ( count( $recipes ) > 0 ) {
23
+ $recipes_to_import[ $importer->get_uid() ] = array(
24
+ 'name' => $importer->get_name(),
25
+ 'count' => count( $recipes ),
26
+ );
27
+ }
28
+ }
29
+
30
+ if ( 0 === count( $recipes_to_import ) ) :
31
+ echo '<p>' . esc_html__( 'No recipes found.', 'wp-recipe-maker' ) . '</p>';
32
+ else :
33
+ ?>
34
+ <?php foreach ( $recipes_to_import as $uid => $importer ) : ?>
35
+ <h4 style="margin-bottom: 0"><?php echo esc_html( $importer['name'] ); ?></h4>
36
+ <?php printf( esc_html( _n( '%d recipe found', '%d recipes found', $importer['count'], 'wp-recipe-maker' ) ), intval( $importer['count'] ) ); ?><br />
37
+ <a href="<?php echo esc_url( add_query_arg( 'from', $uid, admin_url( 'admin.php?page=wprm_import' ) ) ); ?>"><?php esc_html_e( 'Explore import options', 'wp-recipe-maker' ); ?></a>
38
+ <?php endforeach; // Each importer. ?>
39
+ <?php endif; // Recipes to import. ?>
40
+
41
+ <h3><?php esc_html_e( 'Imported Recipes to Check', 'wp-recipe-maker' ); ?></h3>
42
+ <?php
43
+ $imported_recipes = array();
44
+ foreach ( self::$importers as $importer ) {
45
+ $recipes = self::get_imported_recipes( $importer->get_uid(), true );
46
+
47
+ if ( count( $recipes ) > 0 ) {
48
+ $imported_recipes[ $importer->get_uid() ] = array(
49
+ 'name' => $importer->get_name(),
50
+ 'recipes' => $recipes,
51
+ );
52
+ }
53
+ }
54
+
55
+ if ( 0 === count( $imported_recipes ) ) :
56
+ echo '<p>' . esc_html__( 'No recipes found.', 'wp-recipe-maker' ) . '</p>';
57
+ else :
58
+ ?>
59
+ <p>
60
+ <?php esc_html_e( 'We recommend going through all of these recipes to make sure the import process was successful. Pay attention to the different ingredient parts to be able to make use of all of our features.', 'wp-recipe-maker' ); ?>
61
+ </p>
62
+ <p>
63
+ <?php esc_html_e( 'After doing so you can mark a recipe as checked to keep track of the recipes you still have to go through.', 'wp-recipe-maker' ); ?>
64
+ </p>
65
+ <?php foreach ( $imported_recipes as $uid => $importer ) : ?>
66
+ <h4 style="margin-bottom: 0"><?php echo esc_html( $importer['name'] ); ?></h4>
67
+ <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
68
+ <input type="hidden" name="action" value="wprm_check_imported_recipes">
69
+ <input type="hidden" name="importer" value="<?php echo esc_attr( $uid ); ?>">
70
+ <?php wp_nonce_field( 'wprm_check_imported_recipes', 'wprm_check_imported_recipes', false ); ?>
71
+ <table class="wprm-import-recipes">
72
+ <tbody>
73
+ <?php foreach ( $importer['recipes'] as $post ) :
74
+ $recipe = WPRM_Recipe_Manager::get_recipe( $post ); ?>
75
+ <tr>
76
+ <td>
77
+ <input type="checkbox" name="recipes[]" value="<?php echo esc_attr( $recipe->id() ); ?>" />
78
+ </td>
79
+ <td>
80
+ <a href="<?php echo esc_url( get_edit_post_link( $recipe->parent_post_id() ) ); ?>" target="_blank"><?php echo esc_html( $recipe->name() ); ?></a>
81
+ </td>
82
+ </tr>
83
+ <?php endforeach; // Each recipe. ?>
84
+ </tbody>
85
+ </table>
86
+ <?php submit_button( __( 'Mark Selected Recipes as Checked', 'wp-recipe-maker' ) ); ?>
87
+ </form>
88
+ <?php endforeach; // Each importer. ?>
89
+ <?php endif; // Recipes to import. ?>
90
+ </div>
templates/admin/menu/import-recipes.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for recipe import page.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/menu
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wrap wprm-import">
15
+ <?php
16
+ $uid = isset( $_GET['from'] ) ? sanitize_title( wp_unslash( $_GET['from'] ) ) : ''; // Input var okay.
17
+ $importer = self::get_importer( $uid );
18
+
19
+ if ( ! $importer ) :
20
+ esc_html_e( 'Something went wrong.', 'wp-recipe-maker' );
21
+ else :
22
+ ?>
23
+ <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
24
+ <input type="hidden" name="action" value="wprm_import_recipes">
25
+ <input type="hidden" name="importer" value="<?php echo esc_attr( $importer->get_uid() ); ?>">
26
+ <?php wp_nonce_field( 'wprm_import_recipes', 'wprm_import_recipes', false ); ?>
27
+ <h2><?php printf( esc_html__( 'Import from %s', 'wp-recipe-maker' ), esc_html( $importer->get_name() ) ); ?></h2>
28
+ <h3><?php esc_html_e( 'Import Settings', 'wp-recipe-maker' ); ?></h3>
29
+ <h3><?php esc_html_e( 'Recipes to Import', 'wp-recipe-maker' ); ?></h3>
30
+ <?php esc_html_e( 'Select', 'wp-recipe-maker' ); ?>: <a href="#" class="wprm-import-recipes-select-all"><?php esc_html_e( 'all', 'wp-recipe-maker' ); ?></a>, <a href="#" class="wprm-import-recipes-select-none"><?php esc_html_e( 'none', 'wp-recipe-maker' ); ?></a>
31
+ <table class="wprm-import-recipes">
32
+ <tbody>
33
+ <?php
34
+ $recipes = $importer->get_recipes();
35
+ foreach ( $recipes as $id => $recipe ) :
36
+ ?>
37
+ <tr>
38
+ <td>
39
+ <input type="checkbox" name="recipes[]" value="<?php echo esc_attr( $id ); ?>" />
40
+ </td>
41
+ <td>
42
+ <a href="<?php echo esc_url( $recipe['url'] ); ?>" target="_blank"><?php echo esc_html( $recipe['name'] ); ?></a>
43
+ </td>
44
+ </tr>
45
+ <?php endforeach; // Recipes to import. ?>
46
+ </tbody>
47
+ </table>
48
+ <?php submit_button( __( 'Import Selected Recipes', 'wp-recipe-maker' ) ); ?>
49
+ </form>
50
+ <?php endif; // Recipe Importer. ?>
51
+ </div>
templates/admin/menu/support-widget.html ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <!-- Start of bootstrappedventures Zendesk Widget script -->
2
+ <script>/*<![CDATA[*/window.zEmbed||function(e,t){var n,o,d,i,s,a=[],r=document.createElement("iframe");window.zEmbed=function(){a.push(arguments)},window.zE=window.zE||window.zEmbed,r.src="javascript:false",r.title="",r.role="presentation",(r.frameElement||r).style.cssText="display: none",d=document.getElementsByTagName("script"),d=d[d.length-1],d.parentNode.insertBefore(r,d),i=r.contentWindow,s=i.document;try{o=s}catch(e){n=document.domain,r.src='javascript:var d=document.open();d.domain="'+n+'";void(0);',o=s}o.open()._l=function(){var o=this.createElement("script");n&&(this.domain=n),o.id="js-iframe-async",o.src=e,this.t=+new Date,this.zendeskHost=t,this.zEQueue=a,this.body.appendChild(o)},o.write('<body onload="document._l();">'),o.close()}("https://assets.zendesk.com/embeddable_framework/main.js","bootstrappedventures.zendesk.com");
3
+ /*]]>*/</script>
4
+ <!-- End of bootstrappedventures Zendesk Widget script -->
templates/admin/modal/modal.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for the WP Recipe Maker modal.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/modal
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wprm-modal-container">
15
+ <div class="wprm-modal wp-core-ui">
16
+ <button type="button" class="button-link wprm-modal-close"><span class="wprm-modal-icon"><span class="screen-reader-text"><?php esc_html_e( 'Close Modal', 'wp-recipe-maker' ); ?></span></span></button>
17
+
18
+ <div class="wprm-modal-content">
19
+ <div class="wprm-frame wp-core-ui">
20
+ <div class="wprm-frame-menu">
21
+ <div class="wprm-menu">
22
+ <?php
23
+ $active_menu = '';
24
+ foreach ( $menu as $menu_item => $options ) {
25
+ $active = isset( $options['default'] ) && $options['default'];
26
+ $active_class = $active ? ' active' : '';
27
+ $label = isset( $options['label'] ) ? $options['label'] : '';
28
+ $default_tab = isset( $options['default_tab'] ) ? $options['default_tab'] : '';
29
+
30
+ if ( $active ) {
31
+ $active_menu = $label;
32
+ }
33
+
34
+ echo '<a href="#" class="wprm-menu-item' . esc_attr( $active_class ) . '" data-menu="' . esc_attr( $menu_item ) . '" data-tab="' . esc_attr( $menu_item ) . '-' . esc_attr( $default_tab ) . '">' . esc_html( $label ) . '</a>';
35
+ }
36
+ ?>
37
+ </div>
38
+ </div>
39
+ <div class="wprm-frame-title">
40
+ <h1><?php echo esc_html( $active_menu ); ?><span class="dashicons dashicons-arrow-down"></span></h1>
41
+ </div>
42
+
43
+ <div class="wprm-frame-router">
44
+ <?php
45
+ foreach ( $menu as $menu_item => $options ) {
46
+ $active = isset( $options['default'] ) && $options['default'];
47
+ $active_class = $active ? ' active' : '';
48
+ $default_tab = $active && isset( $options['default_tab'] ) ? $options['default_tab'] : '';
49
+
50
+ echo '<div id="wprm-menu-' . esc_attr( $menu_item ) . '" class="wprm-router' . esc_attr( $active_class ) . '">';
51
+
52
+ foreach ( $options['tabs'] as $tab => $tab_options ) {
53
+ $tab_uid = $menu_item . '-' . $tab;
54
+ $tab_class = $default_tab === $tab ? ' active' : '';
55
+ $label = isset( $tab_options['label'] ) ? $tab_options['label'] : '';
56
+
57
+ echo '<a href="#" class="wprm-menu-item' . esc_attr( $tab_class ) . '" data-tab="' . esc_attr( $tab_uid ) . '">' . esc_html( $label ) . '</a>';
58
+ }
59
+
60
+ echo '</div>';
61
+ }
62
+ ?>
63
+ </div>
64
+ <div class="wprm-frame-content">
65
+ <?php
66
+ foreach ( $menu as $menu_item => $options ) {
67
+ $active = isset( $options['default'] ) && $options['default'];
68
+ $default_tab = $active && isset( $options['default_tab'] ) ? $options['default_tab'] : '';
69
+
70
+ foreach ( $options['tabs'] as $tab => $tab_options ) {
71
+ $tab_uid = $menu_item . '-' . $tab;
72
+ $tab_class = $default_tab === $tab ? ' active' : '';
73
+ $label = isset( $tab_options['label'] ) ? $tab_options['label'] : '';
74
+ $template = isset( $tab_options['template'] ) ? $tab_options['template'] : '';
75
+
76
+ echo '<div id="wprm-tab-' . esc_attr( $tab_uid ) . '" class="wprm-frame-content-tab' . esc_attr( $tab_class ) . '">';
77
+
78
+ if ( file_exists( $template ) ) {
79
+ include( $template );
80
+ }
81
+
82
+ echo '</div>';
83
+ }
84
+ }
85
+ ?>
86
+ </div>
87
+
88
+ <div class="wprm-frame-toolbar">
89
+ <div class="wprm-toolbar">
90
+ <div class="wprm-toolbar-primary search-form">
91
+ <button type="button" class="button wprm-button button-primary button-large wprm-button-insert"><?php esc_html_e( 'Insert', 'wp-recipe-maker' ); ?></button>
92
+ <button type="button" class="button wprm-button button-primary button-large wprm-button-update"><?php esc_html_e( 'Update', 'wp-recipe-maker' ); ?></button>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ <div class="wprm-modal-backdrop"></div>
100
+
101
+ </div>
templates/admin/modal/tabs/recipe-details.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for the Recipe Details tab in the modal.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/modal/tabs
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wprm-recipe-details-form">
15
+ <div class="wprm-recipe-details-form-container wprm-recipe-image-container">
16
+ <label for="wprm-recipe-image-id"><?php esc_html_e( 'Image', 'wp-recipe-maker' ); ?></label>
17
+ <button type="button" class="button wprm-recipe-image-add"><?php esc_html_e( 'Add Image', 'wp-recipe-maker' ); ?></button>
18
+ <button type="button" class="button wprm-recipe-image-remove hidden"><?php esc_html_e( 'Remove Image', 'wp-recipe-maker' ); ?></button>
19
+ <input type="hidden" id="wprm-recipe-image-id" />
20
+ <div class="wprm-recipe-image-preview"></div>
21
+ </div>
22
+ <div class="wprm-recipe-details-form-container">
23
+ <label for="wprm-recipe-name"><?php esc_html_e( 'Name', 'wp-recipe-maker' ); ?></label>
24
+ <input type="text" id="wprm-recipe-name" placeholder="<?php esc_attr_e( 'Recipe Name', 'wp-recipe-maker' ); ?>" />
25
+ </div>
26
+ <div class="wprm-recipe-details-form-container wprm-recipe-summary-container">
27
+ <label for="wprm-recipe-summary"><?php esc_html_e( 'Summary', 'wp-recipe-maker' ); ?></label>
28
+ <textarea id="wprm-recipe-summary" class="wprm-rich-editor" rows="4"></textarea>
29
+ </div>
30
+ <div class="wprm-recipe-details-form-container">
31
+ <label for="wprm-recipe-servings"><?php esc_html_e( 'Servings', 'wp-recipe-maker' ); ?></label>
32
+ <input type="number" id="wprm-recipe-servings" placeholder="4" /> <input type="text" id="wprm-recipe-servings-unit" placeholder="<?php esc_attr_e( 'people', 'wp-recipe-maker' ); ?>" />
33
+ </div>
34
+ <div class="wprm-recipe-details-form-container wprm-recipe-details-form-container-thirds">
35
+ <label for="wprm-recipe-prep-time"><?php esc_html_e( 'Prep Time', 'wp-recipe-maker' ); ?></label>
36
+ <input type="number" id="wprm-recipe-prep-time" class="wprm-recipe-time" placeholder="10" /> <?php esc_html_e( 'minutes', 'wp-recipe-maker' ); ?>
37
+ </div><div class="wprm-recipe-details-form-container wprm-recipe-details-form-container-thirds">
38
+ <label for="wprm-recipe-cook-time"><?php esc_html_e( 'Cook Time', 'wp-recipe-maker' ); ?></label>
39
+ <input type="number" id="wprm-recipe-cook-time" class="wprm-recipe-time" placeholder="20" /> <?php esc_html_e( 'minutes', 'wp-recipe-maker' ); ?>
40
+ </div><div class="wprm-recipe-details-form-container wprm-recipe-details-form-container-thirds">
41
+ <label for="wprm-recipe-total-time"><?php esc_html_e( 'Total Time', 'wp-recipe-maker' ); ?></label>
42
+ <input type="number" id="wprm-recipe-total-time" class="wprm-recipe-time" placeholder="30" /> <?php esc_html_e( 'minutes', 'wp-recipe-maker' ); ?>
43
+ </div>
44
+ <div class="wprm-recipe-details-form-container wprm-recipe-details-form-container-halfs">
45
+ <label for="wprm-recipe-tag-course"><?php esc_html_e( 'Course', 'wp-recipe-maker' ); ?></label>
46
+ <select id="wprm-recipe-tag-course" class="wprm-recipe-tags" multiple>
47
+ <?php
48
+ $terms = get_terms( array(
49
+ 'taxonomy' => 'wprm_course',
50
+ 'hide_empty' => false,
51
+ ) );
52
+
53
+ foreach ( $terms as $term ) {
54
+ echo '<option value="' . esc_attr( $term->term_id ) . '">' . esc_html( $term->name ) . '</option>';
55
+ }
56
+ ?>
57
+ </select>
58
+ </div><div class="wprm-recipe-details-form-container wprm-recipe-details-form-container-halfs">
59
+ <label for="wprm-recipe-tag-cuisine"><?php esc_html_e( 'Cuisine', 'wp-recipe-maker' ); ?></label>
60
+ <select id="wprm-recipe-tag-cuisine" class="wprm-recipe-tags" multiple>
61
+ <?php
62
+ $terms = get_terms( array(
63
+ 'taxonomy' => 'wprm_cuisine',
64
+ 'hide_empty' => false,
65
+ ) );
66
+
67
+ foreach ( $terms as $term ) {
68
+ echo '<option value="' . esc_attr( $term->term_id ) . '">' . esc_html( $term->name ) . '</option>';
69
+ }
70
+ ?>
71
+ </select>
72
+ </div>
73
+ </div>
templates/admin/modal/tabs/recipe-ingredients-instructions.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for the Ingredients & Instructions tab in the modal.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/modal/tabs
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wprm-recipe-ingredients-instructions-form">
15
+ <div class="wprm-recipe-ingredients-form">
16
+ <table class="wprm-recipe-ingredients-container">
17
+ <thead>
18
+ <th><span class="dashicons dashicons-sort"></span></th>
19
+ <th><?php esc_html_e( 'Amount', 'wp-recipe-maker' ); ?></th>
20
+ <th><?php esc_html_e( 'Unit', 'wp-recipe-maker' ); ?></th>
21
+ <th><?php esc_html_e( 'Name', 'wp-recipe-maker' ); ?></th>
22
+ <th><?php esc_html_e( 'Notes', 'wp-recipe-maker' ); ?></th>
23
+ <th>&nbsp;</th>
24
+ </thead>
25
+ <tbody class="wprm-recipe-ingredients-placeholder">
26
+ <tr class="wprm-recipe-ingredient-group">
27
+ <td><span class="dashicons dashicons-menu wprm-recipe-ingredients-instructions-sort"></span></td>
28
+ <td colspan="4"><input type="text" class="wprm-recipe-ingredient-group-name" placeholder="<?php esc_attr_e( 'Ingredient Group', 'wp-recipe-maker' );?>" /></td>
29
+ <td><span class="dashicons dashicons-trash wprm-recipe-ingredients-instructions-delete"></span></td>
30
+ </tr>
31
+ <tr class="wprm-recipe-ingredient">
32
+ <td><span class="dashicons dashicons-menu wprm-recipe-ingredients-instructions-sort"></span></td>
33
+ <td><input type="text" class="wprm-recipe-ingredient-amount" placeholder="1" /></td>
34
+ <td><input type="text" class="wprm-recipe-ingredient-unit" placeholder="<?php esc_attr_e( 'tbsp', 'wp-recipe-maker' );?>" /></td>
35
+ <td><input type="text" class="wprm-recipe-ingredient-name" placeholder="<?php esc_attr_e( 'olive oil', 'wp-recipe-maker' );?>" /></td>
36
+ <td><input type="text" class="wprm-recipe-ingredient-notes" placeholder="<?php esc_attr_e( 'extra virgin', 'wp-recipe-maker' );?>" /></td>
37
+ <td><span class="dashicons dashicons-trash wprm-recipe-ingredients-instructions-delete"></span></td>
38
+ </tr>
39
+ </tbody>
40
+ <tbody class="wprm-recipe-ingredients">
41
+ </tbody>
42
+ </table>
43
+ <div class="wprm-recipe-ingredients-actions">
44
+ <button type="button" class="button wprm-recipe-ingredients-add"><?php esc_html_e( 'Add Ingredient', 'wp-recipe-maker' ); ?></button>
45
+ <button type="button" class="button wprm-recipe-ingredients-add-group"><?php esc_html_e( 'Add Ingredient Group', 'wp-recipe-maker' ); ?></button>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="wprm-recipe-instructions-form">
50
+ <table class="wprm-recipe-instructions-container">
51
+ <thead>
52
+ <th><span class="dashicons dashicons-sort"></span></th>
53
+ <th><?php esc_html_e( 'Instruction', 'wp-recipe-maker' ); ?></th>
54
+ <th><?php esc_html_e( 'Image', 'wp-recipe-maker' ); ?></th>
55
+ <th>&nbsp;</th>
56
+ </thead>
57
+ <tbody class="wprm-recipe-instructions-placeholder">
58
+ <tr class="wprm-recipe-instruction-group">
59
+ <td><span class="dashicons dashicons-menu wprm-recipe-ingredients-instructions-sort"></span></td>
60
+ <td colspan="2"><input type="text" class="wprm-recipe-instruction-group-name" placeholder="<?php esc_attr_e( 'Instruction Group', 'wp-recipe-maker' );?>" /></td>
61
+ <td><span class="dashicons dashicons-trash wprm-recipe-ingredients-instructions-delete"></span></td>
62
+ </tr>
63
+ <tr class="wprm-recipe-instruction">
64
+ <td><span class="dashicons dashicons-menu wprm-recipe-ingredients-instructions-sort"></span></td>
65
+ <td>
66
+ <textarea class="wprm-recipe-instruction-text" rows="3"></textarea>
67
+ </td>
68
+ <td class="wprm-recipe-image-container">
69
+ <div class="wprm-recipe-image-preview"></div>
70
+ <button type="button" class="button wprm-recipe-image-add" tabindex="-1"><?php esc_html_e( 'Add Image', 'wp-recipe-maker' ); ?></button>
71
+ <button type="button" class="button wprm-recipe-image-remove hidden" tabindex="-1"><?php esc_html_e( 'Remove Image', 'wp-recipe-maker' ); ?></button>
72
+ <input type="hidden" class="wprm-recipe-instruction-image" />
73
+ </td>
74
+ <td><span class="dashicons dashicons-trash wprm-recipe-ingredients-instructions-delete"></span></td>
75
+ </tr>
76
+ </tbody>
77
+ <tbody class="wprm-recipe-instructions">
78
+ </tbody>
79
+ </table>
80
+ <div class="wprm-recipe-instructions-actions">
81
+ <button type="button" class="button wprm-recipe-instructions-add"><?php esc_html_e( 'Add Instruction', 'wp-recipe-maker' ); ?></button>
82
+ <button type="button" class="button wprm-recipe-instructions-add-group"><?php esc_html_e( 'Add Instruction Group', 'wp-recipe-maker' ); ?></button>
83
+ </div>
84
+ </div>
85
+ </div>
templates/admin/modal/tabs/recipe-notes.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for the Ingredient Notes tab in the modal.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/admin/modal/tabs
10
+ */
11
+
12
+ ?>
13
+
14
+ <div class="wprm-recipe-notes-form">
15
+ <div class="wprm-recipe-notes-form-container">
16
+ <label for="wprm-recipe-notes"><?php esc_html_e( 'Notes', 'wp-recipe-maker' ); ?></label>
17
+ <?php
18
+ $editor_settings = array(
19
+ 'editor_height' => 300,
20
+ );
21
+ wp_editor( '', 'wprm_recipe_notes', $editor_settings );
22
+ ?>
23
+ </div>
24
+ </div>
templates/public/fallback-recipe.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template to be used as a fallback when the plugin is deactivated.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/public
10
+ */
11
+
12
+ ?>
13
+ <!--WPRM Recipe <?php echo esc_html( $recipe->id() ); ?>-->
14
+ <div class="wprm-fallback-recipe">
15
+ <h2 class="wprm-fallback-recipe-name"><?php echo esc_html( $recipe->name() ); ?></h2>
16
+ <?php
17
+ if ( $recipe->image_url() ) {
18
+ echo '<img class="wprm-fallback-recipe-image" src="' . esc_attr( $recipe->image_url() ) . '"/>';
19
+ }
20
+ ?>
21
+ <p class="wprm-fallback-recipe-summary">
22
+ <?php echo wp_kses_post( $recipe->summary() ); ?>
23
+ </p>
24
+ <div class="wprm-fallback-recipe-ingredients">
25
+ <?php
26
+ $ingredients = $recipe->ingredients();
27
+
28
+ foreach ( $ingredients as $ingredient_group ) {
29
+ if ( $ingredient_group['name'] ) {
30
+ echo '<h4>'. esc_html( $ingredient_group['name'] ) . '</h4>';
31
+ }
32
+
33
+ if ( count( $ingredient_group['ingredients'] > 0 ) ) {
34
+ echo '<ul>';
35
+
36
+ foreach ( $ingredient_group['ingredients'] as $ingredient ) {
37
+ echo '<li>';
38
+ if ( $ingredient['amount'] ) {
39
+ echo esc_html( $ingredient['amount'] ) . ' ';
40
+ }
41
+ if ( $ingredient['unit'] ) {
42
+ echo esc_html( $ingredient['unit'] ) . ' ';
43
+ }
44
+ echo esc_html( $ingredient['name'] );
45
+ if ( $ingredient['notes'] ) {
46
+ echo ' (' . esc_html( $ingredient['notes'] ) . ')';
47
+ }
48
+ echo '</li>';
49
+ }
50
+
51
+ echo '</ul>';
52
+ }
53
+ }
54
+ ?>
55
+ </div>
56
+ <div class="wprm-fallback-recipe-instructions">
57
+ <?php
58
+ $instructions = $recipe->instructions();
59
+
60
+ foreach ( $instructions as $instruction_group ) {
61
+ if ( $instruction_group['name'] ) {
62
+ echo '<h4>'. esc_html( $instruction_group['name'] ) . '</h4>';
63
+ }
64
+
65
+ if ( count( $instruction_group['instructions'] > 0 ) ) {
66
+ echo '<ol>';
67
+
68
+ foreach ( $instruction_group['instructions'] as $instruction ) {
69
+ echo '<li>';
70
+ if ( $instruction['text'] ) {
71
+ echo wp_kses_post( $instruction['text'] );
72
+ }
73
+
74
+ if ( $instruction['image'] ) {
75
+ $thumb = wp_get_attachment_image_src( $instruction['image'], 'thumbnail' );
76
+ $image_url = $thumb && isset( $thumb[0] ) ? $thumb[0] : '';
77
+
78
+ if ( $instruction['text'] && $image_url ) {
79
+ echo '<br />';
80
+ }
81
+
82
+ if ( $image_url ) {
83
+ echo '<img src="' . esc_attr( $image_url ) . '"/>';
84
+ echo '<br />';
85
+ }
86
+ }
87
+ echo '</li>';
88
+ }
89
+
90
+ echo '</ol>';
91
+ }
92
+ }
93
+ ?>
94
+ </div>
95
+ <div class="wprm-fallback-recipe-notes">
96
+ <?php echo wp_kses_post( $recipe->notes() ); ?>
97
+ </div>
98
+ </div>
99
+ <!--End WPRM Recipe-->
templates/recipe/clean-print/clean-print.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .wprm-recipe-clean-print{font-size:0.9em}.wprm-recipe-clean-print h2.wprm-recipe-name,.wprm-recipe-clean-print h3.wprm-recipe-header,.wprm-recipe-clean-print h4.wprm-recipe-group-name{font-variant:normal;text-transform:none;letter-spacing:normal;margin:0}.wprm-recipe-clean-print h2.wprm-recipe-name{clear:none;font-size:1.8em}.wprm-recipe-clean-print .wprm-recipe-details-container,.wprm-recipe-clean-print .wprm-recipe-summary{margin-bottom:15px}.wprm-recipe-clean-print .wprm-recipe-details-name{display:inline-block;font-weight:bold;min-width:130px}.wprm-recipe-clean-print .wprm-recipe-details-unit{font-size:0.8em}.wprm-recipe-clean-print h3.wprm-recipe-header{margin-top:10px;font-size:1.2em}.wprm-recipe-clean-print ol,.wprm-recipe-clean-print ul{margin:0 0 8px}.wprm-recipe-clean-print ol li,.wprm-recipe-clean-print ul li{margin:0 0 0 32px}.wprm-recipe-clean-print h4.wprm-recipe-group-name{margin-top:5px !important;font-weight:300;font-size:1em}.wprm-recipe-clean-print .wprm-recipe-ingredient-notes{color:#999999}.wprm-recipe-clean-print .wprm-recipe-instructions .wprm-recipe-instruction{margin-bottom:5px}.wprm-recipe-clean-print .wprm-recipe-instruction-text p{margin:0 0 5px}.wprm-recipe-clean-print .wprm-recipe-instruction-text p:last-of-type{margin-bottom:0}
templates/recipe/clean-print/clean-print.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Clean print recipe template.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/recipe/clean-print
10
+ */
11
+
12
+ // @codingStandardsIgnoreStart
13
+ ?>
14
+ <div class="wprm-recipe wprm-recipe-clean-print">
15
+ <h2 class="wprm-recipe-name"><?php echo $recipe->name(); ?></h2>
16
+ <div class="wprm-recipe-summary">
17
+ <?php echo $recipe->summary(); ?>
18
+ </div>
19
+ <div class="wprm-recipe-details-container wprm-recipe-tags-container">
20
+ <?php
21
+ $courses = $recipe->tags( 'course' );
22
+ if ( count( $courses ) > 0 ) : ?>
23
+ <div class="wprm-recipe-course-container">
24
+ <span class="wprm-recipe-details-name wprm-recipe-course-name"><?php _e( 'Course', 'wp-recipe-maker' ); ?></span>
25
+ <span class="wprm-recipe-course">
26
+ <?php foreach ( $courses as $index => $course ) {
27
+ if ( 0 !== $index ) {
28
+ echo ', ';
29
+ }
30
+ echo $course->name;
31
+ } ?>
32
+ </span>
33
+ </div>
34
+ <?php endif; // Course. ?>
35
+ <?php
36
+ $cuisines = $recipe->tags( 'cuisine' );
37
+ if ( count( $recipe->tags( 'cuisine' ) ) > 0 ) : ?>
38
+ <div class="wprm-recipe-cuisine-container">
39
+ <span class="wprm-recipe-details-name wprm-recipe-cuisine-name"><?php _e( 'Cuisine', 'wp-recipe-maker' ); ?></span>
40
+ <span class="wprm-recipe-details wprm-recipe-cuisine">
41
+ <?php foreach ( $cuisines as $index => $cuisine ) {
42
+ if ( 0 !== $index ) {
43
+ echo ', ';
44
+ }
45
+ echo $cuisine->name;
46
+ } ?>
47
+ </span>
48
+ </div>
49
+ <?php endif; // Cuisine. ?>
50
+ </div>
51
+ <div class="wprm-recipe-details-container wprm-recipe-times-container">
52
+ <?php if ( $recipe->prep_time() ) : ?>
53
+ <div class="wprm-recipe-prep-time-container">
54
+ <span class="wprm-recipe-details-name wprm-recipe-prep-time-name"><?php _e( 'Prep Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-prep-time"><?php echo $recipe->prep_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-prep-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
55
+ </div>
56
+ <?php endif; // Prep time. ?>
57
+ <?php if ( $recipe->cook_time() ) : ?>
58
+ <div class="wprm-recipe-cook-time-container">
59
+ <span class="wprm-recipe-details-name wprm-recipe-cook-time-name"><?php _e( 'Cook Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-cook-time"><?php echo $recipe->cook_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-cook-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
60
+ </div>
61
+ <?php endif; // Cook time. ?>
62
+ <?php if ( $recipe->total_time() ) : ?>
63
+ <div class="wprm-recipe-total-time-container">
64
+ <span class="wprm-recipe-details-name wprm-recipe-total-time-name"><?php _e( 'Total Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-total-time"><?php echo $recipe->total_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-total-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
65
+ </div>
66
+ <?php endif; // Total time. ?>
67
+ </div>
68
+ <?php if ( $recipe->servings() ) : ?>
69
+ <div class="wprm-recipe-details-container wprm-recipe-servings-container">
70
+ <span class="wprm-recipe-details-name wprm-recipe-servings-name"><?php _e( 'Servings', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-servings"><?php echo $recipe->servings(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-servings-unit"><?php echo $recipe->servings_unit(); ?></span>
71
+ </div>
72
+ <?php endif; // Servings. ?>
73
+
74
+ <?php
75
+ $ingredients = $recipe->ingredients();
76
+ if ( count( $ingredients ) > 0 ) : ?>
77
+ <div class="wprm-recipe-ingredients-container">
78
+ <h3 class="wprm-recipe-header"><?php _e( 'Ingredients', 'wp-recipe-maker' ); ?></h3>
79
+ <?php foreach ( $ingredients as $ingredient_group ) : ?>
80
+ <div class="wprm-recipe-ingredient-group">
81
+ <?php if ( $ingredient_group['name'] ) : ?>
82
+ <h4 class="wprm-recipe-group-name wprm-recipe-ingredient-group-name"><?php echo $ingredient_group['name']; ?></h4>
83
+ <?php endif; // Ingredient group name. ?>
84
+ <ul class="wprm-recipe-ingredients">
85
+ <?php foreach ( $ingredient_group['ingredients'] as $ingredient ) : ?>
86
+ <li class="wprm-recipe-ingredient">
87
+ <?php if ( $ingredient['amount'] ) : ?>
88
+ <span class="wprm-recipe-ingredient-amount"><?php echo $ingredient['amount']; ?></span>
89
+ <?php endif; // Ingredient amount. ?>
90
+ <?php if ( $ingredient['unit'] ) : ?>
91
+ <span class="wprm-recipe-ingredient-unit"><?php echo $ingredient['unit']; ?></span>
92
+ <?php endif; // Ingredient unit. ?>
93
+ <?php if ( $ingredient['name'] ) : ?>
94
+ <span class="wprm-recipe-ingredient-name"><?php echo $ingredient['name']; ?></span>
95
+ <?php endif; // Ingredient name. ?>
96
+ <?php if ( $ingredient['notes'] ) : ?>
97
+ <span class="wprm-recipe-ingredient-notes"><?php echo $ingredient['notes']; ?></span>
98
+ <?php endif; // Ingredient notes. ?>
99
+ </li>
100
+ <?php endforeach; // Ingredients. ?>
101
+ </ul>
102
+ </div>
103
+ <?php endforeach; // Ingredient groups. ?>
104
+ </div>
105
+ <?php endif; // Ingredients. ?>
106
+ <?php
107
+ $instructions = $recipe->instructions();
108
+ if ( count( $instructions ) > 0 ) : ?>
109
+ <div class="wprm-recipe-instructions-container">
110
+ <h3 class="wprm-recipe-header"><?php _e( 'Instructions', 'wp-recipe-maker' ); ?></h3>
111
+ <?php foreach ( $instructions as $instruction_group ) : ?>
112
+ <div class="wprm-recipe-instruction-group">
113
+ <?php if ( $instruction_group['name'] ) : ?>
114
+ <h4 class="wprm-recipe-group-name wprm-recipe-instruction-group-name"><?php echo $instruction_group['name']; ?></h4>
115
+ <?php endif; // Instruction group name. ?>
116
+ <ol class="wprm-recipe-instructions">
117
+ <?php foreach ( $instruction_group['instructions'] as $instruction ) : ?>
118
+ <li class="wprm-recipe-instruction">
119
+ <?php if ( $instruction['text'] ) : ?>
120
+ <div class="wprm-recipe-instruction-text"><?php echo $instruction['text']; ?></div>
121
+ <?php endif; // Instruction text. ?>
122
+ </li>
123
+ <?php endforeach; // Instructions. ?>
124
+ </ol>
125
+ </div>
126
+ <?php endforeach; // Instruction groups. ?>
127
+ </div>
128
+ <?php endif; // Instructions. ?>
129
+ <?php if ( $recipe->notes() ) : ?>
130
+ <div class="wprm-recipe-notes-container">
131
+ <h3 class="wprm-recipe-header"><?php _e( 'Recipe Notes', 'wp-recipe-maker' ); ?></h3>
132
+ <?php echo $recipe->notes(); ?>
133
+ </div>
134
+ <?php endif; // Notes ?>
135
+ </div>
templates/recipe/clean-print/clean-print.scss ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wprm-recipe-clean-print {
2
+ font-size: 0.9em;
3
+
4
+ h2.wprm-recipe-name,
5
+ h3.wprm-recipe-header,
6
+ h4.wprm-recipe-group-name {
7
+ font-variant: normal;
8
+ text-transform: none;
9
+ letter-spacing: normal;
10
+ margin: 0;
11
+ }
12
+
13
+ h2.wprm-recipe-name {
14
+ clear: none;
15
+ font-size: 1.8em;
16
+ }
17
+
18
+ .wprm-recipe-details-container,
19
+ .wprm-recipe-summary {
20
+ margin-bottom: 15px;
21
+ }
22
+
23
+ .wprm-recipe-details-name {
24
+ display: inline-block;
25
+ font-weight: bold;
26
+ min-width: 130px;
27
+ }
28
+
29
+ .wprm-recipe-details-unit {
30
+ font-size: 0.8em;
31
+ }
32
+
33
+ h3.wprm-recipe-header {
34
+ margin-top: 10px;
35
+ font-size: 1.2em;
36
+ }
37
+
38
+ ol,
39
+ ul {
40
+ margin: 0 0 8px;
41
+
42
+ li {
43
+ margin: 0 0 0 32px;
44
+ }
45
+ }
46
+
47
+ h4.wprm-recipe-group-name {
48
+ margin-top: 5px !important;
49
+ font-weight: 300;
50
+ font-size: 1em;
51
+ }
52
+
53
+ .wprm-recipe-ingredient-notes {
54
+ color: #999999;
55
+ }
56
+
57
+ .wprm-recipe-instructions {
58
+ .wprm-recipe-instruction {
59
+ margin-bottom: 5px;
60
+ }
61
+ }
62
+
63
+ .wprm-recipe-instruction-text {
64
+ p {
65
+ margin: 0 0 5px;
66
+ }
67
+
68
+ p:last-of-type {
69
+ margin-bottom: 0;
70
+ }
71
+ }
72
+ }
templates/recipe/simple/simple.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .wprm-recipe-simple{border-top:1px solid #aaaaaa;background-color:#fafafa;padding:10px;margin:20px 0;font-size:0.9em}.wprm-recipe-simple h2.wprm-recipe-name,.wprm-recipe-simple h3.wprm-recipe-header,.wprm-recipe-simple h4.wprm-recipe-group-name{font-variant:normal;text-transform:none;letter-spacing:normal;margin:0}.wprm-recipe-simple .wprm-recipe-image-container{float:right;text-align:center;margin-bottom:10px}.wprm-recipe-simple .wprm-recipe-image-container .wprm-recipe-image{margin:0 0 5px 5px}.wprm-recipe-simple .wprm-recipe-image-container .wprm-recipe-print{font-size:0.9em;cursor:pointer}.wprm-recipe-simple h2.wprm-recipe-name{clear:none;font-size:1.8em}.wprm-recipe-simple .wprm-recipe-details-container,.wprm-recipe-simple .wprm-recipe-summary{margin-bottom:15px}.wprm-recipe-simple .wprm-recipe-details-icon svg{vertical-align:middle;width:16px;height:16px}.wprm-recipe-simple .wprm-recipe-details-name{display:inline-block;font-weight:bold;min-width:130px}.wprm-recipe-simple .wprm-recipe-details-unit{font-size:0.8em}.wprm-recipe-simple h3.wprm-recipe-header{margin-top:10px;font-size:1.2em}.wprm-recipe-simple ol,.wprm-recipe-simple ul{margin:0 0 8px}.wprm-recipe-simple ol li,.wprm-recipe-simple ul li{margin:0 0 0 32px}.wprm-recipe-simple h4.wprm-recipe-group-name{margin-top:5px !important;font-weight:300;font-size:1em}.wprm-recipe-simple .wprm-recipe-ingredient-notes{color:#999999}.wprm-recipe-simple .wprm-recipe-instructions .wprm-recipe-instruction{margin-bottom:5px}.wprm-recipe-simple .wprm-recipe-instruction-text p{margin:0 0 5px}.wprm-recipe-simple .wprm-recipe-instruction-text p:last-of-type{margin-bottom:0}.wprm-recipe-simple .wprm-recipe-instruction-image{margin:5px 0 15px}@media only screen and (max-width: 640px){.wprm-recipe-simple .wprm-recipe-image-container{float:none}.wprm-recipe-simple .wprm-recipe-details-name{min-width:0}}
templates/recipe/simple/simple.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Simple recipe template.
4
+ *
5
+ * @link http://bootstrapped.ventures
6
+ * @since 1.0.0
7
+ *
8
+ * @package WP_Recipe_Maker
9
+ * @subpackage WP_Recipe_Maker/templates/recipe/simple
10
+ */
11
+
12
+ // @codingStandardsIgnoreStart
13
+ ?>
14
+ <div class="wprm-recipe wprm-recipe-simple">
15
+ <div class="wprm-recipe-image-container">
16
+ <div class="wprm-recipe-image"><?php echo $recipe->image(); ?></div>
17
+ <div class="wprm-recipe-buttons"><span class="wprm-recipe-print"><span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/printer.svg' ); ?></span> <?php _e( 'Print', 'wp-recipe-maker' ); ?></span></div>
18
+ </div>
19
+ <h2 class="wprm-recipe-name"><?php echo $recipe->name(); ?></h2>
20
+ <div class="wprm-recipe-summary">
21
+ <?php echo $recipe->summary(); ?>
22
+ </div>
23
+ <div class="wprm-recipe-details-container wprm-recipe-tags-container">
24
+ <?php
25
+ $courses = $recipe->tags( 'course' );
26
+ if ( count( $courses ) > 0 ) : ?>
27
+ <div class="wprm-recipe-course-container">
28
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/tag.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-course-name"><?php _e( 'Course', 'wp-recipe-maker' ); ?></span>
29
+ <span class="wprm-recipe-course">
30
+ <?php foreach ( $courses as $index => $course ) {
31
+ if ( 0 !== $index ) {
32
+ echo ', ';
33
+ }
34
+ echo $course->name;
35
+ } ?>
36
+ </span>
37
+ </div>
38
+ <?php endif; // Course. ?>
39
+ <?php
40
+ $cuisines = $recipe->tags( 'cuisine' );
41
+ if ( count( $recipe->tags( 'cuisine' ) ) > 0 ) : ?>
42
+ <div class="wprm-recipe-cuisine-container">
43
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/tag.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-cuisine-name"><?php _e( 'Cuisine', 'wp-recipe-maker' ); ?></span>
44
+ <span class="wprm-recipe-details wprm-recipe-cuisine">
45
+ <?php foreach ( $cuisines as $index => $cuisine ) {
46
+ if ( 0 !== $index ) {
47
+ echo ', ';
48
+ }
49
+ echo $cuisine->name;
50
+ } ?>
51
+ </span>
52
+ </div>
53
+ <?php endif; // Cuisine. ?>
54
+ </div>
55
+ <div class="wprm-recipe-details-container wprm-recipe-times-container">
56
+ <?php if ( $recipe->prep_time() ) : ?>
57
+ <div class="wprm-recipe-prep-time-container">
58
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/knife.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-prep-time-name"><?php _e( 'Prep Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-prep-time"><?php echo $recipe->prep_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-prep-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
59
+ </div>
60
+ <?php endif; // Prep time. ?>
61
+ <?php if ( $recipe->cook_time() ) : ?>
62
+ <div class="wprm-recipe-cook-time-container">
63
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/pan.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-cook-time-name"><?php _e( 'Cook Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-cook-time"><?php echo $recipe->cook_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-cook-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
64
+ </div>
65
+ <?php endif; // Cook time. ?>
66
+ <?php if ( $recipe->total_time() ) : ?>
67
+ <div class="wprm-recipe-total-time-container">
68
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/clock.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-total-time-name"><?php _e( 'Total Time', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-total-time"><?php echo $recipe->total_time(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-total-time-unit"><?php _e( 'minutes', 'wp-recipe-maker' ); ?></span>
69
+ </div>
70
+ <?php endif; // Total time. ?>
71
+ </div>
72
+ <?php if ( $recipe->servings() ) : ?>
73
+ <div class="wprm-recipe-details-container wprm-recipe-servings-container">
74
+ <span class="wprm-recipe-details-icon"><?php include( WPRM_DIR . 'assets/icons/cutlery.svg' ); ?></span> <span class="wprm-recipe-details-name wprm-recipe-servings-name"><?php _e( 'Servings', 'wp-recipe-maker' ); ?></span> <span class="wprm-recipe-details wprm-recipe-servings"><?php echo $recipe->servings(); ?></span> <span class="wprm-recipe-details-unit wprm-recipe-servings-unit"><?php echo $recipe->servings_unit(); ?></span>
75
+ </div>
76
+ <?php endif; // Servings. ?>
77
+
78
+ <?php
79
+ $ingredients = $recipe->ingredients();
80
+ if ( count( $ingredients ) > 0 ) : ?>
81
+ <div class="wprm-recipe-ingredients-container">
82
+ <h3 class="wprm-recipe-header"><?php _e( 'Ingredients', 'wp-recipe-maker' ); ?></h3>
83
+ <?php foreach ( $ingredients as $ingredient_group ) : ?>
84
+ <div class="wprm-recipe-ingredient-group">
85
+ <?php if ( $ingredient_group['name'] ) : ?>
86
+ <h4 class="wprm-recipe-group-name wprm-recipe-ingredient-group-name"><?php echo $ingredient_group['name']; ?></h4>
87
+ <?php endif; // Ingredient group name. ?>
88
+ <ul class="wprm-recipe-ingredients">
89
+ <?php foreach ( $ingredient_group['ingredients'] as $ingredient ) : ?>
90
+ <li class="wprm-recipe-ingredient">
91
+ <?php if ( $ingredient['amount'] ) : ?>
92
+ <span class="wprm-recipe-ingredient-amount"><?php echo $ingredient['amount']; ?></span>
93
+ <?php endif; // Ingredient amount. ?>
94
+ <?php if ( $ingredient['unit'] ) : ?>
95
+ <span class="wprm-recipe-ingredient-unit"><?php echo $ingredient['unit']; ?></span>
96
+ <?php endif; // Ingredient unit. ?>
97
+ <?php if ( $ingredient['name'] ) : ?>
98
+ <span class="wprm-recipe-ingredient-name"><?php echo $ingredient['name']; ?></span>
99
+ <?php endif; // Ingredient name. ?>
100
+ <?php if ( $ingredient['notes'] ) : ?>
101
+ <span class="wprm-recipe-ingredient-notes"><?php echo $ingredient['notes']; ?></span>
102
+ <?php endif; // Ingredient notes. ?>
103
+ </li>
104
+ <?php endforeach; // Ingredients. ?>
105
+ </ul>
106
+ </div>
107
+ <?php endforeach; // Ingredient groups. ?>
108
+ </div>
109
+ <?php endif; // Ingredients. ?>
110
+ <?php
111
+ $instructions = $recipe->instructions();
112
+ if ( count( $instructions ) > 0 ) : ?>
113
+ <div class="wprm-recipe-instructions-container">
114
+ <h3 class="wprm-recipe-header"><?php _e( 'Instructions', 'wp-recipe-maker' ); ?></h3>
115
+ <?php foreach ( $instructions as $instruction_group ) : ?>
116
+ <div class="wprm-recipe-instruction-group">
117
+ <?php if ( $instruction_group['name'] ) : ?>
118
+ <h4 class="wprm-recipe-group-name wprm-recipe-instruction-group-name"><?php echo $instruction_group['name']; ?></h4>
119
+ <?php endif; // Instruction group name. ?>
120
+ <ol class="wprm-recipe-instructions">
121
+ <?php foreach ( $instruction_group['instructions'] as $instruction ) : ?>
122
+ <li class="wprm-recipe-instruction">
123
+ <?php if ( $instruction['text'] ) : ?>
124
+ <div class="wprm-recipe-instruction-text"><?php echo $instruction['text']; ?></div>
125
+ <?php endif; // Instruction text. ?>
126
+ <?php if ( $instruction['image'] ) : ?>
127
+ <div class="wprm-recipe-instruction-image"><?php echo wp_get_attachment_image( $instruction['image'], 'thumbnail' ); ?></div>
128
+ <?php endif; // Instruction image. ?>
129
+ </li>
130
+ <?php endforeach; // Instructions. ?>
131
+ </ol>
132
+ </div>
133
+ <?php endforeach; // Instruction groups. ?>
134
+ </div>
135
+ <?php endif; // Instructions. ?>
136
+ <?php if ( $recipe->notes() ) : ?>
137
+ <div class="wprm-recipe-notes-container">
138
+ <h3 class="wprm-recipe-header"><?php _e( 'Recipe Notes', 'wp-recipe-maker' ); ?></h3>
139
+ <?php echo $recipe->notes(); ?>
140
+ </div>
141
+ <?php endif; // Notes ?>
142
+ </div>
templates/recipe/simple/simple.scss ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wprm-recipe-simple {
2
+ border-top: 1px solid #aaaaaa;
3
+ background-color: #fafafa;
4
+ padding: 10px;
5
+ margin: 20px 0;
6
+ font-size: 0.9em;
7
+
8
+ h2.wprm-recipe-name,
9
+ h3.wprm-recipe-header,
10
+ h4.wprm-recipe-group-name {
11
+ font-variant: normal;
12
+ text-transform: none;
13
+ letter-spacing: normal;
14
+ margin: 0;
15
+ }
16
+
17
+ .wprm-recipe-image-container {
18
+ float: right;
19
+ text-align: center;
20
+ margin-bottom: 10px;
21
+
22
+ .wprm-recipe-image {
23
+ margin: 0 0 5px 5px;
24
+ }
25
+
26
+ .wprm-recipe-print {
27
+ font-size: 0.9em;
28
+ cursor: pointer
29
+ }
30
+ }
31
+
32
+ h2.wprm-recipe-name {
33
+ clear: none;
34
+ font-size: 1.8em;
35
+ }
36
+
37
+ .wprm-recipe-details-container,
38
+ .wprm-recipe-summary {
39
+ margin-bottom: 15px;
40
+ }
41
+
42
+ .wprm-recipe-details-icon {
43
+ svg {
44
+ vertical-align: middle;
45
+ width: 16px;
46
+ height: 16px;
47
+ }
48
+ }
49
+
50
+ .wprm-recipe-details-name {
51
+ display: inline-block;
52
+ font-weight: bold;
53
+ min-width: 130px;
54
+ }
55
+
56
+ .wprm-recipe-details-unit {
57
+ font-size: 0.8em;
58
+ }
59
+
60
+ h3.wprm-recipe-header {
61
+ margin-top: 10px;
62
+ font-size: 1.2em;
63
+ }
64
+
65
+ ol,
66
+ ul {
67
+ margin: 0 0 8px;
68
+
69
+ li {
70
+ margin: 0 0 0 32px;
71
+ }
72
+ }
73
+
74
+ h4.wprm-recipe-group-name {
75
+ margin-top: 5px !important;
76
+ font-weight: 300;
77
+ font-size: 1em;
78
+ }
79
+
80
+ .wprm-recipe-ingredient-notes {
81
+ color: #999999;
82
+ }
83
+
84
+ .wprm-recipe-instructions {
85
+ .wprm-recipe-instruction {
86
+ margin-bottom: 5px;
87
+ }
88
+ }
89
+
90
+ .wprm-recipe-instruction-text {
91
+ p {
92
+ margin: 0 0 5px;
93
+ }
94
+
95
+ p:last-of-type {
96
+ margin-bottom: 0;
97
+ }
98
+ }
99
+
100
+ .wprm-recipe-instruction-image {
101
+ margin: 5px 0 15px;
102
+ }
103
+ }
104
+ @media only screen and (max-width: 640px) {
105
+ .wprm-recipe-simple {
106
+ .wprm-recipe-image-container {
107
+ float: none;
108
+ }
109
+
110
+ .wprm-recipe-details-name {
111
+ min-width: 0;
112
+ }
113
+ }
114
+ }
vendor/medium-editor/LICENSE ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright Davi Ferreira, http://www.daviferreira.com/
2
+
3
+ This software consists of voluntary contributions made by many
4
+ individuals. For exact contribution history, see the revision history
5
+ available at https://github.com/yabwe/medium-editor
6
+
7
+ The following license applies to all parts of this software except as
8
+ documented below:
9
+
10
+ ====
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining
13
+ a copy of this software and associated documentation files (the
14
+ "Software"), to deal in the Software without restriction, including
15
+ without limitation the rights to use, copy, modify, merge, publish,
16
+ distribute, sublicense, and/or sell copies of the Software, and to
17
+ permit persons to whom the Software is furnished to do so, subject to
18
+ the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be
21
+ included in all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ ====
32
+
33
+ All files located in the node_modules directory are
34
+ externally maintained libraries used by this software which have their
35
+ own licenses; we recommend you read them, as their terms may differ from
36
+ the terms above.
vendor/medium-editor/README.md ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MediumEditor
2
+
3
+ [![Join the chat at https://gitter.im/yabwe/medium-editor](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/yabwe/medium-editor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
+
5
+ This is a clone of [medium.com](https://medium.com) inline editor toolbar.
6
+
7
+ MediumEditor has been written using vanilla JavaScript, no additional frameworks required.
8
+
9
+ ## Browser Support
10
+
11
+ [![Sauce Test Status](https://saucelabs.com/browser-matrix/mediumeditor.svg)](https://saucelabs.com/u/mediumeditor)
12
+
13
+ ![Supported Browsers](https://cloud.githubusercontent.com/assets/2444240/12874138/d3960a04-cd9b-11e5-8cc5-8136d82cf5f6.png)
14
+
15
+ [![NPM info](https://nodei.co/npm/medium-editor.png?downloads=true)](https://www.npmjs.com/package/medium-editor)
16
+
17
+ [![Travis build status](https://travis-ci.org/yabwe/medium-editor.svg?branch=master)](https://travis-ci.org/yabwe/medium-editor)
18
+ [![Dependency Status](https://david-dm.org/yabwe/medium-editor.svg)](https://david-dm.org/yabwe/medium-editor)
19
+ [![devDependency Status](https://david-dm.org/yabwe/medium-editor/dev-status.svg)](https://david-dm.org/yabwe/medium-editor#info=devDependencies)
20
+ [![Coverage Status](https://coveralls.io/repos/yabwe/medium-editor/badge.svg?branch=master&service=github)](https://coveralls.io/github/yabwe/medium-editor?branch=master)
21
+
22
+ # Basic usage
23
+
24
+ ![screenshot](https://raw.github.com/yabwe/medium-editor/master/demo/img/medium-editor.jpg)
25
+
26
+ __demo__: [http://yabwe.github.io/medium-editor/](http://yabwe.github.io/medium-editor/)
27
+
28
+ ### Installation
29
+
30
+ **Via npm:**
31
+
32
+ Run in your console: `npm install medium-editor`
33
+
34
+ **Via bower:**
35
+
36
+ `bower install medium-editor`
37
+
38
+ **Via an external CDN**
39
+
40
+ * Using [jsDelivr](http://www.jsdelivr.com/#!medium-editor).
41
+
42
+ For the latest version:
43
+
44
+ ```html
45
+ <script src="//cdn.jsdelivr.net/medium-editor/latest/js/medium-editor.min.js"></script>
46
+ <link rel="stylesheet" href="//cdn.jsdelivr.net/medium-editor/latest/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
47
+ ```
48
+
49
+ For a custom one:
50
+
51
+ ```html
52
+ <script src="//cdn.jsdelivr.net/medium-editor/4.11.1/js/medium-editor.min.js"></script>
53
+ <link rel="stylesheet" href="//cdn.jsdelivr.net/medium-editor/4.11.1/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
54
+ ```
55
+
56
+ * Using [CDNJS](https://cdnjs.com/libraries/medium-editor).
57
+
58
+ **Manual installation:**
59
+
60
+ Download the [latest release](https://github.com/yabwe/medium-editor/releases) and attach medium editor's stylesheets to your page:
61
+
62
+ ```html
63
+ <link rel="stylesheet" href="css/medium-editor.css"> <!-- Core -->
64
+ <link rel="stylesheet" href="css/themes/default.css"> <!-- or any other theme -->
65
+ ```
66
+
67
+ ### Usage
68
+
69
+ The next step is to reference the editor's script
70
+
71
+ ```html
72
+ <script src="js/medium-editor.js"></script>
73
+ ```
74
+
75
+ You can now instantiate a new MediumEditor object:
76
+ ```html
77
+ <script>var editor = new MediumEditor('.editable');</script>
78
+ ```
79
+
80
+ The above code will transform all the elements with the .editable class into HTML5 editable contents and add the medium editor toolbar to them.
81
+
82
+ You can also pass a list of HTML elements:
83
+
84
+ ```javascript
85
+ var elements = document.querySelectorAll('.editable'),
86
+ editor = new MediumEditor(elements);
87
+ ```
88
+
89
+ MediumEditor also supports textarea. If you provide a textarea element, the script will create a new div with `contentEditable=true`, hide the textarea and link the textarea value to the div HTML content.
90
+
91
+ ##### Integrating with various frameworks
92
+
93
+ People have contributed wrappers around MediumEditor for integrating with different frameworks and tech stacks. Take a look at the list of existing [Wrappers and Integrations](https://github.com/yabwe/medium-editor/wiki/Wrappers-and-Integration) that have already been written for MediumEditor!
94
+
95
+ ## MediumEditor Options
96
+
97
+ View the [MediumEditor Options documentation](OPTIONS.md) on all the various options for MediumEditor.
98
+
99
+ Options to customize medium-editor are passed as the second argument to the [MediumEditor constructor](API.md#mediumeditorelements-options). Example:
100
+
101
+ ```js
102
+ var editor = new MediumEditor('.editor', {
103
+ // options go here
104
+ });
105
+ ```
106
+
107
+ ### Core options
108
+ * __activeButtonClass__: CSS class added to active buttons in the toolbar. Default: `'medium-editor-button-active'`
109
+ * __buttonLabels__: type of labels on the buttons. Values: `false` | 'fontawesome'. Default: `false`
110
+
111
+ #### NOTE:
112
+ Using `'fontawesome'` as the buttonLabels requires version 4.1.0 of the fontawesome css to be on the page to ensure all icons will be displayed correctly
113
+
114
+ * __delay__: time in milliseconds to show the toolbar or anchor tag preview. Default: `0`
115
+ * __disableReturn__: enables/disables the use of the return-key. You can also set specific element behavior by using setting a data-disable-return attribute. Default: `false`
116
+ * __disableDoubleReturn__: allows/disallows two (or more) empty new lines. You can also set specific element behavior by using setting a data-disable-double-return attribute. Default: `false`
117
+ * __disableExtraSpaces__: when set to true, it disallows spaces at the beginning and end of the element. Also it disallows entering 2 consecutive spaces between 2 words. Default: `false`
118
+ * __disableEditing__: enables/disables adding the contenteditable behavior. Useful for using the toolbar with customized buttons/actions. You can also set specific element behavior by using setting a data-disable-editing attribute. Default: `false`
119
+ * __elementsContainer__: specifies a DOM node to contain MediumEditor's toolbar and anchor preview elements. Default: `document.body`
120
+ * __extensions__: extension to use (see [Custom Buttons and Extensions](src/js/extensions)) for more. Default: `{}`
121
+ * __spellcheck__: Enable/disable native contentEditable automatic spellcheck. Default: `true`
122
+ * __targetBlank__: enables/disables target="\_blank" for anchor tags. Default: `false`
123
+
124
+ ### Toolbar options
125
+
126
+ The toolbar for MediumEditor is implemented as a built-in extension which automatically displays whenever the user selects some text. The toolbar can hold any set of defined built-in buttons, but can also hold any custom buttons passed in as extensions.
127
+
128
+ Options for the toolbar are passed as an object that is a member of the outer options object. Example:
129
+ ```javascript
130
+ var editor = new MediumEditor('.editable', {
131
+ toolbar: {
132
+ /* These are the default options for the toolbar,
133
+ if nothing is passed this is what is used */
134
+ allowMultiParagraphSelection: true,
135
+ buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
136
+ diffLeft: 0,
137
+ diffTop: -10,
138
+ firstButtonClass: 'medium-editor-button-first',
139
+ lastButtonClass: 'medium-editor-button-last',
140
+ relativeContainer: null,
141
+ standardizeSelectionStart: false,
142
+ static: false,
143
+ /* options which only apply when static is true */
144
+ align: 'center',
145
+ sticky: false,
146
+ updateOnEmptySelection: false
147
+ }
148
+ });
149
+ ```
150
+
151
+ * __allowMultiParagraphSelection__: enables/disables whether the toolbar should be displayed when selecting multiple paragraphs/block elements. Default: `true`
152
+ * __buttons__: the set of buttons to display on the toolbar. Default: `['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote']`
153
+ * See [Button Options](#button-options) for details on more button options
154
+ * __diffLeft__: value in pixels to be added to the X axis positioning of the toolbar. Default: `0`
155
+ * __diffTop__: value in pixels to be added to the Y axis positioning of the toolbar. Default: `-10`
156
+ * __firstButtonClass__: CSS class added to the first button in the toolbar. Default: `'medium-editor-button-first'`
157
+ * __lastButtonClass__: CSS class added to the last button in the toolbar. Default: `'medium-editor-button-last'`
158
+ * __relativeContainer__: DOMElement to append the toolbar to instead of the body. When passed, the toolbar will also be positioned `relative` instead of `absolute`. Default: `null`
159
+ * __standardizeSelectionStart__: enables/disables standardizing how the beginning of a range is decided between browsers whenever the selected text is analyzed for updating toolbar buttons status. Default: `false`
160
+ * __static__: enable/disable the toolbar always displaying in the same location relative to the medium-editor element. Default: `false`
161
+
162
+ ##### Options which only apply when the `static` option is being used:
163
+ * __align__: `left`|`center`|`right` - When the __static__ option is `true`, this aligns the static toolbar relative to the medium-editor element. Default: `center`
164
+ * __sticky__: When the __static__ option is `true`, this enables/disables the toolbar "sticking" to the viewport and staying visible on the screen while the page scrolls. Default: `false`
165
+ * __updateOnEmptySelection__: When the __static__ option is `true`, this enables/disables updating the state of the toolbar buttons even when the selection is collapsed (there is no selection, just a cursor). Default: `false`
166
+
167
+ To disable the toolbar (which also disables the anchor-preview extension), set the value of the `toolbar` option to `false`:
168
+ ```javascript
169
+ var editor = new MediumEditor('.editable', {
170
+ toolbar: false
171
+ });
172
+ ```
173
+
174
+ #### Button Options
175
+
176
+ Button behavior can be modified by passing an object into the buttons array instead of a string. This allow for overriding some of the default behavior of buttons. The following options are some of the basic parts of buttons that you may override, but any part of the `MediumEditor.Extension.prototype` can be overridden via these button options. (Check out the [source code for buttons](src/js/extensions/button.js) to see what all can be overridden).
177
+
178
+ * __name__: name of the button being overridden
179
+ * __action__: argument to pass to `MediumEditor.execAction()` when the button is clicked.
180
+ * __aria__: value to add as the aria-label attribute of the button element displayed in the toolbar. This is also used as the tooltip for the button.
181
+ * __tagNames__: array of element tag names that would indicate that this button has already been applied. If this action has already been applied, the button will be displayed as 'active' in the toolbar.
182
+ * _Example_: For 'bold', if the text is ever within a `<b>` or `<strong>` tag that indicates the text is already bold. So the array of tagNames for bold would be: `['b', 'strong']`
183
+ * __NOTE__: This is not used if `useQueryState` is set to `true`.
184
+ * __style__: A pair of css property & value(s) that indicate that this button has already been applied. If this action has already been applied, the button will be displayed as 'active' in the toolbar.
185
+ * _Example_: For 'bold', if the text is ever within an element with a `'font-weight'` style property set to `700` or `'bold'`, that indicates the text is already bold. So the style object for bold would be `{ prop: 'font-weight', value: '700|bold' }`
186
+ * __NOTE__: This is not used if `useQueryState` is set to `true`.
187
+ * Properties of the __style__ object:
188
+ * __prop__: name of the css property
189
+ * __value__: value(s) of the css property (multiple values can be separated by a `'|'`)
190
+ * __useQueryState__: Enables/disables whether this button should use the built-in `document.queryCommandState()` method to determine whether the action has already been applied. If the action has already been applied, the button will be displayed as 'active' in the toolbar
191
+ * _Example_: For 'bold', if this is set to true, the code will call `document.queryCommandState('bold')` which will return true if the browser thinks the text is already bold, and false otherwise
192
+ * __contentDefault__: Default `innerHTML` to put inside the button
193
+ * __contentFA__: The `innerHTML` to use for the content of the button if the __buttonLabels__ option for MediumEditor is set to `'fontawesome'`
194
+ * __classList__: An array of classNames (strings) to be added to the button
195
+ * __attrs__: A set of key-value pairs to add to the button as custom attributes to the button element.
196
+
197
+ Example of overriding buttons (here, the goal is to mimic medium by having <kbd>H1</kbd> and <kbd>H2</kbd> buttons which actually produce `<h2>` and `<h3>` tags respectively):
198
+ ```javascript
199
+ var editor = new MediumEditor('.editable', {
200
+ toolbar: {
201
+ buttons: [
202
+ 'bold',
203
+ 'italic',
204
+ {
205
+ name: 'h1',
206
+ action: 'append-h2',
207
+ aria: 'header type 1',
208
+ tagNames: ['h2'],
209
+ contentDefault: '<b>H1</b>',
210
+ classList: ['custom-class-h1'],
211
+ attrs: {
212
+ 'data-custom-attr': 'attr-value-h1'
213
+ }
214
+ },
215
+ {
216
+ name: 'h2',
217
+ action: 'append-h3',
218
+ aria: 'header type 2',
219
+ tagNames: ['h3'],
220
+ contentDefault: '<b>H2</b>',
221
+ classList: ['custom-class-h2'],
222
+ attrs: {
223
+ 'data-custom-attr': 'attr-value-h2'
224
+ }
225
+ },
226
+ 'justifyCenter',
227
+ 'quote',
228
+ 'anchor'
229
+ ]
230
+ }
231
+ });
232
+ ```
233
+
234
+ ### Anchor Preview options
235
+
236
+ The anchor preview is a built-in extension which automatically displays a 'tooltip' when the user is hovering over a link in the editor. The tooltip will display the `href` of the link, and when click, will open the anchor editing form in the toolbar.
237
+
238
+ Options for the anchor preview 'tooltip' are passed as an object that is a member of the outer options object. Example:
239
+ ```javascript
240
+ var editor = new MediumEditor('.editable', {
241
+ anchorPreview: {
242
+ /* These are the default options for anchor preview,
243
+ if nothing is passed this is what it used */
244
+ hideDelay: 500,
245
+ previewValueSelector: 'a'
246
+ }
247
+ }
248
+ });
249
+ ```
250
+
251
+ * __hideDelay__: time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag. Default: `500`
252
+ * __previewValueSelector__: the default selector to locate where to put the activeAnchor value in the preview. You should only need to override this if you've modified the way in which the anchor-preview extension renders. Default: `'a'`
253
+ * __showWhenToolbarIsVisible__: determines whether the anchor tag preview shows up when the toolbar is visible. You should set this value to true if the static option for the toolbar is true and you want the preview to show at the same time. Default: `false`
254
+ * __showOnEmptyLinks__: determines whether the anchor tag preview shows up on link with href as '' or '#something'. You should set this value to false if you do not want the preview to show up in such use cases. Default: `true`
255
+
256
+ To disable the anchor preview, set the value of the `anchorPreview` option to `false`:
257
+ ```javascript
258
+ var editor = new MediumEditor('.editable', {
259
+ anchorPreview: false
260
+ });
261
+ ```
262
+ ##### NOTE:
263
+ * If the toolbar is disabled (via `toolbar: false` option or `data-disable-toolbar` attribute) the anchor-preview is automatically disabled.
264
+ * If the anchor editing form is not enabled, clicking on the anchor-preview will not allow the href of the link to be edited
265
+
266
+ ### Placeholder Options
267
+
268
+ The placeholder handler is a built-in extension which displays placeholder text when the editor is empty.
269
+
270
+ Options for placeholder are passed as an object that is a member of the outer options object. Example:
271
+ ```javascript
272
+ var editor = new MediumEditor('.editable', {
273
+ placeholder: {
274
+ /* This example includes the default options for placeholder,
275
+ if nothing is passed this is what it used */
276
+ text: 'Type your text',
277
+ hideOnClick: true
278
+ }
279
+ });
280
+ ```
281
+
282
+ * __text__: Defines the default placeholder for empty contenteditables when __placeholder__ is not set to false. You can overwrite it by setting a `data-placeholder` attribute on the editor elements. Default: `'Type your text'`
283
+
284
+ * __hideOnClick__: Causes the placeholder to disappear as soon as the field gains focus. Default: `true`.
285
+ To hide the placeholder only after starting to type, and to show it again as soon as field is empty, set this option to `false`.
286
+
287
+
288
+ To disable the placeholder, set the value of the `placeholder` option to `false`:
289
+ ```javascript
290
+ var editor = new MediumEditor('.editable', {
291
+ placeholder: false
292
+ });
293
+ ```
294
+
295
+ ### Anchor Form options
296
+
297
+ The anchor form is a built-in button extension which allows the user to add/edit/remove links from within the editor. When 'anchor' is passed in as a button in the list of buttons, this extension will be enabled and can be triggered by clicking the corresponding button in the toolbar.
298
+
299
+ Options for the anchor form are passed as an object that is a member of the outer options object. Example:
300
+ ```javascript
301
+ var editor = new MediumEditor('.editable', {
302
+ toolbar: {
303
+ buttons: ['bold', 'italic', 'underline', 'anchor']
304
+ },
305
+ anchor: {
306
+ /* These are the default options for anchor form,
307
+ if nothing is passed this is what it used */
308
+ customClassOption: null,
309
+ customClassOptionText: 'Button',
310
+ linkValidation: false,
311
+ placeholderText: 'Paste or type a link',
312
+ targetCheckbox: false,
313
+ targetCheckboxText: 'Open in new window'
314
+ }
315
+ }
316
+ });
317
+ ```
318
+
319
+ * __customClassOption__: custom class name the user can optionally have added to their created links (ie 'button'). If passed as a non-empty string, a checkbox will be displayed allowing the user to choose whether to have the class added to the created link or not. Default: `null`
320
+ * __customClassOptionText__: text to be shown in the checkbox when the __customClassOption__ is being used. Default: `'Button'`
321
+ * __linkValidation__: enables/disables check for common URL protocols on anchor links. Converts invalid url characters (ie spaces) to valid characters using `encodeURI`. Default: `false`
322
+ * __placeholderText__: text to be shown as placeholder of the anchor input. Default: `'Paste or type a link'`
323
+ * __targetCheckbox__: enables/disables displaying a "Open in new window" checkbox, which when checked changes the `target` attribute of the created link. Default: `false`
324
+ * __targetCheckboxText__: text to be shown in the checkbox enabled via the __targetCheckbox__ option. Default: `'Open in new window'`
325
+
326
+ ### Paste Options
327
+
328
+ The paste handler is a built-in extension which attempts to filter the content when the user pastes. How the paste handler filters is configurable via specific options.
329
+
330
+ Options for paste handling are passed as an object that is a member of the outer options object. Example:
331
+ ```javascript
332
+ var editor = new MediumEditor('.editable', {
333
+ paste: {
334
+ /* This example includes the default options for paste,
335
+ if nothing is passed this is what it used */
336
+ forcePlainText: true,
337
+ cleanPastedHTML: false,
338
+ cleanReplacements: [],
339
+ cleanAttrs: ['class', 'style', 'dir'],
340
+ cleanTags: ['meta']
341
+ }
342
+ });
343
+ ```
344
+
345
+ * __forcePlainText__: Forces pasting as plain text. Default: `true`
346
+ * __cleanPastedHTML__: cleans pasted content from different sources, like google docs etc. Default: `false`
347
+ * __preCleanReplacements__: custom pairs (2 element arrays) of RegExp and replacement text to use during paste when __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. These replacements are executed _before_ builtin replacements. Default: `[]`
348
+ * __cleanReplacements__: custom pairs (2 element arrays) of RegExp and replacement text to use during paste when __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. These replacements are executed _after_ builtin replacements. Default: `[]`
349
+ * __cleanAttrs__: list of element attributes to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods. Default: `['class', 'style', 'dir']`
350
+ * __cleanTags__: list of element tag names to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods. Default: `['meta']`
351
+
352
+ ### KeyboardCommands Options
353
+
354
+ The keyboard commands handler is a built-in extension for mapping key-combinations to actions to execute in the editor.
355
+
356
+ Options for KeyboardCommands are passed as an object that is a member of the outer options object. Example:
357
+ ```javascript
358
+ var editor = new MediumEditor('.editable', {
359
+ keyboardCommands: {
360
+ /* This example includes the default options for keyboardCommands,
361
+ if nothing is passed this is what it used */
362
+ commands: [
363
+ {
364
+ command: 'bold',
365
+ key: 'B',
366
+ meta: true,
367
+ shift: false,
368
+ alt: false
369
+ },
370
+ {
371
+ command: 'italic',
372
+ key: 'I',
373
+ meta: true,
374
+ shift: false,
375
+ alt: false
376
+ },
377
+ {
378
+ command: 'underline',
379
+ key: 'U',
380
+ meta: true,
381
+ shift: false,
382
+ alt: false
383
+ }
384
+ ],
385
+ }
386
+ });
387
+ ```
388
+
389
+ * __commands__: Array of objects describing each command and the combination of keys that will trigger it. Required for each object:
390
+ * _command_: argument passed to `editor.execAction()` when key-combination is used
391
+ * if defined as `false`, the shortcut will be disabled
392
+ * _key_: keyboard character that triggers this command
393
+ * _meta_: whether the ctrl/meta key has to be active or inactive
394
+ * _shift_: whether the shift key has to be active or inactive
395
+ * _alt_: whether the alt key has to be active or inactive
396
+
397
+ To disable the keyboard commands, set the value of the `keyboardCommands` option to `false`:
398
+ ```javascript
399
+ var editor = new MediumEditor('.editable', {
400
+ keyboardCommands: false
401
+ });
402
+ ```
403
+
404
+ ### Auto Link Options
405
+
406
+ The auto-link handler is a built-in extension which automatically turns URLs entered into the text field into HTML anchor tags (similar to the functionality of Markdown). This feature is OFF by default.
407
+
408
+ To enable built-in auto-link support, set the value of the `autoLink` option to `true`:
409
+
410
+ ```javascript
411
+ var editor = new MediumEditor('.editable', {
412
+ autoLink: true
413
+ });
414
+ ```
415
+
416
+ ### Image Dragging Options
417
+
418
+ The image dragging handler is a built-in extension for handling dragging & dropping images into the contenteditable. This feature is ON by default.
419
+
420
+ To disable built-in image dragging, set the value of the `imageDragging` option to `false`:
421
+ ```javascript
422
+ var editor = new MediumEditor('.editable', {
423
+ imageDragging: false
424
+ });
425
+ ```
426
+
427
+ #### Disable File Dragging
428
+ To stop preventing drag & drop events and disable file dragging in general, provide a dummy ImageDragging extension.
429
+ ```javascript
430
+ var editor = new MediumEditor('.editor', {
431
+ extensions: {
432
+ 'imageDragging': {}
433
+ }
434
+ });
435
+ ```
436
+ Due to the [state of code](https://github.com/yabwe/medium-editor/issues/966) in 5.0.0, the editor *ALWAYS* prevented any drag and drop actions.
437
+ We will have a better way to disable file dragging in 6.*
438
+
439
+ ### Options Example:
440
+
441
+ ```javascript
442
+ var editor = new MediumEditor('.editable', {
443
+ delay: 1000,
444
+ targetBlank: true,
445
+ toolbar: {
446
+ buttons: ['bold', 'italic', 'quote'],
447
+ diffLeft: 25,
448
+ diffTop: 10,
449
+ },
450
+ anchor: {
451
+ placeholderText: 'Type a link',
452
+ customClassOption: 'btn',
453
+ customClassOptionText: 'Create Button'
454
+ },
455
+ paste: {
456
+ cleanPastedHTML: true,
457
+ cleanAttrs: ['style', 'dir'],
458
+ cleanTags: ['label', 'meta']
459
+ },
460
+ anchorPreview: {
461
+ hideDelay: 300
462
+ },
463
+ placeholder: {
464
+ text: 'Click to edit'
465
+ }
466
+ });
467
+ ```
468
+
469
+ ## Buttons
470
+
471
+ By default, MediumEditor supports buttons for most of the commands for `document.execCommand()` that are well-supported across all its supported browsers.
472
+
473
+ ### Default buttons.
474
+
475
+ MediumEditor, by default, will show only the buttons listed here to avoid a huge toolbar:
476
+
477
+ * __bold__
478
+ * __italic__
479
+ * __underline__
480
+ * __anchor__ _(built-in support for collecting a url via the anchor extension)_
481
+ * __h2__
482
+ * __h3__
483
+ * __quote__
484
+
485
+ ### All buttons.
486
+
487
+ These are all the built-in buttons supported by MediumEditor.
488
+
489
+ * __bold__
490
+ * __italic__
491
+ * __underline__
492
+ * __strikethrough__
493
+ * __subscript__
494
+ * __superscript__
495
+ * __anchor__
496
+ * __image__ (this simply converts selected text to an image tag)
497
+ * __quote__
498
+ * __pre__
499
+ * __orderedlist__
500
+ * __unorderedlist__
501
+ * __indent__ (moves the selected text up one level)
502
+ * __outdent__ (moves the selected text down one level)
503
+ * __justifyLeft__
504
+ * __justifyCenter__
505
+ * __justifyRight__
506
+ * __justifyFull__
507
+ * __h1__
508
+ * __h2__
509
+ * __h3__
510
+ * __h4__
511
+ * __h5__
512
+ * __h6__
513
+ * __removeFormat__ (clears inline style formatting, preserves blocks)
514
+
515
+ ## Themes
516
+
517
+ Check out the Wiki page for a list of available themes: [https://github.com/yabwe/medium-editor/wiki/Themes](https://github.com/yabwe/medium-editor/wiki/Themes)
518
+
519
+ ## API
520
+
521
+ View the [MediumEditor Object API documentation](API.md) on the Wiki for details on all the methods supported on the MediumEditor object.
522
+
523
+ ### Initialization methods
524
+ * __MediumEditor(elements, options)__: Creates an instance of MediumEditor
525
+ * __.destroy()__: tears down the editor if already setup, removing all DOM elements and event handlers
526
+ * __.setup()__: rebuilds the editor if it has already been destroyed, recreating DOM elements and attaching event handlers
527
+ * __.addElements()__: add elements to an already initialized instance of MediumEditor
528
+ * __.removeElements()__: remove elements from an already initialized instance of MediumEditor
529
+
530
+ ### Event Methods
531
+ * __.on(target, event, listener, useCapture)__: attach a listener to a DOM event which will be detached when MediumEditor is deactivated
532
+ * __.off(target, event, listener, useCapture)__: detach a listener to a DOM event that was attached via `on()`
533
+ * __.subscribe(event, listener)__: attaches a listener to a custom medium-editor event
534
+ * __.unsubscribe(event, listener)__: detaches a listener from a custom medium-editor event
535
+ * __.trigger(name, data, editable)__: manually triggers a custom medium-editor event
536
+
537
+ ### Selection Methods
538
+ * __.checkSelection()__: manually trigger an update of the toolbar and extensions based on the current selection
539
+ * __.exportSelection()__: return a data representation of the selected text, which can be applied via `importSelection()`
540
+ * __.importSelection(selectionState)__: restore the selection using a data representation of previously selected text (ie value returned by `exportSelection()`)
541
+ * __.getFocusedElement()__: returns an element if any contenteditable element monitored by MediumEditor currently has focused
542
+ * __.getSelectedParentElement(range)__: get the parent contenteditable element that contains the current selection
543
+ * __.restoreSelection()__: restore the selection to what was selected when `saveSelection()` was called
544
+ * __.saveSelection()__: internally store the set of selected text
545
+ * __.selectAllContents()__: expands the selection to contain all text within the focused contenteditable
546
+ * __.selectElement(element)__: change selection to be a specific element and update the toolbar to reflect the selection
547
+ * __.stopSelectionUpdates()__: stop the toolbar from updating to reflect the state of the selected text
548
+ * __.startSelectionUpdates()__: put the toolbar back into its normal updating state
549
+
550
+ ### Editor Action Methods
551
+ * __.cleanPaste(text)__: convert text to plaintext and replace current selection with result
552
+ * __.createLink(opts)__: creates a link via the native `document.execCommand('createLink')` command
553
+ * __.execAction(action, opts)__: executes an built-in action via `document.execCommand`
554
+ * __.pasteHTML(html, options)__: replace the current selection with html
555
+ * __.queryCommandState(action)__: wrapper around the browser's built in `document.queryCommandState(action)` for checking whether a specific action has already been applied to the selection.
556
+
557
+ ### Helper Methods
558
+ * __.delay(fn)__: delay any function from being executed by the amount of time passed as the `delay` option
559
+ * __.getContent(index)__: gets the trimmed `innerHTML` of the element at `index`
560
+ * __.getExtensionByName(name)__: get a reference to an extension with the specified name
561
+ * __.resetContent(element)__: reset the content of all elements or a specific element to its value when added to the editor initially
562
+ * __.serialize()__: returns a JSON object with elements contents
563
+ * __.setContent(html, index)__: sets the `innerHTML` to `html` of the element at `index`
564
+
565
+ ### Static Methods/Properties
566
+ * __.getEditorFromElement(element)__: retrieve the instance of MediumEditor that is monitoring the provided editor element
567
+ * __.version__: the version information for the MediumEditor library
568
+
569
+ ## Dynamically add/remove elements to your instance
570
+
571
+ It is possible to dynamically add new elements to your existing MediumEditor instance:
572
+
573
+ ```javascript
574
+ var editor = new MediumEditor('.editable');
575
+ editor.subscribe('editableInput', this._handleEditableInput.bind(this));
576
+
577
+ // imagine an ajax fetch/any other dynamic functionality which will add new '.editable' elements to the DOM
578
+
579
+ editor.addElements('.editable');
580
+ // OR editor.addElements(document.getElementsByClassName('editable'));
581
+ // OR editor.addElements(document.querySelectorAll('.editable'));
582
+ ```
583
+
584
+ Passing an elements or array of elements to `addElements(elements)` will:
585
+ * Add the given element or array of elements to the internal `this.elements` array.
586
+ * Ensure the element(s) are initialized with the proper attributes and event handlers as if the element had been passed during instantiation of the editor.
587
+ * For any `<textarea>` elements:
588
+ * Hide the `<textarea>`
589
+ * Create a new `<div contenteditable=true>` element and add it to the elements array.
590
+ * Ensure the 2 elements remain sync'd.
591
+ * Be intelligent enough to run the necessary code only once per element, no matter how often you will call it.
592
+
593
+ ### Removing elements dynamically
594
+
595
+ Straight forward, just call `removeElements` with the element or array of elements you to want to tear down. Each element itself will remain a contenteditable - it will just remove all event handlers and all references to it so you can safely remove it from DOM.
596
+
597
+ ```javascript
598
+ editor.removeElements(document.querySelector('#myElement'));
599
+ // OR editor.removeElements(document.getElementById('myElement'));
600
+ // OR editor.removeElements('#myElement');
601
+
602
+ // in case you have jQuery and don't exactly know when an element was removed, for example after routing state change
603
+ var removedElements = [];
604
+ editor.elements.forEach(function (element) {
605
+ // check if the element is still available in current DOM
606
+ if (!$(element).parents('body').length) {
607
+ removedElements.push(element);
608
+ }
609
+ });
610
+
611
+ editor.removeElements(removedElements);
612
+ ```
613
+
614
+ ## Capturing DOM changes
615
+
616
+ For observing any changes on contentEditable, use the custom `'editableInput'` event exposed via the `subscribe()` method:
617
+
618
+ ```js
619
+ var editor = new MediumEditor('.editable');
620
+ editor.subscribe('editableInput', function (event, editable) {
621
+ // Do some work
622
+ });
623
+ ```
624
+
625
+ This event is supported in all browsers supported by MediumEditor (including IE9+ and Edge)! To help with cases when one instance of MediumEditor is monitoring multiple elements, the 2nd argument passed to the event handler (`editable` in the example above) will be a reference to the contenteditable element that has actually changed.
626
+
627
+ This is handy when you need to capture any modifications to the contenteditable element including:
628
+ * Typing
629
+ * Cutting/Pasting
630
+ * Changes from clicking on buttons in the toolbar
631
+ * Undo/Redo
632
+
633
+ Why is this interesting and why should you use this event instead of just attaching to the `input` event on the contenteditable element?
634
+
635
+ So for most modern browsers (Chrome, Firefox, Safari, etc.), the `input` event works just fine. In fact, `editableInput` is just a proxy for the `input` event in those browsers. However, the `input` event [is not supported for contenteditable elements in IE 9-11](https://connect.microsoft.com/IE/feedback/details/794285/ie10-11-input-event-does-not-fire-on-div-with-contenteditable-set) and is _mostly_ supported in Microsoft Edge, but not fully.
636
+
637
+ So, to properly support the `editableInput` event in Internet Explorer and Microsoft Edge, MediumEditor uses a combination of the `selectionchange` and `keypress` events, as well as monitoring calls to `document.execCommand`.
638
+
639
+ ## Extensions & Plugins
640
+
641
+ Check the [documentation](src/js/extensions) in order to learn how to develop extensions for MediumEditor.
642
+
643
+ A list of existing extensions and plugins, such as [Images and Media embeds](http://orthes.github.io/medium-editor-insert-plugin/), [Tables](https://github.com/yabwe/medium-editor-tables) and [Markdown](https://github.com/IonicaBizau/medium-editor-markdown) can be found [here](https://github.com/yabwe/medium-editor/wiki/Extensions-Plugins).
644
+
645
+ ## Development
646
+
647
+ MediumEditor development tasks are managed by Grunt. To install all the necessary packages, just invoke:
648
+
649
+ ```bash
650
+ npm install
651
+ ```
652
+
653
+ To run all the test and build the dist files for testing on demo pages, just invoke:
654
+ ```bash
655
+ grunt
656
+ ```
657
+
658
+ These are the other available grunt tasks:
659
+
660
+ * __js__: runs jslint and jasmine tests and creates minified and concatenated versions of the script;
661
+ * __css__: runs autoprefixer and csslint
662
+ * __test__: runs jasmine tests, jslint and csslint
663
+ * __watch__: watch for modifications on script/scss files
664
+ * __spec__: runs a task against a specified file
665
+
666
+ The source files are located inside the __src__ directory. Be sure to make changes to these files and not files in the dist directory.
667
+
668
+ ## Contributing
669
+
670
+ [Kill some bugs :)](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
671
+
672
+ 1. Fork it
673
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
674
+ 3. Test your changes to the best of your ability.
675
+ 4. Update the documentation to reflect your changes if they add or changes current functionality.
676
+ 5. Commit your changes (`git commit -am 'Added some feature'`) **without files from the _dist_ directory**.
677
+ 6. Push to the branch (`git push origin my-new-feature`)
678
+ 7. Create new Pull Request
679
+
680
+ ### Code Consistency
681
+
682
+ To help create consistent looking code throughout the project, we use a few tools to help us. They have plugins for most popular editors/IDEs to make coding for our project, but you should use them in your project as well!
683
+
684
+ #### JSHint
685
+
686
+ We use [JSHint](http://jshint.com/) on each build to find easy-to-catch errors and potential problems in our js. You can find our JSHint settings in the `.jshintrc` file in the root of the project.
687
+
688
+ #### jscs
689
+
690
+ We use [jscs](http://jscs.info/) on each build to enforce some code style rules we have for our project. You can find our jscs settings in the `.jscsrc` file in the root of the project.
691
+
692
+ #### EditorConfig
693
+
694
+ We use [EditorConfig](http://EditorConfig.org) to maintain consistent coding styles between various editors and IDEs. You can find our settings in the `.editorconfig` file in the root of the project.
695
+
696
+ ### Easy First Bugs
697
+
698
+ Looking for something simple for a first contribution? Try fixing an [easy first bug](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3A%22easy+first+bug%22)!
699
+
700
+ ## Contributors (100+ and counting!)
701
+
702
+ [https://github.com/yabwe/medium-editor/graphs/contributors](https://github.com/yabwe/medium-editor/graphs/contributors)
703
+
704
+ ## Is Your Org Using MediumEditor?
705
+
706
+ Add your org [here](https://github.com/yabwe/medium-editor/issues/828) and we can add you to our [landing page](https://yabwe.github.io/medium-editor/#who-is-using-it)!
707
+
708
+ ## License
709
+
710
+ MIT: https://github.com/yabwe/medium-editor/blob/master/LICENSE
vendor/medium-editor/css/medium-editor.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .medium-editor-anchor-preview,.medium-editor-toolbar{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;z-index:2000}@-webkit-keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}100%{-webkit-transform:matrix(1,0,0,1,0,0);transform:matrix(1,0,0,1,0,0)}}@keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}100%{-webkit-transform:matrix(1,0,0,1,0,0);transform:matrix(1,0,0,1,0,0)}}.medium-editor-anchor-preview{left:0;line-height:1.4;max-width:280px;position:absolute;text-align:center;top:0;word-break:break-all;word-wrap:break-word;visibility:hidden}.medium-editor-anchor-preview a{color:#fff;display:inline-block;margin:5px 5px 10px}.medium-editor-placeholder-relative:after,.medium-editor-placeholder:after{content:attr(data-placeholder)!important;white-space:pre;padding:inherit;margin:inherit;font-style:italic}.medium-editor-anchor-preview-active{visibility:visible}.medium-editor-dragover{background:#ddd}.medium-editor-image-loading{-webkit-animation:medium-editor-image-loading 1s infinite ease-in-out;animation:medium-editor-image-loading 1s infinite ease-in-out;background-color:#333;border-radius:100%;display:inline-block;height:40px;width:40px}.medium-editor-placeholder{position:relative}.medium-editor-placeholder:after{position:absolute;left:0;top:0}.medium-editor-placeholder-relative,.medium-editor-placeholder-relative:after{position:relative}.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{border-style:solid;content:'';display:block;height:0;left:50%;margin-left:-8px;position:absolute;width:0}.medium-toolbar-arrow-under:after{border-width:8px 8px 0}.medium-toolbar-arrow-over:before{border-width:0 8px 8px;top:-8px}.medium-editor-toolbar{left:0;position:absolute;top:0;visibility:hidden}.medium-editor-toolbar ul{margin:0;padding:0}.medium-editor-toolbar li{float:left;list-style:none;margin:0;padding:0}.medium-editor-toolbar li button{box-sizing:border-box;cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}.medium-editor-toolbar li button:focus{outline:0}.medium-editor-toolbar li .medium-editor-action-underline{text-decoration:underline}.medium-editor-toolbar li .medium-editor-action-pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;font-weight:100;padding:15px 0}.medium-editor-toolbar-active{visibility:visible}.medium-editor-sticky-toolbar{position:fixed;top:1px}.medium-editor-relative-toolbar{position:relative}.medium-editor-toolbar-active.medium-editor-stalker-toolbar{-webkit-animation:medium-editor-pop-upwards 160ms forwards linear;animation:medium-editor-pop-upwards 160ms forwards linear}.medium-editor-action-bold{font-weight:bolder}.medium-editor-action-italic{font-style:italic}.medium-editor-toolbar-form{display:none}.medium-editor-toolbar-form a,.medium-editor-toolbar-form input{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.medium-editor-toolbar-form .medium-editor-toolbar-form-row{line-height:14px;margin-left:5px;padding-bottom:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input,.medium-editor-toolbar-form label{border:none;box-sizing:border-box;font-size:14px;margin:0;padding:6px;width:316px;display:inline-block}.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,.medium-editor-toolbar-form label:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-shadow:none;outline:0}.medium-editor-toolbar-form a{display:inline-block;font-size:24px;font-weight:bolder;margin:0 10px;text-decoration:none}.medium-editor-toolbar-form-active{display:block}.medium-editor-toolbar-actions:after{clear:both;content:"";display:table}.medium-editor-element{word-wrap:break-word;min-height:30px}.medium-editor-element img{max-width:100%}.medium-editor-element sub{vertical-align:sub}.medium-editor-element sup{vertical-align:super}.medium-editor-hidden{display:none}
vendor/medium-editor/css/themes/beagle.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .medium-toolbar-arrow-under:after{border-color:#000 transparent transparent;top:40px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #000}.medium-editor-toolbar{background-color:#000;border:none;border-radius:50px}.medium-editor-toolbar li button{background-color:transparent;border:none;box-sizing:border-box;color:#ccc;height:40px;min-width:40px;padding:5px 12px;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li .medium-editor-button-active,.medium-editor-toolbar li button:hover{background-color:#000;color:#a2d7c7}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:24px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:50px;border-right:none;border-top-right-radius:50px;padding-right:24px}.medium-editor-toolbar-form{background:#000;border-radius:50px;color:#ccc;overflow:hidden}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#000;box-sizing:border-box;color:#ccc;height:40px;padding-left:16px;width:220px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form a{color:#ccc;-webkit-transform:translateY(2px);transform:translateY(2px)}.medium-editor-toolbar-form .medium-editor-toolbar-close{margin-right:16px}.medium-editor-toolbar-anchor-preview{background:#000;border-radius:50px;padding:5px 12px}.medium-editor-anchor-preview a{color:#ccc;text-decoration:none}
vendor/medium-editor/js/medium-editor.min.js ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ "classList"in document.createElement("_")||!function(a){"use strict";if("Element"in a){var b="classList",c="prototype",d=a.Element[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(""===b)throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){for(var b=f.call(a.getAttribute("class")||""),c=b?b.split(/\s+/):[],d=0,e=c.length;e>d;d++)this.push(c[d]);this._updateClassName=function(){a.setAttribute("class",this.toString())}},k=j[c]=[],l=function(){return new j(this)};if(h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",-1!==i(this,a)},k.add=function(){var a,b=arguments,c=0,d=b.length,e=!1;do a=b[c]+"",-1===i(this,a)&&(this.push(a),e=!0);while(++c<d);e&&this._updateClassName()},k.remove=function(){var a,b,c=arguments,d=0,e=c.length,f=!1;do for(a=c[d]+"",b=i(this,a);-1!==b;)this.splice(b,1),f=!0,b=i(this,a);while(++d<e);f&&this._updateClassName()},k.toggle=function(a,b){a+="";var c=this.contains(a),d=c?b!==!0&&"remove":b!==!1&&"add";return d&&this[d](a),b===!0||b===!1?b:!c},k.toString=function(){return this.join(" ")},e.defineProperty){var m={get:l,enumerable:!0,configurable:!0};try{e.defineProperty(d,b,m)}catch(n){-2146823252===n.number&&(m.enumerable=!1,e.defineProperty(d,b,m))}}else e[c].__defineGetter__&&d.__defineGetter__(b,l)}}(self),function(a){"use strict";if(a.URL=a.URL||a.webkitURL,a.Blob&&a.URL)try{return void new Blob}catch(b){}var c=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||function(a){var b=function(a){return Object.prototype.toString.call(a).match(/^\[object\s(.*)\]$/)[1]},c=function(){this.data=[]},d=function(a,b,c){this.data=a,this.size=a.length,this.type=b,this.encoding=c},e=c.prototype,f=d.prototype,g=a.FileReaderSync,h=function(a){this.code=this[this.name=a]},i="NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR".split(" "),j=i.length,k=a.URL||a.webkitURL||a,l=k.createObjectURL,m=k.revokeObjectURL,n=k,o=a.btoa,p=a.atob,q=a.ArrayBuffer,r=a.Uint8Array,s=/^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;for(d.fake=f.fake=!0;j--;)h.prototype[i[j]]=j+1;return k.createObjectURL||(n=a.URL=function(a){var b,c=document.createElementNS("http://www.w3.org/1999/xhtml","a");return c.href=a,"origin"in c||("data:"===c.protocol.toLowerCase()?c.origin=null:(b=a.match(s),c.origin=b&&b[1])),c}),n.createObjectURL=function(a){var b,c=a.type;return null===c&&(c="application/octet-stream"),a instanceof d?(b="data:"+c,"base64"===a.encoding?b+";base64,"+a.data:"URI"===a.encoding?b+","+decodeURIComponent(a.data):o?b+";base64,"+o(a.data):b+","+encodeURIComponent(a.data)):l?l.call(k,a):void 0},n.revokeObjectURL=function(a){"data:"!==a.substring(0,5)&&m&&m.call(k,a)},e.append=function(a){var c=this.data;if(r&&(a instanceof q||a instanceof r)){for(var e="",f=new r(a),i=0,j=f.length;j>i;i++)e+=String.fromCharCode(f[i]);c.push(e)}else if("Blob"===b(a)||"File"===b(a)){if(!g)throw new h("NOT_READABLE_ERR");var k=new g;c.push(k.readAsBinaryString(a))}else a instanceof d?"base64"===a.encoding&&p?c.push(p(a.data)):"URI"===a.encoding?c.push(decodeURIComponent(a.data)):"raw"===a.encoding&&c.push(a.data):("string"!=typeof a&&(a+=""),c.push(unescape(encodeURIComponent(a))))},e.getBlob=function(a){return arguments.length||(a=null),new d(this.data.join(""),a,"raw")},e.toString=function(){return"[object BlobBuilder]"},f.slice=function(a,b,c){var e=arguments.length;return 3>e&&(c=null),new d(this.data.slice(a,e>1?b:this.data.length),c,this.encoding)},f.toString=function(){return"[object Blob]"},f.close=function(){this.size=0,delete this.data},c}(a);a.Blob=function(a,b){var d=b?b.type||"":"",e=new c;if(a)for(var f=0,g=a.length;g>f;f++)Uint8Array&&a[f]instanceof Uint8Array?e.append(a[f].buffer):e.append(a[f]);var h=e.getBlob(d);return!h.slice&&h.webkitSlice&&(h.slice=h.webkitSlice),h};var d=Object.getPrototypeOf||function(a){return a.__proto__};a.Blob.prototype=d(new a.Blob)}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content||this),function(a,b){"use strict";var c="object"==typeof module&&process&&process.versions&&process.versions.electron;c||"object"!=typeof module?"function"==typeof define&&define.amd?define(function(){return b}):a.MediumEditor=b:module.exports=b}(this,function(){"use strict";function a(a,b){return this.init(a,b)}return a.extensions={},function(b){function c(a,b){var c,d=Array.prototype.slice.call(arguments,2);b=b||{};for(var e=0;e<d.length;e++){var f=d[e];if(f)for(c in f)f.hasOwnProperty(c)&&"undefined"!=typeof f[c]&&(a||b.hasOwnProperty(c)===!1)&&(b[c]=f[c])}return b}var d=!1;try{var e=document.createElement("div"),f=document.createTextNode(" ");e.appendChild(f),d=e.contains(f)}catch(g){}var h={isIE:"Microsoft Internet Explorer"===navigator.appName||"Netscape"===navigator.appName&&null!==new RegExp("Trident/.*rv:([0-9]{1,}[.0-9]{0,})").exec(navigator.userAgent),isEdge:null!==/Edge\/\d+/.exec(navigator.userAgent),isFF:navigator.userAgent.toLowerCase().indexOf("firefox")>-1,isMac:b.navigator.platform.toUpperCase().indexOf("MAC")>=0,keyCode:{BACKSPACE:8,TAB:9,ENTER:13,ESCAPE:27,SPACE:32,DELETE:46,K:75,M:77,V:86},isMetaCtrlKey:function(a){return!!(h.isMac&&a.metaKey||!h.isMac&&a.ctrlKey)},isKey:function(a,b){var c=h.getKeyCode(a);return!1===Array.isArray(b)?c===b:-1!==b.indexOf(c)},getKeyCode:function(a){var b=a.which;return null===b&&(b=null!==a.charCode?a.charCode:a.keyCode),b},blockContainerElementNames:["p","h1","h2","h3","h4","h5","h6","blockquote","pre","ul","li","ol","address","article","aside","audio","canvas","dd","dl","dt","fieldset","figcaption","figure","footer","form","header","hgroup","main","nav","noscript","output","section","video","table","thead","tbody","tfoot","tr","th","td"],emptyElementNames:["br","col","colgroup","hr","img","input","source","wbr"],extend:function(){var a=[!0].concat(Array.prototype.slice.call(arguments));return c.apply(this,a)},defaults:function(){var a=[!1].concat(Array.prototype.slice.call(arguments));return c.apply(this,a)},createLink:function(a,b,c,d){var e=a.createElement("a");return h.moveTextRangeIntoElement(b[0],b[b.length-1],e),e.setAttribute("href",c),d&&e.setAttribute("target",d),e},findOrCreateMatchingTextNodes:function(a,b,c){for(var d=a.createTreeWalker(b,NodeFilter.SHOW_ALL,null,!1),e=[],f=0,g=!1,i=null,j=null;null!==(i=d.nextNode());)if(!(i.nodeType>3))if(3===i.nodeType){if(!g&&c.start<f+i.nodeValue.length&&(g=!0,j=h.splitStartNodeIfNeeded(i,c.start,f)),g&&h.splitEndNodeIfNeeded(i,j,c.end,f),g&&f===c.end)break;if(g&&f>c.end+1)throw new Error("PerformLinking overshot the target!");g&&e.push(j||i),f+=i.nodeValue.length,null!==j&&(f+=j.nodeValue.length,d.nextNode()),j=null}else"img"===i.tagName.toLowerCase()&&(!g&&c.start<=f&&(g=!0),g&&e.push(i));return e},splitStartNodeIfNeeded:function(a,b,c){return b!==c?a.splitText(b-c):null},splitEndNodeIfNeeded:function(a,b,c,d){var e,f;e=d+(b||a).nodeValue.length+(b?a.nodeValue.length:0)-1,f=(b||a).nodeValue.length-(e+1-c),e>=c&&d!==e&&0!==f&&(b||a).splitText(f)},splitByBlockElements:function(b){if(3!==b.nodeType&&1!==b.nodeType)return[];var c=[],d=a.util.blockContainerElementNames.join(",");if(3===b.nodeType||0===b.querySelectorAll(d).length)return[b];for(var e=0;e<b.childNodes.length;e++){var f=b.childNodes[e];if(3===f.nodeType)c.push(f);else if(1===f.nodeType){var g=f.querySelectorAll(d);0===g.length?c.push(f):c=c.concat(a.util.splitByBlockElements(f))}}return c},findAdjacentTextNodeWithContent:function(a,b,c){var d,e=!1,f=c.createNodeIterator(a,NodeFilter.SHOW_TEXT,null,!1);for(d=f.nextNode();d;){if(d===b)e=!0;else if(e&&3===d.nodeType&&d.nodeValue&&d.nodeValue.trim().length>0)break;d=f.nextNode()}return d},findPreviousSibling:function(a){if(!a||h.isMediumEditorElement(a))return!1;for(var b=a.previousSibling;!b&&!h.isMediumEditorElement(a.parentNode);)a=a.parentNode,b=a.previousSibling;return b},isDescendant:function(a,b,c){if(!a||!b)return!1;if(a===b)return!!c;if(1!==a.nodeType)return!1;if(d||3!==b.nodeType)return a.contains(b);for(var e=b.parentNode;null!==e;){if(e===a)return!0;e=e.parentNode}return!1},isElement:function(a){return!(!a||1!==a.nodeType)},throttle:function(a,b){var c,d,e,f=50,g=null,h=0,i=function(){h=Date.now(),g=null,e=a.apply(c,d),g||(c=d=null)};return b||0===b||(b=f),function(){var f=Date.now(),j=b-(f-h);return c=this,d=arguments,0>=j||j>b?(g&&(clearTimeout(g),g=null),h=f,e=a.apply(c,d),g||(c=d=null)):g||(g=setTimeout(i,j)),e}},traverseUp:function(a,b){if(!a)return!1;do{if(1===a.nodeType){if(b(a))return a;if(h.isMediumEditorElement(a))return!1}a=a.parentNode}while(a);return!1},htmlEntities:function(a){return String(a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},insertHTMLCommand:function(b,c){var d,e,f,g,i,j,k,l=!1,m=["insertHTML",!1,c];if(!a.util.isEdge&&b.queryCommandSupported("insertHTML"))try{return b.execCommand.apply(b,m)}catch(n){}if(d=b.getSelection(),d.rangeCount){if(e=d.getRangeAt(0),k=e.commonAncestorContainer,h.isMediumEditorElement(k)&&!k.firstChild)e.selectNode(k.appendChild(b.createTextNode("")));else if(3===k.nodeType&&0===e.startOffset&&e.endOffset===k.nodeValue.length||3!==k.nodeType&&k.innerHTML===e.toString()){for(;!h.isMediumEditorElement(k)&&k.parentNode&&1===k.parentNode.childNodes.length&&!h.isMediumEditorElement(k.parentNode);)k=k.parentNode;e.selectNode(k)}for(e.deleteContents(),f=b.createElement("div"),f.innerHTML=c,g=b.createDocumentFragment();f.firstChild;)i=f.firstChild,j=g.appendChild(i);e.insertNode(g),j&&(e=e.cloneRange(),e.setStartAfter(j),e.collapse(!0),a.selection.selectRange(b,e)),l=!0}return b.execCommand.callListeners&&b.execCommand.callListeners(m,l),l},execFormatBlock:function(b,c){var d,e=h.getTopBlockContainer(a.selection.getSelectionStart(b));if("blockquote"===c){if(e&&(d=Array.prototype.slice.call(e.childNodes),d.some(function(a){return h.isBlockContainer(a)})))return b.execCommand("outdent",!1,null);if(h.isIE)return b.execCommand("indent",!1,c)}if(e&&c===e.nodeName.toLowerCase()&&(c="p"),h.isIE&&(c="<"+c+">"),e&&"blockquote"===e.nodeName.toLowerCase()){if(h.isIE&&"<p>"===c)return b.execCommand("outdent",!1,c);if((h.isFF||h.isEdge)&&"p"===c)return d=Array.prototype.slice.call(e.childNodes),d.some(function(a){return!h.isBlockContainer(a)})&&b.execCommand("formatBlock",!1,c),b.execCommand("outdent",!1,c)}return b.execCommand("formatBlock",!1,c)},setTargetBlank:function(a,b){var c,d=b||!1;if("a"===a.nodeName.toLowerCase())a.target="_blank";else for(a=a.getElementsByTagName("a"),c=0;c<a.length;c+=1)!1!==d&&d!==a[c].attributes.href.value||(a[c].target="_blank")},removeTargetBlank:function(a,b){var c;if("a"===a.nodeName.toLowerCase())a.removeAttribute("target");else for(a=a.getElementsByTagName("a"),c=0;c<a.length;c+=1)b===a[c].attributes.href.value&&a[c].removeAttribute("target")},addClassToAnchors:function(a,b){var c,d,e=b.split(" ");if("a"===a.nodeName.toLowerCase())for(d=0;d<e.length;d+=1)a.classList.add(e[d]);else for(a=a.getElementsByTagName("a"),c=0;c<a.length;c+=1)for(d=0;d<e.length;d+=1)a[c].classList.add(e[d])},isListItem:function(a){if(!a)return!1;if("li"===a.nodeName.toLowerCase())return!0;for(var b=a.parentNode,c=b.nodeName.toLowerCase();"li"===c||!h.isBlockContainer(b)&&"div"!==c;){if("li"===c)return!0;if(b=b.parentNode,!b)return!1;c=b.nodeName.toLowerCase()}return!1},cleanListDOM:function(b,c){if("li"===c.nodeName.toLowerCase()){var d=c.parentElement;"p"===d.parentElement.nodeName.toLowerCase()&&(h.unwrap(d.parentElement,b),a.selection.moveCursor(b,c.firstChild,c.firstChild.textContent.length))}},splitOffDOMTree:function(a,b,c){for(var d=b,e=null,f=!c;d!==a;){var g,h=d.parentNode,i=h.cloneNode(!1),j=f?d:h.firstChild;for(e&&(f?i.appendChild(e):g=e),e=i;j;){var k=j.nextSibling;j===d?(j.hasChildNodes()?j=j.cloneNode(!1):j.parentNode.removeChild(j),j.textContent&&e.appendChild(j),j=f?k:null):(j.parentNode.removeChild(j),(j.hasChildNodes()||j.textContent)&&e.appendChild(j),j=k)}g&&e.appendChild(g),d=h}return e},moveTextRangeIntoElement:function(a,b,c){if(!a||!b)return!1;var d=h.findCommonRoot(a,b);if(!d)return!1;if(b===a){var e=a.parentNode,f=a.nextSibling;return e.removeChild(a),c.appendChild(a),f?e.insertBefore(c,f):e.appendChild(c),c.hasChildNodes()}for(var g,i,j,k=[],l=0;l<d.childNodes.length;l++)if(j=d.childNodes[l],g){if(h.isDescendant(j,b,!0)){i=j;break}k.push(j)}else h.isDescendant(j,a,!0)&&(g=j);var m=i.nextSibling,n=d.ownerDocument.createDocumentFragment();return g===a?(g.parentNode.removeChild(g),n.appendChild(g)):n.appendChild(h.splitOffDOMTree(g,a)),k.forEach(function(a){a.parentNode.removeChild(a),n.appendChild(a)}),i===b?(i.parentNode.removeChild(i),n.appendChild(i)):n.appendChild(h.splitOffDOMTree(i,b,!0)),c.appendChild(n),i.parentNode===d?d.insertBefore(c,i):m?d.insertBefore(c,m):d.appendChild(c),c.hasChildNodes()},depthOfNode:function(a){for(var b=0,c=a;null!==c.parentNode;)c=c.parentNode,b++;return b},findCommonRoot:function(a,b){for(var c=h.depthOfNode(a),d=h.depthOfNode(b),e=a,f=b;c!==d;)c>d?(e=e.parentNode,c-=1):(f=f.parentNode,d-=1);for(;e!==f;)e=e.parentNode,f=f.parentNode;return e},isElementAtBeginningOfBlock:function(a){for(var b,c;!h.isBlockContainer(a)&&!h.isMediumEditorElement(a);){for(c=a;c=c.previousSibling;)if(b=3===c.nodeType?c.nodeValue:c.textContent,b.length>0)return!1;a=a.parentNode}return!0},isMediumEditorElement:function(a){return a&&a.getAttribute&&!!a.getAttribute("data-medium-editor-element")},getContainerEditorElement:function(a){return h.traverseUp(a,function(a){return h.isMediumEditorElement(a)})},isBlockContainer:function(a){return a&&3!==a.nodeType&&-1!==h.blockContainerElementNames.indexOf(a.nodeName.toLowerCase())},getClosestBlockContainer:function(a){return h.traverseUp(a,function(a){return h.isBlockContainer(a)||h.isMediumEditorElement(a)})},getTopBlockContainer:function(a){var b=h.isBlockContainer(a)?a:!1;return h.traverseUp(a,function(a){return h.isBlockContainer(a)&&(b=a),!b&&h.isMediumEditorElement(a)?(b=a,!0):!1}),b},getFirstSelectableLeafNode:function(a){for(;a&&a.firstChild;)a=a.firstChild;if(a=h.traverseUp(a,function(a){return-1===h.emptyElementNames.indexOf(a.nodeName.toLowerCase())}),"table"===a.nodeName.toLowerCase()){var b=a.querySelector("th, td");b&&(a=b)}return a},getFirstTextNode:function(a){return h.warn("getFirstTextNode is deprecated and will be removed in version 6.0.0"),h._getFirstTextNode(a)},_getFirstTextNode:function(a){if(3===a.nodeType)return a;for(var b=0;b<a.childNodes.length;b++){var c=h._getFirstTextNode(a.childNodes[b]);if(null!==c)return c}return null},ensureUrlHasProtocol:function(a){return-1===a.indexOf("://")?"http://"+a:a},warn:function(){void 0!==b.console&&"function"==typeof b.console.warn&&b.console.warn.apply(b.console,arguments)},deprecated:function(a,b,c){var d=a+" is deprecated, please use "+b+" instead.";c&&(d+=" Will be removed in "+c),h.warn(d)},deprecatedMethod:function(a,b,c,d){h.deprecated(a,b,d),"function"==typeof this[b]&&this[b].apply(this,c)},cleanupAttrs:function(a,b){b.forEach(function(b){a.removeAttribute(b)})},cleanupTags:function(a,b){b.forEach(function(b){a.nodeName.toLowerCase()===b&&a.parentNode.removeChild(a)})},getClosestTag:function(a,b){return h.traverseUp(a,function(a){return a.nodeName.toLowerCase()===b.toLowerCase()})},unwrap:function(a,b){for(var c=b.createDocumentFragment(),d=Array.prototype.slice.call(a.childNodes),e=0;e<d.length;e++)c.appendChild(d[e]);c.childNodes.length?a.parentNode.replaceChild(c,a):a.parentNode.removeChild(a)},guid:function(){function a(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}};a.util=h}(window),function(){var b=function(b){a.util.extend(this,b)};b.extend=function(b){var c,d=this;c=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return d.apply(this,arguments)},a.util.extend(c,d);var e=function(){this.constructor=c};return e.prototype=d.prototype,c.prototype=new e,b&&a.util.extend(c.prototype,b),c},b.prototype={init:function(){},base:void 0,name:void 0,checkState:void 0,destroy:void 0,queryCommandState:void 0,isActive:void 0,isAlreadyApplied:void 0,setActive:void 0,setInactive:void 0,getInteractionElements:void 0,window:void 0,document:void 0,getEditorElements:function(){return this.base.elements},getEditorId:function(){return this.base.id},getEditorOption:function(a){return this.base.options[a]}},["execAction","on","off","subscribe","trigger"].forEach(function(a){b.prototype[a]=function(){return this.base[a].apply(this.base,arguments)}}),a.Extension=b}(),function(){function b(b){return a.util.isBlockContainer(b)?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}var c={findMatchingSelectionParent:function(b,c){var d,e,f=c.getSelection();return 0===f.rangeCount?!1:(d=f.getRangeAt(0),e=d.commonAncestorContainer,a.util.traverseUp(e,b))},getSelectionElement:function(b){return this.findMatchingSelectionParent(function(b){return a.util.isMediumEditorElement(b)},b)},exportSelection:function(a,b){if(!a)return null;var c=null,d=b.getSelection();if(d.rangeCount>0){var e,f=d.getRangeAt(0),g=f.cloneRange();g.selectNodeContents(a),g.setEnd(f.startContainer,f.startOffset),e=g.toString().length,c={start:e,end:e+f.toString().length},this.doesRangeStartWithImages(f,b)&&(c.startsWithImage=!0);var h=this.getTrailingImageCount(a,c,f.endContainer,f.endOffset);if(h&&(c.trailingImageCount=h),0!==e){var i=this.getIndexRelativeToAdjacentEmptyBlocks(b,a,f.startContainer,f.startOffset);-1!==i&&(c.emptyBlocksIndex=i)}}return c},importSelection:function(a,b,c,d){if(a&&b){var e=c.createRange();e.setStart(b,0),e.collapse(!0);var f,g=b,h=[],i=0,j=!1,k=!1,l=0,m=!1,n=!1,o=null;for((d||a.startsWithImage||"undefined"!=typeof a.emptyBlocksIndex)&&(n=!0);!m&&g;)if(g.nodeType>3)g=h.pop();else{if(3!==g.nodeType||k){if(a.trailingImageCount&&k&&("img"===g.nodeName.toLowerCase()&&l++,l===a.trailingImageCount)){for(var p=0;g.parentNode.childNodes[p]!==g;)p++;e.setEnd(g.parentNode,p+1),m=!0}if(!m&&1===g.nodeType)for(var q=g.childNodes.length-1;q>=0;)h.push(g.childNodes[q]),q-=1}else f=i+g.length,!j&&a.start>=i&&a.start<=f&&(n||a.start<f?(e.setStart(g,a.start-i),j=!0):o=g),j&&a.end>=i&&a.end<=f&&(a.trailingImageCount?k=!0:(e.setEnd(g,a.end-i),m=!0)),i=f;m||(g=h.pop())}!j&&o&&(e.setStart(o,o.length),e.setEnd(o,o.length)),"undefined"!=typeof a.emptyBlocksIndex&&(e=this.importSelectionMoveCursorPastBlocks(c,b,a.emptyBlocksIndex,e)),d&&(e=this.importSelectionMoveCursorPastAnchor(a,e)),this.selectRange(c,e)}},importSelectionMoveCursorPastAnchor:function(b,c){var d=function(a){return"a"===a.nodeName.toLowerCase()};if(b.start===b.end&&3===c.startContainer.nodeType&&c.startOffset===c.startContainer.nodeValue.length&&a.util.traverseUp(c.startContainer,d)){for(var e=c.startContainer,f=c.startContainer.parentNode;null!==f&&"a"!==f.nodeName.toLowerCase();)f.childNodes[f.childNodes.length-1]!==e?f=null:(e=f,f=f.parentNode);if(null!==f&&"a"===f.nodeName.toLowerCase()){for(var g=null,h=0;null===g&&h<f.parentNode.childNodes.length;h++)f.parentNode.childNodes[h]===f&&(g=h);c.setStart(f.parentNode,g+1),c.collapse(!0)}}return c},importSelectionMoveCursorPastBlocks:function(c,d,e,f){var g,h,i=c.createTreeWalker(d,NodeFilter.SHOW_ELEMENT,b,!1),j=f.startContainer,k=0;for(e=e||1,g=3===j.nodeType&&a.util.isBlockContainer(j.previousSibling)?j.previousSibling:a.util.getClosestBlockContainer(j);i.nextNode();)if(h){if(h=i.currentNode,k++,k===e)break;if(h.textContent.length>0)break}else g===i.currentNode&&(h=i.currentNode);return h||(h=g),f.setStart(a.util.getFirstSelectableLeafNode(h),0),f},getIndexRelativeToAdjacentEmptyBlocks:function(c,d,e,f){if(e.textContent.length>0&&f>0)return-1;var g=e;if(3!==g.nodeType&&(g=e.childNodes[f]),g){if(!a.util.isElementAtBeginningOfBlock(g))return-1;var h=a.util.findPreviousSibling(g);if(!h)return-1;if(h.nodeValue)return-1}for(var i=a.util.getClosestBlockContainer(e),j=c.createTreeWalker(d,NodeFilter.SHOW_ELEMENT,b,!1),k=0;j.nextNode();){var l=""===j.currentNode.textContent;if((l||k>0)&&(k+=1),j.currentNode===i)return k;l||(k=0)}return k},doesRangeStartWithImages:function(a,b){if(0!==a.startOffset||1!==a.startContainer.nodeType)return!1;if("img"===a.startContainer.nodeName.toLowerCase())return!0;var c=a.startContainer.querySelector("img");if(!c)return!1;for(var d=b.createTreeWalker(a.startContainer,NodeFilter.SHOW_ALL,null,!1);d.nextNode();){var e=d.currentNode;if(e===c)break;if(e.nodeValue)return!1}return!0},getTrailingImageCount:function(a,b,c,d){if(0===d||1!==c.nodeType)return 0;if("img"!==c.nodeName.toLowerCase()&&!c.querySelector("img"))return 0;for(var e=c.childNodes[d-1];e.hasChildNodes();)e=e.lastChild;for(var f,g=a,h=[],i=0,j=!1,k=!1,l=!1,m=0;!l&&g;)if(g.nodeType>3)g=h.pop();else{if(3!==g.nodeType||k){if("img"===g.nodeName.toLowerCase()&&m++,g===e)l=!0;else if(1===g.nodeType)for(var n=g.childNodes.length-1;n>=0;)h.push(g.childNodes[n]),n-=1}else m=0,f=i+g.length,!j&&b.start>=i&&b.start<=f&&(j=!0),j&&b.end>=i&&b.end<=f&&(k=!0),i=f;l||(g=h.pop())}return m},selectionContainsContent:function(a){var b=a.getSelection();if(!b||b.isCollapsed||!b.rangeCount)return!1;if(""!==b.toString().trim())return!0;var c=this.getSelectedParentElement(b.getRangeAt(0));return!(!c||!("img"===c.nodeName.toLowerCase()||1===c.nodeType&&c.querySelector("img")))},selectionInContentEditableFalse:function(a){var b,c=this.findMatchingSelectionParent(function(a){var c=a&&a.getAttribute("contenteditable");return"true"===c&&(b=!0),"#text"!==a.nodeName&&"false"===c},a);return!b&&c},getSelectionHtml:function(a){var b,c,d,e="",f=a.getSelection();if(f.rangeCount){for(d=a.createElement("div"),b=0,c=f.rangeCount;c>b;b+=1)d.appendChild(f.getRangeAt(b).cloneContents());e=d.innerHTML}return e},getCaretOffsets:function(a,b){var c,d;return b||(b=window.getSelection().getRangeAt(0)),c=b.cloneRange(),d=b.cloneRange(),c.selectNodeContents(a),c.setEnd(b.endContainer,b.endOffset),d.selectNodeContents(a),d.setStart(b.endContainer,b.endOffset),{left:c.toString().length,right:d.toString().length}},rangeSelectsSingleNode:function(a){var b=a.startContainer;return b===a.endContainer&&b.hasChildNodes()&&a.endOffset===a.startOffset+1},getSelectedParentElement:function(a){return a?this.rangeSelectsSingleNode(a)&&3!==a.startContainer.childNodes[a.startOffset].nodeType?a.startContainer.childNodes[a.startOffset]:3===a.startContainer.nodeType?a.startContainer.parentNode:a.startContainer:null},getSelectedElements:function(a){var b,c,d,e=a.getSelection();if(!e.rangeCount||e.isCollapsed||!e.getRangeAt(0).commonAncestorContainer)return[];if(b=e.getRangeAt(0),3===b.commonAncestorContainer.nodeType){for(c=[],d=b.commonAncestorContainer;d.parentNode&&1===d.parentNode.childNodes.length;)c.push(d.parentNode),d=d.parentNode;return c}return[].filter.call(b.commonAncestorContainer.getElementsByTagName("*"),function(a){return"function"==typeof e.containsNode?e.containsNode(a,!0):!0})},selectNode:function(a,b){var c=b.createRange();c.selectNodeContents(a),this.selectRange(b,c)},select:function(a,b,c,d,e){var f=a.createRange();return f.setStart(b,c),d?f.setEnd(d,e):f.collapse(!0),this.selectRange(a,f),f},clearSelection:function(a,b){b?a.getSelection().collapseToStart():a.getSelection().collapseToEnd()},moveCursor:function(a,b,c){this.select(a,b,c)},getSelectionRange:function(a){var b=a.getSelection();return 0===b.rangeCount?null:b.getRangeAt(0)},selectRange:function(a,b){var c=a.getSelection();c.removeAllRanges(),c.addRange(b)},getSelectionStart:function(a){var b=a.getSelection().anchorNode,c=b&&3===b.nodeType?b.parentNode:b;return c}};a.selection=c}(),function(){function b(b,c){return b.some(function(b){if("function"!=typeof b.getInteractionElements)return!1;var d=b.getInteractionElements();return d?(Array.isArray(d)||(d=[d]),d.some(function(b){return a.util.isDescendant(b,c,!0)})):!1})}var c=function(a){this.base=a,this.options=this.base.options,this.events=[],this.disabledEvents={},this.customEvents={},this.listeners={}};c.prototype={InputEventOnContenteditableSupported:!a.util.isIE&&!a.util.isEdge,attachDOMEvent:function(b,c,d,e){b=a.util.isElement(b)||[window,document].indexOf(b)>-1?[b]:b,Array.prototype.forEach.call(b,function(a){a.addEventListener(c,d,e),this.events.push([a,c,d,e])}.bind(this))},detachDOMEvent:function(b,c,d,e){var f,g;b=a.util.isElement(b)||[window,document].indexOf(b)>-1?[b]:b,Array.prototype.forEach.call(b,function(a){f=this.indexOfListener(a,c,d,e),-1!==f&&(g=this.events.splice(f,1)[0],g[0].removeEventListener(g[1],g[2],g[3]))}.bind(this))},indexOfListener:function(a,b,c,d){var e,f,g;for(e=0,f=this.events.length;f>e;e+=1)if(g=this.events[e],g[0]===a&&g[1]===b&&g[2]===c&&g[3]===d)return e;return-1},detachAllDOMEvents:function(){for(var a=this.events.pop();a;)a[0].removeEventListener(a[1],a[2],a[3]),a=this.events.pop()},detachAllEventsFromElement:function(a){for(var b=this.events.filter(function(b){return b&&b[0].getAttribute&&b[0].getAttribute("medium-editor-index")===a.getAttribute("medium-editor-index")}),c=0,d=b.length;d>c;c++){var e=b[c];this.detachDOMEvent(e[0],e[1],e[2],e[3])}},attachAllEventsToElement:function(a){this.listeners.editableInput&&(this.contentCache[a.getAttribute("medium-editor-index")]=a.innerHTML),this.eventsCache&&this.eventsCache.forEach(function(b){this.attachDOMEvent(a,b.name,b.handler.bind(this))},this)},enableCustomEvent:function(a){void 0!==this.disabledEvents[a]&&delete this.disabledEvents[a]},disableCustomEvent:function(a){this.disabledEvents[a]=!0},attachCustomEvent:function(a,b){this.setupListener(a),this.customEvents[a]||(this.customEvents[a]=[]),this.customEvents[a].push(b)},detachCustomEvent:function(a,b){var c=this.indexOfCustomListener(a,b);-1!==c&&this.customEvents[a].splice(c,1)},indexOfCustomListener:function(a,b){return this.customEvents[a]&&this.customEvents[a].length?this.customEvents[a].indexOf(b):-1},detachAllCustomEvents:function(){this.customEvents={}},triggerCustomEvent:function(a,b,c){this.customEvents[a]&&!this.disabledEvents[a]&&this.customEvents[a].forEach(function(a){a(b,c)})},destroy:function(){this.detachAllDOMEvents(),this.detachAllCustomEvents(),this.detachExecCommand(),this.base.elements&&this.base.elements.forEach(function(a){a.removeAttribute("data-medium-focused")})},attachToExecCommand:function(){this.execCommandListener||(this.execCommandListener=function(a){this.handleDocumentExecCommand(a)}.bind(this),this.wrapExecCommand(),this.options.ownerDocument.execCommand.listeners.push(this.execCommandListener))},detachExecCommand:function(){var a=this.options.ownerDocument;if(this.execCommandListener&&a.execCommand.listeners){var b=a.execCommand.listeners.indexOf(this.execCommandListener);-1!==b&&a.execCommand.listeners.splice(b,1),a.execCommand.listeners.length||this.unwrapExecCommand()}},wrapExecCommand:function(){var a=this.options.ownerDocument;if(!a.execCommand.listeners){var b=function(b,c){a.execCommand.listeners&&a.execCommand.listeners.forEach(function(a){a({command:b[0],value:b[2],args:b,result:c})})},c=function(){var c=a.execCommand.orig.apply(this,arguments);if(!a.execCommand.listeners)return c;var d=Array.prototype.slice.call(arguments);return b(d,c),c};c.orig=a.execCommand,c.listeners=[],c.callListeners=b,a.execCommand=c}},unwrapExecCommand:function(){var a=this.options.ownerDocument;a.execCommand.orig&&(a.execCommand=a.execCommand.orig)},setupListener:function(a){if(!this.listeners[a]){switch(a){case"externalInteraction":this.attachDOMEvent(this.options.ownerDocument.body,"mousedown",this.handleBodyMousedown.bind(this),!0),this.attachDOMEvent(this.options.ownerDocument.body,"click",this.handleBodyClick.bind(this),!0),this.attachDOMEvent(this.options.ownerDocument.body,"focus",this.handleBodyFocus.bind(this),!0);break;case"blur":this.setupListener("externalInteraction");break;case"focus":this.setupListener("externalInteraction");break;case"editableInput":this.contentCache={},this.base.elements.forEach(function(a){this.contentCache[a.getAttribute("medium-editor-index")]=a.innerHTML},this),this.InputEventOnContenteditableSupported&&this.attachToEachElement("input",this.handleInput),this.InputEventOnContenteditableSupported||(this.setupListener("editableKeypress"),this.keypressUpdateInput=!0,this.attachDOMEvent(document,"selectionchange",this.handleDocumentSelectionChange.bind(this)),this.attachToExecCommand());break;case"editableClick":this.attachToEachElement("click",this.handleClick);break;case"editableBlur":this.attachToEachElement("blur",this.handleBlur);break;case"editableKeypress":this.attachToEachElement("keypress",this.handleKeypress);break;case"editableKeyup":this.attachToEachElement("keyup",this.handleKeyup);break;case"editableKeydown":this.attachToEachElement("keydown",this.handleKeydown);break;case"editableKeydownSpace":this.setupListener("editableKeydown");break;case"editableKeydownEnter":this.setupListener("editableKeydown");break;case"editableKeydownTab":this.setupListener("editableKeydown");break;case"editableKeydownDelete":this.setupListener("editableKeydown");break;case"editableMouseover":this.attachToEachElement("mouseover",this.handleMouseover);break;case"editableDrag":this.attachToEachElement("dragover",this.handleDragging),this.attachToEachElement("dragleave",this.handleDragging);break;case"editableDrop":this.attachToEachElement("drop",this.handleDrop);break;case"editablePaste":this.attachToEachElement("paste",this.handlePaste)}this.listeners[a]=!0}},attachToEachElement:function(a,b){this.eventsCache||(this.eventsCache=[]),this.base.elements.forEach(function(c){this.attachDOMEvent(c,a,b.bind(this))},this),this.eventsCache.push({name:a,handler:b})},cleanupElement:function(a){var b=a.getAttribute("medium-editor-index");b&&(this.detachAllEventsFromElement(a),this.contentCache&&delete this.contentCache[b])},focusElement:function(a){a.focus(),this.updateFocus(a,{target:a,type:"focus"})},updateFocus:function(c,d){var e,f=this.base.getFocusedElement();f&&"click"===d.type&&this.lastMousedownTarget&&(a.util.isDescendant(f,this.lastMousedownTarget,!0)||b(this.base.extensions,this.lastMousedownTarget))&&(e=f),e||this.base.elements.some(function(b){return!e&&a.util.isDescendant(b,c,!0)&&(e=b),!!e},this);var g=!a.util.isDescendant(f,c,!0)&&!b(this.base.extensions,c);e!==f&&(f&&g&&(f.removeAttribute("data-medium-focused"),this.triggerCustomEvent("blur",d,f)),e&&(e.setAttribute("data-medium-focused",!0),this.triggerCustomEvent("focus",d,e))),g&&this.triggerCustomEvent("externalInteraction",d)},updateInput:function(a,b){if(this.contentCache){var c=a.getAttribute("medium-editor-index"),d=a.innerHTML;d!==this.contentCache[c]&&this.triggerCustomEvent("editableInput",b,a),this.contentCache[c]=d}},handleDocumentSelectionChange:function(b){if(b.currentTarget&&b.currentTarget.activeElement){var c,d=b.currentTarget.activeElement;this.base.elements.some(function(b){return a.util.isDescendant(b,d,!0)?(c=b,!0):!1},this),c&&this.updateInput(c,{target:d,currentTarget:c})}},handleDocumentExecCommand:function(){var a=this.base.getFocusedElement();a&&this.updateInput(a,{target:a,currentTarget:a})},handleBodyClick:function(a){this.updateFocus(a.target,a)},handleBodyFocus:function(a){this.updateFocus(a.target,a)},handleBodyMousedown:function(a){this.lastMousedownTarget=a.target},handleInput:function(a){this.updateInput(a.currentTarget,a)},handleClick:function(a){this.triggerCustomEvent("editableClick",a,a.currentTarget)},handleBlur:function(a){this.triggerCustomEvent("editableBlur",a,a.currentTarget)},handleKeypress:function(a){if(this.triggerCustomEvent("editableKeypress",a,a.currentTarget),this.keypressUpdateInput){var b={target:a.target,currentTarget:a.currentTarget
2
+ };setTimeout(function(){this.updateInput(b.currentTarget,b)}.bind(this),0)}},handleKeyup:function(a){this.triggerCustomEvent("editableKeyup",a,a.currentTarget)},handleMouseover:function(a){this.triggerCustomEvent("editableMouseover",a,a.currentTarget)},handleDragging:function(a){this.triggerCustomEvent("editableDrag",a,a.currentTarget)},handleDrop:function(a){this.triggerCustomEvent("editableDrop",a,a.currentTarget)},handlePaste:function(a){this.triggerCustomEvent("editablePaste",a,a.currentTarget)},handleKeydown:function(b){return this.triggerCustomEvent("editableKeydown",b,b.currentTarget),a.util.isKey(b,a.util.keyCode.SPACE)?this.triggerCustomEvent("editableKeydownSpace",b,b.currentTarget):a.util.isKey(b,a.util.keyCode.ENTER)||b.ctrlKey&&a.util.isKey(b,a.util.keyCode.M)?this.triggerCustomEvent("editableKeydownEnter",b,b.currentTarget):a.util.isKey(b,a.util.keyCode.TAB)?this.triggerCustomEvent("editableKeydownTab",b,b.currentTarget):a.util.isKey(b,[a.util.keyCode.DELETE,a.util.keyCode.BACKSPACE])?this.triggerCustomEvent("editableKeydownDelete",b,b.currentTarget):void 0}},a.Events=c}(),function(){var b=a.Extension.extend({action:void 0,aria:void 0,tagNames:void 0,style:void 0,useQueryState:void 0,contentDefault:void 0,contentFA:void 0,classList:void 0,attrs:void 0,constructor:function(c){b.isBuiltInButton(c)?a.Extension.call(this,this.defaults[c]):a.Extension.call(this,c)},init:function(){a.Extension.prototype.init.apply(this,arguments),this.button=this.createButton(),this.on(this.button,"click",this.handleClick.bind(this))},getButton:function(){return this.button},getAction:function(){return"function"==typeof this.action?this.action(this.base.options):this.action},getAria:function(){return"function"==typeof this.aria?this.aria(this.base.options):this.aria},getTagNames:function(){return"function"==typeof this.tagNames?this.tagNames(this.base.options):this.tagNames},createButton:function(){var a=this.document.createElement("button"),b=this.contentDefault,c=this.getAria(),d=this.getEditorOption("buttonLabels");return a.classList.add("medium-editor-action"),a.classList.add("medium-editor-action-"+this.name),this.classList&&this.classList.forEach(function(b){a.classList.add(b)}),a.setAttribute("data-action",this.getAction()),c&&(a.setAttribute("title",c),a.setAttribute("aria-label",c)),this.attrs&&Object.keys(this.attrs).forEach(function(b){a.setAttribute(b,this.attrs[b])},this),"fontawesome"===d&&this.contentFA&&(b=this.contentFA),a.innerHTML=b,a},handleClick:function(a){a.preventDefault(),a.stopPropagation();var b=this.getAction();b&&this.execAction(b)},isActive:function(){return this.button.classList.contains(this.getEditorOption("activeButtonClass"))},setInactive:function(){this.button.classList.remove(this.getEditorOption("activeButtonClass")),delete this.knownState},setActive:function(){this.button.classList.add(this.getEditorOption("activeButtonClass")),delete this.knownState},queryCommandState:function(){var a=null;return this.useQueryState&&(a=this.base.queryCommandState(this.getAction())),a},isAlreadyApplied:function(a){var b,c,d=!1,e=this.getTagNames();return this.knownState===!1||this.knownState===!0?this.knownState:(e&&e.length>0&&(d=-1!==e.indexOf(a.nodeName.toLowerCase())),!d&&this.style&&(b=this.style.value.split("|"),c=this.window.getComputedStyle(a,null).getPropertyValue(this.style.prop),b.forEach(function(a){this.knownState||(d=-1!==c.indexOf(a),(d||"text-decoration"!==this.style.prop)&&(this.knownState=d))},this)),d)}});b.isBuiltInButton=function(b){return"string"==typeof b&&a.extensions.button.prototype.defaults.hasOwnProperty(b)},a.extensions.button=b}(),function(){a.extensions.button.prototype.defaults={bold:{name:"bold",action:"bold",aria:"bold",tagNames:["b","strong"],style:{prop:"font-weight",value:"700|bold"},useQueryState:!0,contentDefault:"<b>B</b>",contentFA:'<i class="fa fa-bold"></i>'},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"<b><i>I</i></b>",contentFA:'<i class="fa fa-italic"></i>'},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"<b><u>U</u></b>",contentFA:'<i class="fa fa-underline"></i>'},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"<s>A</s>",contentFA:'<i class="fa fa-strikethrough"></i>'},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"<b>x<sup>1</sup></b>",contentFA:'<i class="fa fa-superscript"></i>'},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"<b>x<sub>1</sub></b>",contentFA:'<i class="fa fa-subscript"></i>'},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"<b>image</b>",contentFA:'<i class="fa fa-picture-o"></i>'},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"<b>1.</b>",contentFA:'<i class="fa fa-list-ol"></i>'},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"<b>&bull;</b>",contentFA:'<i class="fa fa-list-ul"></i>'},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"<b>&rarr;</b>",contentFA:'<i class="fa fa-indent"></i>'},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"<b>&larr;</b>",contentFA:'<i class="fa fa-outdent"></i>'},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"<b>C</b>",contentFA:'<i class="fa fa-align-center"></i>'},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"<b>J</b>",contentFA:'<i class="fa fa-align-justify"></i>'},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"<b>L</b>",contentFA:'<i class="fa fa-align-left"></i>'},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"<b>R</b>",contentFA:'<i class="fa fa-align-right"></i>'},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"<b>X</b>",contentFA:'<i class="fa fa-eraser"></i>'},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"<b>&ldquo;</b>",contentFA:'<i class="fa fa-quote-right"></i>'},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"<b>0101</b>",contentFA:'<i class="fa fa-code fa-lg"></i>'},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"<b>H1</b>",contentFA:'<i class="fa fa-header"><sup>1</sup>'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"<b>H2</b>",contentFA:'<i class="fa fa-header"><sup>2</sup>'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"<b>H3</b>",contentFA:'<i class="fa fa-header"><sup>3</sup>'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"<b>H4</b>",contentFA:'<i class="fa fa-header"><sup>4</sup>'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"<b>H5</b>",contentFA:'<i class="fa fa-header"><sup>5</sup>'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"<b>H6</b>",contentFA:'<i class="fa fa-header"><sup>6</sup>'}}}(),function(){var b=a.extensions.button.extend({init:function(){a.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"&#10003;",formCloseLabel:"&times;",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return this.hasForm?this.getForm().classList.contains(this.activeClass):!1},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var a=this.base.getExtensionByName("toolbar");a&&a.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var a=this.base.getExtensionByName("toolbar");a&&a.hideToolbarDefaultActions()},setToolbarPosition:function(){var a=this.base.getExtensionByName("toolbar");a&&a.setToolbarPosition()}});a.extensions.form=b}(),function(){var b=a.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"<b>#</b>",contentFA:'<i class="fa fa-link"></i>',init:function(){a.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(b){b.preventDefault(),b.stopPropagation();var c=a.selection.getSelectionRange(this.document);return"a"===c.startContainer.nodeName.toLowerCase()||"a"===c.endContainer.nodeName.toLowerCase()||a.util.getClosestTag(a.selection.getSelectedParentElement(c),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(b){a.util.isKey(b,a.util.keyCode.K)&&a.util.isMetaCtrlKey(b)&&!b.shiftKey&&this.handleClick(b)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var a=['<input type="text" class="medium-editor-toolbar-input" placeholder="',this.placeholderText,'">'];return a.push('<a href="#" class="medium-editor-toolbar-save">',"fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-check"></i>':this.formSaveLabel,"</a>"),a.push('<a href="#" class="medium-editor-toolbar-close">',"fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-times"></i>':this.formCloseLabel,"</a>"),this.targetCheckbox&&a.push('<div class="medium-editor-toolbar-form-row">','<input type="checkbox" class="medium-editor-toolbar-anchor-target">',"<label>",this.targetCheckboxText,"</label>","</div>"),this.customClassOption&&a.push('<div class="medium-editor-toolbar-form-row">','<input type="checkbox" class="medium-editor-toolbar-anchor-button">',"<label>",this.customClassOptionText,"</label>","</div>"),a.join("")},isDisplayed:function(){return a.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){a.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(b){var c=this.getInput(),d=this.getAnchorTargetCheckbox(),e=this.getAnchorButtonCheckbox();if(b=b||{value:""},"string"==typeof b&&(b={value:b}),this.base.saveSelection(),this.hideToolbarDefaultActions(),a.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),c.value=b.value,c.focus(),d&&(d.checked="_blank"===b.target),e){var f=b.buttonClass?b.buttonClass.split(" "):[];e.checked=-1!==f.indexOf(this.customClassOption)}},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},getFormOpts:function(){var a=this.getAnchorTargetCheckbox(),b=this.getAnchorButtonCheckbox(),c={value:this.getInput().value.trim()};return this.linkValidation&&(c.value=this.checkLinkFormat(c.value)),c.target="_self",a&&a.checked&&(c.target="_blank"),b&&b.checked&&(c.buttonClass=this.customClassOption),c},doFormSave:function(){var a=this.getFormOpts();this.completeFormSave(a)},completeFormSave:function(a){this.base.restoreSelection(),this.execAction(this.action,a),this.base.checkSelection()},checkLinkFormat:function(a){var b=/^([a-z]+:)?\/\/|^(mailto|tel|maps):/i,c=/^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/;return c.test(a)?"tel:"+a:(b.test(a)?"":"http://")+encodeURI(a)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(a){var b=a.querySelector(".medium-editor-toolbar-close"),c=a.querySelector(".medium-editor-toolbar-save"),d=a.querySelector(".medium-editor-toolbar-input");this.on(a,"click",this.handleFormClick.bind(this)),this.on(d,"keyup",this.handleTextboxKeyup.bind(this)),this.on(b,"click",this.handleCloseClick.bind(this)),this.on(c,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var a=this.document,b=a.createElement("div");return b.className="medium-editor-toolbar-form",b.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),b.innerHTML=this.getTemplate(),this.attachFormEvents(b),b},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(b){return b.keyCode===a.util.keyCode.ENTER?(b.preventDefault(),void this.doFormSave()):void(b.keyCode===a.util.keyCode.ESCAPE&&(b.preventDefault(),this.doFormCancel()))},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.anchor=b}(),function(){var b=a.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var a=this.document.createElement("div");return a.id="medium-editor-anchor-preview-"+this.getEditorId(),a.className="medium-editor-anchor-preview",a.innerHTML=this.getTemplate(),this.on(a,"click",this.handleClick.bind(this)),a},getTemplate:function(){return'<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview"> <a class="medium-editor-toolbar-anchor-preview-inner"></a></div>'},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(a){return this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||a.getAttribute("data-disable-preview")?!0:(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=a.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=a.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=a,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(a){a=a||this.activeAnchor;var b,c,d=this.anchorPreview.offsetHeight,e=a.getBoundingClientRect(),f=(e.left+e.right)/2,g=this.diffLeft,h=this.diffTop;b=this.anchorPreview.offsetWidth/2;var i=this.base.getExtensionByName("toolbar");i&&(g=i.diffLeft,h=i.diffTop),c=g-b,this.anchorPreview.style.top=Math.round(d+e.bottom-h+this.window.pageYOffset-this.anchorPreview.offsetHeight)+"px",this.anchorPreview.style.right="initial",b>f?(this.anchorPreview.style.left=c+b+"px",this.anchorPreview.style.right="initial"):this.window.innerWidth-f<b?(this.anchorPreview.style.left="auto",this.anchorPreview.style.right=0):(this.anchorPreview.style.left=c+f+"px",this.anchorPreview.style.right="initial")},attachToEditables:function(){this.subscribe("editableMouseover",this.handleEditableMouseover.bind(this)),this.subscribe("positionedToolbar",this.handlePositionedToolbar.bind(this))},handlePositionedToolbar:function(){this.showWhenToolbarIsVisible||this.hidePreview()},handleClick:function(a){var b=this.base.getExtensionByName("anchor"),c=this.activeAnchor;b&&c&&(a.preventDefault(),this.base.selectElement(this.activeAnchor),this.base.delay(function(){if(c){var a={value:c.attributes.href.value,target:c.getAttribute("target"),buttonClass:c.getAttribute("class")};b.showForm(a),c=null}}.bind(this))),this.hidePreview()},handleAnchorMouseout:function(){this.anchorToPreview=null,this.off(this.activeAnchor,"mouseout",this.instanceHandleAnchorMouseout),this.instanceHandleAnchorMouseout=null},handleEditableMouseover:function(b){var c=a.util.getClosestTag(b.target,"a");if(!1!==c){if(!this.showOnEmptyLinks&&(!/href=["']\S+["']/.test(c.outerHTML)||/href=["']#\S+["']/.test(c.outerHTML)))return!0;var d=this.base.getExtensionByName("toolbar");if(!this.showWhenToolbarIsVisible&&d&&d.isDisplayed&&d.isDisplayed())return!0;this.activeAnchor&&this.activeAnchor!==c&&this.detachPreviewHandlers(),this.anchorToPreview=c,this.instanceHandleAnchorMouseout=this.handleAnchorMouseout.bind(this),this.on(this.anchorToPreview,"mouseout",this.instanceHandleAnchorMouseout),this.base.delay(function(){this.anchorToPreview&&this.showPreview(this.anchorToPreview)}.bind(this))}},handlePreviewMouseover:function(){this.lastOver=(new Date).getTime(),this.hovering=!0},handlePreviewMouseout:function(a){a.relatedTarget&&/anchor-preview/.test(a.relatedTarget.className)||(this.hovering=!1)},updatePreview:function(){if(this.hovering)return!0;var a=(new Date).getTime()-this.lastOver;a>this.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}});a.extensions.anchorPreview=b}(),function(){function b(b){return!a.util.getClosestTag(b,"a")}var c,d,e,f;c=[" "," ","\n","\r"," "," "," "," "," ","\u2028","\u2029"],d="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw",e="(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.]("+d+")\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.("+d+"))",f=new RegExp("^("+d+")$","i");var g=a.Extension.extend({init:function(){a.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var a=0,b=0;b<this.window._mediumEditors.length;b++){var c=this.window._mediumEditors[b];null!==c&&void 0!==c.getExtensionByName("autoLink")&&a++}return 1===a},destroy:function(){this.document.queryCommandSupported("AutoUrlDetect")&&this.isLastInstance()&&this.document.execCommand("AutoUrlDetect",!1,!0)},onBlur:function(a,b){this.performLinking(b)},onKeypress:function(b){this.disableEventHandling||a.util.isKey(b,[a.util.keyCode.SPACE,a.util.keyCode.ENTER])&&(clearTimeout(this.performLinkingTimeout),this.performLinkingTimeout=setTimeout(function(){try{var a=this.base.exportSelection();this.performLinking(b.target)&&this.base.importSelection(a,!0)}catch(c){window.console&&window.console.error("Failed to perform linking",c),this.disableEventHandling=!0}}.bind(this),0))},performLinking:function(b){var c=a.util.splitByBlockElements(b),d=!1;0===c.length&&(c=[b]);for(var e=0;e<c.length;e++)d=this.removeObsoleteAutoLinkSpans(c[e])||d,d=this.performLinkingWithinElement(c[e])||d;return this.base.events.updateInput(b,{target:b,currentTarget:b}),d},removeObsoleteAutoLinkSpans:function(c){if(!c||3===c.nodeType)return!1;for(var d=c.querySelectorAll('span[data-auto-link="true"]'),e=!1,f=0;f<d.length;f++){var g=d[f].textContent;if(-1===g.indexOf("://")&&(g=a.util.ensureUrlHasProtocol(g)),d[f].getAttribute("data-href")!==g&&b(d[f])){e=!0;var h=g.replace(/\s+$/,"");if(d[f].getAttribute("data-href")===h){var i=g.length-h.length,j=a.util.splitOffDOMTree(d[f],this.splitTextBeforeEnd(d[f],i));d[f].parentNode.insertBefore(j,d[f].nextSibling)}else a.util.unwrap(d[f],this.document)}}return e},splitTextBeforeEnd:function(a,b){for(var c=this.document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1),d=!0;d;)d=null!==c.lastChild();for(var e,f,g;b>0&&null!==g;)e=c.currentNode,f=e.nodeValue,f.length>b?(g=e.splitText(f.length-b),b=0):(g=c.previousNode(),b-=f.length);return g},performLinkingWithinElement:function(b){for(var c=this.findLinkableText(b),d=!1,e=0;e<c.length;e++){var f=a.util.findOrCreateMatchingTextNodes(this.document,b,c[e]);this.shouldNotLink(f)||this.createAutoLink(f,c[e].href)}return d},shouldNotLink:function(b){for(var c=!1,d=0;d<b.length&&c===!1;d++)c=!!a.util.traverseUp(b[d],function(a){return"a"===a.nodeName.toLowerCase()||a.getAttribute&&"true"===a.getAttribute("data-auto-link")});return c},findLinkableText:function(a){for(var b=new RegExp(e,"gi"),d=a.textContent,g=null,h=[];null!==(g=b.exec(d));){var i=!0,j=g.index+g[0].length;i=!(0!==g.index&&-1===c.indexOf(d[g.index-1])||j!==d.length&&-1===c.indexOf(d[j])),i=i&&(-1!==g[0].indexOf("/")||f.test(g[0].split(".").pop().split("?").shift())),i&&h.push({href:g[0],start:g.index,end:j})}return h},createAutoLink:function(b,c){c=a.util.ensureUrlHasProtocol(c);var d=a.util.createLink(this.document,b,c,this.getEditorOption("targetBlank")?"_blank":null),e=this.document.createElement("span");for(e.setAttribute("data-auto-link","true"),e.setAttribute("data-href",c),d.insertBefore(e,d.firstChild);d.childNodes.length>1;)e.appendChild(d.childNodes[1])}});a.extensions.autoLink=g}(),function(){function b(b){var d=a.util.getContainerEditorElement(b),e=Array.prototype.slice.call(d.parentElement.querySelectorAll("."+c));e.forEach(function(a){a.classList.remove(c)})}var c="medium-editor-dragover",d=a.Extension.extend({name:"fileDragging",allowedTypes:["image"],init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableDrag",this.handleDrag.bind(this)),this.subscribe("editableDrop",this.handleDrop.bind(this))},handleDrag:function(a){a.preventDefault(),a.dataTransfer.dropEffect="copy";var d=a.target.classList?a.target:a.target.parentElement;b(d),"dragover"===a.type&&d.classList.add(c)},handleDrop:function(a){a.preventDefault(),a.stopPropagation(),this.base.selectElement(a.target);var c=this.base.exportSelection();c.start=c.end,this.base.importSelection(c),a.dataTransfer.files&&Array.prototype.slice.call(a.dataTransfer.files).forEach(function(a){this.isAllowedFile(a)&&a.type.match("image")&&this.insertImageFile(a)},this),b(a.target)},isAllowedFile:function(a){return this.allowedTypes.some(function(b){return!!a.type.match(b)})},insertImageFile:function(b){if("function"==typeof FileReader){var c=new FileReader;c.readAsDataURL(b),c.addEventListener("load",function(b){var c=this.document.createElement("img");c.src=b.target.result,a.util.insertHTMLCommand(this.document,c.outerHTML)}.bind(this))}}});a.extensions.fileDragging=d}(),function(){var b=a.Extension.extend({name:"keyboard-commands",commands:[{command:"bold",key:"B",meta:!0,shift:!1,alt:!1},{command:"italic",key:"I",meta:!0,shift:!1,alt:!1},{command:"underline",key:"U",meta:!0,shift:!1,alt:!1}],init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.keys={},this.commands.forEach(function(a){var b=a.key.charCodeAt(0);this.keys[b]||(this.keys[b]=[]),this.keys[b].push(a)},this)},handleKeydown:function(b){var c=a.util.getKeyCode(b);if(this.keys[c]){var d=a.util.isMetaCtrlKey(b),e=!!b.shiftKey,f=!!b.altKey;this.keys[c].forEach(function(a){a.meta!==d||a.shift!==e||a.alt!==f&&void 0!==a.alt||(b.preventDefault(),b.stopPropagation(),"function"==typeof a.command?a.command.apply(this):!1!==a.command&&this.execAction(a.command))},this)}}});a.extensions.keyboardCommands=b}(),function(){var b=a.extensions.form.extend({name:"fontname",action:"fontName",aria:"change font name",contentDefault:"&#xB1;",contentFA:'<i class="fa fa-font"></i>',fonts:["","Arial","Verdana","Times New Roman"],init:function(){a.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(a){if(a.preventDefault(),a.stopPropagation(),!this.isDisplayed()){var b=this.document.queryCommandValue("fontName")+"";this.showForm(b)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(a){var b=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),b.value=a||"",b.focus()},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var a,b=this.document,c=b.createElement("div"),d=b.createElement("select"),e=b.createElement("a"),f=b.createElement("a");c.className="medium-editor-toolbar-form",c.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(c,"click",this.handleFormClick.bind(this));for(var g=0;g<this.fonts.length;g++)a=b.createElement("option"),a.innerHTML=this.fonts[g],a.value=this.fonts[g],d.appendChild(a);return d.className="medium-editor-toolbar-select",c.appendChild(d),this.on(d,"change",this.handleFontChange.bind(this)),f.setAttribute("href","#"),f.className="medium-editor-toobar-save",f.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-check"></i>':"&#10003;",c.appendChild(f),this.on(f,"click",this.handleSaveClick.bind(this),!0),e.setAttribute("href","#"),e.className="medium-editor-toobar-close",e.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-times"></i>':"&times;",c.appendChild(e),this.on(e,"click",this.handleCloseClick.bind(this)),c},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){a.selection.getSelectedElements(this.document).forEach(function(a){"font"===a.nodeName.toLowerCase()&&a.hasAttribute("face")&&a.removeAttribute("face")})},handleFontChange:function(){var a=this.getSelect().value;""===a?this.clearFontName():this.execAction("fontName",{value:a})},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.fontName=b}(),function(){var b=a.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"&#xB1;",contentFA:'<i class="fa fa-text-height"></i>',init:function(){a.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(a){if(a.preventDefault(),a.stopPropagation(),!this.isDisplayed()){var b=this.document.queryCommandValue("fontSize")+"";this.showForm(b)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(a){var b=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),b.value=a||"",b.focus()},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var a=this.document,b=a.createElement("div"),c=a.createElement("input"),d=a.createElement("a"),e=a.createElement("a");return b.className="medium-editor-toolbar-form",b.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(b,"click",this.handleFormClick.bind(this)),c.setAttribute("type","range"),c.setAttribute("min","1"),c.setAttribute("max","7"),c.className="medium-editor-toolbar-input",b.appendChild(c),this.on(c,"change",this.handleSliderChange.bind(this)),e.setAttribute("href","#"),e.className="medium-editor-toobar-save",e.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-check"></i>':"&#10003;",b.appendChild(e),this.on(e,"click",this.handleSaveClick.bind(this),!0),d.setAttribute("href","#"),d.className="medium-editor-toobar-close",d.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'<i class="fa fa-times"></i>':"&times;",b.appendChild(d),this.on(d,"click",this.handleCloseClick.bind(this)),b},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){a.selection.getSelectedElements(this.document).forEach(function(a){"font"===a.nodeName.toLowerCase()&&a.hasAttribute("size")&&a.removeAttribute("size")})},handleSliderChange:function(){var a=this.getInput().value;"4"===a?this.clearFontSize():this.execAction("fontSize",{value:a})},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.fontSize=b}(),function(){function b(){return[[new RegExp(/^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/<!--StartFragment-->|<!--EndFragment-->/g),""],[new RegExp(/<br>$/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(<br[^>]*>)?$/gi),""],[new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g)," "],[new RegExp(/<br class="Apple-interchange-newline">/g),"<br>"],[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi),'<span class="replace-with italic bold">'],[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi),'<span class="replace-with italic">'],[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi),'<span class="replace-with bold">'],[new RegExp(/&lt;(\/?)(i|b|a)&gt;/gi),"<$1$2>"],[new RegExp(/&lt;a(?:(?!href).)+href=(?:&quot;|&rdquo;|&ldquo;|"|“|”)(((?!&quot;|&rdquo;|&ldquo;|"|“|”).)*)(?:&quot;|&rdquo;|&ldquo;|"|“|”)(?:(?!&gt;).)*&gt;/gi),'<a href="$1">'],[new RegExp(/<\/p>\n+/gi),"</p>"],[new RegExp(/\n+<p/gi),"<p"],[new RegExp(/<\/?o:[a-z]*>/gi),""],[new RegExp(/<!\[if !supportLists\]>(((?!<!).)*)<!\[endif]\>/gi),"$1"]];
3
+ }function c(a,b,c){var d=a.clipboardData||b.clipboardData||c.dataTransfer,e={};if(!d)return e;if(d.getData){var f=d.getData("Text");f&&f.length>0&&(e["text/plain"]=f)}if(d.types)for(var g=0;g<d.types.length;g++){var h=d.types[g];e[h]=d.getData(h)}return e}var d="%ME_PASTEBIN%",e=null,f=null,g=function(a){a.stopPropagation()},h=a.Extension.extend({forcePlainText:!0,cleanPastedHTML:!1,preCleanReplacements:[],cleanReplacements:[],cleanAttrs:["class","style","dir"],cleanTags:["meta"],init:function(){a.Extension.prototype.init.apply(this,arguments),(this.forcePlainText||this.cleanPastedHTML)&&(this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.getEditorElements().forEach(function(a){this.on(a,"paste",this.handlePaste.bind(this))},this),this.subscribe("addElement",this.handleAddElement.bind(this)))},handleAddElement:function(a,b){this.on(b,"paste",this.handlePaste.bind(this))},destroy:function(){(this.forcePlainText||this.cleanPastedHTML)&&this.removePasteBin()},handlePaste:function(a,b){if(!a.defaultPrevented){var d=c(a,this.window,this.document),e=d["text/html"],f=d["text/plain"];this.window.clipboardData&&void 0===a.clipboardData&&!e&&(e=f),(e||f)&&(a.preventDefault(),this.doPaste(e,f,b))}},doPaste:function(b,c,d){var e,f,g="";if(this.cleanPastedHTML&&b)return this.cleanPaste(b);if(this.getEditorOption("disableReturn")||d&&d.getAttribute("data-disable-return"))g=a.util.htmlEntities(c);else if(e=c.split(/[\r\n]+/g),e.length>1)for(f=0;f<e.length;f+=1)""!==e[f]&&(g+="<p>"+a.util.htmlEntities(e[f])+"</p>");else g=a.util.htmlEntities(e[0]);a.util.insertHTMLCommand(this.document,g)},handlePasteBinPaste:function(a){if(a.defaultPrevented)return void this.removePasteBin();var b=c(a,this.window,this.document),d=b["text/html"],e=b["text/plain"],g=f;return!this.cleanPastedHTML||d?(a.preventDefault(),this.removePasteBin(),this.doPaste(d,e,g),void this.trigger("editablePaste",{currentTarget:g,target:g},g)):void setTimeout(function(){this.cleanPastedHTML&&(d=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(d,e,g),this.trigger("editablePaste",{currentTarget:g,target:g},g)}.bind(this),0)},handleKeydown:function(b,c){a.util.isKey(b,a.util.keyCode.V)&&a.util.isMetaCtrlKey(b)&&(b.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(c))},createPasteBin:function(b){var c,h=a.selection.getSelectionRange(this.document),i=this.window.pageYOffset;f=b,h&&(c=h.getClientRects(),i+=c.length?c[0].top:h.startContainer.getBoundingClientRect().top),e=h;var j=this.document.createElement("div");j.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),j.setAttribute("style","border: 1px red solid; position: absolute; top: "+i+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),j.setAttribute("contentEditable",!0),j.innerHTML=d,this.document.body.appendChild(j),this.on(j,"focus",g),this.on(j,"focusin",g),this.on(j,"focusout",g),j.focus(),a.selection.selectNode(j,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(j,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==e&&(a.selection.selectRange(this.document,e),e=null),null!==f&&(f=null);var b=this.getPasteBin();b&&b&&(this.off(b,"focus",g),this.off(b,"focusin",g),this.off(b,"focusout",g),this.off(b,"paste",this.boundHandlePaste),b.parentElement.removeChild(b))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var a=this.getPasteBin();if(!a)return!1;if(a.firstChild&&"mcepastebin"===a.firstChild.id)return!1;var b=a.innerHTML;return b&&b!==d?b:!1},cleanPaste:function(a){var c,d,e,f,g=/<p|<br|<div/.test(a),h=[].concat(this.preCleanReplacements||[],b(),this.cleanReplacements||[]);for(c=0;c<h.length;c+=1)a=a.replace(h[c][0],h[c][1]);if(!g)return this.pasteHTML(a);for(e=this.document.createElement("div"),e.innerHTML="<p>"+a.split("<br><br>").join("</p><p>")+"</p>",d=e.querySelectorAll("a,p,div,br"),c=0;c<d.length;c+=1)switch(f=d[c],f.innerHTML=f.innerHTML.replace(/\n/gi," "),f.nodeName.toLowerCase()){case"p":case"div":this.filterCommonBlocks(f);break;case"br":this.filterLineBreak(f)}this.pasteHTML(e.innerHTML)},pasteHTML:function(b,c){c=a.util.defaults({},c,{cleanAttrs:this.cleanAttrs,cleanTags:this.cleanTags});var d,e,f,g,h=this.document.createDocumentFragment();for(h.appendChild(this.document.createElement("body")),g=h.querySelector("body"),g.innerHTML=b,this.cleanupSpans(g),d=g.querySelectorAll("*"),f=0;f<d.length;f+=1)e=d[f],"a"===e.nodeName.toLowerCase()&&this.getEditorOption("targetBlank")&&a.util.setTargetBlank(e),a.util.cleanupAttrs(e,c.cleanAttrs),a.util.cleanupTags(e,c.cleanTags);a.util.insertHTMLCommand(this.document,g.innerHTML.replace(/&nbsp;/g," "))},isCommonBlock:function(a){return a&&("p"===a.nodeName.toLowerCase()||"div"===a.nodeName.toLowerCase())},filterCommonBlocks:function(a){/^\s*$/.test(a.textContent)&&a.parentNode&&a.parentNode.removeChild(a)},filterLineBreak:function(a){this.isCommonBlock(a.previousElementSibling)?this.removeWithParent(a):!this.isCommonBlock(a.parentNode)||a.parentNode.firstChild!==a&&a.parentNode.lastChild!==a?a.parentNode&&1===a.parentNode.childElementCount&&""===a.parentNode.textContent&&this.removeWithParent(a):this.removeWithParent(a)},removeWithParent:function(a){a&&a.parentNode&&(a.parentNode.parentNode&&1===a.parentNode.childElementCount?a.parentNode.parentNode.removeChild(a.parentNode):a.parentNode.removeChild(a))},cleanupSpans:function(b){var c,d,e,f=b.querySelectorAll(".replace-with"),g=function(a){return a&&"#text"!==a.nodeName&&"false"===a.getAttribute("contenteditable")};for(c=0;c<f.length;c+=1)d=f[c],e=this.document.createElement(d.classList.contains("bold")?"b":"i"),d.classList.contains("bold")&&d.classList.contains("italic")?e.innerHTML="<i>"+d.innerHTML+"</i>":e.innerHTML=d.innerHTML,d.parentNode.replaceChild(e,d);for(f=b.querySelectorAll("span"),c=0;c<f.length;c+=1){if(d=f[c],a.util.traverseUp(d,g))return!1;a.util.unwrap(d,this.document)}}});a.extensions.paste=h}(),function(){var b=a.Extension.extend({name:"placeholder",text:"Type your text",hideOnClick:!0,init:function(){a.Extension.prototype.init.apply(this,arguments),this.initPlaceholders(),this.attachEventHandlers()},initPlaceholders:function(){this.getEditorElements().forEach(this.initElement,this)},handleAddElement:function(a,b){this.initElement(b)},initElement:function(a){a.getAttribute("data-placeholder")||a.setAttribute("data-placeholder",this.text),this.updatePlaceholder(a)},destroy:function(){this.getEditorElements().forEach(this.cleanupElement,this)},handleRemoveElement:function(a,b){this.cleanupElement(b)},cleanupElement:function(a){a.getAttribute("data-placeholder")===this.text&&a.removeAttribute("data-placeholder")},showPlaceholder:function(b){b&&(a.util.isFF&&0===b.childNodes.length?(b.classList.add("medium-editor-placeholder-relative"),b.classList.remove("medium-editor-placeholder")):(b.classList.add("medium-editor-placeholder"),b.classList.remove("medium-editor-placeholder-relative")))},hidePlaceholder:function(a){a&&(a.classList.remove("medium-editor-placeholder"),a.classList.remove("medium-editor-placeholder-relative"))},updatePlaceholder:function(a,b){return a.querySelector("img, blockquote, ul, ol, table")||""!==a.textContent.replace(/^\s+|\s+$/g,"")?this.hidePlaceholder(a):void(b||this.showPlaceholder(a))},attachEventHandlers:function(){this.hideOnClick&&this.subscribe("focus",this.handleFocus.bind(this)),this.subscribe("editableInput",this.handleInput.bind(this)),this.subscribe("blur",this.handleBlur.bind(this)),this.subscribe("addElement",this.handleAddElement.bind(this)),this.subscribe("removeElement",this.handleRemoveElement.bind(this))},handleInput:function(a,b){var c=this.hideOnClick&&b===this.base.getFocusedElement();this.updatePlaceholder(b,c)},handleFocus:function(a,b){this.hidePlaceholder(b)},handleBlur:function(a,b){this.updatePlaceholder(b)}});a.extensions.placeholder=b}(),function(){var b=a.Extension.extend({name:"toolbar",align:"center",allowMultiParagraphSelection:!0,buttons:["bold","italic","underline","anchor","h2","h3","quote"],diffLeft:0,diffTop:-10,firstButtonClass:"medium-editor-button-first",lastButtonClass:"medium-editor-button-last",standardizeSelectionStart:!1,"static":!1,sticky:!1,stickyTopOffset:0,updateOnEmptySelection:!1,relativeContainer:null,init:function(){a.Extension.prototype.init.apply(this,arguments),this.initThrottledMethods(),this.relativeContainer?this.relativeContainer.appendChild(this.getToolbarElement()):this.getEditorOption("elementsContainer").appendChild(this.getToolbarElement())},forEachExtension:function(a,b){return this.base.extensions.forEach(function(c){return c!==this?a.apply(b||this,arguments):void 0},this)},createToolbar:function(){var a=this.document.createElement("div");return a.id="medium-editor-toolbar-"+this.getEditorId(),a.className="medium-editor-toolbar",this["static"]?a.className+=" static-toolbar":this.relativeContainer?a.className+=" medium-editor-relative-toolbar":a.className+=" medium-editor-stalker-toolbar",a.appendChild(this.createToolbarButtons()),this.forEachExtension(function(b){b.hasForm&&a.appendChild(b.getForm())}),this.attachEventHandlers(),a},createToolbarButtons:function(){var b,c,d,e,f,g,h=this.document.createElement("ul");return h.id="medium-editor-toolbar-actions"+this.getEditorId(),h.className="medium-editor-toolbar-actions",h.style.display="block",this.buttons.forEach(function(d){"string"==typeof d?(f=d,g=null):(f=d.name,g=d),e=this.base.addBuiltInExtension(f,g),e&&"function"==typeof e.getButton&&(c=e.getButton(this.base),b=this.document.createElement("li"),a.util.isElement(c)?b.appendChild(c):b.innerHTML=c,h.appendChild(b))},this),d=h.querySelectorAll("button"),d.length>0&&(d[0].classList.add(this.firstButtonClass),d[d.length-1].classList.add(this.lastButtonClass)),h},destroy:function(){this.toolbar&&(this.toolbar.parentNode&&this.toolbar.parentNode.removeChild(this.toolbar),delete this.toolbar)},getInteractionElements:function(){return this.getToolbarElement()},getToolbarElement:function(){return this.toolbar||(this.toolbar=this.createToolbar()),this.toolbar},getToolbarActionsElement:function(){return this.getToolbarElement().querySelector(".medium-editor-toolbar-actions")},initThrottledMethods:function(){this.throttledPositionToolbar=a.util.throttle(function(){this.base.isActive&&this.positionToolbarIfShown()}.bind(this))},attachEventHandlers:function(){this.subscribe("blur",this.handleBlur.bind(this)),this.subscribe("focus",this.handleFocus.bind(this)),this.subscribe("editableClick",this.handleEditableClick.bind(this)),this.subscribe("editableKeyup",this.handleEditableKeyup.bind(this)),this.on(this.document.documentElement,"mouseup",this.handleDocumentMouseup.bind(this)),this["static"]&&this.sticky&&this.on(this.window,"scroll",this.handleWindowScroll.bind(this),!0),this.on(this.window,"resize",this.handleWindowResize.bind(this))},handleWindowScroll:function(){this.positionToolbarIfShown()},handleWindowResize:function(){this.throttledPositionToolbar()},handleDocumentMouseup:function(b){return b&&b.target&&a.util.isDescendant(this.getToolbarElement(),b.target)?!1:void this.checkState()},handleEditableClick:function(){setTimeout(function(){this.checkState()}.bind(this),0)},handleEditableKeyup:function(){this.checkState()},handleBlur:function(){clearTimeout(this.hideTimeout),clearTimeout(this.delayShowTimeout),this.hideTimeout=setTimeout(function(){this.hideToolbar()}.bind(this),1)},handleFocus:function(){this.checkState()},isDisplayed:function(){return this.getToolbarElement().classList.contains("medium-editor-toolbar-active")},showToolbar:function(){clearTimeout(this.hideTimeout),this.isDisplayed()||(this.getToolbarElement().classList.add("medium-editor-toolbar-active"),this.trigger("showToolbar",{},this.base.getFocusedElement()))},hideToolbar:function(){this.isDisplayed()&&(this.getToolbarElement().classList.remove("medium-editor-toolbar-active"),this.trigger("hideToolbar",{},this.base.getFocusedElement()))},isToolbarDefaultActionsDisplayed:function(){return"block"===this.getToolbarActionsElement().style.display},hideToolbarDefaultActions:function(){this.isToolbarDefaultActionsDisplayed()&&(this.getToolbarActionsElement().style.display="none")},showToolbarDefaultActions:function(){this.hideExtensionForms(),this.isToolbarDefaultActionsDisplayed()||(this.getToolbarActionsElement().style.display="block"),this.delayShowTimeout=this.base.delay(function(){this.showToolbar()}.bind(this))},hideExtensionForms:function(){this.forEachExtension(function(a){a.hasForm&&a.isDisplayed()&&a.hideForm()})},multipleBlockElementsSelected:function(){var b=/<[^\/>][^>]*><\/[^>]+>/gim,c=new RegExp("<("+a.util.blockContainerElementNames.join("|")+")[^>]*>","g"),d=a.selection.getSelectionHtml(this.document).replace(b,""),e=d.match(c);return!!e&&e.length>1},modifySelection:function(){var b=this.window.getSelection(),c=b.getRangeAt(0);if(this.standardizeSelectionStart&&c.startContainer.nodeValue&&c.startOffset===c.startContainer.nodeValue.length){var d=a.util.findAdjacentTextNodeWithContent(a.selection.getSelectionElement(this.window),c.startContainer,this.document);if(d){for(var e=0;0===d.nodeValue.substr(e,1).trim().length;)e+=1;c=a.selection.select(this.document,d,e,c.endContainer,c.endOffset)}}},checkState:function(){if(!this.base.preventSelectionUpdates){if(!this.base.getFocusedElement()||a.selection.selectionInContentEditableFalse(this.window))return this.hideToolbar();var b=a.selection.getSelectionElement(this.window);return!b||-1===this.getEditorElements().indexOf(b)||b.getAttribute("data-disable-toolbar")?this.hideToolbar():this.updateOnEmptySelection&&this["static"]?this.showAndUpdateToolbar():!a.selection.selectionContainsContent(this.document)||this.allowMultiParagraphSelection===!1&&this.multipleBlockElementsSelected()?this.hideToolbar():void this.showAndUpdateToolbar()}},showAndUpdateToolbar:function(){this.modifySelection(),this.setToolbarButtonStates(),this.trigger("positionToolbar",{},this.base.getFocusedElement()),this.showToolbarDefaultActions(),this.setToolbarPosition()},setToolbarButtonStates:function(){this.forEachExtension(function(a){"function"==typeof a.isActive&&"function"==typeof a.setInactive&&a.setInactive()}),this.checkActiveButtons()},checkActiveButtons:function(){var b,c=[],d=null,e=a.selection.getSelectionRange(this.document),f=function(a){"function"==typeof a.checkState?a.checkState(b):"function"==typeof a.isActive&&"function"==typeof a.isAlreadyApplied&&"function"==typeof a.setActive&&!a.isActive()&&a.isAlreadyApplied(b)&&a.setActive()};if(e&&(this.forEachExtension(function(a){return"function"==typeof a.queryCommandState&&(d=a.queryCommandState(),null!==d)?void(d&&"function"==typeof a.setActive&&a.setActive()):void c.push(a)}),b=a.selection.getSelectedParentElement(e),this.getEditorElements().some(function(c){return a.util.isDescendant(c,b,!0)})))for(;b&&(c.forEach(f),!a.util.isMediumEditorElement(b));)b=b.parentNode},positionToolbarIfShown:function(){this.isDisplayed()&&this.setToolbarPosition()},setToolbarPosition:function(){var a=this.base.getFocusedElement(),b=this.window.getSelection();return a?void(!this["static"]&&b.isCollapsed||(this.showToolbar(),this.relativeContainer||(this["static"]?this.positionStaticToolbar(a):this.positionToolbar(b)),this.trigger("positionedToolbar",{},this.base.getFocusedElement()))):this},positionStaticToolbar:function(a){this.getToolbarElement().style.left="0";var b,c=this.document.documentElement&&this.document.documentElement.scrollTop||this.document.body.scrollTop,d=this.window.innerWidth,e=this.getToolbarElement(),f=a.getBoundingClientRect(),g=f.top+c,h=f.left+f.width/2,i=e.offsetHeight,j=e.offsetWidth,k=j/2;switch(this.sticky?c>g+a.offsetHeight-i-this.stickyTopOffset?(e.style.top=g+a.offsetHeight-i+"px",e.classList.remove("medium-editor-sticky-toolbar")):c>g-i-this.stickyTopOffset?(e.classList.add("medium-editor-sticky-toolbar"),e.style.top=this.stickyTopOffset+"px"):(e.classList.remove("medium-editor-sticky-toolbar"),e.style.top=g-i+"px"):e.style.top=g-i+"px",this.align){case"left":b=f.left;break;case"right":b=f.right-j;break;case"center":b=h-k}0>b?b=0:b+j>d&&(b=d-Math.ceil(j)-1),e.style.left=b+"px"},positionToolbar:function(a){this.getToolbarElement().style.left="0",this.getToolbarElement().style.right="initial";var b=a.getRangeAt(0),c=b.getBoundingClientRect();(!c||0===c.height&&0===c.width&&b.startContainer===b.endContainer)&&(c=1===b.startContainer.nodeType&&b.startContainer.querySelector("img")?b.startContainer.querySelector("img").getBoundingClientRect():b.startContainer.getBoundingClientRect());var d=this.window.innerWidth,e=(c.left+c.right)/2,f=this.getToolbarElement(),g=f.offsetHeight,h=f.offsetWidth,i=h/2,j=50,k=this.diffLeft-i;c.top<j?(f.classList.add("medium-toolbar-arrow-over"),f.classList.remove("medium-toolbar-arrow-under"),f.style.top=j+c.bottom-this.diffTop+this.window.pageYOffset-g+"px"):(f.classList.add("medium-toolbar-arrow-under"),f.classList.remove("medium-toolbar-arrow-over"),f.style.top=c.top+this.diffTop+this.window.pageYOffset-g+"px"),i>e?(f.style.left=k+i+"px",f.style.right="initial"):i>d-e?(f.style.left="auto",f.style.right=0):(f.style.left=k+e+"px",f.style.right="initial")}});a.extensions.toolbar=b}(),function(){var b=a.Extension.extend({init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableDrag",this.handleDrag.bind(this)),this.subscribe("editableDrop",this.handleDrop.bind(this))},handleDrag:function(a){var b="medium-editor-dragover";a.preventDefault(),a.dataTransfer.dropEffect="copy","dragover"===a.type?a.target.classList.add(b):"dragleave"===a.type&&a.target.classList.remove(b)},handleDrop:function(b){var c,d="medium-editor-dragover";b.preventDefault(),b.stopPropagation(),b.dataTransfer.files&&(c=Array.prototype.slice.call(b.dataTransfer.files,0),c.some(function(b){if(b.type.match("image")){var c,d;c=new FileReader,c.readAsDataURL(b),d="medium-img-"+ +new Date,a.util.insertHTMLCommand(this.document,'<img class="medium-editor-image-loading" id="'+d+'" />'),c.onload=function(){var a=this.document.getElementById(d);a&&(a.removeAttribute("id"),a.removeAttribute("class"),a.src=c.result)}.bind(this)}}.bind(this))),b.target.classList.remove(d)}});a.extensions.imageDragging=b}(),function(){function b(b){var c=a.selection.getSelectionStart(this.options.ownerDocument),d=c.textContent,e=a.selection.getCaretOffsets(c);(void 0===d[e.left-1]||""===d[e.left-1].trim()||void 0!==d[e.left]&&""===d[e.left].trim())&&b.preventDefault()}function c(b,c){if(this.options.disableReturn||c.getAttribute("data-disable-return"))b.preventDefault();else if(this.options.disableDoubleReturn||c.getAttribute("data-disable-double-return")){var d=a.selection.getSelectionStart(this.options.ownerDocument);(d&&""===d.textContent.trim()&&"li"!==d.nodeName.toLowerCase()||d.previousElementSibling&&"br"!==d.previousElementSibling.nodeName.toLowerCase()&&""===d.previousElementSibling.textContent.trim())&&b.preventDefault()}}function d(b){var c=a.selection.getSelectionStart(this.options.ownerDocument),d=c&&c.nodeName.toLowerCase();"pre"===d&&(b.preventDefault(),a.util.insertHTMLCommand(this.options.ownerDocument," ")),a.util.isListItem(c)&&(b.preventDefault(),b.shiftKey?this.options.ownerDocument.execCommand("outdent",!1,null):this.options.ownerDocument.execCommand("indent",!1,null))}function e(b){var c,d=a.selection.getSelectionStart(this.options.ownerDocument),e=d.nodeName.toLowerCase(),f=/^(\s+|<br\/?>)?$/i,g=/h\d/i;a.util.isKey(b,[a.util.keyCode.BACKSPACE,a.util.keyCode.ENTER])&&d.previousElementSibling&&g.test(e)&&0===a.selection.getCaretOffsets(d).left?a.util.isKey(b,a.util.keyCode.BACKSPACE)&&f.test(d.previousElementSibling.innerHTML)?(d.previousElementSibling.parentNode.removeChild(d.previousElementSibling),b.preventDefault()):!this.options.disableDoubleReturn&&a.util.isKey(b,a.util.keyCode.ENTER)&&(c=this.options.ownerDocument.createElement("p"),c.innerHTML="<br>",d.previousElementSibling.parentNode.insertBefore(c,d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.DELETE)&&d.nextElementSibling&&d.previousElementSibling&&!g.test(e)&&f.test(d.innerHTML)&&g.test(d.nextElementSibling.nodeName.toLowerCase())?(a.selection.moveCursor(this.options.ownerDocument,d.nextElementSibling),d.previousElementSibling.parentNode.removeChild(d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.BACKSPACE)&&"li"===e&&f.test(d.innerHTML)&&!d.previousElementSibling&&!d.parentElement.previousElementSibling&&d.nextElementSibling&&"li"===d.nextElementSibling.nodeName.toLowerCase()?(c=this.options.ownerDocument.createElement("p"),c.innerHTML="<br>",d.parentElement.parentElement.insertBefore(c,d.parentElement),a.selection.moveCursor(this.options.ownerDocument,c),d.parentElement.removeChild(d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.BACKSPACE)&&a.util.getClosestTag(d,"blockquote")!==!1&&0===a.selection.getCaretOffsets(d).left&&(b.preventDefault(),a.util.execFormatBlock(this.options.ownerDocument,"p"))}function f(b){var c,d=a.selection.getSelectionStart(this.options.ownerDocument);d&&(a.util.isMediumEditorElement(d)&&0===d.children.length&&!a.util.isBlockContainer(d)&&this.options.ownerDocument.execCommand("formatBlock",!1,"p"),!a.util.isKey(b,a.util.keyCode.ENTER)||a.util.isListItem(d)||a.util.isBlockContainer(d)||(c=d.nodeName.toLowerCase(),"a"===c?this.options.ownerDocument.execCommand("unlink",!1,null):b.shiftKey||b.ctrlKey||this.options.ownerDocument.execCommand("formatBlock",!1,"p")))}function g(a,b){var c=b.parentNode.querySelector('textarea[medium-editor-textarea-id="'+b.getAttribute("medium-editor-textarea-id")+'"]');c&&(c.value=b.innerHTML.trim())}function h(a){a._mediumEditors||(a._mediumEditors=[null]),this.id||(this.id=a._mediumEditors.length),a._mediumEditors[this.id]=this}function i(a){a._mediumEditors&&a._mediumEditors[this.id]&&(a._mediumEditors[this.id]=null)}function j(b,c,d){var e=[];if(b||(b=[]),"string"==typeof b&&(b=c.querySelectorAll(b)),a.util.isElement(b)&&(b=[b]),d)for(var f=0;f<b.length;f++){var g=b[f];!a.util.isElement(g)||g.getAttribute("data-medium-editor-element")||g.getAttribute("medium-editor-textarea-id")||e.push(g)}else e=Array.prototype.slice.apply(b);return e}function k(a){var b=a.parentNode.querySelector('textarea[medium-editor-textarea-id="'+a.getAttribute("medium-editor-textarea-id")+'"]');b&&(b.classList.remove("medium-editor-hidden"),b.removeAttribute("medium-editor-textarea-id")),a.parentNode&&a.parentNode.removeChild(a)}function l(a,b){return Object.keys(b).forEach(function(c){void 0===a[c]&&(a[c]=b[c])}),a}function m(a,b,c){var d={window:c.options.contentWindow,document:c.options.ownerDocument,base:c};return a=l(a,d),"function"==typeof a.init&&a.init(),a.name||(a.name=b),a}function n(){return this.elements.every(function(a){return!!a.getAttribute("data-disable-toolbar")})?!1:this.options.toolbar!==!1}function o(){return n.call(this)?this.options.anchorPreview!==!1:!1}function p(){return this.options.placeholder!==!1}function q(){return this.options.autoLink!==!1}function r(){return this.options.imageDragging!==!1}function s(){return this.options.keyboardCommands!==!1}function t(){return!this.options.extensions.imageDragging}function u(a){for(var b=this.options.ownerDocument.createElement("div"),c=Date.now(),d="medium-editor-"+c,e=a.attributes;this.options.ownerDocument.getElementById(d);)c++,d="medium-editor-"+c;b.className=a.className,b.id=d,b.innerHTML=a.value,a.setAttribute("medium-editor-textarea-id",d);for(var f=0,g=e.length;g>f;f++)b.hasAttribute(e[f].nodeName)||b.setAttribute(e[f].nodeName,e[f].nodeValue);return a.form&&this.on(a.form,"reset",function(a){a.defaultPrevented||this.resetContent(this.options.ownerDocument.getElementById(d))}.bind(this)),a.classList.add("medium-editor-hidden"),a.parentNode.insertBefore(b,a),b}function v(b,d){if(!b.getAttribute("data-medium-editor-element")){"textarea"===b.nodeName.toLowerCase()&&(b=u.call(this,b),this.instanceHandleEditableInput||(this.instanceHandleEditableInput=g.bind(this),this.subscribe("editableInput",this.instanceHandleEditableInput))),this.options.disableEditing||b.getAttribute("data-disable-editing")||(b.setAttribute("contentEditable",!0),b.setAttribute("spellcheck",this.options.spellcheck)),this.instanceHandleEditableKeydownEnter||(b.getAttribute("data-disable-return")||b.getAttribute("data-disable-double-return"))&&(this.instanceHandleEditableKeydownEnter=c.bind(this),this.subscribe("editableKeydownEnter",this.instanceHandleEditableKeydownEnter)),this.options.disableReturn||b.getAttribute("data-disable-return")||this.on(b,"keyup",f.bind(this));var e=a.util.guid();b.setAttribute("data-medium-editor-element",!0),b.classList.add("medium-editor-element"),b.setAttribute("role","textbox"),b.setAttribute("aria-multiline",!0),b.setAttribute("data-medium-editor-editor-index",d),b.setAttribute("medium-editor-index",e),B[e]=b.innerHTML,this.events.attachAllEventsToElement(b)}return b}function w(){this.subscribe("editableKeydownTab",d.bind(this)),this.subscribe("editableKeydownDelete",e.bind(this)),this.subscribe("editableKeydownEnter",e.bind(this)),this.options.disableExtraSpaces&&this.subscribe("editableKeydownSpace",b.bind(this)),this.instanceHandleEditableKeydownEnter||(this.options.disableReturn||this.options.disableDoubleReturn)&&(this.instanceHandleEditableKeydownEnter=c.bind(this),this.subscribe("editableKeydownEnter",this.instanceHandleEditableKeydownEnter))}function x(){if(this.extensions=[],Object.keys(this.options.extensions).forEach(function(a){"toolbar"!==a&&this.options.extensions[a]&&this.extensions.push(m(this.options.extensions[a],a,this))},this),t.call(this)){var b=this.options.fileDragging;b||(b={},r.call(this)||(b.allowedTypes=[])),this.addBuiltInExtension("fileDragging",b)}var c={paste:!0,"anchor-preview":o.call(this),autoLink:q.call(this),keyboardCommands:s.call(this),placeholder:p.call(this)};Object.keys(c).forEach(function(a){c[a]&&this.addBuiltInExtension(a)},this);var d=this.options.extensions.toolbar;if(!d&&n.call(this)){var e=a.util.extend({},this.options.toolbar,{allowMultiParagraphSelection:this.options.allowMultiParagraphSelection});d=new a.extensions.toolbar(e)}d&&this.extensions.push(m(d,"toolbar",this))}function y(b,c){var d=[["allowMultiParagraphSelection","toolbar.allowMultiParagraphSelection"]];return c&&d.forEach(function(b){c.hasOwnProperty(b[0])&&void 0!==c[b[0]]&&a.util.deprecated(b[0],b[1],"v6.0.0")}),a.util.defaults({},c,b)}function z(b,c){var d,e,f=/^append-(.+)$/gi,g=/justify([A-Za-z]*)$/g;if(d=f.exec(b))return a.util.execFormatBlock(this.options.ownerDocument,d[1]);if("fontSize"===b)return c.size&&a.util.deprecated(".size option for fontSize command",".value","6.0.0"),e=c.value||c.size,this.options.ownerDocument.execCommand("fontSize",!1,e);if("fontName"===b)return c.name&&a.util.deprecated(".name option for fontName command",".value","6.0.0"),e=c.value||c.name,this.options.ownerDocument.execCommand("fontName",!1,e);if("createLink"===b)return this.createLink(c);if("image"===b){var h=this.options.contentWindow.getSelection().toString().trim();return this.options.ownerDocument.execCommand("insertImage",!1,h)}if(g.exec(b)){var i=this.options.ownerDocument.execCommand(b,!1,null),j=a.selection.getSelectedParentElement(a.selection.getSelectionRange(this.options.ownerDocument));return j&&A.call(this,a.util.getTopBlockContainer(j)),i}return e=c&&c.value,this.options.ownerDocument.execCommand(b,!1,e)}function A(b){if(b){var c,d=Array.prototype.slice.call(b.childNodes).filter(function(a){var b="div"===a.nodeName.toLowerCase();return b&&!c&&(c=a.style.textAlign),b});d.length&&(this.saveSelection(),d.forEach(function(b){if(b.style.textAlign===c){var d=b.lastChild;if(d){a.util.unwrap(b,this.options.ownerDocument);var e=this.options.ownerDocument.createElement("BR");d.parentNode.insertBefore(e,d.nextSibling)}}},this),b.style.textAlign=c,this.restoreSelection())}}var B={};a.prototype={init:function(a,b){return this.options=y.call(this,this.defaults,b),this.origElements=a,this.options.elementsContainer||(this.options.elementsContainer=this.options.ownerDocument.body),this.setup()},setup:function(){this.isActive||(h.call(this,this.options.contentWindow),this.events=new a.Events(this),this.elements=[],this.addElements(this.origElements),0!==this.elements.length&&(this.isActive=!0,x.call(this),w.call(this)))},destroy:function(){this.isActive&&(this.isActive=!1,this.extensions.forEach(function(a){"function"==typeof a.destroy&&a.destroy()},this),this.events.destroy(),this.elements.forEach(function(a){this.options.spellcheck&&(a.innerHTML=a.innerHTML),a.removeAttribute("contentEditable"),a.removeAttribute("spellcheck"),a.removeAttribute("data-medium-editor-element"),a.classList.remove("medium-editor-element"),a.removeAttribute("role"),a.removeAttribute("aria-multiline"),a.removeAttribute("medium-editor-index"),a.removeAttribute("data-medium-editor-editor-index"),a.getAttribute("medium-editor-textarea-id")&&k(a)},this),this.elements=[],this.instanceHandleEditableKeydownEnter=null,this.instanceHandleEditableInput=null,i.call(this,this.options.contentWindow))},on:function(a,b,c,d){return this.events.attachDOMEvent(a,b,c,d),this},off:function(a,b,c,d){return this.events.detachDOMEvent(a,b,c,d),this},subscribe:function(a,b){return this.events.attachCustomEvent(a,b),this},unsubscribe:function(a,b){return this.events.detachCustomEvent(a,b),this},trigger:function(a,b,c){return this.events.triggerCustomEvent(a,b,c),this},delay:function(a){var b=this;return setTimeout(function(){b.isActive&&a()},this.options.delay)},serialize:function(){var a,b,c={},d=this.elements.length;for(a=0;d>a;a+=1)b=""!==this.elements[a].id?this.elements[a].id:"element-"+a,c[b]={value:this.elements[a].innerHTML.trim()};return c},getExtensionByName:function(a){var b;return this.extensions&&this.extensions.length&&this.extensions.some(function(c){return c.name===a?(b=c,!0):!1}),b},addBuiltInExtension:function(b,c){var d,e=this.getExtensionByName(b);if(e)return e;switch(b){case"anchor":d=a.util.extend({},this.options.anchor,c),e=new a.extensions.anchor(d);break;case"anchor-preview":e=new a.extensions.anchorPreview(this.options.anchorPreview);break;case"autoLink":e=new a.extensions.autoLink;break;case"fileDragging":e=new a.extensions.fileDragging(c);break;case"fontname":e=new a.extensions.fontName(this.options.fontName);break;case"fontsize":e=new a.extensions.fontSize(c);break;case"keyboardCommands":e=new a.extensions.keyboardCommands(this.options.keyboardCommands);break;case"paste":e=new a.extensions.paste(this.options.paste);break;case"placeholder":e=new a.extensions.placeholder(this.options.placeholder);break;default:a.extensions.button.isBuiltInButton(b)&&(c?(d=a.util.defaults({},c,a.extensions.button.prototype.defaults[b]),e=new a.extensions.button(d)):e=new a.extensions.button(b))}return e&&this.extensions.push(m(e,b,this)),e},stopSelectionUpdates:function(){this.preventSelectionUpdates=!0},startSelectionUpdates:function(){this.preventSelectionUpdates=!1},checkSelection:function(){var a=this.getExtensionByName("toolbar");return a&&a.checkState(),this},queryCommandState:function(a){var b,c=/^full-(.+)$/gi,d=null;b=c.exec(a),b&&(a=b[1]);try{d=this.options.ownerDocument.queryCommandState(a)}catch(e){d=null}return d},execAction:function(b,c){var d,e,f=/^full-(.+)$/gi;return d=f.exec(b),d?(this.saveSelection(),this.selectAllContents(),e=z.call(this,d[1],c),this.restoreSelection()):e=z.call(this,b,c),"insertunorderedlist"!==b&&"insertorderedlist"!==b||a.util.cleanListDOM(this.options.ownerDocument,this.getSelectedParentElement()),this.checkSelection(),e},getSelectedParentElement:function(b){return void 0===b&&(b=this.options.contentWindow.getSelection().getRangeAt(0)),a.selection.getSelectedParentElement(b)},selectAllContents:function(){var b=a.selection.getSelectionElement(this.options.contentWindow);if(b){for(;1===b.children.length;)b=b.children[0];
4
+ this.selectElement(b)}},selectElement:function(b){a.selection.selectNode(b,this.options.ownerDocument);var c=a.selection.getSelectionElement(this.options.contentWindow);c&&this.events.focusElement(c)},getFocusedElement:function(){var a;return this.elements.some(function(b){return!a&&b.getAttribute("data-medium-focused")&&(a=b),!!a},this),a},exportSelection:function(){var b=a.selection.getSelectionElement(this.options.contentWindow),c=this.elements.indexOf(b),d=null;return c>=0&&(d=a.selection.exportSelection(b,this.options.ownerDocument)),null!==d&&0!==c&&(d.editableElementIndex=c),d},saveSelection:function(){this.selectionState=this.exportSelection()},importSelection:function(b,c){if(b){var d=this.elements[b.editableElementIndex||0];a.selection.importSelection(b,d,this.options.ownerDocument,c)}},restoreSelection:function(){this.importSelection(this.selectionState)},createLink:function(b){var c,d=a.selection.getSelectionElement(this.options.contentWindow),e={};if(-1!==this.elements.indexOf(d)){try{if(this.events.disableCustomEvent("editableInput"),b.url&&a.util.deprecated(".url option for createLink",".value","6.0.0"),c=b.url||b.value,c&&c.trim().length>0){var f=this.options.contentWindow.getSelection();if(f){var g,h,i,j,k=f.getRangeAt(0),l=k.commonAncestorContainer;if(3===k.endContainer.nodeType&&3!==k.startContainer.nodeType&&0===k.startOffset&&k.startContainer.firstChild===k.endContainer&&(l=k.endContainer),h=a.util.getClosestBlockContainer(k.startContainer),i=a.util.getClosestBlockContainer(k.endContainer),3!==l.nodeType&&0!==l.textContent.length&&h===i){var m=h||d,n=this.options.ownerDocument.createDocumentFragment();this.execAction("unlink"),g=this.exportSelection(),n.appendChild(m.cloneNode(!0)),d===m?a.selection.select(this.options.ownerDocument,m.firstChild,0,m.lastChild,3===m.lastChild.nodeType?m.lastChild.nodeValue.length:m.lastChild.childNodes.length):a.selection.select(this.options.ownerDocument,m,0,m,m.childNodes.length);var o=this.exportSelection();j=a.util.findOrCreateMatchingTextNodes(this.options.ownerDocument,n,{start:g.start-o.start,end:g.end-o.start,editableElementIndex:g.editableElementIndex}),0===j.length&&(n=this.options.ownerDocument.createDocumentFragment(),n.appendChild(l.cloneNode(!0)),j=[n.firstChild.firstChild,n.firstChild.lastChild]),a.util.createLink(this.options.ownerDocument,j,c.trim());var p=(n.firstChild.innerHTML.match(/^\s+/)||[""])[0].length;a.util.insertHTMLCommand(this.options.ownerDocument,n.firstChild.innerHTML.replace(/^\s+/,"")),g.start-=p,g.end-=p,this.importSelection(g)}else this.options.ownerDocument.execCommand("createLink",!1,c);this.options.targetBlank||"_blank"===b.target?a.util.setTargetBlank(a.selection.getSelectionStart(this.options.ownerDocument),c):a.util.removeTargetBlank(a.selection.getSelectionStart(this.options.ownerDocument),c),b.buttonClass&&a.util.addClassToAnchors(a.selection.getSelectionStart(this.options.ownerDocument),b.buttonClass)}}if(this.options.targetBlank||"_blank"===b.target||b.buttonClass){e=this.options.ownerDocument.createEvent("HTMLEvents"),e.initEvent("input",!0,!0,this.options.contentWindow);for(var q=0,r=this.elements.length;r>q;q+=1)this.elements[q].dispatchEvent(e)}}finally{this.events.enableCustomEvent("editableInput")}this.events.triggerCustomEvent("editableInput",e,d)}},cleanPaste:function(a){this.getExtensionByName("paste").cleanPaste(a)},pasteHTML:function(a,b){this.getExtensionByName("paste").pasteHTML(a,b)},setContent:function(a,b){if(b=b||0,this.elements[b]){var c=this.elements[b];c.innerHTML=a,this.checkContentChanged(c)}},getContent:function(a){return a=a||0,this.elements[a]?this.elements[a].innerHTML.trim():null},checkContentChanged:function(b){b=b||a.selection.getSelectionElement(this.options.contentWindow),this.events.updateInput(b,{target:b,currentTarget:b})},resetContent:function(a){if(a){var b=this.elements.indexOf(a);return void(-1!==b&&this.setContent(B[a.getAttribute("medium-editor-index")],b))}this.elements.forEach(function(a,b){this.setContent(B[a.getAttribute("medium-editor-index")],b)},this)},addElements:function(a){var b=j(a,this.options.ownerDocument,!0);return 0===b.length?!1:void b.forEach(function(a){a=v.call(this,a,this.id),this.elements.push(a),this.trigger("addElement",{target:a,currentTarget:a},a)},this)},removeElements:function(a){var b=j(a,this.options.ownerDocument),c=b.map(function(a){return a.getAttribute("medium-editor-textarea-id")&&a.parentNode?a.parentNode.querySelector('div[medium-editor-textarea-id="'+a.getAttribute("medium-editor-textarea-id")+'"]'):a});this.elements=this.elements.filter(function(a){return-1!==c.indexOf(a)?(this.events.cleanupElement(a),a.getAttribute("medium-editor-textarea-id")&&k(a),this.trigger("removeElement",{target:a,currentTarget:a},a),!1):!0},this)}},a.getEditorFromElement=function(a){var b=a.getAttribute("data-medium-editor-editor-index"),c=a&&a.ownerDocument&&(a.ownerDocument.defaultView||a.ownerDocument.parentWindow);return c&&c._mediumEditors&&c._mediumEditors[b]?c._mediumEditors[b]:null}}(),function(){a.prototype.defaults={activeButtonClass:"medium-editor-button-active",buttonLabels:!1,delay:0,disableReturn:!1,disableDoubleReturn:!1,disableExtraSpaces:!1,disableEditing:!1,autoLink:!1,elementsContainer:!1,contentWindow:window,ownerDocument:document,targetBlank:!1,extensions:{},spellcheck:!0}}(),a.parseVersionString=function(a){var b=a.split("-"),c=b[0].split("."),d=b.length>1?b[1]:"";return{major:parseInt(c[0],10),minor:parseInt(c[1],10),revision:parseInt(c[2],10),preRelease:d,toString:function(){return[c[0],c[1],c[2]].join(".")+(d?"-"+d:"")}}},a.version=a.parseVersionString.call(this,{version:"5.21.0"}.version),a}());
vendor/select2/LICENSE.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
vendor/select2/README.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Select2
2
+ =======
3
+ [![Build Status][travis-ci-image]][travis-ci-status]
4
+
5
+ Select2 is a jQuery-based replacement for select boxes. It supports searching,
6
+ remote data sets, and pagination of results.
7
+
8
+ To get started, checkout examples and documentation at
9
+ https://select2.github.io/
10
+
11
+ Use cases
12
+ ---------
13
+ * Enhancing native selects with search.
14
+ * Enhancing native selects with a better multi-select interface.
15
+ * Loading data from JavaScript: easily load items via AJAX and have them
16
+ searchable.
17
+ * Nesting optgroups: native selects only support one level of nesting. Select2
18
+ does not have this restriction.
19
+ * Tagging: ability to add new items on the fly.
20
+ * Working with large, remote datasets: ability to partially load a dataset based
21
+ on the search term.
22
+ * Paging of large datasets: easy support for loading more pages when the results
23
+ are scrolled to the end.
24
+ * Templating: support for custom rendering of results and selections.
25
+
26
+ Browser compatibility
27
+ ---------------------
28
+ * IE 8+
29
+ * Chrome 8+
30
+ * Firefox 10+
31
+ * Safari 3+
32
+ * Opera 10.6+
33
+
34
+ Select2 is automatically tested on the following browsers.
35
+
36
+ [![Sauce Labs Test Status][saucelabs-matrix]][saucelabs-status]
37
+
38
+ Usage
39
+ -----
40
+ You can source Select2 directly from a CDN like [JSDliver][jsdelivr] or
41
+ [CDNJS][cdnjs], [download it from this GitHub repo][releases], or use one of
42
+ the integrations below.
43
+
44
+ Integrations
45
+ ------------
46
+ Third party developers have create plugins for platforms which allow Select2 to be integrated more natively and quickly. For many platforms, additional plugins are not required because Select2 acts as a standard `<select>` box.
47
+
48
+ Plugins
49
+
50
+ * [Django]
51
+ - [django-easy-select2]
52
+ - [django-select2]
53
+ * [Meteor] - [meteor-select2]
54
+ * [Ruby on Rails][ruby-on-rails] - [select2-rails]
55
+ * [Wicket] - [wicketstuff-select2]
56
+ * [Yii 2][yii2] - [yii2-widget-select2]
57
+
58
+ Themes
59
+
60
+ - [Bootstrap 3][bootstrap3] - [select2-bootstrap-theme]
61
+ - [Flat UI][flat-ui] - [select2-flat-theme]
62
+ - [Metro UI][metro-ui] - [select2-metro]
63
+
64
+ Missing an integration? Modify this `README` and make a pull request back here to Select2 on GitHub.
65
+
66
+ Internationalization (i18n)
67
+ ---------------------------
68
+ Select2 supports multiple languages by simply including the right language JS
69
+ file (`dist/js/i18n/it.js`, `dist/js/i18n/nl.js`, etc.) after
70
+ `dist/js/select2.js`.
71
+
72
+ Missing a language? Just copy `src/js/select2/i18n/en.js`, translate it, and
73
+ make a pull request back to Select2 here on GitHub.
74
+
75
+ Documentation
76
+ -------------
77
+ The documentation for Select2 is available
78
+ [through GitHub Pages][documentation] and is located within this repository
79
+ in the [`docs` folder][documentation-folder].
80
+
81
+ Community
82
+ ---------
83
+ You can find out about the different ways to get in touch with the Select2
84
+ community at the [Select2 community page][community].
85
+
86
+ Copyright and license
87
+ ---------------------
88
+ The license is available within the repository in the [LICENSE][license] file.
89
+
90
+ [cdnjs]: http://www.cdnjs.com/libraries/select2
91
+ [community]: https://select2.github.io/community.html
92
+ [documentation]: https://select2.github.io/
93
+ [documentation-folder]: https://github.com/select2/select2/tree/master/docs
94
+ [freenode]: https://freenode.net/
95
+ [jsdelivr]: http://www.jsdelivr.com/#!select2
96
+ [license]: LICENSE.md
97
+ [releases]: https://github.com/select2/select2/releases
98
+ [saucelabs-matrix]: https://saucelabs.com/browser-matrix/select2.svg
99
+ [saucelabs-status]: https://saucelabs.com/u/select2
100
+ [travis-ci-image]: https://img.shields.io/travis/select2/select2/master.svg
101
+ [travis-ci-status]: https://travis-ci.org/select2/select2
102
+
103
+ [bootstrap3]: https://getbootstrap.com/
104
+ [django]: https://www.djangoproject.com/
105
+ [django-easy-select2]: https://github.com/asyncee/django-easy-select2
106
+ [django-select2]: https://github.com/applegrew/django-select2
107
+ [flat-ui]: http://designmodo.github.io/Flat-UI/
108
+ [meteor]: https://www.meteor.com/
109
+ [meteor-select2]: https://github.com/nate-strauser/meteor-select2
110
+ [metro-ui]: http://metroui.org.ua/
111
+ [select2-metro]: http://metroui.org.ua/select2.html
112
+ [ruby-on-rails]: http://rubyonrails.org/
113
+ [select2-bootstrap-theme]: https://github.com/select2/select2-bootstrap-theme
114
+ [select2-flat-theme]: https://github.com/techhysahil/select2-Flat_Theme
115
+ [select2-rails]: https://github.com/argerim/select2-rails
116
+ [vue.js]: http://vuejs.org/
117
+ [select2-vue]: http://vuejs.org/examples/select2.html
118
+ [wicket]: https://wicket.apache.org/
119
+ [wicketstuff-select2]: https://github.com/wicketstuff/core/tree/master/select2-parent
120
+ [yii2]: http://www.yiiframework.com/
121
+ [yii2-widget-select2]: https://github.com/kartik-v/yii2-widget-select2
vendor/select2/css/select2.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .select2_wprm-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2_wprm-container .select2_wprm-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2_wprm-container .select2_wprm-selection--single .select2_wprm-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2_wprm-container .select2_wprm-selection--single .select2_wprm-selection__clear{position:relative}.select2_wprm-container[dir="rtl"] .select2_wprm-selection--single .select2_wprm-selection__rendered{padding-right:8px;padding-left:20px}.select2_wprm-container .select2_wprm-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2_wprm-container .select2_wprm-selection--multiple .select2_wprm-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2_wprm-container .select2_wprm-search--inline{float:left}.select2_wprm-container .select2_wprm-search--inline .select2_wprm-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2_wprm-container .select2_wprm-search--inline .select2_wprm-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2_wprm-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2_wprm-results{display:block}.select2_wprm-results__options{list-style:none;margin:0;padding:0}.select2_wprm-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2_wprm-results__option[aria-selected]{cursor:pointer}.select2_wprm-container--open .select2_wprm-dropdown{left:0}.select2_wprm-container--open .select2_wprm-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2_wprm-container--open .select2_wprm-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2_wprm-search--dropdown{display:block;padding:4px}.select2_wprm-search--dropdown .select2_wprm-search__field{padding:4px;width:100%;box-sizing:border-box}.select2_wprm-search--dropdown .select2_wprm-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2_wprm-search--dropdown.select2_wprm-search--hide{display:none}.select2_wprm-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2_wprm-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2_wprm-container--default .select2_wprm-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2_wprm-container--default .select2_wprm-selection--single .select2_wprm-selection__rendered{color:#444;line-height:28px}.select2_wprm-container--default .select2_wprm-selection--single .select2_wprm-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2_wprm-container--default .select2_wprm-selection--single .select2_wprm-selection__placeholder{color:#999}.select2_wprm-container--default .select2_wprm-selection--single .select2_wprm-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2_wprm-container--default .select2_wprm-selection--single .select2_wprm-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--single .select2_wprm-selection__clear{float:left}.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--single .select2_wprm-selection__arrow{left:1px;right:auto}.select2_wprm-container--default.select2_wprm-container--disabled .select2_wprm-selection--single{background-color:#eee;cursor:default}.select2_wprm-container--default.select2_wprm-container--disabled .select2_wprm-selection--single .select2_wprm-selection__clear{display:none}.select2_wprm-container--default.select2_wprm-container--open .select2_wprm-selection--single .select2_wprm-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2_wprm-container--default .select2_wprm-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__rendered li{list-style:none}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__placeholder{color:#999;margin-top:5px;float:left}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2_wprm-container--default .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove:hover{color:#333}.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice,.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__placeholder,.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-search--inline{float:right}.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice{margin-left:5px;margin-right:auto}.select2_wprm-container--default[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove{margin-left:2px;margin-right:auto}.select2_wprm-container--default.select2_wprm-container--focus .select2_wprm-selection--multiple{border:solid black 1px;outline:0}.select2_wprm-container--default.select2_wprm-container--disabled .select2_wprm-selection--multiple{background-color:#eee;cursor:default}.select2_wprm-container--default.select2_wprm-container--disabled .select2_wprm-selection__choice__remove{display:none}.select2_wprm-container--default.select2_wprm-container--open.select2_wprm-container--above .select2_wprm-selection--single,.select2_wprm-container--default.select2_wprm-container--open.select2_wprm-container--above .select2_wprm-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2_wprm-container--default.select2_wprm-container--open.select2_wprm-container--below .select2_wprm-selection--single,.select2_wprm-container--default.select2_wprm-container--open.select2_wprm-container--below .select2_wprm-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2_wprm-container--default .select2_wprm-search--dropdown .select2_wprm-search__field{border:1px solid #aaa}.select2_wprm-container--default .select2_wprm-search--inline .select2_wprm-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2_wprm-container--default .select2_wprm-results>.select2_wprm-results__options{max-height:200px;overflow-y:auto}.select2_wprm-container--default .select2_wprm-results__option[role=group]{padding:0}.select2_wprm-container--default .select2_wprm-results__option[aria-disabled=true]{color:#999}.select2_wprm-container--default .select2_wprm-results__option[aria-selected=true]{background-color:#ddd}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option{padding-left:1em}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__group{padding-left:0}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option{margin-left:-1em;padding-left:2em}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option{margin-left:-2em;padding-left:3em}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option{margin-left:-3em;padding-left:4em}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option{margin-left:-4em;padding-left:5em}.select2_wprm-container--default .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option .select2_wprm-results__option{margin-left:-5em;padding-left:6em}.select2_wprm-container--default .select2_wprm-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2_wprm-container--default .select2_wprm-results__group{cursor:default;display:block;padding:6px}.select2_wprm-container--classic .select2_wprm-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2_wprm-container--classic .select2_wprm-selection--single:focus{border:1px solid #5897fb}.select2_wprm-container--classic .select2_wprm-selection--single .select2_wprm-selection__rendered{color:#444;line-height:28px}.select2_wprm-container--classic .select2_wprm-selection--single .select2_wprm-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2_wprm-container--classic .select2_wprm-selection--single .select2_wprm-selection__placeholder{color:#999}.select2_wprm-container--classic .select2_wprm-selection--single .select2_wprm-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2_wprm-container--classic .select2_wprm-selection--single .select2_wprm-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2_wprm-container--classic[dir="rtl"] .select2_wprm-selection--single .select2_wprm-selection__clear{float:left}.select2_wprm-container--classic[dir="rtl"] .select2_wprm-selection--single .select2_wprm-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2_wprm-container--classic.select2_wprm-container--open .select2_wprm-selection--single{border:1px solid #5897fb}.select2_wprm-container--classic.select2_wprm-container--open .select2_wprm-selection--single .select2_wprm-selection__arrow{background:transparent;border:none}.select2_wprm-container--classic.select2_wprm-container--open .select2_wprm-selection--single .select2_wprm-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2_wprm-container--classic.select2_wprm-container--open.select2_wprm-container--above .select2_wprm-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2_wprm-container--classic.select2_wprm-container--open.select2_wprm-container--below .select2_wprm-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2_wprm-container--classic .select2_wprm-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2_wprm-container--classic .select2_wprm-selection--multiple:focus{border:1px solid #5897fb}.select2_wprm-container--classic .select2_wprm-selection--multiple .select2_wprm-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2_wprm-container--classic .select2_wprm-selection--multiple .select2_wprm-selection__clear{display:none}.select2_wprm-container--classic .select2_wprm-selection--multiple .select2_wprm-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2_wprm-container--classic .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2_wprm-container--classic .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove:hover{color:#555}.select2_wprm-container--classic[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice{float:right}.select2_wprm-container--classic[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice{margin-left:5px;margin-right:auto}.select2_wprm-container--classic[dir="rtl"] .select2_wprm-selection--multiple .select2_wprm-selection__choice__remove{margin-left:2px;margin-right:auto}.select2_wprm-container--classic.select2_wprm-container--open .select2_wprm-selection--multiple{border:1px solid #5897fb}.select2_wprm-container--classic.select2_wprm-container--open.select2_wprm-container--above .select2_wprm-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2_wprm-container--classic.select2_wprm-container--open.select2_wprm-container--below .select2_wprm-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2_wprm-container--classic .select2_wprm-search--dropdown .select2_wprm-search__field{border:1px solid #aaa;outline:0}.select2_wprm-container--classic .select2_wprm-search--inline .select2_wprm-search__field{outline:0;box-shadow:none}.select2_wprm-container--classic .select2_wprm-dropdown{background-color:#fff;border:1px solid transparent}.select2_wprm-container--classic .select2_wprm-dropdown--above{border-bottom:none}.select2_wprm-container--classic .select2_wprm-dropdown--below{border-top:none}.select2_wprm-container--classic .select2_wprm-results>.select2_wprm-results__options{max-height:200px;overflow-y:auto}.select2_wprm-container--classic .select2_wprm-results__option[role=group]{padding:0}.select2_wprm-container--classic .select2_wprm-results__option[aria-disabled=true]{color:grey}.select2_wprm-container--classic .select2_wprm-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2_wprm-container--classic .select2_wprm-results__group{cursor:default;display:block;padding:6px}.select2_wprm-container--classic.select2_wprm-container--open .select2_wprm-dropdown{border-color:#5897fb}
vendor/select2/js/select2.min.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ /*! select2_wprm 4.0.3 | https://github.com/select2_wprm/select2_wprm/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2_wprm&&a.fn.select2_wprm.amd)var b=a.fn.select2_wprm.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.slice(0,n.length-1).concat(a),k=0;k<a.length;k+=1)if(m=a[k],"."===m)a.splice(k,1),k-=1;else if(".."===m){if(1===k&&(".."===a[2]||".."===a[0]))break;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=v.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),n.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n<c.length;n+=1)if(m=o(c[n],f),k=m.f,"require"===k)u[n]=p.require(a);else if("exports"===k)u[n]=p.exports(a),s=!0;else if("module"===k)h=u[n]=p.module(a);else if(e(q,k)||e(r,k)||e(t,k))u[n]=j(k);else{if(!m.p)throw new Error(a+" missing "+k);m.p.load(m.n,g(f,!0),i(k),{}),u[n]=q[k]}l=d?d.apply(q[a],u):void 0,a&&(h&&h.exports!==b&&h.exports!==q[a]?q[a]=h.exports:l===b&&s||(q[a]=l))}else a&&(q[a]=d)},a=c=n=function(a,c,d,e,f){if("string"==typeof a)return p[a]?p[a](c):j(o(a,c).f);if(!a.splice){if(s=a,s.deps&&n(s.deps,s.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?m(b,a,c,d):setTimeout(function(){m(b,a,c,d)},4),n},n.config=function(a){return n(a)},a._defined=q,d=function(a,b,c){if("string"!=typeof a)throw new Error("See almond README: incorrect module build, no module name");b.splice||(c=b,b=[]),e(q,a)||e(r,a)||(r[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("select2_wprm: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before select2_wprm on your web page."),b}),b.define("select2_wprm/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){var e=b[d];"function"==typeof e&&"constructor"!==d&&c.push(d)}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){var a=Array.prototype.unshift;return a.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};return d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice,c=b.call(arguments,1);this.listeners=this.listeners||{},null==c&&(c=[]),0===c.length&&c.push({}),c[0]._type=a,a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;d>c;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return e!==f||"hidden"!==f&&"visible"!==f?"scroll"===e||"scroll"===f?!0:d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth:!1},c.escapeMarkup=function(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2_wprm/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2_wprm-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" aria-live="assertive" class="select2_wprm-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2_wprm-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2_wprm-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){var c=b.find(".select2_wprm-results");c.append(a)},c.prototype.sort=function(a){var b=this.options.get("sorter");return b(a)},c.prototype.highlightFirstItem=function(){var a=this.$results.find(".select2_wprm-results__option[aria-selected]"),b=a.filter("[aria-selected=true]");b.length>0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2_wprm-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2_wprm-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2_wprm-results__group";a(h);this.template(b,h);for(var i=[],j=0;j<b.children.length;j++){var k=b.children[j],l=this.option(k);i.push(l)}var m=a("<ul></ul>",{"class":"select2_wprm-results__options select2_wprm-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):0>h-g&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2_wprm-results__option--highlighted")}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2_wprm-results__option[aria-selected]",function(b){var c=a(this),e=c.data("data");return"true"===c.attr("aria-selected")?void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{})):void d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2_wprm-results__option[aria-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2_wprm-results__option--highlighted"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2_wprm-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2_wprm/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2_wprm/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('<span class="select2_wprm-selection" role="combobox" aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2_wprm."+b.id,function(b){var c=a(b.target),d=c.closest(".select2_wprm"),e=a(".select2_wprm.select2_wprm-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2_wprm("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2_wprm."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2_wprm/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2_wprm-selection--single"),a.html('<span class="select2_wprm-selection__rendered"></span><span class="select2_wprm-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2_wprm-selection__rendered").attr("id",d),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2_wprm-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},e.prototype.selectionContainer=function(){return a("<span></span>")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2_wprm-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2_wprm/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2_wprm-selection--multiple"),a.html('<ul class="select2_wprm-selection__rendered"></ul>'),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2_wprm-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2_wprm-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},d.prototype.selectionContainer=function(){var b=a('<li class="select2_wprm-selection__choice"><span class="select2_wprm-selection__choice__remove" role="presentation">&times;</span></li>');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.selectionContainer(),g=this.display(e,f);f.append(g),f.prop("title",e.title||e.text),f.data("data",e),b.push(f)}var h=this.$selection.find(".select2_wprm-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2_wprm/selection/placeholder",["../utils"],function(a){function b(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return b.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},b.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2_wprm-selection__placeholder").removeClass("select2_wprm-selection__choice"),c},b.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id,d=b.length>1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2_wprm-selection__rendered").append(e)},b}),b.define("select2_wprm/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("select2_wprm: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2_wprm-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2_wprm-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e<d.length;e++){var f={data:d[e]};if(this.trigger("unselect",f),f.prevented)return}this.$element.val(this.placeholder.id).trigger("change"),this.trigger("toggle",{})}}},c.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||(c.which==b.DELETE||c.which==b.BACKSPACE)&&this._handleClear(c)},c.prototype.update=function(b,c){if(b.call(this,c),!(this.$selection.find(".select2_wprm-selection__placeholder").length>0||0===c.length)){var d=a('<span class="select2_wprm-selection__clear">&times;</span>');d.data("data",c),this.$selection.find(".select2_wprm-selection__rendered").prepend(d)}},c}),b.define("select2_wprm/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2_wprm-search select2_wprm-search--inline"><input class="select2_wprm-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2_wprm-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2_wprm-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2_wprm-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2_wprm-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}});var f=document.documentMode,g=f&&11>=f;this.$selection.on("input.searchcheck",".select2_wprm-search--inline",function(a){return g?void e.$selection.off("input.search input.searchcheck"):void e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2_wprm-search--inline",function(a){if(g&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2_wprm-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2_wprm-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2_wprm/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2_wprm:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2_wprm/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2_wprm/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2_wprm/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2_wprm/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");
2
+ if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple"))return a.selected=!1,c(a.element).is("option")?(a.element.selected=!1,void this.$element.trigger("change")):void this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})},d.prototype.bind=function(a,b){var c=this;this.container=a,a.on("select",function(a){c.select(a.data)}),a.on("unselect",function(a){c.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){c.removeData(this,"data")})},d.prototype.query=function(a,b){var d=[],e=this,f=this.$element.children();f.each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var b;a.children?(b=document.createElement("optgroup"),b.label=a.text):(b=document.createElement("option"),void 0!==b.textContent?b.textContent=a.text:b.innerText=a.text),a.id&&(b.value=a.id),a.disabled&&(b.disabled=!0),a.selected&&(b.selected=!0),a.title&&(b.title=a.title);var d=c(b),e=this._normalizeItem(a);return e.element=b,c.data(b,"data",e),d},d.prototype.item=function(a){var b={};if(b=c.data(a[0],"data"),null!=b)return b;if(a.is("option"))b={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){b={text:a.prop("label"),children:[],title:a.prop("title")};for(var d=a.children("option"),e=[],f=0;f<d.length;f++){var g=c(d[f]),h=this.item(g);e.push(h)}b.children=e}return b=this._normalizeItem(b),b.element=a[0],c.data(a[0],"data",b),b},d.prototype._normalizeItem=function(a){c.isPlainObject(a)||(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){var c=this.options.get("matcher");return c(a,b)},d}),b.define("select2_wprm/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2_wprm/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("select2_wprm: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2_wprm/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h<e.length;h++){var i=e[h],j=this._normalizeItem(i),k=this.option(j);this.$element.append(k)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0),k=i.text===b.term;if(k||j)return f?!1:(a.data=g,void c(a))}if(f)return!0;var l=e.createTag(b);if(null!=l){var m=e.option(l);m.attr("data-select2_wprm-tag",!0),e.addOptions([m]),e.insertTag(g,l)}a.results=g,c(a)}var e=this;return this._removeOldTags(),null==b.term||null!=b.page?void a.call(this,b,c):void a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(b){var c=(this._lastTag,this.$element.find("option[data-select2_wprm-tag]"));c.each(function(){this.selected||a(this).remove()})},b}),b.define("select2_wprm/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2_wprm-search__field")},b.prototype.query=function(b,c,d){function e(b){var c=g._normalizeItem(b),d=g.$element.find("option").filter(function(){return a(this).val()===c.id});if(!d.length){var e=g.option(c);e.attr("data-select2_wprm-tag",!0),g._removeOldTags(),g.addOptions([e])}f(c)}function f(a){g.trigger("select",{data:a})}var g=this;c.term=c.term||"";var h=this.tokenizer(c,this.options,e);h.term!==c.term&&(this.$search.length&&(this.$search.val(h.term),this.$search.focus()),c.term=h.term),b.call(this,c,d)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);null!=m?(e(m),g=g.substr(h+1)||"",h=0):h++}else h++}return{term:g}},b}),b.define("select2_wprm/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",b.term.length<this.minimumInputLength?void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2_wprm/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2_wprm/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2_wprm/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2_wprm-dropdown"><span class="select2_wprm-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2_wprm/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2_wprm-search select2_wprm-search--dropdown"><input class="select2_wprm-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2_wprm-search--hide"):e.$searchContainer.addClass("select2_wprm-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2_wprm/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2_wprm/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="select2_wprm-results__option select2_wprm-results__option--load-more"role="treeitem" aria-disabled="true"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2_wprm/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2_wprm"),b.addClass("select2_wprm-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2_wprm."+d.id,g="resize.select2_wprm."+d.id,h="orientationchange.select2_wprm."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2_wprm-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2_wprm-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2_wprm."+d.id,f="resize.select2_wprm."+d.id,g="orientationchange.select2_wprm."+d.id,h=this.$container.parents().filter(b.hasScroll);h.off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2_wprm-dropdown--above"),d=this.$dropdown.hasClass("select2_wprm-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2_wprm-dropdown--below select2_wprm-dropdown--above").addClass("select2_wprm-dropdown--"+e),this.$container.removeClass("select2_wprm-container--below select2_wprm-container--above").addClass("select2_wprm-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2_wprm/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return a(c.data.results)<this.minimumResultsForSearch?!1:b.call(this,c)},b}),b.define("select2_wprm/dropdown/selectOnClose",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(a){d._handleSelectOnClose(a)})},a.prototype._handleSelectOnClose=function(a,b){if(b&&null!=b.originalselect2_wprmEvent){var c=b.originalselect2_wprmEvent;if("select"===c._type||"unselect"===c._type)return}var d=this.getHighlightedResults();if(!(d.length<1)){var e=d.data("data");null!=e.element&&e.element.selected||null==e.element&&e.selected||this.trigger("select",{data:e})}},a}),b.define("select2_wprm/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&c.ctrlKey||this.trigger("close",{originalEvent:c,originalselect2_wprmEvent:b})},a}),b.define("select2_wprm/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(a){var b=a.input.length-a.maximum,c="Please delete "+b+" character";return 1!=b&&(c+="s"),c},inputTooShort:function(a){var b=a.minimum-a.input.length,c="Please enter "+b+" or more characters";return c},loadingMore:function(){return"Loading more results…"},maximumSelected:function(a){var b="You can only select "+a.maximum+" item";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),b.define("select2_wprm/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}D.prototype.apply=function(l){if(l=a.extend(!0,{},this.defaults,l),null==l.dataAdapter){if(null!=l.ajax?l.dataAdapter=o:null!=l.data?l.dataAdapter=n:l.dataAdapter=m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(O){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(P){l.debug&&window.console&&console.warn&&console.warn('select2_wprm: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var Q=k.loadPath(this.defaults.amdLanguageBase+"en"),R=new k(l.language);R.extend(Q),l.translations=R}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2_wprm/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2_wprm"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2_wprmTags")&&(this.options.debug&&window.console&&console.warn&&console.warn('select2_wprm: The `data-select2_wprm-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of select2_wprm.'),a.data("data",a.data("select2_wprmTags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("select2_wprm: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of select2_wprm."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2_wprm/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2_wprm")&&a.data("select2_wprm").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2_wprm-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2_wprm",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2_wprm-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2_wprm",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2_wprm",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2_wprm-container--open")}),this.on("close",function(){a.$container.removeClass("select2_wprm-container--open")}),this.on("enable",function(){a.$container.removeClass("select2_wprm-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2_wprm-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2_wprm-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e<b.addedNodes.length;e++){var f=b.addedNodes[e];f.selected&&(c=!0)}else b.removedNodes&&b.removedNodes.length>0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2_wprm-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2_wprm-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2_wprm-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('select2_wprm: The `select2_wprm("enable")` method has been deprecated and will be removed in later select2_wprm versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('select2_wprm: Data can no longer be set using `select2_wprm("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('select2_wprm: The `select2_wprm("val")` method has been deprecated and will be removed in later select2_wprm versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2_wprm"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2_wprm-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2_wprm"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null;
3
+ },e.prototype.render=function(){var b=a('<span class="select2_wprm select2_wprm-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2_wprm-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2_wprm",["jquery","jquery-mousewheel","./select2_wprm/core","./select2_wprm/defaults"],function(a,b,c,d){if(null==a.fn.select2_wprm){var e=["open","close","destroy"];a.fn.select2_wprm=function(b){if(b=b||{},"object"==typeof b)return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,f=Array.prototype.slice.call(arguments,1);return this.each(function(){var c=a(this).data("select2_wprm");null==c&&window.console&&console.error&&console.error("The select2_wprm('"+b+"') method was called on an element that is not using select2_wprm."),d=c[b].apply(c,f)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for select2_wprm: "+b)}}return null==a.fn.select2_wprm.defaults&&(a.fn.select2_wprm.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2_wprm");return a.fn.select2_wprm.amd=b,c});
vendor/simple_html_dom/simple_html_dom.php ADDED
@@ -0,0 +1,1721 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Website: http://sourceforge.net/projects/simplehtmldom/
4
+ * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/)
5
+ * Contributions by:
6
+ * Yousuke Kumakura (Attribute filters)
7
+ * Vadim Voituk (Negative indexes supports of "find" method)
8
+ * Antcs (Constructor with automatically load contents either text or file/url)
9
+ *
10
+ * all affected sections have comments starting with "PaperG"
11
+ *
12
+ * Paperg - Added case insensitive testing of the value of the selector.
13
+ * Paperg - Added tag_start for the starting index of tags - NOTE: This works but not accurately.
14
+ * This tag_start gets counted AFTER \r\n have been crushed out, and after the remove_noice calls so it will not reflect the REAL position of the tag in the source,
15
+ * it will almost always be smaller by some amount.
16
+ * We use this to determine how far into the file the tag in question is. This "percentage will never be accurate as the $dom->size is the "real" number of bytes the dom was created from.
17
+ * but for most purposes, it's a really good estimation.
18
+ * Paperg - Added the forceTagsClosed to the dom constructor. Forcing tags closed is great for malformed html, but it CAN lead to parsing errors.
19
+ * Allow the user to tell us how much they trust the html.
20
+ * Paperg add the text and plaintext to the selectors for the find syntax. plaintext implies text in the innertext of a node. text implies that the tag is a text node.
21
+ * This allows for us to find tags based on the text they contain.
22
+ * Create find_ancestor_tag to see if a tag is - at any level - inside of another specific tag.
23
+ * Paperg: added parse_charset so that we know about the character set of the source document.
24
+ * NOTE: If the user's system has a routine called get_last_retrieve_url_contents_content_type availalbe, we will assume it's returning the content-type header from the
25
+ * last transfer or curl_exec, and we will parse that and use it in preference to any other method of charset detection.
26
+ *
27
+ * Found infinite loop in the case of broken html in restore_noise. Rewrote to protect from that.
28
+ * PaperG (John Schlick) Added get_display_size for "IMG" tags.
29
+ *
30
+ * Licensed under The MIT License
31
+ * Redistributions of files must retain the above copyright notice.
32
+ *
33
+ * @author S.C. Chen <me578022@gmail.com>
34
+ * @author John Schlick
35
+ * @author Rus Carroll
36
+ * @version 1.5 ($Rev: 196 $)
37
+ * @package PlaceLocalInclude
38
+ * @subpackage simple_html_dom
39
+ */
40
+
41
+ /**
42
+ * All of the Defines for the classes below.
43
+ * @author S.C. Chen <me578022@gmail.com>
44
+ */
45
+ define('HDOM_TYPE_ELEMENT', 1);
46
+ define('HDOM_TYPE_COMMENT', 2);
47
+ define('HDOM_TYPE_TEXT', 3);
48
+ define('HDOM_TYPE_ENDTAG', 4);
49
+ define('HDOM_TYPE_ROOT', 5);
50
+ define('HDOM_TYPE_UNKNOWN', 6);
51
+ define('HDOM_QUOTE_DOUBLE', 0);
52
+ define('HDOM_QUOTE_SINGLE', 1);
53
+ define('HDOM_QUOTE_NO', 3);
54
+ define('HDOM_INFO_BEGIN', 0);
55
+ define('HDOM_INFO_END', 1);
56
+ define('HDOM_INFO_QUOTE', 2);
57
+ define('HDOM_INFO_SPACE', 3);
58
+ define('HDOM_INFO_TEXT', 4);
59
+ define('HDOM_INFO_INNER', 5);
60
+ define('HDOM_INFO_OUTER', 6);
61
+ define('HDOM_INFO_ENDSPACE',7);
62
+ define('DEFAULT_TARGET_CHARSET', 'UTF-8');
63
+ define('DEFAULT_BR_TEXT', "\r\n");
64
+ define('DEFAULT_SPAN_TEXT', " ");
65
+ define('MAX_FILE_SIZE', 600000);
66
+ // helper functions
67
+ // -----------------------------------------------------------------------------
68
+ // get html dom from file
69
+ // $maxlen is defined in the code as PHP_STREAM_COPY_ALL which is defined as -1.
70
+ function file_get_html($url, $use_include_path = false, $context=null, $offset = -1, $maxLen=-1, $lowercase = true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
71
+ {
72
+ // We DO force the tags to be terminated.
73
+ $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText);
74
+ // For sourceforge users: uncomment the next line and comment the retreive_url_contents line 2 lines down if it is not already done.
75
+ $contents = file_get_contents($url, $use_include_path, $context, $offset);
76
+ // Paperg - use our own mechanism for getting the contents as we want to control the timeout.
77
+ //$contents = retrieve_url_contents($url);
78
+ if (empty($contents) || strlen($contents) > MAX_FILE_SIZE)
79
+ {
80
+ return false;
81
+ }
82
+ // The second parameter can force the selectors to all be lowercase.
83
+ $dom->load($contents, $lowercase, $stripRN);
84
+ return $dom;
85
+ }
86
+
87
+ // get html dom from string
88
+ function str_get_html($str, $lowercase=true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
89
+ {
90
+ $dom = new simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText);
91
+ if (empty($str) || strlen($str) > MAX_FILE_SIZE)
92
+ {
93
+ $dom->clear();
94
+ return false;
95
+ }
96
+ $dom->load($str, $lowercase, $stripRN);
97
+ return $dom;
98
+ }
99
+
100
+ // dump html dom tree
101
+ function dump_html_tree($node, $show_attr=true, $deep=0)
102
+ {
103
+ $node->dump($node);
104
+ }
105
+
106
+
107
+ /**
108
+ * simple html dom node
109
+ * PaperG - added ability for "find" routine to lowercase the value of the selector.
110
+ * PaperG - added $tag_start to track the start position of the tag in the total byte index
111
+ *
112
+ * @package PlaceLocalInclude
113
+ */
114
+ class simple_html_dom_node
115
+ {
116
+ public $nodetype = HDOM_TYPE_TEXT;
117
+ public $tag = 'text';
118
+ public $attr = array();
119
+ public $children = array();
120
+ public $nodes = array();
121
+ public $parent = null;
122
+ // The "info" array - see HDOM_INFO_... for what each element contains.
123
+ public $_ = array();
124
+ public $tag_start = 0;
125
+ private $dom = null;
126
+
127
+ function __construct($dom)
128
+ {
129
+ $this->dom = $dom;
130
+ $dom->nodes[] = $this;
131
+ }
132
+
133
+ function __destruct()
134
+ {
135
+ $this->clear();
136
+ }
137
+
138
+ function __toString()
139
+ {
140
+ return $this->outertext();
141
+ }
142
+
143
+ // clean up memory due to php5 circular references memory leak...
144
+ function clear()
145
+ {
146
+ $this->dom = null;
147
+ $this->nodes = null;
148
+ $this->parent = null;
149
+ $this->children = null;
150
+ }
151
+
152
+ // dump node's tree
153
+ function dump($show_attr=true, $deep=0)
154
+ {
155
+ $lead = str_repeat(' ', $deep);
156
+
157
+ echo $lead.$this->tag;
158
+ if ($show_attr && count($this->attr)>0)
159
+ {
160
+ echo '(';
161
+ foreach ($this->attr as $k=>$v)
162
+ echo "[$k]=>\"".$this->$k.'", ';
163
+ echo ')';
164
+ }
165
+ echo "\n";
166
+
167
+ if ($this->nodes)
168
+ {
169
+ foreach ($this->nodes as $c)
170
+ {
171
+ $c->dump($show_attr, $deep+1);
172
+ }
173
+ }
174
+ }
175
+
176
+
177
+ // Debugging function to dump a single dom node with a bunch of information about it.
178
+ function dump_node($echo=true)
179
+ {
180
+
181
+ $string = $this->tag;
182
+ if (count($this->attr)>0)
183
+ {
184
+ $string .= '(';
185
+ foreach ($this->attr as $k=>$v)
186
+ {
187
+ $string .= "[$k]=>\"".$this->$k.'", ';
188
+ }
189
+ $string .= ')';
190
+ }
191
+ if (count($this->_)>0)
192
+ {
193
+ $string .= ' $_ (';
194
+ foreach ($this->_ as $k=>$v)
195
+ {
196
+ if (is_array($v))
197
+ {
198
+ $string .= "[$k]=>(";
199
+ foreach ($v as $k2=>$v2)
200
+ {
201
+ $string .= "[$k2]=>\"".$v2.'", ';
202
+ }
203
+ $string .= ")";
204
+ } else {
205
+ $string .= "[$k]=>\"".$v.'", ';
206
+ }
207
+ }
208
+ $string .= ")";
209
+ }
210
+
211
+ if (isset($this->text))
212
+ {
213
+ $string .= " text: (" . $this->text . ")";
214
+ }
215
+
216
+ $string .= " HDOM_INNER_INFO: '";
217
+ if (isset($node->_[HDOM_INFO_INNER]))
218
+ {
219
+ $string .= $node->_[HDOM_INFO_INNER] . "'";
220
+ }
221
+ else
222
+ {
223
+ $string .= ' NULL ';
224
+ }
225
+
226
+ $string .= " children: " . count($this->children);
227
+ $string .= " nodes: " . count($this->nodes);
228
+ $string .= " tag_start: " . $this->tag_start;
229
+ $string .= "\n";
230
+
231
+ if ($echo)
232
+ {
233
+ echo $string;
234
+ return;
235
+ }
236
+ else
237
+ {
238
+ return $string;
239
+ }
240
+ }
241
+
242
+ // returns the parent of node
243
+ // If a node is passed in, it will reset the parent of the current node to that one.
244
+ function parent($parent=null)
245
+ {
246
+ // I am SURE that this doesn't work properly.
247
+ // It fails to unset the current node from it's current parents nodes or children list first.
248
+ if ($parent !== null)
249
+ {
250
+ $this->parent = $parent;
251
+ $this->parent->nodes[] = $this;
252
+ $this->parent->children[] = $this;
253
+ }
254
+
255
+ return $this->parent;
256
+ }
257
+
258
+ // verify that node has children
259
+ function has_child()
260
+ {
261
+ return !empty($this->children);
262
+ }
263
+
264
+ // returns children of node
265
+ function children($idx=-1)
266
+ {
267
+ if ($idx===-1)
268
+ {
269
+ return $this->children;
270
+ }
271
+ if (isset($this->children[$idx])) return $this->children[$idx];
272
+ return null;
273
+ }
274
+
275
+ // returns the first child of node
276
+ function first_child()
277
+ {
278
+ if (count($this->children)>0)
279
+ {
280
+ return $this->children[0];
281
+ }
282
+ return null;
283
+ }
284
+
285
+ // returns the last child of node
286
+ function last_child()
287
+ {
288
+ if (($count=count($this->children))>0)
289
+ {
290
+ return $this->children[$count-1];
291
+ }
292
+ return null;
293
+ }
294
+
295
+ // returns the next sibling of node
296
+ function next_sibling()
297
+ {
298
+ if ($this->parent===null)
299
+ {
300
+ return null;
301
+ }
302
+
303
+ $idx = 0;
304
+ $count = count($this->parent->children);
305
+ while ($idx<$count && $this!==$this->parent->children[$idx])
306
+ {
307
+ ++$idx;
308
+ }
309
+ if (++$idx>=$count)
310
+ {
311
+ return null;
312
+ }
313
+ return $this->parent->children[$idx];
314
+ }
315
+
316
+ // returns the previous sibling of node
317
+ function prev_sibling()
318
+ {
319
+ if ($this->parent===null) return null;
320
+ $idx = 0;
321
+ $count = count($this->parent->children);
322
+ while ($idx<$count && $this!==$this->parent->children[$idx])
323
+ ++$idx;
324
+ if (--$idx<0) return null;
325
+ return $this->parent->children[$idx];
326
+ }
327
+
328
+ // function to locate a specific ancestor tag in the path to the root.
329
+ function find_ancestor_tag($tag)
330
+ {
331
+ global $debugObject;
332
+ if (is_object($debugObject)) { $debugObject->debugLogEntry(1); }
333
+
334
+ // Start by including ourselves in the comparison.
335
+ $returnDom = $this;
336
+
337
+ while (!is_null($returnDom))
338
+ {
339
+ if (is_object($debugObject)) { $debugObject->debugLog(2, "Current tag is: " . $returnDom->tag); }
340
+
341
+ if ($returnDom->tag == $tag)
342
+ {
343
+ break;
344
+ }
345
+ $returnDom = $returnDom->parent;
346
+ }
347
+ return $returnDom;
348
+ }
349
+
350
+ // get dom node's inner html
351
+ function innertext()
352
+ {
353
+ if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
354
+ if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
355
+
356
+ $ret = '';
357
+ foreach ($this->nodes as $n)
358
+ $ret .= $n->outertext();
359
+ return $ret;
360
+ }
361
+
362
+ // get dom node's outer text (with tag)
363
+ function outertext()
364
+ {
365
+ global $debugObject;
366
+ if (is_object($debugObject))
367
+ {
368
+ $text = '';
369
+ if ($this->tag == 'text')
370
+ {
371
+ if (!empty($this->text))
372
+ {
373
+ $text = " with text: " . $this->text;
374
+ }
375
+ }
376
+ $debugObject->debugLog(1, 'Innertext of tag: ' . $this->tag . $text);
377
+ }
378
+
379
+ if ($this->tag==='root') return $this->innertext();
380
+
381
+ // trigger callback
382
+ if ($this->dom && $this->dom->callback!==null)
383
+ {
384
+ call_user_func_array($this->dom->callback, array($this));
385
+ }
386
+
387
+ if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER];
388
+ if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
389
+
390
+ // render begin tag
391
+ if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]])
392
+ {
393
+ $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup();
394
+ } else {
395
+ $ret = "";
396
+ }
397
+
398
+ // render inner text
399
+ if (isset($this->_[HDOM_INFO_INNER]))
400
+ {
401
+ // If it's a br tag... don't return the HDOM_INNER_INFO that we may or may not have added.
402
+ if ($this->tag != "br")
403
+ {
404
+ $ret .= $this->_[HDOM_INFO_INNER];
405
+ }
406
+ } else {
407
+ if ($this->nodes)
408
+ {
409
+ foreach ($this->nodes as $n)
410
+ {
411
+ $ret .= $this->convert_text($n->outertext());
412
+ }
413
+ }
414
+ }
415
+
416
+ // render end tag
417
+ if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END]!=0)
418
+ $ret .= '</'.$this->tag.'>';
419
+ return $ret;
420
+ }
421
+
422
+ // get dom node's plain text
423
+ function text()
424
+ {
425
+ if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
426
+ switch ($this->nodetype)
427
+ {
428
+ case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
429
+ case HDOM_TYPE_COMMENT: return '';
430
+ case HDOM_TYPE_UNKNOWN: return '';
431
+ }
432
+ if (strcasecmp($this->tag, 'script')===0) return '';
433
+ if (strcasecmp($this->tag, 'style')===0) return '';
434
+
435
+ $ret = '';
436
+ // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed for some span tags, and some p tags) $this->nodes is set to NULL.
437
+ // NOTE: This indicates that there is a problem where it's set to NULL without a clear happening.
438
+ // WHY is this happening?
439
+ if (!is_null($this->nodes))
440
+ {
441
+ foreach ($this->nodes as $n)
442
+ {
443
+ $ret .= $this->convert_text($n->text());
444
+ }
445
+
446
+ // If this node is a span... add a space at the end of it so multiple spans don't run into each other. This is plaintext after all.
447
+ if ($this->tag == "span")
448
+ {
449
+ $ret .= $this->dom->default_span_text;
450
+ }
451
+
452
+
453
+ }
454
+ return $ret;
455
+ }
456
+
457
+ function xmltext()
458
+ {
459
+ $ret = $this->innertext();
460
+ $ret = str_ireplace('<![CDATA[', '', $ret);
461
+ $ret = str_replace(']]>', '', $ret);
462
+ return $ret;
463
+ }
464
+
465
+ // build node's text with tag
466
+ function makeup()
467
+ {
468
+ // text, comment, unknown
469
+ if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
470
+
471
+ $ret = '<'.$this->tag;
472
+ $i = -1;
473
+
474
+ foreach ($this->attr as $key=>$val)
475
+ {
476
+ ++$i;
477
+
478
+ // skip removed attribute
479
+ if ($val===null || $val===false)
480
+ continue;
481
+
482
+ $ret .= $this->_[HDOM_INFO_SPACE][$i][0];
483
+ //no value attr: nowrap, checked selected...
484
+ if ($val===true)
485
+ $ret .= $key;
486
+ else {
487
+ switch ($this->_[HDOM_INFO_QUOTE][$i])
488
+ {
489
+ case HDOM_QUOTE_DOUBLE: $quote = '"'; break;
490
+ case HDOM_QUOTE_SINGLE: $quote = '\''; break;
491
+ default: $quote = '';
492
+ }
493
+ $ret .= $key.$this->_[HDOM_INFO_SPACE][$i][1].'='.$this->_[HDOM_INFO_SPACE][$i][2].$quote.$val.$quote;
494
+ }
495
+ }
496
+ $ret = $this->dom->restore_noise($ret);
497
+ return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>';
498
+ }
499
+
500
+ // find elements by css selector
501
+ //PaperG - added ability for find to lowercase the value of the selector.
502
+ function find($selector, $idx=null, $lowercase=false)
503
+ {
504
+ $selectors = $this->parse_selector($selector);
505
+ if (($count=count($selectors))===0) return array();
506
+ $found_keys = array();
507
+
508
+ // find each selector
509
+ for ($c=0; $c<$count; ++$c)
510
+ {
511
+ // The change on the below line was documented on the sourceforge code tracker id 2788009
512
+ // used to be: if (($levle=count($selectors[0]))===0) return array();
513
+ if (($levle=count($selectors[$c]))===0) return array();
514
+ if (!isset($this->_[HDOM_INFO_BEGIN])) return array();
515
+
516
+ $head = array($this->_[HDOM_INFO_BEGIN]=>1);
517
+
518
+ // handle descendant selectors, no recursive!
519
+ for ($l=0; $l<$levle; ++$l)
520
+ {
521
+ $ret = array();
522
+ foreach ($head as $k=>$v)
523
+ {
524
+ $n = ($k===-1) ? $this->dom->root : $this->dom->nodes[$k];
525
+ //PaperG - Pass this optional parameter on to the seek function.
526
+ $n->seek($selectors[$c][$l], $ret, $lowercase);
527
+ }
528
+ $head = $ret;
529
+ }
530
+
531
+ foreach ($head as $k=>$v)
532
+ {
533
+ if (!isset($found_keys[$k]))
534
+ $found_keys[$k] = 1;
535
+ }
536
+ }
537
+
538
+ // sort keys
539
+ ksort($found_keys);
540
+
541
+ $found = array();
542
+ foreach ($found_keys as $k=>$v)
543
+ $found[] = $this->dom->nodes[$k];
544
+
545
+ // return nth-element or array
546
+ if (is_null($idx)) return $found;
547
+ else if ($idx<0) $idx = count($found) + $idx;
548
+ return (isset($found[$idx])) ? $found[$idx] : null;
549
+ }
550
+
551
+ // seek for given conditions
552
+ // PaperG - added parameter to allow for case insensitive testing of the value of a selector.
553
+ protected function seek($selector, &$ret, $lowercase=false)
554
+ {
555
+ global $debugObject;
556
+ if (is_object($debugObject)) { $debugObject->debugLogEntry(1); }
557
+
558
+ list($tag, $key, $val, $exp, $no_key) = $selector;
559
+
560
+ // xpath index
561
+ if ($tag && $key && is_numeric($key))
562
+ {
563
+ $count = 0;
564
+ foreach ($this->children as $c)
565
+ {
566
+ if ($tag==='*' || $tag===$c->tag) {
567
+ if (++$count==$key) {
568
+ $ret[$c->_[HDOM_INFO_BEGIN]] = 1;
569
+ return;
570
+ }
571
+ }
572
+ }
573
+ return;
574
+ }
575
+
576
+ $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0;
577
+ if ($end==0) {
578
+ $parent = $this->parent;
579
+ while (!isset($parent->_[HDOM_INFO_END]) && $parent!==null) {
580
+ $end -= 1;
581
+ $parent = $parent->parent;
582
+ }
583
+ $end += $parent->_[HDOM_INFO_END];
584
+ }
585
+
586
+ for ($i=$this->_[HDOM_INFO_BEGIN]+1; $i<$end; ++$i) {
587
+ $node = $this->dom->nodes[$i];
588
+
589
+ $pass = true;
590
+
591
+ if ($tag==='*' && !$key) {
592
+ if (in_array($node, $this->children, true))
593
+ $ret[$i] = 1;
594
+ continue;
595
+ }
596
+
597
+ // compare tag
598
+ if ($tag && $tag!=$node->tag && $tag!=='*') {$pass=false;}
599
+ // compare key
600
+ if ($pass && $key) {
601
+ if ($no_key) {
602
+ if (isset($node->attr[$key])) $pass=false;
603
+ } else {
604
+ if (($key != "plaintext") && !isset($node->attr[$key])) $pass=false;
605
+ }
606
+ }
607
+ // compare value
608
+ if ($pass && $key && $val && $val!=='*') {
609
+ // If they have told us that this is a "plaintext" search then we want the plaintext of the node - right?
610
+ if ($key == "plaintext") {
611
+ // $node->plaintext actually returns $node->text();
612
+ $nodeKeyValue = $node->text();
613
+ } else {
614
+ // this is a normal search, we want the value of that attribute of the tag.
615
+ $nodeKeyValue = $node->attr[$key];
616
+ }
617
+ if (is_object($debugObject)) {$debugObject->debugLog(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue);}
618
+
619
+ //PaperG - If lowercase is set, do a case insensitive test of the value of the selector.
620
+ if ($lowercase) {
621
+ $check = $this->match($exp, strtolower($val), strtolower($nodeKeyValue));
622
+ } else {
623
+ $check = $this->match($exp, $val, $nodeKeyValue);
624
+ }
625
+ if (is_object($debugObject)) {$debugObject->debugLog(2, "after match: " . ($check ? "true" : "false"));}
626
+
627
+ // handle multiple class
628
+ if (!$check && strcasecmp($key, 'class')===0) {
629
+ foreach (explode(' ',$node->attr[$key]) as $k) {
630
+ // Without this, there were cases where leading, trailing, or double spaces lead to our comparing blanks - bad form.
631
+ if (!empty($k)) {
632
+ if ($lowercase) {
633
+ $check = $this->match($exp, strtolower($val), strtolower($k));
634
+ } else {
635
+ $check = $this->match($exp, $val, $k);
636
+ }
637
+ if ($check) break;
638
+ }
639
+ }
640
+ }
641
+ if (!$check) $pass = false;
642
+ }
643
+ if ($pass) $ret[$i] = 1;
644
+ unset($node);
645
+ }
646
+ // It's passed by reference so this is actually what this function returns.
647
+ if (is_object($debugObject)) {$debugObject->debugLog(1, "EXIT - ret: ", $ret);}
648
+ }
649
+
650
+ protected function match($exp, $pattern, $value) {
651
+ global $debugObject;
652
+ if (is_object($debugObject)) {$debugObject->debugLogEntry(1);}
653
+
654
+ switch ($exp) {
655
+ case '=':
656
+ return ($value===$pattern);
657
+ case '!=':
658
+ return ($value!==$pattern);
659
+ case '^=':
660
+ return preg_match("/^".preg_quote($pattern,'/')."/", $value);
661
+ case '$=':
662
+ return preg_match("/".preg_quote($pattern,'/')."$/", $value);
663
+ case '*=':
664
+ if ($pattern[0]=='/') {
665
+ return preg_match($pattern, $value);
666
+ }
667
+ return preg_match("/".$pattern."/i", $value);
668
+ }
669
+ return false;
670
+ }
671
+
672
+ protected function parse_selector($selector_string) {
673
+ global $debugObject;
674
+ if (is_object($debugObject)) {$debugObject->debugLogEntry(1);}
675
+
676
+ // pattern of CSS selectors, modified from mootools
677
+ // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does.
678
+ // Note: if you try to look at this attribute, yo MUST use getAttribute since $dom->x:y will fail the php syntax check.
679
+ // Notice the \[ starting the attbute? and the @? following? This implies that an attribute can begin with an @ sign that is not captured.
680
+ // This implies that an html attribute specifier may start with an @ sign that is NOT captured by the expression.
681
+ // farther study is required to determine of this should be documented or removed.
682
+ // $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
683
+ $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
684
+ preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER);
685
+ if (is_object($debugObject)) {$debugObject->debugLog(2, "Matches Array: ", $matches);}
686
+
687
+ $selectors = array();
688
+ $result = array();
689
+ //print_r($matches);
690
+
691
+ foreach ($matches as $m) {
692
+ $m[0] = trim($m[0]);
693
+ if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue;
694
+ // for browser generated xpath
695
+ if ($m[1]==='tbody') continue;
696
+
697
+ list($tag, $key, $val, $exp, $no_key) = array($m[1], null, null, '=', false);
698
+ if (!empty($m[2])) {$key='id'; $val=$m[2];}
699
+ if (!empty($m[3])) {$key='class'; $val=$m[3];}
700
+ if (!empty($m[4])) {$key=$m[4];}
701
+ if (!empty($m[5])) {$exp=$m[5];}
702
+ if (!empty($m[6])) {$val=$m[6];}
703
+
704
+ // convert to lowercase
705
+ if ($this->dom->lowercase) {$tag=strtolower($tag); $key=strtolower($key);}
706
+ //elements that do NOT have the specified attribute
707
+ if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;}
708
+
709
+ $result[] = array($tag, $key, $val, $exp, $no_key);
710
+ if (trim($m[7])===',') {
711
+ $selectors[] = $result;
712
+ $result = array();
713
+ }
714
+ }
715
+ if (count($result)>0)
716
+ $selectors[] = $result;
717
+ return $selectors;
718
+ }
719
+
720
+ function __get($name) {
721
+ if (isset($this->attr[$name]))
722
+ {
723
+ return $this->convert_text($this->attr[$name]);
724
+ }
725
+ switch ($name) {
726
+ case 'outertext': return $this->outertext();
727
+ case 'innertext': return $this->innertext();
728
+ case 'plaintext': return $this->text();
729
+ case 'xmltext': return $this->xmltext();
730
+ default: return array_key_exists($name, $this->attr);
731
+ }
732
+ }
733
+
734
+ function __set($name, $value) {
735
+ switch ($name) {
736
+ case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value;
737
+ case 'innertext':
738
+ if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value;
739
+ return $this->_[HDOM_INFO_INNER] = $value;
740
+ }
741
+ if (!isset($this->attr[$name])) {
742
+ $this->_[HDOM_INFO_SPACE][] = array(' ', '', '');
743
+ $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
744
+ }
745
+ $this->attr[$name] = $value;
746
+ }
747
+
748
+ function __isset($name) {
749
+ switch ($name) {
750
+ case 'outertext': return true;
751
+ case 'innertext': return true;
752
+ case 'plaintext': return true;
753
+ }
754
+ //no value attr: nowrap, checked selected...
755
+ return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]);
756
+ }
757
+
758
+ function __unset($name) {
759
+ if (isset($this->attr[$name]))
760
+ unset($this->attr[$name]);
761
+ }
762
+
763
+ // PaperG - Function to convert the text from one character set to another if the two sets are not the same.
764
+ function convert_text($text)
765
+ {
766
+ global $debugObject;
767
+ if (is_object($debugObject)) {$debugObject->debugLogEntry(1);}
768
+
769
+ $converted_text = $text;
770
+
771
+ $sourceCharset = "";
772
+ $targetCharset = "";
773
+
774
+ if ($this->dom)
775
+ {
776
+ $sourceCharset = strtoupper($this->dom->_charset);
777
+ $targetCharset = strtoupper($this->dom->_target_charset);
778
+ }
779
+ if (is_object($debugObject)) {$debugObject->debugLog(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset);}
780
+
781
+ if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0))
782
+ {
783
+ // Check if the reported encoding could have been incorrect and the text is actually already UTF-8
784
+ if ((strcasecmp($targetCharset, 'UTF-8') == 0) && ($this->is_utf8($text)))
785
+ {
786
+ $converted_text = $text;
787
+ }
788
+ else
789
+ {
790
+ $converted_text = iconv($sourceCharset, $targetCharset, $text);
791
+ }
792
+ }
793
+
794
+ // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output.
795
+ if ($targetCharset == 'UTF-8')
796
+ {
797
+ if (substr($converted_text, 0, 3) == "\xef\xbb\xbf")
798
+ {
799
+ $converted_text = substr($converted_text, 3);
800
+ }
801
+ if (substr($converted_text, -3) == "\xef\xbb\xbf")
802
+ {
803
+ $converted_text = substr($converted_text, 0, -3);
804
+ }
805
+ }
806
+
807
+ return $converted_text;
808
+ }
809
+
810
+ /**
811
+ * Returns true if $string is valid UTF-8 and false otherwise.
812
+ *
813
+ * @param mixed $str String to be tested
814
+ * @return boolean
815
+ */
816
+ static function is_utf8($str)
817
+ {
818
+ $c=0; $b=0;
819
+ $bits=0;
820
+ $len=strlen($str);
821
+ for($i=0; $i<$len; $i++)
822
+ {
823
+ $c=ord($str[$i]);
824
+ if($c > 128)
825
+ {
826
+ if(($c >= 254)) return false;
827
+ elseif($c >= 252) $bits=6;
828
+ elseif($c >= 248) $bits=5;
829
+ elseif($c >= 240) $bits=4;
830
+ elseif($c >= 224) $bits=3;
831
+ elseif($c >= 192) $bits=2;
832
+ else return false;
833
+ if(($i+$bits) > $len) return false;
834
+ while($bits > 1)
835
+ {
836
+ $i++;
837
+ $b=ord($str[$i]);
838
+ if($b < 128 || $b > 191) return false;
839
+ $bits--;
840
+ }
841
+ }
842
+ }
843
+ return true;
844
+ }
845
+ /*
846
+ function is_utf8($string)
847
+ {
848
+ //this is buggy
849
+ return (utf8_encode(utf8_decode($string)) == $string);
850
+ }
851
+ */
852
+
853
+ /**
854
+ * Function to try a few tricks to determine the displayed size of an img on the page.
855
+ * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types.
856
+ *
857
+ * @author John Schlick
858
+ * @version April 19 2012
859
+ * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out.
860
+ */
861
+ function get_display_size()
862
+ {
863
+ global $debugObject;
864
+
865
+ $width = -1;
866
+ $height = -1;
867
+
868
+ if ($this->tag !== 'img')
869
+ {
870
+ return false;
871
+ }
872
+
873
+ // See if there is aheight or width attribute in the tag itself.
874
+ if (isset($this->attr['width']))
875
+ {
876
+ $width = $this->attr['width'];
877
+ }
878
+
879
+ if (isset($this->attr['height']))
880
+ {
881
+ $height = $this->attr['height'];
882
+ }
883
+
884
+ // Now look for an inline style.
885
+ if (isset($this->attr['style']))
886
+ {
887
+ // Thanks to user gnarf from stackoverflow for this regular expression.
888
+ $attributes = array();
889
+ preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->attr['style'], $matches, PREG_SET_ORDER);
890
+ foreach ($matches as $match) {
891
+ $attributes[$match[1]] = $match[2];
892
+ }
893
+
894
+ // If there is a width in the style attributes:
895
+ if (isset($attributes['width']) && $width == -1)
896
+ {
897
+ // check that the last two characters are px (pixels)
898
+ if (strtolower(substr($attributes['width'], -2)) == 'px')
899
+ {
900
+ $proposed_width = substr($attributes['width'], 0, -2);
901
+ // Now make sure that it's an integer and not something stupid.
902
+ if (filter_var($proposed_width, FILTER_VALIDATE_INT))
903
+ {
904
+ $width = $proposed_width;
905
+ }
906
+ }
907
+ }
908
+
909
+ // If there is a width in the style attributes:
910
+ if (isset($attributes['height']) && $height == -1)
911
+ {
912
+ // check that the last two characters are px (pixels)
913
+ if (strtolower(substr($attributes['height'], -2)) == 'px')
914
+ {
915
+ $proposed_height = substr($attributes['height'], 0, -2);
916
+ // Now make sure that it's an integer and not something stupid.
917
+ if (filter_var($proposed_height, FILTER_VALIDATE_INT))
918
+ {
919
+ $height = $proposed_height;
920
+ }
921
+ }
922
+ }
923
+
924
+ }
925
+
926
+ // Future enhancement:
927
+ // Look in the tag to see if there is a class or id specified that has a height or width attribute to it.
928
+
929
+ // Far future enhancement
930
+ // Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width
931
+ // Note that in this case, the class or id will have the img subselector for it to apply to the image.
932
+
933
+ // ridiculously far future development
934
+ // If the class or id is specified in a SEPARATE css file thats not on the page, go get it and do what we were just doing for the ones on the page.
935
+
936
+ $result = array('height' => $height,
937
+ 'width' => $width);
938
+ return $result;
939
+ }
940
+
941
+ // camel naming conventions
942
+ function getAllAttributes() {return $this->attr;}
943
+ function getAttribute($name) {return $this->__get($name);}
944
+ function setAttribute($name, $value) {$this->__set($name, $value);}
945
+ function hasAttribute($name) {return $this->__isset($name);}
946
+ function removeAttribute($name) {$this->__set($name, null);}
947
+ function getElementById($id) {return $this->find("#$id", 0);}
948
+ function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);}
949
+ function getElementByTagName($name) {return $this->find($name, 0);}
950
+ function getElementsByTagName($name, $idx=null) {return $this->find($name, $idx);}
951
+ function parentNode() {return $this->parent();}
952
+ function childNodes($idx=-1) {return $this->children($idx);}
953
+ function firstChild() {return $this->first_child();}
954
+ function lastChild() {return $this->last_child();}
955
+ function nextSibling() {return $this->next_sibling();}
956
+ function previousSibling() {return $this->prev_sibling();}
957
+ function hasChildNodes() {return $this->has_child();}
958
+ function nodeName() {return $this->tag;}
959
+ function appendChild($node) {$node->parent($this); return $node;}
960
+
961
+ }
962
+
963
+ /**
964
+ * simple html dom parser
965
+ * Paperg - in the find routine: allow us to specify that we want case insensitive testing of the value of the selector.
966
+ * Paperg - change $size from protected to public so we can easily access it
967
+ * Paperg - added ForceTagsClosed in the constructor which tells us whether we trust the html or not. Default is to NOT trust it.
968
+ *
969
+ * @package PlaceLocalInclude
970
+ */
971
+ class simple_html_dom
972
+ {
973
+ public $root = null;
974
+ public $nodes = array();
975
+ public $callback = null;
976
+ public $lowercase = false;
977
+ // Used to keep track of how large the text was when we started.
978
+ public $original_size;
979
+ public $size;
980
+ protected $pos;
981
+ protected $doc;
982
+ protected $char;
983
+ protected $cursor;
984
+ protected $parent;
985
+ protected $noise = array();
986
+ protected $token_blank = " \t\r\n";
987
+ protected $token_equal = ' =/>';
988
+ protected $token_slash = " />\r\n\t";
989
+ protected $token_attr = ' >';
990
+ // Note that this is referenced by a child node, and so it needs to be public for that node to see this information.
991
+ public $_charset = '';
992
+ public $_target_charset = '';
993
+ protected $default_br_text = "";
994
+ public $default_span_text = "";
995
+
996
+ // use isset instead of in_array, performance boost about 30%...
997
+ protected $self_closing_tags = array('img'=>1, 'br'=>1, 'input'=>1, 'meta'=>1, 'link'=>1, 'hr'=>1, 'base'=>1, 'embed'=>1, 'spacer'=>1);
998
+ protected $block_tags = array('root'=>1, 'body'=>1, 'form'=>1, 'div'=>1, 'span'=>1, 'table'=>1);
999
+ // Known sourceforge issue #2977341
1000
+ // B tags that are not closed cause us to return everything to the end of the document.
1001
+ protected $optional_closing_tags = array(
1002
+ 'tr'=>array('tr'=>1, 'td'=>1, 'th'=>1),
1003
+ 'th'=>array('th'=>1),
1004
+ 'td'=>array('td'=>1),
1005
+ 'li'=>array('li'=>1),
1006
+ 'dt'=>array('dt'=>1, 'dd'=>1),
1007
+ 'dd'=>array('dd'=>1, 'dt'=>1),
1008
+ 'dl'=>array('dd'=>1, 'dt'=>1),
1009
+ 'p'=>array('p'=>1),
1010
+ 'nobr'=>array('nobr'=>1),
1011
+ 'b'=>array('b'=>1),
1012
+ 'option'=>array('option'=>1),
1013
+ );
1014
+
1015
+ function __construct($str=null, $lowercase=true, $forceTagsClosed=true, $target_charset=DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
1016
+ {
1017
+ if ($str)
1018
+ {
1019
+ if (preg_match("/^http:\/\//i",$str) || is_file($str))
1020
+ {
1021
+ $this->load_file($str);
1022
+ }
1023
+ else
1024
+ {
1025
+ $this->load($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText);
1026
+ }
1027
+ }
1028
+ // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html.
1029
+ if (!$forceTagsClosed) {
1030
+ $this->optional_closing_array=array();
1031
+ }
1032
+ $this->_target_charset = $target_charset;
1033
+ }
1034
+
1035
+ function __destruct()
1036
+ {
1037
+ $this->clear();
1038
+ }
1039
+
1040
+ // load html from string
1041
+ function load($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
1042
+ {
1043
+ global $debugObject;
1044
+
1045
+ // prepare
1046
+ $this->prepare($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText);
1047
+ // strip out comments
1048
+ $this->remove_noise("'<!--(.*?)-->'is");
1049
+ // strip out cdata
1050
+ $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true);
1051
+ // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037
1052
+ // Script tags removal now preceeds style tag removal.
1053
+ // strip out <script> tags
1054
+ $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is");
1055
+ $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is");
1056
+ // strip out <style> tags
1057
+ $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is");
1058
+ $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is");
1059
+ // strip out preformatted tags
1060
+ $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is");
1061
+ // strip out server side scripts
1062
+ $this->remove_noise("'(<\?)(.*?)(\?>)'s", true);
1063
+ // strip smarty scripts
1064
+ $this->remove_noise("'(\{\w)(.*?)(\})'s", true);
1065
+
1066
+ // parsing
1067
+ while ($this->parse());
1068
+ // end
1069
+ $this->root->_[HDOM_INFO_END] = $this->cursor;
1070
+ $this->parse_charset();
1071
+
1072
+ // make load function chainable
1073
+ return $this;
1074
+
1075
+ }
1076
+
1077
+ // load html from file
1078
+ function load_file()
1079
+ {
1080
+ $args = func_get_args();
1081
+ $this->load(call_user_func_array('file_get_contents', $args), true);
1082
+ // Throw an error if we can't properly load the dom.
1083
+ if (($error=error_get_last())!==null) {
1084
+ $this->clear();
1085
+ return false;
1086
+ }
1087
+ }
1088
+
1089
+ // set callback function
1090
+ function set_callback($function_name)
1091
+ {
1092
+ $this->callback = $function_name;
1093
+ }
1094
+
1095
+ // remove callback function
1096
+ function remove_callback()
1097
+ {
1098
+ $this->callback = null;
1099
+ }
1100
+
1101
+ // save dom as string
1102
+ function save($filepath='')
1103
+ {
1104
+ $ret = $this->root->innertext();
1105
+ if ($filepath!=='') file_put_contents($filepath, $ret, LOCK_EX);
1106
+ return $ret;
1107
+ }
1108
+
1109
+ // find dom node by css selector
1110
+ // Paperg - allow us to specify that we want case insensitive testing of the value of the selector.
1111
+ function find($selector, $idx=null, $lowercase=false)
1112
+ {
1113
+ return $this->root->find($selector, $idx, $lowercase);
1114
+ }
1115
+
1116
+ // clean up memory due to php5 circular references memory leak...
1117
+ function clear()
1118
+ {
1119
+ foreach ($this->nodes as $n) {$n->clear(); $n = null;}
1120
+ // This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
1121
+ if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}
1122
+ if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
1123
+ if (isset($this->root)) {$this->root->clear(); unset($this->root);}
1124
+ unset($this->doc);
1125
+ unset($this->noise);
1126
+ }
1127
+
1128
+ function dump($show_attr=true)
1129
+ {
1130
+ $this->root->dump($show_attr);
1131
+ }
1132
+
1133
+ // prepare HTML data and init everything
1134
+ protected function prepare($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
1135
+ {
1136
+ $this->clear();
1137
+
1138
+ // set the length of content before we do anything to it.
1139
+ $this->size = strlen($str);
1140
+ // Save the original size of the html that we got in. It might be useful to someone.
1141
+ $this->original_size = $this->size;
1142
+
1143
+ //before we save the string as the doc... strip out the \r \n's if we are told to.
1144
+ if ($stripRN) {
1145
+ $str = str_replace("\r", " ", $str);
1146
+ $str = str_replace("\n", " ", $str);
1147
+
1148
+ // set the length of content since we have changed it.
1149
+ $this->size = strlen($str);
1150
+ }
1151
+
1152
+ $this->doc = $str;
1153
+ $this->pos = 0;
1154
+ $this->cursor = 1;
1155
+ $this->noise = array();
1156
+ $this->nodes = array();
1157
+ $this->lowercase = $lowercase;
1158
+ $this->default_br_text = $defaultBRText;
1159
+ $this->default_span_text = $defaultSpanText;
1160
+ $this->root = new simple_html_dom_node($this);
1161
+ $this->root->tag = 'root';
1162
+ $this->root->_[HDOM_INFO_BEGIN] = -1;
1163
+ $this->root->nodetype = HDOM_TYPE_ROOT;
1164
+ $this->parent = $this->root;
1165
+ if ($this->size>0) $this->char = $this->doc[0];
1166
+ }
1167
+
1168
+ // parse html content
1169
+ protected function parse()
1170
+ {
1171
+ if (($s = $this->copy_until_char('<'))==='')
1172
+ {
1173
+ return $this->read_tag();
1174
+ }
1175
+
1176
+ // text
1177
+ $node = new simple_html_dom_node($this);
1178
+ ++$this->cursor;
1179
+ $node->_[HDOM_INFO_TEXT] = $s;
1180
+ $this->link_nodes($node, false);
1181
+ return true;
1182
+ }
1183
+
1184
+ // PAPERG - dkchou - added this to try to identify the character set of the page we have just parsed so we know better how to spit it out later.
1185
+ // NOTE: IF you provide a routine called get_last_retrieve_url_contents_content_type which returns the CURLINFO_CONTENT_TYPE from the last curl_exec
1186
+ // (or the content_type header from the last transfer), we will parse THAT, and if a charset is specified, we will use it over any other mechanism.
1187
+ protected function parse_charset()
1188
+ {
1189
+ global $debugObject;
1190
+
1191
+ $charset = null;
1192
+
1193
+ if (function_exists('get_last_retrieve_url_contents_content_type'))
1194
+ {
1195
+ $contentTypeHeader = get_last_retrieve_url_contents_content_type();
1196
+ $success = preg_match('/charset=(.+)/', $contentTypeHeader, $matches);
1197
+ if ($success)
1198
+ {
1199
+ $charset = $matches[1];
1200
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'header content-type found charset of: ' . $charset);}
1201
+ }
1202
+
1203
+ }
1204
+
1205
+ if (empty($charset))
1206
+ {
1207
+ $el = $this->root->find('meta[http-equiv=Content-Type]',0);
1208
+ if (!empty($el))
1209
+ {
1210
+ $fullvalue = $el->content;
1211
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'meta content-type tag found' . $fullvalue);}
1212
+
1213
+ if (!empty($fullvalue))
1214
+ {
1215
+ $success = preg_match('/charset=(.+)/', $fullvalue, $matches);
1216
+ if ($success)
1217
+ {
1218
+ $charset = $matches[1];
1219
+ }
1220
+ else
1221
+ {
1222
+ // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1
1223
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'meta content-type tag couldn\'t be parsed. using iso-8859 default.');}
1224
+ $charset = 'ISO-8859-1';
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // If we couldn't find a charset above, then lets try to detect one based on the text we got...
1231
+ if (empty($charset))
1232
+ {
1233
+ // Have php try to detect the encoding from the text given to us.
1234
+ $charset = mb_detect_encoding($this->root->plaintext . "ascii", $encoding_list = array( "UTF-8", "CP1252" ) );
1235
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'mb_detect found: ' . $charset);}
1236
+
1237
+ // and if this doesn't work... then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need...
1238
+ if ($charset === false)
1239
+ {
1240
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'since mb_detect failed - using default of utf-8');}
1241
+ $charset = 'UTF-8';
1242
+ }
1243
+ }
1244
+
1245
+ // Since CP1252 is a superset, if we get one of it's subsets, we want it instead.
1246
+ if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1')))
1247
+ {
1248
+ if (is_object($debugObject)) {$debugObject->debugLog(2, 'replacing ' . $charset . ' with CP1252 as its a superset');}
1249
+ $charset = 'CP1252';
1250
+ }
1251
+
1252
+ if (is_object($debugObject)) {$debugObject->debugLog(1, 'EXIT - ' . $charset);}
1253
+
1254
+ return $this->_charset = $charset;
1255
+ }
1256
+
1257
+ // read tag info
1258
+ protected function read_tag()
1259
+ {
1260
+ if ($this->char!=='<')
1261
+ {
1262
+ $this->root->_[HDOM_INFO_END] = $this->cursor;
1263
+ return false;
1264
+ }
1265
+ $begin_tag_pos = $this->pos;
1266
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1267
+
1268
+ // end tag
1269
+ if ($this->char==='/')
1270
+ {
1271
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1272
+ // This represents the change in the simple_html_dom trunk from revision 180 to 181.
1273
+ // $this->skip($this->token_blank_t);
1274
+ $this->skip($this->token_blank);
1275
+ $tag = $this->copy_until_char('>');
1276
+
1277
+ // skip attributes in end tag
1278
+ if (($pos = strpos($tag, ' '))!==false)
1279
+ $tag = substr($tag, 0, $pos);
1280
+
1281
+ $parent_lower = strtolower($this->parent->tag);
1282
+ $tag_lower = strtolower($tag);
1283
+
1284
+ if ($parent_lower!==$tag_lower)
1285
+ {
1286
+ if (isset($this->optional_closing_tags[$parent_lower]) && isset($this->block_tags[$tag_lower]))
1287
+ {
1288
+ $this->parent->_[HDOM_INFO_END] = 0;
1289
+ $org_parent = $this->parent;
1290
+
1291
+ while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower)
1292
+ $this->parent = $this->parent->parent;
1293
+
1294
+ if (strtolower($this->parent->tag)!==$tag_lower) {
1295
+ $this->parent = $org_parent; // restore origonal parent
1296
+ if ($this->parent->parent) $this->parent = $this->parent->parent;
1297
+ $this->parent->_[HDOM_INFO_END] = $this->cursor;
1298
+ return $this->as_text_node($tag);
1299
+ }
1300
+ }
1301
+ else if (($this->parent->parent) && isset($this->block_tags[$tag_lower]))
1302
+ {
1303
+ $this->parent->_[HDOM_INFO_END] = 0;
1304
+ $org_parent = $this->parent;
1305
+
1306
+ while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower)
1307
+ $this->parent = $this->parent->parent;
1308
+
1309
+ if (strtolower($this->parent->tag)!==$tag_lower)
1310
+ {
1311
+ $this->parent = $org_parent; // restore origonal parent
1312
+ $this->parent->_[HDOM_INFO_END] = $this->cursor;
1313
+ return $this->as_text_node($tag);
1314
+ }
1315
+ }
1316
+ else if (($this->parent->parent) && strtolower($this->parent->parent->tag)===$tag_lower)
1317
+ {
1318
+ $this->parent->_[HDOM_INFO_END] = 0;
1319
+ $this->parent = $this->parent->parent;
1320
+ }
1321
+ else
1322
+ return $this->as_text_node($tag);
1323
+ }
1324
+
1325
+ $this->parent->_[HDOM_INFO_END] = $this->cursor;
1326
+ if ($this->parent->parent) $this->parent = $this->parent->parent;
1327
+
1328
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1329
+ return true;
1330
+ }
1331
+
1332
+ $node = new simple_html_dom_node($this);
1333
+ $node->_[HDOM_INFO_BEGIN] = $this->cursor;
1334
+ ++$this->cursor;
1335
+ $tag = $this->copy_until($this->token_slash);
1336
+ $node->tag_start = $begin_tag_pos;
1337
+
1338
+ // doctype, cdata & comments...
1339
+ if (isset($tag[0]) && $tag[0]==='!') {
1340
+ $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until_char('>');
1341
+
1342
+ if (isset($tag[2]) && $tag[1]==='-' && $tag[2]==='-') {
1343
+ $node->nodetype = HDOM_TYPE_COMMENT;
1344
+ $node->tag = 'comment';
1345
+ } else {
1346
+ $node->nodetype = HDOM_TYPE_UNKNOWN;
1347
+ $node->tag = 'unknown';
1348
+ }
1349
+ if ($this->char==='>') $node->_[HDOM_INFO_TEXT].='>';
1350
+ $this->link_nodes($node, true);
1351
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1352
+ return true;
1353
+ }
1354
+
1355
+ // text
1356
+ if ($pos=strpos($tag, '<')!==false) {
1357
+ $tag = '<' . substr($tag, 0, -1);
1358
+ $node->_[HDOM_INFO_TEXT] = $tag;
1359
+ $this->link_nodes($node, false);
1360
+ $this->char = $this->doc[--$this->pos]; // prev
1361
+ return true;
1362
+ }
1363
+
1364
+ if (!preg_match("/^[\w-:]+$/", $tag)) {
1365
+ $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until('<>');
1366
+ if ($this->char==='<') {
1367
+ $this->link_nodes($node, false);
1368
+ return true;
1369
+ }
1370
+
1371
+ if ($this->char==='>') $node->_[HDOM_INFO_TEXT].='>';
1372
+ $this->link_nodes($node, false);
1373
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1374
+ return true;
1375
+ }
1376
+
1377
+ // begin tag
1378
+ $node->nodetype = HDOM_TYPE_ELEMENT;
1379
+ $tag_lower = strtolower($tag);
1380
+ $node->tag = ($this->lowercase) ? $tag_lower : $tag;
1381
+
1382
+ // handle optional closing tags
1383
+ if (isset($this->optional_closing_tags[$tag_lower]) )
1384
+ {
1385
+ while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)]))
1386
+ {
1387
+ $this->parent->_[HDOM_INFO_END] = 0;
1388
+ $this->parent = $this->parent->parent;
1389
+ }
1390
+ $node->parent = $this->parent;
1391
+ }
1392
+
1393
+ $guard = 0; // prevent infinity loop
1394
+ $space = array($this->copy_skip($this->token_blank), '', '');
1395
+
1396
+ // attributes
1397
+ do
1398
+ {
1399
+ if ($this->char!==null && $space[0]==='')
1400
+ {
1401
+ break;
1402
+ }
1403
+ $name = $this->copy_until($this->token_equal);
1404
+ if ($guard===$this->pos)
1405
+ {
1406
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1407
+ continue;
1408
+ }
1409
+ $guard = $this->pos;
1410
+
1411
+ // handle endless '<'
1412
+ if ($this->pos>=$this->size-1 && $this->char!=='>') {
1413
+ $node->nodetype = HDOM_TYPE_TEXT;
1414
+ $node->_[HDOM_INFO_END] = 0;
1415
+ $node->_[HDOM_INFO_TEXT] = '<'.$tag . $space[0] . $name;
1416
+ $node->tag = 'text';
1417
+ $this->link_nodes($node, false);
1418
+ return true;
1419
+ }
1420
+
1421
+ // handle mismatch '<'
1422
+ if ($this->doc[$this->pos-1]=='<') {
1423
+ $node->nodetype = HDOM_TYPE_TEXT;
1424
+ $node->tag = 'text';
1425
+ $node->attr = array();
1426
+ $node->_[HDOM_INFO_END] = 0;
1427
+ $node->_[HDOM_INFO_TEXT] = substr($this->doc, $begin_tag_pos, $this->pos-$begin_tag_pos-1);
1428
+ $this->pos -= 2;
1429
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1430
+ $this->link_nodes($node, false);
1431
+ return true;
1432
+ }
1433
+
1434
+ if ($name!=='/' && $name!=='') {
1435
+ $space[1] = $this->copy_skip($this->token_blank);
1436
+ $name = $this->restore_noise($name);
1437
+ if ($this->lowercase) $name = strtolower($name);
1438
+ if ($this->char==='=') {
1439
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1440
+ $this->parse_attr($node, $name, $space);
1441
+ }
1442
+ else {
1443
+ //no value attr: nowrap, checked selected...
1444
+ $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO;
1445
+ $node->attr[$name] = true;
1446
+ if ($this->char!='>') $this->char = $this->doc[--$this->pos]; // prev
1447
+ }
1448
+ $node->_[HDOM_INFO_SPACE][] = $space;
1449
+ $space = array($this->copy_skip($this->token_blank), '', '');
1450
+ }
1451
+ else
1452
+ break;
1453
+ } while ($this->char!=='>' && $this->char!=='/');
1454
+
1455
+ $this->link_nodes($node, true);
1456
+ $node->_[HDOM_INFO_ENDSPACE] = $space[0];
1457
+
1458
+ // check self closing
1459
+ if ($this->copy_until_char_escape('>')==='/')
1460
+ {
1461
+ $node->_[HDOM_INFO_ENDSPACE] .= '/';
1462
+ $node->_[HDOM_INFO_END] = 0;
1463
+ }
1464
+ else
1465
+ {
1466
+ // reset parent
1467
+ if (!isset($this->self_closing_tags[strtolower($node->tag)])) $this->parent = $node;
1468
+ }
1469
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1470
+
1471
+ // If it's a BR tag, we need to set it's text to the default text.
1472
+ // This way when we see it in plaintext, we can generate formatting that the user wants.
1473
+ // since a br tag never has sub nodes, this works well.
1474
+ if ($node->tag == "br")
1475
+ {
1476
+ $node->_[HDOM_INFO_INNER] = $this->default_br_text;
1477
+ }
1478
+
1479
+ return true;
1480
+ }
1481
+
1482
+ // parse attributes
1483
+ protected function parse_attr($node, $name, &$space)
1484
+ {
1485
+ // Per sourceforge: http://sourceforge.net/tracker/?func=detail&aid=3061408&group_id=218559&atid=1044037
1486
+ // If the attribute is already defined inside a tag, only pay atetntion to the first one as opposed to the last one.
1487
+ if (isset($node->attr[$name]))
1488
+ {
1489
+ return;
1490
+ }
1491
+
1492
+ $space[2] = $this->copy_skip($this->token_blank);
1493
+ switch ($this->char) {
1494
+ case '"':
1495
+ $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
1496
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1497
+ $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('"'));
1498
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1499
+ break;
1500
+ case '\'':
1501
+ $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_SINGLE;
1502
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1503
+ $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('\''));
1504
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1505
+ break;
1506
+ default:
1507
+ $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO;
1508
+ $node->attr[$name] = $this->restore_noise($this->copy_until($this->token_attr));
1509
+ }
1510
+ // PaperG: Attributes should not have \r or \n in them, that counts as html whitespace.
1511
+ $node->attr[$name] = str_replace("\r", "", $node->attr[$name]);
1512
+ $node->attr[$name] = str_replace("\n", "", $node->attr[$name]);
1513
+ // PaperG: If this is a "class" selector, lets get rid of the preceeding and trailing space since some people leave it in the multi class case.
1514
+ if ($name == "class") {
1515
+ $node->attr[$name] = trim($node->attr[$name]);
1516
+ }
1517
+ }
1518
+
1519
+ // link node's parent
1520
+ protected function link_nodes(&$node, $is_child)
1521
+ {
1522
+ $node->parent = $this->parent;
1523
+ $this->parent->nodes[] = $node;
1524
+ if ($is_child)
1525
+ {
1526
+ $this->parent->children[] = $node;
1527
+ }
1528
+ }
1529
+
1530
+ // as a text node
1531
+ protected function as_text_node($tag)
1532
+ {
1533
+ $node = new simple_html_dom_node($this);
1534
+ ++$this->cursor;
1535
+ $node->_[HDOM_INFO_TEXT] = '</' . $tag . '>';
1536
+ $this->link_nodes($node, false);
1537
+ $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1538
+ return true;
1539
+ }
1540
+
1541
+ protected function skip($chars)
1542
+ {
1543
+ $this->pos += strspn($this->doc, $chars, $this->pos);
1544
+ $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1545
+ }
1546
+
1547
+ protected function copy_skip($chars)
1548
+ {
1549
+ $pos = $this->pos;
1550
+ $len = strspn($this->doc, $chars, $pos);
1551
+ $this->pos += $len;
1552
+ $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1553
+ if ($len===0) return '';
1554
+ return substr($this->doc, $pos, $len);
1555
+ }
1556
+
1557
+ protected function copy_until($chars)
1558
+ {
1559
+ $pos = $this->pos;
1560
+ $len = strcspn($this->doc, $chars, $pos);
1561
+ $this->pos += $len;
1562
+ $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next
1563
+ return substr($this->doc, $pos, $len);
1564
+ }
1565
+
1566
+ protected function copy_until_char($char)
1567
+ {
1568
+ if ($this->char===null) return '';
1569
+
1570
+ if (($pos = strpos($this->doc, $char, $this->pos))===false) {
1571
+ $ret = substr($this->doc, $this->pos, $this->size-$this->pos);
1572
+ $this->char = null;
1573
+ $this->pos = $this->size;
1574
+ return $ret;
1575
+ }
1576
+
1577
+ if ($pos===$this->pos) return '';
1578
+ $pos_old = $this->pos;
1579
+ $this->char = $this->doc[$pos];
1580
+ $this->pos = $pos;
1581
+ return substr($this->doc, $pos_old, $pos-$pos_old);
1582
+ }
1583
+
1584
+ protected function copy_until_char_escape($char)
1585
+ {
1586
+ if ($this->char===null) return '';
1587
+
1588
+ $start = $this->pos;
1589
+ while (1)
1590
+ {
1591
+ if (($pos = strpos($this->doc, $char, $start))===false)
1592
+ {
1593
+ $ret = substr($this->doc, $this->pos, $this->size-$this->pos);
1594
+ $this->char = null;
1595
+ $this->pos = $this->size;
1596
+ return $ret;
1597
+ }
1598
+
1599
+ if ($pos===$this->pos) return '';
1600
+
1601
+ if ($this->doc[$pos-1]==='\\') {
1602
+ $start = $pos+1;
1603
+ continue;
1604
+ }
1605
+
1606
+ $pos_old = $this->pos;
1607
+ $this->char = $this->doc[$pos];
1608
+ $this->pos = $pos;
1609
+ return substr($this->doc, $pos_old, $pos-$pos_old);
1610
+ }
1611
+ }
1612
+
1613
+ // remove noise from html content
1614
+ // save the noise in the $this->noise array.
1615
+ protected function remove_noise($pattern, $remove_tag=false)
1616
+ {
1617
+ global $debugObject;
1618
+ if (is_object($debugObject)) { $debugObject->debugLogEntry(1); }
1619
+
1620
+ $count = preg_match_all($pattern, $this->doc, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE);
1621
+
1622
+ for ($i=$count-1; $i>-1; --$i)
1623
+ {
1624
+ $key = '___noise___'.sprintf('% 5d', count($this->noise)+1000);
1625
+ if (is_object($debugObject)) { $debugObject->debugLog(2, 'key is: ' . $key); }
1626
+ $idx = ($remove_tag) ? 0 : 1;
1627
+ $this->noise[$key] = $matches[$i][$idx][0];
1628
+ $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0]));
1629
+ }
1630
+
1631
+ // reset the length of content
1632
+ $this->size = strlen($this->doc);
1633
+ if ($this->size>0)
1634
+ {
1635
+ $this->char = $this->doc[0];
1636
+ }
1637
+ }
1638
+
1639
+ // restore noise to html content
1640
+ function restore_noise($text)
1641
+ {
1642
+ global $debugObject;
1643
+ if (is_object($debugObject)) { $debugObject->debugLogEntry(1); }
1644
+
1645
+ while (($pos=strpos($text, '___noise___'))!==false)
1646
+ {
1647
+ // Sometimes there is a broken piece of markup, and we don't GET the pos+11 etc... token which indicates a problem outside of us...
1648
+ if (strlen($text) > $pos+15)
1649
+ {
1650
+ $key = '___noise___'.$text[$pos+11].$text[$pos+12].$text[$pos+13].$text[$pos+14].$text[$pos+15];
1651
+ if (is_object($debugObject)) { $debugObject->debugLog(2, 'located key of: ' . $key); }
1652
+
1653
+ if (isset($this->noise[$key]))
1654
+ {
1655
+ $text = substr($text, 0, $pos).$this->noise[$key].substr($text, $pos+16);
1656
+ }
1657
+ else
1658
+ {
1659
+ // do this to prevent an infinite loop.
1660
+ $text = substr($text, 0, $pos).'UNDEFINED NOISE FOR KEY: '.$key . substr($text, $pos+16);
1661
+ }
1662
+ }
1663
+ else
1664
+ {
1665
+ // There is no valid key being given back to us... We must get rid of the ___noise___ or we will have a problem.
1666
+ $text = substr($text, 0, $pos).'NO NUMERIC NOISE KEY' . substr($text, $pos+11);
1667
+ }
1668
+ }
1669
+ return $text;
1670
+ }
1671
+
1672
+ // Sometimes we NEED one of the noise elements.
1673
+ function search_noise($text)
1674
+ {
1675
+ global $debugObject;
1676
+ if (is_object($debugObject)) { $debugObject->debugLogEntry(1); }
1677
+
1678
+ foreach($this->noise as $noiseElement)
1679
+ {
1680
+ if (strpos($noiseElement, $text)!==false)
1681
+ {
1682
+ return $noiseElement;
1683
+ }
1684
+ }
1685
+ }
1686
+ function __toString()
1687
+ {
1688
+ return $this->root->innertext();
1689
+ }
1690
+
1691
+ function __get($name)
1692
+ {
1693
+ switch ($name)
1694
+ {
1695
+ case 'outertext':
1696
+ return $this->root->innertext();
1697
+ case 'innertext':
1698
+ return $this->root->innertext();
1699
+ case 'plaintext':
1700
+ return $this->root->text();
1701
+ case 'charset':
1702
+ return $this->_charset;
1703
+ case 'target_charset':
1704
+ return $this->_target_charset;
1705
+ }
1706
+ }
1707
+
1708
+ // camel naming conventions
1709
+ function childNodes($idx=-1) {return $this->root->childNodes($idx);}
1710
+ function firstChild() {return $this->root->first_child();}
1711
+ function lastChild() {return $this->root->last_child();}
1712
+ function createElement($name, $value=null) {return @str_get_html("<$name>$value</$name>")->first_child();}
1713
+ function createTextNode($value) {return @end(str_get_html($value)->nodes);}
1714
+ function getElementById($id) {return $this->find("#$id", 0);}
1715
+ function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);}
1716
+ function getElementByTagName($name) {return $this->find($name, 0);}
1717
+ function getElementsByTagName($name, $idx=-1) {return $this->find($name, $idx);}
1718
+ function loadFile() {$args = func_get_args();$this->load_file($args);}
1719
+ }
1720
+
1721
+ ?>
wp-recipe-maker.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The plugin bootstrap file
4
+ *
5
+ * This file is read by WordPress to generate the plugin information in the plugin
6
+ * admin area. This file also includes all of the dependencies used by the plugin,
7
+ * registers the activation and deactivation functions, and defines a function
8
+ * that starts the plugin.
9
+ *
10
+ * @link http://bootstrapped.ventures/
11
+ * @since 1.0.0
12
+ * @package WP_Recipe_Maker
13
+ *
14
+ * @wordpress-plugin
15
+ * Plugin Name: WP Recipe Maker
16
+ * Plugin URI: http://bootstrapped.ventures/
17
+ * Description: The easy and user-friendly recipe plugin for everyone. Automatic JSON-LD metadata for better SEO will get you more visitors!
18
+ * Version: 1.0.0
19
+ * Author: Bootstrapped Ventures
20
+ * Author URI: http://bootstrapped.ventures/
21
+ * License: GPL-2.0+
22
+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
23
+ * Text Domain: wp-recipe-maker
24
+ * Domain Path: /languages
25
+ */
26
+
27
+ // If this file is called directly, abort.
28
+ if ( ! defined( 'WPINC' ) ) {
29
+ die;
30
+ }
31
+
32
+ /**
33
+ * The code that runs during plugin activation.
34
+ * This action is documented in includes/class-wprm-activator.php
35
+ */
36
+ function activate_wp_recipe_maker() {
37
+ require_once plugin_dir_path( __FILE__ ) . 'includes/class-wprm-activator.php';
38
+ WPRM_Activator::activate();
39
+ }
40
+
41
+ /**
42
+ * The code that runs during plugin deactivation.
43
+ * This action is documented in includes/class-wprm-deactivator.php
44
+ */
45
+ function deactivate_wp_recipe_maker() {
46
+ require_once plugin_dir_path( __FILE__ ) . 'includes/class-wprm-deactivator.php';
47
+ WPRM_Deactivator::deactivate();
48
+ }
49
+
50
+ register_activation_hook( __FILE__, 'activate_wp_recipe_maker' );
51
+ register_deactivation_hook( __FILE__, 'deactivate_wp_recipe_maker' );
52
+
53
+ /**
54
+ * The core plugin class that is used to define internationalization,
55
+ * admin-specific hooks, and public-facing site hooks.
56
+ */
57
+ require plugin_dir_path( __FILE__ ) . 'includes/class-wp-recipe-maker.php';
58
+
59
+ /**
60
+ * Begins execution of the plugin.
61
+ *
62
+ * Since everything within the plugin is registered via hooks,
63
+ * then kicking off the plugin from this point in the file does
64
+ * not affect the page life cycle.
65
+ *
66
+ * @since 1.0.0
67
+ */
68
+ function run_wp_recipe_maker() {
69
+ $plugin = new WP_Recipe_Maker();
70
+ }
71
+ run_wp_recipe_maker();