ThirstyAffiliates Affiliate Link Manager - Version 3.0.1

Version Description

  • Bug Fix: Link Fixer overrides existing CSS classes on the link
  • Bug Fix: Delete all stats data of a link when its permanently deleted
  • Bug Fix: Minor code fixes on data migration ( from V2 to V3 )
  • Bug Fix: Properly escape destination urls
Download this release

Release Info

Developer jkohlbach
Plugin Icon 128x128 ThirstyAffiliates Affiliate Link Manager
Version 3.0.1
Comparing to
See all releases

Code changes from version 2.7.0 to 3.0.1

Files changed (124) hide show
  1. Abstracts/Abstract_Main_Plugin_Class.php +112 -0
  2. Abstracts/index.php +1 -0
  3. Helpers/Helper_Functions.php +474 -0
  4. Helpers/Plugin_Constants.php +157 -0
  5. Helpers/index.php +1 -0
  6. Interfaces/Activatable_Interface.php +22 -0
  7. Interfaces/Initiable_Interface.php +22 -0
  8. Interfaces/Model_Interface.php +22 -0
  9. Interfaces/index.php +1 -0
  10. Models/Affiliate_Link.php +597 -0
  11. Models/Affiliate_Link_Attachment.php +323 -0
  12. Models/Affiliate_Links_CPT.php +626 -0
  13. Models/Bootstrap.php +424 -0
  14. Models/Guided_Tour.php +386 -0
  15. Models/Link_Fixer.php +233 -0
  16. Models/Link_Picker.php +483 -0
  17. Models/Marketing.php +403 -0
  18. Models/Migration.php +853 -0
  19. Models/Rewrites_Redirection.php +291 -0
  20. Models/Script_Loader.php +288 -0
  21. Models/Settings.php +1821 -0
  22. Models/Shortcodes.php +262 -0
  23. Models/Stats_Reporting.php +775 -0
  24. Models/index.php +1 -0
  25. ThirstyAddonPage.php +0 -147
  26. ThirstyAdminPage.php +0 -558
  27. ThirstyShortcode.php +0 -111
  28. css/admin/index.php +1 -0
  29. css/admin/ta-guided-tour.css +11 -0
  30. css/admin/ta-reports.css +241 -0
  31. css/admin/ta-settings.css +29 -0
  32. css/admin/tinymce/editor.css +38 -0
  33. css/index.php +1 -0
  34. css/lib/jquery-tiptip/jquery-tiptip.css +109 -0
  35. css/lib/select2/select2.css +484 -0
  36. css/lib/select2/select2.min.css +1 -0
  37. css/thirstystyle.css +0 -276
  38. images/admin-review-notice-logo.png +0 -0
  39. images/deleteImg.png +0 -0
  40. images/detailsbg.jpg +0 -0
  41. images/icon-aff.png +0 -0
  42. images/icon-images-disabled.png +0 -0
  43. images/icon-images.png +0 -0
  44. images/icon-link.png +0 -0
  45. images/icon-shortcode.png +0 -0
  46. images/index.php +1 -0
  47. images/license +0 -18
  48. images/lightgreytransparent.png +0 -0
  49. images/lightgreytransparentalt.png +0 -0
  50. images/linkpickerlogo.png +0 -0
  51. images/media-button.png +0 -0
  52. images/search-load-more.png +0 -0
  53. images/spinner-2x.gif +0 -0
  54. images/spinner.gif +0 -0
  55. images/thirsty-loader.gif +0 -0
  56. images/thirstylogo.png +0 -0
  57. images/white-grad.png +0 -0
  58. index.php +1 -0
  59. js/ThirstyLinkPicker.js +0 -168
  60. js/ThirstyQuickAddLinkPicker.js +0 -263
  61. js/app/advance_link_picker/dist/advance-link-picker.css +1 -0
  62. js/app/advance_link_picker/dist/advance-link-picker.js +24 -0
  63. js/app/affiliate_link_page/dist/affiliate-link-page.css +1 -0
  64. js/app/affiliate_link_page/dist/affiliate-link-page.js +24 -0
  65. js/app/import_export/dist/import-export.css +1 -0
  66. js/app/import_export/dist/import-export.js +25 -0
  67. js/app/migration/dist/migration.css +1 -0
  68. js/app/migration/dist/migration.js +25 -0
  69. js/app/quick_add_affiliate_link/dist/quick-add-affiliate-link.css +1 -0
  70. js/app/quick_add_affiliate_link/dist/quick-add-affiliate-link.js +24 -0
  71. js/app/ta-editor.js +129 -0
  72. js/app/ta-guided-tour.js +210 -0
  73. js/app/ta-reports.js +329 -0
  74. js/app/ta-review-request.js +38 -0
  75. js/app/ta-settings.js +65 -0
  76. js/app/ta.js +117 -0
  77. js/index.php +1 -0
  78. js/lib/chosen/chosen.jquery.js +1321 -0
  79. js/lib/chosen/chosen.jquery.min.js +2 -2
  80. js/lib/chosen/chosen.min.css +2 -2
  81. js/lib/flot/jquery.flot.min.js +8 -0
  82. js/lib/flot/jquery.flot.time.min.js +7 -0
  83. js/lib/jquery-tiptip/jquery.tipTip.js +191 -0
  84. js/lib/jquery-tiptip/jquery.tipTip.min.js +1 -0
  85. js/lib/select2/select2.js +5725 -0
  86. js/lib/select2/select2.min.js +3 -0
  87. js/lib/selectize/selectize.default.css +394 -0
  88. js/lib/selectize/selectize.min.js +3 -0
  89. js/lib/thirstymce/editor-plugin.js +367 -0
  90. js/lib/thirstymce/img/aff-new.gif +0 -0
  91. js/lib/thirstymce/img/aff.gif +0 -0
  92. js/thirstyPickerHelper.js +0 -43
  93. js/thirstyhelper.js +0 -390
  94. languages/index.php +1 -0
  95. languages/thirstyaffiliates-de_DE.mo +0 -0
  96. languages/thirstyaffiliates-de_DE.po +0 -587
  97. languages/thirstyaffiliates-es_ES.mo +0 -0
  98. languages/thirstyaffiliates-es_ES.po +0 -581
  99. languages/thirstyaffiliates-id_ID.mo +0 -0
  100. languages/thirstyaffiliates-id_ID.po +0 -547
  101. languages/thirstyaffiliates-it_IT.mo +0 -0
  102. languages/thirstyaffiliates-it_IT.po +0 -584
  103. languages/thirstyaffiliates-pt_BR.mo +0 -0
  104. languages/thirstyaffiliates-pt_BR.po +0 -584
  105. languages/thirstyaffiliates-ru_RU.mo +0 -0
  106. languages/thirstyaffiliates-ru_RU.po +0 -581
  107. languages/thirstyaffiliates.pot +1060 -0
  108. logs/.hgkeep +0 -0
  109. readme.txt +133 -56
  110. thirstyaffiliates.php +278 -2348
  111. thirstymce/editor_plugin.js +0 -1
  112. thirstymce/editor_plugin_src.js +0 -64
  113. thirstymce/img/aff-new.gif +0 -0
  114. thirstymce/img/aff.gif +0 -0
  115. uninstall.php +41 -0
  116. views/cpt/view-attach-images-metabox-single-image.php +8 -0
  117. views/cpt/view-attach-images-metabox.php +37 -0
  118. views/cpt/view-link-options-metabox.php +89 -0
  119. views/cpt/view-save-affiliate-link-metabox.php +15 -0
  120. views/cpt/view-urls-metabox.php +45 -0
  121. views/index.php +1 -0
  122. views/linkpicker/advance-link-picker.php +62 -0
  123. views/linkpicker/quick-add-affiliate-link.php +95 -0
  124. views/reports/link-performance-report.php +83 -0
