Site Reviews - Version 1.2.2

Version Description

(2017-01-06) =

  • Added hook to change excerpt length
  • Added hook to disable the excerpt and instead display the full review content
Download this release

Release Info

Developer geminilabs
Plugin Icon 128x128 Site Reviews
Version 1.2.2
Comparing to
See all releases

Version 1.2.2

Files changed (128) hide show
  1. activate.php +73 -0
  2. assets/css/site-reviews-admin.css +1 -0
  3. assets/css/site-reviews.css +8 -0
  4. assets/css/twenty-eleven.css +8 -0
  5. assets/css/twenty-fifteen.css +8 -0
  6. assets/css/twenty-fourteen.css +8 -0
  7. assets/css/twenty-sixteen.css +8 -0
  8. assets/css/twenty-ten.css +8 -0
  9. assets/css/twenty-thirteen.css +8 -0
  10. assets/css/twenty-twelve.css +8 -0
  11. assets/img/booking.svg +14 -0
  12. assets/img/facebook.svg +11 -0
  13. assets/img/google.svg +15 -0
  14. assets/img/icon.png +0 -0
  15. assets/img/pinned.svg +6 -0
  16. assets/img/star-empty.svg +6 -0
  17. assets/img/star-filled.svg +6 -0
  18. assets/img/star-half.svg +6 -0
  19. assets/img/tripadvisor.svg +21 -0
  20. assets/img/yelp.svg +14 -0
  21. assets/js/site-reviews-admin.js +1 -0
  22. assets/js/site-reviews.js +1 -0
  23. autoload.php +39 -0
  24. languages/readme.txt +5 -0
  25. languages/site-reviews-en_US.mo +0 -0
  26. languages/site-reviews-en_US.po +896 -0
  27. languages/site-reviews.pot +850 -0
  28. license.txt +2 -0
  29. plugin/App.php +161 -0
  30. plugin/Commands/EnqueueAssets.php +27 -0
  31. plugin/Commands/RegisterPointers.php +23 -0
  32. plugin/Commands/RegisterPostType.php +43 -0
  33. plugin/Commands/RegisterShortcodes.php +23 -0
  34. plugin/Commands/RegisterWidgets.php +21 -0
  35. plugin/Commands/SubmitReview.php +53 -0
  36. plugin/Commands/TogglePinned.php +27 -0
  37. plugin/Container.php +218 -0
  38. plugin/Controllers/AjaxController.php +57 -0
  39. plugin/Controllers/BaseController.php +121 -0
  40. plugin/Controllers/MainController.php +574 -0
  41. plugin/Controllers/ReviewController.php +69 -0
  42. plugin/Database.php +535 -0
  43. plugin/Email.php +245 -0
  44. plugin/Handlers/EnqueueAssets.php +118 -0
  45. plugin/Handlers/RegisterPointers.php +64 -0
  46. plugin/Handlers/RegisterPostType.php +640 -0
  47. plugin/Handlers/RegisterShortcodes.php +53 -0
  48. plugin/Handlers/RegisterWidgets.php +55 -0
  49. plugin/Handlers/SubmitReview.php +223 -0
  50. plugin/Handlers/TogglePinned.php +50 -0
  51. plugin/Html.php +249 -0
  52. plugin/Html/Field.php +365 -0
  53. plugin/Html/Fields/Base.php +361 -0
  54. plugin/Html/Fields/Checkbox.php +48 -0
  55. plugin/Html/Fields/Code.php +26 -0
  56. plugin/Html/Fields/Colorpicker.php +58 -0
  57. plugin/Html/Fields/Email.php +27 -0
  58. plugin/Html/Fields/Heading.php +33 -0
  59. plugin/Html/Fields/Hidden.php +39 -0
  60. plugin/Html/Fields/Number.php +28 -0
  61. plugin/Html/Fields/Progress.php +64 -0
  62. plugin/Html/Fields/Radio.php +33 -0
  63. plugin/Html/Fields/Roles.php +26 -0
  64. plugin/Html/Fields/Select.php +34 -0
  65. plugin/Html/Fields/Submit.php +31 -0
  66. plugin/Html/Fields/Text.php +34 -0
  67. plugin/Html/Fields/Textarea.php +36 -0
  68. plugin/Html/Fields/Url.php +27 -0
  69. plugin/Html/Fields/Yesno.php +31 -0
  70. plugin/Html/Form.php +288 -0
  71. plugin/Html/Normalize.php +396 -0
  72. plugin/Html/Partial.php +75 -0
  73. plugin/Html/Partials/Addon.php +50 -0
  74. plugin/Html/Partials/Base.php +49 -0
  75. plugin/Html/Partials/Filterby.php +43 -0
  76. plugin/Html/Partials/Rating.php +49 -0
  77. plugin/Html/Partials/Reviews.php +311 -0
  78. plugin/Html/Partials/Subsubsub.php +54 -0
  79. plugin/Html/Partials/Tabs.php +48 -0
  80. plugin/Log/LogLevel.php +18 -0
  81. plugin/Log/Logger.php +305 -0
  82. plugin/Log/LoggerInterface.php +123 -0
  83. plugin/Notices.php +186 -0
  84. plugin/Providers/MainProvider.php +64 -0
  85. plugin/Providers/ProviderInterface.php +21 -0
  86. plugin/Router.php +123 -0
  87. plugin/Session.php +336 -0
  88. plugin/Settings.php +295 -0
  89. plugin/Shortcode.php +103 -0
  90. plugin/Shortcodes/SiteReviews.php +76 -0
  91. plugin/Shortcodes/SiteReviewsForm.php +95 -0
  92. plugin/Strings.php +65 -0
  93. plugin/SystemInfo.php +457 -0
  94. plugin/Validator.php +587 -0
  95. plugin/Widget.php +113 -0
  96. plugin/Widgets/RecentReviews.php +184 -0
  97. plugin/Widgets/ReviewsForm.php +146 -0
  98. readme.txt +146 -0
  99. site-reviews.php +64 -0
  100. uninstall.php +26 -0
  101. vendor/sinergi/browser-detector/LICENSE +20 -0
  102. vendor/sinergi/browser-detector/src/Browser.php +322 -0
  103. vendor/sinergi/browser-detector/src/BrowserDetector.php +1006 -0
  104. vendor/sinergi/browser-detector/src/DetectorInterface.php +7 -0
  105. vendor/sinergi/browser-detector/src/InvalidArgumentException.php +7 -0
  106. vendor/sinergi/browser-detector/src/UserAgent.php +56 -0
  107. views/base.php +12 -0
  108. views/edit/meta.php +63 -0
  109. views/edit/notice.php +3 -0
  110. views/edit/pinned.php +39 -0
  111. views/edit/review.php +9 -0
  112. views/email/body.php +3 -0
  113. views/email/footer.php +4 -0
  114. views/email/header.php +9 -0
  115. views/email/index.php +5 -0
  116. views/email/templates/review-notification.php +11 -0
  117. views/menu/addons/addons.php +25 -0
  118. views/menu/help/documentation/hooks.php +28 -0
  119. views/menu/help/documentation/shortcodes.php +33 -0
  120. views/menu/help/documentation/support.php +37 -0
  121. views/menu/help/system.php +50 -0
  122. views/menu/index.php +38 -0
  123. views/menu/settings/licenses.php +3 -0
  124. views/menu/settings/settings/form.php +3 -0
  125. views/menu/settings/settings/general.php +3 -0
  126. views/menu/settings/welcome.php +26 -0
  127. views/menu/status.php +81 -0
  128. views/submit/index.php +125 -0
activate.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ defined( 'WPINC' ) || die;
12
+
13
+ if( !function_exists( 'glsr_version_check' ) ) {
14
+ function glsr_version_check() {
15
+ global $wp_version;
16
+ return [
17
+ 'php' => version_compare( PHP_VERSION, '5.4.0', '<' ),
18
+ 'wordpress' => version_compare( $wp_version, '4.0', '<' ),
19
+ ];
20
+ }
21
+ }
22
+
23
+ if( !function_exists( 'glsr_deactivate_plugin' ) ) {
24
+ function glsr_deactivate_plugin( $plugin )
25
+ {
26
+ $check = glsr_version_check();
27
+
28
+ if( !$check['php'] && !$check['wordpress'] )return;
29
+
30
+ $plugin_name = plugin_basename( dirname( __FILE__ ) . '/site-reviews.php' );
31
+
32
+ if( $plugin == $plugin_name ) {
33
+ $paged = filter_input( INPUT_GET, 'paged' );
34
+ $s = filter_input( INPUT_GET, 's' );
35
+ $status = filter_input( INPUT_GET, 'plugin_status' );
36
+
37
+ wp_redirect( self_admin_url( sprintf( 'plugins.php?plugin_status=%s&paged=%s&s=%s', $status, $paged, $s ) ) );
38
+ die;
39
+ }
40
+
41
+ deactivate_plugins( $plugin_name );
42
+
43
+ $title = __( 'The Site Reviews plugin was deactivated.', 'site-reviews' );
44
+ $msg_1 = '';
45
+ $msg_2 = '';
46
+
47
+ if( $check['php'] ) {
48
+ $msg_1 = __( 'Sorry, this plugin requires PHP version 5.4 or greater in order to work properly.', 'site-reviews' );
49
+ $msg_2 = __( 'Please contact your hosting provider or server administrator to upgrade the version of PHP on your server (your server is running PHP version %s), or try to find an alternative plugin.', 'site-reviews' );
50
+ $msg_2 = sprintf( $msg_2, PHP_VERSION );
51
+ }
52
+
53
+ // WordPress check overrides the PHP check
54
+ if( $check['wordpress'] ) {
55
+ $msg_1 = __( 'Sorry, this plugin requires WordPress version 4.0.0 or greater in order to work properly.', 'site-reviews' );
56
+ $msg_2 = sprintf( '<a href="%s">%s</a>', admin_url( 'update-core.php' ), __( 'Update WordPress', 'site-reviews' ) );
57
+ }
58
+
59
+ printf( '<div id="message" class="notice notice-error error is-dismissible"><p><strong>%s</strong></p><p>%s</p><p>%s</p></div>',
60
+ $title,
61
+ $msg_1,
62
+ $msg_2
63
+ );
64
+ }
65
+ }
66
+
67
+ $check = glsr_version_check();
68
+
69
+ // PHP >= 5.4.0 and WordPress version >= 4.0.0 check
70
+ if( $check['php'] || $check['wordpress'] ) {
71
+ add_action( 'activated_plugin', 'glsr_deactivate_plugin' );
72
+ add_action( 'admin_notices', 'glsr_deactivate_plugin' );
73
+ }
assets/css/site-reviews-admin.css ADDED
@@ -0,0 +1 @@
 
1
+ .glsr-addon-name,.glsr-addon-wrap,.glsr-progress *{box-sizing:border-box}.glsr-addons{margin:-20px 0 0 -20px}.glsr-addon-description p,.glsr-addon-name{text-overflow:ellipsis;margin:0;overflow:hidden}.glsr-addon-wrap{float:left;position:relative;width:100%;padding:20px 0 0 20px}@media only screen and (min-width:480px){.glsr-addon-wrap{width:50%}}@media only screen and (min-width:782px){.glsr-addon-wrap{width:33.33%}}@media only screen and (min-width:1400px){.glsr-addon-wrap{width:25%}}.glsr-addon{border:1px solid #ddd;box-shadow:0 1px 1px -1px rgba(0,0,0,.1)}.glsr-addon-description{background-color:#fff;padding:1em 15px}.glsr-addon-description p{height:calc(13px * 1.5 * 4)}.glsr-addon-name{height:48px;font-size:15px;font-weight:600;background:rgba(255,255,255,.65);box-shadow:inset 0 1px 0 rgba(0,0,0,.1);white-space:nowrap;padding:15px}.glsr-addon-screenshot{display:block;background-position:50%;background-repeat:no-repeat;background-size:100% auto;border-bottom:1px solid #ddd;padding-top:56.25%}.glsr-addon-screenshot[data-name=booking]{background-image:url(../img/booking.svg);background-color:#00347D}.glsr-addon-screenshot[data-name=facebook]{background-image:url(../img/facebook.svg);background-color:#3B5998}.glsr-addon-screenshot[data-name=google]{background-image:url(../img/google.svg);background-color:#C6DAFC}.glsr-addon-screenshot[data-name=tripadvisor]{background-color:#4b7e37;background-image:url(../img/tripadvisor.svg)}.glsr-addon-screenshot[data-name=yelp]{background-image:url(../img/yelp.svg);background-color:#d32323}.glsr-addon-link{position:absolute;right:15px;bottom:10px}.glsr-addon-link:after{display:inline-block;content:"\00a0\f504";font-family:dashicons;vertical-align:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media screen and (max-width:782px){.glsr-addon-link{bottom:5px;text-indent:-100%;padding:6px 10px!important;overflow:hidden}.glsr-addon-link:after{float:right;height:16px;width:16px;content:"\f504";font-size:18px;text-indent:0}}#dashboard_right_now li a.glsr-review-count:before,#dashboard_right_now li>span.glsr-review-count:before{content:'\f459';line-height:.8em}#titlediv #title{margin-bottom:20px}#contentdiv textarea,#titlediv #title{border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04);background-color:#fff}#contentdiv textarea,.post-type-site-review .wp-editor-area{width:100%;height:320px;font-family:Georgia,"Times New Roman","Bitstream Charter",Times,serif;font-size:16px;line-height:1.5;padding:10px;resize:vertical;-webkit-font-smoothing:antialiased!important;overflow-wrap:break-word}body.branch-4 .misc-pub-pinned:before,body.branch-4-1 .misc-pub-pinned:before,body.branch-4-2 .misc-pub-pinned:before,body.branch-4-3 .misc-pub-pinned:before{display:none}body.branch-4 .misc-pub-pinned .pinned-icon,body.branch-4-1 .misc-pub-pinned .pinned-icon,body.branch-4-2 .misc-pub-pinned .pinned-icon,body.branch-4-3 .misc-pub-pinned .pinned-icon{position:relative;display:inline-block;width:20px;padding-right:2px}body.branch-4 .misc-pub-pinned .pinned-icon svg,body.branch-4-1 .misc-pub-pinned .pinned-icon svg,body.branch-4-2 .misc-pub-pinned .pinned-icon svg,body.branch-4-3 .misc-pub-pinned .pinned-icon svg{position:absolute;top:-13px;left:-1px;width:19px;height:18px;color:#888}.misc-pub-pinned:before{display:inline-block;position:relative;content:"\f537";font:400 20px/1 dashicons;speak:none;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;top:0;left:-1px;color:#82878c;padding:0 2px 0 0}.glsr-card dt,.glsr-metabox-table td:first-child,.misc-pub-pinned .pinned-status-text{font-weight:600}.glsr-field textarea.code,.glsr-log[readonly],.post-type-site-review .code,.post-type-site-review code{font-family:"Operator Mono","Fira Code",monaco,monospace}.misc-pub-pinned .pinned-status-select{margin-top:3px}#geminilabs-site-reviews_review.postbox .inside{padding:0;margin:0}.glsr-metabox-table{width:auto;padding:0 12px 12px;margin-top:12px}.glsr-metabox-table td{line-height:1.5;vertical-align:top;padding:0 14px 8px 0}.glsr-metabox-table td:last-child{padding-bottom:0}.revert-action{line-height:23px;text-align:right;border-top:1px solid #ddd;background:#f5f5f5;padding:10px}.revert-action .spinner{float:none}body.branch-4 .notice-warning{border-color:#ffba00}.glsr-progress{position:relative;display:block;width:300px;height:24px;opacity:.65;text-align:center}@media screen and (max-width:782px){.glsr-progress{height:39px;font-size:16px}}.glsr-progress.active{opacity:1}.glsr-progress-background,.glsr-progress-bar{height:100%;background-size:40px 40px}.glsr-progress.active .glsr-progress-background,.glsr-progress.active .glsr-progress-bar{-webkit-transition:width .6s ease;transition:width .6s ease;-webkit-animation:glsr-progress-bar 1s linear infinite;animation:glsr-progress-bar 1s linear infinite}.glsr-progress-bar{position:relative;width:0;color:#fff;background-color:#616b75;background-image:-webkit-linear-gradient(135deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);box-shadow:0 1px 0 #4a5259;border-width:1px;border-style:solid;border-color:#616b75 #4a5259 #4a5259;border-radius:3px 0 0 3px;overflow:hidden;z-index:1}.glsr-progress-bar[style="width: 0%;"]{border-width:0}.glsr-progress-bar[style="width: 100%;"]{border-radius:3px}.glsr-progress-background{position:absolute;top:0;left:0;width:100%;height:25px;background-color:#f7f7f7;background-image:-webkit-linear-gradient(135deg,#fff 25%,transparent 25%,transparent 50%,#fff 50%,#fff 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,#fff 25%,transparent 25%,transparent 50%,#fff 50%,#fff 75%,transparent 75%,transparent);border-radius:3px;box-shadow:inset 0 1px 2px #e5e5e5;border:1px solid #ccc}.glsr-progress span{position:absolute;width:300px;left:0;top:0;line-height:23px;color:#555;padding:0 6px}@media screen and (max-width:782px){.glsr-progress-background{height:40px}.glsr-progress span{line-height:38px}}.glsr-progress-bar>span{color:#fff;text-shadow:0 -1px 1px #4a5259,1px 0 1px #4a5259,0 1px 1px #4a5259,-1px 0 1px #4a5259}@-webkit-keyframes glsr-progress-bar{from{background-position:0 0}to{background-position:40px 0}}@keyframes glsr-progress-bar{from{background-position:0 0}to{background-position:40px 0}}.glsr-progress.blue .glsr-progress-bar{background-color:#0073aa;box-shadow:0 1px 0 #006799;border-color:#0073aa #006799 #006799}.glsr-progress.blue .glsr-progress-bar>span{text-shadow:0 -1px 1px #006799,1px 0 1px #006799,0 1px 1px #006799,-1px 0 1px #006799}.glsr-progress.green .glsr-progress-bar{background-color:#46b44f;box-shadow:0 1px 0 #3fa247;border-color:#46b44f #3fa247 #3fa247}.glsr-progress.green .glsr-progress-bar>span{text-shadow:0 -1px 1px #3fa247,1px 0 1px #3fa247,0 1px 1px #3fa247,-1px 0 1px #3fa247}.star-rating .glsr-star{width:16px;font-size:16px;line-height:20px;color:#555}.glsr-card code,.glsr-card pre{white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-card pre{border:1px solid #e5e5e5;border-radius:3px;position:relative;background-color:#f9f9f9;overflow:visible;padding:0}.glsr-card pre code{background:0 0;max-height:inherit;height:100%;padding:.5em;display:block;overflow:auto}.glsr-card dd,.glsr-card dt{margin:1em 0}.glsr-card code a{text-decoration:none}.wrap h2.nav-tab-wrapper{padding-top:0;margin-top:9px}.float-left{float:left;padding-right:.25em}.glsr-subsubsub{float:none;text-align:left}.glsr-log[readonly]{width:100%;background-color:rgba(255,255,255,.5);border-color:rgba(222,222,222,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(50,55,60,.75);font-weight:300;font-size:13px;-webkit-font-smoothing:antialiased!important;resize:vertical;white-space:pre;overflow:auto;padding:3px 6px}@media only screen and (max-width:782px){.glsr-card{max-width:inherit}}tr.glsr-field li,tr.glsr-field ul{margin:0 auto}.glsr-field ul.inline li{display:inline}.glsr-field ul.inline li:not(:last-child){margin-right:1em}.glsr-field{margin:1em 0;line-height:1.5}.glsr-field>label{display:block}@media screen and (max-width:782px){.glsr-field>label+input.small-text{margin-left:0}}.glsr-field>ul{margin:0}.glsr-field>ul li{margin-bottom:2px}body.branch-4 .wrap h1.page-title,body.branch-4-1 .wrap h1.page-title,body.branch-4-2 .wrap h1.page-title{font-size:23px;font-weight:400;line-height:29px;padding:9px 15px 4px 0;margin:0}body.branch-4 .wrap h2.title,body.branch-4-1 .wrap h2.title,body.branch-4-2 .wrap h2.title{color:#23282d;font-size:1.3em;font-weight:600;line-height:1.4em;padding:0;margin:1em 0}body.branch-4 .glsr-card,body.branch-4-1 .glsr-card{position:relative;margin-top:20px;padding:.7em 2em 1em;min-width:255px;max-width:520px;border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff}.glsr-status{max-width:1000px}.glsr-status th.site{width:25%}.glsr-status td{vertical-align:middle}.glsr-status td.site{font-weight:600}.glsr-status td.site span{font-weight:400;font-size:90%}.glsr-status .toggle-row{top:0}.glsr-status .toggle-row:before{top:6px}@media only screen and (min-width:783px){.glsr-status td.total-fetched,.glsr-status th.total-fetched{text-align:center}}@media only screen and (max-width:782px){.glsr-status th.site,.glsr-status tr:not(.inline-edit-row):not(.no-items) td.site{display:table-cell!important;width:230px!important;vertical-align:top;padding:8px 10px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:not(.site){overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 10px 0 260px;margin-left:-250px;margin-bottom:8px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:before{width:240px}}@media only screen and (max-width:691px){.glsr-status th.site,.glsr-status tr:not(.inline-edit-row):not(.no-items) td.site{width:200px!important}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:not(.site){padding-left:230px;margin-left:-220px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:before{width:210px}}@media only screen and (max-width:600px){.glsr-status th.site,.glsr-status tr:not(.inline-edit-row):not(.no-items) td.site{width:170px!important}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:not(.site){padding-left:200px;margin-left:-190px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:before{width:180px}}@media only screen and (max-width:480px){.glsr-status th.site,.glsr-status tr:not(.inline-edit-row):not(.no-items) td.site{width:130px!important}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:not(.site){padding-left:160px;margin-left:-150px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:before{width:140px}}@media only screen and (max-width:425px){.glsr-status td:before{font-weight:600}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:not(.site){padding-left:10px}.glsr-status tr:not(.inline-edit-row):not(.no-items) td.total-fetched~td:before{position:static}}body.branch-4 .glsr-status.striped>tbody>:nth-child(odd),body.branch-4-1 .glsr-status.striped>tbody>:nth-child(odd){background-color:#f9f9f9}body.branch-4 .glsr-status .toggle-row,body.branch-4-1 .glsr-status .toggle-row,body.branch-4-2 .glsr-status .toggle-row{display:none;position:absolute;top:10px;right:8px;width:40px;height:40px;border:none;outline:0;background:0 0;padding:0}body.branch-4 .glsr-status .toggle-row:hover,body.branch-4-1 .glsr-status .toggle-row:hover,body.branch-4-2 .glsr-status .toggle-row:hover{cursor:pointer}body.branch-4 .glsr-status .toggle-row:focus:before,body.branch-4-1 .glsr-status .toggle-row:focus:before,body.branch-4-2 .glsr-status .toggle-row:focus:before{box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px rgba(30,140,190,.8)}body.branch-4 .glsr-status .toggle-row:active,body.branch-4-1 .glsr-status .toggle-row:active,body.branch-4-2 .glsr-status .toggle-row:active{box-shadow:none}body.branch-4 .glsr-status .toggle-row:before,body.branch-4-1 .glsr-status .toggle-row:before,body.branch-4-2 .glsr-status .toggle-row:before{display:block;position:absolute;top:-5px;left:10px;border-radius:50%;color:#666;content:'\f140';font:400 20px/1 dashicons;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;speak:none;padding:1px 2px 1px 0}body.branch-4 .glsr-status .is-expanded .toggle-row:before,body.branch-4-1 .glsr-status .is-expanded .toggle-row:before,body.branch-4-2 .glsr-status .is-expanded .toggle-row:before{content:'\f142'}@media screen and (max-width:782px){body.branch-4 .glsr-status .column-primary,body.branch-4-1 .glsr-status .column-primary,body.branch-4-2 .glsr-status .column-primary{width:100%}body.branch-4 .glsr-status .column-primary~td,body.branch-4 .glsr-status .column-primary~th,body.branch-4-1 .glsr-status .column-primary~td,body.branch-4-1 .glsr-status .column-primary~th,body.branch-4-2 .glsr-status .column-primary~td,body.branch-4-2 .glsr-status .column-primary~th{display:none}body.branch-4 .glsr-status .column-primary .toggle-row,body.branch-4-1 .glsr-status .column-primary .toggle-row,body.branch-4-2 .glsr-status .column-primary .toggle-row{display:block}body.branch-4 .glsr-status td,body.branch-4-1 .glsr-status td,body.branch-4-2 .glsr-status td{position:relative;clear:both;display:block;width:auto!important}body.branch-4 .glsr-status td.column-primary,body.branch-4-1 .glsr-status td.column-primary,body.branch-4-2 .glsr-status td.column-primary{padding-right:50px}body.branch-4 .glsr-status td.column-primary~td:not(.check-column),body.branch-4-1 .glsr-status td.column-primary~td:not(.check-column),body.branch-4-2 .glsr-status td.column-primary~td:not(.check-column){padding:3px 8px 3px 35%}body.branch-4 .glsr-status td:not(.column-primary)::before,body.branch-4-1 .glsr-status td:not(.column-primary)::before,body.branch-4-2 .glsr-status td:not(.column-primary)::before{position:absolute;left:10px;display:block;overflow:hidden;width:32%;content:attr(data-colname);white-space:nowrap;text-overflow:ellipsis}body.branch-4 .glsr-status .is-expanded td:not(.hidden),body.branch-4-1 .glsr-status .is-expanded td:not(.hidden),body.branch-4-2 .glsr-status .is-expanded td:not(.hidden){display:block;overflow:hidden}body.branch-4-1.post-type-review .fixed .column-rating,body.branch-4-1.post-type-review .fixed .column-reviewer,body.branch-4-1.post-type-review .fixed .column-site,body.branch-4-1.post-type-review .fixed .column-stars,body.branch-4-2.post-type-review .fixed .column-rating,body.branch-4-2.post-type-review .fixed .column-reviewer,body.branch-4-2.post-type-review .fixed .column-site,body.branch-4-2.post-type-review .fixed .column-stars,body.branch-4.post-type-review .fixed .column-rating,body.branch-4.post-type-review .fixed .column-reviewer,body.branch-4.post-type-review .fixed .column-site,body.branch-4.post-type-review .fixed .column-stars{display:none}}.post-type-site-review .wp-list-table .approve,.post-type-site-review .wp-list-table .status-pending .unapprove{display:none}.post-type-site-review .wp-list-table .spam .approve,.post-type-site-review .wp-list-table .status-pending .approve,.post-type-site-review .wp-list-table .trash .approve{display:inline}.post-type-site-review .wp-list-table .approve a{color:#006505}.post-type-site-review .wp-list-table .approve a:hover{color:#007f06}.post-type-site-review .wp-list-table .unapprove a{color:#d98500}.post-type-site-review .wp-list-table .unapprove a:hover{color:#f39500}.post-type-site-review .wp-list-table th.column-stars{width:100px}.post-type-site-review .wp-list-table td.column-sticky i{line-height:24px;text-indent:-3px;cursor:pointer}.post-type-site-review .wp-list-table td.column-sticky i:not(.pinned){color:rgba(85,85,85,.2)}.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending th.check-column{box-shadow:inset 0 1px 0 rgba(0,0,0,.07),inset 0 -1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending td:nth-child(2){box-shadow:inset 0 1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending:not(.is-expanded) td:nth-child(2){box-shadow:inset 0 1px 0 rgba(0,0,0,.07),inset 0 -1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table tr.status-pending th.check-column,.post-type-site-review .wp-list-table tr.status-pending.is-expanded td:last-child,.post-type-site-review .wp-list-table tr.status-pending:not(.is-expanded) td:nth-child(2){box-shadow:inset 0 -1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table tr.status-pending td,.post-type-site-review .wp-list-table tr.status-pending th.check-column{background-color:#fef7f1}.post-type-site-review .wp-list-table tr.status-pending th.check-column{position:relative;border-left:4px solid #d54e21;z-index:1}.post-type-site-review .wp-list-table tr.status-pending th.check-column input{margin-left:4px}@media screen and (max-width:782px){body.branch-4-1.post-type-site-review .wp-list-table .column-title,body.branch-4-2.post-type-site-review .wp-list-table .column-title,body.branch-4.post-type-site-review .wp-list-table .column-title{width:auto}body.branch-4-1.post-type-site-review .wp-list-table .column-title~[class*=column-]:not(.column-stars),body.branch-4-2.post-type-site-review .wp-list-table .column-title~[class*=column-]:not(.column-stars),body.branch-4.post-type-site-review .wp-list-table .column-title~[class*=column-]:not(.column-stars){display:none}body.branch-4-1.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending td.column-stars,body.branch-4-2.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending td.column-stars,body.branch-4.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending td.column-stars{box-shadow:inset 0 1px 0 rgba(0,0,0,.07)}body.branch-4-1.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending:not(.is-expanded) td.column-stars,body.branch-4-2.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending:not(.is-expanded) td.column-stars,body.branch-4.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending:not(.is-expanded) td.column-stars{box-shadow:inset 0 1px 0 rgba(0,0,0,.07),inset 0 -1px 0 rgba(0,0,0,.07)}body.branch-4-1.post-type-site-review .wp-list-table tr.status-pending:not(.is-expanded) td.column-stars,body.branch-4-2.post-type-site-review .wp-list-table tr.status-pending:not(.is-expanded) td.column-stars,body.branch-4.post-type-site-review .wp-list-table tr.status-pending:not(.is-expanded) td.column-stars{box-shadow:inset 0 -1px 0 rgba(0,0,0,.07)}}@media screen and (min-width:783px){.post-type-site-review .wp-list-table th.column-sticky span.pinned-icon,.post-type-site-review .wp-list-table th.column-sticky span.sorting-indicator,body.branch-4-1.post-type-site-review .wp-list-table .dashicons-sticky:before,body.branch-4-1.post-type-site-review .wp-list-table th.column-sticky a:after,body.branch-4-2.post-type-site-review .wp-list-table .dashicons-sticky:before,body.branch-4-2.post-type-site-review .wp-list-table th.column-sticky a:after,body.branch-4-3.post-type-site-review .wp-list-table .dashicons-sticky:before,body.branch-4-3.post-type-site-review .wp-list-table th.column-sticky a:after,body.branch-4.post-type-site-review .wp-list-table .dashicons-sticky:before,body.branch-4.post-type-site-review .wp-list-table th.column-sticky a:after{display:none}body.branch-4-1.post-type-site-review .wp-list-table th.column-sticky,body.branch-4-2.post-type-site-review .wp-list-table th.column-sticky,body.branch-4-3.post-type-site-review .wp-list-table th.column-sticky,body.branch-4.post-type-site-review .wp-list-table th.column-sticky{position:relative}body.branch-4-1.post-type-site-review .wp-list-table th.column-sticky svg,body.branch-4-2.post-type-site-review .wp-list-table th.column-sticky svg,body.branch-4-3.post-type-site-review .wp-list-table th.column-sticky svg,body.branch-4.post-type-site-review .wp-list-table th.column-sticky svg{position:absolute;top:10px;width:16px;height:16px}.post-type-site-review .wp-list-table :not(.status-pending)+tr.status-pending td{box-shadow:inset 0 1px 0 rgba(0,0,0,.07),inset 0 -1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table tr.status-pending td{box-shadow:inset 0 -1px 0 rgba(0,0,0,.07)}.post-type-site-review .wp-list-table th.column-reviewer{width:140px}.post-type-site-review .wp-list-table th.column-site{width:120px}.post-type-site-review .wp-list-table th.column-sticky{width:36px}.post-type-site-review .wp-list-table th.column-sticky a:after{display:inline-block;position:relative;content:"\f537";font:400 20px/1 dashicons;speak:none;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;width:20px;height:20px;line-height:20px;font-size:16px;text-align:center;-webkit-transition:color .1s ease-in 0;transition:color .1s ease-in 0}.post-type-site-review .wp-list-table td.column-sticky{padding:8px}.post-type-site-review .wp-list-table td.column-sticky i{text-indent:0}}@media screen and (max-width:1100px) and (min-width:782px),(max-width:480px){body.branch-4-1.post-type-site-review .wp-list-table th.column-date,body.branch-4-2.post-type-site-review .wp-list-table th.column-date,body.branch-4-3.post-type-site-review .wp-list-table th.column-date,body.branch-4.post-type-site-review .wp-list-table th.column-date{width:14%}}::placeholder{color:#72777c!important;font-size:inherit!important;line-height:inherit!important;opacity:1}::-webkit-input-placeholder{color:#72777c!important;font-size:inherit!important;line-height:inherit!important;opacity:1}:-moz-placeholder{color:#72777c!important;font-size:inherit!important;line-height:inherit!important;opacity:1}::-moz-placeholder{color:#72777c!important;font-size:inherit!important;line-height:inherit!important;opacity:1}:-ms-input-placeholder{color:#72777c!important;font-size:inherit!important;line-height:inherit!important;opacity:1}
assets/css/site-reviews.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field{margin-bottom:1em}.glsr-field-error{display:block;font-size:.875em;color:#D32F2F}.glsr-field ul{list-style-type:none}.glsr-field label{display:block}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";color:#D32F2F;margin-left:.33em}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}
assets/css/twenty-eleven.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field{margin-bottom:1em}.glsr-field ul{list-style-type:none}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#009781;font-size:15px}.gslr-has-errors{color:#bd3500}h3.glsr-form-title{color:#373737;font-size:24px;font-weight:700;line-height:30px;text-transform:none;letter-spacing:normal}.glsr-form-description{font-size:13px;margin:10px 0}.glsr-form-description span{color:#bd3500}.entry-content .glsr-field input[type=text],.entry-content .glsr-field input[type=email],.entry-content .glsr-field input[type=url],.entry-content .glsr-field textarea,.one .glsr-field input[type=text],.one .glsr-field input[type=email],.one .glsr-field input[type=url],.one .glsr-field textarea{padding:10px}@media (max-width:800px){.entry-content .glsr-field input[type=text],.entry-content .glsr-field input[type=email],.entry-content .glsr-field input[type=url],.entry-content .glsr-field textarea,.one .glsr-field input[type=text],.one .glsr-field input[type=email],.one .glsr-field input[type=url],.one .glsr-field textarea{padding:6px}}.glsr-field-error{display:block;color:#bd3500;font-size:13px}.glsr-field label{display:block;font-size:13px}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{color:#bd3500;line-height:13px;font-size:22px;font-weight:600;vertical-align:middle}.glsr-field input[type=text],.glsr-field input[type=email],.glsr-field input[type=url],.glsr-field textarea{position:relative;display:block;width:100%;border-radius:3px;box-sizing:border-box;padding:6px}.glsr-field input[type=submit]{position:relative;background:#222;border:none;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.3);color:#eee;cursor:pointer;text-shadow:0 -1px 0 rgba(0,0,0,.3);padding:5px 22px}.glsr-field input[type=submit]:active{background:#1982d1;color:#bfddf3}
assets/css/twenty-fifteen.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field ul{list-style-type:none}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#21759b}.gslr-has-errors{color:#c0392b}.shortcode-reviews-form{border-top:1px solid #eaeaea;border-top:1px solid rgba(51,51,51,.1);padding-top:1.6em}.shortcode-reviews-form>:last-child{margin-bottom:0}.shortcode-reviews-form h3.glsr-form-title{font-family:"Noto Serif",serif;font-size:18px;font-size:1.8rem;line-height:1.3333333333;margin-top:0;margin-bottom:1.3333333333em}@media screen and (min-width:46.25em){.shortcode-reviews-form{padding-top:1.6470588235em}.shortcode-reviews-form h3.glsr-form-title{font-size:24px;font-size:2.4rem;line-height:1.1666666667;margin-bottom:1.1666666667em}}@media screen and (min-width:55em){.shortcode-reviews-form{padding-top:1.6842105263em}.shortcode-reviews-form h3.glsr-form-title{font-size:27px;font-size:2.7rem;line-height:1.1851851852;margin-bottom:1.1851851852em}}@media screen and (min-width:59.6875em){.shortcode-reviews-form{padding-top:1.6em}.shortcode-reviews-form h3.glsr-form-title{font-size:18px;font-size:1.8rem;line-height:1.3333333333;margin-bottom:1.3333333333em}}@media screen and (min-width:68.75em){.shortcode-reviews-form{padding-top:1.6470588235em}.shortcode-reviews-form h3.glsr-form-title{font-size:24px;font-size:2.4rem;line-height:1.1666666667;margin-bottom:1.1666666667em}}@media screen and (min-width:77.5em){.shortcode-reviews-form{padding-top:1.6842105263em}.shortcode-reviews-form h3.glsr-form-title{font-size:27px;font-size:2.7rem;line-height:1.1851851852;margin-bottom:1.1851851852em}}.glsr-field label,.glsr-field-error,.glsr-form-description{font-family:"Noto Sans",sans-serif;line-height:1.5}.glsr-form-description{color:#707070;color:rgba(51,51,51,.7);margin-bottom:2em}.glsr-form-description span{color:#c0392b}.glsr-field-error,.glsr-form-description{font-size:12px;font-size:1.2rem}@media screen and (min-width:46.25em){.glsr-field-error,.glsr-form-description{font-size:14px;font-size:1.4rem}}@media screen and (min-width:55em){.glsr-field-error,.glsr-form-description{font-size:16px;font-size:1.6rem}}@media screen and (min-width:59.6875em){.glsr-field-error,.glsr-form-description{font-size:12px;font-size:1.2rem}}@media screen and (min-width:68.75em){.glsr-field-error,.glsr-form-description{font-size:14px;font-size:1.4rem}}@media screen and (min-width:77.5em){.glsr-field-error,.glsr-form-description{font-size:16px;font-size:1.6rem}}.glsr-field{margin-bottom:1.6em}.glsr-field:last-child{margin-bottom:0}.glsr-field-error{display:block;color:#c0392b}.glsr-field-error:first-child{margin-top:2em}.glsr-field label{display:block;color:#707070;color:rgba(51,51,51,.7);font-size:12px;font-size:1.2rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em;color:#c0392b}.glsr-field input[type=checkbox]:focus{outline:0}.glsr-field input[type=submit]{width:100%}@media screen and (min-width:46.25em){.glsr-field label{font-size:14px;font-size:1.4rem}.glsr-field{margin-bottom:1.6471em}}@media screen and (min-width:55em){.glsr-field label{font-size:16px;font-size:1.6rem}.glsr-field{margin-bottom:1.6842em}}@media screen and (min-width:59.6875em){.glsr-field label{font-size:12px;font-size:1.2rem}.glsr-field{margin-bottom:1.6em}}@media screen and (min-width:68.75em){.glsr-field label{font-size:14px;font-size:1.4rem}.glsr-field{margin-bottom:1.6471em}}@media screen and (min-width:77.5em){.glsr-field label{font-size:16px;font-size:1.6rem}.glsr-field{margin-bottom:1.6842em}}
assets/css/twenty-fourteen.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-field,h3.glsr-form-title{margin-bottom:24px}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field-error{display:block;font-size:.875em}.glsr-field ul{list-style-type:none}.glsr-field label{display:block}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#21759b}.glsr-form-description span,.gslr-has-errors{color:#c0392b}h3.glsr-form-title{font:900 16px/1.5 Lato,sans-serif;text-transform:uppercase;margin-top:0}.shortcode-reviews-form .glsr-form-description{font-size:14px;line-height:1.7142857143;color:#767676}.glsr-field label[for^=rating]:after,.glsr-field-error,.glsr-field.glsr-required label:after{color:#c0392b}.glsr-field input[type=text],.glsr-field input[type=email],.glsr-field input[type=url]{width:100%}@media screen and (max-width:1079px){#secondary .gl-star-rating[data-star-rating] .gl-star-rating-stars>span{width:21px;height:21px;background-size:21px}#secondary .gl-star-rating[data-star-rating] .gl-star-rating-stars>span:last-child{margin-right:0}}
assets/css/twenty-sixteen.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.glsr-field label:not([for^=terms]),.glsr-form-messages,.glsr-form-title{font-family:Montserrat,"Helvetica Neue",sans-serif}.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field ul{list-style-type:none}.glsr-field label{display:block}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-title{font-size:23px;font-size:1.4375rem;font-weight:700;line-height:1.3125;border-top:4px solid #1a1a1a;padding-top:1.2173913043em;margin-bottom:1.2173913043em}.glsr-form-description{color:#686868;font-size:13px;font-size:.8125rem;line-height:1.6153846154;margin-bottom:2.1538461538em}.glsr-form-description span,.glsr-form-messages{color:#007acc}.gslr-has-errors{color:#e4002e}.glsr-field{margin-bottom:1.75em}.glsr-field-error{display:block;color:#e4002e;font-size:13px;font-size:.8125rem;line-height:1.6153846154}.glsr-field-error:first-child{margin-top:.5384615385em}.glsr-field label:not([for^=terms]){color:#686868;font-size:13px;font-size:.8125rem;letter-spacing:.0769230769em;line-height:1.6153846154;margin-bottom:.5384615385em;text-transform:uppercase}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";font-family:Merriweather,Georgia,serif;color:#007acc;margin-left:5px}.glsr-field ::-webkit-input-placeholder{font-family:Merriweather,Georgia,serif}.glsr-field :-moz-placeholder{font-family:Merriweather,Georgia,serif}.glsr-field ::-moz-placeholder{font-family:Merriweather,Georgia,serif}.glsr-field :-ms-input-placeholder{font-family:Merriweather,Georgia,serif}
assets/css/twenty-ten.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field{margin-bottom:1em}.glsr-field-error{display:block;font-size:.875em}.glsr-field ul{list-style-type:none}.glsr-field label{display:block}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#2b54d6}.gslr-has-errors{color:#ff4b33}.shortcode-reviews-form{position:relative;border-top:1px solid #e7e7e7;margin:24px 0}#content .glsr-form-title,.glsr-form-title{color:#000;font-size:20px;font-weight:600;font-family:"Helvetica Neue",Arial,Helvetica,"Nimbus Sans L",sans-serif;margin:18px 0}.glsr-field label[for^=rating]:after,.glsr-field-error,.glsr-field.glsr-required label:after,.glsr-form-description span{color:#ff4b33}.glsr-field{margin:0 0 9px}.glsr-field input[type=text],.glsr-field input[type=email],.glsr-field input[type=url]{width:98%;margin:0}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field textarea{width:98%}
assets/css/twenty-thirteen.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field-error{display:block;font-size:.875em}.glsr-field ul{list-style-type:none}.glsr-field label{display:block}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#21759b}.glsr-field label[for^=rating]:after,.glsr-field-error,.glsr-field.glsr-required label:after,.glsr-form-description span,.gslr-has-errors{color:#ed331c}.glsr-field{margin-bottom:12px}.glsr-field input[type=text],.glsr-field input[type=email],.glsr-field input[type=url],.glsr-field textarea{width:100%}
assets/css/twenty-twelve.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Star Rating
3
+ *
4
+ * Version: 1.3.0
5
+ * Author: Paul Ryley (http://geminilabs.io)
6
+ * URL: https://github.com/geminilabs/star-rating.js
7
+ * License: MIT
8
+ */.gl-star-rating[data-star-rating]{position:relative;display:block}.gl-star-rating[data-star-rating]>select{position:absolute!important;top:0;left:0;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);-webkit-clip-path:circle(1px at 0 0);clip-path:circle(1px at 0 0);visibility:visible}.gl-star-rating[data-star-rating] .gl-star-rating-stars{display:inline-block;height:26px;vertical-align:middle;cursor:pointer}.gl-star-rating[data-star-rating] .gl-star-rating-stars>span{display:inline-block;width:24px;height:24px;background-size:24px;background-repeat:no-repeat;background-image:url(../img/star-empty.svg);margin-right:4px}.gl-star-rating[data-star-rating] .gl-star-rating-stars.s100>span,.gl-star-rating[data-star-rating] .gl-star-rating-stars.s10>span:nth-child(1),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s20>span:nth-child(-1n+2),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s30>span:nth-child(-1n+3),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s40>span:nth-child(-1n+4),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s50>span:nth-child(-1n+5),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s60>span:nth-child(-1n+6),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s70>span:nth-child(-1n+7),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s80>span:nth-child(-1n+8),.gl-star-rating[data-star-rating] .gl-star-rating-stars.s90>span:nth-child(-1n+9){background-image:url(../img/star-filled.svg)}.gl-star-rating[data-star-rating] .gl-star-rating-text{display:inline-block;position:relative;height:26px;line-height:26px;font-size:.8em;font-weight:600;color:#fff;background-color:#1a1a1a;white-space:nowrap;vertical-align:middle;padding:0 12px 0 6px;margin:0 0 0 12px}.gl-star-rating[data-star-rating] .gl-star-rating-text:before{position:absolute;top:0;left:-12px;width:0;height:0;content:"";border-style:solid;border-width:13px 12px 13px 0;border-color:transparent #1a1a1a transparent transparent}[id^=geminilabs-site-reviews].widget ul.glsr-reviews{list-style:none;margin-left:0}[id^=geminilabs-site-reviews].widget .glsr-review-title,[id^=geminilabs-site-reviews].widget p{margin-bottom:1em}.glsr-review{margin-bottom:2em}.glsr-review p{margin:0 0 1em}.glsr-review-date{font-style:italic}.glsr-review-meta>span:not(:last-child){margin-right:.5em}.glsr-review-link{white-space:nowrap}[class*=glsr-star-]{display:inline-block;width:16px;height:16px;font-size:16px;vertical-align:text-top;background-repeat:no-repeat;background-size:16px}.glsr-star-empty{background-image:url(../img/star-empty.svg)}.glsr-star-full{background-image:url(../img/star-filled.svg)}.glsr-star-half{background-image:url(../img/star-half.svg)}.gl-star-rating[data-star-rating]{line-height:1}.glsr-field-messages{color:#1976D2}.glsr-has-error{color:#D32F2F}.glsr-field{margin-bottom:1em}.glsr-field-error{display:block;font-size:.875em}.glsr-field ul{list-style-type:none}.glsr-field label[for^=terms]{-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}.glsr-field label[for^=rating]:after,.glsr-field.glsr-required label:after{content:"*";margin-left:.33em}.glsr-field input[type=checkbox]{margin-right:.25em}.glsr-field select[name=rating]{display:block;height:26px;visibility:hidden}.glsr-form-messages{color:#0088d0}.glsr-field label[for^=rating]:after,.glsr-field-error,.glsr-field.glsr-required label:after,.glsr-form-description span,.gslr-has-errors{color:red}h3.glsr-form-title{margin:1.7142857143rem 0}.glsr-field,.shortcode-reviews-form p.glsr-form-description,.widget-area .widget p.glsr-form-description{line-height:1.5;margin:.7857142857rem 0}.glsr-field label{display:block;line-height:1.7142857143}.glsr-field input[type=text],.glsr-field input[type=email],.glsr-field input[type=url],.glsr-field textarea{width:100%;box-sizing:border-box;font-size:12px;font-size:.8571428571 rem;line-height:1.7142857143;padding:10px;padding:.8571428571 rem}
assets/img/booking.svg ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540">
2
+ <title>booking-reviews</title>
3
+ <clipPath>
4
+ <path d="M0 0h960v540H0z"/>
5
+ </clipPath>
6
+ <g fill="none">
7
+ <path fill="#00347D" d="M0 0h960v540H0z"/>
8
+ <path fill="#003E8C" d="M676.33 566.25c2.26-.97 4.22-2.36 5.84-4.03l-4.2-28.26c-4.5-2.5-10.03-3.06-15.2-1.07-8.73 3.32-13.35 12.7-10.93 21.53l24.5 11.8zm-53.78-25.92c7.43-4.4 10.83-13.62 7.63-21.95-3.56-9.3-13.98-13.92-23.27-10.36-7.95 3.06-12.5 11.17-11.37 19.3l27.03 13zm-54.07-26.06c6.17-4.73 8.8-13.12 5.86-20.75-3.57-9.3-14-13.93-23.27-10.36-7.28 2.8-11.7 9.8-11.56 17.15l29 13.98zm95.87-71.8c-3.72 4.8-4.92 11.4-2.58 17.48 1.33 3.45 3.6 6.26 6.4 8.25l-3.82-25.74zM317.7 549.82c-.2-1.25-.52-2.5-1-3.72-3.55-9.28-13.97-13.92-23.26-10.35-9.3 3.56-13.92 13.98-10.36 23.27.83 2.17 2.04 4.1 3.53 5.7l31.1-14.9zm39.48-18.9c-.04-.1-.08-.2-.1-.3-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27.4 1 .86 1.98 1.4 2.88l32.34-15.48zm38.87-18.63c-4.4-7.37-13.6-10.73-21.9-7.55-9.2 3.54-13.84 13.8-10.45 23.04l32.35-15.5zm38.25-18.33c-4.85-5.34-12.65-7.45-19.8-4.7-8.07 3.1-12.64 11.4-11.33 19.6L434.3 494zm37.56-18c-4.76-3.42-11.1-4.46-17-2.2-6.85 2.63-11.17 9-11.53 15.88L471.86 476zm42.3 12.12c5.06-4.88 7-12.5 4.33-19.46-3.6-9.28-14-13.92-23.3-10.36-6.6 2.53-10.85 8.54-11.47 15.14l30.42 14.66zm143.28-92.07c-.27.1-.53.2-.8.3-9.28 3.55-13.92 13.97-10.36 23.26 2.7 7 9.3 11.37 16.4 11.54l-5.24-35.1zm-368.9 98.64c5.6 1.33 10.46 5.3 12.68 11.07 3.56 9.3-1.07 19.7-10.36 23.27-2.65 1.03-5.4 1.38-8.03 1.14l5.72-35.47zm367.8-106.13c-.73.4-1.5.75-2.28 1.05-9.3 3.56-19.7-1.08-23.27-10.37-3.58-9.3 1.05-19.7 10.34-23.27 3.38-1.3 6.9-1.5 10.2-.8l4.98 33.38zm-359.5 54.76c1.68-1.58 3.7-2.88 5.97-3.76 9.3-3.56 19.73 1.08 23.3 10.36 3.56 9.3-1.08 19.7-10.37 23.27-8.58 3.3-18.14-.42-22.35-8.34l3.48-21.52zm390.03-137.2c-4.7-6.26-13.17-8.93-20.85-5.98-9.3 3.57-13.93 14-10.36 23.27 1.25 3.26 3.36 5.95 5.97 7.9l25.24-25.2zm67.47-67.37c-3.72-9-13.96-13.45-23.1-9.94-9.3 3.54-13.93 13.96-10.36 23.25 1.84 4.8 5.5 8.36 9.92 10.18l23.54-23.5zm-443.7 105.3c.5-.25 1-.48 1.55-.7 9.26-3.55 19.7 1.1 23.25 10.37 3.56 9.3-1.08 19.7-10.36 23.27-5.97 2.3-12.4 1.2-17.17-2.32l4.63-28.66-1.94-1.98zm459.34-170.68c4.17 1.88 7.62 5.34 9.4 9.95 3.56 9.3-1.08 19.7-10.37 23.27-9.27 3.56-19.7-1.07-23.25-10.36-3.56-9.27 1.07-19.7 10.36-23.25 1.4-.53 2.8-.88 4.23-1.06l9.66 1.46zm14.97 2.27c.13 1.7.5 3.42 1.15 5.1 2.5 6.5 8.37 10.74 14.85 11.45l12.27-12.25-28.27-4.3zM285.2 318.38c.53-6.7 4.8-12.83 11.5-15.4 9.28-3.56 19.7 1.08 23.27 10.37 3.56 9.28-1.08 19.7-10.36 23.27-1.7.65-3.43 1.03-5.17 1.15l-19.23-19.4zm439.46-151.92c-.6 6.64-4.85 12.7-11.5 15.25-9.28 3.58-19.7-1.06-23.27-10.35-1.3-3.4-1.53-6.97-.8-10.3l35.55 5.42zm-96.53-14.67c1.8 8.5-2.78 17.3-11.17 20.52-9.3 3.57-19.7-1.07-23.27-10.36-2-5.2-1.44-10.74 1.1-15.25l33.32 5.08zm30.26 4.6c-.37.14-.73.3-1.1.43-6.53 2.5-13.63.95-18.5-3.43l19.6 3zM238.5 271.3c9.12-3.66 13.64-13.97 10.1-23.17-3.56-9.3-13.97-13.93-23.26-10.36-4.82 1.85-8.4 5.54-10.2 9.96l23.36 23.57zm333.9-137.45c-1.1 5.98-5.2 11.28-11.3 13.62-9.27 3.56-19.7-1.08-23.26-10.36-3.56-9.27 1.08-19.7 10.36-23.25 5.4-2.08 11.2-1.37 15.8 1.4l8.4 18.6zm-235.72 15.9c7.5-.3 14.7 4.15 17.52 11.55 3.57 9.28-1.07 19.7-10.36 23.27-9.28 3.56-19.7-1.08-23.27-10.36-3.56-9.27 1.08-19.7 10.36-23.25.62-.24 1.23-.44 1.85-.6l3.9-.6zm49.12-13.6c3.88 1.94 7.08 5.28 8.76 9.66 3.56 9.3-1.07 19.7-10.36 23.3-9.3 3.55-19.7-1.1-23.27-10.37-1.62-4.23-1.55-8.68-.1-12.62l21.92-3.3 3.05-6.64zm170.03-39.02c-1.82 4.43-5.38 8.13-10.2 9.98-9.3 3.6-19.7-1.05-23.28-10.34-3.56-9.3 1.08-19.7 10.36-23.27 4.16-1.6 8.5-1.56 12.4-.2l10.74 23.84zM171.45 203.65c6.16-4.73 8.77-13.12 5.84-20.74-1.3-3.37-3.53-6.13-6.26-8.1l-24.55 3.7 24.94 25.16zm46.3-35.93c3.36 9.2-1.28 19.44-10.46 22.96-9.3 3.57-19.73-1.07-23.3-10.36-.92-2.4-1.3-4.9-1.17-7.32l34.92-5.28zm41.18-6.22c-1.07 6.02-5.17 11.34-11.3 13.7-8.66 3.32-18.33-.5-22.47-8.6l33.77-5.1zM411.3 80.63c3.6 1.98 6.54 5.2 8.12 9.33 3.57 9.3-1.07 19.7-10.36 23.27-4.27 1.64-8.8 1.54-12.76.05l15-32.65zm127.6-21.06c-2 3.17-5 5.74-8.77 7.2-9.28 3.55-19.7-1.1-23.27-10.37-3.56-9.3 1.08-19.7 10.36-23.27 3.08-1.2 6.28-1.47 9.3-.98l12.38 27.42zM436.78 25.14c3.3 2 6 5.08 7.5 8.97 3.57 9.3-1.06 19.73-10.35 23.3-4.04 1.54-8.28 1.54-12.1.3l14.95-32.55zm84.94-3.66c-1.86 2.12-4.26 3.84-7.08 4.92-9.3 3.56-19.7-1.07-23.27-10.36-3.56-9.3 1.07-19.7 10.36-23.27 2.14-.82 4.34-1.2 6.5-1.2l13.5 29.9zm-59.47-51.8c3.04 2 5.5 4.93 6.9 8.6 3.57 9.27-1.07 19.7-10.36 23.26-3.8 1.45-7.8 1.54-11.4.5l14.84-32.36zm42.1 13.28c-1.5 1.28-3.25 2.34-5.2 3.08-9.3 3.57-19.7-1.07-23.27-10.36-3.56-9.28 1.07-19.7 10.36-23.27 1.28-.47 2.58-.8 3.88-1l14.23 31.57zM486.83-55.9c-.98.62-2.04 1.16-3.17 1.6-3.55 1.35-7.27 1.52-10.7.68l7.5-16.38 6.37 14.1zm173.35 582.06c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.9 23.3 10.35zm-55.85-24.87c9.3-3.58 13.93-14 10.36-23.28-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.5c9.26-3.57 13.9-14 10.34-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm-96.23-9.38c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.97-10.37 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.57-13.92 14-10.36 23.28 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.57-13.94 14-10.37 23.28 3.57 9.27 14 13.9 23.28 10.35zm-298 68.08c9.3-3.56 13.93-13.98 10.37-23.27-3.57-9.3-14-13.92-23.27-10.36-9.28 3.56-13.92 13.97-10.35 23.26 3.56 9.3 13.98 13.92 23.26 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.57-13.92 14-10.36 23.28 3.56 9.3 13.97 13.92 23.26 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.57-13.93 14-10.36 23.28 3.56 9.27 13.98 13.9 23.27 10.35zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.94-23.27-10.37-9.26 3.6-13.9 14-10.36 23.3 3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.37zm40.35-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.95 23.27 10.38zm40.35-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.57-9.27-13.98-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-14 10.37-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm-257.62 52.58c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-13.98-13.9-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.56 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.28-3.56 13.92-13.98 10.35-23.27-3.56-9.3-13.98-13.9-23.27-10.35-9.3 3.57-13.92 13.98-10.36 23.27 3.56 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.56 9.3 14 13.92 23.26 10.36zm40.36-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.58-9.3-14-13.93-23.28-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.35-15.48c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.97-13.93-23.26-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm-297.98 68.1c9.27-3.58 13.9-14 10.35-23.28-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.34-15.5c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.35zM502 355.37c9.3-3.56 13.94-13.98 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.27-14-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.3-3.57 13.93-14 10.36-23.28-3.56-9.28-13.98-13.9-23.27-10.35-9.26 3.57-13.9 13.98-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.27 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.27-3.55 13.9-13.97 10.34-23.25-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.92 13.97-10.36 23.26 3.57 9.28 14 13.92 23.28 10.36zm-338.36 83.6c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.28-13.97-13.9-23.26-10.35-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36zM405.8 346c9.3-3.56 13.93-13.97 10.37-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.55 9.28 13.97 13.92 23.25 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.27 3.56-13.9 13.98-10.34 23.27 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.28 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.26-3.55 13.9-13.97 10.34-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.92 23.28 10.35zm40.33-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.57-9.3-14-13.93-23.28-10.37-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.55 13.9-13.97 10.35-23.26-3.57-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.57 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.92-23.25-10.36-9.3 3.57-13.93 13.98-10.37 23.27 3.57 9.3 14 13.94 23.27 10.37zm-378.7 99.06c9.3-3.56 13.94-13.98 10.37-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.57-9.28-14-13.92-23.27-10.36-9.28 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.92 23.27 10.35zm40.36-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.58-13.92 14-10.36 23.28 3.56 9.3 14 13.94 23.26 10.37zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.28-14-13.92-23.27-10.35-9.3 3.56-13.94 13.98-10.37 23.27 3.57 9.28 14 13.92 23.28 10.36zm40.34-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.92-23.27-10.36-9.27 3.57-13.9 13.98-10.34 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.6-9.28-14-13.92-23.3-10.35-9.3 3.56-13.92 13.98-10.35 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.6 9.28 14 13.92 23.3 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.27-3.56 13.9-13.98 10.34-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.94 14-10.37 23.27 3.56 9.3 13.98 13.93 23.26 10.36zM278.6 255.9c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.37-9.26 3.57-13.9 14-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.58 9.3 14 13.93 23.3 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.25 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.27-3.55 13.9-13.97 10.34-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 13.98-10.36 23.27 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.6-13.93 14-10.36 23.3 3.56 9.3 13.98 13.92 23.27 10.35zm40.35-15.5c9.3-3.56 13.92-13.97 10.35-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.58 9.27 14 13.9 23.28 10.34zm40.35-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.56-9.3-13.97-13.95-23.26-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm-297.98 68.08c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.3-14-13.92-23.27-10.36-9.3 3.6-13.93 14-10.36 23.3 3.57 9.3 14 13.9 23.28 10.35zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.27 14 13.9 23.27 10.34zm40.36-15.48c9.28-3.57 13.9-14 10.35-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm121.06-46.48c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.3-3.55 13.93-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.93 14-10.37 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zM449.4 97.74c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.58-13.9 14-10.35 23.3 3.57 9.27 13.98 13.9 23.27 10.35zm40.37-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.26 14 13.9 23.28 10.34zm-15.5-40.35c9.3-3.58 13.93-14 10.37-23.3-3.57-9.3-14-13.9-23.27-10.35-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36z"/>
9
+ <ellipse fill="#fff" cx="414.946" cy="222.281" rx="10.624" ry="10.624"/>
10
+ <path fill="#fff" d="M556.4 238.9c-3.46 0-6.12 1.4-7.46 3.93l-.52.97-.84-.74c-2.96-2.54-8.24-5.57-16.8-5.57-17.1 0-28.6 12.8-28.6 31.9 0 19.06 11.9 32.38 28.94 32.38 5.8 0 10.4-1.35 14.04-4.12l1.4-1.04v1.76c0 8.58-5.54 13.3-15.56 13.3-4.9 0-9.32-1.18-12.3-2.26-3.86-1.17-6.12-.2-7.7 3.68l-1.44 3.58-2.06 5.24 1.26.68c6.43 3.4 14.78 5.42 22.34 5.42 15.56 0 33.7-7.95 33.7-30.37l.08-58.76h-8.47zm-22.02 46.9c-10 0-13.56-8.73-13.56-16.88 0-3.6.9-15.3 12.58-15.3 5.8 0 13.56 1.65 13.56 15.9 0 13.45-6.86 16.28-12.58 16.28zm-61.4-48.18c-9.35 0-15.3 4.15-18.64 7.67l-1.12 1.12-.4-1.54c-.98-3.78-4.3-5.84-9.3-5.84h-8.23l.05 64.06h18.25v-29.5c0-2.9.4-5.4 1.14-7.68 2.04-6.9 7.7-11.2 14.8-11.2 5.7 0 7.9 3 7.9 10.76v27.9c0 6.64 3.1 9.75 9.72 9.75h8.7l-.03-40.75c0-16.2-7.9-24.77-22.85-24.77v.02zm-57.8 1.44h-8.65l.05 49.56v14.5h9.25c.1 0 .2.03.33.03l4.3-.02h4.28v-.03h.03l.03-54.3c0-6.55-3.13-9.73-9.6-9.73h-.03zm-31.43 33.54c-.78-1.44-1.63-2.6-2.55-3.57l-.6-.63.6-.6c.92-.94 1.8-2.05 2.7-3.34l17.07-25.42h-20.72l-12.87 19.9c-.72 1.04-2.2 1.6-4.38 1.6h-2.95v-37.57c0-7.52-4.7-8.55-9.73-8.55h-8.67v88.72h18.4v-26.6h1.72c2.1 0 3.52.24 4.18 1.38l10.16 19.16c2.83 5.2 5.66 6.07 10.96 6.07h14.1l-10.5-17.36-6.95-13.18.05-.02zm-82.2-34.83c-19.4 0-33.46 13.95-33.46 33.2 0 19.2 14.04 33.16 33.43 33.16 19.42 0 33.56-13.95 33.56-33.16 0-19.23-14.14-33.18-33.57-33.18v-.03zm0 50.84c-9.16 0-15.52-7.26-15.52-17.64 0-10.4 6.36-17.67 15.5-17.67 9.2 0 15.65 7.3 15.65 17.67 0 10.56-6.3 17.66-15.64 17.66v-.02zm-71.75-50.83c-19.37 0-33.43 13.95-33.43 33.2 0 19.2 14.08 33.16 33.43 33.16 19.43 0 33.57-13.95 33.57-33.16 0-19.23-14.13-33.18-33.57-33.18v-.03zm0 50.84c-9.16 0-15.5-7.26-15.5-17.64 0-10.4 6.34-17.67 15.5-17.67 9.2 0 15.65 7.3 15.65 17.67 0 10.56-6.3 17.66-15.65 17.66v-.02zm-49-30.93l-2.72-1.52 2.35-2c2.73-2.34 7.3-7.6 7.3-16.73 0-13.95-10.82-22.95-27.55-22.95h-21.25c-4.97.2-8.96 4.2-9.02 9.22v79.1l8.8.04H160.56c18.6 0 30.64-10.14 30.64-25.8 0-8.48-3.9-15.7-10.42-19.37zm-19 30.2h-14.87v-17.8c0-3.8 1.46-5.76 4.7-6.22h10.16c7.22 0 11.9 4.56 11.9 11.94 0 7.58-4.56 12.06-11.9 12.08zm-14.87-47.94v-4.7c0-4.07 1.72-6.03 5.52-6.25h7.62c6.5 0 10.42 3.9 10.42 10.43 0 4.96-2.68 10.8-10.2 10.8h-13.37v-10.27z"/>
11
+ <ellipse fill="#009FE2" cx="582.698" cy="293.181" rx="10.624" ry="10.624"/>
12
+ <path fill="#009FE2" d="M808.2 237.83c-7.55 0-14.9 3.56-19.6 9.52l-1.34 1.67-1.04-1.88c-3.4-6.2-9.2-9.32-17.3-9.32-8.47 0-14.18 4.74-16.8 7.56l-1.75 1.86-.68-2.47c-.97-3.52-4.12-5.48-8.92-5.48h-7.65l-.08 63.8h17.44v-28.16c0-2.47.3-4.9.9-7.46 1.7-6.82 6.26-14.14 13.95-13.4 4.74.45 7.07 4.1 7.07 11.2v37.8h17.55v-28.15c0-3.1.3-5.4 1-7.7 1.4-6.5 6.17-13.16 13.6-13.16 5.37 0 7.36 3.03 7.36 11.22v28.48c0 6.45 2.86 9.32 9.32 9.32h8.2v-40.72c0-16.3-7.14-24.55-21.24-24.55zm-115.14-.05c-19.43 0-33.47 13.95-33.47 33.2 0 19.2 14.05 33.16 33.44 33.16 19.43 0 33.56-13.94 33.56-33.16 0-19.23-14.15-33.17-33.57-33.17l.03-.02zm0 50.84c-9.18 0-15.56-7.26-15.56-17.65 0-10.4 6.4-17.66 15.54-17.66 9.18 0 15.65 7.3 15.65 17.68 0 10.56-6.32 17.66-15.66 17.66l.02-.02zm-43.57-7.64c-.06.05-7.57 7.95-17.45 7.95-9 0-18.1-5.5-18.1-17.83 0-10.65 7.05-18.1 17.15-18.1 3.28 0 7 1.18 7.6 3.16l.06.33c1.35 4.47 5.4 4.7 6.22 4.7h9.56v-8.34c0-11-14.02-15-23.47-15-20.15 0-34.8 14.02-34.8 33.36s14.5 33.37 34.45 33.37c17.3 0 26.72-11.4 26.8-11.5l.52-.64-7.56-12.55-1 1.08z"/>
13
+ </g>
14
+ </svg>
assets/img/facebook.svg ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540">
2
+ <title>facebook-reviews</title>
3
+ <clipPath>
4
+ <path d="M0 0h960v540H0z"/>
5
+ </clipPath>
6
+ <g fill="none">
7
+ <path fill="#3B5998" d="M0 0h960v540H0z"/>
8
+ <path fill="#4161A1" d="M676.33 566.25c2.26-.97 4.22-2.36 5.84-4.03l-4.2-28.26c-4.5-2.5-10.03-3.06-15.2-1.07-8.73 3.3-13.35 12.7-10.93 21.52l24.5 11.8zm-53.78-25.92c7.43-4.4 10.83-13.62 7.63-21.95-3.56-9.3-13.98-13.92-23.27-10.36-7.94 3.06-12.5 11.17-11.36 19.3l27.03 13zm-54.07-26.06c6.17-4.73 8.8-13.12 5.86-20.75-3.57-9.3-14-13.93-23.27-10.36-7.28 2.8-11.7 9.8-11.56 17.15l29 14zm95.87-71.8c-3.72 4.8-4.92 11.4-2.58 17.48 1.33 3.45 3.6 6.26 6.4 8.25l-3.82-25.74zM317.7 549.82c-.2-1.25-.52-2.5-1-3.72-3.55-9.28-13.97-13.92-23.26-10.35-9.3 3.56-13.92 13.98-10.36 23.27.83 2.17 2.04 4.1 3.53 5.7l31.1-14.9zm39.48-18.9c-.04-.1-.08-.2-.1-.3-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27.4 1 .86 1.98 1.4 2.88l32.34-15.48zm38.87-18.63c-4.4-7.38-13.6-10.74-21.9-7.56-9.2 3.54-13.84 13.8-10.45 23.04l32.35-15.5zm38.25-18.34c-4.85-5.34-12.65-7.45-19.8-4.7-8.07 3.1-12.64 11.4-11.33 19.6L434.3 494zm37.56-18c-4.76-3.42-11.1-4.46-17-2.2-6.85 2.63-11.17 9-11.53 15.88L471.86 476zm42.3 12.12c5.06-4.88 7-12.5 4.33-19.46-3.6-9.28-14-13.92-23.3-10.36-6.6 2.53-10.86 8.54-11.48 15.14l30.42 14.66zM657.44 396c-.27.1-.53.2-.8.3-9.28 3.56-13.92 13.98-10.36 23.27 2.7 7 9.3 11.37 16.4 11.54l-5.24-35.1zm-368.9 98.65c5.6 1.33 10.46 5.3 12.68 11.07 3.56 9.3-1.07 19.7-10.36 23.27-2.65 1.02-5.4 1.37-8.03 1.13l5.72-35.47zm367.8-106.13c-.73.4-1.5.75-2.28 1.05-9.3 3.56-19.7-1.08-23.27-10.37-3.6-9.3 1.04-19.7 10.33-23.27 3.38-1.3 6.9-1.5 10.2-.8l4.98 33.38zm-359.5 54.76c1.68-1.58 3.7-2.88 5.97-3.76 9.3-3.56 19.74 1.08 23.3 10.36 3.57 9.3-1.07 19.7-10.36 23.27-8.58 3.3-18.14-.42-22.35-8.34l3.47-21.5zm390.03-137.2c-4.7-6.26-13.17-8.93-20.85-5.98-9.3 3.57-13.93 14-10.36 23.27 1.25 3.26 3.36 5.95 5.97 7.9l25.24-25.2zm67.47-67.37c-3.72-9-13.96-13.44-23.1-9.93-9.3 3.54-13.93 13.96-10.36 23.25 1.84 4.8 5.5 8.36 9.92 10.18l23.54-23.5zM310.64 344c.5-.24 1-.47 1.55-.7 9.25-3.54 19.7 1.1 23.24 10.38 3.56 9.3-1.08 19.7-10.36 23.27-5.97 2.3-12.4 1.2-17.17-2.32l4.64-28.66-1.94-1.98zm459.34-170.67c4.17 1.88 7.62 5.34 9.4 9.95 3.56 9.3-1.08 19.7-10.37 23.27-9.26 3.56-19.7-1.07-23.24-10.36-3.56-9.28 1.07-19.7 10.36-23.26 1.4-.53 2.8-.88 4.23-1.06l9.66 1.46zm14.97 2.27c.13 1.7.5 3.42 1.15 5.1 2.5 6.5 8.37 10.74 14.85 11.45l12.27-12.25-28.27-4.3zM285.2 318.38c.53-6.7 4.8-12.83 11.5-15.4 9.28-3.56 19.7 1.08 23.27 10.37 3.56 9.28-1.08 19.7-10.36 23.27-1.7.65-3.42 1.03-5.16 1.15l-19.23-19.4zm439.46-151.92c-.6 6.64-4.85 12.7-11.5 15.25-9.28 3.6-19.7-1.05-23.27-10.34-1.3-3.4-1.54-6.97-.8-10.3l35.54 5.42zm-96.53-14.67c1.8 8.5-2.78 17.3-11.17 20.5-9.3 3.58-19.7-1.06-23.27-10.35-2-5.2-1.45-10.74 1.1-15.25l33.3 5.08zm30.26 4.6c-.38.13-.74.3-1.1.42-6.54 2.5-13.64.95-18.5-3.43l19.6 3zM238.5 271.3c9.12-3.66 13.64-13.97 10.1-23.17-3.56-9.3-13.97-13.93-23.26-10.36-4.82 1.85-8.4 5.54-10.2 9.96l23.36 23.57zm333.9-137.45c-1.1 5.98-5.2 11.28-11.3 13.62-9.27 3.56-19.7-1.08-23.26-10.36-3.56-9.26 1.08-19.7 10.36-23.24 5.4-2.08 11.2-1.37 15.8 1.4l8.4 18.6zm-235.72 15.9c7.5-.3 14.7 4.15 17.52 11.55 3.57 9.28-1.07 19.7-10.36 23.27-9.28 3.56-19.7-1.08-23.27-10.36-3.56-9.26 1.08-19.7 10.36-23.24.62-.24 1.23-.44 1.85-.6l3.9-.6zm49.12-13.6c3.88 1.94 7.08 5.28 8.76 9.66 3.56 9.3-1.07 19.7-10.36 23.3-9.3 3.56-19.7-1.1-23.27-10.36-1.62-4.23-1.55-8.68-.1-12.62l21.92-3.3 3.05-6.64zm170.03-39.02c-1.82 4.43-5.38 8.13-10.2 9.98-9.3 3.6-19.7-1.04-23.28-10.33-3.56-9.3 1.08-19.7 10.36-23.27 4.17-1.6 8.5-1.56 12.4-.2l10.75 23.84zM171.45 203.65c6.16-4.73 8.77-13.12 5.84-20.74-1.3-3.36-3.54-6.12-6.27-8.1l-24.55 3.7 24.94 25.17zm46.3-35.93c3.36 9.2-1.28 19.44-10.46 22.96-9.3 3.57-19.74-1.07-23.3-10.36-.93-2.4-1.3-4.9-1.18-7.32l34.92-5.28zm41.18-6.22c-1.07 6.02-5.17 11.34-11.3 13.7-8.66 3.32-18.33-.5-22.47-8.6l33.77-5.1zM411.3 80.63c3.6 1.98 6.54 5.2 8.12 9.33 3.57 9.3-1.07 19.7-10.36 23.27-4.27 1.64-8.8 1.54-12.76.05l15-32.65zm127.6-21.06c-2 3.17-5 5.74-8.77 7.2-9.28 3.55-19.7-1.1-23.27-10.37-3.56-9.3 1.08-19.7 10.36-23.27 3.08-1.2 6.28-1.47 9.3-.98l12.38 27.42zM436.78 25.14c3.3 2 6 5.08 7.5 8.97 3.57 9.3-1.06 19.74-10.35 23.3-4.04 1.55-8.28 1.55-12.1.3l14.95-32.54zm84.94-3.66c-1.86 2.12-4.26 3.84-7.08 4.92-9.3 3.56-19.7-1.07-23.27-10.36-3.56-9.3 1.07-19.7 10.36-23.27 2.14-.82 4.34-1.2 6.5-1.2l13.5 29.9zm-59.47-51.8c3.04 2 5.5 4.93 6.9 8.6 3.57 9.27-1.07 19.7-10.36 23.26-3.8 1.45-7.8 1.54-11.4.5l14.83-32.36zm42.1 13.28c-1.5 1.28-3.25 2.34-5.2 3.08-9.3 3.57-19.7-1.07-23.27-10.36-3.56-9.28 1.07-19.7 10.36-23.27 1.28-.46 2.58-.8 3.88-1L504.35-17zM486.83-55.9c-.98.62-2.04 1.16-3.17 1.6-3.55 1.35-7.27 1.52-10.7.68l7.5-16.38 6.37 14.1zm173.35 582.06c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.96-23.27-10.4-9.3 3.57-13.93 14-10.36 23.28 3.6 9.3 14 13.9 23.3 10.35zm-55.85-24.87c9.3-3.6 13.93-14 10.36-23.3-3.6-9.3-14-13.92-23.3-10.35-9.28 3.56-13.9 13.98-10.36 23.26 3.57 9.3 13.98 13.94 23.27 10.37zm40.36-15.5c9.25-3.58 13.9-14 10.33-23.3-3.57-9.27-14-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm-96.24-9.4c9.3-3.55 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.97-10.37 23.26 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.93-13.96 10.36-23.25-3.57-9.3-14-13.93-23.28-10.36-9.3 3.56-13.92 14-10.36 23.27 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.47c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.57-13.94 14-10.37 23.28 3.56 9.27 14 13.9 23.27 10.35zm-298 68.08c9.3-3.55 13.93-13.97 10.37-23.26-3.57-9.3-14-13.92-23.27-10.36-9.28 3.56-13.92 13.97-10.35 23.26 3.56 9.3 13.98 13.92 23.26 10.36zm40.36-15.5c9.3-3.55 13.93-13.96 10.36-23.25-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 14-10.36 23.27 3.56 9.3 13.97 13.92 23.26 10.36zm40.36-15.47c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.57-13.93 14-10.36 23.28 3.56 9.27 13.98 13.9 23.27 10.35zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.94-23.26-10.37-9.26 3.6-13.9 14-10.36 23.3 3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.58-9.3-14-13.94-23.28-10.37-9.3 3.57-13.94 14-10.37 23.28 3.56 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.55-9.3-13.97-13.93-23.26-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.95 23.27 10.38zm40.35-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.57-9.28-13.98-13.9-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.28-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm-257.62 52.58c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-13.98-13.9-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.56 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.28-3.56 13.92-13.98 10.35-23.27-3.56-9.3-13.98-13.9-23.27-10.35-9.3 3.57-13.92 13.98-10.36 23.27 3.56 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.56-9.3-13.98-13.9-23.26-10.35-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.35-15.48c9.3-3.58 13.93-14 10.36-23.28-3.55-9.3-13.97-13.93-23.26-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.56 9.3 14 13.92 23.26 10.36zm40.36-15.5c9.3-3.57 13.93-14 10.37-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.56-13.94 13.98-10.37 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.36-15.5c9.3-3.56 13.94-14 10.37-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.55 13.92-13.97 10.36-23.25-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.92 13.97-10.36 23.26 3.56 9.27 13.98 13.9 23.27 10.35zm-297.98 68.1c9.27-3.57 13.9-14 10.35-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.34-15.5c9.3-3.56 13.93-14 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 14-10.37 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.25-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.55 9.27 13.97 13.9 23.26 10.35zm40.36-15.5c9.28-3.54 13.92-13.96 10.36-23.25-3.58-9.28-14-13.92-23.28-10.36-9.3 3.56-13.93 14-10.36 23.26 3.55 9.28 13.97 13.92 23.26 10.35zM502 355.38c9.3-3.56 13.94-13.98 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.26-14-13.9-23.27-10.34-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.3-3.58 13.93-14 10.36-23.3-3.56-9.27-13.98-13.9-23.27-10.34-9.25 3.57-13.9 13.98-10.33 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.58 13.92-14 10.36-23.28-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.6 9.3 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.57 13.93-13.98 10.36-23.27-3.6-9.3-14-13.93-23.3-10.36-9.28 3.56-13.9 13.98-10.36 23.27 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.26-3.56 13.9-13.98 10.33-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.92 13.97-10.36 23.26 3.57 9.28 14 13.92 23.28 10.36zm-338.37 83.6c9.3-3.57 13.93-14 10.36-23.28-3.56-9.28-13.98-13.9-23.27-10.35-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36zM405.8 346c9.3-3.56 13.93-13.97 10.37-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.36-9.3 3.54-13.93 13.96-10.37 23.25 3.54 9.28 13.96 13.92 23.24 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.26 3.56-13.9 13.98-10.33 23.27 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.57-13.93 14-10.36 23.28 3.6 9.28 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.6-9.3-14-13.93-23.3-10.36-9.28 3.56-13.9 13.98-10.36 23.26 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.25-3.55 13.9-13.97 10.33-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.9 23.28 10.34zm40.32-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.57-9.3-14-13.94-23.28-10.38-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.55 13.9-13.97 10.35-23.26-3.57-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.56 9.3 14 13.94 23.27 10.37zm40.34-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.9-23.25-10.35-9.3 3.57-13.93 13.98-10.37 23.27 3.57 9.3 14 13.94 23.27 10.37zm-378.7 99.06c9.3-3.56 13.94-13.98 10.37-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.9 23.27 10.34zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.94-23.26-10.38-9.28 3.58-13.92 14-10.36 23.28 3.56 9.3 14 13.94 23.26 10.37zm40.36-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.36-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.9-23.26-10.35-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.28-14-13.92-23.27-10.35-9.3 3.56-13.94 13.98-10.37 23.27 3.56 9.28 14 13.92 23.27 10.36zm40.34-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.54-13.93 13.96-10.36 23.25 3.56 9.28 13.98 13.92 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.9-23.27-10.35-9.28 3.57-13.9 13.98-10.35 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.3-3.57 13.93-13.98 10.37-23.27-3.6-9.28-14-13.92-23.3-10.35-9.3 3.56-13.92 13.98-10.35 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.6 9.28 14 13.92 23.3 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.54-13.92 13.96-10.35 23.25 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.27-3.56 13.9-13.98 10.34-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.56-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.36-15.48c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.58-13.94 14-10.37 23.28 3.56 9.3 13.98 13.93 23.26 10.36zM278.6 255.9c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.37-9.27 3.57-13.9 14-10.35 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.93 23.3 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.54-13.92 13.96-10.35 23.24 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.27-3.55 13.9-13.97 10.34-23.26-3.55-9.28-13.97-13.92-23.26-10.36-9.28 3.57-13.92 13.98-10.36 23.27 3.58 9.3 14 13.93 23.28 10.36zM440 193.94c9.3-3.56 13.93-13.98 10.37-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.6-13.92 14-10.35 23.3 3.56 9.3 13.98 13.93 23.27 10.36zm40.36-15.5c9.3-3.56 13.92-13.97 10.35-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.58 9.26 14 13.9 23.28 10.33zm40.35-15.48c9.3-3.57 13.93-13.98 10.37-23.27-3.56-9.3-13.97-13.96-23.26-10.4-9.3 3.57-13.92 14-10.35 23.28 3.55 9.27 13.97 13.9 23.26 10.35zm-297.97 68.08c9.28-3.56 13.92-13.98 10.36-23.27-3.58-9.3-14-13.92-23.28-10.36-9.3 3.6-13.93 14-10.36 23.3 3.57 9.3 14 13.9 23.28 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.26 14 13.9 23.27 10.33zm40.36-15.48c9.28-3.57 13.9-14 10.35-23.27-3.58-9.3-14-13.96-23.28-10.4-9.3 3.57-13.93 14-10.36 23.28 3.56 9.27 13.98 13.9 23.27 10.35zm121.06-46.48c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.94 14-10.37 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.3-3.55 13.93-13.97 10.36-23.26-3.55-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.93 14-10.37 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.48c9.3-3.56 13.93-13.98 10.37-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm-55.8-24.86c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.6-13.9 14-10.35 23.3 3.57 9.28 13.98 13.9 23.27 10.36zm40.37-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.25 14 13.9 23.28 10.33zm-15.5-40.35c9.3-3.6 13.93-14 10.37-23.3-3.57-9.3-14-13.9-23.27-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.58 9.3 14 13.94 23.28 10.37z"/>
9
+ <path fill="#fff" d="M500.92 246.9c-5.35 0-9.2 1.77-13.12 3.56v40.45c3.75.37 5.9.37 9.45.37 12.85 0 14.6-5.88 14.6-14.1v-19.32c0-6.06-2-10.94-10.93-10.94zm-85.3-2.2c-8.92 0-10.96 4.9-10.96 10.96v3.4h21.88v-3.4c0-6.06-2.03-10.95-10.93-10.95zm-165.28 42.12c0 4.8 2.26 7.28 7.25 7.28 5.35 0 8.52-1.74 12.43-3.54v-9.6h-11.7c-5.56 0-8 1.04-8 5.86zm333.57-39.9c-8.92 0-12 4.87-12 10.93V280c0 6.07 3.08 10.96 12 10.96 8.9 0 12.03-4.9 12.03-10.97v-22.15c0-6.06-3.12-10.94-12.02-10.94zm-377.3 64.72h-26.25v-63.52h-13.12v-21.9h13.12V213.1c0-17.85 7.4-28.47 28.43-28.47h17.52v21.9h-10.95c-8.2 0-8.73 3.06-8.73 8.77l-.03 10.96h19.82l-2.32 21.9h-17.5v63.5zm89.68.16H274.4l-.94-5.53c-10 5.53-18.9 6.43-24.78 6.43-16.04 0-24.58-10.72-24.58-25.54 0-17.48 9.96-23.72 27.8-23.72h18.13v-3.78c0-8.92-1.03-11.54-14.75-11.54h-22.44l2.2-21.9h24.52c30.12 0 36.72 9.52 36.72 33.62v51.96zm74.38-62.08c-13.6-2.33-17.52-2.84-24.07-2.84-11.78 0-15.33 2.6-15.33 12.58v18.9c0 10 3.55 12.6 15.32 12.6 6.54 0 10.45-.53 24.06-2.87v21.34c-11.92 2.67-19.7 3.38-26.26 3.38-28.17 0-39.37-14.82-39.37-36.23v-15.34c0-21.42 11.2-36.27 39.37-36.27 6.57 0 14.34.7 26.26 3.38v21.34zm82.13 26.87h-48.14v1.76c0 10 3.56 12.6 15.32 12.6 10.57 0 17.02-.53 30.6-2.87v21.34c-13.1 2.67-19.92 3.38-32.78 3.38-28.17 0-39.38-14.82-39.38-36.23v-17.54c0-18.73 8.3-34.07 37.2-34.07 28.86 0 37.17 15.16 37.17 34.06v17.53zm85.3.4c0 20.7-5.92 35.78-41.74 35.78-12.93 0-20.52-1.14-34.8-3.34V191.18l26.24-4.38v41.37c5.67-2.1 13-3.18 19.7-3.18 26.23 0 30.6 11.76 30.6 30.66V277zm84.1.45c0 17.85-7.37 35.16-38.2 35.16-30.84 0-38.35-17.3-38.35-35.15v-17.23c0-17.86 7.5-35.17 38.35-35.17 30.83 0 38.2 17.3 38.2 35.17v17.23zm84.04 0c0 17.85-7.38 35.16-38.2 35.16-30.85 0-38.35-17.3-38.35-35.15v-17.23c0-17.86 7.5-35.17 38.34-35.17 30.82 0 38.2 17.3 38.2 35.17v17.23zm86.24 34.2h-28.43L740 271.48v40.14h-26.25V191.18L740 186.8v77.55l24.05-38.12h28.43l-26.25 41.6 26.25 43.8zM667.95 246.9c-8.9 0-12 4.9-12 10.95V280c0 6.07 3.1 10.96 12 10.96S680 286.06 680 280v-22.15c0-6.06-3.15-10.94-12.05-10.94zM807.46 296.66c4.42 0 7.94 3.6 7.94 8.1 0 4.57-3.52 8.13-7.97 8.13-4.43 0-8.02-3.57-8.02-8.14 0-4.5 3.6-8.1 8.03-8.1h.03zm-.03 1.26c-3.56 0-6.47 3.06-6.47 6.84 0 3.85 2.9 6.87 6.5 6.87 3.6.04 6.47-3.02 6.47-6.84s-2.87-6.88-6.47-6.88h-.03zm-1.5 11.56h-1.45v-9.04c.75-.1 1.47-.22 2.55-.22 1.37 0 2.26.3 2.8.68.54.4.83 1 .83 1.87 0 1.2-.8 1.9-1.75 2.2v.08c.8.14 1.33.86 1.5 2.2.22 1.4.44 1.93.6 2.23h-1.52c-.22-.3-.43-1.12-.62-2.3-.2-1.16-.8-1.6-1.94-1.6h-1v3.9zm0-5h1.03c1.18 0 2.2-.44 2.2-1.56 0-.8-.58-1.58-2.2-1.58-.47 0-.8.03-1.04.07v3.07z"/>
10
+ </g>
11
+ </svg>
assets/img/google.svg ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540">
2
+ <title>google-reviews</title>
3
+ <clipPath>
4
+ <path d="M0 0h960v540H0z"/>
5
+ </clipPath>
6
+ <g fill="none">
7
+ <path fill="#C6DAFC" d="M0 0h960v540H0z"/>
8
+ <path fill="#D3E3FC" d="M676.33 566.25c2.26-.97 4.22-2.36 5.84-4.03l-4.2-28.26c-4.5-2.5-10.03-3.06-15.2-1.07-8.73 3.32-13.35 12.7-10.93 21.53l24.5 11.8zm-53.78-25.92c7.43-4.4 10.83-13.62 7.63-21.95-3.56-9.3-13.98-13.92-23.27-10.36-7.95 3.06-12.5 11.17-11.37 19.3l27.03 13zm-54.07-26.06c6.17-4.73 8.8-13.12 5.86-20.75-3.57-9.3-14-13.93-23.27-10.36-7.28 2.8-11.7 9.8-11.56 17.15l29 13.98zm95.87-71.8c-3.72 4.8-4.92 11.4-2.58 17.48 1.33 3.45 3.6 6.26 6.4 8.25l-3.82-25.74zM317.7 549.82c-.2-1.25-.52-2.5-1-3.72-3.55-9.28-13.97-13.92-23.26-10.35-9.3 3.56-13.92 13.98-10.36 23.27.83 2.17 2.04 4.1 3.53 5.7l31.1-14.9zm39.48-18.9c-.04-.1-.08-.2-.1-.3-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27.4 1 .86 1.98 1.4 2.88l32.34-15.48zm38.87-18.63c-4.4-7.37-13.6-10.73-21.9-7.55-9.2 3.54-13.84 13.8-10.45 23.04l32.35-15.5zm38.25-18.33c-4.85-5.34-12.65-7.45-19.8-4.7-8.07 3.1-12.64 11.4-11.33 19.6L434.3 494zm37.56-18c-4.76-3.42-11.1-4.46-17-2.2-6.85 2.63-11.17 9-11.53 15.88L471.86 476zm42.3 12.12c5.06-4.88 7-12.5 4.33-19.46-3.6-9.28-14-13.92-23.3-10.36-6.6 2.53-10.85 8.54-11.47 15.14l30.42 14.66zm143.28-92.07c-.27.1-.53.2-.8.3-9.28 3.55-13.92 13.97-10.36 23.26 2.7 7 9.3 11.37 16.4 11.54l-5.24-35.1zm-368.9 98.64c5.6 1.33 10.46 5.3 12.68 11.07 3.56 9.3-1.07 19.7-10.36 23.27-2.65 1.03-5.4 1.38-8.03 1.14l5.72-35.47zm367.8-106.13c-.73.4-1.5.75-2.28 1.05-9.3 3.56-19.7-1.08-23.27-10.37-3.58-9.3 1.05-19.7 10.34-23.27 3.38-1.3 6.9-1.5 10.2-.8l4.98 33.38zm-359.5 54.76c1.68-1.58 3.7-2.88 5.97-3.76 9.3-3.56 19.73 1.08 23.3 10.36 3.56 9.3-1.08 19.7-10.37 23.27-8.58 3.3-18.14-.42-22.35-8.34l3.48-21.52zm390.03-137.2c-4.7-6.26-13.17-8.93-20.85-5.98-9.3 3.57-13.93 14-10.36 23.27 1.25 3.26 3.36 5.95 5.97 7.9l25.24-25.2zm67.47-67.37c-3.72-9-13.96-13.45-23.1-9.94-9.3 3.54-13.93 13.96-10.36 23.25 1.84 4.8 5.5 8.36 9.92 10.18l23.54-23.5zm-443.7 105.3c.5-.25 1-.48 1.55-.7 9.26-3.55 19.7 1.1 23.25 10.37 3.56 9.3-1.08 19.7-10.36 23.27-5.97 2.3-12.4 1.2-17.17-2.32l4.63-28.66-1.94-1.98zm459.34-170.68c4.17 1.88 7.62 5.34 9.4 9.95 3.56 9.3-1.08 19.7-10.37 23.27-9.27 3.56-19.7-1.07-23.25-10.36-3.56-9.27 1.07-19.7 10.36-23.25 1.4-.53 2.8-.88 4.23-1.06l9.66 1.46zm14.97 2.27c.13 1.7.5 3.42 1.15 5.1 2.5 6.5 8.37 10.74 14.85 11.45l12.27-12.25-28.27-4.3zM285.2 318.38c.53-6.7 4.8-12.83 11.5-15.4 9.28-3.56 19.7 1.08 23.27 10.37 3.56 9.28-1.08 19.7-10.36 23.27-1.7.65-3.43 1.03-5.17 1.15l-19.23-19.4zm439.46-151.92c-.6 6.64-4.85 12.7-11.5 15.25-9.28 3.58-19.7-1.06-23.27-10.35-1.3-3.4-1.53-6.97-.8-10.3l35.55 5.42zm-96.53-14.67c1.8 8.5-2.78 17.3-11.17 20.52-9.3 3.57-19.7-1.07-23.27-10.36-2-5.2-1.44-10.74 1.1-15.25l33.32 5.08zm30.26 4.6c-.37.14-.73.3-1.1.43-6.53 2.5-13.63.95-18.5-3.43l19.6 3zM238.5 271.3c9.12-3.66 13.64-13.97 10.1-23.17-3.56-9.3-13.97-13.93-23.26-10.36-4.82 1.85-8.4 5.54-10.2 9.96l23.36 23.57zm333.9-137.45c-1.1 5.98-5.2 11.28-11.3 13.62-9.27 3.56-19.7-1.08-23.26-10.36-3.56-9.27 1.08-19.7 10.36-23.25 5.4-2.08 11.2-1.37 15.8 1.4l8.4 18.6zm-235.72 15.9c7.5-.3 14.7 4.15 17.52 11.55 3.57 9.28-1.07 19.7-10.36 23.27-9.28 3.56-19.7-1.08-23.27-10.36-3.56-9.27 1.08-19.7 10.36-23.25.62-.24 1.23-.44 1.85-.6l3.9-.6zm49.12-13.6c3.88 1.94 7.08 5.28 8.76 9.66 3.56 9.3-1.07 19.7-10.36 23.3-9.3 3.55-19.7-1.1-23.27-10.37-1.62-4.23-1.55-8.68-.1-12.62l21.92-3.3 3.05-6.64zm170.03-39.02c-1.82 4.43-5.38 8.13-10.2 9.98-9.3 3.6-19.7-1.05-23.28-10.34-3.56-9.3 1.08-19.7 10.36-23.27 4.16-1.6 8.5-1.56 12.4-.2l10.74 23.84zM171.45 203.65c6.16-4.73 8.77-13.12 5.84-20.74-1.3-3.37-3.53-6.13-6.26-8.1l-24.55 3.7 24.94 25.16zm46.3-35.93c3.36 9.2-1.28 19.44-10.46 22.96-9.3 3.57-19.73-1.07-23.3-10.36-.92-2.4-1.3-4.9-1.17-7.32l34.92-5.28zm41.18-6.22c-1.07 6.02-5.17 11.34-11.3 13.7-8.66 3.32-18.33-.5-22.47-8.6l33.77-5.1zM411.3 80.63c3.6 1.98 6.54 5.2 8.12 9.33 3.57 9.3-1.07 19.7-10.36 23.27-4.27 1.64-8.8 1.54-12.76.05l15-32.65zm127.6-21.06c-2 3.17-5 5.74-8.77 7.2-9.28 3.55-19.7-1.1-23.27-10.37-3.56-9.3 1.08-19.7 10.36-23.27 3.08-1.2 6.28-1.47 9.3-.98l12.38 27.42zM436.78 25.14c3.3 2 6 5.08 7.5 8.97 3.57 9.3-1.06 19.73-10.35 23.3-4.04 1.54-8.28 1.54-12.1.3l14.95-32.55zm84.94-3.66c-1.86 2.12-4.26 3.84-7.08 4.92-9.3 3.56-19.7-1.07-23.27-10.36-3.56-9.3 1.07-19.7 10.36-23.27 2.14-.82 4.34-1.2 6.5-1.2l13.5 29.9zm-59.47-51.8c3.04 2 5.5 4.93 6.9 8.6 3.57 9.27-1.07 19.7-10.36 23.26-3.8 1.45-7.8 1.54-11.4.5l14.84-32.36zm42.1 13.28c-1.5 1.28-3.25 2.34-5.2 3.08-9.3 3.57-19.7-1.07-23.27-10.36-3.56-9.28 1.07-19.7 10.36-23.27 1.28-.47 2.58-.8 3.88-1l14.23 31.57zM486.83-55.9c-.98.62-2.04 1.16-3.17 1.6-3.55 1.35-7.27 1.52-10.7.68l7.5-16.38 6.37 14.1zm173.35 582.06c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.9 23.3 10.35zm-55.85-24.87c9.3-3.58 13.93-14 10.36-23.28-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.5c9.26-3.57 13.9-14 10.34-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm-96.23-9.38c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.97-10.37 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.57-13.92 14-10.36 23.28 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.57-13.94 14-10.37 23.28 3.57 9.27 14 13.9 23.28 10.35zm-298 68.08c9.3-3.56 13.93-13.98 10.37-23.27-3.57-9.3-14-13.92-23.27-10.36-9.28 3.56-13.92 13.97-10.35 23.26 3.56 9.3 13.98 13.92 23.26 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.57-13.92 14-10.36 23.28 3.56 9.3 13.97 13.92 23.26 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.57-13.93 14-10.36 23.28 3.56 9.27 13.98 13.9 23.27 10.35zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.94-23.27-10.37-9.26 3.6-13.9 14-10.36 23.3 3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.37zm40.35-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.95 23.27 10.38zm40.35-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.57-9.27-13.98-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-14 10.37-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm-257.62 52.58c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-13.98-13.9-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.56 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.28-3.56 13.92-13.98 10.35-23.27-3.56-9.3-13.98-13.9-23.27-10.35-9.3 3.57-13.92 13.98-10.36 23.27 3.56 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.56 9.3 14 13.92 23.26 10.36zm40.36-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.58-9.3-14-13.93-23.28-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.35-15.48c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.97-13.93-23.26-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm-297.98 68.1c9.27-3.58 13.9-14 10.35-23.28-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.34-15.5c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.35zM502 355.37c9.3-3.56 13.94-13.98 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.27-14-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.3-3.57 13.93-14 10.36-23.28-3.56-9.28-13.98-13.9-23.27-10.35-9.26 3.57-13.9 13.98-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.27 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.27-3.55 13.9-13.97 10.34-23.25-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.92 13.97-10.36 23.26 3.57 9.28 14 13.92 23.28 10.36zm-338.36 83.6c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.28-13.97-13.9-23.26-10.35-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36zM405.8 346c9.3-3.56 13.93-13.97 10.37-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.55 9.28 13.97 13.92 23.25 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.27 3.56-13.9 13.98-10.34 23.27 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.28 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.26-3.55 13.9-13.97 10.34-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.92 23.28 10.35zm40.33-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.57-9.3-14-13.93-23.28-10.37-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.55 13.9-13.97 10.35-23.26-3.57-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.57 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.92-23.25-10.36-9.3 3.57-13.93 13.98-10.37 23.27 3.57 9.3 14 13.94 23.27 10.37zm-378.7 99.06c9.3-3.56 13.94-13.98 10.37-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.57-9.28-14-13.92-23.27-10.36-9.28 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.92 23.27 10.35zm40.36-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.58-13.92 14-10.36 23.28 3.56 9.3 14 13.94 23.26 10.37zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.28-14-13.92-23.27-10.35-9.3 3.56-13.94 13.98-10.37 23.27 3.57 9.28 14 13.92 23.28 10.36zm40.34-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.92-23.27-10.36-9.27 3.57-13.9 13.98-10.34 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.6-9.28-14-13.92-23.3-10.35-9.3 3.56-13.92 13.98-10.35 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.6 9.28 14 13.92 23.3 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.27-3.56 13.9-13.98 10.34-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.94 14-10.37 23.27 3.56 9.3 13.98 13.93 23.26 10.36zM278.6 255.9c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.37-9.26 3.57-13.9 14-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.58 9.3 14 13.93 23.3 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.25 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.27-3.55 13.9-13.97 10.34-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 13.98-10.36 23.27 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.6-13.93 14-10.36 23.3 3.56 9.3 13.98 13.92 23.27 10.35zm40.35-15.5c9.3-3.56 13.92-13.97 10.35-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.58 9.27 14 13.9 23.28 10.34zm40.35-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.56-9.3-13.97-13.95-23.26-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm-297.98 68.08c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.3-14-13.92-23.27-10.36-9.3 3.6-13.93 14-10.36 23.3 3.57 9.3 14 13.9 23.28 10.35zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.27 14 13.9 23.27 10.34zm40.36-15.48c9.28-3.57 13.9-14 10.35-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm121.06-46.48c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.3-3.55 13.93-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.93 14-10.37 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zM449.4 97.74c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.58-13.9 14-10.35 23.3 3.57 9.27 13.98 13.9 23.27 10.35zm40.37-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.26 14 13.9 23.28 10.34zm-15.5-40.35c9.3-3.58 13.93-14 10.37-23.3-3.57-9.3-14-13.9-23.27-10.35-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36z"/>
9
+ <path stroke="#99BAF9" stroke-width="2" d="M606 246H483v51h70.8c-6.6 32.4-34.2 51-70.8 51-43.2 0-78-34.8-78-78s34.8-78 78-78c18.6 0 35.4 6.6 48.6 17.4L570 171c-23.4-20.4-53.4-33-87-33-73.2 0-132 58.8-132 132s58.8 132 132 132c66 0 126-48 126-132 0-7.8-1.2-16.2-3-24z"/>
10
+ <path fill="#4285F4" d="M553.8 297H483v-51h123c1.8 7.8 3 16.2 3 24 0 43.02-15.74 76.6-39.96 99.13L526.9 336.5c13.77-8.7 23.36-22.1 26.9-39.5z"/>
11
+ <path fill="#34A853" d="M526.9 336.5C515 344 499.95 348 483 348c-34.66 0-63.9-22.4-74.12-53.57l-44.38 34.02C385.96 372.12 430.82 402 483 402c32.2 0 62.96-11.42 86.04-32.87L526.9 336.5z"/>
12
+ <path fill="#FBBC05" d="M408.9 294.54c-2.53-7.7-3.9-15.96-3.9-24.54 0-8.58 1.37-16.83 3.9-24.54l-44.38-33.95C355.86 229.13 351 248.97 351 270s4.86 40.88 13.52 58.5l44.4-33.96z"/>
13
+ <path fill="#EA4335" d="M408.9 245.46C419.16 214.36 448.4 192 483 192c18.6 0 35.4 6.6 48.6 17.4L570 171c-23.4-20.4-53.4-33-87-33-52.16 0-97 29.86-118.48 73.5l44.4 33.96z"/>
14
+ </g>
15
+ </svg>
assets/img/icon.png ADDED
Binary file
assets/img/pinned.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
2
+ <title>pinned</title>
3
+ <g fill="none">
4
+ <path fill="currentColor" d="M450 1l900-1-1 258c-62.07 13.33-113.63 44.83-154.67 94.5-41.05 49.67-61.57 107.17-61.57 172.5v50c.67 65.33 21.36 122.67 62.07 172 40.7 49.33 92.43 80.67 155.17 94l-1 258h-341.38l-1 257c0 51.33-11.35 137.67-34.04 259-22.7 121.33-46.72 182-72.08 182-26.03 0-50.56-60.67-73.58-182-23.03-121.33-34.54-207.67-34.54-259v-256L450 1099l1-258c62.07-12.67 113.46-43.83 154.17-93.5 40.7-49.67 61.07-107.17 61.07-172.5v-50c0-65.33-20.36-122.83-61.07-172.5-40.7-49.67-92.43-81.17-155.17-94.5V1z"/>
5
+ </g>
6
+ </svg>
assets/img/star-empty.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
2
+ <title>star-empty</title>
3
+ <g fill="#FFB900">
4
+ <path d="M900 0l300 600 600 75-413 462 113 663-600-300-600 300 112-663L0 675l600-75L900 0zm0 224L666 693l-465 58 318 356-87 515 468-234 468 234-87-515 318-356-465-58-234-469z"/>
5
+ </g>
6
+ </svg>
assets/img/star-filled.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
2
+ <title>star-filled</title>
3
+ <g fill="#FFB900">
4
+ <path d="M900 0L600 600 0 675l412 462-112 663 600-300 600 300-113-663 413-462-600-75z"/>
5
+ </g>
6
+ </svg>
assets/img/star-half.svg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
2
+ <title>star-half</title>
3
+ <g fill="#FFB900">
4
+ <path d="M900 0L600 600 0 675l413 462-113 663 600-300 600 300-112-663 412-462-600-75L900 0zm0 224l234 469 465 58-318 356 87 515-468-234V224z"/>
5
+ </g>
6
+ </svg>
assets/img/tripadvisor.svg ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540">
2
+ <title>tripadvisor-reviews</title>
3
+ <clipPath>
4
+ <path d="M0 0h960v540H0z"/>
5
+ </clipPath>
6
+ <g fill="none">
7
+ <path fill="#4B7E37" d="M0 0h960v540H0z"/>
8
+ <path fill="#558A42" d="M676.33 566.25c2.26-.97 4.22-2.36 5.84-4.03l-4.2-28.26c-4.5-2.5-10.03-3.06-15.2-1.07-8.73 3.32-13.35 12.7-10.93 21.53l24.5 11.8zm-53.78-25.92c7.43-4.4 10.83-13.62 7.63-21.95-3.56-9.3-13.98-13.92-23.27-10.36-7.95 3.06-12.5 11.17-11.37 19.3l27.03 13zm-54.07-26.06c6.17-4.73 8.8-13.12 5.86-20.75-3.57-9.3-14-13.93-23.27-10.36-7.28 2.8-11.7 9.8-11.56 17.15l29 13.98zm95.87-71.8c-3.72 4.8-4.92 11.4-2.58 17.48 1.33 3.45 3.6 6.26 6.4 8.25l-3.82-25.74zM317.7 549.82c-.2-1.25-.52-2.5-1-3.72-3.55-9.28-13.97-13.92-23.26-10.35-9.3 3.56-13.92 13.98-10.36 23.27.83 2.17 2.04 4.1 3.53 5.7l31.1-14.9zm39.48-18.9c-.04-.1-.08-.2-.1-.3-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27.4 1 .86 1.98 1.4 2.88l32.34-15.48zm38.87-18.63c-4.4-7.37-13.6-10.73-21.9-7.55-9.2 3.54-13.84 13.8-10.45 23.04l32.35-15.5zm38.25-18.33c-4.85-5.34-12.65-7.45-19.8-4.7-8.07 3.1-12.64 11.4-11.33 19.6L434.3 494zm37.56-18c-4.76-3.42-11.1-4.46-17-2.2-6.85 2.63-11.17 9-11.53 15.88L471.86 476zm42.3 12.12c5.06-4.88 7-12.5 4.33-19.46-3.6-9.28-14-13.92-23.3-10.36-6.6 2.53-10.85 8.54-11.47 15.14l30.42 14.66zm143.28-92.07c-.27.1-.53.2-.8.3-9.28 3.55-13.92 13.97-10.36 23.26 2.7 7 9.3 11.37 16.4 11.54l-5.24-35.1zm-368.9 98.64c5.6 1.33 10.46 5.3 12.68 11.07 3.56 9.3-1.07 19.7-10.36 23.27-2.65 1.03-5.4 1.38-8.03 1.14l5.72-35.47zm367.8-106.13c-.73.4-1.5.75-2.28 1.05-9.3 3.56-19.7-1.08-23.27-10.37-3.58-9.3 1.05-19.7 10.34-23.27 3.38-1.3 6.9-1.5 10.2-.8l4.98 33.38zm-359.5 54.76c1.68-1.58 3.7-2.88 5.97-3.76 9.3-3.56 19.73 1.08 23.3 10.36 3.56 9.3-1.08 19.7-10.37 23.27-8.58 3.3-18.14-.42-22.35-8.34l3.48-21.52zm390.03-137.2c-4.7-6.26-13.17-8.93-20.85-5.98-9.3 3.57-13.93 14-10.36 23.27 1.25 3.26 3.36 5.95 5.97 7.9l25.24-25.2zm67.47-67.37c-3.72-9-13.96-13.45-23.1-9.94-9.3 3.54-13.93 13.96-10.36 23.25 1.84 4.8 5.5 8.36 9.92 10.18l23.54-23.5zm-443.7 105.3c.5-.25 1-.48 1.55-.7 9.26-3.55 19.7 1.1 23.25 10.37 3.56 9.3-1.08 19.7-10.36 23.27-5.97 2.3-12.4 1.2-17.17-2.32l4.63-28.66-1.94-1.98zm459.34-170.68c4.17 1.88 7.62 5.34 9.4 9.95 3.56 9.3-1.08 19.7-10.37 23.27-9.27 3.56-19.7-1.07-23.25-10.36-3.56-9.27 1.07-19.7 10.36-23.25 1.4-.53 2.8-.88 4.23-1.06l9.66 1.46zm14.97 2.27c.13 1.7.5 3.42 1.15 5.1 2.5 6.5 8.37 10.74 14.85 11.45l12.27-12.25-28.27-4.3zM285.2 318.38c.53-6.7 4.8-12.83 11.5-15.4 9.28-3.56 19.7 1.08 23.27 10.37 3.56 9.28-1.08 19.7-10.36 23.27-1.7.65-3.43 1.03-5.17 1.15l-19.23-19.4zm439.46-151.92c-.6 6.64-4.85 12.7-11.5 15.25-9.28 3.58-19.7-1.06-23.27-10.35-1.3-3.4-1.53-6.97-.8-10.3l35.55 5.42zm-96.53-14.67c1.8 8.5-2.78 17.3-11.17 20.52-9.3 3.57-19.7-1.07-23.27-10.36-2-5.2-1.44-10.74 1.1-15.25l33.32 5.08zm30.26 4.6c-.37.14-.73.3-1.1.43-6.53 2.5-13.63.95-18.5-3.43l19.6 3zM238.5 271.3c9.12-3.66 13.64-13.97 10.1-23.17-3.56-9.3-13.97-13.93-23.26-10.36-4.82 1.85-8.4 5.54-10.2 9.96l23.36 23.57zm333.9-137.45c-1.1 5.98-5.2 11.28-11.3 13.62-9.27 3.56-19.7-1.08-23.26-10.36-3.56-9.27 1.08-19.7 10.36-23.25 5.4-2.08 11.2-1.37 15.8 1.4l8.4 18.6zm-235.72 15.9c7.5-.3 14.7 4.15 17.52 11.55 3.57 9.28-1.07 19.7-10.36 23.27-9.28 3.56-19.7-1.08-23.27-10.36-3.56-9.27 1.08-19.7 10.36-23.25.62-.24 1.23-.44 1.85-.6l3.9-.6zm49.12-13.6c3.88 1.94 7.08 5.28 8.76 9.66 3.56 9.3-1.07 19.7-10.36 23.3-9.3 3.55-19.7-1.1-23.27-10.37-1.62-4.23-1.55-8.68-.1-12.62l21.92-3.3 3.05-6.64zm170.03-39.02c-1.82 4.43-5.38 8.13-10.2 9.98-9.3 3.6-19.7-1.05-23.28-10.34-3.56-9.3 1.08-19.7 10.36-23.27 4.16-1.6 8.5-1.56 12.4-.2l10.74 23.84zM171.45 203.65c6.16-4.73 8.77-13.12 5.84-20.74-1.3-3.37-3.53-6.13-6.26-8.1l-24.55 3.7 24.94 25.16zm46.3-35.93c3.36 9.2-1.28 19.44-10.46 22.96-9.3 3.57-19.73-1.07-23.3-10.36-.92-2.4-1.3-4.9-1.17-7.32l34.92-5.28zm41.18-6.22c-1.07 6.02-5.17 11.34-11.3 13.7-8.66 3.32-18.33-.5-22.47-8.6l33.77-5.1zM411.3 80.63c3.6 1.98 6.54 5.2 8.12 9.33 3.57 9.3-1.07 19.7-10.36 23.27-4.27 1.64-8.8 1.54-12.76.05l15-32.65zm127.6-21.06c-2 3.17-5 5.74-8.77 7.2-9.28 3.55-19.7-1.1-23.27-10.37-3.56-9.3 1.08-19.7 10.36-23.27 3.08-1.2 6.28-1.47 9.3-.98l12.38 27.42zM436.78 25.14c3.3 2 6 5.08 7.5 8.97 3.57 9.3-1.06 19.73-10.35 23.3-4.04 1.54-8.28 1.54-12.1.3l14.95-32.55zm84.94-3.66c-1.86 2.12-4.26 3.84-7.08 4.92-9.3 3.56-19.7-1.07-23.27-10.36-3.56-9.3 1.07-19.7 10.36-23.27 2.14-.82 4.34-1.2 6.5-1.2l13.5 29.9zm-59.47-51.8c3.04 2 5.5 4.93 6.9 8.6 3.57 9.27-1.07 19.7-10.36 23.26-3.8 1.45-7.8 1.54-11.4.5l14.84-32.36zm42.1 13.28c-1.5 1.28-3.25 2.34-5.2 3.08-9.3 3.57-19.7-1.07-23.27-10.36-3.56-9.28 1.07-19.7 10.36-23.27 1.28-.47 2.58-.8 3.88-1l14.23 31.57zM486.83-55.9c-.98.62-2.04 1.16-3.17 1.6-3.55 1.35-7.27 1.52-10.7.68l7.5-16.38 6.37 14.1zm173.35 582.06c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.9 23.3 10.35zm-55.85-24.87c9.3-3.58 13.93-14 10.36-23.28-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.5c9.26-3.57 13.9-14 10.34-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm-96.23-9.38c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.97-10.37 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.57-13.92 14-10.36 23.28 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.57-13.94 14-10.37 23.28 3.57 9.27 14 13.9 23.28 10.35zm-298 68.08c9.3-3.56 13.93-13.98 10.37-23.27-3.57-9.3-14-13.92-23.27-10.36-9.28 3.56-13.92 13.97-10.35 23.26 3.56 9.3 13.98 13.92 23.26 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.57-13.92 14-10.36 23.28 3.56 9.3 13.97 13.92 23.26 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.57-13.93 14-10.36 23.28 3.56 9.27 13.98 13.9 23.27 10.35zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.94-23.27-10.37-9.26 3.6-13.9 14-10.36 23.3 3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.37zm40.35-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.95 23.27 10.38zm40.35-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.57-9.27-13.98-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-14 10.37-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm-257.62 52.58c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-13.98-13.9-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.56 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.28-3.56 13.92-13.98 10.35-23.27-3.56-9.3-13.98-13.9-23.27-10.35-9.3 3.57-13.92 13.98-10.36 23.27 3.56 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.56 9.3 14 13.92 23.26 10.36zm40.36-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.58-9.3-14-13.93-23.28-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.35-15.48c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.97-13.93-23.26-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm-297.98 68.1c9.27-3.58 13.9-14 10.35-23.28-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.34-15.5c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 14-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.35zM502 355.37c9.3-3.56 13.94-13.98 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.27-14-13.9-23.27-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.3-3.57 13.93-14 10.36-23.28-3.56-9.28-13.98-13.9-23.27-10.35-9.26 3.57-13.9 13.98-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.3 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.27 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.27-3.55 13.9-13.97 10.34-23.25-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.92 13.97-10.36 23.26 3.57 9.28 14 13.92 23.28 10.36zm-338.36 83.6c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.28-13.97-13.9-23.26-10.35-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36zM405.8 346c9.3-3.56 13.93-13.97 10.37-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.55 9.28 13.97 13.92 23.25 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.27 3.56-13.9 13.98-10.34 23.27 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.58 9.28 14 13.92 23.3 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.58-9.3-14-13.93-23.3-10.36-9.27 3.56-13.9 13.98-10.35 23.26 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.26-3.55 13.9-13.97 10.34-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.57 9.3 14 13.92 23.28 10.35zm40.33-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.57-9.3-14-13.93-23.28-10.37-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.55 13.9-13.97 10.35-23.26-3.57-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.57 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.92-23.25-10.36-9.3 3.57-13.93 13.98-10.37 23.27 3.57 9.3 14 13.94 23.27 10.37zm-378.7 99.06c9.3-3.56 13.94-13.98 10.37-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.3-3.55 13.93-13.97 10.37-23.26-3.57-9.28-14-13.92-23.27-10.36-9.28 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.92 23.27 10.35zm40.36-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.58-13.92 14-10.36 23.28 3.56 9.3 14 13.94 23.26 10.37zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.94 23.27 10.37zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.27-3.57 13.9-13.98 10.35-23.27-3.57-9.28-14-13.92-23.27-10.35-9.3 3.56-13.94 13.98-10.37 23.27 3.57 9.28 14 13.92 23.28 10.36zm40.34-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.92-23.27-10.36-9.27 3.57-13.9 13.98-10.34 23.27 3.56 9.3 13.98 13.94 23.26 10.37zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.6-9.28-14-13.92-23.3-10.35-9.3 3.56-13.92 13.98-10.35 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.6 9.28 14 13.92 23.3 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.27-3.56 13.9-13.98 10.34-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.94 14-10.37 23.27 3.56 9.3 13.98 13.93 23.26 10.36zM278.6 255.9c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.37-9.26 3.57-13.9 14-10.34 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.58 9.3 14 13.93 23.3 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.25 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.27-3.55 13.9-13.97 10.34-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 13.98-10.36 23.27 3.58 9.3 14 13.93 23.28 10.36zm40.34-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.6-13.93 14-10.36 23.3 3.56 9.3 13.98 13.92 23.27 10.35zm40.35-15.5c9.3-3.56 13.92-13.97 10.35-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.58 9.27 14 13.9 23.28 10.34zm40.35-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.56-9.3-13.97-13.95-23.26-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.27 13.97 13.9 23.26 10.35zm-297.98 68.08c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.3-14-13.92-23.27-10.36-9.3 3.6-13.93 14-10.36 23.3 3.57 9.3 14 13.9 23.28 10.35zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.27 14 13.9 23.27 10.34zm40.36-15.48c9.28-3.57 13.9-14 10.35-23.27-3.57-9.3-13.98-13.95-23.27-10.38-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm121.06-46.48c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.3-3.55 13.93-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.93 14-10.37 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zM449.4 97.74c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.58-13.9 14-10.35 23.3 3.57 9.27 13.98 13.9 23.27 10.35zm40.37-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.26 14 13.9 23.28 10.34zm-15.5-40.35c9.3-3.58 13.93-14 10.37-23.3-3.57-9.3-14-13.9-23.27-10.35-9.3 3.57-13.93 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36z"/>
9
+ <path fill="#fff" d="M405.25 391.43c0 11.1-6.67 18.26-17 18.26-6.3 0-10.7-4.16-10.7-10.12 0-7.82 7.47-12.12 21.03-12.12h6.7v3.98h-.03zm-11.48-38.75c-8 0-15 1.5-21.4 4.6l-.4.17.13 11.14 1.03-.57c6.14-3.44 11.94-5.17 17.24-5.17 9.46 0 14.88 5.4 14.88 14.88v.56h-7.46c-21.5 0-33.33 7.78-33.33 21.92 0 11.35 8.56 19.28 20.8 19.28 9 0 15.95-3.53 20.9-10.2v9.3H418v-38.98c0-17.9-8.13-26.94-24.18-26.94h-.05zm62.68 56.12c-9.95 0-16.14-8.67-16.14-22.65 0-14 6.24-23.05 15.9-23.05 10.8 0 17.26 8.62 17.26 23.05 0 13.76-6.68 22.67-17 22.67v-.02zm17-85.38v39.52c-4.8-6.7-11.53-10.26-20.15-10.26-15.96 0-26.7 13.13-26.7 32.7 0 20.17 10.88 34.23 26.45 34.23 9.23 0 16.4-4.05 21.42-12.02v11.03h12.07v-95.2h-13.08v.02zm71.47 30.77l-18.54 37.17-17.1-37.2-14.73.06 31.56 64.42 32.47-64.4H544.9v-.05h.02zm188.23 10.4v-10.44l-11.94.03v64.4h13.08v-32.35c0-13.52 5.7-21 16.02-21 1.54 0 2.98.2 4.75.72l.84.26.43-12.73-5.6-.45c-7.6 0-13.72 4.1-17.54 11.56h-.04zm-56.1-11.88c-19.65 0-31.83 12.9-31.83 33.72s12.18 33.74 31.83 33.74c19.7 0 31.96-12.93 31.96-33.73 0-20.8-12.24-33.7-31.95-33.7v-.03zm.16 56.75c-10.87 0-18.16-9.32-18.16-23.2 0-13.93 7.3-23.3 18.17-23.3 11.02 0 18.14 9.16 18.14 23.3 0 14.13-7.12 23.22-18.14 23.22v-.03zm-83.6-38.76c0 9.08 6.24 13.84 17.72 19.47 7.26 3.58 11.05 5.66 11.05 10.68 0 5.15-4.67 8.5-11.87 8.5-5.12 0-10.78-1.47-16.4-4.23l-.96-.46-.4 11.35.47.2c5.36 2.12 11 3.18 16.85 3.18 15.98 0 26.3-7.63 26.3-19.4 0-9.83-6.54-14.83-17.85-20.1-8.3-3.9-11.77-6.17-11.77-10.3 0-3.9 3.85-6.43 9.84-6.43 5.4 0 10.67 1.2 15.65 3.58l.93.46.6-11.15-.53-.14c-5.44-1.62-10.77-2.43-15.9-2.43-14.15 0-23.68 6.94-23.68 17.27h-.02l-.02-.03zm-29.65-35.4c0 4.3 3.5 7.78 7.8 7.78 4.27 0 7.76-3.5 7.76-7.77 0-4.28-3.48-7.76-7.76-7.76-4.3 0-7.78 3.52-7.78 7.82v-.04zm13.66 18.9h-12.38v64.4h13.07v-64.4h-.68zm199.97 4.26h1.24c1.6 0 2.36-.53 2.36-1.67 0-1.24-.8-1.76-2.5-1.76h-1.05v3.42h-.03zm1.63-5.64c3.68 0 5.54 1.28 5.54 3.72 0 1.63-1 2.9-2.72 3.44l2.84 5.6H781l-2.4-5.08h-1.03v5.08h-3.52v-12.74h5.15v-.02zm-6.85-.26c-1.9 1.9-2.87 4.32-2.87 6.9 0 2.5.92 4.77 2.7 6.62 1.84 1.86 4.23 2.92 6.78 2.92 2.52 0 4.9-.98 6.76-2.78 1.83-1.73 2.83-4.15 2.83-6.77 0-2.57-.93-4.9-2.7-6.7-1.85-1.88-4.24-2.88-6.84-2.88-2.6 0-4.86.92-6.65 2.68zm15.04-1.27c2.14 2.16 3.26 5.02 3.26 8.14 0 3.24-1.2 6.14-3.45 8.3-2.3 2.2-5.22 3.38-8.22 3.38-3.14 0-6.06-1.23-8.3-3.54-2.17-2.25-3.35-5.1-3.35-8.13 0-3.07 1.24-6.1 3.55-8.35 2.18-2.15 4.96-3.32 8.1-3.32 3.23 0 6.17 1.24 8.4 3.55v-.03z"/>
10
+ <path fill="#0A0A08" d="M194.9 334.75l-11.76 4.06v15.4H170.1v10.28h12.67v33.1c0 15.74 5.37 22.14 18.52 22.14 3.35 0 6.53-.4 9.7-1.26l.55-.13-.14-10.74-.92.34c-2.57.9-4.6 1.32-6.16 1.32-6.18 0-8.5-3.47-8.5-12.86v-31.96h15.1v-10.28h-15.1v-19.72l-.92.32m40.76 29.84v-10.44l-11.94.03v64.4h13.05v-32.35c0-13.52 5.7-21 16.02-21 1.5 0 2.94.2 4.7.72l.85.26.44-12.73-5.62-.45c-7.56 0-13.7 4.1-17.5 11.56m32.75-29.3c0 4.28 3.5 7.76 7.77 7.76 4.3 0 7.78-3.48 7.78-7.77 0-4.3-3.48-7.78-7.78-7.78-4.28 0-7.77 3.53-7.77 7.83m13.63 18.85H269.7v64.4h13.06v-64.4h-.7m46.83-1.52c-9 0-16.52 4.24-21.56 11.97v-10.42H295.4v90.68h13.1v-35.17c4.7 6.4 11.46 9.92 19.9 9.92 16.5 0 27.17-13.53 27.17-34.48 0-19.42-10.74-32.47-26.7-32.47m-3.57 56.36c-10.35 0-16.78-8.83-16.78-23.04 0-13.7 6.76-22.55 17.24-22.55 10.08 0 16.1 8.38 16.1 22.4 0 14.33-6.35 23.2-16.62 23.2M343.77 175.66c-3.65-14.9-14.77-29.68-14.77-29.68l47.22-.03c28.15-18.33 63.86-28.17 103.38-28.17 38.17 0 72.68 9.57 101 27.82l50.13.03s-11.28 14.98-14.86 30c4.2 5.6 7.68 11.93 10.25 18.82 14.44 38.9-5.6 82.24-44.72 96.64-18.9 6.92-39.42 6.13-57.78-2.3-11.03-5.06-20.47-12.5-27.76-21.74l-16.2 24.1-16.1-23.88c-13.86 17.17-35.13 28.15-58.94 28.15-41.65 0-75.57-33.72-75.57-75.2 0-16.66 5.48-32.1 14.73-44.57"/>
11
+ <path fill="#E8C56C" d="M405.8 145.04c21.68-9.25 46.74-14.15 73.8-14.15 27.07 0 51.54 4.88 73.16 14.5-7.9.23-15.93 1.74-23.73 4.6-18.9 6.95-34 20.85-42.48 39.1-3.65 7.93-5.87 16.25-6.63 24.67-3.32-38.06-35.18-68.1-74.13-68.7v-.03z"/>
12
+ <ellipse fill="#fff" cx="404.583" cy="220.508" rx="60.317" ry="60.028"/>
13
+ <ellipse fill="#fff" cx="555.218" cy="220.508" rx="60.317" ry="60.028"/>
14
+ <ellipse fill="#0A0A08" cx="404.6" cy="220.508" rx="37.335" ry="37.156"/>
15
+ <ellipse fill="#0A0A08" cx="555.185" cy="220.508" rx="37.335" ry="37.156"/>
16
+ <ellipse fill="#fff" cx="404.6" cy="220.508" rx="24.475" ry="24.358"/>
17
+ <ellipse fill="#fff" cx="555.185" cy="220.508" rx="24.475" ry="24.358"/>
18
+ <ellipse fill="#922826" cx="404.6" cy="220.508" rx="12.445" ry="12.385"/>
19
+ <ellipse fill="#548C40" cx="555.185" cy="220.508" rx="12.445" ry="12.385"/>
20
+ </g>
21
+ </svg>
assets/img/yelp.svg ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540">
2
+ <title>yelp-reviews</title>
3
+ <clipPath>
4
+ <path d="M0 0h960v540H0z"/>
5
+ </clipPath>
6
+ <g fill="none">
7
+ <path fill="#D32323" d="M0 0h960v540H0z"/>
8
+ <path fill="#DB4123" d="M676.33 566.25c2.26-.97 4.22-2.36 5.84-4.03l-4.2-28.26c-4.5-2.5-10.03-3.06-15.2-1.07-8.73 3.33-13.35 12.72-10.93 21.54l24.5 11.8zm-53.78-25.92c7.43-4.4 10.83-13.62 7.63-21.95-3.56-9.3-13.98-13.92-23.27-10.36-7.96 3.06-12.5 11.17-11.38 19.3l27.03 13zm-54.07-26.06c6.17-4.73 8.8-13.12 5.86-20.75-3.57-9.3-14-13.93-23.27-10.36-7.28 2.8-11.7 9.8-11.56 17.15l28.98 13.97zm95.87-71.8c-3.72 4.8-4.92 11.4-2.58 17.48 1.33 3.45 3.6 6.26 6.4 8.25l-3.82-25.74zM317.7 549.82c-.2-1.25-.52-2.5-1-3.72-3.55-9.28-13.97-13.92-23.26-10.35-9.3 3.56-13.92 13.98-10.36 23.27.83 2.17 2.04 4.1 3.53 5.7l31.1-14.9zm39.48-18.9c-.04-.1-.08-.2-.1-.3-3.58-9.3-14-13.94-23.28-10.37-9.3 3.56-13.93 13.98-10.36 23.27.4 1 .86 1.98 1.4 2.88l32.34-15.48zm38.87-18.63c-4.4-7.36-13.6-10.72-21.9-7.54-9.2 3.54-13.84 13.8-10.45 23.04l32.35-15.5zm38.25-18.32c-4.85-5.34-12.65-7.45-19.8-4.7-8.07 3.1-12.64 11.4-11.33 19.6L434.3 494zm37.56-18c-4.76-3.42-11.1-4.46-17-2.2-6.85 2.63-11.17 9-11.53 15.88L471.86 476zm42.3 12.12c5.06-4.87 7-12.48 4.33-19.45-3.58-9.28-14-13.92-23.28-10.36-6.6 2.53-10.86 8.54-11.48 15.14l30.42 14.66zm143.28-92.06c-.27.1-.53.2-.8.3-9.28 3.55-13.92 13.97-10.36 23.26 2.7 7.02 9.3 11.38 16.4 11.55l-5.24-35.1zm-368.9 98.64c5.6 1.33 10.46 5.3 12.68 11.07 3.56 9.3-1.07 19.7-10.36 23.27-2.65 1.02-5.4 1.37-8.03 1.13l5.72-35.47zm367.8-106.13c-.73.4-1.5.75-2.28 1.05-9.3 3.57-19.7-1.07-23.27-10.36-3.57-9.3 1.06-19.7 10.35-23.27 3.38-1.3 6.9-1.5 10.2-.8l4.98 33.38zm-359.5 54.76c1.68-1.58 3.7-2.88 5.97-3.76 9.3-3.56 19.72 1.08 23.28 10.36 3.57 9.3-1.07 19.7-10.36 23.27-8.58 3.3-18.14-.42-22.35-8.34l3.48-21.52zm390.03-137.2c-4.7-6.26-13.17-8.93-20.85-5.98-9.3 3.57-13.93 14-10.36 23.27 1.25 3.27 3.36 5.96 5.97 7.92l25.24-25.2zm67.47-67.37c-3.72-9-13.96-13.45-23.1-9.94-9.3 3.55-13.93 13.97-10.36 23.26 1.84 4.8 5.5 8.36 9.92 10.18l23.54-23.5zm-443.7 105.3c.5-.25 1-.48 1.55-.7 9.27-3.55 19.7 1.1 23.26 10.37 3.56 9.3-1.08 19.7-10.36 23.27-5.96 2.3-12.38 1.2-17.16-2.32l4.63-28.66-1.94-1.97zm459.34-170.68c4.17 1.88 7.62 5.34 9.4 9.95 3.56 9.3-1.08 19.7-10.37 23.27-9.28 3.56-19.7-1.07-23.26-10.36-3.56-9.28 1.07-19.7 10.36-23.26 1.4-.53 2.8-.88 4.22-1.06l9.66 1.47zm14.97 2.27c.13 1.7.5 3.42 1.15 5.1 2.5 6.5 8.37 10.74 14.85 11.45l12.27-12.25-28.27-4.3zM285.2 318.38c.53-6.7 4.8-12.83 11.5-15.4 9.28-3.56 19.7 1.08 23.27 10.37 3.56 9.28-1.08 19.7-10.36 23.27-1.7.65-3.44 1.03-5.18 1.15l-19.23-19.4zm439.46-151.92c-.6 6.64-4.85 12.7-11.5 15.25-9.28 3.57-19.7-1.07-23.27-10.36-1.3-3.4-1.52-6.97-.8-10.3l35.56 5.42zm-96.53-14.67c1.8 8.5-2.78 17.3-11.17 20.53-9.3 3.57-19.7-1.07-23.27-10.36-2-5.2-1.43-10.74 1.1-15.25l33.33 5.07zm30.26 4.6c-.36.15-.72.3-1.08.44-6.54 2.5-13.64.95-18.52-3.43l19.6 3zM238.5 271.3c9.12-3.66 13.64-13.97 10.1-23.17-3.56-9.3-13.97-13.93-23.26-10.36-4.82 1.85-8.4 5.54-10.2 9.96l23.36 23.57zM572.4 133.85c-1.1 5.98-5.2 11.28-11.3 13.62-9.27 3.56-19.7-1.08-23.26-10.36-3.56-9.28 1.08-19.7 10.36-23.26 5.4-2.08 11.2-1.37 15.8 1.4l8.4 18.6zm-235.72 15.9c7.5-.3 14.7 4.15 17.52 11.55 3.57 9.28-1.07 19.7-10.36 23.27-9.28 3.56-19.7-1.08-23.27-10.36-3.56-9.28 1.08-19.7 10.36-23.26.62-.24 1.23-.44 1.85-.6l3.9-.6zm49.12-13.6c3.88 1.94 7.08 5.28 8.76 9.66 3.56 9.3-1.07 19.7-10.36 23.28-9.3 3.56-19.7-1.08-23.27-10.36-1.62-4.23-1.55-8.68-.1-12.62l21.92-3.3 3.05-6.65zm170.03-39.02c-1.82 4.43-5.38 8.13-10.2 9.98-9.3 3.58-19.7-1.06-23.28-10.35-3.56-9.3 1.08-19.7 10.36-23.27 4.15-1.58 8.5-1.55 12.38-.2l10.75 23.85zM171.45 203.65c6.16-4.73 8.77-13.12 5.84-20.74-1.3-3.38-3.52-6.14-6.25-8.12l-24.55 3.7 24.95 25.17zm46.3-35.93c3.36 9.2-1.28 19.44-10.46 22.96-9.3 3.57-19.72-1.07-23.28-10.36-.93-2.4-1.3-4.9-1.18-7.32l34.92-5.28zm41.18-6.22c-1.07 6.02-5.17 11.34-11.3 13.7-8.66 3.32-18.33-.5-22.47-8.6l33.77-5.1zM411.3 80.63c3.6 1.98 6.54 5.2 8.12 9.33 3.57 9.3-1.07 19.7-10.36 23.27-4.27 1.64-8.8 1.54-12.76.05l15-32.65zm127.6-21.06c-2 3.17-5 5.74-8.77 7.2-9.28 3.55-19.7-1.1-23.27-10.37-3.56-9.3 1.08-19.7 10.36-23.27 3.08-1.2 6.28-1.47 9.3-.98l12.38 27.42zM436.78 25.14c3.3 2 6 5.08 7.5 8.97 3.57 9.3-1.06 19.72-10.35 23.28-4.04 1.55-8.28 1.55-12.1.3l14.95-32.54zm84.94-3.66c-1.86 2.12-4.26 3.84-7.08 4.92-9.3 3.56-19.7-1.07-23.27-10.36-3.56-9.3 1.07-19.7 10.36-23.27 2.14-.82 4.34-1.2 6.5-1.2l13.5 29.9zm-59.47-51.8c3.04 2 5.5 4.93 6.9 8.6 3.57 9.27-1.07 19.7-10.36 23.26-3.8 1.45-7.78 1.54-11.4.5l14.85-32.36zm42.1 13.28c-1.5 1.28-3.25 2.34-5.2 3.08-9.3 3.57-19.7-1.07-23.27-10.36-3.56-9.28 1.07-19.7 10.36-23.27 1.28-.48 2.58-.8 3.88-1l14.23 31.56zM486.83-55.9c-.98.62-2.04 1.16-3.17 1.6-3.55 1.35-7.27 1.52-10.7.68l7.5-16.38 6.37 14.1zm173.35 582.06c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.94-23.27-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.3 14 13.92 23.28 10.36zm-55.85-24.87c9.3-3.57 13.93-14 10.36-23.27-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.57 9.3 13.98 13.93 23.27 10.36zm40.36-15.5c9.27-3.56 13.9-13.98 10.35-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm-96.22-9.37c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.57-13.93 13.98-10.37 23.27 3.58 9.3 14 13.93 23.28 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.57-13.92 14-10.36 23.28 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.94 13.98-10.37 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm-298 68.08c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.92-23.27-10.36-9.28 3.57-13.92 13.98-10.35 23.27 3.56 9.3 13.98 13.92 23.26 10.36zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.57-13.92 14-10.36 23.28 3.57 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.58-9.3-14-13.93-23.28-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.94-23.27-10.37C430.1 437 425.45 447.4 429 456.7c3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.37zm40.35-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.94 23.27 10.37zm40.35-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.27-13.97-13.9-23.26-10.35-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-14 10.37-23.28-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.26 10.36zm-257.62 52.58c9.3-3.56 13.92-13.98 10.36-23.27-3.57-9.27-13.98-13.9-23.27-10.35-9.3 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.28-3.56 13.92-13.98 10.35-23.27-3.56-9.3-13.98-13.92-23.27-10.36-9.3 3.57-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.35-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.37 23.26 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm40.35-15.48c9.3-3.57 13.94-14 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.57-13.92 14-10.36 23.28 3.57 9.28 14 13.92 23.27 10.36zm40.37-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm-297.98 68.1c9.27-3.58 13.9-14 10.35-23.28-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.34-15.5c9.3-3.57 13.93-14 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.57-13.92 14-10.36 23.28 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.5c9.3-3.56 13.92-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.56 9.27 13.98 13.9 23.27 10.35zm40.36-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.35zM502 355.37c9.3-3.56 13.94-13.98 10.37-23.27-3.56-9.3-13.98-13.93-23.27-10.37-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.94 23.27 10.37zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.28-13.98-13.9-23.27-10.35-9.27 3.57-13.9 13.98-10.35 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.28-3.56 13.92-13.97 10.36-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.3 14 13.92 23.28 10.36zm40.35-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.3 14 13.92 23.27 10.36zm40.36-15.5c9.28-3.56 13.9-13.98 10.35-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zM365.44 361.5c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.28-13.97-13.9-23.26-10.35-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.37-23.26-3.57-9.3-13.98-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.56 9.28 13.98 13.92 23.26 10.36zm40.36-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.9 13.98-10.35 23.27 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.55-13.93 13.97-10.36 23.26 3.57 9.28 14 13.92 23.28 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.5c9.27-3.55 13.9-13.97 10.35-23.26-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.34-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.57-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.94 13.98-10.37 23.27 3.56 9.28 13.98 13.92 23.27 10.35zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.55-9.3-13.97-13.93-23.25-10.37-9.3 3.57-13.93 13.98-10.37 23.27 3.57 9.3 14 13.93 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.37-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.28-3.55 13.92-13.97 10.36-23.26-3.57-9.28-14-13.92-23.27-10.36-9.28 3.57-13.92 14-10.35 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.58-13.92 14-10.36 23.28 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.58-9.28-14-13.92-23.28-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.35zm40.35-15.48c9.3-3.56 13.93-13.98 10.36-23.27-3.56-9.3-13.98-13.93-23.26-10.37-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.26 10.36zm40.36-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.35-9.3 3.56-13.94 13.98-10.37 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.55-9.3-13.97-13.93-23.26-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.92 23.27 10.36zm40.36-15.48c9.3-3.57 13.93-14 10.37-23.27-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm-378.7 99.06c9.3-3.56 13.94-13.98 10.38-23.27-3.57-9.3-14-13.93-23.27-10.37-9.28 3.57-13.92 13.98-10.35 23.27 3.56 9.3 13.98 13.93 23.26 10.36zm40.37-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.37zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.58-9.28-14-13.92-23.28-10.35-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.58 9.28 14 13.92 23.28 10.36zm40.36-15.48c9.28-3.57 13.92-14 10.36-23.27-3.57-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm40.35-15.5c9.28-3.56 13.92-13.98 10.35-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.56-13.92 13.98-10.36 23.26 3.57 9.3 14 13.93 23.27 10.36zm40.35-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.97-13.92-23.26-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.3-3.56 13.93-13.98 10.37-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.26 10.36zM278.62 255.9c9.3-3.56 13.93-13.98 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.37-9.27 3.57-13.9 14-10.35 23.27 3.57 9.3 14 13.93 23.27 10.36zm40.36-15.5c9.3-3.55 13.92-13.97 10.36-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.3 3.57-13.92 14-10.36 23.27 3.57 9.3 14 13.93 23.28 10.36zm40.36-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.26 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.28-3.55 13.92-13.97 10.35-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 13.98-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.35-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.56-9.3-13.98-13.92-23.26-10.36-9.3 3.58-13.93 14-10.36 23.28 3.56 9.3 13.98 13.93 23.27 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.56-9.3-13.98-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.37 23.27 3.57 9.28 14 13.9 23.27 10.35zm40.36-15.48c9.28-3.57 13.92-13.98 10.36-23.27-3.57-9.3-13.98-13.94-23.27-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.55 9.28 13.97 13.92 23.26 10.36zm-297.98 68.08c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.3-14-13.92-23.27-10.36-9.3 3.58-13.93 14-10.36 23.28 3.57 9.3 14 13.92 23.28 10.36zm40.35-15.5c9.3-3.56 13.93-13.97 10.36-23.26-3.57-9.3-14-13.93-23.28-10.36-9.28 3.56-13.92 13.98-10.36 23.27 3.57 9.28 14 13.9 23.27 10.35zm40.36-15.48c9.28-3.57 13.9-14 10.35-23.27-3.57-9.3-13.98-13.94-23.27-10.37-9.3 3.56-13.93 13.98-10.36 23.27 3.56 9.28 13.98 13.92 23.27 10.36zm121.06-46.48c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.37-9.3 3.57-13.93 14-10.36 23.27 3.55 9.3 13.97 13.93 23.26 10.36zm40.35-15.5c9.3-3.55 13.94-13.97 10.37-23.26-3.56-9.28-13.98-13.92-23.27-10.36-9.28 3.57-13.92 14-10.36 23.27 3.57 9.3 13.98 13.93 23.27 10.36zm40.37-15.48c9.28-3.56 13.92-13.98 10.36-23.27-3.57-9.28-14-13.92-23.27-10.36-9.3 3.57-13.93 14-10.36 23.27 3.56 9.3 13.98 13.93 23.27 10.36zm-55.85-24.86c9.3-3.57 13.93-14 10.36-23.27-3.56-9.3-13.98-13.93-23.27-10.36-9.28 3.57-13.9 14-10.35 23.28 3.57 9.28 13.98 13.92 23.27 10.36zm40.36-15.5c9.28-3.56 13.92-13.98 10.36-23.26-3.57-9.3-14-13.93-23.27-10.36-9.3 3.56-13.93 13.98-10.36 23.27 3.57 9.27 14 13.9 23.28 10.35zm-15.5-40.35c9.3-3.57 13.93-14 10.37-23.28-3.57-9.3-14-13.92-23.27-10.36-9.3 3.57-13.93 13.98-10.36 23.27 3.56 9.3 13.98 13.93 23.26 10.36z"/>
9
+ <path fill="#C7C6C2" d="M626.86 150c-.68 0-1.4 0-2.1.1-7.68.6-15.38 1.93-22.86 3.93-7.52 2-14.83 4.7-21.78 8.05-6.8 3.22-12 9.2-14.23 16.4-2.25 7.17-1.38 15.1 2.38 21.63l17.95 31.1c-2.44-.72-5-1.1-7.57-1.1-11.52 0-21.78 7.24-25.6 18.07-3.1-2.56-6.56-4.83-10.44-6.62-6.86-3.28-14.46-4.98-22.13-4.98-5.56 0-11 .9-16.12 2.68-3.97-1.97-8.35-2.98-12.83-2.98-5.37 0-10.67 1.5-15.2 4.32v-1.44c0-16.02-13.07-29.05-29.1-29.05-16.1 0-29.1 13.04-29.1 29.06v10.74c-.6-.54-1.26-1.07-1.9-1.55-12.22-9.7-25.64-11.16-32.5-11.16-5.37 0-19 1.1-31.62 10.73-3.58-4.38-8.35-7.6-13.87-9.3-2.84-.9-5.76-1.32-8.65-1.32-12.7 0-23.77 7.98-27.8 19.94l-1.7-3.43c-4.9-10-15.2-16.46-26.36-16.46-4.42 0-8.86 1.02-12.83 3-7.06 3.42-12.34 9.38-14.9 16.78-2.5 7.4-2.03 15.36 1.4 22.37l25.2 51.57c-7.46 3.5-12.82 9.84-15.12 18.02-2.45 8.64-.9 17.64 4.3 24.74 4.05 5.5 12.02 12.1 26.9 12.53h1.67c25.18 0 36.2-14.7 41.05-27.05.53-1.38.98-2.7 1.4-3.94v-.1l5.2-16.7c8.94 16.4 24.84 25.45 44.8 25.45h.6c16.3-.15 29.52-4.7 39.37-13.48 5.37 8.26 14.52 13.27 24.4 13.27 5.37 0 10.65-1.5 15.2-4.32v1.4c0 16.02 13.02 29.05 29.04 29.05 7.67 0 14.86-2.9 20.3-8.23 5.06-4.9 8.04-11.34 8.64-18.2 17.8-.3 28.85-7.87 35.02-14.23 9.2-9.4 14.44-22.8 15.6-39.83 1.6.36 3.28.48 4.9.48.9 0 1.7 0 2.5-.06-3.46 5.27-4.98 11.68-4.24 18.04.9 7.75 5.16 14.76 11.58 19.18 10.32 7.1 22.25 11.93 34.54 14.02 1.5.24 3.04.35 4.53.35 13.28 0 24.3-9.4 26.67-22.08 5.13 5.97 12.53 9.43 20.6 9.43 6.25 0 12.4-2.2 17.3-6.2 9.53-7.94 17.47-18.1 22.87-29.4 3.4-7 3.6-15.2.48-22.36-2.53-5.9-7.07-10.66-12.7-13.53 5.72-2.62 10.44-7.2 13.12-13 3.37-7.04 3.5-15.22.4-22.37-5.08-11.64-12.45-21.78-21.97-30.22-4.98-4.4-11.33-6.8-17.95-6.8-7.46 0-14.56 3.04-19.63 8.35v-36.18c0-14.98-12.17-27.15-27.15-27.15h.1-.04z"/>
10
+ <path fill="#fff" d="M626.86 151.85c13.96 0 25.3 11.33 25.3 25.3v41.25l.48-.75c4.7-6.98 12.52-11.12 21-11.12 6.14 0 12.08 2.24 16.7 6.26 9.3 8.25 16.56 18.2 21.48 29.58 2.9 6.7 2.77 14.32-.3 20.88-3.16 6.57-8.95 11.52-15.93 13.52l-.84.24.8.3c6.95 2.2 12.53 7.33 15.45 14 2.94 6.73 2.8 14.33-.34 20.9-5.28 11.03-13.04 20.97-22.44 28.75-4.53 3.73-10.23 5.8-16.1 5.8-8.77 0-16.8-4.5-21.48-11.94l-.45-.72v.9c-.5 13.67-11.64 24.4-25.28 24.4-1.4 0-2.8-.1-4.17-.35-12.05-2-23.72-6.74-33.8-13.66-6.05-4.13-9.96-10.66-10.83-17.9-.9-7.23 1.43-14.48 6.33-19.88l.5-.65-.83.2c-2.03.45-4.12.7-6.18.7-2.3 0-4.47-.3-6.62-.9-.6 11.63-3.34 28.7-15.2 40.86-6.04 6.27-17.02 13.63-34.7 13.63h-.7c-.38 14.7-12.43 26.55-27.2 26.55-14.97 0-27.14-12.22-27.14-27.22l-.1-4.95c-4.6 3.8-10.55 6.05-17 6.05-10.34 0-19.38-5.8-23.98-14.32-7.57 7.46-19.98 14.32-39.85 14.56h-.6c-19.6 0-35.1-8.95-43.67-25.24-.6-1.07-1.07-2.14-1.58-3.28l-6.56 21.12v.12c-.4 1.26-.84 2.5-1.34 3.88-4.63 11.75-15.22 25.8-39.32 25.8h-1.6c-14.15-.3-21.67-6.56-25.48-11.78-4.87-6.56-6.33-15-4-23.12 2.38-8.4 8.1-14.6 15.8-17.6l-25.94-53.4c-3.3-6.56-3.7-14-1.32-21 2.4-6.9 7.34-12.52 13.93-15.74 3.76-1.8 7.9-2.78 12.06-2.78 10.44 0 20.13 6.06 24.7 15.42l3.75 7.67 1.2-3.8c3.58-11.63 14.1-19.4 26.25-19.4 2.68 0 5.46.46 8.05 1.27 5.82 1.8 10.74 5.36 14.2 10.2 12.53-10.38 26.4-11.52 31.92-11.52 6.65 0 19.57 1.43 31.32 10.74 1.8 1.42 3.43 2.97 4.92 4.47V239.1c0-14.97 12.23-27.2 27.15-27.2 15 0 27.13 12.23 27.13 27.2v4.98c4.77-3.75 10.68-6.02 17.12-6.02 4.57 0 8.9 1.13 12.7 3.16 5.08-1.85 10.63-2.8 16.24-2.8 7.36 0 14.76 1.67 21.35 4.77 4.83 2.32 8.83 5.15 12.1 8.34.25-.77.5-1.55.72-2.3 3.5-10.3 13.13-17.2 24-17.2 3.8 0 7.62.9 11.08 2.56l.78.36-20.58-35.7c-3.58-6.16-4.4-13.52-2.3-20.24 2.1-6.7 6.92-12.3 13.25-15.3 6.86-3.28 14.02-5.96 21.47-7.93 7.3-2 14.92-3.3 22.46-3.9.66-.05 1.35-.08 2-.08v.06l.03-.03z"/>
11
+ <path fill="#C0282A" d="M582.03 250.35l33.56 16.34c6.43 3.16 5.42 12.64-1.5 14.37l-36.25 9.04c-4.47 1.12-8.95-2-9.54-6.6-1.1-9.9 0-19.7 3.08-28.7 1.5-4.4 6.5-6.48 10.65-4.48v.03zm13.42 69.02l24.97-27.74c4.77-5.3 13.66-1.73 13.42 5.43l-1.3 37.3c-.2 4.67-4.4 8.1-8.96 7.33-9.66-1.62-18.8-5.38-26.76-10.84-3.87-2.63-4.47-8.06-1.37-11.5zm59.15-36.92l35.5 11.63c4.48 1.4 6.57 6.4 4.63 10.6-4.18 8.73-10.2 16.5-17.75 22.75-3.58 2.98-8.95 2.1-11.46-1.88l-19.77-31.62c-3.8-6.12 2.08-13.63 8.88-11.43l-.03-.05zm36.1-22.56l-35.8 10.26c-6.92 2-12.53-5.73-8.5-11.7l20.88-30.93c2.57-3.88 7.97-4.57 11.43-1.5 7.17 6.27 13.04 14.2 17 23.33 1.8 4.3-.47 9.25-4.94 10.53h-.06zm-83.73-87.1c-6.48 1.78-12.62 4.02-18.5 6.85-4 1.88-5.52 6.86-3.28 10.74l35.04 60.72c3.94 6.8 14.32 4 14.32-3.87v-70.1c0-4.48-3.8-8.06-8.23-7.67-6.42.5-12.9 1.6-19.33 3.34l-.03-.03z"/>
12
+ <path fill="#0C0D0B" d="M532.72 301.1c-.15 18.13-4.33 23.9-13.63 23.9-8.66 0-13.14-5.3-13.82-18.78-.15-1.8-.15-3.37-.15-4.98 0-1.28 0-2.57.1-3.88.8-14.74 3.7-19.7 10.58-21.18 11.7-2.68 17.16 3.88 16.83 24.9h.08zm19.03-4.6c0-12.53-1.55-29.84-17.45-37.38-8.95-4.27-20.05-4.18-28.55.7-1.7 1.03-3.4 2.1-4.9 3.24-1-4.18-4.76-7.34-9.33-7.34-5.28 0-9.54 4.32-9.54 9.63v95.55c0 5.28 4.26 9.54 9.54 9.54 5.3 0 9.64-4.3 9.64-9.6v-25.06l.83 1.34c3.87 3.52 10.08 6.63 15.86 6.8 36.5 1.2 34.1-38.68 33.9-47.43zm-94.86 38.18c0 5.37-4.33 9.66-9.64 9.66-5.37 0-9.67-4.3-9.67-9.6V239.1c0-5.28 4.32-9.6 9.6-9.6 5.28 0 9.54 4.32 9.54 9.6v95.55h.12l.03.03zm-87.4-43.85c.6-13.3 10.73-15.5 15.2-15.5 4.57 0 14.62 2.22 15.22 15.68-.12.75-.42 1.14-.84 1.14h-28.75c-.45 0-.72-.48-.8-1.34l-.04.03zm4.17 17.12h35.3c4.76-.36 9.3-4.33 9.32-10.68 0 0 0 0 0-.06v-.47c-1.8-35.08-24.3-40.15-34.5-40.15-10.6 0-34.68 6.27-34.68 45.28.15 14.62 3.64 42.96 34.12 42.66 15.5-.12 25.3-5.37 30.9-13.27 1.08-2.18 1.74-4.18 1.7-6.42-.08-5.2-4.77-10.26-10.26-8.44-2.47.83-4.83 3.27-9.33 6.94-8.47 4.95-18.2 4.18-22.85-1.64-1.73-2.18-3.04-5.37-3.73-9.25-.12-.44-.15-.9-.15-1.28 0-2.24 1.2-3.1 4.12-3.22h.03zm-78.75 24.4l-30.04-61.48c-2.4-4.92-.36-10.83 4.53-13.2 4.93-2.5 10.84-.43 13.23 4.46l23.26 47.6 14.3-46.23c1.62-5.16 7.17-8.05 12.36-6.47 5.2 1.58 8.1 7.16 6.5 12.32l-24.76 80.03v.06c-.3.9-.6 1.8-.95 2.63-4.1 10.44-11.54 14.9-24.1 14.6-12.4-.28-14.32-7.6-12.98-12.42 1.5-5.3 5.97-6.87 10.3-6.87 4.38 0 6.67 0 8.17-1.1 2.26-1.73 4.05-6.26.23-14.08l-.08.06.02.1z"/>
13
+ </g>
14
+ </svg>
assets/js/site-reviews-admin.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";var x=jQuery.noConflict(),GLSR={addons:{},pinned:{}};GLSR.colorControls=function(){"object"==typeof x.wp&&"function"==typeof x.wp.wpColorPicker&&x(document).find('input[type="text"].color-picker-hex').each(function(){var e=x(this),n=e.data("colorpicker")||{};e.wpColorPicker(n)})},GLSR.dismissNotices=function(){x(".notice.is-dismissible").each(function(){var e=x(this);e.fadeTo(100,0,function(){e.slideUp(100,function(){e.remove()})})})},GLSR.getURLParameter=function(e){return decodeURIComponent((new RegExp("[?|&]"+e+"=([^&;]+?)(&|#|;|$)").exec(location.search)||[null,""])[1].replace(/\+/g,"%20"))||null},GLSR.insertNotices=function(e){e=e||!1,e&&(x("#glsr-notices").length||(x("#message.notice").remove(),x("form#post").before('<div id="glsr-notices" />')),x("#glsr-notices").html(e),x(document).trigger("wp-updates-notice-added"))},GLSR.normalizeValue=function(e){return["true","on","1"].indexOf(e)>-1||!(["false","off","0"].indexOf(e)>-1)&&e},GLSR.normalizeValues=function(e){return e.map(GLSR.normalizeValue)},GLSR.onClearLog=function(e){e.preventDefault();var n=x(this);if(!n.is(":disabled")){var t={action:site_reviews.action,request:{action:"clear-log"}};n.prop("disabled",!0),x.post(site_reviews.ajaxurl,t,function(e){GLSR.insertNotices(e.notices),x("#log-file").val(e.log),n.prop("disabled",!1)},"json")}},GLSR.onFieldChange=function(){var e=x(this).closest("form").find("[data-depends]");if(e.length)for(var n=this.getAttribute("name"),t=this.getAttribute("type"),i=0;i<e.length;i++)try{var o,s=JSON.parse(e[i].getAttribute("data-depends"));if(s.name!==n)continue;o="checkbox"===t?!!this.checked:x.isArray(s.value)?x.inArray(GLSR.normalizeValue(this.value),GLSR.normalizeValues(s.value))!==-1:GLSR.normalizeValue(s.value)===GLSR.normalizeValue(this.value),GLSR.toggleHiddenField(e[i],o)}catch(a){}},GLSR.pointers=function(e){x(e.target).pointer({content:e.options.content,position:e.options.position,close:function(){x.post(ajaxurl,{pointer:e.id,action:"dismiss-wp-pointer"})}}).pointer("open").pointer("sendToTop"),x(document).on("wp-window-resized",function(){x(e.target).pointer("reposition")})},GLSR.textareaResize=function(e){var n=320,t=e[0];t.style.height="auto",t.style.height=t.scrollHeight>n?t.scrollHeight+"px":n+"px"},GLSR.toggleHiddenField=function(e,n){var t=x(e).closest(".glsr-field");t.length&&(n?t.removeClass("hidden"):t.addClass("hidden"))},GLSR.pinned.events=function(){var e=x("#pinned-status-select");x("a.cancel-pinned-status").on("click",function(n){n.preventDefault(),e.slideUp("fast").siblings("a.edit-pinned-status").show().focus(),e.find("select").val("0"===x("#hidden-pinned-status").val()?1:0)}),x("a.edit-pinned-status").on("click",function(n){n.preventDefault(),e.is(":hidden")&&(e.slideDown("fast",function(){e.find("select").focus()}),x(this).hide())}),x("a.save-pinned-status").on("click",function(n){n.preventDefault(),e.slideUp("fast").siblings("a.edit-pinned-status").show().focus(),GLSR.pinned.save(x(this))}),x("table").on("click","td.sticky i",GLSR.pinned.onToggle)},GLSR.pinned.onToggle=function(){var e=x(this),n={action:site_reviews.action,request:{action:"toggle-pinned",id:e[0].getAttribute("data-id")}};x.post(site_reviews.ajaxurl,n,function(n){n.pinned?e.addClass("pinned"):e.removeClass("pinned")})},GLSR.pinned.save=function(e){var n={action:site_reviews.action,request:{action:"toggle-pinned",id:x("#post_ID").val(),pinned:x("#pinned-status").val()}};x.post(site_reviews.ajaxurl,n,function(n){x("#pinned-status").val(0|!n.pinned),x("#hidden-pinned-status").val(0|n.pinned),x("#pinned-status-text").text(n.pinned?e.data("yes"):e.data("no")),GLSR.insertNotices(n.notices)})},x(function(){var e=GLSR.getURLParameter("fix"),n=x("#contentdiv > textarea");e&&x('td [data-key="'+e+'"]').focus(),n.length&&(GLSR.textareaResize(n),x(document).on("wp-window-resized.editor-expand",function(){GLSR.textareaResize(n)})),x("form").on("change",":input",GLSR.onFieldChange),x("form").on("click","#clear-log",GLSR.onClearLog),GLSR.colorControls(),GLSR.pinned.events(),x.each(site_reviews_pointers.pointers,function(e,n){GLSR.pointers(n)}),x(document).on("click",".branch-4 .toggle-row, .branch-4-1 .toggle-row, .branch-4-2 .toggle-row",function(){x(this).closest("tr").toggleClass("is-expanded")})});
assets/js/site-reviews.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(t,e,s){"use strict";var r=function(t,e){this.el=t,this.options=e,this.metadata=this.el.getAttribute("data-options"),this.stars=0,this.init()};r.prototype={defaults:{clearable:!0,initialText:"Click to Rate",onClick:null,showText:!0},init:function(){if("SELECT"===this.el.tagName){for(var t=0;t<this.el.length;t++)if(""!==this.el[t].value){if(isNaN(parseFloat(this.el[t].value))||!isFinite(this.el[t].value))return;this.stars++}if(!(this.stars<1||this.stars>10))return this.config=this._extend({},this.defaults,this.options,this.metadata),this.build(),this._on("change",this.el,this.change.bind(this)),this._on("mouseenter",this.wrap,this.enter.bind(this)),this._on("mouseleave",this.wrap,this.leave.bind(this)),this._on("click",this.wrap,this.select.bind(this)),this._on("reset",this.el.closest("form"),this.clear.bind(this)),this.current=this.el.options[this.el.selectedIndex].value,this.selected=this.current,this.width=this.wrap.offsetWidth,this.star=Math.round(this.width/this.stars),this.select(),this}},build:function(){var t={},e={},s=this._createEl("span",{"class":"gl-star-rating","data-star-rating":""});this.el.parentNode.insertBefore(s,this.el),s.appendChild(this.el),this.wrap=this._insertAfterEl(this.el,"span",{"class":"gl-star-rating-stars"}),this.config.showText&&(this.text=this._insertAfterEl(this.wrap,"span",{"class":"gl-star-rating-text"}));for(var r=0;r<this.el.length;r++)""!==this.el[r].value&&(e[this.el[r].value]=this.el[r].text);Object.keys(e).sort().forEach(function(s){t[s]=e[s]});for(var i in t)this._appendTo(this.wrap,"span",{"data-value":i,"data-text":t[i]})},change:function(){this.show(this.el.options[this.el.selectedIndex].value)},clear:function(t){(this.config.clearable||t!==s)&&(this.el.value="",this.selected="",this.show(0))},enter:function(){var t=this.wrap.getBoundingClientRect();this._on("mousemove",this.wrap,this.move.bind(this)),this.offsetLeft=t.left+e.body.scrollLeft},getIndexFromPosition:function(t){return Math.min(Math.ceil(Math.max(t-this.offsetLeft,1)/this.star),this.stars)},leave:function(){this._off("mousemove",this.wrap,this.move),this.show(this.selected)},move:function(t){this.show(this.getIndexFromPosition(t.pageX))},select:function(t){var e=this.current;return t!==s&&(e=this.getIndexFromPosition(t.pageX),""!==this.current&&parseFloat(this.selected)===e)?void this.clear():(this.el.value=e,this.selected=e,this.show(e),void(t!==s&&"function"==typeof this.config.onClick&&this.config.onClick(this.el)))},show:function(t){(t<0||""===t)&&(t=0),t>this.stars&&(t=this.stars),this._removeClass(this.wrap,"s"+10*this.current),this._addClass(this.wrap,"s"+10*t),this.config.showText&&(this.text.textContent=t<1?this.config.initialText:this.wrap.childNodes[t-1].dataset.text),this.current=t},_addClass:function(t,e){t.classList?t.classList.add(e):this._hasClass(t,e)||(t.className+=" "+e)},_appendTo:function(t,e,s){var r=this._createEl(e,s);t.innerHTML+=r.outerHTML},_createEl:function(t,s){var r="string"==typeof t?e.createElement(t):t;s=s||{};for(var i in s)r.setAttribute(i,s[i]);return r},_extend:function(){var t=[].slice.call(arguments),e=!1;"boolean"==typeof t[0]&&(e=t.shift());for(var s=t[0],r=t.slice(1),i=r.length,n=0;n<i;n++){var a=r[n];for(var o in a){var l=a[o];if(e&&l&&"object"==typeof l){var c=Array.isArray(l)?[]:{};s[o]=this._extend(!0,c,l)}else s[o]=l}}return s},_hasClass:function(t,e){return t.classList?t.classList.contains(e):new RegExp("\\b"+e+"\\b").test(t.className)},_insertAfterEl:function(t,e,s){var r=this._createEl(e,s);return t.parentNode.insertBefore(r,t.nextSibling),r},_off:function(t,e,s){e.detachEvent?e.detachEvent("on"+t,s):e.removeEventListener(t,s)},_on:function(t,e,s){e.attachEvent?e.attachEvent("on"+t,s):e.addEventListener(t,s)},_removeClass:function(t,e){t.classList?t.classList.remove(e):t.className=t.className.replace(new RegExp("\\b"+e+"\\b","g"),"")}},r.defaults=r.prototype.defaults,t.jQuery&&(jQuery.fn.starrating=function(t){return this.each(function(){jQuery.data(this,"plugin_starrating")||jQuery.data(this,"plugin_starrating",new r(this,t))})}),t.StarRating=r}(window,document),this.Element&&!function(t){t.matches=t.matches||t.matchesSelector||t.webkitMatchesSelector||t.msMatchesSelector||function(t){for(var e=this,s=(e.parentNode||e.document).querySelectorAll(t),r=-1;s[++r]&&s[r]!==e;);return!!s[r]},t.closest=t.closest||function(t){for(var e=this;e.matches&&!e.matches(t);)e=e.parentNode;return e.matches?e:null}}(Element.prototype);var GLSR={};GLSR.addClass=function(t,e){t.classList?t.classList.add(e):GLSR.hasClass(t,e)||(t.className+=" "+e)},GLSR.convertValue=function(t){if(GLSR.isNumeric(t))return parseFloat(t);if("true"===t)return!0;if("false"===t)return!1;if(""!==t&&null!==t)return t},GLSR.hasClass=function(t,e){return t.classList?t.classList.contains(e):new RegExp("\\b"+e+"\\b").test(t.className)},GLSR.inArray=function(t,e){for(var s=e.length;s--;)if(e[s]===t)return!0;return!1},GLSR.isNumeric=function(t){return!(isNaN(parseFloat(t))||!isFinite(t))},GLSR.on=function(t,e,s){[].forEach.call(document.querySelectorAll(e),function(e){e.attachEvent?e.attachEvent("on"+t,s):e.addEventListener(t,s)})},GLSR.off=function(t,e,s){[].forEach.call(document.querySelectorAll(e),function(e){e.detachEvent?e.detachEvent("on"+t,s):e.removeEventListener(t,s)})},GLSR.parseFormData=function(t,e){e=!!e||!1;for(var s=/[^\[\]]+/g,r={},i={},n=(function(t,s,r,a){var o=r.shift();if(a=a?a+"."+o:o,r.length)s[o]||(s[o]={}),n(t,s[o],r,a);else{var l=e?GLSR.convertValue(t.value):t.value;if(a in i&&"radio"!==t.type&&!s[o].isArray()?o in s?s[o]=[s[o]]:s[o]=[]:i[a]=!0,GLSR.inArray(t.type,["radio","checkbox"])&&!t.checked)return;s[o]?s[o].push(l):s[o]=l}}),a=0;a<t.length;a++){var o=t[a];if(o.name&&!o.disabled&&!GLSR.inArray(o.type,["file","reset","submit","button"])){var l=o.name.match(s);l.length||(l=[o.name]),n(o,r,l)}}return r},GLSR.postAjax=function(t,e,s){var r="string"!=typeof e?GLSR.serialize(e):e,i=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");return i.open("POST",t),i.onreadystatechange=function(){i.readyState>3&&200===i.status&&s(JSON.parse(i.responseText))},i.setRequestHeader("X-Requested-With","XMLHttpRequest"),i.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),i.send(r),i},GLSR.ready=function(t){"function"==typeof t&&("loading"!==document.readyState?t():document.addEventListener?document.addEventListener("DOMContentLoaded",t):document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&t()}))},GLSR.removeClass=function(t,e){t.classList?t.classList.remove(e):t.className=t.className.replace(new RegExp("\\b"+e+"\\b","g"),"")},GLSR.serialize=function(t,e){var s=[];for(var r in t)if(t.hasOwnProperty(r)){var i=e?e+"["+r+"]":r,n=t[r];s.push("object"==typeof n?GLSR.serialize(n,i):encodeURIComponent(i)+"="+encodeURIComponent(n))}return s.join("&")},GLSR.insertAfter=function(t,e,s){var r=GLSR.createEl(e,s);return t.parentNode.insertBefore(r,t.nextSibling),r},GLSR.appendTo=function(t,e,s){var r=GLSR.createEl(e,s);return t.appendChild(r),r},GLSR.createEl=function(t,e){var s="string"==typeof t?document.createElement(t):t;e=e||{};for(var r in e)s.setAttribute(r,e[r]);return s},GLSR.clearFieldError=function(t){var e=t.closest(".glsr-field");if(null!==e){var s=e.querySelector(".glsr-field-errors");GLSR.removeClass(e,"glsr-has-error"),null!==s&&s.parentNode.removeChild(s)}},GLSR.clearFormErrors=function(t){for(var e=0;e<t.length;e++)GLSR.clearFieldError(t[e])},GLSR.showFormErrors=function(t,e){var s,r;for(var i in e){s=t.querySelector('[name="'+i+'"]').closest(".glsr-field"),r=s.querySelector(".glsr-field-errors"),GLSR.addClass(s,"glsr-has-error"),null===r&&(r=GLSR.appendTo(s,"span",{"class":"glsr-field-errors"}));for(var n=0;n<e[i].errors.length;n++)null!==e[i].errors[n]&&(r.innerHTML+='<span class="glsr-field-error">'+e[i].errors[n]+"</span>")}},GLSR.showFormMessage=function(t,e){var s=t.querySelector('input[name="form_id"]'),r=t.querySelector(".glsr-form-messages");null===r&&(r=GLSR.insertAfter(s,"div",{"class":"glsr-form-messages"})),e.errors?GLSR.addClass(r,"gslr-has-errors"):GLSR.removeClass(r,"gslr-has-errors"),r.innerHTML="<p>"+e.message+"</p>"},GLSR.submitForm=function(t){var e=t.querySelector('input[type="submit"]'),s={action:site_reviews.action,request:GLSR.parseFormData(t)};GLSR.postAjax(site_reviews.ajaxurl,s,function(s){GLSR.clearFormErrors(t),GLSR.showFormMessage(t,s),e.disabled&&e.removeAttribute("disabled"),s.errors?GLSR.showFormErrors(t,s.errors):t.reset()})},GLSR.on("change","form.glsr-submit-review-form",function(t){GLSR.clearFieldError(t.target)}),GLSR.on("submit","form.glsr-submit-review-form",function(t){t.preventDefault();var e=this.querySelector('input[type="submit"]');e.disabled||e.setAttribute("disabled",""),GLSR.submitForm(this)}),GLSR.ready(function(){for(var t=document.querySelectorAll("select.glsr-star-rating"),e=0;e<t.length;e++)new StarRating(t[e],{clearable:!1,showText:!1,onClick:GLSR.clearFieldError})});
autoload.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ defined( 'WPINC' ) || die;
12
+
13
+ /**
14
+ * PSR-4 autoloader
15
+ */
16
+ spl_autoload_register( function( $class )
17
+ {
18
+ $namespaces = [
19
+ 'GeminiLabs\\SiteReviews\\' => __DIR__ . '/plugin/',
20
+ 'GeminiLabs\\SiteReviews\\Tests\\' => __DIR__ . '/tests/',
21
+ 'Sinergi\\BrowserDetector\\' => __DIR__ . '/vendor/sinergi/browser-detector/src/',
22
+ ];
23
+
24
+ foreach( $namespaces as $prefix => $base_dir ) {
25
+
26
+ $len = strlen( $prefix );
27
+
28
+ if( strncmp( $prefix, $class, $len ) !== 0 )continue;
29
+
30
+ $file = $base_dir . str_replace( '\\', '/', substr( $class, $len ) ) . '.php';
31
+
32
+ if( !file_exists( $file ) )continue;
33
+
34
+ require $file;
35
+ break;
36
+ }
37
+ });
38
+
39
+ require_once( ABSPATH . WPINC . '/class-phpass.php' );
languages/readme.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ == Site Reviews i18n ==
2
+
3
+ Do not put custom translations here as they will be deleted on plugin updates. Instead, put custom translations in /wp-content/languages/site-reviews/
4
+
5
+ If you would like to translate or improve a translation, email: site-reviews@geminilabs.io
languages/site-reviews-en_US.mo ADDED
Binary file
languages/site-reviews-en_US.po ADDED
@@ -0,0 +1,896 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2017 site-reviews
2
+ # This file is distributed under the same license as the site-reviews package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: site-reviews\n"
6
+ "Report-Msgid-Bugs-To: https://github.com/geminilabs/site-reviews/issues/new\n"
7
+ "MIME-Version: 1.0\n"
8
+ "Content-Type: text/plain; charset=utf-8\n"
9
+ "Content-Transfer-Encoding: 8bit\n"
10
+ "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
11
+ "Last-Translator: Paul Ryley <paul@geminilabs.io>\n"
12
+ "Language-Team: Gemini Labs <support@geminilabs.io>\n"
13
+ "X-Poedit-Basepath: ..\n"
14
+ "X-Poedit-Sourcecharset: UTF-8\n"
15
+ "X-Poedit-Keywordslist: "
16
+ "__;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;esc_attr_e;esc_attr_x:1,"
17
+ "2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;_nx_noop:3c,1,2;__"
18
+ "ngettext_noop:1,2\n"
19
+ "X-Poedit-Searchpath-0: .\n"
20
+ "X-Poedit-Searchpathexcluded-0: *.js\n"
21
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
22
+ "Language: qps-ploc\n"
23
+
24
+ #: activate.php:43
25
+ msgid "The Site Reviews plugin was deactivated."
26
+ msgstr "The Site Reviews plugin was deactivated."
27
+
28
+ #: activate.php:48
29
+ msgid ""
30
+ "Sorry, this plugin requires PHP version 5.4 or greater in order to work "
31
+ "properly."
32
+ msgstr ""
33
+ "Sorry, this plugin requires PHP version 5.4 or greater in order to work "
34
+ "properly."
35
+
36
+ #: activate.php:49
37
+ msgid ""
38
+ "Please contact your hosting provider or server administrator to upgrade the "
39
+ "version of PHP on your server (your server is running PHP version %s), or "
40
+ "try to find an alternative plugin."
41
+ msgstr ""
42
+ "Please contact your hosting provider or server administrator to upgrade the "
43
+ "version of PHP on your server (your server is running PHP version %s), or "
44
+ "try to find an alternative plugin."
45
+
46
+ #: activate.php:55
47
+ msgid ""
48
+ "Sorry, this plugin requires WordPress version 4.0.0 or greater in order to "
49
+ "work properly."
50
+ msgstr ""
51
+ "Sorry, this plugin requires WordPress version 4.0.0 or greater in order to "
52
+ "work properly."
53
+
54
+ #: activate.php:56
55
+ msgid "Update WordPress"
56
+ msgstr "Update WordPress"
57
+
58
+ #: plugin/Commands/RegisterPostType.php:20, plugin/Widgets/RecentReviews.php:48, plugin/Widgets/ReviewsForm.php:40
59
+ msgid "Title"
60
+ msgstr "Title"
61
+
62
+ #: plugin/Commands/RegisterPostType.php:21, views/edit/meta.php:46
63
+ msgid "Date"
64
+ msgstr "Date"
65
+
66
+ #: plugin/Controllers/AjaxController.php:26
67
+ msgid "Log is empty"
68
+ msgstr "Log is empty"
69
+
70
+ #: plugin/Controllers/MainController.php:51
71
+ msgid "Log was cleared."
72
+ msgstr "Log was cleared."
73
+
74
+ #: plugin/Controllers/MainController.php:87, plugin/Controllers/MainController.php:293, plugin/Controllers/MainController.php:478
75
+ msgid "Settings"
76
+ msgstr "Settings"
77
+
78
+ #: plugin/Controllers/MainController.php:106
79
+ msgid "%s Review"
80
+ msgid_plural "%s Reviews"
81
+ msgstr[0] "%s Review"
82
+ msgstr[1] "%s Reviews"
83
+
84
+ #: plugin/Controllers/MainController.php:156
85
+ msgid "Details"
86
+ msgstr "Details"
87
+
88
+ #: plugin/Controllers/MainController.php:170
89
+ msgid "Pin Your Reviews"
90
+ msgstr "Pin Your Reviews"
91
+
92
+ #: plugin/Controllers/MainController.php:171
93
+ msgid ""
94
+ "You can pin exceptional reviews so that they are always shown first in your "
95
+ "widgets and shortcodes."
96
+ msgstr ""
97
+ "You can pin exceptional reviews so that they are always shown first in your "
98
+ "widgets and shortcodes."
99
+
100
+ #: plugin/Controllers/MainController.php:193
101
+ msgid "Review"
102
+ msgstr "Review"
103
+
104
+ #: plugin/Controllers/MainController.php:194, views/menu/status.php:17
105
+ msgid "Reviews"
106
+ msgstr "Reviews"
107
+
108
+ #: plugin/Controllers/MainController.php:195
109
+ msgid "Site Reviews"
110
+ msgstr "Site Reviews"
111
+
112
+ #: plugin/Controllers/MainController.php:203, views/edit/meta.php:50
113
+ msgid "Reviewer"
114
+ msgstr "Reviewer"
115
+
116
+ #: plugin/Controllers/MainController.php:204, views/edit/meta.php:42
117
+ msgid "Type"
118
+ msgstr "Type"
119
+
120
+ #: plugin/Controllers/MainController.php:205, views/edit/meta.php:38
121
+ msgid "Rating"
122
+ msgstr "Rating"
123
+
124
+ #: plugin/Controllers/MainController.php:206, views/edit/pinned.php:17
125
+ msgid "Pinned"
126
+ msgstr "Pinned"
127
+
128
+ #: plugin/Controllers/MainController.php:229
129
+ msgid "Approve this review"
130
+ msgstr "Approve this review"
131
+
132
+ #: plugin/Controllers/MainController.php:231, plugin/Handlers/SubmitReview.php:95
133
+ msgid "Approve"
134
+ msgstr "Approve"
135
+
136
+ #: plugin/Controllers/MainController.php:234
137
+ msgid "Unapprove this review"
138
+ msgstr "Unapprove this review"
139
+
140
+ #: plugin/Controllers/MainController.php:236
141
+ msgid "Unapprove"
142
+ msgstr "Unapprove"
143
+
144
+ #: plugin/Controllers/MainController.php:294
145
+ msgid "Get Help"
146
+ msgstr "Get Help"
147
+
148
+ #: plugin/Controllers/MainController.php:295, plugin/Controllers/MainController.php:353
149
+ msgid "Add-Ons"
150
+ msgstr "Add-Ons"
151
+
152
+ #: plugin/Controllers/MainController.php:331
153
+ msgid "Submit a Site Review"
154
+ msgstr "Submit a Site Review"
155
+
156
+ #: plugin/Controllers/MainController.php:332
157
+ msgid "A \"submit a review\" form for your site."
158
+ msgstr "A \"submit a review\" form for your site."
159
+
160
+ #: plugin/Controllers/MainController.php:336
161
+ msgid "Recent Site Reviews"
162
+ msgstr "Recent Site Reviews"
163
+
164
+ #: plugin/Controllers/MainController.php:337
165
+ msgid "Your site’s most recent Local Reviews."
166
+ msgstr "Your site’s most recent Local Reviews."
167
+
168
+ #: plugin/Controllers/MainController.php:373
169
+ msgid "Documentation"
170
+ msgstr "Documentation"
171
+
172
+ #: plugin/Controllers/MainController.php:376
173
+ msgid "System Info"
174
+ msgstr "System Info"
175
+
176
+ #: plugin/Controllers/MainController.php:459
177
+ msgid "%s reviews are read-only."
178
+ msgstr "%s reviews are read-only."
179
+
180
+ #: plugin/Controllers/MainController.php:481
181
+ msgid "Licenses"
182
+ msgstr "Licenses"
183
+
184
+ #: plugin/Controllers/MainController.php:497
185
+ msgid "Logging enabled."
186
+ msgstr "Logging enabled."
187
+
188
+ #: plugin/Controllers/MainController.php:498
189
+ msgid "Logging disabled."
190
+ msgstr "Logging disabled."
191
+
192
+ #: plugin/Controllers/MainController.php:514
193
+ msgid "Settings updated."
194
+ msgstr "Settings updated."
195
+
196
+ #: plugin/Controllers/ReviewController.php:48
197
+ msgid "Anonymous"
198
+ msgstr "Anonymous"
199
+
200
+ #: plugin/Controllers/ReviewController.php:56
201
+ msgid "No Title"
202
+ msgstr "No Title"
203
+
204
+ #: plugin/Controllers/ReviewController.php:60
205
+ msgid "Please fix the submission errors."
206
+ msgstr "Please fix the submission errors."
207
+
208
+ #: plugin/Handlers/RegisterPostType.php:298
209
+ msgid "%s review updated."
210
+ msgid_plural "%s posts updated."
211
+ msgstr[0] "%s review updated."
212
+ msgstr[1] "%s posts updated."
213
+
214
+ #: plugin/Handlers/RegisterPostType.php:299
215
+ msgid "%s review not updated, somebody is editing it."
216
+ msgid_plural "%s reviews not updated, somebody is editing them."
217
+ msgstr[0] "%s review not updated, somebody is editing it."
218
+ msgstr[1] "%s reviews not updated, somebody is editing them."
219
+
220
+ #: plugin/Handlers/RegisterPostType.php:300
221
+ msgid "%s review permanently deleted."
222
+ msgid_plural "%s reviews permanently deleted."
223
+ msgstr[0] "%s review permanently deleted."
224
+ msgstr[1] "%s reviews permanently deleted."
225
+
226
+ #: plugin/Handlers/RegisterPostType.php:301
227
+ msgid "%s review moved to the Trash."
228
+ msgid_plural "%s reviews moved to the Trash."
229
+ msgstr[0] "%s review moved to the Trash."
230
+ msgstr[1] "%s reviews moved to the Trash."
231
+
232
+ #: plugin/Handlers/RegisterPostType.php:302
233
+ msgid "%s review restored from the Trash."
234
+ msgid_plural "%s reviews restored from the Trash."
235
+ msgstr[0] "%s review restored from the Trash."
236
+ msgstr[1] "%s reviews restored from the Trash."
237
+
238
+ #: plugin/Handlers/RegisterPostType.php:593
239
+ msgid "%s star"
240
+ msgid_plural "%s stars"
241
+ msgstr[0] "%s star"
242
+ msgstr[1] "%s stars"
243
+
244
+ #: plugin/Handlers/RegisterPostType.php:597
245
+ msgid "All ratings"
246
+ msgstr "All ratings"
247
+
248
+ #: plugin/Handlers/RegisterPostType.php:615
249
+ msgid "All types"
250
+ msgstr "All types"
251
+
252
+ #: plugin/Handlers/RegisterPostType.php:618, views/edit/meta.php:7
253
+ msgid "Local Review"
254
+ msgstr "Local Review"
255
+
256
+ #: plugin/Handlers/SubmitReview.php:63
257
+ msgid "Your review has been submitted!"
258
+ msgstr "Your review has been submitted!"
259
+
260
+ #: plugin/Handlers/SubmitReview.php:96
261
+ msgid "Discard"
262
+ msgstr "Discard"
263
+
264
+ #: plugin/Handlers/SubmitReview.php:144
265
+ msgid "New %s-Star Review"
266
+ msgstr "New %s-Star Review"
267
+
268
+ #: plugin/Handlers/SubmitReview.php:197, plugin/Strings.php:28
269
+ msgid "View Review"
270
+ msgstr "View Review"
271
+
272
+ #: plugin/Handlers/TogglePinned.php:40
273
+ msgid "The review is pinned."
274
+ msgstr "The review is pinned."
275
+
276
+ #: plugin/Handlers/TogglePinned.php:41
277
+ msgid "The review is unpinned."
278
+ msgstr "The review is unpinned."
279
+
280
+ #: plugin/Html/Fields/Colorpicker.php:40
281
+ msgid "Hex Value"
282
+ msgstr "Hex Value"
283
+
284
+ #: plugin/Html/Fields/Progress.php:37
285
+ msgid "Please wait..."
286
+ msgstr "Please wait..."
287
+
288
+ #: plugin/Html/Fields/Progress.php:38
289
+ msgid "Inactive"
290
+ msgstr "Inactive"
291
+
292
+ #: plugin/Html/Fields/Yesno.php:25, views/edit/pinned.php:13
293
+ msgid "No"
294
+ msgstr "No"
295
+
296
+ #: plugin/Html/Fields/Yesno.php:26, views/edit/pinned.php:14
297
+ msgid "Yes"
298
+ msgstr "Yes"
299
+
300
+ #: plugin/Html/Form.php:119
301
+ msgid "Submit"
302
+ msgstr "Submit"
303
+
304
+ #: plugin/Html/Form.php:255
305
+ msgid "Save Changes"
306
+ msgstr "Save Changes"
307
+
308
+ #: plugin/Html/Partials/Addon.php:40
309
+ msgid "More Info"
310
+ msgstr "More Info"
311
+
312
+ #: plugin/Html/Partials/Reviews.php:77
313
+ msgid "No reviews were found."
314
+ msgstr "No reviews were found."
315
+
316
+ #: plugin/Html/Partials/Reviews.php:133
317
+ msgid "<span class=\"meta-nav\">&larr;</span> Previous"
318
+ msgstr "<span class=\"meta-nav\">&larr;</span> Previous"
319
+
320
+ #: plugin/Html/Partials/Reviews.php:139
321
+ msgid "Next <span class=\"meta-nav\">&rarr;</span>"
322
+ msgstr "Next <span class=\"meta-nav\">&rarr;</span>"
323
+
324
+ #: plugin/Html/Partials/Reviews.php:145
325
+ msgid "Page"
326
+ msgstr "Page"
327
+
328
+ #: plugin/Html/Partials/Reviews.php:148
329
+ msgid "Next &rarr;"
330
+ msgstr "Next &rarr;"
331
+
332
+ #: plugin/Html/Partials/Reviews.php:149
333
+ msgid "&larr; Previous"
334
+ msgstr "&larr; Previous"
335
+
336
+ #: plugin/Html/Partials/Reviews.php:199
337
+ msgid "Site Reviews navigation"
338
+ msgstr "Site Reviews navigation"
339
+
340
+ #: plugin/Html/Partials/Reviews.php:260
341
+ msgid "read more"
342
+ msgstr "read more"
343
+
344
+ #: plugin/Settings.php:113, plugin/Settings.php:196
345
+ msgid "Save Settings"
346
+ msgstr "Save Settings"
347
+
348
+ #: plugin/Settings.php:119
349
+ msgid "Require approval"
350
+ msgstr "Require approval"
351
+
352
+ #: plugin/Settings.php:121
353
+ msgid "Set the status of new review submissions to pending."
354
+ msgstr "Set the status of new review submissions to pending."
355
+
356
+ #: plugin/Settings.php:127
357
+ msgid "Require login"
358
+ msgstr "Require login"
359
+
360
+ #: plugin/Settings.php:128
361
+ msgid "Only allow review submissions from registered users."
362
+ msgstr "Only allow review submissions from registered users."
363
+
364
+ #: plugin/Settings.php:134
365
+ msgid "Notifications"
366
+ msgstr "Notifications"
367
+
368
+ #: plugin/Settings.php:137
369
+ msgid "Do not send review notifications"
370
+ msgstr "Do not send review notifications"
371
+
372
+ #: plugin/Settings.php:138
373
+ msgid "Send to administrator <code>%s</code>"
374
+ msgstr "Send to administrator <code>%s</code>"
375
+
376
+ #: plugin/Settings.php:139
377
+ msgid "Send to one or more email addresses"
378
+ msgstr "Send to one or more email addresses"
379
+
380
+ #: plugin/Settings.php:140
381
+ msgid "Send to <a href=\"https://slack.com/\">Slack</a>"
382
+ msgstr "Send to <a href=\"https://slack.com/\">Slack</a>"
383
+
384
+ #: plugin/Settings.php:147
385
+ msgid "Send notification emails to"
386
+ msgstr "Send notification emails to"
387
+
388
+ #: plugin/Settings.php:151
389
+ msgid "Separate multiple emails with a comma"
390
+ msgstr "Separate multiple emails with a comma"
391
+
392
+ #: plugin/Settings.php:157
393
+ msgid "Webhook URL"
394
+ msgstr "Webhook URL"
395
+
396
+ #: plugin/Settings.php:162
397
+ msgid ""
398
+ "To send notifications to Slack, <a href=\"%s\">create a new Incoming "
399
+ "WebHook</a> and then paste the provided Webhook URL in the field above."
400
+ msgstr ""
401
+ "To send notifications to Slack, <a href=\"%s\">create a new Incoming "
402
+ "WebHook</a> and then paste the provided Webhook URL in the field above."
403
+
404
+ #: plugin/Settings.php:170
405
+ msgid "Notification template"
406
+ msgstr "Notification template"
407
+
408
+ #: plugin/Settings.php:201
409
+ msgid "Form Labels"
410
+ msgstr "Form Labels"
411
+
412
+ #: plugin/Settings.php:202
413
+ msgid "Customize the label text for the review submission form fields."
414
+ msgstr "Customize the label text for the review submission form fields."
415
+
416
+ #: plugin/Settings.php:208
417
+ msgid "Rating label"
418
+ msgstr "Rating label"
419
+
420
+ #: plugin/Settings.php:209
421
+ msgid "Your overall rating"
422
+ msgstr "Your overall rating"
423
+
424
+ #: plugin/Settings.php:216
425
+ msgid "Title label"
426
+ msgstr "Title label"
427
+
428
+ #: plugin/Settings.php:217
429
+ msgid "Title of your review"
430
+ msgstr "Title of your review"
431
+
432
+ #: plugin/Settings.php:224
433
+ msgid "Content label"
434
+ msgstr "Content label"
435
+
436
+ #: plugin/Settings.php:225
437
+ msgid "Your review"
438
+ msgstr "Your review"
439
+
440
+ #: plugin/Settings.php:232
441
+ msgid "Reviewer label"
442
+ msgstr "Reviewer label"
443
+
444
+ #: plugin/Settings.php:233
445
+ msgid "Your name"
446
+ msgstr "Your name"
447
+
448
+ #: plugin/Settings.php:240
449
+ msgid "Email label"
450
+ msgstr "Email label"
451
+
452
+ #: plugin/Settings.php:241
453
+ msgid "Your email"
454
+ msgstr "Your email"
455
+
456
+ #: plugin/Settings.php:248
457
+ msgid "Terms label"
458
+ msgstr "Terms label"
459
+
460
+ #: plugin/Settings.php:249
461
+ msgid "This review is based on my own experience and is my genuine opinion."
462
+ msgstr "This review is based on my own experience and is my genuine opinion."
463
+
464
+ #: plugin/Settings.php:255
465
+ msgid "Form Placeholders"
466
+ msgstr "Form Placeholders"
467
+
468
+ #: plugin/Settings.php:256
469
+ msgid ""
470
+ "Customize the placeholder text for the review submission form fields. Use a "
471
+ "single space character to disable the placeholder text."
472
+ msgstr ""
473
+ "Customize the placeholder text for the review submission form fields. Use a "
474
+ "single space character to disable the placeholder text."
475
+
476
+ #: plugin/Settings.php:263
477
+ msgid "Title placeholder"
478
+ msgstr "Title placeholder"
479
+
480
+ #: plugin/Settings.php:264
481
+ msgid "Summarize your review or highlight an interesting detail"
482
+ msgstr "Summarize your review or highlight an interesting detail"
483
+
484
+ #: plugin/Settings.php:272
485
+ msgid "Content placeholder"
486
+ msgstr "Content placeholder"
487
+
488
+ #: plugin/Settings.php:273
489
+ msgid "Tell people your review"
490
+ msgstr "Tell people your review"
491
+
492
+ #: plugin/Settings.php:281
493
+ msgid "Reviewer placeholder"
494
+ msgstr "Reviewer placeholder"
495
+
496
+ #: plugin/Settings.php:282
497
+ msgid "Tell us your name"
498
+ msgstr "Tell us your name"
499
+
500
+ #: plugin/Settings.php:290
501
+ msgid "Email placeholder"
502
+ msgstr "Email placeholder"
503
+
504
+ #: plugin/Settings.php:291
505
+ msgid "Tell us your email"
506
+ msgstr "Tell us your email"
507
+
508
+ #: plugin/Shortcodes/SiteReviewsForm.php:28, plugin/Widgets/ReviewsForm.php:125
509
+ msgid "You must be <a href=\"%s\">logged in</a> to submit a review."
510
+ msgstr "You must be <a href=\"%s\">logged in</a> to submit a review."
511
+
512
+ #: plugin/Strings.php:18
513
+ msgid "Add New Review"
514
+ msgstr "Add New Review"
515
+
516
+ #: plugin/Strings.php:19, plugin/Widgets/RecentReviews.php:72
517
+ msgid "All Reviews"
518
+ msgstr "All Reviews"
519
+
520
+ #: plugin/Strings.php:20
521
+ msgid "Review Archives"
522
+ msgstr "Review Archives"
523
+
524
+ #: plugin/Strings.php:21
525
+ msgid "Edit Review"
526
+ msgstr "Edit Review"
527
+
528
+ #: plugin/Strings.php:22
529
+ msgid "Insert into review"
530
+ msgstr "Insert into review"
531
+
532
+ #: plugin/Strings.php:23
533
+ msgid "New Review"
534
+ msgstr "New Review"
535
+
536
+ #: plugin/Strings.php:24
537
+ msgid "No Reviews found"
538
+ msgstr "No Reviews found"
539
+
540
+ #: plugin/Strings.php:25
541
+ msgid "No Reviews found in Trash"
542
+ msgstr "No Reviews found in Trash"
543
+
544
+ #: plugin/Strings.php:26
545
+ msgid "Search Reviews"
546
+ msgstr "Search Reviews"
547
+
548
+ #: plugin/Strings.php:27
549
+ msgid "Uploaded to this review"
550
+ msgstr "Uploaded to this review"
551
+
552
+ #: plugin/Strings.php:35
553
+ msgid "Review has been approved and published."
554
+ msgstr "Review has been approved and published."
555
+
556
+ #: plugin/Strings.php:36
557
+ msgid "Review draft updated."
558
+ msgstr "Review draft updated."
559
+
560
+ #: plugin/Strings.php:37
561
+ msgid "Preview review"
562
+ msgstr "Preview review"
563
+
564
+ #: plugin/Strings.php:38
565
+ msgid "Review published."
566
+ msgstr "Review published."
567
+
568
+ #: plugin/Strings.php:39
569
+ msgid "Review restored to revision from %s."
570
+ msgstr "Review restored to revision from %s."
571
+
572
+ #: plugin/Strings.php:40
573
+ msgid "Review has been reverted to its original submission state."
574
+ msgstr "Review has been reverted to its original submission state."
575
+
576
+ #: plugin/Strings.php:41
577
+ msgid "Review saved."
578
+ msgstr "Review saved."
579
+
580
+ #: plugin/Strings.php:42
581
+ msgid "Review scheduled for: %s."
582
+ msgstr "Review scheduled for: %s."
583
+
584
+ #: plugin/Strings.php:43
585
+ msgid "Review submitted."
586
+ msgstr "Review submitted."
587
+
588
+ #: plugin/Strings.php:44
589
+ msgid "Review has been unapproved and is now pending."
590
+ msgstr "Review has been unapproved and is now pending."
591
+
592
+ #: plugin/Strings.php:45
593
+ msgid "Review updated."
594
+ msgstr "Review updated."
595
+
596
+ #: plugin/Strings.php:46
597
+ msgid "View review"
598
+ msgstr "View review"
599
+
600
+ #: plugin/Widgets/RecentReviews.php:58
601
+ msgid "Display only title"
602
+ msgstr "Display only title"
603
+
604
+ #: plugin/Widgets/RecentReviews.php:59
605
+ msgid "Display only excerpt"
606
+ msgstr "Display only excerpt"
607
+
608
+ #: plugin/Widgets/RecentReviews.php:60
609
+ msgid "Display title and excerpt"
610
+ msgstr "Display title and excerpt"
611
+
612
+ #: plugin/Widgets/RecentReviews.php:70
613
+ msgid "Which reviews would you like to display? "
614
+ msgstr "Which reviews would you like to display? "
615
+
616
+ #: plugin/Widgets/RecentReviews.php:79
617
+ msgid "What is the minimum rating to display? "
618
+ msgstr "What is the minimum rating to display? "
619
+
620
+ #: plugin/Widgets/RecentReviews.php:82
621
+ msgid "5 stars"
622
+ msgstr "5 stars"
623
+
624
+ #: plugin/Widgets/RecentReviews.php:83
625
+ msgid "4 stars"
626
+ msgstr "4 stars"
627
+
628
+ #: plugin/Widgets/RecentReviews.php:84
629
+ msgid "3 stars"
630
+ msgstr "3 stars"
631
+
632
+ #: plugin/Widgets/RecentReviews.php:85
633
+ msgid "2 stars"
634
+ msgstr "2 stars"
635
+
636
+ #: plugin/Widgets/RecentReviews.php:86
637
+ msgid "1 star"
638
+ msgstr "1 star"
639
+
640
+ #: plugin/Widgets/RecentReviews.php:93
641
+ msgid "How many reviews would you like to display? "
642
+ msgstr "How many reviews would you like to display? "
643
+
644
+ #: plugin/Widgets/RecentReviews.php:104
645
+ msgid "Show the name of the reviewer?"
646
+ msgstr "Show the name of the reviewer?"
647
+
648
+ #: plugin/Widgets/RecentReviews.php:105
649
+ msgid "Show the review date?"
650
+ msgstr "Show the review date?"
651
+
652
+ #: plugin/Widgets/RecentReviews.php:106
653
+ msgid "Show the review rating?"
654
+ msgstr "Show the review rating?"
655
+
656
+ #: plugin/Widgets/RecentReviews.php:113, plugin/Widgets/ReviewsForm.php:67
657
+ msgid "Enter any custom CSS classes here"
658
+ msgstr "Enter any custom CSS classes here"
659
+
660
+ #: plugin/Widgets/ReviewsForm.php:30
661
+ msgid "Your email address will not be published. Required fields are marked %s*%s"
662
+ msgstr "Your email address will not be published. Required fields are marked %s*%s"
663
+
664
+ #: plugin/Widgets/ReviewsForm.php:48
665
+ msgid "Description"
666
+ msgstr "Description"
667
+
668
+ #: plugin/Widgets/ReviewsForm.php:57
669
+ msgid "Hide the title field"
670
+ msgstr "Hide the title field"
671
+
672
+ #: plugin/Widgets/ReviewsForm.php:58
673
+ msgid "Hide the reviewer field"
674
+ msgstr "Hide the reviewer field"
675
+
676
+ #: plugin/Widgets/ReviewsForm.php:59
677
+ msgid "Hide the email field"
678
+ msgstr "Hide the email field"
679
+
680
+ #: plugin/Widgets/ReviewsForm.php:60
681
+ msgid "Hide the terms field"
682
+ msgstr "Hide the terms field"
683
+
684
+ #: views/edit/meta.php:27
685
+ msgid "Nothing to Revert"
686
+ msgstr "Nothing to Revert"
687
+
688
+ #: views/edit/meta.php:28
689
+ msgid "Revert Changes"
690
+ msgstr "Revert Changes"
691
+
692
+ #: views/edit/meta.php:54
693
+ msgid "Avatar"
694
+ msgstr "Avatar"
695
+
696
+ #: views/edit/pinned.php:21
697
+ msgid "Edit"
698
+ msgstr "Edit"
699
+
700
+ #: views/edit/pinned.php:22
701
+ msgid "Edit pinned status"
702
+ msgstr "Edit pinned status"
703
+
704
+ #: views/edit/pinned.php:30
705
+ msgid "Pin"
706
+ msgstr "Pin"
707
+
708
+ #: views/edit/pinned.php:31
709
+ msgid "Unpin"
710
+ msgstr "Unpin"
711
+
712
+ #: views/edit/pinned.php:34
713
+ msgid "OK"
714
+ msgstr "OK"
715
+
716
+ #: views/edit/pinned.php:35
717
+ msgid "Cancel"
718
+ msgstr "Cancel"
719
+
720
+ #: views/edit/review.php:4
721
+ msgid "no title"
722
+ msgstr "no title"
723
+
724
+ #: views/menu/addons/addons.php:3
725
+ msgid "The following Add-Ons extend the functionality of Site Reviews."
726
+ msgstr "The following Add-Ons extend the functionality of Site Reviews."
727
+
728
+ #: views/menu/addons/addons.php:12
729
+ msgid ""
730
+ "Sync your Tripadvisor business reviews with an optional minimum rating and "
731
+ "display them on your site."
732
+ msgstr ""
733
+ "Sync your Tripadvisor business reviews with an optional minimum rating and "
734
+ "display them on your site."
735
+
736
+ #: views/menu/addons/addons.php:19
737
+ msgid ""
738
+ "Sync your Yelp business reviews with an optional minimum rating and display "
739
+ "them on your site."
740
+ msgstr ""
741
+ "Sync your Yelp business reviews with an optional minimum rating and display "
742
+ "them on your site."
743
+
744
+ #: views/menu/help/system.php:9
745
+ msgid "Download System Info"
746
+ msgstr "Download System Info"
747
+
748
+ #: views/menu/help/system.php:14
749
+ msgid "Logging"
750
+ msgstr "Logging"
751
+
752
+ #: views/menu/help/system.php:15
753
+ msgid ""
754
+ "You can enable logging for debugging purposes. Log files can grow quickly, "
755
+ "so don't leave this on."
756
+ msgstr ""
757
+ "You can enable logging for debugging purposes. Log files can grow quickly, "
758
+ "so don't leave this on."
759
+
760
+ #: views/menu/help/system.php:26
761
+ msgid "Clear Log"
762
+ msgstr "Clear Log"
763
+
764
+ #: views/menu/help/system.php:32
765
+ msgid "Download Log"
766
+ msgstr "Download Log"
767
+
768
+ #: views/menu/help/system.php:44
769
+ msgid "Disable Logging"
770
+ msgstr "Disable Logging"
771
+
772
+ #: views/menu/help/system.php:48
773
+ msgid "Enable Logging"
774
+ msgstr "Enable Logging"
775
+
776
+ #: views/menu/status.php:7
777
+ msgid ""
778
+ "All dates and times shown here use the WordPress <a href='%s'>configured "
779
+ "timezone</a>."
780
+ msgstr ""
781
+ "All dates and times shown here use the WordPress <a href='%s'>configured "
782
+ "timezone</a>."
783
+
784
+ #: views/menu/status.php:16
785
+ msgid "Site"
786
+ msgstr "Site"
787
+
788
+ #: views/menu/status.php:18, views/menu/status.php:35
789
+ msgid "Last fetch"
790
+ msgstr "Last fetch"
791
+
792
+ #: views/menu/status.php:19, views/menu/status.php:38
793
+ msgid "Next scheduled fetch"
794
+ msgstr "Next scheduled fetch"
795
+
796
+ #: views/menu/status.php:36
797
+ msgid "No fetch has been completed"
798
+ msgstr "No fetch has been completed"
799
+
800
+ #: views/menu/status.php:39
801
+ msgid "Nothing currently scheduled"
802
+ msgstr "Nothing currently scheduled"
803
+
804
+ #: views/menu/status.php:59
805
+ msgid "Fetch reviews from"
806
+ msgstr "Fetch reviews from"
807
+
808
+ #: views/menu/status.php:66
809
+ msgid "Fetch status"
810
+ msgstr "Fetch status"
811
+
812
+ #: views/menu/status.php:67
813
+ msgid "Fetching reviews..."
814
+ msgstr "Fetching reviews..."
815
+
816
+ #: views/menu/status.php:79
817
+ msgid "Fetch Reviews"
818
+ msgstr "Fetch Reviews"
819
+
820
+ #: views/submit/index.php:17
821
+ msgid "Select a Rating"
822
+ msgstr "Select a Rating"
823
+
824
+ #: views/submit/index.php:18
825
+ msgid "Excellent"
826
+ msgstr "Excellent"
827
+
828
+ #: views/submit/index.php:19
829
+ msgid "Very Good"
830
+ msgstr "Very Good"
831
+
832
+ #: views/submit/index.php:20
833
+ msgid "Average"
834
+ msgstr "Average"
835
+
836
+ #: views/submit/index.php:21
837
+ msgid "Poor"
838
+ msgstr "Poor"
839
+
840
+ #: views/submit/index.php:22
841
+ msgid "Terrible"
842
+ msgstr "Terrible"
843
+
844
+ #: views/submit/index.php:121
845
+ msgid "Submit your review"
846
+ msgstr "Submit your review"
847
+
848
+ #: plugin/Strings.php:53
849
+ msgctxt ":attribute is a placeholder and should not be translated."
850
+ msgid "The :attribute must be accepted."
851
+ msgstr "The :attribute must be accepted."
852
+
853
+ #: plugin/Strings.php:56
854
+ msgctxt ":attribute is a placeholder and should not be translated."
855
+ msgid "The :attribute must be a valid email address."
856
+ msgstr "The :attribute must be a valid email address."
857
+
858
+ #: plugin/Strings.php:61
859
+ msgctxt ":attribute is a placeholder and should not be translated."
860
+ msgid "The :attribute format is invalid."
861
+ msgstr "The :attribute format is invalid."
862
+
863
+ #: plugin/Strings.php:62
864
+ msgctxt ":attribute is a placeholder and should not be translated."
865
+ msgid "The :attribute field is required."
866
+ msgstr "The :attribute field is required."
867
+
868
+ #: plugin/Strings.php:54
869
+ msgctxt ":attribute, :min, and :max are placeholders and should not be translated."
870
+ msgid "The :attribute must be between :min and :max."
871
+ msgstr "The :attribute must be between :min and :max."
872
+
873
+ #: plugin/Strings.php:55
874
+ msgctxt ":attribute, :min, and :max are placeholders and should not be translated."
875
+ msgid "The :attribute must be between :min and :max characters."
876
+ msgstr "The :attribute must be between :min and :max characters."
877
+
878
+ #: plugin/Strings.php:57
879
+ msgctxt ":attribute and :max are placeholders and should not be translated."
880
+ msgid "The :attribute may not be greater than :max."
881
+ msgstr "The :attribute may not be greater than :max."
882
+
883
+ #: plugin/Strings.php:58
884
+ msgctxt ":attribute and :max are placeholders and should not be translated."
885
+ msgid "The :attribute may not be greater than :max characters."
886
+ msgstr "The :attribute may not be greater than :max characters."
887
+
888
+ #: plugin/Strings.php:59
889
+ msgctxt ":attribute and :min are placeholders and should not be translated."
890
+ msgid "The :attribute must be at least :min."
891
+ msgstr "The :attribute must be at least :min."
892
+
893
+ #: plugin/Strings.php:60
894
+ msgctxt ":attribute and :min are placeholders and should not be translated."
895
+ msgid "The :attribute must be at least :min characters."
896
+ msgstr "The :attribute must be at least :min characters."
languages/site-reviews.pot ADDED
@@ -0,0 +1,850 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2017 site-reviews
2
+ # This file is distributed under the same license as the site-reviews package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: site-reviews\n"
6
+ "Report-Msgid-Bugs-To: https://github.com/geminilabs/site-reviews/issues/new\n"
7
+ "MIME-Version: 1.0\n"
8
+ "Content-Type: text/plain; charset=UTF-8\n"
9
+ "Content-Transfer-Encoding: 8bit\n"
10
+ "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
11
+ "Last-Translator: Paul Ryley <paul@geminilabs.io>\n"
12
+ "Language-Team: Gemini Labs <support@geminilabs.io>\n"
13
+ "X-Poedit-Basepath: ..\n"
14
+ "X-Poedit-SourceCharset: UTF-8\n"
15
+ "X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
16
+ "X-Poedit-SearchPath-0: .\n"
17
+ "X-Poedit-SearchPathExcluded-0: *.js\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+
20
+ #: activate.php:43
21
+ msgid "The Site Reviews plugin was deactivated."
22
+ msgstr ""
23
+
24
+ #: activate.php:48
25
+ msgid "Sorry, this plugin requires PHP version 5.4 or greater in order to work properly."
26
+ msgstr ""
27
+
28
+ #: activate.php:49
29
+ msgid "Please contact your hosting provider or server administrator to upgrade the version of PHP on your server (your server is running PHP version %s), or try to find an alternative plugin."
30
+ msgstr ""
31
+
32
+ #: activate.php:55
33
+ msgid "Sorry, this plugin requires WordPress version 4.0.0 or greater in order to work properly."
34
+ msgstr ""
35
+
36
+ #: activate.php:56
37
+ msgid "Update WordPress"
38
+ msgstr ""
39
+
40
+ #: plugin/Commands/RegisterPostType.php:20, plugin/Widgets/RecentReviews.php:48, plugin/Widgets/ReviewsForm.php:40
41
+ msgid "Title"
42
+ msgstr ""
43
+
44
+ #: plugin/Commands/RegisterPostType.php:21, views/edit/meta.php:46
45
+ msgid "Date"
46
+ msgstr ""
47
+
48
+ #: plugin/Controllers/AjaxController.php:26
49
+ msgid "Log is empty"
50
+ msgstr ""
51
+
52
+ #: plugin/Controllers/MainController.php:51
53
+ msgid "Log was cleared."
54
+ msgstr ""
55
+
56
+ #: plugin/Controllers/MainController.php:87, plugin/Controllers/MainController.php:293, plugin/Controllers/MainController.php:478
57
+ msgid "Settings"
58
+ msgstr ""
59
+
60
+ #: plugin/Controllers/MainController.php:106
61
+ msgid "%s Review"
62
+ msgid_plural "%s Reviews"
63
+ msgstr[0] ""
64
+ msgstr[1] ""
65
+
66
+ #: plugin/Controllers/MainController.php:156
67
+ msgid "Details"
68
+ msgstr ""
69
+
70
+ #: plugin/Controllers/MainController.php:170
71
+ msgid "Pin Your Reviews"
72
+ msgstr ""
73
+
74
+ #: plugin/Controllers/MainController.php:171
75
+ msgid "You can pin exceptional reviews so that they are always shown first in your widgets and shortcodes."
76
+ msgstr ""
77
+
78
+ #: plugin/Controllers/MainController.php:193
79
+ msgid "Review"
80
+ msgstr ""
81
+
82
+ #: plugin/Controllers/MainController.php:194, views/menu/status.php:17
83
+ msgid "Reviews"
84
+ msgstr ""
85
+
86
+ #: plugin/Controllers/MainController.php:195
87
+ msgid "Site Reviews"
88
+ msgstr ""
89
+
90
+ #: plugin/Controllers/MainController.php:203, views/edit/meta.php:50
91
+ msgid "Reviewer"
92
+ msgstr ""
93
+
94
+ #: plugin/Controllers/MainController.php:204, views/edit/meta.php:42
95
+ msgid "Type"
96
+ msgstr ""
97
+
98
+ #: plugin/Controllers/MainController.php:205, views/edit/meta.php:38
99
+ msgid "Rating"
100
+ msgstr ""
101
+
102
+ #: plugin/Controllers/MainController.php:206, views/edit/pinned.php:17
103
+ msgid "Pinned"
104
+ msgstr ""
105
+
106
+ #: plugin/Controllers/MainController.php:229
107
+ msgid "Approve this review"
108
+ msgstr ""
109
+
110
+ #: plugin/Controllers/MainController.php:231, plugin/Handlers/SubmitReview.php:95
111
+ msgid "Approve"
112
+ msgstr ""
113
+
114
+ #: plugin/Controllers/MainController.php:234
115
+ msgid "Unapprove this review"
116
+ msgstr ""
117
+
118
+ #: plugin/Controllers/MainController.php:236
119
+ msgid "Unapprove"
120
+ msgstr ""
121
+
122
+ #: plugin/Controllers/MainController.php:294
123
+ msgid "Get Help"
124
+ msgstr ""
125
+
126
+ #: plugin/Controllers/MainController.php:295, plugin/Controllers/MainController.php:353
127
+ msgid "Add-Ons"
128
+ msgstr ""
129
+
130
+ #: plugin/Controllers/MainController.php:331
131
+ msgid "Submit a Site Review"
132
+ msgstr ""
133
+
134
+ #: plugin/Controllers/MainController.php:332
135
+ msgid "A \"submit a review\" form for your site."
136
+ msgstr ""
137
+
138
+ #: plugin/Controllers/MainController.php:336
139
+ msgid "Recent Site Reviews"
140
+ msgstr ""
141
+
142
+ #: plugin/Controllers/MainController.php:337
143
+ msgid "Your site’s most recent Local Reviews."
144
+ msgstr ""
145
+
146
+ #: plugin/Controllers/MainController.php:373
147
+ msgid "Documentation"
148
+ msgstr ""
149
+
150
+ #: plugin/Controllers/MainController.php:376
151
+ msgid "System Info"
152
+ msgstr ""
153
+
154
+ #: plugin/Controllers/MainController.php:459
155
+ msgid "%s reviews are read-only."
156
+ msgstr ""
157
+
158
+ #: plugin/Controllers/MainController.php:481
159
+ msgid "Licenses"
160
+ msgstr ""
161
+
162
+ #: plugin/Controllers/MainController.php:497
163
+ msgid "Logging enabled."
164
+ msgstr ""
165
+
166
+ #: plugin/Controllers/MainController.php:498
167
+ msgid "Logging disabled."
168
+ msgstr ""
169
+
170
+ #: plugin/Controllers/MainController.php:514
171
+ msgid "Settings updated."
172
+ msgstr ""
173
+
174
+ #: plugin/Controllers/ReviewController.php:48
175
+ msgid "Anonymous"
176
+ msgstr ""
177
+
178
+ #: plugin/Controllers/ReviewController.php:56
179
+ msgid "No Title"
180
+ msgstr ""
181
+
182
+ #: plugin/Controllers/ReviewController.php:60
183
+ msgid "Please fix the submission errors."
184
+ msgstr ""
185
+
186
+ #: plugin/Handlers/RegisterPostType.php:298
187
+ msgid "%s review updated."
188
+ msgid_plural "%s posts updated."
189
+ msgstr[0] ""
190
+ msgstr[1] ""
191
+
192
+ #: plugin/Handlers/RegisterPostType.php:299
193
+ msgid "%s review not updated, somebody is editing it."
194
+ msgid_plural "%s reviews not updated, somebody is editing them."
195
+ msgstr[0] ""
196
+ msgstr[1] ""
197
+
198
+ #: plugin/Handlers/RegisterPostType.php:300
199
+ msgid "%s review permanently deleted."
200
+ msgid_plural "%s reviews permanently deleted."
201
+ msgstr[0] ""
202
+ msgstr[1] ""
203
+
204
+ #: plugin/Handlers/RegisterPostType.php:301
205
+ msgid "%s review moved to the Trash."
206
+ msgid_plural "%s reviews moved to the Trash."
207
+ msgstr[0] ""
208
+ msgstr[1] ""
209
+
210
+ #: plugin/Handlers/RegisterPostType.php:302
211
+ msgid "%s review restored from the Trash."
212
+ msgid_plural "%s reviews restored from the Trash."
213
+ msgstr[0] ""
214
+ msgstr[1] ""
215
+
216
+ #: plugin/Handlers/RegisterPostType.php:593
217
+ msgid "%s star"
218
+ msgid_plural "%s stars"
219
+ msgstr[0] ""
220
+ msgstr[1] ""
221
+
222
+ #: plugin/Handlers/RegisterPostType.php:597
223
+ msgid "All ratings"
224
+ msgstr ""
225
+
226
+ #: plugin/Handlers/RegisterPostType.php:615
227
+ msgid "All types"
228
+ msgstr ""
229
+
230
+ #: plugin/Handlers/RegisterPostType.php:618, views/edit/meta.php:7
231
+ msgid "Local Review"
232
+ msgstr ""
233
+
234
+ #: plugin/Handlers/SubmitReview.php:63
235
+ msgid "Your review has been submitted!"
236
+ msgstr ""
237
+
238
+ #: plugin/Handlers/SubmitReview.php:96
239
+ msgid "Discard"
240
+ msgstr ""
241
+
242
+ #: plugin/Handlers/SubmitReview.php:144
243
+ msgid "New %s-Star Review"
244
+ msgstr ""
245
+
246
+ #: plugin/Handlers/SubmitReview.php:197, plugin/Strings.php:28
247
+ msgid "View Review"
248
+ msgstr ""
249
+
250
+ #: plugin/Handlers/TogglePinned.php:40
251
+ msgid "The review is pinned."
252
+ msgstr ""
253
+
254
+ #: plugin/Handlers/TogglePinned.php:41
255
+ msgid "The review is unpinned."
256
+ msgstr ""
257
+
258
+ #: plugin/Html/Fields/Colorpicker.php:40
259
+ msgid "Hex Value"
260
+ msgstr ""
261
+
262
+ #: plugin/Html/Fields/Progress.php:37
263
+ msgid "Please wait..."
264
+ msgstr ""
265
+
266
+ #: plugin/Html/Fields/Progress.php:38
267
+ msgid "Inactive"
268
+ msgstr ""
269
+
270
+ #: plugin/Html/Fields/Yesno.php:25, views/edit/pinned.php:13
271
+ msgid "No"
272
+ msgstr ""
273
+
274
+ #: plugin/Html/Fields/Yesno.php:26, views/edit/pinned.php:14
275
+ msgid "Yes"
276
+ msgstr ""
277
+
278
+ #: plugin/Html/Form.php:119
279
+ msgid "Submit"
280
+ msgstr ""
281
+
282
+ #: plugin/Html/Form.php:255
283
+ msgid "Save Changes"
284
+ msgstr ""
285
+
286
+ #: plugin/Html/Partials/Addon.php:40
287
+ msgid "More Info"
288
+ msgstr ""
289
+
290
+ #: plugin/Html/Partials/Reviews.php:77
291
+ msgid "No reviews were found."
292
+ msgstr ""
293
+
294
+ #: plugin/Html/Partials/Reviews.php:133
295
+ msgid "<span class=\"meta-nav\">&larr;</span> Previous"
296
+ msgstr ""
297
+
298
+ #: plugin/Html/Partials/Reviews.php:139
299
+ msgid "Next <span class=\"meta-nav\">&rarr;</span>"
300
+ msgstr ""
301
+
302
+ #: plugin/Html/Partials/Reviews.php:145
303
+ msgid "Page"
304
+ msgstr ""
305
+
306
+ #: plugin/Html/Partials/Reviews.php:148
307
+ msgid "Next &rarr;"
308
+ msgstr ""
309
+
310
+ #: plugin/Html/Partials/Reviews.php:149
311
+ msgid "&larr; Previous"
312
+ msgstr ""
313
+
314
+ #: plugin/Html/Partials/Reviews.php:199
315
+ msgid "Site Reviews navigation"
316
+ msgstr ""
317
+
318
+ #: plugin/Html/Partials/Reviews.php:260
319
+ msgid "read more"
320
+ msgstr ""
321
+
322
+ #: plugin/Settings.php:113, plugin/Settings.php:196
323
+ msgid "Save Settings"
324
+ msgstr ""
325
+
326
+ #: plugin/Settings.php:119
327
+ msgid "Require approval"
328
+ msgstr ""
329
+
330
+ #: plugin/Settings.php:121
331
+ msgid "Set the status of new review submissions to pending."
332
+ msgstr ""
333
+
334
+ #: plugin/Settings.php:127
335
+ msgid "Require login"
336
+ msgstr ""
337
+
338
+ #: plugin/Settings.php:128
339
+ msgid "Only allow review submissions from registered users."
340
+ msgstr ""
341
+
342
+ #: plugin/Settings.php:134
343
+ msgid "Notifications"
344
+ msgstr ""
345
+
346
+ #: plugin/Settings.php:137
347
+ msgid "Do not send review notifications"
348
+ msgstr ""
349
+
350
+ #: plugin/Settings.php:138
351
+ msgid "Send to administrator <code>%s</code>"
352
+ msgstr ""
353
+
354
+ #: plugin/Settings.php:139
355
+ msgid "Send to one or more email addresses"
356
+ msgstr ""
357
+
358
+ #: plugin/Settings.php:140
359
+ msgid "Send to <a href=\"https://slack.com/\">Slack</a>"
360
+ msgstr ""
361
+
362
+ #: plugin/Settings.php:147
363
+ msgid "Send notification emails to"
364
+ msgstr ""
365
+
366
+ #: plugin/Settings.php:151
367
+ msgid "Separate multiple emails with a comma"
368
+ msgstr ""
369
+
370
+ #: plugin/Settings.php:157
371
+ msgid "Webhook URL"
372
+ msgstr ""
373
+
374
+ #: plugin/Settings.php:162
375
+ msgid "To send notifications to Slack, <a href=\"%s\">create a new Incoming WebHook</a> and then paste the provided Webhook URL in the field above."
376
+ msgstr ""
377
+
378
+ #: plugin/Settings.php:170
379
+ msgid "Notification template"
380
+ msgstr ""
381
+
382
+ #: plugin/Settings.php:201
383
+ msgid "Form Labels"
384
+ msgstr ""
385
+
386
+ #: plugin/Settings.php:202
387
+ msgid "Customize the label text for the review submission form fields."
388
+ msgstr ""
389
+
390
+ #: plugin/Settings.php:208
391
+ msgid "Rating label"
392
+ msgstr ""
393
+
394
+ #: plugin/Settings.php:209
395
+ msgid "Your overall rating"
396
+ msgstr ""
397
+
398
+ #: plugin/Settings.php:216
399
+ msgid "Title label"
400
+ msgstr ""
401
+
402
+ #: plugin/Settings.php:217
403
+ msgid "Title of your review"
404
+ msgstr ""
405
+
406
+ #: plugin/Settings.php:224
407
+ msgid "Content label"
408
+ msgstr ""
409
+
410
+ #: plugin/Settings.php:225
411
+ msgid "Your review"
412
+ msgstr ""
413
+
414
+ #: plugin/Settings.php:232
415
+ msgid "Reviewer label"
416
+ msgstr ""
417
+
418
+ #: plugin/Settings.php:233
419
+ msgid "Your name"
420
+ msgstr ""
421
+
422
+ #: plugin/Settings.php:240
423
+ msgid "Email label"
424
+ msgstr ""
425
+
426
+ #: plugin/Settings.php:241
427
+ msgid "Your email"
428
+ msgstr ""
429
+
430
+ #: plugin/Settings.php:248
431
+ msgid "Terms label"
432
+ msgstr ""
433
+
434
+ #: plugin/Settings.php:249
435
+ msgid "This review is based on my own experience and is my genuine opinion."
436
+ msgstr ""
437
+
438
+ #: plugin/Settings.php:255
439
+ msgid "Form Placeholders"
440
+ msgstr ""
441
+
442
+ #: plugin/Settings.php:256
443
+ msgid "Customize the placeholder text for the review submission form fields. Use a single space character to disable the placeholder text."
444
+ msgstr ""
445
+
446
+ #: plugin/Settings.php:263
447
+ msgid "Title placeholder"
448
+ msgstr ""
449
+
450
+ #: plugin/Settings.php:264
451
+ msgid "Summarize your review or highlight an interesting detail"
452
+ msgstr ""
453
+
454
+ #: plugin/Settings.php:272
455
+ msgid "Content placeholder"
456
+ msgstr ""
457
+
458
+ #: plugin/Settings.php:273
459
+ msgid "Tell people your review"
460
+ msgstr ""
461
+
462
+ #: plugin/Settings.php:281
463
+ msgid "Reviewer placeholder"
464
+ msgstr ""
465
+
466
+ #: plugin/Settings.php:282
467
+ msgid "Tell us your name"
468
+ msgstr ""
469
+
470
+ #: plugin/Settings.php:290
471
+ msgid "Email placeholder"
472
+ msgstr ""
473
+
474
+ #: plugin/Settings.php:291
475
+ msgid "Tell us your email"
476
+ msgstr ""
477
+
478
+ #: plugin/Shortcodes/SiteReviewsForm.php:28, plugin/Widgets/ReviewsForm.php:125
479
+ msgid "You must be <a href=\"%s\">logged in</a> to submit a review."
480
+ msgstr ""
481
+
482
+ #: plugin/Strings.php:18
483
+ msgid "Add New Review"
484
+ msgstr ""
485
+
486
+ #: plugin/Strings.php:19, plugin/Widgets/RecentReviews.php:72
487
+ msgid "All Reviews"
488
+ msgstr ""
489
+
490
+ #: plugin/Strings.php:20
491
+ msgid "Review Archives"
492
+ msgstr ""
493
+
494
+ #: plugin/Strings.php:21
495
+ msgid "Edit Review"
496
+ msgstr ""
497
+
498
+ #: plugin/Strings.php:22
499
+ msgid "Insert into review"
500
+ msgstr ""
501
+
502
+ #: plugin/Strings.php:23
503
+ msgid "New Review"
504
+ msgstr ""
505
+
506
+ #: plugin/Strings.php:24
507
+ msgid "No Reviews found"
508
+ msgstr ""
509
+
510
+ #: plugin/Strings.php:25
511
+ msgid "No Reviews found in Trash"
512
+ msgstr ""
513
+
514
+ #: plugin/Strings.php:26
515
+ msgid "Search Reviews"
516
+ msgstr ""
517
+
518
+ #: plugin/Strings.php:27
519
+ msgid "Uploaded to this review"
520
+ msgstr ""
521
+
522
+ #: plugin/Strings.php:35
523
+ msgid "Review has been approved and published."
524
+ msgstr ""
525
+
526
+ #: plugin/Strings.php:36
527
+ msgid "Review draft updated."
528
+ msgstr ""
529
+
530
+ #: plugin/Strings.php:37
531
+ msgid "Preview review"
532
+ msgstr ""
533
+
534
+ #: plugin/Strings.php:38
535
+ msgid "Review published."
536
+ msgstr ""
537
+
538
+ #: plugin/Strings.php:39
539
+ msgid "Review restored to revision from %s."
540
+ msgstr ""
541
+
542
+ #: plugin/Strings.php:40
543
+ msgid "Review has been reverted to its original submission state."
544
+ msgstr ""
545
+
546
+ #: plugin/Strings.php:41
547
+ msgid "Review saved."
548
+ msgstr ""
549
+
550
+ #: plugin/Strings.php:42
551
+ msgid "Review scheduled for: %s."
552
+ msgstr ""
553
+
554
+ #: plugin/Strings.php:43
555
+ msgid "Review submitted."
556
+ msgstr ""
557
+
558
+ #: plugin/Strings.php:44
559
+ msgid "Review has been unapproved and is now pending."
560
+ msgstr ""
561
+
562
+ #: plugin/Strings.php:45
563
+ msgid "Review updated."
564
+ msgstr ""
565
+
566
+ #: plugin/Strings.php:46
567
+ msgid "View review"
568
+ msgstr ""
569
+
570
+ #: plugin/Strings.php:53
571
+ msgctxt ":attribute is a placeholder and should not be translated."
572
+ msgid "The :attribute must be accepted."
573
+ msgstr ""
574
+
575
+ #: plugin/Strings.php:54
576
+ msgctxt ":attribute, :min, and :max are placeholders and should not be translated."
577
+ msgid "The :attribute must be between :min and :max."
578
+ msgstr ""
579
+
580
+ #: plugin/Strings.php:55
581
+ msgctxt ":attribute, :min, and :max are placeholders and should not be translated."
582
+ msgid "The :attribute must be between :min and :max characters."
583
+ msgstr ""
584
+
585
+ #: plugin/Strings.php:56
586
+ msgctxt ":attribute is a placeholder and should not be translated."
587
+ msgid "The :attribute must be a valid email address."
588
+ msgstr ""
589
+
590
+ #: plugin/Strings.php:57
591
+ msgctxt ":attribute and :max are placeholders and should not be translated."
592
+ msgid "The :attribute may not be greater than :max."
593
+ msgstr ""
594
+
595
+ #: plugin/Strings.php:58
596
+ msgctxt ":attribute and :max are placeholders and should not be translated."
597
+ msgid "The :attribute may not be greater than :max characters."
598
+ msgstr ""
599
+
600
+ #: plugin/Strings.php:59
601
+ msgctxt ":attribute and :min are placeholders and should not be translated."
602
+ msgid "The :attribute must be at least :min."
603
+ msgstr ""
604
+
605
+ #: plugin/Strings.php:60
606
+ msgctxt ":attribute and :min are placeholders and should not be translated."
607
+ msgid "The :attribute must be at least :min characters."
608
+ msgstr ""
609
+
610
+ #: plugin/Strings.php:61
611
+ msgctxt ":attribute is a placeholder and should not be translated."
612
+ msgid "The :attribute format is invalid."
613
+ msgstr ""
614
+
615
+ #: plugin/Strings.php:62
616
+ msgctxt ":attribute is a placeholder and should not be translated."
617
+ msgid "The :attribute field is required."
618
+ msgstr ""
619
+
620
+ #: plugin/Widgets/RecentReviews.php:58
621
+ msgid "Display only title"
622
+ msgstr ""
623
+
624
+ #: plugin/Widgets/RecentReviews.php:59
625
+ msgid "Display only excerpt"
626
+ msgstr ""
627
+
628
+ #: plugin/Widgets/RecentReviews.php:60
629
+ msgid "Display title and excerpt"
630
+ msgstr ""
631
+
632
+ #: plugin/Widgets/RecentReviews.php:70
633
+ msgid "Which reviews would you like to display? "
634
+ msgstr ""
635
+
636
+ #: plugin/Widgets/RecentReviews.php:79
637
+ msgid "What is the minimum rating to display? "
638
+ msgstr ""
639
+
640
+ #: plugin/Widgets/RecentReviews.php:82
641
+ msgid "5 stars"
642
+ msgstr ""
643
+
644
+ #: plugin/Widgets/RecentReviews.php:83
645
+ msgid "4 stars"
646
+ msgstr ""
647
+
648
+ #: plugin/Widgets/RecentReviews.php:84
649
+ msgid "3 stars"
650
+ msgstr ""
651
+
652
+ #: plugin/Widgets/RecentReviews.php:85
653
+ msgid "2 stars"
654
+ msgstr ""
655
+
656
+ #: plugin/Widgets/RecentReviews.php:86
657
+ msgid "1 star"
658
+ msgstr ""
659
+
660
+ #: plugin/Widgets/RecentReviews.php:93
661
+ msgid "How many reviews would you like to display? "
662
+ msgstr ""
663
+
664
+ #: plugin/Widgets/RecentReviews.php:104
665
+ msgid "Show the name of the reviewer?"
666
+ msgstr ""
667
+
668
+ #: plugin/Widgets/RecentReviews.php:105
669
+ msgid "Show the review date?"
670
+ msgstr ""
671
+
672
+ #: plugin/Widgets/RecentReviews.php:106
673
+ msgid "Show the review rating?"
674
+ msgstr ""
675
+
676
+ #: plugin/Widgets/RecentReviews.php:113, plugin/Widgets/ReviewsForm.php:67
677
+ msgid "Enter any custom CSS classes here"
678
+ msgstr ""
679
+
680
+ #: plugin/Widgets/ReviewsForm.php:30
681
+ msgid "Your email address will not be published. Required fields are marked %s*%s"
682
+ msgstr ""
683
+
684
+ #: plugin/Widgets/ReviewsForm.php:48
685
+ msgid "Description"
686
+ msgstr ""
687
+
688
+ #: plugin/Widgets/ReviewsForm.php:57
689
+ msgid "Hide the title field"
690
+ msgstr ""
691
+
692
+ #: plugin/Widgets/ReviewsForm.php:58
693
+ msgid "Hide the reviewer field"
694
+ msgstr ""
695
+
696
+ #: plugin/Widgets/ReviewsForm.php:59
697
+ msgid "Hide the email field"
698
+ msgstr ""
699
+
700
+ #: plugin/Widgets/ReviewsForm.php:60
701
+ msgid "Hide the terms field"
702
+ msgstr ""
703
+
704
+ #: views/edit/meta.php:27
705
+ msgid "Nothing to Revert"
706
+ msgstr ""
707
+
708
+ #: views/edit/meta.php:28
709
+ msgid "Revert Changes"
710
+ msgstr ""
711
+
712
+ #: views/edit/meta.php:54
713
+ msgid "Avatar"
714
+ msgstr ""
715
+
716
+ #: views/edit/pinned.php:21
717
+ msgid "Edit"
718
+ msgstr ""
719
+
720
+ #: views/edit/pinned.php:22
721
+ msgid "Edit pinned status"
722
+ msgstr ""
723
+
724
+ #: views/edit/pinned.php:30
725
+ msgid "Pin"
726
+ msgstr ""
727
+
728
+ #: views/edit/pinned.php:31
729
+ msgid "Unpin"
730
+ msgstr ""
731
+
732
+ #: views/edit/pinned.php:34
733
+ msgid "OK"
734
+ msgstr ""
735
+
736
+ #: views/edit/pinned.php:35
737
+ msgid "Cancel"
738
+ msgstr ""
739
+
740
+ #: views/edit/review.php:4
741
+ msgid "no title"
742
+ msgstr ""
743
+
744
+ #: views/menu/addons/addons.php:3
745
+ msgid "The following Add-Ons extend the functionality of Site Reviews."
746
+ msgstr ""
747
+
748
+ #: views/menu/addons/addons.php:12
749
+ msgid "Sync your Tripadvisor business reviews with an optional minimum rating and display them on your site."
750
+ msgstr ""
751
+
752
+ #: views/menu/addons/addons.php:19
753
+ msgid "Sync your Yelp business reviews with an optional minimum rating and display them on your site."
754
+ msgstr ""
755
+
756
+ #: views/menu/help/system.php:9
757
+ msgid "Download System Info"
758
+ msgstr ""
759
+
760
+ #: views/menu/help/system.php:14
761
+ msgid "Logging"
762
+ msgstr ""
763
+
764
+ #: views/menu/help/system.php:15
765
+ msgid "You can enable logging for debugging purposes. Log files can grow quickly, so don't leave this on."
766
+ msgstr ""
767
+
768
+ #: views/menu/help/system.php:26
769
+ msgid "Clear Log"
770
+ msgstr ""
771
+
772
+ #: views/menu/help/system.php:32
773
+ msgid "Download Log"
774
+ msgstr ""
775
+
776
+ #: views/menu/help/system.php:44
777
+ msgid "Disable Logging"
778
+ msgstr ""
779
+
780
+ #: views/menu/help/system.php:48
781
+ msgid "Enable Logging"
782
+ msgstr ""
783
+
784
+ #: views/menu/status.php:7
785
+ msgid "All dates and times shown here use the WordPress <a href='%s'>configured timezone</a>."
786
+ msgstr ""
787
+
788
+ #: views/menu/status.php:16
789
+ msgid "Site"
790
+ msgstr ""
791
+
792
+ #: views/menu/status.php:18, views/menu/status.php:35
793
+ msgid "Last fetch"
794
+ msgstr ""
795
+
796
+ #: views/menu/status.php:19, views/menu/status.php:38
797
+ msgid "Next scheduled fetch"
798
+ msgstr ""
799
+
800
+ #: views/menu/status.php:36
801
+ msgid "No fetch has been completed"
802
+ msgstr ""
803
+
804
+ #: views/menu/status.php:39
805
+ msgid "Nothing currently scheduled"
806
+ msgstr ""
807
+
808
+ #: views/menu/status.php:59
809
+ msgid "Fetch reviews from"
810
+ msgstr ""
811
+
812
+ #: views/menu/status.php:66
813
+ msgid "Fetch status"
814
+ msgstr ""
815
+
816
+ #: views/menu/status.php:67
817
+ msgid "Fetching reviews..."
818
+ msgstr ""
819
+
820
+ #: views/menu/status.php:79
821
+ msgid "Fetch Reviews"
822
+ msgstr ""
823
+
824
+ #: views/submit/index.php:17
825
+ msgid "Select a Rating"
826
+ msgstr ""
827
+
828
+ #: views/submit/index.php:18
829
+ msgid "Excellent"
830
+ msgstr ""
831
+
832
+ #: views/submit/index.php:19
833
+ msgid "Very Good"
834
+ msgstr ""
835
+
836
+ #: views/submit/index.php:20
837
+ msgid "Average"
838
+ msgstr ""
839
+
840
+ #: views/submit/index.php:21
841
+ msgid "Poor"
842
+ msgstr ""
843
+
844
+ #: views/submit/index.php:22
845
+ msgid "Terrible"
846
+ msgstr ""
847
+
848
+ #: views/submit/index.php:121
849
+ msgid "Submit your review"
850
+ msgstr ""
license.txt ADDED
@@ -0,0 +1,2 @@
 
 
1
+ GPLv2 or later
2
+ https://www.gnu.org/licenses/gpl-2.0.html
plugin/App.php ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\Container;
14
+
15
+ final class App extends Container
16
+ {
17
+ public $defaults;
18
+ public $file;
19
+ public $id;
20
+ public $name;
21
+ public $path;
22
+ public $prefix;
23
+ public $url;
24
+ public $version;
25
+
26
+ public function __construct()
27
+ {
28
+ // hardcoded path to the plugin file
29
+ $file = realpath( dirname( __DIR__ ) . '/site-reviews.php' );
30
+
31
+ $data = [
32
+ 'id' => 'Text Domain',
33
+ 'name' => 'Plugin Name',
34
+ 'version' => 'Version',
35
+ ];
36
+
37
+ $plugin = get_file_data( $file, $data, 'plugin' );
38
+
39
+ $this->id = 'geminilabs-' . $plugin['id'];
40
+ $this->file = $file;
41
+ $this->name = $plugin['name'];
42
+ $this->path = plugin_dir_path( $file );
43
+ $this->prefix = str_replace( '-', '_', $this->id );
44
+ $this->url = plugin_dir_url( $file );
45
+ $this->version = $plugin['version'];
46
+ }
47
+
48
+ /**
49
+ * This is the Application entry point
50
+ *
51
+ * @return void
52
+ */
53
+ public function init()
54
+ {
55
+ $basename = plugin_basename( $this->file );
56
+
57
+ $controller = $this->make( 'Controllers\MainController' );
58
+ $router = $this->make( 'Router' );
59
+
60
+ // Action Hooks
61
+ add_action( 'plugins_loaded', [ $this, 'registerAddons'] );
62
+ add_action( 'admin_enqueue_scripts', [ $controller, 'enqueueAssets'] );
63
+ add_action( 'wp_enqueue_scripts', [ $controller, 'enqueueAssets'] );
64
+ add_action( 'admin_menu', [ $controller, 'registerMenuCount'] );
65
+ add_action( 'add_meta_boxes_site-review', [ $controller, 'registerMetaBox'] );
66
+ add_action( 'admin_enqueue_scripts', [ $controller, 'registerPointers'], 13 );
67
+ add_action( 'init', [ $controller, 'registerPostType'] );
68
+ add_action( 'admin_init', [ $controller, 'registerSettings'] );
69
+ add_action( 'init', [ $controller, 'registerShortcodes'] );
70
+ add_action( 'admin_menu', [ $controller, 'registerSubMenus'] );
71
+ add_action( 'init', [ $controller, 'registerTextdomain'] );
72
+ add_action( 'widgets_init', [ $controller, 'registerWidgets'] );
73
+ add_action( 'post_submitbox_misc_actions', [ $controller, 'renderMetaBoxPinned'] );
74
+ add_action( 'edit_form_after_title', [ $controller, 'renderReview'] );
75
+ add_action( 'edit_form_top', [ $controller, 'renderReviewNotice'] );
76
+ add_action( "wp_ajax_{$this->prefix}_action", [ $router, 'routeAjaxRequests'] );
77
+ add_action( "wp_ajax_nopriv_{$this->prefix}_action", [ $router, 'routeAjaxRequests'] );
78
+ add_action( 'admin_init', [ $router, 'routePostRequests'] );
79
+ add_action( 'admin_init', [ $router, 'routeWebhookRequests'] );
80
+
81
+ // Filter Hooks
82
+ add_filter( "plugin_action_links_{$basename}", [ $controller, 'registerActionLinks'] );
83
+ add_filter( 'dashboard_glance_items', [ $controller, 'registerDashboardGlanceItems'] );
84
+ add_filter( 'post_row_actions', [ $controller, 'registerRowActions'], 10, 2 );
85
+ }
86
+
87
+ /**
88
+ * Runs on plugin activation
89
+ *
90
+ * @return void
91
+ */
92
+ public function activate()
93
+ {
94
+ $current_version = get_option( "{$this->prefix}_version" );
95
+
96
+ if( $current_version ) {
97
+ update_option( "{$this->prefix}_version_upgraded_from", $current_version );
98
+ }
99
+
100
+ update_option( "{$this->prefix}_logging", 0 );
101
+ update_option( "{$this->prefix}_version", $this->version );
102
+
103
+ $this->make( 'Database' )->setDefaultSettings();
104
+
105
+ // Schedule session purge
106
+ if( !wp_next_scheduled( 'site-reviews/schedule/session/purge' ) ) {
107
+ wp_schedule_event( time(), 'twicedaily', 'site-reviews/schedule/session/purge' );
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Runs on plugin deactivation
113
+ *
114
+ * @return void
115
+ */
116
+ public function deactivate()
117
+ {
118
+ $events = ['site-reviews/schedule/session/purge'];
119
+
120
+ foreach( $events as $event ) {
121
+ wp_unschedule_event( wp_next_scheduled( $event ), $event );
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get the default settings
127
+ *
128
+ * @return array
129
+ */
130
+ public function getDefaults()
131
+ {
132
+ if( !$this->defaults ) {
133
+ $this->defaults = $this->make( 'Settings' )->getSettings();
134
+
135
+ // Allow addons to modify the default settings
136
+ $this->defaults = apply_filters( 'site-reviews/addon/defaults', $this->defaults );
137
+ }
138
+
139
+ return $this->defaults;
140
+ }
141
+
142
+ /**
143
+ * Register available add-ons
144
+ *
145
+ * @return void
146
+ */
147
+ public function registerAddons()
148
+ {
149
+ do_action( 'site-reviews/addon/register', $this );
150
+ }
151
+
152
+ /**
153
+ * Verify permissions
154
+ *
155
+ * @return bool
156
+ */
157
+ public function verify()
158
+ {
159
+ return current_user_can( 'customize' );
160
+ }
161
+ }
plugin/Commands/EnqueueAssets.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Commands;
12
+
13
+ class EnqueueAssets
14
+ {
15
+ public $handle;
16
+ public $path;
17
+ public $url;
18
+ public $version;
19
+
20
+ public function __construct( $input )
21
+ {
22
+ $this->handle = $input['handle'];
23
+ $this->url = $input['url'];
24
+ $this->path = $input['path'];
25
+ $this->version = $input['version'];
26
+ }
27
+ }
plugin/Commands/RegisterPointers.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * =
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Commands;
14
+
15
+ class RegisterPointers
16
+ {
17
+ public $pointers;
18
+
19
+ public function __construct( $input )
20
+ {
21
+ $this->pointers = $input;
22
+ }
23
+ }
plugin/Commands/RegisterPostType.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Commands;
12
+
13
+ class RegisterPostType
14
+ {
15
+ public $args;
16
+
17
+ public function __construct( $input )
18
+ {
19
+ $columns = [
20
+ 'title' => __( 'Title', 'site-reviews' ),
21
+ 'date' => __( 'Date', 'site-reviews' ),
22
+ ];
23
+
24
+ $defaults = [
25
+ 'capability_type' => '',
26
+ 'columns' => $columns,
27
+ 'has_archive' => false,
28
+ 'hierarchical' => false,
29
+ 'map_meta_cap' => true,
30
+ 'menu_icon' => null,
31
+ 'menu_name' => '',
32
+ 'menu_position' => 25,
33
+ 'public' => true,
34
+ 'query_var' => true,
35
+ 'rewrite' => ['slug' => $input['slug'], 'with_front' => false ],
36
+ 'show_in_menu' => true, //'edit.php?post_type=post'
37
+ 'supports' => ['title', 'editor'],
38
+ 'taxonomies' => [],
39
+ ];
40
+
41
+ $this->args = wp_parse_args( $input, $defaults );
42
+ }
43
+ }
plugin/Commands/RegisterShortcodes.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * =
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Commands;
14
+
15
+ class RegisterShortcodes
16
+ {
17
+ public $shortcodes;
18
+
19
+ public function __construct( $input )
20
+ {
21
+ $this->shortcodes = $input;
22
+ }
23
+ }
plugin/Commands/RegisterWidgets.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Commands;
12
+
13
+ class RegisterWidgets
14
+ {
15
+ public $widgets;
16
+
17
+ public function __construct( $input )
18
+ {
19
+ $this->widgets = (object) $input;
20
+ }
21
+ }
plugin/Commands/SubmitReview.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Commands;
12
+
13
+ class SubmitReview
14
+ {
15
+ public $ajaxRequest;
16
+ public $content;
17
+ public $email;
18
+ public $formId;
19
+ public $ipAddress;
20
+ public $rating;
21
+ public $reviewer;
22
+ public $terms;
23
+ public $title;
24
+
25
+ public function __construct( $input )
26
+ {
27
+ $this->ajaxRequest = isset( $input['ajax_request'] ) ? true : false;
28
+ $this->content = $input['content'];
29
+ $this->email = $input['email'];
30
+ $this->formId = $input['form_id'];
31
+ $this->ipAddress = $this->getIpAddress();
32
+ $this->rating = $input['rating'];
33
+ $this->reviewer = $input['reviewer'];
34
+ $this->title = $input['title'];
35
+ $this->terms = isset( $input['terms'] ) ? true : false;
36
+ }
37
+
38
+ /**
39
+ * Get the IP address and domain of the reviewer
40
+ *
41
+ * @return string|null
42
+ */
43
+ protected function getIpAddress()
44
+ {
45
+ $ipAddress = filter_input( INPUT_SERVER, 'REMOTE_ADDR' );
46
+
47
+ if( $ipAddress ) {
48
+ $ipAddress .= ', ' . @gethostbyaddr( $ipAddress );
49
+ }
50
+
51
+ return $ipAddress;
52
+ }
53
+ }
plugin/Commands/TogglePinned.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Commands;
12
+
13
+ class TogglePinned
14
+ {
15
+ public $id;
16
+ public $pinned;
17
+
18
+ public function __construct( $input )
19
+ {
20
+ $pinned = isset( $input['pinned'] )
21
+ ? wp_validate_boolean( $input['pinned'] )
22
+ : null;
23
+
24
+ $this->id = $input['id'];
25
+ $this->pinned = $pinned;
26
+ }
27
+ }
plugin/Container.php ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\Providers\ProviderInterface;
15
+ use ReflectionClass;
16
+
17
+ abstract class Container
18
+ {
19
+ /**
20
+ * The current globally available container (if any).
21
+ *
22
+ * @var static
23
+ */
24
+ protected static $instance;
25
+
26
+ protected $services = [];
27
+
28
+ /**
29
+ * Set/get the globally available instance of the container.
30
+ *
31
+ * @return static
32
+ */
33
+ public static function load()
34
+ {
35
+ if( is_null( static::$instance ) ) {
36
+ static::$instance = new static;
37
+ }
38
+
39
+ return static::$instance;
40
+ }
41
+
42
+ /**
43
+ * This is the Application entry point
44
+ *
45
+ * @return void
46
+ */
47
+ abstract public function init();
48
+
49
+ /**
50
+ *
51
+ * @param string $service
52
+ *
53
+ * @return mixed
54
+ */
55
+ public function __get( $service )
56
+ {
57
+ if( !isset( $this->services[ $service ] ) ) {
58
+ return null;
59
+ }
60
+
61
+ if( !is_callable( $this->services[ $service ] ) ) {
62
+ return $this->services[ $service ];
63
+ }
64
+
65
+ return $this->services[ $service ]( $this );
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param string $service
71
+ * @param string $callback
72
+ *
73
+ * @return void
74
+ */
75
+ public function __set( $service, $callback )
76
+ {
77
+ $this->services[ $service ] = $callback;
78
+ }
79
+
80
+ /**
81
+ * Register a Provider.
82
+ *
83
+ * @param ProviderInterface $provider
84
+ *
85
+ * @return void
86
+ */
87
+ public function register( ProviderInterface $provider )
88
+ {
89
+ $provider->register( $this );
90
+ }
91
+
92
+ /**
93
+ * Bind a singleton instance to the container.
94
+ *
95
+ * @param string $alias
96
+ * @param string $binding
97
+ *
98
+ * @return void
99
+ */
100
+ public function singleton( $alias, $binding )
101
+ {
102
+ $this->bind( $alias, $this->make( $binding ) );
103
+ }
104
+
105
+ /**
106
+ * Bind a listener to an event.
107
+ *
108
+ * @param string $tag
109
+ * @param string $listener
110
+ * @param int $priority
111
+ * @param int $acceptedArgs
112
+ *
113
+ * @return void
114
+ */
115
+ public function addListener( $tag, $listener, $priority = 10, $acceptedArgs = 1 )
116
+ {
117
+ $reviews = $this;
118
+
119
+ add_action( $tag, function( $event ) use ( $listener, $reviews ) {
120
+ $reviews->make( $listener )->handle( $event );
121
+ }, $priority, $acceptedArgs );
122
+ }
123
+
124
+ /**
125
+ * Bind a service to the container.
126
+ *
127
+ * @param string $alias
128
+ * @param string $concrete
129
+ *
130
+ * @return mixed
131
+ */
132
+ public function bind( $alias, $concrete )
133
+ {
134
+ $this->services[ $alias ] = $concrete;
135
+ }
136
+
137
+ /**
138
+ * Request a service from the container.
139
+ *
140
+ * @param string $alias
141
+ * @param bool|string $prefixed
142
+ *
143
+ * @return mixed
144
+ */
145
+ public function make( $alias, $prefixed = false )
146
+ {
147
+ if( isset( $this->services[ $alias ] ) && is_callable( $this->services[ $alias ] ) ) {
148
+ return call_user_func_array( $this->services[ $alias ], [ $this ] );
149
+ }
150
+
151
+ if( isset( $this->services[ $alias ] ) && is_object( $this->services[ $alias ] ) ) {
152
+ return $this->services[ $alias ];
153
+ }
154
+
155
+ if( isset( $this->services[ $alias ] ) && class_exists( $this->services[ $alias ] ) ) {
156
+ return $this->resolve( $this->services[ $alias ] );
157
+ }
158
+
159
+ // Allow unbound aliases that omit the root namespace
160
+ // i.e. 'Html\Field' translates to 'GeminiLabs\SiteReviews\Html\Field'
161
+ if( $prefixed === false && strpos( $alias, __NAMESPACE__ ) === false && !class_exists( $alias ) ) {
162
+ return $this->make( __NAMESPACE__ . "\\$alias" , 'prefix alias with the namespace' );
163
+ }
164
+
165
+ return $this->resolve( $alias );
166
+ }
167
+
168
+ /**
169
+ * Resolve class dependencies automatically
170
+ *
171
+ * @param string $class
172
+ *
173
+ * @return mixed
174
+ */
175
+ private function resolve( $class )
176
+ {
177
+ $reflector = new ReflectionClass( $class );
178
+
179
+ // If the type is not instantiable (i.e. interface/abstract class) then bail.
180
+ if( !$reflector->isInstantiable() ) {
181
+ $message = "Target [$class] is not instantiable.";
182
+
183
+ throw new Exception( $message );
184
+ }
185
+
186
+ $constructor = $reflector->getConstructor();
187
+
188
+ // No constructor means no dependencies so we can just resolve the instance right away.
189
+ if( is_null( $constructor ) ) {
190
+ return new $class;
191
+ }
192
+
193
+ $parameters = $constructor->getParameters();
194
+
195
+ $newInstanceParameters = [];
196
+
197
+ foreach( $parameters as $parameter ) {
198
+
199
+ // Allow array parameters if they have a default value
200
+ if( $parameter->isArray() && $parameter->isDefaultValueAvailable() ) {
201
+ $newInstanceParameters[] = $parameter->getDefaultValue();
202
+ continue;
203
+ }
204
+
205
+ // This will throw a TypeError if parameter is not a class
206
+ if( is_null( $parameter->getClass() ) ) {
207
+ $newInstanceParameters[] = null;
208
+ continue;
209
+ }
210
+
211
+ $newInstanceParameters[] = $this->make(
212
+ $parameter->getClass()->getName()
213
+ );
214
+ }
215
+
216
+ return $reflector->newInstanceArgs( $newInstanceParameters );
217
+ }
218
+ }
plugin/Controllers/AjaxController.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Controllers;
12
+
13
+ use GeminiLabs\SiteReviews\Controllers\BaseController;
14
+ use GeminiLabs\SiteReviews\Commands\TogglePinned;
15
+
16
+ class AjaxController extends BaseController
17
+ {
18
+ /**
19
+ * Clears the log
20
+ */
21
+ public function ajaxClearLog()
22
+ {
23
+ $this->app->make( 'Controllers\MainController' )->postClearLog();
24
+
25
+ wp_send_json([
26
+ 'log' => __( 'Log is empty', 'site-reviews' ),
27
+ 'notices' => $this->notices->show( false ),
28
+ ]);
29
+ }
30
+
31
+ /**
32
+ * Toggle the pinned status of a review
33
+ */
34
+ public function ajaxTogglePinned( $request )
35
+ {
36
+ $response = $this->execute( new TogglePinned( $request ) );
37
+
38
+ wp_send_json([
39
+ 'notices' => $this->notices->show( false ),
40
+ 'pinned' => (bool) $response,
41
+ ]);
42
+ }
43
+
44
+ /**
45
+ * Submit a review
46
+ */
47
+ public function ajaxPostReview( $request )
48
+ {
49
+ $response = $this->app->make( 'Controllers\ReviewController' )->postSubmitReview( $request );
50
+ $errors = $this->app->make( 'Session' )->get( "{$request['form_id']}-errors", false, 'clear errors' );
51
+
52
+ wp_send_json([
53
+ 'errors' => $errors,
54
+ 'message' => $response,
55
+ ]);
56
+ }
57
+ }
plugin/Controllers/BaseController.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Controllers;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+ use InvalidArgumentException;
16
+ use WP_Error;
17
+
18
+ abstract class BaseController
19
+ {
20
+ /**
21
+ * @var App
22
+ */
23
+ protected $app;
24
+
25
+ /**
26
+ * @var Database
27
+ */
28
+ protected $db;
29
+
30
+ /**
31
+ * @var Html
32
+ */
33
+ protected $html;
34
+
35
+ /**
36
+ * @var Logger
37
+ */
38
+ protected $log;
39
+
40
+ /**
41
+ * @var Notices
42
+ */
43
+ protected $notices;
44
+
45
+ public function __construct( App $app )
46
+ {
47
+ $this->app = $app;
48
+ $this->db = $app->make( 'Database' );
49
+ $this->html = $app->make( 'Html' );
50
+ $this->log = $app->make( 'Log\Logger' );
51
+ $this->notices = $app->make( 'Notices' );
52
+ }
53
+
54
+ /**
55
+ * Send a command to its handler and execute it.
56
+ *
57
+ * @param object $command
58
+ *
59
+ * @return void
60
+ */
61
+ public function execute( $command )
62
+ {
63
+ $handlerClass = str_replace( 'Commands', 'Handlers', get_class( $command ) );
64
+
65
+ if( !class_exists( $handlerClass ) ) {
66
+ throw new InvalidArgumentException( "Handler {$handlerClass} doesn't exist.");
67
+ }
68
+
69
+ $handler = $this->app->make( $handlerClass );
70
+
71
+ try {
72
+ return $handler->handle( $command );
73
+ }
74
+ catch( Exception $e ) {
75
+ status_header( 400 );
76
+ $this->notices->addError( new WP_Error( 'reviews_error', $e->getMessage() ) );
77
+ $this->log->error( $e->getMessage() );
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Render a view and pass any provided data to the view
83
+ *
84
+ * @param string $view
85
+ *
86
+ * @return void
87
+ */
88
+ public function render( $view, array $data = [] )
89
+ {
90
+ $data['db'] = $this->db;
91
+ $data['html'] = $this->html; // singleton
92
+ $data['log'] = $this->log;
93
+ $data['notices'] = $this->notices;
94
+
95
+ extract( $data );
96
+
97
+ return include $this->app->path . 'views/base.php';
98
+ }
99
+
100
+ /**
101
+ * Validate form data
102
+ *
103
+ * @return bool
104
+ */
105
+ public function validate( array $request, array $rules = [] )
106
+ {
107
+ $session = $this->app->make( 'Session' ); // singleton
108
+ $validator = $this->app->make( 'Validator' );
109
+
110
+ $errors = $validator->validate( $request, $rules );
111
+
112
+ if( empty( $errors ) ) {
113
+ return true;
114
+ }
115
+
116
+ $session->set( "{$request['form_id']}-errors", $errors );
117
+ $session->set( "{$request['form_id']}-values", $request );
118
+
119
+ return false;
120
+ }
121
+ }
plugin/Controllers/MainController.php ADDED
@@ -0,0 +1,574 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Controllers;
12
+
13
+ use GeminiLabs\SiteReviews\Commands\EnqueueAssets;
14
+ use GeminiLabs\SiteReviews\Commands\RegisterPointers;
15
+ use GeminiLabs\SiteReviews\Commands\RegisterPostType;
16
+ use GeminiLabs\SiteReviews\Commands\RegisterShortcodes;
17
+ use GeminiLabs\SiteReviews\Commands\RegisterWidgets;
18
+ use GeminiLabs\SiteReviews\Commands\SubmitReview;
19
+ use GeminiLabs\SiteReviews\Controllers\BaseController;
20
+ use GeminiLabs\SiteReviews\Strings;
21
+ use WP_Admin_Bar;
22
+ use WP_Post;
23
+
24
+ class MainController extends BaseController
25
+ {
26
+ /**
27
+ * @return void
28
+ *
29
+ * @action wp_enqueue_scripts
30
+ */
31
+ public function enqueueAssets()
32
+ {
33
+ $command = new EnqueueAssets([
34
+ 'handle' => $this->app->id,
35
+ 'path' => $this->app->path . 'assets/',
36
+ 'url' => $this->app->url . 'assets/',
37
+ 'version' => $this->app->version,
38
+ ]);
39
+
40
+ $this->execute( $command );
41
+ }
42
+
43
+ /**
44
+ * Clears the log
45
+ *
46
+ * @return void
47
+ */
48
+ public function postClearLog()
49
+ {
50
+ $this->log->clear();
51
+ $this->notices->addSuccess( __( 'Log was cleared.', 'site-reviews' ) );
52
+ }
53
+
54
+ /**
55
+ * Downloads the log
56
+ *
57
+ * @return void
58
+ */
59
+ public function postDownloadLog()
60
+ {
61
+ $this->log->download();
62
+ }
63
+
64
+ /**
65
+ * Downloads the system info
66
+ *
67
+ * @param string $system_info
68
+ *
69
+ * @return void
70
+ */
71
+ public function postDownloadSystemInfo( $system_info )
72
+ {
73
+ $this->app->make( 'SystemInfo' )->download( $system_info );
74
+ }
75
+
76
+ /**
77
+ * Registers the plugin action links on the plugins page
78
+ *
79
+ * @return array
80
+ *
81
+ * @filter plugin_action_links_reviews/reviews.php
82
+ */
83
+ public function registerActionLinks( array $links )
84
+ {
85
+ $settings_url = admin_url( 'edit.php?post_type=site-review&page=settings' );
86
+
87
+ $links[] = sprintf( '<a href="%s">%s</a>', $settings_url, __( 'Settings', 'site-reviews' ) );
88
+
89
+ return $links;
90
+ }
91
+
92
+ /**
93
+ * Adds the reviews count to the "At a Glance" Dashboard widget
94
+ *
95
+ * @return array
96
+ *
97
+ * @filter dashboard_glance_items
98
+ */
99
+ public function registerDashboardGlanceItems( array $items )
100
+ {
101
+ $post_type = 'site-review';
102
+ $num_posts = wp_count_posts( $post_type );
103
+
104
+ if( $num_posts && $num_posts->publish ) {
105
+
106
+ $text = _n( '%s Review', '%s Reviews', $num_posts->publish, 'site-reviews' );
107
+ $text = sprintf( $text, number_format_i18n( $num_posts->publish ) );
108
+
109
+ $post_type_object = get_post_type_object( $post_type );
110
+
111
+ $items[] = $post_type_object && current_user_can( $post_type_object->cap->edit_posts )
112
+ ? sprintf( '<a class="glsr-review-count" href="edit.php?post_type=%s">%s</a>', $post_type, $text )
113
+ : sprintf( '<span class="glsr-review-count">%s</span>', $text );
114
+ }
115
+
116
+ return $items;
117
+ }
118
+
119
+ /**
120
+ * @return void
121
+ *
122
+ * @action admin_menu
123
+ */
124
+ public function registerMenuCount()
125
+ {
126
+ global $menu, $typenow;
127
+
128
+ $post_type = 'site-review';
129
+
130
+ foreach( $menu as $key => $value ) {
131
+ if( !( isset( $value[2] ) && $value[2] === "edit.php?post_type={$post_type}" ) )continue;
132
+
133
+ $awaiting_mod = wp_count_posts( $post_type );
134
+ $awaiting_mod = $awaiting_mod->pending;
135
+
136
+ $menu[ $key ][0] .= sprintf( ' <span class="awaiting-mod count-%d"><span class="pending-count">%s</span></span>',
137
+ absint( $awaiting_mod ),
138
+ number_format_i18n( $awaiting_mod )
139
+ );
140
+
141
+ if( $typenow === $post_type ) {
142
+ $menu[ $key ][4] .= ' current';
143
+ }
144
+
145
+ break;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @return void
151
+ *
152
+ * @action add_meta_boxes_review
153
+ */
154
+ public function registerMetaBox()
155
+ {
156
+ add_meta_box( "{$this->app->id}_review", __( 'Details', 'site-reviews' ), [ $this, 'renderMetaBox'], null, 'side' );
157
+ }
158
+
159
+ /**
160
+ * @return void
161
+ *
162
+ * @action admin_enqueue_scripts
163
+ */
164
+ public function registerPointers()
165
+ {
166
+ $command = new RegisterPointers([[
167
+ 'id' => 'glsr-pointer-pinned',
168
+ 'screen' => 'site-review',
169
+ 'target' => '#misc-pub-pinned',
170
+ 'title' => __( 'Pin Your Reviews', 'site-reviews' ),
171
+ 'content' => __( 'You can pin exceptional reviews so that they are always shown first in your widgets and shortcodes.', 'site-reviews' ),
172
+ 'position' => [
173
+ 'edge' => 'right', // top, bottom, left, right
174
+ 'align' => 'middle', // top, bottom, left, right, middle
175
+ ],
176
+ ]]);
177
+
178
+ $this->execute( $command );
179
+ }
180
+
181
+ /**
182
+ * @return void
183
+ *
184
+ * @action init
185
+ */
186
+ public function registerPostType()
187
+ {
188
+ if( !$this->app->verify() )return;
189
+
190
+ $command = new RegisterPostType([
191
+ 'post_type' => 'site-review',
192
+ 'slug' => 'reviews',
193
+ 'single' => __( 'Review', 'site-reviews' ),
194
+ 'plural' => __( 'Reviews', 'site-reviews' ),
195
+ 'menu_name' => __( 'Site Reviews', 'site-reviews' ),
196
+ 'menu_icon' => 'dashicons-star-half',
197
+ 'public' => false,
198
+ 'has_archive' => false,
199
+ 'show_ui' => false,
200
+ 'labels' => (new Strings)->post_type_labels(),
201
+ 'columns' => [
202
+ 'title' => '', // empty values use the default label
203
+ 'reviewer' => __( 'Reviewer', 'site-reviews' ),
204
+ 'site' => __( 'Type', 'site-reviews' ),
205
+ 'stars' => __( 'Rating', 'site-reviews' ),
206
+ 'sticky' => __( 'Pinned', 'site-reviews' ),
207
+ 'date' => '',
208
+ ],
209
+ ]);
210
+
211
+ $this->execute( $command );
212
+ }
213
+
214
+ /**
215
+ * Add Approve/Unapprove links and remove Quick-edit
216
+ *
217
+ * @return array
218
+ *
219
+ * @filter post_row_actions
220
+ */
221
+ public function registerRowActions( array $actions, WP_Post $post )
222
+ {
223
+ if( $post->post_type !== 'site-review' || $post->post_status === 'trash' ) {
224
+ return $actions;
225
+ }
226
+
227
+ $atts = [
228
+ 'approve' => [
229
+ 'aria-label' => esc_attr__( 'Approve this review', 'site-reviews' ),
230
+ 'href' => wp_nonce_url( admin_url( sprintf( 'post.php?post=%s&action=approve', $post->ID ) ), 'approve-review_' . $post->ID ),
231
+ 'text' => __( 'Approve', 'site-reviews' ),
232
+ ],
233
+ 'unapprove' => [
234
+ 'aria-label' => esc_attr__( 'Unapprove this review', 'site-reviews' ),
235
+ 'href' => wp_nonce_url( admin_url( sprintf( 'post.php?post=%s&action=unapprove', $post->ID ) ), 'unapprove-review_' . $post->ID ),
236
+ 'text' => __( 'Unapprove', 'site-reviews' ),
237
+ ],
238
+ ];
239
+
240
+ $newActions = [];
241
+
242
+ foreach( $atts as $key => $values ) {
243
+ $newActions[ $key ] = sprintf( '<a href="%s" aria-label="%s">%s</a>',
244
+ $values['href'],
245
+ $values['aria-label'],
246
+ $values['text']
247
+ );
248
+ }
249
+
250
+ // Remove Quick-edit
251
+ unset( $actions['inline hide-if-no-js'] );
252
+
253
+ return $newActions + $actions;
254
+ }
255
+
256
+ /**
257
+ * @return void
258
+ *
259
+ * @action admin_init
260
+ */
261
+ public function registerSettings()
262
+ {
263
+ register_setting( "{$this->app->id}-settings", "{$this->app->prefix}_settings", [ $this, 'sanitizeSettings'] );
264
+ register_setting( "{$this->app->id}-logging", "{$this->app->prefix}_logging", [ $this, 'sanitizeLogging'] );
265
+
266
+ // register the settings form fields
267
+ $this->app->make( 'Settings' )->register();
268
+ }
269
+
270
+ /**
271
+ * @return void
272
+ *
273
+ * @action init
274
+ */
275
+ public function registerShortcodes()
276
+ {
277
+ $command = new RegisterShortcodes([
278
+ 'site_reviews',
279
+ 'site_reviews_form',
280
+ ]);
281
+
282
+ $this->execute( $command );
283
+ }
284
+
285
+ /**
286
+ * @return void
287
+ *
288
+ * @action admin_menu
289
+ */
290
+ public function registerSubMenus()
291
+ {
292
+ $pages = [
293
+ 'settings' => __( 'Settings', 'site-reviews' ),
294
+ 'help' => __( 'Get Help', 'site-reviews' ),
295
+ 'addons' => __( 'Add-Ons', 'site-reviews' ),
296
+ ];
297
+
298
+ $pages = apply_filters( 'site-reviews/addon/submenu/pages', $pages );
299
+
300
+ foreach( $pages as $slug => $title ) {
301
+
302
+ $method = sprintf( 'render%sMenu', ucfirst( $slug ) );
303
+
304
+ $callback = apply_filters( 'site-reviews/addon/submenu/callback', [ $this, $method ], $slug );
305
+
306
+ if( !is_callable( $callback ) )continue;
307
+
308
+ add_submenu_page( 'edit.php?post_type=site-review', $title, $title, 'customize', $slug, $callback );
309
+ }
310
+ }
311
+
312
+ /**
313
+ * @return void
314
+ *
315
+ * @action init
316
+ */
317
+ public function registerTextdomain()
318
+ {
319
+ load_plugin_textdomain( $this->app->id, false, "{$this->app->path}languages" );
320
+ }
321
+
322
+ /**
323
+ * @return void
324
+ *
325
+ * @action widgets_init
326
+ */
327
+ public function registerWidgets()
328
+ {
329
+ $command = new RegisterWidgets([
330
+ 'reviews_form' => [
331
+ 'title' => __( 'Submit a Site Review', 'site-reviews' ),
332
+ 'description' => __( 'A "submit a review" form for your site.', 'site-reviews' ),
333
+ 'class' => 'glsr-widget glsr-widget-reviews-form',
334
+ ],
335
+ 'recent_reviews' => [
336
+ 'title' => __( 'Recent Site Reviews', 'site-reviews' ),
337
+ 'description' => __( 'Your site’s most recent Local Reviews.', 'site-reviews' ),
338
+ 'class' => 'glsr-widget glsr-widget-recent-reviews',
339
+ ],
340
+ ]);
341
+
342
+ $this->execute( $command );
343
+ }
344
+
345
+ /**
346
+ * add_submenu_page() callback
347
+ *
348
+ * @return void
349
+ */
350
+ public function renderAddonsMenu()
351
+ {
352
+ $this->renderMenu( 'addons', [
353
+ 'addons' => __( 'Add-Ons', 'site-reviews' ),
354
+ ]);
355
+ }
356
+
357
+ /**
358
+ * add_submenu_page() callback
359
+ *
360
+ * @return void
361
+ */
362
+ public function renderHelpMenu()
363
+ {
364
+ // allow addons to add their own help sections
365
+ $sections = apply_filters( 'site-reviews/addon/documentation/sections', [
366
+ 'support' => 'Support',
367
+ 'shortcodes' => 'Shortcodes',
368
+ 'hooks' => 'Hooks',
369
+ ]);
370
+
371
+ $this->renderMenu( 'help', [
372
+ 'documentation' => [
373
+ 'title' => __( 'Documentation', 'site-reviews' ),
374
+ 'sections' => $sections,
375
+ ],
376
+ 'system' => __( 'System Info', 'site-reviews' ),
377
+ ],[
378
+ 'system_info' => $this->app->make( 'SystemInfo' ),
379
+ ]);
380
+ }
381
+
382
+ /**
383
+ * @return void
384
+ */
385
+ public function renderMenu( $page, $tabs, $data = [] )
386
+ {
387
+ $tabs = $this->normalizeTabs( $tabs );
388
+ $tab = $this->filterTab( $tabs );
389
+ $section = $this->filterSection( $tabs, $tab );
390
+
391
+ $defaults = [
392
+ 'page' => $page,
393
+ 'tabs' => $tabs,
394
+ 'tabView' => $tab,
395
+ 'tabViewSection' => $section,
396
+ ];
397
+
398
+ $data = apply_filters( 'site-reviews/addon/menu/data', $data, $defaults );
399
+
400
+ $this->render( 'menu/index', wp_parse_args( $data, $defaults ) );
401
+ }
402
+
403
+ /**
404
+ * add_meta_box() callback
405
+ *
406
+ * @return void
407
+ */
408
+ public function renderMetaBox( WP_Post $post )
409
+ {
410
+ if( $post->post_type != 'site-review' )return;
411
+
412
+ $this->render( 'edit/meta', ['post' => $post ] );
413
+ }
414
+
415
+ /**
416
+ * @return void
417
+ *
418
+ * @action post_submitbox_misc_actions
419
+ */
420
+ public function renderMetaBoxPinned()
421
+ {
422
+ global $post;
423
+
424
+ if( $post->post_type != 'site-review' )return;
425
+
426
+ $pinned = get_post_meta( $post->ID, 'pinned', true );
427
+
428
+ $this->render( 'edit/pinned', ['pinned' => $pinned ] );
429
+ }
430
+
431
+ /**
432
+ * @return void
433
+ *
434
+ * @action edit_form_after_title
435
+ */
436
+ public function renderReview( WP_Post $post )
437
+ {
438
+ if( $post->post_type != 'site-review' )return;
439
+ if( post_type_supports( 'site-review', 'title' ) )return;
440
+ if( get_post_meta( $post->ID, 'site_name', true ) == 'local' )return;
441
+
442
+ $this->render( 'edit/review', ['post' => $post ] );
443
+ }
444
+
445
+ /**
446
+ * @return void
447
+ *
448
+ * @action edit_form_top
449
+ */
450
+ public function renderReviewNotice( WP_Post $post )
451
+ {
452
+ if( $post->post_type != 'site-review' )return;
453
+ if( post_type_supports( 'site-review', 'title' ) )return;
454
+
455
+ $type = get_post_meta( $post->ID, 'site_name', true );
456
+
457
+ if( $type == 'local' )return;
458
+
459
+ $this->notices->addWarning( sprintf( __( '%s reviews are read-only.', 'site-reviews' ), ucfirst( $type ) ) );
460
+ $this->render( 'edit/notice' );
461
+ }
462
+
463
+ /**
464
+ * add_submenu_page() callback
465
+ *
466
+ * @return void
467
+ */
468
+ public function renderSettingsMenu()
469
+ {
470
+ // allow addons to add their own setting sections
471
+ $sections = apply_filters( 'site-reviews/addon/settings/sections', [
472
+ 'general' => 'General',
473
+ 'form' => 'Submission Form',
474
+ ]);
475
+
476
+ $this->renderMenu( 'settings', [
477
+ 'settings' => [
478
+ 'title' => __( 'Settings', 'site-reviews' ),
479
+ 'sections' => $sections,
480
+ ],
481
+ 'licenses' => __( 'Licenses', 'site-reviews' ),
482
+ ],[
483
+ 'settings' => $this->app->getDefaults(),
484
+ ]);
485
+ }
486
+
487
+ /**
488
+ * register_setting() callback
489
+ *
490
+ * @param int $input
491
+ *
492
+ * @return int
493
+ */
494
+ public function sanitizeLogging( $input )
495
+ {
496
+ $message = $input
497
+ ? __( 'Logging enabled.', 'site-reviews' )
498
+ : __( 'Logging disabled.', 'site-reviews' );
499
+
500
+ $this->notices->addSuccess( $message );
501
+
502
+ return $input;
503
+ }
504
+
505
+ /**
506
+ * register_setting() callback
507
+ *
508
+ * @return array
509
+ */
510
+ public function sanitizeSettings( array $input )
511
+ {
512
+ $settings = $this->db->getOption();
513
+
514
+ $this->notices->addSuccess( __( 'Settings updated.', 'site-reviews' ) );
515
+
516
+ // Merge the settings tab section arrays
517
+ return array_merge( $settings, $input );
518
+ }
519
+
520
+ /**
521
+ * Gets the current menu page tab section
522
+ *
523
+ * @param string $tab
524
+ *
525
+ * @return string
526
+ */
527
+ protected function filterSection( array $tabs, $tab )
528
+ {
529
+ $section = filter_input( INPUT_GET, 'section' );
530
+
531
+ if( !$section || !isset( $tabs[ $tab ]['sections'][ $section ] ) ) {
532
+ $section = isset( $tabs[ $tab ]['sections'] )
533
+ ? key( $tabs[ $tab ]['sections'] )
534
+ : '';
535
+ }
536
+
537
+ return $section;
538
+ }
539
+
540
+ /**
541
+ * Gets the current menu page tab
542
+ *
543
+ * @return string
544
+ */
545
+ protected function filterTab( array $tabs )
546
+ {
547
+ $tab = filter_input( INPUT_GET, 'tab' );
548
+
549
+ if( !$tab || !array_key_exists( $tab, $tabs ) ) {
550
+ $tab = key( $tabs );
551
+ }
552
+
553
+ return $tab;
554
+ }
555
+
556
+ /**
557
+ * Normalize the tabs array
558
+ *
559
+ * @return array
560
+ */
561
+ protected function normalizeTabs( array $tabs )
562
+ {
563
+ foreach( $tabs as $key => $value ) {
564
+ if( !is_array( $value ) ) {
565
+ $tabs[ $key ] = ['title' => $value ];
566
+ }
567
+ if( $key == 'licenses' && !apply_filters( 'site-reviews/addon/licenses', false ) ) {
568
+ unset( $tabs[ $key ] );
569
+ }
570
+ }
571
+
572
+ return $tabs;
573
+ }
574
+ }
plugin/Controllers/ReviewController.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Controllers;
12
+
13
+ use GeminiLabs\SiteReviews\Commands\SubmitReview;
14
+ use GeminiLabs\SiteReviews\Controllers\BaseController;
15
+
16
+ class ReviewController extends BaseController
17
+ {
18
+ /**
19
+ * Submit the review form
20
+ *
21
+ * @return void
22
+ * @throws Exception
23
+ */
24
+ public function postSubmitReview( array $request )
25
+ {
26
+ $minContentLength = apply_filters( 'site-reviews/local/review/content/minLength', '0' );
27
+
28
+ $rules = [
29
+ 'content' => 'required|min:' . $minContentLength,
30
+ 'email' => 'required|email|min:5',
31
+ 'rating' => 'required|numeric|between:1,5',
32
+ 'reviewer' => 'required',
33
+ 'terms' => 'accepted',
34
+ 'title' => 'required',
35
+ ];
36
+
37
+ $excluded = isset( $request['excluded'] )
38
+ ? json_decode( $request['excluded'] )
39
+ : [];
40
+
41
+ // only use the rules for non-excluded values
42
+ $rules = array_diff_key( $rules, array_flip( $excluded ) );
43
+
44
+ $user = wp_get_current_user();
45
+
46
+ $reviewer = $user->exists()
47
+ ? $user->display_name
48
+ : __( 'Anonymous', 'site-reviews' );
49
+
50
+ $defaults = [
51
+ 'content' => '',
52
+ 'email' => '',
53
+ 'rating' => '',
54
+ 'reviewer' => $reviewer,
55
+ 'terms' => '',
56
+ 'title' => __( 'No Title', 'site-reviews' ),
57
+ ];
58
+
59
+ if( !$this->validate( $request, $rules ) ) {
60
+ return __( 'Please fix the submission errors.', 'site-reviews' );
61
+ }
62
+
63
+ // normalize the request array
64
+ $request = array_merge( $defaults, $request );
65
+
66
+ return $this->execute( new SubmitReview( $request ) );
67
+ }
68
+ }
69
+
plugin/Database.php ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use WP_Query;
15
+
16
+ class Database
17
+ {
18
+ /**
19
+ * @var App
20
+ */
21
+ protected $app;
22
+
23
+ /**
24
+ * @var Logger
25
+ */
26
+ protected $log;
27
+
28
+ public function __construct( App $app )
29
+ {
30
+ $this->app = $app;
31
+ $this->log = $app->make( 'Log\Logger' );
32
+ }
33
+
34
+ /**
35
+ * Count all reviews
36
+ *
37
+ * @param string $siteName
38
+ *
39
+ * @return array
40
+ */
41
+ public function countReviews( $siteName )
42
+ {
43
+ global $wpdb;
44
+
45
+ $counts = wp_cache_get( $this->app->id, 'counts' );
46
+
47
+ if( false !== $counts ) {
48
+ return isset( $counts[ $siteName ] ) ? $counts[ $siteName ] : 0;
49
+ }
50
+
51
+ $results = (array) $wpdb->get_results(
52
+ "SELECT m.meta_value AS name, COUNT( * ) num_posts " .
53
+ "FROM {$wpdb->posts} AS p " .
54
+ "INNER JOIN {$wpdb->postmeta} AS m ON p.ID = m.post_id " .
55
+ "WHERE p.post_type = 'site-review' AND m.meta_key = 'site_name' " .
56
+ "GROUP BY name"
57
+ );
58
+
59
+ $counts = [];
60
+
61
+ foreach( $results as $site ) {
62
+ $counts[ $site->name ] = $site->num_posts;
63
+ }
64
+
65
+ wp_cache_set( $this->app->id, $counts, 'counts' );
66
+
67
+ return isset( $counts[ $siteName ] ) ? $counts[ $siteName ] : 0;
68
+ }
69
+
70
+ /**
71
+ * Delete reviews based on an array of review_id meta values
72
+ *
73
+ * @return void
74
+ */
75
+ public function deleteReviews( array $review_ids = [] )
76
+ {
77
+ global $wpdb;
78
+
79
+ foreach( $review_ids as $review_id ) {
80
+
81
+ $post_id = $this->getReviewPostId( $review_id );
82
+
83
+ if( !empty( $post_id ) ) {
84
+ wp_delete_post( $post_id, true );
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Get array of meta values for all of a post_type
91
+ *
92
+ * @param string $key
93
+ * @param string $status
94
+ * @param string $type
95
+ *
96
+ * @return array
97
+ */
98
+ public function getMetaValues( $key = '', $status = 'publish', $type = 'site-review' )
99
+ {
100
+ if( empty( $key ) ) {
101
+ return [];
102
+ }
103
+
104
+ global $wpdb;
105
+
106
+ if( $status == 'all' || empty( $status ) ) {
107
+ $status = get_post_stati( ['exclude_from_search' => false ] );
108
+ }
109
+
110
+ $status = array_map( function( $ps ) { return "p.post_status = '{$ps}'"; }, (array) $status );
111
+ $status = implode( ' OR ', $status );
112
+
113
+ return $wpdb->get_col( $wpdb->prepare(
114
+ "SELECT DISTINCT pm.meta_value FROM {$wpdb->postmeta} pm " .
115
+ "LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id " .
116
+ "WHERE pm.meta_key = '%s' " .
117
+ "AND p.post_type = '%s' " .
118
+ "AND ({$status}) " .
119
+ "ORDER BY pm.meta_value",
120
+ $key,
121
+ $type
122
+ ));
123
+ }
124
+
125
+ /**
126
+ * Get the current page number for the global query
127
+ *
128
+ * @return int
129
+ */
130
+ public function getCurrentPageNumber()
131
+ {
132
+ $paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : (
133
+ get_query_var( 'page' ) ? get_query_var( 'page' ) : 1
134
+ );
135
+
136
+ return intval( $paged );
137
+ }
138
+
139
+ /**
140
+ * Gets an option from the plugin settings array using dot notation
141
+ *
142
+ * @param string $path
143
+ * @param mixed $fallback
144
+ * @param string $suffix
145
+ *
146
+ * @return mixed
147
+ */
148
+ public function getOption( $path = '', $fallback = '', $suffix = 'settings' )
149
+ {
150
+ $settings = get_option( "{$this->app->prefix}_{$suffix}", [] );
151
+
152
+ $option = $this->getDotNotationValue( $settings, $path, $fallback );
153
+
154
+ // fallback to setting defaults
155
+ if( $suffix == 'settings' && empty( $option ) ) {
156
+ $defaults = $this->app->getDefaults();
157
+
158
+ if( isset( $defaults[ $path ] ) ) {
159
+ $option = $defaults[ $path ];
160
+ }
161
+ }
162
+
163
+ return $option;
164
+ }
165
+
166
+ /**
167
+ * Gets an array of all saved Review IDs
168
+ *
169
+ * @param string $siteName
170
+ *
171
+ * @return array
172
+ */
173
+ public function getReviewIds( $siteName )
174
+ {
175
+ global $wpdb;
176
+
177
+ $ids = $wpdb->get_col( $wpdb->prepare(
178
+ "SELECT m1.meta_value AS review_id " .
179
+ "FROM {$wpdb->posts} AS p " .
180
+ "INNER JOIN {$wpdb->postmeta} AS m1 ON p.ID = m1.post_id " .
181
+ "INNER JOIN {$wpdb->postmeta} AS m2 ON p.ID = m2.post_id " .
182
+ "WHERE p.post_type = 'site-review' " .
183
+ "AND m1.meta_key = 'review_id' " .
184
+ "AND m2.meta_key = 'site_name' " .
185
+ "AND m2.meta_value = '%s'",
186
+ $siteName
187
+ ));
188
+
189
+ return array_keys( array_flip( $ids ) );
190
+ }
191
+
192
+ /**
193
+ * Get the review post ID from the review_id meta value
194
+ *
195
+ * @param string $review_id
196
+ *
197
+ * @return int|null
198
+ */
199
+ public function getReviewPostId( $review_id )
200
+ {
201
+ global $wpdb;
202
+
203
+ return $wpdb->get_var( $wpdb->prepare(
204
+ "SELECT p.ID " .
205
+ "FROM {$wpdb->posts} AS p " .
206
+ "INNER JOIN {$wpdb->postmeta} AS m1 ON p.ID = m1.post_id " .
207
+ "WHERE p.post_type = 'site-review' " .
208
+ "AND m1.meta_key = 'review_id' " .
209
+ "AND m1.meta_value = '%s'",
210
+ $review_id
211
+ ));
212
+ }
213
+
214
+ /**
215
+ * Gets a WP_Query object of saved Reviews
216
+ *
217
+ * @return WP_Query
218
+ */
219
+ public function getReviews( array $args = [] )
220
+ {
221
+ $defaults = [
222
+ 'max_reviews' => '10',
223
+ 'min_rating' => '5',
224
+ 'order_by' => 'date',
225
+ 'pagination' => false,
226
+ 'site_name' => '',
227
+ ];
228
+
229
+ $args = shortcode_atts( $defaults, $args );
230
+
231
+ extract( $args );
232
+
233
+ if( !empty( $site_name ) ) {
234
+ $meta_query[] = [
235
+ 'key' => 'site_name',
236
+ 'value' => $site_name,
237
+ ];
238
+ }
239
+
240
+ $meta_query[] = [
241
+ 'key' => 'rating',
242
+ 'value' => $min_rating,
243
+ 'compare' => '>=',
244
+ ];
245
+
246
+ $pagination = $pagination ? $this->getCurrentPageNumber() : 1;
247
+
248
+ return new WP_Query([
249
+ 'meta_key' => 'pinned',
250
+ 'meta_query' => $meta_query,
251
+ 'order' => 'DESC',
252
+ 'orderby' => "meta_value $order_by",
253
+ 'paged' => $pagination,
254
+ 'post_status' => 'publish',
255
+ 'post_type' => 'site-review',
256
+ 'posts_per_page' => $max_reviews ? $max_reviews : -1,
257
+ ]);
258
+ }
259
+
260
+ /**
261
+ * Gets the review types (default type is "local")
262
+ *
263
+ * @return array
264
+ */
265
+ public function getReviewTypes()
266
+ {
267
+ global $wpdb;
268
+
269
+ $types = $wpdb->get_col(
270
+ "SELECT DISTINCT(meta_value) FROM $wpdb->postmeta WHERE meta_key = 'site_name' ORDER BY meta_value ASC"
271
+ );
272
+
273
+ $types = array_flip( $types );
274
+
275
+ array_walk( $types, function( &$value, $key ) {
276
+ $value = sprintf( '%s reviews', ucfirst( $key ) );
277
+ });
278
+
279
+ return $types;
280
+ }
281
+
282
+ /**
283
+ * Gets an selector option from the plugin settings array using dot notation
284
+ *
285
+ * @param string $path
286
+ * @param mixed $fallback
287
+ *
288
+ * @return mixed
289
+ */
290
+ public function getSelectorOption( $path = '', $fallback = '' )
291
+ {
292
+ $settings = $this->setDefaultSettings(['update' => false ]);
293
+
294
+ return $this->getDotNotationValue( $settings, $path, $fallback );
295
+ }
296
+
297
+ /**
298
+ * Save a review to the database
299
+ *
300
+ * @param string $review_id
301
+ * @param bool $update
302
+ *
303
+ * @return void
304
+ */
305
+ public function postReview( $review_id, array $meta, $update = false )
306
+ {
307
+ $post_id = $this->getReviewPostId( $review_id );
308
+
309
+ if( !empty( $post_id ) && !$update ) {
310
+ return $post_id;
311
+ }
312
+
313
+ // make sure we set post_meta fallback defaults
314
+ $meta = wp_parse_args( $meta, [
315
+ 'author' => '',
316
+ 'avatar' => '',
317
+ 'content' => '',
318
+ 'date' => get_date_from_gmt( gmdate( 'Y-m-d H:i:s' )),
319
+ 'email' => '',
320
+ 'ip_address' => '',
321
+ 'pinned' => false,
322
+ 'rating' => '',
323
+ 'review_id' => '',
324
+ 'site_name' => 'local',
325
+ 'title' => '',
326
+ 'url' => '',
327
+ ]);
328
+
329
+ $post_data = [
330
+ 'comment_status' => 'closed',
331
+ 'ID' => $post_id ? $post_id : 0,
332
+ 'ping_status' => 'closed',
333
+ 'post_content' => $meta['content'],
334
+ 'post_date' => $meta['date'],
335
+ 'post_name' => $meta['site_name'] . str_replace( ['review_', '_'], '-', $review_id ),
336
+ 'post_status' => 'publish',
337
+ 'post_title' => wp_strip_all_tags( $meta['title'] ),
338
+ 'post_type' => 'site-review',
339
+ ];
340
+
341
+ if( $this->getOption( 'general.require.approval', false ) && $meta['site_name'] == 'local' ) {
342
+ $post_data['post_status'] = 'pending';
343
+ }
344
+
345
+ $post_id = wp_insert_post( $post_data, true );
346
+
347
+ if( is_wp_error( $post_id ) ) {
348
+ $this->log->error( sprintf( '%s (%s)', $post_id->get_error_message(), $review_id ) );
349
+
350
+ return false;
351
+ }
352
+
353
+ // add post_meta
354
+ foreach( $meta as $field => $value ) {
355
+ update_post_meta( $post_id, $field, $value );
356
+ }
357
+
358
+ return $post_id;
359
+ }
360
+
361
+ /**
362
+ * Resets an option to the provided value and returns the old value
363
+ *
364
+ * @param mixed $value
365
+ * @param string $path
366
+ * @param string $suffix
367
+ *
368
+ * @return mixed
369
+ */
370
+ public function resetOption( $value, $path = '', $suffix = 'settings' )
371
+ {
372
+ $option = $this->getOption( $path, '', $suffix );
373
+
374
+ $this->setOption( $value, $path, $suffix );
375
+
376
+ return $option;
377
+ }
378
+
379
+ /**
380
+ * Reverts a review title, date, and content to the originally submitted values
381
+ *
382
+ * @param string $postId
383
+ *
384
+ * @return int|false
385
+ */
386
+ public function revertReview( $postId )
387
+ {
388
+ $post = get_post( $postId );
389
+
390
+ if( !isset( $post->post_type ) || $post->post_type != 'site-review' ) {
391
+ return false;
392
+ }
393
+
394
+ delete_post_meta( $post->ID, '_edit_last' );
395
+
396
+ return wp_update_post([
397
+ 'ID' => $post->ID,
398
+ 'post_content' => get_post_meta( $post->ID, 'content', true ),
399
+ 'post_date' => get_post_meta( $post->ID, 'date', true ),
400
+ 'post_title' => get_post_meta( $post->ID, 'title', true ),
401
+ ]);
402
+ }
403
+
404
+ /**
405
+ * Sets an option to the plugin settings array using dot notation
406
+ *
407
+ * @param mixed $value
408
+ * @param string $path
409
+ * @param string $suffix
410
+ *
411
+ * @return bool
412
+ */
413
+ public function setOption( $value, $path = '', $suffix = 'settings' )
414
+ {
415
+ $option = get_option( "{$this->app->prefix}_{$suffix}", [] );
416
+
417
+ $option = $this->convertDotNotationPath( $path, $value, $option );
418
+
419
+ return update_option( "{$this->app->prefix}_{$suffix}", $option );
420
+ }
421
+
422
+ /**
423
+ * Sets the default settings
424
+ *
425
+ * @return array
426
+ */
427
+ public function setDefaultSettings( array $args = [] )
428
+ {
429
+ $defaults = [
430
+ 'data' => null,
431
+ 'merge' => true,
432
+ 'update' => true,
433
+ ];
434
+
435
+ $args = shortcode_atts( $defaults, $args );
436
+
437
+ $currentSettings = $args['merge']
438
+ ? get_option( "{$this->app->prefix}_settings", [] )
439
+ : [];
440
+
441
+ $currentSettings = $this->removeEmptyValuesFrom( $currentSettings );
442
+ $defaultSettings = [];
443
+
444
+ $args['data'] ?: $args['data'] = $this->app->getDefaults();
445
+
446
+ foreach( $args['data'] as $path => $value ) {
447
+ // Don't save the default selector values as they are used anyway by default.
448
+ if( !!$args['update'] && strpos( $path, '.selectors.' ) !== false ) {
449
+ $value = '';
450
+ }
451
+
452
+ $defaultSettings = $this->convertDotNotationPath( $path, $value, $defaultSettings );
453
+ }
454
+
455
+ $settings = array_replace_recursive( $defaultSettings, $currentSettings );
456
+
457
+ if( $args['update'] ) {
458
+ update_option( "{$this->app->prefix}_settings", $settings );
459
+ }
460
+
461
+ return $settings;
462
+ }
463
+
464
+ /**
465
+ * Gets a value from an array using a dot-notation path
466
+ *
467
+ * @param mixed $value
468
+ * @param string $path
469
+ * @param mixed $fallback
470
+ *
471
+ * @return mixed
472
+ */
473
+ protected function getDotNotationValue( $value, $path, $fallback )
474
+ {
475
+ if( empty( $path ) ) {
476
+ return $value;
477
+ }
478
+
479
+ $keys = explode( '.', $path );
480
+
481
+ foreach( $keys as $key ) {
482
+ if( !isset( $value[ $key ] ) ) {
483
+ return $fallback;
484
+ }
485
+ $value = $value[ $key ];
486
+ }
487
+
488
+ return $value;
489
+ }
490
+
491
+ /**
492
+ * Convert a dot-notation path to an array
493
+ *
494
+ * @param string $path
495
+ * @param mixed $value
496
+ * @param mixed $option
497
+ *
498
+ * @return array
499
+ */
500
+ protected function convertDotNotationPath( $path, $value, $option )
501
+ {
502
+ $token = strtok( $path, '.' );
503
+
504
+ $ref = &$option;
505
+
506
+ while( $token !== false ) {
507
+ $ref = is_array( $ref ) ? $ref : [];
508
+ $ref = &$ref[ $token ];
509
+ $token = strtok( '.' );
510
+ }
511
+
512
+ $ref = $value;
513
+
514
+ return $option;
515
+ }
516
+
517
+ /**
518
+ * Removes empty values from an array
519
+ *
520
+ * @return array
521
+ */
522
+ protected function removeEmptyValuesFrom( array $array )
523
+ {
524
+ $result = [];
525
+
526
+ foreach( $array as $key => $value ) {
527
+ if( !$value )continue;
528
+ $result[ $key ] = is_array( $value )
529
+ ? $this->removeEmptyValuesFrom( $value )
530
+ : $value;
531
+ }
532
+
533
+ return $result;
534
+ }
535
+ }
plugin/Email.php ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ class Email
16
+ {
17
+ /**
18
+ * @var App
19
+ */
20
+ protected $app;
21
+
22
+ /**
23
+ * @var array
24
+ */
25
+ protected $attachments;
26
+
27
+ /**
28
+ * @var array
29
+ */
30
+ protected $headers;
31
+
32
+ /**
33
+ * @var string
34
+ */
35
+ protected $message;
36
+
37
+ /**
38
+ * @var string
39
+ */
40
+ protected $subject;
41
+
42
+ /**
43
+ * @var string
44
+ */
45
+ protected $to;
46
+
47
+ public function __construct( App $app )
48
+ {
49
+ $this->app = $app;
50
+ }
51
+
52
+ /**
53
+ * @return Email
54
+ */
55
+ public function compose( array $email )
56
+ {
57
+ $email = $this->normalize( $email );
58
+
59
+ $this->attachments = $email['attachments'];
60
+ $this->headers = $this->buildHeaders( $email );
61
+ $this->message = $this->buildHtmlMessage( $email );
62
+ $this->subject = $email['subject'];
63
+ $this->to = $email['to'];
64
+
65
+ add_action( 'phpmailer_init', [ $this, 'buildPlainTextMessage'] );
66
+
67
+ return $this;
68
+ }
69
+
70
+ /**
71
+ * @return string|null
72
+ */
73
+ public function read( $plaintext = false )
74
+ {
75
+ if( !!$plaintext ) {
76
+ $message = $this->stripHtmlTags( $this->message );
77
+
78
+ return apply_filters( 'site-reviews/email/message', $message, 'text', $this );
79
+ }
80
+
81
+ return $this->message;
82
+ }
83
+
84
+ /**
85
+ * @return bool|null
86
+ */
87
+ public function send()
88
+ {
89
+ if( !$this->message || !$this->subject || !$this->to )return;
90
+
91
+ $sent = wp_mail(
92
+ $this->to,
93
+ $this->subject,
94
+ $this->message,
95
+ $this->headers,
96
+ $this->attachments
97
+ );
98
+
99
+ $this->reset();
100
+
101
+ return $sent;
102
+ }
103
+
104
+ /**
105
+ * @return void
106
+ *
107
+ * @action phpmailer_init
108
+ */
109
+ public function buildPlainTextMessage( $phpmailer )
110
+ {
111
+ if( $phpmailer->ContentType === 'text/plain' || !empty( $phpmailer->AltBody ) )return;
112
+
113
+ $message = $this->stripHtmlTags( $phpmailer->Body );
114
+
115
+ $phpmailer->AltBody = apply_filters( 'site-reviews/email/message', $message, 'text', $this );
116
+ }
117
+
118
+ /**
119
+ * @return array
120
+ */
121
+ protected function buildHeaders( $email )
122
+ {
123
+ $allowed = [
124
+ 'bcc',
125
+ 'cc',
126
+ 'from',
127
+ 'reply-to',
128
+ ];
129
+
130
+ $headers = array_intersect_key( $email, array_flip( $allowed ) );
131
+ $headers = array_filter( $headers );
132
+
133
+ foreach( $headers as $key => $value ) {
134
+ unset( $headers[ $key ] );
135
+ $headers[] = "{$key}: {$value}";
136
+ }
137
+
138
+ $headers[] = 'Content-Type: text/html';
139
+
140
+ return apply_filters( 'site-reviews/email/headers', $headers, $this );
141
+ }
142
+
143
+ /**
144
+ * @return string
145
+ */
146
+ protected function buildHtmlMessage( $email )
147
+ {
148
+ $html = $this->app->make( 'Html' );
149
+
150
+ $template = trim( $this->app->make( 'Database' )->getOption( 'general.notification_message' ) );
151
+
152
+ if( !empty( $template ) ) {
153
+ $message = $html->renderTemplateString( $template, $email['template-tags'], 'return' );
154
+ }
155
+ else if( $email['template'] ) {
156
+ $message = $html->renderTemplate( "email/templates/{$email['template']}", $email['template-tags'], 'return' );
157
+ }
158
+
159
+ if( !isset( $message ) ) {
160
+ $message = $email['message'];
161
+ }
162
+
163
+ $message = $email['before'] . $message . $email['after'];
164
+
165
+ $body = $html->renderTemplate( 'email/index', [], 'return' );
166
+
167
+ $message = strip_shortcodes( $message );
168
+ $message = wptexturize( $message );
169
+ $message = wpautop( $message );
170
+ $message = str_replace( '&lt;&gt; ', '', $message );
171
+ $message = str_replace( ']]>', ']]&gt;', $message );
172
+ $message = str_replace( '{message}', $message, $body );
173
+ $message = stripslashes( $message );
174
+
175
+ return apply_filters( 'site-reviews/email/message', $message, 'html', $this );
176
+ }
177
+
178
+ /**
179
+ * @return array
180
+ */
181
+ protected function normalize( $email )
182
+ {
183
+ $fromName = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
184
+ $fromEmail = get_option( 'admin_email' );
185
+
186
+ $defaults = [
187
+ 'after' => '',
188
+ 'attachments' => [],
189
+ 'bcc' => '',
190
+ 'before' => '',
191
+ 'cc' => '',
192
+ 'from' => "{$fromName} <{$fromEmail}>",
193
+ 'message' => '',
194
+ 'reply-to' => '',
195
+ 'subject' => '',
196
+ 'template' => '',
197
+ 'template-tags' => [],
198
+ 'to' => '',
199
+ ];
200
+
201
+ $email = shortcode_atts( $defaults, $email );
202
+
203
+ !empty( $email['reply-to'] ) ?: $email['reply-to'] = $email['from'];
204
+
205
+ return apply_filters( 'site-reviews/email/compose', $email, $this );
206
+ }
207
+
208
+ /**
209
+ * @return void
210
+ */
211
+ protected function reset()
212
+ {
213
+ $this->attachments = [];
214
+ $this->headers = [];
215
+ $this->message = null;
216
+ $this->subject = null;
217
+ $this->to = null;
218
+ }
219
+
220
+ /**
221
+ * @return string
222
+ */
223
+ protected function stripHtmlTags( $string )
224
+ {
225
+ // remove invisible elements
226
+ $string = preg_replace( '@<(embed|head|noembed|noscript|object|script|style)[^>]*?>.*?</\\1>@siu', '', $string );
227
+
228
+ // replace certain elements with a line-break
229
+ $string = preg_replace( '@</(div|h[1-9]|p|pre|tr)@iu', "\r\n\$0", $string );
230
+
231
+ // replace other elements with a space
232
+ $string = preg_replace( '@</(td|th)@iu', " \$0", $string );
233
+
234
+ // add a placeholder for plain-text bullets to list elements
235
+ $string = preg_replace( '@<(li)[^>]*?>@siu', "\$0-o-^-o-", $string );
236
+
237
+ // strip all remaining HTML tags
238
+ $string = wp_strip_all_tags( $string );
239
+ $string = wp_specialchars_decode( $string, ENT_QUOTES );
240
+ $string = preg_replace( '/\v(?:[\v\h]+){2,}/', "\r\n\r\n", $string );
241
+ $string = str_replace( '-o-^-o-', ' - ', $string );
242
+
243
+ return html_entity_decode( $string, ENT_QUOTES, 'UTF-8' );
244
+ }
245
+ }
plugin/Handlers/EnqueueAssets.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use GeminiLabs\SiteReviews\Commands\EnqueueAssets as Command;
15
+
16
+ class EnqueueAssets
17
+ {
18
+ /**
19
+ * @var App
20
+ */
21
+ protected $app;
22
+
23
+ /**
24
+ * @var array
25
+ */
26
+ protected $dependencies;
27
+
28
+ public function __construct( App $app )
29
+ {
30
+ $this->app = $app;
31
+ }
32
+
33
+ /**
34
+ * @return void
35
+ */
36
+ public function handle( Command $command )
37
+ {
38
+ $this->dependencies = $this->app->make( 'Html' )->getDependencies();
39
+
40
+ if( is_admin() ) {
41
+ $this->enqueueAdmin( $command );
42
+ }
43
+ else {
44
+ $this->enqueuePublic( $command );
45
+ }
46
+
47
+ wp_localize_script( $command->handle, 'site_reviews', [
48
+ 'action' => $this->app->prefix . '_action',
49
+ 'ajaxurl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), "{$this->app->id}-ajax-nonce" ),
50
+ ]);
51
+ }
52
+
53
+ /**
54
+ * Enqueue admin assets
55
+ *
56
+ * @return void
57
+ */
58
+ public function enqueueAdmin( Command $command )
59
+ {
60
+ $screen = get_current_screen();
61
+
62
+ $dependencies = array_merge( $this->dependencies, ['jquery'] );
63
+
64
+ wp_enqueue_style(
65
+ $command->handle,
66
+ $command->url . 'css/site-reviews-admin.css',
67
+ [],
68
+ $command->version
69
+ );
70
+
71
+ if( !( $screen->post_type == 'site-review'
72
+ || $screen->base == 'post'
73
+ || $screen->id == 'dashboard'
74
+ || $screen->id == 'widgets'
75
+ ))return;
76
+
77
+ wp_enqueue_script(
78
+ $command->handle,
79
+ $command->url . 'js/site-reviews-admin.js',
80
+ $dependencies,
81
+ $command->version,
82
+ true
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Enqueue public assets
88
+ *
89
+ * @return void
90
+ */
91
+ public function enqueuePublic( Command $command )
92
+ {
93
+ $currentTheme = sanitize_title( wp_get_theme()->get( 'Name' ) );
94
+
95
+ $stylesheet = file_exists( $command->path . "css/{$currentTheme}.css" )
96
+ ? $command->url . "css/{$currentTheme}.css"
97
+ : $command->url . 'css/site-reviews.css';
98
+
99
+ if( apply_filters( 'site-reviews/assets/css', true ) ) {
100
+ wp_enqueue_style(
101
+ $command->handle,
102
+ $stylesheet,
103
+ [],
104
+ $command->version
105
+ );
106
+ }
107
+
108
+ if( !apply_filters( 'site-reviews/assets/js', true ) )return;
109
+
110
+ wp_enqueue_script(
111
+ $command->handle,
112
+ $command->url . 'js/site-reviews.js',
113
+ ['jquery'],
114
+ $command->version,
115
+ true
116
+ );
117
+ }
118
+ }
plugin/Handlers/RegisterPointers.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use GeminiLabs\SiteReviews\Commands\RegisterPointers as Command;
15
+
16
+ class RegisterPointers
17
+ {
18
+ protected $app;
19
+
20
+ public function __construct( App $app )
21
+ {
22
+ $this->app = $app;
23
+ }
24
+
25
+ /**
26
+ * Add pointers to the current screen if they have not yet been dismissed
27
+ *
28
+ * return void
29
+ */
30
+ public function handle( Command $command )
31
+ {
32
+ $screen = get_current_screen();
33
+
34
+ // Get dismissed pointers
35
+ $dismissed = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
36
+ $dismissed = explode( ',', (string) $dismissed );
37
+
38
+ $pointers = [];
39
+
40
+ foreach( $command->pointers as $pointer ) {
41
+
42
+ if( $pointer['screen'] != $screen->id || in_array( $pointer['id'], $dismissed ) )continue;
43
+
44
+ $pointers[] = [
45
+ 'id' => $pointer['id'],
46
+ 'screen' => $pointer['screen'],
47
+ 'target' => $pointer['target'],
48
+ 'options' => [
49
+ 'content' => sprintf( '<h3>%s</h3><p>%s</p>', $pointer['title'], $pointer['content'] ),
50
+ 'position' => $pointer['position'],
51
+ ],
52
+ ];
53
+ }
54
+
55
+ wp_localize_script( $this->app->id, 'site_reviews_pointers', [
56
+ 'pointers' => $pointers,
57
+ ]);
58
+
59
+ if( empty( $pointers ) )return;
60
+
61
+ wp_enqueue_style( 'wp-pointer' );
62
+ wp_enqueue_script( 'wp-pointer' );
63
+ }
64
+ }
plugin/Handlers/RegisterPostType.php ADDED
@@ -0,0 +1,640 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use GeminiLabs\SiteReviews\Commands\RegisterPostType as Command;
15
+ use GeminiLabs\SiteReviews\Strings;
16
+ use WP_Query;
17
+
18
+ class RegisterPostType
19
+ {
20
+ /**
21
+ * @var App
22
+ */
23
+ protected $app;
24
+
25
+ /**
26
+ * @var array
27
+ */
28
+ protected $columns;
29
+
30
+ /**
31
+ * @var Database
32
+ */
33
+ protected $db;
34
+
35
+ public function __construct( App $app )
36
+ {
37
+ $this->app = $app;
38
+ $this->db = $app->make( 'Database' );
39
+ }
40
+
41
+ public function handle( Command $command )
42
+ {
43
+ extract( $command->args );
44
+
45
+ $this->columns = $columns;
46
+
47
+ if( in_array( $post_type, get_post_types(['_builtin' => true ]) ) )return;
48
+
49
+ $post_type = empty( $post_type )
50
+ ? sanitize_title( $single )
51
+ : $post_type;
52
+
53
+ $slug = empty( $slug )
54
+ ? sanitize_title( $plural )
55
+ : $slug;
56
+
57
+ $menu_name = empty( $menu_name )
58
+ ? $plural
59
+ : $menu_name;
60
+
61
+ $show_in_nav_menus = isset( $args['show_in_nav_menus'] )
62
+ ? $args['show_in_nav_menus']
63
+ : $public;
64
+
65
+ $show_ui = !isset( $args['show_ui'] ) ?: $args['show_ui'];
66
+
67
+ $exclude_from_search = isset( $args['exclude_from_search'] )
68
+ ? $args['exclude_from_search']
69
+ : !$public;
70
+
71
+ $publicly_queryable = isset( $args['publicly_queryable'] )
72
+ ? $args['publicly_queryable']
73
+ : $public;
74
+
75
+ $labels['singular_name'] = $single;
76
+ $labels['name'] = $plural;
77
+ $labels['menu_name'] = $menu_name;
78
+
79
+ $args = [
80
+ 'description' => '',
81
+ 'labels' => $labels,
82
+ 'taxonomies' => $taxonomies,
83
+ 'supports' => $supports,
84
+ 'map_meta_cap' => $map_meta_cap,
85
+ 'menu_position' => $menu_position,
86
+ 'menu_icon' => $menu_icon,
87
+ 'has_archive' => $has_archive,
88
+ 'public' => $public,
89
+ 'show_in_nav_menus' => $show_in_nav_menus,
90
+ 'show_ui' => $show_ui,
91
+ 'exclude_from_search' => $exclude_from_search,
92
+ 'publicly_queryable' => $publicly_queryable,
93
+ 'capabilities' => ['create_posts' => "create_{$slug}"],
94
+ 'hierarchical' => $hierarchical,
95
+ 'rewrite' => $rewrite,
96
+ 'query_var' => $query_var,
97
+ ];
98
+
99
+ register_post_type( $post_type, $args );
100
+
101
+ $this->performHooks( $post_type );
102
+ }
103
+
104
+ /**
105
+ * Removes the autosave functionality
106
+ *
107
+ * @return void
108
+ *
109
+ * @action admin_print_scripts-post.php
110
+ */
111
+ public function disableAutosave()
112
+ {
113
+ if( $this->isEditLocalReviewPage() )return;
114
+
115
+ global $post;
116
+
117
+ if( $post->post_type == 'site-review' ) {
118
+ wp_deregister_script( 'autosave' );
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Removes the slug metabox
124
+ *
125
+ * @return void
126
+ *
127
+ * @action admin_menu
128
+ */
129
+ public function disableMetaBoxes()
130
+ {
131
+ remove_meta_box( 'slugdiv', 'site-review', 'advanced' );
132
+ }
133
+
134
+ /**
135
+ * Creates the custom post_type columns
136
+ *
137
+ * @return array
138
+ *
139
+ * @filter manage_{$post_type}_posts_columns
140
+ */
141
+ public function manageColumns( array $columns )
142
+ {
143
+ $this->columns = ['cb' => ''] + $this->columns;
144
+
145
+ array_walk( $this->columns, function( &$value, $key ) use ( $columns ) {
146
+ if( array_key_exists( $key, $columns ) && empty( $value ) ) {
147
+ $value = $columns[ $key ];
148
+ }
149
+ else if( $key === 'sticky' ) {
150
+ // wrap in <span> so we can replace with a dashicon in CSS @media
151
+ $value = "<span class=\"pinned-icon\">{$value}</span>";
152
+
153
+ global $wp_version;
154
+
155
+ // WP < 4.4 support
156
+ if( version_compare( $wp_version, '4.4', '<' ) ) {
157
+ $value .= file_get_contents( "{$this->app->path}assets/img/pinned.svg" );
158
+ }
159
+ }
160
+ });
161
+
162
+ $sites = $this->db->getMetaValues( 'site_name' );
163
+
164
+ if( count( $sites ) < 1 || ( count( $sites ) == 1 && $sites[0] == 'local' ) ) {
165
+ unset( $this->columns['site'] );
166
+ }
167
+
168
+ // remove all keys with null, false, or empty values
169
+ return array_filter( $this->columns, 'strlen' );
170
+ }
171
+
172
+ /**
173
+ * Sets which custom post_type columns are sortable
174
+ *
175
+ * @return array
176
+ *
177
+ * @filter manage_edit-{$post_type}_sortable_columns
178
+ */
179
+ public function manageSortableColumns( array $columns )
180
+ {
181
+ $columns['reviewer'] = 'author';
182
+ $columns['site'] = 'site_name';
183
+ $columns['stars'] = 'rating';
184
+ $columns['sticky'] = 'pinned';
185
+
186
+ return $columns;
187
+ }
188
+
189
+ /**
190
+ * Modifies the WP_Query meta_query value
191
+ *
192
+ * @return void
193
+ *
194
+ * @action pre_get_posts
195
+ */
196
+ public function modifyColumnMetaQuery( WP_Query $query )
197
+ {
198
+ global $pagenow;
199
+
200
+ if( !is_admin()
201
+ || !$query->is_main_query()
202
+ || $query->query['post_type'] != 'site-review'
203
+ || $pagenow != 'edit.php'
204
+ )return;
205
+
206
+ $meta_keys = [
207
+ 'rating',
208
+ 'site_name',
209
+ ];
210
+
211
+ foreach( $meta_keys as $key ) {
212
+ if( $value = filter_input( INPUT_GET, $key ) ) {
213
+ $query->query_vars['meta_query'][] = [
214
+ 'key' => $key,
215
+ 'value' => $value,
216
+ ];
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Modifies the WP_Query orderby value
223
+ *
224
+ * @return void
225
+ *
226
+ * @action pre_get_posts
227
+ */
228
+ public function modifyColumnOrderby( WP_Query $query )
229
+ {
230
+ global $pagenow;
231
+
232
+ if( !is_admin()
233
+ || !$query->is_main_query()
234
+ || $query->query['post_type'] != 'site-review'
235
+ || $pagenow != 'edit.php'
236
+ )return;
237
+
238
+ $orderby = $query->get( 'orderby' );
239
+
240
+ switch( $orderby ) {
241
+ case 'author':
242
+ case 'site_name':
243
+ case 'rating':
244
+ case 'pinned':
245
+ $query->set( 'meta_key', $orderby );
246
+ $query->set( 'orderby', 'meta_value' );
247
+ break;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Modifies the WP_Editor settings
253
+ *
254
+ * @return array
255
+ *
256
+ * @action wp_editor_settings
257
+ */
258
+ public function modifyContentEditor( array $settings )
259
+ {
260
+ if( $this->isEditLocalReviewPage() !== true ) {
261
+ return $settings;
262
+ }
263
+
264
+ return [
265
+ 'textarea_rows' => 12,
266
+ 'media_buttons' => false,
267
+ 'quicktags' => false,
268
+ 'tinymce' => false,
269
+ ];
270
+ }
271
+
272
+ /**
273
+ * Modify the WP_Editor html to allow autosizing without breaking the `editor-expand` script
274
+ *
275
+ * @param string $html
276
+ *
277
+ * @return string
278
+ *
279
+ * @action the_editor
280
+ */
281
+ public function modifyContentEditorHtml( $html )
282
+ {
283
+ if( $this->isEditLocalReviewPage() !== true ) {
284
+ return $html;
285
+ }
286
+
287
+ return str_replace( '<textarea', '<div id="ed_toolbar"></div><textarea', $html );
288
+ }
289
+
290
+ /**
291
+ * Customize the bulk updated messages array for this post_type
292
+ *
293
+ * return array
294
+ */
295
+ public function modifyPostTypeBulkMessages( array $messages, array $counts )
296
+ {
297
+ $messages['site-review'] = [
298
+ 'updated' => _n( '%s review updated.', '%s posts updated.', $counts['updated'], 'site-reviews' ),
299
+ 'locked' => _n( '%s review not updated, somebody is editing it.', '%s reviews not updated, somebody is editing them.', $counts['locked'], 'site-reviews' ),
300
+ 'deleted' => _n( '%s review permanently deleted.', '%s reviews permanently deleted.', $counts['deleted'], 'site-reviews' ),
301
+ 'trashed' => _n( '%s review moved to the Trash.', '%s reviews moved to the Trash.', $counts['trashed'], 'site-reviews' ),
302
+ 'untrashed' => _n( '%s review restored from the Trash.', '%s reviews restored from the Trash.', $counts['untrashed'], 'site-reviews' ),
303
+ ];
304
+
305
+ return $messages;
306
+ }
307
+
308
+ /**
309
+ * Customize the updated messages array for this post_type
310
+ *
311
+ * return array
312
+ */
313
+ public function modifyPostTypeMessages( array $messages )
314
+ {
315
+ global $post;
316
+
317
+ if( !isset( $post->ID ) || !$post->ID ) {
318
+ return $messages;
319
+ }
320
+
321
+ $strings = (new Strings)->post_updated_messages();
322
+
323
+ $restored = filter_input( INPUT_GET, 'revision' );
324
+ $restored = $restored
325
+ ? sprintf( $strings['restored'], wp_post_revision_title( (int) $restored, false ) )
326
+ : false;
327
+
328
+ $scheduled_date = date_i18n( 'M j, Y @ H:i', strtotime( $post->post_date ) );
329
+
330
+ $messages['site-review'] = [
331
+ 1 => $strings['updated'],
332
+ 4 => $strings['updated'],
333
+ 5 => $restored,
334
+ 6 => $strings['published'],
335
+ 7 => $strings['saved'],
336
+ 8 => $strings['submitted'],
337
+ 9 => sprintf( $strings['scheduled'], sprintf( '<strong>%s</strong>', $scheduled_date ) ),
338
+ 10 => $strings['draft_updated'],
339
+ 50 => $strings['approved'],
340
+ 51 => $strings['unapproved'],
341
+ 52 => $strings['reverted'],
342
+ ];
343
+
344
+ return $messages;
345
+ }
346
+
347
+ /**
348
+ * Prints the column filters
349
+ *
350
+ * @param string $post_type
351
+ *
352
+ * @return void
353
+ *
354
+ * @action restrict_manage_posts
355
+ */
356
+ public function printColumnFilters( $post_type )
357
+ {
358
+ // WP < 4.4 support
359
+ if( !$post_type ) {
360
+ $screen = get_current_screen();
361
+ $post_type = $screen->post_type;
362
+ }
363
+
364
+ if( $post_type !== 'site-review' )return;
365
+
366
+ $status = filter_input( INPUT_GET, 'post_status' );
367
+ $status = $status ? $status : 'publish';
368
+
369
+ $ratings = $this->db->getMetaValues( 'rating', $status );
370
+
371
+ $this->renderRatingsFilter( $ratings );
372
+
373
+ $sites = $this->db->getMetaValues( 'site_name', $status );
374
+
375
+ if( count( $sites ) == 1 && $sites[0] == 'local' )return;
376
+
377
+ $this->renderSitesFilter( $sites );
378
+ }
379
+
380
+ /**
381
+ * Prints the custom column values
382
+ *
383
+ * @param string $column
384
+ *
385
+ * @return void
386
+ *
387
+ * @action manage_{$post_type}_posts_custom_column
388
+ */
389
+ public function printColumnValues( $column )
390
+ {
391
+ global $post;
392
+ global $wp_version;
393
+
394
+ switch( $column ) {
395
+
396
+ case 'slug':
397
+ echo $post->post_name;
398
+ break;
399
+
400
+ case 'featured':
401
+ case 'image':
402
+ case 'thumbnail':
403
+
404
+ if( has_post_thumbnail( $post->ID ) ) {
405
+ $img = wp_get_attachment_image( get_post_thumbnail_id( $post->ID ), [96, 48] );
406
+ }
407
+
408
+ echo ( isset( $img ) && !empty( $img ) ) ? $img : '&mdash;';
409
+ break;
410
+
411
+ case 'reviewer':
412
+ echo get_post_meta( $post->ID, 'author', true );
413
+ break;
414
+
415
+ case 'stars':
416
+ $this->app->make( 'Html' )->renderPartial( 'rating', [
417
+ 'stars' => get_post_meta( $post->ID, 'rating', true ),
418
+ ]);
419
+ break;
420
+
421
+ case 'site':
422
+ echo ucfirst( get_post_meta( $post->ID, 'site_name', true ) );
423
+ break;
424
+
425
+ case 'sticky':
426
+ $pinned = get_post_meta( $post->ID, 'pinned', true )
427
+ ? ' pinned'
428
+ : '';
429
+
430
+ // WP < 4.4 support
431
+ $fallback = version_compare( $wp_version, '4.4', '<' )
432
+ ? file_get_contents( "{$this->app->path}assets/img/pinned.svg" )
433
+ : '';
434
+
435
+ echo sprintf( '<i class="dashicons dashicons-sticky%s" data-id="%s">%s</i>', $pinned, $post->ID, $fallback );
436
+ break;
437
+
438
+ default:
439
+ echo apply_filters( "_populate_column_{$column}", '', $post->ID );
440
+ break;
441
+ }
442
+ }
443
+
444
+ public function revertPost()
445
+ {
446
+ $post_id = filter_input( INPUT_GET, 'post' );
447
+
448
+ if( !$post_id )return;
449
+
450
+ check_admin_referer( 'revert-review_' . $post_id );
451
+
452
+ $this->db->revertReview( $post_id );
453
+
454
+ wp_redirect( $this->getRedirectUrl( $post_id, 52 ) );
455
+
456
+ exit();
457
+ }
458
+
459
+ public function unapprovePost()
460
+ {
461
+ $post_id = filter_input( INPUT_GET, 'post' );
462
+
463
+ if( !$post_id )return;
464
+
465
+ check_admin_referer( 'unapprove-review_' . $post_id );
466
+
467
+ $this->changePostStatus( $post_id, 'pending' );
468
+
469
+ wp_redirect( $this->getRedirectUrl( $post_id, 51 ) );
470
+
471
+ exit();
472
+ }
473
+
474
+ public function approvePost()
475
+ {
476
+ $post_id = filter_input( INPUT_GET, 'post' );
477
+
478
+ if( !$post_id )return;
479
+
480
+ check_admin_referer( 'approve-review_' . $post_id );
481
+
482
+ $this->changePostStatus( $post_id, 'publish' );
483
+
484
+ wp_redirect( $this->getRedirectUrl( $post_id, 50 ) );
485
+
486
+ exit();
487
+ }
488
+
489
+ /**
490
+ * Set/persist custom permissions for the post_type
491
+ *
492
+ * return void
493
+ */
494
+ public function setPermissions()
495
+ {
496
+ foreach( wp_roles()->roles as $role => $value ) {
497
+ wp_roles()->remove_cap( $role, 'create_reviews' );
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Remove post_type support for all non-local reviews
503
+ *
504
+ * @todo: Move this to addons
505
+ *
506
+ * return void
507
+ */
508
+ public function setPostTypeSupport()
509
+ {
510
+ if( $this->isEditLocalReviewPage() )return;
511
+
512
+ remove_post_type_support( 'site-review', 'title' );
513
+ remove_post_type_support( 'site-review', 'editor' );
514
+ }
515
+
516
+ /**
517
+ * @return null|bool
518
+ */
519
+ protected function isEditLocalReviewPage()
520
+ {
521
+ $screen = get_current_screen();
522
+
523
+ $action = filter_input( INPUT_GET, 'action' );
524
+ $postId = filter_input( INPUT_GET, 'post' );
525
+
526
+ if( $action != 'edit'
527
+ || $postId < 1
528
+ || $screen->base != 'post'
529
+ || $screen->post_type != 'site-review'
530
+ )return;
531
+
532
+ $siteName = get_post_meta( $postId, 'site_name', true );
533
+
534
+ if( 'local' === $siteName ) {
535
+ return true;
536
+ }
537
+ }
538
+
539
+ /**
540
+ * @param string $post_type
541
+ *
542
+ * @return void
543
+ */
544
+ protected function performHooks( $post_type = '' )
545
+ {
546
+ add_action( 'admin_action_approve', [ $this, 'approvePost'] );
547
+ add_action( 'admin_print_scripts-post.php', [ $this, 'disableAutosave'], 999 );
548
+ add_action( 'admin_menu', [ $this, 'disableMetaBoxes'] );
549
+ add_action( 'pre_get_posts', [ $this, 'modifyColumnMetaQuery'] );
550
+ add_action( 'pre_get_posts', [ $this, 'modifyColumnOrderby'] );
551
+ add_action( 'restrict_manage_posts', [ $this, 'printColumnFilters'] );
552
+ add_action( "manage_{$post_type}_posts_custom_column", [ $this, 'printColumnValues'] );
553
+ add_action( 'admin_action_revert', [ $this, 'revertPost'] );
554
+ add_action( 'admin_init', [ $this, 'setPermissions'], 999 );
555
+ add_action( 'current_screen', [ $this, 'setPostTypeSupport'] );
556
+ add_action( 'admin_action_unapprove', [ $this, 'unapprovePost'] );
557
+
558
+ add_filter( "manage_{$post_type}_posts_columns", [ $this, 'manageColumns'] );
559
+ add_filter( "manage_edit-{$post_type}_sortable_columns", [ $this, 'manageSortableColumns'] );
560
+ add_filter( 'wp_editor_settings', [ $this, 'modifyContentEditor' ] );
561
+ add_filter( 'the_editor', [ $this, 'modifyContentEditorHtml'] );
562
+ add_filter( 'bulk_post_updated_messages', [ $this, 'modifyPostTypeBulkMessages'], 10, 2 );
563
+ add_filter( 'post_updated_messages', [ $this, 'modifyPostTypeMessages'] );
564
+ }
565
+
566
+ /**
567
+ * @param int $post_id
568
+ * @param int $message_index
569
+ *
570
+ * @return string
571
+ */
572
+ protected function getRedirectUrl( $post_id, $message_index )
573
+ {
574
+ $referer = wp_get_referer();
575
+
576
+ return !$referer || strpos( $referer, 'post.php' ) !== false || strpos( $referer, 'post-new.php' ) !== false
577
+ ? add_query_arg( ['message' => $message_index ], get_edit_post_link( $post_id, false ) )
578
+ : add_query_arg( ['message' => $message_index ], remove_query_arg( ['trashed', 'untrashed', 'deleted', 'ids'], $referer ) );
579
+ }
580
+
581
+ /**
582
+ * @param array $ratings
583
+ *
584
+ * @return void
585
+ */
586
+ protected function renderRatingsFilter( $ratings )
587
+ {
588
+ if( empty( $ratings ) )return;
589
+
590
+ $ratings = array_flip( array_reverse( $ratings ) );
591
+
592
+ array_walk( $ratings, function( &$value, $key ) {
593
+ $label = _n( '%s star', '%s stars', $key, 'site-reviews' );
594
+ $value = sprintf( $label, $key );
595
+ });
596
+
597
+ $ratings = [ __( 'All ratings', 'site-reviews' ) ] + $ratings;
598
+
599
+ $this->app->make( 'Html' )->renderPartial( 'filterby', [
600
+ 'name' => 'rating',
601
+ 'values' => $ratings,
602
+ ]);
603
+ }
604
+
605
+ /**
606
+ * @param array $sites
607
+ *
608
+ * @return void
609
+ */
610
+ protected function renderSitesFilter( $sites )
611
+ {
612
+ if( empty( $sites ) )return;
613
+
614
+ $sites = array_combine( $sites, array_map( 'ucfirst', $sites ) );
615
+ $sites = [ __( 'All types', 'site-reviews' ) ] + $sites;
616
+
617
+ if( isset( $sites['local'] ) ) {
618
+ $sites['local'] = __( 'Local Review', 'site-reviews' );
619
+ }
620
+
621
+ $this->app->make( 'Html' )->renderPartial( 'filterby', [
622
+ 'name' => 'site_name',
623
+ 'values' => $sites,
624
+ ]);
625
+ }
626
+
627
+ /**
628
+ * @param int $post_id
629
+ * @param string $status
630
+ *
631
+ * @return void
632
+ */
633
+ protected function changePostStatus( $post_id, $status )
634
+ {
635
+ return wp_update_post([
636
+ 'ID' => $post_id,
637
+ 'post_status' => $status,
638
+ ]);
639
+ }
640
+ }
plugin/Handlers/RegisterShortcodes.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+ use GeminiLabs\SiteReviews\Commands\RegisterShortcodes as Command;
16
+ use ReflectionException;
17
+
18
+ class RegisterShortcodes
19
+ {
20
+ /**
21
+ * @var App
22
+ */
23
+ protected $app;
24
+
25
+ public function __construct( App $app )
26
+ {
27
+ $this->app = $app;
28
+ }
29
+
30
+ /**
31
+ * return void
32
+ */
33
+ public function handle( Command $command )
34
+ {
35
+ foreach( $command->shortcodes as $key ) {
36
+
37
+ $shortcodeClass = implode( '', array_map( 'ucfirst', explode( '_', $key ) ) );
38
+ $shortcodeClass = "GeminiLabs\SiteReviews\Shortcodes\\$shortcodeClass";
39
+
40
+ try {
41
+ $shortcode = $this->app->make( $shortcodeClass );
42
+ add_shortcode( $key, [ $shortcode, 'printShortcode'] );
43
+ }
44
+ catch( Exception $e ) {
45
+ $this->app->make( 'Log\Logger' )->error( sprintf( 'Error registering shortcode. Message: %s "(%s:%s)"',
46
+ $e->getMessage(),
47
+ $e->getFile(),
48
+ $e->getLine()
49
+ ));
50
+ }
51
+ }
52
+ }
53
+ }
plugin/Handlers/RegisterWidgets.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+ use GeminiLabs\SiteReviews\Commands\RegisterWidgets as Command;
16
+
17
+ class RegisterWidgets
18
+ {
19
+ /**
20
+ * @var App
21
+ */
22
+ protected $app;
23
+
24
+ public function __construct( App $app )
25
+ {
26
+ $this->app = $app;
27
+ }
28
+
29
+ /**
30
+ * return void
31
+ */
32
+ public function handle( Command $command )
33
+ {
34
+ global $wp_widget_factory;
35
+
36
+ foreach( $command->widgets as $key => $values ) {
37
+
38
+ $widgetClass = implode( '', array_map( 'ucfirst', explode( '_', $key ) ) );
39
+ $widgetClass = "GeminiLabs\SiteReviews\Widgets\\$widgetClass";
40
+
41
+ try {
42
+ // bypass register_widget() in order to pass our custom values to the widget
43
+ $widget = new $widgetClass( "{$this->app->id}_{$key}", $values['title'], $values );
44
+ $wp_widget_factory->widgets[ $widgetClass ] = $widget;
45
+ }
46
+ catch( Exception $e ) {
47
+ $this->app->make( 'Log\Logger' )->error( sprintf( 'Error registering widget. Message: %s "(%s:%s)"',
48
+ $e->getMessage(),
49
+ $e->getFile(),
50
+ $e->getLine()
51
+ ));
52
+ }
53
+ }
54
+ }
55
+ }
plugin/Handlers/SubmitReview.php ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+ use GeminiLabs\SiteReviews\Commands\SubmitReview as Command;
16
+ use ReflectionException;
17
+
18
+ class SubmitReview
19
+ {
20
+ /**
21
+ * @var App
22
+ */
23
+ protected $app;
24
+
25
+ /**
26
+ * @var Database
27
+ */
28
+ protected $db;
29
+
30
+ public function __construct( App $app )
31
+ {
32
+ $this->app = $app;
33
+ $this->db = $app->make( 'Database' );
34
+ }
35
+
36
+ /**
37
+ * @todo log any negative result of the sent notification
38
+ *
39
+ * return void
40
+ */
41
+ public function handle( Command $command )
42
+ {
43
+ $reviewId = 'local_' . md5( serialize( $command ) );
44
+
45
+ $review = [
46
+ 'author' => $command->reviewer,
47
+ 'avatar' => get_avatar_url( $command->email ),
48
+ 'content' => $command->content,
49
+ 'email' => $command->email,
50
+ 'ip_address' => $command->ipAddress,
51
+ 'rating' => $command->rating,
52
+ 'review_id' => $reviewId,
53
+ 'site_name' => 'local',
54
+ 'title' => $command->title,
55
+ ];
56
+
57
+ $review = apply_filters( 'site-reviews/local/review', $review, $command );
58
+
59
+ $post_id = $this->db->postReview( $reviewId, $review );
60
+
61
+ $this->sendNotification( $post_id, $command );
62
+
63
+ $message = __( 'Your review has been submitted!', 'site-reviews' );
64
+
65
+ if( $command->ajaxRequest ) {
66
+
67
+ $this->app->make( 'Session' )->clear();
68
+
69
+ return $message;
70
+ }
71
+ else {
72
+ // set message
73
+ $this->app->make( 'Session' )->set( "{$command->formId}-message", $message );
74
+
75
+ wp_redirect( $_SERVER['PHP_SELF'] );
76
+ exit;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param int $post_id
82
+ *
83
+ * @return array
84
+ */
85
+ protected function addNotificationLinks( $post_id, array $args )
86
+ {
87
+ if( $this->db->getOption( 'general.require.approval', false ) ) {
88
+
89
+ $review_approve_link = wp_nonce_url( admin_url( sprintf( 'post.php?post=%s&action=approve', $post_id ) ), 'approve-review_' . $post_id );
90
+ $review_discard_link = wp_nonce_url( admin_url( sprintf( 'post.php?post=%s&action=trash', $post_id ) ), 'trash-post_' . $post_id );
91
+ // $review_discard_link = esc_url( get_delete_post_link( $post_id, false ) );
92
+
93
+ $after = [];
94
+
95
+ $after[] = sprintf( '%1$s: <a href="%2$s">%2$s</a>', __( 'Approve', 'site-reviews' ), $review_approve_link );
96
+ $after[] = sprintf( '%1$s: <a href="%2$s">%2$s</a>', __( 'Discard', 'site-reviews' ), $review_discard_link );
97
+
98
+ $args['after'] = "\r\n\r\n" . implode( "\r\n\r\n", $after ); // makes each line a paragraph
99
+ }
100
+
101
+ return $args;
102
+ }
103
+
104
+ /**
105
+ * @return GeminiLabs\SiteReviews\Email
106
+ */
107
+ protected function createNotification( Command $command, array $args = [] )
108
+ {
109
+ $email = [
110
+ 'to' => $args['recipient'],
111
+ 'subject' => $args['notification_title'],
112
+ 'template' => 'review-notification',
113
+ 'template-tags' => [
114
+ 'review_author' => $command->reviewer,
115
+ 'review_content' => $command->content,
116
+ 'review_email' => $command->email,
117
+ 'review_ip' => $command->ipAddress,
118
+ 'review_link' => sprintf( '<a href="%1$s">%1$s</a>', $args['notification_link'] ),
119
+ 'review_rating' => $command->rating,
120
+ 'review_title' => $command->title,
121
+ ],
122
+ ];
123
+
124
+ // $email = $this->addNotificationLinks( $post_id, $email );
125
+
126
+ return $this->app->make( 'Email' )->compose( $email );
127
+ }
128
+
129
+ /**
130
+ * @param int $post_id
131
+ *
132
+ * @return null|bool|array|WP_Error
133
+ */
134
+ protected function sendNotification( $post_id, Command $command )
135
+ {
136
+ $notificationType = $this->db->getOption( 'general.notification', 'none' );
137
+
138
+ if( !in_array( $notificationType, ['default', 'custom', 'webhook'] ) )return;
139
+
140
+ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
141
+
142
+ $notificationTitle = sprintf( '[%s] %s',
143
+ $blogname,
144
+ sprintf( __( 'New %s-Star Review', 'site-reviews' ), $command->rating )
145
+ );
146
+
147
+ $args = [
148
+ 'notification_link' => esc_url( admin_url( sprintf( 'post.php?post=%s&action=edit', $post_id ) ) ),
149
+ 'notification_title' => $notificationTitle,
150
+ 'notification_type' => $notificationType,
151
+ ];
152
+
153
+ return $args['notification_type'] == 'webhook'
154
+ ? $this->sendNotificationWebhook( $command, $args )
155
+ : $this->sendNotificationEmail( $command, $args );
156
+ }
157
+
158
+ /**
159
+ * @return bool|null
160
+ */
161
+ protected function sendNotificationEmail( Command $command, array $args )
162
+ {
163
+ $args['recipient'] = 'default' === $args['notification_type']
164
+ ? get_option( 'admin_email' )
165
+ : $this->db->getOption( 'general.notification_email' );
166
+
167
+ // no email address has been set
168
+ if( empty( $args['recipient'] ) )return;
169
+
170
+ return $this->createNotification( $command, $args )->send();
171
+ }
172
+
173
+ /**
174
+ * @return null|array|WP_Error
175
+ */
176
+ protected function sendNotificationWebhook( Command $command, array $args )
177
+ {
178
+ if( !( $endpoint = $this->db->getOption( 'general.webhook_url' ) ) )return;
179
+
180
+ $fields = [];
181
+
182
+ $fields[] = ['title' => str_repeat( ':star:', $command->rating ) ];
183
+
184
+ if( $command->title ) {
185
+ $fields[] = ['title' => $command->title ];
186
+ }
187
+
188
+ if( $command->content ) {
189
+ $fields[] = ['value' => $command->content ];
190
+ }
191
+
192
+ if( $command->reviewer ) {
193
+ !$command->email ?: $command->email = sprintf( ' <%s>', $command->email );
194
+ $fields[] = ['value' => trim( sprintf( '%s%s - %s', $command->reviewer, $command->email, $command->ipAddress ) ) ];
195
+ }
196
+
197
+ $fields[] = ['value' => sprintf( '<%s|%s>', $args['notification_link'], __( 'View Review', 'site-reviews' ) ) ];
198
+
199
+ $notification = json_encode([
200
+ 'icon_url' => $this->app->url . 'assets/img/icon.png',
201
+ 'username' => $this->app->name,
202
+ 'attachments' => [
203
+ [
204
+ 'pretext' => $args['notification_title'],
205
+ 'color' => '#665068',
206
+ 'fallback' => $this->createNotification( $command, $args )->read( 'plaintext' ),
207
+ 'fields' => $fields,
208
+ ],
209
+ ],
210
+ ]);
211
+
212
+ return wp_remote_post( $endpoint, [
213
+ 'method' => 'POST',
214
+ 'timeout' => 45,
215
+ 'redirection' => 5,
216
+ 'httpversion' => '1.0',
217
+ 'blocking' => false,
218
+ 'sslverify' => false,
219
+ 'headers' => ['Content-Type' => 'application/json'],
220
+ 'body' => apply_filters( 'site-reviews/webhook/notification', $notification, $command ),
221
+ ]);
222
+ }
223
+ }
plugin/Handlers/TogglePinned.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Handlers;
12
+
13
+ use GeminiLabs\SiteReviews\Commands\TogglePinned as Command;
14
+ use GeminiLabs\SiteReviews\Notices;
15
+
16
+ class TogglePinned
17
+ {
18
+ /**
19
+ * @var Notices
20
+ */
21
+ protected $notices;
22
+
23
+ public function __construct( Notices $notices )
24
+ {
25
+ $this->notices = $notices;
26
+ }
27
+
28
+ public function handle( Command $command )
29
+ {
30
+ if( !get_post( $command->id ) ) {
31
+ return false;
32
+ }
33
+
34
+ if( is_null( $command->pinned ) ) {
35
+ $meta = get_post_meta( $command->id, 'pinned', true );
36
+ $command->pinned = !wp_validate_boolean( $meta );
37
+ }
38
+ else {
39
+ $notice = $command->pinned
40
+ ? __( 'The review is pinned.', 'site-reviews' )
41
+ : __( 'The review is unpinned.', 'site-reviews' );
42
+
43
+ $this->notices->addSuccess( $notice );
44
+ }
45
+
46
+ update_post_meta( $command->id, 'pinned', $command->pinned );
47
+
48
+ return $command->pinned;
49
+ }
50
+ }
plugin/Html.php ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+
16
+ class Html
17
+ {
18
+ /**
19
+ * @var App
20
+ */
21
+ protected $app;
22
+
23
+ /**
24
+ * @var array
25
+ */
26
+ protected $forms;
27
+
28
+ public function __construct( App $app )
29
+ {
30
+ $this->app = $app;
31
+ $this->forms = [];
32
+ }
33
+
34
+ /**
35
+ * Add a field to an existing form
36
+ *
37
+ * @param string $formId
38
+ *
39
+ * @return Form|false
40
+ */
41
+ public function addField( $formId, array $args = [] )
42
+ {
43
+ if( !$this->isForm( $formId ) )return;
44
+
45
+ return $this->forms[ $formId ]->addField( $args );
46
+ }
47
+
48
+ /**
49
+ * Create a new form
50
+ *
51
+ * @param string $formId
52
+ *
53
+ * @return Form
54
+ */
55
+ public function createForm( $formId, array $args = [] )
56
+ {
57
+ if( !$this->isForm( $formId ) ) {
58
+
59
+ $form = $this->app->make( 'Html\Form' )->normalize( $args );
60
+
61
+ $this->forms[ $formId ] = $form;
62
+ }
63
+
64
+ return $this->forms[ $formId ];
65
+ }
66
+
67
+ /**
68
+ * Return dependencies from all forms
69
+ *
70
+ * @return array
71
+ */
72
+ public function getDependencies()
73
+ {
74
+ $dependencies = [];
75
+
76
+ foreach( $this->forms as $form ) {
77
+ $dependencies = array_unique(
78
+ array_merge( $dependencies, $form->dependencies )
79
+ );
80
+ }
81
+
82
+ return $dependencies;
83
+ }
84
+
85
+ /**
86
+ * Return dependencies from a specific form
87
+ *
88
+ * @return array
89
+ */
90
+ public function getFormDependencies( $formId )
91
+ {
92
+ return $this->isForm( $formId )
93
+ ? $this->forms[ $formId ]->dependencies
94
+ : [];
95
+ }
96
+
97
+ /**
98
+ * Render a field (outside of a form)
99
+ *
100
+ * @param string|bool $print
101
+ *
102
+ * @return void|string
103
+ */
104
+ public function renderField( array $args = [], $print = 'print' )
105
+ {
106
+ $field = $this->app->make( 'Html\Field' )->normalize( $args );
107
+
108
+ return $field->render( $print );
109
+ }
110
+
111
+ /**
112
+ * Render a form then remove it
113
+ *
114
+ * @param string $formId
115
+ * @param string|bool $print
116
+ *
117
+ * @return void|string
118
+ */
119
+ public function renderForm( $formId, $print = 'print' )
120
+ {
121
+ if( !$this->isForm( $formId ) )return;
122
+
123
+ $form = $this->forms[ $formId ];
124
+
125
+ unset( $this->forms[ $formId ] );
126
+
127
+ return $form->render( $print );
128
+ }
129
+
130
+ /**
131
+ * Render a partial
132
+ *
133
+ * @param string $partialName
134
+ * @param string|bool $print
135
+ *
136
+ * @return void|string
137
+ */
138
+ public function renderPartial( $partialName, array $args = [], $print = 'print' )
139
+ {
140
+ $partial = $this->app->make( 'Html\Partial' )->normalize( $partialName, $args );
141
+
142
+ return $partial->render( $print );
143
+ }
144
+
145
+ /**
146
+ * Render a template
147
+ *
148
+ * @param string $templatePath
149
+ * @param string|bool $print
150
+ *
151
+ * @return void|string
152
+ */
153
+ public function renderTemplate( $templatePath, array $args = [], $print = 'print' )
154
+ {
155
+ $file = $this->app->path . "views/{$templatePath}.php";
156
+
157
+ if( !file_exists( $file ) )return;
158
+
159
+ ob_start();
160
+ include $file;
161
+ $template = ob_get_clean();
162
+
163
+ return $this->renderTemplateString( $template, $args, $print );
164
+ }
165
+
166
+ /**
167
+ * Render a template string
168
+ *
169
+ * @param string $template
170
+ * @param string|bool $print
171
+ *
172
+ * @return void|string
173
+ */
174
+ public function renderTemplateString( $template, array $args = [], $print = 'print' )
175
+ {
176
+ if( !empty( $args ) ) {
177
+ foreach( $args as $key => $value ) {
178
+ $template = str_replace( '{' . $key . '}', $value, $template );
179
+ }
180
+ }
181
+
182
+ $template = trim( $template );
183
+
184
+ if( !!$print && $print !== 'return' ) {
185
+ echo $template;
186
+ }
187
+
188
+ return $template;
189
+ }
190
+
191
+ /**
192
+ * Reset the forms array
193
+ *
194
+ * @return Html
195
+ */
196
+ public function reset()
197
+ {
198
+ $this->forms = [];
199
+
200
+ return $this;
201
+ }
202
+
203
+ /**
204
+ * Set dependencies of a form
205
+ *
206
+ * @param string $formId
207
+ * @param bool|string $overwrite
208
+ *
209
+ * @return void
210
+ */
211
+ public function setFormDependencies( $formId, array $dependencies, $overwrite = false )
212
+ {
213
+ if( $this->isForm( $formId ) )return;
214
+
215
+ if( !$overwrite || $overwrite == 'merge' ) {
216
+ $dependencies = array_unique(
217
+ array_merge( $this->forms[ $formId ]->dependencies, $dependencies )
218
+ );
219
+ }
220
+
221
+ $this->forms[ $formId ]->dependencies = $dependencies;
222
+ }
223
+
224
+ /**
225
+ * Return a stored form
226
+ *
227
+ * @return Form|false
228
+ */
229
+ public function switchForm( $formId )
230
+ {
231
+ if( $this->isForm( $formId ) ) {
232
+ return $this->forms[ $formId ];
233
+ }
234
+
235
+ return false;
236
+ }
237
+
238
+ /**
239
+ * Check if a form exists
240
+ *
241
+ * @param string $formId
242
+ *
243
+ * @return bool
244
+ */
245
+ protected function isForm( $formId )
246
+ {
247
+ return isset( $this->forms[ $formId ] );
248
+ }
249
+ }
plugin/Html/Field.php ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\App;
15
+ use ReflectionException;
16
+
17
+ class Field
18
+ {
19
+ /**
20
+ * @var App
21
+ */
22
+ protected $app;
23
+
24
+ /**
25
+ * @var array
26
+ */
27
+ protected $args;
28
+
29
+ /**
30
+ * @var array
31
+ */
32
+ protected $dependencies;
33
+
34
+ public function __construct( App $app )
35
+ {
36
+ $this->app = $app;
37
+ $this->args = [];
38
+ $this->dependencies = [];
39
+ }
40
+
41
+ /**
42
+ * @param string $property
43
+ *
44
+ * @return mixed
45
+ * @throws Exception
46
+ */
47
+ public function __get( $property )
48
+ {
49
+ switch( $property ) {
50
+ case 'args';
51
+ case 'dependencies';
52
+ return $this->$property;
53
+ }
54
+
55
+ throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $property );
56
+ }
57
+
58
+ /**
59
+ * Get a specific Field
60
+ *
61
+ * @return mixed GeminiLabs\SiteReviews\Html\Fields\*
62
+ */
63
+ public function getField( array $args = [] )
64
+ {
65
+ if( empty( $args ) ) {
66
+ $args = $this->args;
67
+ }
68
+
69
+ $className = sprintf( 'GeminiLabs\SiteReviews\Html\Fields\%s', ucfirst( $args['type'] ) );
70
+
71
+ if( !class_exists( $className ) ) {
72
+ throw new ReflectionException( "Class does not exist: {$className}" );
73
+ }
74
+
75
+ return (new $className( $args ));
76
+ }
77
+
78
+ /**
79
+ * Normalize the field arguments
80
+ *
81
+ * @return $this
82
+ */
83
+ public function normalize( array $args = [] )
84
+ {
85
+ $defaults = [
86
+ 'after' => '',
87
+ 'attributes' => '',
88
+ 'before' => '',
89
+ 'class' => '',
90
+ 'default' => null,
91
+ 'depends' => null,
92
+ 'desc' => '',
93
+ 'errors' => [],
94
+ 'inline' => false,
95
+ 'label' => '',
96
+ 'name' => '',
97
+ 'options' => [],
98
+ 'placeholder' => '',
99
+ 'prefix' => '',
100
+ 'render' => true,
101
+ 'suffix' => null,
102
+ 'type' => 'text',
103
+ 'value' => '',
104
+ ];
105
+
106
+ $args = $atts = wp_parse_args( $args, $defaults );
107
+
108
+ $args['attributes'] = $this->parseAttributes( $atts );
109
+ $args['id'] = $this->parseId( $atts );
110
+ $args['inline'] = $this->parseInline( $atts );
111
+ $args['type'] = $this->parseType( $atts );
112
+ $args['name'] = $this->parseName( $atts );
113
+ $args['options'] = (array) $atts['options']; // make sure this is always an array
114
+ $args['prefix'] = $this->parsePrefix( $atts );
115
+ $args['value'] = $this->parseValue( $atts );
116
+
117
+ $this->args = $args;
118
+
119
+ $this->dependencies = $this->getField( $args )->dependencies;
120
+
121
+ $this->setDataDepends();
122
+
123
+ $this->checkForErrors( $atts );
124
+
125
+ return $this;
126
+ }
127
+
128
+ /**
129
+ * Render the field
130
+ *
131
+ * @param mixed $print
132
+ *
133
+ * @return string|void
134
+ */
135
+ public function render( $print = true )
136
+ {
137
+ if( $this->args['render'] === false )return;
138
+
139
+ $field = $this->getField();
140
+
141
+ $class = 'glsr-field';
142
+ $class .= $this->args['errors'] ? ' glsr-has-error' : '';
143
+
144
+ $renderedString = '%s';
145
+
146
+ if( ( isset( $field->args['required'] ) && $field->args['required'] )
147
+ || ( isset( $field->args['attributes']['required'] ) || in_array( 'required', $field->args['attributes'] ) ) ) {
148
+ $class .= ' glsr-required';
149
+ }
150
+
151
+ if( $field->args['type'] !== 'hidden' ) {
152
+ $renderedString = sprintf( '<div class="%s">%%s</div>', $class );
153
+ }
154
+
155
+ $rendered = sprintf( $renderedString,
156
+ $this->args['before'] .
157
+ $field->generateLabel() .
158
+ $field->render() .
159
+ $this->args['after'] .
160
+ $this->args['errors']
161
+ );
162
+
163
+ $rendered = apply_filters( 'site-reviews/rendered/field', $rendered, $field->args['type'] );
164
+
165
+ if( !!$print && $print !== 'return' ) {
166
+ echo $rendered;
167
+ }
168
+
169
+ return $rendered;
170
+ }
171
+
172
+ /**
173
+ * Reset the Field
174
+ *
175
+ * @return self
176
+ */
177
+ public function reset()
178
+ {
179
+ $this->args = [];
180
+
181
+ return $this;
182
+ }
183
+
184
+ /**
185
+ * Check for form submission field errors
186
+ *
187
+ * @return void
188
+ */
189
+ protected function checkForErrors( array $atts )
190
+ {
191
+ $args = $this->args;
192
+
193
+ if( !array_key_exists( $atts['name'], $args['errors'] ) ) {
194
+ $this->args['errors'] = ''; // set to an empty string
195
+ return;
196
+ }
197
+
198
+ $field_errors = $args['errors'][ $atts['name'] ];
199
+
200
+ $errors = array_reduce( $field_errors['errors'], function( $carry, $error ) {
201
+ return $carry . sprintf( '<span>%s</span> ', $error );
202
+ });
203
+
204
+ $this->args['errors'] = sprintf( '<span class="glsr-field-errors">%s</span>', $errors );
205
+ }
206
+
207
+ /**
208
+ * Parse the field attributes and convert to an array if needed
209
+ *
210
+ * @return array
211
+ */
212
+ protected function parseAttributes( array $args )
213
+ {
214
+ if( empty( $args['attributes'] ) ) {
215
+ return [];
216
+ }
217
+
218
+ $attributes = (array) $args['attributes'];
219
+
220
+ foreach( $attributes as $key => $value ) {
221
+ if( is_string( $key ) )continue;
222
+ unset( $attributes[ $key ] );
223
+ if( !isset( $attributes[ $value ] ) ) {
224
+ $attributes[ $value ] = '';
225
+ }
226
+ }
227
+
228
+ return $attributes;
229
+ }
230
+
231
+ /**
232
+ * Parse the field ID from the field path
233
+ *
234
+ * @return null|string
235
+ */
236
+ protected function parseId( array $args )
237
+ {
238
+ if( isset( $args['id'] ) && !$args['id'] )return;
239
+
240
+ !$args['suffix'] ?: $args['suffix'] = "-{$args['suffix']}";
241
+
242
+ return str_replace( ['[]','[',']','.'], ['','-','','-'], $this->parseName( $args ) . $args['suffix'] );
243
+ }
244
+
245
+ /**
246
+ * Parse the field inline
247
+ *
248
+ * @return bool
249
+ */
250
+ protected function parseInline( array $args )
251
+ {
252
+ return false !== stripos( $args['type'], '_inline' )
253
+ ? true
254
+ : $args['inline'];
255
+ }
256
+
257
+ /**
258
+ * Parse the field name
259
+ *
260
+ * @return string
261
+ */
262
+ protected function parseName( array $args )
263
+ {
264
+ $name = $args['name'];
265
+ $prefix = $this->parsePrefix( $args );
266
+
267
+ if( $prefix === false ) {
268
+ return $name;
269
+ }
270
+
271
+ $paths = explode( '.', $name );
272
+
273
+ return array_reduce( $paths, function( $result, $value ) {
274
+ return $result .= "[$value]";
275
+ }, $prefix );
276
+ }
277
+
278
+ /**
279
+ * Parse the field prefix
280
+ *
281
+ * @return string|false
282
+ */
283
+ protected function parsePrefix( array $args )
284
+ {
285
+ $prefix = $args['prefix'];
286
+
287
+ return ( '' === trim( $prefix ) && $prefix !== false )
288
+ ? "{$this->app->prefix}_settings"
289
+ : $prefix;
290
+ }
291
+
292
+ /**
293
+ * Parse the field type
294
+ *
295
+ * @return string
296
+ */
297
+ protected function parseType( array $args )
298
+ {
299
+ $type = $args['type'];
300
+
301
+ return false !== stripos( $type, '_inline' )
302
+ ? str_replace( '_inline', '', $type )
303
+ : $type;
304
+ }
305
+
306
+ /**
307
+ * Parse the field value
308
+ *
309
+ * @return string
310
+ */
311
+ protected function parseValue( array $args )
312
+ {
313
+ $default = $args['default'];
314
+ $name = $args['name'];
315
+ $prefix = $args['prefix'];
316
+ $value = $args['value'];
317
+
318
+ if( $default == ':placeholder' ) {
319
+ $default = '';
320
+ }
321
+
322
+ return ( !empty( $value ) || !$name || $prefix === false )
323
+ ? $value
324
+ : $this->app->make( 'Database' )->getOption( $name, $default );
325
+ }
326
+
327
+ /**
328
+ * Get the [data-depends] attribute
329
+ *
330
+ * @return array|null
331
+ */
332
+ public function getDataDepends()
333
+ {
334
+ return $this->setDataDepends();
335
+ }
336
+
337
+ /**
338
+ * Set the [data-depends] attribute
339
+ *
340
+ * @return null|array
341
+ */
342
+ protected function setDataDepends()
343
+ {
344
+ if( !( $depends = $this->args['depends'] ) )return;
345
+
346
+ $name = $depends;
347
+ $value = true;
348
+
349
+ if( is_array( $depends ) ) {
350
+ reset( $depends );
351
+ $name = key( $depends );
352
+ $value = $depends[ $name ];
353
+ }
354
+
355
+ $name = $this->parseName([
356
+ 'name' => $name,
357
+ 'prefix' => $this->args['prefix'],
358
+ ]);
359
+
360
+ return $this->args['attributes']['data-depends'] = [
361
+ 'name' => $name,
362
+ 'value' => $value,
363
+ ];
364
+ }
365
+ }
plugin/Html/Fields/Base.php ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use Exception;
14
+ use GeminiLabs\SiteReviews\Html\Normalize;
15
+
16
+ abstract class Base
17
+ {
18
+ /**
19
+ * @var array
20
+ */
21
+ protected $args;
22
+
23
+ /**
24
+ * @var array
25
+ */
26
+ protected $dependencies = [];
27
+
28
+ /**
29
+ * Whether the field has multiple values
30
+ *
31
+ * @var bool
32
+ */
33
+ protected $multi = false;
34
+
35
+ /**
36
+ * Whether the field is rendered outside of the form table
37
+ *
38
+ * @var bool
39
+ */
40
+ protected $outside = false;
41
+
42
+ /**
43
+ * The field element tag (i.e. "input")
44
+ *
45
+ * @var string
46
+ */
47
+ protected $element;
48
+
49
+ public function __construct( array $args = [] )
50
+ {
51
+ $this->args = $args;
52
+ }
53
+
54
+ /**
55
+ * @param string $property
56
+ *
57
+ * @return mixed
58
+ * @throws Exception
59
+ */
60
+ public function __get( $property )
61
+ {
62
+ switch( $property ) {
63
+ case 'args';
64
+ case 'dependencies';
65
+ case 'element';
66
+ case 'multi';
67
+ case 'outside';
68
+ return $this->$property;
69
+ }
70
+
71
+ throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $property );
72
+ }
73
+
74
+ /**
75
+ * Generate the field description
76
+ *
77
+ * @param bool $paragraph
78
+ *
79
+ * @return null|string
80
+ */
81
+ public function generateDescription( $paragraph = true )
82
+ {
83
+ if( !isset( $this->args['desc'] ) || !$this->args['desc'] )return;
84
+
85
+ $tag = ( !!$paragraph || $paragraph == 'p' ) ? 'p' : 'span';
86
+
87
+ return sprintf( '<%1$s class="description">%2$s</%1$s>', $tag, $this->args['desc'] );
88
+ }
89
+
90
+ /**
91
+ * Generate the field label
92
+ *
93
+ * @return null|string
94
+ */
95
+ public function generateLabel()
96
+ {
97
+ if( empty( $this->args['label'] ) )return;
98
+
99
+ $for = !!$this->args['id']
100
+ ? " for=\"{$this->args['id']}\""
101
+ : '';
102
+
103
+ return sprintf( '<label%s>%s</label>', $for, $this->args['label'] );
104
+ }
105
+
106
+ /**
107
+ * Render this field type
108
+ *
109
+ * @return string
110
+ */
111
+ abstract public function render();
112
+
113
+ /**
114
+ * Convert a value to camel case.
115
+ *
116
+ * @param string $value
117
+ *
118
+ * @return string
119
+ */
120
+ protected function camelCase( $value )
121
+ {
122
+ $value = ucwords( str_replace( ['-', '_'], ' ', $value ) );
123
+
124
+ return lcfirst( str_replace( ' ', '', $value ) );
125
+ }
126
+
127
+ /**
128
+ * Implode the field attributes
129
+ *
130
+ * @return array
131
+ */
132
+ protected function implodeAttributes( $defaults = [] )
133
+ {
134
+ return $this->normalize( $defaults, 'implode' );
135
+ }
136
+
137
+ /**
138
+ * Implode multi-field items
139
+ *
140
+ * @return null|string
141
+ */
142
+ protected function implodeOptions( $method = 'select_option', $default = null )
143
+ {
144
+ $this->args['default'] ?: $this->args['default'] = $default;
145
+
146
+ $method = $this->camelCase( $method );
147
+
148
+ $method = method_exists( $this, $method )
149
+ ? $method
150
+ : 'selectOption';
151
+
152
+ $i = 0;
153
+
154
+ if( $method === 'singleInput' ) {
155
+
156
+ if( !isset( $this->args['options'] ) || empty( $this->args['options'] ) )return;
157
+
158
+ // hack to make sure unset single checkbox values start at 1 instead of 0
159
+ if( key( $this->args['options'] ) === 0 ) {
160
+ $options = ['1' => $this->args['options'][0]];
161
+ $this->args['options'] = $options;
162
+ }
163
+
164
+ return $this->singleInput();
165
+ }
166
+
167
+ return array_reduce( array_keys( $this->args['options'] ), function( $carry, $key ) use ( &$i, $method ) {
168
+ return $carry .= $this->$method( $key, $i++ );
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Normalize attributes for this specific field type
174
+ *
175
+ * @param bool|string $implode
176
+ *
177
+ * @return array
178
+ */
179
+ protected function normalize( array $defaults = [], $implode = false )
180
+ {
181
+ $args = $this->mergeAttributesWith( $defaults );
182
+
183
+ $normalize = new Normalize;
184
+
185
+ return ( $this->element && method_exists( $normalize, $this->element ) )
186
+ ? $normalize->{$this->element}( $args, $implode )
187
+ : ( !!$implode ? '' : [] );
188
+ }
189
+
190
+ /**
191
+ * Merge and overwrite empty $this->args values with $defaults
192
+ *
193
+ * @return array
194
+ */
195
+ protected function mergeAttributesWith( array $defaults )
196
+ {
197
+ // similar to array_merge except overwrite empty values
198
+ foreach( $defaults as $key => $value ) {
199
+ if( isset( $this->args[ $key ] ) && !empty( $this->args[ $key ] ) )continue;
200
+ $this->args[ $key ] = $value;
201
+ }
202
+
203
+ $attributes = $this->args['attributes'];
204
+
205
+ // prioritize $attributes over $this->args, don't worry about duplicates
206
+ return array_merge( $this->args, $attributes );
207
+ }
208
+
209
+ /**
210
+ * Generate checkboxes and radios
211
+ *
212
+ * @param string $optionKey
213
+ * @param string $number
214
+ * @param string $type
215
+ *
216
+ * @return null|string
217
+ */
218
+ protected function multiInput( $optionKey, $number, $type = 'radio' )
219
+ {
220
+ $args = $this->multiInputArgs( $type, $optionKey, $number );
221
+
222
+ if( !$args )return;
223
+
224
+ $attributes = '';
225
+
226
+ foreach( $args['attributes'] as $key => $val ) {
227
+ $attributes .= sprintf( '%s="%s" ', $key, $val );
228
+ }
229
+
230
+ return sprintf( '<li><label for="%s"><input %s%s/> %s</label></li>',
231
+ $args['attributes']['id'],
232
+ $attributes,
233
+ checked( $args['value'], $args['attributes']['value'], false ),
234
+ $args['label']
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Build the checkbox/radio args
240
+ *
241
+ * @param string $type
242
+ * @param string $optionName
243
+ * @param string $number
244
+ *
245
+ * @return array|null
246
+ */
247
+ protected function multiInputArgs( $type, $optionName, $number )
248
+ {
249
+ $defaults = [
250
+ 'class' => '',
251
+ 'name' => '',
252
+ 'type' => $type,
253
+ 'value' => '',
254
+ ];
255
+
256
+ $args = [];
257
+
258
+ $value = $this->args['options'][ $optionName ];
259
+
260
+ if( is_array( $value ) ) {
261
+ $args = $value;
262
+ }
263
+
264
+ if( is_string( $value ) ) {
265
+ $label = $value;
266
+ }
267
+
268
+ isset( $args['name'] ) ?: $args['name'] = $optionName;
269
+ isset( $args['value'] ) ?: $args['value'] = $optionName;
270
+
271
+ $args = wp_parse_args( $args, $defaults );
272
+
273
+ if( !isset( $label ) || $args['name'] === '' )return;
274
+
275
+ $args['id'] = $this->args['id'] . "-{$number}";
276
+ $args['name'] = $this->args['name'] . ( $type === 'checkbox' && $this->multi ? '[]' : '' );
277
+
278
+ $args = array_filter( $args, function( $value ) {
279
+ return $value !== '';
280
+ });
281
+
282
+ if( is_array( $this->args['value'] ) ) {
283
+ if( in_array( $args['value'], $this->args['value'] ) ) {
284
+ $this->args['default'] = $args['value'];
285
+ }
286
+ }
287
+ else if( $this->args['value'] ) {
288
+ $this->args['default'] = $this->args['value'];
289
+ }
290
+ else if( $type == 'radio' && !$this->args['default'] ) {
291
+ $this->args['default'] = 0;
292
+ }
293
+
294
+ return [
295
+ 'attributes' => $args,
296
+ 'label' => $label,
297
+ 'value' => $this->args['default'],
298
+ ];
299
+ }
300
+
301
+ /**
302
+ * Generate checkboxes
303
+ *
304
+ * @param string $optionKey
305
+ * @param string $number
306
+ *
307
+ * @return null|string
308
+ */
309
+ protected function multiInputCheckbox( $optionKey, $number )
310
+ {
311
+ return $this->multiInput( $optionKey, $number, 'checkbox' );
312
+ }
313
+
314
+ /**
315
+ * Generate select options
316
+ *
317
+ * @param string $optionKey
318
+ *
319
+ * @return string
320
+ */
321
+ protected function selectOption( $optionKey )
322
+ {
323
+ return sprintf( '<option value="%s"%s>%s</option>',
324
+ $optionKey,
325
+ selected( $this->args['value'], $optionKey, false ),
326
+ $this->args['options'][ $optionKey ]
327
+ );
328
+ }
329
+
330
+ /**
331
+ * Generate a single checkbox
332
+ *
333
+ * @param string $type
334
+ *
335
+ * @return null|string
336
+ */
337
+ protected function singleInput( $type = 'checkbox' )
338
+ {
339
+ $optionKey = key( $this->args['options'] );
340
+
341
+ $args = $this->multiInputArgs( $type, $optionKey, 1 );
342
+
343
+ if( !$args )return;
344
+
345
+ $atts = $this->normalize();
346
+ $atts = wp_parse_args( $args['attributes'], $atts );
347
+
348
+ $attributes = '';
349
+
350
+ foreach( $atts as $key => $val ) {
351
+ $attributes .= sprintf( '%s="%s" ', $key, $val );
352
+ }
353
+
354
+ return sprintf( '<label for="%s"><input %s%s/> %s</label>',
355
+ $atts['id'],
356
+ $attributes,
357
+ checked( $args['value'], $atts['value'], false ),
358
+ $args['label']
359
+ );
360
+ }
361
+ }
plugin/Html/Fields/Checkbox.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Checkbox extends Base
16
+ {
17
+ protected $element = 'input';
18
+
19
+ public function __construct( array $args = [] )
20
+ {
21
+ parent::__construct( $args );
22
+
23
+ if( count( $args['options'] ) > 1 ) {
24
+ $this->multi = true;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @return string
30
+ */
31
+ public function render()
32
+ {
33
+ $inline = $this->args['inline'] ? ' class="inline"' : '';
34
+
35
+ if( $this->multi ) {
36
+ return sprintf( '<ul%s>%s</ul>%s',
37
+ $inline,
38
+ $this->implodeOptions( 'multi_input_checkbox' ),
39
+ $this->generateDescription()
40
+ );
41
+ }
42
+
43
+ return sprintf( '%s%s',
44
+ $this->implodeOptions( 'single_input' ),
45
+ $this->generateDescription()
46
+ );
47
+ }
48
+ }
plugin/Html/Fields/Code.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Textarea;
14
+
15
+ class Code extends Textarea
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ return parent::render([
23
+ 'class' => 'large-text code',
24
+ ]);
25
+ }
26
+ }
plugin/Html/Fields/Colorpicker.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Colorpicker extends Text
16
+ {
17
+ protected $dependencies = ['wp-color-picker'];
18
+
19
+ /**
20
+ * @return string
21
+ */
22
+ public function render()
23
+ {
24
+ $value = $this->args['value'];
25
+
26
+ $hex_color = '(([a-fA-F0-9]){3}){1,2}$';
27
+
28
+ // Prepend "#" if it's missing
29
+ if( preg_match( '/^' . $hex_color . '/i', $value ) ) {
30
+ $this->args['value'] = "#{$value}";
31
+ }
32
+ // Reset value if it's bad
33
+ else if( !preg_match( '/^#' . $hex_color . '/i', $value ) ) {
34
+ $this->args['value'] = '';
35
+ }
36
+
37
+ return parent::render([
38
+ 'class' => 'color-picker-hex',
39
+ 'maxlength' => 7,
40
+ 'placeholder' => __( 'Hex Value', 'site-reviews' ),
41
+ 'attributes' => [
42
+ 'data-colorpicker' => json_encode([
43
+ 'palettes' => [
44
+ '#F44336',
45
+ '#E91E63',
46
+ '#9C27B0',
47
+ '#2196F3',
48
+ '#4CAF50',
49
+ '#FFEB3B',
50
+ '#FF9800',
51
+ '#795548',
52
+ '#9E9E9E',
53
+ ],
54
+ ]),
55
+ ],
56
+ ]);
57
+ }
58
+ }
plugin/Html/Fields/Email.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Text;
14
+
15
+ class Email extends Text
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ return parent::render([
23
+ 'class' => 'regular-text ltr',
24
+ 'type' => 'email',
25
+ ]);
26
+ }
27
+ }
plugin/Html/Fields/Heading.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Heading extends Base
16
+ {
17
+ protected $outside = true;
18
+
19
+ /**
20
+ * @return string
21
+ */
22
+ public function render()
23
+ {
24
+ isset( $this->args['desc'] ) ?: $this->args['desc'] = '';
25
+
26
+ // WP version check
27
+
28
+ return sprintf( '<h2 class="title">%s</h2>%s',
29
+ $this->args['value'],
30
+ wpautop( $this->args['desc'] )
31
+ );
32
+ }
33
+ }
plugin/Html/Fields/Hidden.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Text;
14
+
15
+ class Hidden extends Text
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ if( isset( $this->args['label'] ) ) {
23
+ unset( $this->args['label'] );
24
+ }
25
+
26
+ if( isset( $this->args['desc'] ) ) {
27
+ unset( $this->args['desc'] );
28
+ }
29
+
30
+ if( isset( $this->args['id'] ) ) {
31
+ unset( $this->args['id'] );
32
+ }
33
+
34
+ return parent::render([
35
+ 'class' => '',
36
+ 'type' => 'hidden',
37
+ ]);
38
+ }
39
+ }
plugin/Html/Fields/Number.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Text;
14
+
15
+ class Number extends Text
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ return parent::render([
23
+ 'class' => 'small-text',
24
+ 'min' => '0',
25
+ 'type' => 'number',
26
+ ]);
27
+ }
28
+ }
plugin/Html/Fields/Progress.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Progress extends Base
16
+ {
17
+ protected $value;
18
+
19
+ public function __construct( array $args = [] )
20
+ {
21
+ $value = intval( $args['value'] );
22
+
23
+ $this->value = ( 0 <= $value && $value <= 100 ) ? $value : 0; // 0-100
24
+
25
+ $args['name'] = '';
26
+ $args['value'] = '';
27
+
28
+ parent::__construct( $args );
29
+ }
30
+
31
+ /**
32
+ * @return string
33
+ */
34
+ public function render()
35
+ {
36
+ $defaults = [
37
+ 'data-active-text' => __( 'Please wait...', 'site-reviews' ),
38
+ 'data-inactive-text' => __( 'Inactive', 'site-reviews' ),
39
+ ];
40
+
41
+ $args = $this->mergeAttributesWith( $defaults );
42
+
43
+ preg_match( '/\bactive(?=$|\s)/', $args['class'], $matches );
44
+
45
+ $initialText = isset( $matches[0] )
46
+ ? $args['data-active-text']
47
+ : $args['data-inactive-text'];
48
+
49
+ $progressBar = '' .
50
+ '<div class="glsr-progress %5$s" data-inactive-text="%2$s" data-active-text="%3$s">' .
51
+ '<div class="glsr-progress-bar" style="width: %4$d%%;"><span>%1$s</span></div>' .
52
+ '<div class="glsr-progress-background"><span>%1$s</span></div>' .
53
+ '</div>';
54
+
55
+ return sprintf( $progressBar,
56
+ $initialText,
57
+ $args['data-inactive-text'],
58
+ $args['data-active-text'],
59
+ $this->value,
60
+ $args['class'],
61
+ $this->generateDescription()
62
+ );
63
+ }
64
+ }
plugin/Html/Fields/Radio.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Radio extends Base
16
+ {
17
+ protected $multi = true;
18
+ protected $element = 'input';
19
+
20
+ /**
21
+ * @return string
22
+ */
23
+ public function render( $default = null )
24
+ {
25
+ $inline = $this->args['inline'] ? ' class="inline"' : '';
26
+
27
+ return sprintf( '<ul%s>%s</ul>%s',
28
+ $inline,
29
+ $this->implodeOptions( 'multi_input', $default ),
30
+ $this->generateDescription()
31
+ );
32
+ }
33
+ }
plugin/Html/Fields/Roles.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Select;
14
+
15
+ class Roles extends Select
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ return parent::render([
23
+ 'options' => wp_roles()->get_names(),
24
+ ]);
25
+ }
26
+ }
plugin/Html/Fields/Select.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Select extends Base
16
+ {
17
+ protected $element = 'select';
18
+
19
+ /**
20
+ * @return string
21
+ */
22
+ public function render( array $defaults = [] )
23
+ {
24
+ $defaults = wp_parse_args( $defaults, [
25
+ 'type' => 'select',
26
+ ]);
27
+
28
+ return sprintf( '<select %s>%s</select>%s',
29
+ $this->implodeAttributes( $defaults ),
30
+ $this->implodeOptions( 'select_option' ),
31
+ $this->generateDescription()
32
+ );
33
+ }
34
+ }
plugin/Html/Fields/Submit.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Text;
14
+
15
+ class Submit extends Text
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ if( isset( $this->args['name'] ) ) {
23
+ $this->args['name'] = 'submit';
24
+ }
25
+
26
+ return parent::render([
27
+ 'class' => 'button button-primary',
28
+ 'type' => 'submit',
29
+ ]);
30
+ }
31
+ }
plugin/Html/Fields/Text.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Text extends Base
16
+ {
17
+ protected $element = 'input';
18
+
19
+ /**
20
+ * @return string
21
+ */
22
+ public function render( array $defaults = [] )
23
+ {
24
+ $defaults = wp_parse_args( $defaults, [
25
+ 'class' => 'regular-text',
26
+ 'type' => 'text',
27
+ ]);
28
+
29
+ return sprintf( '<input %s/>%s',
30
+ $this->implodeAttributes( $defaults ),
31
+ $this->generateDescription()
32
+ );
33
+ }
34
+ }
plugin/Html/Fields/Textarea.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Base;
14
+
15
+ class Textarea extends Base
16
+ {
17
+ protected $element = 'textarea';
18
+
19
+ /**
20
+ * @return string
21
+ */
22
+ public function render( array $defaults = [] )
23
+ {
24
+ $defaults = wp_parse_args( $defaults, [
25
+ 'class' => 'large-text',
26
+ 'rows' => 3,
27
+ 'type' => 'textarea',
28
+ ]);
29
+
30
+ return sprintf( '<textarea %s>%s</textarea>%s',
31
+ $this->implodeAttributes( $defaults ),
32
+ $this->args['value'],
33
+ $this->generateDescription()
34
+ );
35
+ }
36
+ }
plugin/Html/Fields/Url.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Text;
14
+
15
+ class Url extends Text
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ return parent::render([
23
+ 'class' => 'regular-text code',
24
+ 'type' => 'url',
25
+ ]);
26
+ }
27
+ }
plugin/Html/Fields/Yesno.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Fields;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Fields\Radio;
14
+
15
+ class Yesno extends Radio
16
+ {
17
+ /**
18
+ * @return string
19
+ */
20
+ public function render()
21
+ {
22
+ $defaultValue = 0;
23
+
24
+ $this->args['options'] = [
25
+ __( 'No', 'site-reviews' ),
26
+ __( 'Yes', 'site-reviews' ),
27
+ ];
28
+
29
+ return parent::render( $defaultValue );
30
+ }
31
+ }
plugin/Html/Form.php ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ use Exception;
16
+
17
+ class Form
18
+ {
19
+ /**
20
+ * @var App
21
+ */
22
+ protected $app;
23
+
24
+ /**
25
+ * @var array
26
+ */
27
+ protected $args;
28
+
29
+ /**
30
+ * @var array
31
+ */
32
+ protected $dependencies;
33
+
34
+ /**
35
+ * @var array
36
+ */
37
+ protected $fields;
38
+
39
+ public function __construct( App $app )
40
+ {
41
+ $this->app = $app;
42
+ $this->args = [];
43
+ $this->dependencies = [];
44
+ $this->fields = [];
45
+ }
46
+
47
+ /**
48
+ * @param string $property
49
+ *
50
+ * @return mixed
51
+ * @throws Exception
52
+ */
53
+ public function __get( $property )
54
+ {
55
+ switch( $property ) {
56
+ case 'args';
57
+ case 'dependencies';
58
+ case 'fields';
59
+ return $this->$property;
60
+ }
61
+
62
+ throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $property );
63
+ }
64
+
65
+ /**
66
+ * @param string $property
67
+ * @param string $value
68
+ *
69
+ * @return void
70
+ * @throws Exception
71
+ */
72
+ public function __set( $property, $value )
73
+ {
74
+ switch( $property ) {
75
+ case 'args':
76
+ case 'dependencies':
77
+ case 'fields':
78
+ $this->$property = $value;
79
+ break;
80
+ default:
81
+ throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $property );
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Add a field to the form
87
+ *
88
+ * @return Form
89
+ */
90
+ public function addField( array $args = [] )
91
+ {
92
+ $field = $this->app->make( 'Html\Field' )->normalize( $args );
93
+
94
+ if( $field->args['render'] !== false ) {
95
+ $this->dependencies = array_unique(
96
+ array_merge( $field->dependencies, $this->dependencies )
97
+ );
98
+ $this->fields[] = $field;
99
+ }
100
+
101
+ return $this;
102
+ }
103
+
104
+ /**
105
+ * Normalize the form arguments
106
+ *
107
+ * @return Form
108
+ */
109
+ public function normalize( array $args = [] )
110
+ {
111
+ $defaults = [
112
+ 'action' => '',
113
+ 'attributes' => '',
114
+ 'id' => '',
115
+ 'class' => '',
116
+ 'enctype' => 'multipart/form-data',
117
+ 'method' => 'post',
118
+ 'nonce' => '',
119
+ 'submit' => __( 'Submit', 'site-reviews' ),
120
+ ];
121
+
122
+ $this->args = array_merge( $defaults, $args );
123
+
124
+ $attributes = $this->app->make( 'Html\Normalize' )->form( $this->args, 'implode' );
125
+
126
+ $this->args['attributes'] = $attributes;
127
+
128
+ return $this;
129
+ }
130
+
131
+ /**
132
+ * Render the form
133
+ *
134
+ * @param mixed $print
135
+ *
136
+ * @return string|void
137
+ */
138
+ public function render( $print = true )
139
+ {
140
+ $rendered = sprintf( '<form %s>%s%s</form>',
141
+ $this->args['attributes'],
142
+ $this->generateFields(),
143
+ $this->generateSubmitButton()
144
+ );
145
+
146
+ if( !!$print && $print !== 'return' ) {
147
+ echo $rendered;
148
+ }
149
+
150
+ return $rendered;
151
+ }
152
+
153
+ /**
154
+ * Reset the Form
155
+ *
156
+ * @return Form
157
+ */
158
+ public function reset()
159
+ {
160
+ $this->args = [];
161
+ $this->dependencies = [];
162
+ $this->fields = [];
163
+
164
+ return $this;
165
+ }
166
+
167
+ /**
168
+ * Generate the hidden fields of a wp-admin form
169
+ *
170
+ * @return string
171
+ */
172
+ protected function generateAdminFields()
173
+ {
174
+ ob_start();
175
+
176
+ settings_fields( $this->args['nonce'] );
177
+ do_settings_sections( $this->args['nonce'] );
178
+
179
+ return ob_get_clean();
180
+ }
181
+
182
+ /**
183
+ * Generate the form fields
184
+ *
185
+ * @return string
186
+ */
187
+ protected function generateFields()
188
+ {
189
+ $hiddenFields = '';
190
+
191
+ $fields = array_reduce( $this->fields, function( $carry, $formField ) use ( &$hiddenFields ) {
192
+
193
+ $stringLegend = '<legend class="screen-reader-text"><span>%s</span></legend>';
194
+ $stringFieldset = '<fieldset%s>%s%s</fieldset>';
195
+ $stringRendered = '<tr class="glsr-field %s"><th scope="row">%s</th><td>%s</td></tr>';
196
+ $outsideRendered = '</tbody></table>%s<table class="form-table"><tbody>';
197
+
198
+ $field = $formField->getField();
199
+ $multi = $field->multi === true;
200
+ $label = $field->generateLabel();
201
+ $rendered = $field->render();
202
+
203
+ // render hidden inputs outside the table
204
+ if( $field->args['type'] === 'hidden' ) {
205
+ $hiddenFields .= $rendered;
206
+ return $carry;
207
+ }
208
+
209
+ $hiddenClass = $this->isFieldHidden( $formField ) ? 'hidden' : '';
210
+
211
+ if( $multi ) {
212
+ if( $depends = $formField->getDataDepends() ) {
213
+ $depends = sprintf( ' data-depends=\'%s\'', json_encode( $depends ) );
214
+ }
215
+
216
+ $legend = $label ? sprintf( $stringLegend, $label ) : '';
217
+ $rendered = sprintf( $stringFieldset, $depends, $legend, $rendered );
218
+ }
219
+
220
+ $renderedField = $field->outside
221
+ ? sprintf( $outsideRendered, $rendered )
222
+ : sprintf( $stringRendered, $hiddenClass, $label, $rendered );
223
+
224
+ return $carry . $renderedField;
225
+ });
226
+
227
+ $hiddenFields .= is_admin()
228
+ ? $this->generateAdminFields()
229
+ : $this->generatePublicFields();
230
+
231
+ return sprintf( '<table class="form-table"><tbody>%s</tbody></table>%s', $fields, $hiddenFields );
232
+ }
233
+
234
+ /**
235
+ * Generate the hidden fields of a front-end form
236
+ *
237
+ * @return null|string
238
+ */
239
+ protected function generatePublicFields()
240
+ {
241
+ }
242
+
243
+ /**
244
+ * Generate the form submit button
245
+ *
246
+ * @return null|string
247
+ */
248
+ protected function generateSubmitButton()
249
+ {
250
+ $args = $this->args['submit'];
251
+
252
+ is_array( $args ) ?: $args = ['text' => $args ];
253
+
254
+ $args = shortcode_atts([
255
+ 'text' => __( 'Save Changes', 'site-reviews' ),
256
+ 'type' => 'primary',
257
+ 'name' => 'submit',
258
+ 'wrap' => true,
259
+ 'other_attributes' => null,
260
+ ], $args );
261
+
262
+ if( is_admin() ) {
263
+ ob_start();
264
+ submit_button( $args['text'], $args['type'], $args['name'], $args['wrap'], $args['other_attributes'] );
265
+ return ob_get_clean();
266
+ }
267
+ }
268
+
269
+ /**
270
+ * @param object $field GeminiLabs\SiteReviews\Html\Fields\*
271
+ *
272
+ * @return bool|null
273
+ */
274
+ protected function isFieldHidden( $field )
275
+ {
276
+ if( !( $dependsOn = $field->getDataDepends() ) )return;
277
+
278
+ foreach( $this->fields as $formField ) {
279
+ if( $dependsOn['name'] !== $formField->args['name'] )continue;
280
+
281
+ if( is_array( $dependsOn['value'] ) ) {
282
+ return !in_array( $formField->args['value'], $dependsOn['value'] );
283
+ }
284
+
285
+ return $dependsOn['value'] != $formField->args['value'];
286
+ }
287
+ }
288
+ }
plugin/Html/Normalize.php ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html;
12
+
13
+ class Normalize
14
+ {
15
+ /**
16
+ * @var array
17
+ */
18
+ protected $args;
19
+
20
+ /**
21
+ * @var array
22
+ */
23
+ protected $booleanAttributes;
24
+
25
+ /**
26
+ * @var array
27
+ */
28
+ protected $formAttributes;
29
+
30
+ /**
31
+ * @var array
32
+ */
33
+ protected $globalAttributes;
34
+
35
+ /**
36
+ * @var array
37
+ */
38
+ protected $globalWildcardAttributes;
39
+
40
+ /**
41
+ * @var array
42
+ */
43
+ protected $inputAttributes;
44
+
45
+ /**
46
+ * @var array
47
+ */
48
+ protected $inputTypes;
49
+
50
+ /**
51
+ * @var array
52
+ */
53
+ protected $selectAttributes;
54
+
55
+ /**
56
+ * @var array
57
+ */
58
+ protected $textareaAttributes;
59
+
60
+ public function __construct( array $args = [] )
61
+ {
62
+ $this->args = $args;
63
+
64
+ $this->booleanAttributes = apply_filters( 'site-reviews/normalize/boolean/attributes', [
65
+ 'autofocus',
66
+ 'capture',
67
+ 'checked',
68
+ 'disabled',
69
+ 'draggable',
70
+ 'formnovalidate',
71
+ 'hidden',
72
+ 'multiple',
73
+ 'novalidate',
74
+ 'readonly',
75
+ 'required',
76
+ 'selected',
77
+ 'spellcheck',
78
+ 'webkitdirectory',
79
+ ]);
80
+
81
+ $this->formAttributes = apply_filters( 'site-reviews/normalize/form/attributes', [
82
+ 'accept',
83
+ 'accept-charset',
84
+ 'action',
85
+ 'autocapitalize',
86
+ 'autocomplete',
87
+ 'enctype',
88
+ 'method',
89
+ 'name',
90
+ 'novalidate',
91
+ 'target',
92
+ ]);
93
+
94
+ $this->globalAttributes = apply_filters( 'site-reviews/normalize/global/attributes', [
95
+ 'accesskey',
96
+ 'class',
97
+ 'contenteditable',
98
+ 'contextmenu',
99
+ 'dir',
100
+ 'draggable',
101
+ 'dropzone',
102
+ 'hidden',
103
+ 'id',
104
+ 'lang',
105
+ 'spellcheck',
106
+ 'style',
107
+ 'tabindex',
108
+ 'title',
109
+ ]);
110
+
111
+ $this->globalWildcardAttributes = apply_filters( 'site-reviews/normalize/global/wildcard/attributes', [
112
+ 'aria-',
113
+ 'data-',
114
+ 'item',
115
+ 'on',
116
+ ]);
117
+
118
+ $this->inputAttributes = apply_filters( 'site-reviews/normalize/input/attributes', [
119
+ 'accept',
120
+ 'autocapitalize',
121
+ 'autocomplete',
122
+ 'autocorrect',
123
+ 'autofocus',
124
+ 'capture',
125
+ 'checked',
126
+ 'disabled',
127
+ 'form',
128
+ 'formaction',
129
+ 'formenctype',
130
+ 'formmethod',
131
+ 'formnovalidate',
132
+ 'formtarget',
133
+ 'height',
134
+ 'incremental',
135
+ 'inputmode',
136
+ 'list',
137
+ 'max',
138
+ 'maxlength',
139
+ 'min',
140
+ 'minlength',
141
+ 'mozactionhint',
142
+ 'multiple',
143
+ 'name',
144
+ 'pattern',
145
+ 'placeholder',
146
+ 'readonly',
147
+ 'required',
148
+ 'results',
149
+ 'selectionDirection',
150
+ 'size',
151
+ 'src',
152
+ 'step',
153
+ 'type',
154
+ 'value',
155
+ 'webkitdirectory',
156
+ 'width',
157
+ 'x-moz-errormessage',
158
+ ]);
159
+
160
+ $this->inputTypes = apply_filters( 'site-reviews/normalize/input/types', [
161
+ 'button',
162
+ 'checkbox',
163
+ 'color',
164
+ 'date',
165
+ 'datetime',
166
+ 'datetime-local',
167
+ 'email',
168
+ 'file',
169
+ 'hidden',
170
+ 'image',
171
+ 'max',
172
+ 'min',
173
+ 'month',
174
+ 'number',
175
+ 'password',
176
+ 'radio',
177
+ 'range',
178
+ 'reset',
179
+ 'search',
180
+ 'step',
181
+ 'submit',
182
+ 'tel',
183
+ 'text',
184
+ 'time',
185
+ 'url',
186
+ 'value',
187
+ 'week',
188
+ ]);
189
+
190
+ $this->selectAttributes = apply_filters( 'site-reviews/normalize/select/attributes', [
191
+ 'autofocus',
192
+ 'disabled',
193
+ 'form',
194
+ 'multiple',
195
+ 'name',
196
+ 'required',
197
+ 'size',
198
+ ]);
199
+
200
+ $this->textareaAttributes = apply_filters( 'site-reviews/normalize/textarea/attributes', [
201
+ 'autocapitalize',
202
+ 'autocomplete',
203
+ 'autofocus',
204
+ 'cols',
205
+ 'disabled',
206
+ 'form',
207
+ 'maxlength',
208
+ 'minlength',
209
+ 'name',
210
+ 'placeholder',
211
+ 'readonly',
212
+ 'required',
213
+ 'rows',
214
+ 'selectionDirection',
215
+ 'selectionEnd',
216
+ 'selectionStart',
217
+ 'wrap',
218
+ ]);
219
+ }
220
+
221
+ /**
222
+ * Normalize form attributes
223
+ *
224
+ * @return array|string
225
+ */
226
+ public function form( array $args = [], $implode = false )
227
+ {
228
+ $attributes = $this->parseAttributes( $this->formAttributes, $args );
229
+
230
+ return $this->maybeImplode( $attributes, $implode );
231
+ }
232
+
233
+ /**
234
+ * Normalize input attributes
235
+ *
236
+ * @return array|string
237
+ */
238
+ public function input( array $args = [], $implode = false )
239
+ {
240
+ $this->filterInputType();
241
+
242
+ $attributes = $this->parseAttributes( $this->inputAttributes, $args );
243
+
244
+ return $this->maybeImplode( $attributes, $implode );
245
+ }
246
+
247
+ /**
248
+ * Possibly implode attributes into a string
249
+ *
250
+ * @param bool|string $implode
251
+ *
252
+ * @return array|string
253
+ */
254
+ public function maybeImplode( array $attributes, $implode = true )
255
+ {
256
+ if( !$implode || $implode !== 'implode' ) {
257
+ return $attributes;
258
+ }
259
+
260
+ $results = [];
261
+
262
+ foreach( $attributes as $key => $value ) {
263
+
264
+ // if data attributes, use single quotes in case of json encoded values
265
+ $quotes = false !== stripos( $key, 'data-' ) ? "'" : '"';
266
+
267
+ if( is_array( $value ) ) {
268
+ $value = json_encode( $value );
269
+ $quotes = "'";
270
+ }
271
+
272
+ $results[] = is_string( $key )
273
+ ? sprintf( '%1$s=%3$s%2$s%3$s', $key, $value, $quotes )
274
+ : $value;
275
+ }
276
+
277
+ return implode( ' ', $results );
278
+ }
279
+
280
+ /**
281
+ * Normalize select attributes
282
+ *
283
+ * @return array|string
284
+ */
285
+ public function select( array $args = [], $implode = false )
286
+ {
287
+ $attributes = $this->parseAttributes( $this->selectAttributes, $args );
288
+
289
+ return $this->maybeImplode( $attributes, $implode );
290
+ }
291
+
292
+ /**
293
+ * Normalize textarea attributes
294
+ *
295
+ * @return array|string
296
+ */
297
+ public function textarea( array $args = [], $implode = false )
298
+ {
299
+ $attributes = $this->parseAttributes( $this->textareaAttributes, $args );
300
+
301
+ return $this->maybeImplode( $attributes, $implode );
302
+ }
303
+
304
+ /**
305
+ * Filter attributes by the provided attrribute keys and remove any non-boolean keys
306
+ * with empty values
307
+ *
308
+ * @return array
309
+ */
310
+ protected function filterAttributes( array $attributeKeys )
311
+ {
312
+ $filtered = array_intersect_key( $this->args, array_flip( $attributeKeys ));
313
+
314
+ // normalize truthy boolean attributes
315
+ foreach( $filtered as $key => $value ) {
316
+ if( !in_array( $key, $this->booleanAttributes ) )continue;
317
+
318
+ if( $value === false ) {
319
+ unset( $filtered[ $key ] );
320
+ }
321
+ else {
322
+ $filtered[ $key ] = '';
323
+ }
324
+ }
325
+
326
+ $filteredKeys = array_filter( array_keys( $filtered ), function( $key ) use ( $filtered ) {
327
+ return !(
328
+ empty( $filtered[ $key ] )
329
+ && !is_numeric( $filtered[ $key ] )
330
+ && !in_array( $key, $this->booleanAttributes )
331
+ );
332
+ });
333
+
334
+ return array_intersect_key( $filtered, array_flip( $filteredKeys ) );
335
+ }
336
+
337
+ /**
338
+ * @return array
339
+ */
340
+ protected function filterGlobalAttributes()
341
+ {
342
+ $global = $this->filterAttributes( $this->globalAttributes );
343
+
344
+ $wildcards = [];
345
+
346
+ foreach( $this->globalWildcardAttributes as $wildcard ) {
347
+
348
+ foreach( $this->args as $key => $value ) {
349
+
350
+ $length = strlen( $wildcard );
351
+ $result = substr( $key, 0, $length) === $wildcard;
352
+
353
+ if( $result ) {
354
+ // only allow data attributes to have an empty value
355
+ if( $wildcard != 'data-' && empty( $value ) )continue;
356
+
357
+ if( is_array( $value ) ) {
358
+
359
+ if( $wildcard != 'data-' )continue;
360
+
361
+ $value = json_encode( $value );
362
+ }
363
+
364
+ $wildcards[ $key ] = $value;
365
+ }
366
+ }
367
+ }
368
+
369
+ return array_merge( $global, $wildcards );
370
+ }
371
+
372
+ /**
373
+ * @return void
374
+ */
375
+ protected function filterInputType()
376
+ {
377
+ if( !isset( $this->args['type'] ) || !in_array( $this->args['type'], $this->inputTypes ) ) {
378
+ $this->args['type'] = 'text';
379
+ }
380
+ }
381
+
382
+ /**
383
+ * @return array
384
+ */
385
+ protected function parseAttributes( array $attributes, array $args = [] )
386
+ {
387
+ if( !empty( $args ) ) {
388
+ $this->args = array_change_key_case( $args );
389
+ }
390
+
391
+ $global = $this->filterGlobalAttributes();
392
+ $local = $this->filterAttributes( $attributes );
393
+
394
+ return array_merge( $global, $local );
395
+ }
396
+ }
plugin/Html/Partial.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use ReflectionException;
15
+
16
+ class Partial
17
+ {
18
+ /**
19
+ * @var App
20
+ */
21
+ protected $app;
22
+
23
+ /**
24
+ * @var array
25
+ */
26
+ protected $args;
27
+
28
+ public function __construct( App $app )
29
+ {
30
+ $this->app = $app;
31
+ $this->args = [];
32
+ }
33
+
34
+ /**
35
+ * Normalize the partial arguments
36
+ *
37
+ * @return $this
38
+ */
39
+ public function normalize( $name, array $args = [] )
40
+ {
41
+ $defaults = [
42
+ 'partial' => $name,
43
+ ];
44
+
45
+ $this->args = wp_parse_args( $args, $defaults );
46
+
47
+ return $this;
48
+ }
49
+
50
+ /**
51
+ * Render the partial
52
+ *
53
+ * @param mixed $print
54
+ *
55
+ * @return string|void
56
+ */
57
+ public function render( $print = true )
58
+ {
59
+ $className = sprintf( 'GeminiLabs\SiteReviews\Html\Partials\%s', ucfirst( $this->args['partial'] ) );
60
+
61
+ $instance = $this->app->make( $className );
62
+
63
+ $instance->args = $this->args;
64
+
65
+ $rendered = $instance->render();
66
+
67
+ $rendered = apply_filters( 'site-reviews/rendered/partial', $rendered, $this->args['partial'] );
68
+
69
+ if( !!$print && $print !== 'return' ) {
70
+ echo $rendered;
71
+ }
72
+
73
+ return $rendered;
74
+ }
75
+ }
plugin/Html/Partials/Addon.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Addon extends Base
16
+ {
17
+ /**
18
+ * Generate an add-on element
19
+ *
20
+ * @return string
21
+ */
22
+ public function render()
23
+ {
24
+ $defaults = [
25
+ 'name' => '',
26
+ 'title' => '',
27
+ 'description' => '',
28
+ 'link' => '',
29
+ ];
30
+
31
+ $args = shortcode_atts( $defaults, $this->args );
32
+
33
+ extract( $args );
34
+
35
+ $addonPoster = $link
36
+ ? sprintf( '<a href="%s" class="glsr-addon-screenshot" data-name="%s"></a>', $link, $name )
37
+ : sprintf( '<div class="glsr-addon-screenshot" data-name="%s"></div>', $name );
38
+
39
+ $addonLink = $link
40
+ ? sprintf( '<a href="%s" class="glsr-addon-link button button-secondary">%s</a>', $link, __( 'More Info', 'site-reviews' ) )
41
+ : '';
42
+
43
+ return '' .
44
+ '<div class="glsr-addon-wrap"><div class="glsr-addon">' .
45
+ $addonPoster .
46
+ sprintf( '<div class="glsr-addon-description"><p>%s</p></div>', $description ) .
47
+ sprintf( '<h3 class="glsr-addon-name">%s</h3>%s', $title, $addonLink ) .
48
+ '</div></div>';
49
+ }
50
+ }
plugin/Html/Partials/Base.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ abstract class Base
16
+ {
17
+ /**
18
+ * @var App
19
+ */
20
+ public $app;
21
+
22
+ /**
23
+ * @var array
24
+ */
25
+ public $args = [];
26
+
27
+ public function __construct( App $app )
28
+ {
29
+ $this->app = $app;
30
+ }
31
+
32
+ /**
33
+ * Generate select option
34
+ *
35
+ * @param string $value
36
+ * @param string $title
37
+ * @param string $selected
38
+ *
39
+ * @return string
40
+ */
41
+ protected function selectOption( $value, $title, $selected )
42
+ {
43
+ return sprintf( '<option value="%s" %s>%s</option>',
44
+ $value,
45
+ selected( $selected, $value, false ),
46
+ $title
47
+ );
48
+ }
49
+ }
plugin/Html/Partials/Filterby.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Filterby extends Base
16
+ {
17
+ /**
18
+ * Generate a filter dropdown for the admin post_type table
19
+ *
20
+ * @return string|null
21
+ */
22
+ public function render()
23
+ {
24
+ $defaults = [
25
+ 'name' => '',
26
+ 'values' => [],
27
+ ];
28
+
29
+ $args = shortcode_atts( $defaults, $this->args );
30
+
31
+ extract( $args );
32
+
33
+ if( !$name || empty( $values ) )return;
34
+
35
+ $options = '';
36
+
37
+ foreach( $values as $value => $title ) {
38
+ $options .= $this->selectOption( $value, $title, filter_input( INPUT_GET, $name ) );
39
+ }
40
+
41
+ printf( '<select name="%s">%s</select>', $name, $options );
42
+ }
43
+ }
plugin/Html/Partials/Rating.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Rating extends Base
16
+ {
17
+ /**
18
+ * Generate a star rating
19
+ *
20
+ * @return string
21
+ */
22
+ public function render()
23
+ {
24
+ $defaults = [
25
+ 'stars' => 5,
26
+ ];
27
+
28
+ $args = shortcode_atts( $defaults, $this->args );
29
+
30
+ extract( $args );
31
+
32
+ $rating = '';
33
+
34
+ $star = is_admin() ? ' star star%s' : '%s';
35
+ $star = sprintf( '<span class="glsr-star%s"></span>', $star );
36
+
37
+ $class = 'glsr-review-rating' . ( is_admin() ? ' star-rating' : '' );
38
+
39
+ for( $i = 0; $i < $stars; $i++ ) {
40
+ $rating .= sprintf( $star, '-full' );
41
+ }
42
+
43
+ for( $i = 5; $i > $stars; $i-- ) {
44
+ $rating .= sprintf( $star, '-empty' );
45
+ }
46
+
47
+ return sprintf( '<span class="%s">%s</span>', $class, $rating );
48
+ }
49
+ }
plugin/Html/Partials/Reviews.php ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Reviews extends Base
16
+ {
17
+ /**
18
+ * Generate a review
19
+ *
20
+ * @return string
21
+ */
22
+ public function render()
23
+ {
24
+ global $post;
25
+
26
+ $defaults = [
27
+ 'class' => '',
28
+ 'display' => 'title',
29
+ 'max_reviews' => '',
30
+ 'min_rating' => '',
31
+ 'order_by' => '',
32
+ 'pagination' => false,
33
+ 'show_author' => false,
34
+ 'show_date' => false,
35
+ 'show_link' => false,
36
+ 'show_rating' => false,
37
+ 'site_name' => '',
38
+ 'word_limit' => 55,
39
+ ];
40
+
41
+ $args = shortcode_atts( $defaults, $this->args );
42
+
43
+ $reviews = $this->app->make( 'Database' )->getReviews( $args );
44
+
45
+ if( $reviews->have_posts() ) {
46
+
47
+ $html = '';
48
+
49
+ while( $reviews->have_posts() ) :
50
+
51
+ $reviews->the_post();
52
+
53
+ $meta = get_post_meta( $post->ID );
54
+ $meta = (object) array_map( 'array_shift', $meta );
55
+
56
+ $review_author = $this->reviewAuthor( $args['show_author'], $meta->author );
57
+ $review_excerpt = $this->reviewExcerpt( $args, $meta, $post->ID );
58
+ $review_meta = $this->reviewMeta( $args, $meta, $post->ID );
59
+ $review_title = $this->reviewTitle( $args, $meta, $post->ID );
60
+
61
+ $print_both = $review_title . $review_meta . $review_excerpt;
62
+ $print_excerpt = $review_meta . $review_excerpt;
63
+ $print_title = $review_title . $review_meta;
64
+
65
+ $html .= sprintf( '<div class="glsr-review">%s%s</div>', ${"print_{$args['display']}"}, $review_author );
66
+
67
+ endwhile;
68
+
69
+ if( $args['pagination'] ) {
70
+ $html .= $this->buildPagination( $reviews->max_num_pages );
71
+ }
72
+
73
+ wp_reset_postdata();
74
+ }
75
+ else {
76
+ $html = sprintf( '<p class="glsr-review glsr-no-reviews">%s</p>',
77
+ __( 'No reviews were found.', 'site-reviews' )
78
+ );
79
+ }
80
+
81
+ return sprintf( '<div class="glsr-reviews-wrap"><div class="glsr-reviews %s">%s</div></div>',
82
+ $args['class'],
83
+ $html
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Build an excerpt from a string
89
+ *
90
+ * @param string $text
91
+ * @param int $wordCount
92
+ * @param string $more
93
+ *
94
+ * @return string
95
+ */
96
+ protected function buildExcerpt( $text, $wordCount = 55, $more = null )
97
+ {
98
+ $text = strip_shortcodes( $text );
99
+ $text = wptexturize( $text );
100
+ $text = convert_smilies( $text );
101
+ $text = wpautop( $text );
102
+ $text = str_replace( ']]>', ']]&gt;', $text );
103
+
104
+ if( apply_filters( 'site-reviews/reviews/use_excerpt', true ) ) {
105
+ $wordCount = apply_filters( 'site-reviews/reviews/excerpt_length', $wordCount );
106
+ $text = wp_trim_words( $text, $wordCount, $more );
107
+ }
108
+
109
+ return $text;
110
+ }
111
+
112
+ /**
113
+ * Build the reviews pagination
114
+ *
115
+ * @param int $maxPageNum
116
+ *
117
+ * @return string|null
118
+ */
119
+ protected function buildPagination( $maxPageNum )
120
+ {
121
+ if( $maxPageNum < 2 )return;
122
+
123
+ $paged = $this->app->make( 'Database' )->getCurrentPageNumber();
124
+ $theme = wp_get_theme()->get( 'TextDomain' );
125
+
126
+ if( in_array( $theme, ['twentyten','twentyeleven','twentytwelve','twentythirteen'] ) ) {
127
+
128
+ $links = '';
129
+
130
+ if( $paged > 1 ) {
131
+ $links .= sprintf( '<div class="nav-previous"><a href="%s">%s</a></div>',
132
+ get_pagenum_link( $paged - 1 ),
133
+ __( '<span class="meta-nav">&larr;</span> Previous', 'site-reviews' )
134
+ );
135
+ }
136
+ if( $paged < $maxPageNum ) {
137
+ $links .= sprintf( '<div class="nav-next"><a href="%s">%s</a></div>',
138
+ get_pagenum_link( $paged + 1 ),
139
+ __( 'Next <span class="meta-nav">&rarr;</span>', 'site-reviews' )
140
+ );
141
+ }
142
+ }
143
+ else {
144
+ $links = paginate_links([
145
+ 'before_page_number' => '<span class="meta-nav screen-reader-text">' . __( 'Page', 'site-reviews' ) . ' </span>',
146
+ 'current' => $paged,
147
+ 'mid_size' => 1,
148
+ 'next_text' => __( 'Next &rarr;', 'site-reviews' ),
149
+ 'prev_text' => __( '&larr; Previous', 'site-reviews' ),
150
+ 'total' => $maxPageNum,
151
+ ]);
152
+ }
153
+
154
+ $links = apply_filters( 'site-reviews/reviews/navigation_links', $links, $paged, $maxPageNum );
155
+
156
+ if( !$links )return;
157
+
158
+ return $this->paginationTemplate( $links );
159
+ }
160
+
161
+ /**
162
+ * Get the correct pagination template
163
+ *
164
+ * @param string $links
165
+ *
166
+ * @return string
167
+ */
168
+ protected function paginationTemplate( $links )
169
+ {
170
+ $class = '';
171
+ $theme = wp_get_theme()->get( 'TextDomain' );
172
+
173
+ switch( $theme ) {
174
+
175
+ case 'twentyten':
176
+ case 'twentyeleven':
177
+ case 'twentytwelve':
178
+ $template = '<nav class="navigation" role="navigation">%3$s</nav>';
179
+ break;
180
+ case 'twentyfourteen':
181
+ $class = 'paging-navigation';
182
+ $template = '' .
183
+ '<nav class="navigation %1$s" role="navigation">' .
184
+ '<h2 class="screen-reader-text">%2$s</h2>' .
185
+ '<div class="pagination loop-pagination">%3$s</div>' .
186
+ '</nav>';
187
+ break;
188
+ default:
189
+ $class = 'pagination';
190
+ $template = '' .
191
+ '<nav class="navigation %1$s" role="navigation">' .
192
+ '<h2 class="screen-reader-text">%2$s</h2>' .
193
+ '<div class="nav-links">%3$s</div>' .
194
+ '</nav>';
195
+ }
196
+
197
+ $template = apply_filters( 'navigation_markup_template', $template, $class );
198
+
199
+ $screenReaderText = __( 'Site Reviews navigation', 'site-reviews' );
200
+
201
+ return sprintf( $template, $class, $screenReaderText, $links );
202
+ }
203
+
204
+ /**
205
+ * Build the review author string
206
+ *
207
+ * @param string $author
208
+ * @param string $metaAuthor
209
+ *
210
+ * @return string
211
+ */
212
+ protected function reviewAuthor( $author, $metaAuthor )
213
+ {
214
+ return wp_validate_boolean( $author )
215
+ ? sprintf( '<p class="glsr-review-author">&mdash;%s</p> ', $metaAuthor )
216
+ : '';
217
+ }
218
+
219
+ /**
220
+ * Build the review excerpt string
221
+ *
222
+ * @param object $meta
223
+ * @param string $postId
224
+ *
225
+ * @return string
226
+ */
227
+ protected function reviewExcerpt( array $args, $meta, $postId )
228
+ {
229
+ $excerpt = $this->buildExcerpt( get_the_content( $postId ), $args['word_limit'] );
230
+ $review_link = $this->reviewLink( $args['show_link'], $meta->url );
231
+
232
+ $use_excerpt_as_link = apply_filters( 'site-reviews/reviews/use_excerpt_as_link', false )
233
+ && in_array( $args['display'], ['both', 'excerpt'] )
234
+ && !empty( $review_link );
235
+
236
+ $show_excerpt_read_more = !apply_filters( 'site-reviews/reviews/hide_excerpt_read_more', false )
237
+ ? $review_link
238
+ : '';
239
+
240
+ $review_excerpt = $use_excerpt_as_link
241
+ ? sprintf( '<a href="%s" target="_blank">%s</a>', esc_url( $meta->url ), $excerpt )
242
+ : "{$excerpt} {$show_excerpt_read_more}";
243
+
244
+ return !empty( $review_excerpt )
245
+ ? sprintf( '<p class="glsr-review-excerpt">%s</p>', $review_excerpt )
246
+ : '';
247
+ }
248
+
249
+ /**
250
+ * Build the review url
251
+ *
252
+ * @param string $link
253
+ * @param string $metaLink
254
+ *
255
+ * @return string
256
+ */
257
+ protected function reviewLink( $link, $metaLink )
258
+ {
259
+ return wp_validate_boolean( $link )
260
+ ? sprintf( '<span class="glsr-review-link">[<a href="%s" target="_blank">%s</a>]</span>', esc_url( $metaLink ), __( 'read more', 'site-reviews' ) )
261
+ : '';
262
+ }
263
+
264
+ /**
265
+ * Build the review meta string
266
+ *
267
+ * @param object $meta
268
+ * @param string $postId
269
+ *
270
+ * @return string
271
+ */
272
+ protected function reviewMeta( array $args, $meta, $postId )
273
+ {
274
+ $rating = wp_validate_boolean( $args['show_rating'] )
275
+ ? $this->app->make( 'Html' )->renderPartial( 'rating', ['stars' => $meta->rating ], 'return' )
276
+ : '';
277
+
278
+ $date = wp_validate_boolean( $args['show_date'] )
279
+ ? sprintf( '<span class="glsr-review-date">%s</span> ', get_the_date( 'M j, Y', $postId ) )
280
+ : '';
281
+
282
+ return ( $rating || $date )
283
+ ? sprintf( '<p class="glsr-review-meta">%s%s</p>', $rating, $date )
284
+ : '';
285
+ }
286
+
287
+ /**
288
+ * Build the review title string
289
+ *
290
+ * @param object $meta
291
+ * @param string $postId
292
+ *
293
+ * @return string
294
+ */
295
+ protected function reviewTitle( array $args, $meta, $postId )
296
+ {
297
+ $review_link = $this->reviewLink( $args['show_link'], $meta->url );
298
+
299
+ $use_title_as_link = apply_filters( 'site-reviews/widget/use_title_as_link', false )
300
+ && in_array( $args['display'], ['both', 'excerpt'] )
301
+ && !empty( $review_link );
302
+
303
+ $review_title = $use_title_as_link
304
+ ? sprintf( '<a href="%s" target="_blank">%s</a>', esc_url( $meta->url ), get_the_title( $postId ) )
305
+ : get_the_title( $postId );
306
+
307
+ return !empty( $review_title )
308
+ ? sprintf( '<h3 class="glsr-review-title">%s</h3>', $review_title )
309
+ : '';
310
+ }
311
+ }
plugin/Html/Partials/Subsubsub.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Subsubsub extends Base
16
+ {
17
+ /**
18
+ * Generate tabs
19
+ *
20
+ * @return null|string
21
+ */
22
+ public function render()
23
+ {
24
+ $defaults = [
25
+ 'page' => '',
26
+ 'tabs' => [],
27
+ 'tab' => '',
28
+ 'section' => '',
29
+ ];
30
+
31
+ $args = shortcode_atts( $defaults, $this->args );
32
+
33
+ extract( $args );
34
+
35
+ if( !isset( $tabs[ $tab ]['sections'] ) || count( $tabs[ $tab ]['sections'] ) < 2 )return;
36
+
37
+ $linkEls = array_reduce( array_keys( $tabs[ $tab ]['sections'] ),
38
+ function( $result, $key ) use ( $page, $tabs, $tab, $section ) {
39
+
40
+ $sections = $tabs[ $tab ]['sections'];
41
+
42
+ return $result . sprintf( '<li><a href="?post_type=site-review&page=%s&tab=%s&section=%s"%s>%s</a>%s</li>',
43
+ $page,
44
+ $tab,
45
+ $key,
46
+ $section == $key ? ' class="current"' : null,
47
+ $sections[ $key ],
48
+ end( $sections ) !== $sections[ $key ] ? ' | ' : ''
49
+ );
50
+ });
51
+
52
+ return sprintf( '<ul class="subsubsub glsr-subsubsub">%s</ul>', $linkEls );
53
+ }
54
+ }
plugin/Html/Partials/Tabs.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Html\Partials;
12
+
13
+ use GeminiLabs\SiteReviews\Html\Partials\Base;
14
+
15
+ class Tabs extends Base
16
+ {
17
+ /**
18
+ * Generate tabs
19
+ *
20
+ * @return null|string
21
+ */
22
+ public function render()
23
+ {
24
+ $defaults = [
25
+ 'page' => '',
26
+ 'tabs' => [],
27
+ 'tab' => '',
28
+ ];
29
+
30
+ $args = shortcode_atts( $defaults, $this->args );
31
+
32
+ extract( $args );
33
+
34
+ if( count( $tabs ) < 2 )return;
35
+
36
+ $links = array_reduce( array_keys( $tabs ),
37
+ function( $result, $key ) use ( $page, $tabs, $tab ) {
38
+ return $result . sprintf( '<a href="?post_type=site-review&page=%s&tab=%s" class="nav-tab%s">%s</a>',
39
+ $page,
40
+ $key,
41
+ (0 === strpos( $tab, $key )) ? ' nav-tab-active' : null,
42
+ $tabs[ $key ]['title']
43
+ );
44
+ });
45
+
46
+ return sprintf( '<h2 class="nav-tab-wrapper">%s</h2>', $links );
47
+ }
48
+ }
plugin/Log/LogLevel.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace GeminiLabs\SiteReviews\Log;
4
+
5
+ /**
6
+ * Describes log levels
7
+ */
8
+ class LogLevel
9
+ {
10
+ const EMERGENCY = 'emergency';
11
+ const ALERT = 'alert';
12
+ const CRITICAL = 'critical';
13
+ const ERROR = 'error';
14
+ const WARNING = 'warning';
15
+ const NOTICE = 'notice';
16
+ const INFO = 'info';
17
+ const DEBUG = 'debug';
18
+ }
plugin/Log/Logger.php ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace GeminiLabs\SiteReviews\Log;
4
+
5
+ use GeminiLabs\SiteReviews\Log\LoggerInterface;
6
+ use GeminiLabs\SiteReviews\Log\LogLevel;
7
+
8
+ class Logger implements LoggerInterface
9
+ {
10
+ protected $file;
11
+ protected $id;
12
+ protected $log;
13
+ protected $prefix;
14
+
15
+ /**
16
+ * Load/Create the log file
17
+ *
18
+ * @param string $filename
19
+ * @param string $prefix
20
+ *
21
+ * @return Logger
22
+ */
23
+ public static function file( $filename, $prefix )
24
+ {
25
+ $logger = new static;
26
+
27
+ $logger->file = $filename;
28
+ $logger->log = file_exists( $filename ) ? file_get_contents( $filename ) : 'No log';
29
+ $logger->prefix = $prefix;
30
+
31
+ return $logger;
32
+ }
33
+
34
+ public function __toString()
35
+ {
36
+ return $this->log;
37
+ }
38
+
39
+ /**
40
+ * Download the log file
41
+ *
42
+ * @return void
43
+ */
44
+ public function download()
45
+ {
46
+ if( !current_user_can( 'install_plugins' ) )return;
47
+
48
+ nocache_headers();
49
+
50
+ header( 'Content-Type: text/plain' );
51
+ header( 'Content-Disposition: attachment; filename="log.txt"' );
52
+
53
+ echo wp_strip_all_tags( $this->log );
54
+
55
+ exit;
56
+ }
57
+
58
+ /**
59
+ * System is unusable.
60
+ *
61
+ * @param string $message
62
+ * @param array $context
63
+ *
64
+ * @return void
65
+ */
66
+ public function emergency( $message, array $context = [] )
67
+ {
68
+ $this->log( LogLevel::EMERGENCY, $message, $context );
69
+ }
70
+
71
+ /**
72
+ * Action must be taken immediately.
73
+ *
74
+ * Example: Entire website down, database unavailable, etc. This should
75
+ * trigger the SMS alerts and wake you up.
76
+ *
77
+ * @param string $message
78
+ * @param array $context
79
+ *
80
+ * @return void
81
+ */
82
+ public function alert( $message, array $context = [] )
83
+ {
84
+ $this->log( LogLevel::ALERT, $message, $context );
85
+ }
86
+
87
+ /**
88
+ * Critical conditions.
89
+ *
90
+ * Example: Application component unavailable, unexpected exception.
91
+ *
92
+ * @param string $message
93
+ * @param array $context
94
+ *
95
+ * @return void
96
+ */
97
+ public function critical( $message, array $context = [] )
98
+ {
99
+ $this->log( LogLevel::CRITICAL, $message, $context );
100
+ }
101
+
102
+ /**
103
+ * Runtime errors that do not require immediate action but should typically
104
+ * be logged and monitored.
105
+ *
106
+ * @param string $message
107
+ * @param array $context
108
+ *
109
+ * @return void
110
+ */
111
+ public function error( $message, array $context = [] )
112
+ {
113
+ $this->log( LogLevel::ERROR, $message, $context );
114
+ }
115
+
116
+ /**
117
+ * Exceptional occurrences that are not errors.
118
+ *
119
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
120
+ * that are not necessarily wrong.
121
+ *
122
+ * @param string $message
123
+ * @param array $context
124
+ *
125
+ * @return void
126
+ */
127
+ public function warning( $message, array $context = [] )
128
+ {
129
+ $this->log( LogLevel::WARNING, $message, $context );
130
+ }
131
+
132
+ /**
133
+ * Normal but significant events.
134
+ *
135
+ * @param string $message
136
+ * @param array $context
137
+ *
138
+ * @return void
139
+ */
140
+ public function notice( $message, array $context = [] )
141
+ {
142
+ $this->log( LogLevel::NOTICE, $message, $context );
143
+ }
144
+
145
+ /**
146
+ * Interesting events.
147
+ *
148
+ * Example: User logs in, SQL logs.
149
+ *
150
+ * @param string $message
151
+ * @param array $context
152
+ *
153
+ * @return void
154
+ */
155
+ public function info( $message, array $context = [] )
156
+ {
157
+ $this->log( LogLevel::INFO, $message, $context );
158
+ }
159
+
160
+ /**
161
+ * Detailed debug information.
162
+ *
163
+ * @param string $message
164
+ * @param array $context
165
+ *
166
+ * @return void
167
+ */
168
+ public function debug( $message, array $context = [] )
169
+ {
170
+ $this->log( LogLevel::DEBUG, $message, $context );
171
+ }
172
+
173
+ /**
174
+ * Logs with an arbitrary level.
175
+ *
176
+ * @param mixed $level
177
+ * @param string $message
178
+ * @param array $context
179
+ *
180
+ * @return void
181
+ */
182
+ public function log( $level, $message, array $context = [] )
183
+ {
184
+ // Check if logging is enabled
185
+ if( get_option( "{$this->prefix}_logging" ) != 1 )return;
186
+
187
+ $reflection = new \ReflectionClass( __NAMESPACE__ . '\LogLevel' );
188
+
189
+ $levels = $reflection->getConstants();
190
+
191
+ $custom_levels = array_intersect( apply_filters( 'site-reviews/logger/levels', $levels ), $levels );
192
+ $custom_levels = array_keys( array_flip( $custom_levels ) );
193
+
194
+ $levels = empty( $custom_levels ) ? $levels : $custom_levels;
195
+
196
+ // Check if log level is allowed.
197
+ if( !in_array( $level, $levels, true ) )return;
198
+
199
+ $date = get_date_from_gmt( gmdate('Y-m-d H:i:s') );
200
+ $level = strtoupper( $level );
201
+ $message = $this->interpolate( $message, $context );
202
+ $entry = "[{$date}] {$level}: $message" . PHP_EOL;
203
+
204
+ if( $this->log == 'Log is empty' ) {
205
+ file_put_contents( $this->file, '' );
206
+ }
207
+
208
+ file_put_contents( $this->file, $entry, FILE_APPEND|LOCK_EX );
209
+ }
210
+
211
+ /**
212
+ * Wrapper for print_r()
213
+ *
214
+ * @return void
215
+ */
216
+ public function display()
217
+ {
218
+ call_user_func_array( [ $this, 'print_r' ], func_get_args() );
219
+ }
220
+
221
+ /**
222
+ * Capture print_r() output as variable
223
+ *
224
+ * @return string
225
+ */
226
+ public function capture()
227
+ {
228
+ ob_start();
229
+ call_user_func_array( [ $this, 'display' ], func_get_args() );
230
+ return ob_get_clean();
231
+ }
232
+
233
+ /**
234
+ * Wrapper for print_r()
235
+ *
236
+ * @param mixed $value ...
237
+ *
238
+ * @return void
239
+ */
240
+ public function print_r( $value )
241
+ {
242
+ if( func_num_args() == 1 ) {
243
+ printf( '<div class="print__r"><pre>%s</pre></div>',
244
+ htmlspecialchars( print_r( $value, true ), ENT_QUOTES, 'UTF-8' )
245
+ );
246
+ }
247
+ else {
248
+ echo '<div class="print__r_group">';
249
+ foreach( func_get_args() as $param ) {
250
+ $this->print_r( $param );
251
+ }
252
+ echo '</div>';
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Clear the log file
258
+ *
259
+ * @return void
260
+ */
261
+ public function clear()
262
+ {
263
+ $log = 'Log is empty';
264
+
265
+ file_put_contents( $this->file, $log );
266
+
267
+ $this->log = $log;
268
+ }
269
+
270
+ /**
271
+ * Interpolates context values into the message placeholders.
272
+ *
273
+ * @param string $message
274
+ * @param array $context
275
+ *
276
+ * @return array|string
277
+ */
278
+ protected function interpolate( $message, array $context = [] )
279
+ {
280
+ if( is_array( $message ) ) {
281
+ return $message;
282
+ }
283
+
284
+ // build a replacement array with braces around the context keys
285
+ $replace = [];
286
+
287
+ foreach( $context as $key => $val ) {
288
+
289
+ if( is_object( $val ) && get_class( $val ) === 'DateTime' ) {
290
+ $val = $val->format( 'Y-m-d H:i:s' );
291
+ }
292
+ else if( is_object( $val ) || is_array( $val ) ) {
293
+ $val = json_encode( $val );
294
+ }
295
+ else if( is_resource( $val ) ) {
296
+ $val = (string) $val;
297
+ }
298
+
299
+ $replace['{' . $key . '}'] = $val;
300
+ }
301
+
302
+ // interpolate replacement values into the message and return
303
+ return strtr( $message, $replace );
304
+ }
305
+ }
plugin/Log/LoggerInterface.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace GeminiLabs\SiteReviews\Log;
4
+
5
+ /**
6
+ * Describes a logger instance
7
+ *
8
+ * The message MUST be a string or object implementing __toString().
9
+ *
10
+ * The message MAY contain placeholders in the form: {foo} where foo
11
+ * will be replaced by the context data in key "foo".
12
+ *
13
+ * The context array can contain arbitrary data, the only assumption that
14
+ * can be made by implementors is that if an Exception instance is given
15
+ * to produce a stack trace, it MUST be in a key named "exception".
16
+ *
17
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
18
+ * for the full interface specification.
19
+ */
20
+ interface LoggerInterface
21
+ {
22
+ /**
23
+ * System is unusable.
24
+ *
25
+ * @param string $message
26
+ * @param array $context
27
+ *
28
+ * @return void
29
+ */
30
+ public function emergency( $message, array $context = [] );
31
+
32
+ /**
33
+ * Action must be taken immediately.
34
+ *
35
+ * Example: Entire website down, database unavailable, etc. This should
36
+ * trigger the SMS alerts and wake you up.
37
+ *
38
+ * @param string $message
39
+ * @param array $context
40
+ *
41
+ * @return void
42
+ */
43
+ public function alert( $message, array $context = [] );
44
+
45
+ /**
46
+ * Critical conditions.
47
+ *
48
+ * Example: Application component unavailable, unexpected exception.
49
+ *
50
+ * @param string $message
51
+ * @param array $context
52
+ *
53
+ * @return void
54
+ */
55
+ public function critical( $message, array $context = [] );
56
+
57
+ /**
58
+ * Runtime errors that do not require immediate action but should typically
59
+ * be logged and monitored.
60
+ *
61
+ * @param string $message
62
+ * @param array $context
63
+ *
64
+ * @return void
65
+ */
66
+ public function error( $message, array $context = [] );
67
+
68
+ /**
69
+ * Exceptional occurrences that are not errors.
70
+ *
71
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
72
+ * that are not necessarily wrong.
73
+ *
74
+ * @param string $message
75
+ * @param array $context
76
+ *
77
+ * @return void
78
+ */
79
+ public function warning( $message, array $context = [] );
80
+
81
+ /**
82
+ * Normal but significant events.
83
+ *
84
+ * @param string $message
85
+ * @param array $context
86
+ *
87
+ * @return void
88
+ */
89
+ public function notice( $message, array $context = [] );
90
+
91
+ /**
92
+ * Interesting events.
93
+ *
94
+ * Example: User logs in, SQL logs.
95
+ *
96
+ * @param string $message
97
+ * @param array $context
98
+ *
99
+ * @return void
100
+ */
101
+ public function info( $message, array $context = [] );
102
+
103
+ /**
104
+ * Detailed debug information.
105
+ *
106
+ * @param string $message
107
+ * @param array $context
108
+ *
109
+ * @return void
110
+ */
111
+ public function debug( $message, array $context = [] );
112
+
113
+ /**
114
+ * Logs with an arbitrary level.
115
+ *
116
+ * @param mixed $level
117
+ * @param string $message
118
+ * @param array $context
119
+ *
120
+ * @return void
121
+ */
122
+ public function log( $level, $message, array $context = [] );
123
+ }
plugin/Notices.php ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ class Notices
16
+ {
17
+ /**
18
+ * @var App
19
+ */
20
+ protected $app;
21
+
22
+ public function __construct( App $app )
23
+ {
24
+ $this->app = $app;
25
+ }
26
+
27
+ /**
28
+ * Add a notice
29
+ *
30
+ * @param string $type
31
+ * @param string|array $message
32
+ */
33
+ public function add( $type, $message, array $args = [] )
34
+ {
35
+ if( empty( $type ) || empty( $message ) )return;
36
+
37
+ $defaults = [
38
+ 'dismissible' => true,
39
+ 'inline' => true,
40
+ 'messages' => [],
41
+ 'type' => '',
42
+ ];
43
+
44
+ $args = (object) shortcode_atts( $defaults, $args );
45
+
46
+ $args->messages = is_wp_error( $message )
47
+ ? (array) $message->get_error_message()
48
+ : (array) $message;
49
+
50
+ $args->type = in_array( $type, ['error','warning','success'] )
51
+ ? $type
52
+ : 'success';
53
+
54
+ add_settings_error( $this->app->id, '', $args );
55
+ }
56
+
57
+ /**
58
+ * Add an error notice
59
+ *
60
+ * @param string|array $message
61
+ */
62
+ public function addError( $message, array $args = [] )
63
+ {
64
+ $this->add( 'error', $message, $args );
65
+ }
66
+
67
+ /**
68
+ * Add a success notice
69
+ *
70
+ * @param string|array $message
71
+ */
72
+ public function addSuccess( $message, array $args = [] )
73
+ {
74
+ $this->add( 'success', $message, $args );
75
+ }
76
+
77
+ /**
78
+ * Add a warning notice
79
+ *
80
+ * @param string|array $message
81
+ */
82
+ public function addWarning( $message, array $args = [] )
83
+ {
84
+ $this->add( 'warning', $message, $args );
85
+ }
86
+
87
+ /**
88
+ * Show all notices
89
+ *
90
+ * @param bool $sanitize
91
+ * @param bool $hide_on_update
92
+ */
93
+ public function show( $print = true, $sanitize = false, $hide_on_update = false )
94
+ {
95
+ global $wp_settings_errors;
96
+
97
+ if( $hide_on_update && !empty( $_GET['settings-updated'] ) )return;
98
+
99
+ $settings_errors = get_settings_errors( $this->app->id, $sanitize );
100
+
101
+ // make sure each notice is unique
102
+ $unique_notices = array_map( 'unserialize', array_unique( array_map( 'serialize', $settings_errors ) ) );
103
+
104
+ if( empty( $unique_notices ) )return;
105
+
106
+ // Empty $wp_settings_errors in case ajax is being used (we don't want to keep old notices)
107
+ $wp_settings_errors = [];
108
+ $notices = [];
109
+
110
+ foreach( $unique_notices as $key => $notice ) {
111
+ if( is_string( $notice['message'] ) ) {
112
+ $notices[] = $this->returnNotice( $notice['type'], true, true, $notice['message'] );
113
+ }
114
+ else {
115
+ $notices[] = $this->returnNotice(
116
+ $notice['message']->type,
117
+ $notice['message']->inline,
118
+ $notice['message']->dismissible,
119
+ $notice['message']->messages
120
+ );
121
+ }
122
+ }
123
+
124
+ $notices = implode( '', $notices );
125
+
126
+ if( $print ) {
127
+ echo $notices;
128
+ }
129
+ else {
130
+ return $notices;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Print a notice to the page
136
+ *
137
+ * @param string $type
138
+ * @param bool $inline
139
+ * @param bool $dismissible
140
+ * @param mixed $messages
141
+ */
142
+ public function printNotice( $type, $inline, $dismissible, $messages )
143
+ {
144
+ global $wp_version;
145
+
146
+ $type = $type == 'updated' ? 'success' : $type;
147
+
148
+ // WP 4.0 support
149
+ if( version_compare( $wp_version, '4.1', '<' ) ) {
150
+ $type = $type == 'error' ? 'error error' : "$type updated";
151
+ }
152
+
153
+ if( is_string( $messages ) ) {
154
+ $messages = (array) $messages;
155
+ }
156
+
157
+ array_walk( $messages, function( &$message ) {
158
+ $value = is_wp_error( $message )
159
+ ? $message->get_error_message()
160
+ : $message;
161
+ $message = sprintf( '<p>%s</p>', $value );
162
+ });
163
+
164
+ printf( '<div class="notice notice-%s%s%s">%s</div>',
165
+ $type,
166
+ $inline ? ' inline' : '',
167
+ $dismissible ? ' is-dismissible' : '',
168
+ implode( '', $messages )
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Return a notice
174
+ *
175
+ * @param string $type
176
+ * @param bool $inline
177
+ * @param bool $dismissible
178
+ * @param mixed $messages
179
+ */
180
+ public function returnNotice( $type, $inline, $dismissible, $messages )
181
+ {
182
+ ob_start();
183
+ $this->printNotice( $type, $inline, $dismissible, $messages );
184
+ return ob_get_clean();
185
+ }
186
+ }
plugin/Providers/MainProvider.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Providers;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use GeminiLabs\SiteReviews\Log\Logger;
15
+ use GeminiLabs\SiteReviews\Providers\ProviderInterface;
16
+
17
+ /**
18
+ * Note: We're using the full "namespace\classname" because "::class" isn't supported in PHP 5.4
19
+ */
20
+ class MainProvider implements ProviderInterface
21
+ {
22
+ public function register( App $app )
23
+ {
24
+ // Bind the Reviews instance itself to the container
25
+ $app->bind( 'GeminiLabs\SiteReviews\App', $app );
26
+
27
+ // Initialise logger from log file
28
+ $app->bind( 'GeminiLabs\SiteReviews\Log\Logger', function( App $app )
29
+ {
30
+ return Logger::file( trailingslashit( $app->path ) . 'debug.log', $app->prefix );
31
+ });
32
+
33
+ // Singletons must be last since they call "make()"
34
+ $app->singleton(
35
+ 'GeminiLabs\SiteReviews\Controllers\AjaxController',
36
+ 'GeminiLabs\SiteReviews\Controllers\AjaxController'
37
+ );
38
+
39
+ $app->singleton(
40
+ 'GeminiLabs\SiteReviews\Html',
41
+ 'GeminiLabs\SiteReviews\Html'
42
+ );
43
+
44
+ $app->singleton(
45
+ 'GeminiLabs\SiteReviews\Controllers\MainController',
46
+ 'GeminiLabs\SiteReviews\Controllers\MainController'
47
+ );
48
+
49
+ $app->singleton(
50
+ 'GeminiLabs\SiteReviews\Controllers\ReviewController',
51
+ 'GeminiLabs\SiteReviews\Controllers\ReviewController'
52
+ );
53
+
54
+ $app->singleton(
55
+ 'GeminiLabs\SiteReviews\Session',
56
+ 'GeminiLabs\SiteReviews\Session'
57
+ );
58
+
59
+ $app->singleton(
60
+ 'GeminiLabs\SiteReviews\Settings',
61
+ 'GeminiLabs\SiteReviews\Settings'
62
+ );
63
+ }
64
+ }
plugin/Providers/ProviderInterface.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews\Providers;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ interface ProviderInterface
16
+ {
17
+ /**
18
+ * @return void
19
+ */
20
+ public function register( App $app );
21
+ }
plugin/Router.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+
15
+ class Router
16
+ {
17
+ /**
18
+ * @var App
19
+ */
20
+ protected $app;
21
+
22
+ protected $id;
23
+ protected $prefix;
24
+
25
+ public function __construct( App $app )
26
+ {
27
+ $this->app = $app;
28
+ $this->id = $app->id;
29
+ $this->prefix = $app->prefix;
30
+ }
31
+
32
+ /**
33
+ * @param string $prefix
34
+ * @param string $action
35
+ *
36
+ * @return string
37
+ */
38
+ public function getMethodName( $prefix, $action )
39
+ {
40
+ $callback = function( $matches ) { return strtoupper( $matches[1] ); };
41
+ return $prefix . preg_replace_callback( '/[-_](.)/', $callback, strtolower( $action ) );
42
+ }
43
+
44
+ public function routeAjaxRequests()
45
+ {
46
+ $request = $_REQUEST['request'];
47
+
48
+ if( isset( $request[ $this->prefix ]['action'] ) ) {
49
+ $request = $request[ $this->prefix ];
50
+ }
51
+
52
+ // All ajax requests are triggered by a single action hook,
53
+ // each route is determined by the request["action"].
54
+ if( !isset( $request['action'] ) ) {
55
+ wp_die();
56
+ }
57
+
58
+ // Nonce url is localized in "GeminiLabs\SiteReviews\Handlers\EnqueueAssets"
59
+ check_ajax_referer( sprintf( '%s-ajax-nonce', $this->id ) );
60
+
61
+ $request['ajax_request'] = true;
62
+
63
+ // undo damage done by javascript: encodeURIComponent()
64
+ array_walk_recursive( $request, function( &$value ) {
65
+ $value = stripslashes( $value );
66
+ });
67
+
68
+ $ajaxController = $this->app->make( 'Controllers\AjaxController' );
69
+ $method = $this->getMethodName( 'ajax', $request['action'] );
70
+
71
+ if( is_callable([ $ajaxController, $method ]) ) {
72
+ $ajaxController->$method( $request );
73
+ }
74
+ else {
75
+ do_action( 'site-reviews/router/ajax/request', $method, $request );
76
+ }
77
+
78
+ wp_die();
79
+ }
80
+
81
+ public function routePostRequests()
82
+ {
83
+ // get the request data that is prefixed with the app prefix
84
+ $request = filter_input( INPUT_POST, $this->prefix, FILTER_DEFAULT , FILTER_REQUIRE_ARRAY );
85
+
86
+ if( !isset( $request['action'] ) )return;
87
+
88
+ $nonce = ( filter_input( INPUT_POST, 'option_page' ) == $request['action'] && filter_input( INPUT_POST, 'action' ) == 'update' )
89
+ ? $request['action'] . '-options'
90
+ : $request['action'];
91
+
92
+ check_admin_referer( $nonce );
93
+
94
+ switch( $request['action'] ) {
95
+ case 'clear-log':
96
+ $this->app->make( 'Controllers\MainController' )->postClearLog();
97
+ break;
98
+
99
+ case 'download-log':
100
+ $this->app->make( 'Controllers\MainController' )->postDownloadLog();
101
+ break;
102
+
103
+ case 'download-system-info':
104
+ $this->app->make( 'Controllers\MainController' )->postDownloadSystemInfo( $request['system-info'] );
105
+ break;
106
+
107
+ case 'post-review':
108
+ $this->app->make( 'Controllers\ReviewController' )->postSubmitReview( $request );
109
+ break;
110
+
111
+ default:
112
+ do_action( 'site-reviews/router/post/request', $request['action'], $request );
113
+ break;
114
+ }
115
+ }
116
+
117
+ public function routeWebhookRequests()
118
+ {
119
+ $request = filter_input( INPUT_GET, sprintf( '%s-hook', $this->id ) );
120
+
121
+ if( !$request )return;
122
+ }
123
+ }
plugin/Session.php ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ *
9
+ * Much of the code in this class is derived from WP Session Manager (1.2.0)
10
+ * Copyright (c) <Eric Mann>
11
+ * -------------------------------------------------------------------------------------------------
12
+ */
13
+
14
+ namespace GeminiLabs\SiteReviews;
15
+
16
+ use GeminiLabs\SiteReviews\App;
17
+ use PasswordHash;
18
+
19
+ class Session
20
+ {
21
+ /**
22
+ * Unix timestamp when session expires.
23
+ *
24
+ * @var int
25
+ */
26
+ protected $expiry;
27
+
28
+ /**
29
+ * Unix timestamp indicating when the expiration time needs to be reset.
30
+ *
31
+ * @var int
32
+ */
33
+ protected $expiryReset;
34
+
35
+ /**
36
+ * @var string
37
+ */
38
+ protected $prefix;
39
+
40
+ /**
41
+ * @var App
42
+ */
43
+ protected $app;
44
+
45
+ /**
46
+ * The current session data.
47
+ *
48
+ * @var array
49
+ */
50
+ protected $session;
51
+
52
+ /**
53
+ * ID of the current session.
54
+ *
55
+ * @var string
56
+ */
57
+ protected $sessionId;
58
+
59
+ public function __construct( App $app )
60
+ {
61
+ $this->app = $app;
62
+
63
+ $this->prefix = "_{$this->app->prefix}_session";
64
+
65
+ if( isset( $_COOKIE[ $this->app->id ] ) ) {
66
+
67
+ $cookie = explode( '||', stripslashes( $_COOKIE[ $this->app->id ] ) );
68
+
69
+ $this->sessionId = $cookie[0];
70
+ $this->expiry = $cookie[1];
71
+ $this->expiryReset = $cookie[2];
72
+
73
+ // Update the session expiration if we're past the reset time
74
+ if( time() > $this->expiryReset ) {
75
+ $this->resetCookieExpiration();
76
+ $this->resetSessionExpiration();
77
+ }
78
+ }
79
+ else {
80
+ $this->sessionId = $this->generateSessionId();
81
+ $this->resetCookieExpiration();
82
+ }
83
+
84
+ $this->getSession();
85
+ $this->setCookie();
86
+
87
+ add_action( 'site-reviews/schedule/session/purge', [ $this, 'purgeSessions'] );
88
+ }
89
+
90
+ /**
91
+ * Alias for delete()
92
+ *
93
+ * @return void
94
+ */
95
+ public function clear()
96
+ {
97
+ $this->delete();
98
+ }
99
+
100
+ /**
101
+ * Delete the current session
102
+ *
103
+ * @return void
104
+ */
105
+ public function delete()
106
+ {
107
+ $this->resetCookieExpiration();
108
+ $this->regenerateSessionId( 'and delete session!' );
109
+ }
110
+
111
+ /**
112
+ * Get a variable from the current session
113
+ *
114
+ * @param string $key
115
+ * @param string|array $fallback
116
+ * @param mixed $unset
117
+ *
118
+ * @return string
119
+ */
120
+ public function get( $key, $fallback = '', $unset = false )
121
+ {
122
+ $key = sanitize_key( $key );
123
+
124
+ $value = isset( $this->session[ $key ] )
125
+ ? maybe_unserialize( $this->session[ $key ] )
126
+ : $fallback;
127
+
128
+ if( !!$unset ) {
129
+ unset( $this->session[ $key ] );
130
+ $this->updateSession();
131
+ }
132
+
133
+ return $value;
134
+ }
135
+
136
+ /**
137
+ * Reset the current session to an empty array, does not touch the database
138
+ *
139
+ * @return self
140
+ */
141
+ public function reset()
142
+ {
143
+ $this->session = [];
144
+
145
+ return $this;
146
+ }
147
+
148
+ /**
149
+ * Set a variable to the current session
150
+ *
151
+ * @param string $key
152
+ * @param mixed $value
153
+ *
154
+ * @return string
155
+ */
156
+ public function set( $key, $value )
157
+ {
158
+ $key = sanitize_key( $key );
159
+
160
+ $this->session[ $key ] = maybe_serialize( $value );
161
+
162
+ $this->updateSession();
163
+
164
+ return $this->session[ $key ];
165
+ }
166
+
167
+ /**
168
+ * @return string
169
+ */
170
+ protected function generateSessionId()
171
+ {
172
+ return md5((new PasswordHash( 8, false ))->get_random_bytes( 32 ));
173
+ }
174
+
175
+ /**
176
+ * @param mixed $purge
177
+ *
178
+ * @return void
179
+ */
180
+ protected function regenerateSessionId( $purge = false )
181
+ {
182
+ $this->sessionId = $this->generateSessionId();
183
+ $this->setCookie();
184
+
185
+ if( !!$purge ) {
186
+ $this->deleteSession();
187
+ }
188
+ }
189
+
190
+ /**
191
+ * @return void
192
+ */
193
+ protected function resetCookieExpiration()
194
+ {
195
+ $this->expiry = time() + (30 * 60); // 30 minutes
196
+ $this->expiryReset = time() + (24 * 60); // 24 minutes
197
+ }
198
+
199
+ /**
200
+ * @return void
201
+ */
202
+ protected function setCookie()
203
+ {
204
+ $cookie = "{$this->sessionId}||{$this->expiry}||{$this->expiryReset}";
205
+
206
+ $secure = apply_filters( 'site-reviews/session/cookie/secure', false );
207
+ $httponly = apply_filters( 'site-reviews/session/cookie/httponly', false );
208
+
209
+ if( !defined( 'COOKIEPATH' ) ) {
210
+ define( 'COOKIEPATH', preg_replace( '|https?://[^/]+|i', '', get_option( 'home' ) . '/' ) );
211
+ }
212
+
213
+ if( !defined( 'COOKIE_DOMAIN' ) ) {
214
+ define( 'COOKIE_DOMAIN', false );
215
+ }
216
+
217
+ if( !headers_sent() ) {
218
+ setcookie( $this->app->id, $cookie, $this->expiry, COOKIEPATH, COOKIE_DOMAIN, $secure, $httponly );
219
+ }
220
+ }
221
+
222
+ // DATABASE
223
+ // ---------------------------------------------------------------------------------------------
224
+
225
+ /**
226
+ * Delete ALL sessions from the database.
227
+ *
228
+ * @return int
229
+ */
230
+ public function deleteSessions()
231
+ {
232
+ global $wpdb;
233
+
234
+ $count = $wpdb->query(
235
+ "DELETE FROM $wpdb->options " .
236
+ "WHERE option_name LIKE '{$this->prefix}_%'"
237
+ );
238
+
239
+ return (int) ( $count / 2 );
240
+ }
241
+
242
+ /**
243
+ * Delete old sessions from the database.
244
+ *
245
+ * @param int $limit
246
+ *
247
+ * @return int
248
+ */
249
+ public function purgeSessions( $limit = 1000 )
250
+ {
251
+ global $wpdb;
252
+
253
+ $sessions = $wpdb->get_results( $wpdb->prepare(
254
+ "SELECT option_name AS name, option_value AS expiry " .
255
+ "FROM $wpdb->options " .
256
+ "WHERE option_name LIKE '{$this->prefix}_expires_%' " .
257
+ "ORDER BY option_value ASC " .
258
+ "LIMIT 0, %d", absint( $limit )
259
+ ));
260
+
261
+ if( empty( $sessions ) ) {
262
+ return 0;
263
+ }
264
+
265
+ $now = time();
266
+ $expired = [];
267
+ $count = 0;
268
+
269
+ foreach( $sessions as $session ) {
270
+ if( $now <= $session->expiry )continue;
271
+
272
+ $session_id = addslashes( substr( $session->name, 20 ) );
273
+
274
+ $expired[] = $session->name;
275
+ $expired[] = "{$this->prefix}_{$session_id}";
276
+
277
+ $count++;
278
+ }
279
+
280
+ // Delete expired sessions
281
+ if( !empty( $expired ) ) {
282
+ $session_names = implode( "','", $expired );
283
+
284
+ $wpdb->query(
285
+ "DELETE FROM $wpdb->options " .
286
+ "WHERE option_name IN ('{$session_names}')"
287
+ );
288
+ }
289
+
290
+ return $count;
291
+ }
292
+
293
+ /**
294
+ * @return bool
295
+ */
296
+ protected function createSession()
297
+ {
298
+ return add_option( "{$this->prefix}_{$this->sessionId}", $this->session, '', false );
299
+ }
300
+
301
+ /**
302
+ * @return bool
303
+ */
304
+ protected function deleteSession()
305
+ {
306
+ return delete_option( "{$this->prefix}_{$this->sessionId}" );
307
+ }
308
+
309
+ /**
310
+ * @return array
311
+ */
312
+ protected function getSession()
313
+ {
314
+ return $this->session = get_option( "{$this->prefix}_{$this->sessionId}", [] );
315
+ }
316
+
317
+ /**
318
+ * @return bool
319
+ */
320
+ protected function resetSessionExpiration()
321
+ {
322
+ return update_option( "{$this->prefix}_expires_{$this->sessionId}", $this->expiry, false );
323
+ }
324
+
325
+ /**
326
+ * @return bool
327
+ */
328
+ protected function updateSession()
329
+ {
330
+ if( false === get_option( "{$this->prefix}_{$this->sessionId}" ) ) {
331
+ $this->resetSessionExpiration();
332
+ }
333
+
334
+ return update_option( "{$this->prefix}_{$this->sessionId}", $this->session, false );
335
+ }
336
+ }
plugin/Settings.php ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use ReflectionClass;
15
+ use ReflectionMethod;
16
+
17
+ class Settings
18
+ {
19
+ /**
20
+ * @var App
21
+ */
22
+ protected $app;
23
+
24
+ /**
25
+ * @var Html
26
+ */
27
+ protected $html;
28
+
29
+ /**
30
+ * @var array
31
+ */
32
+ protected $settings;
33
+
34
+ public function __construct( App $app )
35
+ {
36
+ $this->app = $app;
37
+ $this->html = $app->make( 'Html' );
38
+ $this->settings = [];
39
+ }
40
+
41
+ /**
42
+ * Add a setting default
43
+ *
44
+ * @param string $formId
45
+ *
46
+ * @return void
47
+ */
48
+ public function addSetting( $formId, array $args )
49
+ {
50
+ if( isset( $args['name'] ) ) {
51
+ $this->settings[ $args['name'] ] = $this->getDefault( $args );
52
+ }
53
+
54
+ $this->html->addfield( $formId, $args );
55
+ }
56
+
57
+ /**
58
+ * Get the default field value
59
+ *
60
+ * @return string
61
+ */
62
+ public function getDefault( array $args )
63
+ {
64
+ isset( $args['default'] ) ?: $args['default'] = '';
65
+ isset( $args['placeholder'] ) ?: $args['placeholder'] = '';
66
+
67
+ if( $args['default'] === ':placeholder' ) {
68
+ $args['default'] = $args['placeholder'];
69
+ }
70
+
71
+ return $args['default'];
72
+ }
73
+
74
+ /**
75
+ * Get the default settings
76
+ *
77
+ * @return array
78
+ */
79
+ public function getSettings()
80
+ {
81
+ $this->register();
82
+
83
+ return $this->settings;
84
+ }
85
+
86
+ /**
87
+ * Register the settings for each form
88
+ *
89
+ * @return void
90
+ *
91
+ * @action admin_init
92
+ */
93
+ public function register()
94
+ {
95
+ if( !empty( $this->settings ) )return;
96
+
97
+ $methods = (new ReflectionClass( __CLASS__ ))->getMethods( ReflectionMethod::IS_PROTECTED );
98
+
99
+ foreach( $methods as $method ) {
100
+ if( substr( $method->name, 0, 3 ) === 'set' ) {
101
+ $this->{$method->name}();
102
+ }
103
+ }
104
+ }
105
+
106
+ protected function setGeneral()
107
+ {
108
+ $formId = 'settings/general';
109
+
110
+ $this->html->createForm( $formId, [
111
+ 'action' => admin_url( 'options.php' ),
112
+ 'nonce' => $this->app->id . '-settings',
113
+ 'submit' => __( 'Save Settings', 'site-reviews' ),
114
+ ]);
115
+
116
+ $this->addSetting( $formId, [
117
+ 'type' => 'yesno_inline',
118
+ 'name' => 'general.require.approval',
119
+ 'label' => __( 'Require approval', 'site-reviews' ),
120
+ 'default' => true,
121
+ 'desc' => __( 'Set the status of new review submissions to pending.', 'site-reviews' ),
122
+ ]);
123
+
124
+ $this->addSetting( $formId, [
125
+ 'type' => 'yesno_inline',
126
+ 'name' => 'general.require.login',
127
+ 'label' => __( 'Require login', 'site-reviews' ),
128
+ 'desc' => __( 'Only allow review submissions from registered users.', 'site-reviews' ),
129
+ ]);
130
+
131
+ $this->addSetting( $formId, [
132
+ 'type' => 'radio',
133
+ 'name' => 'general.notification',
134
+ 'label' => __( 'Notifications', 'site-reviews' ),
135
+ 'default' => 'none',
136
+ 'options' => [
137
+ 'none' => __( 'Do not send review notifications', 'site-reviews' ),
138
+ 'default' => sprintf( __( 'Send to administrator <code>%s</code>', 'site-reviews' ), get_option( 'admin_email' ) ),
139
+ 'custom' => __( 'Send to one or more email addresses', 'site-reviews' ),
140
+ 'webhook' => __( 'Send to <a href="https://slack.com/">Slack</a>', 'site-reviews' ),
141
+ ],
142
+ ]);
143
+
144
+ $this->addSetting( $formId, [
145
+ 'type' => 'text',
146
+ 'name' => 'general.notification_email',
147
+ 'label' => __( 'Send notification emails to', 'site-reviews' ),
148
+ 'depends' => [
149
+ 'general.notification' => 'custom',
150
+ ],
151
+ 'placeholder' => __( 'Separate multiple emails with a comma', 'site-reviews' ),
152
+ ]);
153
+
154
+ $this->addSetting( $formId, [
155
+ 'type' => 'url',
156
+ 'name' => 'general.webhook_url',
157
+ 'label' => __( 'Webhook URL', 'site-reviews' ),
158
+ 'depends' => [
159
+ 'general.notification' => 'webhook',
160
+ ],
161
+ 'desc' => sprintf(
162
+ __( 'To send notifications to Slack, <a href="%s">create a new Incoming WebHook</a> and then paste the provided Webhook URL in the field above.', 'site-reviews' ),
163
+ esc_url( 'https://slack.com/apps/new/A0F7XDUAZ-incoming-webhooks' )
164
+ ),
165
+ ]);
166
+
167
+ $this->addSetting( $formId, [
168
+ 'type' => 'code',
169
+ 'name' => 'general.notification_message',
170
+ 'label' => __( 'Notification template', 'site-reviews' ),
171
+ 'rows' => 9,
172
+ 'depends' => [
173
+ 'general.notification' => ['custom', 'default', 'webhook'],
174
+ ],
175
+ 'default' => $this->html->renderTemplate( 'email/templates/review-notification', [], 'return' ),
176
+ 'desc' => 'To restore the default text, save an empty template.
177
+ If you are sending notifications to Slack then this template will only be used as a fallback in the event that <a href="https://api.slack.com/docs/attachments">Message Attachments</a> have been disabled.<br>
178
+ Available template tags:<br>
179
+ <code>{review_rating}</code> - The review rating number (1-5)<br>
180
+ <code>{review_title}</code> - The review title<br>
181
+ <code>{review_content}</code> - The review content<br>
182
+ <code>{review_author}</code> - The review author<br>
183
+ <code>{review_email}</code> - The email of the review author<br>
184
+ <code>{review_ip}</code> - The IP address of the review author<br>
185
+ <code>{review_link}</code> - The link to edit/view a review',
186
+ ]);
187
+ }
188
+
189
+ protected function setForm()
190
+ {
191
+ $formId = 'settings/form';
192
+
193
+ $this->html->createForm( $formId, [
194
+ 'action' => admin_url( 'options.php' ),
195
+ 'nonce' => $this->app->id . '-settings',
196
+ 'submit' => __( 'Save Settings', 'site-reviews' ),
197
+ ]);
198
+
199
+ $this->html->addfield( $formId, [
200
+ 'type' => 'heading',
201
+ 'value' => __( 'Form Labels', 'site-reviews' ),
202
+ 'desc' => __( 'Customize the label text for the review submission form fields.', 'site-reviews' ),
203
+ ]);
204
+
205
+ $this->addSetting( $formId, [
206
+ 'type' => 'text',
207
+ 'name' => 'form.rating.label',
208
+ 'label' => __( 'Rating label', 'site-reviews' ),
209
+ 'placeholder' => __( 'Your overall rating', 'site-reviews' ),
210
+ 'default' => ':placeholder',
211
+ ]);
212
+
213
+ $this->addSetting( $formId, [
214
+ 'type' => 'text',
215
+ 'name' => 'form.title.label',
216
+ 'label' => __( 'Title label', 'site-reviews' ),
217
+ 'placeholder' => __( 'Title of your review', 'site-reviews' ),
218
+ 'default' => ':placeholder',
219
+ ]);
220
+
221
+ $this->addSetting( $formId, [
222
+ 'type' => 'text',
223
+ 'name' => 'form.content.label',
224
+ 'label' => __( 'Content label', 'site-reviews' ),
225
+ 'placeholder' => __( 'Your review', 'site-reviews' ),
226
+ 'default' => ':placeholder',
227
+ ]);
228
+
229
+ $this->addSetting( $formId, [
230
+ 'type' => 'text',
231
+ 'name' => 'form.reviewer.label',
232
+ 'label' => __( 'Reviewer label', 'site-reviews' ),
233
+ 'placeholder' => __( 'Your name', 'site-reviews' ),
234
+ 'default' => ':placeholder',
235
+ ]);
236
+
237
+ $this->addSetting( $formId, [
238
+ 'type' => 'text',
239
+ 'name' => 'form.email.label',
240
+ 'label' => __( 'Email label', 'site-reviews' ),
241
+ 'placeholder' => __( 'Your email', 'site-reviews' ),
242
+ 'default' => ':placeholder',
243
+ ]);
244
+
245
+ $this->addSetting( $formId, [
246
+ 'type' => 'textarea',
247
+ 'name' => 'form.terms.label',
248
+ 'label' => __( 'Terms label', 'site-reviews' ),
249
+ 'placeholder' => __( 'This review is based on my own experience and is my genuine opinion.', 'site-reviews' ),
250
+ 'default' => ':placeholder',
251
+ ]);
252
+
253
+ $this->html->addfield( $formId, [
254
+ 'type' => 'heading',
255
+ 'value' => __( 'Form Placeholders', 'site-reviews' ),
256
+ 'desc' => __( 'Customize the placeholder text for the review submission form fields. Use a single space character to disable the placeholder text.', 'site-reviews' ),
257
+ ]);
258
+
259
+ $this->addSetting( $formId, [
260
+ 'type' => 'text',
261
+ 'name' => 'form.title.placeholder',
262
+ 'class' => 'large-text',
263
+ 'label' => __( 'Title placeholder', 'site-reviews' ),
264
+ 'placeholder' => __( 'Summarize your review or highlight an interesting detail', 'site-reviews' ),
265
+ 'default' => ':placeholder',
266
+ ]);
267
+
268
+ $this->addSetting( $formId, [
269
+ 'type' => 'text',
270
+ 'name' => 'form.content.placeholder',
271
+ 'class' => 'large-text',
272
+ 'label' => __( 'Content placeholder', 'site-reviews' ),
273
+ 'placeholder' => __( 'Tell people your review', 'site-reviews' ),
274
+ 'default' => ':placeholder',
275
+ ]);
276
+
277
+ $this->addSetting( $formId, [
278
+ 'type' => 'text',
279
+ 'name' => 'form.reviewer.placeholder',
280
+ 'class' => 'large-text',
281
+ 'label' => __( 'Reviewer placeholder', 'site-reviews' ),
282
+ 'placeholder' => __( 'Tell us your name', 'site-reviews' ),
283
+ 'default' => ':placeholder',
284
+ ]);
285
+
286
+ $this->addSetting( $formId, [
287
+ 'type' => 'text',
288
+ 'name' => 'form.email.placeholder',
289
+ 'class' => 'large-text',
290
+ 'label' => __( 'Email placeholder', 'site-reviews' ),
291
+ 'placeholder' => __( 'Tell us your email', 'site-reviews' ),
292
+ 'default' => ':placeholder',
293
+ ]);
294
+ }
295
+ }
plugin/Shortcode.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * = Shortcode Boilerplate
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace Geminilabs\SiteReviews;
14
+
15
+ use GeminiLabs\SiteReviews\App;
16
+
17
+ abstract class Shortcode
18
+ {
19
+ /**
20
+ * @var App
21
+ */
22
+ protected $app;
23
+
24
+ /**
25
+ * @var Database
26
+ */
27
+ protected $db;
28
+
29
+ /**
30
+ * @var Html
31
+ */
32
+ protected $html;
33
+
34
+ /**
35
+ * @var Session
36
+ */
37
+ protected $session;
38
+
39
+ public function __construct( App $app )
40
+ {
41
+ $this->app = $app;
42
+ $this->db = $app->make( 'Database' );
43
+ $this->html = $app->make( 'Html' );
44
+ $this->session = $app->make( 'Session' );
45
+ }
46
+
47
+ /**
48
+ * Add a help page for the shortcodes
49
+ *
50
+ * @return void
51
+ */
52
+ public function addHelpPage()
53
+ {
54
+ add_action( 'load-post.php', [ $this, 'helpTabsHook'] );
55
+ add_action( 'load-post-new.php', [ $this, 'helpTabsHook'] );
56
+ }
57
+
58
+ /**
59
+ * Add the shortcode help tabs and content
60
+ *
61
+ * @return void
62
+ */
63
+ public function addHelpTabs()
64
+ {
65
+ $screen = get_current_screen();
66
+
67
+ $screen->add_help_tab([
68
+ 'id' => $this->app->id . '-shortcodes',
69
+ 'title' => $this->app->name . ' Shortcodes',
70
+ 'content' => $this->helpContent(),
71
+ ]);
72
+ }
73
+
74
+ /**
75
+ * The shortcode help content
76
+ *
77
+ * @return string
78
+ */
79
+ public function helpContent()
80
+ {
81
+ return '';
82
+ }
83
+
84
+ /**
85
+ * Hook to insert help tabs at the end of existing tabs
86
+ *
87
+ * @return void
88
+ */
89
+ public function helpTabsHook()
90
+ {
91
+ add_action( 'in_admin_header', [ $this, 'addHelpTabs'] );
92
+ }
93
+
94
+ /**
95
+ * Generate a unique ID string from the Shortcode attributes array
96
+ *
97
+ * @return string
98
+ */
99
+ protected function generate_id( array $atts )
100
+ {
101
+ return substr( md5( serialize( $atts ) ), 0, 8 );
102
+ }
103
+ }
plugin/Shortcodes/SiteReviews.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * = Reviews Form shortcode
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Shortcodes;
14
+
15
+ use GeminiLabs\SiteReviews\Shortcode;
16
+
17
+ class SiteReviews extends Shortcode
18
+ {
19
+ /**
20
+ * @return string
21
+ */
22
+ public function printShortcode( $atts = [] )
23
+ {
24
+ $defaults = [
25
+ 'class' => '',
26
+ 'count' => 10,
27
+ 'display' => 'title,excerpt,author,date,rating,url',
28
+ 'pagination' => false,
29
+ 'rating' => 5,
30
+ 'title' => '',
31
+ 'type' => '',
32
+ ];
33
+
34
+ $args = shortcode_atts( $defaults, $atts );
35
+
36
+ extract( $args );
37
+
38
+ $display = array_map( 'trim', explode( ',', $display ) );
39
+
40
+ $author = in_array( 'author', $display );
41
+ $date = in_array( 'date', $display );
42
+ $link = in_array( 'link', $display );
43
+ $rating = in_array( 'rating', $display );
44
+
45
+ $display = array_intersect( ['title', 'excerpt'], $display );
46
+ $display = $display
47
+ ? ( count($display) > 1 ? 'both' : array_shift( $display ) )
48
+ : 'both';
49
+
50
+ ob_start();
51
+
52
+ echo '<div class="shortcode-site-reviews">';
53
+
54
+ if( !empty( $title ) ) {
55
+ printf( '<h2>%s</h2>', $title );
56
+ }
57
+
58
+ $this->html->renderPartial( 'reviews', [
59
+ 'class' => $class,
60
+ 'display' => $display,
61
+ 'max_reviews' => $count,
62
+ 'min_rating' => $rating,
63
+ 'order_by' => 'date',
64
+ 'pagination' => $pagination,
65
+ 'show_author' => $author,
66
+ 'show_date' => $date,
67
+ 'show_link' => $link,
68
+ 'show_rating' => $rating,
69
+ 'site_name' => $type,
70
+ ]);
71
+
72
+ echo '</div>';
73
+
74
+ return ob_get_clean();
75
+ }
76
+ }
plugin/Shortcodes/SiteReviewsForm.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * = Reviews Form shortcode
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Shortcodes;
14
+
15
+ use GeminiLabs\SiteReviews\Shortcode;
16
+
17
+ class SiteReviewsForm extends Shortcode
18
+ {
19
+ /**
20
+ * @return null|string
21
+ */
22
+ public function printShortcode( $atts = [] )
23
+ {
24
+ $requireUser = glsr_resolve( 'Database' )->getOption( 'general.require.login', false );
25
+
26
+ if( $requireUser && !is_user_logged_in() ) {
27
+ $message = sprintf(
28
+ __( 'You must be <a href="%s">logged in</a> to submit a review.', 'site-reviews' ),
29
+ wp_login_url( get_permalink() )
30
+ );
31
+ echo wpautop( $message );
32
+ return;
33
+ }
34
+
35
+ $defaults = [
36
+ 'class' => '',
37
+ 'description' => '',
38
+ 'hide' => '',
39
+ 'title' => '',
40
+ ];
41
+
42
+ $atts = shortcode_atts( $defaults, $atts );
43
+
44
+ $fields = explode( ',', $atts['hide'] );
45
+ $fields = array_filter( $fields, function( $value ) {
46
+ return in_array( $value, [
47
+ 'title',
48
+ 'reviewer',
49
+ 'email',
50
+ 'terms'
51
+ ]);
52
+ });
53
+
54
+ $formId = $this->generate_id( $atts );
55
+
56
+ $errors = $this->session->get( "{$formId}-errors", [], 'and then remove errors' );
57
+ $message = $this->session->get( "{$formId}-message", [], 'and then remove message' );
58
+
59
+ $values = !empty( $errors )
60
+ ? $this->session->get( "{$formId}-values", [], 'and then remove values' )
61
+ : [];
62
+
63
+ ob_start();
64
+
65
+ echo '<div class="shortcode-reviews-form">';
66
+
67
+ if( !empty( $atts['title'] ) ) {
68
+ printf( '<h3 class="glsr-form-title">%s</h3>', $atts['title'] );
69
+ }
70
+
71
+ if( !empty( $atts['description'] ) ) {
72
+ printf( '<p class="glsr-form-description">%s</p>', $atts['description'] );
73
+ }
74
+
75
+ $this->app->make( 'Controllers\ReviewController' )->render( 'submit/index', [
76
+ 'class' => trim( 'glsr-submit-review-form ' . $atts['class'] ),
77
+ 'errors' => $errors,
78
+ 'exclude' => $fields,
79
+ 'form_id' => $formId,
80
+ 'message' => $message,
81
+ 'values' => shortcode_atts([
82
+ 'rating' => '',
83
+ 'title' => '',
84
+ 'content' => '',
85
+ 'reviewer' => '',
86
+ 'email' => '',
87
+ 'terms' => '',
88
+ ], $values ),
89
+ ]);
90
+
91
+ echo '</div>';
92
+
93
+ return ob_get_clean();
94
+ }
95
+ }
plugin/Strings.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ class Strings
14
+ {
15
+ public function post_type_labels()
16
+ {
17
+ return [
18
+ 'add_new_item' => __( 'Add New Review', 'site-reviews' ),
19
+ 'all_items' => __( 'All Reviews', 'site-reviews' ),
20
+ 'archives' => __( 'Review Archives', 'site-reviews' ),
21
+ 'edit_item' => __( 'Edit Review', 'site-reviews' ),
22
+ 'insert_into_item' => __( 'Insert into review', 'site-reviews' ),
23
+ 'new_item' => __( 'New Review', 'site-reviews' ),
24
+ 'not_found' => __( 'No Reviews found', 'site-reviews' ),
25
+ 'not_found_in_trash' => __( 'No Reviews found in Trash', 'site-reviews' ),
26
+ 'search_items' => __( 'Search Reviews', 'site-reviews' ),
27
+ 'uploaded_to_this_item' => __( 'Uploaded to this review', 'site-reviews' ),
28
+ 'view_item' => __( 'View Review', 'site-reviews' ),
29
+ ];
30
+ }
31
+
32
+ public function post_updated_messages()
33
+ {
34
+ return [
35
+ 'approved' => __( 'Review has been approved and published.', 'site-reviews' ),
36
+ 'draft_updated' => __( 'Review draft updated.', 'site-reviews' ),
37
+ 'preview' => __( 'Preview review', 'site-reviews' ),
38
+ 'published' => __( 'Review published.', 'site-reviews' ),
39
+ 'restored' => __( 'Review restored to revision from %s.', 'site-reviews' ),
40
+ 'reverted' => __( 'Review has been reverted to its original submission state.', 'site-reviews' ),
41
+ 'saved' => __( 'Review saved.', 'site-reviews' ),
42
+ 'scheduled' => __( 'Review scheduled for: %s.', 'site-reviews' ),
43
+ 'submitted' => __( 'Review submitted.', 'site-reviews' ),
44
+ 'unapproved' => __( 'Review has been unapproved and is now pending.', 'site-reviews' ),
45
+ 'updated' => __( 'Review updated.', 'site-reviews' ),
46
+ 'view' => __( 'View review', 'site-reviews' ),
47
+ ];
48
+ }
49
+
50
+ public function validation()
51
+ {
52
+ return [
53
+ 'accepted' => _x( 'The :attribute must be accepted.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ),
54
+ 'between.numeric' => _x( 'The :attribute must be between :min and :max.', ':attribute, :min, and :max are placeholders and should not be translated.', 'site-reviews' ),
55
+ 'between.string' => _x( 'The :attribute must be between :min and :max characters.', ':attribute, :min, and :max are placeholders and should not be translated.', 'site-reviews' ),
56
+ 'email' => _x( 'The :attribute must be a valid email address.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ),
57
+ 'max.numeric' => _x( 'The :attribute may not be greater than :max.', ':attribute and :max are placeholders and should not be translated.', 'site-reviews' ),
58
+ 'max.string' => _x( 'The :attribute may not be greater than :max characters.', ':attribute and :max are placeholders and should not be translated.', 'site-reviews' ),
59
+ 'min.numeric' => _x( 'The :attribute must be at least :min.', ':attribute and :min are placeholders and should not be translated.', 'site-reviews' ),
60
+ 'min.string' => _x( 'The :attribute must be at least :min characters.', ':attribute and :min are placeholders and should not be translated.', 'site-reviews' ),
61
+ 'regex' => _x( 'The :attribute format is invalid.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ),
62
+ 'required' => _x( 'The :attribute field is required.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ),
63
+ ];
64
+ }
65
+ }
plugin/SystemInfo.php ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ namespace GeminiLabs\SiteReviews;
12
+
13
+ use GeminiLabs\SiteReviews\App;
14
+ use Sinergi\BrowserDetector\Browser;
15
+
16
+ class SystemInfo
17
+ {
18
+ /**
19
+ * @var int
20
+ */
21
+ public $pad = 30;
22
+
23
+ /**
24
+ * @var App
25
+ */
26
+ protected $app;
27
+
28
+ /**
29
+ * @var array
30
+ */
31
+ protected $sysinfo = [];
32
+
33
+ public function __construct( App $app )
34
+ {
35
+ $this->app = $app;
36
+ }
37
+
38
+ /**
39
+ * Downloads the system info as a text file
40
+ *
41
+ * @param string $system_info
42
+ *
43
+ * @return void
44
+ */
45
+ public function download( $system_info )
46
+ {
47
+ if( !current_user_can( 'install_plugins' ) )return;
48
+
49
+ nocache_headers();
50
+
51
+ header( 'Content-Type: text/plain' );
52
+ header( 'Content-Disposition: attachment; filename="system-info.txt"' );
53
+
54
+ echo wp_strip_all_tags( $system_info );
55
+
56
+ exit;
57
+ }
58
+
59
+ /**
60
+ * Get all system info
61
+ *
62
+ * @return string
63
+ */
64
+ public function getAll()
65
+ {
66
+ return trim(
67
+ $this->getPlugin() .
68
+ $this->getBrowser() .
69
+ $this->getWebserver() .
70
+ $this->getPHPConfig() .
71
+ $this->getPHPExtensions() .
72
+ $this->getWordpress() .
73
+ $this->getWordpressMuplugins() .
74
+ $this->getWordpressPlugins() .
75
+ $this->getWordpressMultisitePlugins()
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Get browser info
81
+ *
82
+ * @param string $title
83
+ *
84
+ * @return null|string
85
+ */
86
+ public function getBrowser( $title = 'Browser Details' )
87
+ {
88
+ if( !$this->title( $title ) )return;
89
+
90
+ $browser = new Browser;
91
+ $userAgent = $browser->getUserAgent();
92
+
93
+ $this->sysinfo[ $title ]['Browser Name'] = sprintf( '%s v%s', $browser->getName(), $browser->getVersion() );
94
+ $this->sysinfo[ $title ]['Browser UA'] = $userAgent->getUserAgentString();
95
+
96
+ return $this->implode( $title );
97
+ }
98
+
99
+ /**
100
+ * Get plugin info
101
+ *
102
+ * @param string $title
103
+ *
104
+ * @return null|string
105
+ */
106
+ public function getPlugin( $title = 'Plugin Details' )
107
+ {
108
+ if( !$this->title( $title ) )return;
109
+
110
+ $this->sysinfo[ $title ]['Current version'] = $this->app->version;
111
+ $this->sysinfo[ $title ]['Previous version'] = 'None';
112
+
113
+ return $this->implode( $title );
114
+ }
115
+
116
+ /**
117
+ * Get WordPress configuration info
118
+ *
119
+ * @param string $title
120
+ *
121
+ * @return null|string
122
+ */
123
+ public function getWordpress( $title = 'WordPress Configuration' )
124
+ {
125
+ if( !$this->title( $title ) )return;
126
+
127
+ global $wpdb;
128
+
129
+ $theme = wp_get_theme();
130
+ $response = wp_remote_post( 'https://api.wordpress.org/stats/php/1.0/' );
131
+
132
+ $wp_prefix_status = strlen( $wpdb->prefix ) > 16 ? 'ERROR: Too long' : 'Acceptable';
133
+
134
+ $wp_remote_post = !is_wp_error( $response )
135
+ && $response['response']['code'] >= 200
136
+ && $response['response']['code'] < 300;
137
+
138
+ $this->sysinfo[ $title ]['Active Theme'] = sprintf( '%s v%s', $theme->Name, $theme->Version );
139
+ $this->sysinfo[ $title ]['Home URL'] = home_url();
140
+ $this->sysinfo[ $title ]['Language'] = ( defined( 'WPLANG' ) && WPLANG ) ? WPLANG : 'en_US';
141
+ $this->sysinfo[ $title ]['Memory Limit'] = WP_MEMORY_LIMIT;
142
+ $this->sysinfo[ $title ]['Multisite'] = is_multisite() ? 'Yes' : 'No';
143
+ $this->sysinfo[ $title ]['Page For Posts ID'] = get_option( 'page_for_posts' );
144
+ $this->sysinfo[ $title ]['Page On Front ID'] = get_option( 'page_on_front' );
145
+ $this->sysinfo[ $title ]['Permalink Structure'] = get_option( 'permalink_structure', 'Default' );
146
+ $this->sysinfo[ $title ]['Post Stati'] = implode( ', ', get_post_stati() );
147
+ $this->sysinfo[ $title ]['Remote Post'] = $wp_remote_post ? 'Works' : 'Does not work';
148
+ $this->sysinfo[ $title ]['Show On Front'] = get_option( 'show_on_front' );
149
+ $this->sysinfo[ $title ]['Site URL'] = site_url();
150
+ $this->sysinfo[ $title ]['Table Prefix'] = sprintf( '%s (%d)', $wp_prefix_status, strlen( $wpdb->prefix ) );
151
+ $this->sysinfo[ $title ]['Timezone'] = get_option( 'timezone_string' );
152
+ $this->sysinfo[ $title ]['Version'] = get_bloginfo( 'version' );
153
+ $this->sysinfo[ $title ]['WP Debug'] = defined( 'WP_DEBUG' ) ? WP_DEBUG ? 'Enabled' : 'Disabled' : 'Not set';
154
+ $this->sysinfo[ $title ]['WP Max Upload Size'] = size_format( wp_max_upload_size() );
155
+ $this->sysinfo[ $title ]['WP Memory Limit'] = WP_MEMORY_LIMIT;
156
+
157
+ return $this->implode( $title );
158
+ }
159
+
160
+ /**
161
+ * Get all active WordPress multisite plugins
162
+ *
163
+ * @param string $title
164
+ *
165
+ * @return null|string
166
+ */
167
+ public function getWordpressMultisitePlugins( $title = 'Network Active Plugins' )
168
+ {
169
+ if( !is_multisite() )return;
170
+
171
+ $active = get_site_option( 'active_sitewide_plugins', [] );
172
+
173
+ if( !$this->title( $title ) || !count( $active ) )return;
174
+
175
+ $plugins = wp_get_active_network_plugins();
176
+
177
+ foreach( $plugins as $plugin_path ) {
178
+
179
+ if( !in_array( plugin_basename( $plugin_path ), $active ) )continue;
180
+
181
+ $plugin = get_plugin_data( $plugin_path );
182
+
183
+ $this->sysinfo[ $title ][] = sprintf( '%s v%s', $plugin['Name'], $plugin['Version'] );
184
+ }
185
+
186
+ return $this->implode( $title );
187
+ }
188
+
189
+ /**
190
+ * Get all WordPress mu-plugins
191
+ *
192
+ * @param string $title
193
+ *
194
+ * @return null|string
195
+ */
196
+ public function getWordpressMuplugins( $title = 'Must-Use Plugins' )
197
+ {
198
+ $auto_plugins = get_plugins( '/../' . basename( WPMU_PLUGIN_DIR ) );
199
+ $mu_plugins = get_mu_plugins();
200
+
201
+ $plugins = $this->formatPlugins( array_merge( $mu_plugins, $auto_plugins ) );
202
+
203
+ return $this->wordpressPlugins( $title, $plugins );
204
+ }
205
+
206
+ /**
207
+ * Get all active/inactive WordPress plugins
208
+ *
209
+ * @param string $active_title
210
+ * @param string $inactive_title
211
+ *
212
+ * @return null|string
213
+ */
214
+ public function getWordpressPlugins( $active_title = 'Active Plugins', $inactive_title = 'Inactive Plugins' )
215
+ {
216
+ $plugins = get_plugins();
217
+
218
+ if( !count( $plugins ) )return;
219
+
220
+ $active_plugins = get_option( 'active_plugins', [] );
221
+
222
+ $inactive = $this->formatPlugins( array_diff_key( $plugins, array_flip( $active_plugins ) ) );
223
+ $active = $this->formatPlugins( array_diff_key( $plugins, $inactive ) );
224
+
225
+ $active_plugins = $this->wordpressPlugins( $active_title, $active );
226
+ $inactive_plugins = $this->wordpressPlugins( $inactive_title, $inactive );
227
+
228
+ return $active_plugins . $inactive_plugins;
229
+ }
230
+
231
+ /**
232
+ * Get the webhost/webserver info
233
+ *
234
+ * @param string $title
235
+ *
236
+ * @return null|string
237
+ */
238
+ public function getWebserver( $title = 'Server configuration' )
239
+ {
240
+ if( !$this->title( $title ) )return;
241
+
242
+ global $wpdb;
243
+
244
+ $server_ip = filter_input( INPUT_SERVER, 'SERVER_ADDR' );
245
+
246
+ $this->sysinfo[ $title ]['Host Name'] = sprintf( '%s (%s)', $this->webhost(), gethostbyaddr( $server_ip ) );
247
+ $this->sysinfo[ $title ]['MySQL Version'] = $wpdb->db_version();
248
+ $this->sysinfo[ $title ]['PHP Version'] = PHP_VERSION;
249
+ $this->sysinfo[ $title ]['Server Info'] = filter_input( INPUT_SERVER, 'SERVER_SOFTWARE' );
250
+ $this->sysinfo[ $title ]['Server IP Address'] = $server_ip;
251
+
252
+ return $this->implode( $title );
253
+ }
254
+
255
+ /**
256
+ * Get the PHP config
257
+ *
258
+ * @param string $title
259
+ *
260
+ * @return null|string
261
+ */
262
+ public function getPHPConfig( $title = 'PHP Configuration' )
263
+ {
264
+ if( !$this->title( $title ) )return;
265
+
266
+ $this->sysinfo[ $title ]['Display Errors'] = ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A';
267
+ $this->sysinfo[ $title ]['Max Execution Time'] = ini_get( 'max_execution_time' );
268
+ $this->sysinfo[ $title ]['Max Input Nesting Level'] = ini_get( 'max_input_nesting_level' );
269
+ $this->sysinfo[ $title ]['Max Input Vars'] = ini_get( 'max_input_vars' );
270
+ $this->sysinfo[ $title ]['Memory Limit'] = ini_get( 'memory_limit' );
271
+ $this->sysinfo[ $title ]['Post Max Size'] = ini_get( 'post_max_size' );
272
+ $this->sysinfo[ $title ]['Session Cookie Path'] = esc_html( ini_get( 'session.cookie_path' ) );
273
+ $this->sysinfo[ $title ]['Session Name'] = esc_html( ini_get( 'session.name' ) );
274
+ $this->sysinfo[ $title ]['Session Save Path'] = esc_html( ini_get( 'session.save_path' ) );
275
+ $this->sysinfo[ $title ]['Session Use Cookies'] = ini_get( 'session.use_cookies' ) ? 'On' : 'Off';
276
+ $this->sysinfo[ $title ]['Session Use Only Cookies'] = ini_get( 'session.use_only_cookies' ) ? 'On' : 'Off';
277
+ $this->sysinfo[ $title ]['Upload Max Filesize'] = ini_get( 'upload_max_filesize' );
278
+
279
+ return $this->implode( $title );
280
+ }
281
+
282
+ /**
283
+ * Get the status of required PHP extensions
284
+ *
285
+ * @param string $title
286
+ *
287
+ * @return null|string
288
+ */
289
+ public function getPHPExtensions( $title = 'PHP Extensions' )
290
+ {
291
+ if( !$this->title( $title ) )return;
292
+
293
+ $this->sysinfo[ $title ]['cURL'] = function_exists( 'curl_init' ) ? 'Supported' : 'Not Supported';
294
+ $this->sysinfo[ $title ]['fsockopen'] = function_exists( 'fsockopen' ) ? 'Supported' : 'Not Supported';
295
+ $this->sysinfo[ $title ]['SOAP Client'] = class_exists( 'SoapClient' ) ? 'Installed' : 'Not Installed';
296
+ $this->sysinfo[ $title ]['Suhosin'] = extension_loaded( 'suhosin' ) ? 'Installed' : 'Not Installed';
297
+
298
+ return $this->implode( $title );
299
+ }
300
+
301
+ /**
302
+ * Formats an array of plugins to [path] => [name version] and sort by value
303
+ *
304
+ * @param array $plugins
305
+ *
306
+ * @return array
307
+ */
308
+ protected function formatPlugins( array $plugins )
309
+ {
310
+ $plugins = array_map( function( $plugin ) {
311
+ return sprintf( '%s v%s', $plugin['Name'], $plugin['Version'] );
312
+ }, $plugins );
313
+
314
+ natcasesort( $plugins );
315
+
316
+ return $plugins;
317
+ }
318
+
319
+ /**
320
+ * Generates a string from a system info section array
321
+ *
322
+ * @param string $section
323
+ *
324
+ * @return null|string
325
+ */
326
+ protected function implode( $section )
327
+ {
328
+ if( !isset( $this->sysinfo[ $section ] ) )return;
329
+
330
+ $strings = [];
331
+
332
+ $strings[] = sprintf( '[%s]', strtoupper( $section ) );
333
+
334
+ foreach( $this->sysinfo[ $section ] as $key => $value ) {
335
+ if( is_int( $key ) ) {
336
+ $strings[] = " - {$value}";
337
+ continue;
338
+ }
339
+
340
+ $strings[] = sprintf( '%s : %s', $this->pad( $key ), $value );
341
+ }
342
+
343
+ return implode( PHP_EOL, $strings ) . PHP_EOL . PHP_EOL;
344
+ }
345
+
346
+ /**
347
+ * Pad a string with periods to the required length
348
+ *
349
+ * @param string $string
350
+ * @param int|null $pad
351
+ * @param string $char
352
+ *
353
+ * @return string
354
+ */
355
+ protected function pad( $string, $pad = null, $char = '.' )
356
+ {
357
+ $pad = $pad ?: $this->pad;
358
+
359
+ if( strlen( $string ) === $pad ) {
360
+ return $string;
361
+ }
362
+
363
+ return str_pad( "{$string} ", $pad, $char );
364
+ }
365
+
366
+ /**
367
+ * Get the info title
368
+ *
369
+ * @param string $title
370
+ *
371
+ * @return bool
372
+ */
373
+ protected function title( $title )
374
+ {
375
+ if( !$title ) {
376
+ return false;
377
+ }
378
+
379
+ $this->sysinfo[ strtoupper( $title ) ] = [];
380
+
381
+ return true;
382
+ }
383
+
384
+ /**
385
+ * Get the webhost name
386
+ *
387
+ * @return string
388
+ */
389
+ protected function webhost()
390
+ {
391
+ $server_name = filter_input( INPUT_SERVER, 'SERVER_NAME' );
392
+
393
+ $uname = php_uname();
394
+
395
+ if( defined( 'WPE_APIKEY' ) ) {
396
+ $host = 'WP Engine';
397
+ } elseif( defined( 'PAGELYBIN' ) ) {
398
+ $host = 'Pagely';
399
+ } elseif( DB_HOST == 'localhost:/tmp/mysql5.sock' ) {
400
+ $host = 'ICDSoft';
401
+ } elseif( DB_HOST == 'mysqlv5' ) {
402
+ $host = 'NetworkSolutions';
403
+ } elseif( strpos( DB_HOST, 'ipagemysql.com' ) !== false ) {
404
+ $host = 'iPage';
405
+ } elseif( strpos( DB_HOST, 'ipowermysql.com' ) !== false ) {
406
+ $host = 'IPower';
407
+ } elseif( strpos( DB_HOST, '.gridserver.com' ) !== false ) {
408
+ $host = 'MediaTemple Grid';
409
+ } elseif( strpos( DB_HOST, '.pair.com' ) !== false ) {
410
+ $host = 'pair Networks';
411
+ } elseif( strpos( DB_HOST, '.stabletransit.com' ) !== false ) {
412
+ $host = 'Rackspace Cloud';
413
+ } elseif( strpos( DB_HOST, '.sysfix.eu' ) !== false ) {
414
+ $host = 'SysFix.eu Power Hosting';
415
+ } elseif( strpos( $server_name, 'Flywheel' ) !== false ) {
416
+ $host = 'Flywheel';
417
+ } elseif( filter_input( INPUT_SERVER, 'DH_USER' ) ) {
418
+ $host = 'DreamHost';
419
+ } elseif( strpos( $uname, 'bluehost.com' ) !== false ) {
420
+ $host = 'Bluehost';
421
+ } elseif( strpos( $uname, 'secureserver.net') !== false ) {
422
+ $host = 'GoDaddy';
423
+ } elseif( strpos( $uname, '.inmotionhosting.com') !== false ) {
424
+ $host = 'InMotion Hosting';
425
+ } elseif( strpos( $uname, '.ovh.net') !== false ) {
426
+ $host = 'OVH';
427
+ } elseif( strpos( $uname, '.accountservergroup.com ') !== false ) {
428
+ $host = 'Site5';
429
+ } elseif( strpos( $uname, '.stratoserver.net ') !== false ) {
430
+ $host = 'STRATO';
431
+ } else {
432
+ $host = sprintf( '%s, %s', DB_HOST, $server_name );
433
+ }
434
+
435
+ return $host;
436
+ }
437
+
438
+ /**
439
+ * Format an array of passed WordPress plugins
440
+ *
441
+ * @param string $title
442
+ *
443
+ * @return string|null
444
+ */
445
+ protected function wordpressPlugins( $title, array $plugins )
446
+ {
447
+ if( !count( $plugins ) || !$this->title( $title ) )return;
448
+
449
+ $pad = max( array_map( 'strlen', $plugins ) );
450
+
451
+ foreach( $plugins as $path => $plugin ) {
452
+ $this->sysinfo[ $title ][] = sprintf( '%s : %s', $this->pad( $plugin, $pad ), $path );
453
+ }
454
+
455
+ return $this->implode( $title );
456
+ }
457
+ }
plugin/Validator.php ADDED
@@ -0,0 +1,587 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ *
9
+ * Much of the code in this class is derived from Illuminate\Validation\Validator (5.3)
10
+ * Copyright (c) <Taylor Otwell>
11
+ * -------------------------------------------------------------------------------------------------
12
+ */
13
+
14
+ namespace GeminiLabs\SiteReviews;
15
+
16
+ use BadMethodCallException;
17
+ use GeminiLabs\SiteReviews\Strings;
18
+ use InvalidArgumentException;
19
+
20
+ class Validator
21
+ {
22
+ /**
23
+ * @var array
24
+ */
25
+ public $errors = [];
26
+
27
+ /**
28
+ * The data under validation.
29
+ *
30
+ * @var array
31
+ */
32
+ protected $data = [];
33
+
34
+ /**
35
+ * The failed validation rules.
36
+ *
37
+ * @var array
38
+ */
39
+ protected $failedRules = [];
40
+
41
+ /**
42
+ * The rules to be applied to the data.
43
+ *
44
+ * @var array
45
+ */
46
+ protected $rules = [];
47
+
48
+ /**
49
+ * The size related validation rules.
50
+ *
51
+ * @var array
52
+ */
53
+ protected $sizeRules = [
54
+ 'Between',
55
+ 'Max',
56
+ 'Min',
57
+ ];
58
+
59
+ /**
60
+ * The validation rules that imply the field is required.
61
+ *
62
+ * @var array
63
+ */
64
+ protected $implicitRules = [
65
+ 'Required',
66
+ ];
67
+
68
+ /**
69
+ * The numeric related validation rules.
70
+ *
71
+ * @var array
72
+ */
73
+ protected $numericRules = [
74
+ 'Numeric',
75
+ ];
76
+
77
+ /**
78
+ * Run the validator's rules against its data.
79
+ *
80
+ * @param mixed $data
81
+ *
82
+ * @return array
83
+ */
84
+ public function validate( $data, array $rules = [] )
85
+ {
86
+ $this->normalizeData( $data );
87
+ $this->setRules( $rules );
88
+
89
+ foreach( $this->rules as $attribute => $rules ) {
90
+ foreach( $rules as $rule ) {
91
+ $this->validateAttribute( $attribute, $rule );
92
+
93
+ if( $this->shouldStopValidating( $attribute ) )break;
94
+ }
95
+ }
96
+
97
+ return $this->errors;
98
+ }
99
+
100
+ /**
101
+ * Add an error message to the validator's collection of errors.
102
+ *
103
+ * @param string $attribute
104
+ * @param string $rule
105
+ *
106
+ * @return void
107
+ */
108
+ protected function addError( $attribute, $rule, array $parameters )
109
+ {
110
+ $message = $this->getMessage( $attribute, $rule, $parameters );
111
+
112
+ $this->errors[ $attribute ]['errors'][] = $message;
113
+
114
+ if( !isset( $this->errors[ $attribute ]['value'] ) ) {
115
+ $this->errors[ $attribute ]['value'] = $this->getValue( $attribute );
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Add a failed rule and error message to the collection.
121
+ *
122
+ * @param string $attribute
123
+ * @param string $rule
124
+ *
125
+ * @return void
126
+ */
127
+ protected function addFailure( $attribute, $rule, array $parameters )
128
+ {
129
+ $this->addError( $attribute, $rule, $parameters );
130
+
131
+ $this->failedRules[ $attribute ][ $rule ] = $parameters;
132
+ }
133
+
134
+ /**
135
+ * Get the data type of the given attribute.
136
+ *
137
+ * @param string $attribute
138
+ * @return string
139
+ */
140
+ protected function getAttributeType( $attribute )
141
+ {
142
+ return $this->hasRule( $attribute, $this->numericRules )
143
+ ? 'numeric'
144
+ : 'string';
145
+ }
146
+
147
+ /**
148
+ * Get the validation message for an attribute and rule.
149
+ *
150
+ * @param string $attribute
151
+ * @param string $rule
152
+ *
153
+ * @return string|null
154
+ */
155
+ protected function getMessage( $attribute, $rule, array $parameters )
156
+ {
157
+ if( in_array( $rule, $this->sizeRules ) ) {
158
+ return $this->getSizeMessage( $attribute, $rule, $parameters );
159
+ }
160
+
161
+ $lowerRule = $this->snakeCase( $rule );
162
+
163
+ return $this->translator( $lowerRule, $rule, $attribute, $parameters );
164
+ }
165
+
166
+ /**
167
+ * Get a rule and its parameters for a given attribute.
168
+ *
169
+ * @param string $attribute
170
+ * @param string|array $rules
171
+ *
172
+ * @return array|null
173
+ */
174
+ protected function getRule( $attribute, $rules )
175
+ {
176
+ if( !array_key_exists( $attribute, $this->rules ) )return;
177
+
178
+ $rules = (array) $rules;
179
+
180
+ foreach( $this->rules[ $attribute ] as $rule ) {
181
+ list( $rule, $parameters ) = $this->parseRule( $rule );
182
+
183
+ if( in_array( $rule, $rules ) ) {
184
+ return [ $rule, $parameters ];
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Get the size of an attribute.
191
+ *
192
+ * @param string $attribute
193
+ * @param mixed $value
194
+ *
195
+ * @return mixed
196
+ */
197
+ protected function getSize( $attribute, $value )
198
+ {
199
+ $hasNumeric = $this->hasRule( $attribute, $this->numericRules );
200
+
201
+ if( is_numeric( $value ) && $hasNumeric ) {
202
+ return $value;
203
+ }
204
+ elseif( is_array( $value ) ) {
205
+ return count( $value );
206
+ }
207
+
208
+ return mb_strlen( $value );
209
+ }
210
+
211
+ /**
212
+ * Get the proper error message for an attribute and size rule.
213
+ *
214
+ * @param string $attribute
215
+ * @param string $rule
216
+ *
217
+ * @return string|null
218
+ */
219
+ protected function getSizeMessage( $attribute, $rule, array $parameters )
220
+ {
221
+ $lowerRule = $this->snakeCase( $rule );
222
+ $type = $this->getAttributeType( $attribute );
223
+
224
+ $lowerRule .= ".{$type}";
225
+
226
+ return $this->translator( $lowerRule, $rule, $attribute, $parameters );
227
+ }
228
+
229
+ /**
230
+ * Get the value of a given attribute.
231
+ *
232
+ * @param string $attribute
233
+ *
234
+ * @return mixed
235
+ */
236
+ protected function getValue( $attribute )
237
+ {
238
+ if( isset( $this->data[ $attribute ] ) ) {
239
+ return $this->data[ $attribute ];
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Determine if the given attribute has a rule in the given set.
245
+ *
246
+ * @param string $attribute
247
+ * @param string|array $rules
248
+ *
249
+ * @return bool
250
+ */
251
+ protected function hasRule( $attribute, $rules )
252
+ {
253
+ return !is_null( $this->getRule( $attribute, $rules ) );
254
+ }
255
+
256
+ /**
257
+ * Normalize the provided data to an array.
258
+ *
259
+ * @param mixed $data
260
+ *
261
+ * @return $this
262
+ */
263
+ protected function normalizeData( $data )
264
+ {
265
+ // If an object was provided, get its public properties
266
+ if( is_object( $data ) ) {
267
+ $this->data = get_object_vars( $data );
268
+ }
269
+ else {
270
+ $this->data = $data;
271
+ }
272
+
273
+ return $this;
274
+ }
275
+
276
+ /**
277
+ * Parse a parameter list.
278
+ *
279
+ * @param string $rule
280
+ * @param string $parameter
281
+ *
282
+ * @return array
283
+ */
284
+ protected function parseParameters( $rule, $parameter )
285
+ {
286
+ if( strtolower( $rule ) == 'regex' ) {
287
+ return [ $parameter ];
288
+ }
289
+
290
+ return str_getcsv( $parameter );
291
+ }
292
+
293
+ /**
294
+ * Extract the rule name and parameters from a rule.
295
+ *
296
+ * @param string $rule
297
+ *
298
+ * @return array
299
+ */
300
+ protected function parseRule( $rule )
301
+ {
302
+ $parameters = [];
303
+
304
+ // example: {rule}:{parameters}
305
+ if( strpos( $rule, ':' ) !== false ) {
306
+ list( $rule, $parameter ) = explode( ':', $rule, 2 );
307
+
308
+ // example: {parameter1,parameter2,...}
309
+ $parameters = $this->parseParameters( $rule, $parameter );
310
+ }
311
+
312
+ $rule = ucwords( str_replace( ['-', '_'], ' ', trim( $rule ) ) );
313
+ $rule = str_replace( ' ', '', $rule );
314
+
315
+ return [ $rule, $parameters ];
316
+ }
317
+
318
+ /**
319
+ * Replace all placeholders for the between rule.
320
+ *
321
+ * @param string $message
322
+ *
323
+ * @return string
324
+ */
325
+ protected function replaceBetween( $message, array $parameters )
326
+ {
327
+ return str_replace([':min', ':max'], $parameters, $message );
328
+ }
329
+
330
+ /**
331
+ * Replace all placeholders for the max rule.
332
+ *
333
+ * @param string $message
334
+ *
335
+ * @return string
336
+ */
337
+ protected function replaceMax( $message, array $parameters )
338
+ {
339
+ return str_replace( ':max', $parameters[0], $message );
340
+ }
341
+
342
+ /**
343
+ * Replace all placeholders for the min rule.
344
+ *
345
+ * @param string $message
346
+ *
347
+ * @return string
348
+ */
349
+ protected function replaceMin( $message, array $parameters )
350
+ {
351
+ return str_replace( ':min', $parameters[0], $message );
352
+ }
353
+
354
+ /**
355
+ * Require a certain number of parameters to be present.
356
+ *
357
+ * @param int $count
358
+ * @param string $rule
359
+ *
360
+ * @return void
361
+ * @throws InvalidArgumentException
362
+ */
363
+ protected function requireParameterCount( $count, array $parameters, $rule )
364
+ {
365
+ if( count( $parameters ) < $count ) {
366
+ throw new InvalidArgumentException( "Validation rule $rule requires at least $count parameters." );
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Set the validation rules.
372
+ *
373
+ * @return $this
374
+ */
375
+ protected function setRules( array $rules )
376
+ {
377
+ foreach( $rules as $key => $rule ) {
378
+ $rules[ $key ] = is_string( $rule ) ? explode( '|', $rule ) : $rule;
379
+ }
380
+
381
+ $this->rules = $rules;
382
+
383
+ return $this;
384
+ }
385
+
386
+ /**
387
+ * Check if we should stop further validations on a given attribute.
388
+ *
389
+ * @param string $attribute
390
+ *
391
+ * @return bool
392
+ */
393
+ protected function shouldStopValidating( $attribute )
394
+ {
395
+ return $this->hasRule( $attribute, $this->implicitRules )
396
+ && isset( $this->failedRules[ $attribute ] )
397
+ && array_intersect( array_keys( $this->failedRules[ $attribute ] ), $this->implicitRules );
398
+ }
399
+
400
+ /**
401
+ * Convert a string to snake case.
402
+ *
403
+ * @param string $string
404
+ *
405
+ * @return string
406
+ */
407
+ protected function snakeCase( $string )
408
+ {
409
+ if( !ctype_lower( $string ) ) {
410
+ $string = preg_replace( '/\s+/u', '', $string );
411
+ $string = preg_replace( '/(.)(?=[A-Z])/u', '$1_', $string );
412
+ $string = mb_strtolower( $string, 'UTF-8' );
413
+ }
414
+
415
+ return $string;
416
+ }
417
+
418
+ /**
419
+ * Returns a translated message for the attribute
420
+ *
421
+ * @param string $key
422
+ * @param string $rule
423
+ * @param string $attribute
424
+ *
425
+ * @return string|null
426
+ */
427
+ protected function translator( $key, $rule, $attribute, array $parameters )
428
+ {
429
+ $strings = (new Strings)->validation();
430
+
431
+ $message = isset( $strings[ $key ] )
432
+ ? $strings[ $key ]
433
+ : false;
434
+
435
+ if( !$message )return;
436
+
437
+ $message = str_replace( ':attribute', $attribute, $message );
438
+
439
+ if( method_exists( $this, $replacer = "replace{$rule}" ) ) {
440
+ $message = $this->$replacer( $message, $parameters );
441
+ }
442
+
443
+ return $message;
444
+ }
445
+
446
+ // Rules Validation
447
+ // ---------------------------------------------------------------------------------------------
448
+
449
+ /**
450
+ * Validate that an attribute was "accepted".
451
+ *
452
+ * This validation rule implies the attribute is "required".
453
+ *
454
+ * @param string $attribute
455
+ * @param mixed $value
456
+ *
457
+ * @return bool
458
+ */
459
+ protected function validateAccepted( $attribute, $value )
460
+ {
461
+ $acceptable = ['yes', 'on', '1', 1, true, 'true'];
462
+
463
+ return $this->validateRequired( $attribute, $value ) && in_array( $value, $acceptable, true );
464
+ }
465
+
466
+ /**
467
+ * Validate a given attribute against a rule.
468
+ *
469
+ * @param string $attribute
470
+ * @param string $rule
471
+ *
472
+ * @return void
473
+ * @throws BadMethodCallException
474
+ */
475
+ protected function validateAttribute( $attribute, $rule )
476
+ {
477
+ list( $rule, $parameters ) = $this->parseRule( $rule );
478
+
479
+ if( $rule == '' )return;
480
+
481
+ $value = $this->getValue( $attribute );
482
+
483
+ // is the value filled or is the attribute required?
484
+ // - removed $validatable assignment
485
+ $this->validateRequired( $attribute, $value ) || in_array( $rule, $this->implicitRules );
486
+
487
+ $method = "validate{$rule}";
488
+
489
+ if( !method_exists( $this, $method ) ) {
490
+ throw new BadMethodCallException( "Method [$method] does not exist." );
491
+ }
492
+
493
+ if( !$this->$method( $attribute, $value, $parameters ) ) {
494
+ $this->addFailure( $attribute, $rule, $parameters );
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Validate the size of an attribute is between a set of values.
500
+ *
501
+ * @param string $attribute
502
+ * @param mixed $value
503
+ *
504
+ * @return bool
505
+ */
506
+ protected function validateBetween( $attribute, $value, array $parameters )
507
+ {
508
+ $this->requireParameterCount( 2, $parameters, 'between' );
509
+
510
+ $size = $this->getSize( $attribute, $value );
511
+
512
+ return $size >= $parameters[0] && $size <= $parameters[1];
513
+ }
514
+
515
+ /**
516
+ * Validate that an attribute is a valid e-mail address.
517
+ *
518
+ * @param string $attribute
519
+ * @param mixed $value
520
+ *
521
+ * @return bool
522
+ */
523
+ protected function validateEmail( $attribute, $value )
524
+ {
525
+ return filter_var( $value, FILTER_VALIDATE_EMAIL ) !== false;
526
+ }
527
+
528
+ /**
529
+ * Validate the size of an attribute is less than a maximum value.
530
+ *
531
+ * @param string $attribute
532
+ * @param mixed $value
533
+ *
534
+ * @return bool
535
+ */
536
+ protected function validateMax( $attribute, $value, array $parameters )
537
+ {
538
+ $this->requireParameterCount( 1, $parameters, 'max' );
539
+
540
+ return $this->getSize( $attribute, $value ) <= $parameters[0];
541
+ }
542
+
543
+ /**
544
+ * Validate the size of an attribute is greater than a minimum value.
545
+ *
546
+ * @param string $attribute
547
+ * @param mixed $value
548
+ *
549
+ * @return bool
550
+ */
551
+ protected function validateMin( $attribute, $value, array $parameters )
552
+ {
553
+ $this->requireParameterCount( 1, $parameters, 'min' );
554
+
555
+ return $this->getSize( $attribute, $value ) >= $parameters[0];
556
+ }
557
+
558
+ /**
559
+ * Validate that an attribute is numeric.
560
+ *
561
+ * @param string $attribute
562
+ * @param mixed $value
563
+ *
564
+ * @return bool
565
+ */
566
+ protected function validateNumeric( $attribute, $value )
567
+ {
568
+ return is_numeric( $value );
569
+ }
570
+
571
+ /**
572
+ * Validate that a required attribute exists.
573
+ *
574
+ * @param string $attribute
575
+ * @param mixed $value
576
+ *
577
+ * @return bool
578
+ */
579
+ protected function validateRequired( $attribute, $value )
580
+ {
581
+ return is_null( $value )
582
+ || ( is_string( $value ) && trim( $value ) === '' )
583
+ || ( is_array( $value ) && count( $value ) < 1 )
584
+ ? false
585
+ : true;
586
+ }
587
+ }
plugin/Widget.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Widget Boilerplate
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license GPLv2 or later
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews;
14
+
15
+ use WP_Widget;
16
+
17
+ abstract class Widget extends WP_Widget
18
+ {
19
+ public function __construct( $id_base, $name, $values )
20
+ {
21
+ $control_options = $widget_options = [];
22
+
23
+ if( isset( $values['class'] ) ) {
24
+ $widget_options['classname'] = $values['class'];
25
+ }
26
+
27
+ if( isset( $values['description'] ) ) {
28
+ $widget_options['description'] = $values['description'];
29
+ }
30
+
31
+ if( isset( $values['width'] ) ) {
32
+ $control_options['width'] = $values['width'];
33
+ }
34
+
35
+ parent::__construct( $id_base, $name, $widget_options, $control_options );
36
+ }
37
+
38
+ /**
39
+ * Display the widget form
40
+ * Subclasses should over-ride this function to generate the widget form
41
+ *
42
+ * @param array $instance
43
+ *
44
+ * @return void
45
+ */
46
+ public function form( $instance )
47
+ {
48
+ parent::form( $instance );
49
+ }
50
+
51
+ /**
52
+ * Update the widget form
53
+ *
54
+ * @param array $new_instance
55
+ * @param array $old_instance
56
+ *
57
+ * @return array
58
+ */
59
+ public function update( $new_instance, $old_instance )
60
+ {
61
+ return parent::update( $new_instance, $old_instance );
62
+ }
63
+
64
+ /**
65
+ * Display the widget Html
66
+ * Subclasses should over-ride this function to generate the widget
67
+ *
68
+ * @param array $args
69
+ * @param array $instance
70
+ *
71
+ * @return void
72
+ */
73
+ public function widget( $args, $instance )
74
+ {
75
+ parent::widget( $args, $instance );
76
+ }
77
+
78
+ /**
79
+ * Create a widget form field
80
+ *
81
+ * @return void
82
+ */
83
+ protected function create_field( array $atts = [] )
84
+ {
85
+ // don't prefix the name field
86
+ $atts['prefix'] = false;
87
+ $atts['name'] = $this->get_field_name( $atts['name'] );
88
+
89
+ if( isset( $atts['depends'] ) ) {
90
+ $atts['depends'] = $this->get_field_name( $atts['depends'] );
91
+ }
92
+
93
+ if( !isset( $atts['type'] ) || $atts['type'] == 'text' ) {
94
+ $atts['class'] = isset( $atts['class'] ) ? trim( "{$atts['class']} widefat" ) : 'widefat';
95
+ }
96
+
97
+ glsr_resolve( 'Html' )->renderField( $atts, 'echo' );
98
+ }
99
+
100
+ /**
101
+ * Generate a unique ID string
102
+ *
103
+ * @param mixed $from
104
+ *
105
+ * @return string
106
+ */
107
+ protected function generate_id( $from = [] )
108
+ {
109
+ !empty( $from ) ?: $from = $this->id;
110
+
111
+ return substr( md5( serialize( $from ) ), 0, 8 );
112
+ }
113
+ }
plugin/Widgets/RecentReviews.php ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Recent Reviews widget
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license GPLv2 or later
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Widgets;
14
+
15
+ use GeminiLabs\SiteReviews\Widget;
16
+
17
+ class RecentReviews extends Widget
18
+ {
19
+ /**
20
+ * Display the widget form
21
+ *
22
+ * @param array $instance
23
+ *
24
+ * @return void
25
+ */
26
+ public function form( $instance )
27
+ {
28
+ $defaults = [
29
+ 'class' => '',
30
+ 'display' => 'both',
31
+ 'max_reviews' => 5,
32
+ 'min_rating' => '',
33
+ 'order_by' => '',
34
+ 'show' => ['show_author', 'show_date', 'show_rating'],
35
+ 'title' => '',
36
+ 'type' => '',
37
+ ];
38
+
39
+ if( !empty( $instance ) ) {
40
+ isset( $instance['show'] ) ?: $instance['show'] = [];
41
+ }
42
+
43
+ $args = shortcode_atts( $defaults, $instance );
44
+
45
+ $this->create_field([
46
+ 'type' => 'text',
47
+ 'name' => 'title',
48
+ 'label' => __( 'Title', 'site-reviews' ),
49
+ 'value' => $args['title'],
50
+ ]);
51
+
52
+ $this->create_field([
53
+ 'type' => 'select',
54
+ 'name' => 'display',
55
+ 'class' => 'widefat',
56
+ 'value' => $args['display'],
57
+ 'options' => [
58
+ 'title' => __( 'Display only title', 'site-reviews' ),
59
+ 'excerpt' => __( 'Display only excerpt', 'site-reviews' ),
60
+ 'both' => __( 'Display title and excerpt', 'site-reviews' ),
61
+ ],
62
+ ]);
63
+
64
+ $types = glsr_resolve( 'Database' )->getReviewTypes();
65
+
66
+ if( count( $types ) > 1 ) {
67
+ $this->create_field([
68
+ 'type' => 'select',
69
+ 'name' => 'type',
70
+ 'label' => __( 'Which reviews would you like to display? ', 'site-reviews' ),
71
+ 'value' => $args['type'],
72
+ 'options' => ['' => __( 'All Reviews', 'site-reviews' ) ] + $types,
73
+ ]);
74
+ }
75
+
76
+ $this->create_field([
77
+ 'type' => 'select',
78
+ 'name' => 'min_rating',
79
+ 'label' => __( 'What is the minimum rating to display? ', 'site-reviews' ),
80
+ 'value' => $args['min_rating'],
81
+ 'options' => [
82
+ '5' => __( '5 stars', 'site-reviews' ),
83
+ '4' => __( '4 stars', 'site-reviews' ),
84
+ '3' => __( '3 stars', 'site-reviews' ),
85
+ '2' => __( '2 stars', 'site-reviews' ),
86
+ '1' => __( '1 star', 'site-reviews' ),
87
+ ],
88
+ ]);
89
+
90
+ $this->create_field([
91
+ 'type' => 'number',
92
+ 'name' => 'max_reviews',
93
+ 'label' => __( 'How many reviews would you like to display? ', 'site-reviews' ),
94
+ 'value' => $args['max_reviews'],
95
+ 'default' => 5,
96
+ 'max' => 100,
97
+ ]);
98
+
99
+ $this->create_field([
100
+ 'type' => 'checkbox',
101
+ 'name' => 'show',
102
+ 'value' => $args['show'],
103
+ 'options' => [
104
+ 'show_author' => __( 'Show the name of the reviewer?', 'site-reviews' ),
105
+ 'show_date' => __( 'Show the review date?', 'site-reviews' ),
106
+ 'show_rating' => __( 'Show the review rating?', 'site-reviews' ),
107
+ ],
108
+ ]);
109
+
110
+ $this->create_field([
111
+ 'type' => 'text',
112
+ 'name' => 'class',
113
+ 'label' => __( 'Enter any custom CSS classes here', 'site-reviews' ),
114
+ 'value' => $args['class'],
115
+ ]);
116
+ }
117
+
118
+ /**
119
+ * Update the widget form
120
+ *
121
+ * @param array $new_instance
122
+ * @param array $old_instance
123
+ *
124
+ * @return array
125
+ */
126
+ public function update( $new_instance, $old_instance )
127
+ {
128
+ if( $new_instance['max_reviews'] < 0 ) {
129
+ $new_instance['max_reviews'] = 0;
130
+ }
131
+
132
+ if( $new_instance['max_reviews'] > 100 ) {
133
+ $new_instance['max_reviews'] = 100;
134
+ }
135
+
136
+ return parent::update( $new_instance, $old_instance );
137
+ }
138
+
139
+ /**
140
+ * Display the widget Html
141
+ *
142
+ * @param array $args
143
+ * @param array $instance
144
+ *
145
+ * @return void
146
+ */
147
+ public function widget( $args, $instance )
148
+ {
149
+ $defaults = [
150
+ 'class' => '',
151
+ 'display' => 'both',
152
+ 'max_reviews' => '10',
153
+ 'min_rating' => '5',
154
+ 'order_by' => 'date',
155
+ 'show' => [],
156
+ 'title' => '',
157
+ 'type' => '',
158
+ ];
159
+
160
+ $instance = shortcode_atts( $defaults, $instance );
161
+
162
+ foreach( $instance['show'] as $key ) {
163
+ $instance[ $key ] = true;
164
+ }
165
+
166
+ unset( $instance['show'] );
167
+
168
+ $instance['order_by'] ?: $instance['order_by'] = 'date';
169
+
170
+ $instance['site_name'] = $instance['type'];
171
+
172
+ unset( $instance['type'] );
173
+
174
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
175
+
176
+ echo $args['before_widget'];
177
+
178
+ echo $title ? $args['before_title'] . $title . $args['after_title'] : '';
179
+
180
+ glsr_resolve( 'Html' )->renderPartial( 'reviews', $instance );
181
+
182
+ echo $args['after_widget'];
183
+ }
184
+ }
plugin/Widgets/ReviewsForm.php ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Reviews Form widget
5
+ *
6
+ * @package GeminiLabs\SiteReviews
7
+ * @copyright Copyright (c) 2016, Paul Ryley
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 1.0.0
10
+ * -------------------------------------------------------------------------------------------------
11
+ */
12
+
13
+ namespace GeminiLabs\SiteReviews\Widgets;
14
+
15
+ use GeminiLabs\SiteReviews\Widget;
16
+
17
+ class ReviewsForm extends Widget
18
+ {
19
+ /**
20
+ * Display the widget form
21
+ *
22
+ * @param array $instance
23
+ *
24
+ * @return void
25
+ */
26
+ public function form( $instance )
27
+ {
28
+ $defaults = [
29
+ 'class' => '',
30
+ 'description' => sprintf( __( 'Your email address will not be published. Required fields are marked %s*%s', 'site-reviews' ), '<span>', '</span>' ),
31
+ 'fields' => [],
32
+ 'title' => '',
33
+ ];
34
+
35
+ $args = shortcode_atts( $defaults, $instance );
36
+
37
+ $this->create_field([
38
+ 'type' => 'text',
39
+ 'name' => 'title',
40
+ 'label' => __( 'Title', 'site-reviews' ),
41
+ 'value' => $args['title'],
42
+ ]);
43
+
44
+ $this->create_field([
45
+ 'type' => 'textarea',
46
+ 'name' => 'description',
47
+ 'class' => 'widefat',
48
+ 'label' => __( 'Description', 'site-reviews' ),
49
+ 'value' => $args['description'],
50
+ ]);
51
+
52
+ $this->create_field([
53
+ 'type' => 'checkbox',
54
+ 'name' => 'fields',
55
+ 'value' => $args['fields'],
56
+ 'options' => [
57
+ 'title' => __( 'Hide the title field', 'site-reviews' ),
58
+ 'reviewer' => __( 'Hide the reviewer field', 'site-reviews' ),
59
+ 'email' => __( 'Hide the email field', 'site-reviews' ),
60
+ 'terms' => __( 'Hide the terms field', 'site-reviews' ),
61
+ ],
62
+ ]);
63
+
64
+ $this->create_field([
65
+ 'type' => 'text',
66
+ 'name' => 'class',
67
+ 'label' => __( 'Enter any custom CSS classes here', 'site-reviews' ),
68
+ 'value' => $args['class'],
69
+ ]);
70
+ }
71
+
72
+ /**
73
+ * Display the widget Html
74
+ *
75
+ * @param array $args
76
+ * @param array $instance
77
+ *
78
+ * @return void
79
+ */
80
+ public function widget( $args, $instance )
81
+ {
82
+ $defaults = [
83
+ 'class' => '',
84
+ 'description' => '',
85
+ 'fields' => [],
86
+ 'title' => '',
87
+ ];
88
+
89
+ // custom widget attributes
90
+ $instance = shortcode_atts( $defaults, $instance );
91
+
92
+ $controller = glsr_resolve( 'Controllers\ReviewController' );
93
+ $session = glsr_resolve( 'Session' );
94
+
95
+ $defaults = [
96
+ 'content' => '',
97
+ 'email' => '',
98
+ 'rating' => '',
99
+ 'reviewer' => '',
100
+ 'terms' => '',
101
+ 'title' => '',
102
+ ];
103
+
104
+ $formId = $this->generate_id();
105
+
106
+ $errors = $session->get( "{$formId}-errors", [], 'and then remove errors' );
107
+ $message = $session->get( "{$formId}-message", [], 'and then remove message' );
108
+
109
+ $values = !empty( $errors )
110
+ ? $this->session->get( "{$formId}-values", [], 'and then remove values' )
111
+ : [];
112
+
113
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
114
+
115
+ $description = apply_filters( 'widget_description', $instance['description'], $instance, $this->id_base );
116
+
117
+ echo $args['before_widget'];
118
+
119
+ echo $title ? $args['before_title'] . $title . $args['after_title'] : '';
120
+
121
+ $requireUser = glsr_resolve( 'Database' )->getOption( 'general.require.login', false );
122
+
123
+ if( $requireUser && !is_user_logged_in() ) {
124
+ $message = sprintf(
125
+ __( 'You must be <a href="%s">logged in</a> to submit a review.', 'site-reviews' ),
126
+ wp_login_url( get_permalink() )
127
+ );
128
+ echo wpautop( $message );
129
+ return;
130
+ }
131
+ else {
132
+ echo $description ? sprintf( '<p class="glsr-form-description">%s</p>', $description ) : '';
133
+
134
+ $controller->render( 'submit/index', [
135
+ 'class' => trim( 'glsr-submit-review-form ' . $instance['class'] ),
136
+ 'errors' => $errors,
137
+ 'exclude' => $instance['fields'],
138
+ 'form_id' => $formId,
139
+ 'message' => $message,
140
+ 'values' => shortcode_atts( $defaults, $values ),
141
+ ]);
142
+ }
143
+
144
+ echo $args['after_widget'];
145
+ }
146
+ }
readme.txt ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Site Reviews ===
2
+ Contributors: geminilabs, pryley
3
+ Donate link: http://geminilabs.io/donate
4
+ Tags: best reviews, business ratings, business reviews, curated reviews, moderated reviews, rating widget, rating, ratings shortcode, review widget, reviews login, reviews shortcode, reviews, simple reviews, site reviews, star rating, star review, submit review, testimonial, user rating, user review, user reviews, wp rating, wp review, wp testimonials
5
+ Requires at least: 4.0.0
6
+ Tested up to: 4.7
7
+ Stable tag: 1.2.2
8
+ License: GPLv2 or later
9
+ License URI: https://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ Site Reviews is a WordPress plugin which allows you to easily receive and display reviews for your business or website.
12
+
13
+ == Description ==
14
+
15
+ Site Reviews is a plugin that allows your visitors to submit site reviews with a 1-5 star rating on your website, similar to the way you would on TripAdvisor or Yelp, and display them using a widget or shortcode.
16
+
17
+ You can pin your best reviews so that they are always shown first, require approval before new review submissions are published, require visitors to be logged-in in order to write a review, send custom notifications on a new submission, and more. The plugin provides both widgets and shortcodes along with full shortcode documentation.
18
+
19
+ Add-ons will be released shortly that allow you to sync your TripAdvisor and Yelp reviews in order to display them locally on your own website.
20
+
21
+ The plugin [roadmap](https://github.com/geminilabs/site-reviews/blob/develop/ROADMAP.md) defines the upcoming features which include support for custom review categories, webhook notifications, paginated reviews, allowing visitors to up/down vote submitted reviews, reviewer avatars, percentage/fraction ratings, and more.
22
+
23
+ Follow plugin development on github at: https://github.com/geminilabs/site-reviews/
24
+
25
+ = Current Features =
26
+
27
+ * [new] Webhook notifications for Slack
28
+ * Actively developed and supported
29
+ * Clean and easy-to-configure user interface
30
+ * Configurable Widgets
31
+ * Easy setup and implementation
32
+ * Filter reviews by rating
33
+ * Logging
34
+ * Minimal widget styling (tested with all official WP themes)
35
+ * Review Pagination
36
+ * Shortcodes: Display reviews in your post content and templates
37
+ * WordPress.org support
38
+ * WP Filter Hooks
39
+
40
+ == Installation ==
41
+
42
+ = Minimum plugin requirements =
43
+
44
+ * WordPress 4.0.0
45
+ * PHP 5.4
46
+
47
+ = Automatic installation =
48
+
49
+ Log in to your WordPress dashboard, navigate to the Plugins menu and click "Add New".
50
+
51
+ In the search field type "Site Reviews" and click Search Plugins. Once you have found the plugin you can view details about it such as the point release, rating and description. You can install it by simply clicking "Install Now".
52
+
53
+ = Manual installation =
54
+
55
+ Download the Site Reviews plugin and uploading it to your server via your favorite FTP application. The WordPress codex contains [instructions on how to do this here](https://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation).
56
+
57
+ == Frequently Asked Questions ==
58
+
59
+ = I don't want form placeholders. How do I remove them? =
60
+
61
+ To remove the form placeholders, go to "Settings" -> "Submission Form" and replace all placeholder text with a single space character. Placeholders no more!
62
+
63
+ = How do I disable the plugin CSS and/or Javascript? =
64
+
65
+ To disable the plugin stylesheet or javascript from loading on your website, copy and paste the following WordPress Filter Hooks into your theme's functions.php file:
66
+
67
+ `add_filter( 'site-reviews/assets/css', '__return_false' );`
68
+ `add_filter( 'site-reviews/assets/js', '__return_false' );`
69
+
70
+ = The widgets look funny in my sidebar. What's happening? =
71
+
72
+ Some themes may have very small sidebars and/or CSS styles that conflict or alter the styles within Site Reviews. To correct any styling errors you can either disable the plugin's CSS altogether, or override the CSS selectors in use to make the widget or shortcode appear how you'd like. CSS-related issues are not actively supported as there are too many variations between the thousands of WordPress themes available.
73
+
74
+ == Screenshots ==
75
+
76
+ 1. A view of the All Reviews page
77
+
78
+ 2. A view of the Edit Review page
79
+
80
+ 3. A view of the Site Reviews &gt; Settings page
81
+
82
+ 4. A view of the Site Reviews &gt; Get Help &gt; Documentation tab
83
+
84
+ 5. A view of the Site Reviews &gt; Get Help &gt; System Info tab
85
+
86
+ 6. A view of the Site Reviews &gt; Add-Ons page
87
+
88
+ 7. A view of the Recent Site Reviews widget settings
89
+
90
+ 8. A view of the Submit a Site Review widget settings
91
+
92
+ 9. How the Recent Site Reviews widget looks like using the Twenty Sixteen WordPress theme
93
+
94
+ 10. How the Submit a Site Review widget looks like using the Twenty Sixteen WordPress theme
95
+
96
+ == Changelog ==
97
+
98
+ = 1.2.2 (2017-01-06) =
99
+
100
+ * Added hook to change excerpt length
101
+ * Added hook to disable the excerpt and instead display the full review content
102
+
103
+ = 1.2.1 (2016-12-28) =
104
+
105
+ * Fix PHP 5.4 compatibility regression
106
+
107
+ = 1.2.0 (2016-12-27) =
108
+
109
+ * [feature] Send notifications to Slack
110
+ * Fix notifications to use the email template setting
111
+
112
+ = 1.1.1 (2016-12-05) =
113
+
114
+ * Remove ".star-rating" class on frontend which conflicts with the woocommerce plugin CSS
115
+ * Added hooks to modify rendered fields/partials HTML
116
+
117
+ = 1.1.0 (2016-11-16) =
118
+
119
+ * [feature] Pagination
120
+ * [breaking] Changed internal widget hook names
121
+ * [breaking] Changed text-domain to "site-reviews"
122
+ * Set post_meta defaults when creating a review
123
+ * [addon support] Display read-only reviews
124
+ * [addon support] Display widget link options (conditional field)
125
+ * [addon support] Show all review types by default in widgets and shortcodes
126
+
127
+ = 1.0.4 (2016-11-14) =
128
+
129
+ * use the logged-in user's display_name by default instead of "Anonymous" when submitting reviews
130
+ * Fix shortcodes to insert in the_content correctly
131
+
132
+ = 1.0.3 (2016-11-09) =
133
+
134
+ * Updated plugin description
135
+ * Fix plain-text emails
136
+ * Fix inconsistencies with plugin settings form fields
137
+ * Fix internal add-on integration code
138
+
139
+ = 1.0.2 (2016-10-24) =
140
+
141
+ * Set widget and settings defaults
142
+ * Fix PHP error that is thrown when settings have not yet been saved to DB
143
+
144
+ = 1.0.0 (2016-10-21) =
145
+
146
+ * Initial plugin release
site-reviews.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * ╔═╗╔═╗╔╦╗╦╔╗╔╦ ╦ ╔═╗╔╗ ╔═╗
4
+ * ║ ╦║╣ ║║║║║║║║ ║ ╠═╣╠╩╗╚═╗
5
+ * ╚═╝╚═╝╩ ╩╩╝╚╝╩ ╩═╝╩ ╩╚═╝╚═╝
6
+ *
7
+ * Plugin Name: Site Reviews
8
+ * Plugin URI: https://wordpress.org/plugins/site-reviews
9
+ * Description: Receive and display site reviews
10
+ * Version: 1.2.2
11
+ * Author: Paul Ryley
12
+ * Author URI: http://geminilabs.io
13
+ * License: GPL2
14
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
15
+ * Text Domain: site-reviews
16
+ */
17
+
18
+ defined( 'WPINC' ) || die;
19
+
20
+ require_once __DIR__ . '/activate.php';
21
+ require_once __DIR__ . '/autoload.php';
22
+
23
+ use GeminiLabs\SiteReviews\App;
24
+ use GeminiLabs\SiteReviews\Providers\MainProvider;
25
+
26
+ $app = App::load();
27
+
28
+ $app->register( new MainProvider );
29
+
30
+ register_activation_hook( __FILE__, array( $app, 'activate' ) );
31
+ register_deactivation_hook( __FILE__, array( $app, 'deactivate' ) );
32
+
33
+ $app->init();
34
+
35
+ // Global helper to return $app
36
+ function glsr_app() {
37
+ return App::load();
38
+ }
39
+
40
+ // Global helper to resolve a class instance where $app is not accessible
41
+ function glsr_resolve( $class ) {
42
+ return App::load()->make( $class );
43
+ }
44
+
45
+ // Wordpress 4.0-4.2 support
46
+ if( !function_exists( 'wp_roles' ) ) {
47
+ function wp_roles() {
48
+ global $wp_roles;
49
+ isset( $wp_roles ) ?: $wp_roles = new WP_Roles;
50
+ return $wp_roles;
51
+ }
52
+ }
53
+
54
+ // Wordpress 4.0-4.2 support
55
+ if( !function_exists( 'get_avatar_url' ) ) {
56
+ function get_avatar_url( $id_or_email, $args = null ) {
57
+ isset( $args['size'] ) ?: $args['size'] = 96;
58
+ isset( $args['default'] ) ?: $args['default'] = 'mystery';
59
+ $avatar = get_avatar( $id_or_email, $args['size'], $args['default'] );
60
+ $dom = new \DOMDocument;
61
+ $dom->loadHTML( $avatar );
62
+ return $dom->getElementsByTagName( 'img' )->item(0)->getAttribute( 'src' );
63
+ }
64
+ }
uninstall.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @package GeminiLabs\SiteReviews
5
+ * @copyright Copyright (c) 2016, Paul Ryley
6
+ * @license GPLv2 or later
7
+ * @since 1.0.0
8
+ * -------------------------------------------------------------------------------------------------
9
+ */
10
+
11
+ defined( 'WP_UNINSTALL_PLUGIN' ) or die;
12
+
13
+ require_once __DIR__ . '/site-reviews.php';
14
+
15
+ $options = [
16
+ 'logging',
17
+ 'settings',
18
+ 'version',
19
+ 'version_upgraded_from',
20
+ ];
21
+
22
+ foreach( $options as $option ) {
23
+ delete_option( glsr_app()->prefix . "_{$option}" );
24
+ }
25
+
26
+ glsr_resolve( 'Session' )->deleteSessions();
vendor/sinergi/browser-detector/LICENSE ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-2017 Chris Schuld <chris@chrisschuld.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ 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, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/sinergi/browser-detector/src/Browser.php ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sinergi\BrowserDetector;
4
+
5
+ /**
6
+ * Browser Detection.
7
+ */
8
+ class Browser
9
+ {
10
+ const UNKNOWN = 'unknown';
11
+ const VIVALDI = 'Vivaldi';
12
+ const OPERA = 'Opera';
13
+ const OPERA_MINI = 'Opera Mini';
14
+ const WEBTV = 'WebTV';
15
+ const IE = 'Internet Explorer';
16
+ const POCKET_IE = 'Pocket Internet Explorer';
17
+ const KONQUEROR = 'Konqueror';
18
+ const ICAB = 'iCab';
19
+ const OMNIWEB = 'OmniWeb';
20
+ const FIREBIRD = 'Firebird';
21
+ const FIREFOX = 'Firefox';
22
+ const SEAMONKEY = 'SeaMonkey';
23
+ const ICEWEASEL = 'Iceweasel';
24
+ const SHIRETOKO = 'Shiretoko';
25
+ const MOZILLA = 'Mozilla';
26
+ const AMAYA = 'Amaya';
27
+ const LYNX = 'Lynx';
28
+ const WKHTMLTOPDF = 'wkhtmltopdf';
29
+ const SAFARI = 'Safari';
30
+ const SAMSUNG_BROWSER = 'SamsungBrowser';
31
+ const CHROME = 'Chrome';
32
+ const NAVIGATOR = 'Navigator';
33
+ const GOOGLEBOT = 'GoogleBot';
34
+ const SLURP = 'Yahoo! Slurp';
35
+ const W3CVALIDATOR = 'W3C Validator';
36
+ const BLACKBERRY = 'BlackBerry';
37
+ const ICECAT = 'IceCat';
38
+ const NOKIA_S60 = 'Nokia S60 OSS Browser';
39
+ const NOKIA = 'Nokia Browser';
40
+ const MSN = 'MSN Browser';
41
+ const MSNBOT = 'MSN Bot';
42
+ const NETSCAPE_NAVIGATOR = 'Netscape Navigator';
43
+ const GALEON = 'Galeon';
44
+ const NETPOSITIVE = 'NetPositive';
45
+ const PHOENIX = 'Phoenix';
46
+ const GSA = 'Google Search Appliance';
47
+ const YANDEX = 'Yandex';
48
+ const EDGE = 'Edge';
49
+ const DRAGON = 'Dragon';
50
+
51
+ const VERSION_UNKNOWN = 'unknown';
52
+
53
+ /**
54
+ * @var UserAgent
55
+ */
56
+ private $userAgent;
57
+
58
+ /**
59
+ * @var string
60
+ */
61
+ private $name;
62
+ /**
63
+ * @var string
64
+ */
65
+ private $version;
66
+
67
+ /**
68
+ * @var bool
69
+ */
70
+ private $isRobot = false;
71
+
72
+ /**
73
+ * @var bool
74
+ */
75
+ private $isChromeFrame = false;
76
+
77
+ /**
78
+ * @var bool
79
+ */
80
+ private $isFacebookWebView = false;
81
+
82
+ /**
83
+ * @var bool
84
+ */
85
+ private $isCompatibilityMode = false;
86
+
87
+ /**
88
+ * @param null|string|UserAgent $userAgent
89
+ *
90
+ * @throws \Sinergi\BrowserDetector\InvalidArgumentException
91
+ */
92
+ public function __construct($userAgent = null)
93
+ {
94
+ if ($userAgent instanceof UserAgent) {
95
+ $this->setUserAgent($userAgent);
96
+ } elseif (null === $userAgent || is_string($userAgent)) {
97
+ $this->setUserAgent(new UserAgent($userAgent));
98
+ } else {
99
+ throw new InvalidArgumentException();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Set the name of the OS.
105
+ *
106
+ * @param string $name
107
+ *
108
+ * @return $this
109
+ */
110
+ public function setName($name)
111
+ {
112
+ $this->name = (string)$name;
113
+
114
+ return $this;
115
+ }
116
+
117
+ /**
118
+ * Return the name of the Browser.
119
+ *
120
+ * @return string
121
+ */
122
+ public function getName()
123
+ {
124
+ if (!isset($this->name)) {
125
+ BrowserDetector::detect($this, $this->getUserAgent());
126
+ }
127
+
128
+ return $this->name;
129
+ }
130
+
131
+ /**
132
+ * Check to see if the specific browser is valid.
133
+ *
134
+ * @param string $name
135
+ *
136
+ * @return bool
137
+ */
138
+ public function isBrowser($name)
139
+ {
140
+ return (0 == strcasecmp($this->getName(), trim($name)));
141
+ }
142
+
143
+ /**
144
+ * Set the version of the browser.
145
+ *
146
+ * @param string $version
147
+ *
148
+ * @return $this
149
+ */
150
+ public function setVersion($version)
151
+ {
152
+ $this->version = (string)$version;
153
+
154
+ return $this;
155
+ }
156
+
157
+ /**
158
+ * The version of the browser.
159
+ *
160
+ * @return string
161
+ */
162
+ public function getVersion()
163
+ {
164
+ if (!isset($this->name)) {
165
+ BrowserDetector::detect($this, $this->getUserAgent());
166
+ }
167
+
168
+ return (string) $this->version;
169
+ }
170
+
171
+ /**
172
+ * Set the Browser to be a robot.
173
+ *
174
+ * @param bool $isRobot
175
+ *
176
+ * @return $this
177
+ */
178
+ public function setIsRobot($isRobot)
179
+ {
180
+ $this->isRobot = (bool)$isRobot;
181
+
182
+ return $this;
183
+ }
184
+
185
+ /**
186
+ * Is the browser from a robot (ex Slurp,GoogleBot)?
187
+ *
188
+ * @return bool
189
+ */
190
+ public function getIsRobot()
191
+ {
192
+ if (!isset($this->name)) {
193
+ BrowserDetector::detect($this, $this->getUserAgent());
194
+ }
195
+
196
+ return $this->isRobot;
197
+ }
198
+
199
+ /**
200
+ * @return bool
201
+ */
202
+ public function isRobot()
203
+ {
204
+ return $this->getIsRobot();
205
+ }
206
+
207
+ /**
208
+ * @param bool $isChromeFrame
209
+ *
210
+ * @return $this
211
+ */
212
+ public function setIsChromeFrame($isChromeFrame)
213
+ {
214
+ $this->isChromeFrame = (bool)$isChromeFrame;
215
+
216
+ return $this;
217
+ }
218
+
219
+ /**
220
+ * Used to determine if the browser is actually "chromeframe".
221
+ *
222
+ * @return bool
223
+ */
224
+ public function getIsChromeFrame()
225
+ {
226
+ if (!isset($this->name)) {
227
+ BrowserDetector::detect($this, $this->getUserAgent());
228
+ }
229
+
230
+ return $this->isChromeFrame;
231
+ }
232
+
233
+ /**
234
+ * @return bool
235
+ */
236
+ public function isChromeFrame()
237
+ {
238
+ return $this->getIsChromeFrame();
239
+ }
240
+
241
+ /**
242
+ * @param bool $isFacebookWebView
243
+ *
244
+ * @return $this
245
+ */
246
+ public function setIsFacebookWebView($isFacebookWebView)
247
+ {
248
+ $this->isFacebookWebView = (bool) $isFacebookWebView;
249
+
250
+ return $this;
251
+ }
252
+
253
+ /**
254
+ * Used to determine if the browser is actually "facebook".
255
+ *
256
+ * @return bool
257
+ */
258
+ public function getIsFacebookWebView()
259
+ {
260
+ if (!isset($this->name)) {
261
+ BrowserDetector::detect($this, $this->getUserAgent());
262
+ }
263
+
264
+ return $this->isFacebookWebView;
265
+ }
266
+
267
+ /**
268
+ * @return bool
269
+ */
270
+ public function isFacebookWebView()
271
+ {
272
+ return $this->getIsFacebookWebView();
273
+ }
274
+
275
+ /**
276
+ * @param UserAgent $userAgent
277
+ *
278
+ * @return $this
279
+ */
280
+ public function setUserAgent(UserAgent $userAgent)
281
+ {
282
+ $this->userAgent = $userAgent;
283
+
284
+ return $this;
285
+ }
286
+
287
+ /**
288
+ * @return UserAgent
289
+ */
290
+ public function getUserAgent()
291
+ {
292
+ return $this->userAgent;
293
+ }
294
+
295
+ /**
296
+ * @param bool
297
+ *
298
+ * @return $this
299
+ */
300
+ public function setIsCompatibilityMode($isCompatibilityMode)
301
+ {
302
+ $this->isCompatibilityMode = $isCompatibilityMode;
303
+
304
+ return $this;
305
+ }
306
+
307
+ /**
308
+ * @return bool
309
+ */
310
+ public function isCompatibilityMode()
311
+ {
312
+ return $this->isCompatibilityMode;
313
+ }
314
+
315
+ /**
316
+ * Render pages outside of IE's compatibility mode.
317
+ */
318
+ public function endCompatibilityMode()
319
+ {
320
+ header('X-UA-Compatible: IE=edge');
321
+ }
322
+ }
vendor/sinergi/browser-detector/src/BrowserDetector.php ADDED
@@ -0,0 +1,1006 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sinergi\BrowserDetector;
4
+
5
+ class BrowserDetector implements DetectorInterface
6
+ {
7
+ const FUNC_PREFIX = 'checkBrowser';
8
+
9
+ protected static $userAgentString;
10
+
11
+ /**
12
+ * @var Browser
13
+ */
14
+ protected static $browser;
15
+
16
+ protected static $browsersList = array(
17
+ // well-known, well-used
18
+ // Special Notes:
19
+ // (1) Opera must be checked before FireFox due to the odd
20
+ // user agents used in some older versions of Opera
21
+ // (2) WebTV is strapped onto Internet Explorer so we must
22
+ // check for WebTV before IE
23
+ // (3) Because of Internet Explorer 11 using
24
+ // "Mozilla/5.0 ([...] Trident/7.0; rv:11.0) like Gecko"
25
+ // as user agent, tests for IE must be run before any
26
+ // tests checking for "Mozilla"
27
+ // (4) (deprecated) Galeon is based on Firefox and needs to be
28
+ // tested before Firefox is tested
29
+ // (5) OmniWeb is based on Safari so OmniWeb check must occur
30
+ // before Safari
31
+ // (6) Netscape 9+ is based on Firefox so Netscape checks
32
+ // before FireFox are necessary
33
+ // (7) Microsoft Edge must be checked before Chrome and Safari
34
+ // (7) Vivaldi must be checked before Chrome
35
+ 'WebTv',
36
+ 'InternetExplorer',
37
+ 'Edge',
38
+ 'Opera',
39
+ 'Vivaldi',
40
+ 'Dragon',
41
+ 'Galeon',
42
+ 'NetscapeNavigator9Plus',
43
+ 'SeaMonkey',
44
+ 'Firefox',
45
+ 'Yandex',
46
+ 'Samsung',
47
+ 'Chrome',
48
+ 'OmniWeb',
49
+ // common mobile
50
+ 'Android',
51
+ 'BlackBerry',
52
+ 'Nokia',
53
+ 'Gsa',
54
+ // common bots
55
+ 'Robot',
56
+ // wkhtmltopdf before Safari
57
+ 'Wkhtmltopdf',
58
+ // WebKit base check (post mobile and others)
59
+ 'Safari',
60
+ // everyone else
61
+ 'NetPositive',
62
+ 'Firebird',
63
+ 'Konqueror',
64
+ 'Icab',
65
+ 'Phoenix',
66
+ 'Amaya',
67
+ 'Lynx',
68
+ 'Shiretoko',
69
+ 'IceCat',
70
+ 'Iceweasel',
71
+ 'Mozilla', /* Mozilla is such an open standard that you must check it last */
72
+ );
73
+
74
+ /**
75
+ * Routine to determine the browser type.
76
+ *
77
+ * @param Browser $browser
78
+ * @param UserAgent $userAgent
79
+ *
80
+ * @return bool
81
+ */
82
+ public static function detect(Browser $browser, UserAgent $userAgent = null)
83
+ {
84
+ self::$browser = $browser;
85
+ if (is_null($userAgent)) {
86
+ $userAgent = self::$browser->getUserAgent();
87
+ }
88
+ self::$userAgentString = $userAgent->getUserAgentString();
89
+
90
+ self::$browser->setName(Browser::UNKNOWN);
91
+ self::$browser->setVersion(Browser::VERSION_UNKNOWN);
92
+
93
+ self::checkChromeFrame();
94
+ self::checkFacebookWebView();
95
+
96
+ foreach (self::$browsersList as $browserName) {
97
+ $funcName = self::FUNC_PREFIX . $browserName;
98
+
99
+ if (self::$funcName()) {
100
+ return true;
101
+ }
102
+ }
103
+
104
+ return false;
105
+ }
106
+
107
+ /**
108
+ * Determine if the user is using Chrome Frame.
109
+ *
110
+ * @return bool
111
+ */
112
+ public static function checkChromeFrame()
113
+ {
114
+ if (strpos(self::$userAgentString, 'chromeframe') !== false) {
115
+ self::$browser->setIsChromeFrame(true);
116
+
117
+ return true;
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Determine if the user is using Facebook.
125
+ *
126
+ * @return bool
127
+ */
128
+ public static function checkFacebookWebView()
129
+ {
130
+ if (strpos(self::$userAgentString, 'FBAV') !== false) {
131
+ self::$browser->setIsFacebookWebView(true);
132
+
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Determine if the user is using a BlackBerry.
141
+ *
142
+ * @return bool
143
+ */
144
+ public static function checkBrowserBlackBerry()
145
+ {
146
+ if (stripos(self::$userAgentString, 'blackberry') !== false) {
147
+ if (stripos(self::$userAgentString, 'Version/') !== false) {
148
+ $aresult = explode('Version/', self::$userAgentString);
149
+ if (isset($aresult[1])) {
150
+ $aversion = explode(' ', $aresult[1]);
151
+ self::$browser->setVersion($aversion[0]);
152
+ }
153
+ } else {
154
+ $aresult = explode('/', stristr(self::$userAgentString, 'BlackBerry'));
155
+ if (isset($aresult[1])) {
156
+ $aversion = explode(' ', $aresult[1]);
157
+ self::$browser->setVersion($aversion[0]);
158
+ }
159
+ }
160
+ self::$browser->setName(Browser::BLACKBERRY);
161
+
162
+ return true;
163
+ } elseif (stripos(self::$userAgentString, 'BB10') !== false) {
164
+ $aresult = explode('Version/10.', self::$userAgentString);
165
+ if (isset($aresult[1])) {
166
+ $aversion = explode(' ', $aresult[1]);
167
+ self::$browser->setVersion('10.' . $aversion[0]);
168
+ }
169
+ self::$browser->setName(Browser::BLACKBERRY);
170
+ return true;
171
+ }
172
+
173
+ return false;
174
+ }
175
+
176
+ /**
177
+ * Determine if the browser is a robot.
178
+ *
179
+ * @return bool
180
+ */
181
+ public static function checkBrowserRobot()
182
+ {
183
+ if (stripos(self::$userAgentString, 'bot') !== false ||
184
+ stripos(self::$userAgentString, 'spider') !== false ||
185
+ stripos(self::$userAgentString, 'crawler') !== false
186
+ ) {
187
+ self::$browser->setIsRobot(true);
188
+
189
+ return true;
190
+ }
191
+
192
+ return false;
193
+ }
194
+
195
+ /**
196
+ * Determine if the browser is Internet Explorer.
197
+ *
198
+ * @return bool
199
+ */
200
+ public static function checkBrowserInternetExplorer()
201
+ {
202
+ // Test for v1 - v1.5 IE
203
+ if (stripos(self::$userAgentString, 'microsoft internet explorer') !== false) {
204
+ self::$browser->setName(Browser::IE);
205
+ self::$browser->setVersion('1.0');
206
+ $aresult = stristr(self::$userAgentString, '/');
207
+ if (preg_match('/308|425|426|474|0b1/i', $aresult)) {
208
+ self::$browser->setVersion('1.5');
209
+ }
210
+
211
+ return true;
212
+ } // Test for versions > 1.5 and < 11 and some cases of 11
213
+ else {
214
+ if (stripos(self::$userAgentString, 'msie') !== false && stripos(self::$userAgentString, 'opera') === false
215
+ ) {
216
+ // See if the browser is the odd MSN Explorer
217
+ if (stripos(self::$userAgentString, 'msnb') !== false) {
218
+ $aresult = explode(' ', stristr(str_replace(';', '; ', self::$userAgentString), 'MSN'));
219
+ self::$browser->setName(Browser::MSN);
220
+ if (isset($aresult[1])) {
221
+ self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1]));
222
+ }
223
+
224
+ return true;
225
+ }
226
+ $aresult = explode(' ', stristr(str_replace(';', '; ', self::$userAgentString), 'msie'));
227
+ self::$browser->setName(Browser::IE);
228
+ if (isset($aresult[1])) {
229
+ self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1]));
230
+ }
231
+ // See https://msdn.microsoft.com/en-us/library/ie/hh869301%28v=vs.85%29.aspx
232
+ // Might be 11, anyway !
233
+ if (stripos(self::$userAgentString, 'trident') !== false) {
234
+ preg_match('/rv:(\d+\.\d+)/', self::$userAgentString, $matches);
235
+ if (isset($matches[1])) {
236
+ self::$browser->setVersion($matches[1]);
237
+ }
238
+
239
+ // At this poing in the method, we know the MSIE and Trident
240
+ // strings are present in the $userAgentString. If we're in
241
+ // compatibility mode, we need to determine the true version.
242
+ // If the MSIE version is 7.0, we can look at the Trident
243
+ // version to *approximate* the true IE version. If we don't
244
+ // find a matching pair, ( e.g. MSIE 7.0 && Trident/7.0 )
245
+ // we're *not* in compatibility mode and the browser really
246
+ // is version 7.0.
247
+ if (stripos(self::$userAgentString, 'MSIE 7.0;')) {
248
+ if (stripos(self::$userAgentString, 'Trident/7.0;')) {
249
+ // IE11 in compatibility mode
250
+ self::$browser->setVersion('11.0');
251
+ self::$browser->setIsCompatibilityMode(true);
252
+ } elseif (stripos(self::$userAgentString, 'Trident/6.0;')) {
253
+ // IE10 in compatibility mode
254
+ self::$browser->setVersion('10.0');
255
+ self::$browser->setIsCompatibilityMode(true);
256
+ } elseif (stripos(self::$userAgentString, 'Trident/5.0;')) {
257
+ // IE9 in compatibility mode
258
+ self::$browser->setVersion('9.0');
259
+ self::$browser->setIsCompatibilityMode(true);
260
+ } elseif (stripos(self::$userAgentString, 'Trident/4.0;')) {
261
+ // IE8 in compatibility mode
262
+ self::$browser->setVersion('8.0');
263
+ self::$browser->setIsCompatibilityMode(true);
264
+ }
265
+ }
266
+ }
267
+
268
+ return true;
269
+ } // Test for versions >= 11
270
+ else {
271
+ if (stripos(self::$userAgentString, 'trident') !== false) {
272
+ self::$browser->setName(Browser::IE);
273
+
274
+ preg_match('/rv:(\d+\.\d+)/', self::$userAgentString, $matches);
275
+ if (isset($matches[1])) {
276
+ self::$browser->setVersion($matches[1]);
277
+
278
+ return true;
279
+ } else {
280
+ return false;
281
+ }
282
+ } // Test for Pocket IE
283
+ else {
284
+ if (stripos(self::$userAgentString, 'mspie') !== false ||
285
+ stripos(
286
+ self::$userAgentString,
287
+ 'pocket'
288
+ ) !== false
289
+ ) {
290
+ $aresult = explode(' ', stristr(self::$userAgentString, 'mspie'));
291
+ self::$browser->setName(Browser::POCKET_IE);
292
+
293
+ if (stripos(self::$userAgentString, 'mspie') !== false) {
294
+ if (isset($aresult[1])) {
295
+ self::$browser->setVersion($aresult[1]);
296
+ }
297
+ } else {
298
+ $aversion = explode('/', self::$userAgentString);
299
+ if (isset($aversion[1])) {
300
+ self::$browser->setVersion($aversion[1]);
301
+ }
302
+ }
303
+
304
+ return true;
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ return false;
311
+ }
312
+
313
+ /**
314
+ * Determine if the browser is Opera.
315
+ *
316
+ * @return bool
317
+ */
318
+ public static function checkBrowserOpera()
319
+ {
320
+ if (stripos(self::$userAgentString, 'opera mini') !== false) {
321
+ $resultant = stristr(self::$userAgentString, 'opera mini');
322
+ if (preg_match('/\//', $resultant)) {
323
+ $aresult = explode('/', $resultant);
324
+ if (isset($aresult[1])) {
325
+ $aversion = explode(' ', $aresult[1]);
326
+ self::$browser->setVersion($aversion[0]);
327
+ }
328
+ } else {
329
+ $aversion = explode(' ', stristr($resultant, 'opera mini'));
330
+ if (isset($aversion[1])) {
331
+ self::$browser->setVersion($aversion[1]);
332
+ }
333
+ }
334
+ self::$browser->setName(Browser::OPERA_MINI);
335
+
336
+ return true;
337
+ } elseif (stripos(self::$userAgentString, 'OPiOS') !== false) {
338
+ $aresult = explode('/', stristr(self::$userAgentString, 'OPiOS'));
339
+ if (isset($aresult[1])) {
340
+ $aversion = explode(' ', $aresult[1]);
341
+ self::$browser->setVersion($aversion[0]);
342
+ }
343
+ self::$browser->setName(Browser::OPERA_MINI);
344
+
345
+ return true;
346
+ } elseif (stripos(self::$userAgentString, 'opera') !== false) {
347
+ $resultant = stristr(self::$userAgentString, 'opera');
348
+ if (preg_match('/Version\/(1[0-2].*)$/', $resultant, $matches)) {
349
+ if (isset($matches[1])) {
350
+ self::$browser->setVersion($matches[1]);
351
+ }
352
+ } elseif (preg_match('/\//', $resultant)) {
353
+ $aresult = explode('/', str_replace('(', ' ', $resultant));
354
+ if (isset($aresult[1])) {
355
+ $aversion = explode(' ', $aresult[1]);
356
+ self::$browser->setVersion($aversion[0]);
357
+ }
358
+ } else {
359
+ $aversion = explode(' ', stristr($resultant, 'opera'));
360
+ self::$browser->setVersion(isset($aversion[1]) ? $aversion[1] : '');
361
+ }
362
+ self::$browser->setName(Browser::OPERA);
363
+
364
+ return true;
365
+ } elseif (stripos(self::$userAgentString, ' OPR/') !== false) {
366
+ self::$browser->setName(Browser::OPERA);
367
+ if (preg_match('/OPR\/([\d\.]*)/', self::$userAgentString, $matches)) {
368
+ if (isset($matches[1])) {
369
+ self::$browser->setVersion($matches[1]);
370
+ }
371
+ }
372
+
373
+ return true;
374
+ }
375
+
376
+ return false;
377
+ }
378
+
379
+ /**
380
+ * Determine if the browser is Samsung.
381
+ *
382
+ * @return bool
383
+ */
384
+ public static function checkBrowserSamsung()
385
+ {
386
+ if (stripos(self::$userAgentString, 'SamsungBrowser') !== false) {
387
+ $aresult = explode('/', stristr(self::$userAgentString, 'SamsungBrowser'));
388
+ if (isset($aresult[1])) {
389
+ $aversion = explode(' ', $aresult[1]);
390
+ self::$browser->setVersion($aversion[0]);
391
+ }
392
+ self::$browser->setName(Browser::SAMSUNG_BROWSER);
393
+
394
+ return true;
395
+ }
396
+
397
+ return false;
398
+ }
399
+
400
+ /**
401
+ * Determine if the browser is Chrome.
402
+ *
403
+ * @return bool
404
+ */
405
+ public static function checkBrowserChrome()
406
+ {
407
+ if (stripos(self::$userAgentString, 'Chrome') !== false) {
408
+ $aresult = explode('/', stristr(self::$userAgentString, 'Chrome'));
409
+ if (isset($aresult[1])) {
410
+ $aversion = explode(' ', $aresult[1]);
411
+ self::$browser->setVersion($aversion[0]);
412
+ }
413
+ self::$browser->setName(Browser::CHROME);
414
+
415
+ return true;
416
+ } elseif (stripos(self::$userAgentString, 'CriOS') !== false) {
417
+ $aresult = explode('/', stristr(self::$userAgentString, 'CriOS'));
418
+ if (isset($aresult[1])) {
419
+ $aversion = explode(' ', $aresult[1]);
420
+ self::$browser->setVersion($aversion[0]);
421
+ }
422
+ self::$browser->setName(Browser::CHROME);
423
+
424
+ return true;
425
+ }
426
+
427
+ return false;
428
+ }
429
+
430
+ /**
431
+ * Determine if the browser is Vivaldi.
432
+ *
433
+ * @return bool
434
+ */
435
+ public static function checkBrowserVivaldi()
436
+ {
437
+ if (stripos(self::$userAgentString, 'Vivaldi') !== false) {
438
+ $aresult = explode('/', stristr(self::$userAgentString, 'Vivaldi'));
439
+ if (isset($aresult[1])) {
440
+ $aversion = explode(' ', $aresult[1]);
441
+ self::$browser->setVersion($aversion[0]);
442
+ }
443
+ self::$browser->setName(Browser::VIVALDI);
444
+
445
+ return true;
446
+ }
447
+
448
+ return false;
449
+ }
450
+
451
+ /**
452
+ * Determine if the browser is Microsoft Edge.
453
+ *
454
+ * @return bool
455
+ */
456
+ public static function checkBrowserEdge()
457
+ {
458
+ if (stripos(self::$userAgentString, 'Edge') !== false) {
459
+ $version = explode('Edge/', self::$userAgentString);
460
+ if (isset($version[1])) {
461
+ self::$browser->setVersion((float)$version[1]);
462
+ }
463
+ self::$browser->setName(Browser::EDGE);
464
+
465
+ return true;
466
+ }
467
+
468
+ return false;
469
+ }
470
+
471
+ /**
472
+ * Determine if the browser is Google Search Appliance.
473
+ *
474
+ * @return bool
475
+ */
476
+ public static function checkBrowserGsa()
477
+ {
478
+ if (stripos(self::$userAgentString, 'GSA') !== false) {
479
+ $aresult = explode('/', stristr(self::$userAgentString, 'GSA'));
480
+ if (isset($aresult[1])) {
481
+ $aversion = explode(' ', $aresult[1]);
482
+ self::$browser->setVersion($aversion[0]);
483
+ }
484
+ self::$browser->setName(Browser::GSA);
485
+
486
+ return true;
487
+ }
488
+
489
+ return false;
490
+ }
491
+
492
+ /**
493
+ * Determine if the browser is WebTv.
494
+ *
495
+ * @return bool
496
+ */
497
+ public static function checkBrowserWebTv()
498
+ {
499
+ if (stripos(self::$userAgentString, 'webtv') !== false) {
500
+ $aresult = explode('/', stristr(self::$userAgentString, 'webtv'));
501
+ if (isset($aresult[1])) {
502
+ $aversion = explode(' ', $aresult[1]);
503
+ self::$browser->setVersion($aversion[0]);
504
+ }
505
+ self::$browser->setName(Browser::WEBTV);
506
+
507
+ return true;
508
+ }
509
+
510
+ return false;
511
+ }
512
+
513
+ /**
514
+ * Determine if the browser is NetPositive.
515
+ *
516
+ * @return bool
517
+ */
518
+ public static function checkBrowserNetPositive()
519
+ {
520
+ if (stripos(self::$userAgentString, 'NetPositive') !== false) {
521
+ $aresult = explode('/', stristr(self::$userAgentString, 'NetPositive'));
522
+ if (isset($aresult[1])) {
523
+ $aversion = explode(' ', $aresult[1]);
524
+ self::$browser->setVersion(str_replace(array('(', ')', ';'), '', $aversion[0]));
525
+ }
526
+ self::$browser->setName(Browser::NETPOSITIVE);
527
+
528
+ return true;
529
+ }
530
+
531
+ return false;
532
+ }
533
+
534
+ /**
535
+ * Determine if the browser is Galeon.
536
+ *
537
+ * @return bool
538
+ */
539
+ public static function checkBrowserGaleon()
540
+ {
541
+ if (stripos(self::$userAgentString, 'galeon') !== false) {
542
+ $aresult = explode(' ', stristr(self::$userAgentString, 'galeon'));
543
+ $aversion = explode('/', $aresult[0]);
544
+ if (isset($aversion[1])) {
545
+ self::$browser->setVersion($aversion[1]);
546
+ }
547
+ self::$browser->setName(Browser::GALEON);
548
+
549
+ return true;
550
+ }
551
+
552
+ return false;
553
+ }
554
+
555
+ /**
556
+ * Determine if the browser is Konqueror.
557
+ *
558
+ * @return bool
559
+ */
560
+ public static function checkBrowserKonqueror()
561
+ {
562
+ if (stripos(self::$userAgentString, 'Konqueror') !== false) {
563
+ $aresult = explode(' ', stristr(self::$userAgentString, 'Konqueror'));
564
+ $aversion = explode('/', $aresult[0]);
565
+ if (isset($aversion[1])) {
566
+ self::$browser->setVersion($aversion[1]);
567
+ }
568
+ self::$browser->setName(Browser::KONQUEROR);
569
+
570
+ return true;
571
+ }
572
+
573
+ return false;
574
+ }
575
+
576
+ /**
577
+ * Determine if the browser is iCab.
578
+ *
579
+ * @return bool
580
+ */
581
+ public static function checkBrowserIcab()
582
+ {
583
+ if (stripos(self::$userAgentString, 'icab') !== false) {
584
+ $aversion = explode(' ', stristr(str_replace('/', ' ', self::$userAgentString), 'icab'));
585
+ if (isset($aversion[1])) {
586
+ self::$browser->setVersion($aversion[1]);
587
+ }
588
+ self::$browser->setName(Browser::ICAB);
589
+
590
+ return true;
591
+ }
592
+
593
+ return false;
594
+ }
595
+
596
+ /**
597
+ * Determine if the browser is OmniWeb.
598
+ *
599
+ * @return bool
600
+ */
601
+ public static function checkBrowserOmniWeb()
602
+ {
603
+ if (stripos(self::$userAgentString, 'omniweb') !== false) {
604
+ $aresult = explode('/', stristr(self::$userAgentString, 'omniweb'));
605
+ $aversion = explode(' ', isset($aresult[1]) ? $aresult[1] : '');
606
+ self::$browser->setVersion($aversion[0]);
607
+ self::$browser->setName(Browser::OMNIWEB);
608
+
609
+ return true;
610
+ }
611
+
612
+ return false;
613
+ }
614
+
615
+ /**
616
+ * Determine if the browser is Phoenix.
617
+ *
618
+ * @return bool
619
+ */
620
+ public static function checkBrowserPhoenix()
621
+ {
622
+ if (stripos(self::$userAgentString, 'Phoenix') !== false) {
623
+ $aversion = explode('/', stristr(self::$userAgentString, 'Phoenix'));
624
+ if (isset($aversion[1])) {
625
+ self::$browser->setVersion($aversion[1]);
626
+ }
627
+ self::$browser->setName(Browser::PHOENIX);
628
+
629
+ return true;
630
+ }
631
+
632
+ return false;
633
+ }
634
+
635
+ /**
636
+ * Determine if the browser is Firebird.
637
+ *
638
+ * @return bool
639
+ */
640
+ public static function checkBrowserFirebird()
641
+ {
642
+ if (stripos(self::$userAgentString, 'Firebird') !== false) {
643
+ $aversion = explode('/', stristr(self::$userAgentString, 'Firebird'));
644
+ if (isset($aversion[1])) {
645
+ self::$browser->setVersion($aversion[1]);
646
+ }
647
+ self::$browser->setName(Browser::FIREBIRD);
648
+
649
+ return true;
650
+ }
651
+
652
+ return false;
653
+ }
654
+
655
+ /**
656
+ * Determine if the browser is Netscape Navigator 9+.
657
+ *
658
+ * @return bool
659
+ */
660
+ public static function checkBrowserNetscapeNavigator9Plus()
661
+ {
662
+ if (stripos(self::$userAgentString, 'Firefox') !== false &&
663
+ preg_match('/Navigator\/([^ ]*)/i', self::$userAgentString, $matches)
664
+ ) {
665
+ if (isset($matches[1])) {
666
+ self::$browser->setVersion($matches[1]);
667
+ }
668
+ self::$browser->setName(Browser::NETSCAPE_NAVIGATOR);
669
+
670
+ return true;
671
+ } elseif (stripos(self::$userAgentString, 'Firefox') === false &&
672
+ preg_match('/Netscape6?\/([^ ]*)/i', self::$userAgentString, $matches)
673
+ ) {
674
+ if (isset($matches[1])) {
675
+ self::$browser->setVersion($matches[1]);
676
+ }
677
+ self::$browser->setName(Browser::NETSCAPE_NAVIGATOR);
678
+
679
+ return true;
680
+ }
681
+
682
+ return false;
683
+ }
684
+
685
+ /**
686
+ * Determine if the browser is Shiretoko.
687
+ *
688
+ * @return bool
689
+ */
690
+ public static function checkBrowserShiretoko()
691
+ {
692
+ if (stripos(self::$userAgentString, 'Mozilla') !== false &&
693
+ preg_match('/Shiretoko\/([^ ]*)/i', self::$userAgentString, $matches)
694
+ ) {
695
+ if (isset($matches[1])) {
696
+ self::$browser->setVersion($matches[1]);
697
+ }
698
+ self::$browser->setName(Browser::SHIRETOKO);
699
+
700
+ return true;
701
+ }
702
+
703
+ return false;
704
+ }
705
+
706
+ /**
707
+ * Determine if the browser is Ice Cat.
708
+ *
709
+ * @return bool
710
+ */
711
+ public static function checkBrowserIceCat()
712
+ {
713
+ if (stripos(self::$userAgentString, 'Mozilla') !== false &&
714
+ preg_match('/IceCat\/([^ ]*)/i', self::$userAgentString, $matches)
715
+ ) {
716
+ if (isset($matches[1])) {
717
+ self::$browser->setVersion($matches[1]);
718
+ }
719
+ self::$browser->setName(Browser::ICECAT);
720
+
721
+ return true;
722
+ }
723
+
724
+ return false;
725
+ }
726
+
727
+ /**
728
+ * Determine if the browser is Nokia.
729
+ *
730
+ * @return bool
731
+ */
732
+ public static function checkBrowserNokia()
733
+ {
734
+ if (preg_match("/Nokia([^\/]+)\/([^ SP]+)/i", self::$userAgentString, $matches)) {
735
+ self::$browser->setVersion($matches[2]);
736
+ if (stripos(self::$userAgentString, 'Series60') !== false ||
737
+ strpos(self::$userAgentString, 'S60') !== false
738
+ ) {
739
+ self::$browser->setName(Browser::NOKIA_S60);
740
+ } else {
741
+ self::$browser->setName(Browser::NOKIA);
742
+ }
743
+
744
+ return true;
745
+ }
746
+
747
+ return false;
748
+ }
749
+
750
+ /**
751
+ * Determine if the browser is Firefox.
752
+ *
753
+ * @return bool
754
+ */
755
+ public static function checkBrowserFirefox()
756
+ {
757
+ if (stripos(self::$userAgentString, 'safari') === false) {
758
+ if (preg_match("/Firefox[\/ \(]([^ ;\)]+)/i", self::$userAgentString, $matches)) {
759
+ if (isset($matches[1])) {
760
+ self::$browser->setVersion($matches[1]);
761
+ }
762
+ self::$browser->setName(Browser::FIREFOX);
763
+
764
+ return true;
765
+ } elseif (preg_match('/Firefox$/i', self::$userAgentString, $matches)) {
766
+ self::$browser->setVersion('');
767
+ self::$browser->setName(Browser::FIREFOX);
768
+
769
+ return true;
770
+ }
771
+ }
772
+
773
+ return false;
774
+ }
775
+
776
+ /**
777
+ * Determine if the browser is SeaMonkey.
778
+ *
779
+ * @return bool
780
+ */
781
+ public static function checkBrowserSeaMonkey()
782
+ {
783
+ if (stripos(self::$userAgentString, 'safari') === false) {
784
+ if (preg_match("/SeaMonkey[\/ \(]([^ ;\)]+)/i", self::$userAgentString, $matches)) {
785
+ if (isset($matches[1])) {
786
+ self::$browser->setVersion($matches[1]);
787
+ }
788
+ self::$browser->setName(Browser::SEAMONKEY);
789
+
790
+ return true;
791
+ } elseif (preg_match('/SeaMonkey$/i', self::$userAgentString, $matches)) {
792
+ self::$browser->setVersion('');
793
+ self::$browser->setName(Browser::SEAMONKEY);
794
+
795
+ return true;
796
+ }
797
+ }
798
+
799
+ return false;
800
+ }
801
+
802
+ /**
803
+ * Determine if the browser is Iceweasel.
804
+ *
805
+ * @return bool
806
+ */
807
+ public static function checkBrowserIceweasel()
808
+ {
809
+ if (stripos(self::$userAgentString, 'Iceweasel') !== false) {
810
+ $aresult = explode('/', stristr(self::$userAgentString, 'Iceweasel'));
811
+ if (isset($aresult[1])) {
812
+ $aversion = explode(' ', $aresult[1]);
813
+ self::$browser->setVersion($aversion[0]);
814
+ }
815
+ self::$browser->setName(Browser::ICEWEASEL);
816
+
817
+ return true;
818
+ }
819
+
820
+ return false;
821
+ }
822
+
823
+ /**
824
+ * Determine if the browser is Mozilla.
825
+ *
826
+ * @return bool
827
+ */
828
+ public static function checkBrowserMozilla()
829
+ {
830
+ if (stripos(self::$userAgentString, 'mozilla') !== false &&
831
+ preg_match('/rv:[0-9].[0-9][a-b]?/i', self::$userAgentString) &&
832
+ stripos(self::$userAgentString, 'netscape') === false
833
+ ) {
834
+ $aversion = explode(' ', stristr(self::$userAgentString, 'rv:'));
835
+ preg_match('/rv:[0-9].[0-9][a-b]?/i', self::$userAgentString, $aversion);
836
+ self::$browser->setVersion(str_replace('rv:', '', $aversion[0]));
837
+ self::$browser->setName(Browser::MOZILLA);
838
+
839
+ return true;
840
+ } elseif (stripos(self::$userAgentString, 'mozilla') !== false &&
841
+ preg_match('/rv:[0-9]\.[0-9]/i', self::$userAgentString) &&
842
+ stripos(self::$userAgentString, 'netscape') === false
843
+ ) {
844
+ $aversion = explode('', stristr(self::$userAgentString, 'rv:'));
845
+ self::$browser->setVersion(str_replace('rv:', '', $aversion[0]));
846
+ self::$browser->setName(Browser::MOZILLA);
847
+
848
+ return true;
849
+ } elseif (stripos(self::$userAgentString, 'mozilla') !== false &&
850
+ preg_match('/mozilla\/([^ ]*)/i', self::$userAgentString, $matches) &&
851
+ stripos(self::$userAgentString, 'netscape') === false
852
+ ) {
853
+ if (isset($matches[1])) {
854
+ self::$browser->setVersion($matches[1]);
855
+ }
856
+ self::$browser->setName(Browser::MOZILLA);
857
+
858
+ return true;
859
+ }
860
+
861
+ return false;
862
+ }
863
+
864
+ /**
865
+ * Determine if the browser is Lynx.
866
+ *
867
+ * @return bool
868
+ */
869
+ public static function checkBrowserLynx()
870
+ {
871
+ if (stripos(self::$userAgentString, 'lynx') !== false) {
872
+ $aresult = explode('/', stristr(self::$userAgentString, 'Lynx'));
873
+ $aversion = explode(' ', (isset($aresult[1]) ? $aresult[1] : ''));
874
+ self::$browser->setVersion($aversion[0]);
875
+ self::$browser->setName(Browser::LYNX);
876
+
877
+ return true;
878
+ }
879
+
880
+ return false;
881
+ }
882
+
883
+ /**
884
+ * Determine if the browser is Amaya.
885
+ *
886
+ * @return bool
887
+ */
888
+ public static function checkBrowserAmaya()
889
+ {
890
+ if (stripos(self::$userAgentString, 'amaya') !== false) {
891
+ $aresult = explode('/', stristr(self::$userAgentString, 'Amaya'));
892
+ if (isset($aresult[1])) {
893
+ $aversion = explode(' ', $aresult[1]);
894
+ self::$browser->setVersion($aversion[0]);
895
+ }
896
+ self::$browser->setName(Browser::AMAYA);
897
+
898
+ return true;
899
+ }
900
+
901
+ return false;
902
+ }
903
+
904
+ /**
905
+ * Determine if the browser is Safari.
906
+ *
907
+ * @return bool
908
+ */
909
+ public static function checkBrowserWkhtmltopdf()
910
+ {
911
+ if (stripos(self::$userAgentString, 'wkhtmltopdf') !== false) {
912
+ self::$browser->setName(Browser::WKHTMLTOPDF);
913
+ return true;
914
+ }
915
+
916
+ return false;
917
+ }
918
+ /**
919
+ * Determine if the browser is Safari.
920
+ *
921
+ * @return bool
922
+ */
923
+ public static function checkBrowserSafari()
924
+ {
925
+ if (stripos(self::$userAgentString, 'Safari') !== false) {
926
+ $aresult = explode('/', stristr(self::$userAgentString, 'Version'));
927
+ if (isset($aresult[1])) {
928
+ $aversion = explode(' ', $aresult[1]);
929
+ self::$browser->setVersion($aversion[0]);
930
+ } else {
931
+ self::$browser->setVersion(Browser::VERSION_UNKNOWN);
932
+ }
933
+ self::$browser->setName(Browser::SAFARI);
934
+
935
+ return true;
936
+ }
937
+
938
+ return false;
939
+ }
940
+
941
+ /**
942
+ * Determine if the browser is Yandex.
943
+ *
944
+ * @return bool
945
+ */
946
+ public static function checkBrowserYandex()
947
+ {
948
+ if (stripos(self::$userAgentString, 'YaBrowser') !== false) {
949
+ $aresult = explode('/', stristr(self::$userAgentString, 'YaBrowser'));
950
+ if (isset($aresult[1])) {
951
+ $aversion = explode(' ', $aresult[1]);
952
+ self::$browser->setVersion($aversion[0]);
953
+ }
954
+ self::$browser->setName(Browser::YANDEX);
955
+
956
+ return true;
957
+ }
958
+
959
+ return false;
960
+ }
961
+
962
+ /**
963
+ * Determine if the browser is Comodo Dragon / Ice Dragon / Chromodo.
964
+ *
965
+ * @return bool
966
+ */
967
+ public static function checkBrowserDragon()
968
+ {
969
+ if (stripos(self::$userAgentString, 'Dragon') !== false) {
970
+ $aresult = explode('/', stristr(self::$userAgentString, 'Dragon'));
971
+ if (isset($aresult[1])) {
972
+ $aversion = explode(' ', $aresult[1]);
973
+ self::$browser->setVersion($aversion[0]);
974
+ }
975
+ self::$browser->setName(Browser::DRAGON);
976
+
977
+ return true;
978
+ }
979
+
980
+ return false;
981
+ }
982
+
983
+ /**
984
+ * Determine if the browser is Android.
985
+ *
986
+ * @return bool
987
+ */
988
+ public static function checkBrowserAndroid()
989
+ {
990
+ // Navigator
991
+ if (stripos(self::$userAgentString, 'Android') !== false) {
992
+ if (preg_match('/Version\/([\d\.]*)/i', self::$userAgentString, $matches)) {
993
+ if (isset($matches[1])) {
994
+ self::$browser->setVersion($matches[1]);
995
+ }
996
+ } else {
997
+ self::$browser->setVersion(Browser::VERSION_UNKNOWN);
998
+ }
999
+ self::$browser->setName(Browser::NAVIGATOR);
1000
+
1001
+ return true;
1002
+ }
1003
+
1004
+ return false;
1005
+ }
1006
+ }
vendor/sinergi/browser-detector/src/DetectorInterface.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sinergi\BrowserDetector;
4
+
5
+ interface DetectorInterface
6
+ {
7
+ }
vendor/sinergi/browser-detector/src/InvalidArgumentException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sinergi\BrowserDetector;
4
+
5
+ class InvalidArgumentException extends \InvalidArgumentException
6
+ {
7
+ }
vendor/sinergi/browser-detector/src/UserAgent.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sinergi\BrowserDetector;
4
+
5
+ class UserAgent
6
+ {
7
+ /**
8
+ * @var string
9
+ */
10
+ private $userAgentString;
11
+
12
+ /**
13
+ * @param string $userAgentString
14
+ */
15
+ public function __construct($userAgentString = null)
16
+ {
17
+ if (null !== $userAgentString) {
18
+ $this->setUserAgentString($userAgentString);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * @param string $userAgentString
24
+ *
25
+ * @return $this
26
+ */
27
+ public function setUserAgentString($userAgentString)
28
+ {
29
+ $this->userAgentString = (string)$userAgentString;
30
+
31
+ return $this;
32
+ }
33
+
34
+ /**
35
+ * @return string
36
+ */
37
+ public function getUserAgentString()
38
+ {
39
+ if (null === $this->userAgentString) {
40
+ $this->createUserAgentString();
41
+ }
42
+
43
+ return $this->userAgentString;
44
+ }
45
+
46
+ /**
47
+ * @return string
48
+ */
49
+ public function createUserAgentString()
50
+ {
51
+ $userAgentString = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
52
+ $this->setUserAgentString($userAgentString);
53
+
54
+ return $userAgentString;
55
+ }
56
+ }
views/base.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die;
2
+
3
+ $file = trailingslashit( __DIR__ ) . "{$view}.php";
4
+
5
+ $file = apply_filters( 'site-reviews/addon/views/file', $file, $view, $data );
6
+
7
+ if( file_exists( $file ) ) {
8
+ include $file;
9
+ }
10
+ else {
11
+ $log->error( sprintf( 'File not found: %s', $file ) );
12
+ }
views/edit/meta.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <?php
4
+ $siteName = get_post_meta( $post->ID, 'site_name', true );
5
+
6
+ if( $siteName == 'local' ) {
7
+ $siteName = __( 'Local Review', 'site-reviews' );
8
+ }
9
+
10
+ $reviewUrl = get_post_meta( $post->ID, 'url', true );
11
+
12
+ if( $reviewUrl ) {
13
+ $siteName = sprintf( '<a href="%s" target="_blank">%s</a>', $reviewUrl, ucfirst( $siteName ) );
14
+ }
15
+
16
+ $modified = false;
17
+
18
+ if( $post->post_title !== get_post_meta( $post->ID, 'title', true )
19
+ || $post->post_content !== get_post_meta( $post->ID, 'content', true )
20
+ || $post->post_date !== get_post_meta( $post->ID, 'date', true ) ) {
21
+ $modified = true;
22
+ }
23
+
24
+ $revertUrl = wp_nonce_url( admin_url( sprintf( 'post.php?post=%s&action=revert', $post->ID ) ), 'revert-review_' . $post->ID );
25
+
26
+ $revert = !$modified
27
+ ? __( 'Nothing to Revert', 'site-reviews' )
28
+ : __( 'Revert Changes', 'site-reviews' );
29
+
30
+ $revertButton = !$modified
31
+ ? sprintf( '<button id="revert" class="button button-large" disabled>%s</button>', $revert )
32
+ : sprintf( '<a href="%s" id="revert" class="button button-large">%s</a>', $revertUrl, $revert );
33
+ ?>
34
+
35
+ <table class="glsr-metabox-table">
36
+ <tbody>
37
+ <tr>
38
+ <td><?= __( 'Rating', 'site-reviews' ); ?></th>
39
+ <td><?php $html->renderPartial( 'rating', ['stars' => get_post_meta( $post->ID, 'rating', true ) ] ); ?></td>
40
+ </tr>
41
+ <tr>
42
+ <td><?= __( 'Type', 'site-reviews' ); ?></th>
43
+ <td><?= $siteName; ?></td>
44
+ </tr>
45
+ <tr>
46
+ <td><?= __( 'Date', 'site-reviews' ); ?></th>
47
+ <td><?= get_date_from_gmt( get_post_meta( $post->ID, 'date', true ), 'F j, Y' ); ?></td>
48
+ </tr>
49
+ <tr>
50
+ <td><?= __( 'Reviewer', 'site-reviews' ); ?></th>
51
+ <td><?= get_post_meta( $post->ID, 'author', true ); ?></td>
52
+ </tr>
53
+ <tr>
54
+ <td><?= __( 'Avatar', 'site-reviews' ); ?></th>
55
+ <td><img src="<?= get_post_meta( $post->ID, 'avatar', true ); ?>" width="96"></td>
56
+ </tr>
57
+ </tbody>
58
+ </table>
59
+
60
+ <div class="revert-action">
61
+ <span class="spinner"></span>
62
+ <?= $revertButton; ?>
63
+ </div>
views/edit/notice.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php defined( 'WPINC' ) || die;
2
+
3
+ $notices->show();
views/edit/pinned.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div id="misc-pub-pinned" class="misc-pub-section misc-pub-pinned">
4
+
5
+ <?php
6
+ global $wp_version;
7
+
8
+ // WP < 4.4 support
9
+ if( version_compare( $wp_version, '4.4', '<' ) ) {
10
+ printf( '<span class="pinned-icon">%s</span>', file_get_contents( glsr_app()->path . 'assets/img/pinned.svg' ) );
11
+ }
12
+
13
+ $pinnedNo = __( 'No', 'site-reviews' );
14
+ $pinnedYes = __( 'Yes', 'site-reviews' );
15
+ ?>
16
+
17
+ <label for="pinned-status"><?= __( 'Pinned', 'site-reviews' ) ?>:</label>
18
+ <span id="pinned-status-text" class="pinned-status-text"><?= $pinned ? $pinnedYes : $pinnedNo; ?></span>
19
+
20
+ <a href="#pinned-status" class="edit-pinned-status hide-if-no-js">
21
+ <span aria-hidden="true"><?= __( 'Edit', 'site-reviews' ); ?></span>
22
+ <span class="screen-reader-text"><?= __( 'Edit pinned status', 'site-reviews' ); ?></span>
23
+ </a>
24
+
25
+ <div id="pinned-status-select" class="pinned-status-select hide-if-js">
26
+
27
+ <input type="hidden" id="hidden-pinned-status" value="<?= intval( $pinned ); ?>">
28
+
29
+ <select name="pinned" id="pinned-status">
30
+ <option value="1"<?php selected( $pinned, false ); ?>><?= __( 'Pin', 'site-reviews' ); ?></option>
31
+ <option value="0"<?php selected( $pinned, true ); ?>><?= __( 'Unpin', 'site-reviews' ); ?></option>
32
+ </select>
33
+
34
+ <a href="#pinned-status" class="save-pinned-status hide-if-no-js button" data-no="<?= $pinnedNo; ?>" data-yes="<?= $pinnedYes; ?>"><?= __( 'OK', 'site-reviews' ); ?></a>
35
+ <a href="#pinned-status" class="cancel-pinned-status hide-if-no-js button-cancel"><?= __( 'Cancel', 'site-reviews' ); ?></a>
36
+
37
+ </div>
38
+
39
+ </div>
views/edit/review.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div id="titlediv">
4
+ <input type="text" id="title" value="<?= $post->post_title ? esc_attr( $post->post_title ) : sprintf( '(%s)', __( 'no title', 'site-reviews' ) ); ?>" readonly>
5
+ </div>
6
+
7
+ <div id="contentdiv">
8
+ <textarea readonly><?= esc_attr( $post->post_content ); ?></textarea>
9
+ </div>
views/email/body.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ {message}
views/email/footer.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ </body>
4
+ </html>
views/email/header.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <!doctype html>
4
+ <html>
5
+ <head>
6
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7
+ <title><?= wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); ?></title>
8
+ </head>
9
+ <body>
views/email/index.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die;
2
+
3
+ include trailingslashit(__DIR__) . "header.php";
4
+ include trailingslashit(__DIR__) . "body.php";
5
+ include trailingslashit(__DIR__) . "footer.php";
views/email/templates/review-notification.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <strong>A new {review_rating}-star review has been submitted:</strong>
4
+
5
+ {review_title}
6
+
7
+ {review_content}
8
+
9
+ {review_author} &lt;{review_email}&gt; - {review_ip}
10
+
11
+ {review_link}
views/menu/addons/addons.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <p><?= __( 'The following Add-Ons extend the functionality of Site Reviews.', 'site-reviews' ); ?></p>
4
+
5
+ <div class="glsr-addons wp-clearfix">
6
+
7
+ <?php
8
+
9
+ $html->renderPartial( 'addon', [
10
+ 'name' => 'tripadvisor',
11
+ 'title' => 'Coming soon...',
12
+ 'description' => __( 'Sync your Tripadvisor business reviews with an optional minimum rating and display them on your site.', 'site-reviews' ),
13
+ 'link' => '',
14
+ ]);
15
+
16
+ $html->renderPartial( 'addon', [
17
+ 'name' => 'yelp',
18
+ 'title' => 'Coming soon...',
19
+ 'description' => __( 'Sync your Yelp business reviews with an optional minimum rating and display them on your site.', 'site-reviews' ),
20
+ 'link' => '',
21
+ ]);
22
+
23
+ ?>
24
+
25
+ </div>
views/menu/help/documentation/hooks.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <p>Hooks (also known as Filters &amp; Actions) are used to make changes to the plugin without modifying the core files of the plugin directly. In order to use the following hooks, you must add them to your theme's <code>functions.php</code> file.</p>
4
+
5
+ <div class="glsr-card card">
6
+ <h3>Change the review excerpt length</h3>
7
+ <pre><code>add_filter( 'site-reviews/reviews/excerpt_length', function() { return 55; });</code></pre>
8
+ <p>Use this hook if you want to modify the excerpt word length (the default excerpt length is 55 words).</p>
9
+ </div>
10
+
11
+ <div class="glsr-card card">
12
+ <h3>Disable the plugin CSS</h3>
13
+ <pre><code>add_filter( 'site-reviews/assets/css', '__return_false' );</code></pre>
14
+ <p>Use this hook if you want to disable the plugin stylesheet from loading on your website.</p>
15
+ </div>
16
+
17
+ <div class="glsr-card card">
18
+ <h3>Disable the plugin javascript</h3>
19
+ <pre><code>add_filter( 'site-reviews/assets/js', '__return_false' );</code></pre>
20
+ <p>Use this hook if you want to disable the plugin javascript from loading on your website.</p>
21
+ </div>
22
+
23
+ <div class="glsr-card card">
24
+ <h3>Display the full review instead of an excerpt</h3>
25
+ <pre><code>add_filter( 'site-reviews/reviews/use_excerpt', '__return_false' );</code></pre>
26
+ <p>Use this hook if you want to show the full review content instead of just an excerpt.</p>
27
+ </div>
28
+
views/menu/help/documentation/shortcodes.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div class="glsr-card card">
4
+ <h3>[site_reviews_form]</h3>
5
+ <p>This shortcode displays the review submission form.</p>
6
+ <code>id="type some random text here"</code>
7
+ <p>This shortcode should only be used on a page once. However, if for any reason you need to include more than one on a page, add the "id" attribute to each with some random text to make it a unique shortcode form.</p>
8
+ <code>title="Submit a Review"</code>
9
+ <p>By default, the shortcode displays no heading. Include the "title" attribute to display a custom shortcode heading.</p>
10
+ <code>description="Required fields are marked &lt;span&gt;*&lt;/span&gt;"</code>
11
+ <p>By default, the shortcode displays no description. Include the "description" attribute to display a custom shortcode description.</p>
12
+ <code>hide=title,reviewer,email,terms</code>
13
+ <p>Add the "hide" attribute to exclude certain fields in the form.</p>
14
+ <code>class="my-reviews-form"</code>
15
+ <p>Include the "class" attribute to add custom CSS classes to the shortcode form.</p>
16
+ </div>
17
+
18
+ <div class="glsr-card card">
19
+ <h3>[site_reviews]</h3>
20
+ <p>This shortcode displays your most recent submitted reviews.</p>
21
+ <code>title="Our Reviews"</code>
22
+ <p>By default, the shortcode displays no heading. Include the "title" attribute to display a custom shortcode heading.</p>
23
+ <code>display=title,excerpt,author,date,rating,url</code>
24
+ <p>By default, the shortcode displays all review fields. Include the "display" attribute to display only the specific fields you want.</p>
25
+ <code>count=10</code>
26
+ <p>By default, the shortcode displays the latest 10 reviews. Include the "count" attribute to change the number of reviews that are displayed.</p>
27
+ <code>rating=4</code>
28
+ <p>By default, the shortcode displays only 5-star reviews. Include the "rating" attribute to set the minimum star-rating of reviews to display.</p>
29
+ <code>class="my-reviews full-width"</code>
30
+ <p>Include the "class" attribute to add custom CSS classes to the shortcode.</p>
31
+ <code>pagination=true</code>
32
+ <p>Include the "pagination" attribute to display reviews in multiple pages (i.e. Page 1, Page 2, etc.). When using pagination, only one [site_reviews] shortcode can be used on a page at a time.</p>
33
+ </div>
views/menu/help/documentation/support.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div class="glsr-card card">
4
+ <h3>F.A.Q.</h3>
5
+ <dl>
6
+ <dt>I don't want form placeholders. How do I remove them?</dt>
7
+ <dd>To remove the form placeholders, go to <code><a href="<?= admin_url( 'edit.php?post_type=site-review&page=settings&tab=settings&section=form' ); ?>">Settings -> Submission Form</a></code> and replace all placeholder text with a single space character. Placeholders no more!</dd>
8
+
9
+ <dt>How do I disable the plugin CSS and/or Javascript?</dt>
10
+ <dd>To disable the plugin stylesheet or javascript from loading on your website, copy and paste the provided <code><a href="<?= admin_url( 'edit.php?post_type=site-review&page=help&tab=documentation&section=hooks' ); ?>">WordPress Filter Hooks</a></code> into your theme's <code>functions.php</code> file.</dd>
11
+ </dl>
12
+ </div>
13
+
14
+ <div class="glsr-card card">
15
+ <h3>Basic Troubleshooting</h3>
16
+ <ol>
17
+ <li>
18
+ <p><strong>Make sure you are using the latest version of Site Reviews.</strong></p>
19
+ <p>Site Reviews is updated frequently with bug patches, security updates, code improvements, and new features. It’s always best to be on the latest version of Site Reviews, and if you are not, chances are that if you are experiencing a real bug it has already been fixed.</p>
20
+ </li>
21
+ <li>
22
+ <p><strong>Switch to the default “Twenty Sixteen” WordPress Theme.</strong></p>
23
+ <p>If you have jQuery or Javascript errors on your page, try switching to the default “&quot;Twenty Sixteen” WordPress Theme and then see if you are still experiencing problems with the plugin. This is especially true if you have purchased a third party theme or had a theme developed by someone else. Theme authors are notorious for improperly adding their own Javascript and/or jQuery.</p>
24
+ </li>
25
+ <li>
26
+ <p><strong>Deactivate all of your plugins.</strong></p>
27
+ <p>If switching to the default WordPress theme did not fix anything, the next thing to try is to deactivate all your plugins and then activate them one at a time, starting with Site Reviews, and reloading the page between each activation. Once the Javascript on your page stops working properly, you have probably found the problem plugin. If you think that you’ve found the problem plugin, deactivate it and go through the rest of your plugins anyway. Hopefully you don’t find any more, but it’s better to be safe than sorry.</p>
28
+ </li>
29
+ </ol>
30
+ </div>
31
+
32
+ <div class="glsr-card card">
33
+ <h3>Contacting Support</h3>
34
+ <p>If none of the above troubleshooting steps help, then congratulations you <em>may</em> just have found a pesky little bug that must be squashed!</p>
35
+ <p>Send an email to <a href="mailto:site-reviews@geminilabs.io?subject=Support%20request">site-reviews@geminilabs.io</a> and make sure to write a detailed description of the problem you are having and the steps needed to reproduce it.</p>
36
+ <p><span class="required">Important:</span> Please also make sure to attach the <em>System Info</em> report to your email. You can download it from the <a href="<?= admin_url( 'edit.php?post_type=site-review&page=help&tab=system' ); ?>">System Info</a> tab.</p>
37
+ </div>
views/menu/help/system.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <br>
4
+
5
+ <form method="post"><?php
6
+ wp_nonce_field( 'download-system-info' );
7
+ printf( '<textarea rows="13" name="%s[system-info]" class="glsr-log" onclick="this.select()" readonly>%s</textarea>', glsr_app()->prefix, $system_info->getAll() );
8
+ printf( '<input type="hidden" name="%s[action]" value="download-system-info">', glsr_app()->prefix );
9
+ submit_button( __( 'Download System Info', 'site-reviews' ), 'secondary', '', false ); ?>
10
+ </form>
11
+
12
+ <br>
13
+
14
+ <h3><?= __( 'Logging', 'site-reviews' ); ?></h3>
15
+ <p><?= __( "You can enable logging for debugging purposes. Log files can grow quickly, so don't leave this on.", 'site-reviews' ); ?></p>
16
+
17
+ <?php $logging_enabled = $db->getOption( '', '', 'logging' ); ?>
18
+
19
+ <?php if( $logging_enabled == 1 ) : ?>
20
+
21
+ <textarea rows="13" id="log-file" class="glsr-log" onclick="this.select()" readonly><?= $log; ?></textarea>
22
+
23
+ <form method="post" class="float-left"><?php
24
+ wp_nonce_field( 'clear-log' );
25
+ printf( '<input type="hidden" name="%s[action]" value="clear-log">', glsr_app()->prefix );
26
+ submit_button( __( 'Clear Log', 'site-reviews' ), 'secondary', 'clear-log', false ); ?>
27
+ </form>
28
+
29
+ <form method="post"><?php
30
+ wp_nonce_field( 'download-log' );
31
+ printf( '<input type="hidden" name="%s[action]" value="download-log">', glsr_app()->prefix );
32
+ submit_button( __( 'Download Log', 'site-reviews' ), 'secondary', '', false ); ?>
33
+ </form>
34
+
35
+ <?php endif; ?>
36
+
37
+ <form method="post" action="<?= admin_url('options.php'); ?>"><?php
38
+
39
+ settings_fields( glsr_app()->id . '-logging' );
40
+ do_settings_sections( glsr_app()->id . '-logging' );
41
+
42
+ if( $logging_enabled == 1 ) {
43
+ printf( '<input type="hidden" name="%s_logging" value="0">', glsr_app()->prefix );
44
+ submit_button( __( 'Disable Logging', 'site-reviews' ) );
45
+ }
46
+ else {
47
+ printf( '<input type="hidden" name="%s_logging" value="1">', glsr_app()->prefix );
48
+ submit_button( __( 'Enable Logging', 'site-reviews' ) );
49
+ }?>
50
+ </form>
views/menu/index.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div class="wrap">
4
+
5
+ <h1 class="page-title"><?= esc_html( get_admin_page_title() ); ?></h1>
6
+
7
+ <?php
8
+
9
+ printf( '<div id="glsr-notices">%s</div>', $notices->show( false ) );
10
+
11
+ $html->renderPartial( 'tabs' , [
12
+ 'page' => $page,
13
+ 'tabs' => $tabs,
14
+ 'tab' => $tabView,
15
+ ]);
16
+
17
+ $html->renderPartial( 'subsubsub' , [
18
+ 'page' => $page,
19
+ 'tabs' => $tabs,
20
+ 'tab' => $tabView,
21
+ 'section' => $tabViewSection,
22
+ ]);
23
+
24
+ $file = $tabViewSection ? sprintf( '%s/%s', $tabView, $tabViewSection ) : $tabView;
25
+ $file = trailingslashit( __DIR__ ) . sprintf( '%s/%s.php', $page, $file );
26
+
27
+ $file = apply_filters( 'site-reviews/addon/views/file', $file, $view, $data );
28
+
29
+ if( file_exists( $file ) ) {
30
+ include $file;
31
+ }
32
+ else {
33
+ $log->error( sprintf( 'File not found: %s', $file ) );
34
+ }
35
+
36
+ ?>
37
+
38
+ </div>
views/menu/settings/licenses.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <br>
views/menu/settings/settings/form.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php defined( 'WPINC' ) || die;
2
+
3
+ $html->renderForm( "{$tabView}/{$tabViewSection}" );
views/menu/settings/settings/general.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php defined( 'WPINC' ) || die;
2
+
3
+ $html->renderForm( "{$tabView}/{$tabViewSection}" );
views/menu/settings/welcome.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <div id="welcome-panel" class="welcome-panel">
4
+ <a class="welcome-panel-close" href="<?= admin_url( 'edit.php?post_type=site-review&page=site-reviews&welcome=0' ); ?>" aria-label="Dismiss the welcome panel">Dismiss</a>
5
+ <div class="welcome-panel-content">
6
+
7
+ <h2>Thank you for installing Reviews!</h2>
8
+ <p class="about-description">Here is how to get started:</p>
9
+
10
+ <div class="welcome-panel-column-container">
11
+
12
+ <div class="welcome-panel-column" style="width:68%;">
13
+ <h3>Get Started</h3>
14
+ <p></p>
15
+ </div>
16
+
17
+ <div class="welcome-panel-column" style="width:32%;">
18
+ <h3>More Actions</h3>
19
+ <ul>
20
+ <li><div class="welcome-icon welcome-widgets-menus">Manage <a href="<?= admin_url( 'widgets.php' ); ?>">widgets</a></div></li>
21
+ </ul>
22
+ </div>
23
+
24
+ </div>
25
+ </div>
26
+ </div>
views/menu/status.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <form method="post" action="">
4
+
5
+ <?php
6
+ echo $html->p( sprintf(
7
+ __( "All dates and times shown here use the WordPress <a href='%s'>configured timezone</a>.", 'site-reviews' ),
8
+ admin_url( 'options-general.php' )
9
+ ));
10
+ ?>
11
+
12
+ <table class="wp-list-table widefat fixed striped glsr-status">
13
+
14
+ <thead>
15
+ <tr>
16
+ <th class="site"><?= __( 'Site', 'site-reviews' ); ?></th>
17
+ <th class="total-fetched column-primary"><?= __( 'Reviews', 'site-reviews' ); ?></th>
18
+ <th class="last-fetch"><?= __( 'Last fetch', 'site-reviews' ); ?></th>
19
+ <th class="next-fetch"><?= __( 'Next scheduled fetch', 'site-reviews' ); ?></th>
20
+ </tr>
21
+ </thead>
22
+
23
+ <tbody>
24
+
25
+ <?php foreach( $tabs['settings']['sections'] as $key => $title ) : ?>
26
+
27
+ <tr data-site="<?= $key; ?>">
28
+ <td class="site">
29
+ <a href="<?= admin_url( 'edit.php?post_type=site-review&page=' . glsr_app()->id . "&tab=settings&section={$key}" ); ?>"><?= $title; ?></a>
30
+ </td>
31
+ <td class="total-fetched column-primary">
32
+ <a href="<?= admin_url( "edit.php?post_type=site-review&post_status=all&site_name={$key}" ); ?>"><?= $db->countReviews( $key ); ?></a>
33
+ <button type="button" class="toggle-row"><span class="screen-reader-text">Show more details</span></button>
34
+ </td>
35
+ <td class="last-fetch" data-colname="<?= __( 'Last fetch', 'site-reviews' ); ?>">
36
+ <?= $db->getOption( $key, __( 'No fetch has been completed', 'site-reviews' ), 'last_fetch' ); ?>
37
+ </td>
38
+ <td class="next-fetch" data-colname="<?= __( 'Next scheduled fetch', 'site-reviews' ); ?>">
39
+ <?= $db->getOption( $key, __( 'Nothing currently scheduled', 'site-reviews' ), 'next_fetch' ); ?>
40
+ </td>
41
+ </tr>
42
+
43
+ <?php endforeach; ?>
44
+
45
+ </tbody>
46
+
47
+ </table>
48
+
49
+ <br>
50
+
51
+ <hr>
52
+
53
+ <table class="form-table">
54
+ <tbody>
55
+
56
+ <?php
57
+
58
+ echo $html->row()->select( 'site', [
59
+ 'label' => __( 'Fetch reviews from', 'site-reviews' ),
60
+ 'options' => $tabs['settings']['sections'],
61
+ 'attributes' => 'data-site',
62
+ 'prefix' => glsr_app()->prefix,
63
+ ]);
64
+
65
+ echo $html->row()->progress([
66
+ 'label' => __( 'Fetch status', 'site-reviews' ),
67
+ 'active' => __( 'Fetching reviews...', 'site-reviews' ),
68
+ 'class' => 'green',
69
+ ]);
70
+ ?>
71
+
72
+ </tbody>
73
+ </table>
74
+
75
+ <?php wp_nonce_field( 'fetch-reviews' ); ?>
76
+
77
+ <?php printf( '<input type="hidden" name="%s[action]" value="fetch-reviews">', glsr_app()->prefix ); ?>
78
+
79
+ <?php submit_button( __( 'Fetch Reviews', 'site-reviews' ), 'large primary', 'fetch-reviews' ); ?>
80
+
81
+ </form>
views/submit/index.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php defined( 'WPINC' ) || die; ?>
2
+
3
+ <form method="post" action="" name="glsr-<?= $form_id; ?>" class="<?= $class; ?>">
4
+ <?php
5
+
6
+ $html->renderField([
7
+ 'type' => 'select',
8
+ 'name' => 'rating',
9
+ 'class' => 'glsr-star-rating',
10
+ 'errors' => $errors,
11
+ 'label' => $db->getOption( 'form.rating.label' ),
12
+ 'prefix' => false,
13
+ 'render' => !in_array( 'rating', $exclude ),
14
+ 'suffix' => $form_id,
15
+ 'value' => $values['rating'],
16
+ 'options' => [
17
+ '' => __( 'Select a Rating', 'site-reviews' ),
18
+ '5' => __( 'Excellent', 'site-reviews' ),
19
+ '4' => __( 'Very Good', 'site-reviews' ),
20
+ '3' => __( 'Average', 'site-reviews' ),
21
+ '2' => __( 'Poor', 'site-reviews' ),
22
+ '1' => __( 'Terrible', 'site-reviews' ),
23
+ ],
24
+ ]);
25
+
26
+ $html->renderField([
27
+ 'type' => 'text',
28
+ 'name' => 'title',
29
+ 'errors' => $errors,
30
+ 'label' => $db->getOption( 'form.title.label' ),
31
+ 'placeholder' => $db->getOption( 'form.title.placeholder' ),
32
+ 'prefix' => false,
33
+ 'render' => !in_array( 'title', $exclude ),
34
+ 'required' => true,
35
+ 'suffix' => $form_id,
36
+ 'value' => $values['title'],
37
+ ]);
38
+
39
+ $html->renderField([
40
+ 'type' => 'textarea',
41
+ 'name' => 'content',
42
+ 'errors' => $errors,
43
+ 'label' => $db->getOption( 'form.content.label' ),
44
+ 'placeholder' => $db->getOption( 'form.content.placeholder' ),
45
+ 'prefix' => false,
46
+ 'rows' => 5,
47
+ 'render' => !in_array( 'content', $exclude ),
48
+ 'required' => true,
49
+ 'suffix' => $form_id,
50
+ 'value' => $values['content'],
51
+ ]);
52
+
53
+ $html->renderField([
54
+ 'type' => 'text',
55
+ 'name' => 'reviewer',
56
+ 'errors' => $errors,
57
+ 'label' => $db->getOption( 'form.reviewer.label' ),
58
+ 'placeholder' => $db->getOption( 'form.reviewer.placeholder' ),
59
+ 'prefix' => false,
60
+ 'render' => !in_array( 'reviewer', $exclude ),
61
+ 'required' => true,
62
+ 'suffix' => $form_id,
63
+ 'value' => $values['reviewer'],
64
+ ]);
65
+
66
+ $html->renderField([
67
+ 'type' => 'email',
68
+ 'name' => 'email',
69
+ 'errors' => $errors,
70
+ 'label' => $db->getOption( 'form.email.label' ),
71
+ 'placeholder' => $db->getOption( 'form.email.placeholder' ),
72
+ 'prefix' => false,
73
+ 'render' => !in_array( 'email', $exclude ),
74
+ 'required' => true,
75
+ 'suffix' => $form_id,
76
+ 'value' => $values['email'],
77
+ ]);
78
+
79
+ $html->renderField([
80
+ 'type' => 'checkbox',
81
+ 'name' => 'terms',
82
+ 'errors' => $errors,
83
+ 'options' => $db->getOption( 'form.terms.label' ),
84
+ 'prefix' => false,
85
+ 'render' => !in_array( 'terms', $exclude ),
86
+ 'required' => true,
87
+ 'suffix' => $form_id,
88
+ 'value' => $values['terms'],
89
+ ]);
90
+
91
+ $html->renderField([
92
+ 'type' => 'hidden',
93
+ 'name' => 'excluded',
94
+ 'prefix' => false,
95
+ 'value' => esc_attr( json_encode( $exclude ) ),
96
+ ]);
97
+
98
+ $html->renderField([
99
+ 'type' => 'hidden',
100
+ 'name' => 'action',
101
+ 'prefix' => false,
102
+ 'value' => 'post-review',
103
+ ]);
104
+
105
+ $html->renderField([
106
+ 'type' => 'hidden',
107
+ 'name' => 'form_id',
108
+ 'prefix' => false,
109
+ 'value' => $form_id,
110
+ ]);
111
+
112
+ wp_nonce_field( 'post-review' );
113
+
114
+ if( $message ) {
115
+ printf( '<div class="glsr-form-messages%s">%s</div>', ( $errors ? ' gslr-has-errors' : '' ), $message );
116
+ }
117
+
118
+ $html->renderField([
119
+ 'type' => 'submit',
120
+ 'prefix' => false,
121
+ 'value' => __( 'Submit your review', 'site-reviews' ),
122
+ ]);
123
+
124
+ ?>
125
+ </form>