Plugin Directory

Changeset 3431938


Ignore:
Timestamp:
01/04/2026 04:35:03 AM (2 weeks ago)
Author:
arothman
Message:

fixing SEO and optimization plugin integrations

Location:
pcrecruiter-extensions
Files:
35 added
3 edited

Legend:

Unmodified
Added
Removed
  • pcrecruiter-extensions/trunk/PCRecruiter-Extensions.php

    r3431406 r3431938  
    55 * Plugin URI: https://www.pcrecruiter.net
    66 * Description: Integrates PCRecruiter job boards with WordPress via iframe embed or full job sync with /job/ custom post type.
    7  * Version: 2.0.5
     7 * Version: 2.0.6
    88 * Requires at least: 5.6
    99 * Requires PHP: 7.4
     
    7979    require_once plugin_dir_path(__FILE__) . 'includes/class-social-widget.php';
    8080    require_once plugin_dir_path(__FILE__) . 'includes/class-deactivation-handler.php';
     81    require_once plugin_dir_path(__FILE__) . 'includes/class-optimization-exclusions.php';
    8182
    8283    // Load job-specific classes but don't init yet
     
    717718    if ($a['analytics'] != '') {
    718719        $analytics = ' analytics="true" ';
    719     };
    720 
     720    }
     721
     722    // Handle jobmanager and internaljobmanager (Full Sync mode)
    721723    if ($loadurl == "jobmanager" || $loadurl == "internaljobmanager") {
    722724        $job_manager = new PCR_Job_Manager();
     
    728730    if (strpos($loadurl, '.asp?') === false && strpos($loadurl, '.exe?') === false && strpos($loadurl, '.aspx?') === false) {
    729731        // Parse URL to extract base UID and any additional parameters
    730         // This handles: "pcr demo.pcrdemo", "pcr%20demo.pcrdemo", and "pcr%20demo.pcrdemo&filter=value"
    731732        $parts = explode('&', $loadurl, 2);
    732733        $uid = $parts[0]; // Base UID (may have spaces or %20)
     
    738739    }
    739740
     741    // Handle custom forms
    740742    if (is_numeric($sid) && $loadurl !== "about:blank") {
    741743        $script_url = 'https://www2.pcrecruiter.net/pcrbin/' . $loadurl . '&action=opencustomform&sid=' . rawurlencode($sid);
     
    754756        return '<script src="' . esc_url($script_url) . '"></script>';
    755757    } else {
     758        // Handle iframe output
    756759        if (substr($loadurl, 0, 4) !== "http" && substr($loadurl, 0, 8) !== "jobboard") {
    757760            $aspurl = 'https://www2.pcrecruiter.net/pcrbin/' . $loadurl;
    758761            $loadurl = $aspurl;
    759         };
     762        }
    760763        if (substr($loadurl, 0, 4) !== "http" && substr($loadurl, 0, 8) == "jobboard") {
    761764            $needs_css = false;
    762765            $aspurl = 'https://host.pcrecruiter.net/pcrbin/' . $loadurl;
    763766            $loadurl = $aspurl;
    764         };
     767        }
    765768
    766769        if ($needs_css) {
     
    768771        }
    769772
    770         // Note: appendSrcToFrame() is now provided by pcr-frontend.js when in Full Sync mode
    771         // For iframe/passthrough mode, this function may not be available but is also not needed
     773        // Build iframe attributes
     774        $iframe_attrs = array(
     775            'frameborder' => '0',
     776            'host' => $loadurl,
     777            'id' => 'pcrframe',
     778            'name' => 'pcrframe',
     779            'src' => 'about:blank',
     780            'style' => "height:{$initialheight}px;width:100%;background-color:{$background};border:0;margin:0;padding:0",
     781            'onload' => 'pcrframeurl();',
     782        );
     783
     784        // Add analytics attribute if present
     785        if (!empty($analytics)) {
     786            $iframe_attrs['analytics'] = 'true';
     787        }
     788
     789        // Apply optimization exclusion attributes (filter hook for class to add attributes)
     790        $iframe_attrs = apply_filters('pcrecruiter_iframe_attributes', $iframe_attrs);
     791
     792        // Build attribute string
     793        $attr_string = '';
     794        foreach ($iframe_attrs as $key => $value) {
     795            $attr_string .= ' ' . $key . '="' . esc_attr($value) . '"';
     796        }
    772797
    773798        $version = pcrecruiter_get_plugin_version();
    774799
    775         return "<!-- Start PCRecruiter WP {$version}--><iframe frameborder=\"0\" host=\"{$loadurl}\" id=\"pcrframe\" name=\"pcrframe\" src=\"about:blank\" style=\"height:{$initialheight}px;width:100%;background-color:{$background};border:0;margin:0;padding:0\" {$analytics} onload=\"pcrframeurl();\"></iframe><!-- End PCRecruiter WP -->";
     800        return "<!-- Start PCRecruiter WP {$version}--><iframe{$attr_string}></iframe><!-- End PCRecruiter WP -->";
    776801    }
    777802}
  • pcrecruiter-extensions/trunk/includes/class-seo-enhancements.php

    r3431406 r3431938  
    6161            $conflicts[] = array(
    6262                'name' => 'Rank Math',
    63                 'config' => 'Rank Math &rarr; Titles &amp; Meta &rarr; Job Posts'
     63                'config' => 'Rank Math &rarr; Titles & Meta &rarr; Job Posts'
    6464            );
    6565        }
     
    134134    /**
    135135     * Add comprehensive meta tags and Open Graph for job posts
    136      * Only outputs when no SEO plugin is handling meta tags
     136     *
     137     * NOTE: If Yoast SEO or Rank Math is active, we rely on their systems
     138     * to handle meta tag output. For Yoast, we populate fields via populate_yoast_meta().
     139     * This function only outputs tags when no SEO plugin is handling it.
    137140     */
    138141    public static function add_job_meta_tags()
     
    142145        }
    143146
    144         // Skip if Yoast or Rank Math is active - they handle meta tags
    145         // and we populate their fields via populate_yoast_meta()
    146         if (defined('WPSEO_VERSION') || defined('RANK_MATH_VERSION')) {
     147        $job_id = get_the_ID();
     148
     149        // If Yoast is active, check if it has data
     150        if (defined('WPSEO_VERSION')) {
     151            $yoast_title = get_post_meta($job_id, '_yoast_wpseo_title', true);
     152           
     153            // If Yoast fields are empty, populate them now
     154            // This handles old jobs created before populate_yoast_meta() existed
     155            if (empty($yoast_title)) {
     156                self::populate_yoast_meta($job_id);
     157            }
     158           
     159            // Let Yoast handle all meta output
    147160            return;
    148161        }
     162
     163        // If Rank Math is active, let it handle output
     164        // (We don't populate Rank Math fields yet, but its filters will work as fallback)
     165        if (defined('RANK_MATH_VERSION')) {
     166            return;
     167        }
     168
     169        // No SEO plugin active - PCR outputs meta tags directly
    149170
    150171        $job_id = get_the_ID();
     
    182203
    183204        // Open Graph tags
    184         echo '<meta property="og:type" content="website">' . "\n";
     205        echo '<meta property="og:type" content="article">' . "\n";
    185206        echo '<meta property="og:title" content="' . esc_attr($seo_title) . '">' . "\n";
    186207        echo '<meta property="og:description" content="' . esc_attr($description) . '">' . "\n";
     
    383404
    384405    /**
     406     * Extract content based on HTML comment markers
     407     * Simple, fast, and reliable - no complex tag parsing needed
     408     *
     409     * Supports:
     410     *   <!-- pcr-description-start -->content<!-- pcr-description-end -->
     411     *   <!-- pcr-description-start -->content (uses rest of content)
     412     *
     413     * @param string $content HTML content
     414     * @return string Extracted content or original if no markers found
     415     */
     416    private static function extract_marked_description($content)
     417    {
     418        // Check for start/end markers
     419        if (preg_match('/<!--\s*pcr-description-start\s*-->(.*?)<!--\s*pcr-description-end\s*-->/is', $content, $matches)) {
     420            return $matches[1];
     421        }
     422
     423        // Check for start marker only (use rest of content)
     424        if (preg_match('/<!--\s*pcr-description-start\s*-->(.*)/is', $content, $matches)) {
     425            return $matches[1];
     426        }
     427
     428        // No markers found - return original content
     429        return $content;
     430    }
     431
     432    /**
    385433     * Build meta description from job content
    386434     */
     
    397445            $content = $post->post_content;
    398446
    399             // Look for HTML comment or data attribute markers
    400             if (preg_match('/<!--\s*pcr-description-start\s*-->(.*)/is', $content, $matches)) {
    401                 // Found comment marker - use everything after it
    402                 $content = $matches[1];
    403             } elseif (preg_match("/<div[^>]*data-pcr-description=[\"']start[\"'][^>]*>(.*?)<\/div>/is", $content, $matches)) {
    404                 // Found data attribute marker - use content inside the element
    405                 $content = $matches[1];
    406             }
     447            // Extract marked description (handles comments and data attributes with balanced tags)
     448            $content = self::extract_marked_description($content);
    407449
    408450            // Strip HTML and shortcodes
     
    608650    /**
    609651     * Override Yoast title if not manually configured
     652     *
     653     * NOTE: This is primarily a FALLBACK for old jobs created before populate_yoast_meta() was added.
     654     * For new jobs, populate_yoast_meta() writes directly to the database, making this filter
     655     * redundant (it will find the value and return it). However, this filter provides:
     656     * - Backward compatibility for old jobs without database values
     657     * - Safety net if user manually clears Yoast field
     658     * - Graceful degradation if database population fails
    610659     */
    611660    public static function maybe_override_yoast_title($title)
     
    615664        }
    616665
    617         // If Yoast has a custom title set, respect it
     666        // If Yoast has a custom title set (manually or auto-populated), respect it
    618667        $yoast_title = get_post_meta(get_the_ID(), '_yoast_wpseo_title', true);
    619668        if (!empty($yoast_title)) {
     
    621670        }
    622671
    623         // Otherwise use our optimized title
     672        // FALLBACK: Generate title for old jobs or if field was cleared
    624673        $title_parts = self::filter_job_title(array('title' => ''));
    625674        return $title_parts['title'];
     
    628677    /**
    629678     * Override Yoast description if not manually configured
     679     *
     680     * NOTE: This is primarily a FALLBACK for old jobs created before populate_yoast_meta() was added.
     681     * See maybe_override_yoast_title() for detailed explanation.
    630682     */
    631683    public static function maybe_override_yoast_description($description)
     
    635687        }
    636688
    637         // If Yoast has a custom description set, respect it
     689        // If Yoast has a custom description set (manually or auto-populated), respect it
    638690        $yoast_desc = get_post_meta(get_the_ID(), '_yoast_wpseo_metadesc', true);
    639691        if (!empty($yoast_desc)) {
     
    641693        }
    642694
    643         // Otherwise use our optimized description
     695        // FALLBACK: Generate description for old jobs or if field was cleared
    644696        return self::build_meta_description(get_the_ID());
    645697    }
     
    755807    /**
    756808     * Populate Yoast SEO meta fields for job posts
    757      * Called after job creation/update
    758      * Only populates if fields are empty (preserves manual edits)
    759      *
    760      * @param int $job_id The job post ID
    761      */
    762     /**
    763      * Populate Yoast SEO meta fields for job posts
    764      * Called after job creation/update
    765      * Only populates if fields are empty (preserves manual edits)
    766      *
    767      * @param int $job_id The job post ID
    768      */
    769     /**
    770      * Populate Yoast SEO meta fields for job posts
    771809     * Called after job creation/update via save_post_job hook
    772810     * Only populates if fields are empty (preserves manual edits)
     
    776814    public static function populate_yoast_meta($job_id)
    777815    {
     816        // Skip if this is an autosave or revision
     817        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
     818            return;
     819        }
     820
     821        // Skip quick edits (admin only, not sync)
     822        if (isset($_POST['_inline_edit'])) {
     823            return;
     824        }
     825
     826        // Skip bulk edits (performance optimization)
     827        if (isset($_REQUEST['bulk_edit'])) {
     828            return;
     829        }
     830
    778831        // Only for job posts
    779832        if (get_post_type($job_id) !== 'job') {
     
    784837        if (!defined('WPSEO_VERSION')) {
    785838            return;
     839        }
     840
     841        // Skip if Yoast Premium has templates configured for jobs
     842        if (defined('WPSEO_PREMIUM_VERSION')) {
     843            $yoast_titles = get_option('wpseo_titles', array());
     844            if (!empty($yoast_titles['title-job']) || !empty($yoast_titles['metadesc-job'])) {
     845                // Let Yoast templates handle it
     846                return;
     847            }
    786848        }
    787849
     
    794856        $seo_title = self::build_seo_title($job_id, $job_title);
    795857        $meta_description = self::build_meta_description($job_id);
     858
     859        // Fallback if build methods return empty (Issue #5)
     860        if (empty($seo_title) && !empty($job_title)) {
     861            $seo_title = $job_title;
     862           
     863            // Log for debugging
     864            if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
     865                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     866                error_log("PCR: build_seo_title returned empty for job {$job_id}, using fallback");
     867            }
     868        }
     869
     870        if (empty($meta_description)) {
     871            // Try to build a basic description from title and location
     872            $location = get_post_meta($job_id, '[[position.city_state]]', true);
     873            if (!empty($location)) {
     874                $meta_description = "Join our team as a {$job_title} in {$location}.";
     875            } else {
     876                $meta_description = "Join our team as a {$job_title}.";
     877            }
     878           
     879            // Log for debugging
     880            if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
     881                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     882                error_log("PCR: build_meta_description returned empty for job {$job_id}, using fallback");
     883            }
     884        }
    796885
    797886        // Only populate if Yoast fields are empty (preserves manual edits)
  • pcrecruiter-extensions/trunk/readme.txt

    r3431406 r3431938  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.0.5
     7Stable tag: 2.0.6
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    123123
    124124== Changelog ==
     125= 2.0.6 - 2026-01-03 =
     126* Exempt PCR iframes/js from interference by optimization plugins
     127* Deprecated pcr-start-description data attribute in echange for <!-- pcr-description-start / end --> comments
     128
    125129= 2.0.5 - 2026-01-02 =
    126130* Improved handling of company and position logos
    127131* Radius search sort by distance
    128132* Yoast fields generation
    129 * Exempt PCR frames/js from interference by cache and optimization defer/lazyload plugins
    130133* Performance optimizations
    131134
Note: See TracChangeset for help on using the changeset viewer.