WP RSS Aggregator - Version 4.15.1

Version Description

(2019-08-14) =

Added - New link to the custom feed in the "Custom Feed" settings page.

Changed - Updated the logging system to no longer cause VaultPress to trigger false positive warnings. - The date format in the custom feed now uses the "RFC3339 Extended" format.

Fixed - Items with the same title were not being imported even when "Unique titles only" was turned off. - Items with future dates where marked as "scheduled" by WordPress. - The custom feed's "Content-Type" header was set for RSS 2.0 instead of Atom. - Imported images were not being deleted from the media library when the imported item is deleted. - PHP notice for "undefined index enclosure" when a feed cannot be fetched. - Deprecation notice on PHP 7.2 or later for "each" function. - Warnings when the wprss_log function is used incorrectly. - PHP notice for "property of non-object" when using YoastSEO. - After using the Templates add-on, images would continue to be imported after the add-on was deactivated.

Download this release

Release Info

Developer Mekku
Plugin Icon 128x128 WP RSS Aggregator
Version 4.15.1
Comparing to
See all releases

Code changes from version 4.15 to 4.15.1

Files changed (221) hide show
  1. CHANGELOG.md +19 -0
  2. README.md +24 -0
  3. css/admin-3.8.css +0 -0
  4. css/admin-editor.css +0 -0
  5. css/admin-styles.css +1 -1
  6. css/admin-tracking-styles.css +0 -0
  7. css/colorbox.css +0 -0
  8. css/font-awesome.min.css +0 -0
  9. css/src/common/index.scss +3 -0
  10. css/src/common/info-box.scss +16 -0
  11. css/src/common/notice-block.scss +64 -0
  12. css/src/gutenberg-block/index.scss +14 -0
  13. css/src/intro/animation.scss +48 -0
  14. css/src/intro/expander.scss +26 -0
  15. css/src/intro/loading.scss +131 -0
  16. css/src/intro/steps.scss +647 -0
  17. css/src/mixins.scss +32 -0
  18. css/src/pagination/index.scss +10 -0
  19. css/src/plugins/form.scss +48 -0
  20. css/src/plugins/index.scss +25 -0
  21. css/src/plugins/modal.scss +173 -0
  22. css/src/templates/bottom-panel.scss +15 -0
  23. css/src/templates/grid.scss +18 -0
  24. css/src/templates/index.scss +216 -0
  25. css/src/templates/loading.scss +220 -0
  26. css/src/templates/notifications.scss +28 -0
  27. css/src/update/index.scss +149 -0
  28. fonts/FontAwesome.otf +0 -0
  29. fonts/fontawesome-webfont.eot +0 -0
  30. fonts/fontawesome-webfont.svg +0 -0
  31. fonts/fontawesome-webfont.ttf +0 -0
  32. fonts/fontawesome-webfont.woff +0 -0
  33. images/add-ons/wprss.jpg +0 -0
  34. images/colorbox/border.png +0 -0
  35. images/colorbox/controls.png +0 -0
  36. images/colorbox/ie6/borderBottomCenter.png +0 -0
  37. images/colorbox/ie6/borderBottomLeft.png +0 -0
  38. images/colorbox/ie6/borderBottomRight.png +0 -0
  39. images/colorbox/ie6/borderMiddleLeft.png +0 -0
  40. images/colorbox/ie6/borderMiddleRight.png +0 -0
  41. images/colorbox/ie6/borderTopCenter.png +0 -0
  42. images/colorbox/ie6/borderTopLeft.png +0 -0
  43. images/colorbox/ie6/borderTopRight.png +0 -0
  44. images/colorbox/loading.gif +0 -0
  45. images/colorbox/loading_background.png +0 -0
  46. images/colorbox/overlay.png +0 -0
  47. images/facebook.png +0 -0
  48. images/icon-adminmenu16-sprite.png +0 -0
  49. images/icon-adminpage32.png +0 -0
  50. images/twitter.png +0 -0
  51. includes/Aventura/Wprss/Core/Model/Regex/HtmlEncoder.php +10 -3
  52. includes/OPML.php +0 -0
  53. includes/admin-addons.php +0 -137
  54. includes/admin-ajax-notice.php +0 -0
  55. includes/admin-debugging.php +0 -57
  56. includes/admin-display.php +6 -2
  57. includes/admin-editor.php +0 -0
  58. includes/admin-heartbeat.php +0 -0
  59. includes/admin-help-metaboxes.php +0 -0
  60. includes/admin-help-settings.php +2 -2
  61. includes/admin-help.php +1 -0
  62. includes/admin-import-export.php +0 -0
  63. includes/admin-log.php +14 -12
  64. includes/admin-metaboxes.php +1 -9
  65. includes/admin-options.php +43 -25
  66. includes/admin.php +0 -4
  67. includes/cpt-feeds.php +0 -0
  68. includes/cron-jobs.php +0 -0
  69. includes/fallback-mbstring.php +0 -0
  70. includes/feed-access.php +0 -0
  71. includes/feed-blacklist.php +0 -0
  72. includes/feed-importing-images.php +92 -69
  73. includes/feed-importing.php +50 -30
  74. includes/feed-processing.php +0 -0
  75. includes/feed-states.php +0 -0
  76. includes/functions.php +98 -2
  77. includes/image-caching.php +9 -2
  78. includes/libraries/WordPress-Readme-Parser/ReadmeParser.php +0 -0
  79. includes/libraries/browser.php +0 -0
  80. includes/libraries/php-markdown/markdown.php +0 -0
  81. includes/licensing.php +0 -0
  82. includes/misc-functions.php +0 -0
  83. includes/opml-importer.php +0 -0
  84. includes/readme.php +0 -0
  85. includes/roles-capabilities.php +0 -0
  86. includes/scripts.php +0 -5
  87. includes/system-info.php +141 -117
  88. includes/update.php +1 -1
  89. js/admin-addon-ajax.js +0 -0
  90. js/admin-custom-bulk-actions-feed-item.js +0 -16
  91. js/admin-custom-bulk-actions.js +0 -0
  92. js/admin-custom.js +0 -0
  93. js/admin-help.js +0 -0
  94. js/admin-license-manager.js +0 -0
  95. js/admin-licensing.js +0 -0
  96. js/custom.js +0 -0
  97. js/editor.js +0 -0
  98. js/heartbeat.js +0 -0
  99. js/jquery-ui-timepicker-addon.js +0 -0
  100. js/jquery.colorbox-min.js +0 -0
  101. js/pointers.js +0 -0
  102. js/src/components/BottomPanel.vue +9 -0
  103. js/src/components/Button.js +14 -0
  104. js/src/components/Expander.vue +36 -0
  105. js/src/components/Input.js +100 -0
  106. js/src/components/Layout.js +11 -0
  107. js/src/components/Main.js +11 -0
  108. js/src/components/MediaInput.js +96 -0
  109. js/src/components/Modal.vue +114 -0
  110. js/src/components/NoticeBlock.js +143 -0
  111. js/src/components/Postbox.vue +44 -0
  112. js/src/components/RouteLink.js +27 -0
  113. js/src/components/RouterApp.js +47 -0
  114. js/src/components/SerializedForm.vue +89 -0
  115. js/src/components/Sidebar.js +11 -0
  116. js/src/components/TransitionExpand.vue +66 -0
  117. js/src/components/index.js +15 -0
  118. js/src/libs/NotificationCenter.js +45 -0
  119. js/src/libs/Router.js +122 -0
  120. js/src/mixins/DataChangesAware.js +47 -0
  121. js/src/modules/gutenberg-block/components/MultipleSelectControl.js +85 -0
  122. js/src/modules/gutenberg-block/index.js +209 -0
  123. js/src/modules/intro/Wizard.vue +440 -0
  124. js/src/modules/intro/index.js +11 -0
  125. js/src/modules/pagination/index.js +47 -0
  126. js/src/modules/plugins/PluginDisablePoll.vue +122 -0
  127. js/src/modules/plugins/index.js +10 -0
  128. js/src/modules/templates/Edit.js +491 -0
  129. js/src/modules/templates/List.js +350 -0
  130. js/src/modules/templates/app.js +117 -0
  131. js/src/modules/templates/index.js +36 -0
  132. js/src/modules/templates/store/actions.js +1 -0
  133. js/src/modules/templates/store/getters.js +7 -0
  134. js/src/modules/templates/store/index.js +12 -0
  135. js/src/modules/templates/store/mutations.js +10 -0
  136. js/src/modules/templates/store/state.js +18 -0
  137. js/src/utils/Collection.js +178 -0
  138. js/src/utils/base64.js +133 -0
  139. js/src/utils/copy.js +43 -0
  140. js/src/utils/deepmerge.js +72 -0
  141. js/src/utils/fetch.js +26 -0
  142. js/src/utils/jsonClone.js +3 -0
  143. languages/default.mo +0 -0
  144. languages/default.po +0 -0
  145. languages/wprss-it.mo +0 -0
  146. languages/wprss-it.po +0 -0
  147. languages/wprss-nl_NL.mo +0 -0
  148. languages/wprss-nl_NL.po +0 -0
  149. languages/wprss-pt_BR.mo +0 -0
  150. languages/wprss-pt_BR.po +0 -0
  151. languages/wprss-ru_RU.mo +0 -0
  152. languages/wprss-ru_RU.po +0 -0
  153. readme.txt +14 -15
  154. src/Database/TableInterface.php +5 -0
  155. src/Entities/Feeds/Sources/WpPostFeedSource.php +0 -9
  156. src/Handlers/CustomFeed/RenderCustomFeedHandler.php +1 -1
  157. src/Handlers/FeedTemplates/RenderTemplateContentHandler.php +4 -0
  158. src/Handlers/Images/DeleteImagesHandler.php +8 -5
  159. src/Logger/ConditionalLogger.php +0 -54
  160. src/Logger/FeedLoggerInterface.php +24 -0
  161. src/Logger/ProblemLogger.php +53 -0
  162. src/Logger/WpdbLogger.php +20 -12
  163. src/Logger/WpraLogger.php +81 -0
  164. src/Modules/ImagesModule.php +7 -49
  165. src/Modules/LoggerModule.php +21 -43
  166. src/Modules/LoremModule.php +123 -0
  167. src/Modules/UpsellModule.php +284 -0
  168. src/Util/NullFunction.php +20 -0
  169. templates/admin/upsell/add-on-list.twig +10 -0
  170. templates/admin/upsell/more-features-page/add-on.twig +32 -0
  171. templates/admin/upsell/more-features-page/lorem.twig +2 -0
  172. templates/admin/upsell/more-features-page/main.twig +21 -0
  173. templates/custom-feed/entry.twig +1 -1
  174. templates/help-footer-js.php +0 -0
  175. templates/help-tooltip-content.php +0 -0
  176. templates/help-tooltip-handle.php +0 -0
  177. templates/wprss.css +0 -0
  178. uninstall.php +0 -0
  179. vendor/autoload.php +1 -1
  180. vendor/composer/ClassLoader.php +0 -0
  181. vendor/composer/LICENSE +0 -0
  182. vendor/composer/autoload_classmap.php +6 -1
  183. vendor/composer/autoload_namespaces.php +0 -0
  184. vendor/composer/autoload_psr4.php +0 -0
  185. vendor/composer/autoload_real.php +7 -7
  186. vendor/composer/autoload_static.php +11 -6
  187. vendor/composer/installed.json +0 -0
  188. vendor/dhii/collections-abstract-base/composer.json +0 -0
  189. vendor/dhii/collections-abstract-base/composer.lock +0 -0
  190. vendor/dhii/collections-abstract-base/src/AbstractCollection.php +0 -0
  191. vendor/dhii/collections-abstract-base/src/AbstractHasher.php +0 -0
  192. vendor/dhii/collections-abstract-base/src/AbstractIterableCollection.php +0 -0
  193. vendor/dhii/collections-abstract/composer.json +0 -0
  194. vendor/dhii/collections-abstract/composer.lock +0 -0
  195. vendor/dhii/collections-abstract/src/AbstractCallbackCollection.php +0 -0
  196. vendor/dhii/collections-abstract/src/AbstractCallbackCollectionBase.php +0 -0
  197. vendor/dhii/collections-abstract/src/AbstractCallbackIterator.php +0 -0
  198. vendor/dhii/collections-abstract/src/AbstractGenericAccessibleCollection.php +0 -0
  199. vendor/dhii/collections-abstract/src/AbstractGenericCollection.php +0 -0
  200. vendor/dhii/collections-abstract/src/AbstractGenericMutableCollection.php +0 -0
  201. vendor/dhii/collections-abstract/src/AbstractSearchableCollection.php +0 -0
  202. vendor/dhii/collections-abstract/src/AppendIterator.php +0 -0
  203. vendor/dhii/collections-abstract/src/CallbackIterator.php +0 -0
  204. vendor/dhii/collections-interface/composer.json +0 -0
  205. vendor/dhii/collections-interface/composer.lock +0 -0
  206. vendor/dhii/collections-interface/src/AccessibleCollectionInterface.php +0 -0
  207. vendor/dhii/collections-interface/src/CallbackIterableInterface.php +0 -0
  208. vendor/dhii/collections-interface/src/CallbackIteratorInterface.php +0 -0
  209. vendor/dhii/collections-interface/src/CollectionInterface.php +0 -0
  210. vendor/dhii/collections-interface/src/MutableCollectionInterface.php +0 -0
  211. vendor/dhii/collections-interface/src/SearchableCollectionInterface.php +0 -0
  212. vendor/dhii/collections-interface/src/SequenceIteratorIteratorInterface.php +0 -0
  213. vendor/dhii/collections-interface/src/SetInterface.php +0 -0
  214. vendor/dhii/stats-abstract/composer.json +0 -0
  215. vendor/dhii/stats-abstract/composer.lock +0 -0
  216. vendor/dhii/stats-abstract/src/AbstractAggregatableCollection.php +0 -0
  217. vendor/dhii/stats-abstract/src/AbstractAggregator.php +0 -0
  218. vendor/dhii/stats-interface/composer.json +0 -0
  219. vendor/dhii/stats-interface/composer.lock +0 -0
  220. vendor/dhii/stats-interface/src/AggregatorInterface.php +0 -0
  221. wp-rss-aggregator.php +6 -5
CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ## [4.15] - 2019-07-16
8
  ### Added
9
  * New error handling for catchable PHP7 `Throwable` errors.
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
 
7
+ ## [4.15.1] - 2019-08-14
8
+ ### Added
9
+ * New link to the custom feed in the "Custom Feed" settings page.
10
+
11
+ ### Changed
12
+ * Updated the logging system to no longer cause VaultPress to trigger false positive warnings.
13
+ * The date format in the custom feed now uses the "RFC3339 Extended" format.
14
+
15
+ ### Fixed
16
+ * Items with the same title were not being imported even when "Unique titles only" was turned off.
17
+ * Items with future dates where marked as "scheduled" by WordPress.
18
+ * The custom feed's "Content-Type" header was set for RSS 2.0 instead of Atom.
19
+ * Imported images were not being deleted from the media library when the imported item is deleted.
20
+ * PHP notice for "undefined index enclosure" when a feed cannot be fetched.
21
+ * Deprecation notice on PHP 7.2 or later for "each" function.
22
+ * Warnings when the `wprss_log` function is used incorrectly.
23
+ * PHP notice for "property of non-object" when using YoastSEO.
24
+ * After using the Templates add-on, images would continue to be imported after the add-on was deactivated.
25
+
26
  ## [4.15] - 2019-07-16
27
  ### Added
28
  * New error handling for catchable PHP7 `Throwable` errors.
README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ![](https://repository-images.githubusercontent.com/15269888/73ca9400-669f-11e9-9e18-5662f7b5bd68)
2
+
3
+ ## Requirements
4
+
5
+ **Recommended**
6
+ * PHP 7+
7
+ * PHP `curl` extension
8
+ * PHP `simplexml` extension
9
+ * PHP `mbstring` extension
10
+ * PHP `gd` extension
11
+ * WordPress 5.0+
12
+
13
+ **Minimum**
14
+ * PHP 5.4+
15
+ * PHP `curl` extension
16
+ * PHP `simplexml` extension
17
+ * WordPress 4.8+
18
+
19
+ ## Links
20
+
21
+ * [Website](https://wprssaggregator.com)
22
+ * [Premium Addons](https://wprssaggregator.com/pricing)
23
+ * [Knowledge Base](https://kb.wprssaggregator.com)
24
+ * [WordPress.org Page](https://wordpress.org/plugins/wp-rss-aggregator)
css/admin-3.8.css CHANGED
File without changes
css/admin-editor.css CHANGED
File without changes
css/admin-styles.css CHANGED
@@ -738,7 +738,7 @@ i.wprss-updating-feed-icon.wprss-show {
738
  border: 1px solid #E5E5E5;
739
  position: relative;
740
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
741
- padding-bottom: 60px;
742
  }
743
 
744
  #add-ons .add-on h3 {
738
  border: 1px solid #E5E5E5;
739
  position: relative;
740
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
741
+ min-height: 320px;
742
  }
743
 
744
  #add-ons .add-on h3 {
css/admin-tracking-styles.css CHANGED
File without changes
css/colorbox.css CHANGED
File without changes
css/font-awesome.min.css CHANGED
File without changes
css/src/common/index.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ @import "./../mixins";
2
+ @import "notice-block";
3
+ @import "info-box";
css/src/common/info-box.scss ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-info-box {
2
+ background: #daf4ff;
3
+ padding: 8px 12px;
4
+ border-radius: 3px;
5
+ display: flex;
6
+
7
+ &__icon {
8
+ opacity: .4;
9
+ flex-shrink: 0;
10
+ }
11
+
12
+ &__text {
13
+ flex-grow: 1;
14
+ padding-left: 8px;
15
+ }
16
+ }
css/src/common/notice-block.scss ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-notice-block {
2
+ background-color: white;
3
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,0.1);
4
+ padding: 12px 16px;
5
+ margin-top: .5rem;
6
+ margin-bottom: .15rem;
7
+ //border-left: 4px solid #ff792b;
8
+
9
+ &.postbox {
10
+ margin-top: 0;
11
+ margin-bottom: 20px;
12
+ box-shadow: 0;
13
+
14
+ .wpra-notice-block__body {
15
+ font-size: 14px;
16
+ opacity: .8;
17
+ }
18
+ }
19
+
20
+ &__title {
21
+ font-size: 22px;
22
+ padding: .5rem 0 1rem;
23
+ max-width: 65rem;
24
+ }
25
+
26
+ &__body {
27
+ line-height: 1.5;
28
+ font-size: 16px;
29
+ opacity: .7;
30
+ max-width: 65rem;
31
+ }
32
+
33
+ &__buttons {
34
+ padding: 1rem 0 .5rem;
35
+
36
+ .button {
37
+ height: 34px;
38
+ line-height: 32px;
39
+ padding: 0 16px 2px;
40
+
41
+ .dashicons {
42
+ font-size: 16px;
43
+ vertical-align: middle;
44
+ opacity: .75;
45
+ margin-left: 2px;
46
+ }
47
+
48
+ &-clear {
49
+ margin-left: 8px;
50
+ font-weight: 500;
51
+ color: #0085ba;
52
+ border: none;
53
+ background: none;
54
+ box-shadow: none;
55
+
56
+ &:hover, &:active, &:focus {
57
+ border: none;
58
+ background: none;
59
+ box-shadow: none;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
css/src/gutenberg-block/index.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-gutenberg-block, [data-type="wpra-shortcode/wpra-shortcode"] {
2
+ .wpra-item {
3
+ // Prevent clicking on feed item links.
4
+ a {
5
+ pointer-events: none !important;
6
+ }
7
+ }
8
+ }
9
+
10
+ .nav-links::after {
11
+ display: block;
12
+ content: '';
13
+ clear: both;
14
+ }
css/src/intro/animation.scss ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wrpa-expander {
2
+ will-change: height;
3
+ transform: translateZ(0);
4
+ backface-visibility: hidden;
5
+ perspective: 1000px;
6
+ }
7
+
8
+ .expand-enter-active,
9
+ .expand-leave-active {
10
+ transition: height .35s ease;
11
+ overflow: hidden;
12
+ }
13
+
14
+ .expand-enter,
15
+ .expand-leave-to {
16
+ height: 0;
17
+ }
18
+
19
+ .fade-enter, .fade-leave-to {
20
+ opacity: 0;
21
+ }
22
+ .fade-enter-active, .fade-leave-active {
23
+ transition: opacity .3s ease;
24
+ }
25
+
26
+ .slide-up-enter {
27
+ transform: translateY(10px);
28
+ opacity: 0;
29
+ }
30
+ .slide-up-leave-to {
31
+ transform: translateY(-10px);
32
+ opacity: 0;
33
+ }
34
+ .slide-up-enter-active, .slide-up-leave-active {
35
+ transition: all .3s ease;
36
+ }
37
+
38
+ .slide-down-enter {
39
+ transform: translateY(-10px);
40
+ opacity: 0;
41
+ }
42
+ .slide-down-leave-to {
43
+ transform: translateY(10px);
44
+ opacity: 0;
45
+ }
46
+ .slide-down-enter-active, .slide-down-leave-active {
47
+ transition: all .3s ease;
48
+ }
css/src/intro/expander.scss ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-expander {
2
+ &__title {
3
+ cursor: pointer;
4
+ user-select: none;
5
+ span {
6
+ opacity: .5;
7
+ vertical-align: bottom;
8
+ transition: transform .3s ease;
9
+ }
10
+ }
11
+ &__content {
12
+ padding: 0 .5rem;
13
+ opacity: 0;
14
+ transition: opacity .3s ease;
15
+ }
16
+ &--expanded {
17
+ .wpra-expander__content {
18
+ opacity: 1;
19
+ }
20
+ .wpra-expander__title {
21
+ span {
22
+ transform: rotate(180deg);
23
+ }
24
+ }
25
+ }
26
+ }
css/src/intro/loading.scss ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [v-cloak] {
2
+ .vcloak {
3
+ &--visible {
4
+ display: block;
5
+ }
6
+ &--hidden {
7
+ display: none;
8
+ }
9
+ }
10
+ }
11
+ .vcloak {
12
+ &--visible {
13
+ min-height: 60px;
14
+ display: none;
15
+ }
16
+ }
17
+
18
+ .loading-container {
19
+ position: relative;
20
+ &:before {
21
+ display: block;
22
+ content: '';
23
+ position: absolute;
24
+ left: calc(50% - 20px);
25
+ top: calc(50% - 20px);
26
+ box-sizing: border-box;
27
+ height: 40px;
28
+ width: 40px;
29
+ border: 0px solid #d0d0d0;
30
+ border-radius: 50%;
31
+ box-shadow: 0 -12px 0 16px #d0d0d0 inset;
32
+ animation: rotate 1s infinite linear;
33
+ z-index: 3;
34
+ }
35
+ &:after {
36
+ display: block;
37
+ content: '';
38
+ position: absolute;
39
+ z-index: 2;
40
+ background-color: rgba(241, 241, 241, 0.5);
41
+ width: 100%;
42
+ height: 100%;
43
+ left: 0;
44
+ top: 0;
45
+ }
46
+ &--white {
47
+ &:after {
48
+ background-color: white;
49
+ }
50
+ }
51
+ }
52
+
53
+
54
+ .loading-button {
55
+ pointer-events: none;
56
+ position: relative;
57
+ &:before {
58
+ display: block;
59
+ content: '';
60
+ position: absolute;
61
+ left: calc(50% - 8px);
62
+ top: calc(50% - 8px);
63
+ box-sizing: border-box;
64
+ height: 16px;
65
+ width: 16px;
66
+ border: 0px solid #fff;
67
+ border-radius: 50%;
68
+ box-shadow: 0 -4px 0 6px #fff inset;
69
+ animation: rotate 1s infinite linear;
70
+ z-index: 3;
71
+ }
72
+ &:after {
73
+ display: block;
74
+ content: '';
75
+ position: absolute;
76
+ z-index: 2;
77
+ background-color: inherit;
78
+ width: 100%;
79
+ height: 100%;
80
+ left: 0;
81
+ top: 0;
82
+ }
83
+ }
84
+
85
+ .button-default {
86
+ &.loading-button:before {
87
+ box-shadow: inset 0 -4px 0 6px #6f6f6f;
88
+ }
89
+ }
90
+
91
+ .loading-inline {
92
+ display: inline-block;
93
+ pointer-events: none;
94
+ position: relative;
95
+ &:before {
96
+ display: block;
97
+ content: '';
98
+ position: absolute;
99
+ left: calc(50% + 1px);
100
+ top: calc(50% - 11px);
101
+ box-sizing: border-box;
102
+ height: 14px;
103
+ width: 14px;
104
+ border: 0px solid #d0d0d0;
105
+ border-radius: 50%;
106
+ box-shadow: 0 -3px 0 5px #d0d0d0 inset;
107
+ animation: rotate 1s infinite linear;
108
+ z-index: 3;
109
+ }
110
+ &:after {
111
+ display: block;
112
+ content: '';
113
+ position: absolute;
114
+ z-index: 2;
115
+ background-color: inherit;
116
+ width: 100%;
117
+ height: 100%;
118
+ left: 0;
119
+ top: 0;
120
+ }
121
+ }
122
+
123
+
124
+ @keyframes rotate {
125
+ 0% {
126
+ transform: rotate(0deg);
127
+ }
128
+ 100% {
129
+ transform: rotate(360deg);
130
+ }
131
+ }
css/src/intro/steps.scss ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $dark-gray: transparentize(#000, .25);
2
+ $medium-gray: transparentize(#000, .5);
3
+ $deep-gray: transparentize(#000, .95);
4
+ $light-gray: transparentize(#000, .95);
5
+ $borderColor: transparentize(#000, .95);
6
+ $white: #fff;
7
+ $button-radius: 4px;
8
+
9
+ $wizard-width: 42rem;
10
+
11
+ $success-color: #00b479;
12
+ $primary-color: #1a53e6;
13
+
14
+ @import "animation";
15
+ @import "expander";
16
+ @import "loading";
17
+
18
+ @mixin clear() {
19
+ &:after {
20
+ clear: both;
21
+ display: block;
22
+ content: '';
23
+ }
24
+ }
25
+
26
+ @mixin breakpoint($point) {
27
+ @if $point == desktop {
28
+ @media (min-width: 70em) { @content ; }
29
+ }
30
+ @else if $point == laptop {
31
+ @media (min-width: 64em) { @content ; }
32
+ }
33
+ @else if $point == tablet {
34
+ @media (max-width: 50em) { @content ; }
35
+ }
36
+ @else if $point == phablet {
37
+ @media (min-width: 37.5em) { @content ; }
38
+ }
39
+ @else if $point == mobileonly {
40
+ @media (max-width: 37.5em) { @content ; }
41
+
42
+ }
43
+ }
44
+
45
+ .wpra-wizard-head {
46
+ display: flex;
47
+ padding-bottom: 1.5rem;
48
+ padding-top: .75rem;
49
+ &__logo {
50
+ flex-shrink: 0;
51
+ img {
52
+ width: 57px;
53
+ }
54
+ }
55
+ &__copy {
56
+ flex-grow: 1;
57
+ padding-left: 1rem;
58
+ display: flex;
59
+ flex-direction: column;
60
+ justify-content: center;
61
+ }
62
+ &__title {
63
+ font-size: 1.25rem;
64
+ font-weight: 500;
65
+ padding-bottom: 12px;
66
+ }
67
+ &__subtitle {
68
+ font-size: 1rem;
69
+ opacity: .85;
70
+ }
71
+ }
72
+
73
+ .wpra-cols {
74
+ &-title {
75
+ font-weight: normal;
76
+ font-size: 1rem;
77
+ padding-bottom: 1rem;
78
+ }
79
+
80
+ display: flex;
81
+ margin: 0 -.5rem;
82
+ border-top: 2px solid rgba(0,0,0,.05);
83
+ padding-top: 1.45rem;
84
+
85
+ @include breakpoint(tablet) {
86
+ flex-wrap: wrap;
87
+ }
88
+ .col {
89
+ flex-basis: 50%;
90
+ padding: 0 .5rem;
91
+
92
+ @include breakpoint(tablet) {
93
+ flex-basis: 100%;
94
+ padding-bottom: 1rem;
95
+ }
96
+
97
+ img {
98
+ max-width: 100%;
99
+ }
100
+
101
+ p {
102
+ font-size: 14px;
103
+ line-height: 1.6;
104
+ }
105
+ p:first-child {
106
+ margin-top: 0;
107
+ }
108
+ }
109
+ }
110
+
111
+ .wpra-feed-input {
112
+ width: 26rem;
113
+ padding: 6px 10px;
114
+ max-width: 100%;
115
+ }
116
+
117
+ .wpra-feedback {
118
+ display: flex;
119
+ &__photo {
120
+ flex-shrink: 0;
121
+ img {
122
+ width: 70px;
123
+ border-radius: 100%;
124
+ }
125
+ }
126
+ &__copy {
127
+ padding-left: .5rem;
128
+ flex-grow: 1;
129
+ }
130
+ &__text {
131
+ font-size: 1rem;
132
+ font-family: Georgia, serif;
133
+ font-style: italic;
134
+ line-height: 1.5;
135
+ }
136
+ &__rating {
137
+ padding: .25rem 0 .45rem;
138
+ span {
139
+ color: #f8ca29;
140
+ }
141
+ }
142
+ &__by {
143
+ opacity: .85;
144
+ a {
145
+ color: #617181;
146
+ }
147
+ }
148
+ }
149
+
150
+ .wpra-demo-photo {
151
+ margin-bottom: 1rem;
152
+ border-radius: 4px;
153
+ border: 1px solid rgba(0, 0, 0, 0.15);
154
+ }
155
+
156
+ .wpra-feed-items {
157
+ padding-left: .5rem;
158
+ margin-bottom: 1rem;
159
+ border-left: 4px solid #ff792b;
160
+ .wpra-feed-item {
161
+ padding: .75rem;
162
+ &__link {
163
+ padding-bottom: 4px;
164
+ }
165
+ }
166
+ }
167
+
168
+ .step-items {
169
+ $size: 35px;
170
+
171
+ display: flex;
172
+ position: relative;
173
+
174
+ flex-direction: column;
175
+ align-items: flex-end;
176
+
177
+ .step-progress {
178
+ position: absolute;
179
+ width: 3px;
180
+ height: 0;
181
+ background-color: $success-color;
182
+ left: $size / 2;
183
+ top: 0;
184
+ transition: height .3s ease;
185
+ z-index: -1;
186
+ &--1 {
187
+ height: 50%;
188
+ }
189
+ &--2 {
190
+ height: 100%;
191
+ }
192
+
193
+ @include breakpoint(tablet) {
194
+ display: none;
195
+ }
196
+ }
197
+
198
+ @include breakpoint(tablet) {
199
+ padding: .625rem;
200
+ flex-direction: row;
201
+ align-items: flex-start;
202
+ }
203
+
204
+ .step-item {
205
+ font-size: 16px;
206
+ width: 15rem;
207
+ margin-bottom: 2.25rem;
208
+ display: flex;
209
+ background-color: #f1f1f1;
210
+
211
+ &:last-child {
212
+ margin-bottom: 0;
213
+ }
214
+
215
+ @include breakpoint(tablet) {
216
+ flex-direction: column;
217
+ margin-bottom: 0;
218
+ align-items: center;
219
+ }
220
+
221
+ &_active {
222
+ .step-item__info {
223
+ opacity: 1;
224
+ }
225
+ .step-item__status {
226
+ opacity: 1;
227
+ }
228
+ opacity: 1;
229
+ @include breakpoint(tablet) {
230
+ flex-grow: 1;
231
+ }
232
+ }
233
+ &__title {
234
+ }
235
+ &__description {
236
+ //color: $dark-gray;
237
+ font-size: 14px;
238
+ }
239
+
240
+ &__info {
241
+ display: flex;
242
+ align-items: center;
243
+ padding-left: .75rem;
244
+ opacity: .5;
245
+ @include breakpoint(tablet) {
246
+ padding-left: 0;
247
+ padding-top: .3rem;
248
+ display: none;
249
+ }
250
+ }
251
+
252
+ &__status {
253
+ width: $size;
254
+ height: $size;
255
+ border-radius: 50%;
256
+ border: 2px dashed $medium-gray;
257
+ flex-shrink: 0;
258
+ opacity: .5;
259
+
260
+ text-align: center;
261
+ font-size: 1.25rem;
262
+ line-height: 2.375rem;
263
+
264
+ color: $success-color;
265
+
266
+ span {
267
+ display: block;
268
+ margin: 0 !important;
269
+ width: 100%;
270
+ line-height: 35px;
271
+ font-size: 26px;
272
+ }
273
+ }
274
+
275
+ &_completed {
276
+ opacity: 1;
277
+ .step-item {
278
+ &__status {
279
+ border-style: solid;
280
+ border-color: $success-color;
281
+ opacity: 1;
282
+
283
+ span {
284
+ position: relative;
285
+ }
286
+ }
287
+ &__info {
288
+ opacity: .5;
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ .wizard {
296
+ width: 100%;
297
+ margin-left: 1rem;
298
+
299
+ @include breakpoint(tablet) {
300
+ margin-left: 0;
301
+ }
302
+
303
+ ol {
304
+ margin-left: 1.5rem;
305
+ line-height: 1.5;
306
+ font-size: 13px;
307
+ ul {
308
+ margin-bottom: 1rem;
309
+ }
310
+ }
311
+
312
+ .form-group {
313
+ display: flex;
314
+ align-items: center;
315
+ @include breakpoint(tablet) {
316
+ display: block;
317
+ align-items: unset;
318
+ input {
319
+ margin-bottom: 8px;
320
+ }
321
+ }
322
+ input {
323
+ flex-shrink: 0;
324
+ }
325
+ .warning-icon {
326
+ padding-left: 4px;
327
+ color: #ee5a65;
328
+ }
329
+ a {
330
+ flex-shrink: 0;
331
+ margin-left: 6px;
332
+ }
333
+ }
334
+
335
+ .button-clear {
336
+ background-color: transparent;
337
+ border: none;
338
+ cursor: pointer;
339
+ height: 30px;
340
+ line-height: 28px;
341
+ padding: 0 12px 2px;
342
+ vertical-align: baseline;
343
+
344
+ opacity: .55;
345
+ &:hover {
346
+ opacity: .8;
347
+ }
348
+ }
349
+
350
+ .button {
351
+ vertical-align: baseline !important;
352
+ }
353
+
354
+ .pad {
355
+ display: flex;
356
+ &-item {
357
+ &--grow {
358
+ flex-grow: 1;
359
+ }
360
+ &--no-shrink {
361
+ flex-shrink: 0;
362
+ }
363
+ }
364
+ }
365
+
366
+ .wpra-success {
367
+ padding-right: 1rem;
368
+ font-size: 14px;
369
+ }
370
+
371
+ &_content {
372
+ padding: 1rem;
373
+ font-size: 14px;
374
+ max-width: $wizard-width;
375
+ min-height: 16rem;
376
+ padding-top: 12px;
377
+
378
+ @include breakpoint(tablet) {
379
+ padding: 0;
380
+ }
381
+ }
382
+
383
+ &-holder {
384
+ width: 100%;
385
+ position: relative;
386
+ display: flex;
387
+ //align-items: center;
388
+ //justify-content: center;
389
+
390
+ @include breakpoint(tablet) {
391
+ flex-direction: column;
392
+ }
393
+
394
+ .connect-steps {
395
+ flex-shrink: 0;
396
+ max-width: 450px;
397
+ padding: 1rem;
398
+
399
+ @include breakpoint(tablet) {
400
+ max-width: 100%;
401
+ flex-basis: auto;
402
+ padding: .5rem;
403
+ }
404
+ }
405
+
406
+ .wizard {
407
+ //background-color: #00aced;
408
+
409
+ flex-grow: 1;
410
+ }
411
+ }
412
+
413
+ &_text {
414
+ padding: 0 0 25px 0;
415
+ max-width: 480px;
416
+ font-size: 14px;
417
+ }
418
+
419
+ &_more {
420
+ user-select: none;
421
+ display: inline-block;
422
+ color: $medium-gray;
423
+ font-size: 12px;
424
+ margin-top: 20px;
425
+
426
+ &:hover {
427
+ cursor: pointer;
428
+ text-decoration: underline;
429
+ }
430
+ }
431
+
432
+ &_hello {
433
+ font-size: 18px;
434
+ margin: 15px 0 10px 0;
435
+ img {
436
+ height: 26px;
437
+ vertical-align: sub;
438
+ margin-right: 3px;
439
+ }
440
+ }
441
+
442
+ &_button {
443
+ min-width: 137px;
444
+ margin: 0 .3rem 1rem .3rem;
445
+ }
446
+
447
+ &_buttons {
448
+ margin: 0 -.5rem;
449
+ display: flex;
450
+
451
+ @include breakpoint(tablet) {
452
+ flex-direction: row;
453
+ flex-wrap: wrap;
454
+ }
455
+ }
456
+ &_network {
457
+ //flex-shrink: 1;
458
+ //width: 33%;
459
+ padding: 0 .5rem;
460
+ margin-bottom: .35rem;
461
+ //text-align: center;
462
+
463
+ .button {
464
+ min-width: 145px;
465
+ text-align: center;
466
+ i {
467
+ margin-left: 4px;
468
+ }
469
+ }
470
+
471
+ .button-transparent {
472
+ margin-bottom: 10px;
473
+ }
474
+
475
+ &-icon {
476
+ width: 50px;
477
+ height: 50px;
478
+ border-radius: 100%;
479
+ background-color: lighten($light-gray, 7);
480
+ margin: 0 auto .8rem auto;
481
+ i {
482
+ font-size: 22px;
483
+ line-height: 50px;
484
+ }
485
+ }
486
+ }
487
+
488
+ &_list {
489
+ padding: 10px 0;
490
+ display: flex;
491
+ flex-wrap: wrap;
492
+ margin: 0 -0.3125rem;
493
+ max-width: 49rem;
494
+ }
495
+
496
+ &_item {
497
+ padding: 5px;
498
+ margin: 5px;
499
+ border: 1px solid $borderColor;
500
+ display: flex;
501
+
502
+ width: 31%;
503
+ align-items: center;
504
+
505
+ @include breakpoint(tablet) {
506
+ width: 100%;
507
+ margin: 0;
508
+ margin-bottom: .625rem;
509
+ }
510
+
511
+ vertical-align: middle;
512
+ border-radius: $button-radius;
513
+ text-align: left;
514
+
515
+ .description {
516
+ font-size: 12px;
517
+ color: $dark-gray;
518
+ margin-top: 2px;
519
+ }
520
+
521
+ .account-item_picture {
522
+ float: none;
523
+ margin-right: 10px;
524
+ flex-shrink: 0;
525
+ }
526
+ }
527
+
528
+ /*
529
+ * This is form
530
+ */
531
+ &_info {
532
+ max-width: 20rem;
533
+ text-align: left;
534
+
535
+ .form-group {
536
+ margin-bottom: 1rem;
537
+ }
538
+ }
539
+
540
+ &_label {
541
+ font-size: 1rem;
542
+ padding-top: .2rem;
543
+ padding-bottom: .75rem;
544
+ }
545
+ }
546
+
547
+ .wrpa-shortcode {
548
+ display: flex;
549
+ &-form, &-preview {
550
+ padding-bottom: 10px;
551
+ padding-right: 75px;
552
+ flex-basis: 50%;
553
+ .wrpa-shortcode-label {
554
+ font-size: 1rem;
555
+ padding-bottom: 12px;
556
+ line-height: 1.5;
557
+ }
558
+ .button.loading-button {
559
+ &::before {
560
+ box-shadow: inset 0 -4px 0 6px #797979 !important;
561
+ }
562
+ }
563
+ }
564
+ .wrpa-shortcode-form {
565
+ cursor: pointer;
566
+ .wrpa-shortcode-form__shortcode {
567
+ display: inline-block;
568
+ }
569
+ .wrpa-shortcode-form__button {
570
+ display: inline-block;
571
+ opacity: .65;
572
+ font-size: 11px;
573
+ font-style: italic;
574
+ padding-right: 3px;
575
+ }
576
+ }
577
+ }
578
+
579
+ .connect {
580
+ &_progress {
581
+ position: absolute;
582
+ left: calc(50% - 22px);
583
+ top: 30px;
584
+
585
+ @include clear;
586
+
587
+ &-point {
588
+ display: block;
589
+ float: left;
590
+ margin-left: 5px;
591
+ width: 8px;
592
+ height: 8px;
593
+ border-radius: 100%;
594
+ background-color: transparentize($primary-color, .7);
595
+
596
+ &__active {
597
+ border: 1px solid $primary-color;
598
+ }
599
+
600
+ &__done {
601
+ background-color: $primary-color;
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ .connect-actions {
608
+ width: 100%;
609
+ max-width: $wizard-width;
610
+ border-top: 2px solid $light-gray;
611
+ padding: 1rem 0;
612
+
613
+ .button {
614
+ margin: 0;
615
+ }
616
+
617
+ @include breakpoint(tablet) {
618
+ padding-bottom: 2rem;
619
+ }
620
+
621
+ .fa-animated {
622
+ font-size: 2rem;
623
+ color: transparentize($primary-color, .35);
624
+ vertical-align: middle;
625
+ margin-right: 5px;
626
+ }
627
+ }
628
+
629
+ @keyframes moveRightLeftLoop {
630
+ 0% {
631
+ transform: translateX(-10px);
632
+ }
633
+
634
+ 50% {
635
+ transform: translateX(0);
636
+ }
637
+
638
+ 100% {
639
+ transform: translateX(-10px);
640
+ }
641
+ }
642
+
643
+ .moveRightLeftLoop {
644
+ animation-iteration-count: infinite;
645
+ animation-name: moveRightLeftLoop;
646
+ animation-duration: 1000ms;
647
+ }
css/src/mixins.scss ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // grid
2
+ $breakpoint-small: 782px; // 540px
3
+ $breakpoint-med: 60em; // 720px
4
+ $breakpoint-large: 68em; // 960px
5
+
6
+ @mixin breakpoint($point) {
7
+ @if $point == desktop {
8
+ @media (min-width: $breakpoint-large) { @content ; }
9
+ }
10
+ @else if $point == mobile {
11
+ @media (max-width: $breakpoint-small) { @content ; }
12
+ }
13
+ @else if $point == not-mobile {
14
+ @media (min-width: $breakpoint-small) { @content ; }
15
+ }
16
+ }
17
+
18
+ .mobile-collapsed {
19
+ display: inherit;
20
+ }
21
+ .mobile-only {
22
+ display: none !important;
23
+ }
24
+
25
+ @include breakpoint(mobile) {
26
+ .mobile-only {
27
+ display: inherit !important;
28
+ }
29
+ .mobile-collapsed {
30
+ display: none !important;
31
+ }
32
+ }
css/src/pagination/index.scss ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-loading {
2
+ animation: pulse 1s infinite ease-in-out;
3
+ pointer-events: none;
4
+ }
5
+
6
+ @keyframes pulse {
7
+ 0% { opacity: .25; }
8
+ 50% { opacity: .6; }
9
+ 100% { opacity: .25; }
10
+ }
css/src/plugins/form.scss ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .form {
2
+ &-check {
3
+ input {
4
+ display: inline-block !important;
5
+ width: auto !important;
6
+ margin: 0 4px 0 0;
7
+ }
8
+ label {
9
+ display: inline-block !important;
10
+ }
11
+ padding: 4px 0;
12
+ }
13
+ &-group {
14
+ padding-bottom: 8px;
15
+ label {
16
+ display: block;
17
+ }
18
+ & > label {
19
+ margin-bottom: 2px;
20
+ }
21
+ input, textarea {
22
+ display: block;
23
+ width: 100%;
24
+ }
25
+ &:last-child {
26
+ padding-bottom: 0;
27
+ }
28
+ p {
29
+ &:first-child {
30
+ margin-top: 0;
31
+ }
32
+ &:last-child {
33
+ margin-bottom: 0;
34
+ }
35
+ }
36
+ .error {
37
+ margin: 0 !important;
38
+ }
39
+ }
40
+ }
41
+
42
+ .wpra-plugin-disable-poll {
43
+ &__logo {
44
+ img {
45
+ max-height: 40px;
46
+ }
47
+ }
48
+ }
css/src/plugins/index.scss ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $white: #fff;
2
+ $radius: 4px;
3
+
4
+ @mixin breakpoint($point) {
5
+ @if $point == desktop {
6
+ @media (min-width: 70em) { @content ; }
7
+ }
8
+ @else if $point == laptop {
9
+ @media (min-width: 64em) { @content ; }
10
+ }
11
+ @else if $point == tablet {
12
+ @media (max-width: 50em) { @content ; }
13
+ }
14
+ @else if $point == phablet {
15
+ @media (min-width: 37.5em) { @content ; }
16
+ }
17
+ @else if $point == mobileonly {
18
+ @media (max-width: 37.5em) { @content ; }
19
+
20
+ }
21
+ }
22
+
23
+ @import "./../intro/loading";
24
+ @import "modal";
25
+ @import "form";
css/src/plugins/modal.scss ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .modal {
2
+ &-opened {
3
+ overflow: hidden;
4
+ }
5
+
6
+ position: fixed;
7
+ background-color: rgba(0, 0, 0, 0.65);
8
+ width: 100%;
9
+ height: 100%;
10
+ left: 0;
11
+ top: 0;
12
+ z-index: 9999;
13
+ overflow-y: auto;
14
+
15
+ @include breakpoint(mobile) {
16
+ z-index: 100000;
17
+ }
18
+
19
+ &__body {
20
+ width: 100%;
21
+ max-width: 30rem;
22
+ margin: 5rem auto;
23
+
24
+ background-color: $white;
25
+ border-radius: $radius;
26
+
27
+ &--no-content-padding {
28
+ .modal__content {
29
+ padding: 0;
30
+ }
31
+ }
32
+
33
+ &--wide {
34
+ max-width: 50rem;
35
+ }
36
+
37
+ @include breakpoint(mobile) {
38
+ min-height: 100vh;
39
+ margin: 0;
40
+ border-radius: 0;
41
+ }
42
+ }
43
+
44
+ &__header {
45
+ padding: .5rem 1rem;
46
+ font-size: 20px;
47
+ font-weight: bold;
48
+ line-height: 1.5;
49
+ border-bottom: 1px solid #eee;
50
+
51
+ &.invisible-header {
52
+ border: none;
53
+ font-size: unset;
54
+ font-weight: unset;
55
+ padding-top: 12px !important;
56
+ h3 {
57
+ margin-top: 0;
58
+ line-height: 1.5;
59
+ margin-bottom: 0;
60
+ }
61
+ p {
62
+ margin-top: .25rem;
63
+ font-size: 14px;
64
+ opacity: .6;
65
+ margin-bottom: 0;
66
+ }
67
+ & + .modal__content {
68
+ padding-top: 6px !important;
69
+ }
70
+ }
71
+
72
+ @include breakpoint(mobile) {
73
+ position: relative;
74
+
75
+ .modal--right {
76
+ position: absolute;
77
+ top: 0;
78
+ padding: 6px 1rem 0 1rem;
79
+ right: 0;
80
+ background-color: white;
81
+ }
82
+ }
83
+
84
+ &-buttons {
85
+ font-weight: normal;
86
+ }
87
+ }
88
+
89
+ &__close {
90
+ opacity: .5;
91
+ cursor: pointer;
92
+ &:hover {
93
+ opacity: .8;
94
+ }
95
+ }
96
+
97
+ &--right {
98
+ float: right;
99
+ }
100
+
101
+ &__content {
102
+ padding: 1rem;
103
+ }
104
+
105
+ &__footer {
106
+ padding: 1rem;
107
+ border-top: 1px solid #eee;
108
+ text-align: right;
109
+
110
+ .button {
111
+ display: inline-block !important;
112
+ margin-left: 4px !important;
113
+ }
114
+ .footer-confirm {
115
+ margin: 0;
116
+ padding: 0;
117
+ display: flex;
118
+ align-items: center;
119
+
120
+ @include breakpoint(mobile) {
121
+ display: block;
122
+ &__message {
123
+ padding-right: 0;
124
+ padding-bottom: 10px;
125
+ }
126
+ }
127
+
128
+ &__buttons {
129
+ flex-shrink: 0;
130
+
131
+ &--left {
132
+ margin-right: auto;
133
+ flex-grow: 1;
134
+ text-align: left;
135
+ }
136
+ }
137
+
138
+ &__message {
139
+ margin-right: auto;
140
+ &.__right {
141
+ flex-grow: 1;
142
+ padding-right: 10px;
143
+ text-align: right;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ .modal-transition {
151
+ &-enter, &-leave-to {
152
+ opacity: 0;
153
+
154
+ .modal__body {
155
+ opacity: 0;
156
+ transform: scaleX(.8) scaleY(.7);
157
+ }
158
+ }
159
+
160
+ &-enter-active, &-leave-active {
161
+ transition: opacity 0.25s ease-out;
162
+
163
+ .modal__body {
164
+ transition: all .25s cubic-bezier(.665,1.65,0,.845);
165
+ }
166
+ }
167
+ }
168
+
169
+ .button-clear {
170
+ border: none !important;
171
+ background: transparent !important;
172
+ box-shadow: none !important;
173
+ }
css/src/templates/bottom-panel.scss ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .wpra-bottom-panel {
2
+ position: sticky;
3
+ bottom: 0;
4
+ z-index: 9;
5
+ background-color: #fffce9;
6
+ padding: 1rem;
7
+ border: 1px solid #e5e5e5;
8
+ box-shadow: 0 1px 1px rgba(0,0,0,0.04);
9
+
10
+ &__title {
11
+ font-size: 1rem;
12
+ font-weight: 500;
13
+ padding-bottom: 8px;
14
+ }
15
+ }
css/src/templates/grid.scss ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .flex {
2
+ &-row {
3
+ display: flex;
4
+ }
5
+ &-col {
6
+ box-sizing: border-box;
7
+ padding: 0 10px;
8
+ flex: 1 1 0;
9
+
10
+ &:first-child {
11
+ padding-left: 0;
12
+ }
13
+
14
+ &:last-child {
15
+ padding-right: 0;
16
+ }
17
+ }
18
+ }
css/src/templates/index.scss ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "./../mixins";
2
+ @import "bottom-panel";
3
+ @import "loading";
4
+ @import "notifications";
5
+ @import "grid";
6
+
7
+ .wpra-postbox {
8
+ .hndle {
9
+ cursor: unset !important;
10
+ }
11
+ }
12
+
13
+ .wpra-shortcode-copy {
14
+ font-size: 13px;
15
+ background-color: #fff;
16
+ margin-left: auto;
17
+ padding: 9px 14px;
18
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,0.1);
19
+
20
+ border-left: 4px solid #0085ba;
21
+
22
+ display: flex;
23
+ align-items: center;
24
+
25
+ @include breakpoint (mobile) {
26
+ display: block;
27
+ }
28
+
29
+ &__icon {
30
+ padding-left: 16px;
31
+
32
+ @include breakpoint (mobile) {
33
+ padding-left: 0;
34
+ margin-top: 1rem;
35
+ }
36
+ }
37
+ }
38
+
39
+ .page-title {
40
+ display: flex;
41
+ align-items: center;
42
+ padding: 9px 0 4px 0;
43
+
44
+ @include breakpoint (mobile) {
45
+ display: block;
46
+ }
47
+
48
+ a {
49
+ &:focus {
50
+ text-decoration: none;
51
+ }
52
+ }
53
+
54
+ h1 {
55
+ margin-left: 6px;
56
+ padding: 0;
57
+ }
58
+ }
59
+
60
+ .back-button {
61
+ opacity: .75;
62
+ text-decoration: none;
63
+ font-size: 20px;
64
+ display: flex;
65
+ align-items: center;
66
+ padding-right: 10px;
67
+ margin-right: 4px;
68
+ border-right: 1px solid #b4b4b4;
69
+
70
+ @include breakpoint (mobile) {
71
+ border-right: none;
72
+ margin-bottom: .5rem;
73
+ }
74
+
75
+ .dashicons {
76
+ margin-right: 8px;
77
+ }
78
+ }
79
+
80
+ .tippy-tooltip.light-theme {
81
+ font-size: 13px !important;
82
+ font-family: unset !important;
83
+ text-align: left !important;
84
+
85
+ hr {
86
+ border: none;
87
+ height: 1px;
88
+ background-color: #efefef;
89
+ }
90
+ }
91
+
92
+ .form-input {
93
+ display: flex;
94
+ margin: .75rem 0;
95
+ &--disabled {
96
+ pointer-events: none;
97
+ .form-input__label {
98
+ *:not(.disable-ignored) {
99
+ opacity: .5;
100
+ user-select: none;
101
+ }
102
+ }
103
+ }
104
+ .disable-ignored {
105
+ opacity: 1;
106
+ }
107
+ &--vertical {
108
+ flex-direction: column;
109
+ .form-input__label {
110
+ padding-right: 0;
111
+ padding-bottom: 4px;
112
+ flex-basis: 100%;
113
+ }
114
+ }
115
+
116
+ @include breakpoint(mobile) {
117
+ flex-direction: column;
118
+ .form-input__label {
119
+ padding-right: 0;
120
+ padding-bottom: 4px;
121
+ flex-basis: 100%;
122
+ }
123
+ }
124
+
125
+ &:last-child {
126
+ margin-bottom: 0;
127
+ }
128
+ &__tip {
129
+ cursor: pointer;
130
+ display: inline-block;
131
+ margin-left: 4px;
132
+ opacity: .25;
133
+ vertical-align: middle;
134
+ &:hover {
135
+ opacity: .85;
136
+ }
137
+ .dashicons {
138
+ font-size: 18px;
139
+ }
140
+ }
141
+ &__label {
142
+ padding-right: 12px;
143
+ flex-basis: 260px;
144
+ &-description {
145
+ padding-top: 2px;
146
+ padding-right: 10px;
147
+ line-height: 1.65;
148
+ opacity: .6;
149
+ font-size: 12px;
150
+
151
+ a {
152
+ pointer-events: auto !important;
153
+ }
154
+ }
155
+ }
156
+ &__field {
157
+ flex-grow: 1;
158
+ input:not([type="checkbox"]), textarea, select {
159
+ width: 100%;
160
+ max-width: 325px;
161
+ }
162
+ }
163
+ }
164
+
165
+ .built-in {
166
+ background: #f1f1f1;
167
+
168
+ [type="checkbox"] {
169
+ display: none;
170
+ }
171
+ }
172
+
173
+ .wpra-preview-link {
174
+ vertical-align: middle;
175
+
176
+ span.dashicons {
177
+ opacity: .75;
178
+ font-size: 16px;
179
+ vertical-align: middle;
180
+ }
181
+ }
182
+
183
+ .wpra-no-cb {
184
+ .column-cb {
185
+ input[type="checkbox"] {
186
+ display: none;
187
+ }
188
+ }
189
+ }
190
+
191
+ .column.name {
192
+ small {
193
+ @include breakpoint (mobile) {
194
+ display: block;
195
+ }
196
+ }
197
+ }
198
+
199
+ .inside {
200
+ .wpra-preview-link {
201
+ @include breakpoint (mobile) {
202
+ float: none;
203
+ }
204
+ }
205
+ }
206
+
207
+ .wpra-postbox-container {
208
+ @include breakpoint (mobile) {
209
+ display: flex;
210
+ flex-direction: column;
211
+
212
+ .wpra-postbox-last {
213
+ order: 100;
214
+ }
215
+ }
216
+ }
css/src/templates/loading.scss ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ a.disabled {
2
+ color: #9c9c9c;
3
+ pointer-events: none;
4
+ }
5
+
6
+ [v-cloak] {
7
+ .vcloak {
8
+ &--visible {
9
+ display: block;
10
+ }
11
+ &--hidden {
12
+ display: none;
13
+ }
14
+ }
15
+ }
16
+ .vcloak {
17
+ &--visible {
18
+ min-height: 60px;
19
+ display: none;
20
+ }
21
+ }
22
+
23
+ .loading-container {
24
+ min-height: 5rem;
25
+ position: relative;
26
+ &:before {
27
+ display: block;
28
+ content: '';
29
+ position: absolute;
30
+ left: calc(50% - 20px);
31
+ top: calc(50% - 20px);
32
+ box-sizing: border-box;
33
+ height: 40px;
34
+ width: 40px;
35
+ border: 0px solid #d0d0d0;
36
+ border-radius: 50%;
37
+ box-shadow: 0 -12px 0 16px #d0d0d0 inset;
38
+ animation: rotate 1s infinite linear;
39
+ z-index: 3;
40
+ }
41
+ &:after {
42
+ display: block;
43
+ content: '';
44
+ position: absolute;
45
+ z-index: 2;
46
+ background-color: rgba(241, 241, 241, 0.5);
47
+ width: 100%;
48
+ height: 100%;
49
+ left: 0;
50
+ top: 0;
51
+ }
52
+ &--white {
53
+ &:after {
54
+ background-color: white;
55
+ }
56
+ }
57
+ }
58
+
59
+
60
+ .loading-button {
61
+ pointer-events: none;
62
+ position: relative;
63
+ &:before {
64
+ display: block;
65
+ content: '';
66
+ position: absolute;
67
+ left: calc(50% - 8px);
68
+ top: calc(50% - 8px);
69
+ box-sizing: border-box;
70
+ height: 16px;
71
+ width: 16px;
72
+ border: 0px solid #fff;
73
+ border-radius: 50%;
74
+ box-shadow: 0 -4px 0 6px #fff inset;
75
+ animation: rotate 1s infinite linear;
76
+ z-index: 3;
77
+ }
78
+ &:after {
79
+ display: block;
80
+ content: '';
81
+ position: absolute;
82
+ z-index: 2;
83
+ background-color: inherit;
84
+ width: 100%;
85
+ height: 100%;
86
+ left: 0;
87
+ top: 0;
88
+ }
89
+ }
90
+
91
+ .button-default {
92
+ &.loading-button:before {
93
+ box-shadow: inset 0 -4px 0 6px #6f6f6f;
94
+ }
95
+ }
96
+
97
+ .loading-inline {
98
+ display: inline-block;
99
+ pointer-events: none;
100
+ position: relative;
101
+ &:before {
102
+ display: block;
103
+ content: '';
104
+ position: absolute;
105
+ left: calc(50% + 1px);
106
+ top: calc(50% - 11px);
107
+ box-sizing: border-box;
108
+ height: 14px;
109
+ width: 14px;
110
+ border: 0px solid #d0d0d0;
111
+ border-radius: 50%;
112
+ box-shadow: 0 -3px 0 5px #d0d0d0 inset;
113
+ animation: rotate 1s infinite linear;
114
+ z-index: 3;
115
+ }
116
+ &:after {
117
+ display: block;
118
+ content: '';
119
+ position: absolute;
120
+ z-index: 2;
121
+ background-color: inherit;
122
+ width: 100%;
123
+ height: 100%;
124
+ left: 0;
125
+ top: 0;
126
+ }
127
+ }
128
+
129
+
130
+ @keyframes rotate {
131
+ 0% {
132
+ transform: rotate(0deg);
133
+ }
134
+ 100% {
135
+ transform: rotate(360deg);
136
+ }
137
+ }
138
+
139
+ .table-loading {
140
+ position: relative;
141
+ }
142
+ .table-loading .table-loader-wrap {
143
+ position: absolute;
144
+ width: 100%;
145
+ height: 100%;
146
+ z-index: 9;
147
+ }
148
+ .table-loading .table-loader-wrap .table-loader-center {
149
+ position: absolute;
150
+ top: 50%;
151
+ transform: translateY(-50%);
152
+ width: 100%;
153
+ }
154
+ .table-loading .wp-list-table,
155
+ .table-loading .tablenav {
156
+ opacity: 0.4;
157
+ }
158
+ .table-loader {
159
+ font-size: 10px;
160
+ margin: 50px auto;
161
+ text-indent: -9999em;
162
+ width: 11em;
163
+ height: 11em;
164
+ border-radius: 50%;
165
+ background: #ffffff;
166
+ background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
167
+ background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
168
+ background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
169
+ background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
170
+ background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
171
+ position: relative;
172
+ -webkit-animation: tableLoading 1s infinite linear;
173
+ animation: tableLoading 1s infinite linear;
174
+ -webkit-transform: translateZ(0);
175
+ -ms-transform: translateZ(0);
176
+ transform: translateZ(0);
177
+ }
178
+ .table-loader:before {
179
+ width: 50%;
180
+ height: 50%;
181
+ background: #ffffff;
182
+ border-radius: 100% 0 0 0;
183
+ position: absolute;
184
+ top: 0;
185
+ left: 0;
186
+ content: '';
187
+ }
188
+ .table-loader:after {
189
+ background: #f4f4f4;
190
+ width: 75%;
191
+ height: 75%;
192
+ border-radius: 50%;
193
+ content: '';
194
+ margin: auto;
195
+ position: absolute;
196
+ top: 0;
197
+ left: 0;
198
+ bottom: 0;
199
+ right: 0;
200
+ }
201
+ @-webkit-keyframes tableLoading {
202
+ 0% {
203
+ -webkit-transform: rotate(0deg);
204
+ transform: rotate(0deg);
205
+ }
206
+ 100% {
207
+ -webkit-transform: rotate(360deg);
208
+ transform: rotate(360deg);
209
+ }
210
+ }
211
+ @keyframes tableLoading {
212
+ 0% {
213
+ -webkit-transform: rotate(0deg);
214
+ transform: rotate(0deg);
215
+ }
216
+ 100% {
217
+ -webkit-transform: rotate(360deg);
218
+ transform: rotate(360deg);
219
+ }
220
+ }
css/src/templates/notifications.scss ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .toasted-container.top-center {
2
+ top: 44px !important;
3
+ }
4
+
5
+ .toasted {
6
+ background: #23282d !important;
7
+ color: #e5e5e5 !important;
8
+ font-size: inherit !important;
9
+ font-weight: inherit !important;
10
+
11
+ justify-content: start !important;
12
+ border-left: none !important;
13
+
14
+ &.success {}
15
+
16
+ &.error {
17
+ .dashicons {
18
+ color: #ff7781;
19
+ }
20
+ }
21
+
22
+ .dashicons {
23
+ margin-right: 12px;
24
+ margin-left: -8px;
25
+ display: inline-block;
26
+ opacity: .65;
27
+ }
28
+ }
css/src/update/index.scss ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "./../mixins";
2
+
3
+ .wrap--wpra-update {
4
+ padding-top: 1rem;
5
+
6
+ @include breakpoint(not-mobile) {
7
+ width: 100%;
8
+ max-width: 1400px;
9
+ margin: 0 auto;
10
+ padding-right: 20px;
11
+ box-sizing: border-box;
12
+ }
13
+ }
14
+
15
+ .wpra-text-center {
16
+ text-align: center;
17
+ }
18
+
19
+ .wpra-updates ul {
20
+ list-style: disc;
21
+ padding: 0 1.25rem;
22
+ }
23
+
24
+ .wpra-inline-form {
25
+ display: flex;
26
+ align-items: baseline;
27
+
28
+ button {
29
+ margin-right: 12px !important;
30
+ }
31
+
32
+ small {
33
+ font-size: 12px;
34
+ opacity: .75;
35
+ }
36
+ }
37
+
38
+
39
+ .wpra-update-head__title {
40
+ font-size: 24px;
41
+ font-weight: 500;
42
+ padding: 1.5rem 0 1.5rem;
43
+ line-height: 1.4;
44
+ }
45
+
46
+ .wpra-update__logo img {
47
+ width: 56px;
48
+ }
49
+
50
+ .button-icon span {
51
+ vertical-align: baseline;
52
+ margin-right: -7px;
53
+ opacity: .6;
54
+ font-size: 16px;
55
+ top: 4px;
56
+ position: relative;
57
+ }
58
+
59
+ .wpra-links {
60
+ padding-top: .5rem;
61
+ }
62
+
63
+ .wpra-update-head__link {
64
+ font-size: 1rem;
65
+ padding-bottom: 1.5rem;
66
+ line-height: 1.5;
67
+ }
68
+
69
+ .wpra-update-feature {
70
+ display: flex;
71
+ padding: 0 18px 12px !important;
72
+
73
+ @include breakpoint(mobile) {
74
+ flex-direction: column-reverse;
75
+ }
76
+ }
77
+
78
+ .wpra-update-feature__text {
79
+ @include breakpoint(not-mobile) {
80
+ padding-right: 12px;
81
+ }
82
+
83
+ h3 {
84
+ margin-top: 8px;
85
+ @include breakpoint(mobile) {
86
+ margin-top: 16px;
87
+ }
88
+ }
89
+
90
+ p {
91
+ font-size: 14px;
92
+ }
93
+ }
94
+
95
+ .wpra-update-feature__image {
96
+ flex-shrink: 0;
97
+ width: 30rem;
98
+ overflow: hidden;
99
+ min-height: 336px;
100
+ position: relative;
101
+ margin: -11px -18px -23px 0;
102
+ cursor: pointer;
103
+ transition-duration: 0.5s;
104
+
105
+ &:hover {
106
+ transition-duration: 0.2s;
107
+ background-color: #111;
108
+
109
+ img {
110
+ opacity: 0.95;
111
+
112
+ }
113
+
114
+ &::before {
115
+ content: "\f177";
116
+ display: block;
117
+ position: absolute;
118
+ top: calc(50% - 45px);
119
+ width: 100%;
120
+ height: 90px;
121
+ font-size: 5em;
122
+ font-family: dashicons;
123
+ text-align: center;
124
+ z-index: 100;
125
+ opacity: 0.5;
126
+ }
127
+ }
128
+
129
+ @include breakpoint(mobile) {
130
+ flex-shrink: unset;
131
+ width: unset;
132
+ min-height: unset;
133
+
134
+ max-height: 15rem;
135
+ margin: -11px -18px 0px;
136
+ }
137
+
138
+ img {
139
+ max-height: 29rem;
140
+ position: absolute;
141
+
142
+ @include breakpoint(mobile) {
143
+ max-height: unset;
144
+ position: unset;
145
+
146
+ max-width: 100%;
147
+ }
148
+ }
149
+ }
fonts/FontAwesome.otf CHANGED
File without changes
fonts/fontawesome-webfont.eot CHANGED
File without changes
fonts/fontawesome-webfont.svg CHANGED
File without changes
fonts/fontawesome-webfont.ttf CHANGED
File without changes
fonts/fontawesome-webfont.woff CHANGED
File without changes
images/add-ons/wprss.jpg CHANGED
File without changes
images/colorbox/border.png CHANGED
File without changes
images/colorbox/controls.png CHANGED
File without changes
images/colorbox/ie6/borderBottomCenter.png CHANGED
File without changes
images/colorbox/ie6/borderBottomLeft.png CHANGED
File without changes
images/colorbox/ie6/borderBottomRight.png CHANGED
File without changes
images/colorbox/ie6/borderMiddleLeft.png CHANGED
File without changes
images/colorbox/ie6/borderMiddleRight.png CHANGED
File without changes
images/colorbox/ie6/borderTopCenter.png CHANGED
File without changes
images/colorbox/ie6/borderTopLeft.png CHANGED
File without changes
images/colorbox/ie6/borderTopRight.png CHANGED
File without changes
images/colorbox/loading.gif CHANGED
File without changes
images/colorbox/loading_background.png CHANGED
File without changes
images/colorbox/overlay.png CHANGED
File without changes
images/facebook.png CHANGED
File without changes
images/icon-adminmenu16-sprite.png CHANGED
File without changes
images/icon-adminpage32.png CHANGED
File without changes
images/twitter.png CHANGED
File without changes
includes/Aventura/Wprss/Core/Model/Regex/HtmlEncoder.php CHANGED
@@ -555,11 +555,18 @@ class HtmlEncoder extends AbstractRegex
555
  $symPrefix = $this->getSymPrefix();
556
  }
557
 
 
 
 
 
558
  /**
559
  * Iterating through all matches.
560
  * http://php.net/manual/en/control-structures.foreach.php#88578
561
  */
562
- while (list($key, $value) = each($matches)) {
 
 
 
563
  // Is this a symmetrical char match key?
564
  if (!$this->isSymKey($key, $symPrefix)) {
565
  continue;
@@ -577,7 +584,7 @@ class HtmlEncoder extends AbstractRegex
577
  }
578
  // Remove the symmetric char match
579
  unset($matches[$key]);
580
- }
581
 
582
  $matches = array_merge($matches);
583
  return $matches;
@@ -618,4 +625,4 @@ class HtmlEncoder extends AbstractRegex
618
  $startLength = strlen($start);
619
  return ($actualStart = substr($string, 0, $startLength)) === $start;
620
  }
621
- }
555
  $symPrefix = $this->getSymPrefix();
556
  }
557
 
558
+ if (count($matches) === 0) {
559
+ return $matches;
560
+ }
561
+
562
  /**
563
  * Iterating through all matches.
564
  * http://php.net/manual/en/control-structures.foreach.php#88578
565
  */
566
+ reset($matches);
567
+ do {
568
+ $key = key($matches);
569
+ $value = current($matches);
570
  // Is this a symmetrical char match key?
571
  if (!$this->isSymKey($key, $symPrefix)) {
572
  continue;
584
  }
585
  // Remove the symmetric char match
586
  unset($matches[$key]);
587
+ } while (next($matches) !== false && key($matches) !== null);
588
 
589
  $matches = array_merge($matches);
590
  return $matches;
625
  $startLength = strlen($start);
626
  return ($actualStart = substr($string, 0, $startLength)) === $start;
627
  }
628
+ }
includes/OPML.php CHANGED
File without changes
includes/admin-addons.php DELETED
@@ -1,137 +0,0 @@
1
- <?php
2
- /**
3
- * Build the Add-ons page (Code borrowed from the ACF plugin)
4
- *
5
- * @since 4.2
6
- * @link http://www.advancedcustomfields.com/
7
- *
8
- */
9
- function wprss_addons_page_display() {
10
-
11
- $premium = wprss_addons_get_extra();
12
-
13
- ?>
14
- <div class="wrap">
15
- <h2><?php _e( 'More Features With Our Premium Add-Ons', WPRSS_TEXT_DOMAIN ); ?></h2>
16
- <p><?php echo sprintf(__( 'The following <a href="%1$s" target="_blank">add-ons</a> are available to increase the functionality of the WP RSS Aggregator plugin.', WPRSS_TEXT_DOMAIN ), 'https://www.wprssaggregator.com/extensions') ?></p>
17
- <p>
18
- <?php
19
- $pricingLink = sprintf(
20
- '<a href="%s" target="_blank">%s</a>',
21
- 'https://www.wprssaggregator.com/pricing',
22
- _x('pricing plans', 'Check out our pricing plans for bigger savings!', 'wprss')
23
- );
24
-
25
- printf(_x('Check out our %s for bigger savings!', '%s = "pricing plans"', 'wprss'), $pricingLink);
26
- ?>
27
- </p>
28
-
29
- <div id="add-ons" class="clearfix">
30
-
31
- <div class="add-on-group clearfix">
32
- <?php foreach( $premium as $_code => $addon ): ?>
33
- <?php $isActive = is_plugin_active($addon['basename']) ?>
34
- <?php $isInstalledInactive = wprss_is_plugin_inactive($addon['basename']) ?>
35
- <div class="add-on wp-box<?php if( $isActive ): ?> add-on-active<?php endif; ?> <?php echo sprintf('add-on-code-%1$s', $_code) ?>">
36
- <!-- <a target="_blank" href="<?php echo $addon['url']; ?>">
37
- <img src="<?php echo $addon['thumbnail']; ?>" />
38
- </a> -->
39
- <div class="inner">
40
- <h3><a target="_blank" href="<?php echo $addon['url']; ?>"><?php echo $addon['title']; ?></a></h3>
41
- <p><?php echo $addon['description']; ?></p>
42
- </div>
43
- <div class="footer">
44
- <?php if( $isActive ): ?>
45
- <a class="button button-disabled"><span class="wprss-sprite-tick"></span><?php _e( "Installed", WPRSS_TEXT_DOMAIN ); ?></a>
46
- <?php elseif( $isInstalledInactive ): ?>
47
- <a class="button" href="<?php echo wp_nonce_url('plugins.php?action=activate&amp;plugin='.$addon['basename'], 'activate-plugin_'.$addon['basename'] ) ?>"><?php _e( "Activate", WPRSS_TEXT_DOMAIN ); ?></a>
48
- <?php else: ?>
49
- <a target="_blank" href="<?php echo $addon['url']; ?>" class="button"><?php _e( "Purchase & Install", WPRSS_TEXT_DOMAIN ); ?></a>
50
- <?php endif; ?>
51
- </div>
52
- </div>
53
- <?php endforeach; ?>
54
- </div>
55
-
56
- </div>
57
-
58
- </div>
59
-
60
- <?php
61
-
62
- }
63
-
64
- function wprss_addons_get_extra()
65
- {
66
- return apply_filters('wprss_extra_addons', array(
67
- 'ftp' => array(
68
- 'title' => 'Feed to Post',
69
- 'description' => __("An advanced importer that lets you import RSS feed items as WordPress posts or any other custom post type. You can use it to populate a website in minutes (auto-blog). This is the most popular and feature-filled extension.", WPRSS_TEXT_DOMAIN),
70
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
71
- 'basename' => 'wp-rss-feed-to-post/wp-rss-feed-to-post.php',
72
- 'url' => 'https://www.wprssaggregator.com/extension/feed-to-post/'
73
- ),
74
- 'ftr' => array(
75
- 'title' => 'Full Text RSS Feeds',
76
- 'description' => __("An extension for Feed to Post that adds connectivity to our premium full text service, which allows you to import the full post content for an unlimited number of feed items per feed source, even when the feed itself doesn't provide it", WPRSS_TEXT_DOMAIN),
77
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
78
- 'basename' => 'wp-rss-full-text-feeds/wp-rss-full-text.php',
79
- 'url' => 'https://www.wprssaggregator.com/extension/full-text-rss-feeds/'
80
- ),
81
- 'tmp' => array(
82
- 'title' => 'Templates',
83
- 'description' => __('Premium templates to display images and excerpts in various ways. It includes a fully customisable grid template and a list template that includes excerpts & thumbnails, both of which will spruce up your site!', WPRSS_TEXT_DOMAIN),
84
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
85
- 'basename' => 'wp-rss-templates/wp-rss-templates.php',
86
- 'url' => 'https://www.wprssaggregator.com/extension/templates/'
87
- ),
88
- 'kf' => array(
89
- 'title' => 'Keyword Filtering',
90
- 'description' => __("Filters the feed items to be imported based on your own keywords, key phrases, or tags; you only get the items you're interested in. It is compatible with all other add-ons.", WPRSS_TEXT_DOMAIN),
91
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
92
- 'basename' => 'wp-rss-keyword-filtering/wp-rss-keyword-filtering.php',
93
- 'url' => 'https://www.wprssaggregator.com/extension/keyword-filtering/'
94
- ),
95
- 'c' => array(
96
- 'title' => 'Source Categories',
97
- 'description' => __("Categorises your feed sources and allows you to display feed items from a particular category within your site using the shortcode parameters.", WPRSS_TEXT_DOMAIN),
98
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
99
- 'basename' => 'wp-rss-categories/wp-rss-categories.php',
100
- 'url' => 'https://www.wprssaggregator.com/extension/categories/'
101
- ),
102
- 'wai' => array(
103
- 'title' => 'WordAi',
104
- 'description' => __("An extension for Feed to Post that allows you to integrate the WordAi article spinner so that the imported content is both completely unique and completely readable.", WPRSS_TEXT_DOMAIN),
105
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
106
- 'basename' => 'wp-rss-wordai/wp-rss-wordai.php',
107
- 'url' => 'https://www.wprssaggregator.com/extension/wordai/'
108
- ),
109
- 'spc' => array(
110
- 'title' => 'SpinnerChief',
111
- 'description' => __("An extension for Feed to Post that allows you to integrate the SpinnerChief article spinner so that the imported content is both completely unique and completely readable.", WPRSS_TEXT_DOMAIN),
112
- 'thumbnail' => WPRSS_IMG . 'add-ons/wprss.jpg',
113
- 'basename' => 'wp-rss-spinnerchief/wp-rss-spinnerchief.php',
114
- 'url' => 'https://www.wprssaggregator.com/extension/spinnerchief/'
115
- ),
116
- ));
117
- }
118
-
119
- /**
120
- * Check if plugin file exists but plugin is inactive
121
- * @param $path Path to plugin file
122
- * @since 4.7.3
123
- * @return bool TRUE if plugin file found but plugin inactive. False otherwise
124
- */
125
- function wprss_is_plugin_inactive( $path ){
126
-
127
- if( ! isset( $path ) ){
128
- return FALSE;
129
- }
130
-
131
- if( file_exists( WP_PLUGIN_DIR . '/' . $path ) && is_plugin_inactive( $path ) ){
132
- return TRUE; // plugin found but inactive
133
- }
134
-
135
- return FALSE;
136
-
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin-ajax-notice.php CHANGED
File without changes
includes/admin-debugging.php CHANGED
@@ -167,63 +167,6 @@ use Psr\Log\LogLevel;
167
  <?php
168
  }
169
 
170
- /**
171
- * Renders the debug log.
172
- */
173
- function wprss_debug_render_error_log() {
174
- $num = 200;
175
- $logs = wpra_get_logger()->getLogs($num, 1);
176
- ?>
177
-
178
- <h3><?= __( 'Debug Log', 'wprss' ); ?></h3>
179
- <p><i><?= sprintf(__( 'Showing the most recent %d log messages', 'wprss' ), $num); ?></i></p>
180
-
181
- <?php if (count($logs) === 0) : ?>
182
- <section class="notice notice-success notice-inline wpra-empty-log-notice">
183
- <p><?= __('The log is empty', 'wprss'); ?></p>
184
- </section>
185
- <?php else: ?>
186
- <div class="wpra-log">
187
- <p>
188
- <strong><?= __('Filters:', 'wprss') ?></strong>
189
-
190
- <span class="wpra-toggle-logs" data-level="all">
191
- <a href="#"><?= __('All', 'wprss') ?></a>
192
- </span>
193
- <span class="wpra-toggle-logs wpra-selected" data-level="error">
194
- <a href="#"><?= __('Errors', 'wprss') ?></a>
195
- </span>
196
- <span class="wpra-toggle-logs wpra-selected" data-level="info">
197
- <a href="#"><?= __('Info', 'wprss') ?></a>
198
- </span>
199
- <span class="wpra-toggle-logs" data-level="notice">
200
- <a href="#"><?= __('Notice', 'wprss') ?></a>
201
- </span>
202
- <span class="wpra-toggle-logs" data-level="warning">
203
- <a href="#"><?= __('Warnings', 'wprss') ?></a>
204
- </span>
205
- <span class="wpra-toggle-logs" data-level="debug">
206
- <a href="#"><?= __('Debug', 'wprss') ?></a>
207
- </span>
208
- </p>
209
- <div class="wpra-log-container">
210
- <table>
211
- <tbody>
212
- <?php foreach ($logs as $log) : ?>
213
- <tr class="wpra-log-<?= $log['level'] ?>">
214
- <td class="wpra-log-date"><?= $log['date'] ?></td>
215
- <td class="wpra-log-level"><?= ucfirst($log['level']) ?></td>
216
- <td class="wpra-log-feed"><?= get_the_title(ucfirst($log['feed_id'])) ?></td>
217
- <td class="wpra-log-message"><?= htmlentities($log['message']) ?></td>
218
- </tr>
219
- <?php endforeach; ?>
220
- </tbody>
221
- </table>
222
- </div>
223
- </div>
224
- <?php endif;
225
- }
226
-
227
  /**
228
  * Renders the "Clear log" button
229
  *
167
  <?php
168
  }
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  /**
171
  * Renders the "Clear log" button
172
  *
includes/admin-display.php CHANGED
@@ -395,7 +395,7 @@
395
 
396
  $page = isset( $_GET['paged'] )? '&paged=' . $_GET['paged'] : '';
397
  if ( get_post_type($post) === 'wprss_feed_item' ) {
398
- if ( apply_filters('wpra_dev_mode', false) === false ) {
399
  unset($actions['edit']);
400
  }
401
  unset( $actions[ 'view' ] );
@@ -657,7 +657,11 @@
657
  *
658
  * @since 2.0
659
  */
660
- function wprss_custom_feed_item_bulk_actions( $actions ){
 
 
 
 
661
  return apply_filters( 'wprss_custom_feed_item_bulk_actions', $actions );
662
  }
663
 
395
 
396
  $page = isset( $_GET['paged'] )? '&paged=' . $_GET['paged'] : '';
397
  if ( get_post_type($post) === 'wprss_feed_item' ) {
398
+ if (!wpra_is_dev_mode()) {
399
  unset($actions['edit']);
400
  }
401
  unset( $actions[ 'view' ] );
657
  *
658
  * @since 2.0
659
  */
660
+ function wprss_custom_feed_item_bulk_actions( $actions ) {
661
+ if (!wpra_is_dev_mode()) {
662
+ unset($actions['edit']);
663
+ }
664
+
665
  return apply_filters( 'wprss_custom_feed_item_bulk_actions', $actions );
666
  }
667
 
includes/admin-editor.php CHANGED
File without changes
includes/admin-heartbeat.php CHANGED
File without changes
includes/admin-help-metaboxes.php CHANGED
File without changes
includes/admin-help-settings.php CHANGED
@@ -164,7 +164,7 @@ function wprss_settings_add_tooltips() {
164
 
165
  '. 'Some servers react in unexpected ways to the default value. In such cases, try changing this to something else.
166
 
167
- '. 'The default value is determined by the SimplePie library, and reflects its name, version and build numbers, and some other information.'),
168
  'feed_cache_enabled' => __( 'Tick this box to enable caching for a small performance gain.
169
 
170
  '. 'When enabled, websites may ignore the plugin if their RSS feed did not change. So we suggest testing it out first to ensure that your RSS feeds work well with this option enabled.
@@ -185,7 +185,7 @@ function wprss_settings_add_tooltips() {
185
 
186
  '. 'Some servers react in unexpected ways to the default value. In such cases, try changing this to something else.
187
 
188
- '. 'The default value is determined by the SimplePie library, and reflects its name, version and build numbers, and some other information.',
189
  $wprss->getName()))
190
  ), $prefix);
191
  }
164
 
165
  '. 'Some servers react in unexpected ways to the default value. In such cases, try changing this to something else.
166
 
167
+ '. 'The default value is the useragent that the Chrome Browser uses.'),
168
  'feed_cache_enabled' => __( 'Tick this box to enable caching for a small performance gain.
169
 
170
  '. 'When enabled, websites may ignore the plugin if their RSS feed did not change. So we suggest testing it out first to ensure that your RSS feeds work well with this option enabled.
185
 
186
  '. 'Some servers react in unexpected ways to the default value. In such cases, try changing this to something else.
187
 
188
+ '. 'The default value is the useragent that the Chrome browser uses.',
189
  $wprss->getName()))
190
  ), $prefix);
191
  }
includes/admin-help.php CHANGED
@@ -23,6 +23,7 @@
23
 
24
  <div class="wrap">
25
  <h2><?php _e( 'Help & Support', WPRSS_TEXT_DOMAIN ); ?></h2>
 
26
  <h3><?php _e( 'Knowledge Base', WPRSS_TEXT_DOMAIN ) ?></h3>
27
  <?php
28
  printf(
23
 
24
  <div class="wrap">
25
  <h2><?php _e( 'Help & Support', WPRSS_TEXT_DOMAIN ); ?></h2>
26
+ <?php do_action('wpra/help_page/after_title') ?>
27
  <h3><?php _e( 'Knowledge Base', WPRSS_TEXT_DOMAIN ) ?></h3>
28
  <?php
29
  printf(
includes/admin-import-export.php CHANGED
File without changes
includes/admin-log.php CHANGED
@@ -3,6 +3,7 @@
3
  use Psr\Log\LoggerInterface;
4
  use Psr\Log\LogLevel;
5
  use RebelCode\Wpra\Core\Logger\ClearableLoggerInterface;
 
6
  use RebelCode\Wpra\Core\Logger\LogReaderInterface;
7
 
8
  define('WPRSS_OPTION_CODE_LOG_LEVEL', 'log_level');
@@ -26,14 +27,11 @@ define('WPRSS_LOG_LEVEL_DEFAULT', WPRSS_LOG_LEVEL_NONE);
26
  */
27
  function wpra_get_logger($feed_id = null)
28
  {
29
- if ($feed_id === null) {
30
- return wpra_container()->get('wpra/logging/logger');
31
- }
32
-
33
- $dataset = wpra_container()->get('wpra/logging/feed_logger_dataset');
34
- $logger = $dataset[$feed_id];
35
 
36
- return $logger;
 
 
37
  }
38
 
39
  /**
@@ -54,7 +52,9 @@ function wpra_get_logger($feed_id = null)
54
  */
55
  function wprss_log_read($length = null, $start = null)
56
  {
57
- $logs = wpra_get_logger()->getLogs($length, $start);
 
 
58
 
59
  $output = '';
60
  foreach ($logs as $log) {
@@ -81,7 +81,9 @@ function wprss_get_log()
81
  */
82
  function wprss_clear_log()
83
  {
84
- wpra_get_logger()->clearLogs();
 
 
85
  }
86
 
87
  /**
@@ -101,11 +103,11 @@ function wprss_reset_log()
101
  *
102
  * @since 3.9.6
103
  */
104
- function wprss_log($message, $src = null, $log_level = LogLevel::ERROR)
105
  {
106
- wpra_get_logger()->log($log_level, $message);
107
 
108
- return;
109
  }
110
 
111
  /**
3
  use Psr\Log\LoggerInterface;
4
  use Psr\Log\LogLevel;
5
  use RebelCode\Wpra\Core\Logger\ClearableLoggerInterface;
6
+ use RebelCode\Wpra\Core\Logger\FeedLoggerInterface;
7
  use RebelCode\Wpra\Core\Logger\LogReaderInterface;
8
 
9
  define('WPRSS_OPTION_CODE_LOG_LEVEL', 'log_level');
27
  */
28
  function wpra_get_logger($feed_id = null)
29
  {
30
+ $logger = wpra_container()->get('wpra/logging/logger');
 
 
 
 
 
31
 
32
+ return ($feed_id !== null && $logger instanceof FeedLoggerInterface)
33
+ ? $logger->forFeedSource($feed_id)
34
+ : $logger;
35
  }
36
 
37
  /**
52
  */
53
  function wprss_log_read($length = null, $start = null)
54
  {
55
+ /* @var $reader LogReaderInterface */
56
+ $reader = wpra_container()->get('wpra/logging/reader');
57
+ $logs = $reader->getLogs($length, $start);
58
 
59
  $output = '';
60
  foreach ($logs as $log) {
81
  */
82
  function wprss_clear_log()
83
  {
84
+ /* @var $clearer ClearableLoggerInterface */
85
+ $clearer = wpra_container()->get('wpra/logging/clearer');
86
+ $clearer->clearLogs();
87
  }
88
 
89
  /**
103
  *
104
  * @since 3.9.6
105
  */
106
+ function wprss_log($message, $src = null, $log_level = null)
107
  {
108
+ $log_level = ($log_level) ? LogLevel::ERROR : $log_level;
109
 
110
+ wpra_get_logger()->log($log_level, $message);
111
  }
112
 
113
  /**
includes/admin-metaboxes.php CHANGED
@@ -694,16 +694,8 @@
694
  <ul>
695
  <li><a href="https://wordpress.org/support/view/plugin-reviews/wp-rss-aggregator?rate=5#postform" target="_blank"><?php _e( 'Give it a 5 star rating on WordPress.org', WPRSS_TEXT_DOMAIN ) ?></a></li>
696
  </ul>
697
- <p><strong><?php _e( 'Add functionality with our premium extensions:', WPRSS_TEXT_DOMAIN ) ?></strong></p>
698
- <?php $addons = wprss_addons_get_extra() ?>
699
- <?php if (count($addons)): ?>
700
- <ul class="add-on-list">
701
- <?php foreach ($addons as $_code => $_addon): ?>
702
- <li class="add-on <?php echo sprintf('add-on-code-%1$s', $_code) ?>"><a href="<?php echo $_addon['url'] ?>"><?php echo $_addon['title'] ?></a></li>
703
- <?php endforeach ?>
704
- </ul>
705
- <?php endif ?>
706
  <?php
 
707
  }
708
 
709
 
694
  <ul>
695
  <li><a href="https://wordpress.org/support/view/plugin-reviews/wp-rss-aggregator?rate=5#postform" target="_blank"><?php _e( 'Give it a 5 star rating on WordPress.org', WPRSS_TEXT_DOMAIN ) ?></a></li>
696
  </ul>
 
 
 
 
 
 
 
 
 
697
  <?php
698
+ do_action('wpra_share_the_love_metabox');
699
  }
700
 
701
 
includes/admin-options.php CHANGED
@@ -190,7 +190,6 @@
190
  /**
191
  * Register and define options and settings
192
  * @since 2.0
193
- * @todo add option for cron frequency
194
  *
195
  * Note: In the future might change to
196
  * the way EDD builds the settings pages, cleaner method.
@@ -507,9 +506,9 @@
507
  */
508
  function wprss_setting_unique_titles( $field ) {
509
  $unique_titles = wprss_get_general_setting( 'unique_titles' );
510
- ?>
511
- <input id="<?php echo $field['field_id'] ?>" name="wprss_settings_general[unique_titles]" type="checkbox" value="1" <?php echo checked( 1, $unique_titles, false ) ?> />
512
- <?php echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] );
513
  }
514
 
515
 
@@ -518,16 +517,24 @@
518
  * @since 3.3
519
  */
520
  function wprss_settings_custom_feed_url_callback( $field ) {
521
- $siteUrl = get_site_url();
522
  $custom_feed_url = wprss_get_general_setting( 'custom_feed_url' );
 
523
  ?>
524
- <code><?= $siteUrl ?>/</code>
525
  <input id="<?php echo $field['field_id'] ?>"
526
  name="wprss_settings_general[custom_feed_url]"
527
  type="text"
528
  value="<?php echo $custom_feed_url ?>" />
529
 
530
- <?php echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] );
 
 
 
 
 
 
 
531
  }
532
 
533
  /**
@@ -558,9 +565,8 @@
558
  */
559
  function wprss_setting_styles_disable_callback( $field ) {
560
  $styles_disable = wprss_get_general_setting( 'styles_disable' );
561
- ?>
562
- <input id="<?php echo $field['field_id'] ?>" name="wprss_settings_general[styles_disable]" type="checkbox" value="1" <?php echo checked( 1, $styles_disable, false ) ?> />
563
- <?php echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] );
564
  }
565
 
566
  /**
@@ -650,21 +656,6 @@
650
  }
651
 
652
 
653
- /**
654
- * Tracking checkbox
655
- * @since 3.6
656
- */
657
- function wprss_tracking_callback( $field ) {
658
- $tracking = wprss_get_general_setting( 'tracking' );
659
- ?>
660
- <input type="checkbox" id="<?php echo $field['field_id'] ?>" name="wprss_settings_general[tracking]" value="1" <?php echo checked( 1, $tracking, false ) ?> />
661
- <?php echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] ) ?>
662
- <label class="description" for="<?php echo $field['field_id'] ?>">
663
- <?php _e( 'Please help us improve WP RSS Aggregator by allowing us to gather anonymous usage statistics. No sensitive data is collected.', WPRSS_TEXT_DOMAIN ) ?>
664
- </label>
665
- <?php
666
- }
667
-
668
  /**
669
  * Gets options that should go in a dropdown which represents a
670
  * feed-source-specific boolean setting.
@@ -982,3 +973,30 @@
982
  )
983
  );
984
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  /**
191
  * Register and define options and settings
192
  * @since 2.0
 
193
  *
194
  * Note: In the future might change to
195
  * the way EDD builds the settings pages, cleaner method.
506
  */
507
  function wprss_setting_unique_titles( $field ) {
508
  $unique_titles = wprss_get_general_setting( 'unique_titles' );
509
+
510
+ echo wprss_options_render_checkbox( $field['field_id'], 'unique_titles', $unique_titles );
511
+ echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] );
512
  }
513
 
514
 
517
  * @since 3.3
518
  */
519
  function wprss_settings_custom_feed_url_callback( $field ) {
520
+ $siteUrl = trailingslashit(get_site_url());
521
  $custom_feed_url = wprss_get_general_setting( 'custom_feed_url' );
522
+ $fullUrl = $siteUrl . $custom_feed_url;
523
  ?>
524
+ <code><?= $siteUrl ?></code>
525
  <input id="<?php echo $field['field_id'] ?>"
526
  name="wprss_settings_general[custom_feed_url]"
527
  type="text"
528
  value="<?php echo $custom_feed_url ?>" />
529
 
530
+ <?php echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] ); ?>
531
+
532
+ <p style="font-style: normal">
533
+ <a href="<?php echo esc_attr($fullUrl); ?>" target="_blank">
534
+ <?php _e('Open custom feed', 'wprss') ?>
535
+ </a>
536
+ </p>
537
+ <?php
538
  }
539
 
540
  /**
565
  */
566
  function wprss_setting_styles_disable_callback( $field ) {
567
  $styles_disable = wprss_get_general_setting( 'styles_disable' );
568
+ echo wprss_options_render_checkbox( $field['field_id'], 'styles_disable', $styles_disable );
569
+ echo wprss_settings_inline_help( $field['field_id'], $field['tooltip'] );
 
570
  }
571
 
572
  /**
656
  }
657
 
658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  /**
660
  * Gets options that should go in a dropdown which represents a
661
  * feed-source-specific boolean setting.
973
  )
974
  );
975
  }
976
+
977
+ /**
978
+ * Renders a checkbox with a hidden field for the default value (when unchecked).
979
+ *
980
+ * @param string $id
981
+ * @param string $name
982
+ * @param string $value
983
+ * @param string $checked_value
984
+ * @param string $default_value
985
+ *
986
+ * @return string
987
+ */
988
+ function wprss_options_render_checkbox($id, $name, $value, $checked_value = '1', $default_value = '0') {
989
+ $nameAttr = esc_attr(sprintf('wprss_settings_general[%s]', $name));
990
+ ob_start();
991
+
992
+ ?>
993
+ <input type="hidden" name="<?= $nameAttr; ?>" value="<?= esc_attr($default_value) ?>"/>
994
+ <input type="checkbox"
995
+ id="<?= $id ?>"
996
+ name="<?= $nameAttr ?>"
997
+ value="<?= esc_attr($checked_value) ?>"
998
+ <?= checked( $checked_value, $value, false ) ?> />
999
+ <?php
1000
+
1001
+ return ob_get_clean();
1002
+ }
includes/admin.php CHANGED
@@ -37,10 +37,6 @@
37
  add_submenu_page( 'edit.php?post_type=wprss_feed', __( 'Debugging', WPRSS_TEXT_DOMAIN ), __( 'Debugging', WPRSS_TEXT_DOMAIN ), apply_filters( 'wprss_capability', 'manage_feed_settings'), 'wprss-debugging', 'wprss_debugging_page_display' );
38
  }, 40);
39
 
40
- add_action('admin_menu', function () {
41
- add_submenu_page( 'edit.php?post_type=wprss_feed', __( 'More Features', WPRSS_TEXT_DOMAIN ), __( 'More Features', WPRSS_TEXT_DOMAIN ) . '<span class="dashicons dashicons-star-filled wprss-more-features-glyph"></span>', apply_filters( 'wprss_capability', 'manage_feed_settings'), 'wprss-addons', 'wprss_addons_page_display' );
42
- }, 50);
43
-
44
  add_action('admin_menu', function () {
45
  add_submenu_page( 'edit.php?post_type=wprss_feed', __( 'Help & Support', WPRSS_TEXT_DOMAIN ), __( 'Help & Support', WPRSS_TEXT_DOMAIN ), apply_filters( 'wprss_capability', 'manage_feed_settings'), 'wprss-help', 'wprss_help_page_display' );
46
  }, 60);
37
  add_submenu_page( 'edit.php?post_type=wprss_feed', __( 'Debugging', WPRSS_TEXT_DOMAIN ), __( 'Debugging', WPRSS_TEXT_DOMAIN ), apply_filters( 'wprss_capability', 'manage_feed_settings'), 'wprss-debugging', 'wprss_debugging_page_display' );
38
  }, 40);
39
 
 
 
 
 
40
  add_action('admin_menu', function () {
41
  add_submenu_page( 'edit.php?post_type=wprss_feed', __( 'Help & Support', WPRSS_TEXT_DOMAIN ), __( 'Help & Support', WPRSS_TEXT_DOMAIN ), apply_filters( 'wprss_capability', 'manage_feed_settings'), 'wprss-help', 'wprss_help_page_display' );
42
  }, 60);
includes/cpt-feeds.php CHANGED
File without changes
includes/cron-jobs.php CHANGED
File without changes
includes/fallback-mbstring.php CHANGED
File without changes
includes/feed-access.php CHANGED
File without changes
includes/feed-blacklist.php CHANGED
File without changes
includes/feed-importing-images.php CHANGED
@@ -3,6 +3,7 @@
3
  // Save item image info during import
4
  use Psr\Log\LoggerInterface;
5
  use RebelCode\Wpra\Core\Data\DataSetInterface;
 
6
 
7
  class Wpra_Rss_Namespace {
8
  const ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
@@ -45,6 +46,24 @@ function wpra_detect_item_type($itemId, $item, $sourceId)
45
  }
46
  }
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  /**
49
  * Imports images for a feed item.
50
  *
@@ -61,10 +80,8 @@ function wpra_import_item_images($itemId, $item, $sourceId)
61
  update_post_meta($itemId, 'wprss_images', []);
62
  update_post_meta($itemId, 'wprss_best_image', '');
63
 
64
- /* @var $logger LoggerInterface */
65
- $container = wpra_container();
66
- $loggerDataSet = $container->get('wpra/images/logging/feed_logger_dataset');
67
- $logger = $loggerDataSet[$sourceId];
68
 
69
  $title = $item->get_title();
70
  $logger->debug('Importing images for item "{title}"', ['title' => $title]);
@@ -91,82 +108,88 @@ function wpra_import_item_images($itemId, $item, $sourceId)
91
  // Process the images, removing duds, and find the best image
92
  $images = wpra_process_images($allImages, $source, $bestImage);
93
 
94
- $ftImageUrl = null;
95
- switch ($ftImageOpt)
96
- {
97
- case 'auto':
98
- if (!empty($bestImage)) {
99
- $ftImageUrl = $bestImage;
100
- }
101
- break;
 
 
102
 
103
- case 'media':
104
- if (isset($images['media'])) {
105
- $ftImageUrl = $images['media'];
106
- }
107
- break;
108
 
109
- case 'enclosure':
110
- if (is_array($images['enclosure']) && !empty($images['enclosure'])) {
111
- $ftImageUrl = reset($images['enclosure']);
112
- }
113
- break;
114
 
115
- case 'content':
116
- if (is_array($images['content']) && !empty($images['content'])) {
117
- $ftImageUrl = reset($images['content']);
118
- }
119
- break;
120
 
121
- case 'itunes':
122
- if (is_array($images['itunes']) && !empty($images['itunes'])) {
123
- $ftImageUrl = reset($images['itunes']);
124
- }
125
- break;
126
 
127
- case 'default':
128
- default:
129
- $ftImageUrl = '';
130
- break;
131
- }
132
 
133
- if (empty($ftImageUrl)) {
134
- // If not always using the default image, and items must have an image, delete the item
135
- if ($ftImageOpt !== 'default' && wpra_image_feature_enabled('must_have_ft_image') && $source['must_have_ft_image']) {
136
- $logger->debug('Rejecting item "{title}" due to a lack of a featured image.', [
137
- 'title' => get_post($itemId)->post_title,
138
- ]);
139
 
140
- wp_delete_post($itemId, true);
141
- } else {
142
- // Get the feed source's default featured image
143
- $defaultFtImage = get_post_thumbnail_id($sourceId);
144
- // Assign it to the feed item
145
- $defaultSuccessful = set_post_thumbnail($itemId, $defaultFtImage);
146
- // The feed item is classified as using the default image if:
147
- // - the default image was successfully assigned
148
- // - the user did NOT explicitly want to use the default
149
- $usedDefault = $defaultSuccessful && $ftImageOpt !== 'default';
150
-
151
- if ($usedDefault) {
152
- update_post_meta($itemId, 'wprss_item_is_using_def_image', '1');
153
- $logger->notice('Used the feed source\'s default featured image for "{title}"', ['title' => $title]);
154
  } else {
155
- $logger->notice('No featured image was found for item "{title}"', ['title' => $title]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
- }
158
- } else {
159
- $logger->info('Set featured image from URL: "{url}"', ['url' => $ftImageUrl]);
160
- wpra_set_featured_image_from_url($itemId, $ftImageUrl);
161
 
162
- if (wpra_image_feature_enabled('siphon_ft_image') && $source['siphon_ft_image']) {
163
- $content = get_post($itemId)->post_content;
164
- $newContent = wpra_remove_image_from_content($content, $ftImageUrl);
165
 
166
- wp_update_post([
167
- 'ID' => $itemId,
168
- 'post_content' => $newContent
169
- ]);
 
170
  }
171
  }
172
 
3
  // Save item image info during import
4
  use Psr\Log\LoggerInterface;
5
  use RebelCode\Wpra\Core\Data\DataSetInterface;
6
+ use RebelCode\Wpra\Core\Logger\FeedLoggerInterface;
7
 
8
  class Wpra_Rss_Namespace {
9
  const ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
46
  }
47
  }
48
 
49
+ /**
50
+ * Retrieves the logger instance to use for image importing.
51
+ *
52
+ * @since 4.15.1
53
+ *
54
+ * @param int $feedId Optional feed source ID to log messages specifically for that feed source.
55
+ *
56
+ * @return LoggerInterface
57
+ */
58
+ function wpra_get_images_logger($feedId = null)
59
+ {
60
+ $logger = wpra_container()->get('wpra/images/logging/logger');
61
+
62
+ return ($feedId !== null && $logger instanceof FeedLoggerInterface)
63
+ ? $logger->forFeedSource($feedId)
64
+ : $logger;
65
+ }
66
+
67
  /**
68
  * Imports images for a feed item.
69
  *
80
  update_post_meta($itemId, 'wprss_images', []);
81
  update_post_meta($itemId, 'wprss_best_image', '');
82
 
83
+ /* @var $logger FeedLoggerInterface */
84
+ $logger = wpra_get_images_logger($sourceId);
 
 
85
 
86
  $title = $item->get_title();
87
  $logger->debug('Importing images for item "{title}"', ['title' => $title]);
108
  // Process the images, removing duds, and find the best image
109
  $images = wpra_process_images($allImages, $source, $bestImage);
110
 
111
+ // If the featured image importing feature is enabled, import the featured image
112
+ if (wpra_image_feature_enabled('import_ft_images')) {
113
+ $ftImageUrl = null;
114
+ switch ($ftImageOpt)
115
+ {
116
+ case 'auto':
117
+ if (!empty($bestImage)) {
118
+ $ftImageUrl = $bestImage;
119
+ }
120
+ break;
121
 
122
+ case 'media':
123
+ if (isset($images['media'])) {
124
+ $ftImageUrl = $images['media'];
125
+ }
126
+ break;
127
 
128
+ case 'enclosure':
129
+ if (isset($images['enclosure']) && !empty($images['enclosure'])) {
130
+ $ftImageUrl = reset($images['enclosure']);
131
+ }
132
+ break;
133
 
134
+ case 'content':
135
+ if (isset($images['content']) && !empty($images['content'])) {
136
+ $ftImageUrl = reset($images['content']);
137
+ }
138
+ break;
139
 
140
+ case 'itunes':
141
+ if (isset($images['itunes']) && !empty($images['itunes'])) {
142
+ $ftImageUrl = reset($images['itunes']);
143
+ }
144
+ break;
145
 
146
+ case 'default':
147
+ default:
148
+ $ftImageUrl = '';
149
+ break;
150
+ }
151
 
152
+ // Filter the featured image URL
153
+ $ftImageUrl = apply_filters('wpra/importer/images/ft_image_url', $ftImageUrl, $item, $sourceId);
 
 
 
 
154
 
155
+ if (empty($ftImageUrl)) {
156
+ // If not always using the default image, and items must have an image, delete the item
157
+ if ($ftImageOpt !== 'default' && wpra_image_feature_enabled('must_have_ft_image') && $source['must_have_ft_image']) {
158
+ $logger->debug('Rejecting item "{title}" due to a lack of a featured image.', [
159
+ 'title' => get_post($itemId)->post_title,
160
+ ]);
161
+
162
+ wp_delete_post($itemId, true);
 
 
 
 
 
 
163
  } else {
164
+ // Get the feed source's default featured image
165
+ $defaultFtImage = get_post_thumbnail_id($sourceId);
166
+ // Assign it to the feed item
167
+ $defaultSuccessful = set_post_thumbnail($itemId, $defaultFtImage);
168
+ // The feed item is classified as using the default image if:
169
+ // - the default image was successfully assigned
170
+ // - the user did NOT explicitly want to use the default
171
+ $usedDefault = $defaultSuccessful && $ftImageOpt !== 'default';
172
+
173
+ if ($usedDefault) {
174
+ update_post_meta($itemId, 'wprss_item_is_using_def_image', '1');
175
+ $logger->notice('Used the feed source\'s default featured image for "{title}"', ['title' => $title]);
176
+ } else {
177
+ $logger->notice('No featured image was found for item "{title}"', ['title' => $title]);
178
+ }
179
  }
180
+ } else {
181
+ $logger->info('Set featured image from URL: "{url}"', ['url' => $ftImageUrl]);
182
+ wpra_set_featured_image_from_url($itemId, $ftImageUrl);
 
183
 
184
+ if (wpra_image_feature_enabled('siphon_ft_image') && $source['siphon_ft_image']) {
185
+ $content = get_post($itemId)->post_content;
186
+ $newContent = wpra_remove_image_from_content($content, $ftImageUrl);
187
 
188
+ wp_update_post([
189
+ 'ID' => $itemId,
190
+ 'post_content' => $newContent
191
+ ]);
192
+ }
193
  }
194
  }
195
 
includes/feed-importing.php CHANGED
@@ -6,9 +6,7 @@
6
  * @package WPRSSAggregator
7
  */
8
 
9
- use Psr\Log\LogLevel;
10
-
11
- // Warning: Order may be important
12
  add_filter('wprss_normalize_permalink', 'wprss_google_news_url_fix', 8);
13
  add_filter('wprss_normalize_permalink', 'wprss_bing_news_url_fix', 9);
14
  add_filter('wprss_normalize_permalink', 'wprss_google_alerts_url_fix', 10);
@@ -100,11 +98,18 @@
100
  }
101
  }
102
 
103
- // Gather the permalinks of existing feed item's related to this feed source
104
- $existing_permalinks = wprss_get_existing_permalinks( $feed_ID );
 
 
 
105
  // Gather the titles of the items that are imported
 
106
  $existing_titles = [];
107
 
 
 
 
108
  // Generate a list of items fetched, that are not already in the DB
109
  $new_items = array();
110
  foreach ( $items_to_insert as $item ) {
@@ -113,31 +118,33 @@
113
  $permalink = wprss_normalize_permalink( $item->get_permalink(), $item, $feed_ID );
114
  $logger->debug('Checking item "{0}"', [$item_title]);
115
 
116
- // Check if not blacklisted and not already imported
117
- $is_blacklisted = wprss_is_blacklisted( $permalink );
118
- $permalink_exists = array_key_exists( $permalink, $existing_permalinks );
119
- $title_exists_db = wprss_item_title_exists( $item->get_title() );
120
- $title_exists_feed = array_key_exists($item_title, $existing_titles);
121
- $title_exists = $title_exists_db || $title_exists_feed;
122
-
123
- $existing_titles[$item_title] = 1;
124
-
125
- if ($is_blacklisted) {
126
  $logger->debug('Item "{0}" is blacklisted', [$item_title]);
127
 
128
  continue;
129
  }
130
 
131
- if ($permalink_exists) {
 
132
  $logger->debug('Item "{0}" already exists in the database', [$item_title]);
133
 
134
  continue;
135
  }
136
 
137
- if ($title_exists) {
138
- $logger->debug('An item with the title "{0}" already exists', [$item_title]);
 
 
 
 
 
139
 
140
- continue;
 
 
 
 
141
  }
142
 
143
  $new_items[] = $item;
@@ -231,6 +238,9 @@
231
  /* Fetch the feed from the soure URL specified */
232
  $feed = wprss_fetch_feed( $feed_url, $source, $force_feed );
233
 
 
 
 
234
  // Remove previously added filters and actions
235
  remove_filter( 'wp_feed_cache_transient_lifetime' , 'wprss_feed_cache_lifetime' );
236
 
@@ -547,7 +557,7 @@ function wprss_get_feed_cache_dir()
547
  // Count of items inserted
548
  $items_inserted = 0;
549
 
550
- foreach ( $items as $item ) {
551
 
552
  // Normalize the URL
553
  $permalink = $item->get_permalink(); // Link or enclosure URL
@@ -590,8 +600,13 @@ function wprss_get_feed_cache_dir()
590
  $format = 'Y-m-d H:i:s';
591
  $has_date = $item->get_date( 'U' ) ? TRUE : FALSE;
592
  $timestamp = $has_date ? $item->get_date( 'U' ) : date( 'U' );
593
- $date = date( $format, $timestamp );
594
- $date_gmt = gmdate( $format, $timestamp );
 
 
 
 
 
595
 
596
  // Do not let WordPress sanitize the excerpt
597
  // WordPress sanitizes the excerpt because it's expected to be typed by a user and sent in a POST
@@ -665,15 +680,20 @@ function wprss_get_feed_cache_dir()
665
  update_post_meta( $feed_ID, 'wprss_last_update_items', $items_inserted );
666
  }
667
 
668
-
669
- /**
670
- * Inserts the appropriate post meta for feed items.
671
- *
672
- * Called from 'wprss_items_insert_post'
673
- *
674
- * @since 2.3
675
- */
 
 
 
 
676
  function wprss_items_insert_post_meta( $inserted_ID, $item, $feed_ID, $permalink, $enclosure_url ) {
 
677
  update_post_meta( $inserted_ID, 'wprss_item_permalink', $permalink );
678
  update_post_meta( $inserted_ID, 'wprss_item_enclosure', $enclosure_url );
679
 
6
  * @package WPRSSAggregator
7
  */
8
 
9
+ // Warning: Order may be important
 
 
10
  add_filter('wprss_normalize_permalink', 'wprss_google_news_url_fix', 8);
11
  add_filter('wprss_normalize_permalink', 'wprss_bing_news_url_fix', 9);
12
  add_filter('wprss_normalize_permalink', 'wprss_google_alerts_url_fix', 10);
98
  }
99
  }
100
 
101
+ $unique_titles_only = get_post_meta($feed_ID, 'wprss_unique_titles', true);
102
+ $unique_titles_only = ($unique_titles_only === '')
103
+ ? wprss_get_general_setting('unique_titles')
104
+ : $unique_titles_only;
105
+ $unique_titles_only = filter_var($unique_titles_only, FILTER_VALIDATE_BOOLEAN);
106
  // Gather the titles of the items that are imported
107
+ // The import process will check not only the titles in the DB but the titles currently in the feed
108
  $existing_titles = [];
109
 
110
+ // Gather the permalinks of existing feed item's related to this feed source
111
+ $existing_permalinks = wprss_get_existing_permalinks( $feed_ID );
112
+
113
  // Generate a list of items fetched, that are not already in the DB
114
  $new_items = array();
115
  foreach ( $items_to_insert as $item ) {
118
  $permalink = wprss_normalize_permalink( $item->get_permalink(), $item, $feed_ID );
119
  $logger->debug('Checking item "{0}"', [$item_title]);
120
 
121
+ // Check if blacklisted
122
+ if (wprss_is_blacklisted($permalink)) {
 
 
 
 
 
 
 
 
123
  $logger->debug('Item "{0}" is blacklisted', [$item_title]);
124
 
125
  continue;
126
  }
127
 
128
+ // Check if already imported
129
+ if (array_key_exists($permalink, $existing_permalinks)) {
130
  $logger->debug('Item "{0}" already exists in the database', [$item_title]);
131
 
132
  continue;
133
  }
134
 
135
+ // Check if title exists (if the option is enabled)
136
+ if ($unique_titles_only) {
137
+ $title_exists_db = wprss_item_title_exists($item->get_title());
138
+ $title_exists_feed = array_key_exists($item_title, $existing_titles);
139
+ $title_exists = $title_exists_db || $title_exists_feed;
140
+ // Add this item's title to the list to check against
141
+ $existing_titles[$item_title] = 1;
142
 
143
+ if ($title_exists) {
144
+ $logger->debug('An item with the title "{0}" already exists', [$item_title]);
145
+
146
+ continue;
147
+ }
148
  }
149
 
150
  $new_items[] = $item;
238
  /* Fetch the feed from the soure URL specified */
239
  $feed = wprss_fetch_feed( $feed_url, $source, $force_feed );
240
 
241
+ update_post_meta( $source, 'wprss_site_url', $feed->get_permalink() );
242
+ update_post_meta( $source, 'wprss_feed_image', $feed->get_image_url() );
243
+
244
  // Remove previously added filters and actions
245
  remove_filter( 'wp_feed_cache_transient_lifetime' , 'wprss_feed_cache_lifetime' );
246
 
557
  // Count of items inserted
558
  $items_inserted = 0;
559
 
560
+ foreach ( $items as $i => $item ) {
561
 
562
  // Normalize the URL
563
  $permalink = $item->get_permalink(); // Link or enclosure URL
600
  $format = 'Y-m-d H:i:s';
601
  $has_date = $item->get_date( 'U' ) ? TRUE : FALSE;
602
  $timestamp = $has_date ? $item->get_date( 'U' ) : date( 'U' );
603
+
604
+ if (apply_filters('wpra/importer/allow_scheduled_items', false) !== true) {
605
+ $timestamp = min(time() - $i, $timestamp);
606
+ }
607
+
608
+ $date = date( $format, $timestamp );
609
+ $date_gmt = gmdate( $format, $timestamp );
610
 
611
  // Do not let WordPress sanitize the excerpt
612
  // WordPress sanitizes the excerpt because it's expected to be typed by a user and sent in a POST
680
  update_post_meta( $feed_ID, 'wprss_last_update_items', $items_inserted );
681
  }
682
 
683
+ /**
684
+ * Inserts the appropriate post meta for feed items.
685
+ *
686
+ * Called from 'wprss_items_insert_post'
687
+ *
688
+ * @since 2.3
689
+ *
690
+ * @param int $inserted_ID The inserted post ID.
691
+ * @param SimplePie_Item $item The SimplePie item object.
692
+ * @param string $permalink The item's permalink.
693
+ * @param string $enclosure_url The URL to the item's enclosure.
694
+ */
695
  function wprss_items_insert_post_meta( $inserted_ID, $item, $feed_ID, $permalink, $enclosure_url ) {
696
+ update_post_meta( $inserted_ID, 'wprss_item_date', $item->get_date(DATE_ISO8601) );
697
  update_post_meta( $inserted_ID, 'wprss_item_permalink', $permalink );
698
  update_post_meta( $inserted_ID, 'wprss_item_enclosure', $enclosure_url );
699
 
includes/feed-processing.php CHANGED
File without changes
includes/feed-states.php CHANGED
File without changes
includes/functions.php CHANGED
@@ -4,10 +4,106 @@ use Aventura\Wprss\Core\Model\Regex\HtmlEncoder;
4
 
5
  /**
6
  * Helper and misc functions.
7
- *
8
  * @todo Make this part of Core instead
9
  */
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Returns a representation of an HTML expression that matches all representations of that HTML.
13
  *
@@ -117,7 +213,7 @@ if (!function_exists('uri_is_absolute')) {
117
 
118
  /**
119
  * Check if the URI is absolute.
120
- *
121
  * Check is made based on whether or not there's a '//' sequence
122
  * somewhere in the beginning.
123
  *
4
 
5
  /**
6
  * Helper and misc functions.
7
+ *
8
  * @todo Make this part of Core instead
9
  */
10
 
11
+ /**
12
+ * Checks if developer mode is enabled.
13
+ *
14
+ * @since 4.15.1
15
+ *
16
+ * @return bool
17
+ */
18
+ function wpra_is_dev_mode()
19
+ {
20
+ return apply_filters('wpra_dev_mode', false) === true;
21
+ }
22
+
23
+ /**
24
+ * WP RSS Aggregator's version of {@link wp_remote_get()}.
25
+ *
26
+ * It ensures that the feed request useragent is used as the HTTP request's User Agent String.
27
+ *
28
+ * @since 4.15.1
29
+ *
30
+ * @see https://developer.wordpress.org/reference/classes/WP_Http/request/ Information on the $args array parameter.
31
+ *
32
+ * @param string $url The URL
33
+ * @param array $args The arguments.
34
+ *
35
+ * @return array|WP_Error
36
+ */
37
+ function wpra_remote_get($url, $args)
38
+ {
39
+ $args['user-agent'] = wprss_get_general_setting('feed_request_useragent');
40
+
41
+ return wp_remote_get($url, $args);
42
+ }
43
+
44
+ /**
45
+ * WP RSS Aggregator's version of {@link wp_safe_remote_get()}.
46
+ *
47
+ * It ensures that the feed request useragent is used as the HTTP request's User Agent String.
48
+ *
49
+ * @since 4.15.1
50
+ *
51
+ * @see https://developer.wordpress.org/reference/classes/WP_Http/request/ Information on the $args array parameter.
52
+ *
53
+ * @param string $url The URL
54
+ * @param array $args The arguments.
55
+ *
56
+ * @return array|WP_Error
57
+ */
58
+ function wpra_safe_remote_get($url, $args)
59
+ {
60
+ $args['user-agent'] = wprss_get_general_setting('feed_request_useragent');
61
+
62
+ return wp_safe_remote_get($url, $args);
63
+ }
64
+
65
+ /**
66
+ * Utility function for checking a plugin's state, even if it's inactive.
67
+ *
68
+ * @since 4.15.1
69
+ *
70
+ * @param string $basename The basename of the plugin.
71
+ *
72
+ * @return int 0 if the plugin is not installed, 1 if installed but not activated, 2 if installed and activated.
73
+ */
74
+ function wpra_get_plugin_state($basename)
75
+ {
76
+ // ACTIVE
77
+ if (is_plugin_active($basename)) {
78
+ return 2;
79
+ }
80
+
81
+ // INSTALLED & INACTIVE
82
+ if (file_exists(WP_PLUGIN_DIR . '/' . $basename) && is_plugin_inactive($basename)) {
83
+ return 1;
84
+ }
85
+
86
+ // NOT INSTALLED
87
+ return 0;
88
+ }
89
+
90
+ /**
91
+ * Utillity function for generating a URL that activates a plugin.
92
+ *
93
+ * @since 4.15.1
94
+ *
95
+ * @param string $basename The basename of the plugin.
96
+ *
97
+ * @return string
98
+ */
99
+ function wpra_get_activate_plugin_url($basename)
100
+ {
101
+ return wp_nonce_url(
102
+ sprintf('plugins.php?action=activate&amp;plugin=%s', $basename),
103
+ sprintf('activate-plugin_%s', $basename)
104
+ );
105
+ }
106
+
107
  /**
108
  * Returns a representation of an HTML expression that matches all representations of that HTML.
109
  *
213
 
214
  /**
215
  * Check if the URI is absolute.
216
+ *
217
  * Check is made based on whether or not there's a '//' sequence
218
  * somewhere in the beginning.
219
  *
includes/image-caching.php CHANGED
@@ -488,7 +488,7 @@ class WPRSS_Image_Cache {
488
  * @see WPRSS_Image_Cache_Image::get_current_path()
489
  * @see get_tmp_dir()
490
  * @see wp_mkdir_p()
491
- * @see wp_safe_remote_get()
492
  * @see verify_file_md5()
493
  * @param WPRSS_Image_Cache_Image|string $image An instance of a cache file, or the URL to download.
494
  * @param int|null $request_timeout The timeout for the download request.
@@ -544,7 +544,14 @@ class WPRSS_Image_Cache {
544
  require_once( $file_lib_path );
545
 
546
  // Retrieving the remote resource
547
- $response = wp_safe_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
 
 
 
 
 
 
 
548
 
549
  // Could not retrieve
550
  if ( is_wp_error( $response ) ) {
488
  * @see WPRSS_Image_Cache_Image::get_current_path()
489
  * @see get_tmp_dir()
490
  * @see wp_mkdir_p()
491
+ * @see wpra_safe_remote_get()
492
  * @see verify_file_md5()
493
  * @param WPRSS_Image_Cache_Image|string $image An instance of a cache file, or the URL to download.
494
  * @param int|null $request_timeout The timeout for the download request.
544
  require_once( $file_lib_path );
545
 
546
  // Retrieving the remote resource
547
+ $response = wpra_remote_get(
548
+ $url,
549
+ array(
550
+ 'timeout' => $timeout,
551
+ 'stream' => true,
552
+ 'filename' => $tmpfname
553
+ )
554
+ );
555
 
556
  // Could not retrieve
557
  if ( is_wp_error( $response ) ) {
includes/libraries/WordPress-Readme-Parser/ReadmeParser.php CHANGED
File without changes
includes/libraries/browser.php CHANGED
File without changes
includes/libraries/php-markdown/markdown.php CHANGED
File without changes
includes/licensing.php CHANGED
File without changes
includes/misc-functions.php CHANGED
File without changes
includes/opml-importer.php CHANGED
File without changes
includes/readme.php CHANGED
File without changes
includes/roles-capabilities.php CHANGED
File without changes
includes/scripts.php CHANGED
@@ -51,11 +51,6 @@
51
  'pause' => __( 'Pause', WPRSS_TEXT_DOMAIN )
52
  ));
53
 
54
- wp_register_script( 'wprss-custom-bulk-actions-feed-item', WPRSS_JS . 'admin-custom-bulk-actions-feed-item.js', array( 'jquery' ), $version );
55
- wp_localize_script( 'wprss-custom-bulk-actions-feed-item', 'wprss_admin_bulk_feed_item', array(
56
- 'trash' => __( 'Move to Trash', WPRSS_TEXT_DOMAIN )
57
- ));
58
-
59
  wp_register_script( 'wprss-feed-source-table-heartbeat', WPRSS_JS .'heartbeat.js', array(), $version );
60
  wp_localize_script( 'wprss-feed-source-table-heartbeat', 'wprss_admin_heartbeat', array(
61
  'ago' => __( 'ago', WPRSS_TEXT_DOMAIN )
51
  'pause' => __( 'Pause', WPRSS_TEXT_DOMAIN )
52
  ));
53
 
 
 
 
 
 
54
  wp_register_script( 'wprss-feed-source-table-heartbeat', WPRSS_JS .'heartbeat.js', array(), $version );
55
  wp_localize_script( 'wprss-feed-source-table-heartbeat', 'wprss_admin_heartbeat', array(
56
  'ago' => __( 'ago', WPRSS_TEXT_DOMAIN )
includes/system-info.php CHANGED
@@ -13,13 +13,13 @@
13
 
14
  /**
15
  * Generate the system information
16
- *
17
  * @since 3.1
18
- */
19
- function wprss_system_info() {
20
- global $wpdb;
21
 
22
- ?>
23
  <h3><?php _e( 'System Information', WPRSS_TEXT_DOMAIN ) ?></h3>
24
  <?php
25
  $form_url = admin_url( 'edit.php?post_type=wprss_feed&page=wprss-debugging' );
@@ -31,29 +31,29 @@
31
  <p class="submit">
32
  <input type="hidden" name="wprss-action" value="download_sysinfo" />
33
  <button type="submit" class="button button-primary" id="wprss-download-sysinfo">
34
- <i class="fa fa-download"></i>
35
- <?php _e( 'Download System Info File', WPRSS_TEXT_DOMAIN ) ?>
36
  </button>
37
  </p>
38
  </form>
39
 
40
- <?php
41
- }
42
 
43
 
44
- /**
45
- * Prints the system information
46
- *
47
- * @since 4.6.8
48
- */
49
- function wprss_print_system_info() {
50
  global $wpdb;
51
-
52
  if ( ! class_exists( 'Browser' ) )
53
  require_once WPRSS_DIR . 'includes/libraries/browser.php';
54
 
55
  $browser = new Browser();
56
-
57
  ?>
58
  ### Begin System Info ###
59
 
@@ -70,15 +70,29 @@ WordPress Version: <?php echo get_bloginfo( 'version' ) . "\n"; ?>
70
  <?php echo $browser ; ?>
71
 
72
  PHP Version: <?php echo PHP_VERSION . "\n"; ?>
73
- MySQL Version: <?php if ( $server_info = wprss_sysinfo_get_db_server() )
74
- echo sprintf( '%1$s (%2$s)', $server_info['server_info'], $server_info['extension'] );
75
- else
76
- _e( 'Could not determine database driver version', WPRSS_TEXT_DOMAIN );
77
- ?>
 
 
 
 
 
 
 
 
 
 
78
 
79
  Web Server Info: <?php echo $_SERVER['SERVER_SOFTWARE'] . "\n"; ?>
80
 
81
- PHP Safe Mode: <?php echo ini_get( 'safe_mode' ) ? "Yes" : "No\n"; ?>
 
 
 
 
82
  PHP Memory Limit: <?php echo ini_get( 'memory_limit' ) . "\n"; ?>
83
  PHP Post Max Size: <?php echo ini_get( 'post_max_size' ) . "\n"; ?>
84
  PHP Time Limit: <?php echo ini_get( 'max_execution_time' ) . "\n"; ?>
@@ -119,15 +133,15 @@ $plugins = get_plugins();
119
  $active_plugins = get_option( 'active_plugins', array() );
120
  $inactive_plugins = array();
121
  foreach ( $plugins as $plugin_path => $plugin ):
122
- // If the plugin isn't active, don't show it.
123
- if ( ! in_array( $plugin_path, $active_plugins ) ) {
124
- $inactive_plugins[] = $plugin;
125
- continue;
126
- }
127
 
128
  echo $plugin['Name']; ?>: <?php echo $plugin['Version'] ."\n";
129
 
130
- endforeach;
131
 
132
  if ( is_multisite() ) :
133
  ?>
@@ -139,18 +153,18 @@ $plugins = wp_get_active_network_plugins();
139
  $active_plugins = get_site_option( 'active_sitewide_plugins', array() );
140
 
141
  foreach ( $plugins as $plugin_path ) {
142
- $plugin_base = plugin_basename( $plugin_path );
143
 
144
- // If the plugin isn't active, don't show it.
145
- if ( ! array_key_exists( $plugin_base, $active_plugins ) )
146
- continue;
147
 
148
- $plugin = get_plugin_data( $plugin_path );
149
 
150
- echo $plugin['Name'] . ': ' . $plugin['Version'] ."\n";
151
  }
152
 
153
- endif;
154
 
155
  if ( ! is_multisite() ) : ?>
156
 
@@ -162,7 +176,7 @@ foreach ( $inactive_plugins as $inactive_plugin ):
162
 
163
  echo $inactive_plugin['Name']; ?>: <?php echo $inactive_plugin['Version'] ."\n";
164
 
165
- endforeach;
166
 
167
  endif; ?>
168
 
@@ -170,11 +184,11 @@ CURRENT THEME:
170
 
171
  <?php
172
  if ( get_bloginfo( 'version' ) < '3.4' ) {
173
- $theme_data = get_theme_data( get_stylesheet_directory() . '/style.css' );
174
- echo $theme_data['Name'] . ': ' . $theme_data['Version'];
175
  } else {
176
- $theme_data = wp_get_theme();
177
- echo $theme_data->Name . ': ' . $theme_data->Version;
178
  }
179
  ?>
180
 
@@ -219,79 +233,89 @@ foreach ($extensions as $extension) {
219
 
220
  ### End System Info ###
221
  <?php
222
- }
223
-
224
-
225
- /**
226
- * Generates the System Info Download File
227
- *
228
- * @since 3.1
229
- * @return void
230
- */
231
- function wprss_generate_sysinfo_download() {
232
- nocache_headers();
233
-
234
- check_admin_referer('wprss-sysinfo');
235
-
236
- header( "Content-type: text/plain" );
237
- header( 'Content-Disposition: attachment; filename="wprss-system-info.txt"' );
238
-
239
- echo wp_strip_all_tags( $_POST['wprss-sysinfo'] );
240
- exit;
241
- }
242
- add_action( 'wprss_download_sysinfo', 'wprss_generate_sysinfo_download' );
243
-
244
-
245
- /**
246
- * Retrieves information about the DB server.
247
- *
248
- * Will use WordPress configuration by default;
249
- * Currently, the following members are present in the result:
250
- * - 'extension': The extension that is used to connect. Possible values: 'mysqli', 'mysql'.
251
- * - 'server_info': The version number of the database engine, i.e. '5.6.22'.
252
- *
253
- * @since 4.7.2
254
- * @param null|string $host The address of the database host, to which to connect.
255
- * May contain the port number in standard URI format.
256
- * Default: value of the DB_HOST constant, if defined, otherwise null.
257
- * @param null|string $username The username to be used for connecting to the databse.
258
- * Default: value of the DB_USER constant, if defined, otherwise null.
259
- * @param null|string $password The password to be used for connecting to the database.
260
- * Default: value of the DB_PASSWORD constant, if defined, otherwise null.
261
- * @param null|int $port An integer, representing the port, at which to connect to the DB server.
262
- * Default: auto-determined from host.
263
- * @return array|null An array, containing the following indexes, if successful: 'extension', 'server_info'.
264
- * Otherwise, null.
265
- */
266
- function wprss_sysinfo_get_db_server( $host = null, $username = null, $password = null, $port = null ) {
267
- $result = array();
268
-
269
- if ( is_null( $host ) && defined( 'DB_HOST') ) $host = DB_HOST;
270
- if ( is_null( $username ) && defined( 'DB_USER') ) $username = DB_USER;
271
- if ( is_null( $password ) && defined( 'DB_PASSWORD') ) $password = DB_PASSWORD;
272
-
273
- $server_address = explode( ':', $host, 2 );
274
- $host = $server_address[0];
275
- $port = is_null( $port )
276
- ? ( isset( $server_address[1] ) ? $server_address[1] : null )
277
- : $port;
278
- $port = $port ? intval( (string)$port ) : null;
279
-
280
- if ( function_exists( 'mysqli_get_server_info' ) ){
281
- $mysqli = new mysqli( $host, $username, $password, '', $port );
282
- $result['extension'] = 'mysqli';
283
- $result['server_info'] = $mysqli->server_info;
284
- return $result;
285
- }
286
-
287
- if ( function_exists( 'mysql_connect' ) ) {
288
- if ( $port ) $host = implode ( ':', array( $host, $port ) );
289
-
290
- $mysql = mysql_connect( $host, $username, $password );
291
- $result['extension'] = 'mysql';
292
- $result['server_info'] = mysql_get_server_info( $mysql );
293
- return $result;
294
- }
295
-
296
- return null;
297
- }
 
 
 
 
 
 
 
 
 
 
13
 
14
  /**
15
  * Generate the system information
16
+ *
17
  * @since 3.1
18
+ */
19
+ function wprss_system_info() {
20
+ global $wpdb;
21
 
22
+ ?>
23
  <h3><?php _e( 'System Information', WPRSS_TEXT_DOMAIN ) ?></h3>
24
  <?php
25
  $form_url = admin_url( 'edit.php?post_type=wprss_feed&page=wprss-debugging' );
31
  <p class="submit">
32
  <input type="hidden" name="wprss-action" value="download_sysinfo" />
33
  <button type="submit" class="button button-primary" id="wprss-download-sysinfo">
34
+ <i class="fa fa-download"></i>
35
+ <?php _e( 'Download System Info File', WPRSS_TEXT_DOMAIN ) ?>
36
  </button>
37
  </p>
38
  </form>
39
 
40
+ <?php
41
+ }
42
 
43
 
44
+ /**
45
+ * Prints the system information
46
+ *
47
+ * @since 4.6.8
48
+ */
49
+ function wprss_print_system_info() {
50
  global $wpdb;
51
+
52
  if ( ! class_exists( 'Browser' ) )
53
  require_once WPRSS_DIR . 'includes/libraries/browser.php';
54
 
55
  $browser = new Browser();
56
+
57
  ?>
58
  ### Begin System Info ###
59
 
70
  <?php echo $browser ; ?>
71
 
72
  PHP Version: <?php echo PHP_VERSION . "\n"; ?>
73
+ MySQL Version: <?php $server_info = wprss_sysinfo_get_db_server();
74
+ if ( $server_info ) {
75
+ if (isset($server_info['warning'])) {
76
+ echo $server_info['extension'] . ' - ' . $server_info['warning'];
77
+ } else {
78
+ echo sprintf(
79
+ '%1$s (%2$s)',
80
+ $server_info['server_info'],
81
+ $server_info['extension']
82
+ );
83
+ }
84
+ } else {
85
+ _e( 'Could not determine database driver version', WPRSS_TEXT_DOMAIN );
86
+ }
87
+ ?>
88
 
89
  Web Server Info: <?php echo $_SERVER['SERVER_SOFTWARE'] . "\n"; ?>
90
 
91
+ PHP Safe Mode: <?php if (version_compare(PHP_VERSION, '5.4', '>=')) {
92
+ echo "No\n";
93
+ } else {
94
+ echo ini_get( 'safe_mode' ) ? "Yes" : "No\n";
95
+ } ?>
96
  PHP Memory Limit: <?php echo ini_get( 'memory_limit' ) . "\n"; ?>
97
  PHP Post Max Size: <?php echo ini_get( 'post_max_size' ) . "\n"; ?>
98
  PHP Time Limit: <?php echo ini_get( 'max_execution_time' ) . "\n"; ?>
133
  $active_plugins = get_option( 'active_plugins', array() );
134
  $inactive_plugins = array();
135
  foreach ( $plugins as $plugin_path => $plugin ):
136
+ // If the plugin isn't active, don't show it.
137
+ if ( ! in_array( $plugin_path, $active_plugins ) ) {
138
+ $inactive_plugins[] = $plugin;
139
+ continue;
140
+ }
141
 
142
  echo $plugin['Name']; ?>: <?php echo $plugin['Version'] ."\n";
143
 
144
+ endforeach;
145
 
146
  if ( is_multisite() ) :
147
  ?>
153
  $active_plugins = get_site_option( 'active_sitewide_plugins', array() );
154
 
155
  foreach ( $plugins as $plugin_path ) {
156
+ $plugin_base = plugin_basename( $plugin_path );
157
 
158
+ // If the plugin isn't active, don't show it.
159
+ if ( ! array_key_exists( $plugin_base, $active_plugins ) )
160
+ continue;
161
 
162
+ $plugin = get_plugin_data( $plugin_path );
163
 
164
+ echo $plugin['Name'] . ': ' . $plugin['Version'] ."\n";
165
  }
166
 
167
+ endif;
168
 
169
  if ( ! is_multisite() ) : ?>
170
 
176
 
177
  echo $inactive_plugin['Name']; ?>: <?php echo $inactive_plugin['Version'] ."\n";
178
 
179
+ endforeach;
180
 
181
  endif; ?>
182
 
184
 
185
  <?php
186
  if ( get_bloginfo( 'version' ) < '3.4' ) {
187
+ $theme_data = get_theme_data( get_stylesheet_directory() . '/style.css' );
188
+ echo $theme_data['Name'] . ': ' . $theme_data['Version'];
189
  } else {
190
+ $theme_data = wp_get_theme();
191
+ echo $theme_data->Name . ': ' . $theme_data->Version;
192
  }
193
  ?>
194
 
233
 
234
  ### End System Info ###
235
  <?php
236
+ }
237
+
238
+
239
+ /**
240
+ * Generates the System Info Download File
241
+ *
242
+ * @since 3.1
243
+ * @return void
244
+ */
245
+ function wprss_generate_sysinfo_download() {
246
+ nocache_headers();
247
+
248
+ check_admin_referer('wprss-sysinfo');
249
+
250
+ header( "Content-type: text/plain" );
251
+ header( 'Content-Disposition: attachment; filename="wprss-system-info.txt"' );
252
+
253
+ echo wp_strip_all_tags( $_POST['wprss-sysinfo'] );
254
+ exit;
255
+ }
256
+ add_action( 'wprss_download_sysinfo', 'wprss_generate_sysinfo_download' );
257
+
258
+
259
+ /**
260
+ * Retrieves information about the DB server.
261
+ *
262
+ * Will use WordPress configuration by default;
263
+ * Currently, the following members are present in the result:
264
+ * - 'extension': The extension that is used to connect. Possible values: 'mysqli', 'mysql'.
265
+ * - 'server_info': The version number of the database engine, i.e. '5.6.22'.
266
+ *
267
+ * @since 4.7.2
268
+ * @param null|string $host The address of the database host, to which to connect.
269
+ * May contain the port number in standard URI format.
270
+ * Default: value of the DB_HOST constant, if defined, otherwise null.
271
+ * @param null|string $username The username to be used for connecting to the databse.
272
+ * Default: value of the DB_USER constant, if defined, otherwise null.
273
+ * @param null|string $password The password to be used for connecting to the database.
274
+ * Default: value of the DB_PASSWORD constant, if defined, otherwise null.
275
+ * @param null|int $port An integer, representing the port, at which to connect to the DB server.
276
+ * Default: auto-determined from host.
277
+ * @return array|null An array, containing the following indexes, if successful: 'extension', 'server_info'.
278
+ * Otherwise, null.
279
+ */
280
+ function wprss_sysinfo_get_db_server( $host = null, $username = null, $password = null, $port = null ) {
281
+ $result = array();
282
+
283
+ if ( is_null( $host ) && defined( 'DB_HOST') ) $host = DB_HOST;
284
+ if ( is_null( $username ) && defined( 'DB_USER') ) $username = DB_USER;
285
+ if ( is_null( $password ) && defined( 'DB_PASSWORD') ) $password = DB_PASSWORD;
286
+
287
+ $server_address = explode( ':', $host, 2 );
288
+ $host = $server_address[0];
289
+ $port = is_null( $port )
290
+ ? ( isset( $server_address[1] ) ? $server_address[1] : null )
291
+ : $port;
292
+ $port = $port ? intval( (string)$port ) : null;
293
+
294
+ if ( function_exists( 'mysqli_get_server_info' ) ){
295
+ $mysqli = new mysqli( $host, $username, $password, '', $port );
296
+ $result['extension'] = 'mysqli';
297
+ $result['server_info'] = $mysqli->server_info;
298
+ return $result;
299
+ }
300
+
301
+ if ( function_exists( 'mysql_connect' ) ) {
302
+ if (version_compare(PHP_VERSION, '7.0', '>=')) {
303
+ $result['warning'] = __(
304
+ 'The mysql extension is deprecated since PHP 5.5 and removed since PHP 7.0; Use mysqli instead',
305
+ 'wprss'
306
+ );
307
+ $result['extension'] = 'mysql';
308
+ $result['server_info'] = '';
309
+ return $result;
310
+ }
311
+
312
+ if ( $port ) $host = implode ( ':', array( $host, $port ) );
313
+
314
+ $mysql = mysql_connect( $host, $username, $password );
315
+ $result['extension'] = 'mysql';
316
+ $result['server_info'] = mysql_get_server_info( $mysql );
317
+ return $result;
318
+ }
319
+
320
+ return null;
321
+ }
includes/update.php CHANGED
@@ -285,7 +285,7 @@
285
  'expiration_notice_period' => '2 weeks',
286
 
287
  // From 4.8.2
288
- 'feed_request_useragent' => 'WordPress',
289
 
290
  // From 4.11.2
291
  'limit_feed_items_per_import' => null,
285
  'expiration_notice_period' => '2 weeks',
286
 
287
  // From 4.8.2
288
+ 'feed_request_useragent' => 'Mozilla/5.0 (Linux 10.0; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
289
 
290
  // From 4.11.2
291
  'limit_feed_items_per_import' => null,
js/admin-addon-ajax.js CHANGED
File without changes
js/admin-custom-bulk-actions-feed-item.js DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Adds and manages custom bulk actions for the Feed Items page.
3
- *
4
- * @since 4.7
5
- */
6
- (function($, wprss_admin_bulk_feed_item){
7
-
8
- $(document).ready( function(){
9
- var bulk_actions_select = $( 'select#bulk-action-selector-top, select#bulk-action-selector-bottom' );
10
- var bulk_actions_edit = bulk_actions_select.find( "option[value='edit']" );
11
-
12
- $( '<option>' ).attr( 'value', 'trash' ).text( wprss_admin_bulk_feed_item.trash ).insertBefore( bulk_actions_edit );
13
- bulk_actions_edit.remove();
14
- });
15
-
16
- })(jQuery, wprss_admin_bulk_feed_item);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/admin-custom-bulk-actions.js CHANGED
File without changes
js/admin-custom.js CHANGED
File without changes
js/admin-help.js CHANGED
File without changes
js/admin-license-manager.js CHANGED
File without changes
js/admin-licensing.js CHANGED
File without changes
js/custom.js CHANGED
File without changes
js/editor.js CHANGED
File without changes
js/heartbeat.js CHANGED
File without changes
js/jquery-ui-timepicker-addon.js CHANGED
File without changes
js/jquery.colorbox-min.js CHANGED
File without changes
js/pointers.js CHANGED
File without changes
js/src/components/BottomPanel.vue ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="wpra-bottom-panel">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+ export default {}
9
+ </script>
js/src/components/Button.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ props: {
3
+ loading: {
4
+ type: Boolean,
5
+ default: false,
6
+ }
7
+ },
8
+ render () {
9
+ return <button
10
+ disabled={this.loading}
11
+ class={{'button': true, 'loading-button': this.loading}}
12
+ >{ this.$slots.default }</button>
13
+ }
14
+ }
js/src/components/Expander.vue ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="wpra-expander" :class="{'wpra-expander--expanded':isExpanded}">
3
+ <div class="wpra-expander__title" @click="isExpanded = !isExpanded">
4
+ {{ title }}
5
+ <span class="dashicons dashicons-arrow-down-alt2"></span>
6
+ </div>
7
+ <transition-expand>
8
+ <div v-if="isExpanded">
9
+ <div class="wpra-expander__content">
10
+ <slot></slot>
11
+ </div>
12
+ </div>
13
+ </transition-expand>
14
+ </div>
15
+ </template>
16
+
17
+ <script>
18
+ import TransitionExpand from './TransitionExpand'
19
+
20
+ export default {
21
+ data () {
22
+ return {
23
+ isExpanded: this.defaultExpanded
24
+ }
25
+ },
26
+ props: {
27
+ title: {},
28
+ defaultExpanded: {
29
+ value: false
30
+ }
31
+ },
32
+ components: {
33
+ TransitionExpand
34
+ }
35
+ }
36
+ </script>
js/src/components/Input.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import MediaInput from './MediaInput'
2
+
3
+ export default {
4
+ mixins: [
5
+ MediaInput
6
+ ],
7
+ props: {
8
+ id: {
9
+ type: String,
10
+ default () {
11
+ return Math.random().toString(36).substr(0, 12)
12
+ }
13
+ },
14
+ label: {},
15
+ description: {},
16
+ after: {},
17
+ type: {},
18
+ value: {},
19
+ placeholder: {},
20
+ title: {},
21
+ inputDisabled: {},
22
+ options: {
23
+ default () {
24
+ return {}
25
+ }
26
+ },
27
+ },
28
+ methods: {
29
+ inputNode () {
30
+ if (this.type === 'media') {
31
+ return this.mediaNode()
32
+ }
33
+
34
+ if (this.type === 'checkbox') {
35
+ return <input type="checkbox"
36
+ id={this.id}
37
+ checked={!!this.value}
38
+ onChange={() => this.$emit('input', !this.value)}
39
+ placeholder={this.placeholder}
40
+ disabled={this.$attrs.disabled || this.inputDisabled}
41
+ {...{attrs: this.$attrs}}
42
+ />
43
+ }
44
+
45
+ if (this.type !== 'select') {
46
+ return <input type={this.type}
47
+ value={this.value}
48
+ id={this.id}
49
+ onInput={(e) => this.$emit('input', e.target.value)}
50
+ placeholder={this.placeholder}
51
+ disabled={this.$attrs.disabled || this.inputDisabled}
52
+ {...{attrs: this.$attrs}}
53
+ />
54
+ }
55
+ return this.selectNode()
56
+ },
57
+
58
+ selectNode () {
59
+ let options = Object.keys(this.options)
60
+ .map(key => <option value={key} selected={ this.value === key }>{ this.options[key] }</option>)
61
+
62
+ return <select
63
+ {...{attrs: this.$attrs}}
64
+ id={this.id}
65
+ disabled={this.$attrs.disabled || this.inputDisabled}
66
+ onChange={(e) => this.$emit('input', e.target.value)}
67
+ >{ options }</select>
68
+ },
69
+ },
70
+ render () {
71
+ let directives = []
72
+
73
+ if (this.title) {
74
+ directives.push({
75
+ name: 'tippy',
76
+ })
77
+ }
78
+
79
+ return <div class={{'form-input': true, 'form-input--disabled': this.$attrs.disabled || false}}>
80
+ { this.label ? (
81
+ <label class="form-input__label" for={this.id}>
82
+ <div>
83
+ {this.label}
84
+ {
85
+ this.title ? (
86
+ <div class="form-input__tip" {...{directives}} title={this.title}>
87
+ <span class="dashicons dashicons-editor-help"/>
88
+ </div>
89
+ ) : null
90
+ }
91
+ </div>
92
+ {this.description ? <div class="form-input__label-description" {...{domProps: {innerHTML: this.description}}}/> : ''}
93
+ </label>
94
+ ) : null }
95
+ <div class="form-input__field">
96
+ { this.inputNode() }{ this.after }
97
+ </div>
98
+ </div>
99
+ }
100
+ }
js/src/components/Layout.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ render () {
3
+ return (
4
+ <div id="post-body">
5
+ {
6
+ this.$slots.default
7
+ }
8
+ </div>
9
+ )
10
+ }
11
+ }
js/src/components/Main.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ render () {
3
+ return (
4
+ <div id="postbox-container-2" class="postbox-container">
5
+ {
6
+ this.$slots.default
7
+ }
8
+ </div>
9
+ )
10
+ }
11
+ }
js/src/components/MediaInput.js ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let imageFrame = null
2
+
3
+ /**
4
+ * @see https://core.trac.wordpress.org/browser/tags/5.1.1/src/js/_enqueues/wp/media?order=name
5
+ *
6
+ * @var {{media: Function}} wp
7
+ *
8
+ * @property {{}} value
9
+ */
10
+ export default {
11
+ props: {
12
+ mediaType: {
13
+ type: String,
14
+ default: 'image'
15
+ },
16
+ mediaTitle: {
17
+ type: String,
18
+ default: 'Select Media'
19
+ },
20
+ /**
21
+ * Property to use as a value for the media. Can be `id` or `url`.
22
+ *
23
+ * @property {string} mediaValueProperty
24
+ */
25
+ mediaValueProperty: {
26
+ type: String,
27
+ default: 'id',
28
+ },
29
+ },
30
+ methods: {
31
+ mediaNode () {
32
+ this.assertMediaLoaded()
33
+
34
+ return <div>
35
+ <input type="text" value={this.value}/>
36
+ <button class="button" onClick={this.openFrame}>Choose image</button>
37
+ </div>
38
+ },
39
+
40
+ openFrame () {
41
+ if (!imageFrame) {
42
+ imageFrame = this.createFrame()
43
+ }
44
+ imageFrame.open()
45
+ },
46
+
47
+ createFrame () {
48
+ imageFrame = wp.media({
49
+ title: this.mediaTitle,
50
+ multiple: false,
51
+ library: {
52
+ type: this.mediaType,
53
+ }
54
+ })
55
+
56
+ imageFrame.on('close', () => {
57
+ const selection = imageFrame.state().get('selection')
58
+ let selectedAttachment = null
59
+ selection.each((attachment) => {
60
+ console.info({attachment})
61
+ selectedAttachment = attachment
62
+ })
63
+ if (!selectedAttachment || !selectedAttachment.id) {
64
+ return
65
+ }
66
+ this.$emit('input', ({
67
+ id: selectedAttachment.id,
68
+ url: selectedAttachment.attributes.url
69
+ })[this.mediaValueProperty])
70
+ })
71
+
72
+ imageFrame.on('open', () => {
73
+ const selection = imageFrame.state().get('selection')
74
+
75
+ if (this.mediaValueProperty === 'id' && this.value) {
76
+ const attachment = wp.media.attachment(this.value)
77
+ attachment.fetch()
78
+ selection.add(attachment ? [attachment] : [])
79
+ }
80
+ })
81
+
82
+ return imageFrame
83
+ },
84
+
85
+ /**
86
+ * Check whether wp.media is loaded. If not - throw an error.
87
+ *
88
+ * @throws Error When wp.media is not loaded.
89
+ */
90
+ assertMediaLoaded () {
91
+ if (!window.wp.media) {
92
+ throw Error('[MediaInput] wp.media dependency is not loaded')
93
+ }
94
+ }
95
+ },
96
+ }
js/src/components/Modal.vue ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <transition name="modal-transition">
3
+ <div class="modal" v-if="active" @click.self="$emit('close')">
4
+ <div :class="['modal__body', this.modalBodyClass]">
5
+ <div class="modal__header" :class="headerClass">
6
+ <slot name="header"></slot>
7
+ </div>
8
+ <div class="modal__content">
9
+ <slot name="body"></slot>
10
+ </div>
11
+ <div class="modal__footer">
12
+ <slot name="footer"></slot>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </transition>
17
+ </template>
18
+
19
+ <script>
20
+ /**
21
+ * Abstract dialog component, solid foundation for
22
+ * any modals and dialogs that opened over the rest page content.
23
+ *
24
+ * @param Vue
25
+ */
26
+ export default {
27
+ props: {
28
+ /**
29
+ * Determines dialog visibility. This property is passed
30
+ * from outside and cannot be changed inside dialog.
31
+ * Dialog's consumer is responsible for manipulating dialog's visibility.
32
+ *
33
+ * @property {bool}
34
+ */
35
+ active: {
36
+ type: Boolean
37
+ },
38
+
39
+ /**
40
+ * Modal title
41
+ *
42
+ * @property {string}
43
+ */
44
+ title: {
45
+ type: String
46
+ },
47
+
48
+ /**
49
+ * Additional class modifier for modal customization.
50
+ *
51
+ * @property {string}
52
+ */
53
+ modalBodyClass: {
54
+ type: String,
55
+ default: ''
56
+ },
57
+
58
+ /**
59
+ * Additional classes for modal header.
60
+ *
61
+ * @property {object|Array}
62
+ */
63
+ headerClass: {
64
+ default () {
65
+ return {}
66
+ }
67
+ },
68
+
69
+ /**
70
+ * Class that applies to the body and used
71
+ * to prevent body's scroll catch, so long dialog can be scrolled
72
+ * without interfering with body scroll.
73
+ *
74
+ * @property {string}
75
+ */
76
+ dialogOpenedClass: {
77
+ type: String,
78
+ default: 'modal-opened'
79
+ }
80
+ },
81
+
82
+ watch: {
83
+ /**
84
+ * Watch for "active" property change and emit corresponding
85
+ * event when it changed.
86
+ *
87
+ * @param isDialogActive {bool}
88
+ */
89
+ active (isDialogActive) {
90
+ this.$emit(isDialogActive ? 'open' : 'close')
91
+ }
92
+ },
93
+
94
+ mounted () {
95
+ /*
96
+ * Add body "frozen" class to the body when dialog is opened.
97
+ */
98
+ this.$on('open', () => {
99
+ document.querySelector('body')
100
+ .classList
101
+ .add(this.dialogOpenedClass);
102
+ });
103
+
104
+ /*
105
+ * Remove body "frozen" class from the body when dialog is closed.
106
+ */
107
+ this.$on('close', () => {
108
+ document.querySelector('body')
109
+ .classList
110
+ .remove(this.dialogOpenedClass);
111
+ });
112
+ },
113
+ }
114
+ </script>
js/src/components/NoticeBlock.js ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from './Button'
2
+
3
+ /**
4
+ * Big notice block that is used to provide some information for users.
5
+ * Also can display "learn more" button to allow users to read more about
6
+ * the announce.
7
+ */
8
+ export default {
9
+ data () {
10
+ return {
11
+ shouldBeVisible: true,
12
+ }
13
+ },
14
+
15
+ props: {
16
+ /**
17
+ * Unique identifier of the notice block.
18
+ *
19
+ * @property {string}
20
+ */
21
+ id: {
22
+ type: String,
23
+ required: true,
24
+ },
25
+
26
+ /**
27
+ * Visible title on top of the block.
28
+ *
29
+ * @property {string}
30
+ */
31
+ title: {
32
+ type: String,
33
+ },
34
+
35
+ /**
36
+ * The notice's block body. Can be HTML with text formatting, links and so on.
37
+ *
38
+ * @property {string}
39
+ */
40
+ body: {
41
+ type: String,
42
+ },
43
+
44
+ /**
45
+ * The link to a "learn more" article. Will be opened in a new tab.
46
+ * If value is empty, "learn more" button won't be rendered.
47
+ *
48
+ * @property {string|boolean}
49
+ */
50
+ learnMore: {
51
+ default: false,
52
+ },
53
+
54
+ /**
55
+ * Text on the button that will hide the message.
56
+ *
57
+ * @property {string}
58
+ */
59
+ okayText: {
60
+ type: String,
61
+ default: 'Got it'
62
+ },
63
+
64
+ /**
65
+ * Text on the button that will open a "learn more" article.
66
+ *
67
+ * @property {string}
68
+ */
69
+ learnMoreText: {
70
+ type: String,
71
+ default: 'Learn more'
72
+ },
73
+
74
+ /**
75
+ * Whether the notice block is visible in UI.
76
+ *
77
+ * @property {boolean}
78
+ */
79
+ visible: {
80
+ type: Boolean,
81
+ default: true,
82
+ },
83
+ },
84
+
85
+ computed: {
86
+ isVisible () {
87
+ return this.visible
88
+ && this.shouldBeVisible
89
+ && JSON.parse(localStorage.getItem(this.getBlockKey()) || 'true')
90
+ }
91
+ },
92
+
93
+ methods: {
94
+ /**
95
+ * Hide the block.
96
+ */
97
+ onOkayClick () {
98
+ this.shouldBeVisible = false
99
+ localStorage.setItem(this.getBlockKey(), JSON.stringify(false))
100
+ },
101
+
102
+ /**
103
+ * Open learn more link in a new tab when user clicks on the "learn more" button.
104
+ */
105
+ onLearnMoreClick () {
106
+ window.open(this.learnMore, '_blank').focus()
107
+ },
108
+
109
+ /**
110
+ * Notice's block key in local storage.
111
+ *
112
+ * @return {string}
113
+ */
114
+ getBlockKey () {
115
+ return `wpra-${this.id}-visible`
116
+ }
117
+ },
118
+
119
+ render () {
120
+ /*
121
+ * Template is not rendered when it was hidden by user, or server.
122
+ */
123
+ if (!this.isVisible) {
124
+ return null
125
+ }
126
+
127
+ let learnMoreButton = this.learnMore ?
128
+ <Button class="button-clear" nativeOnClick={this.onLearnMoreClick}>
129
+ {this.learnMoreText} <span class="dashicons dashicons-external"/>
130
+ </Button> : null
131
+
132
+ return (
133
+ <div class="wpra-notice-block">
134
+ <div class="wpra-notice-block__title">{this.title}</div>
135
+ <div class="wpra-notice-block__body" {...{domProps: {innerHTML: this.body}}}/>
136
+ <div class="wpra-notice-block__buttons">
137
+ <Button class="brand button-primary" nativeOnClick={this.onOkayClick}>{this.okayText}</Button>
138
+ {learnMoreButton}
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+ }
js/src/components/Postbox.vue ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ export default {
3
+ inject: ['hooks'],
4
+ data () {
5
+ return {
6
+ expanded: true,
7
+ }
8
+ },
9
+ props: {
10
+ title: {},
11
+ id: {},
12
+ submit: {
13
+ type: Boolean,
14
+ default: false,
15
+ },
16
+ context: {},
17
+ },
18
+ methods: {
19
+ toggle () {
20
+ this.expanded = !this.expanded
21
+ }
22
+ },
23
+ render (h) {
24
+ return this.hooks.apply('postbox-' + this.id, this.context || this, (
25
+ <div class="postbox wpra-postbox" id={ this.submit ? 'submitdiv' : ''}>
26
+ <button type="button" class="handlediv" aria-expanded="true" onClick={this.toggle}>
27
+ <span class="screen-reader-text">Toggle panel: { this.title }</span>
28
+ <span class="toggle-indicator" aria-hidden="true"></span>
29
+ </button>
30
+ <h2 class="hndle ui-sortable-handle"
31
+ onClick={this.toggle}
32
+ ><span>{ this.title }</span></h2>
33
+ <div class="inside">
34
+ {
35
+ this.hooks.apply('postbox-content-' + this.id, this.context || this, [
36
+ this.$slots.default
37
+ ], {h})
38
+ }
39
+ </div>
40
+ </div>
41
+ ), {h})
42
+ }
43
+ }
44
+ </script>
js/src/components/RouteLink.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ props: {
3
+ path: {},
4
+ gate: {}
5
+ },
6
+ inject: [
7
+ 'router'
8
+ ],
9
+ methods: {
10
+ getPath () {
11
+ return this.router.buildRoute(this.path)
12
+ },
13
+ navigate (e) {
14
+ const allowed = !this.gate || this.gate()
15
+
16
+ e.preventDefault()
17
+
18
+ if (allowed) {
19
+ this.router.navigate(this.path)
20
+ }
21
+ }
22
+ },
23
+ render () {
24
+ const path = this.getPath()
25
+ return <a href={path} onClick={this.navigate}>{ this.$slots.default }</a>
26
+ }
27
+ }
js/src/components/RouterApp.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function ({ store, router }) {
2
+ return {
3
+ store,
4
+ data () {
5
+ return {
6
+ afterNavigate: () => {},
7
+ params: {},
8
+ currentRoute: null,
9
+ }
10
+ },
11
+ created () {
12
+ router.setApp(this)
13
+ this.currentRoute = router.parseLocation(window.location)
14
+ this.navigated()
15
+ },
16
+ mounted () {
17
+ window.addEventListener('popstate', () => {
18
+ this.currentRoute = router.parseLocation(window.location)
19
+ this.navigated()
20
+ })
21
+ },
22
+ methods: {
23
+ ViewComponent () {
24
+ const matchingView = router.findRoute(this.currentRoute)
25
+ return matchingView.component
26
+ },
27
+ navigated () {
28
+ this.$nextTick(() => {
29
+ const main = this.$refs.main
30
+ if (!main || !main.navigated) {
31
+ return
32
+ }
33
+ main.navigated({
34
+ route: router.findRoute(this.currentRoute),
35
+ })
36
+ })
37
+ }
38
+ },
39
+ render (h) {
40
+ const content = h(this.ViewComponent(), {
41
+ ref: 'main'
42
+ })
43
+ this.afterNavigate()
44
+ return content
45
+ }
46
+ }
47
+ }
js/src/components/SerializedForm.vue ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="form">
3
+ <div class="form-group"
4
+ v-for="datum of form"
5
+ v-if="satisfiesCondition(datum)"
6
+ >
7
+ <template v-if="datum.type === 'radio'">
8
+ <label :for="datum.name" v-html="datum.label" v-if="datum.label"></label>
9
+ <div class="form-check" v-for="(radio, $i) of datum.options">
10
+ <input type="radio"
11
+ :name="datum.name"
12
+ :id="datum.name + '_' + $i"
13
+ :value="radio.value"
14
+ v-model="model[datum.name]"
15
+ >
16
+ <label :for="datum.name + '_' + $i">
17
+ {{ radio.label || radio.value }}
18
+ </label>
19
+ </div>
20
+ </template>
21
+
22
+ <template v-if="datum.type === 'textarea'">
23
+ <label :for="datum.name" v-html="datum.label" v-if="datum.label"></label>
24
+ <textarea v-model="model[datum.name]" :id="datum.name"></textarea>
25
+ </template>
26
+
27
+ <template v-if="datum.type === 'content'">
28
+ <div :class="datum.className">
29
+ <p v-html="datum.label"></p>
30
+ </div>
31
+ </template>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script>
37
+ export default {
38
+ props: {
39
+ /*
40
+ * Form, described by object containing information
41
+ * about each field.
42
+ */
43
+ form: {
44
+ type: Array,
45
+ },
46
+
47
+ /*
48
+ * Form model.
49
+ */
50
+ value: {
51
+ type: Object,
52
+ }
53
+ },
54
+ computed: {
55
+ model: {
56
+ get () {
57
+ return this.value
58
+ },
59
+ set (value) {
60
+ this.$emit('input', value)
61
+ }
62
+ }
63
+ },
64
+ methods: {
65
+ satisfiesCondition (datum) {
66
+ if (!datum.condition) {
67
+ return true
68
+ }
69
+ let compareFunction = this.getConditionFunction(datum.condition.operator);
70
+ if (!compareFunction) {
71
+ return false
72
+ }
73
+ return compareFunction(datum.condition.field, datum.condition.value)
74
+ },
75
+
76
+ getConditionFunction (operator) {
77
+ const fns = {
78
+ '=': (field, value) => {
79
+ return this.model[field] === value
80
+ }
81
+ }
82
+ if (!fns[operator]) {
83
+ return null
84
+ }
85
+ return fns[operator]
86
+ }
87
+ }
88
+ }
89
+ </script>
js/src/components/Sidebar.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ render () {
3
+ return (
4
+ <div id="postbox-container-1" class="wpra-postbox-container postbox-container">
5
+ {
6
+ this.$slots.default
7
+ }
8
+ </div>
9
+ )
10
+ }
11
+ }
js/src/components/TransitionExpand.vue ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ export default {
3
+ name: `TransitionExpand`,
4
+ functional: true,
5
+ render (createElement, context) {
6
+ const data = {
7
+ props: {
8
+ name: `expand`,
9
+ },
10
+ on: {
11
+ afterEnter (element) {
12
+ // eslint-disable-next-line no-param-reassign
13
+ element.style.height = `auto`
14
+ },
15
+ enter (element) {
16
+ const {width} = getComputedStyle(element)
17
+
18
+ /* eslint-disable no-param-reassign */
19
+ element.style.width = width
20
+ element.style.position = `absolute`
21
+ element.style.visibility = `hidden`
22
+ element.style.height = `auto`
23
+ /* eslint-enable */
24
+
25
+ const {height} = getComputedStyle(element)
26
+
27
+ /* eslint-disable no-param-reassign */
28
+ element.style.width = null
29
+ element.style.position = null
30
+ element.style.visibility = null
31
+ element.style.height = 0
32
+ /* eslint-enable */
33
+
34
+ // Force repaint to make sure the
35
+ // animation is triggered correctly.
36
+ // eslint-disable-next-line no-unused-expressions
37
+ getComputedStyle(element).height
38
+
39
+ setTimeout(() => {
40
+ // eslint-disable-next-line no-param-reassign
41
+ element.style.height = height
42
+ })
43
+ },
44
+ leave (element) {
45
+ const {height} = getComputedStyle(element)
46
+
47
+ // eslint-disable-next-line no-param-reassign
48
+ element.style.height = height
49
+
50
+ // Force repaint to make sure the
51
+ // animation is triggered correctly.
52
+ // eslint-disable-next-line no-unused-expressions
53
+ getComputedStyle(element).height
54
+
55
+ setTimeout(() => {
56
+ // eslint-disable-next-line no-param-reassign
57
+ element.style.height = 0
58
+ })
59
+ },
60
+ },
61
+ }
62
+
63
+ return createElement(`transition`, data, context.children)
64
+ },
65
+ }
66
+ </script>
js/src/components/index.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Input from './Input'
2
+ import NoticeBlock from './NoticeBlock'
3
+ import Postbox from './Postbox'
4
+ import RouteLink from './RouteLink'
5
+ import TransitionExpand from './TransitionExpand'
6
+ import Button from './Button'
7
+
8
+ export default {
9
+ Input,
10
+ NoticeBlock,
11
+ Postbox,
12
+ RouteLink,
13
+ TransitionExpand,
14
+ Button,
15
+ }
js/src/libs/NotificationCenter.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Library agnostic wrapper for notifications.
3
+ *
4
+ * @since 4.14
5
+ *
6
+ * @class NotificationsCenter
7
+ */
8
+ export default class NotificationsCenter {
9
+ /**
10
+ * NotificationsCenter constructor.
11
+ *
12
+ * @since 4.14
13
+ *
14
+ * @param {Function} show Function implementation for displaying messages.
15
+ * @param {Function} error Function implementation for displaying errors.
16
+ */
17
+ constructor (show, error) {
18
+ this.showMethod = show
19
+ this.errorMethod = error
20
+ }
21
+
22
+ /**
23
+ * Display informational message.
24
+ *
25
+ * @since 4.14
26
+ *
27
+ * @param {string} msg Message for displaying
28
+ * @param {object} options Options for notification.
29
+ */
30
+ show (msg, options = {}) {
31
+ this.showMethod(msg, options)
32
+ }
33
+
34
+ /**
35
+ * Display error message.
36
+ *
37
+ * @since 4.14
38
+ *
39
+ * @param {string} msg Message for displaying
40
+ * @param {object} options Options for notification.
41
+ */
42
+ error (msg, options = {}) {
43
+ this.errorMethod(msg, options)
44
+ }
45
+ }
js/src/libs/Router.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const getJsonFromUrl = (url) => {
2
+ if (!url) url = location.href
3
+ var question = url.indexOf('?')
4
+ var hash = url.indexOf('#')
5
+ if (hash == -1 && question == -1) return {}
6
+ if (hash == -1) hash = url.length
7
+ var query = question == -1 || hash == question + 1 ? url.substring(hash) :
8
+ url.substring(question + 1, hash)
9
+ var result = {}
10
+ query.split('&').forEach(function (part) {
11
+ if (!part) return
12
+ part = part.split('+').join(' ') // replace every + with space, regexp-free version
13
+ var eq = part.indexOf('=')
14
+ var key = eq > -1 ? part.substr(0, eq) : part
15
+ var val = eq > -1 ? decodeURIComponent(part.substr(eq + 1)) : ''
16
+ var from = key.indexOf('[')
17
+ if (from == -1) result[decodeURIComponent(key)] = val
18
+ else {
19
+ var to = key.indexOf(']', from)
20
+ var index = decodeURIComponent(key.substring(from + 1, to))
21
+ key = decodeURIComponent(key.substring(0, from))
22
+ if (!result[key]) result[key] = []
23
+ if (!index) result[key].push(val)
24
+ else result[key][index] = val
25
+ }
26
+ })
27
+ return result
28
+ }
29
+
30
+ export default class Router {
31
+ constructor (routes, options) {
32
+ this.routes = routes
33
+ this.options = options
34
+ this.baseParams = options.baseParams || ['post_type', 'page', 'action', 'id']
35
+ }
36
+
37
+ get params () {
38
+ return this.app ? this.app.params : {}
39
+ }
40
+
41
+ setApp (app) {
42
+ this.app = app
43
+ this.app.afterNavigate = this.options.afterNavigating || (() => {})
44
+ }
45
+
46
+ findRoute (location) {
47
+ return this.routes.find(({route}) => {
48
+ return location.indexOf(route) !== -1
49
+ })
50
+ }
51
+
52
+ updateParams (params) {
53
+ this.app.$set(this.app, 'params', params)
54
+ }
55
+
56
+ mergeParams (paramsPart) {
57
+ let currentParams = Object.keys(this.params).filter(key => {
58
+ return this.baseParams.indexOf(key) !== -1 || paramsPart.hasOwnProperty(key)
59
+ }).reduce((acc, key) => {
60
+ acc[key] = this.params[key]
61
+ return acc
62
+ }, {})
63
+
64
+ let params = Object.assign({}, currentParams, paramsPart)
65
+
66
+ this.updateParams(params)
67
+
68
+ window.history.pushState(
69
+ null,
70
+ null,
71
+ this.routeFromParams()
72
+ )
73
+
74
+ this.app.navigated()
75
+ }
76
+
77
+ routeFromParams () {
78
+ const hasParams = !!Object.keys(this.params).length
79
+ return location.pathname + (hasParams ? '?' + this.buildParams(this.params) : '')
80
+ }
81
+
82
+ buildRoute (route) {
83
+ if (route.name) {
84
+ let routeObject = this.routes.find(r => r.name === route.name)
85
+ if (!routeObject) {
86
+ return null
87
+ }
88
+ const routeStr = routeObject.route
89
+ const join = routeStr.indexOf('?') !== -1 ? '&' : '?'
90
+
91
+ return routeStr + (route.params ? join + this.buildParams(route.params ? route.params : {}) : '')
92
+ }
93
+ }
94
+
95
+ buildParams (params) {
96
+ return Object.keys(params).map(param => {
97
+ return `${param}=${params[param]}`
98
+ }).join('&')
99
+ }
100
+
101
+ parseLocation (location) {
102
+ this.updateParams(getJsonFromUrl(location.search))
103
+ console.info('ROUTE PARSE LOCATION PARAMS', getJsonFromUrl(location.search))
104
+ return location.pathname + location.search
105
+ }
106
+
107
+ navigate (route) {
108
+ if (this.app) {
109
+ this.app.currentRoute = this.buildRoute(route)
110
+ }
111
+
112
+ this.updateParams(Object.assign({}, route.params || {}, getJsonFromUrl(this.buildRoute(route))))
113
+
114
+ window.history.pushState(
115
+ null,
116
+ null,
117
+ this.buildRoute(route)
118
+ )
119
+
120
+ this.app.navigated()
121
+ }
122
+ }
js/src/mixins/DataChangesAware.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import jsonClone from 'app/utils/jsonClone'
2
+ import equal from 'fast-deep-equal'
3
+
4
+ /**
5
+ * Mixing for functionality that allows to track down and revert changes made on
6
+ * the primary editing data model.
7
+ *
8
+ * `model` property on base object is required for using this mixin.
9
+ */
10
+ export default {
11
+ data () {
12
+ return {
13
+ changes: {
14
+ model: {},
15
+ }
16
+ }
17
+ },
18
+ methods: {
19
+ /**
20
+ * Whether the model has been changed after last model "remembering".
21
+ *
22
+ * @return {boolean}
23
+ */
24
+ isChanged () {
25
+ return !equal(this.model, this.changes.model)
26
+ },
27
+
28
+ /**
29
+ * Remember current data model, but without any references to main model
30
+ * properties. It is important to clean any additional observers from object
31
+ * otherwise "memorized" model clone will be changed when model get changed.
32
+ */
33
+ rememberModel () {
34
+ this.$set(this.changes, 'model', jsonClone(this.model))
35
+ },
36
+
37
+ /**
38
+ * Cancel any changes on main model object.
39
+ */
40
+ cancelChanges () {
41
+ if (!confirm('Are you sure you want to cancel your changes for this template? This action cannot be reverted and all changes made since your last save will be lost.')) {
42
+ return
43
+ }
44
+ this.$set(this, 'model', jsonClone(this.changes.model))
45
+ },
46
+ }
47
+ }
js/src/modules/gutenberg-block/components/MultipleSelectControl.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component } from '@wordpress/element'
2
+ import {
3
+ FormTokenField,
4
+ Placeholder,
5
+ Spinner,
6
+ BaseControl,
7
+ } from '@wordpress/components'
8
+
9
+ export default class MultipleSelectControl extends Component {
10
+ constructor (props) {
11
+ super(...arguments)
12
+ this.props = props
13
+ this.state = {
14
+ tokens: [],
15
+ loading: false,
16
+ items: []
17
+ }
18
+ }
19
+
20
+ componentDidMount () {
21
+ this.setState({loading: true})
22
+
23
+ jQuery.post(WPRA_BLOCK.ajax_url, {
24
+ action: 'wprss_fetch_items',
25
+ }, (data) => {
26
+ data = JSON.parse(data)
27
+ this.setState({
28
+ loading: false,
29
+ items: data.items
30
+ })
31
+ })
32
+ }
33
+
34
+ render () {
35
+ const setState = this.setState.bind(this)
36
+ const onChange = this.props.onChange
37
+ const items = this.state.items
38
+
39
+ if (this.state.loading) {
40
+ return <Placeholder>
41
+ <Spinner/>
42
+ </Placeholder>
43
+ }
44
+ return <BaseControl
45
+ help={this.props.help || ''}
46
+ >
47
+ <FormTokenField
48
+ label={this.props.label || ''}
49
+ placeholder={this.props.placeholder || ''}
50
+ value={this.props.value.map(function (id) {
51
+ return items.find(item => item.value === id)
52
+ }).filter(item => !!item)}
53
+ suggestions={this.state.items.map(function (item) {
54
+ item.toLocaleLowerCase = function () {
55
+ return item.title.toLocaleLowerCase()
56
+ }
57
+ item.toString = function () {
58
+ return item.title
59
+ }
60
+ return item
61
+ })}
62
+ displayTransform={function (item) {
63
+ if ('number' === typeof item) {
64
+ item = items.find(function (iteratedItem) {
65
+ return iteratedItem.value === item
66
+ })
67
+ }
68
+ if ('object' === typeof item) {
69
+ return item.title
70
+ }
71
+ return item
72
+ }}
73
+ saveTransform={function (token) {
74
+ return token
75
+ }}
76
+ onChange={function (tokens) {
77
+ setState({tokens})
78
+ onChange(tokens.map(function (item) {
79
+ return item.value
80
+ }))
81
+ }}
82
+ />
83
+ </BaseControl>
84
+ }
85
+ }
js/src/modules/gutenberg-block/index.js ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('css/src/gutenberg-block/index.scss')
2
+
3
+ import { __ } from '@wordpress/i18n'
4
+ import { registerBlockType } from '@wordpress/blocks'
5
+ import { InspectorControls } from '@wordpress/editor'
6
+ import {
7
+ ToggleControl,
8
+ ServerSideRender,
9
+ TextControl,
10
+ TextareaControl,
11
+ BaseControl,
12
+ PanelBody,
13
+ PanelRow,
14
+ Spinner,
15
+ Placeholder,
16
+ FormTokenField,
17
+ SelectControl,
18
+ } from '@wordpress/components'
19
+ import { addFilter, applyFilters } from '@wordpress/hooks'
20
+
21
+ import MultipleSelectControl from './components/MultipleSelectControl'
22
+
23
+ // Default template is selected by default.
24
+ let selectedTemplate = WPRA_BLOCK.templates[0]
25
+
26
+ // Selected template field getter. Additional function can be passed.
27
+ const getTemplateDefault = (field, wrapper = val => val, def = 0) => selectedTemplate[field] ? wrapper(selectedTemplate[field]) : def
28
+
29
+ // Helps to not override attributes that selected manually by user.
30
+ let templateLock = {}
31
+
32
+ // Whether the block is loaded initial information.
33
+ let _isLoaded = false
34
+
35
+ registerBlockType('wpra-shortcode/wpra-shortcode', {
36
+ title: __('WP RSS Aggregator Feeds'),
37
+ description: __('Display feed items imported using WP RSS Aggregator.'),
38
+ icon: 'rss',
39
+ category: 'widgets',
40
+
41
+ // Remove to make block editable in HTML mode.
42
+ supportHTML: false,
43
+
44
+ attributes: applyFilters('wpra.gutenbergBlock.attributes', {
45
+ isAll: {
46
+ type: 'boolean',
47
+ default: true
48
+ },
49
+ template: {
50
+ type: 'string',
51
+ default: 'default'
52
+ },
53
+ pagination: {
54
+ type: 'boolean',
55
+ default: true
56
+ },
57
+ limit: {
58
+ type: 'number',
59
+ },
60
+ page: {
61
+ type: 'number',
62
+ },
63
+ exclude: {
64
+ type: 'string'
65
+ },
66
+ source: {
67
+ type: 'string'
68
+ }
69
+ }),
70
+
71
+ /**
72
+ * Called when Gutenberg initially loads the block.
73
+ */
74
+ edit: function (props) {
75
+ /*
76
+ * If block is not loaded, check whether we should block auto limit selection.
77
+ * It will be blocked if user selected entered limit value different from template's default.
78
+ */
79
+ if (!_isLoaded && props.attributes.template) {
80
+ selectedTemplate = WPRA_BLOCK.templates.find(item => item.value === (props.attributes.template || 'default'))
81
+
82
+ if (parseInt(props.attributes.limit) !== getTemplateDefault('limit', parseInt)) {
83
+ templateLock['limit'] = true
84
+ }
85
+
86
+ if (!!props.attributes.pagination !== getTemplateDefault('pagination', v => !!v, false)) {
87
+ templateLock['pagination'] = true
88
+ }
89
+
90
+ _isLoaded = true
91
+ }
92
+
93
+ const etWarning = WPRA_BLOCK.is_et_active ? <p style={{fontStyle: 'italic'}}>
94
+ Excerpts & Thumbnails is incompatible with the WP RSS Aggregator Feeds block. <a
95
+ href="https://kb.wprssaggregator.com/article/459-using-excerpts-thumbnails-with-templates" target={'_blank'}>Learn
96
+ more</a>.
97
+ </p> : null
98
+
99
+ const panels = applyFilters('wpra.gutenbergBlock.panels', [
100
+ <PanelBody
101
+ title={__('Feed Sources')}
102
+ initialOpen={true}
103
+ >
104
+ {applyFilters('wpra.gutenbergBlock.panelItems', [
105
+ <ToggleControl
106
+ label={__('Show all Feed Sources ')}
107
+ checked={props.attributes.isAll}
108
+ onChange={(value) => {
109
+ props.setAttributes({isAll: value})
110
+ props.setAttributes({exclude: ''})
111
+ props.setAttributes({source: ''})
112
+ }}
113
+ />,
114
+ <MultipleSelectControl
115
+ label={props.attributes.isAll ? __('Feed Sources to Exclude') : __('Feed Sources to Show')}
116
+ key={'select'}
117
+ help={__('Start typing to search feed sources by name')}
118
+ value={((props.attributes.isAll ? props.attributes.exclude : props.attributes.source) || '').split(',').map(item => parseInt(item))}
119
+ onChange={(selected) => {
120
+ selected = selected.join(',')
121
+ if (props.attributes.isAll) {
122
+ props.setAttributes({exclude: selected})
123
+ props.setAttributes({source: ''})
124
+ return
125
+ }
126
+ props.setAttributes({exclude: ''})
127
+ props.setAttributes({source: selected})
128
+ }}
129
+ />
130
+ ], 'feedSources', {props})}
131
+ </PanelBody>,
132
+ <PanelBody
133
+ title={__('Display Options')}
134
+ initialOpen={false}
135
+ >
136
+ {applyFilters('wpra.gutenbergBlock.panelItems', [
137
+ <SelectControl
138
+ label={__('Select Template')}
139
+ value={props.attributes.template}
140
+ onChange={(template) => {
141
+ selectedTemplate = WPRA_BLOCK.templates.find(item => item.value === template)
142
+ props.setAttributes({template: template || ''})
143
+ if (!templateLock['limit']) {
144
+ props.setAttributes({limit: getTemplateDefault('limit', parseInt, 15)})
145
+ }
146
+ if (!templateLock['pagination']) {
147
+ props.setAttributes({pagination: getTemplateDefault('pagination', v => !!v, false)})
148
+ }
149
+ }}
150
+ options={WPRA_BLOCK.templates}
151
+ />,
152
+ <TextControl
153
+ label={__('Feed Limit')}
154
+ help={__('Number of feed items to display')}
155
+ placeholder={getTemplateDefault('limit', parseInt)}
156
+ type={'number'}
157
+ min={1}
158
+ value={props.attributes.limit || getTemplateDefault('limit', parseInt)}
159
+ onChange={(value) => {
160
+ templateLock['limit'] = true
161
+ props.setAttributes({limit: parseInt(value) || getTemplateDefault('limit', parseInt)})
162
+ }}
163
+ />,
164
+ <ToggleControl
165
+ label={__('Show Pagination ')}
166
+ checked={props.attributes.pagination}
167
+ onChange={(value) => {
168
+ templateLock['pagination'] = true
169
+ props.setAttributes({pagination: value})
170
+ }}
171
+ />,
172
+ <TextControl
173
+ label={__('Page')}
174
+ placeholder={__('1')}
175
+ type={'number'}
176
+ min={1}
177
+ value={props.attributes.page || 1}
178
+ onChange={(value) => {
179
+ props.setAttributes({page: parseInt(value) || 1})
180
+ }}
181
+ />,
182
+ etWarning
183
+ ], 'displayOptions', {props})}
184
+ </PanelBody>,
185
+ ], {props})
186
+
187
+ console.warn({panels})
188
+
189
+ return <div>
190
+ <ServerSideRender
191
+ block={'wpra-shortcode/wpra-shortcode'}
192
+ attributes={props.attributes}
193
+ className={'wpra-gutenberg-block'}
194
+ />
195
+ {applyFilters('wpra.gutenbergBlock.before', [], {props})}
196
+ <InspectorControls>
197
+ {panels}
198
+ </InspectorControls>
199
+ {applyFilters('wpra.gutenbergBlock.after', [], {props})}
200
+ </div>
201
+ },
202
+
203
+ /**
204
+ * Called when Gutenberg "saves" the block to post_content
205
+ */
206
+ save: function (props) {
207
+ return null
208
+ }
209
+ })
js/src/modules/intro/Wizard.vue ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="wizard-holder animated fadeIn">
3
+ <div class="connect-steps">
4
+ <div class="step-items">
5
+ <div class="step-progress" :class="'step-progress--' + activeScreenIndex"></div>
6
+ <div class="step-item"
7
+ :class="{ 'step-item_active': active(screen.id), 'step-item_completed' : screen.completed() && index < activeScreenIndex }"
8
+ v-for="(screen, index) of screens"
9
+ >
10
+ <div class="step-item__status">
11
+ <span class="dashicons dashicons-yes" v-if="screen.completed() && index < activeScreenIndex"></span>
12
+ </div>
13
+ <div class="step-item__info">
14
+ <div class="step-item__title">{{ screen.title }}</div>
15
+ <div class="step-item__description" v-if="screen.description">{{ screen.description }}</div>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ <div class="wizard">
21
+ <transition :name="transition" mode="out-in">
22
+ <div class="wizard_content" :key="activeScreen" v-if="active('feed')">
23
+ <div class="wizard_hello">
24
+ Enter your first RSS Feed URL
25
+ </div>
26
+
27
+ <form id="feedForm" @submit.prevent="next" class="wizard_info">
28
+ <div class="form-group">
29
+ <input type="text" placeholder="https://www.sourcedomain.com/feed/" v-model="form.feedSourceUrl"
30
+ class="wpra-feed-input"
31
+ >
32
+ <span class="dashicons dashicons-warning warning-icon" v-if="isFeedError"></span>
33
+ <a :href="validateLink" target="_blank" v-if="isFeedError">Validate feed</a>
34
+ </div>
35
+ </form>
36
+
37
+ <div class="wizard_error" v-if="isFeedError">
38
+ <p>This RSS feed URL appears to be invalid. Here are a couple of things you can try:</p>
39
+ <ol>
40
+ <li>Check whether the URL you entered is the correct one by trying one of the options when clicking on "How do I find an RSS feed URL?" below.</li>
41
+ <li>
42
+ Test out this other RSS feed URL to make sure the plugin is working correctly - https://www.wpmayor.com/feed/ - If it works, you may <a :href="supportUrl" target="_blank">contact us here</a> to help you with your source.
43
+ </li>
44
+ <li>Test the URL's validity by W3C standards, the standards we use in our plugins, using the “Validate feed” link above.</li>
45
+ </ol>
46
+ </div>
47
+
48
+ <expander title="How do I find an RSS feed URL?">
49
+ <p>WP RSS Aggregator fetches feed items through RSS feeds. Almost every website in the world provides an RSS feed. Here's how to find it:</p>
50
+ <p>Option 1: Add /feed to the website's homepage URL </p>
51
+ <p>Many sites have their RSS feed at the same URL. For instance, if the website's URL is www.thiswebsite.com, then the RSS feed could be at www.thiswebsite.com/feed.</p>
52
+ <p>Option 2: Look for the RSS share icon</p>
53
+ <p>Many websites have share icons on their pages for Facebook, Twitter and more. Many times, there will also be an orange RSS icon. Click on that to access the RSS feed URL.</p>
54
+ <p>Option 3: Browser RSS Auto-Discovery</p>
55
+ <p>Most browsers either include an RSS auto-discovery tool by default or they allow you to add extensions for it. Firefox shows an RSS icon above the website, in the address bar, which you can click on directly. Chrome offers extensions such as this one.</p>
56
+ <p>Option 4: Look at the Page Source</p>
57
+ <p>When on any page of the website you're looking to import feed items from, right click and press "View Page Source". Once the new window opens, use the “Find” feature (Ctrl-F on PC, Command-F on Mac) and search for " RSS". This should take you to a line that reads like this (or similar):</p>
58
+ <p>
59
+ <code>
60
+ &#x3C;link rel=&#x22;alternate&#x22; type=&#x22;application/rss+xml&#x22; title=&#x22;RSS Feed&#x22; href=&#x22;https://www.sourcedomain.com/feed/&#x22; /&#x3E;
61
+ </code>
62
+ </p>
63
+ <p>The RSS feed’s URL is found between the quotes after href=. In the above case, it would be https://www.sourcedomain.com/feed/.</p>
64
+ <p><a :href="knowledgeBaseUrl" target="_blank">Browse our Knowledge Base for more information.</a></p>
65
+ </expander>
66
+ </div>
67
+
68
+ <div class="wizard_content" :key="activeScreen" v-if="active('feedItems')">
69
+ <div class="wizard_hello">
70
+ Latest feed items from your selected feed source:
71
+ </div>
72
+
73
+ <div class="wpra-feed-items">
74
+ <div class="wpra-feed-item" v-for="item of feed.items">
75
+ <div class="wpra-feed-item__link">
76
+ <a :href="item.permalink" target="_blank">{{ item.title }}</a>
77
+ </div>
78
+ <div class="wpra-feed-item__info">
79
+ <template v-if="item.date || item.author">
80
+ <template v-if="item.date">
81
+ Published on {{ item.date }}
82
+ </template>
83
+ <template v-if="item.date && item.author">|</template>
84
+ <template v-if="item.author">
85
+ By {{ item.author }}
86
+ </template>
87
+ </template>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <div class="wrpa-shortcode">
93
+ <div class="wrpa-shortcode-preview">
94
+ <div class="wrpa-shortcode-label">
95
+ Create a draft page to preview these feed items on your site:
96
+ </div>
97
+ <a :href="previewUrl" target="_blank" class="button"
98
+ @click="preparePreview"
99
+ :class="{'button-primary': isPrepared, 'loading-button': isPreparing}"
100
+ >
101
+ {{ isPrepared ? 'Preview the Page' : 'Create Draft Page' }}
102
+ </a>
103
+ </div>
104
+ <div class="wrpa-shortcode-form" @click="copyToClipboard()">
105
+ <div class="wrpa-shortcode-label">
106
+ Copy the shortcode to any page or post on your site:
107
+ </div>
108
+ <input class="wrpa-shortcode-form__shortcode"
109
+ type="text"
110
+ readonly
111
+ value="[wp-rss-aggregator]"
112
+ ref="selected"
113
+ />
114
+ <div class="wrpa-shortcode-form__button">
115
+ {{ isCopied ? 'Copied!' : 'Click to copy' }}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="wizard_content" :key="activeScreen" v-if="active('finish')">
122
+ <div class="wizard_hello">
123
+ You’ve successfully set up your first feed source 😄
124
+ </div>
125
+
126
+ <div class="wpra-cols-title">
127
+ Do more with WP RSS Aggregator - here is what we did at CryptoHeadlines.com.
128
+ </div>
129
+
130
+ <div class="wpra-cols">
131
+ <div class="col">
132
+ <p>CryptoHeadlines.com displays latest news, Youtube videos, podcasts, jobs and more from the Cryptocurrency industry.</p>
133
+ <p>It uses Feed to Post to import articles, Youtube videos, and podcast links.</p>
134
+ <p>Full Text RSS Feeds is used to fetch the full content of the job listings to present more information to the potential applicant.</p>
135
+ <p>Keyword Filtering is used to filter out content that contains profanity and keywords or phrases deemed as inappropriate.</p>
136
+ <div style="margin-bottom: .5rem">
137
+ <a :href="addOnsUrl" class="button" target="_blank">
138
+ Browse Add-ons ⭐️
139
+ </a>
140
+ </div>
141
+ <div>
142
+ <a :href="supportUrl" target="_blank" style="font-size: .9em">Contact support for more information.</a>
143
+ </div>
144
+ </div>
145
+ <div class="col">
146
+ <img :src="demoImageUrl"
147
+ class="img wpra-demo-photo">
148
+
149
+ <div class="wpra-feedback">
150
+ <!--<div class="wpra-feedback__photo">-->
151
+ <!--<img src="https://www.wprssaggregator.com/wp-content/themes/wp_rss_theme/assets/images/review2.jpg">-->
152
+ <!--</div>-->
153
+ <div class="wpra-feedback__copy">
154
+ <div class="wpra-feedback__text">
155
+ This plugin has made my life a lot easier, and the support has been great as well.
156
+ </div>
157
+ <div class="wpra-feedback__rating">
158
+ <span class="dashicons dashicons-star-filled"></span>
159
+ <span class="dashicons dashicons-star-filled"></span>
160
+ <span class="dashicons dashicons-star-filled"></span>
161
+ <span class="dashicons dashicons-star-filled"></span>
162
+ <span class="dashicons dashicons-star-filled"></span>
163
+ </div>
164
+ <div class="wpra-feedback__by">
165
+ <a :href="feedbackUrl" target="_blank">
166
+ Review by officeinnovator
167
+ </a>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </transition>
175
+
176
+ <div class="connect-actions pad">
177
+ <div class="pad-item--grow">
178
+ <button v-if="!active('finish')"
179
+ class="button-clear"
180
+ @click="finish"
181
+ >
182
+ Skip the introduction
183
+ </button>
184
+ </div>
185
+ <div class="pad-item--no-shrink">
186
+ <button class="button-clear"
187
+ @click="back"
188
+ v-if="isBackAvailable"
189
+ >
190
+ Back
191
+ </button>
192
+ <button @click="next"
193
+ class="button button-primary button-large"
194
+ :class="{'loading-button': isLoading}"
195
+ >
196
+ {{ active('finish') ? 'Continue to Plugin' : 'Next' }}
197
+ </button>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </template>
203
+
204
+ <script>
205
+ import Expander from 'app/components/Expander'
206
+ import { post } from 'app/utils/fetch'
207
+ import { copyToClipboard } from 'app/utils/copy'
208
+
209
+ const _ = (str) => str
210
+
211
+ const CONFIG = window.wprssWizardConfig
212
+
213
+ export default {
214
+ data () {
215
+ return {
216
+ prevHeight: 0,
217
+ screens: [{
218
+ id: 'feed',
219
+ title: _('Add feed source URL'),
220
+ description: false,
221
+ next: this.submitFeed,
222
+ completed: () => {
223
+ return this.feed.items.length
224
+ },
225
+ entered: () => {
226
+ this.focusOnInput('feed')
227
+ }
228
+ }, {
229
+ id: 'feedItems',
230
+ title: _('Display feed items'),
231
+ description: false,
232
+ next: this.continueItems,
233
+ completed: () => {
234
+ return this.feed.items.length && this.itemsPassed
235
+ }
236
+ }, {
237
+ id: 'finish',
238
+ title: _('Complete introduction'),
239
+ description: false,
240
+ next: this.completeIntroduction,
241
+ completed: () => {
242
+ return this.feed.items.length && this.itemsPassed
243
+ }
244
+ }],
245
+ isCopied: false,
246
+
247
+ isPreparing: false,
248
+ isPrepared: false,
249
+
250
+ transition: 'slide-up', // 'slide-down',
251
+
252
+ activeScreen: 'feed',
253
+ form: {
254
+ feedSourceUrl: null,
255
+ },
256
+ itemsPassed: false,
257
+
258
+ stepLoading: false,
259
+ isLoading: false,
260
+
261
+ isFeedError: false,
262
+
263
+ feed: {
264
+ items: [],
265
+ },
266
+ previewUrl: CONFIG.previewUrl,
267
+ addOnsUrl: CONFIG.addOnsUrl,
268
+ supportUrl: CONFIG.supportUrl,
269
+ demoImageUrl: CONFIG.demoImageUrl,
270
+ feedbackUrl: CONFIG.feedbackUrl,
271
+ knowledgeBaseUrl: CONFIG.knowledgeBaseUrl,
272
+ }
273
+ },
274
+ computed: {
275
+ validateLink () {
276
+ return 'https://validator.w3.org/feed/check.cgi?url=' + encodeURI(this.form.feedSourceUrl)
277
+ },
278
+
279
+ activeScreenIndex () {
280
+ return this.screens.findIndex(screen => screen.id === this.activeScreen)
281
+ },
282
+ currentScreen () {
283
+ return this.screens.find(screen => screen.id === this.activeScreen)
284
+ },
285
+ actionCompleted () {
286
+ return this.currentScreen.completed()
287
+ },
288
+ isBackAvailable () {
289
+ return this.activeScreenIndex > 0 && this.activeScreenIndex < this.screens.length
290
+ },
291
+ },
292
+ mounted () {
293
+ this.onScreenEnter()
294
+ },
295
+ methods: {
296
+ preparePreview (e) {
297
+ if (this.isPreparing) {
298
+ e.preventDefault()
299
+ return
300
+ }
301
+ if (!this.isPrepared) {
302
+ e.preventDefault()
303
+ this.isPreparing = true
304
+ fetch(this.previewUrl).then(() => {
305
+ this.isPreparing = false
306
+ this.isPrepared = true
307
+ })
308
+ }
309
+ },
310
+
311
+ /**
312
+ * Submits first feed step.
313
+ *
314
+ * @return {Promise<any>}
315
+ */
316
+ submitFeed () {
317
+ const data = Object.assign(CONFIG.feedEndpoint.defaultPayload, {
318
+ wprss_intro_feed_url: this.form.feedSourceUrl
319
+ })
320
+ this.isLoading = true
321
+ this.isFeedError = false
322
+ return post(CONFIG.feedEndpoint.url, data).then((responseData) => {
323
+ this.feed.items = responseData.data.feed_items.slice(0, 3)
324
+ this.isLoading = false
325
+ return {}
326
+ }).catch((resp) => {
327
+ this.isLoading = false
328
+ this.isFeedError = true
329
+ throw resp
330
+ })
331
+ },
332
+
333
+ /**
334
+ * Continue from items step.
335
+ *
336
+ * @return {Promise<any>}
337
+ */
338
+ continueItems () {
339
+ this.itemsPassed = true
340
+ return Promise.resolve({})
341
+ },
342
+
343
+ /**
344
+ * Complete the introduction and proceed to sources list.
345
+ *
346
+ * @return {Promise<any>}
347
+ */
348
+ completeIntroduction () {
349
+ return Promise.resolve({})
350
+ },
351
+
352
+ /**
353
+ * Go to the next screen in this wizard.
354
+ */
355
+ next () {
356
+ this.transition = 'slide-up'
357
+ const nextTransistor = this.currentScreen.next ? this.currentScreen.next : () => Promise.resolve(false)
358
+ this.stepLoading = true
359
+ nextTransistor().then(result => {
360
+ this.stepLoading = false
361
+ }, (err) => {
362
+ throw err
363
+ }).then(() => {
364
+ const nextStepIndex = this.activeScreenIndex + 1
365
+ if (nextStepIndex >= this.screens.length) {
366
+ this.finish()
367
+ }
368
+ else {
369
+ this.activeScreen = this.screens[nextStepIndex].id
370
+ this.onScreenEnter()
371
+ }
372
+ }).catch((err) => {
373
+ console.error(err)
374
+ })
375
+ },
376
+
377
+ /**
378
+ * Run on screen event.
379
+ */
380
+ onScreenEnter () {
381
+ this.$nextTick(() => {
382
+ if (this.currentScreen.entered) {
383
+ this.currentScreen.entered()
384
+ }
385
+ })
386
+ },
387
+
388
+ /**
389
+ * Focus on some ref input.
390
+ */
391
+ focusOnInput (refName) {
392
+ if (!this.$refs[refName] || !this.$refs[refName].focus) {
393
+ return false
394
+ }
395
+ this.$refs[refName].focus()
396
+ },
397
+
398
+ /**
399
+ * Go back in the wizard on one step.
400
+ */
401
+ back () {
402
+ this.transition = 'slide-down'
403
+ this.activeScreen = this.screens[this.activeScreenIndex - 1].id
404
+ },
405
+
406
+ /**
407
+ * Finish this wizard.
408
+ */
409
+ finish (confirmFinish = false) {
410
+ const visitList = () => window.location.href = CONFIG.feedListUrl
411
+ if (confirmFinish) {
412
+ if (confirm('Are you sure you want to skip the introduction?')) {
413
+ visitList()
414
+ }
415
+ return
416
+ }
417
+ visitList()
418
+ // redirect to the URL.
419
+ },
420
+
421
+ active (pageName) {
422
+ return this.activeScreen === pageName
423
+ },
424
+
425
+ copyToClipboard () {
426
+ if (this.isCopied) {
427
+ return
428
+ }
429
+ copyToClipboard('[wp-rss-aggregator]')
430
+ this.isCopied = true
431
+ setTimeout(() => {
432
+ this.isCopied = false
433
+ }, 1000)
434
+ }
435
+ },
436
+ components: {
437
+ Expander
438
+ }
439
+ }
440
+ </script>
js/src/modules/intro/index.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ require('css/src/intro/steps.scss')
2
+
3
+ import Vue from 'vue'
4
+ import Wizard from './Wizard'
5
+ import 'whatwg-fetch'
6
+
7
+ new Vue({
8
+ el: '#wpra-wizard-app',
9
+ template: '<Wizard/>',
10
+ components: { Wizard }
11
+ })
js/src/modules/pagination/index.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('css/src/pagination/index.scss')
2
+
3
+ jQuery(document).ready(($) => {
4
+ const fetchList = function ($targetEl, params) {
5
+ $targetEl.addClass('wpra-loading')
6
+
7
+ $([document.documentElement, document.body]).animate({
8
+ scrollTop: $targetEl.offset().top - 50
9
+ }, 500);
10
+
11
+ const template = params.template
12
+ delete params.template
13
+
14
+ let tmp = template.length? template : '0'
15
+ let url = WpraPagination.baseUri.replace('%s', tmp)
16
+
17
+ $.ajax({
18
+ type: 'POST',
19
+ url,
20
+ data: JSON.stringify(params),
21
+ contentType: 'application/json',
22
+ }).done((data) => {
23
+ $newEl = $(data.html)
24
+ $newEl.find('.colorbox').colorbox({iframe:true, width:'80%', height:'80%'})
25
+ $targetEl.replaceWith($newEl)
26
+ })
27
+ }
28
+
29
+ const handleClick = function ($link) {
30
+ const $targetEl = $link.closest('[data-template-ctx]')
31
+
32
+ const template = $targetEl.data('wpra-template')
33
+ const templateOptions = $targetEl.data('template-ctx')
34
+
35
+ let options = Object.assign({}, {
36
+ template
37
+ }, JSON.parse(atob(templateOptions)))
38
+ options['page'] = $link.data('wpra-page')
39
+
40
+ fetchList($targetEl, options)
41
+ }
42
+
43
+ $('body').on('click', 'a[data-wpra-page]', function (e) {
44
+ e.preventDefault()
45
+ handleClick($(this))
46
+ });
47
+ })
js/src/modules/plugins/PluginDisablePoll.vue ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="wpra-plugin-disable-poll">
3
+ <modal :active="isModalVisible"
4
+ @close="closeModal"
5
+ :header-class="'invisible-header'"
6
+ >
7
+ <div slot="header">
8
+ <div class="wpra-plugin-disable-poll__logo">
9
+ <img :src="image('light-line-logo.png')" alt="">
10
+ </div>
11
+ <h3>
12
+ Do you have a moment to share why you are deactivating WP RSS Aggregator?
13
+ </h3>
14
+ <p>
15
+ Your feedback will help us to improve our plugins and service.
16
+ </p>
17
+ </div>
18
+
19
+ <div slot="body">
20
+ <SerializedForm :form="form" v-model="model"/>
21
+ </div>
22
+
23
+ <div slot="footer">
24
+ <div class="footer-confirm__buttons">
25
+ <button class="button button-clear" @click="deactivate">
26
+ Skip & Deactivate
27
+ </button>
28
+ <button class="button button-primary"
29
+ :class="{'loading-button': isDeactivating}"
30
+ @click="submit">
31
+ Submit & Deactivate
32
+ </button>
33
+ </div>
34
+ </div>
35
+ </modal>
36
+ </div>
37
+ </template>
38
+
39
+ <script>
40
+ import Modal from 'app/components/Modal'
41
+ import SerializedForm from 'app/components/SerializedForm'
42
+ import axios from 'axios'
43
+
44
+ /**
45
+ * Selector string for plugin's deactivation link.
46
+ *
47
+ * @type {string}
48
+ */
49
+ const deactivateSelector = '[data-slug="wp-rss-aggregator"] .deactivate a'
50
+ const deactivateLink = document.querySelector(deactivateSelector)
51
+
52
+ export default {
53
+ components: {
54
+ Modal,
55
+ SerializedForm,
56
+ },
57
+ data () {
58
+ return {
59
+ isDeactivating: false,
60
+ deactivateUrl: null,
61
+ submitUrl: WrpaDisablePoll.url,
62
+ model: WrpaDisablePoll.model,
63
+ form: WrpaDisablePoll.form,
64
+ audience: WrpaDisablePoll.audience || 100,
65
+ isModalVisible: false
66
+ }
67
+ },
68
+ watch: {
69
+ 'model.reason' () {
70
+ this.model.follow_up = null
71
+ }
72
+ },
73
+ mounted () {
74
+ const isVisible = this.getRandomInt(0, 100) < this.audience
75
+ if (isVisible) {
76
+ deactivateLink.addEventListener('click', this.handleDeactivateClick)
77
+ }
78
+ },
79
+ methods: {
80
+ getRandomInt (min, max) {
81
+ min = Math.ceil(min);
82
+ max = Math.floor(max);
83
+ return Math.floor(Math.random() * (max - min + 1)) + min;
84
+ },
85
+
86
+ image (path) {
87
+ return WrpaDisablePoll.image + path
88
+ },
89
+
90
+ handleDeactivateClick (e) {
91
+ if (this.isModalVisible) {
92
+ return
93
+ }
94
+
95
+ e.preventDefault()
96
+ this.isModalVisible = true
97
+ },
98
+
99
+ closeModal () {
100
+ this.isModalVisible = false
101
+ this.deactivateUrl = null
102
+ },
103
+
104
+ submit () {
105
+ this.isDeactivating = true
106
+ axios.post(this.submitUrl, this.model, {
107
+ headers: {
108
+ 'Content-Type': 'application/x-www-form-urlencoded'
109
+ }
110
+ }).then(() => {
111
+ this.deactivate()
112
+ }).finally(() => {
113
+ this.isDeactivating = false
114
+ })
115
+ },
116
+
117
+ deactivate () {
118
+ deactivateLink.click()
119
+ }
120
+ }
121
+ }
122
+ </script>
js/src/modules/plugins/index.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ require('css/src/plugins/index.scss')
2
+
3
+ import Vue from 'vue'
4
+ import PluginDisablePoll from './PluginDisablePoll'
5
+
6
+ new Vue({
7
+ el: '#wpra-plugins-app',
8
+ template: '<PluginDisablePoll/>',
9
+ components: { PluginDisablePoll }
10
+ })
js/src/modules/templates/Edit.js ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Postbox from 'app/components/Postbox'
2
+ import Main from 'app/components/Main'
3
+ import Sidebar from 'app/components/Sidebar'
4
+ import Layout from 'app/components/Layout'
5
+ import RouteLink from 'app/components/RouteLink'
6
+ import Input from 'app/components/Input'
7
+ import Button from 'app/components/Button'
8
+ import NoticeBlock from 'app/components/NoticeBlock'
9
+ import deepmerge from 'app/utils/deepmerge'
10
+ import DataChangesAware from 'app/mixins/DataChangesAware'
11
+ import jsonClone from 'app/utils/jsonClone'
12
+ import base64 from 'app/utils/base64'
13
+ import { copyToClipboard } from 'app/utils/copy'
14
+
15
+ export default {
16
+ mixins: [DataChangesAware],
17
+ data () {
18
+ return {
19
+ typeOptions: Object.keys(WpraTemplates.options.type)
20
+ .filter(key => key[0] !== '_')
21
+ .reduce((acc, key) => {
22
+ acc[key] = WpraTemplates.options.type[key]
23
+ return acc
24
+ }, {}),
25
+
26
+ model: jsonClone(WpraTemplates.model_schema),
27
+ validation: jsonClone(WpraTemplates.model_schema),
28
+ tooltips: jsonClone(WpraTemplates.model_tooltips),
29
+
30
+ baseUrl: WpraTemplates.base_url,
31
+ isSaving: false,
32
+ isLoading: false,
33
+ }
34
+ },
35
+ inject: [
36
+ 'hooks',
37
+ 'http',
38
+ 'router',
39
+ 'notification',
40
+ ],
41
+ mounted () {
42
+ this.resolveEditingItem()
43
+ },
44
+ computed: {
45
+ previewUrl () {
46
+ let options = base64.encode(JSON.stringify(this.model.options))
47
+
48
+ return `${WpraGlobal.admin_base_url}?wpra_preview_template=${this.router.params.id}&wpra_template_options=${options}`
49
+ }
50
+ },
51
+ methods: {
52
+ resolveEditingItem () {
53
+ let modelDefault = deepmerge(jsonClone(WpraTemplates.model_schema), this.$store.state.templates.preset)
54
+
55
+ this.isLoading = true
56
+ const loadItem = () => {
57
+ const id = this.router.params.id
58
+ if (!id) {
59
+ return Promise.resolve(null)
60
+ }
61
+ let item = this.$store.getters['templates/item'](id)
62
+ if (item) {
63
+ return Promise.resolve(item)
64
+ }
65
+ return this.http.get(`${this.baseUrl}/${id}`).then(response => {
66
+ return response.data
67
+ })
68
+ }
69
+
70
+ loadItem().then(item => {
71
+ this.isLoading = false
72
+ if (!item) {
73
+ this.$set(this, 'model', modelDefault)
74
+ this.rememberModel()
75
+ return
76
+ }
77
+ item = Object.assign({}, item)
78
+ this.model = deepmerge(this.model, item)
79
+ this.rememberModel()
80
+ })
81
+ },
82
+ save () {
83
+ const isNew = !this.model.id
84
+ this.isSaving = true
85
+ this.runRequest().then(response => {
86
+ this.model = deepmerge(this.model, response.data)
87
+ this.rememberModel()
88
+
89
+ this.notification.show('Template saved!', {
90
+ type: 'success',
91
+ icon (el) {
92
+ el.classList.add('dashicons', 'dashicons-yes')
93
+ return el
94
+ },
95
+ })
96
+
97
+ if (!isNew) {
98
+ return
99
+ }
100
+ this.router.navigate({
101
+ name: 'templates',
102
+ params: {
103
+ action: 'edit',
104
+ id: response.data.id
105
+ }
106
+ })
107
+ }, response => {
108
+ this.notification.show('Something went wrong. Template is not saved!', {
109
+ type: 'error',
110
+ icon (el) {
111
+ el.classList.add('dashicons', 'dashicons-warning')
112
+ return el
113
+ },
114
+ })
115
+ }).finally(() => {
116
+ this.isSaving = false
117
+ })
118
+ },
119
+ runRequest () {
120
+ const method = this.model.id ? 'put' : 'post'
121
+ const url = this.model.id ? `${this.baseUrl}/${this.model.id}` : this.baseUrl
122
+ return this.http[method](url, this.prepareModel())
123
+ },
124
+ prepareModel () {
125
+ const availableKeys = Object.keys(WpraTemplates.model_schema)
126
+
127
+ return Object.keys(this.model)
128
+ .filter(key => {
129
+ /*
130
+ * Only keys from model_schema can be saved.
131
+ */
132
+ if (!availableKeys.includes(key)) {
133
+ return false
134
+ }
135
+
136
+ /*
137
+ * Name and type shouldn't be passed for default template.
138
+ */
139
+ if (this.model.type === '__built_in' && ['name', 'type'].includes(key)) {
140
+ return false
141
+ }
142
+
143
+ /*
144
+ * Slug and ID fields are always ignored and not sent in the request body.
145
+ */
146
+ return !['slug', 'id'].includes(key)
147
+ })
148
+ .reduce((acc, key) => {
149
+ acc[key] = this.model[key]
150
+ return acc
151
+ }, {})
152
+ },
153
+ getShortcode () {
154
+ return `[wp-rss-aggregator template="${this.model.slug}"]`
155
+ },
156
+ preventLoosingNotSavedData () {
157
+ return !this.isChanged() || confirm('You have unsaved changes. All changes will be lost if you go back to the Template list before updating. Are you sure you want to continue?')
158
+ },
159
+ copyShortcode (e) {
160
+ copyToClipboard(this.getShortcode())
161
+
162
+ const text = e.target.innerText
163
+
164
+ e.target.style.width = e.target.getBoundingClientRect().width + 'px'
165
+ e.target.disabled = true
166
+ e.target.innerText = 'Copied!'
167
+
168
+ setTimeout(() => {
169
+ e.target.style.width = null
170
+ e.target.innerText = text
171
+ e.target.disabled = false
172
+ }, 5000)
173
+ },
174
+ },
175
+ render () {
176
+ let back = {
177
+ name: 'templates'
178
+ }
179
+
180
+ let minorActions = null,
181
+ shortcode = null,
182
+ noticeBlock = <NoticeBlock
183
+ class={'postbox'}
184
+ id={'templates-usage'}
185
+ title={'Setting up your Templates'}
186
+ body={'Templates are used to display the items imported using WP RSS Aggregator. Choose the preferred options ' +
187
+ 'below and use them anywhere on your site via our <a href="https://kb.wprssaggregator.com/article/54-displaying-imported-items-shortcode#tinymce" target="_blank">shortcode</a> ' +
188
+ 'or our <a href="https://kb.wprssaggregator.com/article/454-displaying-imported-items-block-gutenberg" target="_blank">block</a>.'}
189
+ learnMore={'https://kb.wprssaggregator.com/article/457-templates'}
190
+ />
191
+
192
+ if (this.router.params.id) {
193
+ minorActions = <div id="" style={{padding: '6px 0'}}>
194
+ <div class="misc-pub-section misc-pub-visibility">
195
+ <a href={this.previewUrl}
196
+ class="wpra-preview-link"
197
+ role="button"
198
+ target="wpra-preview-template"
199
+ style={{marginLeft: '4px', textDecoration: 'none'}}
200
+ >
201
+ Open preview
202
+ <span class="dashicons dashicons-external"/>
203
+ </a>
204
+ </div>
205
+ </div>
206
+ }
207
+
208
+ if (this.model.id) {
209
+ shortcode = <div class="wpra-shortcode-copy" title={'Copy chortcode'}>
210
+ <div class="wpra-shortcode-copy__content">
211
+ <strong>Shortcode: </strong>
212
+ <code>{ this.getShortcode() }</code>
213
+ </div>
214
+ <div class="wpra-shortcode-copy__icon">
215
+ <button class="button" onClick={this.copyShortcode}>Copy Shortcode</button>
216
+ </div>
217
+ </div>
218
+ }
219
+
220
+ let content = <div>
221
+ <div class="page-title">
222
+ <RouteLink class="back-button" path={back} gate={this.preventLoosingNotSavedData}>
223
+ <span class="dashicons dashicons-arrow-left-alt"></span>
224
+ Templates
225
+ </RouteLink>
226
+ <h1>
227
+ {this.router.params.id ? (this.changes.model.name || this.changes.model.slug) : 'Create a New Template'}
228
+ </h1>
229
+ {shortcode}
230
+ </div>
231
+ <div id="poststuff">
232
+ {
233
+ this.isLoading ? <div class="loading-container"/> : <Layout class="metabox-holder columns-2">
234
+ <Main>
235
+ {noticeBlock}
236
+ <Postbox id="template-details" title="Template Details" context={this}>
237
+ <Input type="text"
238
+ label={'Template name'}
239
+ value={this.model.name}
240
+ onInput={(e) => this.model.name = e}
241
+ disabled={this.model.type === '__built_in'}
242
+ />
243
+ <Input type="select"
244
+ label={'Template type'}
245
+ value={this.model.type}
246
+ options={this.typeOptions}
247
+ onInput={(e) => this.model.type = e}
248
+ disabled={this.model.type === '__built_in'}
249
+ inputDisabled={!WpraTemplates.options.is_type_enabled}
250
+ description={
251
+ WpraTemplates.options.is_type_enabled ? null : '<div class="disable-ignored"><strong class="disable-ignored">Get more template types, including a customisable grid template.</strong> <a target="_blank" href="https://www.wprssaggregator.com/extensions/templates/" class="disable-ignored">Learn more.</a></div>'
252
+ }
253
+ />
254
+ {
255
+ (this.model.type === '__built_in') ?
256
+ <div class="wpra-info-box">
257
+ <div class="wpra-info-box__icon">
258
+ <span class="dashicons dashicons-info"/>
259
+ </div>
260
+ <div class="wpra-info-box__text">
261
+ This is the default template for WP RSS Aggregator. It is used as the fallback template when one is not selected via the shortcode or block. To create a new one, please go back to the Templates List.
262
+ </div>
263
+ </div>
264
+ :
265
+ null
266
+ }
267
+ </Postbox>
268
+ <Postbox id="template-options" title="Template Options" context={this}>
269
+ <Input type="checkbox"
270
+ label={'Link title to original article'}
271
+ value={this.model.options.title_is_link}
272
+ onInput={(e) => this.model.options.title_is_link = e}
273
+ title={this.tooltips.options.title_is_link}
274
+ />
275
+ <Input type="number"
276
+ label={'Title maximum length'}
277
+ value={this.model.options.title_max_length || ''}
278
+ placeholder={'No limit'}
279
+ onInput={(e) => this.model.options.title_max_length = e}
280
+ title={this.tooltips.options.title_max_length}
281
+ />
282
+ <Input type="number"
283
+ label={'Number of items to show'}
284
+ value={this.model.options.limit || ''}
285
+ onInput={(e) => this.model.options.limit = (e === '') ? 0 : e}
286
+ title={this.tooltips.options.limit}
287
+ placeholder={"Show all items"}
288
+ min="0"
289
+ />
290
+
291
+ <div id={'wpra-list-template-publish-date'} style={{paddingTop: '10px'}}>
292
+ <Input type="checkbox"
293
+ label={'Show publish date'}
294
+ value={this.model.options.date_enabled}
295
+ onInput={(e) => this.model.options.date_enabled = e}
296
+ style={{fontWeight: 'bold'}}
297
+ title={this.tooltips.options.date_enabled}
298
+ />
299
+ <Input type="text"
300
+ label={'Date prefix'}
301
+ value={this.model.options.date_prefix}
302
+ onInput={(e) => this.model.options.date_prefix = e}
303
+ disabled={!this.model.options.date_enabled}
304
+ title={this.tooltips.options.date_prefix}
305
+ />
306
+ <Input type="text"
307
+ label={'Date format'}
308
+ value={this.model.options.date_format}
309
+ onInput={(e) => this.model.options.date_format = e}
310
+ disabled={this.model.options.date_use_time_ago || !this.model.options.date_enabled}
311
+ title={this.tooltips.options.date_format}
312
+ />
313
+ <Input type="checkbox"
314
+ label={'Use "time ago" format'}
315
+ description={'Example: 20 minutes ago'}
316
+ value={this.model.options.date_use_time_ago}
317
+ onInput={(e) => this.model.options.date_use_time_ago = e}
318
+ disabled={!this.model.options.date_enabled}
319
+ title={this.tooltips.options.date_use_time_ago}
320
+ />
321
+ </div>
322
+
323
+ <div id={'wpra-list-template-source'} style={{paddingTop: '10px'}}>
324
+ <Input type="checkbox"
325
+ label={'Show source name'}
326
+ value={this.model.options.source_enabled}
327
+ onInput={(e) => this.model.options.source_enabled = e}
328
+ style={{fontWeight: 'bold'}}
329
+ title={this.tooltips.options.source_enabled}
330
+ />
331
+ <Input type="text"
332
+ label={'Source prefix'}
333
+ value={this.model.options.source_prefix}
334
+ onInput={(e) => this.model.options.source_prefix = e}
335
+ disabled={!this.model.options.source_enabled}
336
+ title={this.tooltips.options.source_prefix}
337
+ />
338
+ <Input type="checkbox"
339
+ label={'Link source name'}
340
+ value={this.model.options.source_is_link}
341
+ onInput={(e) => this.model.options.source_is_link = e}
342
+ disabled={!this.model.options.source_enabled}
343
+ title={this.tooltips.options.source_is_link}
344
+ />
345
+ </div>
346
+
347
+ <div id={'wpra-list-template-author'} style={{paddingTop: '10px'}}>
348
+ <Input type="checkbox"
349
+ label={'Show author name'}
350
+ value={this.model.options.author_enabled}
351
+ onInput={(e) => this.model.options.author_enabled = e}
352
+ style={{fontWeight: 'bold'}}
353
+ title={this.tooltips.options.author_enabled}
354
+ />
355
+ <Input type="text"
356
+ label={'Author prefix'}
357
+ value={this.model.options.author_prefix}
358
+ onInput={(e) => this.model.options.author_prefix = e}
359
+ disabled={!this.model.options.author_enabled}
360
+ title={this.tooltips.options.author_prefix}
361
+ />
362
+ </div>
363
+
364
+ <div id={'wpra-list-template-pagination'} style={{paddingTop: '10px'}}>
365
+ <Input type="checkbox"
366
+ label={'Pagination'}
367
+ value={this.model.options.pagination}
368
+ onInput={(e) => this.model.options.pagination = e}
369
+ style={{fontWeight: 'bold'}}
370
+ title={this.tooltips.options.pagination}
371
+ disabled={parseInt(this.model.options.limit) < 1 || !this.model.options.limit}
372
+ />
373
+ <Input type="select"
374
+ label={'Pagination style'}
375
+ options={WpraTemplates.options.pagination_type}
376
+ value={this.model.options.pagination_type}
377
+ onInput={(e) => this.model.options.pagination_type = e}
378
+ disabled={!this.model.options.pagination || parseInt(this.model.options.limit) < 1 || !this.model.options.limit}
379
+ title={this.tooltips.options.pagination_type}
380
+ />
381
+ </div>
382
+
383
+ <div id={'wpra-list-template-bullets'} style={{paddingTop: '10px'}}>
384
+ <Input type="checkbox"
385
+ label={'Show bullets'}
386
+ value={this.model.options.bullets_enabled}
387
+ onInput={(e) => this.model.options.bullets_enabled = e}
388
+ style={{fontWeight: 'bold'}}
389
+ title={this.tooltips.options.bullets_enabled}
390
+ />
391
+ <Input type="select"
392
+ label={'Bullet style'}
393
+ options={WpraTemplates.options.bullet_type}
394
+ value={this.model.options.bullet_type}
395
+ onInput={(e) => this.model.options.bullet_type = e}
396
+ disabled={!this.model.options.bullets_enabled}
397
+ title={this.tooltips.options.bullet_type}
398
+ />
399
+ </div>
400
+ </Postbox>
401
+ </Main>
402
+ <Sidebar>
403
+ <Postbox id="template-create"
404
+ title={this.model.id ? 'Update Template' : 'Create Template'}
405
+ submit={true}
406
+ class={'wpra-postbox-last'}
407
+ context={this}
408
+ >
409
+ <div class="submitbox" id="submitpost">
410
+ {minorActions}
411
+
412
+ <div id="major-publishing-actions">
413
+ <div id="delete-action">
414
+ {
415
+ this.isChanged() ? <a href="#" class="submitdelete" onClick={(e) => {
416
+ e.preventDefault()
417
+ this.cancelChanges()
418
+ }}>
419
+ Cancel Changes
420
+ </a> : null
421
+ }
422
+ </div>
423
+
424
+ <div id="publishing-action">
425
+ <Button class="button-primary button-large"
426
+ loading={this.isSaving}
427
+ nativeOnClick={this.save}
428
+ >
429
+ {this.model.id ? 'Save' : 'Publish'}
430
+ </Button>
431
+ </div>
432
+ <div class="clear"></div>
433
+ </div>
434
+ </div>
435
+ </Postbox>
436
+ <Postbox id="template-link-preferences" title="Link Preferences" context={this}>
437
+ <p style={{opacity: .65}}>
438
+ These options apply to all links within this template.
439
+ </p>
440
+ <Input type="checkbox"
441
+ label={'Set links as nofollow'}
442
+ value={this.model.options.links_nofollow}
443
+ onInput={(e) => this.model.options.links_nofollow = e}
444
+ title={this.tooltips.options.links_nofollow}
445
+ />
446
+ <Input type="select"
447
+ label={'Open links behaviour'}
448
+ class="form-input--vertical"
449
+ value={this.model.options.links_behavior}
450
+ options={WpraTemplates.options.links_behavior}
451
+ onInput={(e) => this.model.options.links_behavior = e}
452
+ title={this.tooltips.options.links_behavior}
453
+ />
454
+ {
455
+ (this.model.options.links_behavior === 'lightbox' )?
456
+ <div class="notice notice-info notice-alt">
457
+ <p>Some sites may not allow their content to be shown in a lightbox.</p>
458
+ <p>Embedded content usually works. Try ticking the below checkbox.</p>
459
+ <p>
460
+ <a href="https://kb.wprssaggregator.com/article/471-q-why-wont-some-of-my-feed-items-work-with-the-lightbox-link-behaviour-for-templates"
461
+ target="_blank">
462
+ {'Learn more'}
463
+ </a>
464
+ </p>
465
+ </div>
466
+ : null
467
+ }
468
+ <Input type="checkbox"
469
+ label={'Set links to open embeds'}
470
+ value={this.model.options.link_to_embed}
471
+ onInput={(e) => this.model.options.link_to_embed = e}
472
+ title={this.tooltips.options.link_to_embed}
473
+ />
474
+ </Postbox>
475
+ <Postbox id="template-custom-css" title="Custom Style" context={this}>
476
+ <Input type="text"
477
+ class="form-input--vertical"
478
+ label={'Custom HTML class name'}
479
+ value={this.model.options.custom_css_classname}
480
+ onInput={(e) => this.model.options.custom_css_classname = e}
481
+ title={this.tooltips.options.custom_css_classname}
482
+ />
483
+ </Postbox>
484
+ </Sidebar>
485
+ </Layout>
486
+ }
487
+ </div>
488
+ </div>
489
+ return this.hooks.apply('wpra-templates-form', this, content)
490
+ }
491
+ }
js/src/modules/templates/List.js ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import VueTable from '@rebelcode/vue-wp-list-table/dist/vue-wp-list-table.common'
2
+ import RouteLink from 'app/components/RouteLink'
3
+ import Input from 'app/components/Input'
4
+ import BottomPanel from 'app/components/BottomPanel'
5
+ import jsonClone from 'app/utils/jsonClone'
6
+ import collect from 'app/utils/Collection'
7
+
8
+ export default {
9
+ data () {
10
+ return {
11
+ loading: false,
12
+
13
+ columns: {
14
+ name: {
15
+ label: ('Template Name'),
16
+ class: 'column-primary'
17
+ },
18
+ style: {
19
+ label: ('Template Type'),
20
+ },
21
+ previewTemplate: {
22
+ label: ('Preview')
23
+ }
24
+ },
25
+
26
+ filters: WpraTemplates.options.type,
27
+
28
+ checked: [],
29
+
30
+ filter: {
31
+ paged: parseInt(this.router.params.paged || 1),
32
+ type: this.router.params.type || '',
33
+ s: this.router.params.s || '',
34
+ },
35
+
36
+ baseUrl: WpraTemplates.base_url,
37
+
38
+ total: 0,
39
+ }
40
+ },
41
+ inject: [
42
+ 'hooks',
43
+ 'http',
44
+ 'router',
45
+ ],
46
+ computed: {
47
+ totalPages () {
48
+ return Math.ceil(this.total / 20)
49
+ },
50
+ list: {
51
+ get () {
52
+ return this.$store.state.templates.items
53
+ },
54
+ set (value) {
55
+ this.$store.commit('templates/set', value)
56
+ },
57
+ }
58
+ },
59
+ methods: {
60
+ navigated () {
61
+ Object.keys(this.filter).forEach(key => {
62
+ this.filter[key] = this.router.params[key] || ''
63
+ })
64
+ this.filter.paged = parseInt(this.filter.paged || 1)
65
+ this.fetchList()
66
+ },
67
+
68
+ fetchList () {
69
+ this.loading = true
70
+
71
+ let params = this.getParams()
72
+
73
+ let paged = parseInt(params.paged)
74
+ delete params.paged
75
+ if (!!paged && paged !== 1) {
76
+ params['page'] = paged
77
+ }
78
+
79
+ return this.http.get(this.baseUrl, {
80
+ params
81
+ }).then((response) => {
82
+ this.list = response.data.items
83
+ this.total = response.data.count
84
+ }).finally(() => {
85
+ this.loading = false
86
+ })
87
+ },
88
+
89
+ deleteTemplate (id) {
90
+ if (!confirm('Are you sure you want to delete this template? If this template is being used in a shortcode or Gutenberg block anywhere on your site, the default template will be used instead.')) {
91
+ return
92
+ }
93
+ this.loading = true
94
+ return this.http.delete(`${this.baseUrl}/${id}`).then(() => {
95
+ return this.fetchList()
96
+ }).then(() => {
97
+ this.loading = false
98
+ })
99
+ },
100
+
101
+ bulkDelete () {
102
+ if (!confirm('Are you sure you want to delete these templates? If a template is being used in a shortcode or Gutenberg block anywhere on your site, the default template will be used instead.')) {
103
+ return
104
+ }
105
+ this.loading = true
106
+ return this.http.delete(this.baseUrl, {
107
+ params: {
108
+ ids: this.checked
109
+ }
110
+ }).then(() => {
111
+ this.checked = []
112
+ this.$refs.table.checkedItems = []
113
+ return this.fetchList()
114
+ }).then(() => {
115
+ this.loading = false
116
+ })
117
+ },
118
+
119
+ duplicateTemplate (row) {
120
+ let template = jsonClone(row)
121
+
122
+ delete template.id
123
+
124
+ if (template.type === '__built_in') {
125
+ delete template.type
126
+ }
127
+
128
+ // add copy to the title so it is mor obvious for the user that this is duplicate.
129
+ template.name = `${template.name} (Copy)`
130
+
131
+ this.$store.commit('templates/updatePreset', template)
132
+ this.router.navigate({
133
+ name: 'templates',
134
+ params: {
135
+ action: 'new',
136
+ }
137
+ })
138
+ },
139
+
140
+ getPreviewLink (row) {
141
+ return `${WpraGlobal.admin_base_url}?wpra_preview_template=${row.id}`
142
+ },
143
+
144
+ createTemplate () {
145
+ this.$store.commit('templates/updatePreset', {})
146
+ this.router.navigate({
147
+ name: 'templates',
148
+ params: {
149
+ action: 'new',
150
+ }
151
+ })
152
+ },
153
+
154
+ setChecked (values) {
155
+ this.checked = values.filter(id => {
156
+ return collect(this.list).find(id, {}).type !== '__built_in'
157
+ })
158
+ },
159
+
160
+ getParams () {
161
+ return Object.keys(this.filter).filter(key => {
162
+ return !!this.filter[key] && this.filter[key] !== 'all'
163
+ }).reduce((acc, key) => {
164
+ acc[key] = this.filter[key]
165
+ return acc
166
+ }, {})
167
+ },
168
+
169
+ setFilter (name, value) {
170
+ this.filter[name] = value
171
+ },
172
+
173
+ submitFilter () {
174
+ this.router.mergeParams(this.getParams())
175
+ },
176
+
177
+ getRowClass (row) {
178
+ return row.type === '__built_in' ? 'built-in' : ''
179
+ }
180
+ },
181
+ render () {
182
+ const editPath = (id) => {
183
+ return {
184
+ name: 'templates',
185
+ params: {
186
+ action: 'edit',
187
+ id,
188
+ }
189
+ }
190
+ }
191
+
192
+ let cells = this.hooks.apply('wpra-templates-list-cells', this, {
193
+ name: ({row}) => {
194
+ return [
195
+ <div>
196
+ <strong><RouteLink path={editPath(row.id)}>{row.name}</RouteLink></strong>
197
+ <small style={{paddingLeft: '4px', opacity: '0.6'}}>{row.slug}</small>
198
+ {
199
+ (row.type === '__built_in') ?
200
+ <span style={{opacity: '0.6', display: 'block'}}>
201
+ This is the default feed template. To create your own, either duplicate it or click "Add New" above.
202
+ </span>
203
+ :
204
+ null
205
+ }
206
+ </div>,
207
+ <div class="row-actions">
208
+ <span className="edit">
209
+ <RouteLink path={editPath(row.id)}>Edit</RouteLink> |
210
+ </span>
211
+ <span class="inline" style={{paddingLeft: '4px'}}>
212
+ <a href="#"
213
+ onClick={(e) => {
214
+ e.preventDefault()
215
+ this.duplicateTemplate(row)
216
+ }}
217
+ >Duplicate</a> {row.type !== '__built_in' ? '|' : ''}
218
+ </span>
219
+ {
220
+ (row.type !== '__built_in')
221
+ ?
222
+ <span class="trash" style={{paddingLeft: '4px'}} onClick={(e) => {
223
+ e.preventDefault()
224
+ this.deleteTemplate(row.id)
225
+ }}>
226
+ <a href="#" class="submitdelete" aria-label="Delete Item">Delete</a>
227
+ </span>
228
+ :
229
+ null
230
+ }
231
+ </div>
232
+ ]
233
+ },
234
+ style: ({row}) => {
235
+ if (this.filters[row.type]) {
236
+ return [
237
+ <div>{this.filters[row.type]}</div>
238
+ ]
239
+ }
240
+
241
+ return [
242
+ <div>
243
+ {this.filters.list}
244
+ &nbsp;
245
+ <span style={{opacity: 0.7, fontSize: '90%'}}>
246
+ (Missing type: <code>{row.type}</code>)
247
+ </span>
248
+ </div>
249
+ ];
250
+ },
251
+ previewTemplate: ({row}) => {
252
+ return [
253
+ <div>
254
+ <a href={this.getPreviewLink(row)}
255
+ target="wpra-preview-template"
256
+ class="wpra-preview-link"
257
+ >
258
+ Open preview <span class="dashicons dashicons-external"></span>
259
+ </a>
260
+ </div>
261
+ ]
262
+ },
263
+ filters: () => {
264
+ let templateTypes = Object.keys(WpraTemplates.options.type)
265
+ .filter(key => key[0] !== '_')
266
+ .reduce((carry, key) => {
267
+ carry[key] = WpraTemplates.options.type[key]
268
+ return carry
269
+ }, {
270
+ 'all': 'Select Template Type',
271
+ })
272
+ return [
273
+ <Input type="select"
274
+ style={{margin: 0}}
275
+ options={templateTypes}
276
+ value={this.filter.type}
277
+ onInput={(value) => {
278
+ this.filter.type = value
279
+ this.submitFilter()
280
+ }}
281
+ />
282
+ ]
283
+ }
284
+ })
285
+
286
+ let content = <div>
287
+ <h1 class="wp-heading-inline">Templates</h1>
288
+ <a class="page-title-action"
289
+ href="#"
290
+ onClick={e => {
291
+ e.preventDefault()
292
+ this.createTemplate()
293
+ }}
294
+ >Add New</a>
295
+
296
+ <p class="search-box" style={{padding: '10px 0'}}>
297
+ <label class="screen-reader-text" for="post-search-input">Search Templates:</label>
298
+ <input type="search"
299
+ id="post-search-input"
300
+ name="s"
301
+ value={this.filter.s}
302
+ onInput={e => this.filter.s = e.target.value}
303
+ onKeyup:enter={this.submitFilter}
304
+ />
305
+ <input type="submit" id="search-submit" class="button" value="Search Templates"
306
+ onClick={this.submitFilter}
307
+ />
308
+ </p>
309
+
310
+ <VueTable
311
+ onChecked={this.setChecked}
312
+ onPagination={page => {
313
+ this.filter.paged = page
314
+ this.submitFilter()
315
+ }}
316
+ columns={this.columns}
317
+ rows={this.list}
318
+ loading={this.loading}
319
+ totalItems={this.total}
320
+ perPage={20}
321
+ totalPages={this.totalPages}
322
+ currentPage={this.filter.paged}
323
+ ref="table"
324
+ notFound="No templates found."
325
+ rowClass={this.getRowClass}
326
+ class={{
327
+ 'wpra-no-cb': this.list.length === 0 || (this.list.length === 1 && this.list[0].type === '__built_in')
328
+ }}
329
+ scopedSlots={
330
+ cells
331
+ }
332
+ />
333
+
334
+ {
335
+ this.checked.length ? <BottomPanel>
336
+ <div class="flex-row">
337
+ <div class="flex-col">
338
+ <div class="wpra-bottom-panel__title">Bulk Actions</div>
339
+ <a href="#" onClick={(e) => {
340
+ e.preventDefault()
341
+ this.bulkDelete()
342
+ }}>Delete</a>
343
+ </div>
344
+ </div>
345
+ </BottomPanel> : null
346
+ }
347
+ </div>
348
+ return this.hooks.apply('wpra-templates-list', this, content)
349
+ }
350
+ }
js/src/modules/templates/app.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios'
2
+ import toasted from 'vue-toasted'
3
+ import VueTippy from 'vue-tippy'
4
+
5
+ import Vuex from 'vuex'
6
+ import List from './List'
7
+ import Edit from './Edit'
8
+
9
+ import makeRouterApp from 'app/components/RouterApp'
10
+ import Router from 'app/libs/Router'
11
+ import templates from './store'
12
+ import NotificationCenter from 'app/libs/NotificationCenter'
13
+
14
+ import components from 'app/components'
15
+
16
+ /**
17
+ * Main application's container.
18
+ */
19
+ export default {
20
+ register (services) {
21
+ /*
22
+ * Component for editing templates.
23
+ */
24
+ services['TemplateEdit'] = () => Edit
25
+
26
+ /*
27
+ * Component for managing templates.
28
+ */
29
+ services['TemplateList'] = () => List
30
+
31
+ /*
32
+ * Application router instance.
33
+ */
34
+ services['router'] = ({ document, TemplateEdit, TemplateList }) => {
35
+ return new Router([{
36
+ route: WpraGlobal.templates_url_base + '&action',
37
+ name: 'templates-form',
38
+ component: TemplateEdit,
39
+ }, {
40
+ route: WpraGlobal.templates_url_base,
41
+ name: 'templates',
42
+ component: TemplateList,
43
+ }], {
44
+ afterNavigating: () => {
45
+ document.querySelector('html').scrollTop = 0
46
+ }
47
+ })
48
+ }
49
+
50
+ /*
51
+ * Application with client side routes.
52
+ */
53
+ services['App'] = (container) => {
54
+ return makeRouterApp(container)
55
+ }
56
+
57
+ /*
58
+ * Setup and register central storage management.
59
+ */
60
+ services['vuex'] = ({ vue }) => {
61
+ vue.use(Vuex)
62
+ return Vuex
63
+ }
64
+
65
+ services['notification'] = ({ vue }) => {
66
+ vue.use(toasted, {
67
+ position: 'top-center',
68
+ duration: 4000,
69
+ iconPack: 'callback'
70
+ })
71
+ return new NotificationCenter(vue.toasted.show, vue.toasted.error)
72
+ }
73
+
74
+ services['store'] = ({ vuex }) => {
75
+ return new vuex.Store({
76
+ modules: {
77
+ templates
78
+ },
79
+ state: {}
80
+ })
81
+ }
82
+
83
+ services['http'] = () => {
84
+ /*
85
+ * Create authorized client for requests when nonce
86
+ * exists in global WPRA variable.
87
+ */
88
+ let httpClientOptions = !!WpraGlobal && !!WpraGlobal.nonce ? {
89
+ headers: {
90
+ 'X-WP-Nonce': WpraGlobal.nonce,
91
+ }
92
+ } : {}
93
+ return axios.create(httpClientOptions)
94
+ }
95
+
96
+ /*
97
+ * Register components.
98
+ */
99
+ for(const [name, definition] of Object.entries(components)) {
100
+ services[name] = () => definition
101
+ }
102
+
103
+ return services
104
+ },
105
+ run ({ container }) {
106
+ /*
107
+ * Enable tippy.js tooltips.
108
+ */
109
+ container.vue.use(VueTippy, {
110
+ theme: 'light',
111
+ animation: 'fade',
112
+ arrow: true,
113
+ arrowTransform: 'scale(0)',
114
+ placement: 'right'
115
+ })
116
+ },
117
+ }
js/src/modules/templates/index.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('css/src/templates/index.scss')
2
+
3
+ import * as UiFramework from '@rebelcode/ui-framework'
4
+ import Bottle from 'bottlejs'
5
+ import TemplatesApplication from './app'
6
+ import Vue from 'vue'
7
+
8
+ const { Container, Core, Services } = UiFramework
9
+
10
+ /*
11
+ * Extend UI framework object.
12
+ */
13
+ if (window.UiFramework) {
14
+ window.UiFramework = Object.assign({}, window.UiFramework, Core.UiFramework)
15
+ }
16
+
17
+ let services = {
18
+ uiFramework: UiFramework,
19
+ hooks: new Services.HookService,
20
+ document: document,
21
+ vue: function (container) {
22
+ Vue.use(container.uiFramework.Core.InjectedComponents, {
23
+ container
24
+ })
25
+ return Vue
26
+ }
27
+ }
28
+ const containerFactory = new Container.ContainerFactory(Bottle)
29
+ const app = new Core.UiFramework.App(containerFactory, services)
30
+
31
+ window.UiFramework.registerPlugin('templates-app', TemplatesApplication)
32
+
33
+ app.use(WpraTemplates.modules || ['templates-app'])
34
+ app.init({
35
+ '#wpra-templates-app': 'App',
36
+ })
js/src/modules/templates/store/actions.js ADDED
@@ -0,0 +1 @@
 
1
+ export default {}
js/src/modules/templates/store/getters.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ import collect from 'app/utils/Collection'
2
+
3
+ export default {
4
+ item: state => id => {
5
+ return collect(state.items).find(id)
6
+ }
7
+ }
js/src/modules/templates/store/index.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mutations from './mutations'
2
+ import actions from './actions'
3
+ import state from './state'
4
+ import getters from './getters'
5
+
6
+ export default {
7
+ namespaced: true,
8
+ mutations,
9
+ actions,
10
+ state,
11
+ getters,
12
+ }
js/src/modules/templates/store/mutations.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ set (state, items = []) {
3
+ state.isInitialized = true
4
+ state.items = items
5
+ },
6
+
7
+ updatePreset (state, preset = null) {
8
+ state.preset = preset
9
+ }
10
+ }
js/src/modules/templates/store/state.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ /*
3
+ * Whether the templates was initialized.
4
+ *
5
+ * @todo: remove it. This is trick to not handle proper route params watcher.
6
+ */
7
+ isInitialized: false,
8
+
9
+ /*
10
+ * All loaded template items.
11
+ */
12
+ items: [],
13
+
14
+ /*
15
+ * Predefined template value. Used for items duplication.
16
+ */
17
+ preset: {},
18
+ }
js/src/utils/Collection.js ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Collection {
2
+ constructor (data, primaryField = 'id') {
3
+ this.data = data
4
+ this.primaryField = primaryField
5
+ return this
6
+ }
7
+
8
+ /**
9
+ * Find element by params
10
+ *
11
+ * @param params
12
+ * @param _default
13
+ * @returns {*}
14
+ */
15
+ find (params, _default = null) {
16
+ if (typeof params !== 'object' && params !== null) {
17
+ params = {
18
+ [this.primaryField]: params
19
+ }
20
+ }
21
+ for (let i in this.data) {
22
+ if (this._isMatching(this.data[i], params)) {
23
+ return this.data[i]
24
+ }
25
+ }
26
+
27
+ return _default
28
+ }
29
+
30
+ /**
31
+ * Get and left only one column
32
+ *
33
+ * @param field
34
+ * @returns {*}
35
+ */
36
+ pluck (field) {
37
+ return this.data.map((item) => {
38
+ return item[field]
39
+ })
40
+ }
41
+
42
+ /**
43
+ * Remove all elements matching given params
44
+ *
45
+ * @param params
46
+ */
47
+ remove (params) {
48
+ for (let i in this.data) {
49
+ if (this._isMatching(this.data[i], params)) {
50
+ this.data.splice(i, 1)
51
+ }
52
+ }
53
+ return this
54
+ }
55
+
56
+ /**
57
+ * Add data from parameter array that not in
58
+ * collection;
59
+ *
60
+ * @param data
61
+ */
62
+ appendDiff (data) {
63
+ for (let el of data) {
64
+ if (!this.contains(el)) {
65
+ this.data.push(el)
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Add data from parameter array that not in
72
+ * collection;
73
+ *
74
+ * @param data
75
+ */
76
+ prependDiff (data) {
77
+ for (let el of data.slice().reverse()) {
78
+ if (!this.contains(el)) {
79
+ this.data.unshift(el)
80
+ }
81
+ }
82
+ }
83
+
84
+ contains (element) {
85
+ for (let el of this.data) {
86
+ if (el['id'] == element['id'])
87
+ return true
88
+ }
89
+ return false
90
+ }
91
+
92
+ filterValues (callback) {
93
+ return Object.keys(this.data).filter(key => {
94
+ return callback(this.data[key], key)
95
+ }).reduce((filteredResult, key) => {
96
+ filteredResult[key] = this.data[key]
97
+ return filteredResult
98
+ }, {})
99
+ }
100
+
101
+ filter (params) {
102
+ return this.data.filter((item) => {
103
+ return this._isMatching(item, params)
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Select all items where value of column in given array
109
+ *
110
+ * @param data
111
+ * @param key
112
+ * @returns {Array}
113
+ */
114
+ whereIn (data, key = 'id') {
115
+ let newCollection = [],
116
+ param = {}
117
+
118
+ param[key] = null
119
+
120
+ for (let val of data) {
121
+ param[key] = val
122
+
123
+ let item = this.find(param)
124
+ if (item) {
125
+ newCollection.push(item)
126
+ }
127
+ }
128
+
129
+ return newCollection
130
+ }
131
+
132
+ key (field) {
133
+ const data = this.data.slice().reduce((obj, item) => {
134
+ obj[item[field]] = item
135
+ return obj
136
+ }, {})
137
+ return new Collection(data)
138
+ }
139
+
140
+ mapValues (callback) {
141
+ Object.keys(this.data).map((key) => {
142
+ this.data[key] = callback(this.data[key], key)
143
+ })
144
+ return this
145
+ }
146
+
147
+ values () {
148
+ return this.data
149
+ }
150
+
151
+ /**
152
+ * Check element is matching params.
153
+ *
154
+ * @param element
155
+ * @param params
156
+ * @return {boolean}
157
+ * @private
158
+ */
159
+ _isMatching (element, params) {
160
+ if (!(element instanceof Object) && !(params instanceof Object)) {
161
+ return element == params
162
+ }
163
+
164
+ let match = true
165
+ for (let key of Object.keys(params)) {
166
+ let keyMatch = element.hasOwnProperty(key) && (element[key] == params[key])
167
+ match = match && (keyMatch)
168
+ }
169
+ return match
170
+ }
171
+
172
+ }
173
+
174
+ function collect (data) {
175
+ return new Collection(data)
176
+ }
177
+
178
+ export default collect
js/src/utils/base64.js ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let Base64 = {
2
+
3
+ // private property
4
+ _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
5
+
6
+ // public method for encoding
7
+ encode: function (input) {
8
+ var output = "";
9
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
10
+ var i = 0;
11
+
12
+ input = Base64._utf8_encode(input);
13
+
14
+ while (i < input.length) {
15
+
16
+ chr1 = input.charCodeAt(i++);
17
+ chr2 = input.charCodeAt(i++);
18
+ chr3 = input.charCodeAt(i++);
19
+
20
+ enc1 = chr1 >> 2;
21
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
22
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
23
+ enc4 = chr3 & 63;
24
+
25
+ if (isNaN(chr2)) {
26
+ enc3 = enc4 = 64;
27
+ } else if (isNaN(chr3)) {
28
+ enc4 = 64;
29
+ }
30
+
31
+ output = output +
32
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
33
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
34
+
35
+ }
36
+
37
+ return output;
38
+ },
39
+
40
+ // public method for decoding
41
+ decode: function (input) {
42
+ var output = "";
43
+ var chr1, chr2, chr3;
44
+ var enc1, enc2, enc3, enc4;
45
+ var i = 0;
46
+
47
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
48
+
49
+ while (i < input.length) {
50
+
51
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
52
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
53
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
54
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
55
+
56
+ chr1 = (enc1 << 2) | (enc2 >> 4);
57
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
58
+ chr3 = ((enc3 & 3) << 6) | enc4;
59
+
60
+ output = output + String.fromCharCode(chr1);
61
+
62
+ if (enc3 != 64) {
63
+ output = output + String.fromCharCode(chr2);
64
+ }
65
+ if (enc4 != 64) {
66
+ output = output + String.fromCharCode(chr3);
67
+ }
68
+
69
+ }
70
+
71
+ output = Base64._utf8_decode(output);
72
+
73
+ return output;
74
+
75
+ },
76
+
77
+ // private method for UTF-8 encoding
78
+ _utf8_encode: function (string) {
79
+ string = string.replace(/\r\n/g, "\n");
80
+ var utftext = "";
81
+
82
+ for (var n = 0; n < string.length; n++) {
83
+
84
+ var c = string.charCodeAt(n);
85
+
86
+ if (c < 128) {
87
+ utftext += String.fromCharCode(c);
88
+ } else if ((c > 127) && (c < 2048)) {
89
+ utftext += String.fromCharCode((c >> 6) | 192);
90
+ utftext += String.fromCharCode((c & 63) | 128);
91
+ } else {
92
+ utftext += String.fromCharCode((c >> 12) | 224);
93
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
94
+ utftext += String.fromCharCode((c & 63) | 128);
95
+ }
96
+
97
+ }
98
+
99
+ return utftext;
100
+ },
101
+
102
+ // private method for UTF-8 decoding
103
+ _utf8_decode: function (utftext) {
104
+ var string = "";
105
+ var i = 0;
106
+ var c = 0, c1 = 0, c2 = 0;
107
+
108
+ while (i < utftext.length) {
109
+
110
+ c = utftext.charCodeAt(i);
111
+
112
+ if (c < 128) {
113
+ string += String.fromCharCode(c);
114
+ i++;
115
+ } else if ((c > 191) && (c < 224)) {
116
+ c2 = utftext.charCodeAt(i + 1);
117
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
118
+ i += 2;
119
+ } else {
120
+ c2 = utftext.charCodeAt(i + 1);
121
+ c3 = utftext.charCodeAt(i + 2);
122
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
123
+ i += 3;
124
+ }
125
+
126
+ }
127
+
128
+ return string;
129
+ }
130
+
131
+ }
132
+
133
+ export default Base64
js/src/utils/copy.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Helper functions to copy text to clipboard.
3
+ *
4
+ * @see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript
5
+ *
6
+ * @param text
7
+ */
8
+
9
+ const fallbackCopyToClipboard = function (text, scrollContainer = null) {
10
+ scrollContainer = scrollContainer || document.body.parentElement
11
+ var textArea = document.createElement('textarea')
12
+ textArea.value = text
13
+
14
+ var currentScrollTop = scrollContainer.scrollTop
15
+
16
+ document.body.appendChild(textArea)
17
+ textArea.focus()
18
+ textArea.select()
19
+
20
+ try {
21
+ var successful = document.execCommand('copy')
22
+ var msg = successful ? 'successful' : 'unsuccessful'
23
+ console.log('Fallback: Copying text command was ' + msg)
24
+ } catch (err) {
25
+ console.error('Fallback: Oops, unable to copy', err)
26
+ }
27
+
28
+ document.body.removeChild(textArea)
29
+
30
+ scrollContainer.scrollTop = currentScrollTop
31
+ }
32
+
33
+ export function copyToClipboard (text, scrollContainer = null) {
34
+ if (!navigator.clipboard) {
35
+ fallbackCopyToClipboard(text, scrollContainer)
36
+ return
37
+ }
38
+ navigator.clipboard.writeText(text).then(function () {
39
+ console.log('Async: Copying to clipboard was successful!')
40
+ }, function (err) {
41
+ console.error('Async: Could not copy text: ', err)
42
+ })
43
+ }
js/src/utils/deepmerge.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function isMergeableObject (val) {
2
+ var nonNullObject = val && typeof val === 'object'
3
+
4
+ return nonNullObject
5
+ && Object.prototype.toString.call(val) !== '[object RegExp]'
6
+ && Object.prototype.toString.call(val) !== '[object Date]'
7
+ }
8
+
9
+ function emptyTarget (val) {
10
+ return Array.isArray(val) ? [] : {}
11
+ }
12
+
13
+ function cloneIfNecessary (value, optionsArgument) {
14
+ var clone = optionsArgument && optionsArgument.clone === true
15
+ return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
16
+ }
17
+
18
+ function defaultArrayMerge (target, source, optionsArgument) {
19
+ var destination = target.slice()
20
+ source.forEach(function (e, i) {
21
+ if (typeof destination[i] === 'undefined') {
22
+ destination[i] = cloneIfNecessary(e, optionsArgument)
23
+ } else if (isMergeableObject(e)) {
24
+ destination[i] = deepmerge(target[i], e, optionsArgument)
25
+ } else if (target.indexOf(e) === -1) {
26
+ destination.push(cloneIfNecessary(e, optionsArgument))
27
+ }
28
+ })
29
+ return destination
30
+ }
31
+
32
+ function mergeObject (target, source, optionsArgument) {
33
+ var destination = {}
34
+ if (isMergeableObject(target)) {
35
+ Object.keys(target).forEach(function (key) {
36
+ destination[key] = cloneIfNecessary(target[key], optionsArgument)
37
+ })
38
+ }
39
+ Object.keys(source).forEach(function (key) {
40
+ if (!isMergeableObject(source[key]) || !target[key]) {
41
+ destination[key] = cloneIfNecessary(source[key], optionsArgument)
42
+ } else {
43
+ destination[key] = deepmerge(target[key], source[key], optionsArgument)
44
+ }
45
+ })
46
+ return destination
47
+ }
48
+
49
+ function deepmerge (target, source, optionsArgument) {
50
+ var array = Array.isArray(source)
51
+ var options = optionsArgument || {arrayMerge: defaultArrayMerge}
52
+ var arrayMerge = options.arrayMerge || defaultArrayMerge
53
+
54
+ if (array) {
55
+ return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
56
+ } else {
57
+ return mergeObject(target, source, optionsArgument)
58
+ }
59
+ }
60
+
61
+ deepmerge.all = function deepmergeAll (array, optionsArgument) {
62
+ if (!Array.isArray(array) || array.length < 2) {
63
+ throw new Error('first argument should be an array with at least two elements')
64
+ }
65
+
66
+ // we are sure there are at least 2 values, so it is safe to have no initial value
67
+ return array.reduce(function (prev, next) {
68
+ return deepmerge(prev, next, optionsArgument)
69
+ })
70
+ }
71
+
72
+ export default deepmerge
js/src/utils/fetch.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Make fetch request and parse response JSON.
3
+ *
4
+ * @param url
5
+ * @param data
6
+ * @param config
7
+ *
8
+ * @return {Promise<Response>}
9
+ */
10
+ export function post (url, data, config = {}) {
11
+ const preparedData = new FormData()
12
+ for (let key of Object.keys(data)) {
13
+ preparedData.set(key, data[key])
14
+ }
15
+ return fetch(url, {
16
+ method: 'post',
17
+ body: preparedData
18
+ }).then(response => {
19
+ return response.json()
20
+ }).then(data => {
21
+ if (data.status !== 200) {
22
+ throw data
23
+ }
24
+ return data
25
+ })
26
+ }
js/src/utils/jsonClone.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ export default function (data) {
2
+ return JSON.parse(JSON.stringify(data))
3
+ }
languages/default.mo CHANGED
File without changes
languages/default.po CHANGED
File without changes
languages/wprss-it.mo CHANGED
File without changes
languages/wprss-it.po CHANGED
File without changes
languages/wprss-nl_NL.mo CHANGED
File without changes
languages/wprss-nl_NL.po CHANGED
File without changes
languages/wprss-pt_BR.mo CHANGED
File without changes
languages/wprss-pt_BR.po CHANGED
File without changes
languages/wprss-ru_RU.mo CHANGED
File without changes
languages/wprss-ru_RU.po CHANGED
File without changes
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: RSS import, RSS aggregator, feed import, content curation, feed to post
5
  Requires at least: 4.0 or higher
6
  Tested up to: 5.2.2
7
  Requires PHP: 5.4
8
- Stable tag: 4.15
9
  License: GPLv3
10
 
11
  WP RSS Aggregator is the original & most popular WordPress solution for importing RSS feeds, auto-blogging, content curation & aggregation.
@@ -259,23 +259,22 @@ Our complete Knowledge Base with FAQs can be found [here](https://kb.wprssaggreg
259
 
260
  == Changelog ==
261
 
262
- = 4.15 (2019-07-16) =
263
 
264
  **Added**
265
- - New error handling for catchable PHP7 `Throwable` errors.
266
- - New option to enable feed caching for better performance.
267
- - New option to import source name and URL for each item individually.
268
- - The custom feed now includes source info for every item.
269
 
270
  **Changed**
271
- - Improved some exception messages to better indicate the cause of certain problems.
272
- - Re-organized settings into multiple tabs.
273
- - Added the current site URL to the custom feed URL option's label.
274
 
275
  **Fixed**
276
- - Feed sources had image importing wrongly enabled by default.
277
- - Downloading the debug log triggered an error.
278
- - The custom feed self URL ignored the settings and was incorrect.
279
- - Items in the custom feed had a missing `rel` attribute for their `<link>` element.
280
- - Fixed placement of WordPress notices on the Templates List and Edit page.
281
- - Fixed WordPress notices disappearing after moving between Templates list and edit page.
 
 
 
5
  Requires at least: 4.0 or higher
6
  Tested up to: 5.2.2
7
  Requires PHP: 5.4
8
+ Stable tag: 4.15.1
9
  License: GPLv3
10
 
11
  WP RSS Aggregator is the original & most popular WordPress solution for importing RSS feeds, auto-blogging, content curation & aggregation.
259
 
260
  == Changelog ==
261
 
262
+ = 4.15.1 (2019-08-14) =
263
 
264
  **Added**
265
+ - New link to the custom feed in the "Custom Feed" settings page.
 
 
 
266
 
267
  **Changed**
268
+ - Updated the logging system to no longer cause VaultPress to trigger false positive warnings.
269
+ - The date format in the custom feed now uses the "RFC3339 Extended" format.
 
270
 
271
  **Fixed**
272
+ - Items with the same title were not being imported even when "Unique titles only" was turned off.
273
+ - Items with future dates where marked as "scheduled" by WordPress.
274
+ - The custom feed's "Content-Type" header was set for RSS 2.0 instead of Atom.
275
+ - Imported images were not being deleted from the media library when the imported item is deleted.
276
+ - PHP notice for "undefined index enclosure" when a feed cannot be fetched.
277
+ - Deprecation notice on PHP 7.2 or later for "each" function.
278
+ - Warnings when the `wprss_log` function is used incorrectly.
279
+ - PHP notice for "property of non-object" when using YoastSEO.
280
+ - After using the Templates add-on, images would continue to be imported after the add-on was deactivated.
src/Database/TableInterface.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace RebelCode\Wpra\Core\Database;
4
 
5
  use RebelCode\Wpra\Core\Data\Collections\CollectionInterface;
 
6
 
7
  /**
8
  * Interface for objects that represent a database table.
@@ -50,6 +51,8 @@ interface TableInterface extends CollectionInterface
50
  * Creates the table if it does not exist in the database.
51
  *
52
  * @since 4.13
 
 
53
  */
54
  public function create();
55
 
@@ -57,6 +60,8 @@ interface TableInterface extends CollectionInterface
57
  * Drops the table if it exists in the database.
58
  *
59
  * @since 4.13
 
 
60
  */
61
  public function drop();
62
  }
3
  namespace RebelCode\Wpra\Core\Database;
4
 
5
  use RebelCode\Wpra\Core\Data\Collections\CollectionInterface;
6
+ use RuntimeException;
7
 
8
  /**
9
  * Interface for objects that represent a database table.
51
  * Creates the table if it does not exist in the database.
52
  *
53
  * @since 4.13
54
+ *
55
+ * @throws RuntimeException If the table could not be created.
56
  */
57
  public function create();
58
 
60
  * Drops the table if it exists in the database.
61
  *
62
  * @since 4.13
63
+ *
64
+ * @throws RuntimeException If the table could not be dropped.
65
  */
66
  public function drop();
67
  }
src/Entities/Feeds/Sources/WpPostFeedSource.php CHANGED
@@ -8,7 +8,6 @@ use RebelCode\Wpra\Core\Data\ArrayDataSet;
8
  use RebelCode\Wpra\Core\Data\DataSetInterface;
9
  use RebelCode\Wpra\Core\Data\MergedDataSet;
10
  use RebelCode\Wpra\Core\Data\Wp\WpCptDataSet;
11
- use SimplePie;
12
  use WP_Post;
13
 
14
  /**
@@ -62,14 +61,6 @@ class WpPostFeedSource extends WpCptDataSet
62
  $meta = parent::createMetaDataSet($postOrId);
63
  $url = isset($meta['url']) ? $meta['url'] : '';
64
 
65
- if (!isset($meta['site_url']) && ($feed = wprss_fetch_feed($url)) instanceof SimplePie) {
66
- $meta['site_url'] = $feed->get_permalink();
67
- $meta['feed_image'] = $feed->get_image_url();
68
- } else {
69
- $meta['site_url'] = $url;
70
- $meta['feed_image'] = '';
71
- }
72
-
73
  $defaults = $this->getDefaultMetaData($meta);
74
  $fullMeta = new MergedDataSet($meta, $defaults);
75
 
8
  use RebelCode\Wpra\Core\Data\DataSetInterface;
9
  use RebelCode\Wpra\Core\Data\MergedDataSet;
10
  use RebelCode\Wpra\Core\Data\Wp\WpCptDataSet;
 
11
  use WP_Post;
12
 
13
  /**
61
  $meta = parent::createMetaDataSet($postOrId);
62
  $url = isset($meta['url']) ? $meta['url'] : '';
63
 
 
 
 
 
 
 
 
 
64
  $defaults = $this->getDefaultMetaData($meta);
65
  $fullMeta = new MergedDataSet($meta, $defaults);
66
 
src/Handlers/CustomFeed/RenderCustomFeedHandler.php CHANGED
@@ -68,7 +68,7 @@ class RenderCustomFeedHandler
68
  $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
69
  header("$protocol 200 OK");
70
  // Send content header and start ATOM output
71
- header('Content-Type: application/rss+xml');
72
  // Disabling caching
73
  header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
74
  header('Pragma: no-cache'); // HTTP 1.0.
68
  $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
69
  header("$protocol 200 OK");
70
  // Send content header and start ATOM output
71
+ header('Content-Type: application/atom+xml');
72
  // Disabling caching
73
  header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
74
  header('Pragma: no-cache'); // HTTP 1.0.
src/Handlers/FeedTemplates/RenderTemplateContentHandler.php CHANGED
@@ -65,6 +65,10 @@ class RenderTemplateContentHandler
65
  {
66
  global $post;
67
 
 
 
 
 
68
  // Check if current post type is a WPRA template
69
  if ($post->post_type !== $this->cpt) {
70
  return $content;
65
  {
66
  global $post;
67
 
68
+ if (!$post) {
69
+ return $content;
70
+ }
71
+
72
  // Check if current post type is a WPRA template
73
  if ($post->post_type !== $this->cpt) {
74
  return $content;
src/Handlers/Images/DeleteImagesHandler.php CHANGED
@@ -36,18 +36,21 @@ class DeleteImagesHandler
36
  *
37
  * @since 4.14
38
  */
39
- public function __invoke($post_id)
40
  {
41
  try {
42
- $item = $this->importedItems[$post_id];
43
  } catch (Exception $e) {
44
- // Item is not imported by WPRA or does not exist
45
- return;
 
 
 
46
  }
47
 
48
  // Get the attachments
49
  $attachments = get_children([
50
- 'post_parent' => $item['id'],
51
  'post_type' => 'attachment',
52
  'post_mime_type' => 'image',
53
  ]);
36
  *
37
  * @since 4.14
38
  */
39
+ public function __invoke($postId)
40
  {
41
  try {
42
+ $item = $this->importedItems[$postId];
43
  } catch (Exception $e) {
44
+ // Item may not be imported by WPRA or does not exist
45
+ // Here we do a manual post meta check, just to be safe
46
+ if (get_post_meta($postId, 'wprss_feed_id', true) === '') {
47
+ return;
48
+ }
49
  }
50
 
51
  // Get the attachments
52
  $attachments = get_children([
53
+ 'post_parent' => $postId,
54
  'post_type' => 'attachment',
55
  'post_mime_type' => 'image',
56
  ]);
src/Logger/ConditionalLogger.php DELETED
@@ -1,54 +0,0 @@
1
- <?php
2
-
3
- namespace RebelCode\Wpra\Core\Logger;
4
-
5
- use Psr\Log\AbstractLogger;
6
- use Psr\Log\LoggerInterface;
7
-
8
- /**
9
- * A PSR-3 logger decorator that conditionally delegates to an inner logger.
10
- *
11
- * @since 4.14
12
- */
13
- class ConditionalLogger extends AbstractLogger
14
- {
15
- /**
16
- * @since 4.14
17
- *
18
- * @var LoggerInterface
19
- */
20
- protected $logger;
21
-
22
- /**
23
- * @since 4.14
24
- *
25
- * @var bool
26
- */
27
- protected $enabled;
28
-
29
- /**
30
- * Constructor.
31
- *
32
- * @since 4.14
33
- *
34
- * @param LoggerInterface $logger The inner logger instance.
35
- * @param bool $enabled The enabled flag: true to log using the inner logger, false to not log.
36
- */
37
- public function __construct(LoggerInterface $logger, $enabled)
38
- {
39
- $this->logger = $logger;
40
- $this->enabled = $enabled;
41
- }
42
-
43
- /**
44
- * @inheritdoc
45
- *
46
- * @since 4.14
47
- */
48
- public function log($level, $message, array $context = [])
49
- {
50
- if ($this->enabled) {
51
- $this->logger->log($level, $message, $context);
52
- }
53
- }
54
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/Logger/FeedLoggerInterface.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Logger;
4
+
5
+ use Psr\Log\LoggerInterface;
6
+
7
+ /**
8
+ * Interface for a logger that can log messages for specific feed sources.
9
+ *
10
+ * @since 4.15.1
11
+ */
12
+ interface FeedLoggerInterface extends LoggerInterface
13
+ {
14
+ /**
15
+ * Returns a copy of the logger instance that logs messages related to a particular feed source.
16
+ *
17
+ * @since 4.15.1
18
+ *
19
+ * @param int $feedId The ID of the feed source.
20
+ *
21
+ * @return LoggerInterface
22
+ */
23
+ public function forFeedSource($feedId);
24
+ }
src/Logger/ProblemLogger.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Logger;
4
+
5
+ use Psr\Log\NullLogger;
6
+
7
+ /**
8
+ * A special null logger that is used when an error occurs and the original logger cannot be used.
9
+ *
10
+ * @since 4.15
11
+ */
12
+ class ProblemLogger extends NullLogger implements LogReaderInterface, ClearableLoggerInterface
13
+ {
14
+ /**
15
+ * The error.
16
+ *
17
+ * @since 4.15
18
+ *
19
+ * @var string
20
+ */
21
+ public $error;
22
+
23
+ /**
24
+ * Constructor.
25
+ *
26
+ * @since [*next-version*]
27
+ *
28
+ * @param string $error The error.
29
+ */
30
+ public function __construct($error)
31
+ {
32
+ $this->error = $error;
33
+ }
34
+
35
+ /**
36
+ * @inheritdoc
37
+ *
38
+ * @since 4.15
39
+ */
40
+ public function clearLogs()
41
+ {
42
+ }
43
+
44
+ /**
45
+ * @inheritdoc
46
+ *
47
+ * @since 4.15
48
+ */
49
+ public function getLogs($num = null, $page = 1)
50
+ {
51
+ return [];
52
+ }
53
+ }
src/Logger/WpdbLogger.php CHANGED
@@ -55,12 +55,12 @@ class WpdbLogger extends AbstractLogger implements ClearableLoggerInterface, Log
55
  /**
56
  * A mapping of log properties to table column names.
57
  *
58
- * @see COL_DATE
59
- * @see COL_LEVEL
60
- * @see COL_MESSAGE
61
- *
62
  * @since 4.13
63
  *
 
 
 
 
64
  * @var string[]
65
  */
66
  protected $columns;
@@ -83,10 +83,12 @@ class WpdbLogger extends AbstractLogger implements ClearableLoggerInterface, Log
83
  * @param array $columns A mapping of log data keys to column names. The `COL_*` constants may be used to
84
  * map standard log properties and any non-standard properties may also be included
85
  * to insert fixed values with the $extra parameter.
86
- * @param array $extra A mapping of log properties and the values to insert for them. Beware that the
87
- * standard log properties MAY be overridden if specified as keys for this
88
- * parameter. All non-standard data is stored in the table as VARCHAR with a limit
89
- * of 100 characters.
 
 
90
  */
91
  public function __construct(TableInterface $table, $columns = [], $extra = [])
92
  {
@@ -203,16 +205,22 @@ class WpdbLogger extends AbstractLogger implements ClearableLoggerInterface, Log
203
  }
204
 
205
  if ($prop === static::LOG_LEVEL) {
206
- return $level;
207
  }
208
 
209
  if ($prop === static::LOG_MESSAGE) {
210
  return $message;
211
  }
212
 
213
- return isset($this->extra[$prop])
214
- ? $this->extra[$prop]
215
- : null;
 
 
 
 
 
 
216
  }
217
 
218
  /**
55
  /**
56
  * A mapping of log properties to table column names.
57
  *
 
 
 
 
58
  * @since 4.13
59
  *
60
+ * @see WpdbLogger::LOG_DATE
61
+ * @see WpdbLogger::LOG_LEVEL
62
+ * @see WpdbLogger::LOG_MESSAGE
63
+ *
64
  * @var string[]
65
  */
66
  protected $columns;
83
  * @param array $columns A mapping of log data keys to column names. The `COL_*` constants may be used to
84
  * map standard log properties and any non-standard properties may also be included
85
  * to insert fixed values with the $extra parameter.
86
+ * @param array $extra A mapping of log properties and the values to insert for them. The values may be
87
+ * functions, which will be invoked during insertion. The function will receive the
88
+ * level, message and this $extra argument array as arguments.
89
+ * Be aware that the standard log properties, "id", "level", "message" and "date",
90
+ * will be overridden if those keys are given in this array.All non-standard data
91
+ * is stored in the table as VARCHAR with a limit of 100 characters.
92
  */
93
  public function __construct(TableInterface $table, $columns = [], $extra = [])
94
  {
205
  }
206
 
207
  if ($prop === static::LOG_LEVEL) {
208
+ return (string) $level;
209
  }
210
 
211
  if ($prop === static::LOG_MESSAGE) {
212
  return $message;
213
  }
214
 
215
+ if (!isset($this->extra[$prop])) {
216
+ return null;
217
+ }
218
+
219
+ if (is_callable($this->extra[$prop])) {
220
+ return call_user_func_array($this->extra[$prop], [$level, $message, $this->extra]);
221
+ }
222
+
223
+ return $this->extra[$prop];
224
  }
225
 
226
  /**
src/Logger/WpraLogger.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Logger;
4
+
5
+ use RebelCode\Wpra\Core\Database\TableInterface;
6
+
7
+ /**
8
+ * A logger specific to WP RSS Aggregator.
9
+ *
10
+ * This logger is an extended version of the {@link WpdbLogger}, that stores the feed ID extra property internally.
11
+ * By implementing the {@link FeedLoggerInterface}, it is able to yield new instances that log messages spefific to
12
+ * that feed source.
13
+ *
14
+ * @since [*next-version*]
15
+ *
16
+ * @see WpdbLogger
17
+ * @see FeedLoggerInterface
18
+ */
19
+ class WpraLogger extends WpdbLogger implements FeedLoggerInterface
20
+ {
21
+ /**
22
+ * The key of the extra feed ID column in the logs table.
23
+ *
24
+ * @since [*next-version*]
25
+ */
26
+ CONST LOG_FEED_ID = 'feed_id';
27
+
28
+ /**
29
+ * The ID of the feed for which messages are being logged.
30
+ *
31
+ * @since [*next-version*]
32
+ *
33
+ * @var string
34
+ */
35
+ protected $feedId;
36
+
37
+ /**
38
+ * @inheritdoc
39
+ *
40
+ * @since [*next-version*]
41
+ */
42
+ public function __construct(TableInterface $table, $columns = [], $extra = [])
43
+ {
44
+ parent::__construct($table, $columns, $extra);
45
+
46
+ // Start without a feed ID
47
+ $this->feedId = '';
48
+ }
49
+
50
+ /**
51
+ * @inheritdoc
52
+ *
53
+ * @since [*next-version*]
54
+ */
55
+ public function forFeedSource($feedId)
56
+ {
57
+ $instance = clone $this;
58
+ $instance->setFeedId($feedId);
59
+
60
+ return $instance;
61
+ }
62
+
63
+ /**
64
+ * Sets the ID of the feed source for which messages will be logged by this instance.
65
+ *
66
+ * Also updates the callback for the extra feed ID table column.
67
+ *
68
+ * @since [*next-version*]
69
+ *
70
+ * @param int $feedId The ID of the feed source.
71
+ */
72
+ protected function setFeedId($feedId)
73
+ {
74
+ $this->feedId = $feedId;
75
+
76
+ // The feed ID extra prop will lazily evaluate to the feed ID in this instance
77
+ $this->extra[static::LOG_FEED_ID] = function () {
78
+ return $this->feedId;
79
+ };
80
+ }
81
+ }
src/Modules/ImagesModule.php CHANGED
@@ -14,8 +14,6 @@ use RebelCode\Wpra\Core\Handlers\RegisterMetaBoxHandler;
14
  use RebelCode\Wpra\Core\Handlers\RenderTemplateHandler;
15
  use RebelCode\Wpra\Core\Importer\Images\FbImageContainer;
16
  use RebelCode\Wpra\Core\Importer\Images\ImageContainer;
17
- use RebelCode\Wpra\Core\Logger\ConditionalLogger;
18
- use RebelCode\Wpra\Core\Logger\FeedLoggerDataSet;
19
  use WPRSS_Help;
20
 
21
  /**
@@ -55,55 +53,15 @@ class ImagesModule implements ModuleInterface
55
  * @since 4.14
56
  */
57
  'wpra/images/logging/logger' => function (ContainerInterface $c) {
 
 
 
 
 
58
  // Get the original logger from WPRA's logger module, if available
59
- $logger = $c->has('wpra/logging/logger')
60
  ? $c->get('wpra/logging/logger')
61
  : new NullLogger();
62
-
63
- // Get the decorator
64
- $decorator = $c->get('wpra/images/logging/decorator');
65
-
66
- // Decorate the original logger and return it
67
- return $decorator($logger);
68
- },
69
- /*
70
- * The decorator for decorating other loggers for image import logging.
71
- *
72
- * @since 4.14
73
- */
74
- 'wpra/images/logging/decorator' => function (ContainerInterface $c) {
75
- return function($logger) use ($c) {
76
- return new ConditionalLogger($logger, $c->get('wpra/images/logging/enabled'));
77
- };
78
- },
79
- /*
80
- * The data set that contains the image import logger instances for each feed source.
81
- *
82
- * @since 4.14
83
- */
84
- 'wpra/images/logging/feed_logger_dataset' => function (ContainerInterface $c) {
85
- return new FeedLoggerDataSet($c->get('wpra/images/logging/feed_logger_factory'));
86
- },
87
- /*
88
- * The factory that creates image importing logger instances for specific feeds.
89
- *
90
- * @since 4.14
91
- */
92
- 'wpra/images/logging/feed_logger_factory' => function (ContainerInterface $c) {
93
- $factory = $c->has('wpra/logging/feed_logger_factory')
94
- ? $c->get('wpra/logging/feed_logger_factory')
95
- : null;
96
-
97
- if ($factory === null) {
98
- return $c->get('wpra/images/logging/logger');
99
- }
100
-
101
- // Get the decorator
102
- $decorator = $c->get('wpra/images/logging/decorator');
103
-
104
- return function($feedId) use ($c, $factory, $decorator) {
105
- return $decorator($factory($feedId));
106
- };
107
  },
108
  /*
109
  * Whether the feature to import featured images is enabled or not.
@@ -402,7 +360,7 @@ class ImagesModule implements ModuleInterface
402
  }
403
 
404
  // Show the developer images meta box for feed items, if the developer filter is enabled
405
- if (apply_filters('wpra_dev_mode', false) === true) {
406
  add_action('add_meta_boxes', $c->get('wpra/images/items/dev_meta_box/handler'));
407
  }
408
 
14
  use RebelCode\Wpra\Core\Handlers\RenderTemplateHandler;
15
  use RebelCode\Wpra\Core\Importer\Images\FbImageContainer;
16
  use RebelCode\Wpra\Core\Importer\Images\ImageContainer;
 
 
17
  use WPRSS_Help;
18
 
19
  /**
53
  * @since 4.14
54
  */
55
  'wpra/images/logging/logger' => function (ContainerInterface $c) {
56
+ // If image logging is disabled, return a null logger
57
+ if (!$c->get('wpra/images/logging/enabled')) {
58
+ return new NullLogger();
59
+ }
60
+
61
  // Get the original logger from WPRA's logger module, if available
62
+ return $c->has('wpra/logging/logger')
63
  ? $c->get('wpra/logging/logger')
64
  : new NullLogger();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  },
66
  /*
67
  * Whether the feature to import featured images is enabled or not.
360
  }
361
 
362
  // Show the developer images meta box for feed items, if the developer filter is enabled
363
+ if (wpra_is_dev_mode()) {
364
  add_action('add_meta_boxes', $c->get('wpra/images/items/dev_meta_box/handler'));
365
  }
366
 
src/Modules/LoggerModule.php CHANGED
@@ -2,7 +2,9 @@
2
 
3
  namespace RebelCode\Wpra\Core\Modules;
4
 
 
5
  use Psr\Container\ContainerInterface;
 
6
  use RebelCode\Wpra\Core\Database\NullTable;
7
  use RebelCode\Wpra\Core\Database\WpdbTable;
8
  use RebelCode\Wpra\Core\Handlers\Logger\ClearLogHandler;
@@ -11,9 +13,9 @@ use RebelCode\Wpra\Core\Handlers\Logger\RenderLogHandler;
11
  use RebelCode\Wpra\Core\Handlers\Logger\SaveLogOptionsHandler;
12
  use RebelCode\Wpra\Core\Handlers\Logger\TruncateLogsCronHandler;
13
  use RebelCode\Wpra\Core\Handlers\ScheduleCronJobHandler;
14
- use RebelCode\Wpra\Core\Logger\ConditionalLogger;
15
- use RebelCode\Wpra\Core\Logger\FeedLoggerDataSet;
16
  use RebelCode\Wpra\Core\Logger\WpdbLogger;
 
17
 
18
  /**
19
  * A module that adds a logger to WP RSS Aggregator.
@@ -44,22 +46,25 @@ class LoggerModule implements ModuleInterface
44
  * @since 4.13
45
  */
46
  'wpra/logging/logger' => function (ContainerInterface $c) {
47
- return new ConditionalLogger(
48
- $c->get('wpra/logging/wpdb_logger'),
49
- $c->get('wpra/logging/enabled')
50
- );
51
  },
52
  /*
53
  * The WPDB logger instance.
54
  *
55
- * @since 4.13
56
  */
57
- 'wpra/logging/wpdb_logger' => function (ContainerInterface $c) {
58
- return new WpdbLogger(
59
- $c->get('wpra/logging/log_table'),
60
- $c->get('wpra/logging/log_table_columns'),
61
- $c->get('wpra/logging/log_table_extra')
62
- );
 
 
 
 
63
  },
64
  /*
65
  * The log reader instance.
@@ -67,7 +72,7 @@ class LoggerModule implements ModuleInterface
67
  * @since 4.14
68
  */
69
  'wpra/logging/reader' => function (ContainerInterface $c) {
70
- return $c->get('wpra/logging/wpdb_logger');
71
  },
72
  /*
73
  * The log clearer instance.
@@ -75,7 +80,7 @@ class LoggerModule implements ModuleInterface
75
  * @since 4.14
76
  */
77
  'wpra/logging/clearer' => function (ContainerInterface $c) {
78
- return $c->get('wpra/logging/wpdb_logger');
79
  },
80
  /*
81
  * The table where logs are stored.
@@ -146,34 +151,7 @@ class LoggerModule implements ModuleInterface
146
  * @since 4.13
147
  */
148
  'wpra/logging/log_table_extra' => function () {
149
- return [
150
- 'feed_id' => '',
151
- ];
152
- },
153
- /*
154
- * The data set that contains the logger instances for each feed source.
155
- *
156
- * @since 4.13
157
- */
158
- 'wpra/logging/feed_logger_dataset' => function (ContainerInterface $c) {
159
- return new FeedLoggerDataSet($c->get('wpra/logging/feed_logger_factory'));
160
- },
161
- /*
162
- * The factory that creates logger instances for specific feeds.
163
- *
164
- * @since 4.13
165
- */
166
- 'wpra/logging/feed_logger_factory' => function (ContainerInterface $c) {
167
- return function ($feedId) use ($c) {
168
- return new ConditionalLogger(
169
- new WpdbLogger(
170
- $c->get('wpra/logging/log_table'),
171
- $c->get('wpra/logging/log_table_columns'),
172
- ['feed_id' => $feedId]
173
- ),
174
- $c->get('wpra/logging/enabled')
175
- );
176
- };
177
  },
178
  /*
179
  * The scheduler for the log truncation cron job.
2
 
3
  namespace RebelCode\Wpra\Core\Modules;
4
 
5
+ use Exception;
6
  use Psr\Container\ContainerInterface;
7
+ use Psr\Log\NullLogger;
8
  use RebelCode\Wpra\Core\Database\NullTable;
9
  use RebelCode\Wpra\Core\Database\WpdbTable;
10
  use RebelCode\Wpra\Core\Handlers\Logger\ClearLogHandler;
13
  use RebelCode\Wpra\Core\Handlers\Logger\SaveLogOptionsHandler;
14
  use RebelCode\Wpra\Core\Handlers\Logger\TruncateLogsCronHandler;
15
  use RebelCode\Wpra\Core\Handlers\ScheduleCronJobHandler;
16
+ use RebelCode\Wpra\Core\Logger\ProblemLogger;
 
17
  use RebelCode\Wpra\Core\Logger\WpdbLogger;
18
+ use RebelCode\Wpra\Core\Logger\WpraLogger;
19
 
20
  /**
21
  * A module that adds a logger to WP RSS Aggregator.
46
  * @since 4.13
47
  */
48
  'wpra/logging/logger' => function (ContainerInterface $c) {
49
+ return $c->get('wpra/logging/enabled')
50
+ ? $c->get('wpra/logging/main_logger')
51
+ : new NullLogger();
 
52
  },
53
  /*
54
  * The WPDB logger instance.
55
  *
56
+ * @since 4.15.1
57
  */
58
+ 'wpra/logging/main_logger' => function (ContainerInterface $c) {
59
+ try {
60
+ return new WpraLogger(
61
+ $c->get('wpra/logging/log_table'),
62
+ $c->get('wpra/logging/log_table_columns'),
63
+ $c->get('wpra/logging/log_table_extra')
64
+ );
65
+ } catch (Exception $exception) {
66
+ return new ProblemLogger($exception->getMessage());
67
+ }
68
  },
69
  /*
70
  * The log reader instance.
72
  * @since 4.14
73
  */
74
  'wpra/logging/reader' => function (ContainerInterface $c) {
75
+ return $c->get('wpra/logging/main_logger');
76
  },
77
  /*
78
  * The log clearer instance.
80
  * @since 4.14
81
  */
82
  'wpra/logging/clearer' => function (ContainerInterface $c) {
83
+ return $c->get('wpra/logging/main_logger');
84
  },
85
  /*
86
  * The table where logs are stored.
151
  * @since 4.13
152
  */
153
  'wpra/logging/log_table_extra' => function () {
154
+ return [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  },
156
  /*
157
  * The scheduler for the log truncation cron job.
src/Modules/LoremModule.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Modules;
4
+
5
+ use Psr\Container\ContainerInterface;
6
+
7
+ /**
8
+ * The module that adds Lorem's embeds in WP RSS Aggregator.
9
+ *
10
+ * @since 4.15.1
11
+ */
12
+ class LoremModule implements ModuleInterface
13
+ {
14
+ /**
15
+ * @inheritdoc
16
+ *
17
+ * @since 4.15.1
18
+ */
19
+ public function run(ContainerInterface $c)
20
+ {
21
+ // Registers the script on the admin-side
22
+ add_action('admin_init', $c->get('wpra/lorem/script/register_fn'));
23
+
24
+ // Enqueues the script on the admin-side
25
+ add_action('wprss_admin_exclusive_scripts_styles', $c->get('wpra/lorem/script/enqueue_fn'));
26
+
27
+ // Adds the Lorem embed on the "Help & Support" page, after the page title.
28
+ add_action('wpra/help_page/after_title', function () use ($c) {
29
+ echo $c->get('wpra/lorem/help_page/html');
30
+ });
31
+ }
32
+
33
+ /**
34
+ * @inheritdoc
35
+ *
36
+ * @since 4.15.1
37
+ */
38
+ public function getFactories()
39
+ {
40
+ return [
41
+ /*
42
+ * The key (or "handle") for the Lorem embed script.
43
+ *
44
+ * @since 4.15.1
45
+ */
46
+ 'wpra/lorem/script/key' => function (ContainerInterface $c) {
47
+ return 'lorem-embed-script';
48
+ },
49
+ /*
50
+ * The URL of the Lorem embed script.
51
+ *
52
+ * @since 4.15.1
53
+ */
54
+ 'wpra/lorem/script/url' => function (ContainerInterface $c) {
55
+ return 'https://embed.asklorem.com/load.js';
56
+ },
57
+ /*
58
+ * The function that registers the Lorem embed script.
59
+ *
60
+ * @since 4.15.1
61
+ */
62
+ 'wpra/lorem/script/register_fn' => function (ContainerInterface $c) {
63
+ $scriptKey = $c->get('wpra/lorem/script/key');
64
+ $scriptUrl = $c->get('wpra/lorem/script/url');
65
+
66
+ return function () use ($scriptKey, $scriptUrl) {
67
+ wp_register_script($scriptKey, $scriptUrl, [], null, true);
68
+ };
69
+ },
70
+ /*
71
+ * The function that enqueues the Lorem embed script.
72
+ *
73
+ * @since 4.15.1
74
+ */
75
+ 'wpra/lorem/script/enqueue_fn' => function (ContainerInterface $c) {
76
+ $scriptKey = $c->get('wpra/lorem/script/key');
77
+
78
+ return function () use ($scriptKey) {
79
+ return wp_enqueue_script($scriptKey);
80
+ };
81
+ },
82
+ /*
83
+ * The Lorem item to show on the "More features" page.
84
+ *
85
+ * @since 4.15.1
86
+ */
87
+ 'wpra/lorem/more_features_page/item' => function (ContainerInterface $c) {
88
+ return [
89
+ 'type' => 'lorem',
90
+ ];
91
+ },
92
+ /**
93
+ * The HTML to add on the "Help & Support" page.
94
+ *
95
+ * @since 4.15.1
96
+ */
97
+ 'wpra/lorem/help_page/html' => function (ContainerInterface $c) {
98
+ return '<div data-lorem-embed-id="rss-help-and-support"></div>';
99
+ },
100
+ ];
101
+ }
102
+
103
+ /**
104
+ * @inheritdoc
105
+ *
106
+ * @since 4.15.1
107
+ */
108
+ public function getExtensions()
109
+ {
110
+ return [
111
+ /*
112
+ * Extends the items on the "More Features" page to add the Lorem item.
113
+ *
114
+ * @since 4.15.1
115
+ */
116
+ 'wpra/upsell/items' => function (ContainerInterface $c, $items) {
117
+ $items[] = $c->get('wpra/lorem/more_features_page/item');
118
+
119
+ return $items;
120
+ },
121
+ ];
122
+ }
123
+ }
src/Modules/UpsellModule.php ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Modules;
4
+
5
+ use Psr\Container\ContainerInterface;
6
+ use RebelCode\Wpra\Core\Handlers\RegisterSubMenuPageHandler;
7
+ use RebelCode\Wpra\Core\Util\NullFunction;
8
+
9
+ /**
10
+ * The module that adds upselling of the addons and other services within WP RSS Aggregator's UI.
11
+ *
12
+ * Specifically, it adds UI elements such as the the "More Features" page, and the upselling of the addons in the
13
+ * "Share The Love" metabox in the Feed Source new/edit page.
14
+ *
15
+ * @since 4.15.1
16
+ */
17
+ class UpsellModule implements ModuleInterface
18
+ {
19
+ /**
20
+ * @inheritdoc
21
+ *
22
+ * @since 4.15.1
23
+ */
24
+ public function run(ContainerInterface $c)
25
+ {
26
+ // Registers the "More Features" menu and page
27
+ add_action(
28
+ 'admin_menu',
29
+ $c->get('wpra/upsell/more_features_page/register'),
30
+ $c->get('wpra/upsell/more_features_page/menu_pos')
31
+ );
32
+
33
+ // Add the add-ons list to the "Share the Love" metabox
34
+ add_action('wpra_share_the_love_metabox', $c->get('wpra/upsell/share_the_love/render_addon_list_fn'));
35
+ }
36
+
37
+ /**
38
+ * @inheritdoc
39
+ *
40
+ * @since 4.15.1
41
+ */
42
+ public function getFactories()
43
+ {
44
+ return array(
45
+ /*
46
+ * The items to upsell.
47
+ *
48
+ * @since 4.15.1
49
+ */
50
+ 'wpra/upsell/items' => function (ContainerInterface $c) {
51
+ $f2pBaseName = 'wp-rss-feed-to-post/wp-rss-feed-to-post.php';
52
+ $ftrBaseName = 'wp-rss-full-text-feeds/wp-rss-full-text.php';
53
+ $tmpBaseName = 'wp-rss-templates/wp-rss-templates.php';
54
+ $kwfBaseName = 'wp-rss-keyword-filtering/wp-rss-keyword-filtering.php';
55
+ $catBaseName = 'wp-rss-categories/wp-rss-categories.php';
56
+ $waiBaseName = 'wp-rss-wordai/wp-rss-wordai.php';
57
+ $spcBaseName = 'wp-rss-spinnerchief/wp-rss-spinnerchief.php';
58
+
59
+ return apply_filters('wprss_extra_addons', [
60
+ [
61
+ 'code' => 'ftp',
62
+ 'type' => 'add-on',
63
+ 'title' => 'Feed to Post',
64
+ 'desc' => __(
65
+ 'An advanced importer that lets you import RSS feed items as WordPress posts or any other custom post type. You can use it to populate a website in minutes (auto-blog). This is the most popular and feature-filled extension.',
66
+ 'wprss'
67
+ ),
68
+ 'url' => 'https://www.wprssaggregator.com/extension/feed-to-post/',
69
+ 'state' => wpra_get_plugin_state($f2pBaseName),
70
+ 'activateUrl' => wpra_get_activate_plugin_url($f2pBaseName),
71
+ ],
72
+ [
73
+ 'code' => 'ftr',
74
+ 'type' => 'add-on',
75
+ 'title' => 'Full Text RSS Feeds',
76
+ 'desc' => __(
77
+ 'An extension for Feed to Post that adds connectivity to our premium full text service, which allows you to import the full post content for an unlimited number of feed items per feed source, even when the feed itself doesn\'t provide it',
78
+ 'wprss'
79
+ ),
80
+ 'url' => 'https://www.wprssaggregator.com/extension/full-text-rss-feeds/',
81
+ 'state' => wpra_get_plugin_state($ftrBaseName),
82
+ 'activateUrl' => wpra_get_activate_plugin_url($ftrBaseName),
83
+ ],
84
+ [
85
+ 'code' => 'tmp',
86
+ 'type' => 'add-on',
87
+ 'title' => 'Templates',
88
+ 'desc' => __(
89
+ 'Premium templates to display images and excerpts in various ways. It includes a fully customisable grid template and a list template that includes excerpts & thumbnails, both of which will spruce up your site!',
90
+ 'wprss'
91
+ ),
92
+ 'url' => 'https://www.wprssaggregator.com/extension/templates/',
93
+ 'state' => wpra_get_plugin_state($tmpBaseName),
94
+ 'activateUrl' => wpra_get_activate_plugin_url($tmpBaseName),
95
+ ],
96
+ [
97
+ 'code' => 'kf',
98
+ 'type' => 'add-on',
99
+ 'title' => 'Keyword Filtering',
100
+ 'desc' => __(
101
+ 'Filters the feed items to be imported based on your own keywords, key phrases, or tags; you only get the items you\'re interested in. It is compatible with all other add-ons.',
102
+ 'wprss'
103
+ ),
104
+ 'url' => 'https://www.wprssaggregator.com/extension/keyword-filtering/',
105
+ 'state' => wpra_get_plugin_state($kwfBaseName),
106
+ 'activateUrl' => wpra_get_activate_plugin_url($kwfBaseName),
107
+ ],
108
+ [
109
+ 'code' => 'cat',
110
+ 'type' => 'add-on',
111
+ 'title' => 'Source Categories',
112
+ 'desc' => __(
113
+ 'Categorises your feed sources and allows you to display feed items from a particular category within your site using the shortcode parameters.',
114
+ 'wprss'
115
+ ),
116
+ 'url' => 'https://www.wprssaggregator.com/extension/categories/',
117
+ 'state' => wpra_get_plugin_state($catBaseName),
118
+ 'activateUrl' => wpra_get_activate_plugin_url($catBaseName),
119
+ ],
120
+ [
121
+ 'code' => 'wai',
122
+ 'type' => 'add-on',
123
+ 'title' => 'WordAi',
124
+ 'desc' => __(
125
+ 'An extension for Feed to Post that allows you to integrate the WordAi article spinner so that the imported content is both completely unique and completely readable.',
126
+ 'wprss'
127
+ ),
128
+ 'url' => 'https://www.wprssaggregator.com/extension/wordai/',
129
+ 'state' => wpra_get_plugin_state($waiBaseName),
130
+ 'activateUrl' => wpra_get_activate_plugin_url($waiBaseName),
131
+ ],
132
+ [
133
+ 'code' => 'spc',
134
+ 'type' => 'add-on',
135
+ 'title' => 'SpinnerChief',
136
+ 'desc' => __(
137
+ 'An extension for Feed to Post that allows you to integrate the SpinnerChief article spinner so that the imported content is both completely unique and completely readable.',
138
+ 'wprss'
139
+ ),
140
+ 'url' => 'https://www.wprssaggregator.com/extension/spinnerchief/',
141
+ 'state' => wpra_get_plugin_state($spcBaseName),
142
+ 'activateUrl' => wpra_get_activate_plugin_url($spcBaseName),
143
+ ],
144
+ ]
145
+ );
146
+ },
147
+ /*
148
+ * The function that registers the "More Features" page and menu.
149
+ *
150
+ * @since 4.15.1
151
+ */
152
+ 'wpra/upsell/more_features_page/register' => function (ContainerInterface $c) {
153
+ return new RegisterSubMenuPageHandler([
154
+ 'parent' => $c->get('wpra/upsell/more_features_page/parent'),
155
+ 'slug' => $c->get('wpra/upsell/more_features_page/slug'),
156
+ 'page_title' => $c->get('wpra/upsell/more_features_page/title'),
157
+ 'menu_label' => $c->get('wpra/upsell/more_features_page/menu_label'),
158
+ 'capability' => $c->get('wpra/upsell/more_features_page/capability'),
159
+ 'callback' => $c->get('wpra/upsell/more_features_page/render_fn'),
160
+ ]);
161
+ },
162
+ /*
163
+ * The slug of the "More Features"'s parent page.
164
+ *
165
+ * @since 4.15.1
166
+ */
167
+ 'wpra/upsell/more_features_page/parent' => function () {
168
+ return 'edit.php?post_type=wprss_feed';
169
+ },
170
+ /*
171
+ * The slug of the "More Features" page.
172
+ *
173
+ * @since 4.15.1
174
+ */
175
+ 'wpra/upsell/more_features_page/slug' => function () {
176
+ return 'wprss_addons';
177
+ },
178
+ /*
179
+ * The title for the "More Features" page.
180
+ *
181
+ * @since 4.15.1
182
+ */
183
+ 'wpra/upsell/more_features_page/title' => function () {
184
+ return __('More Features', 'wprss');
185
+ },
186
+ /*
187
+ * The required admin capability for viewing the "More Features" page.
188
+ *
189
+ * @since 4.15.1
190
+ */
191
+ 'wpra/upsell/more_features_page/capability' => function () {
192
+ return apply_filters('wprss_capability', 'manage_feed_settings');
193
+ },
194
+ /*
195
+ * The label for the "More Features" menu.
196
+ *
197
+ * @since 4.15.1
198
+ */
199
+ 'wpra/upsell/more_features_page/menu_label' => function (ContainerInterface $c) {
200
+ return $c->get('wpra/upsell/more_features_page/title') . $c->get('wpra/upsell/more_features_page/menu_icon');
201
+ },
202
+ /*
203
+ * The icon for the "More Features" menu.
204
+ *
205
+ * @since 4.15.1
206
+ */
207
+ 'wpra/upsell/more_features_page/menu_icon' => function () {
208
+ return '<span class="dashicons dashicons-star-filled wprss-more-features-glyph"></span>';
209
+ },
210
+ /*
211
+ * The position of the "More Features" menu.
212
+ *
213
+ * @since 4.15.1
214
+ */
215
+ 'wpra/upsell/more_features_page/menu_pos' => function () {
216
+ return 50;
217
+ },
218
+ /*
219
+ * The function to use for rendering the "More Features" page.
220
+ *
221
+ * @since 4.15.1
222
+ */
223
+ 'wpra/upsell/more_features_page/render_fn' => function (ContainerInterface $c) {
224
+ if (!$c->has('wpra/twig/collection')) {
225
+ return new NullFunction();
226
+ }
227
+
228
+ return function () use ($c) {
229
+ $collection = $c->get('wpra/twig/collection');
230
+ $template = $collection[$c->get('wpra/upsell/more_features_page/template')];
231
+ $items = $c->get('wpra/upsell/items');
232
+
233
+ echo $template->render(['items' => $items]);
234
+ };
235
+ },
236
+ /*
237
+ * The path to the template to use when rendering the "More Features" page.
238
+ *
239
+ * @since 4.15.1
240
+ */
241
+ 'wpra/upsell/more_features_page/template' => function () {
242
+ return 'admin/upsell/more-features-page/main.twig';
243
+ },
244
+ /*
245
+ * The path to the template to use when rendering the add-on list in the "Share the Love" metabox.
246
+ *
247
+ * @since 4.15.1
248
+ */
249
+ 'wpra/upsell/share_the_love/addon_list_template' => function () {
250
+ return 'admin/upsell/add-on-list.twig';
251
+ },
252
+ /*
253
+ * The function for rendering the add-on list in the "Share the Love" metabox.
254
+ *
255
+ * @since 4.15.1
256
+ */
257
+ 'wpra/upsell/share_the_love/render_addon_list_fn' => function (ContainerInterface $c) {
258
+ if (!$c->has('wpra/twig/collection')) {
259
+ return new NullFunction();
260
+ }
261
+
262
+ return function () use ($c) {
263
+ $collection = $c->get('wpra/twig/collection');
264
+ $template = $collection[$c->get('wpra/upsell/share_the_love/addon_list_template')];
265
+ $addons = array_filter($c->get('wpra/upsell/items'), function ($item) {
266
+ return $item['type'] === 'add-on';
267
+ });
268
+
269
+ echo $template->render(['addons' => $addons]);
270
+ };
271
+ },
272
+ );
273
+ }
274
+
275
+ /**
276
+ * @inheritdoc
277
+ *
278
+ * @since 4.15.1
279
+ */
280
+ public function getExtensions()
281
+ {
282
+ return [];
283
+ }
284
+ }
src/Util/NullFunction.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace RebelCode\Wpra\Core\Util;
4
+
5
+ /**
6
+ * A utility class for a null (no-op) function.
7
+ *
8
+ * @since 4.15.1
9
+ */
10
+ class NullFunction
11
+ {
12
+ /**
13
+ * @inheritdoc
14
+ *
15
+ * @since 4.15.1
16
+ */
17
+ public function __invoke()
18
+ {
19
+ }
20
+ }
templates/admin/upsell/add-on-list.twig ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <p><strong>{% trans 'Add functionality with our premium extensions:' %}</strong></p>
2
+ {% if addons|length > 0 %}
3
+ <ul class="add-on-list">
4
+ {% for addon in addons %}
5
+ <li class="add-on add-on-code-{{ addon.code }}">
6
+ <a href="{{ addon.url }}" target="_blank">{{ addon.title }}</a>
7
+ </li>
8
+ {% endfor %}
9
+ </ul>
10
+ {% endif %}
templates/admin/upsell/more-features-page/add-on.twig ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% set activeClass = item.isActive ? 'add-on-active' : '' %}
2
+ {% set codeClass = 'add-on-code-' ~ item.code %}
3
+
4
+
5
+ <div class="add-on wp-box {{ activeClass }} {{ codeClass }}">
6
+ <div class="inner">
7
+ <h3>
8
+ <a target="_blank" href="{{ item.url|e('html_attr') }}">
9
+ {{ item.title }}
10
+ </a>
11
+ </h3>
12
+ <p>{{ item.desc }}</p>
13
+ </div>
14
+ <div class="footer">
15
+ {% if item.state == 2 %}
16
+ <a class="button button-disabled">
17
+ <span class="wprss-sprite-tick"></span>
18
+ {% trans "Installed!" %}
19
+ </a>
20
+ {% elseif item.state == 1 %}
21
+ <a class="button" href="{{ item.activateUrl|raw }}">
22
+ {% trans "Activate" %}
23
+ </a>
24
+ {% else %}
25
+ <a target="_blank" href="{{ item.url }}" class="button">
26
+ {% autoescape 'html' %}
27
+ {% trans "Purchase & Install" %}
28
+ {% endautoescape %}
29
+ </a>
30
+ {% endif %}
31
+ </div>
32
+ </div>
templates/admin/upsell/more-features-page/lorem.twig ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <div data-lorem-embed-id="rss-add-ons" style="padding-bottom: 0px;" class="add-on wp-box lorem">
2
+ </div>
templates/admin/upsell/more-features-page/main.twig ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="wrap">
2
+ <h2>{% trans "More Features With Our Premium Add-Ons" %}</h2>
3
+ <p>
4
+ {% trans %}
5
+ The following addons are available to increase the functionality of the WP RSS Aggregator plugin.
6
+ {% endtrans %}
7
+ <p>
8
+ <p>
9
+ {% trans %}
10
+ Check out our pricing plans for bigger savings!
11
+ {% endtrans %}
12
+ </p>
13
+
14
+ <div id="add-ons" class="clearfix">
15
+ <div class="add-on-group clearfix">
16
+ {% for item in items %}
17
+ {{ include('admin/upsell/more-features-page/' ~ item.type ~ '.twig') }}
18
+ {% endfor %}
19
+ </div>
20
+ </div>
21
+ </div>
templates/custom-feed/entry.twig CHANGED
@@ -2,7 +2,7 @@
2
  <id>{{ item.permalink }}</id>
3
  <title type="html">{{ item.title }}</title>
4
  <link href="{{ item.permalink }}" rel="alternate" />
5
- <updated>{{ item.timestamp|date('Y-m-d\\TH:i:sP') }}</updated>
6
  <summary>{{ item.excerpt }}</summary>
7
  <content type="html">
8
  <![CDATA[{{ item.content|raw|close_tags }}]]>
2
  <id>{{ item.permalink }}</id>
3
  <title type="html">{{ item.title }}</title>
4
  <link href="{{ item.permalink }}" rel="alternate" />
5
+ <updated>{{ item.timestamp|date('Y-m-d\\TH:i:s.vP') }}</updated>
6
  <summary>{{ item.excerpt }}</summary>
7
  <content type="html">
8
  <![CDATA[{{ item.content|raw|close_tags }}]]>
templates/help-footer-js.php CHANGED
File without changes
templates/help-tooltip-content.php CHANGED
File without changes
templates/help-tooltip-handle.php CHANGED
File without changes
templates/wprss.css CHANGED
File without changes
uninstall.php CHANGED
File without changes
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
- return ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2::getLoader();
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit454b25077a61c37d434b6b16c544309d::getLoader();
vendor/composer/ClassLoader.php CHANGED
File without changes
vendor/composer/LICENSE CHANGED
File without changes
vendor/composer/autoload_classmap.php CHANGED
@@ -362,11 +362,13 @@ return array(
362
  'RebelCode\\Wpra\\Core\\Licensing\\Addon' => $baseDir . '/src/Licensing/Addon.php',
363
  'RebelCode\\Wpra\\Core\\Licensing\\LicenseStatus' => $baseDir . '/src/Licensing/LicenseStatus.php',
364
  'RebelCode\\Wpra\\Core\\Logger\\ClearableLoggerInterface' => $baseDir . '/src/Logger/ClearableLoggerInterface.php',
365
- 'RebelCode\\Wpra\\Core\\Logger\\ConditionalLogger' => $baseDir . '/src/Logger/ConditionalLogger.php',
366
  'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerDataSet' => $baseDir . '/src/Logger/FeedLoggerDataSet.php',
 
367
  'RebelCode\\Wpra\\Core\\Logger\\LogReaderInterface' => $baseDir . '/src/Logger/LogReaderInterface.php',
368
  'RebelCode\\Wpra\\Core\\Logger\\LoggerUtilsTrait' => $baseDir . '/src/Logger/LoggerUtilsTrait.php',
 
369
  'RebelCode\\Wpra\\Core\\Logger\\WpdbLogger' => $baseDir . '/src/Logger/WpdbLogger.php',
 
370
  'RebelCode\\Wpra\\Core\\ModularModule' => $baseDir . '/src/ModularModule.php',
371
  'RebelCode\\Wpra\\Core\\Modules\\AddonsModule' => $baseDir . '/src/Modules/AddonsModule.php',
372
  'RebelCode\\Wpra\\Core\\Modules\\AssetsModule' => $baseDir . '/src/Modules/AssetsModule.php',
@@ -384,11 +386,13 @@ return array(
384
  'RebelCode\\Wpra\\Core\\Modules\\ImporterModule' => $baseDir . '/src/Modules/ImporterModule.php',
385
  'RebelCode\\Wpra\\Core\\Modules\\LicensingModule' => $baseDir . '/src/Modules/LicensingModule.php',
386
  'RebelCode\\Wpra\\Core\\Modules\\LoggerModule' => $baseDir . '/src/Modules/LoggerModule.php',
 
387
  'RebelCode\\Wpra\\Core\\Modules\\ModuleInterface' => $baseDir . '/src/Modules/ModuleInterface.php',
388
  'RebelCode\\Wpra\\Core\\Modules\\ParsedownModule' => $baseDir . '/src/Modules/ParsedownModule.php',
389
  'RebelCode\\Wpra\\Core\\Modules\\RestApiModule' => $baseDir . '/src/Modules/RestApiModule.php',
390
  'RebelCode\\Wpra\\Core\\Modules\\SettingsModule' => $baseDir . '/src/Modules/SettingsModule.php',
391
  'RebelCode\\Wpra\\Core\\Modules\\TwigModule' => $baseDir . '/src/Modules/TwigModule.php',
 
392
  'RebelCode\\Wpra\\Core\\Modules\\WpModule' => $baseDir . '/src/Modules/WpModule.php',
393
  'RebelCode\\Wpra\\Core\\Plugin' => $baseDir . '/src/Plugin.php',
394
  'RebelCode\\Wpra\\Core\\Query\\AbstractWpQueryIterator' => $baseDir . '/src/Query/AbstractWpQueryIterator.php',
@@ -423,6 +427,7 @@ return array(
423
  'RebelCode\\Wpra\\Core\\Util\\IteratorDelegateTrait' => $baseDir . '/src/Util/IteratorDelegateTrait.php',
424
  'RebelCode\\Wpra\\Core\\Util\\MergedIterator' => $baseDir . '/src/Util/MergedIterator.php',
425
  'RebelCode\\Wpra\\Core\\Util\\NormalizeWpPostCapableTrait' => $baseDir . '/src/Util/NormalizeWpPostCapableTrait.php',
 
426
  'RebelCode\\Wpra\\Core\\Util\\PaginatedIterator' => $baseDir . '/src/Util/PaginatedIterator.php',
427
  'RebelCode\\Wpra\\Core\\Util\\ParseArgsWithSchemaCapableTrait' => $baseDir . '/src/Util/ParseArgsWithSchemaCapableTrait.php',
428
  'RebelCode\\Wpra\\Core\\Util\\SanitizeIdCommaListCapableTrait' => $baseDir . '/src/Util/SanitizeIdCommaListCapableTrait.php',
362
  'RebelCode\\Wpra\\Core\\Licensing\\Addon' => $baseDir . '/src/Licensing/Addon.php',
363
  'RebelCode\\Wpra\\Core\\Licensing\\LicenseStatus' => $baseDir . '/src/Licensing/LicenseStatus.php',
364
  'RebelCode\\Wpra\\Core\\Logger\\ClearableLoggerInterface' => $baseDir . '/src/Logger/ClearableLoggerInterface.php',
 
365
  'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerDataSet' => $baseDir . '/src/Logger/FeedLoggerDataSet.php',
366
+ 'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerInterface' => $baseDir . '/src/Logger/FeedLoggerInterface.php',
367
  'RebelCode\\Wpra\\Core\\Logger\\LogReaderInterface' => $baseDir . '/src/Logger/LogReaderInterface.php',
368
  'RebelCode\\Wpra\\Core\\Logger\\LoggerUtilsTrait' => $baseDir . '/src/Logger/LoggerUtilsTrait.php',
369
+ 'RebelCode\\Wpra\\Core\\Logger\\ProblemLogger' => $baseDir . '/src/Logger/ProblemLogger.php',
370
  'RebelCode\\Wpra\\Core\\Logger\\WpdbLogger' => $baseDir . '/src/Logger/WpdbLogger.php',
371
+ 'RebelCode\\Wpra\\Core\\Logger\\WpraLogger' => $baseDir . '/src/Logger/WpraLogger.php',
372
  'RebelCode\\Wpra\\Core\\ModularModule' => $baseDir . '/src/ModularModule.php',
373
  'RebelCode\\Wpra\\Core\\Modules\\AddonsModule' => $baseDir . '/src/Modules/AddonsModule.php',
374
  'RebelCode\\Wpra\\Core\\Modules\\AssetsModule' => $baseDir . '/src/Modules/AssetsModule.php',
386
  'RebelCode\\Wpra\\Core\\Modules\\ImporterModule' => $baseDir . '/src/Modules/ImporterModule.php',
387
  'RebelCode\\Wpra\\Core\\Modules\\LicensingModule' => $baseDir . '/src/Modules/LicensingModule.php',
388
  'RebelCode\\Wpra\\Core\\Modules\\LoggerModule' => $baseDir . '/src/Modules/LoggerModule.php',
389
+ 'RebelCode\\Wpra\\Core\\Modules\\LoremModule' => $baseDir . '/src/Modules/LoremModule.php',
390
  'RebelCode\\Wpra\\Core\\Modules\\ModuleInterface' => $baseDir . '/src/Modules/ModuleInterface.php',
391
  'RebelCode\\Wpra\\Core\\Modules\\ParsedownModule' => $baseDir . '/src/Modules/ParsedownModule.php',
392
  'RebelCode\\Wpra\\Core\\Modules\\RestApiModule' => $baseDir . '/src/Modules/RestApiModule.php',
393
  'RebelCode\\Wpra\\Core\\Modules\\SettingsModule' => $baseDir . '/src/Modules/SettingsModule.php',
394
  'RebelCode\\Wpra\\Core\\Modules\\TwigModule' => $baseDir . '/src/Modules/TwigModule.php',
395
+ 'RebelCode\\Wpra\\Core\\Modules\\UpsellModule' => $baseDir . '/src/Modules/UpsellModule.php',
396
  'RebelCode\\Wpra\\Core\\Modules\\WpModule' => $baseDir . '/src/Modules/WpModule.php',
397
  'RebelCode\\Wpra\\Core\\Plugin' => $baseDir . '/src/Plugin.php',
398
  'RebelCode\\Wpra\\Core\\Query\\AbstractWpQueryIterator' => $baseDir . '/src/Query/AbstractWpQueryIterator.php',
427
  'RebelCode\\Wpra\\Core\\Util\\IteratorDelegateTrait' => $baseDir . '/src/Util/IteratorDelegateTrait.php',
428
  'RebelCode\\Wpra\\Core\\Util\\MergedIterator' => $baseDir . '/src/Util/MergedIterator.php',
429
  'RebelCode\\Wpra\\Core\\Util\\NormalizeWpPostCapableTrait' => $baseDir . '/src/Util/NormalizeWpPostCapableTrait.php',
430
+ 'RebelCode\\Wpra\\Core\\Util\\NullFunction' => $baseDir . '/src/Util/NullFunction.php',
431
  'RebelCode\\Wpra\\Core\\Util\\PaginatedIterator' => $baseDir . '/src/Util/PaginatedIterator.php',
432
  'RebelCode\\Wpra\\Core\\Util\\ParseArgsWithSchemaCapableTrait' => $baseDir . '/src/Util/ParseArgsWithSchemaCapableTrait.php',
433
  'RebelCode\\Wpra\\Core\\Util\\SanitizeIdCommaListCapableTrait' => $baseDir . '/src/Util/SanitizeIdCommaListCapableTrait.php',
vendor/composer/autoload_namespaces.php CHANGED
File without changes
vendor/composer/autoload_psr4.php CHANGED
File without changes
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2
6
  {
7
  private static $loader;
8
 
@@ -19,15 +19,15 @@ class ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2
19
  return self::$loader;
20
  }
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
  require_once __DIR__ . '/autoload_static.php';
29
 
30
- call_user_func(\Composer\Autoload\ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::getInitializer($loader));
31
  } else {
32
  $map = require __DIR__ . '/autoload_namespaces.php';
33
  foreach ($map as $namespace => $path) {
@@ -48,19 +48,19 @@ class ComposerAutoloaderInit2a3a9815e4e052480cff9d68c42d23a2
48
  $loader->register(true);
49
 
50
  if ($useStaticLoader) {
51
- $includeFiles = Composer\Autoload\ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::$files;
52
  } else {
53
  $includeFiles = require __DIR__ . '/autoload_files.php';
54
  }
55
  foreach ($includeFiles as $fileIdentifier => $file) {
56
- composerRequire2a3a9815e4e052480cff9d68c42d23a2($fileIdentifier, $file);
57
  }
58
 
59
  return $loader;
60
  }
61
  }
62
 
63
- function composerRequire2a3a9815e4e052480cff9d68c42d23a2($fileIdentifier, $file)
64
  {
65
  if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
66
  require $file;
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit454b25077a61c37d434b6b16c544309d
6
  {
7
  private static $loader;
8
 
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit454b25077a61c37d434b6b16c544309d', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit454b25077a61c37d434b6b16c544309d', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
  require_once __DIR__ . '/autoload_static.php';
29
 
30
+ call_user_func(\Composer\Autoload\ComposerStaticInit454b25077a61c37d434b6b16c544309d::getInitializer($loader));
31
  } else {
32
  $map = require __DIR__ . '/autoload_namespaces.php';
33
  foreach ($map as $namespace => $path) {
48
  $loader->register(true);
49
 
50
  if ($useStaticLoader) {
51
+ $includeFiles = Composer\Autoload\ComposerStaticInit454b25077a61c37d434b6b16c544309d::$files;
52
  } else {
53
  $includeFiles = require __DIR__ . '/autoload_files.php';
54
  }
55
  foreach ($includeFiles as $fileIdentifier => $file) {
56
+ composerRequire454b25077a61c37d434b6b16c544309d($fileIdentifier, $file);
57
  }
58
 
59
  return $loader;
60
  }
61
  }
62
 
63
+ function composerRequire454b25077a61c37d434b6b16c544309d($fileIdentifier, $file)
64
  {
65
  if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
66
  require $file;
vendor/composer/autoload_static.php CHANGED
@@ -4,7 +4,7 @@
4
 
5
  namespace Composer\Autoload;
6
 
7
- class ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2
8
  {
9
  public static $files = array (
10
  '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
@@ -545,11 +545,13 @@ class ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2
545
  'RebelCode\\Wpra\\Core\\Licensing\\Addon' => __DIR__ . '/../..' . '/src/Licensing/Addon.php',
546
  'RebelCode\\Wpra\\Core\\Licensing\\LicenseStatus' => __DIR__ . '/../..' . '/src/Licensing/LicenseStatus.php',
547
  'RebelCode\\Wpra\\Core\\Logger\\ClearableLoggerInterface' => __DIR__ . '/../..' . '/src/Logger/ClearableLoggerInterface.php',
548
- 'RebelCode\\Wpra\\Core\\Logger\\ConditionalLogger' => __DIR__ . '/../..' . '/src/Logger/ConditionalLogger.php',
549
  'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerDataSet' => __DIR__ . '/../..' . '/src/Logger/FeedLoggerDataSet.php',
 
550
  'RebelCode\\Wpra\\Core\\Logger\\LogReaderInterface' => __DIR__ . '/../..' . '/src/Logger/LogReaderInterface.php',
551
  'RebelCode\\Wpra\\Core\\Logger\\LoggerUtilsTrait' => __DIR__ . '/../..' . '/src/Logger/LoggerUtilsTrait.php',
 
552
  'RebelCode\\Wpra\\Core\\Logger\\WpdbLogger' => __DIR__ . '/../..' . '/src/Logger/WpdbLogger.php',
 
553
  'RebelCode\\Wpra\\Core\\ModularModule' => __DIR__ . '/../..' . '/src/ModularModule.php',
554
  'RebelCode\\Wpra\\Core\\Modules\\AddonsModule' => __DIR__ . '/../..' . '/src/Modules/AddonsModule.php',
555
  'RebelCode\\Wpra\\Core\\Modules\\AssetsModule' => __DIR__ . '/../..' . '/src/Modules/AssetsModule.php',
@@ -567,11 +569,13 @@ class ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2
567
  'RebelCode\\Wpra\\Core\\Modules\\ImporterModule' => __DIR__ . '/../..' . '/src/Modules/ImporterModule.php',
568
  'RebelCode\\Wpra\\Core\\Modules\\LicensingModule' => __DIR__ . '/../..' . '/src/Modules/LicensingModule.php',
569
  'RebelCode\\Wpra\\Core\\Modules\\LoggerModule' => __DIR__ . '/../..' . '/src/Modules/LoggerModule.php',
 
570
  'RebelCode\\Wpra\\Core\\Modules\\ModuleInterface' => __DIR__ . '/../..' . '/src/Modules/ModuleInterface.php',
571
  'RebelCode\\Wpra\\Core\\Modules\\ParsedownModule' => __DIR__ . '/../..' . '/src/Modules/ParsedownModule.php',
572
  'RebelCode\\Wpra\\Core\\Modules\\RestApiModule' => __DIR__ . '/../..' . '/src/Modules/RestApiModule.php',
573
  'RebelCode\\Wpra\\Core\\Modules\\SettingsModule' => __DIR__ . '/../..' . '/src/Modules/SettingsModule.php',
574
  'RebelCode\\Wpra\\Core\\Modules\\TwigModule' => __DIR__ . '/../..' . '/src/Modules/TwigModule.php',
 
575
  'RebelCode\\Wpra\\Core\\Modules\\WpModule' => __DIR__ . '/../..' . '/src/Modules/WpModule.php',
576
  'RebelCode\\Wpra\\Core\\Plugin' => __DIR__ . '/../..' . '/src/Plugin.php',
577
  'RebelCode\\Wpra\\Core\\Query\\AbstractWpQueryIterator' => __DIR__ . '/../..' . '/src/Query/AbstractWpQueryIterator.php',
@@ -606,6 +610,7 @@ class ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2
606
  'RebelCode\\Wpra\\Core\\Util\\IteratorDelegateTrait' => __DIR__ . '/../..' . '/src/Util/IteratorDelegateTrait.php',
607
  'RebelCode\\Wpra\\Core\\Util\\MergedIterator' => __DIR__ . '/../..' . '/src/Util/MergedIterator.php',
608
  'RebelCode\\Wpra\\Core\\Util\\NormalizeWpPostCapableTrait' => __DIR__ . '/../..' . '/src/Util/NormalizeWpPostCapableTrait.php',
 
609
  'RebelCode\\Wpra\\Core\\Util\\PaginatedIterator' => __DIR__ . '/../..' . '/src/Util/PaginatedIterator.php',
610
  'RebelCode\\Wpra\\Core\\Util\\ParseArgsWithSchemaCapableTrait' => __DIR__ . '/../..' . '/src/Util/ParseArgsWithSchemaCapableTrait.php',
611
  'RebelCode\\Wpra\\Core\\Util\\SanitizeIdCommaListCapableTrait' => __DIR__ . '/../..' . '/src/Util/SanitizeIdCommaListCapableTrait.php',
@@ -1027,10 +1032,10 @@ class ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2
1027
  public static function getInitializer(ClassLoader $loader)
1028
  {
1029
  return \Closure::bind(function () use ($loader) {
1030
- $loader->prefixLengthsPsr4 = ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::$prefixLengthsPsr4;
1031
- $loader->prefixDirsPsr4 = ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::$prefixDirsPsr4;
1032
- $loader->prefixesPsr0 = ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::$prefixesPsr0;
1033
- $loader->classMap = ComposerStaticInit2a3a9815e4e052480cff9d68c42d23a2::$classMap;
1034
 
1035
  }, null, ClassLoader::class);
1036
  }
4
 
5
  namespace Composer\Autoload;
6
 
7
+ class ComposerStaticInit454b25077a61c37d434b6b16c544309d
8
  {
9
  public static $files = array (
10
  '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
545
  'RebelCode\\Wpra\\Core\\Licensing\\Addon' => __DIR__ . '/../..' . '/src/Licensing/Addon.php',
546
  'RebelCode\\Wpra\\Core\\Licensing\\LicenseStatus' => __DIR__ . '/../..' . '/src/Licensing/LicenseStatus.php',
547
  'RebelCode\\Wpra\\Core\\Logger\\ClearableLoggerInterface' => __DIR__ . '/../..' . '/src/Logger/ClearableLoggerInterface.php',
 
548
  'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerDataSet' => __DIR__ . '/../..' . '/src/Logger/FeedLoggerDataSet.php',
549
+ 'RebelCode\\Wpra\\Core\\Logger\\FeedLoggerInterface' => __DIR__ . '/../..' . '/src/Logger/FeedLoggerInterface.php',
550
  'RebelCode\\Wpra\\Core\\Logger\\LogReaderInterface' => __DIR__ . '/../..' . '/src/Logger/LogReaderInterface.php',
551
  'RebelCode\\Wpra\\Core\\Logger\\LoggerUtilsTrait' => __DIR__ . '/../..' . '/src/Logger/LoggerUtilsTrait.php',
552
+ 'RebelCode\\Wpra\\Core\\Logger\\ProblemLogger' => __DIR__ . '/../..' . '/src/Logger/ProblemLogger.php',
553
  'RebelCode\\Wpra\\Core\\Logger\\WpdbLogger' => __DIR__ . '/../..' . '/src/Logger/WpdbLogger.php',
554
+ 'RebelCode\\Wpra\\Core\\Logger\\WpraLogger' => __DIR__ . '/../..' . '/src/Logger/WpraLogger.php',
555
  'RebelCode\\Wpra\\Core\\ModularModule' => __DIR__ . '/../..' . '/src/ModularModule.php',
556
  'RebelCode\\Wpra\\Core\\Modules\\AddonsModule' => __DIR__ . '/../..' . '/src/Modules/AddonsModule.php',
557
  'RebelCode\\Wpra\\Core\\Modules\\AssetsModule' => __DIR__ . '/../..' . '/src/Modules/AssetsModule.php',
569
  'RebelCode\\Wpra\\Core\\Modules\\ImporterModule' => __DIR__ . '/../..' . '/src/Modules/ImporterModule.php',
570
  'RebelCode\\Wpra\\Core\\Modules\\LicensingModule' => __DIR__ . '/../..' . '/src/Modules/LicensingModule.php',
571
  'RebelCode\\Wpra\\Core\\Modules\\LoggerModule' => __DIR__ . '/../..' . '/src/Modules/LoggerModule.php',
572
+ 'RebelCode\\Wpra\\Core\\Modules\\LoremModule' => __DIR__ . '/../..' . '/src/Modules/LoremModule.php',
573
  'RebelCode\\Wpra\\Core\\Modules\\ModuleInterface' => __DIR__ . '/../..' . '/src/Modules/ModuleInterface.php',
574
  'RebelCode\\Wpra\\Core\\Modules\\ParsedownModule' => __DIR__ . '/../..' . '/src/Modules/ParsedownModule.php',
575
  'RebelCode\\Wpra\\Core\\Modules\\RestApiModule' => __DIR__ . '/../..' . '/src/Modules/RestApiModule.php',
576
  'RebelCode\\Wpra\\Core\\Modules\\SettingsModule' => __DIR__ . '/../..' . '/src/Modules/SettingsModule.php',
577
  'RebelCode\\Wpra\\Core\\Modules\\TwigModule' => __DIR__ . '/../..' . '/src/Modules/TwigModule.php',
578
+ 'RebelCode\\Wpra\\Core\\Modules\\UpsellModule' => __DIR__ . '/../..' . '/src/Modules/UpsellModule.php',
579
  'RebelCode\\Wpra\\Core\\Modules\\WpModule' => __DIR__ . '/../..' . '/src/Modules/WpModule.php',
580
  'RebelCode\\Wpra\\Core\\Plugin' => __DIR__ . '/../..' . '/src/Plugin.php',
581
  'RebelCode\\Wpra\\Core\\Query\\AbstractWpQueryIterator' => __DIR__ . '/../..' . '/src/Query/AbstractWpQueryIterator.php',
610
  'RebelCode\\Wpra\\Core\\Util\\IteratorDelegateTrait' => __DIR__ . '/../..' . '/src/Util/IteratorDelegateTrait.php',
611
  'RebelCode\\Wpra\\Core\\Util\\MergedIterator' => __DIR__ . '/../..' . '/src/Util/MergedIterator.php',
612
  'RebelCode\\Wpra\\Core\\Util\\NormalizeWpPostCapableTrait' => __DIR__ . '/../..' . '/src/Util/NormalizeWpPostCapableTrait.php',
613
+ 'RebelCode\\Wpra\\Core\\Util\\NullFunction' => __DIR__ . '/../..' . '/src/Util/NullFunction.php',
614
  'RebelCode\\Wpra\\Core\\Util\\PaginatedIterator' => __DIR__ . '/../..' . '/src/Util/PaginatedIterator.php',
615
  'RebelCode\\Wpra\\Core\\Util\\ParseArgsWithSchemaCapableTrait' => __DIR__ . '/../..' . '/src/Util/ParseArgsWithSchemaCapableTrait.php',
616
  'RebelCode\\Wpra\\Core\\Util\\SanitizeIdCommaListCapableTrait' => __DIR__ . '/../..' . '/src/Util/SanitizeIdCommaListCapableTrait.php',
1032
  public static function getInitializer(ClassLoader $loader)
1033
  {
1034
  return \Closure::bind(function () use ($loader) {
1035
+ $loader->prefixLengthsPsr4 = ComposerStaticInit454b25077a61c37d434b6b16c544309d::$prefixLengthsPsr4;
1036
+ $loader->prefixDirsPsr4 = ComposerStaticInit454b25077a61c37d434b6b16c544309d::$prefixDirsPsr4;
1037
+ $loader->prefixesPsr0 = ComposerStaticInit454b25077a61c37d434b6b16c544309d::$prefixesPsr0;
1038
+ $loader->classMap = ComposerStaticInit454b25077a61c37d434b6b16c544309d::$classMap;
1039
 
1040
  }, null, ClassLoader::class);
1041
  }
vendor/composer/installed.json CHANGED
File without changes
vendor/dhii/collections-abstract-base/composer.json CHANGED
File without changes
vendor/dhii/collections-abstract-base/composer.lock CHANGED
File without changes
vendor/dhii/collections-abstract-base/src/AbstractCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract-base/src/AbstractHasher.php CHANGED
File without changes
vendor/dhii/collections-abstract-base/src/AbstractIterableCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/composer.json CHANGED
File without changes
vendor/dhii/collections-abstract/composer.lock CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractCallbackCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractCallbackCollectionBase.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractCallbackIterator.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractGenericAccessibleCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractGenericCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractGenericMutableCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AbstractSearchableCollection.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/AppendIterator.php CHANGED
File without changes
vendor/dhii/collections-abstract/src/CallbackIterator.php CHANGED
File without changes
vendor/dhii/collections-interface/composer.json CHANGED
File without changes
vendor/dhii/collections-interface/composer.lock CHANGED
File without changes
vendor/dhii/collections-interface/src/AccessibleCollectionInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/CallbackIterableInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/CallbackIteratorInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/CollectionInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/MutableCollectionInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/SearchableCollectionInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/SequenceIteratorIteratorInterface.php CHANGED
File without changes
vendor/dhii/collections-interface/src/SetInterface.php CHANGED
File without changes
vendor/dhii/stats-abstract/composer.json CHANGED
File without changes
vendor/dhii/stats-abstract/composer.lock CHANGED
File without changes
vendor/dhii/stats-abstract/src/AbstractAggregatableCollection.php CHANGED
File without changes
vendor/dhii/stats-abstract/src/AbstractAggregator.php CHANGED
File without changes
vendor/dhii/stats-interface/composer.json CHANGED
File without changes
vendor/dhii/stats-interface/composer.lock CHANGED
File without changes
vendor/dhii/stats-interface/src/AggregatorInterface.php CHANGED
File without changes
wp-rss-aggregator.php CHANGED
@@ -4,7 +4,7 @@
4
  * Plugin Name: WP RSS Aggregator
5
  * Plugin URI: https://www.wprssaggregator.com/#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpraplugin
6
  * Description: Imports and aggregates multiple RSS Feeds.
7
- * Version: 4.15
8
  * Author: RebelCode
9
  * Author URI: https://www.wprssaggregator.com
10
  * Text Domain: wprss
@@ -53,11 +53,13 @@ use RebelCode\Wpra\Core\Modules\ImagesModule;
53
  use RebelCode\Wpra\Core\Modules\ImporterModule;
54
  use RebelCode\Wpra\Core\Modules\LicensingModule;
55
  use RebelCode\Wpra\Core\Modules\LoggerModule;
 
56
  use RebelCode\Wpra\Core\Modules\ModuleInterface;
57
  use RebelCode\Wpra\Core\Modules\ParsedownModule;
58
  use RebelCode\Wpra\Core\Modules\RestApiModule;
59
  use RebelCode\Wpra\Core\Modules\SettingsModule;
60
  use RebelCode\Wpra\Core\Modules\TwigModule;
 
61
  use RebelCode\Wpra\Core\Modules\WpModule;
62
  use RebelCode\Wpra\Core\Plugin;
63
 
@@ -67,7 +69,7 @@ use RebelCode\Wpra\Core\Plugin;
67
 
68
  // Set the version number of the plugin.
69
  if( !defined( 'WPRSS_VERSION' ) )
70
- define( 'WPRSS_VERSION', '4.15' );
71
 
72
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
73
  define( 'WPRSS_WP_MIN_VERSION', '4.8' );
@@ -255,9 +257,6 @@ require_once ( WPRSS_INC . 'opml-importer.php' );
255
  /* Load the admin debugging page file */
256
  require_once ( WPRSS_INC . 'admin-debugging.php' );
257
 
258
- /* Load the addons page file */
259
- require_once ( WPRSS_INC . 'admin-addons.php' );
260
-
261
  /* Load the admin display-related functions */
262
  require_once ( WPRSS_INC . 'admin-display.php' );
263
 
@@ -398,6 +397,8 @@ function wpra_modules()
398
  'rest_api' => new RestApiModule(),
399
  'settings' => new SettingsModule(),
400
  'licensing' => new LicensingModule(),
 
 
401
  'logging' => new LoggerModule(),
402
  'i18n' => new I18nModule(),
403
  'twig' => new TwigModule(),
4
  * Plugin Name: WP RSS Aggregator
5
  * Plugin URI: https://www.wprssaggregator.com/#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpraplugin
6
  * Description: Imports and aggregates multiple RSS Feeds.
7
+ * Version: 4.15.1
8
  * Author: RebelCode
9
  * Author URI: https://www.wprssaggregator.com
10
  * Text Domain: wprss
53
  use RebelCode\Wpra\Core\Modules\ImporterModule;
54
  use RebelCode\Wpra\Core\Modules\LicensingModule;
55
  use RebelCode\Wpra\Core\Modules\LoggerModule;
56
+ use RebelCode\Wpra\Core\Modules\LoremModule;
57
  use RebelCode\Wpra\Core\Modules\ModuleInterface;
58
  use RebelCode\Wpra\Core\Modules\ParsedownModule;
59
  use RebelCode\Wpra\Core\Modules\RestApiModule;
60
  use RebelCode\Wpra\Core\Modules\SettingsModule;
61
  use RebelCode\Wpra\Core\Modules\TwigModule;
62
+ use RebelCode\Wpra\Core\Modules\UpsellModule;
63
  use RebelCode\Wpra\Core\Modules\WpModule;
64
  use RebelCode\Wpra\Core\Plugin;
65
 
69
 
70
  // Set the version number of the plugin.
71
  if( !defined( 'WPRSS_VERSION' ) )
72
+ define( 'WPRSS_VERSION', '4.15.1' );
73
 
74
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
75
  define( 'WPRSS_WP_MIN_VERSION', '4.8' );
257
  /* Load the admin debugging page file */
258
  require_once ( WPRSS_INC . 'admin-debugging.php' );
259
 
 
 
 
260
  /* Load the admin display-related functions */
261
  require_once ( WPRSS_INC . 'admin-display.php' );
262
 
397
  'rest_api' => new RestApiModule(),
398
  'settings' => new SettingsModule(),
399
  'licensing' => new LicensingModule(),
400
+ 'upsell' => new UpsellModule(),
401
+ // 'lorem' => new LoremModule(),
402
  'logging' => new LoggerModule(),
403
  'i18n' => new I18nModule(),
404
  'twig' => new TwigModule(),