PluginProbe
Internal Link Juicer: SEO Auto Linker for WordPress / trunk
Internal Link Juicer: SEO Auto Linker for WordPress vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.5.2 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.15.1 1.2.16 1.2.17 All 75 releases
internal-links / backend / editor.php

editor.php in Internal Link Juicer: SEO Auto Linker for WordPress trunk, at backend/editor.php

547 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ILJ\Backend;
3
4 use ILJ\Core\Options;
5 use ILJ\Core\Options\CacheToggleBtnSwitch;
6 use ILJ\Data\Content;
7 use ILJ\Database\Linkindex;
8 use ILJ\Helper\Help;
9 use ILJ\Type\KeywordList;
10 use ILJ\Database\Postmeta;
11 use ILJ\Database\Termmeta;
12 use ILJ\Helper\Capabilities;
13 use ILJ\Helper\IndexAsset as IndexAsset;
14 use ILJ\Helper\Statistic;
15
16 /**
17 * Admin views
18 *
19 * Responsible for all view elements which are required on the backend
20 *
21 * @package ILJ\Backend
22 *
23 * @since 1.0.0
24 */
25 class Editor {
26
27 const ILJ_ADMINVIEW_NONCE = '_ilj_adminview_nonce';
28 const ILJ_ACTION_AFTER_KEYWORDS_UPDATE = 'ilj_after_keywords_update';
29 const ILJ_EDITOR_HANDLE = 'ilj_editor';
30 const ILJ_KEYWORDS_HANDLE = 'ilj_keywords';
31 const ILJ_IS_BLACKLISTED = 'ilj_is_blacklisted';
32 const ILJ_META_KEY_LIMITINCOMINGLINKS = 'ilj_limitincominglinks';
33 const ILJ_META_KEY_MAXINCOMINGLINKS = 'ilj_maxincominglinks';
34 const ILJ_META_KEY_BLACKLISTDEFINITION = 'ilj_blacklistdefinition';
35 const ILJ_META_KEY_LIMITLINKSPERPARAGRAPH = 'ilj_limitlinksperparagraph';
36 const ILJ_META_KEY_LINKSPERPARAGRAPH = 'ilj_linksperparagraph';
37 const ILJ_META_KEY_LIMITOUTGOINGLINKS = 'ilj_limitoutgoinglinks';
38 const ILJ_META_KEY_MAXOUTGOINGLINKS = 'ilj_maxoutgoinglinks';
39 const ILJ_KEYWORD_METABOX_FOOTER_HOOK = 'ilj_keyword_metabox_footer';
40 const ILJ_MODAL = 'ilj_modal';
41
42 /**
43 * Registers the keyword metabox on all public post types
44 *
45 * @since 1.0.0
46 *
47 * @return void
48 */
49 public static function addKeywordMetaBox() {
50 foreach (get_post_types(array('public' => true)) as $type) {
51 add_meta_box(
52 Postmeta::ILJ_META_KEY_LINKDEFINITION,
53 __('Internal Links', 'internal-links'),
54 array(__CLASS__, 'renderKeywordMetaBox'),
55 $type,
56 'side',
57 'default'
58 );
59 }
60
61
62 }
63
64 /**
65 * Renders the keyword metabox
66 *
67 * @since 1.0.0
68 *
69 * @param \WP_Post $post The post object of the current page
70 * @return void
71 */
72 public static function renderKeywordMetaBox(\WP_Post $post) {
73 $keyword_list = KeywordList::fromMeta($post->ID, 'post', Postmeta::ILJ_META_KEY_LINKDEFINITION);
74 $blacklist_keywords = KeywordList::fromMeta($post->ID, 'post', self::ILJ_META_KEY_BLACKLISTDEFINITION);
75
76 wp_nonce_field(basename(__FILE__), self::ILJ_ADMINVIEW_NONCE);
77 ?>
78 <p>
79 <label for="<?php echo esc_attr(Postmeta::ILJ_META_KEY_LINKDEFINITION . '_keys'); ?>"><?php esc_html_e('The keywords', 'internal-links'); ?>:</label>
80 <br />
81 <input
82 type="text"
83 name="<?php echo esc_attr(Postmeta::ILJ_META_KEY_LINKDEFINITION . '_keys'); ?>"
84 value="<?php echo esc_attr($keyword_list->encoded()); ?>" size="30"/>
85 <?php
86
87 ?>
88 <input type="text"
89 name="<?php echo esc_attr(self::ILJ_IS_BLACKLISTED); ?>"
90 value="<?php echo esc_attr(self::isBlacklisted($post->ID, 'post')); ?>"
91 style="display:none"/>
92
93 <input type="text"
94 name="<?php echo esc_attr(self::ILJ_META_KEY_BLACKLISTDEFINITION); ?>"
95 value="<?php echo esc_attr($blacklist_keywords->encoded()); ?>"
96 size="30"
97 style="display:none"/>
98 </p>
99 <template id="ilj_keyword_metabox_footer">
100 <?php
101 $content = Content::from_post($post);
102 /**
103 * This action is used to render content dynamically in to metabox footer.
104 * The HTML content inside <template> tag will rendered at the footer of editor.
105 *
106 * @param $content Content|null The content, null if its rendered on add post or add term page.
107 * @since 2.23.5
108 */
109 do_action(self::ILJ_KEYWORD_METABOX_FOOTER_HOOK, $content);
110 ?>
111 </template>
112 <?php
113 }
114
115 /**
116 * Hooks in to {@link self::ILJ_KEYWORD_METABOX_FOOTER_HOOK} to add custom html to <template> tag
117 *
118 * @param Content|null $content The content, null if its rendered on add post or add term page.
119 * @return void
120 */
121 public static function render_keyword_metabox_footer($content) {
122 ?>
123 <div class="ilj-row">
124 <div class="col-12 ilj-footer">
125 <a href="https://internallinkjuicer.com/docs/editor/?utm_source=editor&utm_medium=help&utm_campaign=plugin"
126 rel="noopener" target="_blank" class="help">
127 <span class="dashicons dashicons-editor-help"></span>
128 <?php esc_html_e('Get help', 'internal-links'); ?>
129 </a>
130 <?php self::render_delete_cache_button($content); ?>
131 </div>
132 </div>
133 <?php
134 }
135
136 /**
137 * Renders delete cache button inside metabox.
138 *
139 * @param Content|null $content The content, null if its rendered on add post or add term page.
140 * @return void
141 */
142 private static function render_delete_cache_button($content) {
143 if (!$content) {
144 return;
145 }
146 $link = admin_url(
147 sprintf( 'admin-ajax.php?action=%s&_wpnonce=%s&ilj_transient_id=%d&ilj_transient_type=%s&ilj_skip_notice=true',
148 'ilj_clear_single_transient',
149 wp_create_nonce('ilj_clear_single_transient'),
150 $content->get_id(),
151 $content->get_type()
152 )
153 );
154 $cache_option = Options::getOption(CacheToggleBtnSwitch::getKey());
155 if ($cache_option) {
156 ?>
157 <div class="ilj-delete-cache-container">
158 <button class="ilj-button-danger button" id="ilj-delete-cache" type="button" data-ilj-delete-cache-url="<?php echo esc_attr($link); ?>">
159 <?php esc_html_e('Delete Cache', 'internal-links'); ?>
160 </button>
161 <span class="spinner is-active ilj-spinner ilj-hidden"></span>
162 </div>
163 <?php
164 }
165 }
166
167 /**
168 * Responsible for saving keyword meta values and
169 * stores limit linking settings for posts and
170 * stores limit linking settings for posts
171 *
172 * @since 1.0.0
173 *
174 * @param int $post_id The ID of the post
175 * @param \WP_Post $post The post object
176 * @return void
177 */
178 public static function saveKeywordMeta($post_id, \WP_Post $post) {
179 if (is_null($post_id) || is_null($post)) {
180 return;
181 }
182
183 if (!isset($_POST[self::ILJ_ADMINVIEW_NONCE]) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::ILJ_ADMINVIEW_NONCE])), basename(__FILE__))
184 ) {
185 return $post_id;
186 }
187
188 $post_type = get_post_type_object($post->post_type);
189
190 if (!current_user_can($post_type->cap->edit_post, $post_id)) {
191 return $post_id;
192 }
193
194
195
196 if (array_key_exists(self::ILJ_META_KEY_BLACKLISTDEFINITION, $_POST)) {
197 $sanitized_blacklist_meta_value = sanitize_text_field(wp_unslash($_POST[self::ILJ_META_KEY_BLACKLISTDEFINITION]));
198 $keywordsblacklist = KeywordList::fromInput($sanitized_blacklist_meta_value);
199
200 {
201 update_post_meta(
202 $post_id,
203 self::ILJ_META_KEY_BLACKLISTDEFINITION,
204 array_slice($keywordsblacklist->getKeywords(), 0, 2)
205 );
206 }
207 }
208
209 if (array_key_exists(self::ILJ_IS_BLACKLISTED, $_POST) && true == $_POST[self::ILJ_IS_BLACKLISTED]) {
210 self::addToBlacklist($post_id, 'post');
211 } else {
212 self::removeFromBlacklist($post_id, 'post');
213 }
214
215 if (array_key_exists(Postmeta::ILJ_META_KEY_LINKDEFINITION . '_keys', $_POST)) {
216
217 $sanitized_meta_value = sanitize_text_field(wp_unslash($_POST[Postmeta::ILJ_META_KEY_LINKDEFINITION . '_keys']));
218 $keywords = KeywordList::fromInput($sanitized_meta_value);
219
220 // Retrieve the old keywords before updating
221 $old_keyword_list = KeywordList::fromMeta($post_id, 'post', Postmeta::ILJ_META_KEY_LINKDEFINITION);
222 $old_keyword_count = $old_keyword_list->getCount();
223 $update_status = self::set_keywords($post_id, $keywords);
224
225 /**
226 * Fires after keyword meta got saved
227 *
228 * @since 1.0.0
229 */
230 if (true == $update_status) {
231 $post_id = isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0;
232 $post_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : '';
233 $keyword_list = KeywordList::fromMeta($post_id, 'post', Postmeta::ILJ_META_KEY_LINKDEFINITION);
234 $keyword_count = $keyword_list->getCount();
235 if ($old_keyword_count > 0 && 0 == $keyword_count) {
236 Linkindex::delete_link_to($post_id, 'post');
237 Statistic::updateStatisticsInfo();
238 } elseif ($keyword_count > 0) {
239 do_action(self::ILJ_ACTION_AFTER_KEYWORDS_UPDATE, $post_id, 'post', $post_status);
240 }
241 }
242 }
243
244 }
245
246 /**
247 * Set keywords for a post id, returns boolean or int based on the value.
248 *
249 * @param int $post_id The post id.
250 * @param KeywordList $keywords The keyword list for post.
251 *
252 * @return bool|int
253 */
254 public static function set_keywords($post_id, $keywords) {
255 $prev_value = get_post_meta($post_id, Postmeta::ILJ_META_KEY_LINKDEFINITION, true);
256 return update_post_meta(
257 $post_id,
258 Postmeta::ILJ_META_KEY_LINKDEFINITION,
259 $keywords->getKeywords(),
260 $prev_value
261 );
262 }
263
264 /**
265 * Logic for adding the assets based on subscription
266 *
267 * @since 1.1.0
268 *
269 * @return void
270 */
271 public static function addAssets() {
272 $required_role = 'administrator';
273
274
275
276 if (!current_user_can($required_role)) {
277 return;
278 }
279
280 global $pagenow;
281
282 if (in_array($pagenow, array('post-new.php', 'post.php'))) {
283
284 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It gets the post type on the add/edit post admin dashboard page. No nonce verification needed.
285 if (!isset($_GET['post_type'])) {
286 self::registerAssets();
287 return;
288 }
289
290 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It gets the post type on the add/edit post admin dashboard page. No nonce verification needed.
291 $post_type = get_post_type_object(sanitize_text_field(wp_unslash($_GET['post_type'])));
292
293
294
295 if (!$post_type || !$post_type->public) {
296 return;
297 }
298
299 self::registerAssets();
300 }
301
302
303 }
304
305 /**
306 * Registering the assets for editor frontend
307 *
308 * @since 1.1.0
309 *
310 * @return void
311 */
312 private static function registerAssets() {
313 add_action(
314 'admin_enqueue_scripts',
315 function () {
316 \ILJ\Helper\Loader::enqueue_script('jquery-ui-sortable');
317 \ILJ\Helper\Loader::register_script(Editor::ILJ_KEYWORDS_HANDLE, ILJ_URL . 'admin/js/ilj_keywords.js', array(), ILJ_VERSION);
318 \ILJ\Helper\Loader::register_script(Editor::ILJ_EDITOR_HANDLE, ILJ_URL . 'admin/js/ilj_editor.js', array(), ILJ_VERSION);
319 \ILJ\Helper\Loader::register_script(Editor::ILJ_MODAL, ILJ_URL . 'admin/js/ilj_modal.js', array(), ILJ_VERSION);
320 wp_localize_script(Editor::ILJ_EDITOR_HANDLE, 'ilj_editor_duplicate', array(
321 'nonce' => wp_create_nonce('ilj-editor-action'),
322 ));
323 wp_localize_script(Editor::ILJ_EDITOR_HANDLE, 'ilj_editor_translation', array_merge(
324 Editor::getTranslation(),
325 array('nonce' => wp_create_nonce('render-keyword-meta-box-nonce'))
326 ));
327 wp_add_inline_script(Editor::ILJ_EDITOR_HANDLE, 'const ilj_editor_basic_restriction = ' . wp_json_encode(Editor::getBasicRestrictions()), 'before');
328 \ILJ\Helper\Loader::enqueue_script('ilj_tipso', ILJ_URL . 'admin/js/tipso.js', array(), ILJ_VERSION);
329 \ILJ\Helper\Loader::enqueue_script(Editor::ILJ_KEYWORDS_HANDLE);
330 \ILJ\Helper\Loader::enqueue_script(Editor::ILJ_EDITOR_HANDLE);
331 \ILJ\Helper\Loader::enqueue_script(Editor::ILJ_MODAL);
332 \ILJ\Helper\Loader::enqueue_style('ilj_tipso', ILJ_URL . 'admin/css/tipso.css', array(), ILJ_VERSION);
333 \ILJ\Helper\Loader::enqueue_style(Editor::ILJ_EDITOR_HANDLE, ILJ_URL . 'admin/css/ilj_editor.css', array(), ILJ_VERSION);
334 \ILJ\Helper\Loader::enqueue_style(Editor::ILJ_EDITOR_HANDLE, ILJ_URL . 'admin/css/ilj_modal.css', array(), ILJ_VERSION);
335 \ILJ\Helper\Loader::enqueue_style('ilj_ui', ILJ_URL . 'admin/css/ilj_ui.css', array(), ILJ_VERSION);
336 },
337 10,
338 1
339 );
340
341 }
342
343 /**
344 * Returns the frontend translation
345 *
346 * @since 1.0.1
347 *
348 * @return array
349 */
350 public static function getTranslation() {
351 $translation = array(
352 'add_keyword' => __('Add Keyword', 'internal-links'),
353 'placeholder_keyword' => __('Keyword', 'internal-links'),
354 'howto_case' => __('Keywords get used <strong>case insensitive</strong>', 'internal-links'),
355 'howto_keyword' => __('Separate multiple keywords by commas', 'internal-links'),
356 'howto_gap' => __('Configure the gap dimension.', 'internal-links') . ' ' . __('It represents the number of keywords that appear between your other keywords dynamically.', 'internal-links') . ' ' . __('Learn more in our documentation:', 'internal-links') . '<br><strong><a target="_blank" rel="noopener" href="' . Help::getLinkUrl('editor/', 'gaps', 'gap help', 'editor') . '">' . __('Click here to open', 'internal-links') . '</a></strong>',
357 'headline_gaps' => __('Keyword gaps', 'internal-links'),
358 'add_gap' => __('Add gap', 'internal-links'),
359 'gap_type' => __('Gap type', 'internal-links'),
360 'type_min' => __('Minimum', 'internal-links'),
361 'type_exact' => __('Exact', 'internal-links'),
362 'type_max' => __('Maximum', 'internal-links'),
363 'howto_gap_min' => __('Minimum amount of keywords within the gap.', 'internal-links') . ' ' . __('No upper limits.', 'internal-links'),
364 'howto_gap_exact' => __('Exact amount of keywords within the gap.', 'internal-links'),
365 'howto_gap_max' => __('Maximum amount of keywords within the gap.', 'internal-links'),
366 'howto_links_per_paragraph' => __('Overrides the general setting for the current asset.', 'internal-links'),
367 'howto_add_to_blacklist' => __('Toggle to add/remove from global blacklist.', 'internal-links'),
368 'howto_limit_incoming_links' => __('Toggle to add/remove incoming links limit.', 'internal-links'),
369 'howto_limit_outgoing_links' => __('Toggle to add/remove outgoing links limit.', 'internal-links'),
370 'insert_gaps' => __('Insert gaps between keywords', 'internal-links'),
371 'headline_configured_keywords' => __('Configured keywords', 'internal-links'),
372 'message_keyword_exists' => __('This keyword already exists.', 'internal-links'),
373 'message_no_keyword' => __('No keyword defined.', 'internal-links'),
374 'message_length_not_valid' => __('Length of given keyword not valid.', 'internal-links'),
375 'message_multiple_placeholder' => __('Multiple consecutive placeholders are not allowed.', 'internal-links'),
376 'no_keywords' => __('No keywords configured.', 'internal-links'),
377 'gap_hover_exact' => __('Exact keyword gap:', 'internal-links'),
378 'gap_hover_max' => __('Maximum keyword gap:', 'internal-links'),
379 'gap_hover_min' => __('Minimum keyword gap:', 'internal-links'),
380 'limit_incoming_links' => __('Limit incoming Links:', 'internal-links'),
381 'max_incoming_links' => __('Maximum incoming links:', 'internal-links'),
382 'blacklist_incoming_links' => __('Keywords, that don`t get linked in the current content:', 'internal-links'),
383 'message_limited_blacklist_keyword' => __('With the free Basic version of the Iternal Link Juicer, you can block 2 keywords from being linked.', 'internal-links'),
384 'message_limited_blacklist_keyword_upgrade' => sprintf('&raquo; <a href="%s">', get_admin_url(null, 'admin.php?page=' . AdminMenu::ILJ_MENUPAGE_SLUG . '-pricing')) . __('Upgrade to Pro and add unlimited keywords', 'internal-links') . '</a>',
385 'headline_configured_keywords_blacklist' => __('Configured keyword blacklist:', 'internal-links'),
386 'is_blacklisted' => __('Is on global blacklist:', 'internal-links'),
387 'limit_links_per_paragraph' => __('Limit links per paragraph:', 'internal-links'),
388 'max_links_per_paragraph' => __('Maximum links per paragraph:', 'internal-links'),
389 'limit_outgoing_links' => __('Limit outgoing Links:', 'internal-links'),
390 'max_outgoing_links' => __('Maximum outgoing links:', 'internal-links'),
391 'cache_cleared' => __('Cache cleared.', 'internal-links'),
392 'upgrade_to_pro_button_text' => __('Upgrade to Pro', 'internal-links'),
393 'upgrade_to_pro_link' => get_admin_url(null, 'admin.php?billing_cycle=annual&trial=true&page=' . AdminMenu::ILJ_MENUPAGE_SLUG . '-pricing'),
394 'pro_feature_title' => __('This is a PRO Feature.', 'internal-links'),
395 'keyword_duplicate_title' => __('Keyword Duplicate for:', 'internal-links')
396 );
397 return $translation;
398 }
399
400 /**
401 * Sets Basic version restrictions
402 *
403 * @version 1.2.15
404 *
405 * @return array
406 */
407 protected static function getBasicRestrictions() {
408 $current_screen = get_current_screen();
409 $basic_restrictions = array(
410 'blacklist_limit' => 2,
411 'is_active' => true,
412 'disable_title' => 'class="pro-title"',
413 'disable_setting' => 'pro-setting',
414 'disabled' => 'disabled',
415 'lock_icon' => '<span class="dashicons dashicons-lock tip" title="' . __('This feature is part of the Pro version', 'internal-links') . '"></span>',
416 'current_screen' => $current_screen->post_type,
417 );
418
419
420
421 return $basic_restrictions;
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436 /**
437 * Checks if an asset is on the blacklist
438 *
439 * @since 1.2.15
440 *
441 * @param int $id The asset ID
442 * @param string $type The asset type
443 * @return bool True if blacklisted , false if not
444 */
445 public static function isBlacklisted($id, $type) {
446 if ('post' == $type) {
447 $postBlacklist = Options::getOption(\ILJ\Core\Options\Blacklist::getKey());
448 $blacklisted = false;
449 if (is_array($postBlacklist)) {
450 if (in_array($id, $postBlacklist)) {
451 $blacklisted = true;
452 }
453 }
454 return $blacklisted;
455 }
456
457
458
459 return false;
460 }
461
462 /**
463 * Removes an asset from blacklist
464 *
465 * @since 1.2.15
466 *
467 * @param int $id The asset id
468 * @param string $type The asset type
469 * @return void
470 */
471 protected static function removeFromBlacklist($id, $type) {
472 $blacklist = array();
473
474 if ('post' == $type) {
475 $blacklist = Options::getOption(\ILJ\Core\Options\Blacklist::getKey());
476 }
477
478
479
480 $blacklist = is_array($blacklist) ? $blacklist : array();
481
482 if (($key = array_search($id, $blacklist)) !== false) {
483 unset($blacklist[$key]);
484 } else {
485 return;
486 }
487
488 if ('post' == $type) {
489 Options::setOption(\ILJ\Core\Options\Blacklist::getKey(), $blacklist);
490 }
491
492
493 }
494
495 /**
496 * Adds ID to Blacklist Option of post/terms
497 *
498 * @since 1.2.15
499 *
500 * @param int $id The asset id
501 * @param string $type The asset type
502 * @return void
503 */
504 protected static function addToBlacklist($id, $type) {
505 $blacklist = array();
506
507 if ('post' == $type) {
508 $blacklist = Options::getOption(\ILJ\Core\Options\Blacklist::getKey());
509 }
510
511
512
513 $blacklist = is_array($blacklist) ? $blacklist : array();
514
515 if (in_array($id, $blacklist)) {
516 return;
517 }
518
519 $blacklist[] = $id;
520
521 if ('post' == $type) {
522 Options::setOption(\ILJ\Core\Options\Blacklist::getKey(), $blacklist);
523 }
524
525
526 }
527
528 /**
529 * Ajax callback that reset keyword meta box for terms
530 *
531 * @return void
532 */
533 public static function render_keyword_meta_box_callback() {
534 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'render-keyword-meta-box-nonce')) {
535 die();
536 }
537 if (!current_user_can('manage_options')) {
538 return;
539 }
540 ob_start();
541 self::render_keyword_meta_box_taxonomy__premium_only(null, 'create');
542 $output = ob_get_clean();
543
544 wp_send_json_success($output);
545 }
546 }
547