Changeset 3801389
- Timestamp:
- 11/14/2026 04:10:40 AM (less than one hour ago)
- Location:
- clickflame-rank-any-site
- Files:
-
- 2 added
- 20 edited
- 1 copied
-
tags/2.14.0 (copied) (copied from clickflame-rank-any-site/trunk )
-
tags/2.14.0/assets/admin.js (modified) (1 diff)
-
tags/2.14.0/clickflame-rank-any-site.php (modified) (3 diffs)
-
tags/2.14.0/includes/admin.php (modified) (2 diffs)
-
tags/2.14.0/includes/citations.php (modified) (6 diffs)
-
tags/2.14.0/includes/facts.php (modified) (9 diffs)
-
tags/2.14.0/includes/fixes.php (modified) (2 diffs)
-
tags/2.14.0/includes/inspect.php (modified) (6 diffs)
-
tags/2.14.0/includes/links.php (modified) (1 diff)
-
tags/2.14.0/includes/speed.php (modified) (1 diff)
-
tags/2.14.0/includes/trades.php (added)
-
tags/2.14.0/readme.txt (modified) (3 diffs)
-
trunk/assets/admin.js (modified) (1 diff)
-
trunk/clickflame-rank-any-site.php (modified) (3 diffs)
-
trunk/includes/admin.php (modified) (2 diffs)
-
trunk/includes/citations.php (modified) (6 diffs)
-
trunk/includes/facts.php (modified) (9 diffs)
-
trunk/includes/fixes.php (modified) (2 diffs)
-
trunk/includes/inspect.php (modified) (6 diffs)
-
trunk/includes/links.php (modified) (1 diff)
-
trunk/includes/speed.php (modified) (1 diff)
-
trunk/includes/trades.php (added)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
clickflame-rank-any-site/tags/2.14.0/assets/admin.js
r3799460 r3801389 214 214 .catch(function () { 215 215 fails += 1; 216 // Never spin forever: after six failed calls in a row, stop and say why. 217 if (fails >= 6) { 218 text.textContent = cfg.stuck || cfg.wait; 219 box.classList.add('is-bad'); 220 return; 221 } 216 222 text.textContent = cfg.wait; 217 223 setTimeout(step, Math.min(30000, 3000 * fails)); -
clickflame-rank-any-site/tags/2.14.0/clickflame-rank-any-site.php
r3799460 r3801389 4 4 * Plugin URI: https://rankanysite.com/how-it-works 5 5 * Description: Find and track your business listings across directories and check that each shows your right name, phone and place. Also checks what your live pages serve to Google and AI tools, and fixes it in one click. 6 * Version: 2.1 3.06 * Version: 2.14.0 7 7 * Requires at least: 6.9 8 8 * Requires PHP: 7.4 … … 36 36 } 37 37 38 define( 'RANKANYSITE_VERSION', '2.1 3.0' );38 define( 'RANKANYSITE_VERSION', '2.14.0' ); 39 39 define( 'RANKANYSITE_FILE', __FILE__ ); 40 40 define( 'RANKANYSITE_API', 'https://rankanysite.com' ); … … 45 45 require_once __DIR__ . '/includes/meta.php'; 46 46 require_once __DIR__ . '/includes/frontend.php'; 47 require_once __DIR__ . '/includes/trades.php'; 47 48 require_once __DIR__ . '/includes/facts.php'; 48 49 require_once __DIR__ . '/includes/house.php'; -
clickflame-rank-any-site/tags/2.14.0/includes/admin.php
r3799460 r3801389 46 46 'build' => __( 'Building your fixes...', 'clickflame-rank-any-site' ), 47 47 'wait' => __( 'Still working, your site is a little slow to answer...', 'clickflame-rank-any-site' ), 48 'stuck' => __( 'The scan stopped: this site could not reach Rank Any Site after several tries. Check the key on this screen, make sure your host allows outgoing connections, then press Rescan Now.', 'clickflame-rank-any-site' ), 48 49 'done' => __( 'Done. Loading your results...', 'clickflame-rank-any-site' ), 49 50 'working' => __( 'Working...', 'clickflame-rank-any-site' ), … … 298 299 } else { 299 300 rankanysite_tile( __( 'fixes live', 'clickflame-rank-any-site' ), 0 ); 301 } 302 // Listings from the Citation Tracker, so a check there moves this number too. 303 if ( function_exists( 'rankanysite_cite_rows' ) && rankanysite_module_on( 'citations' ) ) { 304 $live = 0; 305 foreach ( rankanysite_cite_rows() as $crow ) { 306 $live += 'live' === $crow['status'] ? 1 : 0; 307 } 308 rankanysite_tile( __( 'live listings', 'clickflame-rank-any-site' ), $live, $live ? 'ras-tone-green' : '' ); 300 309 } 301 310 echo '</div>'; -
clickflame-rank-any-site/tags/2.14.0/includes/citations.php
r3790523 r3801389 79 79 ), 80 80 ); 81 return array_merge( $core, isset( $trade[ $type ] ) ? $trade[ $type ] : array() ); 81 $extra = isset( $trade[ $type ] ) ? $trade[ $type ] : array(); 82 // Trades that share a schema type (water damage and roofing are both home trades) still get the home directories. 83 if ( ! $extra && array_intersect( rankanysite_trade_tags(), array( 'contractors', 'plumbers', 'electricians', 'hvac', 'cleaning', 'landscaping', 'pest-control', 'locksmiths' ) ) ) { 84 $extra = $home; 85 } 86 return array_merge( $core, $extra ); 82 87 } 83 88 … … 843 848 echo '<li>' . esc_html( home_url( '/' ) ) . '</li></ul></section>'; 844 849 845 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><section class="ras-card"> <div class="ras-card-head"><h2>' . esc_html__( 'Your Listings', 'clickflame-rank-any-site' ) . '</h2></div>';850 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><section class="ras-card">'; 846 851 wp_nonce_field( 'rankanysite_cite_save' ); 847 852 echo '<input type="hidden" name="action" value="rankanysite_cite_save">'; 848 853 echo '<button type="submit" class="screen-reader-text" tabindex="-1">' . esc_html__( 'Save', 'clickflame-rank-any-site' ) . '</button>'; 854 echo '<div class="ras-card-head"><h2>' . esc_html__( 'Your Listings', 'clickflame-rank-any-site' ) . '</h2><button type="submit" class="ras-btn ras-btn-primary" name="check_all" value="1">' . esc_html__( 'Check All', 'clickflame-rank-any-site' ) . '</button></div>'; 849 855 echo '<table class="widefat striped ras-cite ras-cite-track"><thead><tr><th>' . esc_html__( 'Directory', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Status', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Listing Address', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'What The Page Shows', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Note', 'clickflame-rank-any-site' ) . '</th><th></th></tr></thead><tbody>'; 850 856 foreach ( $rows as $id => $r ) { … … 891 897 892 898 /** 899 * Is a catalog directory a general one (fits any business) rather than one for some other trade? 900 * 901 * @param array $c Catalog row. 902 * @return bool 903 */ 904 function rankanysite_cite_is_general( $c ) { 905 return count( $c['t'] ) >= 9 || array_intersect( $c['g'], array( 'major', 'free-usa' ) ); 906 } 907 908 /** 893 909 * The Finder tab. 894 910 * … … 901 917 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read only filters. 902 918 $q = isset( $_GET['fq'] ) ? sanitize_text_field( wp_unslash( $_GET['fq'] ) ) : ''; 903 $scope = isset( $_GET['scope'] ) && 'all' === $_GET['scope'] ? 'all' : 'trade'; 919 $scope = isset( $_GET['scope'] ) && in_array( $_GET['scope'], array( 'all', 'only' ), true ) ? sanitize_key( $_GET['scope'] ) : 'trade'; 920 $sort = isset( $_GET['sort'] ) && in_array( $_GET['sort'], array( 'name', 'cost' ), true ) ? sanitize_key( $_GET['sort'] ) : 'strength'; 904 921 $paid = ! empty( $_GET['paid'] ); 905 922 $page = isset( $_GET['p'] ) ? max( 1, (int) $_GET['p'] ) : 1; 906 923 // phpcs:enable 907 $t rade = rankanysite_citation_trade( $f['type']);924 $tags = rankanysite_trade_tags(); 908 925 $rows = rankanysite_cite_rows(); 909 926 $track = array( … … 915 932 $track[ $r['host'] ] = true; 916 933 } 917 $list = array(); 934 $trade_list = array(); 935 $general = array(); 918 936 foreach ( rankanysite_citation_catalog() as $c ) { 919 937 if ( ! $paid && 'Paid' === $c['c'] ) { 920 938 continue; 921 939 } 922 if ( 'trade' === $scope && '' !== $trade && ! in_array( $trade, $c['t'], true ) && empty( $c['g'] ) ) {923 continue;924 }925 940 if ( '' !== $q && false === stripos( $c['n'] . ' ' . $c['d'], $q ) ) { 926 941 continue; 927 942 } 928 $list[] = $c; 929 } 943 $mine = $tags && array_intersect( $tags, $c['t'] ) && count( $c['t'] ) < 9; 944 if ( 'all' === $scope ) { 945 $general[] = $c; 946 } elseif ( $mine ) { 947 $c['trade'] = true; 948 $trade_list[] = $c; 949 } elseif ( 'trade' === $scope && ( rankanysite_cite_is_general( $c ) || ( ! $tags && ! $c['t'] && $c['g'] ) ) ) { 950 $general[] = $c; 951 } 952 } 953 $order = function ( $a, $b ) use ( $sort ) { 954 if ( 'name' === $sort ) { 955 return strcasecmp( $a['n'], $b['n'] ); 956 } 957 if ( 'cost' === $sort ) { 958 $rank = array( 959 'Free' => 0, 960 'Freemium' => 1, 961 '' => 2, 962 'Paid' => 3, 963 ); 964 $ra = isset( $rank[ $a['c'] ] ) ? $rank[ $a['c'] ] : 2; 965 $rb = isset( $rank[ $b['c'] ] ) ? $rank[ $b['c'] ] : 2; 966 return $ra === $rb ? $b['s'] - $a['s'] : $ra - $rb; 967 } 968 return $b['s'] - $a['s']; 969 }; 970 usort( $trade_list, $order ); 971 usort( $general, $order ); 972 // Trade directories first, then general ones. Sorting applies inside each part. 973 $list = array_merge( $trade_list, $general ); 974 $ours = '' === $q ? rankanysite_network_for_me() : array(); 930 975 $per = 40; 931 976 $total = count( $list ); 932 977 $list = array_slice( $list, ( $page - 1 ) * $per, $per ); 933 $types = rankanysite_business_types(); 934 935 echo '<section class="ras-card"><div class="ras-card-head"><h2>' . esc_html( sprintf( /* translators: 1: number of directories, 2: business type. */ _n( '%1$d Directory For A %2$s', '%1$d Directories For A %2$s', $total, 'clickflame-rank-any-site' ), $total, isset( $types[ $f['type'] ] ) ? $types[ $f['type'] ] : __( 'Local business', 'clickflame-rank-any-site' ) ) ) . '</h2></div>';936 echo '<p>' . esc_html __( 'Strongest first. Find By Name and Find By Phone open a search in your own browser, so you can see whether a listing exists. Track puts the directory on your tracker. Paste the listing address there once you have it.', 'clickflame-rank-any-site' ) . '</p>';978 979 echo '<section class="ras-card"><div class="ras-card-head"><h2>' . esc_html( sprintf( /* translators: 1: number of directories, 2: trade. */ _n( '%1$d Directory For %2$s', '%1$d Directories For %2$s', $total, 'clickflame-rank-any-site' ), $total, rankanysite_trade_label() ) ) . '</h2></div>'; 980 /* translators: %s: number of trade directories. */ 981 echo '<p>' . esc_html( sprintf( __( '%s made for your trade come first, then directories any business can join. Change your trade in Your Facts. Find By Name and Find By Phone open a search in your own browser, so you can see whether a listing exists. Track puts the directory on your tracker.', 'clickflame-rank-any-site' ), sprintf( /* translators: %d: number. */ _n( '%d directory', '%d directories', count( $trade_list ), 'clickflame-rank-any-site' ), count( $trade_list ) ) ) ) . ' <a href="' . esc_url( admin_url( 'admin.php?page=rank-any-site-facts' ) ) . '">' . esc_html__( 'Your Facts', 'clickflame-rank-any-site' ) . '</a></p>'; 937 982 echo '<form method="get" action="' . esc_url( admin_url( 'admin.php' ) ) . '"><input type="hidden" name="page" value="rank-any-site-citations"><input type="hidden" name="tab" value="finder">'; 938 echo '<input type="search" name="fq" value="' . esc_attr( $q ) . '" placeholder="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '" aria-label="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '"> <select name="scope" aria-label="' . esc_attr__( 'Which directories', 'clickflame-rank-any-site' ) . '"><option value="trade"' . selected( $scope, 'trade', false ) . '>' . esc_html__( 'My trade and general', 'clickflame-rank-any-site' ) . '</option><option value="all"' . selected( $scope, 'all', false ) . '>' . esc_html__( 'Every directory', 'clickflame-rank-any-site' ) . '</option></select> <label><input type="checkbox" name="paid" value="1"' . checked( $paid, true, false ) . '> ' . esc_html__( 'Include paid', 'clickflame-rank-any-site' ) . '</label> <button type="submit" class="button">' . esc_html__( 'Show', 'clickflame-rank-any-site' ) . '</button></form>'; 983 echo '<input type="search" name="fq" value="' . esc_attr( $q ) . '" placeholder="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '" aria-label="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '"> '; 984 echo '<select name="scope" aria-label="' . esc_attr__( 'Which directories', 'clickflame-rank-any-site' ) . '"><option value="trade"' . selected( $scope, 'trade', false ) . '>' . esc_html__( 'My trade first, then general', 'clickflame-rank-any-site' ) . '</option><option value="only"' . selected( $scope, 'only', false ) . '>' . esc_html__( 'My trade only', 'clickflame-rank-any-site' ) . '</option><option value="all"' . selected( $scope, 'all', false ) . '>' . esc_html__( 'Every directory', 'clickflame-rank-any-site' ) . '</option></select> '; 985 echo '<select name="sort" aria-label="' . esc_attr__( 'Sort by', 'clickflame-rank-any-site' ) . '"><option value="strength"' . selected( $sort, 'strength', false ) . '>' . esc_html__( 'Strongest first', 'clickflame-rank-any-site' ) . '</option><option value="name"' . selected( $sort, 'name', false ) . '>' . esc_html__( 'Name, A to Z', 'clickflame-rank-any-site' ) . '</option><option value="cost"' . selected( $sort, 'cost', false ) . '>' . esc_html__( 'Free first', 'clickflame-rank-any-site' ) . '</option></select> '; 986 echo '<label><input type="checkbox" name="paid" value="1"' . checked( $paid, true, false ) . '> ' . esc_html__( 'Include paid', 'clickflame-rank-any-site' ) . '</label> <button type="submit" class="button">' . esc_html__( 'Show', 'clickflame-rank-any-site' ) . '</button></form>'; 987 echo '<p class="ras-muted">' . esc_html__( 'Strength is the directory\'s domain rank from DataForSEO, on a 0 to 1000 scale. It measures how many sites link to the directory and how strong those links are, the way PageRank does. Higher means a listing there carries more weight. Directories run by our network show no number here so the list does not grade our own work.', 'clickflame-rank-any-site' ) . '</p>'; 939 988 echo '<table class="widefat striped ras-cite"><thead><tr><th>' . esc_html__( 'Directory', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Cost', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Strength', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Do You Have One?', 'clickflame-rank-any-site' ) . '</th><th></th></tr></thead><tbody>'; 940 989 $args = array_filter( 941 990 array( 942 991 'fq' => $q, 943 'scope' => 'all' === $scope ? 'all' : '', 992 'scope' => 'trade' !== $scope ? $scope : '', 993 'sort' => 'strength' !== $sort ? $sort : '', 944 994 'paid' => $paid ? '1' : '', 945 995 ) 946 996 ); 947 997 $here = add_query_arg( array_merge( $args, array( 'p' => $page ) ), $base . '&tab=finder' ); 948 foreach ( $list as $c) {949 echo '<tr><td><b>' . esc_html( $c['n'] ) . '</b> <div class="ras-muted">' . esc_html( $c['d'] ) . '</div></td><td>' . esc_html( '' !== $c['c'] ? $c['c'] : __( 'Not known', 'clickflame-rank-any-site' ) ) . '</td><td>' . esc_html( (string) $c['s']) . '</td><td class="ras-cite-links">' . wp_kses_post( rankanysite_cite_find_links( $c['d'], $want ) ) . '<a href="' . esc_url( $c['u'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Add Or Claim', 'clickflame-rank-any-site' ) . '</a></td><td>';998 $row = function ( $c, $badge ) use ( $want, $track, $here ) { 999 echo '<tr><td><b>' . esc_html( $c['n'] ) . '</b> ' . wp_kses_post( $badge ) . '<div class="ras-muted">' . esc_html( $c['d'] ) . '</div></td><td>' . esc_html( '' !== $c['c'] ? $c['c'] : __( 'Not known', 'clickflame-rank-any-site' ) ) . '</td><td>' . esc_html( '' !== (string) $c['s'] ? (string) $c['s'] : '' ) . '</td><td class="ras-cite-links">' . wp_kses_post( rankanysite_cite_find_links( $c['d'], $want ) ) . '<a href="' . esc_url( $c['u'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Add Or Claim', 'clickflame-rank-any-site' ) . '</a></td><td>'; 950 1000 if ( isset( $track[ $c['d'] ] ) ) { 951 1001 echo '<span class="ras-pill ras-ok">' . esc_html__( 'Tracking', 'clickflame-rank-any-site' ) . '</span>'; … … 956 1006 } 957 1007 echo '</td></tr>'; 1008 }; 1009 if ( 1 === $page ) { 1010 foreach ( $ours as $d ) { 1011 $row( 1012 array( 1013 'd' => $d[0], 1014 'n' => $d[1], 1015 'u' => $d[2], 1016 'c' => 'Free', 1017 's' => '', 1018 ), 1019 '<span class="ras-pill ras-ok">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</span> <span class="ras-pill">' . esc_html__( 'Run by Clickflame, the makers of this plugin', 'clickflame-rank-any-site' ) . '</span>' 1020 ); 1021 } 1022 } 1023 foreach ( $list as $c ) { 1024 $row( $c, ! empty( $c['trade'] ) ? '<span class="ras-pill ras-ok">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</span>' : '' ); 958 1025 } 959 1026 echo '</tbody></table>'; 960 if ( ! $list ) {961 echo '<p>' . esc_html __( 'No directories match that search.', 'clickflame-rank-any-site') . '</p>';1027 if ( ! $list && ! $ours ) { 1028 echo '<p>' . esc_html( 'only' === $scope ? __( 'No directories in the list are made for your trade yet. Pick "My trade first, then general" to see the ones any business can join.', 'clickflame-rank-any-site' ) : __( 'No directories match that search.', 'clickflame-rank-any-site' ) ) . '</p>'; 962 1029 } 963 1030 if ( $total > $per ) { -
clickflame-rank-any-site/tags/2.14.0/includes/facts.php
r3790523 r3801389 40 40 'HealthAndBeautyBusiness' => __( 'Salon or spa', 'clickflame-rank-any-site' ), 41 41 'ProfessionalService' => __( 'Professional service', 'clickflame-rank-any-site' ), 42 ); 42 ) + rankanysite_trade_schema_types(); 43 } 44 45 /** 46 * Schema types the trades use, labeled by their first trade, so a saved type from any trade validates. 47 * 48 * @return array 49 */ 50 function rankanysite_trade_schema_types() { 51 $out = array(); 52 foreach ( rankanysite_trades() as $t ) { 53 if ( ! isset( $out[ $t[2] ] ) ) { 54 $out[ $t[2] ] = $t[1]; 55 } 56 } 57 return $out; 43 58 } 44 59 … … 158 173 'help' => __( 'Add the lookup page where anyone can check the number. That link is what makes the claim believable.', 'clickflame-rank-any-site' ), 159 174 'fields' => array( 160 'name' => array( __( 'What it is', 'clickflame-rank-any-site' ), 'text', __( 'C-39 Roofing Contractor', 'clickflame-rank-any-site' )),175 'name' => array( __( 'What it is', 'clickflame-rank-any-site' ), 'text', '@license_name' ), 161 176 'number' => array( __( 'Number', 'clickflame-rank-any-site' ), 'text', '' ), 162 'issuer' => array( __( 'Issued by', 'clickflame-rank-any-site' ), 'text', __( 'California CSLB', 'clickflame-rank-any-site' )),177 'issuer' => array( __( 'Issued by', 'clickflame-rank-any-site' ), 'text', '@license_issuer' ), 163 178 'url' => array( __( 'Lookup link', 'clickflame-rank-any-site' ), 'url', 'https://' ), 164 179 ), … … 219 234 220 235 echo '<section class="ras-card"><h2>' . esc_html__( 'The business', 'clickflame-rank-any-site' ) . '</h2><div class="ras-form">'; 221 rankanysite_fact_input( 'name', __( 'Business name', 'clickflame-rank-any-site' ), $f['name'], get_bloginfo( 'name' ) ); 222 echo '<p><label for="ras-f-type">' . esc_html__( 'What kind of business', 'clickflame-rank-any-site' ) . '</label><select id="ras-f-type" name="rankanysite_facts[type]">'; 223 foreach ( rankanysite_business_types() as $k => $label ) { 224 echo '<option value="' . esc_attr( $k ) . '"' . selected( $k, $f['type'], false ) . '>' . esc_html( $label ) . '</option>'; 225 } 226 echo '</select></p>'; 227 echo '<p class="ras-wide"><label for="ras-f-summary">' . esc_html__( 'One sentence about what you do and where', 'clickflame-rank-any-site' ) . '</label><input type="text" id="ras-f-summary" name="rankanysite_facts[summary]" value="' . esc_attr( $f['summary'] ) . '" placeholder="' . esc_attr__( 'Family-run mobile mechanic serving Riverside County since 2009.', 'clickflame-rank-any-site' ) . '"></p>'; 236 // Real values, not gray examples: the site title is filled in as the name until the owner changes it. 237 rankanysite_fact_input( 'name', __( 'Business name', 'clickflame-rank-any-site' ), '' !== $f['name'] ? $f['name'] : get_bloginfo( 'name' ), '' ); 238 $mine = rankanysite_trade_key( $f ); 239 echo '<p><label for="ras-f-trade">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</label><select id="ras-f-trade" name="rankanysite_facts[trade]">'; 240 $by_group = array(); 241 foreach ( rankanysite_trades() as $k => $t ) { 242 $by_group[ $t[0] ][ $k ] = $t[1]; 243 } 244 foreach ( rankanysite_trade_groups() as $g => $glabel ) { 245 if ( empty( $by_group[ $g ] ) ) { 246 continue; 247 } 248 asort( $by_group[ $g ] ); 249 echo '<optgroup label="' . esc_attr( $glabel ) . '">'; 250 foreach ( $by_group[ $g ] as $k => $label ) { 251 echo '<option value="' . esc_attr( $k ) . '"' . selected( $k, $mine, false ) . '>' . esc_html( $label ) . '</option>'; 252 } 253 echo '</optgroup>'; 254 } 255 echo '</select><span class="ras-muted">' . esc_html__( 'Sets your schema type and which directories the Citation Finder shows first.', 'clickflame-rank-any-site' ) . '</span></p>'; 256 echo '<p class="ras-wide"><label for="ras-f-summary">' . esc_html__( 'One sentence about what you do and where', 'clickflame-rank-any-site' ) . '</label><input type="text" id="ras-f-summary" name="rankanysite_facts[summary]" value="' . esc_attr( $f['summary'] ) . '" placeholder="' . esc_attr( rankanysite_example( __( 'Family-run mobile mechanic serving Riverside County since 2009.', 'clickflame-rank-any-site' ) ) ) . '"></p>'; 228 257 rankanysite_fact_input( 'phone', __( 'Phone', 'clickflame-rank-any-site' ), $f['phone'], '(555) 555-0123' ); 229 258 rankanysite_fact_input( 'email', __( 'Email', 'clickflame-rank-any-site' ), $f['email'], '' ); … … 235 264 rankanysite_fact_input( 'street', __( 'Street address (leave empty if you only go to customers)', 'clickflame-rank-any-site' ), $f['street'], '' ); 236 265 rankanysite_fact_input( 'city', __( 'City', 'clickflame-rank-any-site' ), $f['city'], '' ); 237 rankanysite_fact_input( 'region', __( 'State or region', 'clickflame-rank-any-site' ), $f['region'], ' ' );266 rankanysite_fact_input( 'region', __( 'State or region', 'clickflame-rank-any-site' ), $f['region'], 'OH' ); 238 267 rankanysite_fact_input( 'postal', __( 'ZIP or postal code', 'clickflame-rank-any-site' ), $f['postal'], '' ); 239 268 rankanysite_fact_input( 'country', __( 'Country code', 'clickflame-rank-any-site' ), $f['country'], 'US' ); … … 256 285 foreach ( $def['fields'] as $k => $field ) { 257 286 $v = isset( $row[ $k ] ) ? $row[ $k ] : ''; 258 echo '<td><input type="' . ( 'url' === $field[1] ? 'url' : 'text' ) . '" aria-label="' . esc_attr( $field[0] ) . '" name="' . esc_attr( 'rankanysite_facts[' . $group . '][' . $i . '][' . $k . ']' ) . '" value="' . esc_attr( $v ) . '" placeholder="' . esc_attr( $field[2]) . '"></td>';287 echo '<td><input type="' . ( 'url' === $field[1] ? 'url' : 'text' ) . '" aria-label="' . esc_attr( $field[0] ) . '" name="' . esc_attr( 'rankanysite_facts[' . $group . '][' . $i . '][' . $k . ']' ) . '" value="' . esc_attr( $v ) . '" placeholder="' . esc_attr( rankanysite_example( $field[2] ) ) . '"></td>'; 259 288 } 260 289 echo '</tr>'; … … 280 309 281 310 /** 311 * A placeholder that reads as an example, so a gray hint is never mistaken for a saved value. 312 * 313 * @param string $text Example text, '@license_name' or '@license_issuer'. 314 * @return string 315 */ 316 function rankanysite_example( $text ) { 317 if ( '@license_name' === $text || '@license_issuer' === $text ) { 318 $ex = rankanysite_license_example(); 319 $text = '@license_name' === $text ? $ex[0] : $ex[1]; 320 } 321 if ( '' === $text || 'https://' === $text || 0 === strpos( $text, 'https://www.google.com/maps' ) ) { 322 return $text; 323 } 324 /* translators: %s: example value. */ 325 return sprintf( __( 'Example: %s', 'clickflame-rank-any-site' ), $text ); 326 } 327 328 /** 282 329 * One text input on the facts screen. 283 330 * … … 290 337 */ 291 338 function rankanysite_fact_input( $key, $label, $value, $placeholder, $type = 'text' ) { 339 $placeholder = rankanysite_example( $placeholder ); 292 340 echo '<p><label for="ras-f-' . esc_attr( $key ) . '">' . esc_html( $label ) . '</label><input type="' . esc_attr( $type ) . '" id="ras-f-' . esc_attr( $key ) . '" name="rankanysite_facts[' . esc_attr( $key ) . ']" value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '"></p>'; 293 341 } … … 310 358 $f['founded'] = isset( $raw['founded'] ) && preg_match( '/^(1[89]\d\d|20\d\d)$/', trim( (string) $raw['founded'] ) ) ? trim( (string) $raw['founded'] ) : ''; 311 359 $f['gbp'] = isset( $raw['gbp'] ) ? esc_url_raw( trim( (string) $raw['gbp'] ), array( 'https', 'http' ) ) : ''; 312 $type = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : 'LocalBusiness'; 313 $f['type'] = array_key_exists( $type, rankanysite_business_types() ) ? $type : 'LocalBusiness'; 360 $trade = isset( $raw['trade'] ) ? sanitize_key( (string) $raw['trade'] ) : ''; 361 $trades = rankanysite_trades(); 362 if ( isset( $trades[ $trade ] ) ) { 363 $f['trade'] = $trade; 364 $f['type'] = $trades[ $trade ][2]; 365 } else { 366 $type = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : $f['type']; 367 $f['type'] = array_key_exists( $type, rankanysite_business_types() ) ? $type : 'LocalBusiness'; 368 } 314 369 $f['areas'] = rankanysite_clean_lines( isset( $raw['areas'] ) ? $raw['areas'] : '' ); 315 370 $f['sameas'] = rankanysite_clean_lines( isset( $raw['sameas'] ) ? $raw['sameas'] : '', 'url' ); … … 545 600 } 546 601 $types = (array) $node['@type']; 602 // The original business types plus the one this site saved; not every type a trade can use (a Physician or School node elsewhere on a page is not this business). 603 $known = array_merge( array( 'Organization', 'LocalBusiness', 'Corporation', 'ProfessionalService', 'HomeAndConstructionBusiness', rankanysite_facts()['type'] ), array_keys( array_slice( rankanysite_business_types(), 0, 20, true ) ) ); 547 604 foreach ( $types as $t ) { 548 if ( in_array( $t, array( 'Organization', 'LocalBusiness', 'Corporation', 'ProfessionalService' ), true ) || array_key_exists( $t, rankanysite_business_types()) ) {605 if ( in_array( $t, $known, true ) ) { 549 606 return true; 550 607 } -
clickflame-rank-any-site/tags/2.14.0/includes/fixes.php
r3790523 r3801389 39 39 'cache' => array( 'site', __( 'Page caching', 'clickflame-rank-any-site' ), __( 'Saves each page and serves the saved copy to visitors who are not logged in. Clears itself on every change. Stays off beside another cache.', 'clickflame-rank-any-site' ) ), 40 40 'speed_images' => array( 'site', __( 'Load the main image first', 'clickflame-rank-any-site' ), __( 'The first image on a page loads first and is never lazy, the featured image is preloaded, other images decode in the background, and images get their width and height so the page does not jump.', 'clickflame-rank-any-site' ) ), 41 'speed_analytics' => array( 'site', __( 'Analytics after the page', 'clickflame-rank-any-site' ), __( ' Google Analytics and Tag Manager tags added by your theme or other plugins load after the page is ready instead of first.', 'clickflame-rank-any-site' ) ),41 'speed_analytics' => array( 'site', __( 'Analytics after the page', 'clickflame-rank-any-site' ), __( 'Your own Google Analytics or Tag Manager tag, added by your theme or another plugin, loads after the page is ready instead of first. Its ID stays yours; nothing is added. In the page source it shows as a rankanysite-later meta tag with a note above it.', 'clickflame-rank-any-site' ) ), 42 42 'speed_clean' => array( 'site', __( 'Remove page bloat', 'clickflame-rank-any-site' ), __( 'Removes the emoji script, the embed script, extra head links, and the admin icon font and heartbeat script for visitors who are not logged in.', 'clickflame-rank-any-site' ) ), 43 43 'speed_fonts' => array( 'site', __( 'Show text while fonts load', 'clickflame-rank-any-site' ), __( 'Google Fonts show text right away in a fallback font and connect to the font server early.', 'clickflame-rank-any-site' ) ), … … 387 387 */ 388 388 function rankanysite_recommended_site_fixes() { 389 $rec = array( 'dedupe', 'xrobots', 'schema', 'schema_clean', 'alts', 'llms', 'sitemap_plus', 'robots_sitemap', 'archives', 'speed_images', 'speed_analytics', 'speed_clean', 'speed_fonts', 'webp' ); 389 // speed_analytics is left out on purpose (2.14.0): moving someone's Google tag should be their own choice, never part of a one-click set. 390 $rec = array( 'dedupe', 'xrobots', 'schema', 'schema_clean', 'alts', 'llms', 'sitemap_plus', 'robots_sitemap', 'archives', 'speed_images', 'speed_clean', 'speed_fonts', 'webp' ); 390 391 if ( ! rankanysite_cache_on() && ! rankanysite_cache_conflicts() ) { 391 392 $rec[] = 'cache'; -
clickflame-rank-any-site/tags/2.14.0/includes/inspect.php
r3794812 r3801389 29 29 30 30 /** 31 * Fetch a URL on this site. Short timeout, no redirects followed.31 * Fetch a URL on this site. Waits up to 20 seconds (slow is not broken), no redirects followed. 32 32 * 33 33 * @param string $url URL. … … 38 38 $url, 39 39 array( 40 'timeout' => 10,40 'timeout' => 20, 41 41 'redirection' => 0, 42 42 'user-agent' => 'Mozilla/5.0 (compatible; RankAnySiteBot/2.3; +https://rankanysite.com/bot) WordPress self-check', … … 115 115 } 116 116 return $txt; 117 } 118 119 /** 120 * Split text holding several JSON values back to back ({..}{..}) into pieces. Quoted strings are respected. 121 * 122 * @param string $raw Text. 123 * @return string[] Pieces, or an empty array when it does not split cleanly. 124 */ 125 function rankanysite_json_values( $raw ) { 126 $parts = array(); 127 $depth = 0; 128 $start = -1; 129 $in_str = false; 130 $esc = false; 131 $len = strlen( $raw ); 132 for ( $i = 0; $i < $len; $i++ ) { 133 $c = $raw[ $i ]; 134 if ( $in_str ) { 135 if ( $esc ) { 136 $esc = false; 137 } elseif ( '\\' === $c ) { 138 $esc = true; 139 } elseif ( '"' === $c ) { 140 $in_str = false; 141 } 142 continue; 143 } 144 if ( '"' === $c ) { 145 $in_str = true; 146 } elseif ( '{' === $c || '[' === $c ) { 147 if ( 0 === $depth ) { 148 $start = $i; 149 } 150 ++$depth; 151 } elseif ( '}' === $c || ']' === $c ) { 152 --$depth; 153 if ( $depth < 0 ) { 154 return array(); 155 } 156 if ( 0 === $depth && $start >= 0 ) { 157 $parts[] = substr( $raw, $start, $i - $start + 1 ); 158 $start = -1; 159 } 160 } 161 } 162 return 0 === $depth ? $parts : array(); 117 163 } 118 164 … … 165 211 $ms = (int) round( ( microtime( true ) - $t0 ) * 1000 ); 166 212 if ( is_wp_error( $r ) ) { 213 $slow = false !== stripos( $r->get_error_message(), 'timed out' ) || false !== stripos( $r->get_error_message(), 'timeout' ); 167 214 return array( 168 215 'ok' => false, 169 /* translators: %s: error message. */ 170 'reason' => sprintf( __( 'Your site could not load its own page: %s', 'clickflame-rank-any-site' ), $r->get_error_message() ), 216 'slow' => $slow, 217 'reason' => $slow 218 ? __( 'The page took more than 20 seconds to load, so the check stopped. The page is slow, not broken. Turn on page caching and try again.', 'clickflame-rank-any-site' ) 219 /* translators: %s: error message. */ 220 : sprintf( __( 'Your site could not load its own page: %s', 'clickflame-rank-any-site' ), $r->get_error_message() ), 171 221 ); 172 222 } … … 253 303 rankanysite_find( $f, 'search', 'med', sprintf( __( 'Google sees %d page descriptions. Your theme or another plugin prints an extra one. Keep one.', 'clickflame-rank-any-site' ), count( $descs ) ), 'dedupe' ); 254 304 } elseif ( ! $descs ) { 255 rankanysite_find( $f, 'search', 'med', __( 'The page has no description, so Google writes its own from random text on the page.', 'clickflame-rank-any-site' ), 'description' ); 305 // A share description (og:description, twitter:description) is there but the one Google reads is not: say that, not "missing". 306 $share = $xp->query( '//meta[@property="og:description" or @name="og:description" or @name="twitter:description"]' ); 307 if ( $share->length && '' !== trim( $share->item( 0 )->getAttribute( 'content' ) ) ) { 308 rankanysite_find( $f, 'search', 'med', __( 'The page has a share description (og:description) but no meta name="description", which is the one Google reads. Add it with the same text; the share tag can stay.', 'clickflame-rank-any-site' ), 'description' ); 309 } else { 310 rankanysite_find( $f, 'search', 'med', __( 'The page has no description, so Google writes its own from random text on the page.', 'clickflame-rank-any-site' ), 'description' ); 311 } 256 312 } elseif ( '' !== $seo['desc'] && html_entity_decode( $descs[0], ENT_QUOTES ) !== html_entity_decode( rankanysite_fill_vars( $seo['desc'], $post_id ), ENT_QUOTES ) ) { 257 313 rankanysite_find( $f, 'search', 'med', __( 'The description the page serves is not the one you wrote here. Another plugin or the theme replaces it.', 'clickflame-rank-any-site' ), 'dedupe' ); … … 348 404 $ids = array(); 349 405 foreach ( $xp->query( '//script[@type="application/ld+json"]' ) as $s ) { 350 $j = json_decode( $s->textContent, true ); 406 $raw = trim( $s->textContent ); 407 $j = json_decode( $raw, true ); 351 408 if ( ! is_array( $j ) ) { 352 rankanysite_find( $f, 'ai', 'med', __( 'One block of schema on the page is broken JSON, so it is ignored.', 'clickflame-rank-any-site' ) ); 353 continue; 409 // Two or more objects in one tag with no [ ] around them: read them all, and say so. 410 $parts = rankanysite_json_values( $raw ); 411 $all = array(); 412 foreach ( $parts as $part ) { 413 $one = json_decode( $part, true ); 414 if ( ! is_array( $one ) ) { 415 $all = array(); 416 break; 417 } 418 $all[] = $one; 419 } 420 if ( count( $all ) > 1 ) { 421 $j = $all; 422 $jnames = array(); 423 foreach ( $all as $one ) { 424 if ( ! empty( $one['@type'] ) ) { 425 $jnames = array_merge( $jnames, (array) $one['@type'] ); 426 } 427 } 428 /* translators: 1: number of objects, 2: schema types. */ 429 rankanysite_find( $f, 'ai', 'low', sprintf( __( 'One schema block holds %1$d objects with no [ ] around them (%2$s). We read them, but Google may skip that block. Wrap them in [ ] or give each its own script tag.', 'clickflame-rank-any-site' ), count( $all ), implode( ', ', array_unique( array_map( 'strval', $jnames ) ) ) ) ); 430 } else { 431 preg_match_all( '/"@type"\s*:\s*"([A-Za-z]+)"/', $raw, $tm ); 432 $named = $tm[1] ? ' (' . implode( ', ', array_slice( array_unique( $tm[1] ), 0, 6 ) ) . ')' : ''; 433 /* translators: %s: schema types in the block, or empty. */ 434 rankanysite_find( $f, 'ai', 'med', sprintf( __( 'One block of schema on the page is broken JSON%s, so search engines ignore it.', 'clickflame-rank-any-site' ), $named ) ); 435 continue; 436 } 354 437 } 355 438 $nodes = isset( $j['@graph'] ) ? (array) $j['@graph'] : ( isset( $j[0] ) ? $j : array( $j ) ); -
clickflame-rank-any-site/tags/2.14.0/includes/links.php
r3794812 r3801389 92 92 } 93 93 $args = array( 94 'timeout' => 6,94 'timeout' => $internal ? 20 : 8, // our own slow pages get 20 s: slow is not broken 95 95 'redirection' => 5, 96 96 'user-agent' => 'Mozilla/5.0 (compatible; RankAnySiteBot/2.12; +https://rankanysite.com/bot) WordPress link check', -
clickflame-rank-any-site/tags/2.14.0/includes/speed.php
r3790523 r3801389 261 261 ); 262 262 if ( $later ) { 263 $meta = ''; 263 // Say in the page itself that this is the site's own tag, moved, not one we added. 264 $meta = "\n" . '<!-- Rank Any Site: this site\'s own Google tag, moved to load after the page (Analytics after the page fix). Nothing was added. -->'; 264 265 foreach ( array_unique( $later ) as $src ) { 265 266 $meta .= "\n" . '<meta name="rankanysite-later" content="' . esc_url( $src ) . '">'; -
clickflame-rank-any-site/tags/2.14.0/readme.txt
r3799460 r3801389 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 2.1 3.07 Stable tag: 2.14.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 155 155 == Changelog == 156 156 157 = 2.14.0 = 158 * Fixed: a page with only a share description (og:description) no longer shows as having no description. The live check now says the share tag is there and the meta name="description" tag Google reads is not. 159 * Fixed: a schema block with several objects back to back and no [ ] around them is now read, every type in it counts, and the check says how to make it valid. A truly broken block names the types it was meant to hold. 160 * Fixed: slow pages are no longer called broken. Page checks wait up to 20 seconds, and a page that takes longer is reported as slow. 161 * Fixed: the scan progress no longer spins forever when this site cannot reach Rank Any Site. It stops after several tries and says what to check. 162 * New: Your Facts has about 170 trades in plain words (water damage restoration, mold removal, garage doors, smog check and more). The trade sets your schema type and which directories come first. 163 * New: license examples on Your Facts follow your trade and state, such as IICRC certifications for restoration or your state's contractor board, instead of one California example for everyone. 164 * New: Your Facts fills in your real business name, and every gray hint now starts with "Example:" so it never looks saved. 165 * New: Citation Finder lists directories made for your trade first, then general ones, with a My Trade Only choice, sorting by strength, name or free first, and a plain note on what Strength means. Directories for other trades no longer show up in your trade view. 166 * New: Citation Finder shows the matching directories Clickflame runs for your trade at the top, marked as ours. 167 * New: Check All button at the top of Your Listings, and a live listings count on the dashboard. 168 * Changed: Analytics After The Page is no longer part of the recommended set, and the page source now has a note over the moved tag saying it is the site's own Google tag. 169 157 170 = 2.13.0 = 158 171 * Fixed: saving a license key or pressing Rescan Now no longer leaves the page loading for minutes on a slow site. The scan now runs in the background and the dashboard shows what it is doing, page by page, until your results are in. … … 265 278 == Upgrade Notice == 266 279 280 = 2.14.0 = 281 Slow pages are no longer called broken, the description and schema checks read more formats, and the Citation Finder puts your trade first. 282 267 283 = 2.10.0 = 268 284 Adds the Rebuild screen. -
clickflame-rank-any-site/trunk/assets/admin.js
r3799460 r3801389 214 214 .catch(function () { 215 215 fails += 1; 216 // Never spin forever: after six failed calls in a row, stop and say why. 217 if (fails >= 6) { 218 text.textContent = cfg.stuck || cfg.wait; 219 box.classList.add('is-bad'); 220 return; 221 } 216 222 text.textContent = cfg.wait; 217 223 setTimeout(step, Math.min(30000, 3000 * fails)); -
clickflame-rank-any-site/trunk/clickflame-rank-any-site.php
r3799460 r3801389 4 4 * Plugin URI: https://rankanysite.com/how-it-works 5 5 * Description: Find and track your business listings across directories and check that each shows your right name, phone and place. Also checks what your live pages serve to Google and AI tools, and fixes it in one click. 6 * Version: 2.1 3.06 * Version: 2.14.0 7 7 * Requires at least: 6.9 8 8 * Requires PHP: 7.4 … … 36 36 } 37 37 38 define( 'RANKANYSITE_VERSION', '2.1 3.0' );38 define( 'RANKANYSITE_VERSION', '2.14.0' ); 39 39 define( 'RANKANYSITE_FILE', __FILE__ ); 40 40 define( 'RANKANYSITE_API', 'https://rankanysite.com' ); … … 45 45 require_once __DIR__ . '/includes/meta.php'; 46 46 require_once __DIR__ . '/includes/frontend.php'; 47 require_once __DIR__ . '/includes/trades.php'; 47 48 require_once __DIR__ . '/includes/facts.php'; 48 49 require_once __DIR__ . '/includes/house.php'; -
clickflame-rank-any-site/trunk/includes/admin.php
r3799460 r3801389 46 46 'build' => __( 'Building your fixes...', 'clickflame-rank-any-site' ), 47 47 'wait' => __( 'Still working, your site is a little slow to answer...', 'clickflame-rank-any-site' ), 48 'stuck' => __( 'The scan stopped: this site could not reach Rank Any Site after several tries. Check the key on this screen, make sure your host allows outgoing connections, then press Rescan Now.', 'clickflame-rank-any-site' ), 48 49 'done' => __( 'Done. Loading your results...', 'clickflame-rank-any-site' ), 49 50 'working' => __( 'Working...', 'clickflame-rank-any-site' ), … … 298 299 } else { 299 300 rankanysite_tile( __( 'fixes live', 'clickflame-rank-any-site' ), 0 ); 301 } 302 // Listings from the Citation Tracker, so a check there moves this number too. 303 if ( function_exists( 'rankanysite_cite_rows' ) && rankanysite_module_on( 'citations' ) ) { 304 $live = 0; 305 foreach ( rankanysite_cite_rows() as $crow ) { 306 $live += 'live' === $crow['status'] ? 1 : 0; 307 } 308 rankanysite_tile( __( 'live listings', 'clickflame-rank-any-site' ), $live, $live ? 'ras-tone-green' : '' ); 300 309 } 301 310 echo '</div>'; -
clickflame-rank-any-site/trunk/includes/citations.php
r3790523 r3801389 79 79 ), 80 80 ); 81 return array_merge( $core, isset( $trade[ $type ] ) ? $trade[ $type ] : array() ); 81 $extra = isset( $trade[ $type ] ) ? $trade[ $type ] : array(); 82 // Trades that share a schema type (water damage and roofing are both home trades) still get the home directories. 83 if ( ! $extra && array_intersect( rankanysite_trade_tags(), array( 'contractors', 'plumbers', 'electricians', 'hvac', 'cleaning', 'landscaping', 'pest-control', 'locksmiths' ) ) ) { 84 $extra = $home; 85 } 86 return array_merge( $core, $extra ); 82 87 } 83 88 … … 843 848 echo '<li>' . esc_html( home_url( '/' ) ) . '</li></ul></section>'; 844 849 845 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><section class="ras-card"> <div class="ras-card-head"><h2>' . esc_html__( 'Your Listings', 'clickflame-rank-any-site' ) . '</h2></div>';850 echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '"><section class="ras-card">'; 846 851 wp_nonce_field( 'rankanysite_cite_save' ); 847 852 echo '<input type="hidden" name="action" value="rankanysite_cite_save">'; 848 853 echo '<button type="submit" class="screen-reader-text" tabindex="-1">' . esc_html__( 'Save', 'clickflame-rank-any-site' ) . '</button>'; 854 echo '<div class="ras-card-head"><h2>' . esc_html__( 'Your Listings', 'clickflame-rank-any-site' ) . '</h2><button type="submit" class="ras-btn ras-btn-primary" name="check_all" value="1">' . esc_html__( 'Check All', 'clickflame-rank-any-site' ) . '</button></div>'; 849 855 echo '<table class="widefat striped ras-cite ras-cite-track"><thead><tr><th>' . esc_html__( 'Directory', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Status', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Listing Address', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'What The Page Shows', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Note', 'clickflame-rank-any-site' ) . '</th><th></th></tr></thead><tbody>'; 850 856 foreach ( $rows as $id => $r ) { … … 891 897 892 898 /** 899 * Is a catalog directory a general one (fits any business) rather than one for some other trade? 900 * 901 * @param array $c Catalog row. 902 * @return bool 903 */ 904 function rankanysite_cite_is_general( $c ) { 905 return count( $c['t'] ) >= 9 || array_intersect( $c['g'], array( 'major', 'free-usa' ) ); 906 } 907 908 /** 893 909 * The Finder tab. 894 910 * … … 901 917 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read only filters. 902 918 $q = isset( $_GET['fq'] ) ? sanitize_text_field( wp_unslash( $_GET['fq'] ) ) : ''; 903 $scope = isset( $_GET['scope'] ) && 'all' === $_GET['scope'] ? 'all' : 'trade'; 919 $scope = isset( $_GET['scope'] ) && in_array( $_GET['scope'], array( 'all', 'only' ), true ) ? sanitize_key( $_GET['scope'] ) : 'trade'; 920 $sort = isset( $_GET['sort'] ) && in_array( $_GET['sort'], array( 'name', 'cost' ), true ) ? sanitize_key( $_GET['sort'] ) : 'strength'; 904 921 $paid = ! empty( $_GET['paid'] ); 905 922 $page = isset( $_GET['p'] ) ? max( 1, (int) $_GET['p'] ) : 1; 906 923 // phpcs:enable 907 $t rade = rankanysite_citation_trade( $f['type']);924 $tags = rankanysite_trade_tags(); 908 925 $rows = rankanysite_cite_rows(); 909 926 $track = array( … … 915 932 $track[ $r['host'] ] = true; 916 933 } 917 $list = array(); 934 $trade_list = array(); 935 $general = array(); 918 936 foreach ( rankanysite_citation_catalog() as $c ) { 919 937 if ( ! $paid && 'Paid' === $c['c'] ) { 920 938 continue; 921 939 } 922 if ( 'trade' === $scope && '' !== $trade && ! in_array( $trade, $c['t'], true ) && empty( $c['g'] ) ) {923 continue;924 }925 940 if ( '' !== $q && false === stripos( $c['n'] . ' ' . $c['d'], $q ) ) { 926 941 continue; 927 942 } 928 $list[] = $c; 929 } 943 $mine = $tags && array_intersect( $tags, $c['t'] ) && count( $c['t'] ) < 9; 944 if ( 'all' === $scope ) { 945 $general[] = $c; 946 } elseif ( $mine ) { 947 $c['trade'] = true; 948 $trade_list[] = $c; 949 } elseif ( 'trade' === $scope && ( rankanysite_cite_is_general( $c ) || ( ! $tags && ! $c['t'] && $c['g'] ) ) ) { 950 $general[] = $c; 951 } 952 } 953 $order = function ( $a, $b ) use ( $sort ) { 954 if ( 'name' === $sort ) { 955 return strcasecmp( $a['n'], $b['n'] ); 956 } 957 if ( 'cost' === $sort ) { 958 $rank = array( 959 'Free' => 0, 960 'Freemium' => 1, 961 '' => 2, 962 'Paid' => 3, 963 ); 964 $ra = isset( $rank[ $a['c'] ] ) ? $rank[ $a['c'] ] : 2; 965 $rb = isset( $rank[ $b['c'] ] ) ? $rank[ $b['c'] ] : 2; 966 return $ra === $rb ? $b['s'] - $a['s'] : $ra - $rb; 967 } 968 return $b['s'] - $a['s']; 969 }; 970 usort( $trade_list, $order ); 971 usort( $general, $order ); 972 // Trade directories first, then general ones. Sorting applies inside each part. 973 $list = array_merge( $trade_list, $general ); 974 $ours = '' === $q ? rankanysite_network_for_me() : array(); 930 975 $per = 40; 931 976 $total = count( $list ); 932 977 $list = array_slice( $list, ( $page - 1 ) * $per, $per ); 933 $types = rankanysite_business_types(); 934 935 echo '<section class="ras-card"><div class="ras-card-head"><h2>' . esc_html( sprintf( /* translators: 1: number of directories, 2: business type. */ _n( '%1$d Directory For A %2$s', '%1$d Directories For A %2$s', $total, 'clickflame-rank-any-site' ), $total, isset( $types[ $f['type'] ] ) ? $types[ $f['type'] ] : __( 'Local business', 'clickflame-rank-any-site' ) ) ) . '</h2></div>';936 echo '<p>' . esc_html __( 'Strongest first. Find By Name and Find By Phone open a search in your own browser, so you can see whether a listing exists. Track puts the directory on your tracker. Paste the listing address there once you have it.', 'clickflame-rank-any-site' ) . '</p>';978 979 echo '<section class="ras-card"><div class="ras-card-head"><h2>' . esc_html( sprintf( /* translators: 1: number of directories, 2: trade. */ _n( '%1$d Directory For %2$s', '%1$d Directories For %2$s', $total, 'clickflame-rank-any-site' ), $total, rankanysite_trade_label() ) ) . '</h2></div>'; 980 /* translators: %s: number of trade directories. */ 981 echo '<p>' . esc_html( sprintf( __( '%s made for your trade come first, then directories any business can join. Change your trade in Your Facts. Find By Name and Find By Phone open a search in your own browser, so you can see whether a listing exists. Track puts the directory on your tracker.', 'clickflame-rank-any-site' ), sprintf( /* translators: %d: number. */ _n( '%d directory', '%d directories', count( $trade_list ), 'clickflame-rank-any-site' ), count( $trade_list ) ) ) ) . ' <a href="' . esc_url( admin_url( 'admin.php?page=rank-any-site-facts' ) ) . '">' . esc_html__( 'Your Facts', 'clickflame-rank-any-site' ) . '</a></p>'; 937 982 echo '<form method="get" action="' . esc_url( admin_url( 'admin.php' ) ) . '"><input type="hidden" name="page" value="rank-any-site-citations"><input type="hidden" name="tab" value="finder">'; 938 echo '<input type="search" name="fq" value="' . esc_attr( $q ) . '" placeholder="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '" aria-label="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '"> <select name="scope" aria-label="' . esc_attr__( 'Which directories', 'clickflame-rank-any-site' ) . '"><option value="trade"' . selected( $scope, 'trade', false ) . '>' . esc_html__( 'My trade and general', 'clickflame-rank-any-site' ) . '</option><option value="all"' . selected( $scope, 'all', false ) . '>' . esc_html__( 'Every directory', 'clickflame-rank-any-site' ) . '</option></select> <label><input type="checkbox" name="paid" value="1"' . checked( $paid, true, false ) . '> ' . esc_html__( 'Include paid', 'clickflame-rank-any-site' ) . '</label> <button type="submit" class="button">' . esc_html__( 'Show', 'clickflame-rank-any-site' ) . '</button></form>'; 983 echo '<input type="search" name="fq" value="' . esc_attr( $q ) . '" placeholder="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '" aria-label="' . esc_attr__( 'Search directories', 'clickflame-rank-any-site' ) . '"> '; 984 echo '<select name="scope" aria-label="' . esc_attr__( 'Which directories', 'clickflame-rank-any-site' ) . '"><option value="trade"' . selected( $scope, 'trade', false ) . '>' . esc_html__( 'My trade first, then general', 'clickflame-rank-any-site' ) . '</option><option value="only"' . selected( $scope, 'only', false ) . '>' . esc_html__( 'My trade only', 'clickflame-rank-any-site' ) . '</option><option value="all"' . selected( $scope, 'all', false ) . '>' . esc_html__( 'Every directory', 'clickflame-rank-any-site' ) . '</option></select> '; 985 echo '<select name="sort" aria-label="' . esc_attr__( 'Sort by', 'clickflame-rank-any-site' ) . '"><option value="strength"' . selected( $sort, 'strength', false ) . '>' . esc_html__( 'Strongest first', 'clickflame-rank-any-site' ) . '</option><option value="name"' . selected( $sort, 'name', false ) . '>' . esc_html__( 'Name, A to Z', 'clickflame-rank-any-site' ) . '</option><option value="cost"' . selected( $sort, 'cost', false ) . '>' . esc_html__( 'Free first', 'clickflame-rank-any-site' ) . '</option></select> '; 986 echo '<label><input type="checkbox" name="paid" value="1"' . checked( $paid, true, false ) . '> ' . esc_html__( 'Include paid', 'clickflame-rank-any-site' ) . '</label> <button type="submit" class="button">' . esc_html__( 'Show', 'clickflame-rank-any-site' ) . '</button></form>'; 987 echo '<p class="ras-muted">' . esc_html__( 'Strength is the directory\'s domain rank from DataForSEO, on a 0 to 1000 scale. It measures how many sites link to the directory and how strong those links are, the way PageRank does. Higher means a listing there carries more weight. Directories run by our network show no number here so the list does not grade our own work.', 'clickflame-rank-any-site' ) . '</p>'; 939 988 echo '<table class="widefat striped ras-cite"><thead><tr><th>' . esc_html__( 'Directory', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Cost', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Strength', 'clickflame-rank-any-site' ) . '</th><th>' . esc_html__( 'Do You Have One?', 'clickflame-rank-any-site' ) . '</th><th></th></tr></thead><tbody>'; 940 989 $args = array_filter( 941 990 array( 942 991 'fq' => $q, 943 'scope' => 'all' === $scope ? 'all' : '', 992 'scope' => 'trade' !== $scope ? $scope : '', 993 'sort' => 'strength' !== $sort ? $sort : '', 944 994 'paid' => $paid ? '1' : '', 945 995 ) 946 996 ); 947 997 $here = add_query_arg( array_merge( $args, array( 'p' => $page ) ), $base . '&tab=finder' ); 948 foreach ( $list as $c) {949 echo '<tr><td><b>' . esc_html( $c['n'] ) . '</b> <div class="ras-muted">' . esc_html( $c['d'] ) . '</div></td><td>' . esc_html( '' !== $c['c'] ? $c['c'] : __( 'Not known', 'clickflame-rank-any-site' ) ) . '</td><td>' . esc_html( (string) $c['s']) . '</td><td class="ras-cite-links">' . wp_kses_post( rankanysite_cite_find_links( $c['d'], $want ) ) . '<a href="' . esc_url( $c['u'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Add Or Claim', 'clickflame-rank-any-site' ) . '</a></td><td>';998 $row = function ( $c, $badge ) use ( $want, $track, $here ) { 999 echo '<tr><td><b>' . esc_html( $c['n'] ) . '</b> ' . wp_kses_post( $badge ) . '<div class="ras-muted">' . esc_html( $c['d'] ) . '</div></td><td>' . esc_html( '' !== $c['c'] ? $c['c'] : __( 'Not known', 'clickflame-rank-any-site' ) ) . '</td><td>' . esc_html( '' !== (string) $c['s'] ? (string) $c['s'] : '' ) . '</td><td class="ras-cite-links">' . wp_kses_post( rankanysite_cite_find_links( $c['d'], $want ) ) . '<a href="' . esc_url( $c['u'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Add Or Claim', 'clickflame-rank-any-site' ) . '</a></td><td>'; 950 1000 if ( isset( $track[ $c['d'] ] ) ) { 951 1001 echo '<span class="ras-pill ras-ok">' . esc_html__( 'Tracking', 'clickflame-rank-any-site' ) . '</span>'; … … 956 1006 } 957 1007 echo '</td></tr>'; 1008 }; 1009 if ( 1 === $page ) { 1010 foreach ( $ours as $d ) { 1011 $row( 1012 array( 1013 'd' => $d[0], 1014 'n' => $d[1], 1015 'u' => $d[2], 1016 'c' => 'Free', 1017 's' => '', 1018 ), 1019 '<span class="ras-pill ras-ok">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</span> <span class="ras-pill">' . esc_html__( 'Run by Clickflame, the makers of this plugin', 'clickflame-rank-any-site' ) . '</span>' 1020 ); 1021 } 1022 } 1023 foreach ( $list as $c ) { 1024 $row( $c, ! empty( $c['trade'] ) ? '<span class="ras-pill ras-ok">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</span>' : '' ); 958 1025 } 959 1026 echo '</tbody></table>'; 960 if ( ! $list ) {961 echo '<p>' . esc_html __( 'No directories match that search.', 'clickflame-rank-any-site') . '</p>';1027 if ( ! $list && ! $ours ) { 1028 echo '<p>' . esc_html( 'only' === $scope ? __( 'No directories in the list are made for your trade yet. Pick "My trade first, then general" to see the ones any business can join.', 'clickflame-rank-any-site' ) : __( 'No directories match that search.', 'clickflame-rank-any-site' ) ) . '</p>'; 962 1029 } 963 1030 if ( $total > $per ) { -
clickflame-rank-any-site/trunk/includes/facts.php
r3790523 r3801389 40 40 'HealthAndBeautyBusiness' => __( 'Salon or spa', 'clickflame-rank-any-site' ), 41 41 'ProfessionalService' => __( 'Professional service', 'clickflame-rank-any-site' ), 42 ); 42 ) + rankanysite_trade_schema_types(); 43 } 44 45 /** 46 * Schema types the trades use, labeled by their first trade, so a saved type from any trade validates. 47 * 48 * @return array 49 */ 50 function rankanysite_trade_schema_types() { 51 $out = array(); 52 foreach ( rankanysite_trades() as $t ) { 53 if ( ! isset( $out[ $t[2] ] ) ) { 54 $out[ $t[2] ] = $t[1]; 55 } 56 } 57 return $out; 43 58 } 44 59 … … 158 173 'help' => __( 'Add the lookup page where anyone can check the number. That link is what makes the claim believable.', 'clickflame-rank-any-site' ), 159 174 'fields' => array( 160 'name' => array( __( 'What it is', 'clickflame-rank-any-site' ), 'text', __( 'C-39 Roofing Contractor', 'clickflame-rank-any-site' )),175 'name' => array( __( 'What it is', 'clickflame-rank-any-site' ), 'text', '@license_name' ), 161 176 'number' => array( __( 'Number', 'clickflame-rank-any-site' ), 'text', '' ), 162 'issuer' => array( __( 'Issued by', 'clickflame-rank-any-site' ), 'text', __( 'California CSLB', 'clickflame-rank-any-site' )),177 'issuer' => array( __( 'Issued by', 'clickflame-rank-any-site' ), 'text', '@license_issuer' ), 163 178 'url' => array( __( 'Lookup link', 'clickflame-rank-any-site' ), 'url', 'https://' ), 164 179 ), … … 219 234 220 235 echo '<section class="ras-card"><h2>' . esc_html__( 'The business', 'clickflame-rank-any-site' ) . '</h2><div class="ras-form">'; 221 rankanysite_fact_input( 'name', __( 'Business name', 'clickflame-rank-any-site' ), $f['name'], get_bloginfo( 'name' ) ); 222 echo '<p><label for="ras-f-type">' . esc_html__( 'What kind of business', 'clickflame-rank-any-site' ) . '</label><select id="ras-f-type" name="rankanysite_facts[type]">'; 223 foreach ( rankanysite_business_types() as $k => $label ) { 224 echo '<option value="' . esc_attr( $k ) . '"' . selected( $k, $f['type'], false ) . '>' . esc_html( $label ) . '</option>'; 225 } 226 echo '</select></p>'; 227 echo '<p class="ras-wide"><label for="ras-f-summary">' . esc_html__( 'One sentence about what you do and where', 'clickflame-rank-any-site' ) . '</label><input type="text" id="ras-f-summary" name="rankanysite_facts[summary]" value="' . esc_attr( $f['summary'] ) . '" placeholder="' . esc_attr__( 'Family-run mobile mechanic serving Riverside County since 2009.', 'clickflame-rank-any-site' ) . '"></p>'; 236 // Real values, not gray examples: the site title is filled in as the name until the owner changes it. 237 rankanysite_fact_input( 'name', __( 'Business name', 'clickflame-rank-any-site' ), '' !== $f['name'] ? $f['name'] : get_bloginfo( 'name' ), '' ); 238 $mine = rankanysite_trade_key( $f ); 239 echo '<p><label for="ras-f-trade">' . esc_html__( 'Your trade', 'clickflame-rank-any-site' ) . '</label><select id="ras-f-trade" name="rankanysite_facts[trade]">'; 240 $by_group = array(); 241 foreach ( rankanysite_trades() as $k => $t ) { 242 $by_group[ $t[0] ][ $k ] = $t[1]; 243 } 244 foreach ( rankanysite_trade_groups() as $g => $glabel ) { 245 if ( empty( $by_group[ $g ] ) ) { 246 continue; 247 } 248 asort( $by_group[ $g ] ); 249 echo '<optgroup label="' . esc_attr( $glabel ) . '">'; 250 foreach ( $by_group[ $g ] as $k => $label ) { 251 echo '<option value="' . esc_attr( $k ) . '"' . selected( $k, $mine, false ) . '>' . esc_html( $label ) . '</option>'; 252 } 253 echo '</optgroup>'; 254 } 255 echo '</select><span class="ras-muted">' . esc_html__( 'Sets your schema type and which directories the Citation Finder shows first.', 'clickflame-rank-any-site' ) . '</span></p>'; 256 echo '<p class="ras-wide"><label for="ras-f-summary">' . esc_html__( 'One sentence about what you do and where', 'clickflame-rank-any-site' ) . '</label><input type="text" id="ras-f-summary" name="rankanysite_facts[summary]" value="' . esc_attr( $f['summary'] ) . '" placeholder="' . esc_attr( rankanysite_example( __( 'Family-run mobile mechanic serving Riverside County since 2009.', 'clickflame-rank-any-site' ) ) ) . '"></p>'; 228 257 rankanysite_fact_input( 'phone', __( 'Phone', 'clickflame-rank-any-site' ), $f['phone'], '(555) 555-0123' ); 229 258 rankanysite_fact_input( 'email', __( 'Email', 'clickflame-rank-any-site' ), $f['email'], '' ); … … 235 264 rankanysite_fact_input( 'street', __( 'Street address (leave empty if you only go to customers)', 'clickflame-rank-any-site' ), $f['street'], '' ); 236 265 rankanysite_fact_input( 'city', __( 'City', 'clickflame-rank-any-site' ), $f['city'], '' ); 237 rankanysite_fact_input( 'region', __( 'State or region', 'clickflame-rank-any-site' ), $f['region'], ' ' );266 rankanysite_fact_input( 'region', __( 'State or region', 'clickflame-rank-any-site' ), $f['region'], 'OH' ); 238 267 rankanysite_fact_input( 'postal', __( 'ZIP or postal code', 'clickflame-rank-any-site' ), $f['postal'], '' ); 239 268 rankanysite_fact_input( 'country', __( 'Country code', 'clickflame-rank-any-site' ), $f['country'], 'US' ); … … 256 285 foreach ( $def['fields'] as $k => $field ) { 257 286 $v = isset( $row[ $k ] ) ? $row[ $k ] : ''; 258 echo '<td><input type="' . ( 'url' === $field[1] ? 'url' : 'text' ) . '" aria-label="' . esc_attr( $field[0] ) . '" name="' . esc_attr( 'rankanysite_facts[' . $group . '][' . $i . '][' . $k . ']' ) . '" value="' . esc_attr( $v ) . '" placeholder="' . esc_attr( $field[2]) . '"></td>';287 echo '<td><input type="' . ( 'url' === $field[1] ? 'url' : 'text' ) . '" aria-label="' . esc_attr( $field[0] ) . '" name="' . esc_attr( 'rankanysite_facts[' . $group . '][' . $i . '][' . $k . ']' ) . '" value="' . esc_attr( $v ) . '" placeholder="' . esc_attr( rankanysite_example( $field[2] ) ) . '"></td>'; 259 288 } 260 289 echo '</tr>'; … … 280 309 281 310 /** 311 * A placeholder that reads as an example, so a gray hint is never mistaken for a saved value. 312 * 313 * @param string $text Example text, '@license_name' or '@license_issuer'. 314 * @return string 315 */ 316 function rankanysite_example( $text ) { 317 if ( '@license_name' === $text || '@license_issuer' === $text ) { 318 $ex = rankanysite_license_example(); 319 $text = '@license_name' === $text ? $ex[0] : $ex[1]; 320 } 321 if ( '' === $text || 'https://' === $text || 0 === strpos( $text, 'https://www.google.com/maps' ) ) { 322 return $text; 323 } 324 /* translators: %s: example value. */ 325 return sprintf( __( 'Example: %s', 'clickflame-rank-any-site' ), $text ); 326 } 327 328 /** 282 329 * One text input on the facts screen. 283 330 * … … 290 337 */ 291 338 function rankanysite_fact_input( $key, $label, $value, $placeholder, $type = 'text' ) { 339 $placeholder = rankanysite_example( $placeholder ); 292 340 echo '<p><label for="ras-f-' . esc_attr( $key ) . '">' . esc_html( $label ) . '</label><input type="' . esc_attr( $type ) . '" id="ras-f-' . esc_attr( $key ) . '" name="rankanysite_facts[' . esc_attr( $key ) . ']" value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '"></p>'; 293 341 } … … 310 358 $f['founded'] = isset( $raw['founded'] ) && preg_match( '/^(1[89]\d\d|20\d\d)$/', trim( (string) $raw['founded'] ) ) ? trim( (string) $raw['founded'] ) : ''; 311 359 $f['gbp'] = isset( $raw['gbp'] ) ? esc_url_raw( trim( (string) $raw['gbp'] ), array( 'https', 'http' ) ) : ''; 312 $type = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : 'LocalBusiness'; 313 $f['type'] = array_key_exists( $type, rankanysite_business_types() ) ? $type : 'LocalBusiness'; 360 $trade = isset( $raw['trade'] ) ? sanitize_key( (string) $raw['trade'] ) : ''; 361 $trades = rankanysite_trades(); 362 if ( isset( $trades[ $trade ] ) ) { 363 $f['trade'] = $trade; 364 $f['type'] = $trades[ $trade ][2]; 365 } else { 366 $type = isset( $raw['type'] ) ? sanitize_text_field( (string) $raw['type'] ) : $f['type']; 367 $f['type'] = array_key_exists( $type, rankanysite_business_types() ) ? $type : 'LocalBusiness'; 368 } 314 369 $f['areas'] = rankanysite_clean_lines( isset( $raw['areas'] ) ? $raw['areas'] : '' ); 315 370 $f['sameas'] = rankanysite_clean_lines( isset( $raw['sameas'] ) ? $raw['sameas'] : '', 'url' ); … … 545 600 } 546 601 $types = (array) $node['@type']; 602 // The original business types plus the one this site saved; not every type a trade can use (a Physician or School node elsewhere on a page is not this business). 603 $known = array_merge( array( 'Organization', 'LocalBusiness', 'Corporation', 'ProfessionalService', 'HomeAndConstructionBusiness', rankanysite_facts()['type'] ), array_keys( array_slice( rankanysite_business_types(), 0, 20, true ) ) ); 547 604 foreach ( $types as $t ) { 548 if ( in_array( $t, array( 'Organization', 'LocalBusiness', 'Corporation', 'ProfessionalService' ), true ) || array_key_exists( $t, rankanysite_business_types()) ) {605 if ( in_array( $t, $known, true ) ) { 549 606 return true; 550 607 } -
clickflame-rank-any-site/trunk/includes/fixes.php
r3790523 r3801389 39 39 'cache' => array( 'site', __( 'Page caching', 'clickflame-rank-any-site' ), __( 'Saves each page and serves the saved copy to visitors who are not logged in. Clears itself on every change. Stays off beside another cache.', 'clickflame-rank-any-site' ) ), 40 40 'speed_images' => array( 'site', __( 'Load the main image first', 'clickflame-rank-any-site' ), __( 'The first image on a page loads first and is never lazy, the featured image is preloaded, other images decode in the background, and images get their width and height so the page does not jump.', 'clickflame-rank-any-site' ) ), 41 'speed_analytics' => array( 'site', __( 'Analytics after the page', 'clickflame-rank-any-site' ), __( ' Google Analytics and Tag Manager tags added by your theme or other plugins load after the page is ready instead of first.', 'clickflame-rank-any-site' ) ),41 'speed_analytics' => array( 'site', __( 'Analytics after the page', 'clickflame-rank-any-site' ), __( 'Your own Google Analytics or Tag Manager tag, added by your theme or another plugin, loads after the page is ready instead of first. Its ID stays yours; nothing is added. In the page source it shows as a rankanysite-later meta tag with a note above it.', 'clickflame-rank-any-site' ) ), 42 42 'speed_clean' => array( 'site', __( 'Remove page bloat', 'clickflame-rank-any-site' ), __( 'Removes the emoji script, the embed script, extra head links, and the admin icon font and heartbeat script for visitors who are not logged in.', 'clickflame-rank-any-site' ) ), 43 43 'speed_fonts' => array( 'site', __( 'Show text while fonts load', 'clickflame-rank-any-site' ), __( 'Google Fonts show text right away in a fallback font and connect to the font server early.', 'clickflame-rank-any-site' ) ), … … 387 387 */ 388 388 function rankanysite_recommended_site_fixes() { 389 $rec = array( 'dedupe', 'xrobots', 'schema', 'schema_clean', 'alts', 'llms', 'sitemap_plus', 'robots_sitemap', 'archives', 'speed_images', 'speed_analytics', 'speed_clean', 'speed_fonts', 'webp' ); 389 // speed_analytics is left out on purpose (2.14.0): moving someone's Google tag should be their own choice, never part of a one-click set. 390 $rec = array( 'dedupe', 'xrobots', 'schema', 'schema_clean', 'alts', 'llms', 'sitemap_plus', 'robots_sitemap', 'archives', 'speed_images', 'speed_clean', 'speed_fonts', 'webp' ); 390 391 if ( ! rankanysite_cache_on() && ! rankanysite_cache_conflicts() ) { 391 392 $rec[] = 'cache'; -
clickflame-rank-any-site/trunk/includes/inspect.php
r3794812 r3801389 29 29 30 30 /** 31 * Fetch a URL on this site. Short timeout, no redirects followed.31 * Fetch a URL on this site. Waits up to 20 seconds (slow is not broken), no redirects followed. 32 32 * 33 33 * @param string $url URL. … … 38 38 $url, 39 39 array( 40 'timeout' => 10,40 'timeout' => 20, 41 41 'redirection' => 0, 42 42 'user-agent' => 'Mozilla/5.0 (compatible; RankAnySiteBot/2.3; +https://rankanysite.com/bot) WordPress self-check', … … 115 115 } 116 116 return $txt; 117 } 118 119 /** 120 * Split text holding several JSON values back to back ({..}{..}) into pieces. Quoted strings are respected. 121 * 122 * @param string $raw Text. 123 * @return string[] Pieces, or an empty array when it does not split cleanly. 124 */ 125 function rankanysite_json_values( $raw ) { 126 $parts = array(); 127 $depth = 0; 128 $start = -1; 129 $in_str = false; 130 $esc = false; 131 $len = strlen( $raw ); 132 for ( $i = 0; $i < $len; $i++ ) { 133 $c = $raw[ $i ]; 134 if ( $in_str ) { 135 if ( $esc ) { 136 $esc = false; 137 } elseif ( '\\' === $c ) { 138 $esc = true; 139 } elseif ( '"' === $c ) { 140 $in_str = false; 141 } 142 continue; 143 } 144 if ( '"' === $c ) { 145 $in_str = true; 146 } elseif ( '{' === $c || '[' === $c ) { 147 if ( 0 === $depth ) { 148 $start = $i; 149 } 150 ++$depth; 151 } elseif ( '}' === $c || ']' === $c ) { 152 --$depth; 153 if ( $depth < 0 ) { 154 return array(); 155 } 156 if ( 0 === $depth && $start >= 0 ) { 157 $parts[] = substr( $raw, $start, $i - $start + 1 ); 158 $start = -1; 159 } 160 } 161 } 162 return 0 === $depth ? $parts : array(); 117 163 } 118 164 … … 165 211 $ms = (int) round( ( microtime( true ) - $t0 ) * 1000 ); 166 212 if ( is_wp_error( $r ) ) { 213 $slow = false !== stripos( $r->get_error_message(), 'timed out' ) || false !== stripos( $r->get_error_message(), 'timeout' ); 167 214 return array( 168 215 'ok' => false, 169 /* translators: %s: error message. */ 170 'reason' => sprintf( __( 'Your site could not load its own page: %s', 'clickflame-rank-any-site' ), $r->get_error_message() ), 216 'slow' => $slow, 217 'reason' => $slow 218 ? __( 'The page took more than 20 seconds to load, so the check stopped. The page is slow, not broken. Turn on page caching and try again.', 'clickflame-rank-any-site' ) 219 /* translators: %s: error message. */ 220 : sprintf( __( 'Your site could not load its own page: %s', 'clickflame-rank-any-site' ), $r->get_error_message() ), 171 221 ); 172 222 } … … 253 303 rankanysite_find( $f, 'search', 'med', sprintf( __( 'Google sees %d page descriptions. Your theme or another plugin prints an extra one. Keep one.', 'clickflame-rank-any-site' ), count( $descs ) ), 'dedupe' ); 254 304 } elseif ( ! $descs ) { 255 rankanysite_find( $f, 'search', 'med', __( 'The page has no description, so Google writes its own from random text on the page.', 'clickflame-rank-any-site' ), 'description' ); 305 // A share description (og:description, twitter:description) is there but the one Google reads is not: say that, not "missing". 306 $share = $xp->query( '//meta[@property="og:description" or @name="og:description" or @name="twitter:description"]' ); 307 if ( $share->length && '' !== trim( $share->item( 0 )->getAttribute( 'content' ) ) ) { 308 rankanysite_find( $f, 'search', 'med', __( 'The page has a share description (og:description) but no meta name="description", which is the one Google reads. Add it with the same text; the share tag can stay.', 'clickflame-rank-any-site' ), 'description' ); 309 } else { 310 rankanysite_find( $f, 'search', 'med', __( 'The page has no description, so Google writes its own from random text on the page.', 'clickflame-rank-any-site' ), 'description' ); 311 } 256 312 } elseif ( '' !== $seo['desc'] && html_entity_decode( $descs[0], ENT_QUOTES ) !== html_entity_decode( rankanysite_fill_vars( $seo['desc'], $post_id ), ENT_QUOTES ) ) { 257 313 rankanysite_find( $f, 'search', 'med', __( 'The description the page serves is not the one you wrote here. Another plugin or the theme replaces it.', 'clickflame-rank-any-site' ), 'dedupe' ); … … 348 404 $ids = array(); 349 405 foreach ( $xp->query( '//script[@type="application/ld+json"]' ) as $s ) { 350 $j = json_decode( $s->textContent, true ); 406 $raw = trim( $s->textContent ); 407 $j = json_decode( $raw, true ); 351 408 if ( ! is_array( $j ) ) { 352 rankanysite_find( $f, 'ai', 'med', __( 'One block of schema on the page is broken JSON, so it is ignored.', 'clickflame-rank-any-site' ) ); 353 continue; 409 // Two or more objects in one tag with no [ ] around them: read them all, and say so. 410 $parts = rankanysite_json_values( $raw ); 411 $all = array(); 412 foreach ( $parts as $part ) { 413 $one = json_decode( $part, true ); 414 if ( ! is_array( $one ) ) { 415 $all = array(); 416 break; 417 } 418 $all[] = $one; 419 } 420 if ( count( $all ) > 1 ) { 421 $j = $all; 422 $jnames = array(); 423 foreach ( $all as $one ) { 424 if ( ! empty( $one['@type'] ) ) { 425 $jnames = array_merge( $jnames, (array) $one['@type'] ); 426 } 427 } 428 /* translators: 1: number of objects, 2: schema types. */ 429 rankanysite_find( $f, 'ai', 'low', sprintf( __( 'One schema block holds %1$d objects with no [ ] around them (%2$s). We read them, but Google may skip that block. Wrap them in [ ] or give each its own script tag.', 'clickflame-rank-any-site' ), count( $all ), implode( ', ', array_unique( array_map( 'strval', $jnames ) ) ) ) ); 430 } else { 431 preg_match_all( '/"@type"\s*:\s*"([A-Za-z]+)"/', $raw, $tm ); 432 $named = $tm[1] ? ' (' . implode( ', ', array_slice( array_unique( $tm[1] ), 0, 6 ) ) . ')' : ''; 433 /* translators: %s: schema types in the block, or empty. */ 434 rankanysite_find( $f, 'ai', 'med', sprintf( __( 'One block of schema on the page is broken JSON%s, so search engines ignore it.', 'clickflame-rank-any-site' ), $named ) ); 435 continue; 436 } 354 437 } 355 438 $nodes = isset( $j['@graph'] ) ? (array) $j['@graph'] : ( isset( $j[0] ) ? $j : array( $j ) ); -
clickflame-rank-any-site/trunk/includes/links.php
r3794812 r3801389 92 92 } 93 93 $args = array( 94 'timeout' => 6,94 'timeout' => $internal ? 20 : 8, // our own slow pages get 20 s: slow is not broken 95 95 'redirection' => 5, 96 96 'user-agent' => 'Mozilla/5.0 (compatible; RankAnySiteBot/2.12; +https://rankanysite.com/bot) WordPress link check', -
clickflame-rank-any-site/trunk/includes/speed.php
r3790523 r3801389 261 261 ); 262 262 if ( $later ) { 263 $meta = ''; 263 // Say in the page itself that this is the site's own tag, moved, not one we added. 264 $meta = "\n" . '<!-- Rank Any Site: this site\'s own Google tag, moved to load after the page (Analytics after the page fix). Nothing was added. -->'; 264 265 foreach ( array_unique( $later ) as $src ) { 265 266 $meta .= "\n" . '<meta name="rankanysite-later" content="' . esc_url( $src ) . '">'; -
clickflame-rank-any-site/trunk/readme.txt
r3799460 r3801389 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 2.1 3.07 Stable tag: 2.14.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 155 155 == Changelog == 156 156 157 = 2.14.0 = 158 * Fixed: a page with only a share description (og:description) no longer shows as having no description. The live check now says the share tag is there and the meta name="description" tag Google reads is not. 159 * Fixed: a schema block with several objects back to back and no [ ] around them is now read, every type in it counts, and the check says how to make it valid. A truly broken block names the types it was meant to hold. 160 * Fixed: slow pages are no longer called broken. Page checks wait up to 20 seconds, and a page that takes longer is reported as slow. 161 * Fixed: the scan progress no longer spins forever when this site cannot reach Rank Any Site. It stops after several tries and says what to check. 162 * New: Your Facts has about 170 trades in plain words (water damage restoration, mold removal, garage doors, smog check and more). The trade sets your schema type and which directories come first. 163 * New: license examples on Your Facts follow your trade and state, such as IICRC certifications for restoration or your state's contractor board, instead of one California example for everyone. 164 * New: Your Facts fills in your real business name, and every gray hint now starts with "Example:" so it never looks saved. 165 * New: Citation Finder lists directories made for your trade first, then general ones, with a My Trade Only choice, sorting by strength, name or free first, and a plain note on what Strength means. Directories for other trades no longer show up in your trade view. 166 * New: Citation Finder shows the matching directories Clickflame runs for your trade at the top, marked as ours. 167 * New: Check All button at the top of Your Listings, and a live listings count on the dashboard. 168 * Changed: Analytics After The Page is no longer part of the recommended set, and the page source now has a note over the moved tag saying it is the site's own Google tag. 169 157 170 = 2.13.0 = 158 171 * Fixed: saving a license key or pressing Rescan Now no longer leaves the page loading for minutes on a slow site. The scan now runs in the background and the dashboard shows what it is doing, page by page, until your results are in. … … 265 278 == Upgrade Notice == 266 279 280 = 2.14.0 = 281 Slow pages are no longer called broken, the description and schema checks read more formats, and the Citation Finder puts your trade first. 282 267 283 = 2.10.0 = 268 284 Adds the Rebuild screen.
Note:
See TracChangeset
for help on using the changeset viewer.