Abstracts/Abstract_Main_Plugin_Class.php ADDED
@@ -0,0 +1,112 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Abstracts;
3
+
4
+ use ThirstyAffiliates\Interfaces\Model_Interface;
5
+
6
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
+
8
+ /**
9
+ * Abstract class that the main plugin class needs to extend.
10
+ *
11
+ * @since 3.0.0
12
+ */
13
+ abstract class Abstract_Main_Plugin_Class {
14
+
15
+ /*
16
+ |--------------------------------------------------------------------------
17
+ | Class Properties
18
+ |--------------------------------------------------------------------------
19
+ */
20
+
21
+ /**
22
+ * Property that houses an array of all the "regular models" of the plugin.
23
+ *
24
+ * @since 3.0.0
25
+ * @access protected
26
+ * @var array
27
+ */
28
+ protected $__all_models = array();
29
+
30
+ /**
31
+ * Property that houses an array of all "public regular models" of the plugin.
32
+ * Public models can be accessed and utilized by external entities via the main plugin class.
33
+ *
34
+ * @since 3.0.0
35
+ * @access public
36
+ * @var array
37
+ */
38
+ public $models = array();
39
+
40
+ /**
41
+ * Property that houses an array of all "public helper classes" of the plugin.
42
+ *
43
+ * @since 3.0.0
44
+ * @access public
45
+ * @var array
46
+ */
47
+ public $helpers = array();
48
+
49
+
50
+
51
+
52
+ /*
53
+ |--------------------------------------------------------------------------
54
+ | Class Methods
55
+ |--------------------------------------------------------------------------
56
+ */
57
+
58
+ /**
59
+ * Add a "regular model" to the main plugin class "all models" array.
60
+ *
61
+ * @since 3.0.0
62
+ * @access public
63
+ *
64
+ * @param Model_Interface $model Regular model.
65
+ */
66
+ public function add_to_all_plugin_models( Model_Interface $model ) {
67
+
68
+ $class_reflection = new \ReflectionClass( $model );
69
+ $class_name = $class_reflection->getShortName();
70
+
71
+ if ( !array_key_exists( $class_name , $this->__all_models ) )
72
+ $this->__all_models[ $class_name ] = $model;
73
+
74
+ }
75
+
76
+ /**
77
+ * Add a "regular model" to the main plugin class "public models" array.
78
+ *
79
+ * @since 3.0.0
80
+ * @access public
81
+ *
82
+ * @param Model_Interface $model Regular model.
83
+ */
84
+ public function add_to_public_models( Model_Interface $model ) {
85
+
86
+ $class_reflection = new \ReflectionClass( $model );
87
+ $class_name = $class_reflection->getShortName();
88
+
89
+ if ( !array_key_exists( $class_name , $this->models ) )
90
+ $this->models[ $class_name ] = $model;
91
+
92
+ }
93
+
94
+ /**
95
+ * Add a "helper class instance" to the main plugin class "public helpers" array.
96
+ *
97
+ * @since 3.0.0
98
+ * @access public
99
+ *
100
+ * @param object $helper Helper class instance.
101
+ */
102
+ public function add_to_public_helpers( $helper ) {
103
+
104
+ $class_reflection = new \ReflectionClass( $helper );
105
+ $class_name = $class_reflection->getShortName();
106
+
107
+ if ( !array_key_exists( $class_name , $this->helpers ) )
108
+ $this->helpers[ $class_name ] = $helper;
109
+
110
+ }
111
+
112
+ }
Abstracts/index.php ADDED
@@ -0,0 +1 @@
1
+ <?php /* Silence is Golden */ ?>
Helpers/Helper_Functions.php ADDED
@@ -0,0 +1,474 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Helpers;
3
+
4
+ use ThirstyAffiliates\Abstracts\Abstract_Main_Plugin_Class;
5
+
6
+ use ThirstyAffiliates\Models\Affiliate_Link;
7
+
8
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
9
+
10
+ /**
11
+ * Model that houses all the helper functions of the plugin.
12
+ *
13
+ * 3.0.0
14
+ */
15
+ class Helper_Functions {
16
+
17
+ /*
18
+ |--------------------------------------------------------------------------
19
+ | Class Properties
20
+ |--------------------------------------------------------------------------
21
+ */
22
+
23
+ /**
24
+ * Property that holds the single main instance of Helper_Functions.
25
+ *
26
+ * @since 3.0.0
27
+ * @access private
28
+ * @var Helper_Functions
29
+ */
30
+ private static $_instance;
31
+
32
+ /**
33
+ * Model that houses all the plugin constants.
34
+ *
35
+ * @since 3.0.0
36
+ * @access private
37
+ * @var Plugin_Constants
38
+ */
39
+ private $_constants;
40
+
41
+ /**
42
+ * Property that houses all the saved settings.
43
+ *
44
+ * @since 3.0.0
45
+ * @access private
46
+ */
47
+ private $_settings = array();
48
+
49
+
50
+
51
+
52
+ /*
53
+ |--------------------------------------------------------------------------
54
+ | Class Methods
55
+ |--------------------------------------------------------------------------
56
+ */
57
+
58
+ /**
59
+ * Class constructor.
60
+ *
61
+ * @since 3.0.0
62
+ * @access public
63
+ *
64
+ * @param Abstract_Main_Plugin_Class $main_plugin Main plugin object.
65
+ * @param Plugin_Constants $constants Plugin constants object.
66
+ */
67
+ public function __construct( Abstract_Main_Plugin_Class $main_plugin , Plugin_Constants $constants ) {
68
+
69
+ $this->_constants = $constants;
70
+
71
+ $main_plugin->add_to_public_helpers( $this );
72
+
73
+ }
74
+
75
+ /**
76
+ * Ensure that only one instance of this class is loaded or can be loaded ( Singleton Pattern ).
77
+ *
78
+ * @since 3.0.0
79
+ * @access public
80
+ *
81
+ * @param Abstract_Main_Plugin_Class $main_plugin Main plugin object.
82
+ * @param Plugin_Constants $constants Plugin constants object.
83
+ * @return Helper_Functions
84
+ */
85
+ public static function get_instance( Abstract_Main_Plugin_Class $main_plugin , Plugin_Constants $constants ) {
86
+
87
+ if ( !self::$_instance instanceof self )
88
+ self::$_instance = new self( $main_plugin , $constants );
89
+
90
+ return self::$_instance;
91
+
92
+ }
93
+
94
+
95
+
96
+
97
+ /*
98
+ |--------------------------------------------------------------------------
99
+ | Helper Functions
100
+ |--------------------------------------------------------------------------
101
+ */
102
+
103
+ /**
104
+ * Write data to plugin log file.
105
+ *
106
+ * @since 3.0.0
107
+ * @access public
108
+ *
109
+ * @param mixed Data to log.
110
+ */
111
+ public function write_debug_log( $log ) {
112
+
113
+ error_log( "\n[" . current_time( 'mysql' ) . "]\n" . $log . "\n--------------------------------------------------\n" , 3 , $this->_constants->LOGS_ROOT_PATH() . 'debug.log' );
114
+
115
+ }
116
+
117
+ /**
118
+ * Check if current user is authorized to manage the plugin on the backend.
119
+ *
120
+ * @since 3.0.0
121
+ * @access public
122
+ *
123
+ * @param WP_User $user WP_User object.
124
+ * @return boolean True if authorized, False otherwise.
125
+ */
126
+ public function current_user_authorized( $user = null ) {
127
+
128
+ // Array of roles allowed to access/utilize the plugin
129
+ $admin_roles = apply_filters( 'ucfw_admin_roles' , array( 'administrator' ) );
130
+
131
+ if ( is_null( $user ) )
132
+ $user = wp_get_current_user();
133
+
134
+ if ( $user->ID )
135
+ return count( array_intersect( ( array ) $user->roles , $admin_roles ) ) ? true : false;
136
+ else
137
+ return false;
138
+
139
+ }
140
+
141
+ /**
142
+ * Returns the timezone string for a site, even if it's set to a UTC offset
143
+ *
144
+ * Adapted from http://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155
145
+ *
146
+ * Reference:
147
+ * http://www.skyverge.com/blog/down-the-rabbit-hole-wordpress-and-timezones/
148
+ *
149
+ * @since 3.0.0
150
+ * @access public
151
+ *
152
+ * @return string Valid PHP timezone string
153
+ */
154
+ public function get_site_current_timezone() {
155
+
156
+ // if site timezone string exists, return it
157
+ if ( $timezone = get_option( 'timezone_string' ) )
158
+ return $timezone;
159
+
160
+ // get UTC offset, if it isn't set then return UTC
161
+ if ( 0 === ( $utc_offset = get_option( 'gmt_offset', 0 ) ) )
162
+ return 'UTC';
163
+
164
+ return $this->convert_utc_offset_to_timezone( $utc_offset );
165
+
166
+ }
167
+
168
+ /**
169
+ * Conver UTC offset to timezone.
170
+ *
171
+ * @since 1.2.0
172
+ * @access public
173
+ *
174
+ * @param float|int|string $utc_offset UTC offset.
175
+ * @return string valid PHP timezone string
176
+ */
177
+ public function convert_utc_offset_to_timezone( $utc_offset ) {
178
+
179
+ // adjust UTC offset from hours to seconds
180
+ $utc_offset *= 3600;
181
+
182
+ // attempt to guess the timezone string from the UTC offset
183
+ if ( $timezone = timezone_name_from_abbr( '' , $utc_offset , 0 ) )
184
+ return $timezone;
185
+
186
+ // last try, guess timezone string manually
187
+ $is_dst = date( 'I' );
188
+
189
+ foreach ( timezone_abbreviations_list() as $abbr )
190
+ foreach ( $abbr as $city )
191
+ if ( $city[ 'dst' ] == $is_dst && $city[ 'offset' ] == $utc_offset )
192
+ return $city[ 'timezone_id' ];
193
+
194
+ // fallback to UTC
195
+ return 'UTC';
196
+
197
+ }
198
+
199
+ /**
200
+ * Get all user roles.
201
+ *
202
+ * @since 3.0.0
203
+ * @access public
204
+ *
205
+ * @global WP_Roles $wp_roles Core class used to implement a user roles API.
206
+ *
207
+ * @return array Array of all site registered user roles. User role key as the key and value is user role text.
208
+ */
209
+ public function get_all_user_roles() {
210
+
211
+ global $wp_roles;
212
+ return $wp_roles->get_names();
213
+
214
+ }
215
+
216
+ /**
217
+ * Check validity of a save post action.
218
+ *
219
+ * @since 3.0.0
220
+ * @access private
221
+ *
222
+ * @param int $post_id Id of the coupon post.
223
+ * @param string $post_type Post type to check.
224
+ * @return bool True if valid save post action, False otherwise.
225
+ */
226
+ public function check_if_valid_save_post_action( $post_id , $post_type ) {
227
+
228
+ if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) || !current_user_can( 'edit_page' , $post_id ) || get_post_type() != $post_type || empty( $_POST ) )
229
+ return false;
230
+ else
231
+ return true;
232
+
233
+ }
234
+
235
+ /**
236
+ * Get user IP address.
237
+ *
238
+ * @since 3.0.0
239
+ * @access public
240
+ *
241
+ * @return string User's IP address.
242
+ */
243
+ public function get_user_ip_address() {
244
+
245
+ if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) )
246
+ $ip = $_SERVER['HTTP_CLIENT_IP'];
247
+ elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
248
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
249
+ else
250
+ $ip = $_SERVER['REMOTE_ADDR'];
251
+
252
+ return apply_filters( 'ta_get_user_ip_address', $ip );
253
+ }
254
+
255
+ /**
256
+ * Get the thirstylink slug set on the settings.
257
+ *
258
+ * @since 3.0.0
259
+ * @access public
260
+ *
261
+ * @return string $link_prefix Thirstyling link prefix.
262
+ */
263
+ public function get_thirstylink_link_prefix() {
264
+
265
+ $link_prefix = get_option( 'ta_link_prefix' , 'recommends' );
266
+
267
+ if ( $link_prefix === 'custom' )
268
+ $link_prefix = get_option( 'ta_link_prefix_custom' , 'recommends' );
269
+
270
+ return $link_prefix;
271
+ }
272
+
273
+ /**
274
+ * Get the affiliate link post default category slug.
275
+ *
276
+ * @since 3.0.0
277
+ * @access public
278
+ *
279
+ * @param int $link_id Affiliate Link ID.
280
+ * @return string Affiliate link default category slug.
281
+ */
282
+ public function get_default_category_slug( $link_id ) {
283
+
284
+ $terms = get_the_terms( $link_id , Plugin_Constants::AFFILIATE_LINKS_TAX );
285
+
286
+ if ( is_wp_error( $terms ) || empty( $terms ) )
287
+ return;
288
+
289
+ $link_cat_obj = array_shift( $terms );
290
+
291
+ return $link_cat_obj->slug;
292
+ }
293
+
294
+ /**
295
+ * Search affiliate links query
296
+ *
297
+ * @since 3.0.0
298
+ * @access public
299
+ *
300
+ * @param string $keyword Search keyword.
301
+ * @param int $paged WP_Query paged value.
302
+ * @param string $category Affiliate link category to search.
303
+ * @param array $exclude List of posts to be excluded.
304
+ * @return array List of affiliate link IDs.
305
+ */
306
+ public function search_affiliate_links_query( $keyword = '' , $paged = 1 , $category = '' , $exclude = array() ) {
307
+
308
+ $args = array(
309
+ 'post_type' => Plugin_Constants::AFFILIATE_LINKS_CPT,
310
+ 'post_status' => 'publish',
311
+ 's' => $keyword,
312
+ 'fields' => 'ids',
313
+ 'paged' => $paged,
314
+ 'post__not_in' => $exclude
315
+ );
316
+
317
+ if ( $category ) {
318
+
319
+ $args[ 'tax_query' ] = array(
320
+ array(
321
+ 'taxonomy' => Plugin_Constants::AFFILIATE_LINKS_TAX,
322
+ 'field' => 'slug',
323
+ 'terms' => $category
324
+ )
325
+ );
326
+ }
327
+
328
+ $query = new \WP_Query( $args );
329
+
330
+ return $query->posts;
331
+ }
332
+
333
+ /**
334
+ * Check if affiliate link needs to be uncloaked.
335
+ *
336
+ * @since 3.0.0
337
+ * @access public
338
+ *
339
+ * @param Affiliate_Link $thirstylink Thirsty affiliate link object.
340
+ * @return boolean Sets to true when affiliate link needs to be uncloaked.
341
+ */
342
+ public function is_uncloak_link( $thirstylink ) {
343
+
344
+ // check if global setting for uncloak link is enabled.
345
+ if ( get_option( 'ta_uncloak_link_per_link' ) !== 'yes' )
346
+ return;
347
+
348
+ // return (true) when uncloak link is enabled on a per post basis.
349
+ if ( $thirstylink->get_prop( 'uncloak_link' ) == 'yes' )
350
+ return true;
351
+ elseif ( $thirstylink->get_prop( 'uncloak_link' ) == 'no' )
352
+ return;
353
+
354
+ $uncloak_categories = maybe_unserialize( get_option( 'ta_category_to_uncloak' ) );
355
+ $link_categories = $thirstylink->get_prop( 'categories' );
356
+
357
+ // skip when there are no categories to uncloak (false)
358
+ if ( empty( $uncloak_categories ) || empty( $link_categories ) )
359
+ return;
360
+
361
+ foreach ( $link_categories as $category ) {
362
+
363
+ if ( in_array( $category->term_id , $uncloak_categories ) )
364
+ return true;
365
+ }
366
+
367
+ return;
368
+ }
369
+
370
+ /**
371
+ * Error log with a trace.
372
+ *
373
+ * @since 3.0.0
374
+ * @access public
375
+ */
376
+ public function ta_error_log( $msg ) {
377
+
378
+ $trace = debug_backtrace();
379
+ $caller = array_shift( $trace );
380
+
381
+ error_log( $msg . ' | Trace: ' . $caller[ 'file' ] . ' on line ' . $caller[ 'line' ] );
382
+
383
+ }
384
+
385
+ /**
386
+ * Utility function that determines if a plugin is active or not.
387
+ *
388
+ * @since 3.0.0
389
+ * @access public
390
+ *
391
+ * @param string $plugin_basename Plugin base name. Ex. woocommerce/woocommerce.php
392
+ * @return boolean True if active, false otherwise.
393
+ */
394
+ public function is_plugin_active( $plugin_basename ) {
395
+
396
+ // Makes sure the plugin is defined before trying to use it
397
+ if ( !function_exists( 'is_plugin_active' ) )
398
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
399
+
400
+ return is_plugin_active( $plugin_basename );
401
+
402
+ }
403
+
404
+ /**
405
+ * Send email.
406
+ *
407
+ * @since 3.0.0
408
+ * @access public
409
+ *
410
+ * @param array $recipients Array of recipients emails.
411
+ * @param string $subject Email subject.
412
+ * @param string $message Email message.
413
+ * @param array $headers Array of email headers.
414
+ * @param array $attachments Array of email attachments.
415
+ * @return boolean True if email sending is triggered, note it does not mean that the email was received, it just denotes that the email sending is triggered. False if email sending is not triggered.
416
+ */
417
+ public function send_email( $recipients , $subject , $message , $headers = array() , $attachments = array() ) {
418
+
419
+ $from_name = apply_filters( 'ta_email_from_name' , get_bloginfo( 'name' ) );
420
+ $from_email = apply_filters( 'ta_email_from_email' , get_option( 'admin_email' ) );
421
+
422
+ $headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
423
+ $headers[] = 'Content-Type: text/html; charset=' . get_option( 'blog_charset' );
424
+
425
+ return wp_mail( $recipients , $subject , $message , $headers , $attachments );
426
+
427
+ }
428
+
429
+ /**
430
+ * Get Affiliate_Link data object.
431
+ *
432
+ * @since 3.0.0
433
+ * @access public
434
+ *
435
+ * @param int $id Affiliate_Link post ID.
436
+ * @return Affiliate_Link Affiliate Link object.
437
+ */
438
+ public function get_affiliate_link( $id = 0 ) {
439
+
440
+ return new Affiliate_Link( $id );
441
+ }
442
+
443
+ /**
444
+ * Retrieve all categories as an option array list.
445
+ *
446
+ * @since 3.0.0
447
+ * @access public
448
+ *
449
+ * @return array List of category options.
450
+ */
451
+ public function get_all_category_as_options() {
452
+
453
+ $options = array();
454
+
455
+ $categories = get_terms( array(
456
+ 'taxonomy' => Plugin_Constants::AFFILIATE_LINKS_TAX,
457
+ 'hide_empty' => false,
458
+ ) );
459
+
460
+ if ( ! is_wp_error( $categories ) ) {
461
+
462
+ foreach( $categories as $category )
463
+ $options[ $category->term_id ] = $category->name;
464
+
465
+ } else {
466
+
467
+ // TODO: Handle error
468
+
469
+ }
470
+
471
+ return $options;
472
+ }
473
+
474
+ }
Helpers/Plugin_Constants.php ADDED
@@ -0,0 +1,157 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Helpers;
3
+
4
+ use ThirstyAffiliates\Abstracts\Abstract_Main_Plugin_Class;
5
+
6
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
+
8
+ /**
9
+ * Model that houses all the plugin constants.
10
+ * Note as much as possible, we need to make this class succinct as the only purpose of this is to house all the constants that is utilized by the plugin.
11
+ * Therefore we omit class member comments and minimize comments as much as possible.
12
+ * In fact the only verbouse comment here is this comment you are reading right now.
13
+ * And guess what, it just got worse coz now this comment takes 5 lines instead of 3.
14
+ *
15
+ * @since 3.0.0
16
+ */
17
+ class Plugin_Constants {
18
+
19
+ /*
20
+ |--------------------------------------------------------------------------
21
+ | Class Properties
22
+ |--------------------------------------------------------------------------
23
+ */
24
+
25
+ private static $_instance;
26
+
27
+ // Plugin configuration constants
28
+ const TOKEN = 'ta';
29
+ const INSTALLED_VERSION = 'ta_installed_version';
30
+ const VERSION = '3.0.1';
31
+ const TEXT_DOMAIN = 'thirstyaffiliates';
32
+ const THEME_TEMPLATE_PATH = 'thirstyaffiliates';
33
+ const META_DATA_PREFIX = '_ta_';
34
+
35
+ // CPT Taxonomy constants
36
+ const AFFILIATE_LINKS_CPT = 'thirstylink';
37
+ const AFFILIATE_LINKS_TAX = 'thirstylink-category';
38
+ const DEFAULT_LINK_CATEGORY = 'Uncategorized';
39
+
40
+ // CRON
41
+ const CRON_REQUEST_REVIEW = 'ta_cron_request_review';
42
+ const CRON_MIGRATE_OLD_PLUGIN_DATA = 'ta_cron_migrate_old_plugin_data';
43
+ const CRON_TAPRO_NOTICE = 'ta_cron_tapro_notice';
44
+
45
+ // Options
46
+ const SHOW_REQUEST_REVIEW = 'ta_show_request_review';
47
+ const REVIEW_REQUEST_RESPONSE = 'ta_request_review_response';
48
+ const MIGRATION_COMPLETE_FLAG = 'ta_migration_complete_flag';
49
+ const SHOW_TAPRO_NOTICE = 'ta_show_tapro_notice';
50
+
51
+ // Settings Constants
52
+
53
+ // DB Tables
54
+ const LINK_CLICK_DB = 'ta_link_clicks';
55
+ const LINK_CLICK_META_DB = 'ta_link_clicks_meta';
56
+
57
+ // Help Section
58
+ const CLEAN_UP_PLUGIN_OPTIONS = 'ta_clean_up_plugin_options';
59
+
60
+
61
+
62
+
63
+ /*
64
+ |--------------------------------------------------------------------------
65
+ | Class Methods
66
+ |--------------------------------------------------------------------------
67
+ */
68
+
69
+ public function __construct( Abstract_Main_Plugin_Class $main_plugin ) {
70
+
71
+ // Path constants
72
+ $this->_MAIN_PLUGIN_FILE_PATH = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'thirstyaffiliates' . DIRECTORY_SEPARATOR . 'thirstyaffiliates.php';
73
+ $this->_PLUGIN_DIR_PATH = plugin_dir_path( $this->_MAIN_PLUGIN_FILE_PATH );
74
+ $this->_PLUGIN_DIR_URL = plugin_dir_url( $this->_MAIN_PLUGIN_FILE_PATH );
75
+ $this->_PLUGIN_DIRNAME = plugin_basename( dirname( $this->_MAIN_PLUGIN_FILE_PATH ) );
76
+ $this->_PLUGIN_BASENAME = plugin_basename( $this->_MAIN_PLUGIN_FILE_PATH );
77
+
78
+ $this->_CSS_ROOT_URL = $this->_PLUGIN_DIR_URL . 'css/';
79
+ $this->_IMAGES_ROOT_URL = $this->_PLUGIN_DIR_URL . 'images/';
80
+ $this->_JS_ROOT_URL = $this->_PLUGIN_DIR_URL . 'js/';
81
+
82
+ $this->_VIEWS_ROOT_PATH = $this->_PLUGIN_DIR_PATH . 'views/';
83
+ $this->_TEMPLATES_ROOT_PATH = $this->_PLUGIN_DIR_PATH . 'templates/';
84
+ $this->_LOGS_ROOT_PATH = $this->_PLUGIN_DIR_PATH . 'logs/';
85
+
86
+ $this->_REDIRECT_TYPES = apply_filters( 'ta_redirect_types' , array(
87
+ '301' => __( '301 Permanent' , 'thirstyaffiliates' ),
88
+ '302' => __( '302 Temporary' , 'thirstyaffiliates' ),
89
+ '307' => __( '307 Temporary (alternative)' , 'thirstyaffiliates' )
90
+ ) );
91
+
92
+ $main_plugin->add_to_public_helpers( $this );
93
+
94
+ }
95
+
96
+ public static function get_instance( Abstract_Main_Plugin_Class $main_plugin ) {
97
+
98
+ if ( !self::$_instance instanceof self )
99
+ self::$_instance = new self( $main_plugin );
100
+
101
+ return self::$_instance;
102
+
103
+ }
104
+
105
+ public function VERSION() {
106
+ return self::VERSION;
107
+ }
108
+
109
+ public function MAIN_PLUGIN_FILE_PATH() {
110
+ return $this->_MAIN_PLUGIN_FILE_PATH;
111
+ }
112
+
113
+ public function PLUGIN_DIR_PATH() {
114
+ return $this->_PLUGIN_DIR_PATH;
115
+ }
116
+
117
+ public function PLUGIN_DIR_URL() {
118
+ return $this->_PLUGIN_DIR_URL;
119
+ }
120
+
121
+ public function PLUGIN_DIRNAME() {
122
+ return $this->_PLUGIN_DIRNAME;
123
+ }
124
+
125
+ public function PLUGIN_BASENAME() {
126
+ return $this->_PLUGIN_BASENAME;
127
+ }
128
+
129
+ public function CSS_ROOT_URL() {
130
+ return $this->_CSS_ROOT_URL;
131
+ }
132
+
133
+ public function IMAGES_ROOT_URL() {
134
+ return $this->_IMAGES_ROOT_URL;
135
+ }
136
+
137
+ public function JS_ROOT_URL() {
138
+ return $this->_JS_ROOT_URL;
139
+ }
140
+
141
+ public function VIEWS_ROOT_PATH() {
142
+ return $this->_VIEWS_ROOT_PATH;
143
+ }
144
+
145
+ public function TEMPLATES_ROOT_PATH() {
146
+ return $this->_TEMPLATES_ROOT_PATH;
147
+ }
148
+
149
+ public function LOGS_ROOT_PATH() {
150
+ return $this->_LOGS_ROOT_PATH;
151
+ }
152
+
153
+ public function REDIRECT_TYPES() {
154
+ return $this->_REDIRECT_TYPES;
155
+ }
156
+
157
+ }
Helpers/index.php ADDED
@@ -0,0 +1 @@
1
+ <?php /* Silence is Golden */ ?>
Interfaces/Activatable_Interface.php ADDED
@@ -0,0 +1,22 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Interfaces;
3
+
4
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
+
6
+ /**
7
+ * Abstraction that provides contract relating to activation.
8
+ * Any model that needs some sort of activation must implement this interface.
9
+ *
10
+ * @since 3.0.0
11
+ */
12
+ interface Activatable_Interface {
13
+
14
+ /**
15
+ * Contruct for activation.
16
+ *
17
+ * @since 3.0.0
18
+ * @access public
19
+ */
20
+ public function activate();
21
+
22
+ }
Interfaces/Initiable_Interface.php ADDED
@@ -0,0 +1,22 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Interfaces;
3
+
4
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
+
6
+ /**
7
+ * Abstraction that provides contract relating to initialization.
8
+ * Any model that needs some sort of initialization must implement this interface.
9
+ *
10
+ * @since 3.0.0
11
+ */
12
+ interface Initiable_Interface {
13
+
14
+ /**
15
+ * Contruct for initialization.
16
+ *
17
+ * @since 3.0.0
18
+ * @access public
19
+ */
20
+ public function initialize();
21
+
22
+ }
Interfaces/Model_Interface.php ADDED
@@ -0,0 +1,22 @@
1
+ <?php
2
+ namespace ThirstyAffiliates\Interfaces;
3
+
4
+ if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
+
6
+ /**
7
+ * Abstraction that provides contract relating to plugin models.
8
+ * All "regular models" should implement this interface.
9
+ *
10
+ * @since 3.0.0
11
+ */
12
+ interface Model_Interface {
13
+
14
+ /**
15
+ * Contract for running the model.
16
+ *
17
+ * @since 3.0.0
18
+ * @access public
19
+ */
20
+ public function run();
21
+
22
+ }
Interfaces/index.php ADDED
@@ -0,0 +1 @@
1
+ <?php /* Silence is Golden */ ?>
Models/Affiliate_Link.php ADDED
@@ -0,0 +1,597 @@
1
+ <?php
2
+
3
+ namespace ThirstyAffiliates\Models;
4
+
5
+ use ThirstyAffiliates\Abstracts\Abstract_Main_Plugin_Class;
6
+
7
+ use ThirstyAffiliates\Interfaces\Model_Interface;
8
+
9
+ use ThirstyAffiliates\Helpers\Plugin_Constants;
10
+ use ThirstyAffiliates\Helpers\Helper_Functions;
11
+
12
+ /**
13
+ * Model that houses the data model of an affiliate link.
14
+ *
15
+ * @since 3.0.0
16
+ */
17
+ class Affiliate_Link {
18
+
19
+
20
+ /*
21
+ |--------------------------------------------------------------------------
22
+ | Class Properties
23
+ |--------------------------------------------------------------------------
24
+ */
25
+
26
+ /**
27
+ * Model that houses the main plugin object.
28
+ *
29
+ * @since 3.0.0
30
+ * @access private
31
+ * @var Abstract_Main_Plugin_Class
32
+ */
33
+ private $_main_plugin;
34
+
35
+ /**
36
+ * Model that houses all the plugin constants.
37
+ *
38
+ * @since 3.0.0
39
+ * @access private
40
+ * @var Plugin_Constants
41
+ */
42
+ private $_constants;
43
+
44
+ /**
45
+ * Property that houses all the helper functions of the plugin.
46
+ *
47
+ * @since 3.0.0
48
+ * @access private
49
+ * @var Helper_Functions
50
+ */
51
+ private $_helper_functions;
52
+
53
+ /**
54
+ * Stores affiliate link ID.
55
+ *
56
+ * @since 3.0.0
57
+ * @access private
58
+ * @var array
59
+ */
60
+ protected $id;
61
+
62
+ /**
63
+ * Stores affiliate link data.
64
+ *
65
+ * @since 3.0.0
66
+ * @access private
67
+ * @var array
68
+ */
69
+ protected $data = array();
70
+
71
+ /**
72
+ * Stores affiliate link default data.
73
+ *
74
+ * @since 3.0.0
75
+ * @access private
76
+ * @var array
77
+ */
78
+ protected $default_data = array(
79
+ 'name' => '',
80
+ 'slug' => '',
81
+ 'date_created' => '',
82
+ 'date_modified' => '',
83
+ 'status' => '',
84
+ 'permalink' => '',
85
+ 'destination_url' => '',
86
+ 'rel_tags' => '',
87
+ 'redirect_type' => '',
88
+ 'no_follow' => 'global',
89
+ 'new_window' => 'global',
90
+ 'uncloak_link' => 'global',
91
+ 'pass_query_str' => 'global',
92
+ 'image_ids' => array(),
93
+ 'categories' => array(),
94
+ 'category_slug' => '',
95
+ 'category_slug_id' => 0,
96
+ );
97
+
98
+ /**
99
+ * Stores affiliate link default data.
100
+ *
101
+ * @since 3.0.0
102
+ * @access private
103
+ * @var array
104
+ */
105
+ protected $extend_data = array();
106
+
107
+ /**
108
+ * Stores affiliate link post data.
109
+ *
110
+ * @since 3.0.0
111
+ * @access private
112
+ * @var object
113
+ */
114
+ protected $post_data;
115
+
116
+ /**
117
+ * This is where changes to the $data will be saved.
118
+ *
119
+ * @since 3.0.0
120
+ * @access private
121
+ * @var object
122
+ */
123
+ protected $changes = array();
124
+
125
+ /**
126
+ * Stores boolean if the data has been read from the database or not.
127
+ *
128
+ * @since 3.0.0
129
+ * @access private
130
+ * @var object
131
+ */
132
+ protected $object_is_read = false;
133
+
134
+
135
+
136
+
137
+ /*
138
+ |--------------------------------------------------------------------------
139
+ | Class Methods
140
+ |--------------------------------------------------------------------------
141
+ */
142
+
143
+ /**
144
+ * Class constructor.
145
+ *
146
+ * @since 3.0.0
147
+ * @access public
148
+ *
149
+ * @param Abstract_Main_Plugin_Class $main_plugin Main plugin object.
150
+ * @param Plugin_Constants $constants Plugin constants object.
151
+ * @param Helper_Functions $helper_functions Helper functions object.
152
+ */
153
+ public function __construct( $id = null ) {
154
+
155
+ $this->_constants = ThirstyAffiliates()->helpers[ 'Plugin_Constants' ];
156
+ $this->_helper_functions = ThirstyAffiliates()->helpers[ 'Helper_Functions' ];
157
+
158
+ if ( filter_var( $id , FILTER_VALIDATE_INT ) && $id ) {
159
+
160
+ $this->extend_data = apply_filters( 'ta_affiliate_link_extended_data' , $this->extend_data , $this->default_data );
161
+ $this->data = $this->get_merged_default_extended_data();
162
+ $this->id = absint( $id );
163
+
164
+ $this->read();
165
+
166
+ }
167
+
168
+ }
169
+
170
+ /**
171
+ * Read data from DB and save on instance.
172
+ *
173
+ * @since 3.0.0
174
+ * @access public
175
+ */
176
+ private function read() {
177
+
178
+ $this->post_data = get_post( $this->id );
179
+
180
+ if ( ! is_a( $this->post_data , 'WP_Post' ) || $this->object_is_read )
181
+ return;
182
+
183
+ // set the affiliate link ID
184
+ $this->id = $this->post_data->ID;
185
+
186
+ foreach ( $this->get_merged_default_extended_data() as $prop => $value ) {
187
+
188
+ switch ( $prop ) {
189
+
190
+ case 'name' :
191
+ case 'slug' :
192
+ case 'status' :
193
+ case 'date_created' :
194
+ case 'permalink' :
195
+ $this->data[ $prop ] = $this->get_post_data_equivalent( $prop );
196
+ break;
197
+
198
+ case 'rel_tags' :
199
+ case 'no_follow' :
200
+ case 'new_window' :
201
+ case 'uncloak_link' :
202
+ case 'redirect_type' :
203
+ case 'pass_query_str' :
204
+ $raw_data = get_post_meta( $this->id , Plugin_Constants::META_DATA_PREFIX . $prop , true );
205
+ $this->data[ $prop ] = ! empty( $raw_data ) ? $raw_data : $this->get_prop_global_option_value( $prop );
206
+ break;
207
+
208
+ case 'image_ids' :
209
+ $raw_data = get_post_meta( $this->id , Plugin_Constants::META_DATA_PREFIX . $prop , true );
210
+ $this->data[ $prop ] = ( is_array( $raw_data ) && ! empty( $raw_data ) ) ? $raw_data : $this->default_data[ $prop ];
211
+ break;
212
+
213
+ case 'categories' :
214
+ $categories = wp_get_post_terms( $this->id , Plugin_Constants::AFFILIATE_LINKS_TAX );
215
+ $this->data[ $prop ] = ! empty( $categories ) ? $categories : $this->default_data[ $prop ];
216
+ break;
217
+
218
+ default :
219
+ $value = get_post_meta( $this->id , Plugin_Constants::META_DATA_PREFIX . $prop , true );
220
+ $this->data[ $prop ] = apply_filters( 'ta_read_thirstylink_property' , $value , $prop , $this->default_data );
221
+ break;
222
+
223
+ }
224
+
225
+ }
226
+
227
+ $this->object_is_read = true;
228
+
229
+ }
230
+
231
+
232
+
233
+
234
+ /*
235
+ |--------------------------------------------------------------------------
236
+ | Data getters
237
+ |--------------------------------------------------------------------------
238
+ */
239
+
240
+ /**
241
+ * Get merged $default_data and $extended_data class properties.
242
+ *
243
+ * @since 3.0.0
244
+ * @access public
245
+ *
246
+ * @return array Data properties.
247
+ */
248
+ private function get_merged_default_extended_data() {
249
+
250
+ return array_merge( $this->default_data , $this->extend_data );
251
+
252
+ }
253
+
254
+ /**
255
+ * Return's the post data equivalent of a certain affiliate link data property.
256
+ *
257
+ * @since 3.0.0
258
+ * @access private
259
+ *
260
+ * @param string $prop Affiliate link property name.
261
+ * @return string WP Post property equivalent.
262
+ */
263
+ private function get_post_data_equivalent( $prop ) {
264
+
265
+ $equivalents = apply_filters( 'ta_affiliate_link_post_data_equivalent' , array(
266
+ 'name' => $this->post_data->post_title,
267
+ 'slug' => $this->post_data->post_name,
268
+ 'permalink' => get_permalink( $this->post_data->ID ),
269
+ 'status' => $this->post_data->post_status,
270
+ 'date_created' => $this->post_data->post_date,
271
+ 'date_modified' => $this->post_data->post_modified,
272
+ ) , $this->post_data );
273
+
274
+ if ( array_key_exists( $prop , $equivalents ) )
275
+ return $equivalents[ $prop ];
276
+ else
277
+ return;
278
+
279
+ }
280
+
281
+ /**
282
+ * Return data property.
283
+ *
284
+ * @since 3.0.0
285
+ * @access public
286
+ *
287
+ * @param string $prop Data property slug.
288
+ * @param mixed $default Set property default value (optional).
289
+ * @return mixed Property data.
290
+ */
291
+ public function get_prop( $prop , $default = '' ) {
292
+
293
+ $default_data = $this->get_merged_default_extended_data();
294
+
295
+ if ( array_key_exists( $prop , $this->data ) && $this->data[ $prop ] )
296
+ $return_value = $this->data[ $prop ];
297
+ else
298
+ $return_value = ( $default ) ? $default : $default_data[ $prop ];
299
+
300
+ return $prop === 'destination_url' ? esc_url( $return_value ) : $return_value;
301
+
302
+ }
303
+
304
+ /**
305
+ * Return Affiliate_Link ID.
306
+ *
307
+ * @since 3.0.0
308
+ * @access public
309
+ *
310
+ * @return int Affiliate_Link ID.
311
+ */
312
+ public function get_id() {
313
+
314
+ return absint( $this->id );
315
+
316
+ }
317
+
318
+ /**
319
+ * Return changed data property.
320
+ *
321
+ * @since 3.0.0
322
+ * @access public
323
+ *
324
+ * @param string $prop Data property slug.
325
+ * @param mixed $default Set property default value (optional).
326
+ * @return mixed Property data.
327
+ */
328
+ public function get_changed_prop( $prop , $default = '' ) {
329
+
330
+ return isset( $this->changes[ $prop ] ) ? $this->changes[ $prop ] : $this->get_prop( $prop , $default );
331
+
332
+ }
333
+
334
+ /**
335
+ * Return affiliate link's WP_Post data.
336
+ *
337
+ * @since 3.0.0
338
+ * @access public
339
+ *
340
+ * @return object Post data object.
341
+ */
342
+ public function get_post_data() {
343
+
344
+ return $this->post_data;
345
+
346
+ }
347
+
348
+ /**
349
+ * Get the properties global option value.
350
+ *
351
+ * @since 3.0.0
352
+ * @access public
353
+ *
354
+ * @param string $prop Name of property.
355
+ * @return string Global option value.
356
+ */
357
+ public function get_prop_global_option_value( $prop ) {
358
+
359
+ $default = '';
360
+
361
+ switch( $prop ) {
362
+
363
+ case 'rel_tags' :
364
+ $option = 'ta_additional_rel_tags';
365
+ break;
366
+
367
+ case 'no_follow' :
368
+ $option = 'ta_no_follow';
369
+ break;
370
+
371
+ case 'new_window' :
372
+ $option = 'ta_new_window';
373
+ break;
374
+
375
+ case 'redirect_type' :
376
+ $option = 'ta_link_redirect_type';
377
+ $default = '301';
378
+ break;
379
+
380
+ case 'pass_query_str' :
381
+ $option = 'ta_pass_query_str';
382
+ break;
383
+
384
+ case 'uncloak_link' :
385
+ return;
386
+ break;
387
+ }
388
+
389
+ return get_option( $option , $default );
390
+ }
391
+
392
+ /**
393
+ * Get the global value for the uncloak property.
394
+ *
395
+ * @since 3.0.0
396
+ * @access public
397
+ *
398
+ * @return string Global option value.
399
+ */
400
+ public function get_global_uncloak_value() {
401
+
402
+ $uncloak_cats = maybe_unserialize( get_option( 'ta_category_to_uncloak' , array() ) );
403
+
404
+ if ( ! is_array( $uncloak_cats ) || empty( $uncloak_cats ) )
405
+ return 'no';
406
+
407
+ foreach ( $uncloak_cats as $cat_id ) {
408
+
409
+ if ( has_term( intval( $cat_id ) , Plugin_Constants::AFFILIATE_LINKS_TAX , $this->id ) )
410
+ return 'yes';
411
+ }
412
+
413
+ return 'no';
414
+ }
415
+
416
+
417
+
418
+
419
+ /*
420
+ |--------------------------------------------------------------------------
421
+ | Data setters
422
+ |--------------------------------------------------------------------------
423
+ */
424
+
425
+ /**
426
+ * Set new value to properties and save it to $changes property.
427
+ * This stores changes in a special array so we can track what needs to be saved on the DB later.
428
+ *
429
+ * @since 3.0.0
430
+ * @access public
431
+ *
432
+ * @param string $prop Data property slug.
433
+ * @param string $value New property value.
434
+ */
435
+ public function set_prop( $prop , $value ) {
436
+
437
+ $default_data = $this->get_merged_default_extended_data();
438
+
439
+ if ( array_key_exists( $prop , $this->data ) ) {
440
+
441
+ // permalink property must not be changed
442
+ if ( $prop == 'permalink' )
443
+ return;
444
+
445
+ if ( gettype( $value ) == gettype( $default_data[ $prop ] ) )
446
+ $this->changes[ $prop ] = $value;
447
+ else {
448
+
449
+ // TODO: handle error here.
450
+
451
+ }
452
+
453
+ } else {
454
+
455
+ $this->data[ $prop ] = $value;
456
+ $this->changes[ $prop ] = $value;
457
+
458
+ }
459
+
460
+ }
461
+
462
+
463
+
464
+
465
+ /*
466
+ |--------------------------------------------------------------------------
467
+ | Save (Create / Update) data to DB
468
+ |--------------------------------------------------------------------------
469
+ */
470
+
471
+ /**
472
+ * Save data in $changes to the database.
473
+ *
474
+ * @since 3.0.0
475
+ * @access public
476
+ *
477
+ * @return WP_Error | int On success will return the post ID, otherwise it will return a WP_Error object.
478
+ */
479
+ public function save() {
480
+
481
+ if ( ! empty( $this->changes ) ) {
482
+
483
+ $post_metas = array();
484
+ $post_data = array(
485
+ 'post_title' => $this->get_changed_prop( 'name' ),
486
+ 'post_name' => $this->get_changed_prop( 'slug' ),
487
+ 'post_status' => $this->get_changed_prop( 'status' , 'publish' ),
488
+ 'post_date' => $this->get_changed_prop( 'date_created' , current_time( 'mysql' ) ),
489
+ 'post_modified' => $this->get_changed_prop( 'date_modified' , current_time( 'mysql' ) )
490
+ );
491
+
492
+ foreach ( $this->changes as $prop => $value ) {
493
+
494
+ // make sure that property is registered in default data
495
+ if ( ! array_key_exists( $prop , $this->get_merged_default_extended_data() ) )
496
+ continue;
497
+
498
+ if ( in_array( $prop , array( 'permalink' , 'name' , 'slug' , 'status' , 'date_created' , 'date_modified' ) ) )
499
+ continue;
500
+
501
+ $post_metas[ $prop ] = $value;
502
+ }
503
+
504
+ // create or update post
505
+ if ( $this->id )
506
+ $post_id = $this->update( $post_data );
507
+ else
508
+ $post_id = $this->create( $post_data );
509
+
510
+ if ( ! is_wp_error( $post_id ) )
511
+ $this->update_metas( $post_id , $post_metas );
512
+ else
513
+ return $post_id; // Return WP_Error object on error
514
+
515
+ do_action( 'ta_save_affiliate_link' , $this->changes , $this );
516
+
517
+ // update instance with new changes.
518
+ $this->object_is_read = false;
519
+ $this->read();
520
+
521
+ } else
522
+ return new \WP_Error( 'ta_affiliate_link_no_changes' , __( 'Unable to save affiliate link as there are no changes registered on the object yet.' , 'thirstyaffiliates' ) , array( 'changes' => $this->changes , 'affiliate_link' => $this ) );
523
+
524
+ return $post_id;
525
+ }
526
+
527
+ /**
528
+ * Create the affiliate link post.
529
+ *
530
+ * @since 3.0.0
531
+ * @access private
532
+ *
533
+ * @param array $post_data Affiliate link post data.
534
+ * @param WP_Error|int WP_Error on error, ID of newly created post otherwise.
535
+ */
536
+ private function create( $post_data ) {
537
+
538
+ $post_data = array_merge( array( 'post_type' => Plugin_Constants::AFFILIATE_LINKS_CPT ) , $post_data );
539
+ $this->id = wp_insert_post( $post_data );
540
+
541
+ return $this->id;
542
+
543
+ }
544
+
545
+ /**
546
+ * Update the affiliate link post.
547
+ *
548
+ * @since 3.0.0
549
+ * @access private
550
+ *
551
+ * @param array $post_data Affiliate link post data.
552
+ * @return int ID of the updated post upon success. 0 on failure.
553
+ */
554
+ private function update( $post_data ) {
555
+
556
+ $post_data = array_merge( array( 'ID' => $this->id ) , $post_data );
557
+ return wp_update_post( $post_data , true );
558
+
559
+ }
560
+
561
+ /**
562
+ * Update/add the affiliate link meta data.
563
+ *
564
+ * @since 3.0.0
565
+ * @access private
566
+ *
567
+ * @param int $post_id Affiliate link post ID.
568
+ * @param array $post_metas Affiliate link meta data.
569
+ */
570
+ private function update_metas( $post_id , $post_metas ) {
571
+
572
+ foreach ( $post_metas as $key => $value )
573
+ update_post_meta( $post_id , Plugin_Constants::META_DATA_PREFIX . $key , $value );
574
+
575
+ }
576
+
577
+ /**
578
+ * Count affiliate link clicks.
579
+ *
580
+ * @since 3.0.0
581
+ * @access public
582
+ *
583
+ * @return int Total number of clicks.
584
+ */
585
+ public function count_clicks() {
586
+
587
+ global $wpdb;
588
+
589
+ $table_name = $wpdb->prefix . Plugin_Constants::LINK_CLICK_DB;
590
+ $link_id = $this->get_id();
591
+ $query = "SELECT count(*) from $table_name WHERE link_id = $link_id";
592
+ $clicks = $wpdb->get_var( $query );
593
+
594
+ return (int) $clicks;
595
+ }
596
+
597
+ }
Models/Affiliate_Link_Attachment.php ADDED
@@ -0,0 +1,323 @@
1
+ <?php
2
+
3
+ namespace ThirstyAffiliates\Models;
4
+
5
+ use ThirstyAffiliates\Abstracts\Abstract_Main_Plugin_Class;
6
+
7
+ use ThirstyAffiliates\Interfaces\Model_Interface;
8
+ use ThirstyAffiliates\Interfaces\Initiable_Interface;
9
+
10
+ use ThirstyAffiliates\Helpers\Plugin_Constants;
11
+ use ThirstyAffiliates\Helpers\Helper_Functions;
12
+
13
+ /**
14
+ * Model that houses the logic of media attachments of an affiliate link.
15
+ *
16
+ * @since 3.0.0
17
+ */
18
+ class Affiliate_Link_Attachment implements Model_Interface , Initiable_Interface {
19
+
20
+ /*
21
+ |--------------------------------------------------------------------------
22
+ | Class Properties
23
+ |--------------------------------------------------------------------------
24
+ */
25
+
26
+ /**
27
+ * Property that holds the single main instance of Bootstrap.
28
+ *
29
+ * @since 3.0.0
30
+ * @access private
31
+ * @var Affiliate_Link_Attachment
32
+ */
33
+ private static $_instance;
34
+
35
+ /**
36
+ * Model that houses the main plugin object.
37
+ *
38
+ * @since 3.0.0
39
+ * @access private
40
+ * @var Affiliate_Link_Attachment
41
+ */
42
+ private $_main_plugin;
43
+
44
+ /**
45
+ * Model that houses all the plugin constants.
46
+ *
47
+ * @since 3.0.0
48
+ * @access private
49
+ * @var Plugin_Constants
50
+ */
51
+ private $_constants;
52
+
53
+ /**
54
+ * Property that houses all the helper functions of the plugin.
55
+ *
56
+ * @since 3.0.0
57
+ * @access private
58
+ * @var Helper_Functions
59
+ */
60
+ private $_helper_functions;
61
+
62
+
63
+
64
+
65
+ /*
66
+ |--------------------------------------------------------------------------
67
+ | Class Methods
68
+ |--------------------------------------------------------------------------
69
+ */
70
+
71
+ /**
72
+ * Class constructor.
73
+ *
74
+ * @since 3.0.0
75
+ * @access public
76
+ *
77
+ * @param Abstract_Main_Plugin_Class $main_plugin Main plugin object.
78
+ * @param Plugin_Constants $constants Plugin constants object.
79
+ * @param Helper_Functions $helper_functions Helper functions object.
80
+ */
81
+ public function __construct( Abstract_Main_Plugin_Class $main_plugin , Plugin_Constants $constants , Helper_Functions $helper_functions ) {
82
+
83
+ $this->_constants = $constants;
84
+ $this->_helper_functions = $helper_functions;
85
+
86
+ $main_plugin->add_to_all_plugin_models( $this );
87
+ $main_plugin->add_to_public_models( $this );
88
+
89
+ }
90
+
91
+ /**
92
+ * Ensure that only one instance of this class is loaded or can be loaded ( Singleton Pattern ).
93
+ *
94
+ * @since 3.0.0
95
+ * @access public
96
+ *
97
+ * @param Abstract_Main_Plugin_Class $main_plugin Main plugin object.
98
+ * @param Plugin_Constants $constants Plugin constants object.
99
+ * @param Helper_Functions $helper_functions Helper functions object.
100
+ * @return Affiliate_Link_Attachment
101
+ */
102
+ public static function get_instance( Abstract_Main_Plugin_Class $main_plugin , Plugin_Constants $constants , Helper_Functions $helper_functions ) {
103
+
104
+ if ( !self::$_instance instanceof self )
105
+ self::$_instance = new self( $main_plugin , $constants , $helper_functions );
106
+
107
+ return self::$_instance;
108
+
109
+ }
110
+
111
+
112
+
113
+
114
+
115
+ /*
116
+ |--------------------------------------------------------------------------
117
+ | Attachments
118
+ |--------------------------------------------------------------------------
119
+ */
120
+
121
+ /**
122
+ * Add attachments to affiliate link via ajax.
123
+ *
124
+ * @since 3.0.0
125
+ * @access public
126
+ */
127
+ public function ajax_add_attachments_to_affiliate_link() {
128
+
129
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX )
130
+ $response = array( 'status' => 'fail' , 'error_msg' => __( 'Invalid AJAX call' , 'thirstyaffiliates' ) );
131
+ elseif ( ! isset( $_POST[ 'attachment_ids' ] ) || ! isset( $_POST[ 'affiliate_link_id' ] ) )
132
+ $response = array( 'status' => 'fail' , 'error_msg' => __( 'Missing required post data' , 'thirstyaffiliates' ) );
133
+ else {
134
+
135
+ $result = $this->add_attachments_to_affiliate_link( $_POST[ 'attachment_ids' ] , $_POST[ 'affiliate_link_id' ] );
136
+
137
+ if ( is_wp_error( $result ) )
138
+ $response = array( 'status' => 'fail' , 'error_msg' => $result->get_error_message() );
139
+ else {
140
+
141
+ ob_start();
142
+
143
+ foreach ( $result as $attachment_id ) {
144
+
145
+ $img = wp_get_attachment_image_src( $attachment_id , 'full' );
146
+ include( $this->_constants->VIEWS_ROOT_PATH() . 'cpt/view-attach-images-metabox-single-image.php' );
147
+
148
+ }
149
+
150
+ $added_attachments_markup = ob_get_clean();
151
+
152
+ $response = array( 'status' => 'success' , 'success_msg' => __( 'Attachments successfully added to the affiliate link' , 'thirstyaffiliates' ) , 'added_attachments_markup' => $added_attachments_markup );
153
+
154
+ }
155
+
156
+ }
157
+
158
+ @header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
159
+ echo wp_json_encode( $response );
160
+ wp_die();
161
+
162
+ }
163
+
164
+ /**
165
+ * Add attachments to affiliate link.
166
+ *
167
+ * @since 3.0.0
168
+ * @access public
169
+ *
170
+ * @param array $attachment_ids Array of attachment ids.
171
+ * @param int $affiliate_link_id Id of the current affiliate link.
172
+ * @return WP_Error | boolean WP_Error instance on failure, boolean true otherwise.
173
+ */
174
+ public function add_attachments_to_affiliate_link( $attachment_ids , $affiliate_link_id ) {
175
+
176
+ if ( !is_array( $attachment_ids ) )
177
+ return new \WP_Error( 'ta_invalid_attachment_ids' , __( 'Invalid attachment ids to attach to an affiliate link' , 'thirstyaffiliates' ) , array( 'attachment_ids' => $attachment_ids , 'affiliate_link_id' => $affiliate_link_id ) );
178
+
179
+ $attachments = get_post_meta( $affiliate_link_id , Plugin_Constants::META_DATA_PREFIX . 'image_ids' , true );
180
+ if ( !is_array( $attachments ) )
181
+ $attachments = array();
182
+
183
+ $new_attachment_ids = array_diff( $attachment_ids , $attachments );
184
+
185
+ update_post_meta( $affiliate_link_id , Plugin_Constants::META_DATA_PREFIX . 'image_ids' , array_unique( array_merge( $attachments , $attachment_ids ) ) );
186
+
187
+ return $new_attachment_ids;
188
+
189
+ }
190
+
191
+ /**
192
+ * Add attachments to affiliate link via ajax.
193
+ *
194
+ * @since 3.0.0
195
+ * @access public
196
+ */
197
+ public function ajax_remove_attachment_to_affiliate_link() {
198
+
199
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX )
200
+ $response = array( 'status' => 'fail' , 'error_msg' => __( 'Invalid AJAX call' , 'thirstyaffiliates' ) );
201
+ elseif ( ! isset( $_POST[ 'attachment_id' ] ) || ! isset( $_POST[ 'affiliate_link_id' ] ) )
202
+ $response = array( 'status' => 'fail' , 'error_msg' => __( 'Missing required post data' , 'thirstyaffiliates' ) );
203
+ else {
204
+
205
+ if ( is_wp_error( $this->remove_attachment_to_affiliate_link( $_POST[ 'attachment_id' ] , $_POST[ 'affiliate_link_id' ] ) ) )
206
+ $response = array( 'status' => 'fail' , 'error_msg' => $result->get_error_message() );
207
+ else
208
+ $response = array( 'status' => 'success' , 'success_msg' => __( 'Attachment successfully removed from attachment' , 'thirstyaffiliates' ) );
209
+
210
+ }
211
+
212
+ @header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
213
+ echo wp_json_encode( $response );
214
+ wp_die();
215
+
216
+ }
217
+
218
+ /**
219
+ * Remove an attachment from an affiliate link.
220
+ *
221
+ * @since 3.0.0
222
+ * @access public
223
+ *
224
+ * @param int $attachment_id Attachment id.
225
+ * @param int $affiliate_link_id Affiliate link id.
226
+ * @return WP_Error | boolean WP_Error instance on failure, boolean true otherwise.
227
+ */
228
+ public function remove_attachment_to_affiliate_link( $attachment_id , $affiliate_link_id ) {
229
+
230
+ $attachments = get_post_meta( $affiliate_link_id , Plugin_Constants::META_DATA_PREFIX . 'image_ids' , true );
231
+ if ( !is_array( $attachments ) )
232
+ $attachments = array();
233
+
234
+ if ( !in_array( $attachment_id , $attachments ) )
235
+ return new \WP_Error( 'ta_invalid_attachment_id' , __( 'Invalid attachment id to remove from an affiliate link' , 'thirstyaffiliates' ) , array( 'attachment_id' => $attachment_id , 'affiliate_link_id' => 'affiliate_link_id' ) );
236
+
237
+ $key = array_search( $attachment_id , $attachments );
238
+ unset( $attachments[ $key ] );
239
+
240
+ update_post_meta( $affiliate_link_id , Plugin_Constants::META_DATA_PREFIX . 'image_ids' , $attachments );
241
+
242
+ return true;
243
+
244
+ }
245
+
246
+ /**
247
+ * This is the place where we decide whether if it is an affiliate link page and if it is, well apply custom action modifications that is exclusive only to the affiliate link page.
248
+ *
249
+ * @since 3.0.0
250
+ * @access public
251
+ */
252
+ public function current_screen_filter() {
253
+
254
+ $current_screen = get_current_screen();
255
+
256
+ if ( $current_screen->base === 'post' && $current_screen->post_type === 'thirstylink' ) {
257
+
258
+ add_filter( 'upload_mimes' , array( $this , 'restrict_file_upload_to_images_only' ) , 10 , 1 );
259
+
260
+ }
261
+
262
+ }
263
+
264
+ /**
265
+ * Restrict the media library uploader to accept image files only.
266
+ * The effects of this filter is global, thats why we only apply the filter when we are inside an affiliate link edit page.
267
+ *
268
+ * @since 3.0.0
269
+ * @access public
270
+ *
271
+ * @param array $mime_types Array of accepted mime types.
272
+ * @return array Filtered array of accepted mime types.
273
+ */
274
+ public function restrict_file_upload_to_images_only( $mime_types ) {
275
+
276
+ return array(
277
+ 'jpg|jpeg|jpe' => 'image/jpeg',
278
+ 'gif' => 'image/gif',
279
+ 'png' => 'image/png',
280
+ 'bmp' => 'image/bmp',
281
+ 'tiff|tif' => 'image/tiff',
282
+ 'ico' => 'image/x-icon'
283
+ );
284
+
285
+ }
286
+
287
+
288
+
289
+
290
+ /*
291
+ |--------------------------------------------------------------------------
292
+ | Implemented Interface Methods
293
+ |--------------------------------------------------------------------------
294
+ */
295
+
296
+ /**
297
+ * Execute codes that needs to run on plugin initialization.
298
+ *
299
+ * @since 3.0.0
300
+ * @access public
301
+ * @implements ThirstyAffiliates\Interfaces\Initiable_Interface
302
+ */
303
+ public function initialize() {
304
+
305
+ add_action( 'wp_ajax_ta_add_attachments_to_affiliate_link' , array( $this , 'ajax_add_attachments_to_affiliate_link' ) );
306
+ add_action( 'wp_ajax_ta_remove_attachment_to_affiliate_link' , array( $this , 'ajax_remove_attachment_to_affiliate_link' ) );
307
+
308
+ }
309
+
310
+ /**
311
+ * Execute model core logic.
312
+ *
313
+ * @since 3.0.0
314
+ * @access public
315
+ * @implements ThirstyAffiliates\Interfaces\Model_Interface
316
+ */
317
+ public function run() {
318
+
319
+ add_action( 'current_screen' , array( $this , 'current_screen_filter' ) );
320
+
321
+ }
322
+
323
+ }
Models/Affiliate_Links_CPT.php ADDED
@@ -0,0 +1,626 @@